@selesai/code 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.test.d.ts +1 -0
- package/dist/cli/args.test.js +24 -0
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.js +18 -7
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.js +4 -0
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/keybindings.d.ts +24 -0
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.js +1 -0
- package/dist/core/project-trust.js +2 -2
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/sdk.d.ts +5 -3
- package/dist/core/sdk.js +2 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.js +15 -0
- package/dist/core/system-prompt.js +3 -9
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/bash.js +2 -0
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/schema-prune.d.ts +16 -0
- package/dist/core/tools/schema-prune.js +39 -0
- package/dist/core/tools/schema-prune.test.d.ts +1 -0
- package/dist/core/tools/schema-prune.test.js +111 -0
- package/dist/core/tools/write.js +2 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +187 -41
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +395 -98
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
- package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/question/index.ts +21 -6
- package/dist/extensions/question/question-list.ts +7 -1
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +7 -0
- package/dist/extensions/question/tests/question-list.test.ts +140 -0
- package/dist/extensions/question/tests/row-layout.test.ts +13 -0
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/extensions/tokenin-onboarding.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -0
- package/dist/modes/interactive/components/model-selector.js +2 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/tool-execution.js +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -2
- package/dist/modes/interactive/interactive-mode.js +81 -25
- package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
- package/dist/modes/interactive/model-catalog-refresh.js +33 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +5 -3
- package/dist/modes/interactive/theme/theme.js +12 -2
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/abort.js +47 -0
- package/dist/utils/tools-manager.d.ts +5 -1
- package/dist/utils/tools-manager.js +12 -21
- package/docs/settings.md +6 -0
- package/package.json +6 -6
|
@@ -5,7 +5,8 @@ import * as path from "node:path";
|
|
|
5
5
|
import { describe, it } from "node:test";
|
|
6
6
|
import type { ExtensionContext } from "@selesai/code";
|
|
7
7
|
import { buildCompletionKey } from "../../src/runs/background/completion-dedupe.ts";
|
|
8
|
-
import { createResultWatcher } from "../../src/runs/background/result-watcher.ts";
|
|
8
|
+
import { createResultWatcher as createRawResultWatcher } from "../../src/runs/background/result-watcher.ts";
|
|
9
|
+
import { writeAsyncResultFile, writePendingAsyncResultFile } from "../../src/runs/background/result-files.ts";
|
|
9
10
|
import { createScheduledRunManager, scheduledRunStorePath } from "../../src/runs/background/scheduled-runs.ts";
|
|
10
11
|
import { prepareMissionLaunch, writeMissionAsyncBinding } from "../../src/missions/lifecycle.ts";
|
|
11
12
|
import { readMission, updateMission } from "../../src/missions/store.ts";
|
|
@@ -32,6 +33,24 @@ function createState(): SubagentState {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function createResultWatcher(
|
|
37
|
+
pi: Parameters<typeof createRawResultWatcher>[0],
|
|
38
|
+
state: Parameters<typeof createRawResultWatcher>[1],
|
|
39
|
+
resultsDir: Parameters<typeof createRawResultWatcher>[2],
|
|
40
|
+
completionTtlMs: Parameters<typeof createRawResultWatcher>[3],
|
|
41
|
+
deps: Parameters<typeof createRawResultWatcher>[4] = {},
|
|
42
|
+
): ReturnType<typeof createRawResultWatcher> {
|
|
43
|
+
return createRawResultWatcher(pi, state, resultsDir, completionTtlMs, { coalesceDelayMs: 0, ...deps });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeIndexedResult(filePath: string, data: Record<string, unknown>): void {
|
|
47
|
+
writeAsyncResultFile(filePath, data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function pendingResultPath(resultsDir: string, sessionId: string, runId: string): string {
|
|
51
|
+
return path.join(resultsDir, "result-pending", encodeURIComponent(sessionId), `${encodeURIComponent(runId)}.json`);
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
async function waitForPredicate(predicate: () => boolean, timeoutMs = 2_500): Promise<boolean> {
|
|
36
55
|
const deadline = Date.now() + timeoutMs;
|
|
37
56
|
while (!predicate()) {
|
|
@@ -55,24 +74,25 @@ describe("result watcher", () => {
|
|
|
55
74
|
},
|
|
56
75
|
};
|
|
57
76
|
const state = createState();
|
|
77
|
+
state.asyncJobs.set("session-run", { asyncId: "session-run", asyncDir: path.join(resultsDir, "session-run"), status: "running", startedAt: Date.now(), updatedAt: Date.now() });
|
|
58
78
|
const resultPath = path.join(resultsDir, "session-run.json");
|
|
59
|
-
|
|
79
|
+
writeIndexedResult(resultPath, {
|
|
60
80
|
id: "session-run",
|
|
61
81
|
sessionId: "session-current",
|
|
62
82
|
success: true,
|
|
63
83
|
summary: "done",
|
|
64
|
-
})
|
|
84
|
+
});
|
|
65
85
|
|
|
66
86
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
67
87
|
try {
|
|
68
88
|
watcher.primeExistingResults();
|
|
69
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
70
90
|
assert.equal(emitted.length, 0);
|
|
71
91
|
assert.equal(fs.existsSync(resultPath), true);
|
|
72
92
|
|
|
73
93
|
state.currentSessionId = "session-current";
|
|
74
94
|
watcher.primeExistingResults();
|
|
75
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
76
96
|
} finally {
|
|
77
97
|
watcher.stopResultWatcher();
|
|
78
98
|
}
|
|
@@ -84,38 +104,54 @@ describe("result watcher", () => {
|
|
|
84
104
|
}
|
|
85
105
|
});
|
|
86
106
|
|
|
87
|
-
it("
|
|
107
|
+
it("includes scheduled observer ids while priming current-session results", async () => {
|
|
88
108
|
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-current-prime-"));
|
|
89
109
|
try {
|
|
90
110
|
const state = createState();
|
|
91
111
|
state.currentSessionId = "session-current";
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
sessionId: "session-current",
|
|
96
|
-
success: true,
|
|
97
|
-
summary: "done",
|
|
98
|
-
}), "utf-8");
|
|
99
|
-
const watcher = createResultWatcher({
|
|
100
|
-
events: {
|
|
101
|
-
on: () => () => {},
|
|
102
|
-
emit() {},
|
|
103
|
-
},
|
|
104
|
-
}, state, resultsDir, 60_000, {
|
|
112
|
+
let observedRunIdLookups = 0;
|
|
113
|
+
writeIndexedResult(path.join(resultsDir, "current.json"), { id: "current", sessionId: "session-current", success: true, summary: "done" });
|
|
114
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
105
115
|
deliverIntercomResults: false,
|
|
106
116
|
observedCompletionRunIds() {
|
|
107
|
-
|
|
108
|
-
return [];
|
|
117
|
+
observedRunIdLookups += 1;
|
|
118
|
+
return ["scheduled-a"];
|
|
109
119
|
},
|
|
110
120
|
});
|
|
111
121
|
watcher.primeExistingResults();
|
|
112
|
-
assert.equal(
|
|
122
|
+
assert.equal(observedRunIdLookups, 1);
|
|
113
123
|
watcher.stopResultWatcher();
|
|
114
124
|
} finally {
|
|
115
125
|
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
116
126
|
}
|
|
117
127
|
});
|
|
118
128
|
|
|
129
|
+
it("keeps slow indexed result scans silent", () => {
|
|
130
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-slow-scan-"));
|
|
131
|
+
const originalNow = Date.now;
|
|
132
|
+
const originalError = console.error;
|
|
133
|
+
const logged: unknown[][] = [];
|
|
134
|
+
try {
|
|
135
|
+
const state = createState();
|
|
136
|
+
state.currentSessionId = "session-current";
|
|
137
|
+
state.asyncJobs.set("slow-scan", { asyncId: "slow-scan", asyncDir: path.join(resultsDir, "slow-scan"), status: "running", startedAt: 0, updatedAt: 0 });
|
|
138
|
+
let nowCalls = 0;
|
|
139
|
+
Date.now = () => nowCalls++ === 0 ? 0 : 501;
|
|
140
|
+
console.error = (...args: unknown[]) => { logged.push(args); };
|
|
141
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000);
|
|
142
|
+
try {
|
|
143
|
+
watcher.primeExistingResults();
|
|
144
|
+
} finally {
|
|
145
|
+
watcher.stopResultWatcher();
|
|
146
|
+
}
|
|
147
|
+
assert.deepEqual(logged, []);
|
|
148
|
+
} finally {
|
|
149
|
+
Date.now = originalNow;
|
|
150
|
+
console.error = originalError;
|
|
151
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
119
155
|
it("skips full parsing for unrelated sessions during priming, safety scans, and native watch events", async () => {
|
|
120
156
|
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-filter-"));
|
|
121
157
|
try {
|
|
@@ -133,13 +169,13 @@ describe("result watcher", () => {
|
|
|
133
169
|
unref() {},
|
|
134
170
|
} as fs.FSWatcher;
|
|
135
171
|
const writeResult = (id: string, sessionId: string) => {
|
|
136
|
-
|
|
172
|
+
writeIndexedResult(path.join(resultsDir, `${id}.json`), {
|
|
137
173
|
id,
|
|
138
174
|
results: [{ structuredOutput: { sessionId: "nested-not-owner" } }],
|
|
139
175
|
sessionId,
|
|
140
176
|
success: true,
|
|
141
177
|
summary: "done",
|
|
142
|
-
})
|
|
178
|
+
});
|
|
143
179
|
};
|
|
144
180
|
writeResult("startup-current", "session-current");
|
|
145
181
|
writeResult("startup-stale", "session-stale");
|
|
@@ -224,7 +260,7 @@ describe("result watcher", () => {
|
|
|
224
260
|
const binding = prepareMissionLaunch({
|
|
225
261
|
params: { mission: { title: "Workflow mission" }, task: "Run async child" },
|
|
226
262
|
projectRoot: project,
|
|
227
|
-
config: { globalIndexDir: path.join(root, "global-index") },
|
|
263
|
+
config: { directory: path.join(root, "missions"), globalIndexDir: path.join(root, "global-index") },
|
|
228
264
|
ownerSessionId: "session-current",
|
|
229
265
|
});
|
|
230
266
|
assert.ok(binding);
|
|
@@ -239,7 +275,7 @@ describe("result watcher", () => {
|
|
|
239
275
|
heartbeat: { status: "running" },
|
|
240
276
|
}],
|
|
241
277
|
});
|
|
242
|
-
|
|
278
|
+
writeIndexedResult(path.join(resultsDir, "async-child.json"), {
|
|
243
279
|
id: "async-child",
|
|
244
280
|
runId: "async-child",
|
|
245
281
|
sessionId: "session-current",
|
|
@@ -251,7 +287,7 @@ describe("result watcher", () => {
|
|
|
251
287
|
parentWorkflowRunId: "workflow-1",
|
|
252
288
|
workflowKey: "background",
|
|
253
289
|
results: [{ agent: "worker", success: true, output: "done", artifactPaths: { outputPath } }],
|
|
254
|
-
})
|
|
290
|
+
});
|
|
255
291
|
const state = createState();
|
|
256
292
|
state.currentSessionId = "session-current";
|
|
257
293
|
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
@@ -259,7 +295,7 @@ describe("result watcher", () => {
|
|
|
259
295
|
});
|
|
260
296
|
try {
|
|
261
297
|
watcher.primeExistingResults();
|
|
262
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
298
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
263
299
|
} finally {
|
|
264
300
|
watcher.stopResultWatcher();
|
|
265
301
|
}
|
|
@@ -279,6 +315,91 @@ describe("result watcher", () => {
|
|
|
279
315
|
}
|
|
280
316
|
});
|
|
281
317
|
|
|
318
|
+
it("keeps result files until failed completion observers retry successfully", async () => {
|
|
319
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-observer-retry-"));
|
|
320
|
+
const originalError = console.error;
|
|
321
|
+
try {
|
|
322
|
+
console.error = () => {};
|
|
323
|
+
const resultPath = path.join(resultsDir, "observer-retry.json");
|
|
324
|
+
writeIndexedResult(resultPath, { id: "observer-retry", runId: "observer-retry", sessionId: "session-current", success: true, summary: "done" });
|
|
325
|
+
const state = createState();
|
|
326
|
+
state.currentSessionId = "session-current";
|
|
327
|
+
let observerCalls = 0;
|
|
328
|
+
let deliveries = 0;
|
|
329
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
330
|
+
observeCompletion: () => {
|
|
331
|
+
observerCalls += 1;
|
|
332
|
+
if (observerCalls === 1) throw new Error("observer unavailable");
|
|
333
|
+
},
|
|
334
|
+
notifier: { deliver: async () => { deliveries += 1; return true; } },
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
watcher.primeExistingResults();
|
|
338
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(resultPath) && observerCalls >= 2), true);
|
|
339
|
+
} finally {
|
|
340
|
+
watcher.stopResultWatcher();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
assert.equal(observerCalls, 2);
|
|
344
|
+
assert.equal(deliveries, 1);
|
|
345
|
+
} finally {
|
|
346
|
+
console.error = originalError;
|
|
347
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("does not redeliver user notification after reload while observer retry is pending", async () => {
|
|
352
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-observer-reload-"));
|
|
353
|
+
const originalError = console.error;
|
|
354
|
+
try {
|
|
355
|
+
console.error = () => {};
|
|
356
|
+
const resultPath = path.join(resultsDir, "observer-reload.json");
|
|
357
|
+
writeIndexedResult(resultPath, { id: "observer-reload", runId: "observer-reload", sessionId: "session-current", success: true, summary: "done" });
|
|
358
|
+
let observerCalls = 0;
|
|
359
|
+
let deliveries = 0;
|
|
360
|
+
let emitted = 0;
|
|
361
|
+
const pi = { events: { on: () => () => {}, emit: () => { emitted += 1; } } };
|
|
362
|
+
const firstState = createState();
|
|
363
|
+
firstState.currentSessionId = "session-current";
|
|
364
|
+
const firstWatcher = createResultWatcher(pi, firstState, resultsDir, 60_000, {
|
|
365
|
+
observeCompletion: () => {
|
|
366
|
+
observerCalls += 1;
|
|
367
|
+
throw new Error("observer unavailable");
|
|
368
|
+
},
|
|
369
|
+
notifier: { deliver: async () => { deliveries += 1; return true; } },
|
|
370
|
+
});
|
|
371
|
+
try {
|
|
372
|
+
firstWatcher.primeExistingResults();
|
|
373
|
+
assert.equal(await waitForPredicate(() => {
|
|
374
|
+
if (deliveries !== 1 || !fs.existsSync(resultPath)) return false;
|
|
375
|
+
return typeof (JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { notificationDeliveredAt?: unknown }).notificationDeliveredAt === "number";
|
|
376
|
+
}), true);
|
|
377
|
+
} finally {
|
|
378
|
+
firstWatcher.stopResultWatcher();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const secondState = createState();
|
|
382
|
+
secondState.currentSessionId = "session-current";
|
|
383
|
+
const secondWatcher = createResultWatcher(pi, secondState, resultsDir, 60_000, {
|
|
384
|
+
observeCompletion: () => { observerCalls += 1; },
|
|
385
|
+
notifier: { deliver: async () => { deliveries += 1; return true; } },
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
secondWatcher.primeExistingResults();
|
|
389
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(resultPath)), true);
|
|
390
|
+
} finally {
|
|
391
|
+
secondWatcher.stopResultWatcher();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
assert.equal(observerCalls, 2);
|
|
395
|
+
assert.equal(deliveries, 1);
|
|
396
|
+
assert.equal(emitted, 1);
|
|
397
|
+
} finally {
|
|
398
|
+
console.error = originalError;
|
|
399
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
282
403
|
it("observes retained-project completions without changing active-session delivery", async () => {
|
|
283
404
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-scheduled-"));
|
|
284
405
|
const resultsDir = path.join(root, "results");
|
|
@@ -308,7 +429,7 @@ describe("result watcher", () => {
|
|
|
308
429
|
const state = createState();
|
|
309
430
|
state.currentSessionId = "session-b";
|
|
310
431
|
const resultPath = path.join(resultsDir, "scheduled-a.json");
|
|
311
|
-
|
|
432
|
+
writeIndexedResult(resultPath, { id: "scheduled-a", sessionId: "session-a", success: true, summary: "done" });
|
|
312
433
|
const watcher = createResultWatcher({
|
|
313
434
|
events: {
|
|
314
435
|
on: () => () => {},
|
|
@@ -316,12 +437,13 @@ describe("result watcher", () => {
|
|
|
316
437
|
},
|
|
317
438
|
}, state, resultsDir, 60_000, {
|
|
318
439
|
observeCompletion: (result) => manager.handleAsyncCompletion(result),
|
|
440
|
+
observedCompletionRunIds: () => manager.observedCompletionRunIds(),
|
|
319
441
|
notifier: { deliver: async () => assert.fail("inactive-session completion must not reach the live notifier") },
|
|
320
442
|
});
|
|
321
443
|
try {
|
|
322
444
|
watcher.startResultWatcher();
|
|
323
445
|
watcher.primeExistingResults();
|
|
324
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
446
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
325
447
|
} finally {
|
|
326
448
|
watcher.stopResultWatcher();
|
|
327
449
|
}
|
|
@@ -337,6 +459,192 @@ describe("result watcher", () => {
|
|
|
337
459
|
}
|
|
338
460
|
});
|
|
339
461
|
|
|
462
|
+
it("delivers indexed pending results during reload when public promotion is blocked", async () => {
|
|
463
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-pending-index-"));
|
|
464
|
+
const originalError = console.error;
|
|
465
|
+
try {
|
|
466
|
+
console.error = () => {};
|
|
467
|
+
const emitted: Array<{ event: string; data: unknown }> = [];
|
|
468
|
+
const delivered: unknown[] = [];
|
|
469
|
+
const state = createState();
|
|
470
|
+
state.currentSessionId = "session-current";
|
|
471
|
+
const resultPath = path.join(resultsDir, "pending-run.json");
|
|
472
|
+
fs.mkdirSync(resultPath, { recursive: true });
|
|
473
|
+
writePendingAsyncResultFile(resultPath, {
|
|
474
|
+
id: "pending-run",
|
|
475
|
+
runId: "pending-run",
|
|
476
|
+
sessionId: "session-current",
|
|
477
|
+
success: true,
|
|
478
|
+
state: "complete",
|
|
479
|
+
summary: "done from pending",
|
|
480
|
+
});
|
|
481
|
+
const watcher = createResultWatcher({
|
|
482
|
+
events: {
|
|
483
|
+
on: () => () => {},
|
|
484
|
+
emit(event: string, data: unknown) { emitted.push({ event, data }); },
|
|
485
|
+
},
|
|
486
|
+
}, state, resultsDir, 60_000, {
|
|
487
|
+
deliverIntercomResults: false,
|
|
488
|
+
notifier: { deliver: async (result) => { delivered.push(result); return true; } },
|
|
489
|
+
});
|
|
490
|
+
try {
|
|
491
|
+
watcher.primeExistingResults();
|
|
492
|
+
assert.equal(await waitForPredicate(() => emitted.some((entry) => entry.event === "subagent:async-complete")), true);
|
|
493
|
+
} finally {
|
|
494
|
+
watcher.stopResultWatcher();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
assert.equal((delivered[0] as { summary?: string } | undefined)?.summary, "done from pending");
|
|
498
|
+
assert.equal(fs.existsSync(pendingResultPath(resultsDir, "session-current", "pending-run")), false);
|
|
499
|
+
assert.equal(fs.statSync(resultPath).isDirectory(), true);
|
|
500
|
+
} finally {
|
|
501
|
+
console.error = originalError;
|
|
502
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("uses indexed result files and ignores unindexed stale files during reload", async () => {
|
|
507
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-index-"));
|
|
508
|
+
try {
|
|
509
|
+
for (let i = 0; i < 300; i += 1) {
|
|
510
|
+
fs.writeFileSync(path.join(resultsDir, `stale-${i}.json`), JSON.stringify({ id: `stale-${i}`, sessionId: "session-stale", success: true, summary: "done" }), "utf-8");
|
|
511
|
+
}
|
|
512
|
+
writeIndexedResult(path.join(resultsDir, "current.json"), { id: "current", runId: "current", sessionId: "session-current", success: true, summary: "done" });
|
|
513
|
+
const parsedIds: string[] = [];
|
|
514
|
+
const state = createState();
|
|
515
|
+
state.currentSessionId = "session-current";
|
|
516
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
517
|
+
deliverIntercomResults: false,
|
|
518
|
+
parseResult(raw) {
|
|
519
|
+
const parsed = JSON.parse(raw) as { id: string };
|
|
520
|
+
parsedIds.push(parsed.id);
|
|
521
|
+
return parsed;
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
try {
|
|
525
|
+
watcher.primeExistingResults();
|
|
526
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(path.join(resultsDir, "current.json"))), true);
|
|
527
|
+
} finally {
|
|
528
|
+
watcher.stopResultWatcher();
|
|
529
|
+
}
|
|
530
|
+
assert.deepEqual(parsedIds, ["current"]);
|
|
531
|
+
assert.equal(fs.existsSync(path.join(resultsDir, "stale-0.json")), true);
|
|
532
|
+
} finally {
|
|
533
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("includes observer-known run ids when current-session results are also indexed", async () => {
|
|
538
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-mixed-observer-"));
|
|
539
|
+
try {
|
|
540
|
+
const resultsDir = path.join(root, "results");
|
|
541
|
+
writeIndexedResult(path.join(resultsDir, "current.json"), { id: "current", runId: "current", sessionId: "session-current", success: true, summary: "current" });
|
|
542
|
+
writeIndexedResult(path.join(resultsDir, "scheduled-a.json"), { id: "scheduled-a", runId: "scheduled-a", sessionId: "session-a", success: true, summary: "scheduled" });
|
|
543
|
+
let observations = 0;
|
|
544
|
+
const state = createState();
|
|
545
|
+
state.currentSessionId = "session-current";
|
|
546
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
547
|
+
observedCompletionRunIds: () => ["scheduled-a"],
|
|
548
|
+
observeCompletion: (result) => { if (result.runId === "scheduled-a") observations += 1; },
|
|
549
|
+
notifier: { deliver: async () => true },
|
|
550
|
+
});
|
|
551
|
+
try {
|
|
552
|
+
watcher.primeExistingResults();
|
|
553
|
+
assert.equal(await waitForPredicate(() => observations === 1), true);
|
|
554
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(path.join(resultsDir, "current.json"))), true);
|
|
555
|
+
} finally {
|
|
556
|
+
watcher.stopResultWatcher();
|
|
557
|
+
}
|
|
558
|
+
} finally {
|
|
559
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("delivers observed indexed pending results when public promotion is blocked", async () => {
|
|
564
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-observed-pending-"));
|
|
565
|
+
const originalError = console.error;
|
|
566
|
+
try {
|
|
567
|
+
console.error = () => {};
|
|
568
|
+
for (let i = 0; i < 300; i += 1) {
|
|
569
|
+
fs.writeFileSync(path.join(resultsDir, `stale-${i}.json`), JSON.stringify({ id: `stale-${i}`, sessionId: "session-stale", success: true, summary: "done" }), "utf-8");
|
|
570
|
+
}
|
|
571
|
+
const resultPath = path.join(resultsDir, "scheduled-pending.json");
|
|
572
|
+
fs.mkdirSync(resultPath, { recursive: true });
|
|
573
|
+
writePendingAsyncResultFile(resultPath, {
|
|
574
|
+
id: "scheduled-pending",
|
|
575
|
+
runId: "scheduled-pending",
|
|
576
|
+
sessionId: "session-a",
|
|
577
|
+
success: true,
|
|
578
|
+
state: "complete",
|
|
579
|
+
summary: "scheduled pending done",
|
|
580
|
+
});
|
|
581
|
+
let observations = 0;
|
|
582
|
+
const state = createState();
|
|
583
|
+
state.currentSessionId = "session-b";
|
|
584
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
585
|
+
observedCompletionRunIds: () => ["scheduled-pending"],
|
|
586
|
+
observeCompletion: (result) => {
|
|
587
|
+
if (result.runId === "scheduled-pending") observations += 1;
|
|
588
|
+
},
|
|
589
|
+
notifier: { deliver: async () => assert.fail("observer-owned completion must not reach active delivery") },
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
watcher.primeExistingResults();
|
|
593
|
+
assert.equal(await waitForPredicate(() => observations === 1), true);
|
|
594
|
+
} finally {
|
|
595
|
+
watcher.stopResultWatcher();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
assert.equal(fs.existsSync(pendingResultPath(resultsDir, "session-a", "scheduled-pending")), true);
|
|
599
|
+
assert.equal(fs.statSync(resultPath).isDirectory(), true);
|
|
600
|
+
assert.equal(fs.existsSync(path.join(resultsDir, "stale-0.json")), true);
|
|
601
|
+
} finally {
|
|
602
|
+
console.error = originalError;
|
|
603
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("uses observed run ids directly without scanning a stale result pile", async () => {
|
|
608
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-observed-index-"));
|
|
609
|
+
try {
|
|
610
|
+
for (let i = 0; i < 300; i += 1) {
|
|
611
|
+
fs.writeFileSync(path.join(resultsDir, `stale-${i}.json`), JSON.stringify({ id: `stale-${i}`, sessionId: "session-stale", success: true, summary: "done" }), "utf-8");
|
|
612
|
+
}
|
|
613
|
+
const resultPath = path.join(resultsDir, "scheduled-a.json");
|
|
614
|
+
writeIndexedResult(resultPath, { id: "scheduled-a", runId: "scheduled-a", sessionId: "session-a", success: true, summary: "done" });
|
|
615
|
+
let observations = 0;
|
|
616
|
+
let observed = true;
|
|
617
|
+
const makeWatcher = () => {
|
|
618
|
+
const state = createState();
|
|
619
|
+
state.currentSessionId = "session-b";
|
|
620
|
+
return createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000, {
|
|
621
|
+
observedCompletionRunIds: () => observed ? ["scheduled-a"] : [],
|
|
622
|
+
observeCompletion: () => { observations += 1; observed = false; },
|
|
623
|
+
notifier: { deliver: async () => assert.fail("observer-owned completion must not reach active delivery") },
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
let watcher = makeWatcher();
|
|
627
|
+
try {
|
|
628
|
+
watcher.primeExistingResults();
|
|
629
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
630
|
+
} finally {
|
|
631
|
+
watcher.stopResultWatcher();
|
|
632
|
+
}
|
|
633
|
+
watcher = makeWatcher();
|
|
634
|
+
try {
|
|
635
|
+
watcher.primeExistingResults();
|
|
636
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
637
|
+
} finally {
|
|
638
|
+
watcher.stopResultWatcher();
|
|
639
|
+
}
|
|
640
|
+
assert.equal(observations, 1);
|
|
641
|
+
assert.equal(fs.existsSync(resultPath), true);
|
|
642
|
+
assert.equal(fs.existsSync(path.join(resultsDir, "stale-0.json")), true);
|
|
643
|
+
} finally {
|
|
644
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
340
648
|
it("uses native completion delivery without attempting external grouped intercom when disabled", async () => {
|
|
341
649
|
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-native-delivery-"));
|
|
342
650
|
try {
|
|
@@ -351,7 +659,7 @@ describe("result watcher", () => {
|
|
|
351
659
|
const state = createState();
|
|
352
660
|
state.currentSessionId = "session-native";
|
|
353
661
|
const resultPath = path.join(resultsDir, "native-run.json");
|
|
354
|
-
|
|
662
|
+
writeIndexedResult(resultPath, {
|
|
355
663
|
id: "native-run",
|
|
356
664
|
runId: "native-run",
|
|
357
665
|
sessionId: "session-native",
|
|
@@ -361,14 +669,14 @@ describe("result watcher", () => {
|
|
|
361
669
|
summary: "Subagent process terminated by signal SIGTERM.",
|
|
362
670
|
results: [{ agent: "worker", output: "", success: false, exitCode: 1, processSignal: "SIGTERM" }],
|
|
363
671
|
intercomTarget: "native-parent",
|
|
364
|
-
})
|
|
672
|
+
});
|
|
365
673
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000, {
|
|
366
674
|
deliverIntercomResults: false,
|
|
367
675
|
notifier: { deliver: async (result) => { delivered.push(result); return true; } },
|
|
368
676
|
});
|
|
369
677
|
try {
|
|
370
678
|
watcher.primeExistingResults();
|
|
371
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
679
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
372
680
|
} finally {
|
|
373
681
|
watcher.stopResultWatcher();
|
|
374
682
|
}
|
|
@@ -419,9 +727,8 @@ describe("result watcher", () => {
|
|
|
419
727
|
const ownerWatcher = createResultWatcher(owner.pi, ownerState, resultsDir, 60_000);
|
|
420
728
|
const otherWatcher = createResultWatcher(other.pi, otherState, resultsDir, 60_000);
|
|
421
729
|
const ownerResultPath = path.join(resultsDir, "owner-run.json");
|
|
422
|
-
const sessionlessResultPath = path.join(resultsDir, "sessionless-run.json");
|
|
423
730
|
try {
|
|
424
|
-
|
|
731
|
+
writeIndexedResult(ownerResultPath, {
|
|
425
732
|
id: "owner-run",
|
|
426
733
|
agent: "worker",
|
|
427
734
|
mode: "single",
|
|
@@ -432,23 +739,12 @@ describe("result watcher", () => {
|
|
|
432
739
|
sessionId: "session-owner",
|
|
433
740
|
cwd: "/repo",
|
|
434
741
|
intercomTarget: "owner-target",
|
|
435
|
-
})
|
|
436
|
-
fs.writeFileSync(sessionlessResultPath, JSON.stringify({
|
|
437
|
-
id: "sessionless-run",
|
|
438
|
-
agent: "worker",
|
|
439
|
-
mode: "single",
|
|
440
|
-
success: true,
|
|
441
|
-
state: "complete",
|
|
442
|
-
summary: "legacy cwd-scoped output",
|
|
443
|
-
results: [{ agent: "worker", output: "legacy cwd-scoped output", success: true }],
|
|
444
|
-
cwd: "/repo",
|
|
445
|
-
intercomTarget: "sessionless-target",
|
|
446
|
-
}), "utf-8");
|
|
742
|
+
});
|
|
447
743
|
|
|
448
744
|
otherWatcher.primeExistingResults();
|
|
449
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
745
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
450
746
|
ownerWatcher.primeExistingResults();
|
|
451
|
-
await
|
|
747
|
+
assert.equal(await waitForPredicate(() => owner.emitted.some((entry) => entry.event === "subagent:async-complete")), true);
|
|
452
748
|
} finally {
|
|
453
749
|
ownerWatcher.stopResultWatcher();
|
|
454
750
|
otherWatcher.stopResultWatcher();
|
|
@@ -462,7 +758,6 @@ describe("result watcher", () => {
|
|
|
462
758
|
assert.equal(other.emitted.some((entry) => entry.event === "subagent:async-complete"), false);
|
|
463
759
|
assert.equal(other.emitted.some((entry) => entry.event === "subagent:result-intercom"), false);
|
|
464
760
|
assert.equal(fs.existsSync(ownerResultPath), false);
|
|
465
|
-
assert.equal(fs.existsSync(sessionlessResultPath), true);
|
|
466
761
|
} finally {
|
|
467
762
|
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
468
763
|
}
|
|
@@ -482,6 +777,7 @@ describe("result watcher", () => {
|
|
|
482
777
|
},
|
|
483
778
|
};
|
|
484
779
|
const state = createState();
|
|
780
|
+
state.asyncJobs.set("bad", { asyncId: "bad", asyncDir: path.join(resultsDir, "bad"), status: "running", startedAt: Date.now(), updatedAt: Date.now() });
|
|
485
781
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
486
782
|
const originalError = console.error;
|
|
487
783
|
const logged: unknown[][] = [];
|
|
@@ -490,7 +786,7 @@ describe("result watcher", () => {
|
|
|
490
786
|
};
|
|
491
787
|
try {
|
|
492
788
|
watcher.primeExistingResults();
|
|
493
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
789
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
494
790
|
} finally {
|
|
495
791
|
console.error = originalError;
|
|
496
792
|
watcher.stopResultWatcher();
|
|
@@ -590,16 +886,16 @@ describe("result watcher", () => {
|
|
|
590
886
|
watcher.startResultWatcher();
|
|
591
887
|
assert.equal(state.watcher, fakeWatcher);
|
|
592
888
|
for (const id of ["missed-a", "missed-b"]) {
|
|
593
|
-
|
|
889
|
+
writeIndexedResult(path.join(resultsDir, `${id}.json`), {
|
|
594
890
|
id,
|
|
595
891
|
sessionId: "session-1",
|
|
596
892
|
success: true,
|
|
597
893
|
state: "complete",
|
|
598
894
|
summary: "done",
|
|
599
|
-
})
|
|
895
|
+
});
|
|
600
896
|
}
|
|
601
897
|
scan?.();
|
|
602
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
898
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
603
899
|
} finally {
|
|
604
900
|
watcher.stopResultWatcher();
|
|
605
901
|
}
|
|
@@ -662,6 +958,7 @@ describe("result watcher", () => {
|
|
|
662
958
|
});
|
|
663
959
|
const originalError = console.error;
|
|
664
960
|
const childSessionPath = path.join(resultsDir, "a-session.jsonl");
|
|
961
|
+
const resultPath = path.join(resultsDir, "async-fallback.json");
|
|
665
962
|
console.error = () => {};
|
|
666
963
|
try {
|
|
667
964
|
watcher.startResultWatcher();
|
|
@@ -669,7 +966,7 @@ describe("result watcher", () => {
|
|
|
669
966
|
assert.notEqual(state.watcherRestartTimer, null);
|
|
670
967
|
|
|
671
968
|
fs.writeFileSync(childSessionPath, "", "utf-8");
|
|
672
|
-
|
|
969
|
+
writeIndexedResult(resultPath, {
|
|
673
970
|
id: "async-fallback",
|
|
674
971
|
runId: "run-fallback",
|
|
675
972
|
agent: "parallel:a+b",
|
|
@@ -683,9 +980,9 @@ describe("result watcher", () => {
|
|
|
683
980
|
],
|
|
684
981
|
sessionId: "session-1",
|
|
685
982
|
intercomTarget: "subagent-chat-main",
|
|
686
|
-
})
|
|
983
|
+
});
|
|
687
984
|
poll?.();
|
|
688
|
-
await
|
|
985
|
+
assert.equal(await waitForPredicate(() => emitted.some((entry) => entry.event === "subagent:async-complete") && !fs.existsSync(resultPath)), true);
|
|
689
986
|
} finally {
|
|
690
987
|
console.error = originalError;
|
|
691
988
|
watcher.stopResultWatcher();
|
|
@@ -694,7 +991,7 @@ describe("result watcher", () => {
|
|
|
694
991
|
const intercomEvents = emitted.filter((entry) => entry.event === "subagent:result-intercom");
|
|
695
992
|
assert.equal(intercomEvents.length, 1);
|
|
696
993
|
assert.equal(emitted.some((entry) => entry.event === "subagent:async-complete"), true);
|
|
697
|
-
assert.equal(fs.existsSync(
|
|
994
|
+
assert.equal(fs.existsSync(resultPath), false);
|
|
698
995
|
const payload = intercomEvents[0]?.data as { mode?: string; status?: string; message?: string; children?: Array<{ status?: string; summary?: string; sessionPath?: string }> };
|
|
699
996
|
const completion = emitted.find((entry) => entry.event === "subagent:async-complete")?.data as { results?: Array<{ status?: string; summary?: string; sessionPath?: string }> } | undefined;
|
|
700
997
|
assert.equal(payload.mode, "parallel");
|
|
@@ -764,9 +1061,9 @@ describe("result watcher", () => {
|
|
|
764
1061
|
assert.equal(state.watcher, null);
|
|
765
1062
|
assert.notEqual(state.watcherRestartTimer, null);
|
|
766
1063
|
|
|
767
|
-
|
|
1064
|
+
writeIndexedResult(path.join(resultsDir, "done.json"), { sessionId: "session-1", summary: "done" });
|
|
768
1065
|
poll?.();
|
|
769
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1066
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
770
1067
|
} finally {
|
|
771
1068
|
console.error = originalError;
|
|
772
1069
|
watcher.stopResultWatcher();
|
|
@@ -811,7 +1108,7 @@ describe("result watcher", () => {
|
|
|
811
1108
|
const missingSession = path.join(resultsDir, "b-session.jsonl");
|
|
812
1109
|
try {
|
|
813
1110
|
fs.writeFileSync(firstSession, "", "utf-8");
|
|
814
|
-
|
|
1111
|
+
writeIndexedResult(path.join(resultsDir, "async-1.json"), {
|
|
815
1112
|
id: "async-1",
|
|
816
1113
|
runId: "run-123",
|
|
817
1114
|
agent: "parallel:a+b",
|
|
@@ -828,9 +1125,9 @@ describe("result watcher", () => {
|
|
|
828
1125
|
asyncDir: "/tmp/async-1",
|
|
829
1126
|
parallelHandoff: { version: 1, path: "/tmp/async-1/handoff.json", groupCount: 1, childCount: 2, changedPatches: 1, cleanupState: "complete" },
|
|
830
1127
|
intercomTarget: "subagent-chat-main",
|
|
831
|
-
})
|
|
1128
|
+
});
|
|
832
1129
|
watcher.primeExistingResults();
|
|
833
|
-
await
|
|
1130
|
+
assert.equal(await waitForPredicate(() => emitted.some((entry) => entry.event === "subagent:async-complete") && emitted.some((entry) => entry.event === "subagent:result-intercom")), true);
|
|
834
1131
|
} finally {
|
|
835
1132
|
watcher.stopResultWatcher();
|
|
836
1133
|
}
|
|
@@ -882,7 +1179,7 @@ describe("result watcher", () => {
|
|
|
882
1179
|
state.currentSessionId = "session-1";
|
|
883
1180
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
884
1181
|
try {
|
|
885
|
-
|
|
1182
|
+
writeIndexedResult(path.join(resultsDir, "async-stopped.json"), {
|
|
886
1183
|
id: "async-stopped",
|
|
887
1184
|
runId: "async-stopped",
|
|
888
1185
|
agent: "parallel:a+b",
|
|
@@ -897,9 +1194,9 @@ describe("result watcher", () => {
|
|
|
897
1194
|
],
|
|
898
1195
|
sessionId: "session-1",
|
|
899
1196
|
intercomTarget: "subagent-chat-main",
|
|
900
|
-
}
|
|
1197
|
+
});
|
|
901
1198
|
watcher.primeExistingResults();
|
|
902
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1199
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
903
1200
|
} finally {
|
|
904
1201
|
watcher.stopResultWatcher();
|
|
905
1202
|
}
|
|
@@ -964,7 +1261,7 @@ describe("result watcher", () => {
|
|
|
964
1261
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
965
1262
|
const resultPath = path.join(resultsDir, "async-nested-root.json");
|
|
966
1263
|
try {
|
|
967
|
-
|
|
1264
|
+
writeIndexedResult(resultPath, {
|
|
968
1265
|
id: "async-nested-root",
|
|
969
1266
|
runId: "async-nested-root",
|
|
970
1267
|
agent: "owner",
|
|
@@ -975,9 +1272,9 @@ describe("result watcher", () => {
|
|
|
975
1272
|
results: [{ agent: "owner", output: "owner done", success: true }],
|
|
976
1273
|
sessionId: "session-1",
|
|
977
1274
|
intercomTarget: "subagent-chat-main",
|
|
978
|
-
})
|
|
1275
|
+
});
|
|
979
1276
|
watcher.primeExistingResults();
|
|
980
|
-
await
|
|
1277
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(resultPath) && emitted.some((entry) => entry.event === "subagent:async-complete") && emitted.some((entry) => entry.event === "subagent:result-intercom")), true);
|
|
981
1278
|
} finally {
|
|
982
1279
|
watcher.stopResultWatcher();
|
|
983
1280
|
}
|
|
@@ -1032,7 +1329,7 @@ describe("result watcher", () => {
|
|
|
1032
1329
|
logged.push(args);
|
|
1033
1330
|
};
|
|
1034
1331
|
try {
|
|
1035
|
-
|
|
1332
|
+
writeIndexedResult(resultPath, {
|
|
1036
1333
|
id: "async-explicit-nested",
|
|
1037
1334
|
runId: "async-explicit-nested",
|
|
1038
1335
|
agent: "owner",
|
|
@@ -1055,9 +1352,9 @@ describe("result watcher", () => {
|
|
|
1055
1352
|
],
|
|
1056
1353
|
sessionId: "session-1",
|
|
1057
1354
|
intercomTarget: "subagent-chat-main",
|
|
1058
|
-
})
|
|
1355
|
+
});
|
|
1059
1356
|
watcher.primeExistingResults();
|
|
1060
|
-
await
|
|
1357
|
+
assert.equal(await waitForPredicate(() => !fs.existsSync(resultPath) && emitted.some((entry) => entry.event === "subagent:async-complete")), true);
|
|
1061
1358
|
} finally {
|
|
1062
1359
|
console.error = originalError;
|
|
1063
1360
|
watcher.stopResultWatcher();
|
|
@@ -1123,7 +1420,7 @@ describe("result watcher", () => {
|
|
|
1123
1420
|
logged.push(args);
|
|
1124
1421
|
};
|
|
1125
1422
|
try {
|
|
1126
|
-
|
|
1423
|
+
writeIndexedResult(resultPath, {
|
|
1127
1424
|
id: "async-nested-retry",
|
|
1128
1425
|
runId: "async-nested-retry",
|
|
1129
1426
|
agent: "owner",
|
|
@@ -1132,9 +1429,9 @@ describe("result watcher", () => {
|
|
|
1132
1429
|
summary: "owner done",
|
|
1133
1430
|
sessionId: "session-1",
|
|
1134
1431
|
intercomTarget: "subagent-chat-main",
|
|
1135
|
-
})
|
|
1432
|
+
});
|
|
1136
1433
|
watcher.primeExistingResults();
|
|
1137
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1434
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1138
1435
|
|
|
1139
1436
|
assert.equal(fs.existsSync(resultPath), true);
|
|
1140
1437
|
assert.equal(emitted.length, 0);
|
|
@@ -1186,7 +1483,7 @@ describe("result watcher", () => {
|
|
|
1186
1483
|
state.currentSessionId = "session-1";
|
|
1187
1484
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
1188
1485
|
try {
|
|
1189
|
-
|
|
1486
|
+
writeIndexedResult(path.join(resultsDir, "async-top-session.json"), {
|
|
1190
1487
|
id: "async-top-session",
|
|
1191
1488
|
mode: "parallel",
|
|
1192
1489
|
success: false,
|
|
@@ -1198,9 +1495,9 @@ describe("result watcher", () => {
|
|
|
1198
1495
|
sessionId: "session-1",
|
|
1199
1496
|
sessionFile: "/tmp/top-session.jsonl",
|
|
1200
1497
|
intercomTarget: "subagent-chat-main",
|
|
1201
|
-
})
|
|
1498
|
+
});
|
|
1202
1499
|
watcher.primeExistingResults();
|
|
1203
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1500
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1204
1501
|
} finally {
|
|
1205
1502
|
watcher.stopResultWatcher();
|
|
1206
1503
|
}
|
|
@@ -1243,7 +1540,7 @@ describe("result watcher", () => {
|
|
|
1243
1540
|
state.currentSessionId = "session-1";
|
|
1244
1541
|
const watcher = createResultWatcher(pi, state, resultsDir, 60_000);
|
|
1245
1542
|
try {
|
|
1246
|
-
|
|
1543
|
+
writeIndexedResult(path.join(resultsDir, "async-paused.json"), {
|
|
1247
1544
|
id: "async-paused",
|
|
1248
1545
|
runId: "run-paused",
|
|
1249
1546
|
agent: "chain:a->b",
|
|
@@ -1257,9 +1554,9 @@ describe("result watcher", () => {
|
|
|
1257
1554
|
],
|
|
1258
1555
|
sessionId: "session-1",
|
|
1259
1556
|
intercomTarget: "subagent-chat-main",
|
|
1260
|
-
})
|
|
1557
|
+
});
|
|
1261
1558
|
watcher.primeExistingResults();
|
|
1262
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1559
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1263
1560
|
} finally {
|
|
1264
1561
|
watcher.stopResultWatcher();
|
|
1265
1562
|
}
|
|
@@ -1301,7 +1598,7 @@ describe("result watcher", () => {
|
|
|
1301
1598
|
logged.push(args);
|
|
1302
1599
|
};
|
|
1303
1600
|
try {
|
|
1304
|
-
|
|
1601
|
+
writeIndexedResult(path.join(resultsDir, "async-2.json"), {
|
|
1305
1602
|
id: "async-2",
|
|
1306
1603
|
runId: "run-456",
|
|
1307
1604
|
agent: "worker",
|
|
@@ -1310,9 +1607,9 @@ describe("result watcher", () => {
|
|
|
1310
1607
|
summary: "Worker summary",
|
|
1311
1608
|
sessionId: "session-1",
|
|
1312
1609
|
intercomTarget: "orchestrator",
|
|
1313
|
-
})
|
|
1610
|
+
});
|
|
1314
1611
|
watcher.primeExistingResults();
|
|
1315
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1612
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1316
1613
|
} finally {
|
|
1317
1614
|
console.error = originalError;
|
|
1318
1615
|
watcher.stopResultWatcher();
|
|
@@ -1354,7 +1651,7 @@ describe("result watcher", () => {
|
|
|
1354
1651
|
const loggedWarning = () => logged.some((entry) => /Subagent async grouped result intercom delivery was not acknowledged/.test(String(entry[0] ?? "")));
|
|
1355
1652
|
let warned = false;
|
|
1356
1653
|
try {
|
|
1357
|
-
|
|
1654
|
+
writeIndexedResult(path.join(resultsDir, "unacknowledged.json"), {
|
|
1358
1655
|
id: "unacknowledged",
|
|
1359
1656
|
runId: "run-unacknowledged",
|
|
1360
1657
|
agent: "worker",
|
|
@@ -1363,7 +1660,7 @@ describe("result watcher", () => {
|
|
|
1363
1660
|
summary: "Worker summary",
|
|
1364
1661
|
sessionId: "session-1",
|
|
1365
1662
|
intercomTarget: "orchestrator",
|
|
1366
|
-
})
|
|
1663
|
+
});
|
|
1367
1664
|
watcher.primeExistingResults();
|
|
1368
1665
|
warned = await waitForPredicate(loggedWarning);
|
|
1369
1666
|
} finally {
|
|
@@ -1410,7 +1707,7 @@ describe("result watcher", () => {
|
|
|
1410
1707
|
notifier: { async deliver(result) { delivered.push({ intercomDelivered: result.intercomDelivered }); return true; } },
|
|
1411
1708
|
});
|
|
1412
1709
|
try {
|
|
1413
|
-
|
|
1710
|
+
writeIndexedResult(path.join(resultsDir, "acknowledged.json"), {
|
|
1414
1711
|
id: "acknowledged",
|
|
1415
1712
|
runId: "run-acknowledged",
|
|
1416
1713
|
agent: "worker",
|
|
@@ -1419,9 +1716,9 @@ describe("result watcher", () => {
|
|
|
1419
1716
|
summary: "Worker summary",
|
|
1420
1717
|
sessionId: "session-1",
|
|
1421
1718
|
intercomTarget: "orchestrator",
|
|
1422
|
-
})
|
|
1719
|
+
});
|
|
1423
1720
|
watcher.primeExistingResults();
|
|
1424
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1721
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1425
1722
|
} finally {
|
|
1426
1723
|
watcher.stopResultWatcher();
|
|
1427
1724
|
}
|
|
@@ -1444,11 +1741,11 @@ describe("result watcher", () => {
|
|
|
1444
1741
|
const resultFile = "expired.json";
|
|
1445
1742
|
const result = { sessionId: "session-1", agent: "worker", success: true, summary: "new result", timestamp: 123 };
|
|
1446
1743
|
const resultPath = path.join(resultsDir, resultFile);
|
|
1447
|
-
|
|
1744
|
+
writeIndexedResult(resultPath, result);
|
|
1448
1745
|
state.completionSeen.set(buildCompletionKey(result, `result:${resultFile}`), Date.now() - 61_000);
|
|
1449
1746
|
try {
|
|
1450
1747
|
watcher.primeExistingResults();
|
|
1451
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1748
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1452
1749
|
} finally {
|
|
1453
1750
|
watcher.stopResultWatcher();
|
|
1454
1751
|
}
|
|
@@ -1471,10 +1768,10 @@ describe("result watcher", () => {
|
|
|
1471
1768
|
notifier: { async deliver() { attempts += 1; return attempts > 1; } },
|
|
1472
1769
|
});
|
|
1473
1770
|
const resultPath = path.join(resultsDir, "retry.json");
|
|
1474
|
-
|
|
1771
|
+
writeIndexedResult(resultPath, { id: "retry", sessionId: "session-1", agent: "worker", success: true, summary: "done", timestamp: 1 });
|
|
1475
1772
|
try {
|
|
1476
1773
|
watcher.primeExistingResults();
|
|
1477
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1774
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1478
1775
|
assert.equal(fs.existsSync(resultPath), true);
|
|
1479
1776
|
await new Promise((resolve) => setTimeout(resolve, 180));
|
|
1480
1777
|
} finally {
|
|
@@ -1499,12 +1796,12 @@ describe("result watcher", () => {
|
|
|
1499
1796
|
notifier: { deliver: () => new Promise<boolean>((resolve) => { accept = resolve; }) },
|
|
1500
1797
|
});
|
|
1501
1798
|
const resultPath = path.join(resultsDir, "stale.json");
|
|
1502
|
-
|
|
1799
|
+
writeIndexedResult(resultPath, { id: "stale", sessionId: "session-1", agent: "worker", success: true, summary: "done", timestamp: 1 });
|
|
1503
1800
|
watcher.primeExistingResults();
|
|
1504
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1801
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1505
1802
|
watcher.stopResultWatcher();
|
|
1506
1803
|
accept(true);
|
|
1507
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1804
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1508
1805
|
assert.equal(fs.existsSync(resultPath), true);
|
|
1509
1806
|
assert.deepEqual(emitted, []);
|
|
1510
1807
|
} finally {
|
|
@@ -1522,10 +1819,10 @@ describe("result watcher", () => {
|
|
|
1522
1819
|
const watcher = createResultWatcher({ events: { on: () => () => {}, emit(event) { emitted.push(event); } } }, state, resultsDir, 60_000, {
|
|
1523
1820
|
notifier: { async deliver(result) { delivered.push({ triggerTurn: result.triggerTurn }); return true; } },
|
|
1524
1821
|
});
|
|
1525
|
-
|
|
1822
|
+
writeIndexedResult(path.join(resultsDir, "quiet.json"), { id: "quiet", sessionId: "session-1", agent: "worker", success: true, summary: "done", timestamp: 1, intercomTarget: "host" });
|
|
1526
1823
|
try {
|
|
1527
1824
|
watcher.primeExistingResults({ triggerTurn: false });
|
|
1528
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1825
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1529
1826
|
} finally {
|
|
1530
1827
|
watcher.stopResultWatcher();
|
|
1531
1828
|
}
|