@selesai/code 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.test.d.ts +1 -0
- package/dist/cli/args.test.js +24 -0
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.js +18 -7
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.js +4 -0
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/keybindings.d.ts +24 -0
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.js +1 -0
- package/dist/core/project-trust.js +2 -2
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/sdk.d.ts +5 -3
- package/dist/core/sdk.js +2 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.js +15 -0
- package/dist/core/system-prompt.js +3 -9
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/bash.js +2 -0
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/schema-prune.d.ts +16 -0
- package/dist/core/tools/schema-prune.js +39 -0
- package/dist/core/tools/schema-prune.test.d.ts +1 -0
- package/dist/core/tools/schema-prune.test.js +111 -0
- package/dist/core/tools/write.js +2 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +187 -41
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +395 -98
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
- package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/question/index.ts +21 -6
- package/dist/extensions/question/question-list.ts +7 -1
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +7 -0
- package/dist/extensions/question/tests/question-list.test.ts +140 -0
- package/dist/extensions/question/tests/row-layout.test.ts +13 -0
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/extensions/tokenin-onboarding.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -0
- package/dist/modes/interactive/components/model-selector.js +2 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/tool-execution.js +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -2
- package/dist/modes/interactive/interactive-mode.js +81 -25
- package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
- package/dist/modes/interactive/model-catalog-refresh.js +33 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +5 -3
- package/dist/modes/interactive/theme/theme.js +12 -2
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/abort.js +47 -0
- package/dist/utils/tools-manager.d.ts +5 -1
- package/dist/utils/tools-manager.js +12 -21
- package/docs/settings.md +6 -0
- package/package.json +6 -6
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { afterEach, describe, it } from "node:test";
|
|
5
|
-
import {
|
|
5
|
+
import { getArtifactsDir } from "../../src/shared/artifacts.ts";
|
|
6
6
|
import { updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
|
|
7
7
|
import { SubagentFleetComponent } from "../../src/tui/fleet.ts";
|
|
8
8
|
import { createNestedRoute } from "../../src/runs/shared/nested-events.ts";
|
|
@@ -151,6 +151,55 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
it("stores only parent-registered session roots from async start events", () => {
|
|
155
|
+
const asyncRoot = createTempDir("pi-async-job-tracker-session-root-");
|
|
156
|
+
try {
|
|
157
|
+
const state = createState();
|
|
158
|
+
const recorder = createEventRecorder();
|
|
159
|
+
const tracker = createTracker(recorder.pi, state as never, asyncRoot);
|
|
160
|
+
const sessionRoot = path.join(asyncRoot, "custom-sessions");
|
|
161
|
+
state.liveAsyncSessionRoots = new Map([["run-custom-session", sessionRoot]]);
|
|
162
|
+
|
|
163
|
+
tracker.handleStarted({ id: "run-custom-session", asyncDir: path.join(asyncRoot, "run-custom-session"), agent: "worker", sessionRoot: path.join(asyncRoot, "forged") });
|
|
164
|
+
|
|
165
|
+
assert.equal(state.asyncJobs.get("run-custom-session")?.sessionRoot, sessionRoot);
|
|
166
|
+
assert.equal(state.liveAsyncSessionRoots.has("run-custom-session"), false);
|
|
167
|
+
} finally {
|
|
168
|
+
removeTempDir(asyncRoot);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("ignores unregistered session roots from async start events", () => {
|
|
173
|
+
const asyncRoot = createTempDir("pi-async-job-tracker-forged-session-root-");
|
|
174
|
+
try {
|
|
175
|
+
const state = createState();
|
|
176
|
+
const recorder = createEventRecorder();
|
|
177
|
+
const tracker = createTracker(recorder.pi, state as never, asyncRoot);
|
|
178
|
+
|
|
179
|
+
tracker.handleStarted({ id: "run-forged-session", asyncDir: path.join(asyncRoot, "run-forged-session"), agent: "worker", sessionRoot: path.join(asyncRoot, "forged") });
|
|
180
|
+
|
|
181
|
+
assert.equal(state.asyncJobs.get("run-forged-session")?.sessionRoot, undefined);
|
|
182
|
+
} finally {
|
|
183
|
+
removeTempDir(asyncRoot);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("clears pending live session roots on reset", () => {
|
|
188
|
+
const asyncRoot = createTempDir("pi-async-job-tracker-reset-session-root-");
|
|
189
|
+
try {
|
|
190
|
+
const state = createState();
|
|
191
|
+
const recorder = createEventRecorder();
|
|
192
|
+
const tracker = createTracker(recorder.pi, state as never, asyncRoot);
|
|
193
|
+
state.liveAsyncSessionRoots = new Map([["run-pending", path.join(asyncRoot, "sessions")]]);
|
|
194
|
+
|
|
195
|
+
tracker.resetJobs();
|
|
196
|
+
|
|
197
|
+
assert.equal(state.liveAsyncSessionRoots.size, 0);
|
|
198
|
+
} finally {
|
|
199
|
+
removeTempDir(asyncRoot);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
154
203
|
it("removes completed jobs after retention and requests a rerender", async () => {
|
|
155
204
|
const asyncRoot = createTempDir("pi-async-job-tracker-");
|
|
156
205
|
try {
|
|
@@ -204,10 +253,12 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
204
253
|
try {
|
|
205
254
|
const runDir = path.join(asyncRoot, "run-restored");
|
|
206
255
|
const customCwd = path.join(asyncRoot, "custom-cwd");
|
|
207
|
-
const
|
|
256
|
+
const sessionFile = path.join(asyncRoot, "sessions", "session-restored.jsonl");
|
|
257
|
+
const artifactsRoot = getArtifactsDir(sessionFile, customCwd);
|
|
208
258
|
const transcriptPath = path.join(artifactsRoot, "run-restored_reviewer_transcript.jsonl");
|
|
209
259
|
fs.mkdirSync(runDir, { recursive: true });
|
|
210
260
|
fs.mkdirSync(artifactsRoot, { recursive: true });
|
|
261
|
+
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
211
262
|
fs.writeFileSync(transcriptPath, `${JSON.stringify({ recordType: "message", role: "assistant", text: "Restored custom cwd transcript" })}\n`, "utf-8");
|
|
212
263
|
fs.writeFileSync(path.join(runDir, "status.json"), JSON.stringify({
|
|
213
264
|
runId: "run-restored",
|
|
@@ -215,6 +266,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
215
266
|
state: "running",
|
|
216
267
|
sessionId: "session-restored",
|
|
217
268
|
cwd: customCwd,
|
|
269
|
+
sessionFile,
|
|
218
270
|
startedAt: 1000,
|
|
219
271
|
lastUpdate: 2000,
|
|
220
272
|
currentStep: 1,
|
|
@@ -292,8 +344,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
292
344
|
const routeRoot = path.dirname(nestedRoute.eventSink);
|
|
293
345
|
try {
|
|
294
346
|
const runDir = path.join(asyncRoot, "run-restored-nested");
|
|
347
|
+
const statusPath = path.join(runDir, "status.json");
|
|
295
348
|
fs.mkdirSync(runDir, { recursive: true });
|
|
296
|
-
|
|
349
|
+
const statusPayload = {
|
|
297
350
|
runId: "run-restored-nested",
|
|
298
351
|
mode: "single",
|
|
299
352
|
state: "running",
|
|
@@ -301,7 +354,8 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
301
354
|
startedAt: 1000,
|
|
302
355
|
lastUpdate: 2000,
|
|
303
356
|
steps: [{ agent: "worker", status: "running" }],
|
|
304
|
-
}
|
|
357
|
+
};
|
|
358
|
+
fs.writeFileSync(statusPath, JSON.stringify(statusPayload), "utf-8");
|
|
305
359
|
updateActiveRunIndex(runDir, "running");
|
|
306
360
|
const state = createState();
|
|
307
361
|
state.currentSessionId = "session-restored-nested";
|
|
@@ -327,8 +381,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
327
381
|
agent: "nested-worker",
|
|
328
382
|
},
|
|
329
383
|
}), "utf-8");
|
|
384
|
+
fs.writeFileSync(statusPath, JSON.stringify({ ...statusPayload, lastUpdate: 3001 }), "utf-8");
|
|
330
385
|
|
|
331
|
-
await waitForCondition(() => state.asyncJobs.get("run-restored-nested")?.nestedChildren?.[0]?.id === "nested-child", "restored evented nested child refresh",
|
|
386
|
+
await waitForCondition(() => state.asyncJobs.get("run-restored-nested")?.nestedChildren?.[0]?.id === "nested-child", "restored evented nested child refresh", 3000);
|
|
332
387
|
assert.equal(state.asyncJobs.get("run-restored-nested")?.steps?.[0]?.children?.[0]?.id, "nested-child");
|
|
333
388
|
tracker.resetJobs();
|
|
334
389
|
} finally {
|
|
@@ -413,14 +468,16 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
413
468
|
try {
|
|
414
469
|
const runDir = path.join(asyncRoot, "run-nested-events");
|
|
415
470
|
fs.mkdirSync(runDir, { recursive: true });
|
|
416
|
-
|
|
471
|
+
const statusPath = path.join(runDir, "status.json");
|
|
472
|
+
const statusPayload = {
|
|
417
473
|
runId: "run-nested-events",
|
|
418
474
|
mode: "single",
|
|
419
475
|
state: "running",
|
|
420
476
|
startedAt: 1000,
|
|
421
477
|
lastUpdate: 2000,
|
|
422
478
|
steps: [{ agent: "worker", status: "running" }],
|
|
423
|
-
}
|
|
479
|
+
};
|
|
480
|
+
fs.writeFileSync(statusPath, JSON.stringify(statusPayload), "utf-8");
|
|
424
481
|
const state = createState();
|
|
425
482
|
const tracker = createTracker(createEventRecorder().pi, state as never, asyncRoot, { pollIntervalMs: 60_000 });
|
|
426
483
|
tracker.handleStarted({ id: "run-nested-events", asyncDir: runDir, agent: "worker", nestedRoute });
|
|
@@ -443,8 +500,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
443
500
|
agent: "nested-worker",
|
|
444
501
|
},
|
|
445
502
|
}), "utf-8");
|
|
503
|
+
fs.writeFileSync(statusPath, JSON.stringify({ ...statusPayload, lastUpdate: 3000 }), "utf-8");
|
|
446
504
|
|
|
447
|
-
await waitForCondition(() => state.asyncJobs.get("run-nested-events")?.nestedChildren?.[0]?.id === "nested-child", "evented nested child refresh",
|
|
505
|
+
await waitForCondition(() => state.asyncJobs.get("run-nested-events")?.nestedChildren?.[0]?.id === "nested-child", "evented nested child refresh", 3000);
|
|
448
506
|
assert.equal(state.asyncJobs.get("run-nested-events")?.steps?.[0]?.children?.[0]?.id, "nested-child");
|
|
449
507
|
tracker.resetJobs();
|
|
450
508
|
} finally {
|
|
@@ -798,6 +856,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
798
856
|
runId: "run-stale",
|
|
799
857
|
mode: "single",
|
|
800
858
|
state: "running",
|
|
859
|
+
sessionId: "session-stale",
|
|
801
860
|
pid: 12345,
|
|
802
861
|
startedAt: Date.now() - 1000,
|
|
803
862
|
lastUpdate: Date.now() - 1000,
|
|
@@ -815,7 +874,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
815
874
|
now: () => Date.now(),
|
|
816
875
|
});
|
|
817
876
|
tracker.resetJobs(ui.ctx as never);
|
|
818
|
-
tracker.handleStarted({ id: "run-stale", asyncDir: runDir, agent: "worker" });
|
|
877
|
+
tracker.handleStarted({ id: "run-stale", asyncDir: runDir, agent: "worker", sessionId: "session-stale" });
|
|
819
878
|
|
|
820
879
|
await waitForCondition(() => state.asyncJobs.size === 0, "stale async job cleanup");
|
|
821
880
|
|
|
@@ -4,7 +4,7 @@ import * as os from "node:os";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { describe, it } from "node:test";
|
|
6
6
|
import { formatAsyncRunList, listAsyncRuns } from "../../src/runs/background/async-status.ts";
|
|
7
|
-
import { ACTIVE_RUN_INDEX_DIR, updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
|
|
7
|
+
import { ACTIVE_RUN_INDEX_DIR, DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS, updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
|
|
8
8
|
import { claimRunFanoutBatch, createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../../src/runs/shared/run-fanout-budget.ts";
|
|
9
9
|
|
|
10
10
|
function createAsyncDir(root: string, id: string, status: Record<string, unknown>): string {
|
|
@@ -376,6 +376,7 @@ describe("async status helpers", () => {
|
|
|
376
376
|
try {
|
|
377
377
|
const asyncDir = createAsyncDir(root, "run-stale", {
|
|
378
378
|
runId: "run-stale",
|
|
379
|
+
sessionId: "session-stale",
|
|
379
380
|
mode: "single",
|
|
380
381
|
state: "running",
|
|
381
382
|
pid: 12345,
|
|
@@ -640,4 +641,29 @@ describe("async status helpers", () => {
|
|
|
640
641
|
fs.rmSync(root, { recursive: true, force: true });
|
|
641
642
|
}
|
|
642
643
|
});
|
|
644
|
+
|
|
645
|
+
it("prunes old terminal active markers without process-terminal proof", () => {
|
|
646
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-async-status-index-aged-prune-"));
|
|
647
|
+
try {
|
|
648
|
+
const asyncDir = createAsyncDir(root, "finished", {
|
|
649
|
+
runId: "finished",
|
|
650
|
+
mode: "single",
|
|
651
|
+
state: "complete",
|
|
652
|
+
startedAt: 100,
|
|
653
|
+
lastUpdate: 200,
|
|
654
|
+
displayDismissedAt: 250,
|
|
655
|
+
processTerminal: { version: 1, state: "unknown", runId: "finished", runnerProcessInstanceId: "runner", reason: "process-tree-unverified" },
|
|
656
|
+
steps: [{ agent: "worker", status: "complete" }],
|
|
657
|
+
});
|
|
658
|
+
updateActiveRunIndex(asyncDir, "running");
|
|
659
|
+
const markerPath = path.join(root, ACTIVE_RUN_INDEX_DIR, "finished");
|
|
660
|
+
const oldTime = new Date(1_000);
|
|
661
|
+
fs.utimesSync(markerPath, oldTime, oldTime);
|
|
662
|
+
|
|
663
|
+
assert.deepEqual(listAsyncRuns(root, { states: ["running"], reconcile: false, now: () => 1_000 + DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS + 1 }), []);
|
|
664
|
+
assert.equal(fs.existsSync(markerPath), false);
|
|
665
|
+
} finally {
|
|
666
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
667
|
+
}
|
|
668
|
+
});
|
|
643
669
|
});
|
|
@@ -538,7 +538,7 @@ describe("chain execution — sequential", { skip: !available ? "pi packages not
|
|
|
538
538
|
{
|
|
539
539
|
ctx: {
|
|
540
540
|
...makeMinimalCtx(tempDir),
|
|
541
|
-
model: { provider: "github-copilot" },
|
|
541
|
+
model: { provider: "github-copilot", id: "gpt-5-mini" },
|
|
542
542
|
modelRegistry: {
|
|
543
543
|
getAvailable: () => [
|
|
544
544
|
{ provider: "openai", id: "gpt-5-mini" },
|
|
@@ -160,6 +160,55 @@ describe("runSync error handling", { skip: !piAvailable ? "pi packages not avail
|
|
|
160
160
|
assert.ok(result.error?.includes("connection refused"));
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it("kills a wedged foreground tool at the configured per-tool timeout", { skip: process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
164
|
+
mockPi.onCall({
|
|
165
|
+
steps: [
|
|
166
|
+
{ jsonl: [events.toolStart("bash")] },
|
|
167
|
+
{ delay: 30_000 },
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
const agents = makeAgentConfigs(["slow"]);
|
|
171
|
+
|
|
172
|
+
const result = await runSync(tempDir, agents, "slow", "Wait", {
|
|
173
|
+
toolTimeoutMs: 1_000,
|
|
174
|
+
timeoutMs: 8_000,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
assert.equal(result.timedOut, true);
|
|
178
|
+
assert.match(result.error ?? "", /Tool 'bash' exceeded its timeout of 1000ms\./);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("emits foreground open-tool attention for an earlier overlapping tool", async () => {
|
|
182
|
+
mockPi.onCall({
|
|
183
|
+
steps: [
|
|
184
|
+
{ jsonl: [{ type: "tool_execution_start", toolCallId: "bash-1", toolName: "bash", args: { command: "sleep 2" } }] },
|
|
185
|
+
{ delay: 50, jsonl: [
|
|
186
|
+
{ type: "tool_execution_start", toolCallId: "read-1", toolName: "read", args: { path: "README.md" } },
|
|
187
|
+
{ type: "tool_execution_end", toolCallId: "read-1", toolName: "read" },
|
|
188
|
+
] },
|
|
189
|
+
{ delay: 1_200, jsonl: [
|
|
190
|
+
{ type: "tool_execution_end", toolCallId: "bash-1", toolName: "bash" },
|
|
191
|
+
events.toolResult("bash", "done"),
|
|
192
|
+
events.assistantMessage("Done"),
|
|
193
|
+
] },
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
const agents = makeAgentConfigs(["slow"]);
|
|
197
|
+
const controlEvents: Array<{ type?: string; reason?: string; currentTool?: string; message?: string }> = [];
|
|
198
|
+
|
|
199
|
+
const result = await runSync(tempDir, agents, "slow", "Wait", {
|
|
200
|
+
runId: "foreground-overlap-attention",
|
|
201
|
+
controlConfig: { enabled: true, needsAttentionAfterMs: 999_999, activeNoticeAfterMs: 100, notifyOn: ["needs_attention"] },
|
|
202
|
+
onControlEvent: (event: { type?: string; reason?: string; currentTool?: string; message?: string }) => controlEvents.push(event),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const attention = controlEvents.find((event) => event.reason === "tool_open_threshold");
|
|
206
|
+
assert.equal(result.exitCode, 0);
|
|
207
|
+
assert.equal(attention?.type, "needs_attention");
|
|
208
|
+
assert.equal(attention?.currentTool, "bash");
|
|
209
|
+
assert.match(attention?.message ?? "", /tool 'bash' open/);
|
|
210
|
+
});
|
|
211
|
+
|
|
163
212
|
it("handles abort signal (completes faster than delay)", async () => {
|
|
164
213
|
mockPi.onCall({ delay: 10000 });
|
|
165
214
|
const agents = makeAgentConfigs(["slow"]);
|
|
@@ -286,6 +286,30 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
286
286
|
assert.doesNotMatch(result.content[0]?.text ?? "", /Delivered single subagent result via intercom\./);
|
|
287
287
|
});
|
|
288
288
|
|
|
289
|
+
it("keeps a workflow child bridge override isolated and preserves final output", async () => {
|
|
290
|
+
mockPi.onCall({ matchArgIncludes: "isolated task", output: "Isolated child output" });
|
|
291
|
+
mockPi.onCall({ matchArgIncludes: "normal task", output: "Normal child output" });
|
|
292
|
+
const { executor, events } = makeExecutor({ resultDelivery: true, agents: [makeAgent("worker", { tools: ["read"] })] });
|
|
293
|
+
|
|
294
|
+
const result = await executor.execute(
|
|
295
|
+
"workflow-intercom-override",
|
|
296
|
+
{
|
|
297
|
+
workflowScript: `const isolated = await runs.run("isolated", { agent: "worker", task: "isolated task", intercomBridge: { mode: "off" } }); const normal = await runs.run("normal", { agent: "worker", task: "normal task" }); return { isolated: isolated.output, normal: normal.output };`,
|
|
298
|
+
async: false,
|
|
299
|
+
},
|
|
300
|
+
new AbortController().signal,
|
|
301
|
+
undefined,
|
|
302
|
+
makeMinimalCtx(tempDir),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const isolatedArgs = await readMockCallArgs(0);
|
|
306
|
+
const normalArgs = await readMockCallArgs(1);
|
|
307
|
+
assert.equal(isolatedArgs[isolatedArgs.indexOf("--tools") + 1], "read");
|
|
308
|
+
assert.equal(normalArgs[normalArgs.indexOf("--tools") + 1], "read,contact_supervisor");
|
|
309
|
+
assert.equal(events.emitted.filter((entry) => entry.channel === "subagent:result-intercom").length, 1);
|
|
310
|
+
assert.deepEqual(result.details?.workflow?.value, { isolated: "Isolated child output", normal: "Normal child output" });
|
|
311
|
+
});
|
|
312
|
+
|
|
289
313
|
it("waits for retained workflow resume loops and returns each completed revived child", async () => {
|
|
290
314
|
mockPi.onCall({ output: "Completed retained follow-up one" });
|
|
291
315
|
mockPi.onCall({ output: "Completed retained follow-up two" });
|
|
@@ -959,7 +983,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
959
983
|
}
|
|
960
984
|
});
|
|
961
985
|
|
|
962
|
-
it("revives
|
|
986
|
+
it("revives a removed agent with its persisted bridge override", async () => {
|
|
963
987
|
mockPi.onCall({ output: "descriptor-backed answer" });
|
|
964
988
|
const runId = `resume-descriptor-${Date.now()}`;
|
|
965
989
|
const asyncDir = path.join(ASYNC_DIR, runId);
|
|
@@ -976,6 +1000,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
976
1000
|
model: "anthropic/claude-sonnet-4:high",
|
|
977
1001
|
tools: ["read"],
|
|
978
1002
|
systemPrompt: "Original persisted prompt",
|
|
1003
|
+
intercomBridge: { mode: "off" },
|
|
979
1004
|
maxSubagentDepth: 1,
|
|
980
1005
|
});
|
|
981
1006
|
const { executor } = makeExecutor({ agents: [] });
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { afterEach, describe, it } from "node:test";
|
|
9
|
+
import { TEMP_ROOT_DIR } from "../../src/shared/types.ts";
|
|
10
|
+
import { writeNodeCommand } from "../support/node-command.ts";
|
|
11
|
+
|
|
12
|
+
const tempDirs: string[] = [];
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
const progressDir = path.join(TEMP_ROOT_DIR, "orca-progress");
|
|
15
|
+
for (const dir of tempDirs.splice(0)) {
|
|
16
|
+
const key = createHash("sha256").update(path.resolve(dir)).digest("hex").slice(0, 20);
|
|
17
|
+
fs.rmSync(path.join(progressDir, `counter-${key}`), { force: true });
|
|
18
|
+
fs.rmSync(path.join(progressDir, `counter-${key}.lock`), { recursive: true, force: true });
|
|
19
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
if (fs.existsSync(progressDir)) {
|
|
22
|
+
for (const name of fs.readdirSync(progressDir)) {
|
|
23
|
+
if (name.startsWith("orca-observer-native-") || name.startsWith("concurrent-sequence-")) fs.rmSync(path.join(progressDir, name), { force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function runProcess(command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<number | null> {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const child = spawn(command, args, { cwd, stdio: "inherit", shell: false, env });
|
|
31
|
+
child.once("error", reject);
|
|
32
|
+
child.once("close", resolve);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function waitForFile(file: string): Promise<void> {
|
|
37
|
+
const deadline = Date.now() + 5_000;
|
|
38
|
+
while (!fs.existsSync(file)) {
|
|
39
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${file}`);
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function waitForFileCount(dir: string, count: number): Promise<void> {
|
|
45
|
+
const deadline = Date.now() + 5_000;
|
|
46
|
+
while (fs.readdirSync(dir).length < count) {
|
|
47
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${count} files in ${dir}`);
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("Orca progress-tab observer", () => {
|
|
53
|
+
it("mirrors a native Pi child without replacing its execution path", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
54
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-native-"));
|
|
55
|
+
tempDirs.push(dir);
|
|
56
|
+
const asyncDir = path.join(dir, "async");
|
|
57
|
+
const agentDir = path.join(dir, "agent-dir");
|
|
58
|
+
const capture = path.join(dir, "orca-args.json");
|
|
59
|
+
const fakeOrca = writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_CAPTURE, JSON.stringify(process.argv.slice(2)))");
|
|
60
|
+
fs.mkdirSync(asyncDir);
|
|
61
|
+
fs.mkdirSync(path.join(agentDir, "extensions", "subagent"), { recursive: true });
|
|
62
|
+
fs.writeFileSync(path.join(agentDir, "extensions", "subagent", "config.json"), JSON.stringify({ orcaProgressTabs: { enabled: true } }));
|
|
63
|
+
const childEvents = [
|
|
64
|
+
{ type: "tool_execution_start", toolName: "read", args: { path: "README.md" } },
|
|
65
|
+
{ type: "tool_execution_end", toolName: "read" },
|
|
66
|
+
{ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "native Pi result" }], stopReason: "stop", usage: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0, cost: { total: 0 } } } },
|
|
67
|
+
{ type: "agent_settled" },
|
|
68
|
+
];
|
|
69
|
+
const fakePi = writeNodeCommand(dir, "pi", `for (const event of ${JSON.stringify(childEvents)}) process.stdout.write(JSON.stringify(event)+'\\n')`);
|
|
70
|
+
|
|
71
|
+
const resultPath = path.join(dir, "result.json");
|
|
72
|
+
const configPath = path.join(dir, "config.json");
|
|
73
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
74
|
+
id: "orca-observer-native",
|
|
75
|
+
sessionId: "session-orca-native",
|
|
76
|
+
steps: [{
|
|
77
|
+
agent: "worker",
|
|
78
|
+
task: "Read the repository",
|
|
79
|
+
systemPrompt: "Use native Pi",
|
|
80
|
+
systemPromptMode: "replace",
|
|
81
|
+
inheritProjectContext: false,
|
|
82
|
+
inheritSkills: false,
|
|
83
|
+
}],
|
|
84
|
+
resultPath,
|
|
85
|
+
cwd: dir,
|
|
86
|
+
placeholder: "{previous}",
|
|
87
|
+
artifactConfig: { enabled: false },
|
|
88
|
+
asyncDir,
|
|
89
|
+
resultMode: "single",
|
|
90
|
+
}));
|
|
91
|
+
const repo = path.resolve(import.meta.dirname, "../..");
|
|
92
|
+
const exitCode = await runProcess(
|
|
93
|
+
process.execPath,
|
|
94
|
+
[path.join(path.dirname(fileURLToPath(import.meta.resolve("jiti/package.json"))), "lib", "jiti-cli.mjs"), path.join(repo, "src/runs/background/subagent-runner.ts"), configPath],
|
|
95
|
+
repo,
|
|
96
|
+
{
|
|
97
|
+
...process.env,
|
|
98
|
+
SELESAI_CODING_AGENT_DIR: agentDir,
|
|
99
|
+
SELESAI_SUBAGENT_ORCA_BINARY: fakeOrca,
|
|
100
|
+
SELESAI_SUBAGENT_PI_BINARY: fakePi,
|
|
101
|
+
ORCA_TEST_CAPTURE: capture,
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
assert.equal(exitCode, 0);
|
|
105
|
+
const result = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
|
|
106
|
+
assert.equal(result.success, true);
|
|
107
|
+
assert.equal(result.results[0].runner, undefined);
|
|
108
|
+
assert.match(result.results[0].output, /native Pi result/);
|
|
109
|
+
assert.match(fs.readFileSync(path.join(asyncDir, "output-0.log"), "utf-8"), /read: README\.md[\s\S]*native Pi result/);
|
|
110
|
+
|
|
111
|
+
await waitForFile(capture);
|
|
112
|
+
const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
|
|
113
|
+
assert.deepEqual(args.slice(0, 2), ["terminal", "create"]);
|
|
114
|
+
assert.equal(args[args.indexOf("--worktree") + 1], `path:${path.resolve(dir)}`);
|
|
115
|
+
assert.equal(args[args.indexOf("--title") + 1], "subagent · worker · 1");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("allocates unique worktree-wide numbers across concurrent processes and nested cwd values", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
|
|
119
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-counter-"));
|
|
120
|
+
tempDirs.push(dir);
|
|
121
|
+
fs.mkdirSync(path.join(dir, ".git"));
|
|
122
|
+
const nested = path.join(dir, "packages", "app");
|
|
123
|
+
fs.mkdirSync(nested, { recursive: true });
|
|
124
|
+
const captures = path.join(dir, "captures");
|
|
125
|
+
fs.mkdirSync(captures);
|
|
126
|
+
const fakeOrca = writeNodeCommand(dir, "orca", "const fs=require('fs'),path=require('path');const args=process.argv.slice(2);fs.writeFileSync(path.join(process.env.ORCA_TEST_CAPTURE_DIR, process.pid+'.json'),JSON.stringify(args))");
|
|
127
|
+
const repo = path.resolve(import.meta.dirname, "../..");
|
|
128
|
+
const moduleUrl = new URL("../../src/runs/shared/orca-progress-tabs.ts", import.meta.url).href;
|
|
129
|
+
const childScript = `import {createOrcaProgressTab} from ${JSON.stringify(moduleUrl)};const tab=createOrcaProgressTab({cwd:process.env.CHILD_CWD,runId:'concurrent-sequence',agent:'worker',index:0,config:{enabled:true}});if(!tab)throw new Error('tab unavailable');setTimeout(()=>tab.finish('failed'),100);setTimeout(()=>{},180);`;
|
|
130
|
+
const processes = Array.from({ length: 8 }, (_, index) => runProcess(
|
|
131
|
+
process.execPath,
|
|
132
|
+
["--experimental-strip-types", "--input-type=module", "--eval", childScript],
|
|
133
|
+
repo,
|
|
134
|
+
{ ...process.env, SELESAI_SUBAGENT_ORCA_BINARY: fakeOrca, ORCA_TEST_CAPTURE_DIR: captures, CHILD_CWD: index % 2 === 0 ? dir : nested },
|
|
135
|
+
));
|
|
136
|
+
assert.deepEqual(await Promise.all(processes), Array(8).fill(0));
|
|
137
|
+
await waitForFileCount(captures, 8);
|
|
138
|
+
const titles = fs.readdirSync(captures).map((name) => {
|
|
139
|
+
const args = JSON.parse(fs.readFileSync(path.join(captures, name), "utf-8")) as string[];
|
|
140
|
+
return args[args.indexOf("--title") + 1];
|
|
141
|
+
}).sort((left, right) => Number(left.split(" · ").at(-1)) - Number(right.split(" · ").at(-1)));
|
|
142
|
+
assert.deepEqual(titles, Array.from({ length: 8 }, (_, index) => `subagent · worker · ${index + 1}`));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -209,6 +209,32 @@ describe("subagent async widget rendering", () => {
|
|
|
209
209
|
assert.ok(lines.length <= 10, "collapsed component should stay under Pi's string-widget cap even though it bypasses it");
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
it("uses a bounded string-array snapshot widget in RPC mode", () => {
|
|
213
|
+
const ui = createUiContext();
|
|
214
|
+
(ui.ctx as { mode?: string }).mode = "rpc";
|
|
215
|
+
renderWidget(ui.ctx as never, [{
|
|
216
|
+
asyncId: "run-rpc",
|
|
217
|
+
asyncDir: "/tmp/private-run-rpc",
|
|
218
|
+
cwd: "/repo/private",
|
|
219
|
+
status: "running",
|
|
220
|
+
mode: "single",
|
|
221
|
+
agents: ["worker"],
|
|
222
|
+
currentTool: "bash",
|
|
223
|
+
steps: [{ agent: "worker", status: "running", currentToolArgs: "private args" }],
|
|
224
|
+
}]);
|
|
225
|
+
|
|
226
|
+
const widget = ui.widgets.at(-1);
|
|
227
|
+
assert.ok(Array.isArray(widget), "RPC mode should install a string-array widget payload");
|
|
228
|
+
const [line] = widget as string[];
|
|
229
|
+
assert.ok(line?.startsWith("SELESAI_SUBAGENT_ASYNC_JSON:"));
|
|
230
|
+
const snapshot = JSON.parse(line.slice("SELESAI_SUBAGENT_ASYNC_JSON:".length));
|
|
231
|
+
assert.equal(snapshot.kind, "pi-subagents.async-status-snapshot");
|
|
232
|
+
assert.equal(snapshot.version, 1);
|
|
233
|
+
assert.equal(snapshot.runs[0].id, "run-rpc");
|
|
234
|
+
assert.equal(JSON.stringify(snapshot).includes("/tmp/private-run-rpc"), false);
|
|
235
|
+
assert.equal(JSON.stringify(snapshot).includes("private args"), false);
|
|
236
|
+
});
|
|
237
|
+
|
|
212
238
|
it("honors the component render width instead of the terminal width", () => {
|
|
213
239
|
resetWidgetLayout();
|
|
214
240
|
withStdoutSize(50, 120, () => {
|