@selesai/code 0.8.6 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/core/agent-session.js +13 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/system-prompt.js +2 -8
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/schema-prune.d.ts +16 -0
- package/dist/core/tools/schema-prune.js +39 -0
- package/dist/core/tools/schema-prune.test.d.ts +1 -0
- package/dist/core/tools/schema-prune.test.js +111 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +205 -41
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
- package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/question/index.ts +21 -6
- package/dist/extensions/question/question-list.ts +7 -1
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +7 -0
- package/dist/extensions/question/tests/question-list.test.ts +140 -0
- package/dist/extensions/question/tests/row-layout.test.ts +13 -0
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/docs/settings.md +6 -0
- package/package.json +3 -3
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
} from "../../src/api/delegation.ts";
|
|
39
39
|
import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type AsyncStatus, type SubagentState } from "../../src/shared/types.ts";
|
|
40
40
|
import { ACTIVE_RUN_INDEX_DIR } from "../../src/runs/background/active-run-index.ts";
|
|
41
|
+
import { resolveMissionStoreLocation } from "../../src/missions/store.ts";
|
|
42
|
+
import { missionStatePath } from "../../src/missions/workflow-state.ts";
|
|
41
43
|
import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
|
|
42
44
|
import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
|
|
43
45
|
import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV } from "../../src/runs/shared/tool-budget.ts";
|
|
@@ -656,7 +658,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
656
658
|
const activityDeadline = Date.now() + 5_000;
|
|
657
659
|
while (Date.now() < activityDeadline && !fs.existsSync(resultPath)) {
|
|
658
660
|
const candidate = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatus;
|
|
659
|
-
if (candidate.activityState === "
|
|
661
|
+
if (candidate.activityState === "needs_attention" && candidate.steps?.[0]?.currentTool === "read") {
|
|
660
662
|
liveStatus = candidate;
|
|
661
663
|
break;
|
|
662
664
|
}
|
|
@@ -664,7 +666,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
664
666
|
}
|
|
665
667
|
|
|
666
668
|
assert.ok(liveStatus, "expected workflow status to expose live child activity");
|
|
667
|
-
assert.equal(liveStatus.activityState, "
|
|
669
|
+
assert.equal(liveStatus.activityState, "needs_attention");
|
|
668
670
|
assert.equal(typeof liveStatus.lastActivityAt, "number");
|
|
669
671
|
assert.equal(liveStatus.currentTool, "read");
|
|
670
672
|
assert.match(liveStatus.currentPath ?? "", /src[/\\]example\.ts$/);
|
|
@@ -673,10 +675,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
673
675
|
assert.equal(liveStatus.steps?.[0]?.agent, "echo");
|
|
674
676
|
assert.match(liveStatus.steps?.[0]?.sessionFile ?? "", /session\.jsonl$/);
|
|
675
677
|
assert.equal(fs.existsSync(liveStatus.steps?.[0]?.sessionFile ?? ""), true);
|
|
676
|
-
assert.equal(liveStatus.steps?.[0]?.activityState, "
|
|
678
|
+
assert.equal(liveStatus.steps?.[0]?.activityState, "needs_attention");
|
|
677
679
|
assert.equal(typeof liveStatus.steps?.[0]?.lastActivityAt, "number");
|
|
678
680
|
assert.equal(liveStatus.steps?.[0]?.toolCount, 1);
|
|
679
|
-
assert.equal(asyncJobs.get(workflowRunId)?.activityState, "
|
|
681
|
+
assert.equal(asyncJobs.get(workflowRunId)?.activityState, "needs_attention");
|
|
680
682
|
assert.equal(asyncJobs.get(workflowRunId)?.steps?.[0]?.currentTool, "read");
|
|
681
683
|
|
|
682
684
|
const completionDeadline = Date.now() + 5_000;
|
|
@@ -913,39 +915,58 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
913
915
|
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
914
916
|
});
|
|
915
917
|
|
|
916
|
-
it("
|
|
918
|
+
it("keeps a git worktree clean while routing workflow children through one automatic mission", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
917
919
|
mockPi.onCall({ output: "scanned auth" });
|
|
918
920
|
mockPi.onCall({ output: "reviewed auth" });
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
921
|
+
const projectDir = path.join(tempDir, "project");
|
|
922
|
+
const agentDir = path.join(tempDir, "agent");
|
|
923
|
+
fs.mkdirSync(projectDir);
|
|
924
|
+
execFileSync("git", ["init"], { cwd: projectDir, stdio: "ignore" });
|
|
925
|
+
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: projectDir });
|
|
926
|
+
execFileSync("git", ["config", "user.name", "Test User"], { cwd: projectDir });
|
|
927
|
+
fs.writeFileSync(path.join(projectDir, "base.txt"), "base\n", "utf-8");
|
|
928
|
+
execFileSync("git", ["add", "base.txt"], { cwd: projectDir });
|
|
929
|
+
execFileSync("git", ["commit", "-m", "base"], { cwd: projectDir, stdio: "ignore" });
|
|
930
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
931
|
+
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
932
|
+
try {
|
|
933
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
|
|
934
|
+
const result = await executor.execute(
|
|
935
|
+
"scripted-workflow",
|
|
936
|
+
{
|
|
937
|
+
async: false,
|
|
938
|
+
workflowScript: `
|
|
939
|
+
const stateType = typeof state;
|
|
940
|
+
const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
|
|
941
|
+
const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
|
|
942
|
+
return { output: review.output, stateType };
|
|
943
|
+
`,
|
|
944
|
+
},
|
|
945
|
+
new AbortController().signal,
|
|
946
|
+
undefined,
|
|
947
|
+
makeMinimalCtx(projectDir),
|
|
948
|
+
);
|
|
936
949
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
950
|
+
assert.equal(result.isError, undefined);
|
|
951
|
+
assert.match(result.content[0]?.text ?? "", /reviewed auth/);
|
|
952
|
+
assert.equal(result.details.mode, "workflow");
|
|
953
|
+
assert.equal(result.details.results.length, 2);
|
|
954
|
+
assert.equal(result.details.workflow?.value && (result.details.workflow.value as { stateType?: unknown }).stateType, "object");
|
|
955
|
+
assert.ok(result.details.missionId);
|
|
956
|
+
const missionFiles = fs.readdirSync(path.join(agentDir, "missions", "projects"), { recursive: true })
|
|
957
|
+
.filter((entry) => typeof entry === "string" && entry.endsWith(".json"));
|
|
958
|
+
assert.equal(missionFiles.length, 1);
|
|
959
|
+
const mission = JSON.parse(fs.readFileSync(path.join(agentDir, "missions", "projects", missionFiles[0]!), "utf-8")) as { objective?: string };
|
|
960
|
+
assert.equal(mission.objective, utils.PROMPT_REDACTED);
|
|
961
|
+
assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state === "completed").map((entry) => entry.key), ["scan", "review"]);
|
|
962
|
+
// Selesai fork: artifactDir defaults to "project" (.pi-subagents), so the
|
|
963
|
+
// tree keeps untracked runtime state; only that dir may appear.
|
|
964
|
+
const porcelain = execFileSync("git", ["status", "--porcelain"], { cwd: projectDir, encoding: "utf-8" });
|
|
965
|
+
assert.match(porcelain, /^\?\? \.pi-subagents\/\n$/);
|
|
966
|
+
} finally {
|
|
967
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
968
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
969
|
+
}
|
|
949
970
|
});
|
|
950
971
|
|
|
951
972
|
it("keeps workflow children mission-detached when automatic mission persistence fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
@@ -953,7 +974,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
953
974
|
mockPi.onCall({ output: "reviewed auth" });
|
|
954
975
|
const blockedIndex = path.join(tempDir, "blocked-mission-index");
|
|
955
976
|
fs.writeFileSync(blockedIndex, "not a directory", "utf-8");
|
|
956
|
-
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndexDir: blockedIndex } });
|
|
977
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { directory: ".pi/subagents/missions", globalIndexDir: blockedIndex } });
|
|
957
978
|
|
|
958
979
|
const result = await executor.execute(
|
|
959
980
|
"scripted-workflow-mission-warning",
|
|
@@ -974,50 +995,62 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
974
995
|
assert.equal(result.details.missionId, undefined);
|
|
975
996
|
assert.match(result.details.missionWarning ?? "", /Mission tracking unavailable/);
|
|
976
997
|
assert.equal(result.details.results.length, 2);
|
|
977
|
-
const missionDir = path.join(tempDir, ".pi
|
|
998
|
+
const missionDir = path.join(tempDir, ".pi/subagents", "missions");
|
|
978
999
|
const missionFiles = fs.existsSync(missionDir) ? fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json")) : [];
|
|
979
1000
|
assert.equal(missionFiles.length, 1);
|
|
980
1001
|
});
|
|
981
1002
|
|
|
982
1003
|
it("shares durable workflow state across a mission and omits it for mission:false", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
983
|
-
const
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1004
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
1005
|
+
const projectDir = path.join(tempDir, "project");
|
|
1006
|
+
const agentDir = path.join(tempDir, "agent");
|
|
1007
|
+
fs.mkdirSync(projectDir);
|
|
1008
|
+
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
1009
|
+
try {
|
|
1010
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
|
|
1011
|
+
const first = await executor.execute(
|
|
1012
|
+
"mission-state-first",
|
|
1013
|
+
{
|
|
1014
|
+
async: false,
|
|
1015
|
+
mission: { title: "Stateful workflow" },
|
|
1016
|
+
workflowScript: `await state.set("review.stage", { count: 1 }); return await state.get("review.stage");`,
|
|
1017
|
+
},
|
|
1018
|
+
new AbortController().signal,
|
|
1019
|
+
undefined,
|
|
1020
|
+
makeMinimalCtx(projectDir),
|
|
1021
|
+
);
|
|
1022
|
+
assert.equal(first.isError, undefined, first.content[0]?.text ?? "first workflow failed");
|
|
1023
|
+
assert.ok(first.details.missionId);
|
|
1024
|
+
assert.deepEqual(first.details.workflow?.value, { count: 1 });
|
|
1025
|
+
const location = resolveMissionStoreLocation({ projectRoot: projectDir, agentDir });
|
|
1026
|
+
const statePath = missionStatePath(location, first.details.missionId);
|
|
1027
|
+
assert.equal(fs.existsSync(statePath), true);
|
|
1028
|
+
assert.equal(path.relative(projectDir, statePath).startsWith(".."), true);
|
|
1029
|
+
|
|
1030
|
+
const second = await executor.execute(
|
|
1031
|
+
"mission-state-second",
|
|
1032
|
+
{ async: false, missionId: first.details.missionId, workflowScript: `return await state.get("review.stage");` },
|
|
1033
|
+
new AbortController().signal,
|
|
1034
|
+
undefined,
|
|
1035
|
+
makeMinimalCtx(projectDir),
|
|
1036
|
+
);
|
|
1037
|
+
assert.equal(second.isError, undefined, second.content[0]?.text ?? "second workflow failed");
|
|
1038
|
+
assert.deepEqual(second.details.workflow?.value, { count: 1 });
|
|
1010
1039
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1040
|
+
const ephemeral = await executor.execute(
|
|
1041
|
+
"mission-state-off",
|
|
1042
|
+
{ async: false, mission: false, workflowScript: `return typeof state;` },
|
|
1043
|
+
new AbortController().signal,
|
|
1044
|
+
undefined,
|
|
1045
|
+
makeMinimalCtx(projectDir),
|
|
1046
|
+
);
|
|
1047
|
+
assert.equal(ephemeral.isError, undefined, ephemeral.content[0]?.text ?? "ephemeral workflow failed");
|
|
1048
|
+
assert.equal(ephemeral.details.workflow?.value, "undefined");
|
|
1049
|
+
assert.equal(ephemeral.details.missionId, undefined);
|
|
1050
|
+
} finally {
|
|
1051
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
1052
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
1053
|
+
}
|
|
1021
1054
|
});
|
|
1022
1055
|
|
|
1023
1056
|
it("runs a direct single child in a managed worktree", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
@@ -1356,7 +1389,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1356
1389
|
|
|
1357
1390
|
assert.equal(detachAccepted, true);
|
|
1358
1391
|
assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
|
|
1359
|
-
assert.match(result.content[0]?.text ?? "", /run detaches:
|
|
1392
|
+
assert.match(result.content[0]?.text ?? "", /run detaches: detached/);
|
|
1360
1393
|
const workflowValue = result.details.workflow?.value as Array<{ ok: boolean; artifactPaths: string[] }>;
|
|
1361
1394
|
assert.equal(workflowValue[0]?.ok, false);
|
|
1362
1395
|
const handoffPath = workflowValue[0]?.artifactPaths.find((candidate) => candidate.endsWith(".json"));
|
|
@@ -2668,7 +2701,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2668
2701
|
|
|
2669
2702
|
const result = await runSync(tempDir, agents, "echo", "Investigate behavior", {
|
|
2670
2703
|
runId: "run-active",
|
|
2671
|
-
controlConfig: { enabled: true, activeNoticeAfterTurns: 2, activeNoticeAfterMs: 999_999, activeNoticeAfterTokens: 999_999, notifyOn: ["active_long_running", "
|
|
2704
|
+
controlConfig: { enabled: true, activeNoticeAfterTurns: 2, activeNoticeAfterMs: 999_999, activeNoticeAfterTokens: 999_999, notifyOn: ["active_long_running", "active_long_running"] },
|
|
2672
2705
|
onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
|
|
2673
2706
|
});
|
|
2674
2707
|
|
|
@@ -2725,7 +2758,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2725
2758
|
|
|
2726
2759
|
const result = await runSync(tempDir, agents, "echo", "Investigate behavior", {
|
|
2727
2760
|
runId: "run-control-disabled",
|
|
2728
|
-
controlConfig: { enabled: false, activeNoticeAfterTurns: 1, activeNoticeAfterMs: 1, activeNoticeAfterTokens: 1, notifyOn: ["active_long_running", "
|
|
2761
|
+
controlConfig: { enabled: false, activeNoticeAfterTurns: 1, activeNoticeAfterMs: 1, activeNoticeAfterTokens: 1, notifyOn: ["active_long_running", "active_long_running"] },
|
|
2729
2762
|
onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
|
|
2730
2763
|
});
|
|
2731
2764
|
|
|
@@ -3451,14 +3484,14 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3451
3484
|
|
|
3452
3485
|
const result = await runSync(tempDir, makeAgentConfigs(["echo"]), "echo", "Task", {
|
|
3453
3486
|
runId: "delayed-tool-attention",
|
|
3454
|
-
controlConfig: { enabled: true, needsAttentionAfterMs: 200, activeNoticeAfterMs: 999_999, notifyOn: ["
|
|
3487
|
+
controlConfig: { enabled: true, needsAttentionAfterMs: 200, activeNoticeAfterMs: 999_999, notifyOn: ["active_long_running"] },
|
|
3455
3488
|
onUpdate: (update: { details?: { progress?: ProgressSummary[] } }) => updates.push(update),
|
|
3456
3489
|
onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
|
|
3457
3490
|
});
|
|
3458
3491
|
|
|
3459
3492
|
assert.equal(result.exitCode, 0);
|
|
3460
3493
|
assert.equal(result.progress.activityState, undefined);
|
|
3461
|
-
assert.equal(controlEvents.some((event) => event.type === "
|
|
3494
|
+
assert.equal(controlEvents.some((event) => event.type === "active_long_running"), false);
|
|
3462
3495
|
assert.equal(updates.some((update) => update.details?.progress?.some((progress) => progress.currentTool === "bash")), true);
|
|
3463
3496
|
});
|
|
3464
3497
|
|
|
@@ -46,6 +46,7 @@ interface RegisterSlashCommandsModule {
|
|
|
46
46
|
watcherRestartTimer: ReturnType<typeof setTimeout> | null;
|
|
47
47
|
resultFileCoalescer: { schedule(file: string, delayMs?: number): boolean; clear(): void };
|
|
48
48
|
},
|
|
49
|
+
options?: { foregroundDetachShortcut?: string },
|
|
49
50
|
) => void;
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -480,6 +481,52 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
|
|
|
480
481
|
clearSlashSnapshots?.();
|
|
481
482
|
});
|
|
482
483
|
|
|
484
|
+
it("registers a configured foreground detach shortcut", async () => {
|
|
485
|
+
const shortcuts = new Map<string, { handler(ctx: unknown): Promise<void> }>();
|
|
486
|
+
const sent: unknown[] = [];
|
|
487
|
+
const state = createState(process.cwd());
|
|
488
|
+
let detachCalls = 0;
|
|
489
|
+
state.foregroundControls.set("run-123", {
|
|
490
|
+
runId: "run-123",
|
|
491
|
+
mode: "single",
|
|
492
|
+
updatedAt: Date.now(),
|
|
493
|
+
detach: () => {
|
|
494
|
+
detachCalls += 1;
|
|
495
|
+
return true;
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
state.lastForegroundControlId = "run-123";
|
|
499
|
+
|
|
500
|
+
registerSlashCommands!({
|
|
501
|
+
events: createEventBus(),
|
|
502
|
+
on() {},
|
|
503
|
+
on() {},
|
|
504
|
+
registerCommand() {},
|
|
505
|
+
registerShortcut(key: string, spec: { handler(ctx: unknown): Promise<void> }) {
|
|
506
|
+
shortcuts.set(key, spec);
|
|
507
|
+
},
|
|
508
|
+
sendMessage(message: unknown) { sent.push(message); },
|
|
509
|
+
}, state, { foregroundDetachShortcut: "ctrl+b" });
|
|
510
|
+
|
|
511
|
+
assert.ok(shortcuts.has("ctrl+b"));
|
|
512
|
+
await shortcuts.get("ctrl+b")!.handler(createCommandContext());
|
|
513
|
+
assert.equal(detachCalls, 1);
|
|
514
|
+
assert.match(String((sent[0] as { content?: unknown }).content ?? ""), /Detached foreground run run-123/);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("does not reserve a foreground detach shortcut by default", () => {
|
|
518
|
+
const shortcuts = new Map<string, unknown>();
|
|
519
|
+
registerSlashCommands!({
|
|
520
|
+
events: createEventBus(),
|
|
521
|
+
on() {},
|
|
522
|
+
on() {},
|
|
523
|
+
registerCommand() {},
|
|
524
|
+
registerShortcut(key: string, spec: unknown) { shortcuts.set(key, spec); },
|
|
525
|
+
sendMessage() {},
|
|
526
|
+
}, createState(process.cwd()));
|
|
527
|
+
assert.equal(shortcuts.has("ctrl+b"), false);
|
|
528
|
+
});
|
|
529
|
+
|
|
483
530
|
it("/subagents-stop keeps the selector within its allocated width", async () => {
|
|
484
531
|
await withTempProject("pi-stop-selector-width-", async (root) => {
|
|
485
532
|
const id = "scheduled-width-check";
|
|
@@ -589,7 +636,7 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
|
|
|
589
636
|
assert.equal(requestedCtx, ctx);
|
|
590
637
|
assert.equal(sent.length, 2);
|
|
591
638
|
assert.equal((sent[0] as { display?: boolean }).display, true);
|
|
592
|
-
assert.equal((sent[0] as { content?: string }).content, "
|
|
639
|
+
assert.equal((sent[0] as { content?: string }).content, "Workflow running.");
|
|
593
640
|
assert.equal((sent[1] as { display?: boolean }).display, true);
|
|
594
641
|
assert.match((sent[1] as { content?: string }).content ?? "", /Commit finished/);
|
|
595
642
|
assert.equal(sessionManager.rewrites, 2);
|
|
@@ -636,6 +683,7 @@ Inspect
|
|
|
636
683
|
const pi = {
|
|
637
684
|
events,
|
|
638
685
|
on() {},
|
|
686
|
+
on() {},
|
|
639
687
|
registerCommand(name: string, spec: { handler(args: string, ctx: unknown): Promise<void> }) {
|
|
640
688
|
commands.set(name, spec);
|
|
641
689
|
},
|
|
@@ -63,6 +63,18 @@ describe("slash live state", { skip: !available ? "slash-live-state.ts not impor
|
|
|
63
63
|
assert.equal(snapshot.version > 0, true);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
it("marks async initial slash snapshots as background", () => {
|
|
67
|
+
clearSlashSnapshots!();
|
|
68
|
+
const details = buildSlashInitialResult!("req-bg", {
|
|
69
|
+
workflowScript: "return runs.run(\"run\", { agent: \"scout\", task: \"inspect\" })",
|
|
70
|
+
async: true,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.equal(details.result.details.mode, "workflow");
|
|
74
|
+
assert.equal(details.result.details.background, true);
|
|
75
|
+
assert.equal(details.result.details.asyncId, undefined);
|
|
76
|
+
});
|
|
77
|
+
|
|
66
78
|
it("does not assign a parallel child update to another chain placeholder", () => {
|
|
67
79
|
clearSlashSnapshots!();
|
|
68
80
|
const details = buildSlashInitialResult!("req-parallel", {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function writeNodeCommand(directory: string, name: string, source: string): string {
|
|
5
|
+
if (process.platform === "win32") {
|
|
6
|
+
const scriptPath = path.join(directory, `${name}.cjs`);
|
|
7
|
+
const commandPath = path.join(directory, `${name}.cmd`);
|
|
8
|
+
fs.writeFileSync(scriptPath, source, "utf-8");
|
|
9
|
+
fs.writeFileSync(commandPath, `@echo off\r\n"${process.execPath}" "${scriptPath}" %*\r\n`, "utf-8");
|
|
10
|
+
return commandPath;
|
|
11
|
+
}
|
|
12
|
+
const commandPath = path.join(directory, name);
|
|
13
|
+
fs.writeFileSync(commandPath, `#!/usr/bin/env node\n${source}\n`, { encoding: "utf-8", mode: 0o755 });
|
|
14
|
+
return commandPath;
|
|
15
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
acquireActiveAsyncCapacity,
|
|
8
8
|
ActiveAsyncCapacityError,
|
|
9
9
|
getActiveAsyncCapacitySnapshot,
|
|
10
|
+
inspectActiveAsyncCapacityOwner,
|
|
10
11
|
transferActiveAsyncCapacity,
|
|
11
12
|
} from "../../src/runs/background/active-async-capacity.ts";
|
|
12
13
|
|
|
@@ -104,6 +105,32 @@ describe("active async capacity", () => {
|
|
|
104
105
|
}
|
|
105
106
|
});
|
|
106
107
|
|
|
108
|
+
it("explains runner release verdicts without mutating slots", () => {
|
|
109
|
+
const rootDir = tempRoot();
|
|
110
|
+
const asyncDir = path.join(rootDir, "runs", "run-a");
|
|
111
|
+
try {
|
|
112
|
+
const handle = acquireActiveAsyncCapacity({ sessionId: "session-a", limit: 2, runId: "run-a", kind: "runner", asyncDir }, { rootDir });
|
|
113
|
+
assert.ok(handle);
|
|
114
|
+
handle.markStarted("runner-a");
|
|
115
|
+
writeJson(path.join(asyncDir, "status.json"), { runId: "run-a", sessionId: "session-a", mode: "single", state: "complete", startedAt: 100, processTerminal: { version: 1, state: "unknown", runId: "run-a", runnerProcessInstanceId: "runner-a", reason: "process-tree-unverified" } });
|
|
116
|
+
|
|
117
|
+
const retained = inspectActiveAsyncCapacityOwner({ runId: "run-a", sessionId: "session-a", asyncDir }, { rootDir });
|
|
118
|
+
|
|
119
|
+
assert.equal(retained.relation, "current");
|
|
120
|
+
assert.equal(retained.release.state, "retained");
|
|
121
|
+
assert.match(retained.release.reason, /process-terminal proof is missing/);
|
|
122
|
+
assert.deepEqual(getActiveAsyncCapacitySnapshot("session-a", 2, { rootDir }), { used: 1, limit: 2 });
|
|
123
|
+
|
|
124
|
+
writeJson(path.join(asyncDir, "process-terminal.json"), observedProof("run-a", "runner-a"));
|
|
125
|
+
const releasable = inspectActiveAsyncCapacityOwner({ runId: "run-a", sessionId: "session-a", asyncDir }, { rootDir });
|
|
126
|
+
assert.equal(releasable.release.state, "releasable");
|
|
127
|
+
assert.match(releasable.release.reason, /observed process-terminal proof/);
|
|
128
|
+
assert.equal(releasable.slotDir ? fs.existsSync(releasable.slotDir) : false, true);
|
|
129
|
+
} finally {
|
|
130
|
+
fs.rmSync(rootDir, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
107
134
|
it("retains active and unknown runner proof, then releases matching observed proof", () => {
|
|
108
135
|
const rootDir = tempRoot();
|
|
109
136
|
const asyncDir = path.join(rootDir, "runs", "run-a");
|
|
@@ -211,6 +238,29 @@ describe("active async capacity", () => {
|
|
|
211
238
|
}
|
|
212
239
|
});
|
|
213
240
|
|
|
241
|
+
it("reports transferred source runs as not owned", () => {
|
|
242
|
+
const rootDir = tempRoot();
|
|
243
|
+
const sourceDir = path.join(rootDir, "runs", "source");
|
|
244
|
+
const nextDir = path.join(rootDir, "runs", "next");
|
|
245
|
+
try {
|
|
246
|
+
const source = acquireActiveAsyncCapacity({ sessionId: "session-a", limit: 1, runId: "source", kind: "runner", asyncDir: sourceDir }, { rootDir });
|
|
247
|
+
assert.ok(source);
|
|
248
|
+
source.markStarted("runner-source");
|
|
249
|
+
writeJson(path.join(sourceDir, "status.json"), { runId: "source", sessionId: "session-a", mode: "single", state: "paused", startedAt: 100 });
|
|
250
|
+
const next = transferActiveAsyncCapacity({ sessionId: "session-a", limit: 1, sourceRunId: "source", runId: "next", asyncDir: nextDir }, { rootDir });
|
|
251
|
+
assert.ok(next);
|
|
252
|
+
|
|
253
|
+
const inspection = inspectActiveAsyncCapacityOwner({ runId: "source", sessionId: "session-a", asyncDir: sourceDir }, { rootDir });
|
|
254
|
+
|
|
255
|
+
assert.equal(inspection.relation, "source");
|
|
256
|
+
assert.equal(inspection.owner?.runId, "next");
|
|
257
|
+
assert.equal(inspection.release.state, "not-owned");
|
|
258
|
+
assert.match(inspection.release.reason, /transferred to next/);
|
|
259
|
+
} finally {
|
|
260
|
+
fs.rmSync(rootDir, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
214
264
|
it("transfers a paused reservation without changing usage", () => {
|
|
215
265
|
const rootDir = tempRoot();
|
|
216
266
|
const sourceDir = path.join(rootDir, "runs", "source");
|
|
@@ -472,6 +472,13 @@ Do work
|
|
|
472
472
|
}
|
|
473
473
|
const oracle = agents.find((candidate) => candidate.name === "oracle");
|
|
474
474
|
assert.deepEqual(oracle?.aliases, ["advisor"]);
|
|
475
|
+
assert.doesNotMatch(oracle?.tools?.join(",") ?? "", /contact_supervisor/);
|
|
476
|
+
for (const name of ["scout", "researcher", "oracle", "reviewer"]) {
|
|
477
|
+
assert.equal(agents.find((candidate) => candidate.name === name)?.tools?.includes("intercom"), false, `${name} should not require generic intercom`);
|
|
478
|
+
}
|
|
479
|
+
assert.match(oracle?.systemPrompt ?? "", /asking or consulting the oracle/);
|
|
480
|
+
assert.match(oracle?.systemPrompt ?? "", /When runtime bridge instructions provide `contact_supervisor`/);
|
|
481
|
+
assert.match(oracle?.systemPrompt ?? "", /If no supervisor channel is available/);
|
|
475
482
|
assert.equal(agents.some((candidate) => candidate.name === "planner"), false);
|
|
476
483
|
assert.equal(agents.some((candidate) => candidate.name === "context-builder"), false);
|
|
477
484
|
});
|
|
@@ -493,6 +500,7 @@ describe("agent frontmatter launch defaults", () => {
|
|
|
493
500
|
filePath,
|
|
494
501
|
defaultAsync: false,
|
|
495
502
|
defaultTimeoutMs: 90_000,
|
|
503
|
+
defaultToolTimeoutMs: 600_000,
|
|
496
504
|
defaultTurnBudget: { maxTurns: 12, graceTurns: 2 },
|
|
497
505
|
defaultAcceptance: { level: "none", reason: "lightweight lookup" },
|
|
498
506
|
};
|
|
@@ -500,6 +508,7 @@ describe("agent frontmatter launch defaults", () => {
|
|
|
500
508
|
const serialized = serializeAgent(agent);
|
|
501
509
|
assert.match(serialized, /^async: false$/m);
|
|
502
510
|
assert.match(serialized, /^timeoutMs: 90000$/m);
|
|
511
|
+
assert.match(serialized, /^toolTimeoutMs: 600000$/m);
|
|
503
512
|
assert.match(serialized, /^turnBudget: \{"maxTurns":12,"graceTurns":2\}$/m);
|
|
504
513
|
assert.match(serialized, /^acceptance: \{"level":"none","reason":"lightweight lookup"\}$/m);
|
|
505
514
|
writeAgent(filePath, serialized);
|
|
@@ -507,6 +516,7 @@ describe("agent frontmatter launch defaults", () => {
|
|
|
507
516
|
const worker = discoverAgents(dir, "project").agents.find((candidate) => candidate.name === "worker");
|
|
508
517
|
assert.equal(worker?.defaultAsync, false);
|
|
509
518
|
assert.equal(worker?.defaultTimeoutMs, 90_000);
|
|
519
|
+
assert.equal(worker?.defaultToolTimeoutMs, 600_000);
|
|
510
520
|
assert.deepEqual(worker?.defaultTurnBudget, { maxTurns: 12, graceTurns: 2 });
|
|
511
521
|
assert.deepEqual(worker?.defaultAcceptance, { level: "none", reason: "lightweight lookup" });
|
|
512
522
|
});
|
|
@@ -601,6 +611,24 @@ Do work
|
|
|
601
611
|
/Agent 'worker' has invalid async frontmatter; expected true or false/,
|
|
602
612
|
);
|
|
603
613
|
});
|
|
614
|
+
|
|
615
|
+
it("rejects oversized toolTimeoutMs frontmatter at discovery", () => {
|
|
616
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-agent-oversized-tool-timeout-"));
|
|
617
|
+
tempDirs.push(dir);
|
|
618
|
+
writeAgent(path.join(dir, ".selesai", "agents", "worker.md"), `---
|
|
619
|
+
name: worker
|
|
620
|
+
description: Worker
|
|
621
|
+
toolTimeoutMs: 2147483648
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
Do work
|
|
625
|
+
`);
|
|
626
|
+
|
|
627
|
+
assert.throws(
|
|
628
|
+
() => discoverAgents(dir, "project"),
|
|
629
|
+
/Agent 'worker' has invalid toolTimeoutMs frontmatter; expected a positive integer no larger than 2147483647/,
|
|
630
|
+
);
|
|
631
|
+
});
|
|
604
632
|
});
|
|
605
633
|
|
|
606
634
|
describe("chain discovery", () => {
|
|
@@ -88,6 +88,67 @@ describe("builtin agent overrides", () => {
|
|
|
88
88
|
assert.equal(reviewer?.modelSource, undefined);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
it("lets a builtin agent inherit Pi's normal tools from an override", () => {
|
|
92
|
+
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
93
|
+
subagents: {
|
|
94
|
+
agentOverrides: {
|
|
95
|
+
researcher: { tools: "inherit" },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const researcher = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "researcher");
|
|
101
|
+
assert.equal(researcher?.tools, undefined);
|
|
102
|
+
assert.equal(researcher?.mcpDirectTools, undefined);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("keeps strict builtin tools unless a role opts into inheritance", () => {
|
|
106
|
+
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
107
|
+
subagents: {
|
|
108
|
+
agentOverrides: {
|
|
109
|
+
researcher: { tools: "inherit" },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const builtins = discoverAgentsAll(tempProject).builtin;
|
|
115
|
+
assert.equal(builtins.find((agent) => agent.name === "researcher")?.tools, undefined);
|
|
116
|
+
assert.deepEqual(builtins.find((agent) => agent.name === "reviewer")?.tools, ["read", "grep", "find", "ls"]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("keeps explicit empty builtin tool allowlists distinct from inherited tools", () => {
|
|
120
|
+
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
121
|
+
subagents: {
|
|
122
|
+
agentOverrides: {
|
|
123
|
+
researcher: { tools: [] },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const researcher = discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "researcher");
|
|
129
|
+
assert.deepEqual(researcher?.tools, []);
|
|
130
|
+
assert.equal(researcher?.mcpDirectTools, undefined);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("surfaces invalid string tool override settings", () => {
|
|
134
|
+
const settingsPath = path.join(tempHome, ".selesai", "agent", "settings.json");
|
|
135
|
+
writeJson(settingsPath, {
|
|
136
|
+
subagents: {
|
|
137
|
+
agentOverrides: {
|
|
138
|
+
researcher: { tools: "read" },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
assert.throws(
|
|
144
|
+
() => discoverAgents(tempProject, "both"),
|
|
145
|
+
(error: unknown) => error instanceof Error
|
|
146
|
+
&& error.message.includes(settingsPath)
|
|
147
|
+
&& error.message.includes("researcher")
|
|
148
|
+
&& error.message.includes("tools"),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
91
152
|
it("clears subagents.defaultModel provenance for same-value agent model overrides", () => {
|
|
92
153
|
writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
|
|
93
154
|
subagents: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { StreamFn } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import { agentStreamOptions } from "../../src/shared/agent-stream-options.ts";
|
|
5
|
+
|
|
6
|
+
describe("agent stream options", () => {
|
|
7
|
+
it("supports the Pi 0.81 and 0.84 constructor option names", () => {
|
|
8
|
+
const streamFn: StreamFn = () => { throw new Error("not called"); };
|
|
9
|
+
const options = agentStreamOptions(streamFn);
|
|
10
|
+
|
|
11
|
+
assert.equal(options.streamFunction, streamFn);
|
|
12
|
+
assert.equal(options.streamFn, streamFn);
|
|
13
|
+
});
|
|
14
|
+
});
|