@oh-my-pi/pi-coding-agent 3.24.0 → 3.30.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 +34 -0
- package/package.json +4 -4
- package/src/core/custom-commands/bundled/wt/index.ts +3 -0
- package/src/core/sdk.ts +7 -0
- package/src/core/tools/complete.ts +129 -0
- package/src/core/tools/index.test.ts +9 -1
- package/src/core/tools/index.ts +18 -5
- package/src/core/tools/jtd-to-json-schema.ts +252 -0
- package/src/core/tools/output.ts +125 -14
- package/src/core/tools/read.ts +4 -4
- package/src/core/tools/task/artifacts.ts +6 -9
- package/src/core/tools/task/executor.ts +189 -24
- package/src/core/tools/task/index.ts +23 -18
- package/src/core/tools/task/name-generator.ts +1577 -0
- package/src/core/tools/task/render.ts +137 -8
- package/src/core/tools/task/types.ts +26 -5
- package/src/core/tools/task/worker-protocol.ts +1 -0
- package/src/core/tools/task/worker.ts +136 -14
- package/src/core/tools/web-fetch-handlers/academic.test.ts +239 -0
- package/src/core/tools/web-fetch-handlers/artifacthub.ts +210 -0
- package/src/core/tools/web-fetch-handlers/arxiv.ts +84 -0
- package/src/core/tools/web-fetch-handlers/aur.ts +171 -0
- package/src/core/tools/web-fetch-handlers/biorxiv.ts +136 -0
- package/src/core/tools/web-fetch-handlers/bluesky.ts +277 -0
- package/src/core/tools/web-fetch-handlers/brew.ts +173 -0
- package/src/core/tools/web-fetch-handlers/business.test.ts +82 -0
- package/src/core/tools/web-fetch-handlers/cheatsh.ts +73 -0
- package/src/core/tools/web-fetch-handlers/chocolatey.ts +153 -0
- package/src/core/tools/web-fetch-handlers/coingecko.ts +179 -0
- package/src/core/tools/web-fetch-handlers/crates-io.ts +123 -0
- package/src/core/tools/web-fetch-handlers/dev-platforms.test.ts +254 -0
- package/src/core/tools/web-fetch-handlers/devto.ts +173 -0
- package/src/core/tools/web-fetch-handlers/discogs.ts +303 -0
- package/src/core/tools/web-fetch-handlers/dockerhub.ts +156 -0
- package/src/core/tools/web-fetch-handlers/documentation.test.ts +85 -0
- package/src/core/tools/web-fetch-handlers/finance-media.test.ts +144 -0
- package/src/core/tools/web-fetch-handlers/git-hosting.test.ts +272 -0
- package/src/core/tools/web-fetch-handlers/github-gist.ts +64 -0
- package/src/core/tools/web-fetch-handlers/github.ts +424 -0
- package/src/core/tools/web-fetch-handlers/gitlab.ts +444 -0
- package/src/core/tools/web-fetch-handlers/go-pkg.ts +271 -0
- package/src/core/tools/web-fetch-handlers/hackage.ts +89 -0
- package/src/core/tools/web-fetch-handlers/hackernews.ts +208 -0
- package/src/core/tools/web-fetch-handlers/hex.ts +121 -0
- package/src/core/tools/web-fetch-handlers/huggingface.ts +385 -0
- package/src/core/tools/web-fetch-handlers/iacr.ts +82 -0
- package/src/core/tools/web-fetch-handlers/index.ts +69 -0
- package/src/core/tools/web-fetch-handlers/lobsters.ts +186 -0
- package/src/core/tools/web-fetch-handlers/mastodon.ts +302 -0
- package/src/core/tools/web-fetch-handlers/maven.ts +147 -0
- package/src/core/tools/web-fetch-handlers/mdn.ts +174 -0
- package/src/core/tools/web-fetch-handlers/media.test.ts +138 -0
- package/src/core/tools/web-fetch-handlers/metacpan.ts +247 -0
- package/src/core/tools/web-fetch-handlers/npm.ts +107 -0
- package/src/core/tools/web-fetch-handlers/nuget.ts +201 -0
- package/src/core/tools/web-fetch-handlers/nvd.ts +238 -0
- package/src/core/tools/web-fetch-handlers/opencorporates.ts +273 -0
- package/src/core/tools/web-fetch-handlers/openlibrary.ts +313 -0
- package/src/core/tools/web-fetch-handlers/osv.ts +184 -0
- package/src/core/tools/web-fetch-handlers/package-managers-2.test.ts +199 -0
- package/src/core/tools/web-fetch-handlers/package-managers.test.ts +171 -0
- package/src/core/tools/web-fetch-handlers/package-registries.test.ts +259 -0
- package/src/core/tools/web-fetch-handlers/packagist.ts +170 -0
- package/src/core/tools/web-fetch-handlers/pub-dev.ts +185 -0
- package/src/core/tools/web-fetch-handlers/pubmed.ts +174 -0
- package/src/core/tools/web-fetch-handlers/pypi.ts +125 -0
- package/src/core/tools/web-fetch-handlers/readthedocs.ts +122 -0
- package/src/core/tools/web-fetch-handlers/reddit.ts +100 -0
- package/src/core/tools/web-fetch-handlers/repology.ts +257 -0
- package/src/core/tools/web-fetch-handlers/research.test.ts +107 -0
- package/src/core/tools/web-fetch-handlers/rfc.ts +205 -0
- package/src/core/tools/web-fetch-handlers/rubygems.ts +112 -0
- package/src/core/tools/web-fetch-handlers/sec-edgar.ts +269 -0
- package/src/core/tools/web-fetch-handlers/security.test.ts +103 -0
- package/src/core/tools/web-fetch-handlers/semantic-scholar.ts +190 -0
- package/src/core/tools/web-fetch-handlers/social-extended.test.ts +192 -0
- package/src/core/tools/web-fetch-handlers/social.test.ts +259 -0
- package/src/core/tools/web-fetch-handlers/spotify.ts +218 -0
- package/src/core/tools/web-fetch-handlers/stackexchange.test.ts +120 -0
- package/src/core/tools/web-fetch-handlers/stackoverflow.ts +123 -0
- package/src/core/tools/web-fetch-handlers/standards.test.ts +122 -0
- package/src/core/tools/web-fetch-handlers/terraform.ts +296 -0
- package/src/core/tools/web-fetch-handlers/tldr.ts +47 -0
- package/src/core/tools/web-fetch-handlers/twitter.ts +84 -0
- package/src/core/tools/web-fetch-handlers/types.ts +163 -0
- package/src/core/tools/web-fetch-handlers/utils.ts +91 -0
- package/src/core/tools/web-fetch-handlers/vimeo.ts +152 -0
- package/src/core/tools/web-fetch-handlers/wikidata.ts +349 -0
- package/src/core/tools/web-fetch-handlers/wikipedia.test.ts +73 -0
- package/src/core/tools/web-fetch-handlers/wikipedia.ts +91 -0
- package/src/core/tools/web-fetch-handlers/youtube.test.ts +198 -0
- package/src/core/tools/web-fetch-handlers/youtube.ts +319 -0
- package/src/core/tools/web-fetch.ts +152 -1324
- package/src/prompts/task.md +14 -50
- package/src/prompts/tools/output.md +2 -1
- package/src/prompts/tools/task.md +3 -1
- package/src/utils/tools-manager.ts +110 -8
|
@@ -69,6 +69,118 @@ function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): s
|
|
|
69
69
|
return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function formatJsonScalar(value: unknown): string {
|
|
73
|
+
if (value === null) return "null";
|
|
74
|
+
if (typeof value === "string") return `"${value}"`;
|
|
75
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
|
|
80
|
+
return ancestors.map((hasNext) => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderJsonTreeLines(
|
|
84
|
+
value: unknown,
|
|
85
|
+
theme: Theme,
|
|
86
|
+
maxDepth: number,
|
|
87
|
+
maxLines: number,
|
|
88
|
+
): { lines: string[]; truncated: boolean } {
|
|
89
|
+
const lines: string[] = [];
|
|
90
|
+
let truncated = false;
|
|
91
|
+
|
|
92
|
+
const iconObject = theme.styledSymbol("icon.folder", "muted");
|
|
93
|
+
const iconArray = theme.styledSymbol("icon.package", "muted");
|
|
94
|
+
const iconScalar = theme.styledSymbol("icon.file", "muted");
|
|
95
|
+
|
|
96
|
+
const pushLine = (line: string) => {
|
|
97
|
+
if (lines.length >= maxLines) {
|
|
98
|
+
truncated = true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
lines.push(line);
|
|
102
|
+
return true;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const renderNode = (val: unknown, key: string | undefined, ancestors: boolean[], isLast: boolean, depth: number) => {
|
|
106
|
+
if (lines.length >= maxLines) {
|
|
107
|
+
truncated = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const connector = isLast ? theme.tree.last : theme.tree.branch;
|
|
112
|
+
const prefix = `${buildTreePrefix(ancestors, theme)}${theme.fg("dim", connector)} `;
|
|
113
|
+
const scalar = formatJsonScalar(val);
|
|
114
|
+
|
|
115
|
+
if (scalar) {
|
|
116
|
+
const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
|
|
117
|
+
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", scalar)}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(val)) {
|
|
122
|
+
const header = key ? theme.fg("muted", key) : theme.fg("muted", "array");
|
|
123
|
+
pushLine(`${prefix}${iconArray} ${header}`);
|
|
124
|
+
if (val.length === 0) {
|
|
125
|
+
pushLine(
|
|
126
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", "[]")}`,
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (depth >= maxDepth) {
|
|
131
|
+
pushLine(
|
|
132
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", theme.format.ellipsis)}`,
|
|
133
|
+
);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const nextAncestors = [...ancestors, !isLast];
|
|
137
|
+
for (let i = 0; i < val.length; i++) {
|
|
138
|
+
renderNode(val[i], `[${i}]`, nextAncestors, i === val.length - 1, depth + 1);
|
|
139
|
+
if (lines.length >= maxLines) {
|
|
140
|
+
truncated = true;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (val && typeof val === "object") {
|
|
148
|
+
const header = key ? theme.fg("muted", key) : theme.fg("muted", "object");
|
|
149
|
+
pushLine(`${prefix}${iconObject} ${header}`);
|
|
150
|
+
const entries = Object.entries(val as Record<string, unknown>);
|
|
151
|
+
if (entries.length === 0) {
|
|
152
|
+
pushLine(
|
|
153
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", "{}")}`,
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (depth >= maxDepth) {
|
|
158
|
+
pushLine(
|
|
159
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", theme.format.ellipsis)}`,
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const nextAncestors = [...ancestors, !isLast];
|
|
164
|
+
for (let i = 0; i < entries.length; i++) {
|
|
165
|
+
const [childKey, child] = entries[i];
|
|
166
|
+
renderNode(child, childKey, nextAncestors, i === entries.length - 1, depth + 1);
|
|
167
|
+
if (lines.length >= maxLines) {
|
|
168
|
+
truncated = true;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
|
|
176
|
+
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", String(val))}`);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
renderNode(value, undefined, [], true, 0);
|
|
180
|
+
|
|
181
|
+
return { lines, truncated };
|
|
182
|
+
}
|
|
183
|
+
|
|
72
184
|
function renderOutputSection(
|
|
73
185
|
output: string,
|
|
74
186
|
continuePrefix: string,
|
|
@@ -78,11 +190,30 @@ function renderOutputSection(
|
|
|
78
190
|
maxExpanded = 10,
|
|
79
191
|
): string[] {
|
|
80
192
|
const lines: string[] = [];
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
193
|
+
const trimmedOutput = output.trim();
|
|
194
|
+
if (!trimmedOutput) return lines;
|
|
83
195
|
|
|
84
196
|
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
85
197
|
|
|
198
|
+
if (trimmedOutput.startsWith("{") || trimmedOutput.startsWith("[")) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(trimmedOutput);
|
|
201
|
+
const tree = renderJsonTreeLines(parsed, theme, expanded ? 6 : 2, expanded ? 24 : 6);
|
|
202
|
+
if (tree.lines.length > 0) {
|
|
203
|
+
for (const line of tree.lines) {
|
|
204
|
+
lines.push(`${continuePrefix} ${line}`);
|
|
205
|
+
}
|
|
206
|
+
if (tree.truncated) {
|
|
207
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", theme.format.ellipsis)}`);
|
|
208
|
+
}
|
|
209
|
+
return lines;
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Fall back to raw output
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const outputLines = output.split("\n").filter((line) => line.trim());
|
|
86
217
|
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
87
218
|
for (const line of outputLines.slice(0, previewCount)) {
|
|
88
219
|
lines.push(`${continuePrefix} ${theme.fg("dim", truncate(line, 70, theme.format.ellipsis))}`);
|
|
@@ -144,9 +275,8 @@ function renderAgentProgress(
|
|
|
144
275
|
? "error"
|
|
145
276
|
: "accent";
|
|
146
277
|
|
|
147
|
-
// Main status line -
|
|
148
|
-
|
|
149
|
-
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
|
|
278
|
+
// Main status line - use taskId for Output tool
|
|
279
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", progress.taskId)}`;
|
|
150
280
|
const description = progress.description?.trim();
|
|
151
281
|
if (description) {
|
|
152
282
|
statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
|
|
@@ -342,9 +472,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
342
472
|
const iconColor = success ? "success" : "error";
|
|
343
473
|
const statusText = aborted ? "aborted" : success ? "done" : "failed";
|
|
344
474
|
|
|
345
|
-
// Main status line -
|
|
346
|
-
|
|
347
|
-
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
475
|
+
// Main status line - use taskId for Output tool
|
|
476
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", result.taskId)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
348
477
|
const description = result.description?.trim();
|
|
349
478
|
if (description) {
|
|
350
479
|
statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
|
|
@@ -4,20 +4,34 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
4
4
|
/** Source of an agent definition */
|
|
5
5
|
export type AgentSource = "bundled" | "user" | "project";
|
|
6
6
|
|
|
7
|
+
function getEnv(name: string, defaultValue: number): number {
|
|
8
|
+
const value = process.env[name];
|
|
9
|
+
if (value === undefined) {
|
|
10
|
+
return defaultValue;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const number = Number.parseInt(value, 10);
|
|
14
|
+
if (!Number.isNaN(number) && number > 0) {
|
|
15
|
+
return number;
|
|
16
|
+
}
|
|
17
|
+
} catch {}
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
/** Maximum tasks per call */
|
|
8
|
-
export const MAX_PARALLEL_TASKS = 32;
|
|
22
|
+
export const MAX_PARALLEL_TASKS = getEnv("OMP_TASK_MAX_PARALLEL", 32);
|
|
9
23
|
|
|
10
24
|
/** Maximum concurrent workers */
|
|
11
|
-
export const MAX_CONCURRENCY = 16;
|
|
25
|
+
export const MAX_CONCURRENCY = getEnv("OMP_TASK_MAX_CONCURRENCY", 16);
|
|
12
26
|
|
|
13
27
|
/** Maximum output bytes per agent */
|
|
14
|
-
export const MAX_OUTPUT_BYTES = 500_000;
|
|
28
|
+
export const MAX_OUTPUT_BYTES = getEnv("OMP_TASK_MAX_OUTPUT_BYTES", 500_000);
|
|
15
29
|
|
|
16
30
|
/** Maximum output lines per agent */
|
|
17
|
-
export const MAX_OUTPUT_LINES = 5000;
|
|
31
|
+
export const MAX_OUTPUT_LINES = getEnv("OMP_TASK_MAX_OUTPUT_LINES", 5000);
|
|
18
32
|
|
|
19
33
|
/** Maximum agents to show in description */
|
|
20
|
-
export const MAX_AGENTS_IN_DESCRIPTION = 10;
|
|
34
|
+
export const MAX_AGENTS_IN_DESCRIPTION = getEnv("OMP_TASK_MAX_AGENTS_IN_DESCRIPTION", 10);
|
|
21
35
|
|
|
22
36
|
/** EventBus channel for raw subagent events */
|
|
23
37
|
export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
|
|
@@ -38,6 +52,11 @@ export type TaskItem = Static<typeof taskItemSchema>;
|
|
|
38
52
|
/** Task tool parameters */
|
|
39
53
|
export const taskSchema = Type.Object({
|
|
40
54
|
context: Type.Optional(Type.String({ description: "Shared context prepended to all task prompts" })),
|
|
55
|
+
output_schema: Type.Optional(
|
|
56
|
+
Type.Any({
|
|
57
|
+
description: "JSON schema for structured subagent output (used by the complete tool)",
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
41
60
|
tasks: Type.Array(taskItemSchema, {
|
|
42
61
|
description: "Tasks to run in parallel",
|
|
43
62
|
maxItems: MAX_PARALLEL_TASKS,
|
|
@@ -85,6 +104,7 @@ export interface AgentDefinition {
|
|
|
85
104
|
/** Progress tracking for a single agent */
|
|
86
105
|
export interface AgentProgress {
|
|
87
106
|
index: number;
|
|
107
|
+
taskId: string;
|
|
88
108
|
agent: string;
|
|
89
109
|
agentSource: AgentSource;
|
|
90
110
|
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
@@ -106,6 +126,7 @@ export interface AgentProgress {
|
|
|
106
126
|
/** Result from a single agent execution */
|
|
107
127
|
export interface SingleResult {
|
|
108
128
|
index: number;
|
|
129
|
+
taskId: string;
|
|
109
130
|
agent: string;
|
|
110
131
|
agentSource: AgentSource;
|
|
111
132
|
task: string;
|
|
@@ -24,7 +24,11 @@ import type { SubagentWorkerRequest, SubagentWorkerResponse, SubagentWorkerStart
|
|
|
24
24
|
type PostMessageFn = (message: SubagentWorkerResponse) => void;
|
|
25
25
|
|
|
26
26
|
const postMessageSafe: PostMessageFn = (message) => {
|
|
27
|
-
|
|
27
|
+
try {
|
|
28
|
+
(globalThis as typeof globalThis & { postMessage: PostMessageFn }).postMessage(message);
|
|
29
|
+
} catch {
|
|
30
|
+
// Parent may have terminated worker, nothing we can do
|
|
31
|
+
}
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
interface WorkerMessageEvent<T> {
|
|
@@ -51,7 +55,9 @@ const isAgentEvent = (event: AgentSessionEvent): event is AgentEvent => {
|
|
|
51
55
|
|
|
52
56
|
let running = false;
|
|
53
57
|
let abortRequested = false;
|
|
58
|
+
let doneSent = false;
|
|
54
59
|
let activeSession: { abort: () => Promise<void>; dispose: () => Promise<void> } | null = null;
|
|
60
|
+
let unsubscribe: (() => void) | null = null;
|
|
55
61
|
|
|
56
62
|
/**
|
|
57
63
|
* Resolve model string to Model object with optional thinking level.
|
|
@@ -123,6 +129,9 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
|
|
|
123
129
|
|
|
124
130
|
// Create agent session (equivalent to CLI's createAgentSession)
|
|
125
131
|
// Note: hasUI: false disables interactive features
|
|
132
|
+
const completionInstruction =
|
|
133
|
+
"When finished, call the complete tool exactly once. Do not end with a plain-text final answer.";
|
|
134
|
+
|
|
126
135
|
const { session } = await createAgentSession({
|
|
127
136
|
cwd: payload.cwd,
|
|
128
137
|
authStorage,
|
|
@@ -130,8 +139,10 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
|
|
|
130
139
|
model,
|
|
131
140
|
thinkingLevel,
|
|
132
141
|
toolNames: payload.toolNames,
|
|
142
|
+
outputSchema: payload.outputSchema,
|
|
143
|
+
requireCompleteTool: true,
|
|
133
144
|
// Append system prompt (equivalent to CLI's --append-system-prompt)
|
|
134
|
-
systemPrompt: (defaultPrompt) => `${defaultPrompt}\n\n${payload.systemPrompt}`,
|
|
145
|
+
systemPrompt: (defaultPrompt) => `${defaultPrompt}\n\n${payload.systemPrompt}\n\n${completionInstruction}`,
|
|
135
146
|
sessionManager,
|
|
136
147
|
hasUI: false,
|
|
137
148
|
// Pass spawn restrictions to nested tasks
|
|
@@ -140,6 +151,17 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
|
|
|
140
151
|
|
|
141
152
|
activeSession = session;
|
|
142
153
|
|
|
154
|
+
if (abortRequested) {
|
|
155
|
+
aborted = true;
|
|
156
|
+
exitCode = 1;
|
|
157
|
+
try {
|
|
158
|
+
await session.abort();
|
|
159
|
+
} catch {
|
|
160
|
+
// Ignore abort errors
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
143
165
|
// Initialize extensions (equivalent to CLI's extension initialization)
|
|
144
166
|
// Note: Does not support --extension CLI flag or extension CLI flags
|
|
145
167
|
const extensionRunner = session.extensionRunner;
|
|
@@ -164,16 +186,43 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
|
|
|
164
186
|
await extensionRunner.emit({ type: "session_start" });
|
|
165
187
|
}
|
|
166
188
|
|
|
189
|
+
// Track complete tool calls
|
|
190
|
+
const MAX_COMPLETE_RETRIES = 3;
|
|
191
|
+
let completeCalled = false;
|
|
192
|
+
|
|
167
193
|
// Subscribe to events and forward to parent (equivalent to --mode json output)
|
|
168
|
-
session.subscribe((event: AgentSessionEvent) => {
|
|
194
|
+
unsubscribe = session.subscribe((event: AgentSessionEvent) => {
|
|
169
195
|
if (isAgentEvent(event)) {
|
|
170
196
|
postMessageSafe({ type: "event", event });
|
|
197
|
+
// Track when complete tool is called
|
|
198
|
+
if (event.type === "tool_execution_end" && event.toolName === "complete") {
|
|
199
|
+
completeCalled = true;
|
|
200
|
+
}
|
|
171
201
|
}
|
|
172
202
|
});
|
|
173
203
|
|
|
174
204
|
// Run the prompt (equivalent to --prompt flag)
|
|
175
205
|
await session.prompt(payload.task);
|
|
176
206
|
|
|
207
|
+
// Retry loop if complete was not called
|
|
208
|
+
let retryCount = 0;
|
|
209
|
+
while (!completeCalled && retryCount < MAX_COMPLETE_RETRIES && !abortRequested) {
|
|
210
|
+
retryCount++;
|
|
211
|
+
const reminder = `<system-reminder>
|
|
212
|
+
CRITICAL: You stopped without calling the complete tool. This is reminder ${retryCount} of ${MAX_COMPLETE_RETRIES}.
|
|
213
|
+
|
|
214
|
+
You MUST call the complete tool to finish your task. Options:
|
|
215
|
+
1. Call complete with your result data if you have completed the task
|
|
216
|
+
2. Call complete with status="aborted" and an error message if you cannot complete the task
|
|
217
|
+
|
|
218
|
+
Failure to call complete after ${MAX_COMPLETE_RETRIES} reminders will result in task failure.
|
|
219
|
+
</system-reminder>
|
|
220
|
+
|
|
221
|
+
Call complete now.`;
|
|
222
|
+
|
|
223
|
+
await session.prompt(reminder);
|
|
224
|
+
}
|
|
225
|
+
|
|
177
226
|
// Check if aborted during execution
|
|
178
227
|
const lastMessage = session.state.messages[session.state.messages.length - 1];
|
|
179
228
|
if (lastMessage?.role === "assistant" && lastMessage.stopReason === "aborted") {
|
|
@@ -190,24 +239,39 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
|
|
|
190
239
|
if (exitCode === 0) exitCode = 1;
|
|
191
240
|
}
|
|
192
241
|
|
|
193
|
-
|
|
242
|
+
if (unsubscribe) {
|
|
243
|
+
try {
|
|
244
|
+
unsubscribe();
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore unsubscribe errors
|
|
247
|
+
}
|
|
248
|
+
unsubscribe = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Cleanup session with timeout to prevent hanging
|
|
194
252
|
if (activeSession) {
|
|
253
|
+
const session = activeSession;
|
|
254
|
+
activeSession = null;
|
|
195
255
|
try {
|
|
196
|
-
await
|
|
256
|
+
await Promise.race([session.dispose(), new Promise<void>((resolve) => setTimeout(resolve, 5000))]);
|
|
197
257
|
} catch {
|
|
198
258
|
// Ignore cleanup errors
|
|
199
259
|
}
|
|
200
|
-
activeSession = null;
|
|
201
260
|
}
|
|
202
261
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
262
|
+
running = false;
|
|
263
|
+
|
|
264
|
+
// Send completion message to parent (only once)
|
|
265
|
+
if (!doneSent) {
|
|
266
|
+
doneSent = true;
|
|
267
|
+
postMessageSafe({
|
|
268
|
+
type: "done",
|
|
269
|
+
exitCode,
|
|
270
|
+
durationMs: Date.now() - startTime,
|
|
271
|
+
error,
|
|
272
|
+
aborted,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
211
275
|
}
|
|
212
276
|
}
|
|
213
277
|
|
|
@@ -219,6 +283,64 @@ function handleAbort(): void {
|
|
|
219
283
|
}
|
|
220
284
|
}
|
|
221
285
|
|
|
286
|
+
// Global error handlers to ensure we always send a done message
|
|
287
|
+
// Using self instead of globalThis for proper worker scope typing
|
|
288
|
+
declare const self: {
|
|
289
|
+
addEventListener(type: "error", listener: (event: ErrorEvent) => void): void;
|
|
290
|
+
addEventListener(type: "unhandledrejection", listener: (event: { reason: unknown }) => void): void;
|
|
291
|
+
addEventListener(type: "messageerror", listener: (event: MessageEvent) => void): void;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
self.addEventListener("error", (event) => {
|
|
295
|
+
if (!running || doneSent) return;
|
|
296
|
+
doneSent = true;
|
|
297
|
+
abortRequested = true;
|
|
298
|
+
if (activeSession) {
|
|
299
|
+
void activeSession.abort();
|
|
300
|
+
}
|
|
301
|
+
postMessageSafe({
|
|
302
|
+
type: "done",
|
|
303
|
+
exitCode: 1,
|
|
304
|
+
durationMs: 0,
|
|
305
|
+
error: `Uncaught error: ${event.message || "Unknown error"}`,
|
|
306
|
+
aborted: false,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
self.addEventListener("unhandledrejection", (event) => {
|
|
311
|
+
if (!running || doneSent) return;
|
|
312
|
+
doneSent = true;
|
|
313
|
+
abortRequested = true;
|
|
314
|
+
if (activeSession) {
|
|
315
|
+
void activeSession.abort();
|
|
316
|
+
}
|
|
317
|
+
const reason = event.reason;
|
|
318
|
+
const message = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
319
|
+
postMessageSafe({
|
|
320
|
+
type: "done",
|
|
321
|
+
exitCode: 1,
|
|
322
|
+
durationMs: 0,
|
|
323
|
+
error: `Unhandled rejection: ${message}`,
|
|
324
|
+
aborted: false,
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
self.addEventListener("messageerror", () => {
|
|
329
|
+
if (doneSent) return;
|
|
330
|
+
doneSent = true;
|
|
331
|
+
abortRequested = true;
|
|
332
|
+
if (activeSession) {
|
|
333
|
+
void activeSession.abort();
|
|
334
|
+
}
|
|
335
|
+
postMessageSafe({
|
|
336
|
+
type: "done",
|
|
337
|
+
exitCode: 1,
|
|
338
|
+
durationMs: 0,
|
|
339
|
+
error: "Failed to deserialize parent message",
|
|
340
|
+
aborted: false,
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
222
344
|
// Message handler - receives start/abort commands from parent
|
|
223
345
|
globalThis.addEventListener("message", (event: WorkerMessageEvent<SubagentWorkerRequest>) => {
|
|
224
346
|
const message = event.data;
|