@trim21/personal-pi-extensions 0.1.611 → 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 +2 -2
  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 +5 -3
  6. package/src/bwrap/approval-rules.ts +51 -18
  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 +36 -14
  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 +55 -22
  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 +154 -58
  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 +63 -41
  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 +23 -9
  59. package/src/opencode/todo.ts +3 -1
  60. package/src/session-name.ts +40 -16
  61. package/src/spawn-agent-agents.ts +18 -6
  62. package/src/spawn-agent.ts +58 -21
  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 +10 -5
  70. package/src/talk/registry.ts +35 -12
  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
@@ -189,7 +189,9 @@ function getFinalOutput(messages: AgentMessage[]): string {
189
189
  const msg = messages[i];
190
190
  if (msg.role === "assistant") {
191
191
  for (const part of msg.content) {
192
- if (part.type === "text") return part.text;
192
+ if (part.type === "text") {
193
+ return part.text;
194
+ }
193
195
  }
194
196
  }
195
197
  }
@@ -205,14 +207,18 @@ export function formatSubagentError(result: SubagentResult): { reason: string; m
205
207
  const reason =
206
208
  result.stopReason ?? (result.exitCode === 0 ? "failed" : `exit ${result.exitCode}`);
207
209
  const parts: string[] = [];
208
- if (result.errorMessage) parts.push(`error: ${result.errorMessage}`);
210
+ if (result.errorMessage) {
211
+ parts.push(`error: ${result.errorMessage}`);
212
+ }
209
213
  const stderr = truncateTail(result.stderr, { maxBytes: MAX_STDERR_ERROR_BYTES });
210
214
  if (stderr.content.trim()) {
211
215
  const truncatedMark = stderr.truncated ? "\n[stderr truncated]" : "";
212
216
  parts.push(`stderr: ${stderr.content.trim()}${truncatedMark}`);
213
217
  }
214
218
  const output = getFinalOutput(result.messages);
215
- if (output) parts.push(`output: ${output}`);
219
+ if (output) {
220
+ parts.push(`output: ${output}`);
221
+ }
216
222
  return { reason, message: parts.length > 0 ? parts.join("\n") : "(no output)" };
217
223
  }
218
224
 
