pi-roundtable-coding 0.7.3 → 0.7.4
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 +4 -0
- package/README.md +2 -1
- package/package.json +2 -2
- package/src/coding-desk.ts +27 -18
- package/src/coding-plugin.ts +76 -26
- package/src/index.ts +2 -0
- package/src/pi-coding-worker.ts +9 -2
- package/src/repo-shelf.ts +11 -7
- package/src/worker-entry.ts +9 -2
- package/src/worker-failure.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.4] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
- Add `toolText` and `presentation.list`, accept a 7–40 character sha prefix in `repo_push`, name git and gh stderr (scrubbed) in failures, carry a worker's own error text into the report, restore the earlier timeout, stop and refusal wording, stop listing declined calls as held, and report the channels the service works for.
|
|
8
|
+
|
|
5
9
|
## [0.7.3] - 2026-10-03
|
|
6
10
|
|
|
7
11
|
- Hook caller model/thinking/origin/channel, owner policy, clone adoption, threaded approvals and report formatting through options, and add `pushHoldText` for the held `repo_push` card.
|
package/README.md
CHANGED
|
@@ -166,7 +166,8 @@ The `CODING` service exposes `shelf: RepoShelf` and `desk: CodingDesk` for trust
|
|
|
166
166
|
| `adoptClones` | `[]` | Startup `{ from, repo }` moves of standalone clones; existing shelf destinations are never replaced. |
|
|
167
167
|
| `resolveRun` | configured model/thinking, caller channel | Per-caller model, thinking, report channel and optional thread origin. |
|
|
168
168
|
| `postChangeReport` | caller surface reply | Post the record using the calling identity and channel. |
|
|
169
|
-
| `presentation` | English package text | Separate change-report post/result text
|
|
169
|
+
| `presentation` | English package text | Separate change-report post/result text, task-start/omitted-skill wording, and the `repo_list` result (`list(repos, { shelfDir, fetched })`). |
|
|
170
|
+
| `toolText` | package wording | Trusted description and argument descriptions for each repository tool, as `{ repo_task: { description, parameters: { task: "…" } } }`; it never changes a tool's arguments or approval rules. |
|
|
170
171
|
| `threads` | none | Public `DispatchThreads`-compatible progress and approval thread port. |
|
|
171
172
|
| `threadText` | English package text | Thread introduction, held-action notice, approval title and final report. |
|
|
172
173
|
| `workerWorkspace` | individual clone | Trusted shell-policy write boundary; not an OS sandbox. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-roundtable-coding",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Repository shelves and owner-approved Pi coding workers for pi-roundtable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@earendil-works/pi-ai": ">=1.0.0 <2",
|
|
45
45
|
"@biomejs/biome": "2.5.15",
|
|
46
46
|
"@types/bun": "1.4.2",
|
|
47
|
-
"pi-roundtable": "0.7.
|
|
47
|
+
"pi-roundtable": "0.7.4",
|
|
48
48
|
"typescript": "7.0.2"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/coding-desk.ts
CHANGED
|
@@ -121,18 +121,26 @@ export class CodingDesk {
|
|
|
121
121
|
): Promise<CodingJob> {
|
|
122
122
|
if (this.#stopped) throw new AgentError("The coding desk is stopped.");
|
|
123
123
|
const task = request.task.trim();
|
|
124
|
-
if (!task
|
|
124
|
+
if (!task) throw new AgentError("The task is required.");
|
|
125
|
+
if (task.length > MAX_CODING_TASK_CHARS)
|
|
125
126
|
throw new AgentError(
|
|
126
|
-
`The task
|
|
127
|
+
`The task is ${task.length} characters; keep it within ${MAX_CODING_TASK_CHARS}.`,
|
|
127
128
|
);
|
|
128
129
|
const dir = this.#options.shelf.dirOf(request.repo);
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
const busy = [...this.#running.values()].find(
|
|
131
|
+
({ job }) => job.repo === request.repo,
|
|
132
|
+
);
|
|
133
|
+
if (busy)
|
|
134
|
+
throw new AgentError(
|
|
135
|
+
`Coding task #${busy.job.id} is still working in ${request.repo}; one worker runs per repository.`,
|
|
136
|
+
);
|
|
137
|
+
const inChannel = [...this.#running.values()].filter(
|
|
138
|
+
({ job }) => job.channel === request.channel,
|
|
139
|
+
).length;
|
|
140
|
+
if (inChannel >= 3)
|
|
141
|
+
throw new AgentError(
|
|
142
|
+
`This channel already has ${inChannel} coding workers running; wait for one to report back.`,
|
|
143
|
+
);
|
|
136
144
|
const job: CodingJob = {
|
|
137
145
|
...request,
|
|
138
146
|
task,
|
|
@@ -180,7 +188,7 @@ export class CodingDesk {
|
|
|
180
188
|
try {
|
|
181
189
|
job.startHead = (await shelf.state(job.repo)).head;
|
|
182
190
|
if (controller.signal.aborted)
|
|
183
|
-
throw new AgentError("
|
|
191
|
+
throw new AgentError("the worker was stopped");
|
|
184
192
|
if (threads) {
|
|
185
193
|
try {
|
|
186
194
|
const thread = await threads.open(
|
|
@@ -197,7 +205,7 @@ export class CodingDesk {
|
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
if (controller.signal.aborted)
|
|
200
|
-
throw new AgentError("
|
|
208
|
+
throw new AgentError("the worker was stopped");
|
|
201
209
|
slot.bind(
|
|
202
210
|
threads
|
|
203
211
|
? job.thread && prompts?.(job.thread.channel)
|
|
@@ -222,10 +230,11 @@ export class CodingDesk {
|
|
|
222
230
|
} catch {
|
|
223
231
|
/* A failed card never authorizes the call. */
|
|
224
232
|
}
|
|
225
|
-
|
|
233
|
+
// A call the owner declined is settled, not held for the report.
|
|
234
|
+
if (answer === "held") {
|
|
226
235
|
if (held.length < MAX_HELD_ENTRIES) {
|
|
227
236
|
const entry = bounded(
|
|
228
|
-
`${
|
|
237
|
+
`${call.action}: ${call.tool} ${call.input}`,
|
|
229
238
|
MAX_HELD_CHARS,
|
|
230
239
|
);
|
|
231
240
|
held.push(entry);
|
|
@@ -246,22 +255,22 @@ export class CodingDesk {
|
|
|
246
255
|
review,
|
|
247
256
|
);
|
|
248
257
|
if (controller.signal.aborted)
|
|
249
|
-
throw new AgentError("
|
|
258
|
+
throw new AgentError("the worker was stopped");
|
|
250
259
|
outcome = { ok: true, report: bounded(report, MAX_REPORT_CHARS) };
|
|
251
260
|
} catch (error) {
|
|
252
|
-
//
|
|
261
|
+
// The host's own refusals and the worker's scrubbed, bounded error text reach the report.
|
|
253
262
|
let message =
|
|
254
263
|
"The worker failed; inspect the clone and worker configuration on the host.";
|
|
264
|
+
if (error instanceof AgentError) message = error.message;
|
|
255
265
|
if (error instanceof CodingWorkerFailure) {
|
|
256
|
-
message = error.message;
|
|
257
266
|
this.#options.logger.warn(
|
|
258
267
|
{ job: job.id, category: error.category, exitCode: error.exitCode },
|
|
259
268
|
"Coding worker failed.",
|
|
260
269
|
);
|
|
261
270
|
}
|
|
262
|
-
if (controller.signal.aborted) message = "
|
|
271
|
+
if (controller.signal.aborted) message = "the worker was stopped";
|
|
263
272
|
if (timedOut)
|
|
264
|
-
message = `
|
|
273
|
+
message = `the worker ran out of time (${Math.round(timeoutMs / 60_000)} minutes)`;
|
|
265
274
|
outcome = { ok: false, error: message };
|
|
266
275
|
} finally {
|
|
267
276
|
cancel();
|
package/src/coding-plugin.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type ChangeReport,
|
|
33
33
|
type CloneCommand,
|
|
34
34
|
RepoShelf,
|
|
35
|
+
type RepoSummary,
|
|
35
36
|
reportPost,
|
|
36
37
|
} from "./repo-shelf.ts";
|
|
37
38
|
|
|
@@ -46,7 +47,24 @@ export interface CodingRun {
|
|
|
46
47
|
channel: ChannelKey;
|
|
47
48
|
origin?: ChannelKey;
|
|
48
49
|
}
|
|
50
|
+
export type RepoToolName =
|
|
51
|
+
| "repo_list"
|
|
52
|
+
| "repo_add"
|
|
53
|
+
| "repo_change_report"
|
|
54
|
+
| "repo_push"
|
|
55
|
+
| "repo_task";
|
|
56
|
+
/** Trusted wording of one repository tool, as the model reads it; unset fields keep the defaults. */
|
|
57
|
+
export interface CodingToolText {
|
|
58
|
+
description?: string;
|
|
59
|
+
/** Argument descriptions by argument name. */
|
|
60
|
+
parameters?: Record<string, string>;
|
|
61
|
+
}
|
|
49
62
|
export interface CodingPresentation {
|
|
63
|
+
/** The repo_list result; unset: the managed clones as JSON. */
|
|
64
|
+
list?(
|
|
65
|
+
repos: (RepoSummary & { skills: string[] })[],
|
|
66
|
+
context: { shelfDir: string; fetched: boolean },
|
|
67
|
+
): string;
|
|
50
68
|
/** Separate the owner's posted record from the model's shipping instructions. */
|
|
51
69
|
changeReport?(
|
|
52
70
|
report: ChangeReport,
|
|
@@ -73,6 +91,8 @@ export interface CodingOptions {
|
|
|
73
91
|
/** Post change reports in the caller's identity/channel rather than the default surface. */
|
|
74
92
|
postChangeReport?: (turn: ToolTurn, text: string) => Promise<void>;
|
|
75
93
|
presentation?: CodingPresentation;
|
|
94
|
+
/** Trusted wording of the repository tools' descriptions and arguments. */
|
|
95
|
+
toolText?: Partial<Record<RepoToolName, CodingToolText>>;
|
|
76
96
|
threads?: Pick<DispatchThreads, "open">;
|
|
77
97
|
threadText?: CodingThreadText;
|
|
78
98
|
workerWorkspace?: string;
|
|
@@ -170,14 +190,21 @@ export function coding(options: CodingOptions) {
|
|
|
170
190
|
}
|
|
171
191
|
}
|
|
172
192
|
context.services.provide(CODING, { shelf, desk });
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
const textOf = (tool: RepoToolName) => options.toolText?.[tool];
|
|
194
|
+
const describe = (tool: RepoToolName, fallback: string) =>
|
|
195
|
+
textOf(tool)?.description ?? fallback;
|
|
196
|
+
const about = (tool: RepoToolName, name: string, fallback?: string) => {
|
|
197
|
+
const description = textOf(tool)?.parameters?.[name] ?? fallback;
|
|
198
|
+
return description ? { description } : {};
|
|
199
|
+
};
|
|
200
|
+
const repoSchema = (tool: RepoToolName) =>
|
|
201
|
+
Type.String(about(tool, "repo", "A managed repository, owner/repo."));
|
|
176
202
|
return {
|
|
177
203
|
services: [
|
|
178
204
|
{
|
|
179
205
|
name: "coding-desk",
|
|
180
|
-
|
|
206
|
+
// The host names the channels whose work a shutdown waits for or aborts.
|
|
207
|
+
busy: () => desk.runningChannels(),
|
|
181
208
|
stop: () => desk.stop(),
|
|
182
209
|
},
|
|
183
210
|
],
|
|
@@ -200,30 +227,39 @@ export function coding(options: CodingOptions) {
|
|
|
200
227
|
defineTool({
|
|
201
228
|
name: "repo_list",
|
|
202
229
|
minTier: "owner",
|
|
203
|
-
description:
|
|
230
|
+
description: describe(
|
|
231
|
+
"repo_list",
|
|
204
232
|
"List managed clones, branch, upstream counts, dirty files, last commit, summary, CI hints and linked skills. Fetch first only when requested.",
|
|
205
|
-
|
|
233
|
+
),
|
|
234
|
+
parameters: Type.Object({
|
|
235
|
+
fetch: Type.Optional(Type.Boolean(about("repo_list", "fetch"))),
|
|
236
|
+
}),
|
|
206
237
|
run: ({ fetch }) =>
|
|
207
238
|
refusal(async () => {
|
|
208
|
-
const repos = await shelf.list(fetch === true)
|
|
239
|
+
const repos = (await shelf.list(fetch === true)).map(
|
|
240
|
+
(repo) => ({
|
|
241
|
+
...repo,
|
|
242
|
+
skills: skills?.linkedFrom(repo.repo) ?? [],
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
if (options.presentation?.list)
|
|
246
|
+
return options.presentation.list(repos, {
|
|
247
|
+
shelfDir: shelf.dir,
|
|
248
|
+
fetched: fetch === true,
|
|
249
|
+
});
|
|
209
250
|
return repos.length
|
|
210
|
-
? JSON.stringify(
|
|
211
|
-
repos.map((repo) => ({
|
|
212
|
-
...repo,
|
|
213
|
-
skills: skills?.linkedFrom(repo.repo) ?? [],
|
|
214
|
-
})),
|
|
215
|
-
null,
|
|
216
|
-
2,
|
|
217
|
-
)
|
|
251
|
+
? JSON.stringify(repos, null, 2)
|
|
218
252
|
: `No managed repositories in ${shelf.dir}; use repo_add.`;
|
|
219
253
|
}),
|
|
220
254
|
}),
|
|
221
255
|
defineTool({
|
|
222
256
|
name: "repo_add",
|
|
223
257
|
minTier: "owner",
|
|
224
|
-
description:
|
|
258
|
+
description: describe(
|
|
259
|
+
"repo_add",
|
|
225
260
|
"Clone owner/repo into the shelf using the host's Git login (GitHub CLI by default).",
|
|
226
|
-
|
|
261
|
+
),
|
|
262
|
+
parameters: Type.Object({ repo: repoSchema("repo_add") }),
|
|
227
263
|
run: ({ repo }) =>
|
|
228
264
|
refusal(
|
|
229
265
|
async () => `Cloned ${repo} into ${await shelf.add(repo)}.`,
|
|
@@ -232,9 +268,13 @@ export function coding(options: CodingOptions) {
|
|
|
232
268
|
defineTool({
|
|
233
269
|
name: "repo_change_report",
|
|
234
270
|
minTier: "owner",
|
|
235
|
-
description:
|
|
271
|
+
description: describe(
|
|
272
|
+
"repo_change_report",
|
|
236
273
|
"Fetch and report commits and changed files for the default branch. Requires a clean, ahead-only clone. Review checks before requesting this report.",
|
|
237
|
-
|
|
274
|
+
),
|
|
275
|
+
parameters: Type.Object({
|
|
276
|
+
repo: repoSchema("repo_change_report"),
|
|
277
|
+
}),
|
|
238
278
|
run: ({ repo }, turn) =>
|
|
239
279
|
refusal(() =>
|
|
240
280
|
ship(repo, async () => {
|
|
@@ -260,11 +300,16 @@ export function coding(options: CodingOptions) {
|
|
|
260
300
|
defineTool({
|
|
261
301
|
name: "repo_push",
|
|
262
302
|
minTier: "owner",
|
|
263
|
-
description:
|
|
264
|
-
"
|
|
303
|
+
description: describe(
|
|
304
|
+
"repo_push",
|
|
305
|
+
"Push the SHA from the latest change report to its default branch, without force. Held for owner approval unless the trusted host policy explicitly marks the clone owner-owned.",
|
|
306
|
+
),
|
|
265
307
|
parameters: Type.Object({
|
|
266
|
-
repo: repoSchema,
|
|
267
|
-
sha: Type.String({
|
|
308
|
+
repo: repoSchema("repo_push"),
|
|
309
|
+
sha: Type.String({
|
|
310
|
+
pattern: "^[0-9a-f]{7,40}$",
|
|
311
|
+
...about("repo_push", "sha"),
|
|
312
|
+
}),
|
|
268
313
|
}),
|
|
269
314
|
hold: ({ repo, sha }) =>
|
|
270
315
|
ownerOwned(repo)
|
|
@@ -283,15 +328,20 @@ export function coding(options: CodingOptions) {
|
|
|
283
328
|
defineTool({
|
|
284
329
|
name: "repo_task",
|
|
285
330
|
minTier: "owner",
|
|
286
|
-
description:
|
|
331
|
+
description: describe(
|
|
332
|
+
"repo_task",
|
|
287
333
|
"Start one background Pi coding worker in a clone. The worker reads repository instructions, edits, checks and commits; shipping uses a separate report and approval. Report returns to this channel.",
|
|
334
|
+
),
|
|
288
335
|
parameters: Type.Object({
|
|
289
|
-
repo: repoSchema,
|
|
336
|
+
repo: repoSchema("repo_task"),
|
|
290
337
|
task: Type.String({
|
|
291
338
|
minLength: 1,
|
|
292
339
|
maxLength: MAX_CODING_TASK_CHARS,
|
|
340
|
+
...about("repo_task", "task"),
|
|
293
341
|
}),
|
|
294
|
-
skills: Type.Optional(
|
|
342
|
+
skills: Type.Optional(
|
|
343
|
+
Type.Array(Type.String(), about("repo_task", "skills")),
|
|
344
|
+
),
|
|
295
345
|
}),
|
|
296
346
|
run: ({ repo, task, skills: names }, turn) =>
|
|
297
347
|
refusal(async () => {
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type {
|
|
|
12
12
|
CodingPresentation,
|
|
13
13
|
CodingRun,
|
|
14
14
|
CodingService,
|
|
15
|
+
CodingToolText,
|
|
16
|
+
RepoToolName,
|
|
15
17
|
} from "./coding-plugin.ts";
|
|
16
18
|
export { CODING, coding } from "./coding-plugin.ts";
|
|
17
19
|
export type { PiCodingWorkerOptions } from "./pi-coding-worker.ts";
|
package/src/pi-coding-worker.ts
CHANGED
|
@@ -23,6 +23,7 @@ interface WorkerMessage {
|
|
|
23
23
|
tool?: string;
|
|
24
24
|
input?: Record<string, unknown>;
|
|
25
25
|
report?: string;
|
|
26
|
+
message?: string;
|
|
26
27
|
}
|
|
27
28
|
function isMessage(value: unknown): value is WorkerMessage {
|
|
28
29
|
return (
|
|
@@ -46,9 +47,10 @@ export class PiCodingWorker implements CodingWorker {
|
|
|
46
47
|
): Promise<string> {
|
|
47
48
|
if (process.platform === "win32")
|
|
48
49
|
throw new AgentError("Coding workers require a POSIX host.");
|
|
49
|
-
if (signal.aborted) throw new AgentError("
|
|
50
|
+
if (signal.aborted) throw new AgentError("the worker was stopped");
|
|
50
51
|
const prompt = this.#options.prompt?.(job.dir);
|
|
51
52
|
let report: string | undefined;
|
|
53
|
+
let failure: string | undefined;
|
|
52
54
|
const child = Bun.spawn(
|
|
53
55
|
[
|
|
54
56
|
process.execPath,
|
|
@@ -76,6 +78,11 @@ export class PiCodingWorker implements CodingWorker {
|
|
|
76
78
|
typeof value.report === "string"
|
|
77
79
|
) {
|
|
78
80
|
report = value.report;
|
|
81
|
+
} else if (
|
|
82
|
+
value.type === "failure" &&
|
|
83
|
+
typeof value.message === "string"
|
|
84
|
+
) {
|
|
85
|
+
failure = value.message.slice(0, 2_000);
|
|
79
86
|
} else if (
|
|
80
87
|
value.type === "call" &&
|
|
81
88
|
Number.isSafeInteger(value.id) &&
|
|
@@ -124,7 +131,7 @@ export class PiCodingWorker implements CodingWorker {
|
|
|
124
131
|
if (signal.aborted) kill();
|
|
125
132
|
const code = await child.exited;
|
|
126
133
|
if (signal.aborted) throw new CodingWorkerFailure("stopped");
|
|
127
|
-
if (code !== 0) throw new CodingWorkerFailure("exit", code);
|
|
134
|
+
if (code !== 0) throw new CodingWorkerFailure("exit", code, failure);
|
|
128
135
|
if (!report?.trim()) throw new CodingWorkerFailure("missing-report");
|
|
129
136
|
return report;
|
|
130
137
|
} finally {
|
package/src/repo-shelf.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
renameSync,
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
15
|
-
import { AgentError, checkRepoName } from "pi-roundtable/kit";
|
|
15
|
+
import { AgentError, checkRepoName, scrubDiagnostic } from "pi-roundtable/kit";
|
|
16
16
|
|
|
17
17
|
/** What a clone would push to its default branch (repos-and-skills spec behavior 10). */
|
|
18
18
|
export interface ChangeReport {
|
|
@@ -92,7 +92,7 @@ export const ghClone: CloneCommand = async (repo, dir) => {
|
|
|
92
92
|
checkRepoName(repo);
|
|
93
93
|
if (repo.startsWith("-"))
|
|
94
94
|
throw new AgentError("Repository owner cannot start with a dash.");
|
|
95
|
-
const { code } = await run([
|
|
95
|
+
const { err, code } = await run([
|
|
96
96
|
"gh",
|
|
97
97
|
"repo",
|
|
98
98
|
"clone",
|
|
@@ -103,7 +103,7 @@ export const ghClone: CloneCommand = async (repo, dir) => {
|
|
|
103
103
|
]);
|
|
104
104
|
if (code !== 0)
|
|
105
105
|
throw new AgentError(
|
|
106
|
-
`Cloning ${repo} failed (exit ${code}
|
|
106
|
+
`Cloning ${repo} failed: ${scrubDiagnostic(err) || `exit ${code}`}`,
|
|
107
107
|
);
|
|
108
108
|
};
|
|
109
109
|
|
|
@@ -328,7 +328,7 @@ export class RepoShelf {
|
|
|
328
328
|
/** A synchronous hold description from the latest report, safe to show on an owner card. */
|
|
329
329
|
pushDescription(repo: string, sha: string): string {
|
|
330
330
|
const report = this.#reports.get(repo);
|
|
331
|
-
if (!report ||
|
|
331
|
+
if (!report || !/^[0-9a-f]{7,40}$/.test(sha) || !report.sha.startsWith(sha))
|
|
332
332
|
return `Push ${repo} commit ${sha}; request a fresh change report first`;
|
|
333
333
|
return `Push ${repo} commit ${sha} to ${report.target}, branch ${report.branch}`;
|
|
334
334
|
}
|
|
@@ -338,7 +338,11 @@ export class RepoShelf {
|
|
|
338
338
|
const dir = this.dirOf(repo);
|
|
339
339
|
const head = await this.#git(dir, "rev-parse", "HEAD");
|
|
340
340
|
const report = this.#reports.get(repo);
|
|
341
|
-
if (
|
|
341
|
+
if (
|
|
342
|
+
!/^[0-9a-f]{7,40}$/.test(sha) ||
|
|
343
|
+
!report?.sha.startsWith(sha) ||
|
|
344
|
+
head !== report.sha
|
|
345
|
+
)
|
|
342
346
|
throw new AgentError(
|
|
343
347
|
`${sha} is not HEAD of ${repo} (HEAD is ${head.slice(0, 12)}); report again with repo_change_report.`,
|
|
344
348
|
);
|
|
@@ -476,10 +480,10 @@ export class RepoShelf {
|
|
|
476
480
|
}
|
|
477
481
|
|
|
478
482
|
async #git(dir: string, ...args: string[]): Promise<string> {
|
|
479
|
-
const { out, code } = await run(["git", "-C", dir, ...args]);
|
|
483
|
+
const { out, err, code } = await run(["git", "-C", dir, ...args]);
|
|
480
484
|
if (code !== 0)
|
|
481
485
|
throw new AgentError(
|
|
482
|
-
`git ${args[0]} failed
|
|
486
|
+
`git ${args[0]} failed in ${dir}: ${scrubDiagnostic(err) || `exit ${code}`}`,
|
|
483
487
|
);
|
|
484
488
|
return out;
|
|
485
489
|
}
|
package/src/worker-entry.ts
CHANGED
|
@@ -112,7 +112,7 @@ async function run({
|
|
|
112
112
|
model: job.model,
|
|
113
113
|
task: job.task,
|
|
114
114
|
signal: new AbortController().signal,
|
|
115
|
-
aborted: "
|
|
115
|
+
aborted: "the worker was stopped",
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
if (process.send) {
|
|
@@ -136,7 +136,14 @@ if (process.send) {
|
|
|
136
136
|
(report) => {
|
|
137
137
|
process.send?.({ type: "report", report }, () => process.exit(0));
|
|
138
138
|
},
|
|
139
|
-
() =>
|
|
139
|
+
(error: unknown) =>
|
|
140
|
+
process.send?.(
|
|
141
|
+
{
|
|
142
|
+
type: "failure",
|
|
143
|
+
message: error instanceof Error ? error.message : String(error),
|
|
144
|
+
},
|
|
145
|
+
() => process.exit(1),
|
|
146
|
+
),
|
|
140
147
|
);
|
|
141
148
|
}
|
|
142
149
|
});
|
package/src/worker-failure.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { AgentError } from "pi-roundtable/kit";
|
|
1
|
+
import { AgentError, scrubDiagnostic } from "pi-roundtable/kit";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** Worker diagnostics reach the owner scrubbed of credentials and bounded; stderr and raw responses never do. */
|
|
4
4
|
export class CodingWorkerFailure extends AgentError {
|
|
5
5
|
constructor(
|
|
6
6
|
readonly category: "stopped" | "exit" | "missing-report",
|
|
7
7
|
readonly exitCode?: number,
|
|
8
|
+
detail?: string,
|
|
8
9
|
) {
|
|
9
|
-
|
|
10
|
+
const reason = detail ? scrubDiagnostic(detail) : "";
|
|
11
|
+
let message = "the worker was stopped";
|
|
10
12
|
if (category === "exit")
|
|
11
|
-
message =
|
|
13
|
+
message =
|
|
14
|
+
reason ||
|
|
15
|
+
`The coding worker exited (code ${exitCode}); check its model, login and package configuration.`;
|
|
12
16
|
if (category === "missing-report")
|
|
13
17
|
message = "The coding worker returned no report.";
|
|
14
18
|
super(message);
|