@oh-my-pi/pi-coding-agent 16.2.2 → 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 +49 -0
- package/dist/cli.js +3624 -3568
- 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/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/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- 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 -1
- 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/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- 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/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 +242 -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 +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- 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/settings-schema.ts +77 -0
- 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/edit/hashline/diff.ts +57 -4
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- 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/manager.ts +2 -1
- 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/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +15 -6
- 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/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 +144 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- 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 +106 -0
- 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 -5
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +7 -2
- package/src/modes/utils/ui-helpers.ts +41 -23
- 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 +28 -24
- package/src/session/agent-session.ts +867 -428
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- 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 +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +16 -3
- 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/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- 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/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
package/src/tools/review.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Legacy hidden review-finding tool for agents that have not migrated to
|
|
3
|
+
* incremental `yield` sections.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Hidden by default - only enabled when explicitly listed in an agent's tools.
|
|
6
|
+
* Reviewers now finish via incremental `yield`; this tool remains for
|
|
7
|
+
* compatibility with older or custom review agents.
|
|
7
8
|
*/
|
|
8
9
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -33,6 +34,10 @@ const PRIORITY_INFO: Record<FindingPriority, FindingPriorityInfo> = {
|
|
|
33
34
|
|
|
34
35
|
export const PRIORITY_LABELS: FindingPriority[] = ["P0", "P1", "P2", "P3"];
|
|
35
36
|
|
|
37
|
+
export function isFindingPriority(value: unknown): value is FindingPriority {
|
|
38
|
+
return value === "P0" || value === "P1" || value === "P2" || value === "P3";
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
export function getPriorityInfo(priority: FindingPriority): FindingPriorityInfo {
|
|
37
42
|
return PRIORITY_INFO[priority] ?? { ord: 3, symbol: "status.info", color: "muted" };
|
|
38
43
|
}
|
|
@@ -72,8 +77,13 @@ interface ReportFindingDetails {
|
|
|
72
77
|
line_end: number;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
function
|
|
76
|
-
|
|
80
|
+
function normalizeFindingPriority(value: unknown): FindingPriority | undefined {
|
|
81
|
+
if (isFindingPriority(value)) return value;
|
|
82
|
+
if (value === 0) return "P0";
|
|
83
|
+
if (value === 1) return "P1";
|
|
84
|
+
if (value === 2) return "P2";
|
|
85
|
+
if (value === 3) return "P3";
|
|
86
|
+
return undefined;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
export function parseReportFindingDetails(value: unknown): ReportFindingDetails | undefined {
|
|
@@ -81,7 +91,7 @@ export function parseReportFindingDetails(value: unknown): ReportFindingDetails
|
|
|
81
91
|
|
|
82
92
|
const title = typeof value.title === "string" ? value.title : undefined;
|
|
83
93
|
const body = typeof value.body === "string" ? value.body : undefined;
|
|
84
|
-
const priority =
|
|
94
|
+
const priority = normalizeFindingPriority(value.priority);
|
|
85
95
|
const confidence =
|
|
86
96
|
typeof value.confidence === "number" &&
|
|
87
97
|
Number.isFinite(value.confidence) &&
|
package/src/tools/ssh.ts
CHANGED
|
@@ -369,10 +369,14 @@ export const sshToolRenderer = {
|
|
|
369
369
|
});
|
|
370
370
|
},
|
|
371
371
|
mergeCallAndResult: true,
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
|
|
372
|
+
// Pending call preview can re-anchor wholesale when the final result inserts
|
|
373
|
+
// the `Output` section, so no pending SSH rows may commit to native
|
|
374
|
+
// scrollback — even when expanded. The expanded pending shape was previously
|
|
375
|
+
// allowed to commit, which left two visible shapes in native scrollback once
|
|
376
|
+
// the result settled: a stale `⏳ SSH: [host]` header above the final frame,
|
|
377
|
+
// and the pending `╰──╯` footer reused in-place as the new `├── Output ──┤`
|
|
378
|
+
// separator with a fresh footer pushed below it.
|
|
379
|
+
provisionalPendingPreview: true,
|
|
376
380
|
// Partial-result chrome (pending icon and frame state) differs from the
|
|
377
381
|
// final SSH glyph/state, so the block stays commit-unstable while
|
|
378
382
|
// `options.isPartial` holds. Without this, a long-running SSH command's
|
package/src/tools/todo.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { formatErrorDetail, PREVIEW_LIMITS } from "./render-utils";
|
|
|
19
19
|
// =============================================================================
|
|
20
20
|
|
|
21
21
|
export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned";
|
|
22
|
+
/** Operation names accepted by the todo tool and echoed in successful result details. */
|
|
23
|
+
export type TodoOperation = "init" | "start" | "done" | "rm" | "drop" | "append" | "view";
|
|
22
24
|
|
|
23
25
|
export interface TodoItem {
|
|
24
26
|
content: string;
|
|
@@ -36,6 +38,8 @@ export interface TodoCompletionTransition {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export interface TodoToolDetails {
|
|
41
|
+
/** Operation that produced this snapshot; absent on legacy transcript entries. */
|
|
42
|
+
op?: TodoOperation;
|
|
39
43
|
phases: TodoPhase[];
|
|
40
44
|
storage: "session" | "memory";
|
|
41
45
|
completedTasks?: TodoCompletionTransition[];
|
|
@@ -134,6 +138,18 @@ function normalizeInProgressTask(phases: TodoPhase[]): void {
|
|
|
134
138
|
if (firstPendingTask) firstPendingTask.status = "in_progress";
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
/** Return the active todo task, preferring an in-progress item over the first pending item. */
|
|
142
|
+
export function nextActionableTask(phases: readonly TodoPhase[]): TodoItem | undefined {
|
|
143
|
+
let firstPending: TodoItem | undefined;
|
|
144
|
+
for (const phase of phases) {
|
|
145
|
+
for (const task of phase.tasks) {
|
|
146
|
+
if (task.status === "in_progress") return task;
|
|
147
|
+
if (!firstPending && task.status === "pending") firstPending = task;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return firstPending;
|
|
151
|
+
}
|
|
152
|
+
|
|
137
153
|
export const USER_TODO_EDIT_CUSTOM_TYPE = "user_todo_edit";
|
|
138
154
|
|
|
139
155
|
export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPhase[] {
|
|
@@ -626,7 +642,7 @@ export class TodoTool implements AgentTool<typeof todoSchema, TodoToolDetails> {
|
|
|
626
642
|
const completedTasks = readOnly || failed ? [] : getCompletionTransitions(previousPhases, updated);
|
|
627
643
|
if (!readOnly && !failed) this.session.setTodoPhases?.(updated);
|
|
628
644
|
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
629
|
-
const details: TodoToolDetails = { phases: effective, storage };
|
|
645
|
+
const details: TodoToolDetails = { op: params.op, phases: effective, storage };
|
|
630
646
|
if (completedTasks.length > 0) details.completedTasks = completedTasks;
|
|
631
647
|
|
|
632
648
|
return {
|
package/src/tools/yield.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Result submission tool for subagent output.
|
|
3
3
|
*
|
|
4
|
-
* Subagents
|
|
4
|
+
* Subagents can call this tool incrementally or terminally depending on `type`.
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { TSchema } from "@oh-my-pi/pi-ai/types";
|
|
@@ -17,9 +17,14 @@ import type { ToolSession } from ".";
|
|
|
17
17
|
import { buildOutputValidator, formatAllValidationIssues } from "./output-schema-validator";
|
|
18
18
|
|
|
19
19
|
export interface YieldDetails {
|
|
20
|
-
|
|
20
|
+
/** Successful result payload, or omitted when `useLastTurn` requests last-turn extraction. */
|
|
21
|
+
data?: unknown;
|
|
21
22
|
status: "success" | "aborted";
|
|
22
23
|
error?: string;
|
|
24
|
+
/** Optional result section/classification supplied by the yield caller. */
|
|
25
|
+
type?: string | string[];
|
|
26
|
+
/** True when the caller intentionally omitted success data so the executor uses the last assistant turn. */
|
|
27
|
+
useLastTurn?: boolean;
|
|
23
28
|
/**
|
|
24
29
|
* Set when the yield tool exhausted its in-tool schema-retry budget
|
|
25
30
|
* (MAX_SCHEMA_RETRIES) and accepted the data anyway. Surfaced so the
|
|
@@ -66,30 +71,108 @@ function hasUnresolvedRefs(schema: unknown): boolean {
|
|
|
66
71
|
return false;
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
const yieldTypeSchema: Record<string, unknown> = {
|
|
75
|
+
anyOf: [
|
|
76
|
+
{ type: "string" },
|
|
77
|
+
{
|
|
78
|
+
type: "array",
|
|
79
|
+
minItems: 1,
|
|
80
|
+
items: { type: "string" },
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
description: "Optional result type. A non-empty string array is incremental; a string is terminal.",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function isYieldType(value: unknown): value is string | string[] {
|
|
87
|
+
return (
|
|
88
|
+
typeof value === "string" ||
|
|
89
|
+
(Array.isArray(value) && value.length > 0 && value.every(item => typeof item === "string"))
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseYieldType(value: unknown): string | string[] | undefined {
|
|
94
|
+
// Strict-mode providers (OpenAI/Codex) make the optional `type` property
|
|
95
|
+
// required+nullable, so an untyped final yield arrives as `type: null`.
|
|
96
|
+
if (value === undefined || value === null) return undefined;
|
|
97
|
+
if (isYieldType(value)) return value;
|
|
98
|
+
throw new Error("type must be a string or non-empty array of strings");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Expand a plain-object `data` schema into a strict union that ALSO accepts each
|
|
103
|
+
* top-level section value (and array element) on its own. Agents that yield
|
|
104
|
+
* incrementally (`type: ["findings"]`, `type: ["confidence"]`, …) submit one
|
|
105
|
+
* section per call, so `data` is a single finding object or a lone verdict value
|
|
106
|
+
* — never the full output object. Without this, strict-mode providers constrain
|
|
107
|
+
* `data` to the whole schema and reject/—under constrained decoding—forbid the
|
|
108
|
+
* partial. Every branch is a typed sub-schema, so strict representability holds;
|
|
109
|
+
* the full-output object stays the first (terminal) branch. The assembled whole
|
|
110
|
+
* is still validated against the full schema at finalization. Non-object / loose
|
|
111
|
+
* schemas are returned unchanged.
|
|
112
|
+
*/
|
|
113
|
+
function withSectionVariants(dataSchema: Record<string, unknown>): Record<string, unknown> {
|
|
114
|
+
if (dataSchema.type !== "object") return dataSchema;
|
|
115
|
+
const props = dataSchema.properties;
|
|
116
|
+
if (props === null || typeof props !== "object") return dataSchema;
|
|
117
|
+
const propRecord = props as Record<string, unknown>;
|
|
118
|
+
const { description, ...fullWithoutDescription } = dataSchema;
|
|
119
|
+
const branches: unknown[] = [];
|
|
120
|
+
const seen = new Set<string>();
|
|
121
|
+
const add = (schema: unknown): void => {
|
|
122
|
+
if (schema === null || typeof schema !== "object") return;
|
|
123
|
+
const key = JSON.stringify(schema);
|
|
124
|
+
if (seen.has(key)) return;
|
|
125
|
+
seen.add(key);
|
|
126
|
+
branches.push(schema);
|
|
127
|
+
};
|
|
128
|
+
add(fullWithoutDescription);
|
|
129
|
+
for (const name in propRecord) {
|
|
130
|
+
const prop = propRecord[name];
|
|
131
|
+
add(prop);
|
|
132
|
+
if (prop !== null && typeof prop === "object") {
|
|
133
|
+
const propObj = prop as Record<string, unknown>;
|
|
134
|
+
if (propObj.type === "array") add(propObj.items);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (branches.length <= 1) return dataSchema;
|
|
138
|
+
return description !== undefined ? { description, anyOf: branches } : { anyOf: branches };
|
|
139
|
+
}
|
|
140
|
+
|
|
69
141
|
function wrapYieldParameters(dataSchema: Record<string, unknown>): Record<string, unknown> {
|
|
142
|
+
const successResultSchema = {
|
|
143
|
+
type: "object",
|
|
144
|
+
additionalProperties: false,
|
|
145
|
+
description: "task succeeded",
|
|
146
|
+
properties: { data: dataSchema },
|
|
147
|
+
required: ["data"],
|
|
148
|
+
};
|
|
149
|
+
const errorResultSchema = {
|
|
150
|
+
type: "object",
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
properties: {
|
|
153
|
+
error: { type: "string", description: "error message" },
|
|
154
|
+
},
|
|
155
|
+
required: ["error"],
|
|
156
|
+
};
|
|
157
|
+
const lastTurnResultSchema = {
|
|
158
|
+
type: "object",
|
|
159
|
+
additionalProperties: false,
|
|
160
|
+
description: "typed task succeeded; data omitted so the last assistant turn is used",
|
|
161
|
+
properties: {},
|
|
162
|
+
required: [],
|
|
163
|
+
};
|
|
164
|
+
// The "an empty `result` (last-turn) requires a `type`" invariant is enforced
|
|
165
|
+
// in `execute()` at runtime, NOT in this schema: a top-level combinator
|
|
166
|
+
// (`allOf`/`anyOf`/`oneOf`/...) makes OpenAI/Codex Responses reject the whole
|
|
167
|
+
// tool with `invalid_function_parameters`, so the wrapper stays a plain object.
|
|
70
168
|
return {
|
|
71
169
|
type: "object",
|
|
72
170
|
additionalProperties: false,
|
|
73
171
|
description: "submit data or error",
|
|
74
172
|
properties: {
|
|
173
|
+
type: yieldTypeSchema,
|
|
75
174
|
result: {
|
|
76
|
-
anyOf: [
|
|
77
|
-
{
|
|
78
|
-
type: "object",
|
|
79
|
-
additionalProperties: false,
|
|
80
|
-
description: "task succeeded",
|
|
81
|
-
properties: { data: dataSchema },
|
|
82
|
-
required: ["data"],
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
type: "object",
|
|
86
|
-
additionalProperties: false,
|
|
87
|
-
properties: {
|
|
88
|
-
error: { type: "string", description: "error message" },
|
|
89
|
-
},
|
|
90
|
-
required: ["error"],
|
|
91
|
-
},
|
|
92
|
-
],
|
|
175
|
+
anyOf: [successResultSchema, errorResultSchema, lastTurnResultSchema],
|
|
93
176
|
},
|
|
94
177
|
},
|
|
95
178
|
required: ["result"],
|
|
@@ -110,9 +193,9 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
110
193
|
readonly approval = "read" as const;
|
|
111
194
|
readonly label = "Submit Result";
|
|
112
195
|
readonly description =
|
|
113
|
-
"
|
|
114
|
-
'Pass `
|
|
115
|
-
|
|
196
|
+
"Submit subagent output. Omit `type` for the usual final structured result.\n\n" +
|
|
197
|
+
'Pass `type: ["section"]` to submit an incremental, non-terminal section that accumulates. Pass `type: "result"` to finalize; when `data` is omitted, your last assistant turn becomes the raw final result.\n' +
|
|
198
|
+
'Use `result: { data: <your output> }` for success, or `result: { error: "message" }` for failure. Keep the `result` wrapper.';
|
|
116
199
|
readonly parameters: TSchema;
|
|
117
200
|
strict = true;
|
|
118
201
|
readonly intent = "omit" as const;
|
|
@@ -163,7 +246,7 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
163
246
|
if (hasUnresolvedRefs(resolved)) {
|
|
164
247
|
throw new Error("schema contains unresolved $ref after dereferencing");
|
|
165
248
|
}
|
|
166
|
-
dataSchema = resolved;
|
|
249
|
+
dataSchema = withSectionVariants(resolved);
|
|
167
250
|
} else {
|
|
168
251
|
this.strict = false;
|
|
169
252
|
dataSchema = looseRecordSchema(
|
|
@@ -198,15 +281,21 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
198
281
|
if (!rawResult || typeof rawResult !== "object" || Array.isArray(rawResult)) {
|
|
199
282
|
throw new Error("result must be an object containing either data or error");
|
|
200
283
|
}
|
|
201
|
-
|
|
202
284
|
const resultRecord = rawResult as Record<string, unknown>;
|
|
203
285
|
const errorMessage = typeof resultRecord.error === "string" ? resultRecord.error : undefined;
|
|
204
286
|
const data = resultRecord.data;
|
|
287
|
+
const yieldType = parseYieldType(raw.type);
|
|
288
|
+
const useLastTurn =
|
|
289
|
+
errorMessage === undefined && data === undefined && yieldType !== undefined && !("error" in resultRecord);
|
|
290
|
+
// Incremental array-typed sections carry partial data (one finding, one
|
|
291
|
+
// field) that cannot satisfy the full output schema; the assembled result
|
|
292
|
+
// is validated as a whole at finalization (executor finalizeSubprocessOutput).
|
|
293
|
+
const isIncremental = Array.isArray(yieldType) && yieldType.length > 0;
|
|
205
294
|
|
|
206
295
|
if (errorMessage !== undefined && data !== undefined) {
|
|
207
296
|
throw new Error("result cannot contain both data and error");
|
|
208
297
|
}
|
|
209
|
-
if (errorMessage === undefined && data === undefined) {
|
|
298
|
+
if (errorMessage === undefined && data === undefined && yieldType === undefined) {
|
|
210
299
|
throw new Error(
|
|
211
300
|
'result must contain either `data` or `error`. Use `{result: {data: <your output>}}` for success or `{result: {error: "message"}}` for failure.',
|
|
212
301
|
);
|
|
@@ -214,11 +303,11 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
214
303
|
|
|
215
304
|
const status = errorMessage !== undefined ? "aborted" : "success";
|
|
216
305
|
let schemaValidationOverridden = false;
|
|
217
|
-
if (status === "success") {
|
|
218
|
-
if (data ===
|
|
306
|
+
if (status === "success" && !useLastTurn) {
|
|
307
|
+
if (data === null) {
|
|
219
308
|
throw new Error("data is required when yield indicates success");
|
|
220
309
|
}
|
|
221
|
-
if (this.#validate) {
|
|
310
|
+
if (this.#validate && !isIncremental) {
|
|
222
311
|
const parsed = this.#validate(data);
|
|
223
312
|
if (!parsed.success) {
|
|
224
313
|
this.#schemaValidationFailures++;
|
|
@@ -245,7 +334,14 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
245
334
|
: "Result submitted.";
|
|
246
335
|
return {
|
|
247
336
|
content: [{ type: "text", text: responseText }],
|
|
248
|
-
details: {
|
|
337
|
+
details: {
|
|
338
|
+
data,
|
|
339
|
+
status,
|
|
340
|
+
error: errorMessage,
|
|
341
|
+
type: yieldType,
|
|
342
|
+
useLastTurn: useLastTurn || undefined,
|
|
343
|
+
schemaOverridden: schemaValidationOverridden || undefined,
|
|
344
|
+
},
|
|
249
345
|
};
|
|
250
346
|
}
|
|
251
347
|
}
|
|
@@ -262,8 +358,21 @@ subprocessToolRegistry.register<YieldDetails>("yield", {
|
|
|
262
358
|
data: record.data,
|
|
263
359
|
status,
|
|
264
360
|
error: typeof record.error === "string" ? record.error : undefined,
|
|
361
|
+
type: isYieldType(record.type) ? record.type : undefined,
|
|
362
|
+
useLastTurn: record.useLastTurn === true ? true : undefined,
|
|
265
363
|
schemaOverridden: record.schemaOverridden === true ? true : undefined,
|
|
266
364
|
};
|
|
267
365
|
},
|
|
268
|
-
shouldTerminate: event =>
|
|
366
|
+
shouldTerminate: event => {
|
|
367
|
+
if (event.isError) return false;
|
|
368
|
+
const details = event.result?.details;
|
|
369
|
+
if (!details || typeof details !== "object") return true;
|
|
370
|
+
const record = details as Record<string, unknown>;
|
|
371
|
+
return !(
|
|
372
|
+
record.status === "success" &&
|
|
373
|
+
Array.isArray(record.type) &&
|
|
374
|
+
record.type.length > 0 &&
|
|
375
|
+
record.type.every(item => typeof item === "string")
|
|
376
|
+
);
|
|
377
|
+
},
|
|
269
378
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
|
|
1
3
|
export function canonicalizeMessage(text: string | null | undefined): string {
|
|
2
4
|
if (!text) return "";
|
|
3
5
|
const trimmed = text.trim();
|
|
@@ -79,6 +81,7 @@ export function formatThinkingForDisplay(text: string, proseOnly: boolean): stri
|
|
|
79
81
|
return formatted;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/** Whether a formatted thinking block has non-placeholder content worth rendering. */
|
|
82
85
|
export function hasDisplayableThinking(
|
|
83
86
|
text: string | null | undefined,
|
|
84
87
|
formattedText: string | null | undefined,
|
|
@@ -87,3 +90,15 @@ export function hasDisplayableThinking(
|
|
|
87
90
|
if (!formattedText) return false;
|
|
88
91
|
return formattedText.length > 0 && canonicalizeMessage(text).length > 0;
|
|
89
92
|
}
|
|
93
|
+
|
|
94
|
+
/** Whether an assistant message contains thinking content the TUI can reveal. */
|
|
95
|
+
export function messageHasDisplayableThinking(message: AgentMessage, proseOnly: boolean): boolean {
|
|
96
|
+
if (message.role !== "assistant") return false;
|
|
97
|
+
for (const content of message.content) {
|
|
98
|
+
if (content.type !== "thinking") continue;
|
|
99
|
+
if (hasDisplayableThinking(content.thinking, formatThinkingForDisplay(content.thinking, proseOnly))) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|