@selesai/code 0.13.32 → 0.13.34
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 +28 -0
- package/dist/extensions/capability-gateway/catalog.ts +4 -1
- package/dist/extensions/capability-gateway/index.test.ts +93 -4
- package/dist/extensions/capability-gateway/index.ts +294 -59
- package/dist/extensions/capability-gateway/integration.test.ts +71 -0
- package/dist/extensions/capability-gateway/routing.test.ts +154 -1
- package/dist/extensions/capability-gateway/routing.ts +227 -45
- package/dist/extensions/jev/decisions.test.ts +37 -0
- package/dist/extensions/jev/decisions.ts +148 -43
- package/dist/extensions/jev-ask-tool.test.ts +436 -0
- package/dist/extensions/jev-ask-tool.ts +587 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-hermes-memory/README.md +11 -36
- package/dist/extensions/pi-hermes-memory/src/config.ts +36 -8
- package/dist/extensions/pi-hermes-memory/src/constants.ts +5 -5
- package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +60 -46
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +26 -2
- package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +7 -1
- package/dist/extensions/pi-intercom/index.ts +5 -1
- package/dist/extensions/pi-subagents/agents/worker.md +3 -2
- package/dist/extensions/pi-subagents/docs/agents.md +2 -0
- package/dist/extensions/pi-subagents/docs/extension-api.md +3 -1
- package/dist/extensions/pi-subagents/docs/observability.md +2 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +2 -2
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +10 -1
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +3 -21
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +10 -3
- package/dist/extensions/pi-subagents/src/runs/background/workflow-terminal-proof.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +6 -2
- package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +67 -5
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +6 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/git-environment.ts +29 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +69 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +20 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +4 -238
- package/dist/extensions/pi-subagents/src/slash/subagent-cost.ts +280 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +29 -1
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +6 -3
- package/dist/extensions/pi-subagents/test/integration/single-execution.part-2.test.ts +25 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +5 -5
- package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan-permission-system.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +25 -0
- package/dist/extensions/pi-subagents/test/unit/git-environment.test.ts +38 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +105 -1
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/structured-output-rejection.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-terminal-proof.test.ts +98 -0
- package/dist/extensions/rtk.test.ts +21 -13
- package/dist/extensions/tps.test.ts +32 -1
- package/dist/extensions/tps.ts +3 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +8 -8
- package/dist/skills/pi-subagents/references/execution-controls.md +1 -1
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +1 -1
- package/docs/settings.md +46 -7
- package/package.json +3 -3
|
@@ -105,7 +105,7 @@ import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.ts";
|
|
|
105
105
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
106
106
|
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
107
107
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
108
|
-
import { clearStructuredOutputCaptures, createStructuredOutputFileCapture, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
108
|
+
import { clearStructuredOutputCaptures, createStructuredOutputFileCapture, createStructuredOutputRuntime, formatStructuredOutputRejectionError, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
109
109
|
import { formatMidToolExitError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
110
110
|
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
111
111
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
@@ -285,6 +285,7 @@ interface StepResult {
|
|
|
285
285
|
review?: import("../../shared/types.ts").ReviewProjection;
|
|
286
286
|
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
287
287
|
structuredOutput?: unknown;
|
|
288
|
+
structuredOutputFailed?: boolean;
|
|
288
289
|
structuredOutputPath?: string;
|
|
289
290
|
structuredOutputSchemaPath?: string;
|
|
290
291
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
@@ -1221,7 +1222,8 @@ export async function runSingleStepInner(
|
|
|
1221
1222
|
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
1222
1223
|
outputPath: effectiveStructuredOutput.outputPath,
|
|
1223
1224
|
});
|
|
1224
|
-
if (structured.error) structuredError =
|
|
1225
|
+
if (structured.error === MISSING_STRUCTURED_OUTPUT_CALL_ERROR) structuredError = formatStructuredOutputRejectionError(run.messages);
|
|
1226
|
+
else if (structured.error) structuredError = structured.error;
|
|
1225
1227
|
else {
|
|
1226
1228
|
structuredOutput = structured.value;
|
|
1227
1229
|
const acceptanceReport = readStructuredOutputAcceptanceReport(effectiveStructuredOutput);
|
|
@@ -1343,7 +1345,7 @@ export async function runSingleStepInner(
|
|
|
1343
1345
|
afterCompactionSettlement: run.afterCompactionSettlement === true,
|
|
1344
1346
|
});
|
|
1345
1347
|
const fileMutationEffect = completionEvidence.fileMutation ?? (missingRequiredOutputAfterMutation ? { status: "observed" as const, expected: completionEvidence.mutationExpected, attempted: true, evidence: mutationEvidence } : undefined);
|
|
1346
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as RunChildSessionResult;
|
|
1348
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, structuredOutputFailed: structuredError ? true : undefined, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as RunChildSessionResult;
|
|
1347
1349
|
const abortRecovery = !attempt.success ? planAbortRecovery({
|
|
1348
1350
|
messages: run.messages,
|
|
1349
1351
|
error,
|
|
@@ -1603,6 +1605,7 @@ export async function runSingleStepInner(
|
|
|
1603
1605
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
1604
1606
|
...((finalResult as (RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
|
|
1605
1607
|
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (RunChildSessionResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
1608
|
+
structuredOutputFailed: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : finalResult?.structuredOutputFailed,
|
|
1606
1609
|
structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.outputPath,
|
|
1607
1610
|
structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
1608
1611
|
acceptance: effectiveAcceptance,
|
|
@@ -3788,6 +3791,7 @@ export async function runSubagent(
|
|
|
3788
3791
|
review: pr.review,
|
|
3789
3792
|
timeoutRecovery: pr.timeoutRecovery,
|
|
3790
3793
|
structuredOutput: pr.structuredOutput,
|
|
3794
|
+
structuredOutputFailed: pr.structuredOutputFailed,
|
|
3791
3795
|
structuredOutputPath: pr.structuredOutputPath,
|
|
3792
3796
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
3793
3797
|
acceptance: pr.acceptance,
|
|
@@ -4245,6 +4249,7 @@ export async function runSubagent(
|
|
|
4245
4249
|
review: pr.review,
|
|
4246
4250
|
timeoutRecovery: pr.timeoutRecovery,
|
|
4247
4251
|
structuredOutput: pr.structuredOutput,
|
|
4252
|
+
structuredOutputFailed: pr.structuredOutputFailed,
|
|
4248
4253
|
structuredOutputPath: pr.structuredOutputPath,
|
|
4249
4254
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
4250
4255
|
acceptance: pr.acceptance,
|
|
@@ -4539,6 +4544,7 @@ export async function runSubagent(
|
|
|
4539
4544
|
review: singleResult.review,
|
|
4540
4545
|
timeoutRecovery: singleResult.timeoutRecovery,
|
|
4541
4546
|
structuredOutput: singleResult.structuredOutput,
|
|
4547
|
+
structuredOutputFailed: singleResult.structuredOutputFailed,
|
|
4542
4548
|
structuredOutputPath: singleResult.structuredOutputPath,
|
|
4543
4549
|
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
4544
4550
|
acceptance: singleResult.acceptance,
|
|
@@ -4923,6 +4929,7 @@ export async function runSubagent(
|
|
|
4923
4929
|
review: r.review,
|
|
4924
4930
|
effects: r.effects,
|
|
4925
4931
|
structuredOutput: r.structuredOutput,
|
|
4932
|
+
structuredOutputFailed: r.structuredOutputFailed,
|
|
4926
4933
|
structuredOutputPath: r.structuredOutputPath,
|
|
4927
4934
|
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
4928
4935
|
acceptance: r.acceptance,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AsyncStatus, ProcessTerminal, WorkflowChildSummary, WorkflowTerminalProof } from "../../shared/types.ts";
|
|
4
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
5
|
+
import { readProcessTerminal } from "./process-terminal.ts";
|
|
6
|
+
|
|
7
|
+
const TERMINAL_WORKFLOW_STATES = new Set<WorkflowChildSummary["workflowState"]>(["completed", "failed", "stopped"]);
|
|
8
|
+
|
|
9
|
+
export function isTerminalAsyncState(state: AsyncStatus["state"]): boolean {
|
|
10
|
+
return state !== "queued" && state !== "running" && state !== "paused";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type WorkflowChildProcessEvidence =
|
|
14
|
+
| { state: "observed"; children: ProcessTerminal[] }
|
|
15
|
+
| { state: "pending" | "unknown"; reason: string };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Process evidence for a workflow's async children. Status proofs and capacity
|
|
19
|
+
* release both use this so they cannot disagree about the same workflow.
|
|
20
|
+
* Synchronous children run inside the workflow host and have no process of their own.
|
|
21
|
+
*/
|
|
22
|
+
export function readWorkflowChildProcessEvidence(workflowAsyncDir: string, steps: AsyncStatus["steps"]): WorkflowChildProcessEvidence {
|
|
23
|
+
const children: ProcessTerminal[] = [];
|
|
24
|
+
for (const step of steps ?? []) {
|
|
25
|
+
const label = step.workflowKey ?? step.agent;
|
|
26
|
+
if (typeof step.async !== "boolean") return { state: "unknown", reason: `workflow child ${label} is missing async classification` };
|
|
27
|
+
if (!step.async) continue;
|
|
28
|
+
if (!step.runId || path.basename(step.runId) !== step.runId) return { state: "unknown", reason: `async workflow child ${label} is missing run id` };
|
|
29
|
+
const childDir = path.join(path.dirname(workflowAsyncDir), step.runId);
|
|
30
|
+
if (!fs.existsSync(childDir)) return { state: "unknown", reason: `async workflow child ${label} directory is missing` };
|
|
31
|
+
const childStatus = readStatus(childDir);
|
|
32
|
+
if (!childStatus) return { state: "unknown", reason: `async workflow child ${label} status is missing or unreadable` };
|
|
33
|
+
if (!isTerminalAsyncState(childStatus.state)) return { state: "pending", reason: `async workflow child ${label} is still ${childStatus.state}` };
|
|
34
|
+
const recorded = childStatus.processTerminal;
|
|
35
|
+
if (!recorded?.runnerProcessInstanceId) return { state: "unknown", reason: `async workflow child ${label} has no runner process identity` };
|
|
36
|
+
// A runner startup failure records `not-started` in status and never writes a process-terminal sidecar.
|
|
37
|
+
if (recorded.state === "not-started" && recorded.runId === step.runId && typeof childStatus.error === "string" && childStatus.error) {
|
|
38
|
+
children.push(recorded);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const proof = readProcessTerminal(childDir, { runId: step.runId, runnerProcessInstanceId: recorded.runnerProcessInstanceId });
|
|
42
|
+
if (proof?.state !== "observed" || proof.runId !== step.runId) {
|
|
43
|
+
return { state: !proof || proof.state === "pending" ? "pending" : "unknown", reason: `async workflow child ${label} process-terminal proof is ${proof?.state ?? "missing"}` };
|
|
44
|
+
}
|
|
45
|
+
children.push(proof);
|
|
46
|
+
}
|
|
47
|
+
return { state: "observed", children };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function unresolved(runId: string, state: "pending" | "unknown", dispatchClosed: boolean, reason: string): WorkflowTerminalProof {
|
|
51
|
+
return { version: 1, kind: "workflow", runId, state, dispatchClosed, reason };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A persistent workflow host is terminal only after dispatch closes and every async child has process evidence. */
|
|
55
|
+
export function readWorkflowTerminalProof(asyncDir: string, steps: AsyncStatus["steps"], summary: WorkflowChildSummary, hostCommandCount: number, closedAt: number): WorkflowTerminalProof {
|
|
56
|
+
const runId = summary.workflowRunId;
|
|
57
|
+
if (!summary.inventoryComplete || !TERMINAL_WORKFLOW_STATES.has(summary.workflowState)) {
|
|
58
|
+
return unresolved(runId, "pending", false, "Workflow dispatch is still open.");
|
|
59
|
+
}
|
|
60
|
+
if (hostCommandCount > 0) {
|
|
61
|
+
return unresolved(runId, "unknown", true, "Workflow host commands have no process-terminal proof.");
|
|
62
|
+
}
|
|
63
|
+
const evidence = readWorkflowChildProcessEvidence(asyncDir, steps);
|
|
64
|
+
if (evidence.state !== "observed") return unresolved(runId, evidence.state, true, evidence.reason);
|
|
65
|
+
const observedAt = Math.max(closedAt, ...evidence.children.map((child) => child.state === "observed" ? child.observedAt : 0));
|
|
66
|
+
return { version: 1, kind: "workflow", runId, state: "observed", dispatchClosed: true, observedAt, children: evidence.children };
|
|
67
|
+
}
|
|
@@ -71,7 +71,7 @@ import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
|
71
71
|
import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
72
72
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
73
73
|
import { assertThinkingWithinCeiling, intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
|
|
74
|
-
import { MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.ts";
|
|
74
|
+
import { formatStructuredOutputRejectionError, MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.ts";
|
|
75
75
|
import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.ts";
|
|
76
76
|
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
77
77
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
@@ -1431,10 +1431,14 @@ async function runSingleAttempt(
|
|
|
1431
1431
|
result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
|
|
1432
1432
|
result.structuredOutputPath = options.structuredOutput.outputPath;
|
|
1433
1433
|
const structured = capture.structuredOutput();
|
|
1434
|
-
if (!structuredOutputToolInvoked
|
|
1434
|
+
if (!structuredOutputToolInvoked) {
|
|
1435
1435
|
result.exitCode = 1;
|
|
1436
1436
|
result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1437
1437
|
result.structuredOutputFailed = true;
|
|
1438
|
+
} else if (!structured.called) {
|
|
1439
|
+
result.exitCode = 1;
|
|
1440
|
+
result.error = formatStructuredOutputRejectionError(result.messages ?? []);
|
|
1441
|
+
result.structuredOutputFailed = true;
|
|
1438
1442
|
} else {
|
|
1439
1443
|
result.structuredOutput = structured.value;
|
|
1440
1444
|
const acceptanceMode = options.structuredOutput.acceptanceReportPath
|
|
@@ -30,6 +30,17 @@ import {
|
|
|
30
30
|
} from "./capability-ceiling.ts";
|
|
31
31
|
|
|
32
32
|
const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
|
|
33
|
+
export const CAPABILITY_GATEWAY_EXTENSION_PATH = path.join(
|
|
34
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
35
|
+
"..",
|
|
36
|
+
"..",
|
|
37
|
+
"..",
|
|
38
|
+
"..",
|
|
39
|
+
"capability-gateway",
|
|
40
|
+
"index.ts",
|
|
41
|
+
);
|
|
42
|
+
const CAPABILITY_GATEWAY_TOOL_NAMES = ["capability_catalog", "capability_discover", "capability_skill_show"] as const;
|
|
43
|
+
const CAPABILITY_GATEWAY_ENABLED_ENV = "SELESAI_CAPABILITY_GATEWAY";
|
|
33
44
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
|
|
34
45
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
35
46
|
"subagent-prompt-runtime.ts",
|
|
@@ -172,6 +183,8 @@ export interface PiLaunchToolPlan {
|
|
|
172
183
|
extensionArgs: string[];
|
|
173
184
|
disableAmbientExtensions: boolean;
|
|
174
185
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
186
|
+
/** Whether the capability gateway is loaded for this child's permitted extension/tool policy. */
|
|
187
|
+
capabilityGatewayEnabled: boolean;
|
|
175
188
|
/** Non-fatal launch warnings; they do not change behavior. */
|
|
176
189
|
warnings: string[];
|
|
177
190
|
}
|
|
@@ -195,6 +208,14 @@ function hasPermissionRules(rules: PermissionRules | undefined): boolean {
|
|
|
195
208
|
return rules !== undefined && Object.keys(rules).length > 0;
|
|
196
209
|
}
|
|
197
210
|
|
|
211
|
+
function isCapabilityGatewayExtensionPath(value: string, cwd?: string): boolean {
|
|
212
|
+
const resolved = path.normalize(path.resolve(cwd ?? process.cwd(), value));
|
|
213
|
+
return (
|
|
214
|
+
resolved === path.normalize(CAPABILITY_GATEWAY_EXTENSION_PATH) ||
|
|
215
|
+
resolved === path.normalize(path.dirname(CAPABILITY_GATEWAY_EXTENSION_PATH))
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
198
219
|
/**
|
|
199
220
|
* Children are pi sessions inside the parent or the runner process; a spawned
|
|
200
221
|
* `pi` received extra MCP server definitions as a CLI argument, but a session
|
|
@@ -330,6 +351,9 @@ export function resolvePiLaunchToolPlan(
|
|
|
330
351
|
!excludedToolSet.has("subagent") &&
|
|
331
352
|
(!allowedToolSet || allowedToolSet.has("subagent"))
|
|
332
353
|
);
|
|
354
|
+
if (effectiveDeclaredBuiltinTools.includes("subagent_supervisor") && !fanoutAuthorized) {
|
|
355
|
+
throw new Error("Tool 'subagent_supervisor' requires fanout authorization: include 'subagent' in the effective tools allowlist or enable allowNestedSubagents.");
|
|
356
|
+
}
|
|
333
357
|
const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
|
|
334
358
|
? []
|
|
335
359
|
: (input.tools ?? []).filter(
|
|
@@ -363,13 +387,41 @@ export function resolvePiLaunchToolPlan(
|
|
|
363
387
|
(input.mcpDirectTools?.length ?? 0) > 0 ||
|
|
364
388
|
allowedToolSet !== undefined;
|
|
365
389
|
const internalTools = (input.structuredOutput ? ["structured_output"] : []).filter((tool) => !excludedToolSet.has(tool));
|
|
366
|
-
const
|
|
390
|
+
const baseEffectiveToolAllowlist = [
|
|
367
391
|
...new Set([
|
|
368
392
|
...effectiveDeclaredBuiltinTools,
|
|
369
393
|
...effectiveMcpTools,
|
|
370
394
|
...internalTools,
|
|
371
395
|
]),
|
|
372
396
|
];
|
|
397
|
+
const explicitlyNoTools =
|
|
398
|
+
explicitToolAllowlist &&
|
|
399
|
+
effectiveDeclaredBuiltinTools.length === 0 &&
|
|
400
|
+
effectiveMcpTools.length === 0;
|
|
401
|
+
const gatewayExplicitlyConfigured = [
|
|
402
|
+
...toolExtensionPaths,
|
|
403
|
+
...(input.extensions ?? []),
|
|
404
|
+
...(input.subagentOnlyExtensions ?? []),
|
|
405
|
+
].some((extensionPath) => isCapabilityGatewayExtensionPath(extensionPath, input.cwd));
|
|
406
|
+
const gatewayExtensionPolicyAllows =
|
|
407
|
+
capabilityCeiling?.denyExtensions !== true &&
|
|
408
|
+
fs.existsSync(CAPABILITY_GATEWAY_EXTENSION_PATH) &&
|
|
409
|
+
(input.extensions === undefined || gatewayExplicitlyConfigured);
|
|
410
|
+
const permittedGatewayTools = CAPABILITY_GATEWAY_TOOL_NAMES.filter(
|
|
411
|
+
(name) => (!allowedToolSet || allowedToolSet.has(name)) && !excludedToolSet.has(name),
|
|
412
|
+
);
|
|
413
|
+
const capabilityGatewayEnabled =
|
|
414
|
+
gatewayExtensionPolicyAllows &&
|
|
415
|
+
!explicitlyNoTools &&
|
|
416
|
+
permittedGatewayTools.length > 0 &&
|
|
417
|
+
process.env[CAPABILITY_GATEWAY_ENABLED_ENV] !== "0";
|
|
418
|
+
const gatewayTools = capabilityGatewayEnabled ? permittedGatewayTools : [];
|
|
419
|
+
const effectiveToolAllowlist = [
|
|
420
|
+
...new Set([
|
|
421
|
+
...baseEffectiveToolAllowlist,
|
|
422
|
+
...(explicitToolAllowlist ? gatewayTools : []),
|
|
423
|
+
]),
|
|
424
|
+
];
|
|
373
425
|
// Supervisor-coordination names stay in the --tools allowlist but are never
|
|
374
426
|
// strict requirements: children register contact_supervisor at runtime through
|
|
375
427
|
// the native supervisor channel (or pi-intercom). The pre-0.50 bridge always
|
|
@@ -383,6 +435,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
383
435
|
...(input.tools !== undefined ? effectiveDeclaredBuiltinTools : []),
|
|
384
436
|
...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
|
|
385
437
|
...internalTools,
|
|
438
|
+
...gatewayTools,
|
|
386
439
|
].filter((tool) => tool !== "contact_supervisor" && (!legacySupervisorPairing || tool !== "intercom"))),
|
|
387
440
|
]
|
|
388
441
|
: [];
|
|
@@ -395,6 +448,9 @@ export function resolvePiLaunchToolPlan(
|
|
|
395
448
|
const fastModeExtensions = resolveFastModeExtension({ fast: input.fast, model: input.model, modelCandidates: input.modelCandidates, agentName: input.agentName });
|
|
396
449
|
const runtimeExtensions = [
|
|
397
450
|
PROMPT_RUNTIME_EXTENSION_PATH,
|
|
451
|
+
...(capabilityGatewayEnabled && !gatewayExplicitlyConfigured
|
|
452
|
+
? [CAPABILITY_GATEWAY_EXTENSION_PATH]
|
|
453
|
+
: []),
|
|
398
454
|
...fastModeExtensions,
|
|
399
455
|
...(fanoutAuthorized ? [FANOUT_CHILD_EXTENSION_PATH] : []),
|
|
400
456
|
...(permSystemExt ? [permSystemExt] : []),
|
|
@@ -412,20 +468,25 @@ export function resolvePiLaunchToolPlan(
|
|
|
412
468
|
+ "List the extensions this child actually needs instead of an empty array.",
|
|
413
469
|
);
|
|
414
470
|
}
|
|
471
|
+
const gatewayFilteredExtensions = (extensions: string[]) =>
|
|
472
|
+
extensions.filter(
|
|
473
|
+
(extensionPath) =>
|
|
474
|
+
!isCapabilityGatewayExtensionPath(extensionPath, input.cwd) || capabilityGatewayEnabled,
|
|
475
|
+
);
|
|
415
476
|
const configuredExtensions = capabilityCeiling?.denyExtensions
|
|
416
477
|
? []
|
|
417
|
-
: [
|
|
478
|
+
: gatewayFilteredExtensions([
|
|
418
479
|
...toolExtensionPaths,
|
|
419
480
|
...(input.extensions ?? []),
|
|
420
481
|
...(input.subagentOnlyExtensions ?? []),
|
|
421
|
-
];
|
|
482
|
+
]);
|
|
422
483
|
const extensionArgs = disableAmbientExtensions
|
|
423
484
|
? [...new Set([...runtimeExtensions, ...configuredExtensions])]
|
|
424
485
|
: [
|
|
425
486
|
...new Set([
|
|
426
487
|
...runtimeExtensions,
|
|
427
|
-
...toolExtensionPaths,
|
|
428
|
-
...(input.subagentOnlyExtensions ?? []),
|
|
488
|
+
...gatewayFilteredExtensions(toolExtensionPaths),
|
|
489
|
+
...gatewayFilteredExtensions(input.subagentOnlyExtensions ?? []),
|
|
429
490
|
]),
|
|
430
491
|
];
|
|
431
492
|
const requestedToolNames =
|
|
@@ -494,6 +555,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
494
555
|
configuredExtensions,
|
|
495
556
|
extensionArgs,
|
|
496
557
|
disableAmbientExtensions,
|
|
558
|
+
capabilityGatewayEnabled,
|
|
497
559
|
warnings,
|
|
498
560
|
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
499
561
|
};
|
|
@@ -17,6 +17,12 @@ const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
|
17
17
|
"intercom",
|
|
18
18
|
"contact_supervisor",
|
|
19
19
|
"structured_output",
|
|
20
|
+
// Gateway controls only search, activate, or load capabilities; activation stays
|
|
21
|
+
// filtered by the child's own tool policy, so they are not mutation-capable.
|
|
22
|
+
// Kept literal: child-tool-plan transitively imports this module (via single-output).
|
|
23
|
+
"capability_catalog",
|
|
24
|
+
"capability_discover",
|
|
25
|
+
"capability_skill_show",
|
|
20
26
|
]);
|
|
21
27
|
|
|
22
28
|
// Cursor native edit/write often land as thinking traces (inactive_trace /
|
|
@@ -6,6 +6,7 @@ import { finished } from "node:stream/promises";
|
|
|
6
6
|
import type { ExternalProcessStatus } from "../../shared/types.ts";
|
|
7
7
|
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "../background/owned-process-tree.ts";
|
|
8
8
|
import { omitExtensionBindingsEnv } from "./extension-bindings.ts";
|
|
9
|
+
import { omitGitRoutingEnv } from "./git-environment.ts";
|
|
9
10
|
import {
|
|
10
11
|
invalidateExternalCliPreflight,
|
|
11
12
|
preflightExternalCli,
|
|
@@ -86,7 +87,7 @@ function narrowLimit(value: number | undefined, ceiling: number, label: string):
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
function externalEnvironment(allowlist: readonly string[] | undefined, values: Readonly<Record<string, string>> | undefined): NodeJS.ProcessEnv {
|
|
89
|
-
if (!allowlist) return omitExtensionBindingsEnv(process.env);
|
|
90
|
+
if (!allowlist) return omitGitRoutingEnv(omitExtensionBindingsEnv(process.env));
|
|
90
91
|
const allowed = new Set(allowlist);
|
|
91
92
|
const env: NodeJS.ProcessEnv = {};
|
|
92
93
|
for (const key of allowed) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ponytail: this static list can lag Git additions; refresh from `git rev-parse --local-env-vars` and indexed config keys when Git changes.
|
|
2
|
+
const GIT_ROUTING_VARIABLES = new Set([
|
|
3
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
4
|
+
"GIT_COMMON_DIR",
|
|
5
|
+
"GIT_CONFIG",
|
|
6
|
+
"GIT_CONFIG_COUNT",
|
|
7
|
+
"GIT_CONFIG_PARAMETERS",
|
|
8
|
+
"GIT_DIR",
|
|
9
|
+
"GIT_GRAFT_FILE",
|
|
10
|
+
"GIT_IMPLICIT_WORK_TREE",
|
|
11
|
+
"GIT_INDEX_FILE",
|
|
12
|
+
"GIT_NAMESPACE",
|
|
13
|
+
"GIT_NO_REPLACE_OBJECTS",
|
|
14
|
+
"GIT_OBJECT_DIRECTORY",
|
|
15
|
+
"GIT_PREFIX",
|
|
16
|
+
"GIT_REPLACE_REF_BASE",
|
|
17
|
+
"GIT_SHALLOW_FILE",
|
|
18
|
+
"GIT_WORK_TREE",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function isGitRoutingVariable(name: string): boolean {
|
|
22
|
+
const upper = name.toUpperCase();
|
|
23
|
+
return GIT_ROUTING_VARIABLES.has(upper) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(upper);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Remove inherited values that can route Git commands away from the child cwd. */
|
|
27
|
+
export function omitGitRoutingEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
28
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !isGitRoutingVariable(name)));
|
|
29
|
+
}
|
|
@@ -3,12 +3,81 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import type { Message } from "@earendil-works/pi-ai";
|
|
6
7
|
import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
|
|
7
8
|
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
8
9
|
import type { ResolvedAcceptanceReportMode } from "./acceptance.ts";
|
|
9
10
|
|
|
10
11
|
export const MISSING_STRUCTURED_OUTPUT_CALL_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
|
|
11
12
|
export const MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR = "Missing acceptanceReport in structured_output call; acceptance.report is \"on\".";
|
|
13
|
+
export const STRUCTURED_OUTPUT_REJECTION_ERROR = "structured_output was invoked but no valid output was captured.";
|
|
14
|
+
export const INVALID_STRUCTURED_OUTPUT_SCHEMA_ERROR = "Structured output invocation was rejected: invalid outputSchema.";
|
|
15
|
+
export const STRUCTURED_OUTPUT_VALIDATOR_UNAVAILABLE_ERROR = "Structured output invocation was rejected: validator unavailable.";
|
|
16
|
+
export const MAX_STRUCTURED_OUTPUT_REJECTION_ERROR_BYTES = 4096;
|
|
17
|
+
|
|
18
|
+
function utf8Prefix(value: string, maxBytes: number): string {
|
|
19
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
|
|
20
|
+
const suffix = "...";
|
|
21
|
+
const contentLimit = maxBytes - Buffer.byteLength(suffix, "utf8");
|
|
22
|
+
let bytes = 0;
|
|
23
|
+
let result = "";
|
|
24
|
+
for (const character of value) {
|
|
25
|
+
const characterBytes = Buffer.byteLength(character, "utf8");
|
|
26
|
+
if (bytes + characterBytes > contentLimit) break;
|
|
27
|
+
result += character;
|
|
28
|
+
bytes += characterBytes;
|
|
29
|
+
}
|
|
30
|
+
return result + suffix;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function messageText(content: unknown): string {
|
|
34
|
+
if (typeof content === "string") return content;
|
|
35
|
+
if (!Array.isArray(content)) return "";
|
|
36
|
+
return content
|
|
37
|
+
.filter((part): part is { type: "text"; text: string } => Boolean(part) && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { text?: unknown }).text === "string")
|
|
38
|
+
.map((part) => part.text)
|
|
39
|
+
.join("\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sanitizeStructuredOutputRejection(text: string): string {
|
|
43
|
+
const withoutControls = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
44
|
+
if (/invalid outputSchema(?:\s*:|$)/i.test(withoutControls)) return INVALID_STRUCTURED_OUTPUT_SCHEMA_ERROR;
|
|
45
|
+
if (/failed to validate structured output:|cannot load typebox\/compile/i.test(withoutControls)) return STRUCTURED_OUTPUT_VALIDATOR_UNAVAILABLE_ERROR;
|
|
46
|
+
const validationMarker = "Structured output validation failed:";
|
|
47
|
+
const markerIndex = withoutControls.indexOf(validationMarker);
|
|
48
|
+
const diagnostic = markerIndex >= 0 ? withoutControls.slice(markerIndex) : withoutControls;
|
|
49
|
+
const lines = diagnostic.split(/\r?\n/).filter((line) => {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
return trimmed.length > 0
|
|
52
|
+
&& !/^at\s/.test(trimmed)
|
|
53
|
+
&& !/^(?:arguments?|parameters?|submitted value|input|outputSchema|schema|stack)\s*:/i.test(trimmed);
|
|
54
|
+
});
|
|
55
|
+
const sanitized = lines.join("\n")
|
|
56
|
+
.replace(/\b(received|actual|got)(?:\s+value)?\s*[:=]\s*(?:\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|\{[^\n]*\}|\[[^\n]*\]|\S+)/gi, "$1: [redacted]")
|
|
57
|
+
.trim();
|
|
58
|
+
return sanitized || STRUCTURED_OUTPUT_REJECTION_ERROR;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Returns bounded evidence from the latest failed structured_output result in a terminal transcript. */
|
|
62
|
+
export function formatStructuredOutputRejectionError(messages: readonly Message[]): string {
|
|
63
|
+
const toolCallIds = new Set<string>();
|
|
64
|
+
for (const message of messages) {
|
|
65
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
66
|
+
for (const part of message.content) {
|
|
67
|
+
const call = part as { type?: unknown; id?: unknown; name?: unknown };
|
|
68
|
+
if (call.type === "toolCall" && call.name === "structured_output" && typeof call.id === "string") toolCallIds.add(call.id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
72
|
+
const message = messages[index] as Message & { toolCallId?: unknown; toolName?: unknown; isError?: unknown };
|
|
73
|
+
if (message.role !== "toolResult" || message.isError !== true) continue;
|
|
74
|
+
const matchingName = message.toolName === "structured_output";
|
|
75
|
+
const matchingId = typeof message.toolCallId === "string" && toolCallIds.has(message.toolCallId);
|
|
76
|
+
if (!matchingName && !matchingId) continue;
|
|
77
|
+
return utf8Prefix(sanitizeStructuredOutputRejection(messageText(message.content)), MAX_STRUCTURED_OUTPUT_REJECTION_ERROR_BYTES);
|
|
78
|
+
}
|
|
79
|
+
return STRUCTURED_OUTPUT_REJECTION_ERROR;
|
|
80
|
+
}
|
|
12
81
|
|
|
13
82
|
export interface StructuredOutputRuntime {
|
|
14
83
|
schema: JsonSchemaObject;
|
|
@@ -704,6 +704,25 @@ export type ProcessTerminal =
|
|
|
704
704
|
diagnostic?: string;
|
|
705
705
|
});
|
|
706
706
|
|
|
707
|
+
export type WorkflowTerminalProof =
|
|
708
|
+
| {
|
|
709
|
+
version: 1;
|
|
710
|
+
kind: "workflow";
|
|
711
|
+
runId: string;
|
|
712
|
+
state: "observed";
|
|
713
|
+
dispatchClosed: true;
|
|
714
|
+
observedAt: number;
|
|
715
|
+
children: ProcessTerminal[];
|
|
716
|
+
}
|
|
717
|
+
| {
|
|
718
|
+
version: 1;
|
|
719
|
+
kind: "workflow";
|
|
720
|
+
runId: string;
|
|
721
|
+
state: "pending" | "unknown";
|
|
722
|
+
dispatchClosed: boolean;
|
|
723
|
+
reason: string;
|
|
724
|
+
};
|
|
725
|
+
|
|
707
726
|
/** Identifies the durable schedule that launched a run, so its completion is attributable. */
|
|
708
727
|
export interface ScheduleOrigin {
|
|
709
728
|
id: string;
|
|
@@ -1398,6 +1417,7 @@ export interface Details {
|
|
|
1398
1417
|
context?: "fresh" | "fork" | "mixed";
|
|
1399
1418
|
results: SingleResult[];
|
|
1400
1419
|
workflowChildren?: WorkflowChildSummary;
|
|
1420
|
+
workflowTerminalProof?: WorkflowTerminalProof;
|
|
1401
1421
|
/**
|
|
1402
1422
|
* Terminal completion payloads for runs this bg_wait call observed
|
|
1403
1423
|
* finishing. Async completions travel as result files that are consumed and
|