pi-subagents 0.47.0 → 0.48.0
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 +37 -0
- package/README.md +2 -0
- package/agents/reviewer.md +3 -4
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +16 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +30 -11
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +7 -9
- package/src/extension/schemas.ts +4 -4
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +46 -0
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-job-tracker.ts +279 -134
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +33 -4
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/control-channel.ts +55 -17
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/result-watcher.ts +87 -6
- package/src/runs/background/run-status.ts +23 -1
- package/src/runs/background/scheduled-runs.ts +23 -0
- package/src/runs/background/stale-run-reconciler.ts +10 -3
- package/src/runs/background/subagent-runner.ts +75 -33
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +648 -195
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/artifacts.ts +1 -1
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/types.ts +97 -1
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +26 -6
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
35
35
|
import { INTERCOM_BRIDGE_MARKER } from "../../intercom/intercom-bridge.ts";
|
|
36
36
|
import { runSync } from "./execution.ts";
|
|
37
|
+
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
37
38
|
import { workflowForegroundSteeringLaunchOptions } from "./workflow-foreground-steering.ts";
|
|
38
39
|
import {
|
|
39
40
|
beginForegroundChild,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
settleForegroundSchedulingOwner,
|
|
43
44
|
updateForegroundChild,
|
|
44
45
|
} from "./foreground-control.ts";
|
|
46
|
+
import { updateLiveEffectivePrompt } from "./prompt-audit.ts";
|
|
45
47
|
import { buildChainSummary } from "../../shared/formatters.ts";
|
|
46
48
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
47
49
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
@@ -71,6 +73,7 @@ import {
|
|
|
71
73
|
type ResolvedControlConfig,
|
|
72
74
|
type ResolvedTurnBudget,
|
|
73
75
|
type ResolvedToolBudget,
|
|
76
|
+
type RunFanoutBudgetDescriptor,
|
|
74
77
|
type SingleResult,
|
|
75
78
|
type ToolBudgetConfig,
|
|
76
79
|
type ChainCheckpointState,
|
|
@@ -86,6 +89,7 @@ import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferen
|
|
|
86
89
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
87
90
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
88
91
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
92
|
+
import { claimRunFanoutBatch, RunFanoutLimitError } from "../shared/run-fanout-budget.ts";
|
|
89
93
|
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
90
94
|
import type { ChainOutputMap } from "../../shared/types.ts";
|
|
91
95
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
@@ -167,6 +171,7 @@ interface ParallelChainRunInput {
|
|
|
167
171
|
configToolBudget?: ToolBudgetConfig;
|
|
168
172
|
globalSemaphore?: Semaphore;
|
|
169
173
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
174
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
170
175
|
permissions?: PermissionConfig;
|
|
171
176
|
dynamic?: boolean;
|
|
172
177
|
}
|
|
@@ -368,7 +373,11 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
368
373
|
beginForegroundChild(input.foregroundControl, {
|
|
369
374
|
index: childIndex,
|
|
370
375
|
agent: task.agent,
|
|
371
|
-
|
|
376
|
+
authoredTask: cleanTask,
|
|
377
|
+
effectivePrompt: taskStr,
|
|
378
|
+
cwd: taskCwd,
|
|
379
|
+
...(outputPath ? { outputPath } : {}),
|
|
380
|
+
description: `${task.agent} child`,
|
|
372
381
|
...(effectiveModel ? { model: effectiveModel } : {}),
|
|
373
382
|
...(thinking ? { thinking } : {}),
|
|
374
383
|
interrupt: () => {
|
|
@@ -388,8 +397,10 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
388
397
|
result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
389
398
|
permissions: input.permissions,
|
|
390
399
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
400
|
+
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
391
401
|
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, childIndex),
|
|
392
402
|
capabilityCeiling: input.capabilityCeiling,
|
|
403
|
+
runFanoutBudget: input.runFanoutBudget ? { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}chain[${input.stepIndex}]${input.dynamic ? `.expand[${taskIndex}]` : `.parallel[${taskIndex}]`}` } : undefined,
|
|
393
404
|
context: input.contextForAgent?.(task.agent),
|
|
394
405
|
cwd: taskCwd,
|
|
395
406
|
signal: input.signal,
|
|
@@ -422,6 +433,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
422
433
|
agentContract,
|
|
423
434
|
acceptance: task.acceptance,
|
|
424
435
|
acceptanceContext: { mode: "chain", dynamic: input.dynamic && task.acceptance === undefined },
|
|
436
|
+
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, childIndex, prompt) : undefined,
|
|
425
437
|
timeoutMs: input.timeoutMs,
|
|
426
438
|
deadlineAt: input.deadlineAt,
|
|
427
439
|
turnBudget: input.turnBudget,
|
|
@@ -540,6 +552,7 @@ interface ChainExecutionParams {
|
|
|
540
552
|
/** Global cap on simultaneously-running tasks within this chain. Defaults to DEFAULT_GLOBAL_CONCURRENCY_LIMIT. */
|
|
541
553
|
globalConcurrencyLimit?: number;
|
|
542
554
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
555
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
543
556
|
}
|
|
544
557
|
|
|
545
558
|
interface ChainExecutionResult {
|
|
@@ -876,6 +889,7 @@ ${step.message}` : ""}` }],
|
|
|
876
889
|
configToolBudget: params.configToolBudget,
|
|
877
890
|
globalSemaphore,
|
|
878
891
|
permissions: params.permissions,
|
|
892
|
+
runFanoutBudget: params.runFanoutBudget,
|
|
879
893
|
});
|
|
880
894
|
globalTaskIndex += step.parallel.length;
|
|
881
895
|
|
|
@@ -1002,6 +1016,19 @@ ${step.message}` : ""}` }],
|
|
|
1002
1016
|
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
1003
1017
|
}
|
|
1004
1018
|
|
|
1019
|
+
try {
|
|
1020
|
+
if (params.runFanoutBudget) claimRunFanoutBatch(params.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1023
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
1024
|
+
const result = buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
1025
|
+
if (error instanceof RunFanoutLimitError) {
|
|
1026
|
+
result.details.runFanoutBudget = error.snapshot;
|
|
1027
|
+
result.details.runFanoutRejection = error.rejection;
|
|
1028
|
+
}
|
|
1029
|
+
return result;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1005
1032
|
dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
|
|
1006
1033
|
agent: step.parallel.agent,
|
|
1007
1034
|
label: materialized.parallel[itemIndex]?.label,
|
|
@@ -1137,6 +1164,7 @@ ${step.message}` : ""}` }],
|
|
|
1137
1164
|
configToolBudget: params.configToolBudget,
|
|
1138
1165
|
globalSemaphore,
|
|
1139
1166
|
permissions: params.permissions,
|
|
1167
|
+
runFanoutBudget: params.runFanoutBudget,
|
|
1140
1168
|
dynamic: true,
|
|
1141
1169
|
});
|
|
1142
1170
|
globalTaskIndex = dynamicStartIndex + reservedDynamicItems;
|
|
@@ -1332,7 +1360,11 @@ ${step.message}` : ""}` }],
|
|
|
1332
1360
|
beginForegroundChild(foregroundControl, {
|
|
1333
1361
|
index: childIndex,
|
|
1334
1362
|
agent: seqStep.agent,
|
|
1335
|
-
|
|
1363
|
+
authoredTask: cleanTask,
|
|
1364
|
+
effectivePrompt: stepTask,
|
|
1365
|
+
cwd,
|
|
1366
|
+
...(outputPath ? { outputPath } : {}),
|
|
1367
|
+
description: `${seqStep.agent} child`,
|
|
1336
1368
|
...(effectiveModel ? { model: effectiveModel } : {}),
|
|
1337
1369
|
...(thinking ? { thinking } : {}),
|
|
1338
1370
|
interrupt: () => {
|
|
@@ -1369,8 +1401,10 @@ ${step.message}` : ""}` }],
|
|
|
1369
1401
|
r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
1370
1402
|
permissions: params.permissions,
|
|
1371
1403
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1404
|
+
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
1372
1405
|
...workflowForegroundSteeringLaunchOptions(params.foregroundControl, childIndex),
|
|
1373
1406
|
capabilityCeiling: params.capabilityCeiling,
|
|
1407
|
+
runFanoutBudget: params.runFanoutBudget ? { ...params.runFanoutBudget, parentPath: `${params.runFanoutBudget.parentPath ? `${params.runFanoutBudget.parentPath}/` : ""}chain[${stepIndex}]` } : undefined,
|
|
1374
1408
|
context: params.contextForAgent?.(seqStep.agent),
|
|
1375
1409
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
1376
1410
|
signal,
|
|
@@ -1403,6 +1437,7 @@ ${step.message}` : ""}` }],
|
|
|
1403
1437
|
agentContract,
|
|
1404
1438
|
acceptance: seqStep.acceptance,
|
|
1405
1439
|
acceptanceContext: { mode: "chain" },
|
|
1440
|
+
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, childIndex, prompt) : undefined,
|
|
1406
1441
|
timeoutMs: params.timeoutMs,
|
|
1407
1442
|
deadlineAt,
|
|
1408
1443
|
turnBudget: params.turnBudget,
|
|
@@ -54,18 +54,19 @@ import {
|
|
|
54
54
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
55
55
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
56
56
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
57
|
+
import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
|
|
57
58
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
58
59
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
59
60
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
60
61
|
import { resolvePermissionRules } from "../shared/permissions.ts";
|
|
61
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
62
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
62
63
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
63
64
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
64
65
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
65
66
|
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
66
67
|
import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
67
68
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
68
|
-
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
69
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
69
70
|
import {
|
|
70
71
|
buildModelCandidates,
|
|
71
72
|
formatModelAttemptNote,
|
|
@@ -90,6 +91,7 @@ import {
|
|
|
90
91
|
summarizeRecentMutatingFailures,
|
|
91
92
|
} from "../shared/long-running-guard.ts";
|
|
92
93
|
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport, validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
94
|
+
import { PROMPT_REDACTED } from "../../shared/utils.ts";
|
|
93
95
|
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
94
96
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
95
97
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
@@ -117,6 +119,12 @@ function withRunContext<T extends SingleResult>(result: T, context: RunSyncOptio
|
|
|
117
119
|
return result;
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
function redactResultPrompt<T extends SingleResult>(result: T): T {
|
|
123
|
+
result.task = PROMPT_REDACTED;
|
|
124
|
+
if (result.progress) result.progress.task = PROMPT_REDACTED;
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
function sumUsage(target: Usage, source: Usage): void {
|
|
121
129
|
target.input += source.input;
|
|
122
130
|
target.output += source.output;
|
|
@@ -139,7 +147,7 @@ function persistSingleResultMetadata(input: {
|
|
|
139
147
|
writeMetadata(input.metadataPath, {
|
|
140
148
|
runId: input.runId,
|
|
141
149
|
agent: input.agent,
|
|
142
|
-
task:
|
|
150
|
+
task: PROMPT_REDACTED,
|
|
143
151
|
exitCode: target.exitCode,
|
|
144
152
|
processSignal: target.processSignal,
|
|
145
153
|
usage: target.usage,
|
|
@@ -231,6 +239,7 @@ function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): vo
|
|
|
231
239
|
function snapshotProgress(progress: AgentProgress): AgentProgress {
|
|
232
240
|
return {
|
|
233
241
|
...progress,
|
|
242
|
+
task: PROMPT_REDACTED,
|
|
234
243
|
skills: progress.skills ? [...progress.skills] : undefined,
|
|
235
244
|
recentTools: boundStreamedRecentTools(progress.recentTools),
|
|
236
245
|
recentOutput: boundStreamedRecentOutput(progress.recentOutput),
|
|
@@ -240,6 +249,7 @@ function snapshotProgress(progress: AgentProgress): AgentProgress {
|
|
|
240
249
|
function snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {
|
|
241
250
|
return {
|
|
242
251
|
...result,
|
|
252
|
+
task: PROMPT_REDACTED,
|
|
243
253
|
messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,
|
|
244
254
|
usage: { ...result.usage },
|
|
245
255
|
skills: result.skills ? [...result.skills] : undefined,
|
|
@@ -291,6 +301,7 @@ async function runSingleAttempt(
|
|
|
291
301
|
attemptNotes: string[];
|
|
292
302
|
outputSnapshot?: SingleOutputSnapshot;
|
|
293
303
|
originalTask?: string;
|
|
304
|
+
taskDelivery?: SubagentTaskDelivery;
|
|
294
305
|
},
|
|
295
306
|
): Promise<SingleResult> {
|
|
296
307
|
const effectiveThinking = options.thinkingOverride ?? agent.thinking;
|
|
@@ -312,6 +323,7 @@ async function runSingleAttempt(
|
|
|
312
323
|
const { args, env: sharedEnv, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit } = buildPiArgs({
|
|
313
324
|
baseArgs: ["--mode", "json", "-p"],
|
|
314
325
|
task,
|
|
326
|
+
taskDelivery: shared.taskDelivery,
|
|
315
327
|
sessionEnabled: shared.sessionEnabled,
|
|
316
328
|
sessionDir: options.sessionDir,
|
|
317
329
|
sessionFile: options.sessionFile,
|
|
@@ -337,6 +349,7 @@ async function runSingleAttempt(
|
|
|
337
349
|
parentControlInbox: options.nestedRoute?.controlInbox,
|
|
338
350
|
parentRootRunId: options.nestedRoute?.rootRunId,
|
|
339
351
|
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
352
|
+
runFanoutBudget: options.runFanoutBudget,
|
|
340
353
|
parentSessionId: options.parentSessionId,
|
|
341
354
|
steerInboxDir: options.steerInboxDir,
|
|
342
355
|
steerCapabilityPath: options.steerCapabilityPath,
|
|
@@ -1277,15 +1290,38 @@ async function runSingleAttempt(
|
|
|
1277
1290
|
mcpDirectTools: agent.mcpDirectTools,
|
|
1278
1291
|
})
|
|
1279
1292
|
: undefined;
|
|
1280
|
-
|
|
1293
|
+
let completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
|
|
1294
|
+
// The classifier is deliberately narrow, so a read-only review task can
|
|
1295
|
+
// still be misread as implementation. Arbitrate BEFORE any failure side
|
|
1296
|
+
// effect is published (effects, exit code, progress, notifications,
|
|
1297
|
+
// acceptance, output persistence): only a confident read-only verdict
|
|
1298
|
+
// rescues, and the task text alone is evidence — never the child's own
|
|
1299
|
+
// final message.
|
|
1300
|
+
let arbiterRescued = false;
|
|
1301
|
+
if (completionGuardTriggered) {
|
|
1302
|
+
const arbitration = await arbitrateCompletionGuardRescue({
|
|
1303
|
+
guardTriggered: true,
|
|
1304
|
+
task: shared.originalTask ?? task,
|
|
1305
|
+
arbiter: options.llmIntentArbiter,
|
|
1306
|
+
});
|
|
1307
|
+
completionGuardTriggered = arbitration.triggered;
|
|
1308
|
+
arbiterRescued = arbitration.rescued;
|
|
1309
|
+
}
|
|
1281
1310
|
if (completionGuard) {
|
|
1282
1311
|
result.effects = {
|
|
1283
1312
|
...(result.effects ?? {}),
|
|
1284
1313
|
fileMutation: {
|
|
1285
|
-
status: completionGuard.expectedMutation
|
|
1314
|
+
status: completionGuard.expectedMutation
|
|
1315
|
+
? completionGuardTriggered
|
|
1316
|
+
? "missing"
|
|
1317
|
+
: arbiterRescued
|
|
1318
|
+
? "not-applicable"
|
|
1319
|
+
: "observed"
|
|
1320
|
+
: "not-applicable",
|
|
1286
1321
|
expected: completionGuard.expectedMutation,
|
|
1287
1322
|
attempted: completionGuard.attemptedMutation || observedMutationAttempt,
|
|
1288
1323
|
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1324
|
+
...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
|
|
1289
1325
|
},
|
|
1290
1326
|
};
|
|
1291
1327
|
}
|
|
@@ -1355,7 +1391,7 @@ async function runSyncCompletion(
|
|
|
1355
1391
|
};
|
|
1356
1392
|
const agent = agents.find((a) => a.name === agentName);
|
|
1357
1393
|
if (!agent) {
|
|
1358
|
-
return withRunContext({
|
|
1394
|
+
return redactResultPrompt(withRunContext({
|
|
1359
1395
|
index: options.index ?? 0,
|
|
1360
1396
|
agent: agentName,
|
|
1361
1397
|
task,
|
|
@@ -1363,12 +1399,12 @@ async function runSyncCompletion(
|
|
|
1363
1399
|
messages: [],
|
|
1364
1400
|
usage: emptyUsage(),
|
|
1365
1401
|
error: `Unknown agent: ${agentName}`,
|
|
1366
|
-
}, options.context);
|
|
1402
|
+
}, options.context));
|
|
1367
1403
|
}
|
|
1368
1404
|
try {
|
|
1369
1405
|
assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
|
|
1370
1406
|
} catch (error) {
|
|
1371
|
-
return withRunContext({
|
|
1407
|
+
return redactResultPrompt(withRunContext({
|
|
1372
1408
|
index: options.index ?? 0,
|
|
1373
1409
|
agent: agent.name,
|
|
1374
1410
|
task,
|
|
@@ -1377,11 +1413,11 @@ async function runSyncCompletion(
|
|
|
1377
1413
|
usage: emptyUsage(),
|
|
1378
1414
|
error: error instanceof Error ? error.message : String(error),
|
|
1379
1415
|
...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
|
|
1380
|
-
}, options.context);
|
|
1416
|
+
}, options.context));
|
|
1381
1417
|
}
|
|
1382
1418
|
const acceptanceErrors = validateAcceptanceInput(options.acceptance);
|
|
1383
1419
|
if (acceptanceErrors.length > 0) {
|
|
1384
|
-
return withRunContext({
|
|
1420
|
+
return redactResultPrompt(withRunContext({
|
|
1385
1421
|
index: options.index ?? 0,
|
|
1386
1422
|
agent: agentName,
|
|
1387
1423
|
task,
|
|
@@ -1389,11 +1425,11 @@ async function runSyncCompletion(
|
|
|
1389
1425
|
messages: [],
|
|
1390
1426
|
usage: emptyUsage(),
|
|
1391
1427
|
error: acceptanceErrors.join(" "),
|
|
1392
|
-
}, options.context);
|
|
1428
|
+
}, options.context));
|
|
1393
1429
|
}
|
|
1394
1430
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1395
1431
|
if (outputModeValidationError) {
|
|
1396
|
-
return withRunContext({
|
|
1432
|
+
return redactResultPrompt(withRunContext({
|
|
1397
1433
|
index: options.index ?? 0,
|
|
1398
1434
|
agent: agentName,
|
|
1399
1435
|
task,
|
|
@@ -1402,7 +1438,7 @@ async function runSyncCompletion(
|
|
|
1402
1438
|
usage: emptyUsage(),
|
|
1403
1439
|
outputMode: options.outputMode,
|
|
1404
1440
|
error: outputModeValidationError,
|
|
1405
|
-
}, options.context);
|
|
1441
|
+
}, options.context));
|
|
1406
1442
|
}
|
|
1407
1443
|
|
|
1408
1444
|
const shareEnabled = options.share === true;
|
|
@@ -1419,6 +1455,7 @@ async function runSyncCompletion(
|
|
|
1419
1455
|
});
|
|
1420
1456
|
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
|
|
1421
1457
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
1458
|
+
options.onEffectivePrompt?.(taskWithAcceptance);
|
|
1422
1459
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
1423
1460
|
if (options.context === "fork" && options.sessionFile && existsSync(options.sessionFile)) {
|
|
1424
1461
|
alignForkedSessionCwd(options.sessionFile, options.cwd ?? runtimeCwd);
|
|
@@ -1433,7 +1470,7 @@ async function runSyncCompletion(
|
|
|
1433
1470
|
agent.filePath ? path.dirname(agent.filePath) : skillCwd,
|
|
1434
1471
|
);
|
|
1435
1472
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1436
|
-
return withRunContext({
|
|
1473
|
+
return redactResultPrompt(withRunContext({
|
|
1437
1474
|
index: options.index ?? 0,
|
|
1438
1475
|
agent: agentName,
|
|
1439
1476
|
task,
|
|
@@ -1441,7 +1478,7 @@ async function runSyncCompletion(
|
|
|
1441
1478
|
messages: [],
|
|
1442
1479
|
usage: emptyUsage(),
|
|
1443
1480
|
error: "Skills not found: pi-subagents",
|
|
1444
|
-
}, options.context);
|
|
1481
|
+
}, options.context));
|
|
1445
1482
|
}
|
|
1446
1483
|
let systemPrompt = agent.systemPrompt?.trim() || "";
|
|
1447
1484
|
if (resolvedSkills.length > 0) {
|
|
@@ -1476,7 +1513,7 @@ async function runSyncCompletion(
|
|
|
1476
1513
|
artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
|
|
1477
1514
|
ensureArtifactsDir(options.artifactsDir);
|
|
1478
1515
|
if (options.artifactConfig?.includeInput !== false) {
|
|
1479
|
-
|
|
1516
|
+
writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${PROMPT_REDACTED}; live Prompt Audit only.\n`);
|
|
1480
1517
|
}
|
|
1481
1518
|
if (options.artifactConfig?.includeJsonl !== false) {
|
|
1482
1519
|
jsonlPath = artifactPathsResult.jsonlPath;
|
|
@@ -1490,7 +1527,7 @@ async function runSyncCompletion(
|
|
|
1490
1527
|
childIndex: options.index,
|
|
1491
1528
|
cwd: options.cwd ?? runtimeCwd,
|
|
1492
1529
|
});
|
|
1493
|
-
transcriptWriter.writeInitialUserMessage(
|
|
1530
|
+
transcriptWriter.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
|
|
1494
1531
|
}
|
|
1495
1532
|
}
|
|
1496
1533
|
|
|
@@ -1526,6 +1563,9 @@ async function runSyncCompletion(
|
|
|
1526
1563
|
};
|
|
1527
1564
|
let lastResult: SingleResult | undefined;
|
|
1528
1565
|
const modelsToTry = candidates.length > 0 ? candidates : [undefined];
|
|
1566
|
+
// Escalated to "file" after an unexplained zero-activity startup failure so
|
|
1567
|
+
// retries keep the task text out of argv (endpoint pre-exec scans may deny it).
|
|
1568
|
+
let taskDeliveryOverride: SubagentTaskDelivery | undefined;
|
|
1529
1569
|
modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
|
|
1530
1570
|
const candidate = modelsToTry[modelIndex];
|
|
1531
1571
|
for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
|
|
@@ -1544,6 +1584,7 @@ async function runSyncCompletion(
|
|
|
1544
1584
|
.filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
|
|
1545
1585
|
outputSnapshot,
|
|
1546
1586
|
originalTask: task,
|
|
1587
|
+
taskDelivery: taskDeliveryOverride,
|
|
1547
1588
|
});
|
|
1548
1589
|
lastResult = result;
|
|
1549
1590
|
if (startupAttemptIndex === 0) {
|
|
@@ -1612,6 +1653,10 @@ async function runSyncCompletion(
|
|
|
1612
1653
|
}
|
|
1613
1654
|
break modelAttemptsLoop;
|
|
1614
1655
|
}
|
|
1656
|
+
if (!taskDeliveryOverride && result.processSignal === "SIGKILL") {
|
|
1657
|
+
taskDeliveryOverride = "file";
|
|
1658
|
+
attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
|
|
1659
|
+
}
|
|
1615
1660
|
attempt.error = retryNote;
|
|
1616
1661
|
attemptNotes.push(retryNote);
|
|
1617
1662
|
continue;
|
|
@@ -1726,13 +1771,39 @@ async function runSyncCompletion(
|
|
|
1726
1771
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1727
1772
|
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
|
|
1728
1773
|
result.exitCode = 1;
|
|
1774
|
+
if (result.savedOutputPath) {
|
|
1775
|
+
result.finalOutput = finalizeSingleOutput({
|
|
1776
|
+
fullOutput: result.finalOutput ?? "",
|
|
1777
|
+
outputPath: options.outputPath,
|
|
1778
|
+
outputMode: result.outputMode,
|
|
1779
|
+
exitCode: result.exitCode,
|
|
1780
|
+
preserveSavedOutput: true,
|
|
1781
|
+
savedPath: result.savedOutputPath,
|
|
1782
|
+
outputReference: result.outputReference,
|
|
1783
|
+
}).displayOutput;
|
|
1784
|
+
artifactOutputByResult.set(result, result.finalOutput);
|
|
1785
|
+
}
|
|
1729
1786
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1787
|
+
if (artifactPathsResult && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
|
|
1788
|
+
try {
|
|
1789
|
+
writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
|
|
1790
|
+
output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1791
|
+
error: result.error,
|
|
1792
|
+
transcriptPath: result.transcriptPath,
|
|
1793
|
+
metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
|
|
1794
|
+
}));
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
const message = `Artifact output post-processing failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1797
|
+
result.outputSaveError = result.outputSaveError ? `${result.outputSaveError}\n${message}` : message;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1730
1800
|
if (result.progress) {
|
|
1731
1801
|
result.progress.status = "failed";
|
|
1732
1802
|
result.progress.error = result.error;
|
|
1733
1803
|
}
|
|
1734
1804
|
}
|
|
1735
1805
|
if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
|
|
1806
|
+
redactResultPrompt(result);
|
|
1736
1807
|
try {
|
|
1737
1808
|
persistResultMetadata(result);
|
|
1738
1809
|
} catch (error) {
|
|
@@ -1817,7 +1888,7 @@ export async function runSync(
|
|
|
1817
1888
|
durationMs: publishedReceipt.progressSummary?.durationMs ?? 0,
|
|
1818
1889
|
error: failureMessage,
|
|
1819
1890
|
};
|
|
1820
|
-
const failedResult: SingleResult = {
|
|
1891
|
+
const failedResult: SingleResult = redactResultPrompt({
|
|
1821
1892
|
...publishedReceipt,
|
|
1822
1893
|
detached: undefined,
|
|
1823
1894
|
detachedReason,
|
|
@@ -1833,7 +1904,7 @@ export async function runSync(
|
|
|
1833
1904
|
acceptance: publishedReceipt.acceptance
|
|
1834
1905
|
? buildSkippedAcceptanceLedger(publishedReceipt.acceptance.effectiveAcceptance, { id: "completion-pipeline", message: failureMessage })
|
|
1835
1906
|
: undefined,
|
|
1836
|
-
};
|
|
1907
|
+
});
|
|
1837
1908
|
if (strictContract) attachContractProjections(failedResult);
|
|
1838
1909
|
try {
|
|
1839
1910
|
// Replace the provisional detach receipt metadata with the authoritative
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { AgentProgress, ForegroundChildControl, ForegroundRunControl } from "../../shared/types.ts";
|
|
2
|
+
import { registerLivePromptAudit, removeLivePromptAudit, type PromptAuditRerunContract } from "./prompt-audit.ts";
|
|
2
3
|
|
|
3
4
|
interface BeginForegroundChildInput {
|
|
4
5
|
index: number;
|
|
5
6
|
agent: string;
|
|
6
7
|
description?: string;
|
|
8
|
+
authoredTask: string;
|
|
9
|
+
effectivePrompt: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
outputPath?: string;
|
|
12
|
+
rerun?: PromptAuditRerunContract;
|
|
7
13
|
model?: string;
|
|
8
14
|
thinking?: string;
|
|
9
15
|
interrupt: () => boolean;
|
|
@@ -109,6 +115,11 @@ export function beginForegroundChild(control: ForegroundRunControl, input: Begin
|
|
|
109
115
|
}
|
|
110
116
|
control.activeChildren ??= new Map();
|
|
111
117
|
control.activeChildren.set(input.index, child);
|
|
118
|
+
registerLivePromptAudit(control, input.index, input.authoredTask, input.effectivePrompt, {
|
|
119
|
+
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
120
|
+
...(input.outputPath ? { outputPath: input.outputPath } : {}),
|
|
121
|
+
...(input.rerun ? { rerun: input.rerun } : {}),
|
|
122
|
+
});
|
|
112
123
|
syncCurrentChild(control, child);
|
|
113
124
|
}
|
|
114
125
|
|
|
@@ -121,6 +132,7 @@ export function updateForegroundChild(control: ForegroundRunControl, index: numb
|
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
export function finishForegroundChild(control: ForegroundRunControl, index: number): void {
|
|
135
|
+
removeLivePromptAudit(control, index);
|
|
124
136
|
control.activeChildren?.delete(index);
|
|
125
137
|
if (control.currentIndex === index) {
|
|
126
138
|
const next = [...(control.activeChildren?.values() ?? [])]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Agent, type StreamFn, type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { convertToLlm, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
4
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
5
|
+
import type { ForegroundRunControl } from "../../shared/types.ts";
|
|
6
|
+
export type PromptAuditView = "authored" | "runtime" | "effective";
|
|
7
|
+
|
|
8
|
+
export interface PromptAuditRerunContract {
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LivePromptAudit {
|
|
13
|
+
authoredTask: string;
|
|
14
|
+
runtimeAdditions: string;
|
|
15
|
+
finalEffectivePrompt: string;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
outputPath?: string;
|
|
18
|
+
rerun?: PromptAuditRerunContract;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const livePrompts = new WeakMap<ForegroundRunControl, Map<number, LivePromptAudit>>();
|
|
22
|
+
|
|
23
|
+
type RegistryModel = Model<any>;
|
|
24
|
+
|
|
25
|
+
function fullModelId(model: Pick<RegistryModel, "provider" | "id">): string {
|
|
26
|
+
return `${model.provider}/${model.id}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function textContent(value: unknown): string {
|
|
30
|
+
if (typeof value === "string") return value;
|
|
31
|
+
if (!Array.isArray(value)) return "";
|
|
32
|
+
return value.map((item) => item && typeof item === "object" && (item as { type?: unknown }).type === "text" ? String((item as { text?: unknown }).text ?? "") : "").join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function finalAssistantText(agent: Agent): string {
|
|
36
|
+
for (let index = agent.state.messages.length - 1; index >= 0; index--) {
|
|
37
|
+
const message = agent.state.messages[index];
|
|
38
|
+
if (message && typeof message === "object" && (message as { role?: unknown }).role === "assistant") {
|
|
39
|
+
return textContent((message as { content?: unknown }).content).trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function resolveRewriteAuth(ctx: ExtensionContext, model: RegistryModel): Promise<{ apiKey?: string; headers?: Record<string, string>; env?: Record<string, string> }> {
|
|
46
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
47
|
+
if (auth.ok === false) throw new Error(`Prompt redo model auth failed for ${fullModelId(model)}: ${auth.error}`);
|
|
48
|
+
return {
|
|
49
|
+
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
50
|
+
...(auth.headers ? { headers: auth.headers } : {}),
|
|
51
|
+
...(auth.env ? { env: auth.env } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function rewritePromptWithGuidance(input: {
|
|
56
|
+
ctx: ExtensionContext;
|
|
57
|
+
authoredTask: string;
|
|
58
|
+
runtimeAdditions: string;
|
|
59
|
+
finalEffectivePrompt: string;
|
|
60
|
+
guidance: string;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
streamFn?: StreamFn;
|
|
63
|
+
}): Promise<string> {
|
|
64
|
+
const model = input.ctx.model;
|
|
65
|
+
if (!model) throw new Error("Prompt redo needs the current session model to rewrite the authored task.");
|
|
66
|
+
const auth = await resolveRewriteAuth(input.ctx, model);
|
|
67
|
+
const registeredProvider = (input.ctx.modelRegistry as {
|
|
68
|
+
getRegisteredProviderConfig?: (provider: string) => { api?: string; streamSimple?: StreamFn } | undefined;
|
|
69
|
+
}).getRegisteredProviderConfig?.(model.provider);
|
|
70
|
+
const baseStreamFn = input.streamFn ?? (registeredProvider?.streamSimple && registeredProvider.api === model.api
|
|
71
|
+
? registeredProvider.streamSimple
|
|
72
|
+
: streamSimple);
|
|
73
|
+
const streamFn: StreamFn = (nextModel, context, streamOptions) => baseStreamFn(nextModel, context, {
|
|
74
|
+
...streamOptions,
|
|
75
|
+
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
76
|
+
env: auth.env || streamOptions?.env ? { ...(auth.env ?? {}), ...(streamOptions?.env ?? {}) } : undefined,
|
|
77
|
+
headers: { ...(streamOptions?.headers ?? {}), ...(auth.headers ?? {}) },
|
|
78
|
+
});
|
|
79
|
+
const ctxThinking = (input.ctx as { getThinkingLevel?: () => ThinkingLevel }).getThinkingLevel?.();
|
|
80
|
+
const agent = new Agent({
|
|
81
|
+
initialState: {
|
|
82
|
+
systemPrompt: [
|
|
83
|
+
"Rewrite one subagent authored task from Prompt Audit context.",
|
|
84
|
+
"Return only the revised authored task text.",
|
|
85
|
+
"Do not add Markdown fences, explanations, labels, or commentary.",
|
|
86
|
+
"Preserve the original intent unless the guidance explicitly changes it.",
|
|
87
|
+
"Do not include runtime additions unless they are needed as ordinary task context.",
|
|
88
|
+
].join("\n"),
|
|
89
|
+
model,
|
|
90
|
+
thinkingLevel: ctxThinking ?? "off",
|
|
91
|
+
tools: [],
|
|
92
|
+
},
|
|
93
|
+
convertToLlm,
|
|
94
|
+
streamFunction: streamFn,
|
|
95
|
+
getApiKey: (providerName) => providerName === model.provider ? auth.apiKey : undefined,
|
|
96
|
+
toolExecution: "sequential",
|
|
97
|
+
});
|
|
98
|
+
const abort = () => agent.abort();
|
|
99
|
+
input.ctx.signal?.addEventListener("abort", abort, { once: true });
|
|
100
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
101
|
+
try {
|
|
102
|
+
await agent.prompt([
|
|
103
|
+
"Guidance from the human:",
|
|
104
|
+
input.guidance.trim(),
|
|
105
|
+
"",
|
|
106
|
+
"Authored task:",
|
|
107
|
+
input.authoredTask,
|
|
108
|
+
"",
|
|
109
|
+
"Runtime additions for context only:",
|
|
110
|
+
input.runtimeAdditions,
|
|
111
|
+
"",
|
|
112
|
+
"Final effective prompt for context only:",
|
|
113
|
+
input.finalEffectivePrompt,
|
|
114
|
+
].join("\n"));
|
|
115
|
+
} finally {
|
|
116
|
+
input.ctx.signal?.removeEventListener("abort", abort);
|
|
117
|
+
input.signal?.removeEventListener("abort", abort);
|
|
118
|
+
}
|
|
119
|
+
const rewritten = finalAssistantText(agent);
|
|
120
|
+
if (!rewritten) throw new Error("Prompt redo rewrite returned an empty prompt.");
|
|
121
|
+
return rewritten;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function runtimeAdditions(authoredTask: string, effectivePrompt: string): string {
|
|
125
|
+
if (!authoredTask) return effectivePrompt;
|
|
126
|
+
const authoredIndex = effectivePrompt.indexOf(authoredTask);
|
|
127
|
+
if (authoredIndex < 0) return "(runtime additions unavailable)";
|
|
128
|
+
const before = effectivePrompt.slice(0, authoredIndex).trim();
|
|
129
|
+
const after = effectivePrompt.slice(authoredIndex + authoredTask.length).trim();
|
|
130
|
+
return [before, after].filter(Boolean).join("\n\n") || "(none)";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function registerLivePromptAudit(
|
|
134
|
+
control: ForegroundRunControl,
|
|
135
|
+
index: number,
|
|
136
|
+
authoredTask: string,
|
|
137
|
+
effectivePrompt: string,
|
|
138
|
+
metadata: { cwd?: string; outputPath?: string; rerun?: PromptAuditRerunContract } = {},
|
|
139
|
+
): void {
|
|
140
|
+
let prompts = livePrompts.get(control);
|
|
141
|
+
if (!prompts) {
|
|
142
|
+
prompts = new Map();
|
|
143
|
+
livePrompts.set(control, prompts);
|
|
144
|
+
}
|
|
145
|
+
prompts.set(index, {
|
|
146
|
+
authoredTask,
|
|
147
|
+
runtimeAdditions: runtimeAdditions(authoredTask, effectivePrompt),
|
|
148
|
+
finalEffectivePrompt: effectivePrompt,
|
|
149
|
+
...(metadata.cwd ? { cwd: metadata.cwd } : {}),
|
|
150
|
+
...(metadata.outputPath ? { outputPath: metadata.outputPath } : {}),
|
|
151
|
+
...(metadata.rerun ? { rerun: metadata.rerun } : {}),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function updateLiveEffectivePrompt(control: ForegroundRunControl, index: number, effectivePrompt: string): void {
|
|
156
|
+
const prompt = livePrompts.get(control)?.get(index);
|
|
157
|
+
if (!prompt) return;
|
|
158
|
+
prompt.finalEffectivePrompt = effectivePrompt;
|
|
159
|
+
prompt.runtimeAdditions = runtimeAdditions(prompt.authoredTask, effectivePrompt);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getLivePromptAudit(control: ForegroundRunControl, index: number): LivePromptAudit | undefined {
|
|
163
|
+
return livePrompts.get(control)?.get(index);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function removeLivePromptAudit(control: ForegroundRunControl, index: number): void {
|
|
167
|
+
const prompts = livePrompts.get(control);
|
|
168
|
+
if (!prompts) return;
|
|
169
|
+
prompts.delete(index);
|
|
170
|
+
if (prompts.size === 0) livePrompts.delete(control);
|
|
171
|
+
}
|