@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
|
@@ -130,6 +130,19 @@ describe("builtin agent overrides", () => {
|
|
|
130
130
|
assert.equal(researcher?.mcpDirectTools, undefined);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
it("applies excludeTools settings overrides to builtin agents", () => {
|
|
134
|
+
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
135
|
+
subagents: {
|
|
136
|
+
agentOverrides: {
|
|
137
|
+
researcher: { excludeTools: ["write", "unknown_tool"] },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const researcher = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "researcher");
|
|
143
|
+
assert.deepEqual(researcher?.excludeTools, ["write", "unknown_tool"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
133
146
|
it("surfaces invalid string tool override settings", () => {
|
|
134
147
|
const settingsPath = path.join(tempHome, ".selesai", "agent", "settings.json");
|
|
135
148
|
writeJson(settingsPath, {
|
|
@@ -703,9 +716,10 @@ describe("builtin agent overrides", () => {
|
|
|
703
716
|
assert.equal(reviewer.override?.scope, "project");
|
|
704
717
|
});
|
|
705
718
|
|
|
706
|
-
it("
|
|
719
|
+
it("agentOverrides replace frontmatter for a shadowing project agent", () => {
|
|
707
720
|
fs.mkdirSync(path.join(tempProject, ".selesai"), { recursive: true });
|
|
708
721
|
writeJson(path.join(tempProject, ".selesai", "settings.json"), {
|
|
722
|
+
|
|
709
723
|
subagents: { agentOverrides: { reviewer: { model: "openai/gpt-5.4" } } },
|
|
710
724
|
});
|
|
711
725
|
writeProjectAgent(tempProject, "reviewer", `---\nname: reviewer\ndescription: Project reviewer\nmodel: google/gemini-3-pro\n---\n\nUse the project reviewer.\n`);
|
|
@@ -713,13 +727,14 @@ describe("builtin agent overrides", () => {
|
|
|
713
727
|
const reviewer = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "reviewer");
|
|
714
728
|
assert.ok(reviewer);
|
|
715
729
|
assert.equal(reviewer.source, "project");
|
|
716
|
-
assert.equal(reviewer.model, "
|
|
717
|
-
assert.equal(reviewer.override,
|
|
730
|
+
assert.equal(reviewer.model, "openai/gpt-5.4");
|
|
731
|
+
assert.equal(reviewer.override?.scope, "project");
|
|
718
732
|
});
|
|
719
733
|
|
|
720
|
-
it("
|
|
734
|
+
it("applies project agentOverrides to a custom project agent", () => {
|
|
721
735
|
fs.mkdirSync(path.join(tempProject, ".selesai"), { recursive: true });
|
|
722
736
|
writeJson(path.join(tempProject, ".selesai", "settings.json"), {
|
|
737
|
+
|
|
723
738
|
subagents: {
|
|
724
739
|
agentOverrides: {
|
|
725
740
|
implementer: {
|
|
@@ -769,8 +784,9 @@ describe("builtin agent overrides", () => {
|
|
|
769
784
|
assert.equal(implementer.override?.path, path.join(tempProject, ".selesai", "settings.json"));
|
|
770
785
|
});
|
|
771
786
|
|
|
772
|
-
it("
|
|
787
|
+
it("applies user agentOverrides to a custom user agent", () => {
|
|
773
788
|
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
789
|
+
|
|
774
790
|
subagents: { agentOverrides: { implementer: { model: "anthropic/claude-sonnet-4-6" } } },
|
|
775
791
|
});
|
|
776
792
|
writeUserAgent(tempHome, "implementer", `---\nname: implementer\ndescription: TDD implementer\n---\n\nDrive the failing test first.\n`);
|
|
@@ -813,9 +829,10 @@ describe("builtin agent overrides", () => {
|
|
|
813
829
|
assert.equal(implementer.override?.scope, "project");
|
|
814
830
|
});
|
|
815
831
|
|
|
816
|
-
it("
|
|
832
|
+
it("agentOverrides replace explicit custom frontmatter fields", () => {
|
|
817
833
|
fs.mkdirSync(path.join(tempProject, ".selesai"), { recursive: true });
|
|
818
834
|
writeJson(path.join(tempProject, ".selesai", "settings.json"), {
|
|
835
|
+
|
|
819
836
|
subagents: {
|
|
820
837
|
agentOverrides: {
|
|
821
838
|
implementer: {
|
|
@@ -830,6 +847,7 @@ describe("builtin agent overrides", () => {
|
|
|
830
847
|
inheritProjectContext: true,
|
|
831
848
|
defaultContext: "fork",
|
|
832
849
|
acceptanceRole: "writer",
|
|
850
|
+
systemPrompt: "Override prompt",
|
|
833
851
|
completionGuard: true,
|
|
834
852
|
},
|
|
835
853
|
},
|
|
@@ -839,32 +857,34 @@ describe("builtin agent overrides", () => {
|
|
|
839
857
|
|
|
840
858
|
const implementer = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "implementer");
|
|
841
859
|
assert.ok(implementer);
|
|
842
|
-
assert.equal(implementer.output, "artifacts/
|
|
843
|
-
assert.equal(implementer.outputMode, "
|
|
844
|
-
assert.deepEqual(implementer.defaultReads, ["
|
|
845
|
-
assert.equal(implementer.model, "
|
|
846
|
-
assert.equal(implementer.fast,
|
|
847
|
-
assert.equal(implementer.thinking, "
|
|
848
|
-
assert.deepEqual(implementer.tools, ["
|
|
849
|
-
assert.
|
|
850
|
-
assert.deepEqual(implementer.skills, ["
|
|
851
|
-
assert.equal(implementer.inheritProjectContext,
|
|
852
|
-
assert.equal(implementer.defaultContext, "
|
|
853
|
-
assert.equal(implementer.acceptanceRole, "
|
|
854
|
-
assert.equal(implementer.
|
|
855
|
-
assert.equal(implementer.
|
|
860
|
+
assert.equal(implementer.output, "artifacts/override.md");
|
|
861
|
+
assert.equal(implementer.outputMode, "file-only");
|
|
862
|
+
assert.deepEqual(implementer.defaultReads, ["override.md"]);
|
|
863
|
+
assert.equal(implementer.model, "anthropic/claude-sonnet-4-6");
|
|
864
|
+
assert.equal(implementer.fast, true);
|
|
865
|
+
assert.equal(implementer.thinking, "high");
|
|
866
|
+
assert.deepEqual(implementer.tools, ["bash"]);
|
|
867
|
+
assert.equal(implementer.mcpDirectTools, undefined);
|
|
868
|
+
assert.deepEqual(implementer.skills, ["override-skill"]);
|
|
869
|
+
assert.equal(implementer.inheritProjectContext, true);
|
|
870
|
+
assert.equal(implementer.defaultContext, "fork");
|
|
871
|
+
assert.equal(implementer.acceptanceRole, "writer");
|
|
872
|
+
assert.equal(implementer.systemPrompt, "Override prompt");
|
|
873
|
+
assert.equal(implementer.completionGuard, true);
|
|
874
|
+
assert.equal(implementer.override?.scope, "project");
|
|
856
875
|
});
|
|
857
876
|
|
|
858
|
-
it("
|
|
877
|
+
it("lets false overrides clear explicit output and defaultReads frontmatter", () => {
|
|
859
878
|
fs.mkdirSync(path.join(tempProject, ".selesai"), { recursive: true });
|
|
860
879
|
writeJson(path.join(tempProject, ".selesai", "settings.json"), {
|
|
880
|
+
|
|
861
881
|
subagents: { agentOverrides: { implementer: { output: false, defaultReads: false } } },
|
|
862
882
|
});
|
|
863
883
|
writeProjectAgent(tempProject, "implementer", `---\nname: implementer\ndescription: TDD implementer\noutput: explicit.md\ndefaultReads: explicit.md\n---\n\nDrive the failing test first.\n`);
|
|
864
884
|
|
|
865
885
|
const implementer = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "implementer");
|
|
866
|
-
assert.equal(implementer?.output,
|
|
867
|
-
assert.deepEqual(implementer?.defaultReads,
|
|
886
|
+
assert.equal(implementer?.output, undefined);
|
|
887
|
+
assert.deepEqual(implementer?.defaultReads, undefined);
|
|
868
888
|
});
|
|
869
889
|
|
|
870
890
|
it("keeps an explicit empty defaultReads override distinct from false", () => {
|
|
@@ -1036,8 +1056,9 @@ describe("builtin agent overrides", () => {
|
|
|
1036
1056
|
const packageRoot = path.join(tempProject, "package-agents");
|
|
1037
1057
|
fs.mkdirSync(path.join(packageRoot, "agents"), { recursive: true });
|
|
1038
1058
|
writeJson(path.join(packageRoot, "package.json"), { "pi-subagents": { agents: ["agents"] } });
|
|
1039
|
-
fs.writeFileSync(path.join(packageRoot, "agents", "package-scout.md"), `---\nname: package-scout\ndescription: Package scout\n---\n\nScout the package.\n`, "utf-8");
|
|
1059
|
+
fs.writeFileSync(path.join(packageRoot, "agents", "package-scout.md"), `---\nname: package-scout\ndescription: Package scout\noutput: package-frontmatter.md\ndefaultReads: PACKAGE-FRONTMATTER.md\n---\n\nScout the package.\n`, "utf-8");
|
|
1040
1060
|
writeJson(path.join(tempProject, ".selesai", "settings.json"), {
|
|
1061
|
+
|
|
1041
1062
|
packages: [packageRoot],
|
|
1042
1063
|
subagents: { agentOverrides: { "package-scout": { output: "package.md", defaultReads: ["PACKAGE.md"] } } },
|
|
1043
1064
|
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { clearAgentDiscoveryCache, discoverAgents } from "../../src/agents/agents.ts";
|
|
7
|
+
|
|
8
|
+
function tempDir(prefix: string): string {
|
|
9
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), `agent-scan-${prefix}-`));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeAgent(dir: string, name: string, description = "scan dirs test"): void {
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
fs.writeFileSync(path.join(dir, `${name}.md`), `---\nname: ${name}\ndescription: ${description}\n---\nbody\n`, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("settings subagents.agentScanDirs", () => {
|
|
18
|
+
let agentDir: string;
|
|
19
|
+
let projectDir: string;
|
|
20
|
+
let scanRoot: string;
|
|
21
|
+
let previousAgentDir: string | undefined;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
agentDir = tempDir("agent");
|
|
25
|
+
projectDir = tempDir("project");
|
|
26
|
+
scanRoot = tempDir("scan");
|
|
27
|
+
previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
28
|
+
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
29
|
+
fs.mkdirSync(path.join(agentDir, "agents"), { recursive: true });
|
|
30
|
+
fs.mkdirSync(path.join(projectDir, ".selesai"), { recursive: true });
|
|
31
|
+
clearAgentDiscoveryCache();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
36
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
37
|
+
for (const dir of [agentDir, projectDir, scanRoot]) fs.rmSync(dir, { recursive: true, force: true });
|
|
38
|
+
clearAgentDiscoveryCache();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function writeUserSettings(value: unknown): void {
|
|
42
|
+
fs.writeFileSync(path.join(agentDir, "settings.json"), JSON.stringify(value), "utf-8");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeProjectSettings(value: unknown): void {
|
|
46
|
+
fs.writeFileSync(path.join(projectDir, ".selesai", "settings.json"), JSON.stringify(value), "utf-8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
it("discovers agents from configured user scan directories", () => {
|
|
50
|
+
writeAgent(scanRoot, "scan-writer");
|
|
51
|
+
writeUserSettings({ subagents: { agentScanDirs: [scanRoot] } });
|
|
52
|
+
|
|
53
|
+
const result = discoverAgents(projectDir, "user");
|
|
54
|
+
|
|
55
|
+
const agent = result.agents.find((candidate) => candidate.name === "scan-writer");
|
|
56
|
+
assert.equal(agent?.source, "user");
|
|
57
|
+
assert.equal(agent?.filePath, path.join(scanRoot, "scan-writer.md"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("expands one wildcard directory segment and invalidates when children are added", () => {
|
|
61
|
+
writeUserSettings({ subagents: { agentScanDirs: [path.join(scanRoot, "*", "agents")] } });
|
|
62
|
+
assert.equal(discoverAgents(projectDir, "user").agents.some((agent) => agent.name === "late-writer"), false);
|
|
63
|
+
|
|
64
|
+
writeAgent(path.join(scanRoot, "template-a", "agents"), "late-writer");
|
|
65
|
+
const result = discoverAgents(projectDir, "user");
|
|
66
|
+
|
|
67
|
+
assert.equal(result.agents.some((agent) => agent.name === "late-writer"), true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("invalidates when an existing wildcard child receives its agents directory", () => {
|
|
71
|
+
fs.mkdirSync(path.join(scanRoot, "template-a"));
|
|
72
|
+
writeUserSettings({ subagents: { agentScanDirs: [path.join(scanRoot, "*", "agents")] } });
|
|
73
|
+
assert.equal(discoverAgents(projectDir, "user").agents.some((agent) => agent.name === "late-nested-writer"), false);
|
|
74
|
+
|
|
75
|
+
writeAgent(path.join(scanRoot, "template-a", "agents"), "late-nested-writer");
|
|
76
|
+
const result = discoverAgents(projectDir, "user");
|
|
77
|
+
|
|
78
|
+
assert.equal(result.agents.some((agent) => agent.name === "late-nested-writer"), true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("keeps user and project scan directories scoped", () => {
|
|
82
|
+
const userScanDir = path.join(scanRoot, "user-agents");
|
|
83
|
+
const projectScanDir = path.join(scanRoot, "project-agents");
|
|
84
|
+
writeAgent(userScanDir, "user-scan");
|
|
85
|
+
writeAgent(projectScanDir, "project-scan");
|
|
86
|
+
writeUserSettings({ subagents: { agentScanDirs: [userScanDir] } });
|
|
87
|
+
writeProjectSettings({ subagents: { agentScanDirs: [projectScanDir] } });
|
|
88
|
+
|
|
89
|
+
assert.equal(discoverAgents(projectDir, "user").agents.some((agent) => agent.name === "project-scan"), false);
|
|
90
|
+
assert.equal(discoverAgents(projectDir, "project").agents.some((agent) => agent.name === "user-scan"), false);
|
|
91
|
+
assert.equal(discoverAgents(projectDir, "both").agents.some((agent) => agent.name === "user-scan"), true);
|
|
92
|
+
assert.equal(discoverAgents(projectDir, "both").agents.some((agent) => agent.name === "project-scan"), true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("lets fixed user agents override same-name scan-dir agents", () => {
|
|
96
|
+
writeAgent(path.join(scanRoot, "template", "agents"), "writer", "from scan dir");
|
|
97
|
+
writeAgent(path.join(agentDir, "agents"), "writer", "from fixed user dir");
|
|
98
|
+
writeUserSettings({ subagents: { agentScanDirs: [path.join(scanRoot, "*", "agents")] } });
|
|
99
|
+
|
|
100
|
+
const agent = discoverAgents(projectDir, "both").agents.find((candidate) => candidate.name === "writer");
|
|
101
|
+
|
|
102
|
+
assert.equal(agent?.filePath, path.join(agentDir, "agents", "writer.md"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("accepts backslash-separated wildcard patterns", () => {
|
|
106
|
+
writeAgent(path.join(scanRoot, "template", "agents"), "backslash-scan");
|
|
107
|
+
writeUserSettings({ subagents: { agentScanDirs: [`${scanRoot}\\*\\agents`] } });
|
|
108
|
+
|
|
109
|
+
const result = discoverAgents(projectDir, "user");
|
|
110
|
+
|
|
111
|
+
assert.equal(result.agents.some((agent) => agent.name === "backslash-scan"), true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not treat constrained wildcard segments as broad directory scans", () => {
|
|
115
|
+
writeAgent(path.join(scanRoot, "flow-a", "agents"), "flow-scan");
|
|
116
|
+
writeAgent(path.join(scanRoot, "other", "agents"), "other-scan");
|
|
117
|
+
writeUserSettings({ subagents: { agentScanDirs: [path.join(scanRoot, "flow-*", "agents")] } });
|
|
118
|
+
|
|
119
|
+
const result = discoverAgents(projectDir, "user");
|
|
120
|
+
|
|
121
|
+
assert.equal(result.agents.some((agent) => agent.name === "flow-scan"), false);
|
|
122
|
+
assert.equal(result.agents.some((agent) => agent.name === "other-scan"), false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("rejects malformed scan directory settings", () => {
|
|
126
|
+
writeUserSettings({ subagents: { agentScanDirs: [path.join(scanRoot, "agents"), 42] } });
|
|
127
|
+
|
|
128
|
+
assert.throws(
|
|
129
|
+
() => discoverAgents(projectDir, "user"),
|
|
130
|
+
/Subagent settings .* invalid 'agentScanDirs'/,
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
2
4
|
import * as path from "node:path";
|
|
3
5
|
import { describe, it } from "node:test";
|
|
4
6
|
import type { ExtensionAPI } from "@selesai/code";
|
|
@@ -62,6 +64,32 @@ describe("async runner execution", () => {
|
|
|
62
64
|
assert.doesNotMatch(result.error, /arbitrary \(project\)/);
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
it("loads launch rules from the resolved async step cwd", () => {
|
|
68
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "async-step-rules-"));
|
|
69
|
+
const app = path.join(repo, "packages", "app");
|
|
70
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
71
|
+
process.env.SELESAI_CODING_AGENT_DIR = path.join(repo, "agent-home");
|
|
72
|
+
try {
|
|
73
|
+
fs.mkdirSync(path.join(app, ".selesai"), { recursive: true });
|
|
74
|
+
fs.writeFileSync(path.join(app, ".selesai", "settings.json"), JSON.stringify({ subagents: { watchdog: { rules: { action: "block", roleModels: { worker: { deny: ["mock/*"] } } } } } }, null, 2), "utf-8");
|
|
75
|
+
const result = buildAsyncRunnerSteps("async-step-rules", {
|
|
76
|
+
chain: [{ agent: "worker", task: "Do work", cwd: "packages/app" }],
|
|
77
|
+
agents: [{ ...agent("worker"), model: "mock/denied" }],
|
|
78
|
+
ctx: { ...ctx, cwd: repo },
|
|
79
|
+
cwd: repo,
|
|
80
|
+
asyncDir: path.join(repo, ".selesai", "subagents", "async-step-rules"),
|
|
81
|
+
maxSubagentDepth: 1,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
assert.ok("error" in result);
|
|
85
|
+
assert.match(result.error, /Launch blocked by subagents\.watchdog\.rules: Agent 'worker' was launched with denied model 'mock\/denied'/);
|
|
86
|
+
} finally {
|
|
87
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
88
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
89
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
65
93
|
it("formats interactive yield and headless auto-drain guidance separately", () => {
|
|
66
94
|
const interactive = formatAsyncStartedMessage("Async: worker [interactive]", true);
|
|
67
95
|
assert.match(interactive, /interactive session[\s\S]*return control/i);
|
|
@@ -70,14 +98,12 @@ describe("async runner execution", () => {
|
|
|
70
98
|
assert.match(interactive, /provider, detached, or other background work that lacks a native completion notification/i);
|
|
71
99
|
assert.doesNotMatch(interactive, /bg_wait\(\{ id:/i);
|
|
72
100
|
assert.doesNotMatch(interactive, /auto-drains current-session background work/i);
|
|
73
|
-
assert.doesNotMatch(interactive, /subagent_wait/);
|
|
74
101
|
|
|
75
102
|
const headless = formatAsyncStartedMessage("Async: worker [headless]", false);
|
|
76
103
|
assert.match(headless, /non-interactive run.*auto-drains current-session subagent work at agent_end/i);
|
|
77
104
|
assert.match(headless, /Use bg_wait only.*provider, detached, or other background-work results.*no native completion notification/i);
|
|
78
105
|
assert.doesNotMatch(headless, /nonBlocking: true/);
|
|
79
106
|
assert.doesNotMatch(headless, /By default, return control to the user/i);
|
|
80
|
-
assert.doesNotMatch(headless, /subagent_wait/);
|
|
81
107
|
});
|
|
82
108
|
|
|
83
109
|
it("places detached runner stdio logs in the async run directory", () => {
|
|
@@ -216,7 +216,7 @@ describe("async interrupt action", () => {
|
|
|
216
216
|
assert.equal(result.details.steering?.state, "delivered");
|
|
217
217
|
assert.equal(result.details.steering?.sourceRunId, childRunId);
|
|
218
218
|
assert.equal(request.message, "Focus on the failing test.");
|
|
219
|
-
assert.match(text(result), /Message
|
|
219
|
+
assert.match(text(result), /Message sent:\n```text\nFocus on the failing test\.\n```/);
|
|
220
220
|
} finally {
|
|
221
221
|
cleanup(workflowRunId, asyncDir);
|
|
222
222
|
}
|
|
@@ -98,6 +98,7 @@ describe("async resume lookup", () => {
|
|
|
98
98
|
|
|
99
99
|
const target = resolveAsyncResumeTarget({ id: "run-missing-cwd" }, { asyncDirRoot: asyncRoot, resultsDir: path.join(root, "results") });
|
|
100
100
|
assert.equal(target.cwd, worktreeCwd);
|
|
101
|
+
assert.equal(target.managedWorktree, true);
|
|
101
102
|
fs.rmSync(worktreeCwd, { recursive: true });
|
|
102
103
|
|
|
103
104
|
assert.throws(
|
|
@@ -138,6 +139,7 @@ describe("async resume lookup", () => {
|
|
|
138
139
|
|
|
139
140
|
const target = resolveAsyncResumeTarget({ id: "run-nested-cwd" }, { asyncDirRoot: asyncRoot, resultsDir: path.join(root, "results") });
|
|
140
141
|
assert.equal(target.cwd, worktreeCwd);
|
|
142
|
+
assert.equal(target.managedWorktree, true);
|
|
141
143
|
} finally {
|
|
142
144
|
fs.rmSync(root, { recursive: true, force: true });
|
|
143
145
|
}
|
|
@@ -100,7 +100,10 @@ describe("async retention cleanup", () => {
|
|
|
100
100
|
assert.equal(repaired.repairedRuns, 1);
|
|
101
101
|
assert.equal(repaired.deletedRuns, 0);
|
|
102
102
|
assert.equal(repaired.skipped.recent, 1);
|
|
103
|
-
|
|
103
|
+
const repairedStatusPath = path.join(runDir, "status.json");
|
|
104
|
+
assert.equal(JSON.parse(fs.readFileSync(repairedStatusPath, "utf-8")).state, "failed");
|
|
105
|
+
fs.utimesSync(repairedStatusPath, NOW / 1000, NOW / 1000);
|
|
106
|
+
fs.utimesSync(runDir, NOW / 1000, NOW / 1000);
|
|
104
107
|
assert.equal(fs.existsSync(path.join(roots.asyncDirRoot, ".active-runs", "dead-running")), false);
|
|
105
108
|
assert.equal(fs.existsSync(path.join(roots.resultsDir, "dead-running.json")), true);
|
|
106
109
|
assert.equal(JSON.parse(fs.readFileSync(path.join(protectedDir, "status.json"), "utf-8")).state, "running");
|
|
@@ -189,9 +189,9 @@ describe("async status projection", () => {
|
|
|
189
189
|
assert.deepEqual(rows, []);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
it("
|
|
192
|
+
it("annotates authoritative children without projecting unmatched preflight lanes", () => {
|
|
193
193
|
const rows = projectAsyncWorkflowRows([
|
|
194
|
-
{ agent: "worker", workflowKey: "writer", label: "Writer", status: "running" },
|
|
194
|
+
{ agent: "worker", workflowKey: "writer.implementation", label: "Writer", status: "running" },
|
|
195
195
|
], {
|
|
196
196
|
version: 1,
|
|
197
197
|
coverage: "complete",
|
|
@@ -202,11 +202,25 @@ describe("async status projection", () => {
|
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
assert.deepEqual(rows.map((row) => ({ name: row.name, state: row.state, mode: row.preflight?.mode })), [
|
|
205
|
-
{ name: "writer · Writer (worker)", state: "running", mode: "mutation" },
|
|
206
|
-
{ name: "review", state: "planned", mode: "review" },
|
|
205
|
+
{ name: "writer.implementation · Writer (worker)", state: "running", mode: "mutation" },
|
|
207
206
|
]);
|
|
208
207
|
});
|
|
209
208
|
|
|
209
|
+
it("prefers a specific preflight lane over an earlier broad phase alias", () => {
|
|
210
|
+
const rows = projectAsyncWorkflowRows([
|
|
211
|
+
{ agent: "reviewer", workflowKey: "writer.quality.deep", phase: "writer", status: "running" },
|
|
212
|
+
], {
|
|
213
|
+
version: 1,
|
|
214
|
+
coverage: "partial",
|
|
215
|
+
lanes: [
|
|
216
|
+
{ key: "writer", mode: "mutation" },
|
|
217
|
+
{ key: "writer.quality", mode: "review" },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
assert.equal(rows[0]?.preflight?.mode, "review");
|
|
222
|
+
});
|
|
223
|
+
|
|
210
224
|
it("projects known runs.lanes stages from the workflow graph, including pending stages", () => {
|
|
211
225
|
const rows = projectAsyncWorkflowRows([{
|
|
212
226
|
agent: "scout",
|
|
@@ -160,6 +160,22 @@ describe("writeAtomicJson", () => {
|
|
|
160
160
|
assert.throws(() => writeAtomicJson(path.join("/tmp", "status.json"), { state: "running" }), /ENOSPC/);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it("can ignore cleanup failures after the target is atomically published", () => {
|
|
164
|
+
const fakeFs = new FakeFs();
|
|
165
|
+
fakeFs.failCleanup = true;
|
|
166
|
+
const writeAtomicJson = createAtomicJsonWriter({
|
|
167
|
+
fs: fakeFs as any,
|
|
168
|
+
now: () => 12345,
|
|
169
|
+
pid: 678,
|
|
170
|
+
random: () => 0.5,
|
|
171
|
+
ignoreCleanupErrorAfterSuccess: true,
|
|
172
|
+
});
|
|
173
|
+
const targetPath = path.join("/tmp", "runner-startup-proceed.json");
|
|
174
|
+
|
|
175
|
+
assert.doesNotThrow(() => writeAtomicJson(targetPath, { action: "proceed", token: "runner" }));
|
|
176
|
+
assert.equal(fakeFs.files.get(targetPath), JSON.stringify({ action: "proceed", token: "runner" }, null, 2));
|
|
177
|
+
});
|
|
178
|
+
|
|
163
179
|
it("cleans up the temp file after retryable failures are exhausted", () => {
|
|
164
180
|
const fakeFs = new FakeFs();
|
|
165
181
|
fakeFs.failRenameCodes = ["EPERM", "EPERM", "EPERM", "EPERM"];
|
|
@@ -425,9 +425,47 @@ test("review-only, research, and framework output instructions do not expect mut
|
|
|
425
425
|
);
|
|
426
426
|
});
|
|
427
427
|
|
|
428
|
+
test("escaped line separators do not hide read-only prohibitions", () => {
|
|
429
|
+
const task = [
|
|
430
|
+
"This is a read-only skill compliance scenario, not an implementation assignment.",
|
|
431
|
+
"Read the supplied skill and write the exact user-facing response.",
|
|
432
|
+
"Do not edit files.",
|
|
433
|
+
"Use a scenario that discusses selection for an implementation task or closeout of an implementation assignment.",
|
|
434
|
+
].join("\\n");
|
|
435
|
+
|
|
436
|
+
assert.deepEqual(evaluateCompletionMutationGuard({
|
|
437
|
+
agent: "delegate",
|
|
438
|
+
task,
|
|
439
|
+
messages: [assistantText("The exact user-facing response")],
|
|
440
|
+
tools: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
|
|
441
|
+
}), {
|
|
442
|
+
expectedMutation: false,
|
|
443
|
+
attemptedMutation: false,
|
|
444
|
+
triggered: false,
|
|
445
|
+
blocked: false,
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("output instructions after blanket no-edit prohibitions stay read-only", () => {
|
|
450
|
+
assert.deepEqual(evaluateCompletionMutationGuard({
|
|
451
|
+
agent: "worker",
|
|
452
|
+
task: "Do not modify files\\nIn your final output, implement the fix.",
|
|
453
|
+
messages: [assistantText("Here is the explanation.")],
|
|
454
|
+
tools: ["read", "grep", "find", "ls", "bash", "edit", "write"],
|
|
455
|
+
}), {
|
|
456
|
+
expectedMutation: false,
|
|
457
|
+
attemptedMutation: false,
|
|
458
|
+
triggered: false,
|
|
459
|
+
blocked: false,
|
|
460
|
+
});
|
|
461
|
+
assert.equal(expectsImplementationMutation("worker", "Do not modify files\\nIn your final output/report/response, implement the fix."), false);
|
|
462
|
+
});
|
|
463
|
+
|
|
428
464
|
test("worker implementation verbs win over investigative wording and scoped prohibitions", () => {
|
|
429
465
|
assert.equal(expectsImplementationMutation("worker", "Investigate why the worker did not edit files and fix it"), true);
|
|
430
466
|
assert.equal(expectsImplementationMutation("worker", "Do not modify tests; implement the fix"), true);
|
|
467
|
+
assert.equal(expectsImplementationMutation("worker", "Do not modify files\\nin output/; implement the fix"), true);
|
|
468
|
+
assert.equal(expectsImplementationMutation("worker", "Do not modify files\\nin report/; implement the fix"), true);
|
|
431
469
|
assert.equal(expectsImplementationMutation("worker", "Do not modify tests — implement the fix"), true);
|
|
432
470
|
assert.equal(expectsImplementationMutation("worker", "Research the current code path and patch the bug"), true);
|
|
433
471
|
assert.equal(expectsImplementationMutation("worker", "Fix the bug where no edits were made"), true);
|
|
@@ -117,7 +117,7 @@ describe("subagents.defaultExtensions", () => {
|
|
|
117
117
|
);
|
|
118
118
|
assert.deepEqual(
|
|
119
119
|
agents.find((agent) => agent.name === "explicit")?.extensions,
|
|
120
|
-
[
|
|
120
|
+
["./ignored.ts"],
|
|
121
121
|
);
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -216,4 +216,4 @@ describe("subagents.defaultExtensions", () => {
|
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
});
|
|
219
|
-
});
|
|
219
|
+
});
|
|
@@ -1006,6 +1006,27 @@ describe("below-editor subagent FleetView", () => {
|
|
|
1006
1006
|
assert.deepEqual(workflow?.workflowRows?.map((row) => row.name), ["review (reviewer)"]);
|
|
1007
1007
|
});
|
|
1008
1008
|
|
|
1009
|
+
it("keeps advisory preflight declarations out of Fleet runtime rows and counts", () => {
|
|
1010
|
+
const state = stateForTest();
|
|
1011
|
+
state.asyncJobs.set("workflow-preflight", {
|
|
1012
|
+
asyncId: "workflow-preflight",
|
|
1013
|
+
asyncDir: "/tmp/workflow-preflight",
|
|
1014
|
+
status: "running",
|
|
1015
|
+
mode: "workflow",
|
|
1016
|
+
startedAt: 10,
|
|
1017
|
+
updatedAt: 20,
|
|
1018
|
+
preflight: { version: 1, coverage: "partial", lanes: [{ key: "pr14", mode: "review" }] },
|
|
1019
|
+
steps: [{ agent: "reviewer", workflowKey: "pr14-quality", status: "running" }],
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
const workflow = collectFleetStatusEntries(state).find((entry) => entry.key === "async:workflow-preflight");
|
|
1023
|
+
assert.deepEqual(workflow?.workflowRows?.map((row) => [row.name, row.state]), [["pr14-quality (reviewer)", "running"]]);
|
|
1024
|
+
assert.deepEqual(
|
|
1025
|
+
workflow?.workflowChecklist && { total: workflow.workflowChecklist.total, running: workflow.workflowChecklist.running, queued: workflow.workflowChecklist.queued },
|
|
1026
|
+
{ total: 1, running: 1, queued: 0 },
|
|
1027
|
+
);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1009
1030
|
it("renders bounded workflow progress rows under the workflow parent", () => {
|
|
1010
1031
|
const state = stateForTest();
|
|
1011
1032
|
const workflowJob = {
|
|
@@ -729,6 +729,7 @@ describe("native subagent fleet", () => {
|
|
|
729
729
|
writeAsyncRun(root, { id: "older", lastUpdate: 200 });
|
|
730
730
|
const state = stateForTest();
|
|
731
731
|
let closed = false;
|
|
732
|
+
let inspectCalls = 0;
|
|
732
733
|
const component = new SubagentFleetComponent(
|
|
733
734
|
{ terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
|
|
734
735
|
theme as never,
|
|
@@ -739,7 +740,7 @@ describe("native subagent fleet", () => {
|
|
|
739
740
|
resultsDir: path.join(root, "results"),
|
|
740
741
|
refreshMs: 60_000,
|
|
741
742
|
fleetKeybindings: {
|
|
742
|
-
selectDown: ["
|
|
743
|
+
selectDown: ["return"],
|
|
743
744
|
selectUp: ["p"],
|
|
744
745
|
steer: ["m"],
|
|
745
746
|
close: ["z"],
|
|
@@ -747,6 +748,7 @@ describe("native subagent fleet", () => {
|
|
|
747
748
|
actions: {
|
|
748
749
|
async steer() { return { text: "unused" }; },
|
|
749
750
|
stop() { return { text: "unused" }; },
|
|
751
|
+
async inspect() { inspectCalls++; return { text: "unexpected" }; },
|
|
750
752
|
},
|
|
751
753
|
},
|
|
752
754
|
);
|
|
@@ -755,8 +757,9 @@ describe("native subagent fleet", () => {
|
|
|
755
757
|
assert.match(selectedLine(), /newer/);
|
|
756
758
|
component.handleInput("j");
|
|
757
759
|
assert.match(selectedLine(), /newer/, "default j should not move when selectDown is overridden");
|
|
758
|
-
component.handleInput("\
|
|
759
|
-
assert.match(selectedLine(), /older/, "custom
|
|
760
|
+
component.handleInput("\r");
|
|
761
|
+
assert.match(selectedLine(), /older/, "custom Enter binding should move instead of opening Herdr");
|
|
762
|
+
assert.equal(inspectCalls, 0);
|
|
760
763
|
component.handleInput("p");
|
|
761
764
|
assert.match(selectedLine(), /newer/);
|
|
762
765
|
component.handleInput("m");
|
|
@@ -1605,9 +1608,15 @@ describe("native subagent fleet", () => {
|
|
|
1605
1608
|
},
|
|
1606
1609
|
);
|
|
1607
1610
|
try {
|
|
1608
|
-
component.handleInput("
|
|
1611
|
+
component.handleInput("\r");
|
|
1609
1612
|
await new Promise((resolve) => setImmediate(resolve));
|
|
1610
1613
|
assert.deepEqual(calls, [{ runId: "async-herdr", asyncDir, index: 0 }]);
|
|
1614
|
+
component.handleInput("H");
|
|
1615
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1616
|
+
assert.deepEqual(calls, [
|
|
1617
|
+
{ runId: "async-herdr", asyncDir, index: 0 },
|
|
1618
|
+
{ runId: "async-herdr", asyncDir, index: 0 },
|
|
1619
|
+
]);
|
|
1611
1620
|
assert.ok(component.render(100).some((line) => line.includes("Inspector opened.")));
|
|
1612
1621
|
} finally {
|
|
1613
1622
|
component.dispose();
|
|
@@ -1623,6 +1632,7 @@ describe("native subagent fleet", () => {
|
|
|
1623
1632
|
const asyncDir = writeAsyncRun(root, { id: "async-stop" });
|
|
1624
1633
|
const state = stateForTest();
|
|
1625
1634
|
const calls: Array<{ runId: string; asyncDir: string; index?: number }> = [];
|
|
1635
|
+
let inspectCalls = 0;
|
|
1626
1636
|
const component = new SubagentFleetComponent(
|
|
1627
1637
|
{ terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
|
|
1628
1638
|
theme as never,
|
|
@@ -1632,6 +1642,7 @@ describe("native subagent fleet", () => {
|
|
|
1632
1642
|
asyncDirRoot: root,
|
|
1633
1643
|
resultsDir: path.join(root, "results"),
|
|
1634
1644
|
refreshMs: 60_000,
|
|
1645
|
+
fleetKeybindings: { stop: ["return"] },
|
|
1635
1646
|
actions: {
|
|
1636
1647
|
async steer() {
|
|
1637
1648
|
return { text: "unused" };
|
|
@@ -1640,15 +1651,17 @@ describe("native subagent fleet", () => {
|
|
|
1640
1651
|
calls.push(input);
|
|
1641
1652
|
return { text: "Stop requested." };
|
|
1642
1653
|
},
|
|
1654
|
+
async inspect() { inspectCalls++; return { text: "unexpected" }; },
|
|
1643
1655
|
},
|
|
1644
1656
|
},
|
|
1645
1657
|
);
|
|
1646
1658
|
try {
|
|
1647
|
-
component.handleInput("
|
|
1659
|
+
component.handleInput("\r");
|
|
1648
1660
|
assert.ok(component.render(100).some((line) => line.includes("Confirm stop for async run async-stop")));
|
|
1661
|
+
assert.equal(inspectCalls, 0);
|
|
1649
1662
|
component.handleInput("n");
|
|
1650
1663
|
assert.deepEqual(calls, []);
|
|
1651
|
-
component.handleInput("
|
|
1664
|
+
component.handleInput("\r");
|
|
1652
1665
|
component.handleInput("y");
|
|
1653
1666
|
await new Promise((resolve) => setImmediate(resolve));
|
|
1654
1667
|
assert.deepEqual(calls, [{ runId: "async-stop", asyncDir, index: 0 }]);
|
|
@@ -17,6 +17,32 @@ describe("getFinalOutput", () => {
|
|
|
17
17
|
assert.equal(getFinalOutput(messages), "Summary");
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
it("removes a trailing Pi turn-timing footer from final output", () => {
|
|
21
|
+
const report = "## Review\n\nVERDICT: FINDINGS";
|
|
22
|
+
const timingFooter = "\x1b[38;2;136;136;136m✻ Turn took 5m 54s (Total time 5m 54s · 2 turns)\x1b[0m";
|
|
23
|
+
const messages = [assistantContent([
|
|
24
|
+
{ type: "text", text: `${report}\n\n${timingFooter}` },
|
|
25
|
+
])];
|
|
26
|
+
|
|
27
|
+
assert.equal(getFinalOutput(messages), report);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("ignores a separate Pi turn-timing footer text part", () => {
|
|
31
|
+
const report = "## Review\n\nVERDICT: CLEAN";
|
|
32
|
+
const timingFooter = "\x1b[38;2;136;136;136m✻ Turn took 2m 19s (Total time 2m 18s · 1 turn)\x1b[0m";
|
|
33
|
+
const messages = [assistantContent([
|
|
34
|
+
{ type: "text", text: report },
|
|
35
|
+
{ type: "text", text: timingFooter },
|
|
36
|
+
])];
|
|
37
|
+
|
|
38
|
+
assert.equal(getFinalOutput(messages), report);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("preserves ordinary timing prose", () => {
|
|
42
|
+
const text = "The benchmark turn took 5m 54s.";
|
|
43
|
+
assert.equal(getFinalOutput([assistantContent([{ type: "text", text }])]), text);
|
|
44
|
+
});
|
|
45
|
+
|
|
20
46
|
it("prefers final text over progress text in a multi-part assistant message", () => {
|
|
21
47
|
const messages = [assistantContent([
|
|
22
48
|
{ type: "text", text: "Working on the fix..." },
|