@trim21/personal-pi-extensions 0.1.612 → 0.1.613

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.
Files changed (75) hide show
  1. package/package.json +1 -1
  2. package/src/aft/config.ts +9 -3
  3. package/src/aft/index.ts +6 -2
  4. package/src/aft/logger.ts +9 -3
  5. package/src/aft/tools.ts +3 -1
  6. package/src/bwrap/approval-rules.ts +42 -14
  7. package/src/bwrap/approval-suggest.ts +12 -4
  8. package/src/bwrap/core.ts +39 -13
  9. package/src/bwrap/dcg-scan.ts +16 -6
  10. package/src/bwrap/holder.ts +3 -1
  11. package/src/bwrap/mihomo-config.ts +18 -6
  12. package/src/bwrap/network-stack.ts +30 -10
  13. package/src/bwrap/runtime.ts +30 -10
  14. package/src/claude-code/common.ts +6 -2
  15. package/src/claude-code/edit-utils.ts +3 -1
  16. package/src/claude-code/files.ts +40 -14
  17. package/src/claude-code/glob.ts +12 -4
  18. package/src/claude-code/grep.ts +41 -14
  19. package/src/claude-code/session-tools.ts +18 -6
  20. package/src/claude-code/shell.ts +9 -3
  21. package/src/gh/base.ts +72 -24
  22. package/src/gh/tools/download-release-assets.ts +12 -4
  23. package/src/gh/tools/list-releases.ts +3 -1
  24. package/src/gh/tools/list-workflow-runs.ts +9 -3
  25. package/src/gh/tools/read-ci-logs.ts +21 -7
  26. package/src/gh/tools/read-repo.ts +3 -1
  27. package/src/lib/cli-args.ts +6 -2
  28. package/src/lib/cli.ts +48 -16
  29. package/src/lib/file-reads.ts +24 -8
  30. package/src/lib/github.ts +35 -12
  31. package/src/lib/lsp/adapter.ts +6 -2
  32. package/src/lib/lsp/bin.ts +12 -4
  33. package/src/lib/lsp/client.ts +205 -69
  34. package/src/lib/lsp/diagnostic.ts +6 -2
  35. package/src/lib/lsp/inspect-tool.ts +25 -9
  36. package/src/lib/lsp/lsp.ts +138 -46
  37. package/src/lib/lsp/rename-tool.ts +11 -4
  38. package/src/lib/lsp/rename.ts +25 -8
  39. package/src/lib/lsp/server-config.ts +41 -14
  40. package/src/lib/lsp/watcher.ts +51 -17
  41. package/src/lib/path.ts +18 -6
  42. package/src/lib/proc.ts +3 -1
  43. package/src/lib/proxy.ts +9 -3
  44. package/src/lib/request-policy.ts +3 -1
  45. package/src/lib/seq-state.ts +3 -1
  46. package/src/lib/ui.ts +23 -8
  47. package/src/lib/write-guard.ts +21 -7
  48. package/src/openai-cost/config.ts +3 -1
  49. package/src/openai-cost/cost.ts +54 -18
  50. package/src/openai-cost/index.ts +3 -1
  51. package/src/openai-cost/provider.ts +6 -2
  52. package/src/opencode/bash.ts +9 -3
  53. package/src/opencode/edit-engine.ts +24 -8
  54. package/src/opencode/files.ts +30 -10
  55. package/src/opencode/glob.ts +3 -1
  56. package/src/opencode/grep.ts +12 -4
  57. package/src/opencode/question.ts +3 -1
  58. package/src/opencode/ripgrep.ts +18 -6
  59. package/src/opencode/todo.ts +3 -1
  60. package/src/session-name.ts +39 -13
  61. package/src/spawn-agent-agents.ts +18 -6
  62. package/src/spawn-agent.ts +53 -18
  63. package/src/system-prompt/index.ts +9 -3
  64. package/src/talk/core.ts +143 -48
  65. package/src/talk/format.ts +12 -4
  66. package/src/talk/group.ts +9 -3
  67. package/src/talk/index.ts +30 -10
  68. package/src/talk/mailbox.ts +30 -10
  69. package/src/talk/policy.ts +6 -2
  70. package/src/talk/registry.ts +33 -11
  71. package/src/talk/storage.ts +3 -1
  72. package/src/vision-agent.ts +41 -14
  73. package/src/web/config.ts +3 -1
  74. package/src/web/fetch.ts +75 -25
  75. package/src/web/search.ts +30 -10
