pi-ui-extend 1.0.41 → 1.0.42
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/README.md +10 -2
- package/dist/app/app.d.ts +3 -0
- package/dist/app/app.js +26 -0
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +6 -1
- package/dist/app/commands/command-registry.js +1 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +59 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +19 -1
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/types.d.ts +5 -0
- package/dist/default-pix-config.js +10 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +80 -58
- package/dist/schemas/pi-tools-suite-schema.js +43 -78
- package/dist/schemas/pix-schema.d.ts +7 -0
- package/dist/schemas/pix-schema.js +14 -0
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +6 -0
- package/docs/desktop-mvp.md +12 -1
- package/docs/desktop-task-manager.md +93 -82
- package/external/pi-tools-suite/README.md +37 -50
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +19 -0
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +40 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +38 -27
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +14 -70
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +122 -312
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +15 -2
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +7 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -4
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/config.ts +41 -1
- package/external/pi-tools-suite/src/context-gateway/config.ts +11 -2
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +73 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +3 -3
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +9 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +96 -44
- package/external/pi-tools-suite/src/dcp/commands.ts +18 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +170 -45
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/config.ts +56 -13
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +1 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +13 -9
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -4
- package/package.json +4 -2
- package/schemas/pi-tools-suite.json +427 -280
- package/schemas/pix.json +24 -0
- package/schemas/tasks.json +132 -0
- package/skills/spec-wiki/SKILL.md +55 -0
- package/skills/spec-wiki/evals/evals.json +132 -0
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
- package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
- package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
- package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
- package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
- package/skills/spec-wiki/evals/trigger-evals.json +94 -0
- package/skills/spec-wiki/references/bootstrap.md +100 -0
- package/skills/spec-wiki/references/focused-spec.md +117 -0
- package/skills/spec-wiki/references/index-schema.md +330 -0
- package/skills/spec-wiki/references/maintenance.md +134 -0
- package/skills/spec-wiki/references/query.md +74 -0
- package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/spec-lite/SKILL.md +0 -140
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { activityFromProgressRecord } from "./activity.js";
|
|
3
4
|
import { hasLaunchedAgentPrompt, isDir } from "./paths.js";
|
|
4
5
|
import { readStructuredResult } from "./structured-result.js";
|
|
5
6
|
import type { AgentResult, AgentState, RpcEventRecord, RunState } from "./types.js";
|
|
6
7
|
|
|
7
8
|
const MAX_RPC_EVENT_LINE_CHARS = 1024 * 1024;
|
|
9
|
+
const LAST_ACTIVITY_TAIL_BYTES = 64 * 1024;
|
|
8
10
|
|
|
9
11
|
interface AgentStateReadOptions {
|
|
10
12
|
includeLineCounts?: boolean;
|
|
@@ -67,6 +69,9 @@ export function getAgentState(
|
|
|
67
69
|
state.finishedAt = fs.readFileSync(finishedAtFile, "utf-8").trim();
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
const lastActivity = readLastActivity(path.join(agentDir, "progress.jsonl"));
|
|
73
|
+
if (lastActivity) state.lastActivity = lastActivity;
|
|
74
|
+
|
|
70
75
|
const retryPendingFile = path.join(agentDir, "retry_pending");
|
|
71
76
|
const stopRequestedFile = path.join(agentDir, "stop_requested");
|
|
72
77
|
if (fs.existsSync(retryPendingFile) && !fs.existsSync(stopRequestedFile) && state.status !== "running" && state.status !== "done") {
|
|
@@ -109,6 +114,41 @@ function readPid(pidFile: string): number | undefined {
|
|
|
109
114
|
return isNaN(pid) ? undefined : pid;
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
function readLastActivity(filePath: string): AgentState["lastActivity"] | undefined {
|
|
118
|
+
if (!fs.existsSync(filePath)) return undefined;
|
|
119
|
+
let fd: number | undefined;
|
|
120
|
+
try {
|
|
121
|
+
fd = fs.openSync(filePath, "r");
|
|
122
|
+
const size = fs.fstatSync(fd).size;
|
|
123
|
+
if (size <= 0) return undefined;
|
|
124
|
+
const start = Math.max(0, size - LAST_ACTIVITY_TAIL_BYTES);
|
|
125
|
+
const buffer = Buffer.allocUnsafe(size - start);
|
|
126
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, start);
|
|
127
|
+
if (bytesRead <= 0) return undefined;
|
|
128
|
+
const lines = buffer.toString("utf-8", 0, bytesRead).split(/\r?\n/);
|
|
129
|
+
if (start > 0) {
|
|
130
|
+
const previousByte = Buffer.allocUnsafe(1);
|
|
131
|
+
const previousBytesRead = fs.readSync(fd, previousByte, 0, 1, start - 1);
|
|
132
|
+
if (previousBytesRead === 1 && previousByte[0] !== 0x0a) lines.shift();
|
|
133
|
+
}
|
|
134
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
135
|
+
const line = lines[index]?.trim();
|
|
136
|
+
if (!line) continue;
|
|
137
|
+
try {
|
|
138
|
+
const activity = activityFromProgressRecord(JSON.parse(line) as unknown);
|
|
139
|
+
if (activity) return activity;
|
|
140
|
+
} catch {
|
|
141
|
+
// Ignore malformed or partially written tail lines and keep scanning.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
} finally {
|
|
147
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
112
152
|
function hasRpcPromptFailure(eventsFile: string): boolean {
|
|
113
153
|
if (!fs.existsSync(eventsFile)) return false;
|
|
114
154
|
let fd: number | undefined;
|
|
@@ -39,6 +39,13 @@ export interface AgentState {
|
|
|
39
39
|
eventLines?: number;
|
|
40
40
|
/** How many times this agent has been retried (0 = first attempt). */
|
|
41
41
|
retryCount?: number;
|
|
42
|
+
/** Most recent user-visible activity observed from the sub-agent RPC stream. */
|
|
43
|
+
lastActivity?: AgentActivity;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AgentActivity {
|
|
47
|
+
label: string;
|
|
48
|
+
at: string;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/** Retry configuration for failed agents. */
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getRunState,
|
|
8
8
|
getRunRoot,
|
|
9
9
|
getSubagentRegistryPath,
|
|
10
|
+
filterSubagentConfigForParentModel,
|
|
10
11
|
isBlindModelRef,
|
|
11
12
|
loadSubagentConfig,
|
|
12
13
|
listSubagentSessionRecords,
|
|
@@ -14,9 +15,11 @@ import {
|
|
|
14
15
|
removeSubagentRunsFromRegistry,
|
|
15
16
|
stopAgents,
|
|
16
17
|
type AgentCompletionHandler,
|
|
18
|
+
type RpcEventRecord,
|
|
17
19
|
type StopSignal,
|
|
18
20
|
type SubagentSessionRecord,
|
|
19
21
|
} from "./lib.js";
|
|
22
|
+
import { activityFromRpcEvent } from "./core/activity.js";
|
|
20
23
|
import { buildUltraworkPrompt, isUltraworkEnvEnabled, registerCommands } from "./commands.js";
|
|
21
24
|
import { agentStrategyPrompt, appendAgentStrategyPrompt } from "./core/agent-strategy.js";
|
|
22
25
|
import { buildSubagentCatalogPrompt } from "./core/agent-catalog.js";
|
|
@@ -41,6 +44,7 @@ function isTerminalAgentStatus(status: AgentState["status"]): boolean {
|
|
|
41
44
|
|
|
42
45
|
const SUBAGENTS_LIVE_COUNT_EVENT = "pi-tools-suite:async-subagents:live-count";
|
|
43
46
|
const SUBAGENTS_LIVE_STATE_EVENT = "pi-tools-suite:async-subagents:live-state";
|
|
47
|
+
export const SUBAGENTS_CATALOG_STATE_EVENT = "pi-tools-suite:async-subagents:catalog";
|
|
44
48
|
const SESSION_SHUTDOWN_KILL_GRACE_MS = 500;
|
|
45
49
|
const COMPLETION_WATCH_INTERVAL_MS = 2_000;
|
|
46
50
|
|
|
@@ -54,6 +58,14 @@ interface ShutdownPlan {
|
|
|
54
58
|
runDirsToDelete: string[];
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
interface SubagentCatalogStateEvent {
|
|
62
|
+
version: 1;
|
|
63
|
+
sessionId?: string;
|
|
64
|
+
sessionFile?: string;
|
|
65
|
+
model?: string;
|
|
66
|
+
types: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
function createLiveStatePayload(
|
|
58
70
|
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
59
71
|
sessionFile: string | undefined,
|
|
@@ -65,7 +77,13 @@ function createLiveStatePayload(
|
|
|
65
77
|
if (matchingLiveAgents.length === 0) continue;
|
|
66
78
|
const agentIds = matchingLiveAgents.map((agent) => agent.agentId);
|
|
67
79
|
const state = getRunState(runDir, agentIds, { includeLineCounts: false, checkRpcPromptFailure: false });
|
|
68
|
-
const
|
|
80
|
+
const liveAgentsById = new Map(matchingLiveAgents.map((agent) => [agent.agentId, agent]));
|
|
81
|
+
const activeAgents = state.agents
|
|
82
|
+
.filter((agent) => !isTerminalAgentStatus(agent.status))
|
|
83
|
+
.map((agent) => {
|
|
84
|
+
const lastActivity = liveAgentsById.get(agent.id)?.lastActivity;
|
|
85
|
+
return lastActivity ? { ...agent, lastActivity } : agent;
|
|
86
|
+
});
|
|
69
87
|
if (activeAgents.length === 0) continue;
|
|
70
88
|
count += activeAgents.length;
|
|
71
89
|
const tasks = matchingLiveAgents.map((agent) => agent.preview).filter((preview): preview is NonNullable<typeof preview> => Boolean(preview));
|
|
@@ -84,6 +102,25 @@ function createLiveStatePayload(
|
|
|
84
102
|
};
|
|
85
103
|
}
|
|
86
104
|
|
|
105
|
+
function createSubagentCatalogState(ctx: unknown): SubagentCatalogStateEvent {
|
|
106
|
+
const cwd = (ctx as { cwd?: string } | undefined)?.cwd ?? process.cwd();
|
|
107
|
+
const model = modelRefFromContext(ctx);
|
|
108
|
+
const config = safeLoadSubagentConfig(cwd);
|
|
109
|
+
const effective = config ? filterSubagentConfigForParentModel(config, model) : undefined;
|
|
110
|
+
const sessionManager = (ctx as {
|
|
111
|
+
sessionManager?: { getSessionId?: () => string; getSessionFile?: () => string | undefined };
|
|
112
|
+
} | undefined)?.sessionManager;
|
|
113
|
+
const sessionId = typeof sessionManager?.getSessionId === "function" ? sessionManager.getSessionId() : undefined;
|
|
114
|
+
const sessionFile = typeof sessionManager?.getSessionFile === "function" ? sessionManager.getSessionFile() : undefined;
|
|
115
|
+
return {
|
|
116
|
+
version: 1,
|
|
117
|
+
...(sessionId ? { sessionId } : {}),
|
|
118
|
+
...(sessionFile ? { sessionFile } : {}),
|
|
119
|
+
...(model ? { model } : {}),
|
|
120
|
+
types: Object.keys(effective?.types ?? {}).sort(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
87
124
|
function agentMatchesSession(agent: LiveAgent, sessionFile: string | undefined): boolean {
|
|
88
125
|
if (!sessionFile) return true;
|
|
89
126
|
return agent.parentSession !== undefined && pathsEqual(sessionFile, agent.parentSession);
|
|
@@ -106,6 +143,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
106
143
|
let completionWatchTimer: ReturnType<typeof setInterval> | undefined;
|
|
107
144
|
publishSubagentPresetsStartupSection();
|
|
108
145
|
|
|
146
|
+
function publishSubagentCatalogState(ctx: unknown): void {
|
|
147
|
+
const state = createSubagentCatalogState(ctx);
|
|
148
|
+
pi.events?.emit?.(SUBAGENTS_CATALOG_STATE_EVENT, state);
|
|
149
|
+
publishRpcSessionState(ctx as Parameters<typeof publishRpcSessionState>[0], SUBAGENTS_CATALOG_STATE_EVENT, state);
|
|
150
|
+
}
|
|
151
|
+
|
|
109
152
|
function refreshSubagentOverlay(): void {
|
|
110
153
|
try {
|
|
111
154
|
reconcileLiveAgentCompletions();
|
|
@@ -165,7 +208,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
165
208
|
refreshSubagentOverlay();
|
|
166
209
|
};
|
|
167
210
|
|
|
168
|
-
|
|
211
|
+
function handleAgentRpcEvent(runDir: string, agentId: string, event: RpcEventRecord): void {
|
|
212
|
+
const activity = activityFromRpcEvent(event);
|
|
213
|
+
if (!activity) return;
|
|
214
|
+
const liveAgent = liveAgents.get(runDir)?.get(agentId);
|
|
215
|
+
if (!liveAgent) return;
|
|
216
|
+
liveAgent.lastActivity = activity;
|
|
217
|
+
refreshSubagentOverlay();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
registerSubagentsTool(pi, liveAgents, handleAgentCompletion, refreshSubagentOverlay, handleAgentRpcEvent);
|
|
169
221
|
registerCommands(pi);
|
|
170
222
|
|
|
171
223
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -173,6 +225,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
173
225
|
sawAutoUltraworkCandidate = false;
|
|
174
226
|
currentSessionFile = sessionFileFromContext(ctx);
|
|
175
227
|
currentSessionStateContext = ctx;
|
|
228
|
+
publishSubagentCatalogState(ctx);
|
|
176
229
|
subagentOverlay.restoreRunningAgents(ctx.cwd, currentSessionFile);
|
|
177
230
|
refreshSubagentOverlay();
|
|
178
231
|
} catch (error) {
|
|
@@ -180,6 +233,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
233
|
}
|
|
181
234
|
});
|
|
182
235
|
|
|
236
|
+
pi.on("model_select", (_event, ctx) => {
|
|
237
|
+
try {
|
|
238
|
+
publishSubagentCatalogState(ctx);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
ignoreStaleExtensionContextError(error);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
183
244
|
pi.on("tool_execution_end", async (event) => {
|
|
184
245
|
if (event.toolName !== "subagents" && !event.toolName.startsWith("async_subagents_")) return;
|
|
185
246
|
refreshSubagentOverlay();
|
|
@@ -192,7 +253,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
192
253
|
});
|
|
193
254
|
const visionPrompt = visionCapabilityPrompt(event, ctx);
|
|
194
255
|
const catalogPrompt = selectedToolsInclude(event, "subagents")
|
|
195
|
-
? subagentCatalogPrompt(
|
|
256
|
+
? subagentCatalogPrompt(
|
|
257
|
+
(ctx as { cwd?: string } | undefined)?.cwd ?? process.cwd(),
|
|
258
|
+
modelRefFromContext(ctx),
|
|
259
|
+
)
|
|
196
260
|
: undefined;
|
|
197
261
|
if (!strategyPrompt && !visionPrompt && !catalogPrompt) return undefined;
|
|
198
262
|
let systemPrompt = event.systemPrompt ?? "";
|
|
@@ -286,9 +350,9 @@ function safeLoadSubagentConfig(cwd: string) {
|
|
|
286
350
|
}
|
|
287
351
|
}
|
|
288
352
|
|
|
289
|
-
function subagentCatalogPrompt(cwd: string): string | undefined {
|
|
353
|
+
function subagentCatalogPrompt(cwd: string, parentModelRef?: string): string | undefined {
|
|
290
354
|
const config = safeLoadSubagentConfig(cwd);
|
|
291
|
-
return config ? buildSubagentCatalogPrompt(config) : undefined;
|
|
355
|
+
return config ? buildSubagentCatalogPrompt(config, parentModelRef) : undefined;
|
|
292
356
|
}
|
|
293
357
|
|
|
294
358
|
function startupSubagentPresetList(cwd = process.cwd()): string {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type {
|
|
2
|
+
AgentActivity,
|
|
2
3
|
AgentCompletionHandler,
|
|
3
4
|
AgentResult,
|
|
4
5
|
AgentState,
|
|
@@ -12,21 +13,19 @@ export type {
|
|
|
12
13
|
} from "./core/types.js";
|
|
13
14
|
|
|
14
15
|
export { createRunDir, getRunRoot, hasAgentPrompt, hasLaunchedAgentPrompt, hasQueuedAgentPrompt, resolveRunDir, validateBasename } from "./core/paths.js";
|
|
15
|
-
export type {
|
|
16
|
+
export type { ModelByParentEntry, ResolvedAgentTaskConfig, ResolvedSubagentRoutingConfig, ResolveAgentTaskOptions, SubagentConfig, SubagentPreset, SubagentRoutingConfig, SubagentTypeConfig, SubagentVisionConfig } from "./core/config.js";
|
|
16
17
|
export {
|
|
17
|
-
configFiles,
|
|
18
|
-
copySubagentConfigSample,
|
|
19
18
|
currentModelRef,
|
|
20
19
|
defaultSubagentType,
|
|
21
20
|
DEFAULT_MAX_CONCURRENT,
|
|
22
21
|
DEFAULT_RETRY_CONFIG,
|
|
23
22
|
DEFAULT_ROUTING_CONFIG,
|
|
24
|
-
|
|
25
|
-
getDefaultSubagentConfigPath,
|
|
23
|
+
filterSubagentConfigForParentModel,
|
|
26
24
|
getBuiltinSubagentDefinitionsDir,
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
getBuiltinSubagentPresetsPath,
|
|
26
|
+
getProjectSubagentPresetsPath,
|
|
29
27
|
isBlindModelRef,
|
|
28
|
+
isSubagentTypeAvailableForParent,
|
|
30
29
|
loadSubagentConfig,
|
|
31
30
|
resolveAgentTaskConfig,
|
|
32
31
|
resolveRetryConfig,
|
|
@@ -37,7 +36,7 @@ export {
|
|
|
37
36
|
export { routeSubagentTasks, SubagentRoutingError } from "./core/routing.js";
|
|
38
37
|
export { BROWSER_QA_RUNNER_ENV, getBrowserQaRunnerPath } from "./core/browser-qa.js";
|
|
39
38
|
export type { RoutedSubagentTasks, SubagentRoutingContext } from "./core/routing.js";
|
|
40
|
-
export { agentDefinitionFiles, agentNameFromFilename, parseAgentMarkdown, projectAgentDefinitionFiles, readAgentDefinitionsFromDir, readProjectAgentDefinitions } from "./core/agents-dir.js";
|
|
39
|
+
export { agentDefinitionFiles, agentNameFromFilename, parseAgentMarkdown, projectAgentDefinitionFiles, projectAgentsDir, readAgentDefinitionsFromDir, readProjectAgentDefinitions } from "./core/agents-dir.js";
|
|
41
40
|
export type { AgentDefinition, ProjectAgentDefinition } from "./core/agents-dir.js";
|
|
42
41
|
export { buildSubagentCatalogPrompt } from "./core/agent-catalog.js";
|
|
43
42
|
export type { SubagentPresetSelectionState } from "./core/presets.js";
|
|
@@ -7,7 +7,7 @@ import { ASYNC_SUBAGENT_TOOL_DESCRIPTIONS } from "../../tool-descriptions.js";
|
|
|
7
7
|
import { SUBAGENT_TYPE_SELECTION_GUIDANCE } from "../core/agent-catalog.js";
|
|
8
8
|
import { selectAvailableAgentModels } from "../core/model-selection.js";
|
|
9
9
|
import { SubagentModelSelectionError } from "../core/config.js";
|
|
10
|
-
import type { AgentCompletionHandler, AgentTask, ResolvedAgentTaskConfig, Semaphore, SpawnedAgent } from "../lib.js";
|
|
10
|
+
import type { AgentCompletionHandler, AgentTask, ResolvedAgentTaskConfig, RpcEventRecord, Semaphore, SpawnedAgent } from "../lib.js";
|
|
11
11
|
import {
|
|
12
12
|
createRunDir,
|
|
13
13
|
createSemaphore,
|
|
@@ -52,6 +52,7 @@ interface LaunchQueuedAgentOptions {
|
|
|
52
52
|
onCancelled: (reason: string) => void;
|
|
53
53
|
onLaunchError: (error: unknown) => void;
|
|
54
54
|
onUpdate: () => void;
|
|
55
|
+
onRpcEvent?: (event: RpcEventRecord) => void;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const PROJECT_SEMAPHORES = new Map<string, Semaphore>();
|
|
@@ -71,7 +72,7 @@ function normalizedLimit(limit: number): number {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
async function launchQueuedAgent(options: LaunchQueuedAgentOptions): Promise<void> {
|
|
74
|
-
const { resolved, runDir, cwd, parentSession, semaphore, signal, onResult, onComplete, onCancelled, onLaunchError, onUpdate } = options;
|
|
75
|
+
const { resolved, runDir, cwd, parentSession, semaphore, signal, onResult, onComplete, onCancelled, onLaunchError, onUpdate, onRpcEvent } = options;
|
|
75
76
|
let slotAcquired = false;
|
|
76
77
|
try {
|
|
77
78
|
await semaphore.acquire(signal);
|
|
@@ -111,13 +112,14 @@ async function launchQueuedAgent(options: LaunchQueuedAgentOptions): Promise<voi
|
|
|
111
112
|
extraArgs: resolved.extraArgs,
|
|
112
113
|
fallbackModels: resolved.fallbackModels,
|
|
113
114
|
signal,
|
|
115
|
+
onRpcEvent,
|
|
114
116
|
...spawnOptions,
|
|
115
117
|
},
|
|
116
118
|
);
|
|
117
119
|
onResult(retryResult.initial);
|
|
118
120
|
retryResult.done.catch(onLaunchError);
|
|
119
121
|
} else {
|
|
120
|
-
const result = spawnAgent(runDir, resolved.task, cwd, resolved.extraArgs,
|
|
122
|
+
const result = spawnAgent(runDir, resolved.task, cwd, resolved.extraArgs, onRpcEvent, completionHandler, spawnOptions);
|
|
121
123
|
onResult(result);
|
|
122
124
|
}
|
|
123
125
|
onUpdate();
|
|
@@ -195,6 +197,7 @@ export function registerSpawnTool(
|
|
|
195
197
|
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
196
198
|
handleAgentCompletion: AgentCompletionHandler,
|
|
197
199
|
onLiveAgentsChange?: () => void,
|
|
200
|
+
onAgentRpcEvent?: (runDir: string, agentId: string, event: RpcEventRecord) => void,
|
|
198
201
|
): void {
|
|
199
202
|
pi.registerTool({
|
|
200
203
|
...ASYNC_SUBAGENT_TOOL_DESCRIPTIONS.spawnAction,
|
|
@@ -226,7 +229,7 @@ export function registerSpawnTool(
|
|
|
226
229
|
const activePreset = activePresetName ? config.presets?.[activePresetName] : undefined;
|
|
227
230
|
if (getSessionSubagentPresetOverride() && !activePreset) {
|
|
228
231
|
return {
|
|
229
|
-
content: [{ type: "text", text: `AGENTS_PRESET=${activePresetName} does not match any
|
|
232
|
+
content: [{ type: "text", text: `AGENTS_PRESET=${activePresetName} does not match any available sub-agent preset.` }],
|
|
230
233
|
details: {},
|
|
231
234
|
isError: true,
|
|
232
235
|
};
|
|
@@ -342,6 +345,7 @@ export function registerSpawnTool(
|
|
|
342
345
|
details: partialDetails,
|
|
343
346
|
});
|
|
344
347
|
},
|
|
348
|
+
onRpcEvent: (event) => onAgentRpcEvent?.(runDir, task.id, event),
|
|
345
349
|
});
|
|
346
350
|
}
|
|
347
351
|
|
|
@@ -4,7 +4,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
4
4
|
import { asyncSubagentToolDescriptions } from "../../tool-descriptions.js";
|
|
5
5
|
import { SUBAGENT_TYPE_SELECTION_GUIDANCE } from "../core/agent-catalog.js";
|
|
6
6
|
import { hasIndexedProjectRoot } from "../../lib/project.js";
|
|
7
|
-
import type { AgentCompletionHandler } from "../lib.js";
|
|
7
|
+
import type { AgentCompletionHandler, RpcEventRecord } from "../lib.js";
|
|
8
8
|
import { DEFAULT_SPAWN_WATCH_SECONDS, INLINE_RENDERING } from "../constants.js";
|
|
9
9
|
import { renderSubagentSpawnPrompts } from "../render.js";
|
|
10
10
|
import { emptyToolSlot } from "../ui.js";
|
|
@@ -73,10 +73,11 @@ export function registerSubagentsTool(
|
|
|
73
73
|
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
74
74
|
handleAgentCompletion: AgentCompletionHandler,
|
|
75
75
|
onLiveAgentsChange?: () => void,
|
|
76
|
+
onAgentRpcEvent?: (runDir: string, agentId: string, event: RpcEventRecord) => void,
|
|
76
77
|
): void {
|
|
77
78
|
const toolDescriptions = asyncSubagentToolDescriptions(hasIndexedProjectRoot());
|
|
78
79
|
const collector = new ToolCollector();
|
|
79
|
-
registerSpawnTool(collector as any, liveAgents, handleAgentCompletion, onLiveAgentsChange);
|
|
80
|
+
registerSpawnTool(collector as any, liveAgents, handleAgentCompletion, onLiveAgentsChange, onAgentRpcEvent);
|
|
80
81
|
registerStatusTool(collector as any);
|
|
81
82
|
registerStopTool(collector as any, liveAgents, onLiveAgentsChange);
|
|
82
83
|
registerWaitTool(collector as any);
|
|
@@ -12,6 +12,16 @@ export interface PiToolsSuiteConfig {
|
|
|
12
12
|
todoThinkingOverrides: Record<string, TodoThinkingLevel>;
|
|
13
13
|
/** Vision-capable model used by the coding-discipline lookup tool; unset disables lookup. */
|
|
14
14
|
lookupModel?: string;
|
|
15
|
+
resourceRegistry: ResourceRegistryConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResourceRegistryConfig {
|
|
19
|
+
/** Git remote containing reusable skills/agents and project-scoped state under projects/. */
|
|
20
|
+
remote?: string;
|
|
21
|
+
/** Branch used for installs, updates, and pushes. */
|
|
22
|
+
branch: string;
|
|
23
|
+
/** Optional explicit project key for project-scoped registry state when Git remote discovery is unavailable or should be overridden. */
|
|
24
|
+
projectKey?: string;
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
type MutableConfig = {
|
|
@@ -20,6 +30,7 @@ type MutableConfig = {
|
|
|
20
30
|
todoThinking: boolean;
|
|
21
31
|
todoThinkingOverrides: Map<string, TodoThinkingLevel>;
|
|
22
32
|
lookupModel: string | undefined;
|
|
33
|
+
resourceRegistry: ResourceRegistryConfig;
|
|
23
34
|
};
|
|
24
35
|
|
|
25
36
|
const TODO_THINKING_OVERRIDE_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
@@ -35,8 +46,9 @@ const ENABLED_LIST_KEYS = ["enabledModules", "enabledExtensions"];
|
|
|
35
46
|
const MODULE_MAP_KEYS = ["modules", "extensions"];
|
|
36
47
|
const DEFAULT_DISABLED_MODULES = new Set<string>(["credential-firewall", "truncation-metadata-normalizer"]);
|
|
37
48
|
const DEFAULT_TODO_THINKING_OVERRIDES = new Map<string, TodoThinkingLevel>([["zai/glm-5.3", "max"]]);
|
|
49
|
+
const DEFAULT_RESOURCE_REGISTRY_BRANCH = "main";
|
|
38
50
|
|
|
39
|
-
export function getPiToolsSuiteUserConfigPath(homeDir = homedir()): string {
|
|
51
|
+
export function getPiToolsSuiteUserConfigPath(homeDir = process.env.HOME?.trim() || homedir()): string {
|
|
40
52
|
return join(homeDir, ".config", "pi", "pi-tools-suite.jsonc");
|
|
41
53
|
}
|
|
42
54
|
|
|
@@ -68,6 +80,24 @@ function normalizeLookupModel(raw: unknown): string | undefined {
|
|
|
68
80
|
return trimmed ? trimmed : undefined;
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
function normalizeNonEmptyString(raw: unknown): string | undefined {
|
|
84
|
+
if (typeof raw !== "string") return undefined;
|
|
85
|
+
const trimmed = raw.trim();
|
|
86
|
+
return trimmed ? trimmed : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mergeResourceRegistry(config: MutableConfig, raw: unknown): void {
|
|
90
|
+
if (!isRecord(raw)) return;
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(raw, "remote")) {
|
|
92
|
+
config.resourceRegistry.remote = normalizeNonEmptyString(raw.remote);
|
|
93
|
+
}
|
|
94
|
+
const branch = normalizeNonEmptyString(raw.branch);
|
|
95
|
+
if (branch) config.resourceRegistry.branch = branch;
|
|
96
|
+
if (Object.prototype.hasOwnProperty.call(raw, "projectKey")) {
|
|
97
|
+
config.resourceRegistry.projectKey = normalizeNonEmptyString(raw.projectKey);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
71
101
|
function isTodoThinkingLevel(raw: unknown): raw is TodoThinkingLevel {
|
|
72
102
|
return TODO_THINKING_OVERRIDE_LEVELS.includes(raw as TodoThinkingLevel);
|
|
73
103
|
}
|
|
@@ -145,6 +175,7 @@ function mergeConfigLayer(config: MutableConfig, raw: Record<string, unknown>, k
|
|
|
145
175
|
if (typeof raw.todoThinking === "boolean") config.todoThinking = raw.todoThinking;
|
|
146
176
|
mergeTodoThinkingOverrides(config, raw.todoThinkingOverrides);
|
|
147
177
|
if (Object.prototype.hasOwnProperty.call(raw, "lookupModel")) config.lookupModel = normalizeLookupModel(raw.lookupModel);
|
|
178
|
+
mergeResourceRegistry(config, raw.resourceRegistry);
|
|
148
179
|
|
|
149
180
|
for (const key of DISABLED_LIST_KEYS) addDisabled(config, raw[key], knownModules);
|
|
150
181
|
for (const key of ENABLED_LIST_KEYS) removeDisabled(config, raw[key], knownModules);
|
|
@@ -191,6 +222,13 @@ function applyEnv(config: MutableConfig, env: Env, knownModules: ReadonlySet<str
|
|
|
191
222
|
const todoThinking = boolFromEnv(env.PI_TOOLS_SUITE_TODO_THINKING);
|
|
192
223
|
if (todoThinking !== undefined) config.todoThinking = todoThinking;
|
|
193
224
|
|
|
225
|
+
const registryRemote = normalizeNonEmptyString(env.PI_RESOURCE_REGISTRY_REMOTE);
|
|
226
|
+
if (registryRemote) config.resourceRegistry.remote = registryRemote;
|
|
227
|
+
const registryBranch = normalizeNonEmptyString(env.PI_RESOURCE_REGISTRY_BRANCH);
|
|
228
|
+
if (registryBranch) config.resourceRegistry.branch = registryBranch;
|
|
229
|
+
const registryProjectKey = normalizeNonEmptyString(env.PI_RESOURCE_REGISTRY_PROJECT_KEY);
|
|
230
|
+
if (registryProjectKey) config.resourceRegistry.projectKey = registryProjectKey;
|
|
231
|
+
|
|
194
232
|
return config;
|
|
195
233
|
}
|
|
196
234
|
|
|
@@ -203,6 +241,7 @@ export function loadPiToolsSuiteConfig(moduleNames: readonly string[], options:
|
|
|
203
241
|
todoThinking: false,
|
|
204
242
|
todoThinkingOverrides: new Map(DEFAULT_TODO_THINKING_OVERRIDES),
|
|
205
243
|
lookupModel: undefined,
|
|
244
|
+
resourceRegistry: { branch: DEFAULT_RESOURCE_REGISTRY_BRANCH },
|
|
206
245
|
};
|
|
207
246
|
const userConfigPath = getPiToolsSuiteUserConfigPath(options.homeDir);
|
|
208
247
|
|
|
@@ -223,5 +262,6 @@ export function loadPiToolsSuiteConfig(moduleNames: readonly string[], options:
|
|
|
223
262
|
todoThinking: config.todoThinking,
|
|
224
263
|
todoThinkingOverrides: Object.fromEntries(config.todoThinkingOverrides),
|
|
225
264
|
...(config.lookupModel ? { lookupModel: config.lookupModel } : {}),
|
|
265
|
+
resourceRegistry: { ...config.resourceRegistry },
|
|
226
266
|
};
|
|
227
267
|
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ContextGatewayBudgets,
|
|
8
8
|
ContextGatewayMode,
|
|
9
9
|
ContextGatewayResolvedConfig,
|
|
10
|
+
ContextGatewayToolClass,
|
|
10
11
|
} from "./types.js";
|
|
11
12
|
|
|
12
13
|
export const DEFAULT_CONTEXT_GATEWAY_BUDGETS: Readonly<ContextGatewayBudgets> = Object.freeze({
|
|
@@ -17,8 +18,16 @@ export const DEFAULT_CONTEXT_GATEWAY_BUDGETS: Readonly<ContextGatewayBudgets> =
|
|
|
17
18
|
maxSearchMatches: 12,
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
-
|
|
21
|
+
export function contextGatewayBudgetForClass(
|
|
22
|
+
toolClass: ContextGatewayToolClass,
|
|
23
|
+
budgets: ContextGatewayBudgets,
|
|
24
|
+
): number {
|
|
25
|
+
if (toolClass === "code-read") return budgets.maxExactReadBytes;
|
|
26
|
+
if (["repo-search", "repo-ast", "repo-structure", "ast-grep"].includes(toolClass)) {
|
|
27
|
+
return budgets.maxSearchBytes;
|
|
28
|
+
}
|
|
29
|
+
return budgets.maxResultBytes;
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
const MODES = new Set<ContextGatewayMode>(["off", "observe", "enforce"]);
|
|
24
33
|
const MAX_BYTE_BUDGET = 64 * 1024 * 1024;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ShellCommandClassification } from "../shell-command-policy.js";
|
|
2
|
+
import { parseTestBuildOutput, planProspectiveTestOutputDelivery } from "./test-output-parser.js";
|
|
3
|
+
import type { ContextGatewayToolClass } from "./types.js";
|
|
4
|
+
|
|
5
|
+
type ResultContent = unknown;
|
|
6
|
+
|
|
7
|
+
export interface ContextGatewayEnforcementPlan {
|
|
8
|
+
representation: "passthrough" | "test-build-compact";
|
|
9
|
+
content: ResultContent;
|
|
10
|
+
sourceContentBytes: number;
|
|
11
|
+
contentBytes: number;
|
|
12
|
+
textBytes: number;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function byteLengthJson(value: unknown): number {
|
|
17
|
+
if (value === undefined) return 0;
|
|
18
|
+
try {
|
|
19
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
20
|
+
} catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function contentText(content: unknown): string {
|
|
26
|
+
if (typeof content === "string") return content;
|
|
27
|
+
if (!Array.isArray(content)) return "";
|
|
28
|
+
return content
|
|
29
|
+
.filter((part) => part && typeof part === "object" && !Array.isArray(part)
|
|
30
|
+
&& (part as Record<string, unknown>).type === "text"
|
|
31
|
+
&& typeof (part as Record<string, unknown>).text === "string")
|
|
32
|
+
.map((part) => (part as Record<string, unknown>).text as string)
|
|
33
|
+
.join("\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function textOnlyContent(content: unknown): boolean {
|
|
37
|
+
return Array.isArray(content)
|
|
38
|
+
&& content.every((part) => part && typeof part === "object" && !Array.isArray(part)
|
|
39
|
+
&& (part as Record<string, unknown>).type === "text"
|
|
40
|
+
&& typeof (part as Record<string, unknown>).text === "string");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function upstreamTruncated(details: unknown): boolean {
|
|
44
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) return false;
|
|
45
|
+
const record = details as Record<string, unknown>;
|
|
46
|
+
if (record.truncated === true) return true;
|
|
47
|
+
const truncation = record.truncation;
|
|
48
|
+
return !!truncation && typeof truncation === "object" && !Array.isArray(truncation)
|
|
49
|
+
&& (truncation as Record<string, unknown>).truncated === true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Selective storeless enforcement. It never performs generic truncation.
|
|
54
|
+
* Only recognised, complete, simple test/build output is compacted; every
|
|
55
|
+
* other result stays byte-for-byte passthrough until a recovery contract exists.
|
|
56
|
+
*/
|
|
57
|
+
export function planContextGatewayEnforcement(input: {
|
|
58
|
+
event: { content?: unknown; details?: unknown; isError?: boolean };
|
|
59
|
+
toolClass: ContextGatewayToolClass;
|
|
60
|
+
shell: ShellCommandClassification;
|
|
61
|
+
budgetBytes: number;
|
|
62
|
+
maxInlineBytes: number;
|
|
63
|
+
}): ContextGatewayEnforcementPlan {
|
|
64
|
+
const sourceText = contentText(input.event.content);
|
|
65
|
+
const sourceContentBytes = byteLengthJson(input.event.content);
|
|
66
|
+
const passthrough = (reason: string): ContextGatewayEnforcementPlan => ({
|
|
67
|
+
representation: "passthrough",
|
|
68
|
+
content: input.event.content,
|
|
69
|
+
sourceContentBytes,
|
|
70
|
+
contentBytes: sourceContentBytes,
|
|
71
|
+
textBytes: new TextEncoder().encode(sourceText).byteLength,
|
|
72
|
+
reason,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (input.toolClass !== "shell") return passthrough("class-not-enforced");
|
|
76
|
+
if (input.shell.scope !== "simple" || input.shell.kind !== "test-build") {
|
|
77
|
+
return passthrough("shell-not-proven-test-build");
|
|
78
|
+
}
|
|
79
|
+
if (!textOnlyContent(input.event.content)) return passthrough("non-text-content");
|
|
80
|
+
if (sourceContentBytes <= input.budgetBytes) return passthrough("within-class-budget");
|
|
81
|
+
|
|
82
|
+
const parsed = parseTestBuildOutput({
|
|
83
|
+
text: sourceText,
|
|
84
|
+
hostOutcome: input.event.isError === true ? "error" : "success",
|
|
85
|
+
upstreamTruncated: upstreamTruncated(input.event.details),
|
|
86
|
+
});
|
|
87
|
+
const compact = planProspectiveTestOutputDelivery(parsed, input.maxInlineBytes, {
|
|
88
|
+
commandScope: input.shell.scope,
|
|
89
|
+
});
|
|
90
|
+
if (compact.decision !== "compact-candidate" || !compact.text) {
|
|
91
|
+
return passthrough(compact.reason);
|
|
92
|
+
}
|
|
93
|
+
const content = [{ type: "text" as const, text: compact.text }];
|
|
94
|
+
return {
|
|
95
|
+
representation: "test-build-compact",
|
|
96
|
+
content,
|
|
97
|
+
sourceContentBytes,
|
|
98
|
+
contentBytes: byteLengthJson(content),
|
|
99
|
+
textBytes: new TextEncoder().encode(compact.text).byteLength,
|
|
100
|
+
reason: compact.reason,
|
|
101
|
+
};
|
|
102
|
+
}
|