@trim21/personal-pi-extensions 0.0.346 → 0.0.347

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.347",
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
@@ -535,7 +535,7 @@ export class TalkCore {
535
535
  const nameNote = agentName === undefined ? "" : ` You are visible as "${agentName}".`;
536
536
  const existing = await readGroup(this.storage, name);
537
537
  if (existing?.members.includes(self.agentId)) {
538
- return `Already in group ${name} (${existing.members.length} member(s)).${nameNote}`;
538
+ return `Already in group ${name} (${existing.members.length} member(s)). Members: ${await this.groupMemberNames(existing.members)}.${nameNote}`;
539
539
  }
540
540
  await this.leaveCurrentGroup();
541
541
  if (existing) {
@@ -544,7 +544,12 @@ export class TalkCore {
544
544
  members: [...existing.members, self.agentId],
545
545
  updatedAt: this.now(),
546
546
  });
547
- return `Joined group ${name} (${existing.members.length + 1} member(s)). You now see only co-members.${nameNote}`;
547
+ return `Joined group ${name} (${
548
+ existing.members.length + 1
549
+ } member(s)). Members: ${await this.groupMemberNames([
550
+ ...existing.members,
551
+ self.agentId,
552
+ ])}. You now see only co-members.${nameNote}`;
548
553
  }
549
554
  const now = this.now();
550
555
  await writeGroup(this.storage, {
@@ -553,7 +558,7 @@ export class TalkCore {
553
558
  createdAt: now,
554
559
  updatedAt: now,
555
560
  });
556
- return `Created group ${name}.${nameNote} Other agents join it with /talk-group-join ${name}.`;
561
+ return `Created group ${name}. Members: ${await this.groupMemberNames([self.agentId])}.${nameNote} Other agents join it with /talk-group-join ${name}.`;
557
562
  }
558
563
 
559
564
  /**
@@ -621,6 +626,20 @@ export class TalkCore {
621
626
  return `Groups (${groups.length}):\n${lines.join("\n")}`;
622
627
  }
623
628
 
629
+ /** Human-readable member list: `name (shortId)`, self marked `← you`. */
630
+ private async groupMemberNames(memberIds: string[]): Promise<string> {
631
+ const self = this.requireSelf();
632
+ const records = await listRecords(this.storage);
633
+ const label = (agentId: string): string => {
634
+ const rec = records.find((r) => r.agentId === agentId);
635
+ const id = agentId.length > 8 ? `${agentId.slice(0, 8)}…` : agentId;
636
+ return rec ? `${rec.name} (${id})` : `unknown agent (${id})`;
637
+ };
638
+ return memberIds
639
+ .map((agentId) => (agentId === self.agentId ? `${label(agentId)} ← you` : label(agentId)))
640
+ .join(", ");
641
+ }
642
+
624
643
  /**
625
644
  * Status-bar text for the caller: "alias@group" when an explicit alias was
626
645
  * 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
  }