pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// Forking a parent transcript into a child must drop Anthropic's signed and redacted
|
|
2
|
+
// thinking blocks: a thinking signature is bound to the session that produced it and
|
|
3
|
+
// cannot be replayed into a branch. Stripping them is required; disabling the child's
|
|
4
|
+
// thinking is not. Pi >= 0.85.0 recovers from signed-thinking mismatches on the
|
|
5
|
+
// transport, so a sanitized transcript is safe to resume with thinking enabled and the
|
|
6
|
+
// child keeps the level it asked for, reasoning fresh from its first turn.
|
|
2
7
|
import * as fs from "node:fs";
|
|
3
8
|
import * as path from "node:path";
|
|
4
9
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { findModelInfo, type ModelInfo } from "./model-info.ts";
|
|
6
10
|
|
|
7
11
|
type SubagentExecutionContext = "fresh" | "fork";
|
|
8
12
|
|
|
@@ -19,7 +23,6 @@ interface BranchSessionEntry {
|
|
|
19
23
|
api?: string;
|
|
20
24
|
model?: string;
|
|
21
25
|
};
|
|
22
|
-
thinkingLevel?: string;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
interface BranchSessionManager {
|
|
@@ -39,20 +42,11 @@ interface ForkContextResolverOptions {
|
|
|
39
42
|
openSession?: (path: string, sessionDir?: string) => BranchSessionManager;
|
|
40
43
|
/** Rewrite a created fork before its path can be used to spawn a child. */
|
|
41
44
|
pruneSession?: (sessionFile: string) => Promise<void>;
|
|
42
|
-
/** Decide per child index whether a sanitized transcript must also disable the child's
|
|
43
|
-
* thinking. Defaults to true (the pre-existing conservative behavior) when omitted. */
|
|
44
|
-
forceThinkingOffForIndex?: (index: number) => boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface ForkContextResolution {
|
|
48
|
-
sessionFile: string;
|
|
49
|
-
thinkingOverride?: "off";
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
interface ForkContextResolver {
|
|
53
48
|
prepareSessionForIndex(index?: number): Promise<void>;
|
|
54
49
|
sessionFileForIndex(index?: number): string | undefined;
|
|
55
|
-
thinkingOverrideForIndex(index?: number): "off" | undefined;
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
export function resolveSubagentContext(value: unknown): SubagentExecutionContext {
|
|
@@ -101,20 +95,6 @@ export function canPreferForkFromSnapshot(input: PreferredForkSnapshot): boolean
|
|
|
101
95
|
}
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
/** Decide whether a resolved child model uses Anthropic's provider or message API, which
|
|
105
|
-
* requires the sanitized fork to disable thinking. Unknown models stay conservative. */
|
|
106
|
-
export function forkedChildRequiresThinkingOff(
|
|
107
|
-
model: string | undefined,
|
|
108
|
-
availableModels?: ModelInfo[],
|
|
109
|
-
preferredProvider?: string,
|
|
110
|
-
): boolean {
|
|
111
|
-
if (!model) return true;
|
|
112
|
-
const info = findModelInfo(model, availableModels, preferredProvider);
|
|
113
|
-
if (!info) return true;
|
|
114
|
-
return info.provider.toLowerCase() === "anthropic"
|
|
115
|
-
|| info.api?.toLowerCase() === "anthropic-messages";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
98
|
function isUnsafeAnthropicThinkingBlock(message: BranchSessionEntry["message"], block: unknown): boolean {
|
|
119
99
|
if (!message || !block || typeof block !== "object" || !("type" in block)) return false;
|
|
120
100
|
const provider = typeof message.provider === "string" ? message.provider.toLowerCase() : "";
|
|
@@ -128,28 +108,6 @@ function isUnsafeAnthropicThinkingBlock(message: BranchSessionEntry["message"],
|
|
|
128
108
|
return record.redacted === true || (typeof signature === "string" && signature.length > 0);
|
|
129
109
|
}
|
|
130
110
|
|
|
131
|
-
function createEntryId(entries: BranchSessionEntry[]): string {
|
|
132
|
-
const ids = new Set(entries.map((entry) => entry.id).filter((id): id is string => typeof id === "string"));
|
|
133
|
-
for (let attempt = 0; attempt < 100; attempt++) {
|
|
134
|
-
const id = randomUUID().slice(0, 8);
|
|
135
|
-
if (!ids.has(id)) return id;
|
|
136
|
-
}
|
|
137
|
-
return randomUUID();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function appendThinkingOffEntry(entries: BranchSessionEntry[]): void {
|
|
141
|
-
const last = entries[entries.length - 1];
|
|
142
|
-
if (last?.type === "thinking_level_change" && last.thinkingLevel === "off") return;
|
|
143
|
-
const parent = [...entries].reverse().find((entry) => typeof entry.id === "string");
|
|
144
|
-
entries.push({
|
|
145
|
-
type: "thinking_level_change",
|
|
146
|
-
id: createEntryId(entries),
|
|
147
|
-
parentId: parent?.id ?? null,
|
|
148
|
-
timestamp: new Date().toISOString(),
|
|
149
|
-
thinkingLevel: "off",
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
111
|
function sanitizeUnsafeThinkingBlocks(entries: BranchSessionEntry[]): boolean {
|
|
154
112
|
let sanitized = false;
|
|
155
113
|
for (const entry of entries) {
|
|
@@ -183,7 +141,6 @@ export function createForkContextResolver(
|
|
|
183
141
|
return {
|
|
184
142
|
prepareSessionForIndex: async () => {},
|
|
185
143
|
sessionFileForIndex: () => undefined,
|
|
186
|
-
thinkingOverrideForIndex: () => undefined,
|
|
187
144
|
};
|
|
188
145
|
}
|
|
189
146
|
|
|
@@ -217,12 +174,12 @@ export function createForkContextResolver(
|
|
|
217
174
|
path.basename(parentSessionFile, ".jsonl"),
|
|
218
175
|
"forks",
|
|
219
176
|
);
|
|
220
|
-
const
|
|
177
|
+
const cachedSessionFiles = new Map<number, string>();
|
|
221
178
|
const preparedIndexes = new Set<number>();
|
|
222
179
|
const preparationPromises = new Map<number, Promise<void>>();
|
|
223
180
|
|
|
224
|
-
const resolveFork = (index = 0):
|
|
225
|
-
const cached =
|
|
181
|
+
const resolveFork = (index = 0): string => {
|
|
182
|
+
const cached = cachedSessionFiles.get(index);
|
|
226
183
|
if (cached) return cached;
|
|
227
184
|
try {
|
|
228
185
|
if (!fs.existsSync(parentSessionFile)) {
|
|
@@ -233,34 +190,23 @@ export function createForkContextResolver(
|
|
|
233
190
|
if (!sessionFile) {
|
|
234
191
|
throw new Error("Session manager did not return a forked session file.");
|
|
235
192
|
}
|
|
236
|
-
const forceThinkingOff = (sanitized: boolean): boolean =>
|
|
237
|
-
sanitized && (options.forceThinkingOffForIndex?.(index) ?? true);
|
|
238
|
-
let thinkingOverride: "off" | undefined;
|
|
239
193
|
if (!fs.existsSync(sessionFile)) {
|
|
240
194
|
const header = sourceManager.getHeader?.();
|
|
241
195
|
const entries = sourceManager.getEntries?.();
|
|
242
196
|
if (!header || !entries) {
|
|
243
197
|
throw new Error(`Session manager returned a forked session file that does not exist and cannot be persisted by fallback: ${sessionFile}`);
|
|
244
198
|
}
|
|
245
|
-
|
|
246
|
-
appendThinkingOffEntry(entries);
|
|
247
|
-
thinkingOverride = "off";
|
|
248
|
-
}
|
|
199
|
+
sanitizeUnsafeThinkingBlocks(entries);
|
|
249
200
|
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
250
201
|
fs.writeFileSync(sessionFile, `${[header, ...entries].map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
251
202
|
} else {
|
|
252
203
|
const entries = readSessionEntries(sessionFile);
|
|
253
204
|
if (sanitizeUnsafeThinkingBlocks(entries)) {
|
|
254
|
-
if (forceThinkingOff(true)) {
|
|
255
|
-
appendThinkingOffEntry(entries);
|
|
256
|
-
thinkingOverride = "off";
|
|
257
|
-
}
|
|
258
205
|
fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
259
206
|
}
|
|
260
207
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return resolution;
|
|
208
|
+
cachedSessionFiles.set(index, sessionFile);
|
|
209
|
+
return sessionFile;
|
|
264
210
|
} catch (error) {
|
|
265
211
|
const cause = error instanceof Error ? error : new Error(String(error));
|
|
266
212
|
throw new Error(`Failed to create forked subagent session: ${cause.message}`, { cause });
|
|
@@ -269,11 +215,11 @@ export function createForkContextResolver(
|
|
|
269
215
|
|
|
270
216
|
return {
|
|
271
217
|
async prepareSessionForIndex(index = 0): Promise<void> {
|
|
272
|
-
const
|
|
218
|
+
const sessionFile = resolveFork(index);
|
|
273
219
|
if (!options.pruneSession || preparedIndexes.has(index)) return;
|
|
274
220
|
let preparation = preparationPromises.get(index);
|
|
275
221
|
if (!preparation) {
|
|
276
|
-
preparation = options.pruneSession(
|
|
222
|
+
preparation = options.pruneSession(sessionFile).then(() => {
|
|
277
223
|
preparedIndexes.add(index);
|
|
278
224
|
});
|
|
279
225
|
preparationPromises.set(index, preparation);
|
|
@@ -284,10 +230,7 @@ export function createForkContextResolver(
|
|
|
284
230
|
if (options.pruneSession && !preparedIndexes.has(index)) {
|
|
285
231
|
throw new Error(`Pruned fork session ${index} was used before pruning completed.`);
|
|
286
232
|
}
|
|
287
|
-
return resolveFork(index)
|
|
288
|
-
},
|
|
289
|
-
thinkingOverrideForIndex(index = 0): "off" | undefined {
|
|
290
|
-
return resolveFork(index).thinkingOverride;
|
|
233
|
+
return resolveFork(index);
|
|
291
234
|
},
|
|
292
235
|
};
|
|
293
236
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
4
|
+
import type { PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
|
|
4
5
|
import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
5
6
|
|
|
6
7
|
export const AGENT_DEFINITION_PROJECTION_VERSION = 1 as const;
|
|
7
|
-
|
|
8
|
+
// v2: the Intercom bridge prompt and tools are part of the binding on every
|
|
9
|
+
// path, and the bridge text no longer names the parent session.
|
|
10
|
+
export const LAUNCH_BINDING_PROJECTION_VERSION = 2 as const;
|
|
8
11
|
|
|
9
12
|
function stableJson(value: unknown): string {
|
|
10
13
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -75,8 +78,9 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
|
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/** Digest of the parsed definition; a runtime overlay that already captured it wins over re-hashing the overlaid copy. */
|
|
78
82
|
export function agentDefinitionDigest(agent: AgentConfig): string {
|
|
79
|
-
return stableJsonDigest(projectAgentDefinition(agent));
|
|
83
|
+
return agent.definitionDigest ?? stableJsonDigest(projectAgentDefinition(agent));
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
export interface LaunchBindingInput {
|
|
@@ -136,3 +140,62 @@ export function projectLaunchBinding(input: LaunchBindingInput): Record<string,
|
|
|
136
140
|
export function launchBindingDigest(input: LaunchBindingInput): string {
|
|
137
141
|
return stableJsonDigest(projectLaunchBinding(input));
|
|
138
142
|
}
|
|
143
|
+
|
|
144
|
+
type LaunchBindingPromptMode = Pick<LaunchBindingInput, "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills">;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Who the child is: the launched agent, or the identity a persisted step
|
|
148
|
+
* already captured when a runner recomputes the binding per model attempt.
|
|
149
|
+
*/
|
|
150
|
+
export type LaunchBindingIdentity =
|
|
151
|
+
| { agent: AgentConfig }
|
|
152
|
+
| ({ definitionDigest: string } & LaunchBindingPromptMode);
|
|
153
|
+
|
|
154
|
+
export type LaunchBindingSource = LaunchBindingIdentity
|
|
155
|
+
& Pick<LaunchBindingInput, "fast" | "thinking" | "skills" | "outputPath" | "outputMode" | "structuredOutputSchema" | "extensionBindings">
|
|
156
|
+
& {
|
|
157
|
+
task: string;
|
|
158
|
+
modelCandidates: string[];
|
|
159
|
+
/** Effective child system prompt before runtime acceptance prose. */
|
|
160
|
+
systemPrompt: string;
|
|
161
|
+
toolPlan: Pick<PiLaunchToolPlan, "effectiveToolAllowlist" | "excludeTools" | "extensionArgs" | "effectiveMcpTools">;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export interface LaunchBinding {
|
|
165
|
+
definitionDigest: string;
|
|
166
|
+
launchContractDigest: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Assemble launch identity from resolved preflight or execution inputs.
|
|
171
|
+
* The stable projection omits undefined optional fields.
|
|
172
|
+
*/
|
|
173
|
+
export function resolveLaunchBinding(source: LaunchBindingSource): LaunchBinding {
|
|
174
|
+
const identity = "agent" in source ? {
|
|
175
|
+
definitionDigest: agentDefinitionDigest(source.agent),
|
|
176
|
+
systemPromptMode: source.agent.systemPromptMode,
|
|
177
|
+
inheritProjectContext: source.agent.inheritProjectContext,
|
|
178
|
+
inheritGlobalContext: source.agent.inheritGlobalContext,
|
|
179
|
+
inheritSkills: source.agent.inheritSkills,
|
|
180
|
+
} : source;
|
|
181
|
+
return {
|
|
182
|
+
definitionDigest: identity.definitionDigest,
|
|
183
|
+
launchContractDigest: launchBindingDigest({
|
|
184
|
+
...identity,
|
|
185
|
+
task: source.task,
|
|
186
|
+
modelCandidates: source.modelCandidates,
|
|
187
|
+
fast: source.fast,
|
|
188
|
+
thinking: source.thinking || undefined,
|
|
189
|
+
systemPrompt: source.systemPrompt,
|
|
190
|
+
skills: source.skills,
|
|
191
|
+
tools: source.toolPlan.effectiveToolAllowlist,
|
|
192
|
+
excludeTools: source.toolPlan.excludeTools.length > 0 ? source.toolPlan.excludeTools : undefined,
|
|
193
|
+
extensions: source.toolPlan.extensionArgs,
|
|
194
|
+
mcpDirectTools: source.toolPlan.effectiveMcpTools,
|
|
195
|
+
outputPath: source.outputPath || undefined,
|
|
196
|
+
outputMode: source.outputMode,
|
|
197
|
+
structuredOutputSchema: source.structuredOutputSchema || undefined,
|
|
198
|
+
extensionBindings: source.extensionBindings || undefined,
|
|
199
|
+
}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Api, Model, ProviderHeaders } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
const OPENCODE_HOST = "opencode.ai";
|
|
4
|
+
|
|
5
|
+
function matchesOpenCodeHost(baseUrl: string): boolean {
|
|
6
|
+
try {
|
|
7
|
+
return new URL(baseUrl).hostname === OPENCODE_HOST;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* OpenCode session-routing headers for internal subagent model calls.
|
|
15
|
+
*
|
|
16
|
+
* Pi's own session path emits these from coding-agent's provider-attribution
|
|
17
|
+
* merge, but subagent-internal calls (watchdog review, permission arbiter,
|
|
18
|
+
* task-mutation arbiter, prompt audit) stream through bare Agents that bypass
|
|
19
|
+
* that path. Without them OpenCode falls back to client-IP affinity and loses
|
|
20
|
+
* prompt-cache routing (see pi issue #4847). Returns undefined for every other
|
|
21
|
+
* provider so non-OpenCode requests stay byte-identical.
|
|
22
|
+
*/
|
|
23
|
+
export function opencodeSessionHeaders(
|
|
24
|
+
model: Pick<Model<Api>, "provider" | "baseUrl">,
|
|
25
|
+
sessionId: string | undefined,
|
|
26
|
+
): ProviderHeaders | undefined {
|
|
27
|
+
if (!sessionId) return undefined;
|
|
28
|
+
if (model.provider !== "opencode" && model.provider !== "opencode-go" && !matchesOpenCodeHost(model.baseUrl)) return undefined;
|
|
29
|
+
return { "x-opencode-session": sessionId, "x-opencode-client": "pi" };
|
|
30
|
+
}
|