@trim21/personal-pi-extensions 0.0.346 → 0.0.348

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.0.346",
3
+ "version": "0.0.348",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
package/src/talk/core.ts CHANGED
@@ -267,6 +267,21 @@ export class TalkCore {
267
267
  // both timing out.
268
268
  await this.resolveInterlock(letter);
269
269
  await trackIncomingAsk(this.storage, self.addr, letter);
270
+ } else if (letter.kind === "message") {
271
+ // A plain message from a peer we are blocked asking breaks the wait: the
272
+ // peer is engaging, so do not keep the caller stuck waiting for a reply.
273
+ const myAsk = await this.findOutAskTo(letter.from.addr);
274
+ if (myAsk) {
275
+ const waiter = this.askWaiters.get(myAsk.askId);
276
+ if (waiter) {
277
+ this.askWaiters.delete(myAsk.askId);
278
+ waiter({
279
+ replied: false,
280
+ reason: "peer sent a message instead of replying to your ask",
281
+ });
282
+ await clearAsk(this.storage, self.addr, myAsk.askId);
283
+ }
284
+ }
270
285
  }
271
286
  return true;
272
287
  }
@@ -535,7 +550,7 @@ export class TalkCore {
535
550
  const nameNote = agentName === undefined ? "" : ` You are visible as "${agentName}".`;
536
551
  const existing = await readGroup(this.storage, name);
537
552
  if (existing?.members.includes(self.agentId)) {
538
- return `Already in group ${name} (${existing.members.length} member(s)).${nameNote}`;
553
+ return `Already in group ${name} (${existing.members.length} member(s)). Members: ${await this.groupMemberNames(existing.members)}.${nameNote}`;
539
554
  }
540
555
  await this.leaveCurrentGroup();
541
556
  if (existing) {
@@ -544,7 +559,12 @@ export class TalkCore {
544
559
  members: [...existing.members, self.agentId],
545
560
  updatedAt: this.now(),
546
561
  });
547
- return `Joined group ${name} (${existing.members.length + 1} member(s)). You now see only co-members.${nameNote}`;
562
+ return `Joined group ${name} (${
563
+ existing.members.length + 1
564
+ } member(s)). Members: ${await this.groupMemberNames([
565
+ ...existing.members,
566
+ self.agentId,
567
+ ])}. You now see only co-members.${nameNote}`;
548
568
  }
549
569
  const now = this.now();
550
570
  await writeGroup(this.storage, {
@@ -553,7 +573,7 @@ export class TalkCore {
553
573
  createdAt: now,
554
574
  updatedAt: now,
555
575
  });
556
- return `Created group ${name}.${nameNote} Other agents join it with /talk-group-join ${name}.`;
576
+ return `Created group ${name}. Members: ${await this.groupMemberNames([self.agentId])}.${nameNote} Other agents join it with /talk-group-join ${name}.`;
557
577
  }
558
578
 
