@oh-my-pi/pi-coding-agent 16.2.1 → 16.2.3
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 +72 -0
- package/dist/cli.js +3621 -3579
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +15 -1
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/config/model-roles.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +113 -12
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/discovery/omp-extension-roots.d.ts +3 -3
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/edit/index.d.ts +18 -0
- package/dist/types/edit/streaming.d.ts +30 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/oauth-discovery.d.ts +0 -11
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +30 -3
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +7 -2
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +84 -12
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +32 -7
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/session/settings-stream-fn.d.ts +21 -0
- package/dist/types/session/turn-persistence.d.ts +88 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +438 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +77 -4
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/auto-thinking/classifier.ts +2 -2
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/collab/guest.ts +43 -7
- package/src/config/model-registry.ts +80 -18
- package/src/config/model-resolver.ts +5 -1
- package/src/config/model-roles.ts +3 -3
- package/src/config/settings-schema.ts +107 -8
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/discovery/omp-extension-roots.ts +38 -13
- package/src/edit/hashline/diff.ts +57 -4
- package/src/edit/index.ts +21 -0
- package/src/edit/streaming.ts +170 -0
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +18 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +59 -2
- package/src/extensibility/plugins/manager.ts +76 -5
- package/src/extensibility/shared-events.ts +1 -0
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/oauth-discovery.ts +5 -29
- package/src/mcp/transports/http.ts +3 -1
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +15 -6
- package/src/mnemopi/backend.ts +2 -2
- package/src/modes/acp/acp-agent.ts +1 -1
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/assistant-message.ts +5 -5
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +145 -14
- package/src/modes/components/status-line/segments.ts +47 -22
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +23 -2
- package/src/modes/controllers/event-controller.ts +114 -11
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +65 -10
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +7 -2
- package/src/modes/utils/transcript-render-helpers.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +46 -27
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +40 -50
- package/src/session/agent-session.ts +1139 -600
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +192 -21
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +113 -19
- package/src/session/session-persistence.ts +96 -3
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/settings-stream-fn.ts +49 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/session/turn-persistence.ts +142 -0
- package/src/session/unexpected-stop-classifier.ts +2 -2
- package/src/slash-commands/builtin-registry.ts +16 -3
- package/src/slash-commands/helpers/mcp.ts +2 -1
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/models.ts +8 -6
- package/src/tiny/text.ts +37 -7
- package/src/tools/ask.ts +55 -4
- package/src/tools/image-gen.ts +2 -1
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/tts.ts +2 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/providers/tavily.ts +36 -19
package/src/task/render.ts
CHANGED
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
import { framedBlock, renderStatusLine } from "../tui";
|
|
35
35
|
import { repairDoubleEncodedJsonString } from "./repair-args";
|
|
36
36
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
37
|
-
import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails } from "./types";
|
|
37
|
+
import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails, YieldItem } from "./types";
|
|
38
|
+
import { assembleYieldResult } from "./yield-assembly";
|
|
38
39
|
|
|
39
40
|
/** Render context threaded in from `ToolExecutionComponent.#buildRenderContext`. */
|
|
40
41
|
interface TaskRenderContext {
|
|
@@ -143,6 +144,49 @@ function normalizeReportFindings(value: unknown): ReportFindingDetails[] {
|
|
|
143
144
|
return findings;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
/** Reviewer output declares `findings` as an array, so a lone finding section still assembles as a list. */
|
|
148
|
+
const REVIEWER_ARRAY_LABELS: ReadonlySet<string> = new Set(["findings"]);
|
|
149
|
+
|
|
150
|
+
function extractIncrementalReviewResult(
|
|
151
|
+
value: unknown,
|
|
152
|
+
): { summary: SubmitReviewDetails; findings: ReportFindingDetails[] } | undefined {
|
|
153
|
+
const yieldItems: YieldItem[] = normalizeYieldData(value).map(item => ({
|
|
154
|
+
data: item.data,
|
|
155
|
+
type: item.type,
|
|
156
|
+
status: item.status === "aborted" ? "aborted" : item.status === "success" ? "success" : undefined,
|
|
157
|
+
useLastTurn: item.useLastTurn,
|
|
158
|
+
}));
|
|
159
|
+
const assembled = assembleYieldResult(yieldItems, undefined, REVIEWER_ARRAY_LABELS);
|
|
160
|
+
const data = assembled?.data;
|
|
161
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return undefined;
|
|
162
|
+
const record = data as Record<string, unknown>;
|
|
163
|
+
const overallCorrectness = record.overall_correctness;
|
|
164
|
+
const explanation = record.explanation;
|
|
165
|
+
const confidence = record.confidence;
|
|
166
|
+
if (
|
|
167
|
+
(overallCorrectness !== "correct" && overallCorrectness !== "incorrect") ||
|
|
168
|
+
typeof explanation !== "string" ||
|
|
169
|
+
typeof confidence !== "number"
|
|
170
|
+
) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
summary: {
|
|
175
|
+
overall_correctness: overallCorrectness,
|
|
176
|
+
explanation,
|
|
177
|
+
confidence,
|
|
178
|
+
},
|
|
179
|
+
findings: normalizeReportFindings(record.findings),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface RenderYieldItem {
|
|
184
|
+
data?: unknown;
|
|
185
|
+
type?: string | string[];
|
|
186
|
+
status?: string;
|
|
187
|
+
useLastTurn?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
146
190
|
/**
|
|
147
191
|
* Normalize the `yield` slot of `extractedToolData` into an array of
|
|
148
192
|
* yield-detail records. The subprocess executor always populates this slot as
|
|
@@ -153,14 +197,82 @@ function normalizeReportFindings(value: unknown): ReportFindingDetails[] {
|
|
|
153
197
|
* A single object is wrapped as a 1-element array so the review verdict still
|
|
154
198
|
* renders; non-object primitives drop out.
|
|
155
199
|
*/
|
|
156
|
-
function normalizeYieldData(value: unknown):
|
|
157
|
-
|
|
158
|
-
|
|
200
|
+
function normalizeYieldData(value: unknown): RenderYieldItem[] {
|
|
201
|
+
const items = Array.isArray(value) ? value : value !== null && typeof value === "object" ? [value] : [];
|
|
202
|
+
const normalized: RenderYieldItem[] = [];
|
|
203
|
+
for (const item of items) {
|
|
204
|
+
if (item === null || typeof item !== "object") continue;
|
|
205
|
+
const record = item as Record<string, unknown>;
|
|
206
|
+
const typeValue = record.type;
|
|
207
|
+
let type: RenderYieldItem["type"];
|
|
208
|
+
if (typeof typeValue === "string") {
|
|
209
|
+
type = typeValue;
|
|
210
|
+
} else if (Array.isArray(typeValue)) {
|
|
211
|
+
const labels: string[] = [];
|
|
212
|
+
let allLabels = true;
|
|
213
|
+
for (const label of typeValue) {
|
|
214
|
+
if (typeof label !== "string") {
|
|
215
|
+
allLabels = false;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
labels.push(label);
|
|
219
|
+
}
|
|
220
|
+
if (allLabels) type = labels;
|
|
221
|
+
}
|
|
222
|
+
normalized.push({
|
|
223
|
+
data: record.data,
|
|
224
|
+
type,
|
|
225
|
+
status: typeof record.status === "string" ? record.status : undefined,
|
|
226
|
+
useLastTurn: record.useLastTurn === true ? true : undefined,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return normalized;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getRenderYieldLabels(type: RenderYieldItem["type"]): string[] {
|
|
233
|
+
if (typeof type === "string") {
|
|
234
|
+
const label = type.trim();
|
|
235
|
+
return label ? [label] : [];
|
|
236
|
+
}
|
|
237
|
+
if (!Array.isArray(type)) return [];
|
|
238
|
+
const labels: string[] = [];
|
|
239
|
+
for (const value of type) {
|
|
240
|
+
const label = value.trim();
|
|
241
|
+
if (label) labels.push(label);
|
|
242
|
+
}
|
|
243
|
+
return labels;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function formatYieldPreview(item: RenderYieldItem): string {
|
|
247
|
+
if (item.useLastTurn === true && item.data === undefined) return "last assistant turn";
|
|
248
|
+
if (item.data === undefined) return "last assistant turn";
|
|
249
|
+
if (typeof item.data === "string") return previewLine(replaceTabs(item.data), 70);
|
|
250
|
+
try {
|
|
251
|
+
return previewLine(replaceTabs(JSON.stringify(item.data) ?? "null"), 70);
|
|
252
|
+
} catch {
|
|
253
|
+
return previewLine(replaceTabs(String(item.data)), 70);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function renderTypedYieldSections(value: unknown, continuePrefix: string, expanded: boolean, theme: Theme): string[] {
|
|
258
|
+
const typedItems: Array<{ item: RenderYieldItem; labels: string[] }> = [];
|
|
259
|
+
for (const item of normalizeYieldData(value)) {
|
|
260
|
+
const labels = getRenderYieldLabels(item.type);
|
|
261
|
+
if (labels.length === 0) continue;
|
|
262
|
+
typedItems.push({ item, labels });
|
|
159
263
|
}
|
|
160
|
-
|
|
161
|
-
|
|
264
|
+
const displayCount = expanded ? typedItems.length : 3;
|
|
265
|
+
const lines: string[] = [];
|
|
266
|
+
for (const { item, labels } of typedItems.slice(-displayCount)) {
|
|
267
|
+
const terminal = !Array.isArray(item.type);
|
|
268
|
+
const prefix = terminal ? "yield" : "yield+";
|
|
269
|
+
const label = `${prefix}[${labels.join(", ")}]`;
|
|
270
|
+
lines.push(`${continuePrefix}${theme.fg("dim", label)}: ${theme.fg("dim", formatYieldPreview(item))}`);
|
|
271
|
+
}
|
|
272
|
+
if (typedItems.length > displayCount) {
|
|
273
|
+
lines.push(`${continuePrefix}${theme.fg("dim", formatMoreItems(typedItems.length - displayCount, "yield"))}`);
|
|
162
274
|
}
|
|
163
|
-
return
|
|
275
|
+
return lines;
|
|
164
276
|
}
|
|
165
277
|
|
|
166
278
|
function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
@@ -808,10 +920,24 @@ function renderAgentProgress(
|
|
|
808
920
|
|
|
809
921
|
// Render extracted tool data inline (e.g., review findings)
|
|
810
922
|
if (progress.extractedToolData) {
|
|
811
|
-
// For completed tasks,
|
|
923
|
+
// For completed tasks, prefer review verdicts assembled from incremental
|
|
924
|
+
// yield sections. Fall back to the legacy `report_finding` side-channel.
|
|
812
925
|
if (progress.status === "completed") {
|
|
813
926
|
const completeData = normalizeYieldData(progress.extractedToolData.yield);
|
|
927
|
+
const incrementalReview = extractIncrementalReviewResult(progress.extractedToolData.yield);
|
|
814
928
|
const reportFindingData = normalizeReportFindings(progress.extractedToolData.report_finding);
|
|
929
|
+
if (incrementalReview) {
|
|
930
|
+
lines.push(
|
|
931
|
+
...renderReviewResult(
|
|
932
|
+
incrementalReview.summary,
|
|
933
|
+
incrementalReview.findings,
|
|
934
|
+
continuePrefix,
|
|
935
|
+
expanded,
|
|
936
|
+
theme,
|
|
937
|
+
),
|
|
938
|
+
);
|
|
939
|
+
return lines; // Review result handles its own rendering
|
|
940
|
+
}
|
|
815
941
|
const reviewData = completeData
|
|
816
942
|
.map(c => c.data as SubmitReviewDetails)
|
|
817
943
|
.filter(d => d && typeof d === "object" && "overall_correctness" in d);
|
|
@@ -825,6 +951,11 @@ function renderAgentProgress(
|
|
|
825
951
|
|
|
826
952
|
for (const toolName in progress.extractedToolData) {
|
|
827
953
|
const dataArray = progress.extractedToolData[toolName];
|
|
954
|
+
if (toolName === "yield") {
|
|
955
|
+
lines.push(...renderTypedYieldSections(dataArray, continuePrefix, expanded, theme));
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
|
|
828
959
|
// Handle report_finding with tree formatting
|
|
829
960
|
if (toolName === "report_finding") {
|
|
830
961
|
const findings = normalizeReportFindings(dataArray);
|
|
@@ -1068,23 +1199,30 @@ function renderAgentResult(
|
|
|
1068
1199
|
`${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", previewLine(result.abortReason, 80))}`,
|
|
1069
1200
|
);
|
|
1070
1201
|
}
|
|
1071
|
-
// Check for review result
|
|
1072
|
-
//
|
|
1202
|
+
// Check for review result, preferring incremental yield sections and falling
|
|
1203
|
+
// back to the legacy `report_finding` side-channel.
|
|
1073
1204
|
// `normalizeYieldData` guards against a stray non-array `yield` slot —
|
|
1074
1205
|
// optional chaining on `.map` only short-circuits on null/undefined and
|
|
1075
1206
|
// would otherwise crash the renderer with `TypeError: completeData?.map
|
|
1076
1207
|
// is not a function` when the slot is a plain object (see issue #1987).
|
|
1077
1208
|
const completeData = normalizeYieldData(result.extractedToolData?.yield);
|
|
1078
1209
|
const reportFindingData = normalizeReportFindings(result.extractedToolData?.report_finding);
|
|
1210
|
+
const incrementalReview = extractIncrementalReviewResult(result.extractedToolData?.yield);
|
|
1211
|
+
|
|
1212
|
+
if (incrementalReview) {
|
|
1213
|
+
lines.push(
|
|
1214
|
+
...renderReviewResult(incrementalReview.summary, incrementalReview.findings, continuePrefix, expanded, theme),
|
|
1215
|
+
);
|
|
1216
|
+
return lines;
|
|
1217
|
+
}
|
|
1079
1218
|
|
|
1080
|
-
// Extract review verdict from yield
|
|
1219
|
+
// Extract review verdict from legacy yield summary objects if present.
|
|
1081
1220
|
const reviewData = completeData
|
|
1082
1221
|
.map(c => c.data as SubmitReviewDetails)
|
|
1083
1222
|
.filter(d => d && typeof d === "object" && "overall_correctness" in d);
|
|
1084
1223
|
const submitReviewData = reviewData.length > 0 ? reviewData : undefined;
|
|
1085
1224
|
|
|
1086
1225
|
if (submitReviewData) {
|
|
1087
|
-
// Use combined review renderer
|
|
1088
1226
|
const summary = submitReviewData[submitReviewData.length - 1];
|
|
1089
1227
|
const findings = reportFindingData;
|
|
1090
1228
|
lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
|
|
@@ -1105,9 +1243,18 @@ function renderAgentResult(
|
|
|
1105
1243
|
let hasCustomRendering = false;
|
|
1106
1244
|
const deferredToolLines: string[] = [];
|
|
1107
1245
|
if (result.extractedToolData) {
|
|
1108
|
-
for (const
|
|
1246
|
+
for (const toolName in result.extractedToolData) {
|
|
1247
|
+
const dataArray = result.extractedToolData[toolName];
|
|
1248
|
+
if (toolName === "yield") {
|
|
1249
|
+
const yieldLines = renderTypedYieldSections(dataArray, continuePrefix, expanded, theme);
|
|
1250
|
+
if (yieldLines.length > 0) {
|
|
1251
|
+
hasCustomRendering = true;
|
|
1252
|
+
lines.push(...yieldLines);
|
|
1253
|
+
}
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1109
1256
|
// Skip review tools - handled above
|
|
1110
|
-
if (toolName === "
|
|
1257
|
+
if (toolName === "report_finding") continue;
|
|
1111
1258
|
|
|
1112
1259
|
const isTaskTool = toolName === "task";
|
|
1113
1260
|
if (isTaskTool && (dataArray as unknown[]).length > 0) {
|
|
@@ -1541,6 +1688,7 @@ function renderNestedTaskTree(
|
|
|
1541
1688
|
return lines;
|
|
1542
1689
|
}
|
|
1543
1690
|
|
|
1691
|
+
// Register task tool subprocess handler
|
|
1544
1692
|
subprocessToolRegistry.register<TaskToolDetails>("task", {
|
|
1545
1693
|
extractData: event => {
|
|
1546
1694
|
const details = event.result?.details;
|
|
@@ -1551,9 +1699,3 @@ subprocessToolRegistry.register<TaskToolDetails>("task", {
|
|
|
1551
1699
|
return new Text(lines.join("\n"), 0, 0);
|
|
1552
1700
|
},
|
|
1553
1701
|
});
|
|
1554
|
-
|
|
1555
|
-
export const taskToolRenderer = {
|
|
1556
|
-
renderCall,
|
|
1557
|
-
renderResult,
|
|
1558
|
-
mergeCallAndResult: true,
|
|
1559
|
-
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task tool renderer export.
|
|
3
|
+
*
|
|
4
|
+
* Separated from render.ts to avoid circular dependency issues with
|
|
5
|
+
* tools/renderers.ts. This module has no side effects and can be safely
|
|
6
|
+
* imported without triggering the subprocessToolRegistry registration.
|
|
7
|
+
*/
|
|
8
|
+
import { renderCall, renderResult } from "./render";
|
|
9
|
+
|
|
10
|
+
export const taskToolRenderer = {
|
|
11
|
+
renderCall,
|
|
12
|
+
renderResult,
|
|
13
|
+
mergeCallAndResult: true,
|
|
14
|
+
} as const;
|
package/src/task/types.ts
CHANGED
|
@@ -257,6 +257,23 @@ export interface AgentDefinition {
|
|
|
257
257
|
filePath?: string;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/** Details extracted from a subagent `yield` tool call for final-result assembly and task rendering. */
|
|
261
|
+
export interface YieldItem {
|
|
262
|
+
data?: unknown;
|
|
263
|
+
status?: "success" | "aborted";
|
|
264
|
+
error?: string;
|
|
265
|
+
/** A string label is terminal; a non-empty array of labels is incremental. */
|
|
266
|
+
type?: string | string[];
|
|
267
|
+
/** Resolve this yield's payload from the latest durable assistant text instead of `data`. */
|
|
268
|
+
useLastTurn?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Set by the in-tool yield validator when it exhausted its retry budget and
|
|
271
|
+
* accepted schema-invalid data anyway. The executor preserves that override
|
|
272
|
+
* during post-mortem validation.
|
|
273
|
+
*/
|
|
274
|
+
schemaOverridden?: boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
260
277
|
/** Progress tracking for a single agent */
|
|
261
278
|
export interface AgentProgress {
|
|
262
279
|
index: number;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure assembly of subagent `yield` calls into the final payload consumed by
|
|
3
|
+
* output-schema validation.
|
|
4
|
+
*
|
|
5
|
+
* Lives apart from the subagent runtime in `executor.ts` so the rendering path
|
|
6
|
+
* (`render.ts` → `extractIncrementalReviewResult`) can assemble incremental
|
|
7
|
+
* yields without importing that runtime's dependency graph (`sdk`,
|
|
8
|
+
* `session-manager`, the TUI tool renderers). It has no side effects and
|
|
9
|
+
* depends only on the yield type and the output-schema validator.
|
|
10
|
+
*/
|
|
11
|
+
import { buildOutputValidator } from "../tools/output-schema-validator";
|
|
12
|
+
import type { YieldItem } from "./types";
|
|
13
|
+
|
|
14
|
+
/** Outcome of folding a run's yield calls into one payload, with provenance flags. */
|
|
15
|
+
interface AssembledYieldResult {
|
|
16
|
+
data: unknown;
|
|
17
|
+
schemaOverridden: boolean;
|
|
18
|
+
rawText: boolean;
|
|
19
|
+
missingData: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isIncrementalYieldType(type: YieldItem["type"]): type is string[] {
|
|
23
|
+
return Array.isArray(type) && type.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getYieldLabels(type: YieldItem["type"]): string[] {
|
|
27
|
+
if (typeof type === "string") {
|
|
28
|
+
const label = type.trim();
|
|
29
|
+
return label ? [label] : [];
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(type)) return [];
|
|
32
|
+
const labels: string[] = [];
|
|
33
|
+
for (const value of type) {
|
|
34
|
+
if (typeof value !== "string") continue;
|
|
35
|
+
const label = value.trim();
|
|
36
|
+
if (label) labels.push(label);
|
|
37
|
+
}
|
|
38
|
+
return labels;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveYieldPayload(
|
|
42
|
+
item: YieldItem,
|
|
43
|
+
lastAssistantText: string | undefined,
|
|
44
|
+
labels: string[],
|
|
45
|
+
): { value: unknown; fromLastAssistantText: boolean; missingData: boolean } {
|
|
46
|
+
const hasData = item.data !== undefined;
|
|
47
|
+
const shouldUseLastTurn = item.useLastTurn === true || (labels.length > 0 && !hasData);
|
|
48
|
+
if (shouldUseLastTurn && lastAssistantText !== undefined) {
|
|
49
|
+
return {
|
|
50
|
+
value: lastAssistantText,
|
|
51
|
+
fromLastAssistantText: true,
|
|
52
|
+
missingData: lastAssistantText.length === 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
value: item.data,
|
|
57
|
+
fromLastAssistantText: false,
|
|
58
|
+
missingData: item.data === undefined || item.data === null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function appendYieldSection(
|
|
63
|
+
sections: Record<string, unknown>,
|
|
64
|
+
sectionCounts: Map<string, number>,
|
|
65
|
+
label: string,
|
|
66
|
+
value: unknown,
|
|
67
|
+
forceArray: boolean,
|
|
68
|
+
): void {
|
|
69
|
+
const count = sectionCounts.get(label) ?? 0;
|
|
70
|
+
const existing = sections[label];
|
|
71
|
+
if (count === 0) {
|
|
72
|
+
sections[label] = forceArray ? [value] : value;
|
|
73
|
+
} else if (Array.isArray(existing)) {
|
|
74
|
+
existing.push(value);
|
|
75
|
+
} else {
|
|
76
|
+
sections[label] = [existing, value];
|
|
77
|
+
}
|
|
78
|
+
sectionCounts.set(label, count + 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** True when `value` is a JSON-schema node whose instances are arrays. */
|
|
82
|
+
function isArrayTypedSchema(value: unknown): boolean {
|
|
83
|
+
if (value === null || typeof value !== "object") return false;
|
|
84
|
+
const record = value as Record<string, unknown>;
|
|
85
|
+
if (record.type === "array") return true;
|
|
86
|
+
if (Array.isArray(record.type) && record.type.includes("array")) return true;
|
|
87
|
+
for (const key of ["anyOf", "oneOf", "allOf"] as const) {
|
|
88
|
+
const variants = record[key];
|
|
89
|
+
if (Array.isArray(variants) && variants.some(isArrayTypedSchema)) return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Top-level output-schema property names declared as arrays (JTD `elements` →
|
|
96
|
+
* JSON `type: "array"`). An incremental yield section for such a label
|
|
97
|
+
* accumulates into a list even when the agent emits exactly one — otherwise a
|
|
98
|
+
* single `type: ["findings"]` yield would assemble as a bare object and fail
|
|
99
|
+
* array-typed schema validation.
|
|
100
|
+
*/
|
|
101
|
+
export function arrayValuedLabels(outputSchema: unknown): ReadonlySet<string> {
|
|
102
|
+
const labels = new Set<string>();
|
|
103
|
+
// Use the JTD-converted JSON Schema (matches what validation runs against):
|
|
104
|
+
// JTD `optionalProperties.findings.elements` becomes `properties.findings`
|
|
105
|
+
// with `type: "array"`, which raw `normalizeSchema` would not expose.
|
|
106
|
+
const { jsonSchema } = buildOutputValidator(outputSchema);
|
|
107
|
+
if (jsonSchema === undefined) return labels;
|
|
108
|
+
const properties = jsonSchema.properties;
|
|
109
|
+
if (properties === null || typeof properties !== "object") return labels;
|
|
110
|
+
const propRecord = properties as Record<string, unknown>;
|
|
111
|
+
for (const key in propRecord) {
|
|
112
|
+
if (isArrayTypedSchema(propRecord[key])) labels.add(key);
|
|
113
|
+
}
|
|
114
|
+
return labels;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Assemble typed yield calls into the final payload consumed by schema validation.
|
|
119
|
+
*
|
|
120
|
+
* A non-empty array `type` contributes an incremental section and never decides
|
|
121
|
+
* termination by itself. A string `type` with omitted `data` makes the last
|
|
122
|
+
* assistant turn the raw terminal result. Other string-typed yields contribute
|
|
123
|
+
* the terminal labelled section. Untyped terminal yields keep the historical
|
|
124
|
+
* "last yield wins" behavior unless no terminal yield exists, in which case
|
|
125
|
+
* accumulated typed sections finalize on idle.
|
|
126
|
+
*/
|
|
127
|
+
export function assembleYieldResult(
|
|
128
|
+
yieldItems: YieldItem[],
|
|
129
|
+
lastAssistantText?: string,
|
|
130
|
+
arrayLabels?: ReadonlySet<string>,
|
|
131
|
+
): AssembledYieldResult | undefined {
|
|
132
|
+
if (yieldItems.length === 0) return undefined;
|
|
133
|
+
let terminalItem: YieldItem | undefined;
|
|
134
|
+
for (let index = yieldItems.length - 1; index >= 0; index--) {
|
|
135
|
+
const item = yieldItems[index];
|
|
136
|
+
if (!item) continue;
|
|
137
|
+
if (!isIncrementalYieldType(item.type)) {
|
|
138
|
+
terminalItem = item;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let hasTypedSections = false;
|
|
143
|
+
for (const item of yieldItems) {
|
|
144
|
+
if (getYieldLabels(item.type).length > 0) {
|
|
145
|
+
hasTypedSections = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (terminalItem && typeof terminalItem.type === "string" && terminalItem.data === undefined) {
|
|
150
|
+
const resolved = resolveYieldPayload(terminalItem, lastAssistantText, getYieldLabels(terminalItem.type));
|
|
151
|
+
return {
|
|
152
|
+
data: resolved.value,
|
|
153
|
+
schemaOverridden: terminalItem.schemaOverridden === true,
|
|
154
|
+
rawText: resolved.fromLastAssistantText && typeof resolved.value === "string",
|
|
155
|
+
missingData: resolved.missingData,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (!hasTypedSections && terminalItem) {
|
|
159
|
+
const resolved = resolveYieldPayload(terminalItem, lastAssistantText, []);
|
|
160
|
+
return {
|
|
161
|
+
data: resolved.value,
|
|
162
|
+
schemaOverridden: terminalItem.schemaOverridden === true,
|
|
163
|
+
rawText: resolved.fromLastAssistantText && typeof resolved.value === "string",
|
|
164
|
+
missingData: resolved.missingData,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const sections: Record<string, unknown> = {};
|
|
169
|
+
const sectionCounts = new Map<string, number>();
|
|
170
|
+
let schemaOverridden = false;
|
|
171
|
+
let missingData = false;
|
|
172
|
+
let hasSections = false;
|
|
173
|
+
|
|
174
|
+
for (const item of yieldItems) {
|
|
175
|
+
if (item.status === "aborted") continue;
|
|
176
|
+
schemaOverridden ||= item.schemaOverridden === true;
|
|
177
|
+
const labels = getYieldLabels(item.type);
|
|
178
|
+
if (labels.length === 0) continue;
|
|
179
|
+
const resolved = resolveYieldPayload(item, lastAssistantText, labels);
|
|
180
|
+
missingData ||= resolved.missingData;
|
|
181
|
+
const incremental = isIncrementalYieldType(item.type);
|
|
182
|
+
for (const label of labels) {
|
|
183
|
+
appendYieldSection(
|
|
184
|
+
sections,
|
|
185
|
+
sectionCounts,
|
|
186
|
+
label,
|
|
187
|
+
resolved.value,
|
|
188
|
+
incremental && (arrayLabels?.has(label) ?? false),
|
|
189
|
+
);
|
|
190
|
+
hasSections = true;
|
|
191
|
+
}
|
|
192
|
+
if (!isIncrementalYieldType(item.type)) break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (hasSections) {
|
|
196
|
+
return { data: sections, schemaOverridden, rawText: false, missingData };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!terminalItem) return undefined;
|
|
200
|
+
const resolved = resolveYieldPayload(terminalItem, lastAssistantText, []);
|
|
201
|
+
return {
|
|
202
|
+
data: resolved.value,
|
|
203
|
+
schemaOverridden: terminalItem.schemaOverridden === true,
|
|
204
|
+
rawText: resolved.fromLastAssistantText && typeof resolved.value === "string",
|
|
205
|
+
missingData: resolved.missingData,
|
|
206
|
+
};
|
|
207
|
+
}
|
package/src/tiny/models.ts
CHANGED
|
@@ -87,8 +87,9 @@ void TINY_TITLE_MODEL_VALUES_MATCH_REGISTRY;
|
|
|
87
87
|
export const TINY_TITLE_MODEL_OPTIONS = [
|
|
88
88
|
{
|
|
89
89
|
value: ONLINE_TINY_TITLE_MODEL_KEY,
|
|
90
|
-
label: "Online (pi/smol)",
|
|
91
|
-
description:
|
|
90
|
+
label: "Online (TINY role, else pi/smol)",
|
|
91
|
+
description:
|
|
92
|
+
"Online title generation: the TINY model role (set one in /models) when assigned, otherwise the online fallback (commit role, then pi/smol). No local download or on-device inference.",
|
|
92
93
|
},
|
|
93
94
|
...TINY_TITLE_LOCAL_MODELS.map(model => ({
|
|
94
95
|
value: model.key,
|
|
@@ -183,9 +184,9 @@ void TINY_MEMORY_MODEL_VALUES_MATCH_REGISTRY;
|
|
|
183
184
|
export const TINY_MEMORY_MODEL_OPTIONS = [
|
|
184
185
|
{
|
|
185
186
|
value: ONLINE_MEMORY_MODEL_KEY,
|
|
186
|
-
label: "Online (smol
|
|
187
|
+
label: "Online (TINY role, else smol)",
|
|
187
188
|
description:
|
|
188
|
-
"Use the
|
|
189
|
+
"Use the online model: the TINY role from /models when set, otherwise pi/smol. No local model download or on-device inference.",
|
|
189
190
|
},
|
|
190
191
|
...TINY_MEMORY_LOCAL_MODELS.map(model => ({
|
|
191
192
|
value: model.key,
|
|
@@ -245,8 +246,9 @@ export type AutoThinkingModelKey = TinyMemoryModelKey;
|
|
|
245
246
|
export const AUTO_THINKING_MODEL_OPTIONS = [
|
|
246
247
|
{
|
|
247
248
|
value: ONLINE_AUTO_THINKING_MODEL_KEY,
|
|
248
|
-
label: "Online (smol)",
|
|
249
|
-
description:
|
|
249
|
+
label: "Online (TINY role, else smol)",
|
|
250
|
+
description:
|
|
251
|
+
"Classify prompt difficulty online with the TINY role model (set one in /models) or pi/smol; no local download or on-device inference.",
|
|
250
252
|
},
|
|
251
253
|
...TINY_MEMORY_LOCAL_MODELS.map(model => ({
|
|
252
254
|
value: model.key,
|
package/src/tiny/text.ts
CHANGED
|
@@ -43,6 +43,29 @@ export function formatTitleUserMessage(message: string): string {
|
|
|
43
43
|
return `<user-message>\n${prepareTitleInput(message)}\n</user-message>`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** Single recent conversation turn supplied to title refresh after replanning. */
|
|
47
|
+
export interface TitleConversationTurn {
|
|
48
|
+
role: "user" | "assistant";
|
|
49
|
+
text?: string;
|
|
50
|
+
thinking?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Format recent user/assistant context for title generation after a todo replan. */
|
|
54
|
+
export function formatTitleConversationContext(turns: readonly TitleConversationTurn[]): string {
|
|
55
|
+
const formattedTurns: string[] = [];
|
|
56
|
+
for (const turn of turns) {
|
|
57
|
+
const sections: string[] = [];
|
|
58
|
+
const text = turn.text?.trim();
|
|
59
|
+
if (text) sections.push(text);
|
|
60
|
+
const thinking = turn.role === "assistant" ? turn.thinking?.trim() : undefined;
|
|
61
|
+
if (thinking) sections.push(`<thinking>\n${thinking}\n</thinking>`);
|
|
62
|
+
if (sections.length === 0) continue;
|
|
63
|
+
formattedTurns.push(`<${turn.role}>\n${sections.join("\n\n")}\n</${turn.role}>`);
|
|
64
|
+
}
|
|
65
|
+
if (formattedTurns.length === 0) return "";
|
|
66
|
+
return prepareTitleInput(`<conversation>\n${formattedTurns.join("\n\n")}\n</conversation>`);
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
/**
|
|
47
70
|
* Greeting / acknowledgement / filler tokens. A first user message composed
|
|
48
71
|
* entirely of these (or of bare numbers / punctuation / emoji) carries no
|
|
@@ -173,14 +196,15 @@ export function normalizeGeneratedTitle(value: string | null | undefined, source
|
|
|
173
196
|
* (`TinyVMM` → `tinyvmm`). The user's message is the source of truth, so per
|
|
174
197
|
* title token:
|
|
175
198
|
* 1. typed verbatim in the message → keep it (the user established the casing);
|
|
176
|
-
* 2. else the message has the same word with *distinctive* casing
|
|
177
|
-
* (`TinyVMM`, `iOS`, `
|
|
199
|
+
* 2. else the message has the same word with *distinctive* mixed casing
|
|
200
|
+
* (`TinyVMM`, `iOS`, `IDs`) → adopt the user's casing (restoration);
|
|
178
201
|
* 3. else it's a camelCase artifact (lowercase word + stray interior capital,
|
|
179
202
|
* `dAemon`) the user never wrote → lowercase it;
|
|
180
203
|
* 4. else leave it — preserves model-cased proper nouns like `GitHub`, `OAuth`.
|
|
181
204
|
*
|
|
182
|
-
* Restoration is limited to distinctively cased source tokens
|
|
183
|
-
* merely *starts* with `For` can't force a mid-title `for` to `For
|
|
205
|
+
* Restoration is limited to distinctively *mixed*-cased source tokens: a sentence
|
|
206
|
+
* that merely *starts* with `For` can't force a mid-title `for` to `For`, and
|
|
207
|
+
* emphatic all-caps (`ALL ERROR HANDLING`) is never re-shouted over sentence case.
|
|
184
208
|
*/
|
|
185
209
|
function reconcileTitleCasing(title: string, sourceText: string): string {
|
|
186
210
|
const verbatim = new Set<string>();
|
|
@@ -200,10 +224,16 @@ function reconcileTitleCasing(title: string, sourceText: string): string {
|
|
|
200
224
|
});
|
|
201
225
|
}
|
|
202
226
|
|
|
203
|
-
/**
|
|
204
|
-
*
|
|
227
|
+
/** Mixed-case identifier the user cased deliberately (`TinyVMM`, `iOS`, `IDs`):
|
|
228
|
+
* an interior/repeated capital plus at least one lowercase letter. Only these
|
|
229
|
+
* are restored when the model flattens them.
|
|
230
|
+
*
|
|
231
|
+
* Pure all-caps is intentionally excluded. The model preserves its own acronyms
|
|
232
|
+
* verbatim regardless, so restoring all-caps from the source would only ever
|
|
233
|
+
* re-shout emphatic input (`ALL ERROR HANDLING`, `FIX THE BUG`) over the
|
|
234
|
+
* sentence case the prompt asks for. */
|
|
205
235
|
function isDistinctiveCasing(token: string): boolean {
|
|
206
|
-
return /\p{L}\p{Lu}/u.test(token);
|
|
236
|
+
return /\p{Ll}/u.test(token) && /\p{L}\p{Lu}/u.test(token);
|
|
207
237
|
}
|
|
208
238
|
|
|
209
239
|
/** A lowercase word carrying a stray interior capital (`dAemon`, `cReate`): the
|