@selesai/code 0.13.2 → 0.13.4
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 +20 -0
- package/dist/defaults/models.json +85 -13
- package/dist/extensions/cost-reconcile.test.ts +200 -4
- package/dist/extensions/cost-reconcile.ts +88 -102
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +4 -5
- package/dist/extensions/pi-intercom/config.test.ts +3 -31
- package/dist/extensions/pi-intercom/config.ts +0 -15
- package/dist/extensions/pi-intercom/index.ts +9 -46
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +49 -57
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +20 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +8 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +27 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +4 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +48 -243
- package/dist/extensions/pi-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +87 -37
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +49 -8
- 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 +13 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +34 -27
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +157 -28
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -2
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +13 -2
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +10 -2
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +11 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +98 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +138 -4
- package/dist/extensions/pi-subagents/src/runs/shared/background-process-options.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +52 -3
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +47 -1
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +45 -18
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +21 -2
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +15 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +34 -1
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +11 -3
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +31 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +597 -112
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +40 -1
- package/dist/extensions/pi-subagents/src/workflows/host-command.ts +6 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +53 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +206 -38
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +522 -31
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +57 -1
- package/dist/extensions/pi-subagents/test/unit/background-process-options.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +44 -4
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +93 -17
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +103 -26
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +22 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +21 -6
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +11 -0
- package/dist/extensions/pi-web-agent/CHANGELOG.md +410 -0
- package/dist/extensions/pi-web-agent/README.md +131 -0
- package/dist/extensions/pi-web-agent/package.json +4 -2
- package/dist/extensions/pi-web-agent/src/backends/config.ts +62 -5
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +96 -6
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +183 -47
- package/dist/extensions/pi-web-agent/src/extension.ts +62 -25
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +19 -11
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +5 -4
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +2 -25
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +5 -2
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +2 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +72 -7
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +8 -1
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +28 -3
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +4 -0
- package/dist/extensions/pi-web-agent/src/orchestration/url.ts +35 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +18 -6
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +14 -2
- package/dist/extensions/pi-web-agent/src/readers/github-reader.ts +150 -0
- package/dist/extensions/pi-web-agent/src/readers/limits.ts +3 -0
- package/dist/extensions/pi-web-agent/src/readers/pdf-reader.ts +87 -0
- package/dist/extensions/pi-web-agent/src/readers/resolver.ts +25 -0
- package/dist/extensions/pi-web-agent/src/readers/types.ts +11 -0
- package/dist/extensions/pi-web-agent/src/readers/youtube-reader.ts +79 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +32 -5
- package/dist/extensions/pi-web-agent/src/search/exa.ts +109 -0
- package/dist/extensions/pi-web-agent/src/search/fanout.ts +154 -0
- package/dist/extensions/pi-web-agent/src/search/tavily.ts +113 -0
- package/dist/extensions/pi-web-agent/src/search/youcom.ts +109 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +22 -9
- package/dist/extensions/pi-web-agent/src/types.ts +19 -4
- package/dist/extensions/tokenin-onboarding.ts +302 -0
- package/package.json +1 -1
|
@@ -117,6 +117,7 @@ const FAST_MODE_ALLOWED_MODELS = new Set([
|
|
|
117
117
|
"openai-codex/gpt-5.6-luna",
|
|
118
118
|
"openai-codex/gpt-5.6-sol",
|
|
119
119
|
]);
|
|
120
|
+
const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
120
121
|
export const SUBAGENT_CHILD_ENV = "SELESAI_SUBAGENT_CHILD";
|
|
121
122
|
export const SUBAGENT_ORCHESTRATOR_TARGET_ENV =
|
|
122
123
|
"SELESAI_SUBAGENT_ORCHESTRATOR_TARGET";
|
|
@@ -139,6 +140,7 @@ export const SUBAGENT_PARENT_PATH_ENV = "SELESAI_SUBAGENT_PARENT_PATH";
|
|
|
139
140
|
export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV =
|
|
140
141
|
"SELESAI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
|
|
141
142
|
export const SUBAGENT_PARENT_SESSION_ENV = "SELESAI_SUBAGENT_PARENT_SESSION";
|
|
143
|
+
export const SUBAGENT_FORK_CACHE_KEY_ENV = "SELESAI_SUBAGENT_FORK_CACHE_KEY";
|
|
142
144
|
export const SUBAGENT_STEER_INBOX_ENV = "SELESAI_SUBAGENT_STEER_INBOX";
|
|
143
145
|
export const SUBAGENT_STEER_CAPABILITY_ENV = "SELESAI_SUBAGENT_STEER_CAPABILITY";
|
|
144
146
|
export const SUBAGENT_STEER_ACK_DIR_ENV = "SELESAI_SUBAGENT_STEER_ACK_DIR";
|
|
@@ -146,8 +148,16 @@ export const PI_INTERCOM_STABLE_ID_ENV = "PI_INTERCOM_STABLE_ID";
|
|
|
146
148
|
export const PI_INTERCOM_SESSION_ID_ENV = "PI_INTERCOM_SESSION_ID";
|
|
147
149
|
export const SUBAGENT_INHERIT_GLOBAL_CONTEXT_ENV = "SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT";
|
|
148
150
|
|
|
151
|
+
export function deriveForkPromptCacheKey(parentSessionId: string | undefined): string | undefined {
|
|
152
|
+
const parent = parentSessionId?.trim();
|
|
153
|
+
if (!parent) return undefined;
|
|
154
|
+
const digest = createHash("sha256").update(parent).digest("hex").slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH - "pi-fork:".length);
|
|
155
|
+
return `pi-fork:${digest}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
149
158
|
export interface BuildPiArgsInput {
|
|
150
159
|
parentSessionId?: string;
|
|
160
|
+
forkCacheKey?: string;
|
|
151
161
|
baseArgs: string[];
|
|
152
162
|
task: string;
|
|
153
163
|
sessionEnabled: boolean;
|
|
@@ -507,8 +517,13 @@ export function resolvePiLaunchToolPlan(
|
|
|
507
517
|
throw new Error(formatUnresolvedMcpDirectToolSelectors(mcpResolution.unresolvedSelectors));
|
|
508
518
|
}
|
|
509
519
|
const resolvedMcpSelections = mcpResolution.selections;
|
|
520
|
+
const resolvedMcpNames = new Set(resolvedMcpSelections.map((selection) => selection.name));
|
|
521
|
+
const legacyMcpNameCounts = countLegacyUnderscoreMcpToolNames(resolvedMcpSelections);
|
|
510
522
|
const effectiveMcpSelections = resolvedMcpSelections.filter(
|
|
511
|
-
(selection) =>
|
|
523
|
+
(selection) =>
|
|
524
|
+
!allowedToolSet ||
|
|
525
|
+
allowedToolSet.has(selection.name) ||
|
|
526
|
+
isLegacyUnderscoreMcpToolAllowed(selection, allowedToolSet, resolvedMcpNames, legacyMcpNameCounts),
|
|
512
527
|
);
|
|
513
528
|
const effectiveMcpTools = effectiveMcpSelections.map(
|
|
514
529
|
(selection) => selection.name,
|
|
@@ -663,6 +678,36 @@ export function resolvePiLaunchToolPlan(
|
|
|
663
678
|
};
|
|
664
679
|
}
|
|
665
680
|
|
|
681
|
+
// Capability ceilings persisted before #1685 may still name hyphenated MCP server prefixes with underscores.
|
|
682
|
+
function countLegacyUnderscoreMcpToolNames(selections: readonly ResolvedMcpDirectToolSelection[]): Map<string, number> {
|
|
683
|
+
const counts = new Map<string, number>();
|
|
684
|
+
for (const selection of selections) {
|
|
685
|
+
const legacyName = legacyUnderscoreMcpToolName(selection);
|
|
686
|
+
if (legacyName !== selection.name) counts.set(legacyName, (counts.get(legacyName) ?? 0) + 1);
|
|
687
|
+
}
|
|
688
|
+
return counts;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function isLegacyUnderscoreMcpToolAllowed(
|
|
692
|
+
selection: ResolvedMcpDirectToolSelection,
|
|
693
|
+
allowedToolSet: ReadonlySet<string>,
|
|
694
|
+
resolvedMcpNames: ReadonlySet<string>,
|
|
695
|
+
legacyMcpNameCounts: ReadonlyMap<string, number>,
|
|
696
|
+
): boolean {
|
|
697
|
+
const legacyName = legacyUnderscoreMcpToolName(selection);
|
|
698
|
+
return legacyMcpNameCounts.get(legacyName) === 1 && !resolvedMcpNames.has(legacyName) && allowedToolSet.has(legacyName);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function legacyUnderscoreMcpToolName(selection: ResolvedMcpDirectToolSelection): string {
|
|
702
|
+
const slash = selection.selector.indexOf("/");
|
|
703
|
+
if (slash < 1) return selection.name;
|
|
704
|
+
const toolName = selection.selector.slice(slash + 1);
|
|
705
|
+
const suffix = `_${toolName}`;
|
|
706
|
+
if (!selection.name.endsWith(suffix)) return selection.name;
|
|
707
|
+
const prefix = selection.name.slice(0, -suffix.length);
|
|
708
|
+
return `${prefix.replace(/-/g, "_")}${suffix}`;
|
|
709
|
+
}
|
|
710
|
+
|
|
666
711
|
/** Escape XML-significant characters in a string for safe attribute interpolation. */
|
|
667
712
|
function escapeXmlAttr(value: string): string {
|
|
668
713
|
return value
|
|
@@ -974,6 +1019,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
974
1019
|
|
|
975
1020
|
env[SUBAGENT_PARENT_SESSION_ENV] =
|
|
976
1021
|
input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
|
|
1022
|
+
env[SUBAGENT_FORK_CACHE_KEY_ENV] = input.forkCacheKey?.trim() || undefined;
|
|
977
1023
|
|
|
978
1024
|
return {
|
|
979
1025
|
args,
|
|
@@ -8,6 +8,17 @@ export interface SingleOutputSnapshot {
|
|
|
8
8
|
exists: boolean;
|
|
9
9
|
mtimeMs?: number;
|
|
10
10
|
size?: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function missingFileStatError(error: unknown): boolean {
|
|
15
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
16
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function snapshotFromStatError(error: unknown): SingleOutputSnapshot {
|
|
20
|
+
if (missingFileStatError(error)) return { exists: false };
|
|
21
|
+
return { exists: false, error: error instanceof Error ? error.message : String(error) };
|
|
11
22
|
}
|
|
12
23
|
|
|
13
24
|
/**
|
|
@@ -183,12 +194,36 @@ export function captureSingleOutputSnapshot(outputPath: string | undefined): Sin
|
|
|
183
194
|
try {
|
|
184
195
|
const stat = fs.statSync(outputPath);
|
|
185
196
|
return { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
186
|
-
} catch {
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return snapshotFromStatError(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function inspectSingleOutputChange(
|
|
203
|
+
outputPath: string,
|
|
204
|
+
beforeRun: SingleOutputSnapshot | undefined,
|
|
205
|
+
): { changed: boolean; error?: string } {
|
|
206
|
+
if (beforeRun?.error) return { changed: false, error: beforeRun.error };
|
|
207
|
+
try {
|
|
208
|
+
const stat = fs.statSync(outputPath);
|
|
209
|
+
return {
|
|
210
|
+
changed: !beforeRun?.exists || stat.mtimeMs !== beforeRun.mtimeMs || stat.size !== beforeRun.size,
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (missingFileStatError(error)) return { changed: false };
|
|
214
|
+
return { changed: false, error: error instanceof Error ? error.message : String(error) };
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
217
|
|
|
218
|
+
export function hasSingleOutputChangedSinceSnapshot(
|
|
219
|
+
outputPath: string | undefined,
|
|
220
|
+
beforeRun: SingleOutputSnapshot | undefined,
|
|
221
|
+
): boolean | undefined {
|
|
222
|
+
if (!outputPath) return undefined;
|
|
223
|
+
const inspected = inspectSingleOutputChange(outputPath, beforeRun);
|
|
224
|
+
return inspected.error ? undefined : inspected.changed;
|
|
225
|
+
}
|
|
226
|
+
|
|
192
227
|
function persistSingleOutput(
|
|
193
228
|
outputPath: string | undefined,
|
|
194
229
|
fullOutput: string,
|
|
@@ -216,23 +251,15 @@ export function resolveSingleOutput(
|
|
|
216
251
|
const claimError = outputClaimError(outputPath, expectedClaimPath);
|
|
217
252
|
if (claimError) return { fullOutput: fallbackOutput, saveError: claimError, fatalError: true };
|
|
218
253
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
} catch (error) {
|
|
226
|
-
const code = error && typeof error === "object" && "code" in error ? (error as { code?: unknown }).code : undefined;
|
|
227
|
-
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
228
|
-
return {
|
|
229
|
-
fullOutput: fallbackOutput,
|
|
230
|
-
saveError: `Failed to inspect output file: ${error instanceof Error ? error.message : String(error)}`,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
254
|
+
const changedSinceStart = inspectSingleOutputChange(outputPath, beforeRun);
|
|
255
|
+
if (changedSinceStart.error) {
|
|
256
|
+
return {
|
|
257
|
+
fullOutput: fallbackOutput,
|
|
258
|
+
saveError: `Failed to inspect output file: ${changedSinceStart.error}`,
|
|
259
|
+
};
|
|
233
260
|
}
|
|
234
261
|
|
|
235
|
-
if (changedSinceStart) {
|
|
262
|
+
if (changedSinceStart.changed) {
|
|
236
263
|
try {
|
|
237
264
|
return { fullOutput: fs.readFileSync(outputPath, "utf-8"), savedPath: outputPath };
|
|
238
265
|
} catch (error) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
|
|
4
|
+
import type { BeforeProviderRequestEvent, ExtensionAPI, ExtensionContext } from "@selesai/code";
|
|
5
5
|
import { registerNativeSupervisorClient } from "../../intercom/native-supervisor-channel.ts";
|
|
6
6
|
import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
|
|
7
7
|
import { decodePermissionRules, permissionDecision, PERMISSION_AUDIT_PATH_ENV, PERMISSION_POLICY_ENV } from "./permissions.ts";
|
|
8
8
|
import { consumeSteerRequestsFromDir, MAX_STEER_QUEUE_SIZE, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerDeliveryStatus, type SteerRequest } from "../background/control-channel.ts";
|
|
9
|
-
import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_INHERIT_GLOBAL_CONTEXT_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
|
|
9
|
+
import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_FORK_CACHE_KEY_ENV, SUBAGENT_INHERIT_GLOBAL_CONTEXT_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
|
|
10
10
|
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
11
11
|
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
12
12
|
import {
|
|
@@ -296,6 +296,23 @@ const COMPOSITE_TOOL_ID_APIS = new Set([
|
|
|
296
296
|
"openai-responses",
|
|
297
297
|
]);
|
|
298
298
|
|
|
299
|
+
const PROMPT_CACHE_KEY_APIS = new Set([
|
|
300
|
+
"azure-openai-responses",
|
|
301
|
+
"openai-codex-responses",
|
|
302
|
+
"openai-completions",
|
|
303
|
+
"openai-responses",
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
export function rewriteForkCacheProviderRequest(event: BeforeProviderRequestEvent, ctx?: Pick<ExtensionContext, "model">): unknown {
|
|
307
|
+
const forkCacheKey = process.env[SUBAGENT_FORK_CACHE_KEY_ENV]?.trim();
|
|
308
|
+
if (!forkCacheKey || !PROMPT_CACHE_KEY_APIS.has(ctx?.model?.api ?? "")) return undefined;
|
|
309
|
+
if (!event || typeof event !== "object") return undefined;
|
|
310
|
+
const payload = event.payload;
|
|
311
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
|
|
312
|
+
if (typeof (payload as { prompt_cache_key?: unknown }).prompt_cache_key !== "string") return undefined;
|
|
313
|
+
return { ...payload, prompt_cache_key: forkCacheKey };
|
|
314
|
+
}
|
|
315
|
+
|
|
299
316
|
function portableToolId(id: string): string {
|
|
300
317
|
if (PORTABLE_TOOL_ID_PATTERN.test(id) && id.length <= MAX_PORTABLE_TOOL_ID_LENGTH) return id;
|
|
301
318
|
const encoded = `tool_${Buffer.from(id).toString("base64url") || "empty"}`;
|
|
@@ -748,6 +765,8 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
748
765
|
});
|
|
749
766
|
}
|
|
750
767
|
|
|
768
|
+
onRuntimeEvent("before_provider_request", (event: unknown, ctx?: ExtensionContext) => rewriteForkCacheProviderRequest(event as BeforeProviderRequestEvent, ctx));
|
|
769
|
+
|
|
751
770
|
onRuntimeEvent("context", (event: unknown, ctx?: ExtensionContext) => {
|
|
752
771
|
if (!event || typeof event !== "object" || !("messages" in event) || !Array.isArray(event.messages)) return undefined;
|
|
753
772
|
const messages = stripParentOnlySubagentMessages(event.messages, {
|
|
@@ -13,6 +13,21 @@ export interface WorkflowGraphBuildInput {
|
|
|
13
13
|
dynamicGroupStatuses?: Record<number, { status: WorkflowNodeStatus; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** Return displayable workflow stages while hiding structural parallel groups and host monitors. */
|
|
17
|
+
export function workflowGraphStageNodes(graph: WorkflowGraphSnapshot | undefined): WorkflowGraphNode[] {
|
|
18
|
+
if (!graph?.nodes?.length) return [];
|
|
19
|
+
const stages: WorkflowGraphNode[] = [];
|
|
20
|
+
const visit = (node: WorkflowGraphNode): void => {
|
|
21
|
+
if (node.kind === "parallel-group" || node.kind === "dynamic-parallel-group") {
|
|
22
|
+
for (const child of node.children ?? []) visit(child);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (node.kind !== "host-step") stages.push(node);
|
|
26
|
+
};
|
|
27
|
+
for (const node of graph.nodes) visit(node);
|
|
28
|
+
return stages;
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
function normalizeStatus(status: string | undefined): WorkflowNodeStatus | undefined {
|
|
17
32
|
switch (status) {
|
|
18
33
|
case "complete":
|
|
@@ -559,6 +559,10 @@ export interface TimeoutRecoverySummary {
|
|
|
559
559
|
termination: "timed-out" | "stopped";
|
|
560
560
|
changedFiles: string[];
|
|
561
561
|
truncated?: boolean;
|
|
562
|
+
/** True only when a timed-out child left tracked changes without its requested report. */
|
|
563
|
+
recoveryNeeded?: boolean;
|
|
564
|
+
reason?: "timed-out-with-dirty-worktree";
|
|
565
|
+
reportStatus?: "missing" | "written" | "not-requested" | "unknown";
|
|
562
566
|
currentTool?: string;
|
|
563
567
|
currentToolArgs?: string;
|
|
564
568
|
currentPath?: string;
|
|
@@ -569,6 +573,9 @@ export interface TimeoutRecoverySummary {
|
|
|
569
573
|
message: string;
|
|
570
574
|
}
|
|
571
575
|
|
|
576
|
+
/** Safe parent-facing subset of a timeout recovery summary. */
|
|
577
|
+
export type TimeoutRecoveryProjection = Pick<TimeoutRecoverySummary, "termination" | "changedFiles" | "truncated" | "recoveryNeeded" | "reason" | "reportStatus">;
|
|
578
|
+
|
|
572
579
|
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 3;
|
|
573
580
|
export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
|
|
574
581
|
|
|
@@ -1272,6 +1279,7 @@ export interface WaitCompletionChild {
|
|
|
1272
1279
|
model?: string;
|
|
1273
1280
|
contextOverflow?: boolean;
|
|
1274
1281
|
artifactPaths?: Partial<ArtifactPaths>;
|
|
1282
|
+
timeoutRecovery?: TimeoutRecoveryProjection;
|
|
1275
1283
|
}
|
|
1276
1284
|
|
|
1277
1285
|
/**
|
|
@@ -1290,6 +1298,27 @@ export interface WaitCompletion {
|
|
|
1290
1298
|
workflowChildren?: WorkflowChildSummaryV1;
|
|
1291
1299
|
}
|
|
1292
1300
|
|
|
1301
|
+
export interface AgentCapabilitiesSnapshot {
|
|
1302
|
+
agents: AgentCapabilityRow[];
|
|
1303
|
+
restrictedCount: number;
|
|
1304
|
+
capabilityCeilingSources?: string[];
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
export interface AgentCapabilityRow {
|
|
1308
|
+
name: string;
|
|
1309
|
+
description: string;
|
|
1310
|
+
source: "builtin" | "package" | "user" | "project" | "runtime";
|
|
1311
|
+
executable: boolean;
|
|
1312
|
+
restrictionSources?: string[];
|
|
1313
|
+
aliases?: string[];
|
|
1314
|
+
runner: { type: "pi" } | { type: "external-cli"; adapter?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
|
|
1315
|
+
tools: { ambient: boolean; names: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
|
|
1316
|
+
model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
|
|
1317
|
+
execution?: { defaultAsync?: boolean; timeoutMs?: number };
|
|
1318
|
+
output?: { path?: string; mode?: OutputMode };
|
|
1319
|
+
extensions?: { names?: string[]; subagentOnly?: string[]; skills?: string[] };
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1293
1322
|
export interface Details {
|
|
1294
1323
|
mode: SubagentResultMode | "management";
|
|
1295
1324
|
runId?: string;
|
|
@@ -1354,6 +1383,7 @@ export interface Details {
|
|
|
1354
1383
|
activeAsyncCapacity?: ActiveAsyncCapacitySnapshot;
|
|
1355
1384
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1356
1385
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
1386
|
+
agentCapabilities?: AgentCapabilitiesSnapshot;
|
|
1357
1387
|
parallelHandoff?: ParallelHandoffReference;
|
|
1358
1388
|
lifecycleStatus?: {
|
|
1359
1389
|
processTerminal?: ProcessTerminalV1;
|
|
@@ -1852,9 +1882,10 @@ export interface AsyncStatus {
|
|
|
1852
1882
|
parallelHandoff?: ParallelHandoffReference;
|
|
1853
1883
|
}
|
|
1854
1884
|
|
|
1855
|
-
export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
1885
|
+
export type AsyncJobStep = Omit<NonNullable<AsyncStatus["steps"]>[number], "timeoutRecovery"> & {
|
|
1856
1886
|
index?: number;
|
|
1857
1887
|
description?: string;
|
|
1888
|
+
timeoutRecovery?: TimeoutRecoveryProjection;
|
|
1858
1889
|
};
|
|
1859
1890
|
|
|
1860
1891
|
export interface AsyncJobState {
|
|
@@ -1889,6 +1920,8 @@ export interface AsyncJobState {
|
|
|
1889
1920
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
1890
1921
|
/** Bounded host-owned CI/gate nodes loaded from the workflow status graph. */
|
|
1891
1922
|
hostSteps?: HostStepNodeV1[];
|
|
1923
|
+
/** Full bounded workflow plan, including not-yet-materialized stages. */
|
|
1924
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
1892
1925
|
steps?: AsyncJobStep[];
|
|
1893
1926
|
preflight?: WorkflowPreflightV1;
|
|
1894
1927
|
stepsTotal?: number;
|
|
@@ -4,6 +4,7 @@ import { snapshotExternalRuns } from "../api/external-runs.ts";
|
|
|
4
4
|
import { formatModelThinking } from "../shared/formatters.ts";
|
|
5
5
|
import type { AsyncJobState, AsyncJobStep, FleetViewPlacement, HerdrProjectPaneSnapshot, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentState } from "../shared/types.ts";
|
|
6
6
|
import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
|
|
7
|
+
import { contextModeLabel } from "../runs/shared/context-mode.ts";
|
|
7
8
|
import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
|
|
8
9
|
import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
|
|
9
10
|
import { isStaleExtensionContextError } from "../shared/extension-context.ts";
|
|
@@ -362,7 +363,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
362
363
|
if (job.mode === "workflow") {
|
|
363
364
|
const latestEmit = job.workflow?.emits?.length ? formatWorkflowJsonPreview(job.workflow.emits.at(-1), 120) : undefined;
|
|
364
365
|
const workflowSteps = workflowStepsWithoutMaterializedChildren(job.steps, materializedChildrenByWorkflow.get(`async:${job.asyncId}`));
|
|
365
|
-
const workflowRows = projectAsyncWorkflowRows(workflowSteps, job.hostSteps, job.preflight);
|
|
366
|
+
const workflowRows = projectAsyncWorkflowRows(workflowSteps, job.workflowGraph ?? job.hostSteps, job.preflight);
|
|
366
367
|
entries.push({
|
|
367
368
|
key: `async:${job.asyncId}`,
|
|
368
369
|
...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
|
|
@@ -550,7 +551,12 @@ export class SubagentFleetStatus {
|
|
|
550
551
|
this.lastRenderKey = renderKey;
|
|
551
552
|
return;
|
|
552
553
|
}
|
|
553
|
-
if (renderKey === this.lastRenderKey)
|
|
554
|
+
if (renderKey === this.lastRenderKey) {
|
|
555
|
+
// Repaint anyway while anything is running so the wall-clock
|
|
556
|
+
// spinner animates between state changes (500ms tick).
|
|
557
|
+
if (this.entries.some((entry) => entry.state === "running")) this.tui?.requestRender();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
554
560
|
this.lastRenderKey = renderKey;
|
|
555
561
|
this.tui?.requestRender();
|
|
556
562
|
}
|
|
@@ -728,6 +734,7 @@ export class SubagentFleetStatus {
|
|
|
728
734
|
const marker = last ? "└─" : "├─";
|
|
729
735
|
const indent = " ";
|
|
730
736
|
if (row.overflow !== undefined) return truncateToWidth(`${indent}${marker} ${theme.fg("dim", `+${row.overflow} hidden workflow steps`)}`, width);
|
|
737
|
+
const context = contextModeLabel(row.context);
|
|
731
738
|
const modelThinking = row.modelThinking ? ` (${row.modelThinking})` : "";
|
|
732
739
|
const activity = row.activity ? ` · ${row.activity}` : "";
|
|
733
740
|
const kind = row.kind ? `${row.kind}: ` : "";
|
|
@@ -738,7 +745,7 @@ export class SubagentFleetStatus {
|
|
|
738
745
|
row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
|
|
739
746
|
row.preflight.independence ? `independence:${row.preflight.independence}` : undefined,
|
|
740
747
|
].filter((value): value is string => Boolean(value)).join(" · ") : "";
|
|
741
|
-
const left = `${indent}${marker} ${this.workflowRowGlyph(row, theme)} ${theme.fg("muted", `${kind}${row.name}${modelThinking}`)} · ${this.workflowRowStateLabel(row, theme)}${activity}${hints ? ` · ${hints}` : ""}`;
|
|
748
|
+
const left = `${indent}${marker} ${this.workflowRowGlyph(row, theme)} ${theme.fg("muted", `${kind}${row.name}${context ? ` ${context}` : ""}${modelThinking}`)} · ${this.workflowRowStateLabel(row, theme)}${activity}${hints ? ` · ${hints}` : ""}`;
|
|
742
749
|
const details = [
|
|
743
750
|
row.startedAt !== undefined ? formatFleetElapsed(Date.now() - row.startedAt) : undefined,
|
|
744
751
|
row.tokens !== undefined ? formatFleetTokens(row.tokens, row.window) : undefined,
|
|
@@ -806,6 +813,7 @@ export class SubagentFleetStatus {
|
|
|
806
813
|
row.kind,
|
|
807
814
|
row.name,
|
|
808
815
|
row.state,
|
|
816
|
+
row.context,
|
|
809
817
|
row.modelThinking,
|
|
810
818
|
row.activity,
|
|
811
819
|
row.startedAt,
|
|
@@ -30,6 +30,37 @@ export function fuzzyFilter<T extends { name: string; description: string; model
|
|
|
30
30
|
.map((x) => x.item);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Remove a repeated job name from a child label when a safe separator follows it. */
|
|
34
|
+
export function stripRepeatedAgentPrefix(label: string, jobName: string | undefined): string {
|
|
35
|
+
const value = label.trim();
|
|
36
|
+
const prefix = jobName?.trim();
|
|
37
|
+
if (!prefix || !value.startsWith(prefix)) return value;
|
|
38
|
+
|
|
39
|
+
const suffix = value.slice(prefix.length);
|
|
40
|
+
if (!suffix || !/^(?::|·|\s)/u.test(suffix)) return value;
|
|
41
|
+
return suffix.replace(/^\s*(?::|·)?\s*/u, "").trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether a status label may omit the one logical step marker. */
|
|
45
|
+
export function shouldSuppressSingleStep(chainStepCount?: number, stepsTotal?: number): boolean {
|
|
46
|
+
return (chainStepCount ?? stepsTotal) === 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Add an Agent fraction only when visible candidates collide. */
|
|
50
|
+
export function withDuplicateLabelDiscriminators<T extends { index: number; displayName: string }>(
|
|
51
|
+
rows: readonly T[],
|
|
52
|
+
total: number,
|
|
53
|
+
): Array<T & { rowLabel: string }> {
|
|
54
|
+
const counts = new Map<string, number>();
|
|
55
|
+
for (const row of rows) counts.set(row.displayName, (counts.get(row.displayName) ?? 0) + 1);
|
|
56
|
+
return rows.map((row) => ({
|
|
57
|
+
...row,
|
|
58
|
+
rowLabel: (counts.get(row.displayName) ?? 0) > 1
|
|
59
|
+
? `Agent ${row.index + 1}/${total}: ${row.displayName}`
|
|
60
|
+
: row.displayName,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
export function pad(s: string, len: number): string {
|
|
34
65
|
const vis = visibleWidth(s);
|
|
35
66
|
return s + " ".repeat(Math.max(0, len - vis));
|