559
579
  /**
@@ -621,6 +641,20 @@ export class TalkCore {
621
641
  return `Groups (${groups.length}):\n${lines.join("\n")}`;
622
642
  }
623
643
 
644
+ /** Human-readable member list: `name (shortId)`, self marked `← you`. */
645
+ private async groupMemberNames(memberIds: string[]): Promise<string> {
646
+ const self = this.requireSelf();
647
+ const records = await listRecords(this.storage);
648
+ const label = (agentId: string): string => {
649
+ const rec = records.find((r) => r.agentId === agentId);
650
+ const id = agentId.length > 8 ? `${agentId.slice(0, 8)}…` : agentId;
651
+ return rec ? `${rec.name} (${id})` : `unknown agent (${id})`;
652
+ };
653
+ return memberIds
654
+ .map((agentId) => (agentId === self.agentId ? `${label(agentId)} ← you` : label(agentId)))
655
+ .join(", ");
656
+ }
657
+
624
658
  /**
625
659
  * Status-bar text for the caller: "alias@group" when an explicit alias was
626
660
  * set via `talk-group-join --name`, "@group" otherwise; undefined when the
package/src/talk/index.ts CHANGED
@@ -148,12 +148,9 @@ export default function talk(pi: ExtensionAPI) {
148
148
  events: {
149
149
  deliver: deliverToAgent,
150
150
  notify(content) {
151
- // Presence transitions are informational — queue for the next turn
152
- // rather than steering into a busy agent.
153
- pi.sendMessage(
154
- { customType: NOTIFY_TYPE, content, display: true },
155
- { deliverAs: "nextTurn" },
156
- );
151
+ // Presence transitions are informational — show in the TUI only, never
152
+ // in LLM context.
153
+ pi.appendEntry(NOTIFY_TYPE, content);
157
154
  },
158
155
  },
159
156
  });
@@ -289,22 +286,34 @@ export default function talk(pi: ExtensionAPI) {
289
286
 
290
287
  type OkResult<TFlags extends TObject> = Extract<CommandResult<TFlags>, { kind: "ok" }>;
291
288
 
292
- /** Parse a /talk command; on help/error or init failure the text is sent, otherwise run() produces the listing text. */
289
+ /**
290
+ * Parse a /talk command; on help/error or init failure the text is sent,
291
+ * otherwise run() produces the listing text. With `sendToContext` the result
292
+ * is injected into the LLM context (sendMessage); otherwise it is appended
293
+ * as a display-only session entry (appendEntry, never sent to the LLM).
294
+ */
293
295
  function handleCommand<TFlags extends TObject>(
294
296
  spec: CommandSpec<TFlags>,
295
297
  args: string,
296
298
  ctx: ExtensionCommandContext,
297
299
  run: (parsed: OkResult<TFlags>) => Promise<string> | string,
300
+ options?: { sendToContext?: boolean },
298
301
  ): Promise<void> {
302
+ const emit = (text: string): void => {
303
+ if (options?.sendToContext) {
304
+ pi.sendMessage({ customType: LIST_TYPE, content: text, display: true });
305
+ } else {
306
+ pi.appendEntry(LIST_TYPE, text);
307
+ }
308
+ };
299
309
  const parsed = parseCommand(spec, args);
300
310
  if (parsed.kind !== "ok") {
301
- pi.sendMessage({ customType: LIST_TYPE, content: parsed.text, display: true });
311
+ emit(parsed.text);
302
312
  return Promise.resolve();
303
313
  }
304
314
  return (async () => {
305
315
  const initError = requireInit();
306
- const text = initError ?? (await run(parsed));
307
- pi.sendMessage({ customType: LIST_TYPE, content: text, display: true });
316
+ emit(initError ?? (await run(parsed)));
308
317
  // Group membership can change under any of these commands; keep the
309
318
  // footer/status-bar text in sync.
310
319
  refreshGroupStatus(ctx.ui);
@@ -412,27 +421,41 @@ export default function talk(pi: ExtensionAPI) {
412
421
  pi.registerCommand("talk-group-join", {
413
422
  description: TALK_GROUP_JOIN_SPEC.description,
414
423
  handler: (args, ctx) =>
415
- handleCommand(TALK_GROUP_JOIN_SPEC, args, ctx, async (parsed) => {
416
- const agentName = parsed.flags.name?.trim() || undefined;
417
- if (agentName !== undefined) explicitName = agentName;
418
- return core.groupJoin(parsed.args[0], agentName);
419
- }),
424
+ handleCommand(
425
+ TALK_GROUP_JOIN_SPEC,
426
+ args,
427
+ ctx,
428
+ async (parsed) => {
429
+ const agentName = parsed.flags.name?.trim() || undefined;
430
+ if (agentName !== undefined) explicitName = agentName;
431
+ return core.groupJoin(parsed.args[0], agentName);
432
+ },
433
+ { sendToContext: true },
434
+ ),
420
435
  });
421
436
 
422
437
  pi.registerCommand("talk-group-join-last", {
423
438
  description: TALK_GROUP_JOIN_LAST_SPEC.description,
424
439
  handler: (args, ctx) =>
425
- handleCommand(TALK_GROUP_JOIN_LAST_SPEC, args, ctx, async (parsed) => {
426
- const agentName = parsed.flags.name?.trim() || undefined;
427
- if (agentName !== undefined) explicitName = agentName;
428
- return core.groupJoinLast(agentName);
429
- }),
440
+ handleCommand(
441
+ TALK_GROUP_JOIN_LAST_SPEC,
442
+ args,
443
+ ctx,
444
+ async (parsed) => {
445
+ const agentName = parsed.flags.name?.trim() || undefined;
446
+ if (agentName !== undefined) explicitName = agentName;
447
+ return core.groupJoinLast(agentName);
448
+ },
449
+ { sendToContext: true },
450
+ ),
430
451
  });
431
452
 
432
453
  pi.registerCommand("talk-group-leave", {
433
454
  description: TALK_GROUP_LEAVE_SPEC.description,
434
455
  handler: (args, ctx) =>
435
- handleCommand(TALK_GROUP_LEAVE_SPEC, args, ctx, async () => core.groupLeave()),
456
+ handleCommand(TALK_GROUP_LEAVE_SPEC, args, ctx, async () => core.groupLeave(), {
457
+ sendToContext: true,
458
+ }),
436
459
  });
437
460
 
438
461
  pi.registerCommand("talk-group-list", {
@@ -486,4 +509,20 @@ export default function talk(pi: ExtensionAPI) {
486
509
  invalidate: (): void => undefined,
487
510
  };
488
511
  });
512
+
513
+ // ── Display-only entries (appendEntry): shown in chat, persisted, never in LLM context ──
514
+
515
+ for (const type of [LIST_TYPE, NOTIFY_TYPE]) {
516
+ pi.registerEntryRenderer<string>(type, (entry, _options, theme) => {
517
+ const text = typeof entry.data === "string" ? entry.data : "";
518
+ if (!text) return;
519
+ return {
520
+ render: (width: number) =>
521
+ truncateToVisualLines(text, Infinity, width, 1).visualLines.map((line) =>
522
+ theme.bg("customMessageBg", line),
523
+ ),
524
+ invalidate: (): void => undefined,
525
+ };
526
+ });
527
+ }
489
528
  }