@selesai/code 0.8.5 → 0.8.7
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 +28 -0
- package/dist/core/agent-session.js +13 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/system-prompt.js +2 -8
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -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/defaults/models.json +5 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/agent-browser.test.ts +178 -2
- package/dist/extensions/agent-browser.ts +2 -0
- package/dist/extensions/copy-turn.test.ts +229 -0
- package/dist/extensions/copy-turn.ts +11 -0
- package/dist/extensions/grep-app/index.test.ts +356 -1
- package/dist/extensions/grep-app/index.ts +6 -1
- package/dist/extensions/handoff-new.test.ts +351 -161
- package/dist/extensions/handoff-new.ts +3 -0
- package/dist/extensions/inline-skills.test.ts +93 -0
- package/dist/extensions/inline-skills.ts +3 -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/builder.md +5 -2
- package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
- 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 -11
- 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 +4 -4
- 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 +16 -15
- 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 +59 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +146 -23
- 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 +17 -6
- 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 +11 -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/notify.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +224 -42
- 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 +35 -26
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +108 -9
- 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/background/wait-subscriptions.ts +2 -1
- 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/foreground-history.ts +7 -4
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +368 -50
- 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/session-lineage.ts +71 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +66 -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 +129 -17
- 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/src/workflows/workflow-auto-relaunch.ts +28 -0
- 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 +369 -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/handoff-adoption.test.ts +103 -0
- 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 +91 -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/session-lineage.test.ts +73 -0
- 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-auto-relaunch.test.ts +45 -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/ponytail/index.js +11 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
- package/dist/extensions/ponytail/test/extension.test.js +274 -140
- package/dist/extensions/ponytail/test/helpers.test.js +280 -92
- package/dist/extensions/question/index.ts +33 -0
- package/dist/extensions/question/question-list.ts +28 -0
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/batch.test.ts +103 -65
- 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 +75 -35
- package/dist/extensions/question/tests/question-list.test.ts +640 -198
- package/dist/extensions/question/tests/row-layout.test.ts +193 -114
- package/dist/extensions/question/tests/schemas.test.ts +42 -19
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
- package/dist/extensions/question/tests/wizard.test.ts +771 -50
- 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/rtk.test.ts +180 -1
- package/dist/extensions/tokenin-onboarding.ts +3 -0
- package/dist/extensions/undo.test.ts +720 -0
- package/dist/extensions/undo.ts +6 -0
- package/dist/extensions/web-agent-onboarding.test.ts +415 -0
- package/dist/extensions/workflow/extension.ts +3 -3
- package/dist/extensions/workflow/modes.ts +41 -19
- 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 +16 -15
- 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/docs/plans/workflow-autoloop-reference.md +1 -2
- package/docs/plans/workflow-handoff-carryover.md +108 -0
- package/docs/settings.md +6 -0
- package/package.json +4 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { sanitizeDisplayText, truncateDisplayText } from "../../shared/display-text.ts";
|
|
2
|
+
import type { AsyncJobState, AsyncJobStep, NestedRunSummary, NestedStepSummary, SubagentRunMode, SubagentState } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export const ASYNC_STATUS_SNAPSHOT_KIND = "pi-subagents.async-status-snapshot";
|
|
5
|
+
export const ASYNC_STATUS_SNAPSHOT_VERSION = 1;
|
|
6
|
+
export const ASYNC_STATUS_SNAPSHOT_WIDGET_PREFIX = "SELESAI_SUBAGENT_ASYNC_JSON:";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_RUNS = 20;
|
|
9
|
+
const DEFAULT_MAX_CHILDREN_PER_NODE = 8;
|
|
10
|
+
const DEFAULT_MAX_DEPTH = 3;
|
|
11
|
+
const DEFAULT_MAX_STRING_LENGTH = 160;
|
|
12
|
+
const DEFAULT_MAX_SERIALIZED_BYTES = 32 * 1024;
|
|
13
|
+
|
|
14
|
+
export type AsyncStatusSnapshotState = "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
|
|
15
|
+
export type AsyncStatusSnapshotKind = "subagent" | "workflow" | "step";
|
|
16
|
+
|
|
17
|
+
export interface AsyncStatusSnapshotActivityV1 {
|
|
18
|
+
state?: string;
|
|
19
|
+
currentTool?: string;
|
|
20
|
+
lastActivityAt?: number;
|
|
21
|
+
currentToolStartedAt?: number;
|
|
22
|
+
turnCount?: number;
|
|
23
|
+
toolCount?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AsyncStatusSnapshotNodeV1 {
|
|
27
|
+
id: string;
|
|
28
|
+
kind: AsyncStatusSnapshotKind;
|
|
29
|
+
label: string;
|
|
30
|
+
state: AsyncStatusSnapshotState;
|
|
31
|
+
startedAt?: number;
|
|
32
|
+
updatedAt?: number;
|
|
33
|
+
endedAt?: number;
|
|
34
|
+
activity?: AsyncStatusSnapshotActivityV1;
|
|
35
|
+
children?: AsyncStatusSnapshotNodeV1[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AsyncStatusSnapshotCapsV1 {
|
|
39
|
+
maxRuns: number;
|
|
40
|
+
maxChildrenPerNode: number;
|
|
41
|
+
maxDepth: number;
|
|
42
|
+
maxStringLength: number;
|
|
43
|
+
maxSerializedBytes: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AsyncStatusSnapshotOmittedV1 {
|
|
47
|
+
runs: number;
|
|
48
|
+
children: number;
|
|
49
|
+
byteLimitExceeded: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AsyncStatusSnapshotV1 {
|
|
53
|
+
kind: typeof ASYNC_STATUS_SNAPSHOT_KIND;
|
|
54
|
+
version: typeof ASYNC_STATUS_SNAPSHOT_VERSION;
|
|
55
|
+
generatedAt: number;
|
|
56
|
+
caps: AsyncStatusSnapshotCapsV1;
|
|
57
|
+
omitted: AsyncStatusSnapshotOmittedV1;
|
|
58
|
+
runs: AsyncStatusSnapshotNodeV1[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AsyncStatusSnapshotOptions {
|
|
62
|
+
generatedAt?: number;
|
|
63
|
+
maxRuns?: number;
|
|
64
|
+
maxChildrenPerNode?: number;
|
|
65
|
+
maxDepth?: number;
|
|
66
|
+
maxStringLength?: number;
|
|
67
|
+
maxSerializedBytes?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface BuildContext {
|
|
71
|
+
caps: AsyncStatusSnapshotCapsV1;
|
|
72
|
+
omitted: AsyncStatusSnapshotOmittedV1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveCaps(options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshotCapsV1 {
|
|
76
|
+
return {
|
|
77
|
+
maxRuns: Math.max(0, Math.floor(options.maxRuns ?? DEFAULT_MAX_RUNS)),
|
|
78
|
+
maxChildrenPerNode: Math.max(0, Math.floor(options.maxChildrenPerNode ?? DEFAULT_MAX_CHILDREN_PER_NODE)),
|
|
79
|
+
maxDepth: Math.max(0, Math.floor(options.maxDepth ?? DEFAULT_MAX_DEPTH)),
|
|
80
|
+
maxStringLength: Math.max(0, Math.floor(options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH)),
|
|
81
|
+
maxSerializedBytes: Math.max(256, Math.floor(options.maxSerializedBytes ?? DEFAULT_MAX_SERIALIZED_BYTES)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function publicText(value: unknown, fallback: string, maxLength: number): string {
|
|
86
|
+
if (typeof value !== "string") return fallback;
|
|
87
|
+
const normalized = sanitizeDisplayText(value.slice(0, Math.max(0, maxLength * 4)));
|
|
88
|
+
return truncateDisplayText(normalized || fallback, maxLength);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function publicOptionalText(value: unknown, maxLength: number): string | undefined {
|
|
92
|
+
if (typeof value !== "string") return undefined;
|
|
93
|
+
const normalized = sanitizeDisplayText(value.slice(0, Math.max(0, maxLength * 4)));
|
|
94
|
+
return normalized ? truncateDisplayText(normalized, maxLength) : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function publicTime(value: unknown): number | undefined {
|
|
98
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function publicCount(value: unknown): number | undefined {
|
|
102
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeState(value: string): AsyncStatusSnapshotState {
|
|
106
|
+
if (value === "completed") return "complete";
|
|
107
|
+
if (value === "pending") return "queued";
|
|
108
|
+
return value as AsyncStatusSnapshotState;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function terminalState(state: AsyncStatusSnapshotState): boolean {
|
|
112
|
+
return state === "complete" || state === "failed" || state === "paused" || state === "stopped" || state === "rejected";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function kindForMode(mode: SubagentRunMode | undefined): AsyncStatusSnapshotKind {
|
|
116
|
+
return mode === "workflow" ? "workflow" : "subagent";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function labelForAgents(agents: readonly string[] | undefined, fallback: string, maxLength: number): string {
|
|
120
|
+
if (!agents?.length) return publicText(fallback, fallback, maxLength);
|
|
121
|
+
const visible = agents.slice(0, 3).map((agent) => publicText(agent, "agent", maxLength)).join(", ");
|
|
122
|
+
const suffix = agents.length > 3 ? `, +${agents.length - 3} more` : "";
|
|
123
|
+
return truncateDisplayText(`${visible}${suffix}`, maxLength);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function activityFor(source: {
|
|
127
|
+
activityState?: unknown;
|
|
128
|
+
currentTool?: unknown;
|
|
129
|
+
lastActivityAt?: unknown;
|
|
130
|
+
currentToolStartedAt?: unknown;
|
|
131
|
+
turnCount?: unknown;
|
|
132
|
+
toolCount?: unknown;
|
|
133
|
+
}, ctx: BuildContext): AsyncStatusSnapshotActivityV1 | undefined {
|
|
134
|
+
const activity: AsyncStatusSnapshotActivityV1 = {
|
|
135
|
+
...(typeof source.activityState === "string" ? { state: publicText(source.activityState, "unknown", ctx.caps.maxStringLength) } : {}),
|
|
136
|
+
...(publicOptionalText(source.currentTool, ctx.caps.maxStringLength) ? { currentTool: publicOptionalText(source.currentTool, ctx.caps.maxStringLength) } : {}),
|
|
137
|
+
...(publicTime(source.lastActivityAt) !== undefined ? { lastActivityAt: publicTime(source.lastActivityAt) } : {}),
|
|
138
|
+
...(publicTime(source.currentToolStartedAt) !== undefined ? { currentToolStartedAt: publicTime(source.currentToolStartedAt) } : {}),
|
|
139
|
+
...(publicCount(source.turnCount) !== undefined ? { turnCount: publicCount(source.turnCount) } : {}),
|
|
140
|
+
...(publicCount(source.toolCount) !== undefined ? { toolCount: publicCount(source.toolCount) } : {}),
|
|
141
|
+
};
|
|
142
|
+
return Object.keys(activity).length ? activity : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function appendBoundedChildren(children: AsyncStatusSnapshotNodeV1[], source: readonly AsyncStatusSnapshotNodeV1[], ctx: BuildContext): void {
|
|
146
|
+
const remaining = Math.max(0, ctx.caps.maxChildrenPerNode - children.length);
|
|
147
|
+
children.push(...source.slice(0, remaining));
|
|
148
|
+
ctx.omitted.children += Math.max(0, source.length - remaining);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildStepNode(step: AsyncJobStep | NestedStepSummary, index: number, depth: number, ctx: BuildContext): AsyncStatusSnapshotNodeV1 {
|
|
152
|
+
const state = normalizeState(step.status);
|
|
153
|
+
const updatedAt = publicTime(step.endedAt) ?? publicTime(step.lastActivityAt) ?? publicTime(step.startedAt);
|
|
154
|
+
const node: AsyncStatusSnapshotNodeV1 = {
|
|
155
|
+
id: publicText("workflowKey" in step && step.workflowKey ? step.workflowKey : "runId" in step && step.runId ? step.runId : `step:${index}`, `step:${index}`, ctx.caps.maxStringLength),
|
|
156
|
+
kind: "step",
|
|
157
|
+
label: publicText("label" in step && step.label ? step.label : step.agent, "step", ctx.caps.maxStringLength),
|
|
158
|
+
state,
|
|
159
|
+
...(publicTime(step.startedAt) !== undefined ? { startedAt: publicTime(step.startedAt) } : {}),
|
|
160
|
+
...(updatedAt !== undefined ? { updatedAt } : {}),
|
|
161
|
+
...(terminalState(state) && publicTime(step.endedAt) !== undefined ? { endedAt: publicTime(step.endedAt) } : {}),
|
|
162
|
+
...(activityFor(step, ctx) ? { activity: activityFor(step, ctx) } : {}),
|
|
163
|
+
};
|
|
164
|
+
if (depth < ctx.caps.maxDepth && step.children?.length) {
|
|
165
|
+
const nested = step.children.map((child, childIndex) => buildNestedNode(child, childIndex, depth + 1, ctx));
|
|
166
|
+
const bounded: AsyncStatusSnapshotNodeV1[] = [];
|
|
167
|
+
appendBoundedChildren(bounded, nested, ctx);
|
|
168
|
+
if (bounded.length) node.children = bounded;
|
|
169
|
+
} else if (step.children?.length) {
|
|
170
|
+
ctx.omitted.children += step.children.length;
|
|
171
|
+
}
|
|
172
|
+
return node;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function buildNestedNode(child: NestedRunSummary, index: number, depth: number, ctx: BuildContext): AsyncStatusSnapshotNodeV1 {
|
|
176
|
+
const state = normalizeState(child.state);
|
|
177
|
+
const updatedAt = publicTime(child.lastUpdate) ?? publicTime(child.endedAt) ?? publicTime(child.lastActivityAt) ?? publicTime(child.startedAt);
|
|
178
|
+
const node: AsyncStatusSnapshotNodeV1 = {
|
|
179
|
+
id: publicText(child.id, `nested:${index}`, ctx.caps.maxStringLength),
|
|
180
|
+
kind: kindForMode(child.mode),
|
|
181
|
+
label: child.agent ? publicText(child.agent, "subagent", ctx.caps.maxStringLength) : labelForAgents(child.agents, child.mode ?? "subagent", ctx.caps.maxStringLength),
|
|
182
|
+
state,
|
|
183
|
+
...(publicTime(child.startedAt) !== undefined ? { startedAt: publicTime(child.startedAt) } : {}),
|
|
184
|
+
...(updatedAt !== undefined ? { updatedAt } : {}),
|
|
185
|
+
...(terminalState(state) && publicTime(child.endedAt) !== undefined ? { endedAt: publicTime(child.endedAt) } : {}),
|
|
186
|
+
...(activityFor(child, ctx) ? { activity: activityFor(child, ctx) } : {}),
|
|
187
|
+
};
|
|
188
|
+
if (depth < ctx.caps.maxDepth) {
|
|
189
|
+
const nestedSteps = child.steps?.map((step, stepIndex) => buildStepNode(step, stepIndex, depth + 1, ctx)) ?? [];
|
|
190
|
+
const nestedChildren = child.children?.map((nested, childIndex) => buildNestedNode(nested, childIndex, depth + 1, ctx)) ?? [];
|
|
191
|
+
const bounded: AsyncStatusSnapshotNodeV1[] = [];
|
|
192
|
+
appendBoundedChildren(bounded, [...nestedSteps, ...nestedChildren], ctx);
|
|
193
|
+
if (bounded.length) node.children = bounded;
|
|
194
|
+
} else {
|
|
195
|
+
ctx.omitted.children += (child.steps?.length ?? 0) + (child.children?.length ?? 0);
|
|
196
|
+
}
|
|
197
|
+
return node;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildRunNode(job: AsyncJobState, ctx: BuildContext): AsyncStatusSnapshotNodeV1 {
|
|
201
|
+
const state = normalizeState(job.status);
|
|
202
|
+
const updatedAt = publicTime(job.updatedAt) ?? publicTime(job.startedAt);
|
|
203
|
+
const node: AsyncStatusSnapshotNodeV1 = {
|
|
204
|
+
id: publicText(job.asyncId, "async", ctx.caps.maxStringLength),
|
|
205
|
+
kind: kindForMode(job.mode),
|
|
206
|
+
label: labelForAgents(job.agents, job.mode ?? "subagent", ctx.caps.maxStringLength),
|
|
207
|
+
state,
|
|
208
|
+
...(publicTime(job.startedAt) !== undefined ? { startedAt: publicTime(job.startedAt) } : {}),
|
|
209
|
+
...(updatedAt !== undefined ? { updatedAt } : {}),
|
|
210
|
+
...(terminalState(state) && updatedAt !== undefined ? { endedAt: updatedAt } : {}),
|
|
211
|
+
...(activityFor(job, ctx) ? { activity: activityFor(job, ctx) } : {}),
|
|
212
|
+
};
|
|
213
|
+
if (ctx.caps.maxDepth > 0) {
|
|
214
|
+
const stepChildren = job.steps?.map((step, index) => buildStepNode(step, step.index ?? index, 1, ctx)) ?? [];
|
|
215
|
+
const nestedChildren = job.nestedChildren?.map((child, index) => buildNestedNode(child, index, 1, ctx)) ?? [];
|
|
216
|
+
const bounded: AsyncStatusSnapshotNodeV1[] = [];
|
|
217
|
+
appendBoundedChildren(bounded, [...stepChildren, ...nestedChildren], ctx);
|
|
218
|
+
if (bounded.length) node.children = bounded;
|
|
219
|
+
} else {
|
|
220
|
+
ctx.omitted.children += (job.steps?.length ?? 0) + (job.nestedChildren?.length ?? 0);
|
|
221
|
+
}
|
|
222
|
+
return node;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function snapshotBytes(snapshot: AsyncStatusSnapshotV1): number {
|
|
226
|
+
return Buffer.byteLength(JSON.stringify(snapshot), "utf8");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function enforceByteLimit(snapshot: AsyncStatusSnapshotV1): void {
|
|
230
|
+
while (snapshot.runs.length > 0 && snapshotBytes(snapshot) > snapshot.caps.maxSerializedBytes) {
|
|
231
|
+
snapshot.runs.pop();
|
|
232
|
+
snapshot.omitted.runs += 1;
|
|
233
|
+
snapshot.omitted.byteLimitExceeded = true;
|
|
234
|
+
}
|
|
235
|
+
if (snapshotBytes(snapshot) > snapshot.caps.maxSerializedBytes) snapshot.omitted.byteLimitExceeded = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function buildAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshotV1 {
|
|
239
|
+
const caps = resolveCaps(options);
|
|
240
|
+
const ctx: BuildContext = { caps, omitted: { runs: 0, children: 0, byteLimitExceeded: false } };
|
|
241
|
+
const sorted = [...jobs].sort((left, right) => {
|
|
242
|
+
const leftUpdated = left.updatedAt ?? left.startedAt ?? 0;
|
|
243
|
+
const rightUpdated = right.updatedAt ?? right.startedAt ?? 0;
|
|
244
|
+
return rightUpdated - leftUpdated || left.asyncId.localeCompare(right.asyncId);
|
|
245
|
+
});
|
|
246
|
+
ctx.omitted.runs += Math.max(0, sorted.length - caps.maxRuns);
|
|
247
|
+
const snapshot: AsyncStatusSnapshotV1 = {
|
|
248
|
+
kind: ASYNC_STATUS_SNAPSHOT_KIND,
|
|
249
|
+
version: ASYNC_STATUS_SNAPSHOT_VERSION,
|
|
250
|
+
generatedAt: options.generatedAt ?? Date.now(),
|
|
251
|
+
caps,
|
|
252
|
+
omitted: ctx.omitted,
|
|
253
|
+
runs: sorted.slice(0, caps.maxRuns).map((job) => buildRunNode(job, ctx)),
|
|
254
|
+
};
|
|
255
|
+
enforceByteLimit(snapshot);
|
|
256
|
+
return snapshot;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function asyncStatusSnapshotJobsForState(state: SubagentState | undefined, sessionId: string | null | undefined): AsyncJobState[] {
|
|
260
|
+
if (!state || !sessionId || state.currentSessionId !== sessionId) return [];
|
|
261
|
+
const jobs = new Map<string, AsyncJobState>();
|
|
262
|
+
for (const job of state.asyncJobs.values()) {
|
|
263
|
+
if (job.sessionId === sessionId) jobs.set(job.asyncId, job);
|
|
264
|
+
}
|
|
265
|
+
for (const job of state.fleetJobs?.values() ?? []) {
|
|
266
|
+
if (job.sessionId === sessionId && !jobs.has(job.asyncId)) jobs.set(job.asyncId, job);
|
|
267
|
+
}
|
|
268
|
+
return [...jobs.values()];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function buildAsyncStatusSnapshotForState(state: SubagentState | undefined, sessionId: string | null | undefined, options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshotV1 {
|
|
272
|
+
return buildAsyncStatusSnapshot(asyncStatusSnapshotJobsForState(state, sessionId), options);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function encodeAsyncStatusSnapshotWidget(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}): string[] {
|
|
276
|
+
return [`${ASYNC_STATUS_SNAPSHOT_WIDGET_PREFIX}${JSON.stringify(buildAsyncStatusSnapshot(jobs, options))}`];
|
|
277
|
+
}
|
|
@@ -12,7 +12,7 @@ import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-grou
|
|
|
12
12
|
import { contextModeLabel, summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
13
13
|
import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
|
|
14
14
|
import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
|
|
15
|
-
import { ACTIVE_RUN_INDEX_DIR, isActiveAsyncState, readActiveRunIndex, releaseActiveRunIndex, updateActiveRunIndex } from "./active-run-index.ts";
|
|
15
|
+
import { ACTIVE_RUN_INDEX_DIR, DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS, activeRunMarkerAgeMs, isActiveAsyncState, readActiveRunIndex, releaseActiveRunIndex, updateActiveRunIndex } from "./active-run-index.ts";
|
|
16
16
|
|
|
17
17
|
interface AsyncRunStepSummary {
|
|
18
18
|
index: number;
|
|
@@ -25,6 +25,7 @@ interface AsyncRunStepSummary {
|
|
|
25
25
|
structured?: boolean;
|
|
26
26
|
checkpoint?: ChainCheckpointState;
|
|
27
27
|
status: AsyncJobStep["status"];
|
|
28
|
+
runner?: AsyncJobStep["runner"];
|
|
28
29
|
activityState?: ActivityState;
|
|
29
30
|
lastActivityAt?: number;
|
|
30
31
|
currentTool?: string;
|
|
@@ -120,6 +121,8 @@ export interface AsyncRunSummary {
|
|
|
120
121
|
interface AsyncRunListOptions {
|
|
121
122
|
states?: Array<AsyncRunSummary["state"]>;
|
|
122
123
|
sessionId?: string;
|
|
124
|
+
/** Accept runs owned by any of these session identities (union). */
|
|
125
|
+
sessionIds?: string[];
|
|
123
126
|
limit?: number;
|
|
124
127
|
/** Limits status-file reads after candidates are ordered by status mtime. */
|
|
125
128
|
entryLimit?: number;
|
|
@@ -128,6 +131,7 @@ interface AsyncRunListOptions {
|
|
|
128
131
|
now?: () => number;
|
|
129
132
|
reconcile?: boolean;
|
|
130
133
|
runId?: string;
|
|
134
|
+
includeNested?: boolean;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
function getErrorMessage(error: unknown): string {
|
|
@@ -253,6 +257,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
253
257
|
...(step.structured ? { structured: step.structured } : {}),
|
|
254
258
|
...(step.checkpoint ? { checkpoint: step.checkpoint } : {}),
|
|
255
259
|
status: step.status,
|
|
260
|
+
...(step.runner ? { runner: step.runner } : {}),
|
|
256
261
|
...(stepActivityState ? { activityState: stepActivityState } : {}),
|
|
257
262
|
...(stepLastActivityAt ? { lastActivityAt: stepLastActivityAt } : {}),
|
|
258
263
|
...(step.currentTool ? { currentTool: step.currentTool } : {}),
|
|
@@ -383,6 +388,7 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
383
388
|
&& options.states !== undefined
|
|
384
389
|
&& options.states.length > 0
|
|
385
390
|
&& options.states.every(isActiveAsyncState);
|
|
391
|
+
const includeNested = options.includeNested !== false;
|
|
386
392
|
try {
|
|
387
393
|
if (options.runId !== undefined) {
|
|
388
394
|
const resolution = resolveTargetedAsyncRun(asyncDirRoot, options.runId, options.sessionId);
|
|
@@ -443,6 +449,7 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
443
449
|
// entirely when no active runs match.
|
|
444
450
|
let nestedRouteIndex: Map<string, NestedRoute> | undefined;
|
|
445
451
|
const resolveNestedRoute = (rootRunId: string): NestedRoute | undefined => {
|
|
452
|
+
if (!includeNested) return undefined;
|
|
446
453
|
if (usedActiveIndex) return findNestedRouteForRootId(rootRunId);
|
|
447
454
|
if (!nestedRouteIndex) nestedRouteIndex = buildNestedRouteIndex();
|
|
448
455
|
return nestedRouteIndex.get(rootRunId);
|
|
@@ -459,7 +466,7 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
459
466
|
}
|
|
460
467
|
if (usedActiveIndex && !isActiveAsyncState(status.state)) {
|
|
461
468
|
const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId });
|
|
462
|
-
if (processTerminal?.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
469
|
+
if (processTerminal?.state === "observed" || (activeRunMarkerAgeMs(asyncDir, options.now?.()) ?? 0) > DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS) releaseActiveRunIndex(asyncDir);
|
|
463
470
|
}
|
|
464
471
|
if (status.displayDismissedAt !== undefined) continue;
|
|
465
472
|
// Filter before the nested-route lookup: the lookup builds an index over
|
|
@@ -468,9 +475,10 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
468
475
|
// match.
|
|
469
476
|
if (allowedStates && !allowedStates.has(status.state)) continue;
|
|
470
477
|
if (options.sessionId && status.sessionId !== options.sessionId) continue;
|
|
478
|
+
if (options.sessionIds && (status.sessionId === undefined || !options.sessionIds.includes(status.sessionId))) continue;
|
|
471
479
|
const nestedWarnings: string[] = [];
|
|
472
480
|
let nestedRoute: NestedRoute | undefined;
|
|
473
|
-
if (options.reconcile !== false) {
|
|
481
|
+
if (options.reconcile !== false && includeNested) {
|
|
474
482
|
try {
|
|
475
483
|
nestedRoute = resolveNestedRoute(status.runId || path.basename(asyncDir));
|
|
476
484
|
if (nestedRoute) reconcileNestedAsyncDescendants(nestedRoute, { resultsDir: options.resultsDir, kill: options.kill, now: options.now });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import
|
|
2
|
+
import { resultFilePath } from "./result-files.ts";
|
|
3
3
|
import type { AcceptanceLedger, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
|
|
4
4
|
import { readStatus } from "../../shared/utils.ts";
|
|
5
5
|
|
|
@@ -195,5 +195,5 @@ export async function waitForImportedAsyncRoot(
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
export function resolveAsyncRootResultPath(resultsDir: string, runId: string): string {
|
|
198
|
-
return
|
|
198
|
+
return resultFilePath(resultsDir, runId);
|
|
199
199
|
}
|
|
@@ -9,6 +9,8 @@ const ARCHIVE_VERSION = 1;
|
|
|
9
9
|
const ARCHIVE_TEXT_LIMIT_BYTES = 64 * 1024;
|
|
10
10
|
const REPLAY_DIR_NAME = "completion-replay";
|
|
11
11
|
const ARCHIVE_DIR_NAME = "output-archives";
|
|
12
|
+
const CLEANUP_INTERVAL_MS = 60_000;
|
|
13
|
+
const lastCleanupByResultsDir = new Map<string, number>();
|
|
12
14
|
|
|
13
15
|
export interface CompletionArchiveEntry {
|
|
14
16
|
agent?: string;
|
|
@@ -194,7 +196,7 @@ export function writeCompletionReplay(input: {
|
|
|
194
196
|
archivePath,
|
|
195
197
|
};
|
|
196
198
|
writePrivateAtomicJson(completionReplayPath(input.resultsDir, input.runId), record);
|
|
197
|
-
|
|
199
|
+
cleanupCompletionReplayIfDue(input.resultsDir, input.now, input.ttlMs);
|
|
198
200
|
return record;
|
|
199
201
|
}
|
|
200
202
|
|
|
@@ -233,6 +235,14 @@ export function readCompletionArchive(archivePath: string): CompletionArchive |
|
|
|
233
235
|
}
|
|
234
236
|
}
|
|
235
237
|
|
|
238
|
+
export function cleanupCompletionReplayIfDue(resultsDir: string, now: number, maxAgeMs: number, intervalMs = CLEANUP_INTERVAL_MS): boolean {
|
|
239
|
+
const last = lastCleanupByResultsDir.get(resultsDir);
|
|
240
|
+
if (last !== undefined && now - last < intervalMs) return false;
|
|
241
|
+
lastCleanupByResultsDir.set(resultsDir, now);
|
|
242
|
+
cleanupCompletionReplay(resultsDir, now, maxAgeMs);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
236
246
|
/** Opportunistically remove expired replay and orphan archive files without affecting delivery. */
|
|
237
247
|
export function cleanupCompletionReplay(resultsDir: string, now: number, maxAgeMs: number): void {
|
|
238
248
|
const replayDir = path.join(resultsDir, REPLAY_DIR_NAME);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import { safeTerminalText } from "../../shared/display-text.ts";
|
|
4
5
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
5
6
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
6
7
|
import {
|
|
@@ -196,7 +197,7 @@ function readSessionTranscriptTail(sessionFile: string, maxLines: number, truste
|
|
|
196
197
|
try {
|
|
197
198
|
const parsed = JSON.parse(line) as unknown;
|
|
198
199
|
const messageLine = sessionMessageLine(parsed);
|
|
199
|
-
if (messageLine) lines.push(messageLine);
|
|
200
|
+
if (messageLine) lines.push(...messageLine.split(/\r?\n/));
|
|
200
201
|
} catch {
|
|
201
202
|
malformed++;
|
|
202
203
|
}
|
|
@@ -410,6 +411,18 @@ function appendKnownArtifacts(lines: string[], input: { outputPaths: string[]; s
|
|
|
410
411
|
for (const artifact of artifacts) lines.push(` ${artifact}`);
|
|
411
412
|
}
|
|
412
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Sanitizes each assembled line independently.
|
|
416
|
+
*
|
|
417
|
+
* Sanitizing the joined response would let a single binary fragment in child
|
|
418
|
+
* output collapse the whole transcript, including run state, artifact paths, and
|
|
419
|
+
* warnings, into the binary placeholder. Per-line sanitization keeps that damage
|
|
420
|
+
* to the offending line.
|
|
421
|
+
*/
|
|
422
|
+
function safeTranscriptLines(lines: string[]): string {
|
|
423
|
+
return lines.map((line) => safeTerminalText(line)).join("\n");
|
|
424
|
+
}
|
|
425
|
+
|
|
413
426
|
function appendTranscriptBody(lines: string[], sourceLabel: string, sourceLines: string[], truncated: boolean): void {
|
|
414
427
|
lines.push(`${sourceLabel}${truncated ? " (tail truncated)" : ""}:`);
|
|
415
428
|
if (sourceLines.length === 0) {
|
|
@@ -430,6 +443,8 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
430
443
|
: uniqueStrings([runOutputPath]);
|
|
431
444
|
const sessionFile = selected.index !== undefined ? selected.step?.sessionFile : status.sessionFile;
|
|
432
445
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
446
|
+
// Synthesized output paths can name files that were never written. Keep the unfiltered paths
|
|
447
|
+
// below so a present-but-unreadable file still reports its read error.
|
|
433
448
|
|
|
434
449
|
const context = contextModeLabel(status.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)));
|
|
435
450
|
const lines = [
|
|
@@ -439,7 +454,7 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
439
454
|
stepStateLine(status.mode, selected.index, selected.step),
|
|
440
455
|
selected.hint,
|
|
441
456
|
].filter((line): line is string => Boolean(line));
|
|
442
|
-
appendKnownArtifacts(lines, { outputPaths, sessionFile, eventsPath: fs.existsSync(eventsPath) ? eventsPath : undefined, logPath: fs.existsSync(logPath) ? logPath : undefined });
|
|
457
|
+
appendKnownArtifacts(lines, { outputPaths: outputPaths.filter((outputPath) => fs.existsSync(outputPath)), sessionFile, eventsPath: fs.existsSync(eventsPath) ? eventsPath : undefined, logPath: fs.existsSync(logPath) ? logPath : undefined });
|
|
443
458
|
|
|
444
459
|
const warnings: string[] = [];
|
|
445
460
|
let transcriptLines: string[] = [];
|
|
@@ -470,7 +485,7 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
470
485
|
for (const warning of warnings) lines.push(` ${warning}`);
|
|
471
486
|
}
|
|
472
487
|
appendTranscriptBody(lines, transcriptSource, transcriptLines, truncated);
|
|
473
|
-
return lines
|
|
488
|
+
return safeTranscriptLines(lines);
|
|
474
489
|
}
|
|
475
490
|
|
|
476
491
|
export function formatNestedRunTranscript(run: NestedRunSummary, options: TranscriptOptions = {}): string {
|
|
@@ -488,7 +503,7 @@ export function formatNestedRunTranscript(run: NestedRunSummary, options: Transc
|
|
|
488
503
|
appendKnownArtifacts(lines, { outputPaths: [], sessionFile: run.sessionFile });
|
|
489
504
|
if (!run.sessionFile) {
|
|
490
505
|
appendTranscriptBody(lines, "Transcript tail", [], false);
|
|
491
|
-
return lines
|
|
506
|
+
return safeTranscriptLines(lines);
|
|
492
507
|
}
|
|
493
508
|
const sessionTail = readSessionTranscriptTail(run.sessionFile, lineLimit, options.sessionRoots ?? []);
|
|
494
509
|
if (sessionTail.warnings.length) {
|
|
@@ -496,7 +511,7 @@ export function formatNestedRunTranscript(run: NestedRunSummary, options: Transc
|
|
|
496
511
|
for (const warning of sessionTail.warnings) lines.push(` ${warning}`);
|
|
497
512
|
}
|
|
498
513
|
appendTranscriptBody(lines, `Session transcript tail from ${run.sessionFile}`, sessionTail.lines, false);
|
|
499
|
-
return lines
|
|
514
|
+
return safeTranscriptLines(lines);
|
|
500
515
|
}
|
|
501
516
|
|
|
502
517
|
export function formatAsyncResultTranscript(data: {
|
|
@@ -536,5 +551,5 @@ export function formatAsyncResultTranscript(data: {
|
|
|
536
551
|
].filter((line): line is string => Boolean(line));
|
|
537
552
|
appendKnownArtifacts(lines, { outputPaths: [], sessionFile, resultPath });
|
|
538
553
|
appendTranscriptBody(lines, "Result transcript tail", transcriptLines.filter((line) => line.trim()), output.split(/\r?\n/).length > lineLimit);
|
|
539
|
-
return lines
|
|
554
|
+
return safeTranscriptLines(lines);
|
|
540
555
|
}
|
|
@@ -239,7 +239,7 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
239
239
|
|
|
240
240
|
export default function registerSubagentNotify(
|
|
241
241
|
pi: ExtensionAPI,
|
|
242
|
-
state: Pick<SubagentState, "currentSessionId">,
|
|
242
|
+
state: Pick<SubagentState, "currentSessionId" | "sessionLineage">,
|
|
243
243
|
options: RegisterSubagentNotifyOptions = {},
|
|
244
244
|
): CompletionNotifier {
|
|
245
245
|
const seen = new Map<string, number>();
|
|
@@ -250,6 +250,9 @@ export default function registerSubagentNotify(
|
|
|
250
250
|
const batchers = new Map<string, CompletionBatcher<PendingCompletion>>();
|
|
251
251
|
let disposed = false;
|
|
252
252
|
|
|
253
|
+
const ownsSession = (sessionId: string) =>
|
|
254
|
+
sessionId === state.currentSessionId || state.sessionLineage?.includes(sessionId) === true;
|
|
255
|
+
|
|
253
256
|
const settle = (items: PendingCompletion[], accepted: boolean) => {
|
|
254
257
|
for (const item of items) {
|
|
255
258
|
pending.delete(item.key);
|
|
@@ -274,7 +277,7 @@ export default function registerSubagentNotify(
|
|
|
274
277
|
};
|
|
275
278
|
|
|
276
279
|
const deliver = (result: CompletionNotification): Promise<boolean> => {
|
|
277
|
-
if (disposed || typeof result.sessionId !== "string" || result.sessionId
|
|
280
|
+
if (disposed || typeof result.sessionId !== "string" || !ownsSession(result.sessionId)) return Promise.resolve(false);
|
|
278
281
|
if (result.intercomDelivered === true) return Promise.resolve(true);
|
|
279
282
|
const key = buildCompletionKey(result, "notify");
|
|
280
283
|
const seenAt = seen.get(key);
|