@mgiles/perk 3.2.0 → 3.3.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/README.md +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +365 -380
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
- package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/pi/v1/lifecycleGates.ts +127 -0
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -21
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +5 -4
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +263 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +178 -163
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +87 -46
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +43 -18
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -6
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review-browser/stack.md +5 -6
- package/shared/README.md +8 -0
- package/shared/bindings.yaml +3 -3
- package/shared/contracts.md +2601 -506
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +13 -0
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -489
- package/extension/doors/learn.ts +0 -668
- package/extension/doors/lifecycleGates.ts +0 -207
- package/extension/doors/objectiveStack.ts +0 -1543
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -279
- package/extension/doors/submit.ts +0 -373
- package/extension/doors/submitPrReview.ts +0 -505
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectiveSave.ts +0 -366
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
// The perk-owned, in-process read-only child session (context-isolation primitive #1).
|
|
2
|
-
//
|
|
3
|
-
// A deterministic, fully-isolated read-only child spun at the SDK level via `createAgentSession`,
|
|
4
|
-
// plus the handoff contract both context-isolation primitives honor (the in-process one here; the
|
|
5
|
-
// spawned shape later): cap the model-visible output, keep the FULL result in a verified scratch
|
|
6
|
-
// file + a structured block, and return DOUBLE-DELIVERY (compact prose for the human + a structured
|
|
7
|
-
// block for the orchestrator). Route-don't-relay is enforced structurally — the raw child output
|
|
8
|
-
// never enters the parent; only a path/summary does. This is substrate only; its consumer is the
|
|
9
|
-
// read-only CI executor. No registry stage, no door change, no cross-CLI behavior.
|
|
10
|
-
//
|
|
11
|
-
// Relationship to the in-session gate (extension/substrate/toolGating.ts): its READ_ONLY_TOOLS is the *in-session* allowlist
|
|
12
|
-
// and includes a sub-allowlisted `bash`. This SDK-level set is the STRICTER ["read","grep","find",
|
|
13
|
-
// "ls"] (no bash) — a separate constant, not a reuse. The CI executor composes its own allowlist when it needs a
|
|
14
|
-
// gated test-runner command; that is not authored here.
|
|
15
|
-
//
|
|
16
|
-
// Isolation comes from the DefaultResourceLoader `no*` flags + the tools allowlist — NOT from
|
|
17
|
-
// `extensionFactories: []` (which is already the default and controls only inline factories; it
|
|
18
|
-
// does not stop `loader.reload()` from resolving the project's `.pi/settings.json` packages and
|
|
19
|
-
// loading perk's own extension into the child). The `no*` flags keep perk's machinery out of the
|
|
20
|
-
// child and keep the path offline/deterministic. A custom loader is reloaded by the caller.
|
|
21
|
-
|
|
22
|
-
import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
23
|
-
import { tmpdir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
25
|
-
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
26
|
-
import {
|
|
27
|
-
type AgentSession,
|
|
28
|
-
createAgentSession,
|
|
29
|
-
DefaultResourceLoader,
|
|
30
|
-
SessionManager,
|
|
31
|
-
SettingsManager,
|
|
32
|
-
} from "@earendil-works/pi-coding-agent";
|
|
33
|
-
import { atomicWriteFileSync, ensureRunScratch, scratchDir } from "../substrate/cache.ts";
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The SDK-level read-only allowlist (no `bash`; stricter than the in-session READ_ONLY_TOOLS).
|
|
37
|
-
* Mirrors pi's read-only recipe (examples/sdk/05-tools.ts).
|
|
38
|
-
*/
|
|
39
|
-
export const SDK_READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
40
|
-
|
|
41
|
-
/** The model-visible byte cap (matches subagent's PER_TASK_OUTPUT_CAP); overridable per call. */
|
|
42
|
-
export const DEFAULT_MODEL_VISIBLE_CAP = 50 * 1024;
|
|
43
|
-
|
|
44
|
-
/** Minimal structural shape of a session message (we only read assistant text parts). */
|
|
45
|
-
interface MessageLike {
|
|
46
|
-
role?: string;
|
|
47
|
-
content?: unknown;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Scan messages newest-first for the last assistant message's first text part; "" if none.
|
|
52
|
-
* Pure — mirrors subagent's getFinalOutput.
|
|
53
|
-
*/
|
|
54
|
-
export function extractFinalAssistantText(messages: readonly MessageLike[]): string {
|
|
55
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
56
|
-
const msg = messages[i];
|
|
57
|
-
if (msg?.role !== "assistant") continue;
|
|
58
|
-
const content = msg.content;
|
|
59
|
-
if (typeof content === "string") return content;
|
|
60
|
-
if (Array.isArray(content)) {
|
|
61
|
-
for (const part of content) {
|
|
62
|
-
const p = part as { type?: string; text?: string };
|
|
63
|
-
if (p.type === "text" && typeof p.text === "string") return p.text;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return "";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface CapResult {
|
|
71
|
-
/** The (possibly truncated) text safe to show the model. */
|
|
72
|
-
shown: string;
|
|
73
|
-
/** Total UTF-8 byte length of the original text. */
|
|
74
|
-
bytesTotal: number;
|
|
75
|
-
/** UTF-8 byte length of `shown` (before the truncation notice). */
|
|
76
|
-
bytesShown: number;
|
|
77
|
-
/** Whether truncation occurred. */
|
|
78
|
-
truncated: boolean;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* UTF-8-byte-safe truncation (subagent's byte-trim loop). Under cap ⇒ unchanged, truncated:false.
|
|
83
|
-
* When truncated, a notice points at the scratch file holding the full result; the notice sits at
|
|
84
|
-
* the cut edge (appended in head mode, prepended in tail mode) so a top-down reader immediately
|
|
85
|
-
* knows which side is missing.
|
|
86
|
-
*
|
|
87
|
-
* `keep` mirrors the SDK's truncateHead/truncateTail guidance: "head" (default) for
|
|
88
|
-
* model-authored summaries/handoffs where the beginning matters; "tail" for command/CI logs where
|
|
89
|
-
* failure summaries live at the end. Deliberately perk's own byte-only util (not the SDK's
|
|
90
|
-
* line-count-aware `truncateTail`): `CapResult`'s byte fields and the scratch-pointing notice are
|
|
91
|
-
* load-bearing in `ChildStructured`/`CiCheckResult`. Pure.
|
|
92
|
-
*/
|
|
93
|
-
export function capForModel(
|
|
94
|
-
text: string,
|
|
95
|
-
cap: number = DEFAULT_MODEL_VISIBLE_CAP,
|
|
96
|
-
scratchPath: string | null = null,
|
|
97
|
-
keep: "head" | "tail" = "head",
|
|
98
|
-
): CapResult {
|
|
99
|
-
const bytesTotal = Buffer.byteLength(text, "utf8");
|
|
100
|
-
if (bytesTotal <= cap) {
|
|
101
|
-
return { shown: text, bytesTotal, bytesShown: bytesTotal, truncated: false };
|
|
102
|
-
}
|
|
103
|
-
let trimmed = keep === "head" ? text.slice(0, cap) : text.slice(-cap);
|
|
104
|
-
while (Buffer.byteLength(trimmed, "utf8") > cap) {
|
|
105
|
-
trimmed = keep === "head" ? trimmed.slice(0, -1) : trimmed.slice(1);
|
|
106
|
-
}
|
|
107
|
-
const bytesShown = Buffer.byteLength(trimmed, "utf8");
|
|
108
|
-
const omitted = bytesTotal - bytesShown;
|
|
109
|
-
const where = scratchPath ? ` Full output preserved at ${scratchPath}.` : "";
|
|
110
|
-
const shown =
|
|
111
|
-
keep === "head"
|
|
112
|
-
? `${trimmed}\n\n[Output truncated: ${omitted} bytes omitted.${where}]`
|
|
113
|
-
: `[Output truncated: ${omitted} bytes omitted.${where}]\n\n${trimmed}`;
|
|
114
|
-
return { shown, bytesTotal, bytesShown, truncated: true };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* The structured half of the double-delivery handoff — the block the orchestrator parses (and the
|
|
119
|
-
* CI executor will place in a tool's forking-safe `details`).
|
|
120
|
-
*/
|
|
121
|
-
export interface ChildStructured {
|
|
122
|
-
success: boolean;
|
|
123
|
-
summary: string;
|
|
124
|
-
scratchPath: string | null;
|
|
125
|
-
bytesTotal: number;
|
|
126
|
-
bytesShown: number;
|
|
127
|
-
truncated: boolean;
|
|
128
|
-
error?: string;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* The full double-delivery handoff: `prose` is the capped human-facing summary; `structured` is the
|
|
133
|
-
* orchestrator-facing block; `scratchPath` is the verified path to the full result
|
|
134
|
-
* (route-don't-relay — the raw output never enters the parent beyond the cap).
|
|
135
|
-
*/
|
|
136
|
-
export interface ChildHandoff {
|
|
137
|
-
success: boolean;
|
|
138
|
-
prose: string;
|
|
139
|
-
structured: ChildStructured;
|
|
140
|
-
scratchPath: string | null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create a locked-down, SDK-level read-only child session. Isolation = the DefaultResourceLoader
|
|
145
|
-
* `no*` flags + the tools allowlist; a throwaway temp `agentDir` (a locked-down child loads nothing
|
|
146
|
-
* from it, and there is no production ctx.agentDir). Creating the session is OFFLINE; only
|
|
147
|
-
* `prompt()` requires a key. The caller must `dispose()` the returned session.
|
|
148
|
-
*/
|
|
149
|
-
export async function createReadOnlySession(opts: {
|
|
150
|
-
cwd: string;
|
|
151
|
-
model?: Model<Api>;
|
|
152
|
-
tools?: string[];
|
|
153
|
-
}): Promise<{ session: AgentSession }> {
|
|
154
|
-
// A throwaway temp agentDir — a locked-down child loads nothing from it (no production
|
|
155
|
-
// ctx.agentDir exists), exactly as the offline harness does.
|
|
156
|
-
const agentDir = mkdtempSync(join(tmpdir(), "perk-ro-agent-"));
|
|
157
|
-
const loader = new DefaultResourceLoader({
|
|
158
|
-
cwd: opts.cwd,
|
|
159
|
-
agentDir,
|
|
160
|
-
noExtensions: true,
|
|
161
|
-
noSkills: true,
|
|
162
|
-
noPromptTemplates: true,
|
|
163
|
-
noThemes: true,
|
|
164
|
-
noContextFiles: true,
|
|
165
|
-
});
|
|
166
|
-
// A custom loader is NOT auto-reloaded by createAgentSession — reload it ourselves.
|
|
167
|
-
await loader.reload();
|
|
168
|
-
const { session } = await createAgentSession({
|
|
169
|
-
cwd: opts.cwd,
|
|
170
|
-
model: opts.model,
|
|
171
|
-
resourceLoader: loader,
|
|
172
|
-
tools: opts.tools ?? SDK_READ_ONLY_TOOLS,
|
|
173
|
-
sessionManager: SessionManager.inMemory(opts.cwd),
|
|
174
|
-
settingsManager: SettingsManager.inMemory({
|
|
175
|
-
compaction: { enabled: false },
|
|
176
|
-
retry: { enabled: false },
|
|
177
|
-
}),
|
|
178
|
-
});
|
|
179
|
-
return { session };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export interface RunReadOnlyChildOpts {
|
|
183
|
-
cwd: string;
|
|
184
|
-
task: string;
|
|
185
|
-
model?: Model<Api>;
|
|
186
|
-
tools?: string[];
|
|
187
|
-
runId?: string;
|
|
188
|
-
step?: string;
|
|
189
|
-
modelVisibleCap?: number;
|
|
190
|
-
signal?: AbortSignal;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export interface RunReadOnlyChildDeps {
|
|
194
|
-
createSession?: (opts: {
|
|
195
|
-
cwd: string;
|
|
196
|
-
model?: Model<Api>;
|
|
197
|
-
tools?: string[];
|
|
198
|
-
}) => Promise<{ session: AgentSession }>;
|
|
199
|
-
runTask?: (session: AgentSession, task: string, signal?: AbortSignal) => Promise<string>;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Default production runTask: prompt the child, then extract its final assistant text. */
|
|
203
|
-
async function defaultRunTask(
|
|
204
|
-
session: AgentSession,
|
|
205
|
-
task: string,
|
|
206
|
-
_signal?: AbortSignal,
|
|
207
|
-
): Promise<string> {
|
|
208
|
-
await session.prompt(task);
|
|
209
|
-
return extractFinalAssistantText(session.messages as unknown as MessageLike[]);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** Resolve the scratch file path for a child's full output (run-scoped when a runId is given). */
|
|
213
|
-
function resolveScratchPath(cwd: string, runId?: string, step?: string): string {
|
|
214
|
-
if (runId) {
|
|
215
|
-
const dir = ensureRunScratch(cwd, runId);
|
|
216
|
-
return join(dir, `${step ?? "child"}.md`);
|
|
217
|
-
}
|
|
218
|
-
const base = scratchDir(cwd);
|
|
219
|
-
mkdirSync(base, { recursive: true });
|
|
220
|
-
const dir = mkdtempSync(join(base, "child-"));
|
|
221
|
-
return join(dir, `${step ?? "child"}.md`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function failure(prose: string, error: string): ChildHandoff {
|
|
225
|
-
return {
|
|
226
|
-
success: false,
|
|
227
|
-
prose,
|
|
228
|
-
scratchPath: null,
|
|
229
|
-
structured: {
|
|
230
|
-
success: false,
|
|
231
|
-
summary: prose,
|
|
232
|
-
scratchPath: null,
|
|
233
|
-
bytesTotal: 0,
|
|
234
|
-
bytesShown: 0,
|
|
235
|
-
truncated: false,
|
|
236
|
-
error,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* One-shot read-only child orchestrator implementing the handoff contract: create the read-only
|
|
243
|
-
* session, run the task, write the full raw output to a scratch file and VERIFY it
|
|
244
|
-
* (write→verify→pass-path), cap the model-visible output into prose, and return
|
|
245
|
-
* double-delivery. Fail loud + fail closed: NEVER throws to the parent — on any error it returns
|
|
246
|
-
* `{ success:false, scratchPath:null }` with the error in both `prose` and `structured.error`.
|
|
247
|
-
*/
|
|
248
|
-
export async function runReadOnlyChild(
|
|
249
|
-
opts: RunReadOnlyChildOpts,
|
|
250
|
-
deps: RunReadOnlyChildDeps = {},
|
|
251
|
-
): Promise<ChildHandoff> {
|
|
252
|
-
const createSession = deps.createSession ?? createReadOnlySession;
|
|
253
|
-
const runTask = deps.runTask ?? defaultRunTask;
|
|
254
|
-
const cap = opts.modelVisibleCap ?? DEFAULT_MODEL_VISIBLE_CAP;
|
|
255
|
-
|
|
256
|
-
if (opts.signal?.aborted) return failure("read-only child aborted before start.", "aborted");
|
|
257
|
-
|
|
258
|
-
let session: AgentSession | null = null;
|
|
259
|
-
try {
|
|
260
|
-
const created = await createSession({ cwd: opts.cwd, model: opts.model, tools: opts.tools });
|
|
261
|
-
session = created.session;
|
|
262
|
-
|
|
263
|
-
const output = await runTask(session, opts.task, opts.signal);
|
|
264
|
-
|
|
265
|
-
if (opts.signal?.aborted) return failure("read-only child aborted.", "aborted");
|
|
266
|
-
|
|
267
|
-
// write → verify → pass-path: persist the full result, then confirm it landed.
|
|
268
|
-
const scratchPath = resolveScratchPath(opts.cwd, opts.runId, opts.step);
|
|
269
|
-
atomicWriteFileSync(scratchPath, output);
|
|
270
|
-
if (!existsSync(scratchPath)) {
|
|
271
|
-
return failure("read-only child: scratch write could not be verified.", "scratch-verify");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const capped = capForModel(output, cap, scratchPath);
|
|
275
|
-
return {
|
|
276
|
-
success: true,
|
|
277
|
-
prose: capped.shown,
|
|
278
|
-
scratchPath,
|
|
279
|
-
structured: {
|
|
280
|
-
success: true,
|
|
281
|
-
summary: capped.shown,
|
|
282
|
-
scratchPath,
|
|
283
|
-
bytesTotal: capped.bytesTotal,
|
|
284
|
-
bytesShown: capped.bytesShown,
|
|
285
|
-
truncated: capped.truncated,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
} catch (err) {
|
|
289
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
290
|
-
return failure(`read-only child failed: ${message}`, message);
|
|
291
|
-
} finally {
|
|
292
|
-
session?.dispose();
|
|
293
|
-
}
|
|
294
|
-
}
|