viber-channel 0.8.12 → 0.8.13
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/lib/runner_exec.ts +68 -3
- package/lib/spawn_reason.ts +17 -1
- package/package.json +1 -1
package/lib/runner_exec.ts
CHANGED
|
@@ -291,6 +291,49 @@ export interface ExecDeps {
|
|
|
291
291
|
writeSpec?: SpecFileWriter;
|
|
292
292
|
/** Injectable local-log writer (tests). Returns the log path. */
|
|
293
293
|
writeLog?: (commandId: string, recap: string) => string;
|
|
294
|
+
/** Injectable daemon-stderr writer (tests). Defaults to process.stderr. */
|
|
295
|
+
writeDaemonLine?: (line: string) => void;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Where the log lives, as shown to whoever reads the WEB (#506): RELATIVE, so
|
|
299
|
+
* it names the file without carrying the machine's absolute path — home
|
|
300
|
+
* directory and user name included — to the server. The absolute path stays on
|
|
301
|
+
* the daemon's own stderr, which never leaves the machine. */
|
|
302
|
+
export function runnerLogRef(commandId: string): string {
|
|
303
|
+
return `.viber/runner-logs/${commandId}.log`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** What `writeRunnerLog` returns when it could NOT write (unwritable dir, full
|
|
307
|
+
* disk). Named, because the remote message must not point at a file that was
|
|
308
|
+
* never created. */
|
|
309
|
+
export const LOG_UNAVAILABLE = "(local log unavailable)";
|
|
310
|
+
|
|
311
|
+
/** The log reference to put in the REMOTE message, or undefined when no log
|
|
312
|
+
* exists (#506 review, both reviewers): pointing someone at
|
|
313
|
+
* `.viber/runner-logs/<id>.log` when the write failed would be this issue in
|
|
314
|
+
* miniature — a component asserting more than it knows, on the very sentence
|
|
315
|
+
* this step fixes. Callers fall back to the no-path wording. */
|
|
316
|
+
export function remoteLogRef(commandId: string, logPath: string): string | undefined {
|
|
317
|
+
return logPath === LOG_UNAVAILABLE ? undefined : runnerLogRef(commandId);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The one line of a recap worth putting on the daemon's stderr: its first
|
|
321
|
+
* `error:` line, bounded. Already redacted upstream by `redactSecrets`. Falls
|
|
322
|
+
* back to the first non-empty line when the CLI printed no `error:` line, so a
|
|
323
|
+
* failure is never reported as silence.
|
|
324
|
+
*
|
|
325
|
+
* The result must be ONE line: it is written with a trailing newline onto a
|
|
326
|
+
* shared stream, so any control character surviving inside it could forge a
|
|
327
|
+
* second log line. Hence splitting on a LONE `\r` too (Codex review — a bare CR
|
|
328
|
+
* is what a progress-bar writer emits), and stripping the remaining C0/C1
|
|
329
|
+
* controls rather than trusting the split alone. */
|
|
330
|
+
export function firstErrorLine(recap: string, max = 300): string {
|
|
331
|
+
const lines = recap
|
|
332
|
+
.split(/\r\n|\r|\n/)
|
|
333
|
+
.map((l) => l.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").trim())
|
|
334
|
+
.filter(Boolean);
|
|
335
|
+
const line = lines.find((l) => l.startsWith("error:")) ?? lines[0] ?? "(no output)";
|
|
336
|
+
return line.slice(0, max);
|
|
294
337
|
}
|
|
295
338
|
|
|
296
339
|
/** Write the full launch recap to a 0600 LOCAL log (never sent to the server —
|
|
@@ -303,7 +346,7 @@ export function writeRunnerLog(commandId: string, recap: string, cwd: string = p
|
|
|
303
346
|
mkdirSync(dir, { recursive: true });
|
|
304
347
|
writeFileSync(path, recap, { mode: 0o600 });
|
|
305
348
|
} catch {
|
|
306
|
-
return
|
|
349
|
+
return LOG_UNAVAILABLE;
|
|
307
350
|
}
|
|
308
351
|
return path;
|
|
309
352
|
}
|
|
@@ -447,6 +490,21 @@ export async function executeClaim(
|
|
|
447
490
|
// marker (older vibe-master, output lost to maxBuffer) we degrade to exactly
|
|
448
491
|
// the previous message rather than inventing a cause.
|
|
449
492
|
const logPath = deps.writeLog ? deps.writeLog(cmd.id, recap) : writeRunnerLog(cmd.id, recap);
|
|
493
|
+
// #506, the issue's second defect: the remote message sent people to "the
|
|
494
|
+
// machine's log", and the log they actually open — the daemon's — only ever
|
|
495
|
+
// said "reconcile: handled 1 command(s)". A failure now states itself where
|
|
496
|
+
// it is read FIRST. This stream is local, so the ABSOLUTE path belongs here;
|
|
497
|
+
// what travels to the server is the relative ref below.
|
|
498
|
+
if (!ok) {
|
|
499
|
+
const writeDaemonLine =
|
|
500
|
+
deps.writeDaemonLine ?? ((line: string) => process.stderr.write(`${line}\n`));
|
|
501
|
+
writeDaemonLine(
|
|
502
|
+
`[runner] spawn FAILED command=${cmd.id} reason=${reason ?? "unknown"} log=${logPath} ${firstErrorLine(recap)}`,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
// Resolved ONCE: three call sites in a ternary could drift apart under a
|
|
506
|
+
// later edit, and they must agree on whether a log exists (Opus review).
|
|
507
|
+
const logRef = remoteLogRef(cmd.id, logPath);
|
|
450
508
|
const finalStatus = await report(
|
|
451
509
|
auth,
|
|
452
510
|
cmd.id,
|
|
@@ -456,8 +514,15 @@ export async function executeClaim(
|
|
|
456
514
|
fencing_token: cmd.fencing_token,
|
|
457
515
|
status: "failed",
|
|
458
516
|
error: reason
|
|
459
|
-
? messageForReason(
|
|
460
|
-
|
|
517
|
+
? messageForReason(
|
|
518
|
+
reason,
|
|
519
|
+
detail,
|
|
520
|
+
rosterNames(cmd.prefix, cmd.template_spec.roles),
|
|
521
|
+
logRef,
|
|
522
|
+
)
|
|
523
|
+
: logRef
|
|
524
|
+
? `team spawn failed — see ${logRef} on that machine`
|
|
525
|
+
: "team spawn failed, and the local log could not be written on that machine",
|
|
461
526
|
...(reason ? { reason } : {}),
|
|
462
527
|
},
|
|
463
528
|
fetchImpl,
|
package/lib/spawn_reason.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const SPAWN_REASONS = [
|
|
|
22
22
|
"already_running",
|
|
23
23
|
"partial_team",
|
|
24
24
|
"template_invalid",
|
|
25
|
+
"roster_invalid",
|
|
25
26
|
"codex_unavailable",
|
|
26
27
|
"launch_failed",
|
|
27
28
|
// Emitted by the runner itself
|
|
@@ -176,6 +177,7 @@ export function messageForReason(
|
|
|
176
177
|
reason: SpawnReason,
|
|
177
178
|
detail?: string,
|
|
178
179
|
roster: readonly string[],
|
|
180
|
+
logRef?: string,
|
|
179
181
|
): string {
|
|
180
182
|
const safe = keepRosterNames(detail, roster);
|
|
181
183
|
const names = safe ? ` (${safe})` : "";
|
|
@@ -197,7 +199,21 @@ export function messageForReason(
|
|
|
197
199
|
// passes --spec-file. So this means the server's own snapshot could not be
|
|
198
200
|
// read back — a defect. Telling the user to go read a log they cannot open,
|
|
199
201
|
// for a problem they did not cause, would repeat this issue in miniature.
|
|
200
|
-
|
|
202
|
+
// #506, second defect of the issue: this sentence sent the reader to "the
|
|
203
|
+
// machine's log" WITHOUT naming it — and the log they did open, the
|
|
204
|
+
// daemon's, said nothing about the failure. `logRef` is the RELATIVE path
|
|
205
|
+
// (`.viber/runner-logs/<command_id>.log`): it locates the file without
|
|
206
|
+
// shipping an absolute Windows path — hence the machine's user name — to
|
|
207
|
+
// the server and onto someone's screen.
|
|
208
|
+
return `the roster sent for this spawn could not be read back on the runner machine — this is likely a defect, not something you did wrong; the full error is in ${logRef ?? "the machine's log"} on that machine`;
|
|
209
|
+
case "roster_invalid":
|
|
210
|
+
// #506: the OTHER half of what template_invalid used to swallow. The
|
|
211
|
+
// snapshot was read fine — it is the ROSTER that cannot be launched as
|
|
212
|
+
// written, and that IS the template author's to fix. Two catches, two
|
|
213
|
+
// reasons: this one says "you can fix this", template_invalid says "this
|
|
214
|
+
// is a defect on our side". Saying the second for the first sends someone
|
|
215
|
+
// to read a log for a mistake they could correct in two clicks.
|
|
216
|
+
return `this team template cannot be launched as written${names} — fix the template, then spawn again`;
|
|
201
217
|
case "codex_unavailable":
|
|
202
218
|
return "codex is not launchable on this machine, so no agent was started — install codex or set VIBER_CODEX_BIN";
|
|
203
219
|
case "launch_failed":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viber-channel",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "Voice + text MCP channel between a Claude Code session and the Viber UI (https://viber.dgypx.dev). Push transcripts to Claude; send_message tool delivers text back to the UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|