@oh-my-pi/pi-coding-agent 16.2.13 → 16.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/CHANGELOG.md +108 -7
- package/dist/cli.js +6033 -5982
- package/dist/types/advisor/config.d.ts +4 -2
- package/dist/types/collab/host.d.ts +16 -0
- package/dist/types/collab/protocol.d.ts +9 -3
- package/dist/types/commit/model-selection.d.ts +5 -0
- package/dist/types/config/model-resolver.d.ts +12 -10
- package/dist/types/config/settings-schema.d.ts +28 -5
- package/dist/types/edit/modes/patch.d.ts +11 -0
- package/dist/types/eval/agent-bridge.d.ts +5 -1
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +86 -2
- package/dist/types/extensibility/skills.d.ts +2 -1
- package/dist/types/mnemopi/state.d.ts +12 -0
- package/dist/types/modes/components/agent-hub.d.ts +9 -5
- package/dist/types/modes/components/status-line/component.test.d.ts +1 -0
- package/dist/types/modes/components/usage-row.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +6 -0
- package/dist/types/modes/controllers/streaming-reveal.d.ts +17 -1
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +30 -3
- package/dist/types/modes/interactive-mode.d.ts +12 -7
- package/dist/types/modes/rpc/rpc-client.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +64 -1
- package/dist/types/modes/session-teardown.d.ts +63 -0
- package/dist/types/modes/session-teardown.test.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +9 -4
- package/dist/types/modes/types.d.ts +2 -3
- package/dist/types/modes/utils/copy-targets.d.ts +2 -0
- package/dist/types/plan-mode/approved-plan-prompt.test.d.ts +1 -0
- package/dist/types/sdk.d.ts +9 -0
- package/dist/types/session/agent-session.d.ts +46 -13
- package/dist/types/session/exit-diagnostics.d.ts +48 -0
- package/dist/types/session/session-loader.d.ts +5 -0
- package/dist/types/task/executor.d.ts +12 -5
- package/dist/types/task/parallel.d.ts +1 -0
- package/dist/types/task/render.test.d.ts +1 -0
- package/dist/types/thinking.d.ts +2 -0
- package/dist/types/tools/checkpoint.d.ts +8 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +11 -1
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/output-meta.d.ts +7 -0
- package/dist/types/tools/output-schema-validator.d.ts +7 -4
- package/dist/types/tools/path-utils.d.ts +16 -0
- package/dist/types/tools/render-utils.d.ts +4 -1
- package/dist/types/utils/git.d.ts +47 -2
- package/dist/types/web/search/provider.d.ts +7 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +12 -12
- package/src/advisor/config.ts +4 -2
- package/src/cli/bench-cli.ts +7 -2
- package/src/collab/guest.ts +94 -5
- package/src/collab/host.ts +80 -3
- package/src/collab/protocol.ts +9 -2
- package/src/commit/model-selection.ts +8 -2
- package/src/config/keybindings.ts +3 -1
- package/src/config/model-discovery.ts +66 -2
- package/src/config/model-registry.ts +7 -3
- package/src/config/model-resolver.ts +51 -23
- package/src/config/settings-schema.ts +44 -14
- package/src/edit/hashline/diff.ts +13 -2
- package/src/edit/index.ts +58 -8
- package/src/edit/modes/patch.ts +53 -18
- package/src/edit/streaming.ts +7 -6
- package/src/eval/__tests__/agent-bridge.test.ts +57 -1
- package/src/eval/agent-bridge.ts +15 -5
- package/src/exec/bash-executor.ts +7 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +534 -16
- package/src/extensibility/skills.ts +29 -6
- package/src/goals/guided-setup.ts +4 -3
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/lsp/client.ts +11 -14
- package/src/main.ts +38 -10
- package/src/mnemopi/state.ts +43 -7
- package/src/modes/acp/acp-agent.ts +1 -1
- package/src/modes/components/agent-hub.ts +10 -2
- package/src/modes/components/agent-transcript-viewer.ts +39 -7
- package/src/modes/components/assistant-message.ts +16 -10
- package/src/modes/components/chat-transcript-builder.ts +11 -1
- package/src/modes/components/custom-editor.test.ts +20 -0
- package/src/modes/components/hook-selector.ts +44 -25
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/status-line/component.test.ts +44 -0
- package/src/modes/components/status-line/component.ts +9 -1
- package/src/modes/components/status-line/segments.ts +3 -3
- package/src/modes/components/usage-row.ts +16 -2
- package/src/modes/controllers/command-controller.ts +6 -21
- package/src/modes/controllers/event-controller.ts +11 -9
- package/src/modes/controllers/extension-ui-controller.ts +84 -3
- package/src/modes/controllers/input-controller.ts +16 -13
- package/src/modes/controllers/selector-controller.ts +3 -8
- package/src/modes/controllers/streaming-reveal.ts +75 -53
- package/src/modes/controllers/tool-args-reveal.ts +340 -10
- package/src/modes/interactive-mode.ts +122 -79
- package/src/modes/rpc/rpc-client.ts +21 -0
- package/src/modes/rpc/rpc-mode.ts +197 -46
- package/src/modes/session-teardown.test.ts +219 -0
- package/src/modes/session-teardown.ts +82 -0
- package/src/modes/setup-wizard/scenes/theme.ts +2 -2
- package/src/modes/skill-command.ts +7 -20
- package/src/modes/theme/theme.ts +29 -15
- package/src/modes/types.ts +2 -3
- package/src/modes/utils/copy-targets.ts +12 -0
- package/src/modes/utils/ui-helpers.ts +19 -2
- package/src/plan-mode/approved-plan-prompt.test.ts +36 -0
- package/src/prompts/advisor/system.md +1 -1
- package/src/prompts/agents/tester.md +6 -2
- package/src/prompts/skills/autoload.md +8 -0
- package/src/prompts/skills/user-invocation.md +11 -0
- package/src/prompts/steering/parent-irc.md +5 -0
- package/src/prompts/system/irc-incoming.md +2 -0
- package/src/prompts/system/mid-run-todo-nudge.md +3 -0
- package/src/prompts/system/plan-mode-approved.md +7 -10
- package/src/prompts/system/plan-mode-compact-instructions.md +2 -1
- package/src/prompts/system/plan-mode-reference.md +3 -4
- package/src/prompts/system/rewind-report.md +6 -0
- package/src/prompts/system/subagent-system-prompt.md +3 -0
- package/src/prompts/tools/irc.md +2 -1
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/rewind.md +3 -2
- package/src/prompts/tools/task.md +4 -0
- package/src/sdk.ts +18 -4
- package/src/session/agent-session.ts +660 -114
- package/src/session/exit-diagnostics.ts +202 -0
- package/src/session/session-context.ts +25 -13
- package/src/session/session-loader.ts +58 -25
- package/src/session/session-manager.ts +0 -1
- package/src/session/session-persistence.ts +30 -4
- package/src/session/settings-stream-fn.ts +14 -0
- package/src/slash-commands/builtin-registry.ts +35 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/executor.ts +31 -8
- package/src/task/index.ts +31 -9
- package/src/task/isolation-runner.ts +18 -2
- package/src/task/parallel.ts +7 -2
- package/src/task/render.test.ts +121 -0
- package/src/task/render.ts +48 -2
- package/src/task/worktree.ts +12 -13
- package/src/task/yield-assembly.ts +8 -5
- package/src/thinking.ts +5 -0
- package/src/tools/ask.ts +188 -9
- package/src/tools/ast-edit.ts +7 -0
- package/src/tools/ast-grep.ts +11 -0
- package/src/tools/bash.ts +6 -30
- package/src/tools/checkpoint.ts +15 -1
- package/src/tools/eval-render.ts +15 -0
- package/src/tools/fetch.ts +82 -18
- package/src/tools/gh.ts +1 -1
- package/src/tools/grep.ts +45 -27
- package/src/tools/index.ts +3 -1
- package/src/tools/irc.ts +24 -9
- package/src/tools/output-meta.ts +50 -0
- package/src/tools/output-schema-validator.ts +152 -15
- package/src/tools/path-utils.ts +55 -10
- package/src/tools/read.ts +1 -1
- package/src/tools/render-utils.ts +5 -3
- package/src/tools/yield.ts +41 -1
- package/src/utils/commit-message-generator.ts +2 -2
- package/src/utils/git.ts +271 -29
- package/src/utils/thinking-display.ts +13 -0
- package/src/web/search/index.ts +9 -28
- package/src/web/search/provider.ts +26 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/types.ts +5 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { SessionEntry } from "./session-entries";
|
|
3
|
+
|
|
4
|
+
export const TOOL_EXECUTION_START_CUSTOM_TYPE = "tool_execution_start";
|
|
5
|
+
export const SESSION_EXIT_CUSTOM_TYPE = "session_exit";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compact projection of tool-call arguments persisted with the start marker.
|
|
9
|
+
* The assistant message already carries the full arguments; this exists only
|
|
10
|
+
* so `appendArgumentSummary` can name the command/path in resume warnings
|
|
11
|
+
* without duplicating whole argument payloads into the session JSONL.
|
|
12
|
+
*/
|
|
13
|
+
export interface ToolArgumentSummary {
|
|
14
|
+
command?: string;
|
|
15
|
+
path?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Persisted marker written before a tool implementation starts running. */
|
|
19
|
+
export interface ToolExecutionStartData {
|
|
20
|
+
toolCallId: string;
|
|
21
|
+
toolName: string;
|
|
22
|
+
args?: ToolArgumentSummary;
|
|
23
|
+
intent?: string;
|
|
24
|
+
startedAt: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Tool call left without a matching toolResult at the end of a branch. */
|
|
28
|
+
export interface PendingToolCallDiagnostic {
|
|
29
|
+
toolCallId?: string;
|
|
30
|
+
toolName: string;
|
|
31
|
+
args?: unknown;
|
|
32
|
+
intent?: string;
|
|
33
|
+
assistantTimestamp?: number;
|
|
34
|
+
startedAt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Session shutdown marker written during normal and fatal process teardown. */
|
|
38
|
+
export interface SessionExitData {
|
|
39
|
+
reason: string;
|
|
40
|
+
kind: "normal" | "signal" | "fatal" | "process_exit";
|
|
41
|
+
recordedAt: string;
|
|
42
|
+
pendingToolCalls?: PendingToolCallDiagnostic[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface PendingToolCallRecord extends PendingToolCallDiagnostic {
|
|
46
|
+
key: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ToolCallContent {
|
|
50
|
+
type: "toolCall";
|
|
51
|
+
id?: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
arguments?: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
57
|
+
if (typeof value !== "object") return false;
|
|
58
|
+
return value !== null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isToolCallContent(value: unknown): value is ToolCallContent {
|
|
62
|
+
if (!isObject(value)) return false;
|
|
63
|
+
return value.type === "toolCall" && (typeof value.name === "string" || typeof value.id === "string");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Character cap for each summarized argument field. */
|
|
67
|
+
const ARGUMENT_SUMMARY_MAX_CHARS = 200;
|
|
68
|
+
|
|
69
|
+
function truncateSummaryField(value: string): string {
|
|
70
|
+
return value.length > ARGUMENT_SUMMARY_MAX_CHARS ? `${value.slice(0, ARGUMENT_SUMMARY_MAX_CHARS)}…` : value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Project full tool-call arguments down to the fields the pending-tool-call
|
|
75
|
+
* resume warning actually renders (`command`/`path`), truncated. Returns
|
|
76
|
+
* `undefined` when the arguments carry neither, so callers can omit `args`
|
|
77
|
+
* entirely instead of persisting an empty object.
|
|
78
|
+
*/
|
|
79
|
+
export function summarizeToolArguments(args: unknown): ToolArgumentSummary | undefined {
|
|
80
|
+
if (!isObject(args)) return undefined;
|
|
81
|
+
const summary: ToolArgumentSummary = {};
|
|
82
|
+
if (typeof args.command === "string" && args.command.length > 0) {
|
|
83
|
+
summary.command = truncateSummaryField(args.command);
|
|
84
|
+
}
|
|
85
|
+
if (typeof args.path === "string" && args.path.length > 0) {
|
|
86
|
+
summary.path = truncateSummaryField(args.path);
|
|
87
|
+
}
|
|
88
|
+
return summary.command !== undefined || summary.path !== undefined ? summary : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readToolExecutionStart(entry: SessionEntry): ToolExecutionStartData | undefined {
|
|
92
|
+
if (entry.type !== "custom" || entry.customType !== TOOL_EXECUTION_START_CUSTOM_TYPE) return undefined;
|
|
93
|
+
const data = entry.data;
|
|
94
|
+
if (!isObject(data)) return undefined;
|
|
95
|
+
if (typeof data.toolCallId !== "string" || typeof data.toolName !== "string") return undefined;
|
|
96
|
+
const startedAt = typeof data.startedAt === "string" ? data.startedAt : entry.timestamp;
|
|
97
|
+
const result: ToolExecutionStartData = {
|
|
98
|
+
toolCallId: data.toolCallId,
|
|
99
|
+
toolName: data.toolName,
|
|
100
|
+
startedAt,
|
|
101
|
+
};
|
|
102
|
+
// Legacy sessions persisted full argument objects; project them down.
|
|
103
|
+
if ("args" in data) {
|
|
104
|
+
const args = summarizeToolArguments(data.args);
|
|
105
|
+
if (args) result.args = args;
|
|
106
|
+
}
|
|
107
|
+
if (typeof data.intent === "string") result.intent = data.intent;
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function appendAssistantToolCalls(pending: Map<string, PendingToolCallRecord>, message: AgentMessage): void {
|
|
112
|
+
if (message.role !== "assistant") return;
|
|
113
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
114
|
+
const toolCalls: PendingToolCallRecord[] = [];
|
|
115
|
+
for (let index = 0; index < content.length; index++) {
|
|
116
|
+
const part = content[index];
|
|
117
|
+
if (!isToolCallContent(part)) continue;
|
|
118
|
+
const toolName = part.name ?? "unknown";
|
|
119
|
+
const key = part.id ?? `assistant:${message.timestamp ?? "unknown"}:${index}:${toolName}`;
|
|
120
|
+
const record: PendingToolCallRecord = {
|
|
121
|
+
key,
|
|
122
|
+
toolName,
|
|
123
|
+
};
|
|
124
|
+
if (typeof message.timestamp === "number") record.assistantTimestamp = message.timestamp;
|
|
125
|
+
if (part.id) record.toolCallId = part.id;
|
|
126
|
+
if ("arguments" in part) record.args = part.arguments;
|
|
127
|
+
toolCalls.push(record);
|
|
128
|
+
}
|
|
129
|
+
pending.clear();
|
|
130
|
+
for (const toolCall of toolCalls) pending.set(toolCall.key, toolCall);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyToolExecutionStart(pending: Map<string, PendingToolCallRecord>, marker: ToolExecutionStartData): void {
|
|
134
|
+
const existing = pending.get(marker.toolCallId);
|
|
135
|
+
if (existing) {
|
|
136
|
+
existing.startedAt = marker.startedAt;
|
|
137
|
+
// The assistant message carries the full arguments; the marker only has
|
|
138
|
+
// the command/path projection. Keep the richer copy when present.
|
|
139
|
+
existing.args ??= marker.args;
|
|
140
|
+
if (marker.intent) existing.intent = marker.intent;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const record: PendingToolCallRecord = {
|
|
144
|
+
key: marker.toolCallId,
|
|
145
|
+
toolCallId: marker.toolCallId,
|
|
146
|
+
toolName: marker.toolName,
|
|
147
|
+
args: marker.args,
|
|
148
|
+
startedAt: marker.startedAt,
|
|
149
|
+
};
|
|
150
|
+
if (marker.intent) record.intent = marker.intent;
|
|
151
|
+
pending.set(marker.toolCallId, record);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function applyMessageEntry(pending: Map<string, PendingToolCallRecord>, message: AgentMessage): void {
|
|
155
|
+
if (message.role === "toolResult") {
|
|
156
|
+
const toolCallId = typeof message.toolCallId === "string" ? message.toolCallId : undefined;
|
|
157
|
+
if (toolCallId) pending.delete(toolCallId);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
appendAssistantToolCalls(pending, message);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Finds tool calls left pending at the end of a session branch. */
|
|
164
|
+
export function collectPendingToolCalls(entries: readonly SessionEntry[]): PendingToolCallDiagnostic[] {
|
|
165
|
+
const pending = new Map<string, PendingToolCallRecord>();
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
if (entry.type === "message") {
|
|
168
|
+
applyMessageEntry(pending, entry.message);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const marker = readToolExecutionStart(entry);
|
|
172
|
+
if (marker) applyToolExecutionStart(pending, marker);
|
|
173
|
+
}
|
|
174
|
+
return [...pending.values()].map(({ key: _key, ...toolCall }) => toolCall);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function appendArgumentSummary(parts: string[], args: unknown): void {
|
|
178
|
+
if (!isObject(args)) return;
|
|
179
|
+
const command = args.command;
|
|
180
|
+
if (typeof command === "string" && command.length > 0) {
|
|
181
|
+
parts.push(`command \`${command}\``);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const path = args.path;
|
|
185
|
+
if (typeof path === "string" && path.length > 0) parts.push(`path \`${path}\``);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function formatPendingToolCall(call: PendingToolCallDiagnostic): string {
|
|
189
|
+
const parts = [call.toolName];
|
|
190
|
+
if (call.toolCallId) parts.push(call.toolCallId);
|
|
191
|
+
appendArgumentSummary(parts, call.args);
|
|
192
|
+
return parts.join(" ");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Builds the resume warning shown when a prior branch ended mid-tool-call. */
|
|
196
|
+
export function describePendingToolCalls(entries: readonly SessionEntry[]): string | undefined {
|
|
197
|
+
const pending = collectPendingToolCalls(entries);
|
|
198
|
+
if (pending.length === 0) return undefined;
|
|
199
|
+
const formatted = pending.map(formatPendingToolCall).join(", ");
|
|
200
|
+
const noun = pending.length === 1 ? "tool call" : "tool calls";
|
|
201
|
+
return `Previous session ended while ${pending.length} ${noun} remained pending: ${formatted}. The prior OMP process exited before recording tool result(s).`;
|
|
202
|
+
}
|
|
@@ -307,21 +307,23 @@ export function buildSessionContext(
|
|
|
307
307
|
})();
|
|
308
308
|
const remoteReplacementHistory = providerPayload?.items;
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
// model can keep reading the archived history after every context rebuild.
|
|
310
|
+
// Re-attach any archived snapcompact frames so the model can keep
|
|
311
|
+
// reading the archived history after every context rebuild.
|
|
313
312
|
const snapcompactArchive = snapcompact.getPreservedArchive(compaction.preserveData);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
snapcompactHistoryBlocksForContext(snapcompactArchive, options),
|
|
323
|
-
),
|
|
313
|
+
const compactionSummaryMsg = createCompactionSummaryMessage(
|
|
314
|
+
compaction.summary,
|
|
315
|
+
compaction.tokensBefore,
|
|
316
|
+
compaction.timestamp,
|
|
317
|
+
compaction.shortSummary,
|
|
318
|
+
providerPayload,
|
|
319
|
+
undefined,
|
|
320
|
+
snapcompactHistoryBlocksForContext(snapcompactArchive, options),
|
|
324
321
|
);
|
|
322
|
+
// Agent context (non-transcript): summary first so the LLM sees the
|
|
323
|
+
// compacted context before recent messages.
|
|
324
|
+
if (!options?.transcript) {
|
|
325
|
+
pushMessage(compactionSummaryMsg);
|
|
326
|
+
}
|
|
325
327
|
|
|
326
328
|
// Find compaction index in path
|
|
327
329
|
const compactionIdx = path.findIndex(e => e.type === "compaction" && e.id === compaction.id);
|
|
@@ -345,6 +347,16 @@ export function buildSessionContext(
|
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
|
|
350
|
+
// Display transcript: emit the summary at the chronological compaction
|
|
351
|
+
// point (after kept messages, before post-compaction) so it stays in
|
|
352
|
+
// the live region where Ctrl+O can expand it. Reset tracking fires
|
|
353
|
+
// here so the first post-compaction assistant turn — not a kept
|
|
354
|
+
// pre-compaction one — is marked as a cache miss.
|
|
355
|
+
if (options?.transcript) handleEntryResetTracking(compaction);
|
|
356
|
+
if (options?.transcript) {
|
|
357
|
+
pushMessage(compactionSummaryMsg);
|
|
358
|
+
}
|
|
359
|
+
|
|
348
360
|
// Emit messages after compaction
|
|
349
361
|
for (let i = compactionIdx + 1; i < path.length; i++) {
|
|
350
362
|
const entry = path[i];
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as readline from "node:readline";
|
|
3
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
4
2
|
import { getBlobsDir, isEnoent, parseJsonlLenient } from "@oh-my-pi/pi-utils";
|
|
5
3
|
import { BlobStore, isBlobRef, resolveImageData, resolveImageDataUrl } from "./blob-store";
|
|
@@ -103,40 +101,75 @@ function elideSupersededCompactionEntries(entries: FileEntry[]): void {
|
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
/** Exported for testing — the ≥8MiB streaming path (works on any file size). */
|
|
105
|
+
export async function loadEntriesFromFileStream(filePath: string): Promise<{
|
|
107
106
|
entries: FileEntry[];
|
|
108
107
|
titleSlot: SessionTitleUpdate | undefined;
|
|
109
108
|
}> {
|
|
110
109
|
const entries: FileEntry[] = [];
|
|
111
110
|
let titleSlot: SessionTitleUpdate | undefined;
|
|
112
|
-
let
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
let sawFirstLine = false;
|
|
112
|
+
// Byte buffer (NOT a decoded string): multibyte UTF-8 sequences that straddle
|
|
113
|
+
// a stream-chunk boundary stay intact, and Bun.JSONL.parseChunk accepts typed
|
|
114
|
+
// arrays directly. Only the unconsumed remainder is held (≤ one record + a
|
|
115
|
+
// chunk), so the ≥8MiB memory guard is preserved (the file is never fully
|
|
116
|
+
// loaded into memory).
|
|
117
|
+
let buffer: Uint8Array = new Uint8Array();
|
|
118
|
+
const decoder = new TextDecoder();
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
const slot = parseTitleSlotLine(line);
|
|
122
|
-
if (slot) {
|
|
123
|
-
titleSlot = titleUpdateFromSlot(slot);
|
|
124
|
-
sawBodyLine = true;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
sawBodyLine = true;
|
|
120
|
+
const drain = () => {
|
|
121
|
+
while (buffer.length > 0) {
|
|
122
|
+
const { values, error, read, done } = Bun.JSONL.parseChunk(buffer);
|
|
123
|
+
if (values.length > 0) {
|
|
124
|
+
for (const value of values) entries.push(value as FileEntry);
|
|
128
125
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
if (error) {
|
|
127
|
+
// Malformed record: skip past the next newline and continue.
|
|
128
|
+
const nextNewline = buffer.indexOf(0x0a, read);
|
|
129
|
+
if (nextNewline === -1) break; // rest of the bad line not yet received
|
|
130
|
+
buffer = buffer.subarray(nextNewline + 1);
|
|
134
131
|
continue;
|
|
135
132
|
}
|
|
136
|
-
|
|
133
|
+
if (read === 0) break; // incomplete record awaiting more data
|
|
134
|
+
buffer = buffer.subarray(read);
|
|
135
|
+
if (done) {
|
|
136
|
+
buffer = new Uint8Array();
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
for await (const chunk of Bun.file(filePath).stream()) {
|
|
144
|
+
buffer = buffer.length === 0 ? chunk : Buffer.concat([buffer, chunk]);
|
|
145
|
+
// The optional fixed-width title slot is a physical first line that is
|
|
146
|
+
// NOT JSON; peel it before the parser would (correctly) reject it. The
|
|
147
|
+
// first line ends at a '\n' byte, so it is a complete UTF-8 sequence and
|
|
148
|
+
// safe to decode. A non-slot first line is a real entry and is left for
|
|
149
|
+
// the parser; a blank first line is left for the parser to skip.
|
|
150
|
+
if (!sawFirstLine) {
|
|
151
|
+
const newline = buffer.indexOf(0x0a);
|
|
152
|
+
if (newline !== -1) {
|
|
153
|
+
sawFirstLine = true;
|
|
154
|
+
const firstLine = decoder.decode(buffer.subarray(0, newline)).trim();
|
|
155
|
+
if (firstLine) {
|
|
156
|
+
const slot = parseTitleSlotLine(firstLine);
|
|
157
|
+
if (slot) {
|
|
158
|
+
titleSlot = titleUpdateFromSlot(slot);
|
|
159
|
+
buffer = buffer.subarray(newline + 1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
drain();
|
|
165
|
+
}
|
|
166
|
+
// A trailing record without a final newline: terminate it so the parser
|
|
167
|
+
// can complete it (readline yielded it; parseChunk needs the delimiter).
|
|
168
|
+
if (buffer.length > 0 && buffer[buffer.length - 1] !== 0x0a) {
|
|
169
|
+
buffer = Buffer.concat([buffer, new Uint8Array([0x0a])]);
|
|
137
170
|
}
|
|
171
|
+
drain();
|
|
138
172
|
} catch (err) {
|
|
139
|
-
input.destroy();
|
|
140
173
|
if (isEnoent(err)) return { entries: [], titleSlot: undefined };
|
|
141
174
|
throw err;
|
|
142
175
|
}
|
|
@@ -59,9 +59,15 @@ function shouldExternalizeImagePayload(
|
|
|
59
59
|
return (key === TEXT_CONTENT_KEY && isImageBlock(value)) || key === "images";
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** True for a non-empty string — marks signature/encrypted fields whose block must persist verbatim. */
|
|
63
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
64
|
+
return typeof value === "string" && value.length > 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
/**
|
|
63
68
|
* Recursively truncate large strings in an object for session persistence.
|
|
64
|
-
* - Truncates
|
|
69
|
+
* - Truncates oversized string fields (key-agnostic), except signed/encrypted
|
|
70
|
+
* blocks and signature keys, which persist verbatim
|
|
65
71
|
* - Externalizes oversized image payloads to blob refs
|
|
66
72
|
* - Updates lineCount when content is truncated
|
|
67
73
|
* - Returns original object if no changes needed (structural sharing)
|
|
@@ -76,16 +82,36 @@ function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string
|
|
|
76
82
|
if (shouldExternalizeImagePayload(obj, key)) {
|
|
77
83
|
return { ...obj, data: externalizeImageDataSync(blobStore, obj.data, obj.mimeType) };
|
|
78
84
|
}
|
|
85
|
+
// Signed content is bound to its exact bytes: a truncated `thinking`/`text`/
|
|
86
|
+
// `arguments` no longer matches its signature and a truncated
|
|
87
|
+
// `redacted_thinking` blob is undecryptable, so the provider 400s the replay.
|
|
88
|
+
// Persist signed blocks verbatim — never truncate, externalize, or descend.
|
|
89
|
+
// Unsigned blocks (e.g. an interrupted stream) have no such binding and stay
|
|
90
|
+
// truncatable for size control.
|
|
91
|
+
if (typeof obj === "object" && "type" in obj) {
|
|
92
|
+
const signed =
|
|
93
|
+
(obj.type === "thinking" && "thinkingSignature" in obj && isNonEmptyString(obj.thinkingSignature)) ||
|
|
94
|
+
(obj.type === "text" && "textSignature" in obj && isNonEmptyString(obj.textSignature)) ||
|
|
95
|
+
(obj.type === "toolCall" && "thoughtSignature" in obj && isNonEmptyString(obj.thoughtSignature));
|
|
96
|
+
const redacted = obj.type === "redactedThinking" && "data" in obj && isNonEmptyString(obj.data);
|
|
97
|
+
// OpenAI Responses reasoning items (providerPayload.items) carry
|
|
98
|
+
// `encrypted_content`, server-validated on replay — atomic like signed blocks.
|
|
99
|
+
const encryptedReasoning =
|
|
100
|
+
obj.type === "reasoning" && "encrypted_content" in obj && isNonEmptyString(obj.encrypted_content);
|
|
101
|
+
if (signed || redacted || encryptedReasoning) return obj;
|
|
102
|
+
}
|
|
79
103
|
|
|
80
104
|
if (typeof obj === "string") {
|
|
81
105
|
if (key === "image_url" && isImageDataUrl(obj)) {
|
|
82
106
|
return externalizeImageDataUrlSync(blobStore, obj);
|
|
83
107
|
}
|
|
84
108
|
if (obj.length > MAX_PERSIST_CHARS) {
|
|
85
|
-
//
|
|
86
|
-
//
|
|
109
|
+
// Defensive: signature keys normally sit on blocks the guard above returns
|
|
110
|
+
// verbatim, but if one is reached here (unknown carrier shape), preserve it —
|
|
111
|
+
// truncation produces an invalid signature the API rejects, and clearing
|
|
112
|
+
// drops reasoning context the provider needs on replay.
|
|
87
113
|
if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
|
|
88
|
-
return
|
|
114
|
+
return obj;
|
|
89
115
|
}
|
|
90
116
|
const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
|
|
91
117
|
return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { StreamFn } from "@oh-my-pi/pi-agent-core";
|
|
14
14
|
import { type SimpleStreamOptions, streamSimple } from "@oh-my-pi/pi-ai";
|
|
15
|
+
import { isAnthropicFableOrMythosModel } from "@oh-my-pi/pi-catalog/identity";
|
|
15
16
|
import { type Settings, validateProviderMaxInFlightRequests } from "../config/settings";
|
|
16
17
|
|
|
17
18
|
function timeoutSecondsToMs(value: number): number | undefined {
|
|
@@ -38,6 +39,18 @@ export function createSettingsAwareStreamFn(settings: Settings, base: StreamFn =
|
|
|
38
39
|
: undefined;
|
|
39
40
|
const streamFirstEventTimeoutMs = timeoutSecondsToMs(settings.get("providers.streamFirstEventTimeoutSeconds"));
|
|
40
41
|
const streamIdleTimeoutMs = timeoutSecondsToMs(settings.get("providers.streamIdleTimeoutSeconds"));
|
|
42
|
+
// Server-side fallback (opt-in): when the user enables it AND the
|
|
43
|
+
// resolved model is a Claude Fable/Mythos on Anthropic's messages
|
|
44
|
+
// API, inject the `fallbacks: [{ model: "claude-opus-4-8" }]` chain.
|
|
45
|
+
// The provider layer picks it up, sends the beta header, and honors
|
|
46
|
+
// the response signals. Every other model / API is untouched.
|
|
47
|
+
const serverSideFallbackEnabled =
|
|
48
|
+
settings.get("providers.anthropic.serverSideFallback") &&
|
|
49
|
+
model.api === "anthropic-messages" &&
|
|
50
|
+
model.provider === "anthropic" &&
|
|
51
|
+
isAnthropicFableOrMythosModel(model.id);
|
|
52
|
+
const fallbacks =
|
|
53
|
+
streamOptions?.fallbacks ?? (serverSideFallbackEnabled ? [{ model: "claude-opus-4-8" }] : undefined);
|
|
41
54
|
const merged: SimpleStreamOptions = {
|
|
42
55
|
...streamOptions,
|
|
43
56
|
openrouterVariant: streamOptions?.openrouterVariant ?? openrouterVariant,
|
|
@@ -53,6 +66,7 @@ export function createSettingsAwareStreamFn(settings: Settings, base: StreamFn =
|
|
|
53
66
|
checkAssistantContent: settings.get("model.loopGuard.checkAssistantContent"),
|
|
54
67
|
...streamOptions?.loopGuard,
|
|
55
68
|
},
|
|
69
|
+
...(fallbacks !== undefined ? { fallbacks } : {}),
|
|
56
70
|
};
|
|
57
71
|
return base(model, context, merged);
|
|
58
72
|
};
|
|
@@ -27,6 +27,7 @@ import { resolveMemoryBackend } from "../memory-backend";
|
|
|
27
27
|
import { describeLoopLimitRuntime } from "../modes/loop-limit";
|
|
28
28
|
import { theme } from "../modes/theme/theme";
|
|
29
29
|
import type { InteractiveModeContext } from "../modes/types";
|
|
30
|
+
import { extractLastCodeBlock, extractLastCommand } from "../modes/utils/copy-targets";
|
|
30
31
|
import type { AgentSession, FreshSessionResult } from "../session/agent-session";
|
|
31
32
|
import { COMPACT_MODES, parseCompactArgs } from "../session/compact-modes";
|
|
32
33
|
import { resolveResumableSession } from "../session/session-listing";
|
|
@@ -34,6 +35,7 @@ import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
|
34
35
|
import { expandTilde, resolveToCwd } from "../tools/path-utils";
|
|
35
36
|
import { urlHyperlinkAlways } from "../tui";
|
|
36
37
|
import { getChangelogPath, parseChangelog } from "../utils/changelog";
|
|
38
|
+
import { copyToClipboard } from "../utils/clipboard";
|
|
37
39
|
import { CollabQrCodeComponent } from "./helpers/collab-qrcode";
|
|
38
40
|
import { buildContextReportText } from "./helpers/context-report";
|
|
39
41
|
import { formatDuration } from "./helpers/format";
|
|
@@ -69,7 +71,6 @@ export interface TuiBuiltinSlashCommand extends BuiltinSlashCommand {
|
|
|
69
71
|
|
|
70
72
|
function refreshStatusLine(ctx: InteractiveModeContext): void {
|
|
71
73
|
ctx.statusLine.invalidate();
|
|
72
|
-
ctx.updateEditorTopBorder();
|
|
73
74
|
ctx.ui.requestRender();
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -841,8 +842,39 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
841
842
|
{
|
|
842
843
|
name: "copy",
|
|
843
844
|
description: "Pick text or code from the conversation to copy",
|
|
844
|
-
|
|
845
|
-
|
|
845
|
+
allowArgs: true,
|
|
846
|
+
handleTui: async (command, runtime) => {
|
|
847
|
+
const arg = command.args.trim().toLowerCase();
|
|
848
|
+
if (!arg) {
|
|
849
|
+
runtime.ctx.showCopySelector();
|
|
850
|
+
runtime.ctx.editor.setText("");
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (arg === "code") {
|
|
854
|
+
const block = extractLastCodeBlock(runtime.ctx.session.messages);
|
|
855
|
+
if (!block) {
|
|
856
|
+
runtime.ctx.showStatus("No code block to copy.");
|
|
857
|
+
runtime.ctx.editor.setText("");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
await copyToClipboard(block.code);
|
|
861
|
+
runtime.ctx.showStatus("Copied code block to clipboard");
|
|
862
|
+
runtime.ctx.editor.setText("");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (arg === "cmd" || arg === "command") {
|
|
866
|
+
const lastCommand = extractLastCommand(runtime.ctx.session.messages);
|
|
867
|
+
if (!lastCommand) {
|
|
868
|
+
runtime.ctx.showStatus("No command to copy.");
|
|
869
|
+
runtime.ctx.editor.setText("");
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
await copyToClipboard(lastCommand.code);
|
|
873
|
+
runtime.ctx.showStatus(`Copied ${lastCommand.kind === "bash" ? "bash command" : "eval code"} to clipboard`);
|
|
874
|
+
runtime.ctx.editor.setText("");
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
runtime.ctx.showStatus("Usage: /copy [code|cmd]");
|
|
846
878
|
runtime.ctx.editor.setText("");
|
|
847
879
|
},
|
|
848
880
|
},
|
package/src/system-prompt.ts
CHANGED
|
@@ -249,6 +249,20 @@ async function getCachedGpu(): Promise<string | undefined> {
|
|
|
249
249
|
await logger.time("getCachedGpu:saveGpuCache", saveGpuCache, { gpu });
|
|
250
250
|
return gpu ?? undefined;
|
|
251
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Kernel identity for the workstation block. Prefers the uname build string
|
|
254
|
+
* from `os.version()`, but Bun on macOS 15+ (Darwin 24/25) returns the literal
|
|
255
|
+
* `"unknown"` when `uv_os_uname()`'s `version` field is empty — which surfaces
|
|
256
|
+
* `Kernel: unknown` in the system prompt and makes the model misidentify the
|
|
257
|
+
* host as Windows (#4141). Fall back to `<type> <release>` (uname -s + -r) so
|
|
258
|
+
* macOS is always tagged as `Darwin <release>` and Linux keeps its build info.
|
|
259
|
+
*/
|
|
260
|
+
function getKernelIdentity(): string {
|
|
261
|
+
const version = os.version()?.trim();
|
|
262
|
+
if (version && version.toLowerCase() !== "unknown") return version;
|
|
263
|
+
return `${os.type()} ${os.release()}`.trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
252
266
|
function getEnvironmentInfo(gpu: string | undefined): Array<{ label: string; value: string }> {
|
|
253
267
|
let cpuModel: string | undefined;
|
|
254
268
|
try {
|
|
@@ -259,7 +273,7 @@ function getEnvironmentInfo(gpu: string | undefined): Array<{ label: string; val
|
|
|
259
273
|
const entries: Array<{ label: string; value: string | undefined }> = [
|
|
260
274
|
{ label: "OS", value: `${os.platform()} ${os.release()}` },
|
|
261
275
|
{ label: "Distro", value: os.type() },
|
|
262
|
-
{ label: "Kernel", value:
|
|
276
|
+
{ label: "Kernel", value: getKernelIdentity() },
|
|
263
277
|
{ label: "Arch", value: os.arch() },
|
|
264
278
|
{ label: "CPU", value: cpuModel },
|
|
265
279
|
{ label: "GPU", value: gpu },
|
package/src/task/executor.ts
CHANGED
|
@@ -80,10 +80,12 @@ const MCP_CALL_TIMEOUT_MS = 60_000;
|
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Soft per-agent request budgets (assistant requests per run). When a subagent
|
|
83
|
-
* crosses its budget it
|
|
84
|
-
* 1.5x the budget the run is aborted gracefully so partial output is
|
|
85
|
-
* The `default` key applies to agents without an explicit entry and
|
|
86
|
-
* overridden via the `task.softRequestBudget` setting (0 disables the
|
|
83
|
+
* crosses its budget it can receive an optional steering notice asking it to
|
|
84
|
+
* wrap up; at 1.5x the budget the run is aborted gracefully so partial output is
|
|
85
|
+
* salvaged. The `default` key applies to agents without an explicit entry and
|
|
86
|
+
* can be overridden via the `task.softRequestBudget` setting (0 disables the
|
|
87
|
+
* guard). The notice is off by default and controlled separately by
|
|
88
|
+
* `task.softRequestBudgetNotice`.
|
|
87
89
|
*/
|
|
88
90
|
export const SOFT_REQUEST_BUDGET: Record<string, number> = {
|
|
89
91
|
explore: 40,
|
|
@@ -91,7 +93,7 @@ export const SOFT_REQUEST_BUDGET: Record<string, number> = {
|
|
|
91
93
|
default: 90,
|
|
92
94
|
};
|
|
93
95
|
|
|
94
|
-
/**
|
|
96
|
+
/** Optional steering notice injected when a subagent crosses its soft request budget. */
|
|
95
97
|
export function buildBudgetNotice(requests: number): string {
|
|
96
98
|
return `[budget notice] You have used ${requests} requests in this run. Wrap up now: finish the current step and yield your final report.`;
|
|
97
99
|
}
|
|
@@ -295,6 +297,11 @@ export interface ExecutorOptions {
|
|
|
295
297
|
parentActiveModelPattern?: string;
|
|
296
298
|
thinkingLevel?: ThinkingLevel;
|
|
297
299
|
outputSchema?: unknown;
|
|
300
|
+
/**
|
|
301
|
+
* Caller supplied a schema that supersedes the agent's native output prompt.
|
|
302
|
+
* Eval `agent(..., schema=...)` sets this so built-in agents ignore stale yield labels.
|
|
303
|
+
*/
|
|
304
|
+
outputSchemaOverridesAgent?: boolean;
|
|
298
305
|
/** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
299
306
|
taskDepth?: number;
|
|
300
307
|
/**
|
|
@@ -793,6 +800,8 @@ interface RunMonitorArgs {
|
|
|
793
800
|
sessionFile?: string;
|
|
794
801
|
/** Soft assistant-request budget; 0 disables the guard. */
|
|
795
802
|
softRequestBudget: number;
|
|
803
|
+
/** Whether crossing the soft budget injects a wrap-up steering notice. */
|
|
804
|
+
softRequestBudgetNotice: boolean;
|
|
796
805
|
/** Wall-clock cap in ms; 0 disables the timer. */
|
|
797
806
|
maxRuntimeMs: number;
|
|
798
807
|
}
|
|
@@ -835,7 +844,18 @@ interface SubagentRunMonitor {
|
|
|
835
844
|
}
|
|
836
845
|
|
|
837
846
|
function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
838
|
-
const {
|
|
847
|
+
const {
|
|
848
|
+
index,
|
|
849
|
+
id,
|
|
850
|
+
agent,
|
|
851
|
+
task,
|
|
852
|
+
assignment,
|
|
853
|
+
signal,
|
|
854
|
+
onProgress,
|
|
855
|
+
softRequestBudget,
|
|
856
|
+
softRequestBudgetNotice,
|
|
857
|
+
maxRuntimeMs,
|
|
858
|
+
} = args;
|
|
839
859
|
const startTime = Date.now();
|
|
840
860
|
|
|
841
861
|
const progress: AgentProgress = {
|
|
@@ -1232,7 +1252,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1232
1252
|
if (softRequestBudget > 0 && !abortSent) {
|
|
1233
1253
|
if (progress.requests >= softRequestBudget * 1.5) {
|
|
1234
1254
|
requestAbort("budget");
|
|
1235
|
-
} else if (!budgetSteerSent && progress.requests >= softRequestBudget) {
|
|
1255
|
+
} else if (softRequestBudgetNotice && !budgetSteerSent && progress.requests >= softRequestBudget) {
|
|
1236
1256
|
budgetSteerSent = true;
|
|
1237
1257
|
const steerSession = activeSession;
|
|
1238
1258
|
if (steerSession) {
|
|
@@ -1868,6 +1888,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1868
1888
|
);
|
|
1869
1889
|
const softRequestBudget =
|
|
1870
1890
|
configuredDefaultBudget === 0 ? 0 : (SOFT_REQUEST_BUDGET[agent.name] ?? configuredDefaultBudget);
|
|
1891
|
+
const softRequestBudgetNotice = settings.get("task.softRequestBudgetNotice") ?? false;
|
|
1871
1892
|
const parentDepth = options.taskDepth ?? 0;
|
|
1872
1893
|
const childDepth = parentDepth + 1;
|
|
1873
1894
|
const atMaxDepth = maxRecursionDepth >= 0 && childDepth >= maxRecursionDepth;
|
|
@@ -1927,6 +1948,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1927
1948
|
detached: options.detached,
|
|
1928
1949
|
sessionFile: subtaskSessionFile,
|
|
1929
1950
|
softRequestBudget,
|
|
1951
|
+
softRequestBudgetNotice,
|
|
1930
1952
|
maxRuntimeMs,
|
|
1931
1953
|
});
|
|
1932
1954
|
const progress = monitor.progress;
|
|
@@ -2139,6 +2161,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2139
2161
|
planReferencePath: options.planReference?.path ?? "",
|
|
2140
2162
|
worktree: worktree ?? "",
|
|
2141
2163
|
outputSchema: normalizedOutputSchema,
|
|
2164
|
+
outputSchemaOverridesAgent: options.outputSchemaOverridesAgent === true,
|
|
2142
2165
|
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
2143
2166
|
ircSelfId: ircEnabled ? id : "",
|
|
2144
2167
|
});
|
|
@@ -2313,7 +2336,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2313
2336
|
// Autoload skills via sendCustomMessage (same mechanic as /skill:<name>)
|
|
2314
2337
|
if (options.autoloadSkills?.length) {
|
|
2315
2338
|
for (const skill of options.autoloadSkills) {
|
|
2316
|
-
const { message } = await buildSkillPromptMessage(skill, "");
|
|
2339
|
+
const { message } = await buildSkillPromptMessage(skill, "", "autoload");
|
|
2317
2340
|
await session.sendCustomMessage(
|
|
2318
2341
|
{
|
|
2319
2342
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|