package/src/talk/core.ts CHANGED
@@ -102,12 +102,20 @@ export const TALK_JOIN_ENTRY_TYPE = "talk:join";
102
102
  export function restoreTalkAgentId(branchEntries: readonly unknown[]): string | undefined {
103
103
  let agentId: string | undefined;
104
104
  for (const entry of branchEntries) {
105
- if (!isRecord(entry)) continue;
105
+ if (!isRecord(entry)) {
106
+ continue;
107
+ }
106
108
  const { type, customType, data } = entry;
107
- if (type !== "custom" || customType !== TALK_JOIN_ENTRY_TYPE) continue;
108
- if (!isRecord(data)) continue;
109
+ if (type !== "custom" || customType !== TALK_JOIN_ENTRY_TYPE) {
110
+ continue;
111
+ }
112
+ if (!isRecord(data)) {
113
+ continue;
114
+ }
109
115
  const recorded = data.agentId;
110
- if (typeof recorded === "string" && recorded.length > 0) agentId = recorded;
116
+ if (typeof recorded === "string" && recorded.length > 0) {
117
+ agentId = recorded;
118
+ }
111
119
  }
112
120
  return agentId;
113
121
  }
@@ -161,7 +169,9 @@ export class TalkCore {
161
169
 
162
170
  private requireSelf(): AgentRecord {
163
171
  const self = this.self;
164
- if (!self) throw new Error("Talk core is not started");
172
+ if (!self) {
173
+ throw new Error("Talk core is not started");
174
+ }
165
175
  return self;
166
176
  }
167
177
 
@@ -201,10 +211,18 @@ export class TalkCore {
201
211
  }
202
212
 
203
213
  async stop(): Promise<void> {
204
- if (this.initialDrain) clearTimeout(this.initialDrain);
205
- if (this.watchPoller) clearInterval(this.watchPoller);
206
- if (this.inboxPoll) clearInterval(this.inboxPoll);
207
- if (this.sweeper) clearInterval(this.sweeper);
214
+ if (this.initialDrain) {
215
+ clearTimeout(this.initialDrain);
216
+ }
217
+ if (this.watchPoller) {
218
+ clearInterval(this.watchPoller);
219
+ }
220
+ if (this.inboxPoll) {
221
+ clearInterval(this.inboxPoll);
222
+ }
223
+ if (this.sweeper) {
224
+ clearInterval(this.sweeper);
225
+ }
208
226
  if (this.self) {
209
227
  try {
210
228
  await this.writeSelf({ status: "idle", offline: true });
@@ -232,7 +250,9 @@ export class TalkCore {
232
250
  }
233
251
 
234
252
  private async writeSelf(patch: Partial<AgentRecord>): Promise<void> {
235
- if (!this.self) return;
253
+ if (!this.self) {
254
+ return;
255
+ }
236
256
  // A dead agent pins lastSeenAt to 0 so no later event re-freshens it.
237
257
  this.self = { ...this.self, ...patch, lastSeenAt: this.dead ? 0 : this.now() };
238
258
  try {
@@ -247,7 +267,9 @@ export class TalkCore {
247
267
  * 该动作下次成功后再失败会重新上报。通知通道本身出错也不能逃逸。
248
268
  */
249
269
  private notifyBackgroundFailure(action: string, error: unknown): void {
250
- if (this.backgroundFailuresReported.has(action)) return;
270
+ if (this.backgroundFailuresReported.has(action)) {
271
+ return;
272
+ }
251
273
  this.backgroundFailuresReported.add(action);
252
274
  const detail = error instanceof Error ? error.message : String(error);
253
275
  try {
@@ -271,7 +293,9 @@ export class TalkCore {
271
293
  }
272
294
 
273
295
  private startInboxPoll(): void {
274
- if (this.inboxPoll) return;
296
+ if (this.inboxPoll) {
297
+ return;
298
+ }
275
299
  this.inboxPoll = setInterval(() => {
276
300
  this.runInBackground("inbox poll", () => this.checkInbox());
277
301
  }, INBOX_POLL_MS);
@@ -283,11 +307,17 @@ export class TalkCore {
283
307
  /** Drain the inbox and hand each letter to the adapter. Public so tests can drive it. */
284
308
  async checkInbox(): Promise<void> {
285
309
  const self = this.self;
286
- if (!self) return;
287
- if (this.now() - this.lastDeliveryFailureAt < DELIVERY_BACKOFF_MS) return;
310
+ if (!self) {
311
+ return;
312
+ }
313
+ if (this.now() - this.lastDeliveryFailureAt < DELIVERY_BACKOFF_MS) {
314
+ return;
315
+ }
288
316
  // Refuse mode: never drain — letters stay queued (receipts honestly read
289
317
  // 'queued') instead of being silently consumed and dropped.
290
- if (!inboundAccepts()) return;
318
+ if (!inboundAccepts()) {
319
+ return;
320
+ }
291
321
  const items = await listInbox(this.storage, self.addr);
292
322
  for (const item of items) {
293
323
  if (this.deliveredIds.has(item.letter.id)) {
@@ -388,7 +418,9 @@ export class TalkCore {
388
418
  const memberIds = await this.myGroupMemberIds();
389
419
  const others = records.filter((r) => r.addr !== self.addr && memberIds?.has(r.agentId));
390
420
  const target = others.find((r) => r.agentId === to);
391
- if (!target) return { ok: false, error: refusalUnknown(to) };
421
+ if (!target) {
422
+ return { ok: false, error: refusalUnknown(to) };
423
+ }
392
424
  return { ok: true, record: target };
393
425
  }
394
426
 
@@ -404,7 +436,9 @@ export class TalkCore {
404
436
  ? 0
405
437
  : await unreadCount(this.storage, target.addr);
406
438
  const verdict = this.policy.check(body, backlog, target.addr);
407
- if (!verdict.ok) return { ok: false, error: verdict.reason };
439
+ if (!verdict.ok) {
440
+ return { ok: false, error: verdict.reason };
441
+ }
408
442
  const letter: Letter = {
409
443
  id: newMessageId(),
410
444
  from: { addr: self.addr, name: self.name, cwd: self.cwd, agentId: self.agentId },
@@ -448,8 +482,11 @@ export class TalkCore {
448
482
  );
449
483
  const onAbort = () => settle({ reason: "aborted" });
450
484
  this.askWaiters.set(askId, settle);
451
- if (signal?.aborted) onAbort();
452
- else signal?.addEventListener("abort", onAbort, { once: true });
485
+ if (signal?.aborted) {
486
+ onAbort();
487
+ } else {
488
+ signal?.addEventListener("abort", onAbort, { once: true });
489
+ }
453
490
  });
454
491
  }
455
492
 
@@ -458,7 +495,9 @@ export class TalkCore {
458
495
  const self = this.requireSelf();
459
496
  for (const askId of await outgoingAskIds(this.storage, self.addr)) {
460
497
  const out = await readOutgoingAsk(this.storage, self.addr, askId);
461
- if (out && out.toAddr === toAddr) return out;
498
+ if (out && out.toAddr === toAddr) {
499
+ return out;
500
+ }
462
501
  }
463
502
  return undefined;
464
503
  }
@@ -480,14 +519,20 @@ export class TalkCore {
480
519
  private async resolveInterlock(letter: Letter): Promise<void> {
481
520
  const self = this.requireSelf();
482
521
  const myAsk = await this.findOutAskTo(letter.from.addr);
483
- if (!myAsk) return;
522
+ if (!myAsk) {
523
+ return;
524
+ }
484
525
  const waiter = this.askWaiters.get(myAsk.askId);
485
- if (!waiter) return;
526
+ if (!waiter) {
527
+ return;
528
+ }
486
529
  const peerFirst = peerAskedFirst(
487
530
  { ts: letter.ts, cwd: letter.from.cwd, agentId: letter.from.agentId },
488
531
  { ts: myAsk.ts, cwd: self.cwd, agentId: self.agentId },
489
532
  );
490
- if (!peerFirst) return; // we asked first; keep waiting — the peer will yield
533
+ if (!peerFirst) {
534
+ return; // we asked first; keep waiting — the peer will yield
535
+ }
491
536
  this.askWaiters.delete(myAsk.askId);
492
537
  waiter({
493
538
  reason: `peer asked first (their ask id ${letter.id.slice(-8)}) — respond with a message before re-asking`,
@@ -508,7 +553,9 @@ export class TalkCore {
508
553
  const all = await listRecords(this.storage);
509
554
  const memberIds = await this.myGroupMemberIds();
510
555
  const records = all.filter((r) => {
511
- if (r.addr === self.addr) return !this.dead;
556
+ if (r.addr === self.addr) {
557
+ return !this.dead;
558
+ }
512
559
  return memberIds?.has(r.agentId) ?? false;
513
560
  });
514
561
  return formatListing(records, self.addr, presenceOf);
@@ -520,8 +567,12 @@ export class TalkCore {
520
567
  const records = await listRecords(this.storage);
521
568
  const memberIds = await this.myGroupMemberIds();
522
569
  const filtered = records.filter((r) => {
523
- if (r.cwd !== cwd) return false;
524
- if (r.addr === self.addr) return !this.dead;
570
+ if (r.cwd !== cwd) {
571
+ return false;
572
+ }
573
+ if (r.addr === self.addr) {
574
+ return !this.dead;
575
+ }
525
576
  return memberIds?.has(r.agentId) ?? false;
526
577
  });
527
578
  return formatListing(filtered, self.addr, presenceOf);
@@ -548,7 +599,9 @@ export class TalkCore {
548
599
  return "Marked this agent as dead.";
549
600
  }
550
601
  const resolved = await this.resolveTarget(target);
551
- if (!resolved.ok) return resolved.error;
602
+ if (!resolved.ok) {
603
+ return resolved.error;
604
+ }
552
605
  await writeRecord(this.storage, { ...resolved.record, lastSeenAt: 0, offline: true });
553
606
  return `Marked "${resolved.record.name}" as dead.`;
554
607
  }
@@ -568,7 +621,9 @@ export class TalkCore {
568
621
  private async leaveCurrentGroup(): Promise<boolean> {
569
622
  const self = this.requireSelf();
570
623
  const group = await groupForAgent(this.storage, self.agentId);
571
- if (!group) return false;
624
+ if (!group) {
625
+ return false;
626
+ }
572
627
  const others = group.members.filter((m) => m !== self.agentId);
573
628
  if (others.length === 0) {
574
629
  await deleteGroup(this.storage, group.id);
@@ -636,7 +691,9 @@ export class TalkCore {
636
691
  */
637
692
  async groupJoinLast(agentName?: string): Promise<string> {
638
693
  const groups = await listGroups(this.storage);
639
- if (groups.length === 0) return "No groups. Create one with /talk-group-join.";
694
+ if (groups.length === 0) {
695
+ return "No groups. Create one with /talk-group-join.";
696
+ }
640
697
  const latest = groups.reduce((a, b) => (b.createdAt > a.createdAt ? b : a));
641
698
  return this.groupJoin(latest.id, agentName);
642
699
  }
@@ -645,7 +702,9 @@ export class TalkCore {
645
702
  async groupLeave(): Promise<string> {
646
703
  const self = this.requireSelf();
647
704
  const group = await groupForAgent(this.storage, self.agentId);
648
- if (!group) return "Not in any group.";
705
+ if (!group) {
706
+ return "Not in any group.";
707
+ }
649
708
  const others = group.members.filter((m) => m !== self.agentId);
650
709
  if (others.length === 0) {
651
710
  await deleteGroup(this.storage, group.id);
@@ -660,9 +719,13 @@ export class TalkCore {
660
719
  /** Delete a group by name; its members become ungrouped. */
661
720
  async groupDelete(groupName: string): Promise<string> {
662
721
  const name = groupName.trim();
663
- if (!isValidGroupName(name)) return `Invalid group name '${name}'.`;
722
+ if (!isValidGroupName(name)) {
723
+ return `Invalid group name '${name}'.`;
724
+ }
664
725
  const group = await readGroup(this.storage, name);
665
- if (!group) return `Unknown group '${name}'. Run /talk-group-list to see groups.`;
726
+ if (!group) {
727
+ return `Unknown group '${name}'. Run /talk-group-list to see groups.`;
728
+ }
666
729
  await deleteGroup(this.storage, name);
667
730
  return `Deleted group ${name} (${group.members.length} member(s)).`;
668
731
  }
@@ -670,7 +733,9 @@ export class TalkCore {
670
733
  /** Delete every group; all agents become ungrouped. */
671
734
  async groupClear(): Promise<string> {
672
735
  const groups = await listGroups(this.storage);
673
- for (const group of groups) await deleteGroup(this.storage, group.id);
736
+ for (const group of groups) {
737
+ await deleteGroup(this.storage, group.id);
738
+ }
674
739
  return groups.length === 0 ? "No groups." : `Deleted ${groups.length} group(s).`;
675
740
  }
676
741
 
@@ -681,7 +746,9 @@ export class TalkCore {
681
746
  async groupList(): Promise<string> {
682
747
  const self = this.requireSelf();
683
748
  const groups = await listGroups(this.storage);
684
- if (groups.length === 0) return "No groups.";
749
+ if (groups.length === 0) {
750
+ return "No groups.";
751
+ }
685
752
  const records = await listRecords(this.storage);
686
753
  const label = (agentId: string): string => {
687
754
  const rec = records.find((r) => r.agentId === agentId);
@@ -719,32 +786,48 @@ export class TalkCore {
719
786
  async groupStatus(): Promise<string | undefined> {
720
787
  const self = this.requireSelf();
721
788
  const group = await groupForAgent(this.storage, self.agentId);
722
- if (!group) return undefined;
789
+ if (!group) {
790
+ return undefined;
791
+ }
723
792
  return self.alias ? `${self.alias}@${group.id}` : `@${group.id}`;
724
793
  }
725
794
 
726
795
  async send(to: string, body: string): Promise<string> {
727
- if (!to) return 'send requires "to".';
728
- if (!body) return 'send requires "message".';
796
+ if (!to) {
797
+ return 'send requires "to".';
798
+ }
799
+ if (!body) {
800
+ return 'send requires "message".';
801
+ }
729
802
  if (to === "*" || to === "cwd") {
730
803
  return "send requires a single agent id; broadcasting is disabled.";
731
804
  }
732
805
  const resolved = await this.resolveTarget(to);
733
- if (!resolved.ok) return resolved.error;
806
+ if (!resolved.ok) {
807
+ return resolved.error;
808
+ }
734
809
  const sent = await this.sendLetter(resolved.record, "message", body);
735
- if (!sent.ok) return sent.error;
810
+ if (!sent.ok) {
811
+ return sent.error;
812
+ }
736
813
  return `Sent to "${resolved.record.name}" (${shortAddr(resolved.record.addr)}) [id ${sent.letter.id.slice(-8)}]: ${sent.verdict}.`;
737
814
  }
738
815
 
739
816
  async ask(to: string, body: string, timeoutMs: number, signal?: AbortSignal): Promise<string> {
740
- if (!to) return 'ask requires "to".';
741
- if (!body) return 'ask requires "message".';
817
+ if (!to) {
818
+ return 'ask requires "to".';
819
+ }
820
+ if (!body) {
821
+ return 'ask requires "message".';
822
+ }
742
823
  if (to === "*" || to === "cwd") {
743
824
  return "ask is 1:1 and cannot broadcast.";
744
825
  }
745
826
  const self = this.requireSelf();
746
827
  const resolved = await this.resolveTarget(to);
747
- if (!resolved.ok) return resolved.error;
828
+ if (!resolved.ok) {
829
+ return resolved.error;
830
+ }
748
831
  const record = resolved.record;
749
832
  // Fast-path deadlock avoidance: if the target already sent us something,
750
833
  // answer them first instead of blocking on a fresh ask. This closes the
@@ -755,7 +838,9 @@ export class TalkCore {
755
838
  return `You have ${fromTarget.length} unread message(s) from "${record.name}" — respond to them before asking.`;
756
839
  }
757
840
  const sent = await this.sendLetter(record, "ask", body);
758
- if (!sent.ok) return sent.error;
841
+ if (!sent.ok) {
842
+ return sent.error;
843
+ }
759
844
  await trackOutgoingAsk(this.storage, self.addr, {
760
845
  askId: sent.letter.id,
761
846
  toAddr: record.addr,
@@ -776,16 +861,22 @@ export class TalkCore {
776
861
  // ── Presence watch ─────────────────────────────────────────────────────
777
862
 
778
863
  async watch(to: string): Promise<string> {
779
- if (!to) return "watch requires 'to' (a peer name or address prefix).";
864
+ if (!to) {
865
+ return "watch requires 'to' (a peer name or address prefix).";
866
+ }
780
867
  const resolved = await this.resolveTarget(to);
781
- if (!resolved.ok) return resolved.error;
868
+ if (!resolved.ok) {
869
+ return resolved.error;
870
+ }
782
871
  this.watched.set(resolved.record.addr, presenceOf(resolved.record));
783
872
  this.startWatchPoller();
784
873
  return `Watching "${resolved.record.name}" (${shortAddr(resolved.record.addr)}) for presence transitions. Notifications arrive as talk messages.`;
785
874
  }
786
875
 
787
876
  private startWatchPoller(): void {
788
- if (this.watchPoller) return;
877
+ if (this.watchPoller) {
878
+ return;
879
+ }
789
880
  this.watchPoller = setInterval(() => {
790
881
  this.runInBackground("presence watch", () => this.pollWatched());
791
882
  }, WATCH_POLL_MS);
@@ -793,11 +884,15 @@ export class TalkCore {
793
884
  }
794
885
 
795
886
  private async pollWatched(): Promise<void> {
796
- if (!this.self || this.watched.size === 0) return;
887
+ if (!this.self || this.watched.size === 0) {
888
+ return;
889
+ }
797
890
  for (const [addr, prev] of this.watched) {
798
891
  const rec = await readRecord(this.storage, addr);
799
892
  const now: Presence = rec ? presenceOf(rec) : "offline";
800
- if (now === prev) continue;
893
+ if (now === prev) {
894
+ continue;
895
+ }
801
896
  this.watched.set(addr, now);
802
897
  const label = rec ? `"${rec.name}"` : shortAddr(addr);
803
898
  const state = now === "live" ? (rec?.status ?? "idle") : "offline";
@@ -14,9 +14,13 @@ export function shortAddr(addr: string): string {
14
14
 
15
15
  export function age(ts: number, now: number = Date.now()): string {
16
16
  const s = Math.max(0, Math.round((now - ts) / 1000));
17
- if (s < 60) return `${s}s ago`;
17
+ if (s < 60) {
18
+ return `${s}s ago`;
19
+ }
18
20
  const m = Math.round(s / 60);
19
- if (m < 60) return `${m}m ago`;
21
+ if (m < 60) {
22
+ return `${m}m ago`;
23
+ }
20
24
  return `${Math.round(m / 60)}h ago`;
21
25
  }
22
26
 
@@ -45,12 +49,16 @@ export function formatListing(
45
49
  selfAddr: string,
46
50
  presence: (r: AgentRecord) => Presence,
47
51
  ): string {
48
- if (records.length === 0) return "[]";
52
+ if (records.length === 0) {
53
+ return "[]";
54
+ }
49
55
  const items: AgentListItem[] = records.map((r) => {
50
56
  const p = presence(r);
51
57
  const status = p === "live" ? r.status : "offline";
52
58
  const item: AgentListItem = { status, work_dir: r.cwd, id: r.agentId, name: r.name };
53
- if (r.addr === selfAddr) item.self = true;
59
+ if (r.addr === selfAddr) {
60
+ item.self = true;
61
+ }
54
62
  return item;
55
63
  });
56
64
  return JSON.stringify(items, null, 2);
package/src/talk/group.ts CHANGED
@@ -39,7 +39,9 @@ export const GROUPS_NS = "groups";
39
39
  const GROUP_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
40
40
 
41
41
  export function assertGroupId(id: string): void {
42
- if (!GROUP_ID_PATTERN.test(id)) throw new TypeError(`Invalid group name: ${id}`);
42
+ if (!GROUP_ID_PATTERN.test(id)) {
43
+ throw new TypeError(`Invalid group name: ${id}`);
44
+ }
43
45
  }
44
46
 
45
47
  export function isValidGroupName(id: string): boolean {
@@ -60,7 +62,9 @@ export async function listGroups(storage: TalkStorage): Promise<Group[]> {
60
62
  const out: Group[] = [];
61
63
  for (const key of await storage.listKeys(GROUPS_NS)) {
62
64
  const raw = await storage.readJson(GROUPS_NS, key);
63
- if (Value.Check(GroupSchema, raw)) out.push(raw);
65
+ if (Value.Check(GroupSchema, raw)) {
66
+ out.push(raw);
67
+ }
64
68
  }
65
69
  return out.toSorted((a, b) => a.createdAt - b.createdAt);
66
70
  }
@@ -85,7 +89,9 @@ export async function deleteGroup(storage: TalkStorage, id: string): Promise<boo
85
89
  */
86
90
  export async function groupForAgent(storage: TalkStorage, agentId: string): Promise<Group | null> {
87
91
  for (const group of await listGroups(storage)) {
88
- if (group.members.includes(agentId)) return group;
92
+ if (group.members.includes(agentId)) {
93
+ return group;
94
+ }
89
95
  }
90
96
  return null;
91
97
  }
package/src/talk/index.ts CHANGED
@@ -86,7 +86,9 @@ function readTalkSettings(): { dbPath?: string; deliver?: "steer" | "queue" } {
86
86
  const raw = fs.readFileSync(path.join(getAgentDir(), "settings.json"), "utf8");
87
87
  const parsed = JSON.parse(raw) as { talk?: { db_path?: unknown; deliver?: unknown } };
88
88
  const talk = parsed.talk;
89
- if (!talk) return {};
89
+ if (!talk) {
90
+ return {};
91
+ }
90
92
  const dbPath = typeof talk.db_path === "string" ? talk.db_path : undefined;
91
93
  const deliver = talk.deliver === "steer" || talk.deliver === "queue" ? talk.deliver : undefined;
92
94
  return { dbPath, deliver };
@@ -151,14 +153,18 @@ export default function talk(pi: ExtensionAPI) {
151
153
  });
152
154
 
153
155
  function requireInit(): string | undefined {
154
- if (!core.selfAddr) return "Talk is not initialized (no session_start yet).";
156
+ if (!core.selfAddr) {
157
+ return "Talk is not initialized (no session_start yet).";
158
+ }
155
159
  return undefined;
156
160
  }
157
161
 
158
162
  /** Refresh the talk footer/status-bar text: "alias@group", or "@group" with no explicit alias. */
159
163
  function refreshGroupStatus(ui: ExtensionUIContext): void {
160
164
  void (async () => {
161
- if (!self) return;
165
+ if (!self) {
166
+ return;
167
+ }
162
168
  try {
163
169
  const text = await core.groupStatus();
164
170
  ui.setStatus("talk", text && ui.theme.fg("accent", text));
@@ -205,7 +211,9 @@ export default function talk(pi: ExtensionAPI) {
205
211
  pi.on("session_info_changed", () => {
206
212
  // A name set explicitly via `--name` wins over pi's session title;
207
213
  // otherwise follow pi's session name.
208
- if (self) core.setAgentName(explicitName ?? pi.getSessionName() ?? self.name);
214
+ if (self) {
215
+ core.setAgentName(explicitName ?? pi.getSessionName() ?? self.name);
216
+ }
209
217
  });
210
218
  pi.on("session_shutdown", () => {
211
219
  void core.stop();
@@ -223,7 +231,9 @@ export default function talk(pi: ExtensionAPI) {
223
231
  }),
224
232
  async execute(_toolCallId, params) {
225
233
  const initError = requireInit();
226
- if (initError) return toolResult(initError);
234
+ if (initError) {
235
+ return toolResult(initError);
236
+ }
227
237
  return toolResult(params.cwd ? await core.listCwd(params.cwd) : await core.list());
228
238
  },
229
239
  });
@@ -243,7 +253,9 @@ export default function talk(pi: ExtensionAPI) {
243
253
  }),
244
254
  async execute(_toolCallId, params, signal) {
245
255
  const initError = requireInit();
246
- if (initError) return toolResult(initError);
256
+ if (initError) {
257
+ return toolResult(initError);
258
+ }
247
259
  return toolResult(
248
260
  await core.ask(
249
261
  params.to,
@@ -267,7 +279,9 @@ export default function talk(pi: ExtensionAPI) {
267
279
  }),
268
280
  async execute(_toolCallId, params) {
269
281
  const initError = requireInit();
270
- if (initError) return toolResult(initError);
282
+ if (initError) {
283
+ return toolResult(initError);
284
+ }
271
285
  return toolResult(await core.send(params.to, params.message));
272
286
  },
273
287
  });
@@ -417,7 +431,9 @@ export default function talk(pi: ExtensionAPI) {
417
431
  ctx,
418
432
  async (parsed) => {
419
433
  const agentName = parsed.flags.name?.trim() || undefined;
420
- if (agentName !== undefined) explicitName = agentName;
434
+ if (agentName !== undefined) {
435
+ explicitName = agentName;
436
+ }
421
437
  return core.groupJoin(parsed.args[0], agentName);
422
438
  },
423
439
  { sendToContext: true },
@@ -433,7 +449,9 @@ export default function talk(pi: ExtensionAPI) {
433
449
  ctx,
434
450
  async (parsed) => {
435
451
  const agentName = parsed.flags.name?.trim() || undefined;
436
- if (agentName !== undefined) explicitName = agentName;
452
+ if (agentName !== undefined) {
453
+ explicitName = agentName;
454
+ }
437
455
  return core.groupJoinLast(agentName);
438
456
  },
439
457
  { sendToContext: true },
@@ -496,7 +514,9 @@ export default function talk(pi: ExtensionAPI) {
496
514
  for (const type of [LIST_TYPE, NOTIFY_TYPE]) {
497
515
  pi.registerEntryRenderer<string>(type, (entry, _options, theme) => {
498
516
  const text = typeof entry.data === "string" ? entry.data : "";
499
- if (!text) return;
517
+ if (!text) {
518
+ return;
519
+ }
500
520
  return {
501
521
  render: (width: number) =>
502
522
  truncateToVisualLines(text, Infinity, width, 1).visualLines.map((line) =>
@@ -49,11 +49,19 @@ export type LetterKind = Letter["kind"];
49
49
  * unchanged; anything else returns null.
50
50
  */
51
51
  export function normalizeLetter(value: unknown): Letter | null {
52
- if (Value.Check(LetterSchema, value)) return value;
53
- if (!isRecord(value)) return null;
52
+ if (Value.Check(LetterSchema, value)) {
53
+ return value;
54
+ }
55
+ if (!isRecord(value)) {
56
+ return null;
57
+ }
54
58
  const from = value.from;
55
- if (!isRecord(from)) return null;
56
- if (typeof from.sessionId !== "string") return null;
59
+ if (!isRecord(from)) {
60
+ return null;
61
+ }
62
+ if (typeof from.sessionId !== "string") {
63
+ return null;
64
+ }
57
65
  const migrated = {
58
66
  ...value,
59
67
  from: { ...from, agentId: from.sessionId },
@@ -98,7 +106,9 @@ export function newMessageId(): string {
98
106
  }
99
107
 
100
108
  function assertMessageId(id: string): void {
101
- if (!MESSAGE_ID_PATTERN.test(id)) throw new TypeError(`Invalid talk message id: ${id}`);
109
+ if (!MESSAGE_ID_PATTERN.test(id)) {
110
+ throw new TypeError(`Invalid talk message id: ${id}`);
111
+ }
102
112
  }
103
113
 
104
114
  /** The storage key for a letter: `<ts>-<id>.json`, sorted oldest-first. */
@@ -112,7 +122,9 @@ export function letterFileName(letter: Letter): string {
112
122
 
113
123
  /** Structural validation plus filename safety (id/timestamp shape). */
114
124
  export function isValidLetter(value: unknown): value is Letter {
115
- if (!Value.Check(LetterSchema, value)) return false;
125
+ if (!Value.Check(LetterSchema, value)) {
126
+ return false;
127
+ }
116
128
  try {
117
129
  letterFileName(value);
118
130
  return true;
@@ -153,7 +165,9 @@ export async function listInbox(storage: TalkStorage, addr: string): Promise<Inb
153
165
  for (const fileName of await storage.listKeys(inboxNs(addr))) {
154
166
  const raw = await storage.readJson(inboxNs(addr), fileName);
155
167
  const letter = normalizeLetter(raw);
156
- if (letter && isValidLetter(letter)) out.push({ fileName, letter });
168
+ if (letter && isValidLetter(letter)) {
169
+ out.push({ fileName, letter });
170
+ }
157
171
  // corrupt letters are skipped; the caller may remove them separately
158
172
  }
159
173
  return out; // keys are already sorted by listKeys
@@ -194,7 +208,9 @@ export async function awaitReceipt(
194
208
  while (Date.now() < deadline) {
195
209
  const inbox = await listInbox(storage, toAddr);
196
210
  const stillThere = inbox.some((i) => i.letter.id === letter.id);
197
- if (!stillThere) return "delivered";
211
+ if (!stillThere) {
212
+ return "delivered";
213
+ }
198
214
  await sleep(100);
199
215
  }
200
216
  const inbox = await listInbox(storage, toAddr);
@@ -236,7 +252,9 @@ export async function clearAsk(storage: TalkStorage, addr: string, askId: string
236
252
  export async function outgoingAskIds(storage: TalkStorage, addr: string): Promise<string[]> {
237
253
  const out: string[] = [];
238
254
  for (const key of await storage.listKeys(asksNs(addr))) {
239
- if (!key.startsWith("out-") || !key.endsWith(".json")) continue;
255
+ if (!key.startsWith("out-") || !key.endsWith(".json")) {
256
+ continue;
257
+ }
240
258
  out.push(key.slice("out-".length, -".json".length));
241
259
  }
242
260
  return out;
@@ -271,7 +289,9 @@ export async function readAudit(storage: TalkStorage, limit = 50): Promise<Audit
271
289
  for (const line of await storage.readLog(AUDIT_LOG)) {
272
290
  try {
273
291
  const parsed: unknown = JSON.parse(line);
274
- if (Value.Check(AuditRecordSchema, parsed)) out.push(parsed);
292
+ if (Value.Check(AuditRecordSchema, parsed)) {
293
+ out.push(parsed);
294
+ }
275
295
  } catch {
276
296
  // skip corrupt line — append-only, never fatal
277
297
  }
@@ -53,7 +53,9 @@ export class OutboundPolicy {
53
53
  };
54
54
  }
55
55
  const windowStart = now - RATE_LIMIT_WINDOW_MS;
56
- while (this.sentAt.length > 0 && this.sentAt[0] < windowStart) this.sentAt.shift();
56
+ while (this.sentAt.length > 0 && this.sentAt[0] < windowStart) {
57
+ this.sentAt.shift();
58
+ }
57
59
  if (this.sentAt.length >= RATE_LIMIT_MAX) {
58
60
  return {
59
61
  ok: false,
@@ -73,7 +75,9 @@ export class OutboundPolicy {
73
75
  }
74
76
  const cutoff = this.now() - DEDUPE_WINDOW_MS;
75
77
  for (const [key, ts] of this.recentBodies) {
76
- if (ts < cutoff) this.recentBodies.delete(key);
78
+ if (ts < cutoff) {
79
+ this.recentBodies.delete(key);
80
+ }
77
81
  }
78
82
  }
79
83
  }