@yusukeshib/pi-babysit 0.3.7 → 0.3.8
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/README.md +14 -9
- package/index.ts +255 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ reachable from anywhere (`~/.pi-babysit/<pi-session-id>/`). Two kinds:
|
|
|
32
32
|
|
|
33
33
|
| kind | started by | completion | on completion |
|
|
34
34
|
| ---- | ---------- | ---------- | ------------- |
|
|
35
|
-
| **process** | `babysit_run { command }` | process **exit** | automatic notification message (`triggerTurn`) — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
|
|
35
|
+
| **process** | `babysit_run { command }` | process **exit** | automatic notification message (`triggerTurn`), batched for all exits observed in the same poll — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
|
|
36
36
|
| **subagent** | `babysit_run { profile: "subagent", task }` | `agent_end` in the RPC event stream (process stays alive) | none — the agent polls `babysit_check` or blocks on `babysit_wait`; the idle session accepts follow-up tasks |
|
|
37
37
|
|
|
38
38
|
The **profile is a tool parameter, not a separate tool set**: domain knowledge
|
|
@@ -72,9 +72,12 @@ A minimal widget above the editor shows live counts
|
|
|
72
72
|
`babysit_run`, `babysit_wait`, and automatic completion notifications always
|
|
73
73
|
return lifecycle metadata and the absolute path to the complete `output.log`.
|
|
74
74
|
Explicit run/wait results inline complete output up to 8 KB; unsolicited
|
|
75
|
-
completion notifications use a stricter 2 KB cap
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
completion notifications use a stricter 2 KB per-process output cap and an 8 KB
|
|
76
|
+
aggregate message cap. Process exits observed in one poll share one message and
|
|
77
|
+
trigger one agent turn. If even the compact summaries and log paths exceed the
|
|
78
|
+
aggregate cap, the largest fitting prefix is delivered and the remainder stays
|
|
79
|
+
pending for the next poll. Larger output stays out of model context. Inspect it
|
|
80
|
+
through the session id without creating another shell session:
|
|
78
81
|
|
|
79
82
|
```text
|
|
80
83
|
babysit_check { id: "cargo-test", lines: 50 }
|
|
@@ -100,9 +103,10 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
100
103
|
## How completion detection works
|
|
101
104
|
|
|
102
105
|
- **Process**: a 2.5s poller watches for running→exited transitions and injects
|
|
103
|
-
one `pi.sendMessage(…, { triggerTurn: true, deliverAs: "steer" })`
|
|
104
|
-
|
|
105
|
-
reported by `babysit_wait` suppress the
|
|
106
|
+
one `pi.sendMessage(…, { triggerTurn: true, deliverAs: "steer" })` containing
|
|
107
|
+
every deliverable exit observed in that poll (deduped via `meta/<id>.json`).
|
|
108
|
+
`babysit_kill` and an exit already reported by `babysit_wait` suppress the
|
|
109
|
+
notification.
|
|
106
110
|
- **Subagent**: `babysit_wait` blocks on `babysit expect '"type":"agent_end"'`.
|
|
107
111
|
An `agent_end` whose last message is a **parked** toolResult — a
|
|
108
112
|
`babysit_run { command }` result carrying the `[notify-on-exit]` marker (or
|
|
@@ -126,8 +130,9 @@ rule, so a subagent waiting on a long build is never false-killed.
|
|
|
126
130
|
| `PI_BABYSIT_REAP_AFTER` | `120s` | idle grace before a finished subagent self-exits (`off`/`none`/`0` disables) |
|
|
127
131
|
| `PI_BABYSIT_TAIL_MAX_BYTES` | `8000` | cap for explicit log tails/screens returned by `babysit_check` |
|
|
128
132
|
| `PI_BABYSIT_INLINE_OUTPUT_MAX_BYTES` | `8000` | cap for complete output in explicitly requested run/wait results |
|
|
129
|
-
| `PI_BABYSIT_NOTIFY_OUTPUT_MAX_BYTES` | `2000` |
|
|
130
|
-
| `PI_BABYSIT_NOTIFY_COMMAND_MAX_BYTES` | `240` | cap for
|
|
133
|
+
| `PI_BABYSIT_NOTIFY_OUTPUT_MAX_BYTES` | `2000` | per-process output cap for unsolicited completion notifications (`0` omits all output) |
|
|
134
|
+
| `PI_BABYSIT_NOTIFY_COMMAND_MAX_BYTES` | `240` | cap for each command preview in completion notifications |
|
|
135
|
+
| `PI_BABYSIT_NOTIFY_BATCH_MAX_BYTES` | `8000` | hard cap for one aggregated completion notification |
|
|
131
136
|
| `PI_BABYSIT_ALLOW_BASH` | unset | set to `1` to bypass direct-Bash redirection (emergency escape hatch) |
|
|
132
137
|
|
|
133
138
|
Requires `babysit` 0.13.0 or newer and `pi` on `PATH`. The extension does **not**
|
package/index.ts
CHANGED
|
@@ -317,6 +317,16 @@ function readMeta(id: string): Meta | null {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
export function shouldDeliverProcessCompletion(
|
|
321
|
+
meta: {
|
|
322
|
+
kind: "process" | "subagent";
|
|
323
|
+
notified?: boolean;
|
|
324
|
+
notificationPaused?: boolean;
|
|
325
|
+
} | null,
|
|
326
|
+
): meta is { kind: "process"; notified?: boolean; notificationPaused?: boolean } {
|
|
327
|
+
return meta?.kind === "process" && !meta.notified && !meta.notificationPaused;
|
|
328
|
+
}
|
|
329
|
+
|
|
320
330
|
const kindOf = (id: string): "process" | "subagent" => readMeta(id)?.kind ?? "process";
|
|
321
331
|
|
|
322
332
|
// Compact elapsed formatting: "42s", "3m12s", "1h04m".
|
|
@@ -437,6 +447,7 @@ const TAIL_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_TAIL_MAX_BYTES", 8_000);
|
|
|
437
447
|
const INLINE_OUTPUT_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_INLINE_OUTPUT_MAX_BYTES", 8_000);
|
|
438
448
|
const NOTIFY_OUTPUT_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_NOTIFY_OUTPUT_MAX_BYTES", 2_000);
|
|
439
449
|
const NOTIFY_COMMAND_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_NOTIFY_COMMAND_MAX_BYTES", 240);
|
|
450
|
+
const NOTIFY_BATCH_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_NOTIFY_BATCH_MAX_BYTES", 8_000);
|
|
440
451
|
const ANSWER_MAX_BYTES = 24_000; // subagent answers / error messages
|
|
441
452
|
|
|
442
453
|
function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
|
|
@@ -560,6 +571,193 @@ export function summarizeNotificationCommand(command: string | undefined): strin
|
|
|
560
571
|
return `${prefix}…`;
|
|
561
572
|
}
|
|
562
573
|
|
|
574
|
+
type CompletionStatus = "success" | "failed" | "terminated";
|
|
575
|
+
|
|
576
|
+
export interface ProcessCompletionNotice {
|
|
577
|
+
id: string;
|
|
578
|
+
exitCode: number | null | undefined;
|
|
579
|
+
success: boolean;
|
|
580
|
+
status: CompletionStatus;
|
|
581
|
+
runtime: string;
|
|
582
|
+
summary: string;
|
|
583
|
+
command: string | undefined;
|
|
584
|
+
logPath: string;
|
|
585
|
+
output: string;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
interface ProcessCompletionMessage {
|
|
589
|
+
customType: "pi-babysit-process-end";
|
|
590
|
+
content: string;
|
|
591
|
+
display: true;
|
|
592
|
+
details: {
|
|
593
|
+
id?: string;
|
|
594
|
+
exitCode?: number | null;
|
|
595
|
+
success: boolean;
|
|
596
|
+
status: CompletionStatus;
|
|
597
|
+
runtime?: string;
|
|
598
|
+
logPath?: string;
|
|
599
|
+
count: number;
|
|
600
|
+
totalCount: number;
|
|
601
|
+
remainingCount: number;
|
|
602
|
+
processes: Array<{
|
|
603
|
+
id: string;
|
|
604
|
+
exitCode: number | null | undefined;
|
|
605
|
+
success: boolean;
|
|
606
|
+
status: CompletionStatus;
|
|
607
|
+
runtime: string;
|
|
608
|
+
logPath: string;
|
|
609
|
+
}>;
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const COMPLETION_FOOTER =
|
|
614
|
+
"Automatic completion notification. Inspect the bounded log with babysit_check only if needed.";
|
|
615
|
+
const AGGREGATE_OUTPUT_OMISSION = "\nOutput omitted from aggregate notification; inspect log.";
|
|
616
|
+
|
|
617
|
+
function truncateUtf8End(value: string, maxBytes: number): string {
|
|
618
|
+
if (maxBytes <= 0) return "";
|
|
619
|
+
const bytes = Buffer.from(value, "utf8");
|
|
620
|
+
if (bytes.length <= maxBytes) return value;
|
|
621
|
+
const suffix = Buffer.from("…", "utf8");
|
|
622
|
+
if (maxBytes <= suffix.length) return ".".repeat(maxBytes);
|
|
623
|
+
const prefix = bytes
|
|
624
|
+
.subarray(0, maxBytes - suffix.length)
|
|
625
|
+
.toString("utf8")
|
|
626
|
+
.replace(/\uFFFD+$/, "");
|
|
627
|
+
return `${prefix}…`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** Build one bounded message for as many deliverable completions as fit. */
|
|
631
|
+
export function buildProcessCompletionMessage(
|
|
632
|
+
notices: ProcessCompletionNotice[],
|
|
633
|
+
maxBytes = NOTIFY_BATCH_MAX_BYTES,
|
|
634
|
+
): ProcessCompletionMessage {
|
|
635
|
+
if (notices.length === 0) throw new Error("At least one completion notice is required.");
|
|
636
|
+
|
|
637
|
+
const totalCount = notices.length;
|
|
638
|
+
const footer = `\n\n${COMPLETION_FOOTER}`;
|
|
639
|
+
const header = (count: number) =>
|
|
640
|
+
totalCount === 1
|
|
641
|
+
? ""
|
|
642
|
+
: count === totalCount
|
|
643
|
+
? `${count} processes completed:\n\n`
|
|
644
|
+
: `${count} of ${totalCount} processes completed:\n\n`;
|
|
645
|
+
const deferred = (count: number) =>
|
|
646
|
+
count < totalCount
|
|
647
|
+
? `\n\n${totalCount - count} completion${totalCount - count === 1 ? "" : "s"} deferred to the next poll.`
|
|
648
|
+
: "";
|
|
649
|
+
const renderCompact = (batch: ProcessCompletionNotice[]) =>
|
|
650
|
+
header(batch.length) +
|
|
651
|
+
batch.map((notice) => `${notice.summary}\nLog: ${notice.logPath}`).join("\n\n") +
|
|
652
|
+
deferred(batch.length) +
|
|
653
|
+
footer;
|
|
654
|
+
|
|
655
|
+
// If every summary and log path cannot fit, notify the largest fitting prefix
|
|
656
|
+
// and leave the rest unacknowledged for a later poll. Always make progress for
|
|
657
|
+
// pathological single ids/paths by delivering one UTF-8-truncated entry.
|
|
658
|
+
let batch = notices;
|
|
659
|
+
if (Buffer.byteLength(renderCompact(batch), "utf8") > maxBytes) {
|
|
660
|
+
let low = 1;
|
|
661
|
+
let high = notices.length - 1;
|
|
662
|
+
let fittedCount = 0;
|
|
663
|
+
while (low <= high) {
|
|
664
|
+
const mid = Math.floor((low + high) / 2);
|
|
665
|
+
if (Buffer.byteLength(renderCompact(notices.slice(0, mid)), "utf8") <= maxBytes) {
|
|
666
|
+
fittedCount = mid;
|
|
667
|
+
low = mid + 1;
|
|
668
|
+
} else {
|
|
669
|
+
high = mid - 1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
batch = fittedCount > 0 ? notices.slice(0, fittedCount) : [notices[0]];
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const blockBase = (notice: ProcessCompletionNotice) =>
|
|
676
|
+
`${notice.summary}\nCommand: ${summarizeNotificationCommand(notice.command)}\nLog: ${notice.logPath}`;
|
|
677
|
+
const outputs = batch.map((notice) =>
|
|
678
|
+
notice.output.startsWith("\n\nOutput:") ? AGGREGATE_OUTPUT_OMISSION : notice.output,
|
|
679
|
+
);
|
|
680
|
+
const renderDetailed = () =>
|
|
681
|
+
header(batch.length) +
|
|
682
|
+
batch.map((notice, i) => blockBase(notice) + outputs[i]).join("\n\n") +
|
|
683
|
+
deferred(batch.length) +
|
|
684
|
+
footer;
|
|
685
|
+
|
|
686
|
+
let content = renderDetailed();
|
|
687
|
+
let contentBytes = Buffer.byteLength(content, "utf8");
|
|
688
|
+
if (contentBytes <= maxBytes) {
|
|
689
|
+
// Spend the remaining aggregate budget on complete inline outputs without
|
|
690
|
+
// repeatedly rebuilding the entire batch for every candidate.
|
|
691
|
+
for (let i = 0; i < batch.length; i++) {
|
|
692
|
+
if (!batch[i].output.startsWith("\n\nOutput:")) continue;
|
|
693
|
+
const delta =
|
|
694
|
+
Buffer.byteLength(batch[i].output, "utf8") - Buffer.byteLength(outputs[i], "utf8");
|
|
695
|
+
if (contentBytes + delta > maxBytes) continue;
|
|
696
|
+
outputs[i] = batch[i].output;
|
|
697
|
+
contentBytes += delta;
|
|
698
|
+
}
|
|
699
|
+
content = renderDetailed();
|
|
700
|
+
} else {
|
|
701
|
+
content = truncateUtf8End(renderCompact(batch), maxBytes);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const statuses = new Set(batch.map((notice) => notice.status));
|
|
705
|
+
const status: CompletionStatus = statuses.has("failed")
|
|
706
|
+
? "failed"
|
|
707
|
+
: statuses.has("terminated")
|
|
708
|
+
? "terminated"
|
|
709
|
+
: "success";
|
|
710
|
+
const processes = batch.map((notice) => ({
|
|
711
|
+
id: notice.id,
|
|
712
|
+
exitCode: notice.exitCode,
|
|
713
|
+
success: notice.success,
|
|
714
|
+
status: notice.status,
|
|
715
|
+
runtime: notice.runtime,
|
|
716
|
+
logPath: notice.logPath,
|
|
717
|
+
}));
|
|
718
|
+
const single = totalCount === 1 ? batch[0] : undefined;
|
|
719
|
+
return {
|
|
720
|
+
customType: "pi-babysit-process-end",
|
|
721
|
+
content,
|
|
722
|
+
display: true,
|
|
723
|
+
details: {
|
|
724
|
+
id: single?.id,
|
|
725
|
+
exitCode: single?.exitCode,
|
|
726
|
+
success: batch.every((notice) => notice.success),
|
|
727
|
+
status,
|
|
728
|
+
runtime: single?.runtime,
|
|
729
|
+
logPath: single?.logPath,
|
|
730
|
+
count: batch.length,
|
|
731
|
+
totalCount,
|
|
732
|
+
remainingCount: totalCount - batch.length,
|
|
733
|
+
processes,
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Send once and only acknowledge notices represented in the accepted batch. */
|
|
739
|
+
export function deliverProcessCompletionMessage(
|
|
740
|
+
notices: ProcessCompletionNotice[],
|
|
741
|
+
send: (
|
|
742
|
+
message: ProcessCompletionMessage,
|
|
743
|
+
options: { triggerTurn: true; deliverAs: "steer" },
|
|
744
|
+
) => void,
|
|
745
|
+
onSent: (notice: ProcessCompletionNotice) => void,
|
|
746
|
+
): boolean {
|
|
747
|
+
if (notices.length === 0) return false;
|
|
748
|
+
let message: ProcessCompletionMessage;
|
|
749
|
+
try {
|
|
750
|
+
message = buildProcessCompletionMessage(notices);
|
|
751
|
+
send(message, { triggerTurn: true, deliverAs: "steer" });
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
const deliveredIds = new Set(message.details.processes.map(({ id }) => id));
|
|
756
|
+
for (const notice of notices) {
|
|
757
|
+
if (deliveredIds.has(notice.id)) onSent(notice);
|
|
758
|
+
}
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
563
761
|
|
|
564
762
|
// ---------------------------------------------------------------------------
|
|
565
763
|
// parked-turn detection (shared rule with self-reap.ts)
|
|
@@ -1289,78 +1487,81 @@ export default function (pi: ExtensionAPI) {
|
|
|
1289
1487
|
let pollTimer: ReturnType<typeof setInterval> | undefined;
|
|
1290
1488
|
|
|
1291
1489
|
// Exit notifications for kind=process sessions: the poller detects
|
|
1292
|
-
// running→exited transitions and injects ONE message (triggerTurn)
|
|
1293
|
-
//
|
|
1294
|
-
//
|
|
1295
|
-
// reported by babysit_wait are
|
|
1490
|
+
// running→exited transitions and injects ONE message (triggerTurn) for all
|
|
1491
|
+
// processes that became deliverable in the same poll. This resumes an agent
|
|
1492
|
+
// that ended its turn after babysit_run without spending one turn per exit.
|
|
1493
|
+
// Kills via babysit_kill and exits already reported by babysit_wait are
|
|
1494
|
+
// suppressed via meta.notified.
|
|
1296
1495
|
async function notifyEndedProcesses(): Promise<void> {
|
|
1297
1496
|
const { sessions } = await listSessions();
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1497
|
+
const ready: Array<{ session: BsSession; meta: Meta }> = [];
|
|
1498
|
+
for (const session of sessions) {
|
|
1499
|
+
if (session.state === "running") continue;
|
|
1500
|
+
const meta = readMeta(session.id);
|
|
1501
|
+
if (!shouldDeliverProcessCompletion(meta)) continue;
|
|
1302
1502
|
// Delay delivery by one poll interval. This gives an agent that chose
|
|
1303
1503
|
// babysit_wait immediately after babysit_run enough time to claim the
|
|
1304
1504
|
// completion and suppress the otherwise duplicate automatic message.
|
|
1305
1505
|
if (!meta.completionObservedAt) {
|
|
1306
1506
|
meta.completionObservedAt = Date.now();
|
|
1307
|
-
writeMeta(
|
|
1507
|
+
writeMeta(session.id, meta);
|
|
1308
1508
|
continue;
|
|
1309
1509
|
}
|
|
1310
1510
|
if (Date.now() - meta.completionObservedAt < POLL_MS) continue;
|
|
1311
|
-
|
|
1312
|
-
|
|
1511
|
+
ready.push({ session, meta });
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const prepared: ProcessCompletionNotice[] = [];
|
|
1515
|
+
for (const { session, meta } of ready) {
|
|
1516
|
+
const ok = session.exit_code === 0;
|
|
1517
|
+
const status: CompletionStatus = ok
|
|
1313
1518
|
? "success"
|
|
1314
|
-
:
|
|
1519
|
+
: session.state === "dead" || session.exit_code == null
|
|
1315
1520
|
? "terminated"
|
|
1316
1521
|
: "failed";
|
|
1317
|
-
const output = await inlineOutput(
|
|
1522
|
+
const output = await inlineOutput(session.id, session, NOTIFY_OUTPUT_MAX_BYTES);
|
|
1318
1523
|
const runtime = meta.startedAt
|
|
1319
1524
|
? `${Math.round((Date.now() - meta.startedAt) / 1000)}s`
|
|
1320
1525
|
: "?";
|
|
1321
1526
|
const summary = ok
|
|
1322
|
-
? `Process "${
|
|
1323
|
-
:
|
|
1324
|
-
? `Process "${
|
|
1325
|
-
: `Process "${
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
}
|
|
1337
|
-
try {
|
|
1338
|
-
pi.sendMessage(
|
|
1339
|
-
{
|
|
1340
|
-
customType: "pi-babysit-process-end",
|
|
1341
|
-
content:
|
|
1342
|
-
`${summary}\nCommand: ${summarizeNotificationCommand(current.command)}\nLog: ${logPath(s.id)}${output}` +
|
|
1343
|
-
"\n\nAutomatic completion notification. Inspect the bounded log with babysit_check only if needed.",
|
|
1344
|
-
display: true,
|
|
1345
|
-
details: {
|
|
1346
|
-
id: s.id,
|
|
1347
|
-
exitCode: s.exit_code,
|
|
1348
|
-
success: ok,
|
|
1349
|
-
status,
|
|
1350
|
-
runtime,
|
|
1351
|
-
logPath: logPath(s.id),
|
|
1352
|
-
},
|
|
1353
|
-
},
|
|
1354
|
-
{ triggerTurn: true, deliverAs: "steer" },
|
|
1355
|
-
);
|
|
1356
|
-
} catch {
|
|
1357
|
-
// Leave it pending so the next poll can retry delivery.
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
current.notified = true;
|
|
1361
|
-
delete current.notificationPaused;
|
|
1362
|
-
writeMeta(s.id, current);
|
|
1527
|
+
? `Process "${session.id}" completed successfully after ${runtime}.`
|
|
1528
|
+
: session.state === "dead" || session.exit_code == null
|
|
1529
|
+
? `Process "${session.id}" was terminated after ${runtime}.`
|
|
1530
|
+
: `Process "${session.id}" exited with code ${session.exit_code} after ${runtime}.`;
|
|
1531
|
+
prepared.push({
|
|
1532
|
+
id: session.id,
|
|
1533
|
+
exitCode: session.exit_code,
|
|
1534
|
+
success: ok,
|
|
1535
|
+
status,
|
|
1536
|
+
runtime,
|
|
1537
|
+
summary,
|
|
1538
|
+
command: meta.command,
|
|
1539
|
+
logPath: logPath(session.id),
|
|
1540
|
+
output,
|
|
1541
|
+
});
|
|
1363
1542
|
}
|
|
1543
|
+
|
|
1544
|
+
// Output loading above is asynchronous. Re-read every candidate together
|
|
1545
|
+
// immediately before the single send so a concurrent kill/wait cannot claim
|
|
1546
|
+
// an early candidate while a later candidate's output is being loaded.
|
|
1547
|
+
const metadataById = new Map<string, Meta>();
|
|
1548
|
+
const notices = prepared.flatMap((notice) => {
|
|
1549
|
+
const current = readMeta(notice.id);
|
|
1550
|
+
if (!shouldDeliverProcessCompletion(current)) return [];
|
|
1551
|
+
metadataById.set(notice.id, current);
|
|
1552
|
+
return [{ ...notice, command: current.command }];
|
|
1553
|
+
});
|
|
1554
|
+
deliverProcessCompletionMessage(
|
|
1555
|
+
notices,
|
|
1556
|
+
(message, options) => pi.sendMessage(message, options),
|
|
1557
|
+
(notice) => {
|
|
1558
|
+
const meta = metadataById.get(notice.id);
|
|
1559
|
+
if (!meta) return;
|
|
1560
|
+
meta.notified = true;
|
|
1561
|
+
delete meta.notificationPaused;
|
|
1562
|
+
writeMeta(notice.id, meta);
|
|
1563
|
+
},
|
|
1564
|
+
);
|
|
1364
1565
|
}
|
|
1365
1566
|
|
|
1366
1567
|
const refreshWidget = async (ctx: ExtensionContext) => {
|