@selesai/code 0.13.12 → 0.13.14
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 +9 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
- package/dist/extensions/pi-subagents/VISION.md +12 -1
- package/dist/extensions/pi-subagents/docs/agents.md +13 -6
- package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
- package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
- package/dist/extensions/pi-subagents/docs/missions.md +1 -1
- package/dist/extensions/pi-subagents/docs/models.md +6 -6
- package/dist/extensions/pi-subagents/docs/observability.md +6 -3
- package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
- package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/install.mjs +1 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
- package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
- package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
- package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
- package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
- package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
- package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
- package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
- package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
- package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
- package/dist/extensions/pi-zentui/README.md +19 -5
- package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
- package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
- package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
- package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
- package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
- package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
- package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
- package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
- package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
- package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
- package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
- package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
- package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
- package/dist/skills/brandkit/SKILL.md +0 -2
- package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
- package/dist/skills/gpt-taste/SKILL.md +0 -2
- package/dist/skills/image-to-code/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
- package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
- package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
- package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
- package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
- package/dist/skills/stitch/DESIGN.md +121 -0
- package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
- package/package.json +1 -1
|
@@ -252,10 +252,23 @@ describe("Herdr inspector", () => {
|
|
|
252
252
|
assert.match(dashboard, /decision-1: Choose UX/);
|
|
253
253
|
assert.match(dashboard, /closing it does not stop the run/i);
|
|
254
254
|
|
|
255
|
-
assert.match(submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "steer keep going"), /Steering queued for run run-123
|
|
255
|
+
assert.match(submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "steer keep going"), /Steering queued for run run-123\.\n\nMessage sent:\n```text\nkeep going\n```/);
|
|
256
256
|
assert.deepEqual(consumeSteerRequests(asyncDir).map((request) => ({ message: request.message, targetIndex: request.targetIndex, source: request.source })), [
|
|
257
257
|
{ message: "keep going", targetIndex: 0, source: "herdr-inspector" },
|
|
258
258
|
]);
|
|
259
|
+
assert.match(submitInspectorControl({ asyncDir, runId: "run-123", index: 0, refreshMs: 1_500 }, "keep going without a prefix"), /Steering queued for run run-123/);
|
|
260
|
+
assert.deepEqual(consumeSteerRequests(asyncDir).map((request) => ({ message: request.message, targetIndex: request.targetIndex, source: request.source })), [
|
|
261
|
+
{ message: "keep going without a prefix", targetIndex: 0, source: "herdr-inspector" },
|
|
262
|
+
]);
|
|
263
|
+
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({ ...status, mode: "parallel", steps: [...status.steps!, { agent: "reviewer", status: "running" }] }), "utf-8");
|
|
264
|
+
const aggregateDashboard = formatInspectorDashboard({ status: { ...status, mode: "parallel", steps: [...status.steps!, { agent: "reviewer", status: "running" }] }, asyncDir });
|
|
265
|
+
assert.match(aggregateDashboard, /Controls: steer <message> \| stop \| status/);
|
|
266
|
+
assert.doesNotMatch(aggregateDashboard, /type guidance/);
|
|
267
|
+
assert.throws(() => submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "ambiguous plain guidance"), /Plain guidance requires a child-specific inspector/);
|
|
268
|
+
assert.match(submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "steer broadcast guidance"), /Steering queued for run run-123/);
|
|
269
|
+
assert.deepEqual(consumeSteerRequests(asyncDir).map((request) => ({ message: request.message, targetIndexes: request.targetIndexes, source: request.source })), [
|
|
270
|
+
{ message: "broadcast guidance", targetIndexes: [0, 1], source: "herdr-inspector" },
|
|
271
|
+
]);
|
|
259
272
|
assert.match(submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "stop"), /Stop requested/);
|
|
260
273
|
assert.equal(consumeStopRequest(asyncDir), true);
|
|
261
274
|
assert.throws(() => submitInspectorControl({ asyncDir, runId: "run-123", refreshMs: 1_500 }, "reply decision-1 yes"), /parent Pi session/);
|
|
@@ -369,6 +382,8 @@ describe("Herdr inspector", () => {
|
|
|
369
382
|
|
|
370
383
|
it("fails closed when an idle-only project pane close sees active work", async () => {
|
|
371
384
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-herdr-project-pane-busy-"));
|
|
385
|
+
const previousPiBinary = process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV];
|
|
386
|
+
process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV] = path.join(root, "pi-bin");
|
|
372
387
|
try {
|
|
373
388
|
const projectRoot = fs.realpathSync(root);
|
|
374
389
|
const calls: string[][] = [];
|
|
@@ -394,12 +409,16 @@ describe("Herdr inspector", () => {
|
|
|
394
409
|
assert.equal(calls.some((args) => args.join(" ") === "pane close w1:p11"), false);
|
|
395
410
|
assert.equal(readHerdrProjectPaneBinding(root)?.paneId, "w1:p11");
|
|
396
411
|
} finally {
|
|
412
|
+
if (previousPiBinary === undefined) delete process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV];
|
|
413
|
+
else process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV] = previousPiBinary;
|
|
397
414
|
fs.rmSync(root, { recursive: true, force: true });
|
|
398
415
|
}
|
|
399
416
|
});
|
|
400
417
|
|
|
401
418
|
it("preserves legacy model-facing recovery for transient and opaque pane inspection results", async () => {
|
|
402
419
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-herdr-project-pane-legacy-"));
|
|
420
|
+
const previousPiBinary = process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV];
|
|
421
|
+
process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV] = path.join(root, "pi-bin");
|
|
403
422
|
try {
|
|
404
423
|
let splitCount = 0;
|
|
405
424
|
let getMode: "valid" | "timeout" | "opaque" = "valid";
|
|
@@ -436,6 +455,8 @@ describe("Herdr inspector", () => {
|
|
|
436
455
|
assert.match(text(closed), /INVALID_PANE_RESPONSE/);
|
|
437
456
|
assert.equal(readHerdrProjectPaneBinding(root)?.paneId, "w1:p32");
|
|
438
457
|
} finally {
|
|
458
|
+
if (previousPiBinary === undefined) delete process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV];
|
|
459
|
+
else process.env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV] = previousPiBinary;
|
|
439
460
|
fs.rmSync(root, { recursive: true, force: true });
|
|
440
461
|
}
|
|
441
462
|
});
|
|
@@ -485,7 +485,7 @@ describe("subagent extension child mode", () => {
|
|
|
485
485
|
}
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
-
it("registers bg_wait and
|
|
488
|
+
it("registers bg_wait and honors waitTool disabled config", () => {
|
|
489
489
|
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-wait-tool-config-"));
|
|
490
490
|
try {
|
|
491
491
|
const configDir = path.join(agentDir, "extensions", "subagent");
|
|
@@ -496,13 +496,11 @@ describe("subagent extension child mode", () => {
|
|
|
496
496
|
import registerSubagentExtension from "./index.ts";
|
|
497
497
|
const events = { on() { return () => {}; }, emit() {} };
|
|
498
498
|
let bgWaitTool;
|
|
499
|
-
let subagentWaitTool;
|
|
500
499
|
let legacyWaitRegistered = false;
|
|
501
500
|
const fakePi = new Proxy({
|
|
502
501
|
events,
|
|
503
502
|
registerTool(tool) {
|
|
504
503
|
if (tool.name === "bg_wait") bgWaitTool = tool;
|
|
505
|
-
if (tool.name === "subagent_wait") subagentWaitTool = tool;
|
|
506
504
|
if (tool.name === "wait") legacyWaitRegistered = true;
|
|
507
505
|
},
|
|
508
506
|
registerCommand() {},
|
|
@@ -517,11 +515,9 @@ describe("subagent extension child mode", () => {
|
|
|
517
515
|
},
|
|
518
516
|
});
|
|
519
517
|
registerSubagentExtension(fakePi);
|
|
520
|
-
if (!bgWaitTool
|
|
518
|
+
if (!bgWaitTool) throw new Error("bg_wait tool not registered");
|
|
521
519
|
if (legacyWaitRegistered) throw new Error("legacy wait tool must not be registered");
|
|
522
520
|
const result = await bgWaitTool.execute("bg-wait-disabled", {}, new AbortController().signal, undefined, {});
|
|
523
|
-
const aliasResult = await subagentWaitTool.execute("subagent-wait-disabled", {}, new AbortController().signal, undefined, {});
|
|
524
|
-
if (aliasResult.content[0].text !== result.content[0].text) throw new Error("subagent_wait alias behavior differs from bg_wait");
|
|
525
521
|
process.stdout.write(JSON.stringify(result.content[0].text));
|
|
526
522
|
`;
|
|
527
523
|
|
|
@@ -709,6 +709,20 @@ describe("completion formatting helpers", () => {
|
|
|
709
709
|
assert.equal(details.agent, "unknown");
|
|
710
710
|
assert.equal(details.status, "completed");
|
|
711
711
|
});
|
|
712
|
+
|
|
713
|
+
it("surfaces direct structured output when the completion has no text output", () => {
|
|
714
|
+
const details = buildCompletionDetails({
|
|
715
|
+
id: "structured-run",
|
|
716
|
+
agent: "delegate",
|
|
717
|
+
success: true,
|
|
718
|
+
summary: "delegate:\n(no output)",
|
|
719
|
+
results: [{ agent: "delegate", output: "", structuredOutput: { payload: { ok: true } }, success: true }],
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
assert.match(details.resultPreview, /Structured output:/);
|
|
723
|
+
assert.match(details.resultPreview, /"ok": true/);
|
|
724
|
+
assert.match(formatSingleCompletion(details), /"ok": true/);
|
|
725
|
+
});
|
|
712
726
|
});
|
|
713
727
|
|
|
714
728
|
describe("scheduled completions", () => {
|
|
@@ -748,4 +762,55 @@ describe("scheduled completions", () => {
|
|
|
748
762
|
assert.equal(parsed?.scheduleOrigin, undefined);
|
|
749
763
|
assert.equal(parsed?.resultPreview, "Workflow completed with 1 child run(s).");
|
|
750
764
|
});
|
|
751
|
-
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
describe("watchdog blockers in completion notices", () => {
|
|
768
|
+
it("collects child blockers into details and round-trips them through the notice text", () => {
|
|
769
|
+
const details = buildCompletionDetails({
|
|
770
|
+
id: "run-1",
|
|
771
|
+
agent: "workflow",
|
|
772
|
+
mode: "workflow",
|
|
773
|
+
runId: "run-1",
|
|
774
|
+
success: false,
|
|
775
|
+
summary: "worker:\nPatched billing.",
|
|
776
|
+
exitCode: 1,
|
|
777
|
+
results: [
|
|
778
|
+
{
|
|
779
|
+
runId: "child-1",
|
|
780
|
+
agent: "worker",
|
|
781
|
+
success: false,
|
|
782
|
+
watchdog: {
|
|
783
|
+
phase: "idle",
|
|
784
|
+
seq: 3,
|
|
785
|
+
lastUpdate: 1,
|
|
786
|
+
warnings: [
|
|
787
|
+
{ severity: "blocker", category: "test-gap", summary: "Claims tests passed without running them", evidence: "e", recommendedAction: "r", addressed: false, stalemate: false },
|
|
788
|
+
{ severity: "concern", category: "other", summary: "Concern is not listed", evidence: "e", recommendedAction: "r", addressed: true, stalemate: false },
|
|
789
|
+
{ severity: "blocker", category: "scope-drift", summary: "Kept editing after being told to stop", evidence: "e", recommendedAction: "r", addressed: false, stalemate: true },
|
|
790
|
+
],
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
{ runId: "child-2", agent: "reviewer", success: true, output: "clean" },
|
|
794
|
+
],
|
|
795
|
+
sessionId: "session-1",
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
assert.deepEqual(details.watchdogBlockers, [
|
|
799
|
+
{ agent: "worker", summary: "Claims tests passed without running them", addressed: false, stalemate: false },
|
|
800
|
+
{ agent: "worker", summary: "Kept editing after being told to stop", addressed: false, stalemate: true },
|
|
801
|
+
]);
|
|
802
|
+
|
|
803
|
+
const content = formatSingleCompletion(details);
|
|
804
|
+
assert.match(content, /\nWatchdog blockers:\n- worker: Claims tests passed without running them \(unaddressed\)\n- worker: Kept editing after being told to stop \(stalemate\)\n/);
|
|
805
|
+
const parsed = parseSubagentNotifyContent(content);
|
|
806
|
+
assert.deepEqual(parsed?.watchdogBlockers, details.watchdogBlockers);
|
|
807
|
+
assert.match(parsed?.resultPreview ?? "", /^worker:\nPatched billing\./);
|
|
808
|
+
assert.doesNotMatch(parsed?.resultPreview ?? "", /Watchdog blockers/);
|
|
809
|
+
assert.equal(parsed?.workflowRunId, "run-1");
|
|
810
|
+
|
|
811
|
+
const grouped = formatGroupedCompletion([details, { agent: "scout", status: "completed", resultPreview: "ok" }]);
|
|
812
|
+
assert.match(grouped, /Watchdog blockers:\n- worker: Claims tests passed without running them \(unaddressed\)/);
|
|
813
|
+
assert.equal(grouped.split("Watchdog blockers:").length, 2);
|
|
814
|
+
|
|
815
|
+
});
|
|
816
|
+
});
|
|
@@ -56,7 +56,7 @@ afterEach(() => {
|
|
|
56
56
|
function readPromptFile(result: { tempDir?: string }): string {
|
|
57
57
|
assert.ok(result.tempDir, "expected a temp dir from buildPiArgs");
|
|
58
58
|
const files = fs.readdirSync(result.tempDir);
|
|
59
|
-
const promptFile = files.find((f: string) => f.endsWith(".md"));
|
|
59
|
+
const promptFile = files.find((f: string) => f.endsWith(".md") && f !== "task.md");
|
|
60
60
|
assert.ok(promptFile, "expected a prompt.md file");
|
|
61
61
|
return fs.readFileSync(path.join(result.tempDir, promptFile), "utf-8");
|
|
62
62
|
}
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from "../../src/runs/shared/tool-budget.ts";
|
|
17
17
|
import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
|
|
18
18
|
import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts";
|
|
19
|
+
import { STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV } from "../../src/runs/shared/structured-output.ts";
|
|
20
|
+
|
|
19
21
|
import {
|
|
20
22
|
CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
|
|
21
23
|
MCP_DIRECT_CHILD_TOOLS_ENV,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
buildPiArgs,
|
|
47
49
|
projectLaunchResolvedChildExtensions,
|
|
48
50
|
resolvePiLaunchToolPlan,
|
|
51
|
+
shouldDeliverTaskViaFile,
|
|
49
52
|
} from "../../src/runs/shared/pi-args.ts";
|
|
50
53
|
|
|
51
54
|
const originalEnv = {
|
|
@@ -457,7 +460,6 @@ describe("buildPiArgs session wiring", () => {
|
|
|
457
460
|
inheritProjectContext: false,
|
|
458
461
|
inheritSkills: false,
|
|
459
462
|
childWatchdog: {
|
|
460
|
-
enabled: true,
|
|
461
463
|
runId: "run-1",
|
|
462
464
|
agent: "worker",
|
|
463
465
|
childIndex: 2,
|
|
@@ -465,15 +467,13 @@ describe("buildPiArgs session wiring", () => {
|
|
|
465
467
|
agentEndTimeoutMs: 500,
|
|
466
468
|
maxWarnings: 1,
|
|
467
469
|
lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 },
|
|
468
|
-
autoFollowBlockers: true,
|
|
469
|
-
autoFollowMaxAttempts: 3,
|
|
470
470
|
stalemateRepeats: 2,
|
|
471
|
+
cadence: { everyNTools: null },
|
|
471
472
|
},
|
|
472
473
|
});
|
|
473
474
|
const encoded = withWatchdog.env[CHILD_WATCHDOG_CONFIG_ENV];
|
|
474
475
|
assert.equal(typeof encoded, "string");
|
|
475
476
|
assert.deepEqual(JSON.parse(encoded ?? "{}"), {
|
|
476
|
-
enabled: true,
|
|
477
477
|
runId: "run-1",
|
|
478
478
|
agent: "worker",
|
|
479
479
|
childIndex: 2,
|
|
@@ -481,10 +481,67 @@ describe("buildPiArgs session wiring", () => {
|
|
|
481
481
|
agentEndTimeoutMs: 500,
|
|
482
482
|
maxWarnings: 1,
|
|
483
483
|
lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 },
|
|
484
|
-
autoFollowBlockers: true,
|
|
485
|
-
autoFollowMaxAttempts: 3,
|
|
486
484
|
stalemateRepeats: 2,
|
|
485
|
+
cadence: { everyNTools: null },
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe("excludeTools resolution", () => {
|
|
491
|
+
it("filters excluded names from explicit allowlists and runtime-injected tools", () => {
|
|
492
|
+
const plan = resolvePiLaunchToolPlan({
|
|
493
|
+
tools: ["read", "write"],
|
|
494
|
+
excludeTools: ["write", "structured_output"],
|
|
495
|
+
structuredOutput: true,
|
|
496
|
+
});
|
|
497
|
+
assert.deepEqual(plan.declaredBuiltinTools, ["read", "write"]);
|
|
498
|
+
assert.deepEqual(plan.excludeTools, ["write", "structured_output"]);
|
|
499
|
+
assert.deepEqual(plan.effectiveToolAllowlist, ["read"]);
|
|
500
|
+
assert.deepEqual(plan.internalTools, []);
|
|
501
|
+
assert.deepEqual(plan.requiredChildTools, ["read"]);
|
|
502
|
+
|
|
503
|
+
const built = buildPiArgs({
|
|
504
|
+
baseArgs: [],
|
|
505
|
+
task: "hello",
|
|
506
|
+
sessionEnabled: true,
|
|
507
|
+
inheritProjectContext: true,
|
|
508
|
+
inheritSkills: true,
|
|
509
|
+
tools: ["read", "write"],
|
|
510
|
+
excludeTools: ["write"],
|
|
511
|
+
});
|
|
512
|
+
assert.deepEqual(built.args.slice(0, 2), ["--tools", "read"]);
|
|
513
|
+
assert.equal(built.args.includes("--exclude-tools"), false);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("forwards exclusions to Pi without inventing an ambient allowlist", () => {
|
|
517
|
+
const built = buildPiArgs({
|
|
518
|
+
baseArgs: [],
|
|
519
|
+
task: "hello",
|
|
520
|
+
sessionEnabled: true,
|
|
521
|
+
inheritProjectContext: true,
|
|
522
|
+
inheritSkills: true,
|
|
523
|
+
excludeTools: ["write", "unknown_tool"],
|
|
524
|
+
});
|
|
525
|
+
assert.deepEqual(built.args.slice(0, 2), ["--exclude-tools", "write,unknown_tool"]);
|
|
526
|
+
assert.equal(built.args.includes("--tools"), false);
|
|
527
|
+
assert.equal(built.args.includes("--no-tools"), false);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("keeps empty tool sets all-off and makes excluded nested fanout unavailable", () => {
|
|
531
|
+
const empty = buildPiArgs({
|
|
532
|
+
baseArgs: [],
|
|
533
|
+
task: "hello",
|
|
534
|
+
sessionEnabled: true,
|
|
535
|
+
inheritProjectContext: true,
|
|
536
|
+
inheritSkills: true,
|
|
537
|
+
tools: [],
|
|
538
|
+
excludeTools: ["write"],
|
|
487
539
|
});
|
|
540
|
+
assert.equal(empty.args.includes("--no-tools"), true);
|
|
541
|
+
assert.equal(empty.args.includes("--exclude-tools"), false);
|
|
542
|
+
|
|
543
|
+
const plan = resolvePiLaunchToolPlan({ allowNestedSubagents: true, excludeTools: ["subagent"] });
|
|
544
|
+
assert.equal(plan.fanoutAuthorized, false);
|
|
488
545
|
});
|
|
489
546
|
});
|
|
490
547
|
|
|
@@ -634,7 +691,7 @@ describe("buildPiArgs task delivery", () => {
|
|
|
634
691
|
return ref ? ref.slice(1) : undefined;
|
|
635
692
|
}
|
|
636
693
|
|
|
637
|
-
it("delivers short tasks
|
|
694
|
+
it("delivers short tasks through the platform default", () => {
|
|
638
695
|
const { args } = buildPiArgs({
|
|
639
696
|
baseArgs: ["-p"],
|
|
640
697
|
task: "hello",
|
|
@@ -643,8 +700,13 @@ describe("buildPiArgs task delivery", () => {
|
|
|
643
700
|
inheritSkills: false,
|
|
644
701
|
});
|
|
645
702
|
|
|
646
|
-
|
|
647
|
-
|
|
703
|
+
if (process.platform === "darwin") {
|
|
704
|
+
assert.ok(taskFileFromArgs(args), "expected an @task.md argv reference on macOS");
|
|
705
|
+
assert.ok(!args.includes("Task: hello"));
|
|
706
|
+
} else {
|
|
707
|
+
assert.ok(args.includes("Task: hello"));
|
|
708
|
+
assert.equal(taskFileFromArgs(args), undefined);
|
|
709
|
+
}
|
|
648
710
|
});
|
|
649
711
|
|
|
650
712
|
it("delivers tasks over the argv limit via a temp file by default", () => {
|
|
@@ -681,6 +743,7 @@ describe("buildPiArgs task delivery", () => {
|
|
|
681
743
|
|
|
682
744
|
it("falls back to auto when SELESAI_SUBAGENT_TASK_DELIVERY is invalid", () => {
|
|
683
745
|
process.env.SELESAI_SUBAGENT_TASK_DELIVERY = "carrier-pigeon";
|
|
746
|
+
|
|
684
747
|
const { args } = buildPiArgs({
|
|
685
748
|
baseArgs: ["-p"],
|
|
686
749
|
task: longTask,
|
|
@@ -706,6 +769,13 @@ describe("buildPiArgs task delivery", () => {
|
|
|
706
769
|
assert.ok(taskFileFromArgs(args), "expected an @task.md argv reference");
|
|
707
770
|
assert.ok(!args.includes("Task: hello"));
|
|
708
771
|
});
|
|
772
|
+
|
|
773
|
+
it("selects file delivery for macOS or over-limit tasks", () => {
|
|
774
|
+
assert.equal(shouldDeliverTaskViaFile("short", "auto", "darwin"), true);
|
|
775
|
+
assert.equal(shouldDeliverTaskViaFile("short", "auto", "linux"), false);
|
|
776
|
+
assert.equal(shouldDeliverTaskViaFile("short", "auto", "win32"), false);
|
|
777
|
+
assert.equal(shouldDeliverTaskViaFile("x".repeat(8001), "auto", "linux"), true);
|
|
778
|
+
});
|
|
709
779
|
});
|
|
710
780
|
|
|
711
781
|
describe("buildPiArgs system prompt mode wiring", () => {
|
|
@@ -974,6 +1044,27 @@ describe("buildPiArgs system prompt mode wiring", () => {
|
|
|
974
1044
|
assert.equal(env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV], toolDiagnosticPath);
|
|
975
1045
|
});
|
|
976
1046
|
|
|
1047
|
+
it("clears inherited tool diagnostics for nested zero-tool children", () => {
|
|
1048
|
+
const inheritedDiagnosticPath = "/tmp/parent-tool-diagnostic.json";
|
|
1049
|
+
const { env, toolDiagnosticPath } = buildPiArgs({
|
|
1050
|
+
baseArgs: ["-p"],
|
|
1051
|
+
task: "repair retained output",
|
|
1052
|
+
sessionEnabled: false,
|
|
1053
|
+
inheritProjectContext: false,
|
|
1054
|
+
inheritSkills: false,
|
|
1055
|
+
tools: [],
|
|
1056
|
+
});
|
|
1057
|
+
const spawnEnv = {
|
|
1058
|
+
[REQUIRED_CHILD_TOOLS_ENV]: JSON.stringify(["read", "bash"]),
|
|
1059
|
+
[CHILD_TOOL_DIAGNOSTIC_PATH_ENV]: inheritedDiagnosticPath,
|
|
1060
|
+
...env,
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
assert.equal(toolDiagnosticPath, undefined);
|
|
1064
|
+
assert.equal(spawnEnv[REQUIRED_CHILD_TOOLS_ENV], undefined);
|
|
1065
|
+
assert.equal(spawnEnv[CHILD_TOOL_DIAGNOSTIC_PATH_ENV], undefined);
|
|
1066
|
+
});
|
|
1067
|
+
|
|
977
1068
|
it("strips the legacy supervisor pairing from requirements", () => {
|
|
978
1069
|
const { args, env } = buildPiArgs({
|
|
979
1070
|
baseArgs: ["-p"],
|
|
@@ -1042,6 +1133,8 @@ describe("buildPiArgs system prompt mode wiring", () => {
|
|
|
1042
1133
|
schema: { type: "object", properties: {}, additionalProperties: false },
|
|
1043
1134
|
schemaPath: "/tmp/schema.json",
|
|
1044
1135
|
outputPath: "/tmp/output.json",
|
|
1136
|
+
acceptanceReportPath: "/tmp/acceptance.json",
|
|
1137
|
+
acceptanceReportRequired: true,
|
|
1045
1138
|
},
|
|
1046
1139
|
});
|
|
1047
1140
|
|
|
@@ -1054,6 +1147,18 @@ describe("buildPiArgs system prompt mode wiring", () => {
|
|
|
1054
1147
|
"fixture_search",
|
|
1055
1148
|
"structured_output",
|
|
1056
1149
|
]);
|
|
1150
|
+
assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV], "/tmp/acceptance.json");
|
|
1151
|
+
assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV], "1");
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it("clears inherited acceptance capture for launches without outputSchema", () => {
|
|
1155
|
+
const { env } = buildPiArgs({
|
|
1156
|
+
baseArgs: ["-p"], task: "hello", sessionEnabled: false,
|
|
1157
|
+
inheritProjectContext: false, inheritSkills: false,
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV], undefined);
|
|
1161
|
+
assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV], undefined);
|
|
1057
1162
|
});
|
|
1058
1163
|
|
|
1059
1164
|
it("forwards the Pi package root to child processes for host peer resolution", () => {
|
|
@@ -1267,7 +1372,8 @@ describe("buildPiArgs system prompt mode wiring", () => {
|
|
|
1267
1372
|
assert.deepEqual(requestedNames, [serverName, serverName]);
|
|
1268
1373
|
assert.equal(serverLaunch.args[serverLaunch.args.indexOf("--tools") + 1], "read,runtime-github_search_repositories,runtime-github_create_issue");
|
|
1269
1374
|
assert.equal(toolLaunch.args[toolLaunch.args.indexOf("--tools") + 1], "read,runtime-github_search_repositories");
|
|
1270
|
-
|
|
1375
|
+
const serverTaskArgIndex = serverLaunch.args.findIndex((arg) => arg === "Task: hello" || arg.endsWith("task.md"));
|
|
1376
|
+
assert.ok(serverLaunch.args.indexOf("--mcp-config") < serverTaskArgIndex);
|
|
1271
1377
|
for (const launch of [serverLaunch, toolLaunch]) {
|
|
1272
1378
|
const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1];
|
|
1273
1379
|
assert.ok(configPath);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolvePiCliScript,
|
|
9
9
|
type SelesaiSpawnDeps,
|
|
10
10
|
} from "../../src/runs/shared/pi-spawn.ts";
|
|
11
|
+
import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts";
|
|
11
12
|
|
|
12
13
|
function makeDeps(input: {
|
|
13
14
|
platform?: NodeJS.Platform;
|
|
@@ -73,6 +74,33 @@ describe("getPiSpawnCommand", () => {
|
|
|
73
74
|
assert.deepEqual(result, { command: "selesai", args });
|
|
74
75
|
});
|
|
75
76
|
|
|
77
|
+
for (const extension of ["js", "mjs", "cjs"] as const) {
|
|
78
|
+
it(`runs a Windows ${extension} SELESAI_SUBAGENT_SELESAI_BINARY override through Node`, () => {
|
|
79
|
+
const piBinary = `C:\\Program Files\\pi\\bin\\pi-cli.${extension}`;
|
|
80
|
+
const args = ["--mode", "json", "Task: check output"];
|
|
81
|
+
const result = getPiSpawnCommand(args, {
|
|
82
|
+
platform: "win32",
|
|
83
|
+
execPath: "C:\\Program Files\\nodejs\\node.exe",
|
|
84
|
+
env: { SELESAI_SUBAGENT_SELESAI_BINARY: piBinary },
|
|
85
|
+
});
|
|
86
|
+
assert.deepEqual(result, {
|
|
87
|
+
command: "C:\\Program Files\\nodejs\\node.exe",
|
|
88
|
+
args: [piBinary, ...args],
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
it("keeps a JavaScript SELESAI_SUBAGENT_SELESAI_BINARY override direct on POSIX", () => {
|
|
94
|
+
const piBinary = "/opt/pi/bin/pi-cli.mjs";
|
|
95
|
+
const args = ["--mode", "json", "Task: check output"];
|
|
96
|
+
const result = getPiSpawnCommand(args, {
|
|
97
|
+
platform: "darwin",
|
|
98
|
+
execPath: "/usr/local/bin/node",
|
|
99
|
+
env: { SELESAI_SUBAGENT_SELESAI_BINARY: piBinary },
|
|
100
|
+
});
|
|
101
|
+
assert.deepEqual(result, { command: piBinary, args });
|
|
102
|
+
});
|
|
103
|
+
|
|
76
104
|
for (const [platform, execPath] of [
|
|
77
105
|
["darwin", "/opt/selesai/selesai"],
|
|
78
106
|
["linux", "/opt/selesai/selesai"],
|
|
@@ -89,7 +117,8 @@ describe("getPiSpawnCommand", () => {
|
|
|
89
117
|
execPath,
|
|
90
118
|
argv1: "/missing/host.js",
|
|
91
119
|
packageJsonPath,
|
|
92
|
-
packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli.js" } }),
|
|
120
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli.js" } }),
|
|
121
|
+
|
|
93
122
|
existing: [packageJsonPath, cliPath],
|
|
94
123
|
});
|
|
95
124
|
const args = ["--mode", "json", "-p", "Task: review diff"];
|
|
@@ -141,7 +170,8 @@ describe("getPiSpawnCommand", () => {
|
|
|
141
170
|
execPath: "/usr/local/bin/node",
|
|
142
171
|
argv1: "/opt/selesai/subagent-runner.ts",
|
|
143
172
|
packageJsonPath,
|
|
144
|
-
packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
|
|
173
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli/index.js" } }),
|
|
174
|
+
|
|
145
175
|
existing: [packageJsonPath, cliPath],
|
|
146
176
|
});
|
|
147
177
|
const args = ["-p", "Task: hello"];
|
|
@@ -152,7 +182,103 @@ describe("getPiSpawnCommand", () => {
|
|
|
152
182
|
});
|
|
153
183
|
});
|
|
154
184
|
|
|
185
|
+
it("fails closed until the Windows CLI script becomes available", () => {
|
|
186
|
+
const packageJsonPath = "/opt/pi/package.json";
|
|
187
|
+
const cliPath = path.resolve(
|
|
188
|
+
path.dirname(packageJsonPath),
|
|
189
|
+
"dist/cli/index.js",
|
|
190
|
+
);
|
|
191
|
+
let cliAvailable = false;
|
|
192
|
+
const deps = makeDeps({
|
|
193
|
+
platform: "win32",
|
|
194
|
+
execPath: "C:\\Program Files\\nodejs\\node.exe",
|
|
195
|
+
argv1: "/opt/pi-web/dist/server.js",
|
|
196
|
+
packageJsonPath,
|
|
197
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli/index.js" } }),
|
|
198
|
+
existing: [packageJsonPath],
|
|
199
|
+
});
|
|
200
|
+
deps.existsSync = (filePath) =>
|
|
201
|
+
filePath === packageJsonPath || (cliAvailable && filePath === cliPath);
|
|
202
|
+
const args = ["-p", "Task: hello"];
|
|
203
|
+
|
|
204
|
+
assert.throws(
|
|
205
|
+
() => getPiSpawnCommand(args, deps),
|
|
206
|
+
/Could not resolve the Pi CLI on Windows/,
|
|
207
|
+
);
|
|
208
|
+
cliAvailable = true;
|
|
209
|
+
assert.deepEqual(getPiSpawnCommand(args, deps), {
|
|
210
|
+
command: "C:\\Program Files\\nodejs\\node.exe",
|
|
211
|
+
args: [cliPath, ...args],
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("resolves the Windows CLI from the forwarded package-root env for wrapper hosts", () => {
|
|
216
|
+
const packageJsonPath = "/opt/pi/package.json";
|
|
217
|
+
const cliPath = path.resolve(
|
|
218
|
+
path.dirname(packageJsonPath),
|
|
219
|
+
"dist/cli/index.js",
|
|
220
|
+
);
|
|
221
|
+
const args = ["-p", "Task: hello"];
|
|
222
|
+
const result = getPiSpawnCommand(args, {
|
|
223
|
+
platform: "win32",
|
|
224
|
+
execPath: "C:\\Program Files\\nodejs\\node.exe",
|
|
225
|
+
argv1: "/opt/pi-web/dist/server.js",
|
|
226
|
+
env: { [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: "/opt/pi" },
|
|
227
|
+
existsSync: (filePath) => filePath === packageJsonPath || filePath === cliPath,
|
|
228
|
+
readFileSync: () => JSON.stringify({
|
|
229
|
+
name: "@selesai/code",
|
|
230
|
+
bin: { selesai: "dist/cli/index.js" },
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
assert.deepEqual(result, {
|
|
235
|
+
command: "C:\\Program Files\\nodejs\\node.exe",
|
|
236
|
+
args: [cliPath, ...args],
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("skips an unverified forwarded package-root env before resolving a verified Pi package", () => {
|
|
241
|
+
const tempDir = fs.mkdtempSync(
|
|
242
|
+
path.join(os.tmpdir(), "pi-spawn-unverified-env-root-"),
|
|
243
|
+
);
|
|
244
|
+
try {
|
|
245
|
+
const wrongRoot = path.join(tempDir, "not-selesai");
|
|
246
|
+
const wrongCli = path.join(wrongRoot, "bin", "evil.js");
|
|
247
|
+
const selesaiRoot = path.join(tempDir, "node_modules", "@selesai", "code");
|
|
248
|
+
const selesaiEntry = path.join(selesaiRoot, "dist", "index.js");
|
|
249
|
+
const selesaiCli = path.join(selesaiRoot, "dist", "cli.js");
|
|
250
|
+
fs.mkdirSync(path.dirname(wrongCli), { recursive: true });
|
|
251
|
+
fs.mkdirSync(path.dirname(selesaiCli), { recursive: true });
|
|
252
|
+
fs.writeFileSync(wrongCli, "console.log('evil');\n");
|
|
253
|
+
fs.writeFileSync(path.join(wrongRoot, "package.json"), JSON.stringify({ name: "not-selesai", bin: { selesai: "bin/evil.js" } }));
|
|
254
|
+
fs.writeFileSync(selesaiEntry, "export {};\n");
|
|
255
|
+
fs.writeFileSync(selesaiCli, "#!/usr/bin/env node\n");
|
|
256
|
+
fs.writeFileSync(path.join(selesaiRoot, "package.json"), JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli.js" } }));
|
|
257
|
+
|
|
258
|
+
const result = getPiSpawnCommand(["-p", "Task: hello"], {
|
|
259
|
+
platform: "win32",
|
|
260
|
+
execPath: "C:\\Program Files\\nodejs\\node.exe",
|
|
261
|
+
argv1: "/opt/pi-web/dist/server.js",
|
|
262
|
+
// The Selesai host repo root is itself named @selesai/code, so
|
|
263
|
+
// resolvePiPackageRoot() would find it before the temp verified
|
|
264
|
+
// package; pin the verified temp package to keep the test
|
|
265
|
+
// deterministic while still exercising the unverified env root.
|
|
266
|
+
piPackageRoot: selesaiRoot,
|
|
267
|
+
env: { [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: wrongRoot },
|
|
268
|
+
resolvePackageEntry: () => selesaiEntry,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
assert.deepEqual(result, {
|
|
272
|
+
command: "C:\\Program Files\\nodejs\\node.exe",
|
|
273
|
+
args: [selesaiCli, "-p", "Task: hello"],
|
|
274
|
+
});
|
|
275
|
+
} finally {
|
|
276
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
155
280
|
it("falls back to plain selesai command on POSIX when CLI script cannot be resolved", () => {
|
|
281
|
+
|
|
156
282
|
const args = ["--mode", "json", "Task: check output"];
|
|
157
283
|
const result = getPiSpawnCommand(args, {
|
|
158
284
|
platform: "darwin",
|
|
@@ -281,7 +407,8 @@ describe("getPiSpawnCommand", () => {
|
|
|
281
407
|
execPath: "/usr/local/bin/node",
|
|
282
408
|
argv1: "/opt/selesai/subagent-runner.ts",
|
|
283
409
|
packageJsonPath,
|
|
284
|
-
packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
|
|
410
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli/index.js" } }),
|
|
411
|
+
|
|
285
412
|
existing: [packageJsonPath, cliPath],
|
|
286
413
|
});
|
|
287
414
|
const result = getPiSpawnCommand(["-p", "Task: hello"], deps);
|
|
@@ -289,16 +416,6 @@ describe("getPiSpawnCommand", () => {
|
|
|
289
416
|
assert.equal(result.args[0], cliPath);
|
|
290
417
|
});
|
|
291
418
|
|
|
292
|
-
it("falls back to selesai when Windows CLI script cannot be resolved", () => {
|
|
293
|
-
const deps = makeDeps({
|
|
294
|
-
platform: "win32",
|
|
295
|
-
argv1: "/opt/selesai/subagent-runner.ts",
|
|
296
|
-
existing: [],
|
|
297
|
-
});
|
|
298
|
-
const args = ["-p", "Task: hello"];
|
|
299
|
-
const result = getPiSpawnCommand(args, deps);
|
|
300
|
-
assert.deepEqual(result, { command: "selesai", args });
|
|
301
|
-
});
|
|
302
419
|
|
|
303
420
|
it("walks from package main entry to resolve package bin", () => {
|
|
304
421
|
const tempDir = fs.mkdtempSync(
|
|
@@ -334,8 +451,8 @@ describe("getPiSpawnCommand", () => {
|
|
|
334
451
|
});
|
|
335
452
|
});
|
|
336
453
|
|
|
337
|
-
describe("getPiSpawnCommand with
|
|
338
|
-
it("resolves CLI script via
|
|
454
|
+
describe("getPiSpawnCommand with piPackageRoot", () => {
|
|
455
|
+
it("resolves CLI script via piPackageRoot when argv1 is not runnable", () => {
|
|
339
456
|
const packageJsonPath = "/opt/selesai/package.json";
|
|
340
457
|
const cliPath = path.resolve(
|
|
341
458
|
path.dirname(packageJsonPath),
|
|
@@ -346,10 +463,11 @@ describe("getPiSpawnCommand with selesaiPackageRoot", () => {
|
|
|
346
463
|
execPath: "/usr/local/bin/node",
|
|
347
464
|
argv1: "/opt/selesai/subagent-runner.ts",
|
|
348
465
|
packageJsonPath,
|
|
349
|
-
packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
|
|
466
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: { selesai: "dist/cli/index.js" } }),
|
|
467
|
+
|
|
350
468
|
existing: [packageJsonPath, cliPath],
|
|
351
469
|
});
|
|
352
|
-
deps.
|
|
470
|
+
deps.piPackageRoot = "/opt/pi";
|
|
353
471
|
const result = getPiSpawnCommand(["-p", "Task: hello"], deps);
|
|
354
472
|
assert.equal(result.command, "/usr/local/bin/node");
|
|
355
473
|
assert.equal(result.args[0], cliPath);
|
|
@@ -367,7 +485,7 @@ describe("resolvePiCliScript", () => {
|
|
|
367
485
|
platform: "win32",
|
|
368
486
|
argv1: "/opt/selesai/subagent-runner.ts",
|
|
369
487
|
packageJsonPath,
|
|
370
|
-
packageJsonContent: JSON.stringify({ bin: "dist/cli/index.mjs" }),
|
|
488
|
+
packageJsonContent: JSON.stringify({ name: "@selesai/code", bin: "dist/cli/index.mjs" }),
|
|
371
489
|
existing: [packageJsonPath, cliPath],
|
|
372
490
|
});
|
|
373
491
|
assert.equal(resolvePiCliScript(deps), cliPath);
|
|
@@ -804,6 +804,26 @@ Project prompt.
|
|
|
804
804
|
assert.ok(result.contract.tools.extensionArgs.includes("/tmp/subagent-only.ts"));
|
|
805
805
|
});
|
|
806
806
|
|
|
807
|
+
it("projects per-agent tool exclusions and binds them into launch identity", async () => {
|
|
808
|
+
const cwd = path.join(tempDir, "exclude-tools-repo");
|
|
809
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
810
|
+
writeAgent(path.join(cwd, ".selesai", "agents", "worker.md"), `---
|
|
811
|
+
name: worker
|
|
812
|
+
description: Project worker
|
|
813
|
+
tools: read, write
|
|
814
|
+
excludeTools: write, unknown_tool
|
|
815
|
+
---
|
|
816
|
+
Project prompt.
|
|
817
|
+
`);
|
|
818
|
+
|
|
819
|
+
const result = await resolveSubagentLaunchContract({ agent: "worker", cwd, task: "Inspect" });
|
|
820
|
+
assert.equal(result.ok, true);
|
|
821
|
+
if (!result.ok) return;
|
|
822
|
+
assert.deepEqual(result.contract.tools.excludeTools, ["write", "unknown_tool"]);
|
|
823
|
+
assert.deepEqual(result.contract.tools.effectiveAllowlist, ["read"]);
|
|
824
|
+
assert.match(result.contract.launchContractDigest, /^[a-f0-9]{64}$/);
|
|
825
|
+
});
|
|
826
|
+
|
|
807
827
|
it("falls back implicit default fork to fresh when the parent session is not forkable", async () => {
|
|
808
828
|
const cwd = path.join(tempDir, "repo-implicit-fork");
|
|
809
829
|
fs.mkdirSync(cwd, { recursive: true });
|