refactor(core): remove unnecessary | null, unify timestamp naming

- SenseReflexConfig.on: string[] | null → string[] (empty = no conditions)
- NerveConfig.workflows: Record | null → Record (empty = no workflows)
- Signal.ts → Signal.timestamp
- SenseInfo.lastSignalTs → SenseInfo.lastSignalTimestamp
- All consumers across daemon/cli/store updated
- parseNerveConfig: on defaults to [], workflows defaults to {}

Fixes #108
This commit is contained in:
2026-04-25 01:52:58 +00:00
parent 7ce3970027
commit 020a1bfe85
22 changed files with 117 additions and 79 deletions
+37 -11
View File
@@ -29,10 +29,22 @@ const SAMPLE_SENSES: SenseInfo[] = [
group: "system",
throttle: 5000,
timeout: 3000,
lastSignalTs: 1_700_000_000_000,
lastSignalTimestamp: 1_700_000_000_000,
},
{
name: "disk-usage",
group: "system",
throttle: 30000,
timeout: null,
lastSignalTimestamp: null,
},
{
name: "active-tasks",
group: "tasks",
throttle: 10000,
timeout: 30000,
lastSignalTimestamp: null,
},
{ name: "disk-usage", group: "system", throttle: 30000, timeout: null, lastSignalTs: null },
{ name: "active-tasks", group: "tasks", throttle: 10000, timeout: 30000, lastSignalTs: null },
];
// ---------------------------------------------------------------------------
@@ -100,14 +112,14 @@ describe("formatSenseList", () => {
expect(output).toContain("—");
});
it("shows '(never)' when lastSignalTs is null", () => {
it("shows '(never)' when lastSignalTimestamp is null", () => {
const output = formatSenseList(SAMPLE_SENSES);
expect(output).toContain("(never)");
});
it("shows ISO timestamp when lastSignalTs is set", () => {
it("shows ISO timestamp when lastSignalTimestamp is set", () => {
const output = formatSenseList(SAMPLE_SENSES);
// cpu-usage has lastSignalTs = 1_700_000_000_000
// cpu-usage has lastSignalTimestamp = 1_700_000_000_000
expect(output).toContain(new Date(1_700_000_000_000).toISOString());
});
});
@@ -157,11 +169,19 @@ reflexes: []
);
const result = sensesFromConfig(path);
expect(result).toHaveLength(2);
expect(result[0]).toMatchObject({ name: "cpu-usage", group: "system", lastSignalTs: null });
expect(result[1]).toMatchObject({ name: "disk-usage", group: "system", lastSignalTs: null });
expect(result[0]).toMatchObject({
name: "cpu-usage",
group: "system",
lastSignalTimestamp: null,
});
expect(result[1]).toMatchObject({
name: "disk-usage",
group: "system",
lastSignalTimestamp: null,
});
});
it("always sets lastSignalTs to null (static fallback)", () => {
it("always sets lastSignalTimestamp to null (static fallback)", () => {
const path = join(tmpDir, "nerve.yaml");
writeFileSync(
path,
@@ -173,7 +193,7 @@ reflexes: []
`.trim(),
);
const result = sensesFromConfig(path);
expect(result[0].lastSignalTs).toBeNull();
expect(result[0].lastSignalTimestamp).toBeNull();
});
it("populates throttle and timeout from config", () => {
@@ -238,7 +258,13 @@ describe("listSensesViaDaemon", () => {
it("resolves with populated senses array", async () => {
const senses: SenseInfo[] = [
{ name: "cpu-usage", group: "system", throttle: 5000, timeout: 3000, lastSignalTs: 12345 },
{
name: "cpu-usage",
group: "system",
throttle: 5000,
timeout: 3000,
lastSignalTimestamp: 12345,
},
];
const server = createServer((s) => {
s.on("data", () => {
+3 -2
View File
@@ -43,7 +43,8 @@ export function formatSenseList(senses: SenseInfo[]): string {
lines.push(` group: ${s.group}\n`);
lines.push(` throttle: ${formatDuration(s.throttle)}\n`);
lines.push(` timeout: ${formatDuration(s.timeout)}\n`);
const lastSignal = s.lastSignalTs !== null ? new Date(s.lastSignalTs).toISOString() : "(never)";
const lastSignal =
s.lastSignalTimestamp !== null ? new Date(s.lastSignalTimestamp).toISOString() : "(never)";
lines.push(` last signal: ${lastSignal}\n`);
}
return lines.join("");
@@ -64,7 +65,7 @@ export function sensesFromConfig(configPath: string): SenseInfo[] {
group: cfg.group,
throttle: cfg.throttle,
timeout: cfg.timeout,
lastSignalTs: null,
lastSignalTimestamp: null,
}));
}
+1 -1
View File
@@ -31,7 +31,7 @@ export const validateCommand = defineCommand({
const config = result.value;
const senseCount = Object.keys(config.senses).length;
const reflexCount = config.reflexes.length;
const workflowCount = config.workflows ? Object.keys(config.workflows).length : 0;
const workflowCount = Object.keys(config.workflows).length;
process.stdout.write(
`✅ nerve.yaml is valid — ${senseCount} sense(s), ${reflexCount} reflex(es), ${workflowCount} workflow(s)\n`,
+1 -1
View File
@@ -28,7 +28,7 @@ function isSenseInfo(value: unknown): value is SenseInfo {
typeof value.group === "string" &&
(value.throttle === null || typeof value.throttle === "number") &&
(value.timeout === null || typeof value.timeout === "number") &&
(value.lastSignalTs === null || typeof value.lastSignalTs === "number")
(value.lastSignalTimestamp === null || typeof value.lastSignalTimestamp === "number")
);
}
+7 -6
View File
@@ -50,7 +50,7 @@ describe("parseNerveConfig", () => {
kind: "sense",
sense: "cpu",
interval: 30_000,
on: null,
on: [],
});
expect(result.value.reflexes[1]).toEqual({
kind: "sense",
@@ -58,7 +58,7 @@ describe("parseNerveConfig", () => {
interval: null,
on: ["high_usage"],
});
expect(result.value.workflows?.alert).toEqual({
expect(result.value.workflows.alert).toEqual({
concurrency: 2,
overflow: "queue",
maxQueue: 10,
@@ -85,11 +85,12 @@ senses:
group: system
reflexes:
- sense: cpu
interval: 1s
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows).toBeNull();
expect(result.value.workflows).toEqual({});
});
it("sense config has null for omitted throttle/timeout/gracePeriod", () => {
@@ -142,11 +143,11 @@ workflows:
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows?.alert).toEqual({
expect(result.value.workflows.alert).toEqual({
concurrency: 1,
overflow: "drop",
});
expect("maxQueue" in (result.value.workflows?.alert ?? {})).toBe(false);
expect("maxQueue" in result.value.workflows.alert).toBe(false);
});
it("overflow: queue defaults maxQueue to 100", () => {
@@ -163,7 +164,7 @@ workflows:
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows?.alert).toEqual({
expect(result.value.workflows.alert).toEqual({
concurrency: 1,
overflow: "queue",
maxQueue: 100,
+6 -8
View File
@@ -76,8 +76,8 @@ function validateSenseConfig(name: string, raw: unknown): Result<SenseConfig> {
});
}
function parseOnField(index: number, obj: Record<string, unknown>): Result<string[] | null> {
if (obj.on === undefined || obj.on === null) return ok(null);
function parseOnField(index: number, obj: Record<string, unknown>): Result<string[]> {
if (obj.on === undefined || obj.on === null) return ok([]);
if (!Array.isArray(obj.on) || !obj.on.every((item): item is string => typeof item === "string")) {
return err(new Error(`reflexes[${index}].on: must be an array of strings`));
}
@@ -88,7 +88,7 @@ function parseSenseReflex(
index: number,
obj: Record<string, unknown>,
senseNames: Set<string>,
on: string[] | null,
on: string[],
): Result<ReflexConfig> {
if (typeof obj.sense !== "string") {
return err(new Error(`reflexes[${index}].sense: must be a string`));
@@ -100,7 +100,7 @@ function parseSenseReflex(
const intervalResult = parseDurationField(obj.interval, `reflexes[${index}].interval`);
if (!intervalResult.ok) return intervalResult;
if (intervalResult.value === null && on !== null && on.length === 0) {
if (intervalResult.value === null && on.length === 0) {
return err(
new Error(`reflexes[${index}]: sense reflex must have at least one of "interval" or "on"`),
);
@@ -245,10 +245,8 @@ function parseReflexes(
return ok(reflexes);
}
function parseWorkflows(
obj: Record<string, unknown>,
): Result<Record<string, WorkflowConfig> | null> {
if (obj.workflows === undefined || obj.workflows === null) return ok(null);
function parseWorkflows(obj: Record<string, unknown>): Result<Record<string, WorkflowConfig>> {
if (obj.workflows === undefined || obj.workflows === null) return ok({});
if (!isPlainRecord(obj.workflows)) {
return err(new Error("workflows: must be an object if provided"));
+4 -4
View File
@@ -2,7 +2,7 @@ export type Signal = {
id: number;
senseId: string;
payload: unknown;
ts: number;
timestamp: number;
};
export type SenseConfig = {
@@ -18,14 +18,14 @@ export type SenseInfo = {
group: string;
throttle: number | null;
timeout: number | null;
lastSignalTs: number | null;
lastSignalTimestamp: number | null;
};
export type SenseReflexConfig = {
kind: "sense";
sense: string;
interval: number | null;
on: string[] | null;
on: string[];
};
/** Reflexes only schedule Senses; workflow launches come from Sense return values. */
@@ -49,7 +49,7 @@ export type NerveConfig = {
maxRounds: number;
senses: Record<string, SenseConfig>;
reflexes: ReflexConfig[];
workflows: Record<string, WorkflowConfig> | null;
workflows: Record<string, WorkflowConfig>;
};
// ---------------------------------------------------------------------------
@@ -200,8 +200,20 @@ describe("daemon-ipc — list-senses", () => {
it("responds ok:true with senses populated from listSenses", async () => {
const sensesData = [
{ name: "cpu-usage", group: "system", throttle: 5000, timeout: 3000, lastSignalTs: 1000 },
{ name: "disk-usage", group: "system", throttle: 30000, timeout: null, lastSignalTs: null },
{
name: "cpu-usage",
group: "system",
throttle: 5000,
timeout: 3000,
lastSignalTimestamp: 1000,
},
{
name: "disk-usage",
group: "system",
throttle: 30000,
timeout: null,
lastSignalTimestamp: null,
},
];
const listSenses = vi.fn(() => sensesData);
server = createDaemonIpcServer(sockPath, makeMockWorkflowManager() as never, {
@@ -250,7 +250,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
const logStore = makeLogStore();
const config: NerveConfig = {
senses: {},
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
reflexes: [],
workflows: { "my-wf": { concurrency: 1, overflow: "drop" } },
maxRounds: 10,
};
@@ -285,7 +285,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
const logStore = makeLogStore();
const initialConfig: NerveConfig = {
senses: {},
reflexes: [{ kind: "workflow", workflow: "old-wf", on: null } as any],
reflexes: [],
workflows: { "old-wf": { concurrency: 1, overflow: "drop" } },
maxRounds: 10,
};
@@ -307,7 +307,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
const newConfig: NerveConfig = {
senses: {},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
kernel.reloadConfig(newConfig);
@@ -328,7 +328,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
const logStore = makeLogStore();
const initialConfig: NerveConfig = {
senses: {},
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
reflexes: [],
workflows: { "my-wf": { concurrency: 1, overflow: "drop" } },
maxRounds: 10,
};
@@ -344,7 +344,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
// Reload with updated concurrency — should NOT spawn a new workflow worker
const newConfig: NerveConfig = {
senses: {},
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
reflexes: [],
workflows: { "my-wf": { concurrency: 5, overflow: "queue", maxQueue: 50 } },
maxRounds: 10,
};
@@ -26,7 +26,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -73,7 +73,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -180,7 +180,7 @@ describe("kernel — reloadConfig", () => {
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
});
@@ -197,7 +197,7 @@ describe("kernel — reloadConfig", () => {
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
const kernel = createKernel(config, "/tmp/nerve-test");
@@ -212,7 +212,7 @@ describe("kernel — reloadConfig", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
});
@@ -235,7 +235,7 @@ describe("kernel — reloadConfig", () => {
"disk-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
});
@@ -92,7 +92,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -95,7 +95,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -298,7 +298,7 @@ describe("kernel + workflowManager integration", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
});
@@ -366,7 +366,7 @@ describe("kernel + workflowManager integration", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
kernel.reloadConfig(newConfig);
+3 -3
View File
@@ -59,7 +59,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -200,7 +200,7 @@ describe("kernel — groupForSense mapping", () => {
"net-usage": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
const kernel = createKernel(config, "/tmp/nerve-test");
@@ -215,7 +215,7 @@ describe("kernel — groupForSense mapping", () => {
senses: {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: null }],
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: [] }],
});
createKernel(config, "/tmp/nerve-test");
@@ -29,7 +29,7 @@ describe("LogStore + ReflexScheduler integration", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: null, on: ["cpu-usage"] }],
workflows: null,
workflows: {},
maxRounds: 10,
};
const bus = createSignalBus();
@@ -38,7 +38,7 @@ describe("LogStore + ReflexScheduler integration", () => {
logStore,
});
const signal: Signal = { id: 1, senseId: "cpu-usage", payload: 42, ts: Date.now() };
const signal: Signal = { id: 1, senseId: "cpu-usage", payload: 42, timestamp: Date.now() };
bus.emit(signal);
const logs = logStore.query({ source: "reflex", type: "run_start" });
@@ -56,8 +56,8 @@ describe("LogStore + ReflexScheduler integration", () => {
senses: {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
workflows: null,
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
workflows: {},
maxRounds: 10,
};
const bus = createSignalBus();
@@ -88,7 +88,7 @@ describe("LogStore + ReflexScheduler integration", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: null, on: ["cpu-usage"] }],
workflows: null,
workflows: {},
maxRounds: 10,
};
const bus = createSignalBus();
@@ -23,7 +23,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
@@ -136,7 +136,7 @@ describe("phase6 — reloadConfig", () => {
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
@@ -156,7 +156,7 @@ describe("phase6 — reloadConfig", () => {
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
kernel = createKernel(config, "/tmp/nerve-phase6-test", {
@@ -171,7 +171,7 @@ describe("phase6 — reloadConfig", () => {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
@@ -202,7 +202,7 @@ describe("phase6 — error isolation", () => {
"bad-sense": { group: "mixed", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
@@ -306,7 +306,7 @@ describe("phase6 — getHealth", () => {
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
};
kernel.reloadConfig(newConfig);
@@ -10,14 +10,14 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
}
function makeSignal(senseId: string, payload: unknown = 1): Signal {
return { id: 1, senseId, payload, ts: Date.now() };
return { id: 1, senseId, payload, timestamp: Date.now() };
}
describe("ReflexScheduler — throttle + pending deferred trigger", () => {
@@ -16,14 +16,14 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
"system-health": { group: "derived", throttle: null, timeout: null, gracePeriod: null },
},
reflexes: [],
workflows: null,
workflows: {},
maxRounds: 10,
...overrides,
};
}
function makeSignal(senseId: string, payload: unknown = 1): Signal {
return { id: 1, senseId, payload, ts: Date.now() };
return { id: 1, senseId, payload, timestamp: Date.now() };
}
// ---------------------------------------------------------------------------
@@ -41,7 +41,7 @@ describe("ReflexScheduler — interval reflex", () => {
it("fires triggerFn on schedule", () => {
const triggered: string[] = [];
const config = makeConfig({
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
});
const bus = createSignalBus();
// Use a ref so the triggerFn can call back into the scheduler
@@ -66,7 +66,7 @@ describe("ReflexScheduler — interval reflex", () => {
it("stops firing after stop() is called", () => {
const triggered: string[] = [];
const config = makeConfig({
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: null }],
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: [] }],
});
const bus = createSignalBus();
const ref: { scheduler: ReturnType<typeof createReflexScheduler> | null } = {
@@ -89,7 +89,7 @@ describe("ReflexScheduler — interval reflex", () => {
it("starts from current time — does not compensate for past intervals", () => {
const triggered: string[] = [];
const config = makeConfig({
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
@@ -4,7 +4,7 @@ import type { Signal } from "@uncaged/nerve-core";
import { createSignalBus } from "../signal-bus.js";
function makeSignal(senseId: string, payload: unknown = 1): Signal {
return { id: 1, senseId, payload, ts: Date.now() };
return { id: 1, senseId, payload, timestamp: Date.now() };
}
describe("createSignalBus", () => {
+5 -5
View File
@@ -161,14 +161,14 @@ export function createKernel(
id: nextSignalId(),
senseId: msg.sense,
payload: route.payload,
ts: Date.now(),
timestamp: Date.now(),
};
logStore.append({
source: "sense",
type: "signal",
refId: msg.sense,
payload: JSON.stringify(route.payload),
ts: signal.ts,
ts: signal.timestamp,
});
bus.emit(signal);
}
@@ -239,7 +239,7 @@ export function createKernel(
function reloadConfig(newConfig: NerveConfig): void {
const oldGroups = collectSenseGroups(config);
const oldConfig = config;
const oldWorkflows = config.workflows ?? {};
const oldWorkflows = config.workflows;
config = newConfig;
scheduler.stop();
scheduler = createReflexScheduler(config, bus, triggerFn, {
@@ -247,7 +247,7 @@ export function createKernel(
});
workflowManager.updateConfig(newConfig);
const newWorkflows = newConfig.workflows ?? {};
const newWorkflows = newConfig.workflows;
for (const workflowName of Object.keys(oldWorkflows)) {
if (!(workflowName in newWorkflows)) {
@@ -327,7 +327,7 @@ export function createKernel(
group: senseConfig.group,
throttle: senseConfig.throttle,
timeout: senseConfig.timeout,
lastSignalTs: lastEntry !== null ? lastEntry.ts : null,
lastSignalTimestamp: lastEntry !== null ? lastEntry.ts : null,
};
});
},
+1 -1
View File
@@ -164,7 +164,7 @@ export function createReflexScheduler(
intervals.push(id);
}
if (senseReflex.on !== null && senseReflex.on.length > 0) {
if (senseReflex.on.length > 0) {
const watchedSenses = new Set(senseReflex.on);
const unsub = bus.subscribe((signal) => {
if (watchedSenses.has(signal.senseId)) {
+1 -1
View File
@@ -217,7 +217,7 @@ export function createWorkflowManager(
}
function workflowConfig(workflowName: string): WorkflowConfig | null {
return config.workflows?.[workflowName] ?? null;
return config.workflows[workflowName] ?? null;
}
function toWorkflowRunStatus(eventType: string): WorkflowRunStatus | null {