@@ -223,7 +229,9 @@ export function formatSubagentError(result: SubagentResult): { reason: string; m
223
229
  * returned as-is.
224
230
  */
225
231
  function foldProgressLine(text: string): string {
226
- if (text.length <= MAX_PROGRESS_CHARS_PER_LINE) return text;
232
+ if (text.length <= MAX_PROGRESS_CHARS_PER_LINE) {
233
+ return text;
234
+ }
227
235
  const keep = Math.floor((MAX_PROGRESS_CHARS_PER_LINE - 3) / 2);
228
236
  return `${text.slice(0, keep)} … ${text.slice(-keep)}`;
229
237
  }
@@ -238,18 +246,32 @@ function sanitizeProgressLine(text: string): string {
238
246
  }
239
247
 
240
248
  function formatTokens(count: number): string {
241
- if (count < 1000) return count.toString();
242
- if (count < 10_000) return `${(count / 1000).toFixed(1)}k`;
243
- if (count < 1_000_000) return `${Math.round(count / 1000)}k`;
249
+ if (count < 1000) {
250
+ return count.toString();
251
+ }
252
+ if (count < 10_000) {
253
+ return `${(count / 1000).toFixed(1)}k`;
254
+ }
255
+ if (count < 1_000_000) {
256
+ return `${Math.round(count / 1000)}k`;
257
+ }
244
258
  return `${(count / 1_000_000).toFixed(1)}M`;
245
259
  }
246
260
 
247
261
  function formatUsageStats(usage: UsageStats, model?: string): string {
248
262
  const parts: string[] = [];
249
- if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
250
- if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
251
- if (usage.contextTokens > 0) parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
252
- if (model) parts.push(model);
263
+ if (usage.turns) {
264
+ parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
265
+ }
266
+ if (usage.cost) {
267
+ parts.push(`$${usage.cost.toFixed(4)}`);
268
+ }
269
+ if (usage.contextTokens > 0) {
270
+ parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
271
+ }
272
+ if (model) {
273
+ parts.push(model);
274
+ }
253
275
  return parts.join(" ");
254
276
  }
255
277
 
@@ -286,10 +308,12 @@ export function overrideExtensionPaths(tools: string[]): string[] {
286
308
  const paths: string[] = [];
287
309
  for (const tool of tools) {
288
310
  const ext = TOOL_EXTENSION_OVERRIDES[tool];
289
- if (ext && !loaded.has(ext)) {
290
- loaded.add(ext);
291
- paths.push(extensionPath(ext));
311
+ if (!ext || loaded.has(ext)) {
312
+ continue;
292
313
  }
314
+
315
+ loaded.add(ext);
316
+ paths.push(extensionPath(ext));
293
317
  }
294
318
  return paths;
295
319
  }
@@ -324,12 +348,16 @@ export function resolveModel(
324
348
  agent: AgentConfig,
325
349
  settingsManager: SettingsManager,
326
350
  ): ReturnType<ModelRuntime["getModel"]> {
327
- if (!agent.model) return undefined;
351
+ if (!agent.model) {
352
+ return undefined;
353
+ }
328
354
  const slash = agent.model.indexOf("/");
329
355
  if (slash > 0) {
330
356
  return modelRuntime.getModel(agent.model.slice(0, slash), agent.model.slice(slash + 1));
331
357
  }
332
- if (agent.provider) return modelRuntime.getModel(agent.provider, agent.model);
358
+ if (agent.provider) {
359
+ return modelRuntime.getModel(agent.provider, agent.model);
360
+ }
333
361
  const defaultProvider = settingsManager.getDefaultProvider();
334
362
  return defaultProvider ? modelRuntime.getModel(defaultProvider, agent.model) : undefined;
335
363
  }
@@ -485,7 +513,9 @@ export async function runAgent(
485
513
  const name = sanitizeProgressLine(result.agent);
486
514
  const footer = usageLine ? `\`${name}\` ${usageLine}` : `\`${name}\``;
487
515
  const lines = [...logLines];
488
- if (thinkingChars !== undefined) lines.push(`thinking ( ${thinkingChars} chars )`);
516
+ if (thinkingChars !== undefined) {
517
+ lines.push(`thinking ( ${thinkingChars} chars )`);
518
+ }
489
519
  lines.push(footer);
490
520
  onUpdate?.({
491
521
  content: [{ type: "text", text: lines.join("\n") }],
@@ -539,9 +569,13 @@ export async function runAgent(
539
569
  result.usage.turns++;
540
570
  result.usage.cost += msg.usage.cost.total;
541
571
  result.usage.contextTokens = msg.usage.totalTokens;
542
- if (!result.model) result.model = msg.model;
572
+ if (!result.model) {
573
+ result.model = msg.model;
574
+ }
543
575
  result.stopReason = msg.stopReason;
544
- if (msg.errorMessage) result.errorMessage = msg.errorMessage;
576
+ if (msg.errorMessage) {
577
+ result.errorMessage = msg.errorMessage;
578
+ }
545
579
  }
546
580
  emitUpdate();
547
581
 
@@ -560,8 +594,11 @@ export async function runAgent(
560
594
  void session.abort();
561
595
  };
562
596
  if (signal) {
563
- if (signal.aborted) onAbort();
564
- else signal.addEventListener("abort", onAbort, { once: true });
597
+ if (signal.aborted) {
598
+ onAbort();
599
+ } else {
600
+ signal.addEventListener("abort", onAbort, { once: true });
601
+ }
565
602
  }
566
603
 
567
604
  try {
@@ -78,7 +78,9 @@ export function formatGuidelines(promptGuidelines: string[] | undefined): string
78
78
  .map((g) => g.trim())
79
79
  .filter((g) => g.length > 0)
80
80
  .toSorted((a, b) => a.localeCompare(b));
81
- if (items.length === 0) return "";
81
+ if (items.length === 0) {
82
+ return "";
83
+ }
82
84
  return `## Guidelines\n\n${items.join("\n\n")}`;
83
85
  }
84
86
 
@@ -86,7 +88,9 @@ export function formatGuidelines(promptGuidelines: string[] | undefined): string
86
88
  export function formatContextFiles(
87
89
  contextFiles: { path: string; content: string }[] | undefined,
88
90
  ): string {
89
- if (!contextFiles || contextFiles.length === 0) return "";
91
+ if (!contextFiles || contextFiles.length === 0) {
92
+ return "";
93
+ }
90
94
  const inner = contextFiles
91
95
  .toSorted((a, b) => a.path.localeCompare(b.path))
92
96
  .map(
@@ -105,7 +109,9 @@ export function formatSkills(skills: SkillLike[] | undefined): string {
105
109
  const visible = (skills ?? [])
106
110
  .filter((skill) => !skill.disableModelInvocation)
107
111
  .toSorted((a, b) => a.name.localeCompare(b.name) || a.filePath.localeCompare(b.filePath));
108
- if (visible.length === 0) return "";
112
+ if (visible.length === 0) {
113
+ return "";
114
+ }
109
115
  const lines = [
110
116
  "\n\nThe following skills provide specialized instructions for specific tasks.",
111
117
  "Use the read tool to load a skill's file when the task matches its description.",
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
  }