@selesai/code 0.8.6 → 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 +16 -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/settings.json +1 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +205 -41
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +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/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/question/index.ts +21 -6
- package/dist/extensions/question/question-list.ts +7 -1
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +7 -0
- package/dist/extensions/question/tests/question-list.test.ts +140 -0
- package/dist/extensions/question/tests/row-layout.test.ts +13 -0
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/docs/settings.md +6 -0
- package/package.json +3 -3
|
@@ -7,6 +7,7 @@ import { handleMissionAction } from "../../src/missions/actions.ts";
|
|
|
7
7
|
import { collectGoalContinuationNotices } from "../../src/missions/goal-driver.ts";
|
|
8
8
|
import { createMission, readMission, resolveMissionStoreLocation, updateMission } from "../../src/missions/store.ts";
|
|
9
9
|
import { missionStatePath } from "../../src/missions/workflow-state.ts";
|
|
10
|
+
import type { RetainedChild } from "../../src/runs/background/retained-children.ts";
|
|
10
11
|
|
|
11
12
|
function fixture() {
|
|
12
13
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-goal-mission-"));
|
|
@@ -46,7 +47,7 @@ describe("goal mission continuation", () => {
|
|
|
46
47
|
const resumed = collectGoalContinuationNotices({
|
|
47
48
|
location: test.location,
|
|
48
49
|
ownerSessionId: "session-1",
|
|
49
|
-
retainedChildren: [{ runId: "child-review", parentRunId: "workflow-1", agent: "reviewer", taskSummary: "Review", completedAt: 1, sessionPath: "/tmp/child.jsonl" }],
|
|
50
|
+
retainedChildren: [{ runId: "child-review", parentRunId: "workflow-1", state: "complete", agent: "reviewer", taskSummary: "Review", completedAt: 1, sessionPath: "/tmp/child.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/child.jsonl" } }],
|
|
50
51
|
turnId: 3,
|
|
51
52
|
});
|
|
52
53
|
assert.equal(resumed.length, 1);
|
|
@@ -85,8 +86,8 @@ describe("goal mission continuation", () => {
|
|
|
85
86
|
location: test.location,
|
|
86
87
|
ownerSessionId: "session-1",
|
|
87
88
|
retainedChildren: [
|
|
88
|
-
{ runId: "child-old", parentRunId: "workflow-old", agent: "reviewer", taskSummary: "Old", completedAt: 2, sessionPath: "/tmp/old.jsonl" },
|
|
89
|
-
{ runId: "child-new", parentRunId: "workflow-new", agent: "reviewer", taskSummary: "New", completedAt: 1, sessionPath: "/tmp/new.jsonl" },
|
|
89
|
+
{ runId: "child-old", parentRunId: "workflow-old", state: "complete", agent: "reviewer", taskSummary: "Old", completedAt: 2, sessionPath: "/tmp/old.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/old.jsonl" } },
|
|
90
|
+
{ runId: "child-new", parentRunId: "workflow-new", state: "complete", agent: "reviewer", taskSummary: "New", completedAt: 1, sessionPath: "/tmp/new.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/new.jsonl" } },
|
|
90
91
|
],
|
|
91
92
|
turnId: 1,
|
|
92
93
|
});
|
|
@@ -99,6 +100,69 @@ describe("goal mission continuation", () => {
|
|
|
99
100
|
}
|
|
100
101
|
});
|
|
101
102
|
|
|
103
|
+
it("resumes a mission-linked child older than ten unrelated children", () => {
|
|
104
|
+
const test = fixture();
|
|
105
|
+
try {
|
|
106
|
+
const mission = createMission(test.location, {
|
|
107
|
+
title: "Continue linked work",
|
|
108
|
+
objective: "Resume the linked worker",
|
|
109
|
+
goal: true,
|
|
110
|
+
budget: { tokens: 100 },
|
|
111
|
+
status: "active",
|
|
112
|
+
ownerSessionId: "session-1",
|
|
113
|
+
});
|
|
114
|
+
updateMission(test.location, mission.id, { addRuns: [{ runId: "workflow-mission", mode: "workflow", status: "complete" }] });
|
|
115
|
+
const retainedChildren: RetainedChild[] = [
|
|
116
|
+
...Array.from({ length: 10 }, (_, offset) => {
|
|
117
|
+
const index = 10 - offset;
|
|
118
|
+
return { runId: `child-${index}`, parentRunId: `workflow-unrelated-${index}`, state: "complete" as const, agent: "worker", taskSummary: "Unrelated", completedAt: index, resumability: { state: "resumable" as const, sessionPath: `/tmp/child-${index}.jsonl` } };
|
|
119
|
+
}),
|
|
120
|
+
{ runId: "child-0", parentRunId: "workflow-mission", state: "complete", agent: "worker", taskSummary: "Linked", completedAt: 0, resumability: { state: "resumable", sessionPath: "/tmp/child-0.jsonl" } },
|
|
121
|
+
];
|
|
122
|
+
const notices = collectGoalContinuationNotices({
|
|
123
|
+
location: test.location,
|
|
124
|
+
ownerSessionId: "session-1",
|
|
125
|
+
retainedChildren,
|
|
126
|
+
turnId: 1,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
assert.equal(retainedChildren.length, 11);
|
|
130
|
+
assert.equal(notices.length, 1);
|
|
131
|
+
assert.match(notices[0]!.message, /Resume retained child child-0 \(worker\)/);
|
|
132
|
+
assert.doesNotMatch(notices[0]!.message, /child-10/);
|
|
133
|
+
} finally {
|
|
134
|
+
fs.rmSync(test.root, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("does not resume non-resumable retained children from goal notices", () => {
|
|
139
|
+
const test = fixture();
|
|
140
|
+
try {
|
|
141
|
+
const mission = createMission(test.location, {
|
|
142
|
+
title: "Ship auth refresh",
|
|
143
|
+
objective: "Implement and validate token refresh",
|
|
144
|
+
goal: true,
|
|
145
|
+
budget: { tokens: 100 },
|
|
146
|
+
status: "active",
|
|
147
|
+
ownerSessionId: "session-1",
|
|
148
|
+
});
|
|
149
|
+
updateMission(test.location, mission.id, { addRuns: [{ runId: "workflow-stopped", mode: "workflow", status: "complete" }] });
|
|
150
|
+
|
|
151
|
+
const notices = collectGoalContinuationNotices({
|
|
152
|
+
location: test.location,
|
|
153
|
+
ownerSessionId: "session-1",
|
|
154
|
+
retainedChildren: [{ runId: "child-stopped", parentRunId: "workflow-stopped", state: "stopped", agent: "worker", taskSummary: "Stopped", completedAt: 1, resumability: { state: "not-resumable", reason: "stopped run" } }],
|
|
155
|
+
turnId: 1,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
assert.equal(notices.length, 1);
|
|
159
|
+
assert.doesNotMatch(notices[0]!.message, /Resume retained child/);
|
|
160
|
+
assert.match(notices[0]!.message, /Next ready action: Continue objective: Implement and validate token refresh/);
|
|
161
|
+
} finally {
|
|
162
|
+
fs.rmSync(test.root, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
102
166
|
it("supports pause, resume, and opt-out through mission.update", () => {
|
|
103
167
|
const test = fixture();
|
|
104
168
|
try {
|
|
@@ -17,7 +17,7 @@ function projectFixture() {
|
|
|
17
17
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-mission-lifecycle-"));
|
|
18
18
|
const projectRoot = path.join(root, "project");
|
|
19
19
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
20
|
-
return { root, projectRoot, missionConfig: { globalIndexDir: path.join(root, "global-index") } };
|
|
20
|
+
return { root, projectRoot, missionConfig: { directory: ".pi-subagents/missions", globalIndexDir: path.join(root, "global-index") } };
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
describe("mission launch lifecycle", () => {
|
|
@@ -34,6 +34,20 @@ async function waitForFile(filePath: string, timeoutMs = 10_000): Promise<void>
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
describe("mission store", () => {
|
|
37
|
+
it("stores default missions outside the project and preserves configured project storage", () => {
|
|
38
|
+
const test = fixture();
|
|
39
|
+
try {
|
|
40
|
+
assert.equal(path.relative(test.projectRoot, test.location.missionDir).startsWith(".."), true);
|
|
41
|
+
assert.equal(test.location.missionDir.startsWith(path.join(test.agentDir, "missions", "projects")), true);
|
|
42
|
+
assert.equal(
|
|
43
|
+
resolveMissionStoreLocation({ projectRoot: test.projectRoot, agentDir: test.agentDir, config: { directory: ".pi/subagents/missions" } }).missionDir,
|
|
44
|
+
path.join(test.projectRoot, ".pi/subagents", "missions"),
|
|
45
|
+
);
|
|
46
|
+
} finally {
|
|
47
|
+
fs.rmSync(test.root, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
37
51
|
it("creates, reads, updates, lists, and globally indexes project missions", () => {
|
|
38
52
|
const test = fixture();
|
|
39
53
|
try {
|
|
@@ -68,6 +68,13 @@ describe("model fallback helpers", () => {
|
|
|
68
68
|
);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it("rejects fallback models that the active registry cannot resolve", () => {
|
|
72
|
+
assert.throws(
|
|
73
|
+
() => buildModelCandidates("gpt-5-mini", ["does-not-exist"], availableModels),
|
|
74
|
+
/Unknown subagent model 'does-not-exist'/,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
71
78
|
it("detects retryable provider/model failures", () => {
|
|
72
79
|
assert.equal(isRetryableModelFailure("rate limit exceeded for provider"), true);
|
|
73
80
|
assert.equal(isRetryableModelFailure("model unavailable"), true);
|
|
@@ -161,6 +168,17 @@ describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", ()
|
|
|
161
168
|
);
|
|
162
169
|
});
|
|
163
170
|
|
|
171
|
+
it("rejects explicit models that the active registry cannot resolve", () => {
|
|
172
|
+
assert.throws(
|
|
173
|
+
() => resolveSubagentModelOverride("does-not-exist", parentModel, availableModels),
|
|
174
|
+
/Unknown subagent model 'does-not-exist'/,
|
|
175
|
+
);
|
|
176
|
+
assert.throws(
|
|
177
|
+
() => resolveSubagentModelOverride("does-not-exist:high", parentModel, availableModels),
|
|
178
|
+
/Unknown subagent model 'does-not-exist:high'/,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
164
182
|
it("returns undefined when inheriting but no parent model is known", () => {
|
|
165
183
|
// No parent session model available: fall back to the prior behavior of
|
|
166
184
|
// emitting no override rather than inventing an invalid one.
|
|
@@ -150,7 +150,7 @@ describe("native supervisor channel", () => {
|
|
|
150
150
|
channel.start();
|
|
151
151
|
channel.dispose();
|
|
152
152
|
|
|
153
|
-
assert.deepEqual(registeredTools, [NATIVE_SUPERVISOR_TOOL_NAME
|
|
153
|
+
assert.deepEqual(registeredTools, [NATIVE_SUPERVISOR_TOOL_NAME]);
|
|
154
154
|
assert.deepEqual(sent.map(({ message }) => message.details?.id), [matchingId]);
|
|
155
155
|
assert.deepEqual(sent[0]?.options, { triggerTurn: true });
|
|
156
156
|
assert.equal(channel.pending.has(matchingId), false, "disposed channel clears pending requests");
|
|
@@ -553,7 +553,7 @@ describe("native supervisor channel", () => {
|
|
|
553
553
|
registeredTools.set(tool.name, tool);
|
|
554
554
|
},
|
|
555
555
|
};
|
|
556
|
-
registerNativeSupervisorClient(pi as never
|
|
556
|
+
registerNativeSupervisorClient(pi as never);
|
|
557
557
|
const controller = new AbortController();
|
|
558
558
|
controller.abort();
|
|
559
559
|
|
|
@@ -39,9 +39,23 @@ const savedEnv = {
|
|
|
39
39
|
[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]: process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
async function removeDirWithRetry(dir: string): Promise<void> {
|
|
43
|
+
let lastError: unknown;
|
|
44
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
return;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
lastError = error;
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw lastError;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
afterEach(async () => {
|
|
43
57
|
for (const route of routes.splice(0)) {
|
|
44
|
-
|
|
58
|
+
await removeDirWithRetry(path.dirname(route.eventSink));
|
|
45
59
|
}
|
|
46
60
|
for (const [key, value] of Object.entries(savedEnv)) {
|
|
47
61
|
if (value === undefined) delete process.env[key];
|
|
@@ -97,6 +111,15 @@ describe("nested route index", () => {
|
|
|
97
111
|
const tokens = new Set([first.capabilityToken, second.capabilityToken]);
|
|
98
112
|
assert.ok(tokens.has(indexed.capabilityToken), "indexed route must be one of the two created routes");
|
|
99
113
|
});
|
|
114
|
+
|
|
115
|
+
it("recovers a route from its index when the route file is missing", () => {
|
|
116
|
+
const route = trackRoute("late-route-root");
|
|
117
|
+
const routeFile = path.join(path.dirname(route.eventSink), "route.json");
|
|
118
|
+
fs.rmSync(routeFile);
|
|
119
|
+
|
|
120
|
+
assert.equal(buildNestedRouteIndex().get("late-route-root")?.capabilityToken, route.capabilityToken);
|
|
121
|
+
assert.equal(JSON.parse(fs.readFileSync(routeFile, "utf-8")).capabilityToken, route.capabilityToken);
|
|
122
|
+
});
|
|
100
123
|
});
|
|
101
124
|
|
|
102
125
|
describe("nested event route validation", () => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
|
+
import { resolveNodeExecutable } from "../../src/shared/node-executable.ts";
|
|
7
|
+
|
|
8
|
+
describe("Node executable resolution", () => {
|
|
9
|
+
it("uses an executable Node path", () => {
|
|
10
|
+
assert.equal(resolveNodeExecutable(process.execPath), process.execPath);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("falls back to PATH node for a standalone Pi executable", () => {
|
|
14
|
+
assert.equal(resolveNodeExecutable(path.join(os.tmpdir(), process.platform === "win32" ? "pi.exe" : "pi")), process.platform === "win32" ? "node.exe" : "node");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("falls back to PATH node for a stale Node path", () => {
|
|
18
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-node-executable-"));
|
|
19
|
+
try {
|
|
20
|
+
const staleNode = path.join(root, process.platform === "win32" ? "node.exe" : "node");
|
|
21
|
+
assert.equal(resolveNodeExecutable(staleNode), process.platform === "win32" ? "node.exe" : "node");
|
|
22
|
+
} finally {
|
|
23
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -134,6 +134,14 @@ describe("registerSubagentNotify", () => {
|
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
it("does not attach async status snapshots to subagent-notify details", async () => {
|
|
138
|
+
const { notifier, sent } = createPi("session-a");
|
|
139
|
+
assert.equal(await notifier.deliver(completionResult({ id: "direct-no-snapshot" })), true);
|
|
140
|
+
const message = sent[0]?.message as { customType?: string; details?: Record<string, unknown> } | undefined;
|
|
141
|
+
assert.equal(message?.customType, "subagent-notify");
|
|
142
|
+
assert.equal(message?.details?.asyncSnapshot, undefined);
|
|
143
|
+
});
|
|
144
|
+
|
|
137
145
|
it("acknowledges direct delivery only after sendMessage accepts it", async () => {
|
|
138
146
|
const { notifier, sent } = createPi("session-a");
|
|
139
147
|
assert.equal(await notifier.deliver(completionResult({ id: "direct-accepted" })), true);
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { afterEach, test } from "node:test";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { createOrcaProgressTab, resolveOrcaCommand, resolvePiSessionId } from "../../src/runs/shared/orca-progress-tabs.ts";
|
|
9
|
+
import { TEMP_ROOT_DIR } from "../../src/shared/types.ts";
|
|
10
|
+
import { writeNodeCommand } from "../support/node-command.ts";
|
|
11
|
+
|
|
12
|
+
const tempDirs: string[] = [];
|
|
13
|
+
|
|
14
|
+
function removeProgressFiles(prefix: string): void {
|
|
15
|
+
const root = path.join(TEMP_ROOT_DIR, "orca-progress");
|
|
16
|
+
if (!fs.existsSync(root)) return;
|
|
17
|
+
for (const name of fs.readdirSync(root)) {
|
|
18
|
+
if (name.startsWith(prefix)) fs.rmSync(path.join(root, name), { force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
const progressRoot = path.join(TEMP_ROOT_DIR, "orca-progress");
|
|
24
|
+
for (const dir of tempDirs.splice(0)) {
|
|
25
|
+
const key = createHash("sha256").update(path.resolve(dir)).digest("hex").slice(0, 20);
|
|
26
|
+
fs.rmSync(path.join(progressRoot, `counter-${key}`), { force: true });
|
|
27
|
+
fs.rmSync(path.join(progressRoot, `counter-${key}.lock`), { recursive: true, force: true });
|
|
28
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
removeProgressFiles("progress-");
|
|
31
|
+
removeProgressFiles("disabled-run-");
|
|
32
|
+
removeProgressFiles("standalone-pi-");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function tempDir(): string {
|
|
36
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-tabs-test-"));
|
|
37
|
+
tempDirs.push(dir);
|
|
38
|
+
return dir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function waitForFile(file: string): Promise<void> {
|
|
42
|
+
const deadline = Date.now() + 5_000;
|
|
43
|
+
while (!fs.existsSync(file)) {
|
|
44
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${file}`);
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function progressFile(prefix: string, suffix: ".log" | ".done"): string {
|
|
50
|
+
const root = path.join(TEMP_ROOT_DIR, "orca-progress");
|
|
51
|
+
const name = fs.readdirSync(root).find((candidate) => candidate.startsWith(prefix) && candidate.endsWith(suffix));
|
|
52
|
+
assert.ok(name, `Expected ${suffix} file for ${prefix}`);
|
|
53
|
+
return path.join(root, name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function captureCommand(command: string, cwd: string): Promise<string> {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const child = spawn(command, { cwd, shell: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
59
|
+
let output = "";
|
|
60
|
+
child.stdout.setEncoding("utf-8");
|
|
61
|
+
child.stdout.on("data", (chunk: string) => { output += chunk; });
|
|
62
|
+
child.once("error", reject);
|
|
63
|
+
child.once("close", (code) => code === 0 ? resolve(output) : reject(new Error(`Viewer exited ${code}: ${output}`)));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeCaptureOrca(dir: string): string {
|
|
68
|
+
return writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_CAPTURE, JSON.stringify(process.argv.slice(2)))");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test("Orca progress tabs are disabled on Windows", { skip: process.platform === "win32" ? undefined : "Windows-only platform boundary" }, () => {
|
|
72
|
+
const dir = tempDir();
|
|
73
|
+
assert.equal(createOrcaProgressTab({
|
|
74
|
+
cwd: dir,
|
|
75
|
+
runId: "windows-disabled",
|
|
76
|
+
agent: "worker",
|
|
77
|
+
index: 0,
|
|
78
|
+
config: { enabled: true },
|
|
79
|
+
command: process.execPath,
|
|
80
|
+
}), undefined);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("resolveOrcaCommand only returns executable commands", () => {
|
|
84
|
+
const dir = tempDir();
|
|
85
|
+
const executable = writeNodeCommand(dir, "orca", "process.exit(0)");
|
|
86
|
+
assert.equal(resolveOrcaCommand({ PATH: dir, PATHEXT: ".CMD" }), executable);
|
|
87
|
+
assert.equal(resolveOrcaCommand({ PATH: "", SELESAI_SUBAGENT_ORCA_BINARY: path.join(dir, "missing") }), undefined);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("an unavailable Orca command leaves native execution untouched", () => {
|
|
91
|
+
const dir = tempDir();
|
|
92
|
+
assert.equal(createOrcaProgressTab({
|
|
93
|
+
cwd: dir,
|
|
94
|
+
runId: "missing-orca",
|
|
95
|
+
agent: "worker",
|
|
96
|
+
index: 0,
|
|
97
|
+
config: { enabled: true },
|
|
98
|
+
env: { PATH: "", SELESAI_SUBAGENT_ORCA_BINARY: path.join(dir, "missing") },
|
|
99
|
+
}), undefined);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("standalone Pi executables use PATH Node for the watchdog and viewer", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
103
|
+
const dir = tempDir();
|
|
104
|
+
const capture = path.join(dir, "capture.json");
|
|
105
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
106
|
+
const fakePi = path.join(dir, "pi");
|
|
107
|
+
const originalExecPath = process.execPath;
|
|
108
|
+
try {
|
|
109
|
+
process.execPath = fakePi;
|
|
110
|
+
const tab = createOrcaProgressTab({
|
|
111
|
+
cwd: dir,
|
|
112
|
+
runId: "standalone-pi",
|
|
113
|
+
agent: "worker",
|
|
114
|
+
index: 0,
|
|
115
|
+
config: { enabled: true },
|
|
116
|
+
command: fakeOrca,
|
|
117
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
118
|
+
});
|
|
119
|
+
assert.ok(tab);
|
|
120
|
+
tab.finish("failed");
|
|
121
|
+
await waitForFile(capture);
|
|
122
|
+
const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
|
|
123
|
+
const viewer = args[args.indexOf("--command") + 1]!;
|
|
124
|
+
assert.match(viewer, /^'node' '-e' /);
|
|
125
|
+
assert.equal(viewer.includes(fakePi), false);
|
|
126
|
+
assert.match(await captureCommand(viewer, dir), /failed/);
|
|
127
|
+
} finally {
|
|
128
|
+
process.execPath = originalExecPath;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("hung Orca terminal creation does not delay the owning process", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
133
|
+
const dir = tempDir();
|
|
134
|
+
const fakeOrca = writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_PID, String(process.pid));setInterval(()=>{},1000)");
|
|
135
|
+
const pidFile = path.join(dir, "orca.pid");
|
|
136
|
+
const moduleUrl = new URL("../../src/runs/shared/orca-progress-tabs.ts", import.meta.url).href;
|
|
137
|
+
const ownerScript = `import {createOrcaProgressTab} from ${JSON.stringify(moduleUrl)};const tab=createOrcaProgressTab({cwd:${JSON.stringify(dir)},runId:'progress-hung-owner',agent:'worker',index:0,config:{enabled:true},command:${JSON.stringify(fakeOrca)},env:{...process.env,ORCA_TEST_PID:${JSON.stringify(pidFile)}}});if(!tab)throw new Error('tab unavailable');`;
|
|
138
|
+
const startedAt = Date.now();
|
|
139
|
+
const owner = spawn(process.execPath, ["--experimental-strip-types", "--input-type=module", "--eval", ownerScript], { cwd: dir, stdio: "ignore" });
|
|
140
|
+
const ownerClosed = new Promise<number | null>((resolve, reject) => {
|
|
141
|
+
owner.once("error", reject);
|
|
142
|
+
owner.once("close", resolve);
|
|
143
|
+
});
|
|
144
|
+
let fakePid: number | undefined;
|
|
145
|
+
try {
|
|
146
|
+
assert.equal(await ownerClosed, 0);
|
|
147
|
+
assert.ok(Date.now() - startedAt < 2_000, "the Orca observer delayed runner completion");
|
|
148
|
+
await waitForFile(pidFile);
|
|
149
|
+
fakePid = Number.parseInt(fs.readFileSync(pidFile, "utf-8"), 10);
|
|
150
|
+
process.kill(fakePid, 0);
|
|
151
|
+
} finally {
|
|
152
|
+
if (fakePid !== undefined) {
|
|
153
|
+
try { process.kill(fakePid, "SIGKILL"); } catch { /* already stopped */ }
|
|
154
|
+
}
|
|
155
|
+
if (owner.exitCode === null) owner.kill("SIGKILL");
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("malformed optional observer metadata cannot break child execution", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
160
|
+
const dir = tempDir();
|
|
161
|
+
const capture = path.join(dir, "capture.json");
|
|
162
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
163
|
+
const tab = createOrcaProgressTab({
|
|
164
|
+
cwd: dir,
|
|
165
|
+
runId: undefined,
|
|
166
|
+
agent: undefined,
|
|
167
|
+
index: undefined,
|
|
168
|
+
config: { enabled: true },
|
|
169
|
+
command: fakeOrca,
|
|
170
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
171
|
+
} as never);
|
|
172
|
+
assert.ok(tab);
|
|
173
|
+
tab.finish("completed");
|
|
174
|
+
await waitForFile(capture);
|
|
175
|
+
const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
|
|
176
|
+
assert.equal(args[args.indexOf("--title") + 1], "subagent · subagent · 1");
|
|
177
|
+
removeProgressFiles("run-0-");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("disabled Orca progress tabs do not invoke Orca", async () => {
|
|
181
|
+
const dir = tempDir();
|
|
182
|
+
const capture = path.join(dir, "capture.json");
|
|
183
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
184
|
+
const tab = createOrcaProgressTab({
|
|
185
|
+
cwd: dir,
|
|
186
|
+
runId: "disabled-run",
|
|
187
|
+
agent: "worker",
|
|
188
|
+
index: 0,
|
|
189
|
+
config: { enabled: false },
|
|
190
|
+
command: fakeOrca,
|
|
191
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
192
|
+
});
|
|
193
|
+
assert.equal(tab, undefined);
|
|
194
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
195
|
+
assert.equal(fs.existsSync(capture), false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("enabled tabs use a worktree sequence and successful Pi sessions get cleanup guidance", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
199
|
+
const dir = tempDir();
|
|
200
|
+
const capture = path.join(dir, "capture.json");
|
|
201
|
+
const secondCapture = path.join(dir, "capture-2.json");
|
|
202
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
203
|
+
fs.mkdirSync(path.join(dir, ".git"));
|
|
204
|
+
const runId = `progress-${Date.now()}`;
|
|
205
|
+
const tab = createOrcaProgressTab({
|
|
206
|
+
cwd: dir,
|
|
207
|
+
runId,
|
|
208
|
+
agent: "worker",
|
|
209
|
+
index: 2,
|
|
210
|
+
config: { enabled: true },
|
|
211
|
+
command: fakeOrca,
|
|
212
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
213
|
+
});
|
|
214
|
+
assert.ok(tab);
|
|
215
|
+
tab.append("starting\n");
|
|
216
|
+
tab.event({ type: "tool_execution_start", toolName: "read", args: { path: "README.md" } });
|
|
217
|
+
tab.event({ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "done output" }] } as never });
|
|
218
|
+
const sessionId = "019ffd40-4859-7015-94e4-7d15c31885ef";
|
|
219
|
+
const sessionFile = path.join(dir, `session file's ${sessionId}.jsonl`);
|
|
220
|
+
fs.writeFileSync(sessionFile, `${JSON.stringify({ type: "session", version: 3, id: sessionId })}\n`);
|
|
221
|
+
assert.equal(resolvePiSessionId(sessionFile), sessionId);
|
|
222
|
+
assert.equal(resolvePiSessionId(path.join(dir, `missing_${sessionId}.jsonl`)), undefined);
|
|
223
|
+
tab.finish("completed", sessionFile);
|
|
224
|
+
|
|
225
|
+
await waitForFile(capture);
|
|
226
|
+
const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
|
|
227
|
+
assert.deepEqual(args.slice(0, 2), ["terminal", "create"]);
|
|
228
|
+
assert.equal(args[args.indexOf("--worktree") + 1], `path:${path.resolve(dir)}`);
|
|
229
|
+
assert.equal(args[args.indexOf("--title") + 1], "subagent · worker · 1");
|
|
230
|
+
const viewer = args[args.indexOf("--command") + 1];
|
|
231
|
+
assert.ok(viewer.includes(process.execPath));
|
|
232
|
+
assert.doesNotMatch(viewer, /(?:^|;)\s*exec\s/);
|
|
233
|
+
assert.doesNotMatch(viewer, /(?:&|;)\s*exit(?:\s|$)/);
|
|
234
|
+
|
|
235
|
+
const progressDir = path.join(TEMP_ROOT_DIR, "orca-progress");
|
|
236
|
+
const log = fs.readdirSync(progressDir).find((name) => name.startsWith(`${runId}-2-`) && name.endsWith(".log"));
|
|
237
|
+
assert.ok(log);
|
|
238
|
+
const text = fs.readFileSync(path.join(progressDir, log), "utf-8");
|
|
239
|
+
assert.match(text, /starting/);
|
|
240
|
+
assert.match(text, /› read: README\.md/);
|
|
241
|
+
assert.match(text, /done output/);
|
|
242
|
+
const quotedSessionFile = `'${fs.realpathSync(sessionFile).replace(/'/g, `'"'"'`)}'`;
|
|
243
|
+
assert.ok(text.includes(`completed. To remove the Pi session of this subagent, run rm -- ${quotedSessionFile}`));
|
|
244
|
+
assert.doesNotMatch(text, /find ~\/\.pi\/agent\/sessions/);
|
|
245
|
+
|
|
246
|
+
const nestedCwd = path.join(dir, "packages", "app");
|
|
247
|
+
fs.mkdirSync(nestedCwd, { recursive: true });
|
|
248
|
+
const secondTab = createOrcaProgressTab({
|
|
249
|
+
cwd: nestedCwd,
|
|
250
|
+
runId: `${runId}-second`,
|
|
251
|
+
agent: "worker",
|
|
252
|
+
index: 0,
|
|
253
|
+
config: { enabled: true },
|
|
254
|
+
command: fakeOrca,
|
|
255
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: secondCapture },
|
|
256
|
+
});
|
|
257
|
+
assert.ok(secondTab);
|
|
258
|
+
secondTab.finish("failed", sessionFile);
|
|
259
|
+
await waitForFile(secondCapture);
|
|
260
|
+
const secondArgs = JSON.parse(fs.readFileSync(secondCapture, "utf-8")) as string[];
|
|
261
|
+
assert.equal(secondArgs[secondArgs.indexOf("--title") + 1], "subagent · worker · 2");
|
|
262
|
+
const secondLog = fs.readdirSync(progressDir).find((name) => name.startsWith(`${runId}-second-0-`) && name.endsWith(".log"));
|
|
263
|
+
assert.ok(secondLog);
|
|
264
|
+
const secondText = fs.readFileSync(path.join(progressDir, secondLog), "utf-8");
|
|
265
|
+
assert.match(secondText, /failed/);
|
|
266
|
+
assert.doesNotMatch(secondText, /To remove the Pi session/);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("viewer strips split terminal control sequences across poll ticks", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
270
|
+
const dir = tempDir();
|
|
271
|
+
const capture = path.join(dir, "capture.json");
|
|
272
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
273
|
+
const runId = `progress-sanitize-${Date.now()}`;
|
|
274
|
+
const tab = createOrcaProgressTab({
|
|
275
|
+
cwd: dir,
|
|
276
|
+
runId,
|
|
277
|
+
agent: "worker",
|
|
278
|
+
index: 0,
|
|
279
|
+
config: { enabled: true },
|
|
280
|
+
command: fakeOrca,
|
|
281
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
282
|
+
});
|
|
283
|
+
assert.ok(tab);
|
|
284
|
+
await waitForFile(capture);
|
|
285
|
+
const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
|
|
286
|
+
const viewer = args[args.indexOf("--command") + 1]!;
|
|
287
|
+
const outputPromise = captureCommand(viewer, dir);
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
289
|
+
tab.append("safe CSI \u001b[");
|
|
290
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
291
|
+
tab.append("31mred OSC \u001b]0;secret");
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
293
|
+
tab.append(" title\u0007visible\u0000\u0001\r\t\u007f\n");
|
|
294
|
+
tab.finish("failed");
|
|
295
|
+
const output = await outputPromise;
|
|
296
|
+
assert.match(output, /safe CSI red OSC visible\n/);
|
|
297
|
+
assert.doesNotMatch(output, /\u001b|31m|secret|title|\u0000|\u0001|\r|\t|\u007f/);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("mirror output keeps small writes that hit stream backpressure before the byte limit", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
301
|
+
const dir = tempDir();
|
|
302
|
+
const capture = path.join(dir, "capture.json");
|
|
303
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
304
|
+
const runId = `progress-backpressure-${Date.now()}`;
|
|
305
|
+
const tab = createOrcaProgressTab({
|
|
306
|
+
cwd: dir,
|
|
307
|
+
runId,
|
|
308
|
+
agent: "worker",
|
|
309
|
+
index: 0,
|
|
310
|
+
config: { enabled: true },
|
|
311
|
+
command: fakeOrca,
|
|
312
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
313
|
+
});
|
|
314
|
+
assert.ok(tab);
|
|
315
|
+
for (let index = 0; index < 2_000; index++) tab.append(`line-${index.toString().padStart(4, "0")} ${"x".repeat(80)}\n`);
|
|
316
|
+
tab.finish("completed");
|
|
317
|
+
const log = progressFile(`${runId}-0-`, ".log");
|
|
318
|
+
await waitForFile(log.replace(/\.log$/, ".done"));
|
|
319
|
+
const text = fs.readFileSync(log, "utf-8");
|
|
320
|
+
assert.match(text, /line-1999/);
|
|
321
|
+
assert.doesNotMatch(text, /progress mirror truncated/);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("mirror output truncates at a finite byte bound", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
325
|
+
const dir = tempDir();
|
|
326
|
+
const capture = path.join(dir, "capture.json");
|
|
327
|
+
const fakeOrca = writeCaptureOrca(dir);
|
|
328
|
+
const runId = `progress-bounded-${Date.now()}`;
|
|
329
|
+
const tab = createOrcaProgressTab({
|
|
330
|
+
cwd: dir,
|
|
331
|
+
runId,
|
|
332
|
+
agent: "worker",
|
|
333
|
+
index: 0,
|
|
334
|
+
config: { enabled: true },
|
|
335
|
+
command: fakeOrca,
|
|
336
|
+
env: { ...process.env, ORCA_TEST_CAPTURE: capture },
|
|
337
|
+
});
|
|
338
|
+
assert.ok(tab);
|
|
339
|
+
tab.append("x".repeat(2 * 1024 * 1024));
|
|
340
|
+
tab.append("must be dropped");
|
|
341
|
+
tab.finish("completed");
|
|
342
|
+
const log = progressFile(`${runId}-0-`, ".log");
|
|
343
|
+
await waitForFile(log.replace(/\.log$/, ".done"));
|
|
344
|
+
assert.ok(fs.statSync(log).size <= 1024 * 1024);
|
|
345
|
+
const text = fs.readFileSync(log, "utf-8");
|
|
346
|
+
assert.match(text, /progress mirror truncated at 1048576 bytes/);
|
|
347
|
+
assert.doesNotMatch(text, /must be dropped/);
|
|
348
|
+
});
|
|
@@ -69,8 +69,11 @@ test("published extension APIs use supported package entrypoints", async () => {
|
|
|
69
69
|
assert.equal(backgroundWork.BACKGROUND_WORK_PROTOCOL_VERSION, 1);
|
|
70
70
|
assert.equal(backgroundWork.BACKGROUND_WORK_REGISTRY_KEY, "pi-subagents.background-work.v1");
|
|
71
71
|
const externalRuns = await import("pi-subagents/external-runs");
|
|
72
|
-
assert.equal(externalRuns.EXTERNAL_RUN_REGISTRY_VERSION,
|
|
73
|
-
assert.equal(typeof externalRuns.
|
|
72
|
+
assert.equal(externalRuns.EXTERNAL_RUN_REGISTRY_VERSION, 2);
|
|
73
|
+
assert.equal(typeof externalRuns.registerExternalRun, "function");
|
|
74
|
+
assert.equal(typeof externalRuns.updateExternalRun, "function");
|
|
75
|
+
assert.equal(typeof externalRuns.snapshotExternalRuns, "function");
|
|
76
|
+
assert.equal(typeof externalRuns.unregisterExternalRun, "function");
|
|
74
77
|
const capability = await import("pi-subagents/capability-ceiling");
|
|
75
78
|
assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_VERSION, 1);
|
|
76
79
|
assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY, "pi-subagents.capability-ceiling.v1");
|
|
@@ -825,7 +825,7 @@ describe("buildPiArgs system prompt mode wiring", () => {
|
|
|
825
825
|
tools: reviewer.tools,
|
|
826
826
|
});
|
|
827
827
|
|
|
828
|
-
assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls
|
|
828
|
+
assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls");
|
|
829
829
|
assert.doesNotMatch(args[args.indexOf("--tools") + 1] ?? "", /\b(?:bash|edit|write)\b/);
|
|
830
830
|
});
|
|
831
831
|
|