@bastani/atomic 0.9.12 → 0.9.13-alpha.1
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 +20 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/README.md +5 -12
- package/dist/builtin/intercom/broker/client.ts +11 -2
- package/dist/builtin/intercom/contact-supervisor-tool.ts +13 -3
- package/dist/builtin/intercom/index-heavy.ts +13 -3
- package/dist/builtin/intercom/index.ts +43 -19
- package/dist/builtin/intercom/intercom-utils.ts +15 -29
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/source-ownership.ts +9 -12
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/README.md +3 -1
- package/dist/builtin/mcp/index.ts +10 -16
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/startup-warmup.ts +20 -8
- package/dist/builtin/subagents/CHANGELOG.md +30 -0
- package/dist/builtin/subagents/README.md +22 -30
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/doctor.ts +1 -1
- package/dist/builtin/subagents/src/extension/fanout-child.ts +76 -56
- package/dist/builtin/subagents/src/extension/index.ts +23 -32
- package/dist/builtin/subagents/src/extension/startup-maintenance.ts +25 -44
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +7 -7
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +166 -434
- package/dist/builtin/subagents/src/runs/background/notify.ts +29 -8
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +1 -281
- package/dist/builtin/subagents/src/runs/foreground/execution-structured-retries.ts +11 -75
- package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +457 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +119 -246
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +1 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +18 -9
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +1 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +1 -646
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +2 -7
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +5 -5
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +7 -8
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +14 -5
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +158 -93
- package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +179 -0
- package/dist/builtin/subagents/src/runs/inprocess/background.ts +89 -0
- package/dist/builtin/subagents/src/runs/inprocess/child-policy.ts +27 -0
- package/dist/builtin/subagents/src/runs/inprocess/control-registry.ts +55 -0
- package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +106 -0
- package/dist/builtin/subagents/src/runs/inprocess/index.ts +50 -0
- package/dist/builtin/subagents/src/runs/inprocess/nested-routing.ts +218 -0
- package/dist/builtin/subagents/src/runs/{shared/subagent-prompt-runtime.ts → inprocess/prompt-behavior.ts} +37 -75
- package/dist/builtin/subagents/src/runs/inprocess/runner.ts +1138 -0
- package/dist/builtin/subagents/src/runs/{shared/nested-events.ts → inprocess/runtime-support/nested-api.ts} +13 -7
- package/dist/builtin/subagents/src/runs/{shared/nested-events-control.ts → inprocess/runtime-support/nested-control.ts} +18 -17
- package/dist/builtin/subagents/src/runs/{shared/nested-events-core.ts → inprocess/runtime-support/nested-core.ts} +14 -61
- package/dist/builtin/subagents/src/runs/{shared/nested-events-projection.ts → inprocess/runtime-support/nested-projection.ts} +4 -10
- package/dist/builtin/subagents/src/runs/{shared/nested-events-registry.ts → inprocess/runtime-support/nested-registry.ts} +19 -4
- package/dist/builtin/subagents/src/runs/{shared/nested-render.ts → inprocess/runtime-support/nested-rendering.ts} +3 -3
- package/dist/builtin/subagents/src/runs/{shared/nested-events-sanitize.ts → inprocess/runtime-support/nested-sanitize.ts} +3 -3
- package/dist/builtin/subagents/src/runs/inprocess.ts +1 -0
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +10 -4
- package/dist/builtin/subagents/src/runs/shared/model-candidate-filter.ts +0 -1
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +15 -492
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +13 -11
- package/dist/builtin/subagents/src/runs/shared/run-history.ts +3 -5
- package/dist/builtin/subagents/src/runs/shared/single-output.ts +4 -4
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +5 -5
- package/dist/builtin/subagents/src/shared/types-async.ts +0 -1
- package/dist/builtin/subagents/src/shared/types-config.ts +18 -0
- package/dist/builtin/subagents/src/shared/types-results.ts +9 -2
- package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +2 -3
- package/dist/builtin/subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-chain-graph.ts +10 -4
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +12 -11
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +49 -12
- package/dist/builtin/subagents/src/tui/render-result.ts +46 -17
- package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -1
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +8 -5
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -8
- package/dist/builtin/subagents/src/tui/render-widget.ts +0 -24
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +25 -0
- package/dist/builtin/workflows/README.md +13 -3
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +7 -0
- package/dist/builtin/workflows/src/durable/dbos-metadata.ts +11 -1
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +1 -0
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +4 -28
- package/dist/builtin/workflows/src/durable/types.ts +5 -0
- package/dist/builtin/workflows/src/engine/run.ts +17 -1
- package/dist/builtin/workflows/src/extension/atomic-stage-session.ts +12 -39
- package/dist/builtin/workflows/src/extension/config-file-loader.ts +10 -1
- package/dist/builtin/workflows/src/extension/config-loader.ts +1 -1
- package/dist/builtin/workflows/src/extension/dispatcher.ts +4 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1151 -737
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +303 -38
- package/dist/builtin/workflows/src/extension/render-result.ts +4 -0
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -3
- package/dist/builtin/workflows/src/extension/runtime.ts +7 -1
- package/dist/builtin/workflows/src/extension/wiring.ts +53 -11
- package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +12 -3
- package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +5 -2
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +9 -7
- package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +11 -0
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +18 -1
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +10 -8
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +9 -4
- package/dist/builtin/workflows/src/runs/background/quit.ts +28 -4
- package/dist/builtin/workflows/src/runs/background/resume-acknowledgements.ts +2 -1
- package/dist/builtin/workflows/src/runs/background/status.ts +55 -8
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +3 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +688 -38
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -1
- package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +19 -514
- package/dist/builtin/workflows/src/runs/shared/retry.ts +44 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +5 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +6 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +5 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +5 -0
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +5 -0
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +3 -0
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
- package/dist/builtin/workflows/src/shared/run-indicator-status.ts +75 -0
- package/dist/builtin/workflows/src/shared/store-public-types.ts +43 -7
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +42 -8
- package/dist/builtin/workflows/src/shared/store-stage-methods.ts +31 -8
- package/dist/builtin/workflows/src/shared/store-types.ts +34 -0
- package/dist/builtin/workflows/src/shared/timing.ts +10 -0
- package/dist/builtin/workflows/src/tui/chat-surface-message.ts +22 -2
- package/dist/builtin/workflows/src/tui/session-list.ts +1 -1
- package/dist/builtin/workflows/src/tui/session-overlays.ts +14 -10
- package/dist/builtin/workflows/src/tui/session-picker.ts +20 -4
- package/dist/builtin/workflows/src/tui/status-list.ts +31 -47
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +9 -1
- package/dist/builtin/workflows/src/tui/widget.ts +16 -46
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +54 -11
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +49 -14
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +13 -0
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js +12 -1
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +4 -0
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +21 -4
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +232 -106
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-state.d.ts.map +1 -1
- package/dist/core/agent-session-state.js +2 -1
- package/dist/core/agent-session-state.js.map +1 -1
- package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
- package/dist/core/agent-session-tool-hooks.js +12 -0
- package/dist/core/agent-session-tool-hooks.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +1 -1
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +3 -1
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +25 -0
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner-context.d.ts +2 -1
- package/dist/core/extensions/runner-context.d.ts.map +1 -1
- package/dist/core/extensions/runner-context.js +4 -0
- package/dist/core/extensions/runner-context.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/model-fallback-failures.d.ts +30 -0
- package/dist/core/model-fallback-failures.d.ts.map +1 -0
- package/dist/core/model-fallback-failures.js +610 -0
- package/dist/core/model-fallback-failures.js.map +1 -0
- package/dist/core/retry-policy.d.ts +30 -0
- package/dist/core/retry-policy.d.ts.map +1 -0
- package/dist/core/retry-policy.js +25 -0
- package/dist/core/retry-policy.js.map +1 -0
- package/dist/core/sdk-types.d.ts +8 -2
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +6 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +2 -0
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +7 -0
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/docs/intercom.md +5 -10
- package/docs/settings.md +7 -1
- package/docs/subagents.md +19 -19
- package/docs/workflows.md +21 -5
- package/examples/extensions/gondolin/package-lock.json +3 -3
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
- package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +0 -112
- package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +0 -543
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +0 -140
- package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +0 -279
- package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +0 -99
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +0 -17
- package/dist/builtin/subagents/src/runs/background/async-resume.ts +0 -402
- package/dist/builtin/subagents/src/runs/background/async-status.ts +0 -411
- package/dist/builtin/subagents/src/runs/background/completion-claims.ts +0 -196
- package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +0 -102
- package/dist/builtin/subagents/src/runs/background/parallel-groups.ts +0 -59
- package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +0 -300
- package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +0 -166
- package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +0 -77
- package/dist/builtin/subagents/src/runs/background/result-retry-scheduler.ts +0 -48
- package/dist/builtin/subagents/src/runs/background/result-status.ts +0 -87
- package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +0 -72
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +0 -320
- package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +0 -105
- package/dist/builtin/subagents/src/runs/background/run-status.ts +0 -358
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +0 -457
- package/dist/builtin/subagents/src/runs/background/subagent-runner-dynamic.ts +0 -376
- package/dist/builtin/subagents/src/runs/background/subagent-runner-finalize.ts +0 -150
- package/dist/builtin/subagents/src/runs/background/subagent-runner-output.ts +0 -109
- package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel-helpers.ts +0 -128
- package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel.ts +0 -309
- package/dist/builtin/subagents/src/runs/background/subagent-runner-sequential.ts +0 -173
- package/dist/builtin/subagents/src/runs/background/subagent-runner-state.ts +0 -578
- package/dist/builtin/subagents/src/runs/background/subagent-runner-step.ts +0 -330
- package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +0 -330
- package/dist/builtin/subagents/src/runs/background/subagent-runner-types.ts +0 -210
- package/dist/builtin/subagents/src/runs/background/subagent-runner-utils.ts +0 -69
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +0 -90
- package/dist/builtin/subagents/src/runs/background/top-level-async.ts +0 -12
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-control.ts +0 -140
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +0 -108
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-types.ts +0 -17
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +0 -558
- package/dist/builtin/subagents/src/runs/shared/attempt-watchdog.ts +0 -126
- package/dist/builtin/subagents/src/runs/shared/final-drain.ts +0 -39
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +0 -339
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +0 -165
- package/dist/builtin/subagents/src/runs/shared/spawn-env.ts +0 -21
- package/dist/builtin/subagents/src/shared/post-exit-stdio-guard.ts +0 -86
- /package/dist/builtin/subagents/src/runs/{shared/nested-path.ts → inprocess/runtime-support/nested-paths.ts} +0 -0
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export interface RetrySchedulerTimers {
|
|
2
|
-
setTimeout: typeof setTimeout;
|
|
3
|
-
clearTimeout: typeof clearTimeout;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface RetryScheduler {
|
|
7
|
-
has(key: string): boolean;
|
|
8
|
-
attempt(key: string): number;
|
|
9
|
-
schedule(key: string, callback: () => void): boolean;
|
|
10
|
-
clear(key: string): void;
|
|
11
|
-
clearAll(): void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function createRetryScheduler(
|
|
15
|
-
timers: RetrySchedulerTimers,
|
|
16
|
-
baseDelayMs: number,
|
|
17
|
-
maxDelayMs: number,
|
|
18
|
-
): RetryScheduler {
|
|
19
|
-
const pending = new Map<string, ReturnType<typeof setTimeout>>();
|
|
20
|
-
const attempts = new Map<string, number>();
|
|
21
|
-
const clear = (key: string) => {
|
|
22
|
-
const timer = pending.get(key);
|
|
23
|
-
if (timer) timers.clearTimeout(timer);
|
|
24
|
-
pending.delete(key);
|
|
25
|
-
attempts.delete(key);
|
|
26
|
-
};
|
|
27
|
-
return {
|
|
28
|
-
has: (key) => pending.has(key),
|
|
29
|
-
attempt: (key) => attempts.get(key) ?? 0,
|
|
30
|
-
schedule(key, callback) {
|
|
31
|
-
if (pending.has(key)) return false;
|
|
32
|
-
const attempt = (attempts.get(key) ?? 0) + 1;
|
|
33
|
-
attempts.set(key, attempt);
|
|
34
|
-
const delay = Math.min(maxDelayMs, baseDelayMs * 2 ** Math.min(attempt - 1, 30));
|
|
35
|
-
const timer = timers.setTimeout(() => {
|
|
36
|
-
pending.delete(key);
|
|
37
|
-
callback();
|
|
38
|
-
}, delay);
|
|
39
|
-
timer.unref?.();
|
|
40
|
-
pending.set(key, timer);
|
|
41
|
-
return true;
|
|
42
|
-
},
|
|
43
|
-
clear,
|
|
44
|
-
clearAll() {
|
|
45
|
-
for (const key of [...pending.keys()]) clear(key);
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { ASYNC_DIR } from "../../shared/types.ts";
|
|
4
|
-
import { NESTED_RUNS_DIR } from "../shared/nested-events.ts";
|
|
5
|
-
import type { ResultFileData } from "./result-watcher-data.js";
|
|
6
|
-
|
|
7
|
-
const TERMINAL_ASYNC_STATES = new Set(["complete", "failed", "paused"]);
|
|
8
|
-
export const DEFAULT_MAX_STATUS_BYTES = 1024 * 1024;
|
|
9
|
-
|
|
10
|
-
export interface ResultStatusFs {
|
|
11
|
-
realpathSync: typeof fs.realpathSync;
|
|
12
|
-
lstatSync: typeof fs.lstatSync;
|
|
13
|
-
openSync: typeof fs.openSync;
|
|
14
|
-
fstatSync: typeof fs.fstatSync;
|
|
15
|
-
readSync: typeof fs.readSync;
|
|
16
|
-
closeSync: typeof fs.closeSync;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ResultStatusOptions {
|
|
20
|
-
allowedRoots?: string[];
|
|
21
|
-
maxBytes?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function contained(root: string, candidate: string): boolean {
|
|
25
|
-
const relative = path.relative(root, candidate);
|
|
26
|
-
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function canonicalRoots(fsApi: ResultStatusFs, roots: string[]): string[] {
|
|
30
|
-
const resolved: string[] = [];
|
|
31
|
-
for (const root of roots) {
|
|
32
|
-
try {
|
|
33
|
-
resolved.push(fsApi.realpathSync(root));
|
|
34
|
-
} catch {
|
|
35
|
-
/* unavailable roots cannot authorize a status path */
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return resolved;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function readBoundedRegularFile(statusPath: string, fsApi: ResultStatusFs, maxBytes: number): string | undefined {
|
|
42
|
-
const linkStat = fsApi.lstatSync(statusPath);
|
|
43
|
-
if (linkStat.isSymbolicLink() || !linkStat.isFile() || linkStat.size > maxBytes) return undefined;
|
|
44
|
-
const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
45
|
-
const handle = fsApi.openSync(statusPath, fs.constants.O_RDONLY | noFollow);
|
|
46
|
-
try {
|
|
47
|
-
const stat = fsApi.fstatSync(handle);
|
|
48
|
-
if (!stat.isFile() || stat.size > maxBytes) return undefined;
|
|
49
|
-
const buffer = Buffer.alloc(stat.size);
|
|
50
|
-
let offset = 0;
|
|
51
|
-
while (offset < buffer.length) {
|
|
52
|
-
const count = fsApi.readSync(handle, buffer, offset, buffer.length - offset, offset);
|
|
53
|
-
if (count === 0) break;
|
|
54
|
-
offset += count;
|
|
55
|
-
}
|
|
56
|
-
return buffer.subarray(0, offset).toString("utf-8");
|
|
57
|
-
} finally {
|
|
58
|
-
fsApi.closeSync(handle);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function modernResultHasTerminalStatus(
|
|
63
|
-
data: ResultFileData,
|
|
64
|
-
fsApi: ResultStatusFs = fs,
|
|
65
|
-
options: ResultStatusOptions = {},
|
|
66
|
-
): boolean {
|
|
67
|
-
if (!Object.hasOwn(data, "asyncDir")) return true;
|
|
68
|
-
const asyncDir = data.asyncDir?.trim();
|
|
69
|
-
const resultRunId = data.runId?.trim() || data.id?.trim();
|
|
70
|
-
if (!asyncDir || !resultRunId) return false;
|
|
71
|
-
try {
|
|
72
|
-
const canonicalAsyncDir = fsApi.realpathSync(asyncDir);
|
|
73
|
-
const roots = canonicalRoots(fsApi, options.allowedRoots ?? [ASYNC_DIR, NESTED_RUNS_DIR]);
|
|
74
|
-
if (!roots.some((root) => contained(root, canonicalAsyncDir))) return false;
|
|
75
|
-
const statusPath = path.join(canonicalAsyncDir, "status.json");
|
|
76
|
-
const content = readBoundedRegularFile(statusPath, fsApi, options.maxBytes ?? DEFAULT_MAX_STATUS_BYTES);
|
|
77
|
-
if (content === undefined) return false;
|
|
78
|
-
const status = JSON.parse(content) as { state?: string; runId?: string };
|
|
79
|
-
return (
|
|
80
|
-
status.runId?.trim() === resultRunId &&
|
|
81
|
-
typeof status.state === "string" &&
|
|
82
|
-
TERMINAL_ASYNC_STATES.has(status.state)
|
|
83
|
-
);
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import type { NestedRunSummary } from "../../shared/types.ts";
|
|
3
|
-
import { sanitizeSummary } from "../shared/nested-events.ts";
|
|
4
|
-
|
|
5
|
-
export interface ResultFileChild {
|
|
6
|
-
agent?: string;
|
|
7
|
-
output?: string;
|
|
8
|
-
error?: string;
|
|
9
|
-
success?: boolean;
|
|
10
|
-
sessionFile?: string;
|
|
11
|
-
artifactPaths?: { outputPath?: string };
|
|
12
|
-
intercomTarget?: string;
|
|
13
|
-
children?: unknown;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ResultFileData {
|
|
17
|
-
id?: string;
|
|
18
|
-
runId?: string;
|
|
19
|
-
agent?: string;
|
|
20
|
-
success?: boolean;
|
|
21
|
-
state?: string;
|
|
22
|
-
mode?: string;
|
|
23
|
-
summary?: string;
|
|
24
|
-
results?: ResultFileChild[];
|
|
25
|
-
nestedChildren?: unknown;
|
|
26
|
-
sessionId?: string;
|
|
27
|
-
cwd?: string;
|
|
28
|
-
sessionFile?: string;
|
|
29
|
-
asyncDir?: string;
|
|
30
|
-
intercomTarget?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function stableStringify(value: unknown): string {
|
|
34
|
-
if (value === null || value === undefined || typeof value !== "object") {
|
|
35
|
-
const serialized = JSON.stringify(value);
|
|
36
|
-
return serialized === undefined ? "undefined" : serialized;
|
|
37
|
-
}
|
|
38
|
-
if (Array.isArray(value)) return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
39
|
-
const object = value as Record<string, unknown>;
|
|
40
|
-
return `{${Object.keys(object)
|
|
41
|
-
.sort()
|
|
42
|
-
.map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`)
|
|
43
|
-
.join(",")}}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Stable identity for every value exposed through local or Intercom completion delivery. */
|
|
47
|
-
export function buildCompletionSignature(delivery: Record<string, unknown>): string {
|
|
48
|
-
return createHash("sha256").update(stableStringify(delivery)).digest("hex");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function sanitizeNestedResultChildren(
|
|
52
|
-
value: unknown,
|
|
53
|
-
resultPath: string,
|
|
54
|
-
label: string,
|
|
55
|
-
): NestedRunSummary[] | undefined {
|
|
56
|
-
if (value === undefined) return undefined;
|
|
57
|
-
if (!Array.isArray(value)) {
|
|
58
|
-
console.error(
|
|
59
|
-
`Ignoring invalid nested children in subagent result file '${resultPath}' at ${label}: expected an array.`,
|
|
60
|
-
);
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
const children = value
|
|
64
|
-
.map((child) => sanitizeSummary(child))
|
|
65
|
-
.filter((child): child is NestedRunSummary => Boolean(child));
|
|
66
|
-
if (children.length !== value.length) {
|
|
67
|
-
console.error(
|
|
68
|
-
`Ignoring ${value.length - children.length} invalid nested child record(s) in subagent result file '${resultPath}' at ${label}.`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
return children.length ? children : undefined;
|
|
72
|
-
}
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { isSafeFsWatchPathError, watchWithErrorHandler } from "@bastani/atomic";
|
|
4
|
-
import { createFileCoalescer } from "../../shared/file-coalescer.js";
|
|
5
|
-
import type { IntercomEventBus, SubagentState } from "../../shared/types.ts";
|
|
6
|
-
import { processResultEntry, type ResultProcessorFs } from "./result-delivery-processor.js";
|
|
7
|
-
import { claimIdFromScheduleKey, claimScheduleKey, listResultClaims } from "./result-file-claims.js";
|
|
8
|
-
import { createRetryScheduler } from "./result-retry-scheduler.js";
|
|
9
|
-
import type { ResultFileData } from "./result-watcher-data.js";
|
|
10
|
-
|
|
11
|
-
const WATCHER_RESTART_DELAY_MS = 3000;
|
|
12
|
-
const POLL_INTERVAL_MS = 3000;
|
|
13
|
-
const DIRECTORY_RESCAN_DELAY_MS = 50;
|
|
14
|
-
const STATUS_RECHECK_BASE_MS = 250;
|
|
15
|
-
const STATUS_RECHECK_MAX_MS = 30_000;
|
|
16
|
-
const DELIVERY_RETRY_BASE_MS = 1000;
|
|
17
|
-
const DELIVERY_RETRY_MAX_MS = 30_000;
|
|
18
|
-
|
|
19
|
-
type ResultWatcherFs = ResultProcessorFs & Pick<typeof fs, "existsSync" | "readdirSync" | "mkdirSync" | "watch">;
|
|
20
|
-
type ResultWatcherTimers = {
|
|
21
|
-
setTimeout: typeof setTimeout;
|
|
22
|
-
clearTimeout: typeof clearTimeout;
|
|
23
|
-
setInterval: typeof setInterval;
|
|
24
|
-
clearInterval: typeof clearInterval;
|
|
25
|
-
};
|
|
26
|
-
type ResultWatcherSafeWatch = typeof watchWithErrorHandler;
|
|
27
|
-
|
|
28
|
-
type ResultWatcherDeps = {
|
|
29
|
-
fs?: ResultWatcherFs;
|
|
30
|
-
timers?: ResultWatcherTimers;
|
|
31
|
-
safeWatch?: ResultWatcherSafeWatch;
|
|
32
|
-
statusRecheckBaseMs?: number;
|
|
33
|
-
statusRecheckMaxMs?: number;
|
|
34
|
-
deliveryRetryBaseMs?: number;
|
|
35
|
-
deliveryRetryMaxMs?: number;
|
|
36
|
-
maxNoProgressFailures?: number;
|
|
37
|
-
intercomTimeoutMs?: number | false;
|
|
38
|
-
allowedStatusRoots?: string[];
|
|
39
|
-
maxStatusBytes?: number;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function getErrorCode(error: unknown): string | undefined {
|
|
43
|
-
return typeof error === "object" && error !== null && "code" in error
|
|
44
|
-
? (error as NodeJS.ErrnoException).code
|
|
45
|
-
: undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isNotFoundError(error: unknown): boolean {
|
|
49
|
-
return getErrorCode(error) === "ENOENT";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function shouldFallBackToPolling(error: unknown): boolean {
|
|
53
|
-
const code = getErrorCode(error);
|
|
54
|
-
return code === "EMFILE" || code === "ENOSPC" || isSafeFsWatchPathError(error);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function createResultWatcher(
|
|
58
|
-
pi: { events: IntercomEventBus },
|
|
59
|
-
state: SubagentState,
|
|
60
|
-
resultsDir: string,
|
|
61
|
-
completionTtlMs: number,
|
|
62
|
-
deps: ResultWatcherDeps = {},
|
|
63
|
-
): { startResultWatcher: () => void; primeExistingResults: () => void; stopResultWatcher: () => void } {
|
|
64
|
-
const fsApi = deps.fs ?? fs;
|
|
65
|
-
const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval };
|
|
66
|
-
const safeWatch = deps.safeWatch ?? watchWithErrorHandler;
|
|
67
|
-
let directoryRescanTimer: ReturnType<typeof setTimeout> | null = null;
|
|
68
|
-
let stopped = false;
|
|
69
|
-
let processingEpoch = 0;
|
|
70
|
-
let watcherInstallation = 0;
|
|
71
|
-
let activeWatcherInstallation = 0;
|
|
72
|
-
const inFlight = new Set<string>();
|
|
73
|
-
const rerunAfterFlight = new Set<string>();
|
|
74
|
-
const statusRetries = createRetryScheduler(
|
|
75
|
-
timers,
|
|
76
|
-
deps.statusRecheckBaseMs ?? STATUS_RECHECK_BASE_MS,
|
|
77
|
-
deps.statusRecheckMaxMs ?? STATUS_RECHECK_MAX_MS,
|
|
78
|
-
);
|
|
79
|
-
const deliveryRetries = createRetryScheduler(
|
|
80
|
-
timers,
|
|
81
|
-
deps.deliveryRetryBaseMs ?? DELIVERY_RETRY_BASE_MS,
|
|
82
|
-
deps.deliveryRetryMaxMs ?? DELIVERY_RETRY_MAX_MS,
|
|
83
|
-
);
|
|
84
|
-
const isEpochActive = (epoch: number): boolean => !stopped && epoch === processingEpoch;
|
|
85
|
-
const entryExists = (entry: string): boolean => {
|
|
86
|
-
const claimId = claimIdFromScheduleKey(entry);
|
|
87
|
-
return claimId
|
|
88
|
-
? Boolean(listResultClaims(resultsDir, fsApi).some((claim) => claim.id === claimId))
|
|
89
|
-
: fsApi.existsSync(path.join(resultsDir, entry));
|
|
90
|
-
};
|
|
91
|
-
const ownsResult = (data: ResultFileData): boolean =>
|
|
92
|
-
!stopped &&
|
|
93
|
-
(!data.sessionId || data.sessionId === state.currentSessionId) &&
|
|
94
|
-
Boolean(data.sessionId || !data.cwd || (state.baseCwd && data.cwd === state.baseCwd));
|
|
95
|
-
|
|
96
|
-
const coalescerKey = (entry: string, epoch: number): string => `${epoch}\0${entry}`;
|
|
97
|
-
const parseCoalescerKey = (key: string): { epoch: number; entry: string } => {
|
|
98
|
-
const split = key.indexOf("\0");
|
|
99
|
-
return { epoch: Number(key.slice(0, split)), entry: key.slice(split + 1) };
|
|
100
|
-
};
|
|
101
|
-
const scheduleResultEntry = (entry: string, delay = 0, epoch = processingEpoch): boolean => {
|
|
102
|
-
if (!isEpochActive(epoch) || statusRetries.has(entry) || deliveryRetries.has(entry)) return false;
|
|
103
|
-
return state.resultFileCoalescer.schedule(coalescerKey(entry, epoch), delay);
|
|
104
|
-
};
|
|
105
|
-
const scheduleStatusRecheck = (entry: string, epoch: number) => {
|
|
106
|
-
if (!isEpochActive(epoch)) return;
|
|
107
|
-
statusRetries.schedule(entry, () => {
|
|
108
|
-
if (!isEpochActive(epoch) || !entryExists(entry)) {
|
|
109
|
-
statusRetries.clear(entry);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
scheduleResultEntry(entry, 0, epoch);
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
const scheduleDeliveryRetry = (entry: string, epoch: number) => {
|
|
116
|
-
if (!isEpochActive(epoch)) return;
|
|
117
|
-
deliveryRetries.schedule(entry, () => {
|
|
118
|
-
if (!isEpochActive(epoch) || !entryExists(entry)) {
|
|
119
|
-
deliveryRetries.clear(entry);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
scheduleResultEntry(entry, 0, epoch);
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const handleResult = async (entry: string, epoch: number) => {
|
|
127
|
-
if (!isEpochActive(epoch)) return;
|
|
128
|
-
const flightKey = coalescerKey(entry, epoch);
|
|
129
|
-
if (inFlight.has(flightKey)) {
|
|
130
|
-
rerunAfterFlight.add(flightKey);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
inFlight.add(flightKey);
|
|
134
|
-
try {
|
|
135
|
-
const outcome = await processResultEntry(entry, {
|
|
136
|
-
pi,
|
|
137
|
-
state,
|
|
138
|
-
resultsDir,
|
|
139
|
-
completionTtlMs,
|
|
140
|
-
fsApi,
|
|
141
|
-
allowedStatusRoots: deps.allowedStatusRoots,
|
|
142
|
-
maxStatusBytes: deps.maxStatusBytes,
|
|
143
|
-
maxNoProgressFailures: deps.maxNoProgressFailures,
|
|
144
|
-
intercomTimeoutMs: deps.intercomTimeoutMs,
|
|
145
|
-
isActive: () => isEpochActive(epoch),
|
|
146
|
-
ownsResult: (data) => isEpochActive(epoch) && ownsResult(data),
|
|
147
|
-
});
|
|
148
|
-
const nextEntry = outcome.entry ?? entry;
|
|
149
|
-
if (outcome.status === "status-pending") scheduleStatusRecheck(nextEntry, epoch);
|
|
150
|
-
else if (outcome.status === "delivery-retry") scheduleDeliveryRetry(nextEntry, epoch);
|
|
151
|
-
else {
|
|
152
|
-
statusRetries.clear(entry);
|
|
153
|
-
deliveryRetries.clear(entry);
|
|
154
|
-
}
|
|
155
|
-
} catch (error) {
|
|
156
|
-
if (!isNotFoundError(error)) {
|
|
157
|
-
console.error(`Failed to process subagent result entry '${entry}':`, error);
|
|
158
|
-
if (isEpochActive(epoch) && entryExists(entry)) scheduleDeliveryRetry(entry, epoch);
|
|
159
|
-
}
|
|
160
|
-
} finally {
|
|
161
|
-
inFlight.delete(flightKey);
|
|
162
|
-
if (rerunAfterFlight.delete(flightKey) && entryExists(entry)) scheduleResultEntry(entry, 0, epoch);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
state.resultFileCoalescer = createFileCoalescer(
|
|
167
|
-
(key) => {
|
|
168
|
-
const { epoch, entry } = parseCoalescerKey(key);
|
|
169
|
-
void handleResult(entry, epoch);
|
|
170
|
-
},
|
|
171
|
-
50,
|
|
172
|
-
{ setTimeout: timers.setTimeout, clearTimeout: timers.clearTimeout },
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const primeExistingResults = () => {
|
|
176
|
-
if (stopped) return;
|
|
177
|
-
const epoch = processingEpoch;
|
|
178
|
-
try {
|
|
179
|
-
for (const file of fsApi.readdirSync(resultsDir))
|
|
180
|
-
if (file.endsWith(".json")) scheduleResultEntry(file, 0, epoch);
|
|
181
|
-
for (const claim of listResultClaims(resultsDir, fsApi))
|
|
182
|
-
scheduleResultEntry(claimScheduleKey(claim.id), 0, epoch);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
if (!isNotFoundError(error)) console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
const scheduleDirectoryRescan = (epoch: number) => {
|
|
188
|
-
if (!isEpochActive(epoch)) return;
|
|
189
|
-
if (directoryRescanTimer) timers.clearTimeout(directoryRescanTimer);
|
|
190
|
-
directoryRescanTimer = timers.setTimeout(() => {
|
|
191
|
-
directoryRescanTimer = null;
|
|
192
|
-
if (isEpochActive(epoch)) primeExistingResults();
|
|
193
|
-
}, DIRECTORY_RESCAN_DELAY_MS);
|
|
194
|
-
directoryRescanTimer.unref?.();
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const startPollingFallback = (reason: unknown, epoch: number, installation?: number) => {
|
|
198
|
-
if (!isEpochActive(epoch) || (installation !== undefined && installation !== activeWatcherInstallation)) return;
|
|
199
|
-
state.watcher?.close();
|
|
200
|
-
state.watcher = null;
|
|
201
|
-
activeWatcherInstallation = 0;
|
|
202
|
-
if (state.watcherRestartTimer) return;
|
|
203
|
-
console.error(
|
|
204
|
-
`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`,
|
|
205
|
-
);
|
|
206
|
-
primeExistingResults();
|
|
207
|
-
state.watcherRestartTimer = timers.setInterval(() => {
|
|
208
|
-
if (isEpochActive(epoch)) primeExistingResults();
|
|
209
|
-
}, POLL_INTERVAL_MS);
|
|
210
|
-
state.watcherRestartTimer.unref?.();
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
function scheduleRestart(epoch: number): void {
|
|
214
|
-
if (!isEpochActive(epoch) || state.watcherRestartTimer) return;
|
|
215
|
-
let timer!: ReturnType<typeof setTimeout>;
|
|
216
|
-
timer = timers.setTimeout(() => {
|
|
217
|
-
if (state.watcherRestartTimer !== timer || !isEpochActive(epoch)) return;
|
|
218
|
-
state.watcherRestartTimer = null;
|
|
219
|
-
try {
|
|
220
|
-
fsApi.mkdirSync(resultsDir, { recursive: true });
|
|
221
|
-
openResultWatcher(epoch);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
if (!isEpochActive(epoch)) return;
|
|
224
|
-
if (shouldFallBackToPolling(error)) startPollingFallback(error, epoch);
|
|
225
|
-
else {
|
|
226
|
-
console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error);
|
|
227
|
-
scheduleRestart(epoch);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}, WATCHER_RESTART_DELAY_MS);
|
|
231
|
-
state.watcherRestartTimer = timer;
|
|
232
|
-
timer.unref?.();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function openResultWatcher(epoch: number): void {
|
|
236
|
-
if (!isEpochActive(epoch) || state.watcher) return;
|
|
237
|
-
const installation = ++watcherInstallation;
|
|
238
|
-
let handle: fs.FSWatcher | null = null;
|
|
239
|
-
let pendingSynchronousError: Error | undefined;
|
|
240
|
-
const handleWatcherError = (error: Error) => {
|
|
241
|
-
if (!handle) {
|
|
242
|
-
pendingSynchronousError = error;
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (!isEpochActive(epoch) || installation !== activeWatcherInstallation || state.watcher !== handle) return;
|
|
246
|
-
if (shouldFallBackToPolling(error)) {
|
|
247
|
-
startPollingFallback(error, epoch, installation);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
|
|
251
|
-
handle.close();
|
|
252
|
-
if (state.watcher === handle) state.watcher = null;
|
|
253
|
-
activeWatcherInstallation = 0;
|
|
254
|
-
scheduleRestart(epoch);
|
|
255
|
-
};
|
|
256
|
-
try {
|
|
257
|
-
handle = safeWatch(
|
|
258
|
-
resultsDir,
|
|
259
|
-
(_event, file) => {
|
|
260
|
-
if (!isEpochActive(epoch) || installation !== activeWatcherInstallation) return;
|
|
261
|
-
if (file) {
|
|
262
|
-
const name = file.toString();
|
|
263
|
-
if (name.endsWith(".json")) scheduleResultEntry(name, 50, epoch);
|
|
264
|
-
}
|
|
265
|
-
scheduleDirectoryRescan(epoch);
|
|
266
|
-
},
|
|
267
|
-
handleWatcherError,
|
|
268
|
-
{ watch: fsApi.watch, realpathSyncNative: fsApi.realpathSync?.native },
|
|
269
|
-
);
|
|
270
|
-
if (!handle) throw pendingSynchronousError ?? new Error("Result watcher installation returned no handle");
|
|
271
|
-
if (!isEpochActive(epoch)) {
|
|
272
|
-
handle.close();
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
state.watcher = handle;
|
|
276
|
-
activeWatcherInstallation = installation;
|
|
277
|
-
handle.unref?.();
|
|
278
|
-
if (pendingSynchronousError) handleWatcherError(pendingSynchronousError);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
handle?.close();
|
|
281
|
-
if (state.watcher === handle) state.watcher = null;
|
|
282
|
-
activeWatcherInstallation = 0;
|
|
283
|
-
if (!isEpochActive(epoch)) return;
|
|
284
|
-
if (shouldFallBackToPolling(error)) startPollingFallback(error, epoch);
|
|
285
|
-
else {
|
|
286
|
-
console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error);
|
|
287
|
-
scheduleRestart(epoch);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const startResultWatcher = () => {
|
|
293
|
-
stopped = false;
|
|
294
|
-
processingEpoch += 1;
|
|
295
|
-
if (state.watcherRestartTimer) {
|
|
296
|
-
timers.clearTimeout(state.watcherRestartTimer);
|
|
297
|
-
timers.clearInterval(state.watcherRestartTimer);
|
|
298
|
-
state.watcherRestartTimer = null;
|
|
299
|
-
}
|
|
300
|
-
openResultWatcher(processingEpoch);
|
|
301
|
-
};
|
|
302
|
-
const stopResultWatcher = () => {
|
|
303
|
-
stopped = true;
|
|
304
|
-
processingEpoch += 1;
|
|
305
|
-
state.watcher?.close();
|
|
306
|
-
state.watcher = null;
|
|
307
|
-
activeWatcherInstallation = 0;
|
|
308
|
-
if (state.watcherRestartTimer) {
|
|
309
|
-
timers.clearTimeout(state.watcherRestartTimer);
|
|
310
|
-
timers.clearInterval(state.watcherRestartTimer);
|
|
311
|
-
}
|
|
312
|
-
state.watcherRestartTimer = null;
|
|
313
|
-
if (directoryRescanTimer) timers.clearTimeout(directoryRescanTimer);
|
|
314
|
-
directoryRescanTimer = null;
|
|
315
|
-
statusRetries.clearAll();
|
|
316
|
-
deliveryRetries.clearAll();
|
|
317
|
-
state.resultFileCoalescer.clear();
|
|
318
|
-
};
|
|
319
|
-
return { startResultWatcher, primeExistingResults, stopResultWatcher };
|
|
320
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { ASYNC_DIR, RESULTS_DIR, type SubagentState } from "../../shared/types.ts";
|
|
4
|
-
import {
|
|
5
|
-
assertSafeNestedId,
|
|
6
|
-
findNestedRunMatchesById,
|
|
7
|
-
type NestedRoute,
|
|
8
|
-
type NestedRunMatch,
|
|
9
|
-
type NestedRunResolutionScope,
|
|
10
|
-
} from "../shared/nested-events.ts";
|
|
11
|
-
import { type AsyncRunLocation, findAsyncRunPrefixMatches } from "./async-resume.ts";
|
|
12
|
-
|
|
13
|
-
export type ResolvedSubagentRunId =
|
|
14
|
-
| { kind: "foreground"; id: string }
|
|
15
|
-
| { kind: "async"; id: string; location: AsyncRunLocation }
|
|
16
|
-
| { kind: "nested"; id: string; match: NestedRunMatch };
|
|
17
|
-
|
|
18
|
-
export interface ResolveSubagentRunIdDeps {
|
|
19
|
-
state?: SubagentState;
|
|
20
|
-
asyncDirRoot?: string;
|
|
21
|
-
resultsDir?: string;
|
|
22
|
-
nested?: NestedRunResolutionScope;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function exactAsyncLocation(id: string, asyncDirRoot: string, resultsDir: string): AsyncRunLocation | undefined {
|
|
26
|
-
const asyncDir = path.join(asyncDirRoot, id);
|
|
27
|
-
const resultPath = path.join(resultsDir, `${id}.json`);
|
|
28
|
-
if (!fs.existsSync(asyncDir) && !fs.existsSync(resultPath)) return undefined;
|
|
29
|
-
return {
|
|
30
|
-
asyncDir: fs.existsSync(asyncDir) ? asyncDir : null,
|
|
31
|
-
resultPath: fs.existsSync(resultPath) ? resultPath : null,
|
|
32
|
-
resolvedId: id,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function foregroundIds(state: SubagentState | undefined): string[] {
|
|
37
|
-
if (!state) return [];
|
|
38
|
-
return [...new Set([...state.foregroundControls.keys(), ...(state.foregroundRuns?.keys() ?? [])])];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolutionScope | undefined {
|
|
42
|
-
if (!state) return undefined;
|
|
43
|
-
const routes: NestedRoute[] = [];
|
|
44
|
-
const seen = new Set<string>();
|
|
45
|
-
const add = (route: NestedRoute | undefined) => {
|
|
46
|
-
if (!route) return;
|
|
47
|
-
const key = `${route.rootRunId}:${route.eventSink}:${route.controlInbox}`;
|
|
48
|
-
if (seen.has(key)) return;
|
|
49
|
-
seen.add(key);
|
|
50
|
-
routes.push(route);
|
|
51
|
-
};
|
|
52
|
-
for (const control of state.foregroundControls.values()) add(control.nestedRoute as NestedRoute | undefined);
|
|
53
|
-
for (const job of state.asyncJobs.values()) add(job.nestedRoute as NestedRoute | undefined);
|
|
54
|
-
return { routes };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function asyncPrefixMatches(
|
|
58
|
-
prefix: string,
|
|
59
|
-
asyncDirRoot: string,
|
|
60
|
-
resultsDir: string,
|
|
61
|
-
): Array<{ id: string; location: AsyncRunLocation }> {
|
|
62
|
-
return findAsyncRunPrefixMatches(prefix, asyncDirRoot, resultsDir);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function resolveSubagentRunId(
|
|
66
|
-
id: string,
|
|
67
|
-
deps: ResolveSubagentRunIdDeps = {},
|
|
68
|
-
): ResolvedSubagentRunId | undefined {
|
|
69
|
-
assertSafeNestedId("id", id);
|
|
70
|
-
const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
|
|
71
|
-
const resultsDir = deps.resultsDir ?? RESULTS_DIR;
|
|
72
|
-
|
|
73
|
-
const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
|
|
74
|
-
if (deps.state?.foregroundControls.has(id) || deps.state?.foregroundRuns?.has(id)) return { kind: "foreground", id };
|
|
75
|
-
const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
|
|
76
|
-
if (exactAsync) return { kind: "async", id, location: exactAsync };
|
|
77
|
-
const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
|
|
78
|
-
if (exactNested.length > 1)
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Nested run id '${id}' is ambiguous across authorized registries. Provide a more specific authorized run id.`,
|
|
81
|
-
);
|
|
82
|
-
if (exactNested[0]) return { kind: "nested", id, match: exactNested[0] };
|
|
83
|
-
|
|
84
|
-
const matches: ResolvedSubagentRunId[] = [];
|
|
85
|
-
for (const foregroundId of foregroundIds(deps.state).filter((candidate) => candidate.startsWith(id))) {
|
|
86
|
-
matches.push({ kind: "foreground", id: foregroundId });
|
|
87
|
-
}
|
|
88
|
-
for (const match of asyncPrefixMatches(id, asyncDirRoot, resultsDir)) {
|
|
89
|
-
matches.push({ kind: "async", id: match.id, location: match.location });
|
|
90
|
-
}
|
|
91
|
-
for (const match of findNestedRunMatchesById(
|
|
92
|
-
id,
|
|
93
|
-
nestedScope ? { prefix: true, scope: nestedScope } : { prefix: true },
|
|
94
|
-
)) {
|
|
95
|
-
matches.push({ kind: "nested", id: match.run.id, match });
|
|
96
|
-
}
|
|
97
|
-
const unique = new Map(matches.map((match) => [`${match.kind}:${match.id}`, match]));
|
|
98
|
-
const values = [...unique.values()];
|
|
99
|
-
if (values.length > 1) {
|
|
100
|
-
throw new Error(
|
|
101
|
-
`Ambiguous subagent run id prefix '${id}' matched: ${values.map((match) => `${match.kind}:${match.id}`).join(", ")}. Provide a longer id.`,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return values[0];
|
|
105
|
-
}
|