@llblab/pi-kit 0.6.0 → 0.7.1

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 (129) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +13 -9
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
  87. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  88. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  89. package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
  90. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
  91. package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
  92. package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
  93. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
  94. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  95. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  96. package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
  97. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  98. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
  99. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  100. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  101. package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
  102. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
  103. package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
  104. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
  105. package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
  106. package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
  107. package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
  108. package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
  109. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  110. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
  111. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  112. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  113. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  114. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  115. package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
  116. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  117. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  118. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  119. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  120. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  121. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  122. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  123. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  124. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  125. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  126. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  127. package/node_modules/@llblab/skills/package.json +2 -3
  128. package/package.json +6 -5
  129. /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Workspace slot allocation policy
3
+ * Zones: telegram, workspace identity
4
+ * Owns bounded profile-wide letter selection and inactivity ordering.
5
+ * Excludes liveness discovery, persistence, routing, and Telegram deletion;
6
+ * a selection is a proposal, never authority to retire a binding.
7
+ */
8
+
9
+ export const TELEGRAM_WORKSPACE_SLOTS = "abcdefghijklmnopqrstuvwxyz";
10
+
11
+ export interface TelegramWorkspaceSlotOccupancy {
12
+ bindingKey: string;
13
+ slot: string;
14
+ inactiveSinceMs?: number;
15
+ protection: "eligible" | "protected" | "unknown";
16
+ }
17
+
18
+ export type TelegramWorkspaceSlotAllocation =
19
+ | { kind: "free"; slot: string }
20
+ | { kind: "reclaim"; candidate: TelegramWorkspaceSlotOccupancy }
21
+ | { kind: "blocked"; reason: "invalid-state" | "protected-capacity" };
22
+
23
+ function isSlot(slot: string): boolean {
24
+ return /^[a-z]$/u.test(slot);
25
+ }
26
+
27
+ function isValidSnapshot(
28
+ bindings: readonly TelegramWorkspaceSlotOccupancy[],
29
+ reservedSlots: readonly string[],
30
+ nowMs: number,
31
+ ): boolean {
32
+ if (!Number.isFinite(nowMs) || nowMs < 0) return false;
33
+ const slots = new Set<string>();
34
+ const keys = new Set<string>();
35
+ for (const binding of bindings) {
36
+ if (!isSlot(binding.slot) || !binding.bindingKey ||
37
+ slots.has(binding.slot) || keys.has(binding.bindingKey)) return false;
38
+ slots.add(binding.slot);
39
+ keys.add(binding.bindingKey);
40
+ }
41
+ return reservedSlots.every(isSlot);
42
+ }
43
+
44
+ function eligibleByInactivity(
45
+ bindings: readonly TelegramWorkspaceSlotOccupancy[],
46
+ reservedSlots: readonly string[],
47
+ nowMs: number,
48
+ ): TelegramWorkspaceSlotOccupancy[] {
49
+ const reserved = new Set(reservedSlots);
50
+ return bindings.filter((binding) =>
51
+ binding.protection === "eligible" &&
52
+ !reserved.has(binding.slot) &&
53
+ typeof binding.inactiveSinceMs === "number" &&
54
+ Number.isFinite(binding.inactiveSinceMs) &&
55
+ binding.inactiveSinceMs >= 0 &&
56
+ binding.inactiveSinceMs <= nowMs,
57
+ ).sort((left, right) =>
58
+ left.inactiveSinceMs! - right.inactiveSinceMs! ||
59
+ left.slot.charCodeAt(0) - right.slot.charCodeAt(0),
60
+ );
61
+ }
62
+
63
+ /** Caller must recheck exact ownership and protected work before retirement. */
64
+ export function planTelegramWorkspaceSlotAllocation(input: {
65
+ bindings: readonly TelegramWorkspaceSlotOccupancy[];
66
+ reservedSlots: readonly string[];
67
+ nowMs: number;
68
+ }): TelegramWorkspaceSlotAllocation {
69
+ const { bindings, reservedSlots, nowMs } = input;
70
+ if (!isValidSnapshot(bindings, reservedSlots, nowMs)) {
71
+ return { kind: "blocked", reason: "invalid-state" };
72
+ }
73
+ const occupied = new Set([
74
+ ...bindings.map((binding) => binding.slot),
75
+ ...reservedSlots,
76
+ ]);
77
+ for (const slot of TELEGRAM_WORKSPACE_SLOTS) {
78
+ if (!occupied.has(slot)) return { kind: "free", slot };
79
+ }
80
+ const candidate = eligibleByInactivity(bindings, reservedSlots, nowMs)[0];
81
+ return candidate
82
+ ? { kind: "reclaim", candidate: { ...candidate } }
83
+ : { kind: "blocked", reason: "protected-capacity" };
84
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,83 @@
1
+ // Counts isolated registry work without sockets, configured profiles, or Telegram calls.
2
+ import assert from "node:assert/strict";
3
+ import { createTelegramBusFollowerRegistry, createTelegramBusProtocolIdentity } from "../lib/bus.ts";
4
+
5
+ const repetitions = 10;
6
+ const methods = ["get", "set", "delete", "entries", "values"];
7
+ const originals = Object.fromEntries(methods.map((name) => [name, Map.prototype[name]]));
8
+ const rows = [];
9
+ let counts;
10
+
11
+ function measure(size, operation, execute) {
12
+ const sample = { gets: 0, sets: 0, deletes: 0, entryVisits: 0, valueVisits: 0 };
13
+ let result;
14
+ counts = sample;
15
+ try {
16
+ for (let index = 0; index < repetitions; index++) result = execute(index);
17
+ } finally {
18
+ counts = undefined;
19
+ }
20
+ rows.push({ size, operation, repetitions, ...sample });
21
+ return { result, counts: sample };
22
+ }
23
+
24
+ try {
25
+ for (const [method, counter] of [["get", "gets"], ["set", "sets"], ["delete", "deletes"]]) {
26
+ Map.prototype[method] = function (...args) {
27
+ if (counts) counts[counter]++;
28
+ return originals[method].apply(this, args);
29
+ };
30
+ }
31
+ for (const [method, counter] of [["entries", "entryVisits"], ["values", "valueVisits"]]) {
32
+ Map.prototype[method] = function* (...args) {
33
+ for (const entry of originals[method].apply(this, args)) {
34
+ if (counts) counts[counter]++;
35
+ yield entry;
36
+ }
37
+ };
38
+ }
39
+ for (const size of [1, 13, 26]) {
40
+ const registry = createTelegramBusFollowerRegistry();
41
+ const protocol = createTelegramBusProtocolIdentity({ runtimeBuild: "measurement" });
42
+ const registrations = Array.from({ length: size }, (_, index) => ({
43
+ instanceId: `fixture-${index}`, profileKey: `manual:fixture-${index}`,
44
+ registrationGeneration: `generation-${index}`, connectedAtMs: 1000,
45
+ target: { chatId: 7, threadId: index + 1 }, protocol,
46
+ }));
47
+ for (const registration of registrations) registry.register(registration);
48
+ const last = registrations.at(-1);
49
+ const registered = measure(size, "reregister-last", () => registry.register(last));
50
+ assert.equal(registered.counts.entryVisits, size * repetitions);
51
+ const heartbeat = measure(size, "heartbeat-last", (index) => registry.heartbeat(last.instanceId, 2000 + index));
52
+ assert.equal(heartbeat.result.lastHeartbeatMs, 2000 + repetitions - 1);
53
+ assert.equal(heartbeat.counts.entryVisits + heartbeat.counts.valueVisits, 0);
54
+ assert.equal(heartbeat.counts.gets, repetitions);
55
+ assert.equal(heartbeat.counts.sets, repetitions);
56
+ const first = measure(size, "target-first", () => registry.getByTarget(registrations[0].target));
57
+ assert.equal(first.result.instanceId, registrations[0].instanceId);
58
+ assert.equal(first.counts.valueVisits, repetitions);
59
+ const tail = measure(size, "target-last", () => registry.getByTarget(last.target));
60
+ assert.equal(tail.result.instanceId, last.instanceId);
61
+ assert.equal(tail.counts.valueVisits, size * repetitions);
62
+ const missing = measure(size, "target-missing-chat", () => registry.getByTarget({ chatId: 8, threadId: size }));
63
+ assert.equal(missing.result, undefined);
64
+ assert.equal(missing.counts.valueVisits, size * repetitions);
65
+ const roster = measure(size, "list", () => registry.list());
66
+ assert.equal(roster.result.length, size);
67
+ assert.equal(roster.counts.valueVisits, size * repetitions);
68
+ for (const view of [registered.result, heartbeat.result, first.result, tail.result, ...roster.result]) {
69
+ const expected = registry.get(view.instanceId);
70
+ view.target.chatId = 99;
71
+ view.protocol.capabilities.push("fixture-only");
72
+ assert.deepEqual(registry.get(view.instanceId), expected, "Returned views must not mutate registry authority");
73
+ }
74
+ }
75
+ } finally {
76
+ for (const method of methods) Map.prototype[method] = originals[method];
77
+ }
78
+ console.log(JSON.stringify({
79
+ scope: "Synchronous isolated follower registry, not IPC, authentication, provisioning, or throughput",
80
+ counters: "Aggregate Map operations and visited entries for each row's repetitions; no allocation or timing claims",
81
+ copyEvidence: "Mutating returned target/protocol capability views leaves registry authority unchanged",
82
+ rows,
83
+ }, null, 2));
@@ -0,0 +1,101 @@
1
+ // Measures isolated Thread-store work; never reads configured profiles or calls Telegram.
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs/promises";
4
+ import { syncBuiltinESMExports } from "node:module";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { createTelegramTopicTargetStore } from "../lib/threads.ts";
8
+
9
+ const repetitions = 10;
10
+ const original = { readFile: fs.readFile, writeFile: fs.writeFile, mkdir: fs.mkdir, rename: fs.rename,
11
+ parse: JSON.parse, stringify: JSON.stringify };
12
+ let counts;
13
+ const rows = [];
14
+ const root = await fs.mkdtemp(join(tmpdir(), "pi-telegram-measure-workspace-"));
15
+
16
+ async function measure(size, operation, execute, samples = repetitions) {
17
+ counts = { reads: 0, readBytes: 0, writes: 0, writeBytes: 0, mkdirs: 0, renames: 0,
18
+ parses: 0, stringifies: 0 };
19
+ try {
20
+ for (let index = 0; index < samples; index++) await execute(index);
21
+ const result = { size, operation, repetitions: samples, ...counts };
22
+ rows.push(result);
23
+ return result;
24
+ } finally {
25
+ counts = undefined;
26
+ }
27
+ }
28
+
29
+ try {
30
+ fs.readFile = async (...args) => {
31
+ if (counts) counts.reads++;
32
+ const result = await original.readFile(...args);
33
+ if (counts) counts.readBytes += Buffer.byteLength(result);
34
+ return result;
35
+ };
36
+ fs.writeFile = async (...args) => {
37
+ if (counts) { counts.writes++; counts.writeBytes += Buffer.byteLength(args[1]); }
38
+ return original.writeFile(...args);
39
+ };
40
+ fs.mkdir = async (...args) => {
41
+ if (counts) counts.mkdirs++;
42
+ return original.mkdir(...args);
43
+ };
44
+ fs.rename = async (...args) => {
45
+ if (counts) counts.renames++;
46
+ return original.rename(...args);
47
+ };
48
+ JSON.parse = (...args) => { if (counts) counts.parses++; return original.parse(...args); };
49
+ JSON.stringify = (...args) => { if (counts) counts.stringifies++; return original.stringify(...args); };
50
+ syncBuiltinESMExports();
51
+
52
+ for (const size of [1, 13, 26]) {
53
+ const path = join(root, `${size}.json`);
54
+ const store = createTelegramTopicTargetStore({ path, getNowMs: () => 1000 });
55
+ for (let index = 0; index < size; index++) {
56
+ const instanceId = `fixture-${index}`;
57
+ const identity = store.claimWorkspaceIdentity(`/fixture/${index}`, instanceId);
58
+ assert.ok(identity);
59
+ const target = { chatId: 7, threadId: index + 1 };
60
+ assert.ok(store.upsertWorkspaceBinding({ ...identity, target, updatedAtMs: 1000 }, instanceId));
61
+ store.upsert({ profileKey: `manual:${instanceId}`, instanceId, slot: identity.slot,
62
+ target, status: "active", createdAtMs: 1000, updatedAtMs: 1000 });
63
+ }
64
+ await store.persist();
65
+ assert.equal(new Set(store.listWorkspaceBindings().map((entry) => entry.slot)).size, size);
66
+ await measure(size, "cold-load", async () => {
67
+ const reader = createTelegramTopicTargetStore({ path });
68
+ await reader.load();
69
+ assert.equal(reader.list().length, size);
70
+ assert.equal(reader.listWorkspaceBindings().length, size);
71
+ });
72
+ const lookup = await measure(size, "lookup-last", () => {
73
+ assert.equal(store.getByProfileKey(`manual:fixture-${size - 1}`)?.target.threadId, size);
74
+ });
75
+ assert.equal(lookup.reads + lookup.writes, 0);
76
+ const beforeReloadSave = JSON.parse(await fs.readFile(path, "utf8"));
77
+ const reloaded = await measure(size, "first-persist-after-reload", () => store.persist(), 1);
78
+ assert.equal(reloaded.writes + reloaded.renames + reloaded.mkdirs, 0);
79
+ assert.deepEqual(JSON.parse(await fs.readFile(path, "utf8")), beforeReloadSave,
80
+ "The first reloaded save must preserve the seeded semantic state");
81
+ const unchanged = await measure(size, "unchanged-persist", () => store.persist());
82
+ assert.equal(unchanged.writes + unchanged.renames + unchanged.mkdirs, 0);
83
+ assert.ok(unchanged.reads >= repetitions, "No-op saves still consult disk authority");
84
+ const changed = await measure(size, "diagnostic-persist", async (index) => {
85
+ store.setStatusSnapshot({ diagnostics: { measurementStep: index } });
86
+ await store.persist();
87
+ });
88
+ assert.equal(changed.writes, repetitions);
89
+ assert.equal(changed.renames, repetitions);
90
+ }
91
+ } finally {
92
+ Object.assign(fs, { readFile: original.readFile, writeFile: original.writeFile,
93
+ mkdir: original.mkdir, rename: original.rename });
94
+ JSON.parse = original.parse;
95
+ JSON.stringify = original.stringify;
96
+ syncBuiltinESMExports();
97
+ await fs.rm(root, { recursive: true, force: true });
98
+ }
99
+ console.log(JSON.stringify({ scope: "Isolated Thread store, not IPC/admission/Telegram or throughput",
100
+ counters: "Aggregate API calls and bytes for each row's repetitions; object-spread clones are not instrumented",
101
+ rows }, null, 2));
@@ -1,17 +1,37 @@
1
1
  ---
2
2
  name: show-me
3
- description: Explain the current topic or work through contextual Markdown replies, concise diagrams, code-shape sketches, and focused HTML artifacts.
3
+ description: Explain completed work, proposed changes, system structure, or ideas through truthful contextual Markdown, concise diagrams, code-shape sketches, and focused HTML artifacts. Use when the user asks to show what happened, what changed, or how something works, especially on a phone-width Telegram surface.
4
4
  ---
5
5
 
6
- Help the user understand the current topic: completed work, a proposed change, system structure, or an idea. Skip the preamble and keep prose brief. Pick the smallest view that makes the key point clear.
6
+ Help the user understand the current topic. Skip the preamble and keep prose brief. Pick the smallest view that makes the key point clear without making the underlying claim less true.
7
7
 
8
8
  ### Output Selection
9
9
 
10
10
  - Infer the subject from the conversation and honor an explicitly requested format. With plain `show me`, choose the form that best explains the subject.
11
11
  - Markdown in the reply is a complete output format for both chat and terminal surfaces. Use headings, emphasis, lists, and focused code blocks to explain outcomes, comparisons, and reasoning; add a diagram when relationships need one.
12
- - `Show me markdown` requests a rendered Markdown reply. Create a Markdown file when the user asks for a saved document or file artifact.
12
+ - `Show me markdown` requests a rendered Markdown reply. Create a Markdown file only when the user asks for a saved document or file artifact.
13
13
  - `Show me html` requests a focused HTML file. Save file artifacts in the project or filesystem as appropriate and deliver them through the active environment's file-delivery mechanism; open locally when that is the requested surface.
14
14
 
15
+ ### Surface Routing
16
+
17
+ - Infer whether the active surface is Telegram, a terminal, or another client. Keep the explanation portable and use surface-specific delivery only when the context or user request authorizes it.
18
+ - For Telegram output, read [`references/telegram-surfaces.md`](./references/telegram-surfaces.md) before selecting Markdown or HTML. Keep the immediate reply useful even when an attached artifact is the deeper view.
19
+ - When asked what changed or happened in current work, inspect the retained diff, status, and validation evidence when available. Separate this task from pre-existing changes and distinguish repository state from released or live behavior.
20
+
21
+ ### Truth And State
22
+
23
+ - Label material claims as `live`, `released`, `locally implemented`, `proposed`, or `unverified` when the distinction affects interpretation. Omit redundant labels when the state is already unambiguous.
24
+ - Preserve the exact mechanism, owner, and boundary when simplifying technical behavior. A friendly label may supplement the implementation term but must not replace it when that would merge distinct timers, states, stages, or authorities.
25
+ - Make a visual diff correspond to the actual changed symbol or contract. Show unchanged neighboring behavior when omission could imply that it changed too.
26
+ - Do not infer that an affordance is clickable, interactive, rendered, or client-visible from source or markup shape alone. Distinguish designed or expected behavior from behavior verified through the relevant renderer, transport, runtime, and client.
27
+ - Treat a mockup, diagram, or rendered artifact as explanatory evidence, not runtime proof. State the unverified boundary when the user could reasonably mistake one for the other.
28
+
29
+ A compact state line is enough when provenance matters:
30
+
31
+ ```text
32
+ State: locally implemented · validated · not released · not live
33
+ ```
34
+
15
35
  ### Visual Forms
16
36
 
17
37
  - Show logic or an algorithm as pseudocode:
@@ -127,7 +147,7 @@ function expandSkill(command: string): string {
127
147
  }
128
148
  ```
129
149
 
130
- - For a visual UI, layout, state comparison, or concept too dense for Mermaid, use a focused HTML artifact — a diagram, an infographic, or a short slide deck, whichever fits the point. Match the product's colors, type, spacing, and components; use real labels and data; support desktop and mobile.
150
+ - For a visual UI, layout, state comparison, or concept too dense for Mermaid, use a focused HTML artifact — a diagram, an infographic, or a short slide deck, whichever fits the point. Match the product's colors, type, spacing, and components; use real labels and data; support desktop and mobile. When practical, render representative narrow and wide viewports and report what was actually inspected.
131
151
 
132
152
  ### Text Rendering
133
153
 
@@ -137,8 +157,10 @@ function expandSkill(command: string): string {
137
157
  - In trees, place comments and explanations as child nodes one level below the item they describe. Keep the item's own line for its label.
138
158
  - Use prose lists for independent statuses and split larger views into meaningful sections.
139
159
 
140
- ### guidance
160
+ ### Guidance
161
+
162
+ Place each visual next to the short text it supports. Keep only the calls, files, props, states, and boundaries needed to answer the user's current question or the options to resolve the current discussion point. Prefer one primary visual and a few decision-relevant takeaways; add another form only when it explains a different necessary relationship.
141
163
 
142
- Place each visual next to the short text it supports. Keep only the calls, files, props, states, and boundaries needed to answer the user's current question or the options to resolve the current discussion point.
164
+ Use source paths, symbol names, or validation evidence when they materially anchor a claim, not as decoration. Report the strongest state the evidence supports and no stronger.
143
165
 
144
166
  You may use one of these, you may use several, it is unlikely you will use all of them. Use your judgement and don't overwhelm the user.
@@ -0,0 +1,43 @@
1
+ # Telegram Explanation Surfaces
2
+
3
+ Use this reference only when Show Me is responding through Telegram or preparing an artifact for Telegram delivery.
4
+
5
+ ## Selection
6
+
7
+ - With plain `show me`, prefer a native Markdown reply when one phone-width view can explain the point.
8
+ - Use HTML when spatial comparison, dense state, a timeline, or a visual hierarchy would become harder to understand in narrow Markdown.
9
+ - `Show me markdown` means rendered Markdown in the current reply, not a `.md` attachment, unless the user explicitly asks for a file.
10
+ - `Show me html` means one focused, self-contained `.html` artifact delivered through the active Telegram file path. The surrounding reply should say what the artifact explains and disclose its evidence state.
11
+ - Do not create both formats by habit. The second format must answer a need the first cannot.
12
+
13
+ ## Telegram Markdown
14
+
15
+ - Design for a phone before a desktop: one governing question, one primary visual, short labels, shallow nesting, and prose that wraps naturally.
16
+ - Prefer compact semantic diffs, call trees, timelines, or state transitions over raw repository diffs. A remote user needs to understand impact before file-level detail.
17
+ - Avoid wide tables, deep trees, side-by-side layouts, and Mermaid when the current Telegram renderer would expose only source text. Move genuinely spatial material to HTML.
18
+ - Put material state near the top: what changed, whether it is local or live, what was validated, and what remains unresolved.
19
+ - Keep source paths and symbol names below the explanation unless they are the explanation.
20
+ - Keep intended Telegram bot-command tokens as plain text rather than inline code. Format Pi/TUI or shell commands as code according to the host contract. Plain source shape does not prove native clickability; claim it only after the active entity-detection and client path is verified.
21
+ - Use explicit Markdown links when a destination matters rather than assuming plain URL auto-detection.
22
+
23
+ ## Telegram HTML
24
+
25
+ - Produce a single self-contained file with UTF-8 metadata and a viewport declaration. Avoid external assets, scripts, fonts, trackers, or network requirements unless the user explicitly requested them.
26
+ - Build mobile-first for roughly phone-width reading, then let the same document expand cleanly in a system or desktop browser. Text must wrap; diagrams must scroll or reflow without clipping.
27
+ - Use semantic headings, sufficient contrast, non-color status meaning, comfortable touch targets, and no hover-only information.
28
+ - Preserve real labels, values, ordering, and uncertainty. An attractive reconstruction must not invent runtime state or imply that a proposed interaction exists.
29
+ - Include a compact provenance line when state matters, such as `Local patch · validated · not released · not live`.
30
+ - When rendering tools are available, inspect at least one narrow viewport and one wider viewport. Report what was inspected; static source review is not visual proof.
31
+ - Deliver the file through the active Telegram attachment mechanism. Do not expose local paths as if the user could open them remotely.
32
+
33
+ ## Current-Work Evidence
34
+
35
+ Before explaining “what we did” or “what happened,” use the narrowest available evidence that can support the answer:
36
+
37
+ 1. Inspect retained repository status and diff for the relevant task.
38
+ 2. Identify pre-existing or unrelated changes and exclude them from the claimed task result.
39
+ 3. Name the actual changed mechanism or contract, not a friendlier neighboring concept.
40
+ 4. Separate implementation evidence from validation, release, deployment, runtime, transport, and client evidence.
41
+ 5. State unresolved causes or missing live checks instead of filling them with a cleaner story.
42
+
43
+ A visual is successful when the user can understand the outcome away from a computer without being given a stronger claim than the evidence supports.
@@ -7,7 +7,7 @@ Read this reference only for explicit local/TUI Telegram delivery, cross-target
7
7
  Use `telegram_message` only when the user explicitly requests Telegram delivery from local/TUI or names a concrete different Telegram target.
8
8
 
9
9
  - Omitted target selects the paired/default target only outside an active Telegram turn.
10
- - `chat_id` plus optional `thread_id` selects an explicit Bot API target.
10
+ - `chat_id` plus optional `thread_id` selects an explicit Bot API target. A public `@username`, or an exact negative numeric channel ID with `channel: true`, uses `chat_id` without `thread_id`; channel delivery requires the direct leader, and Telegram enforces the bot's posting permission.
11
11
  - `thread` selects another live Pi Thread by case-insensitive name or numeric id and admits one attributed turn there.
12
12
  - During an active Telegram turn, answer the current target normally; direct delivery to that same target is rejected.
13
13
  - Direct delivery requires this Pi instance to own transport or hold a live Threaded Mode registration.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/skills",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "private": false,
5
5
  "description": "Dream Skills",
6
6
  "keywords": [
@@ -32,8 +32,7 @@
32
32
  "groq-stt/",
33
33
  "mistral-stt/",
34
34
  "re-review/",
35
- "release-flow/",
36
- "show-me/"
35
+ "release-flow/"
37
36
  ],
38
37
  "publishConfig": {
39
38
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -40,13 +40,13 @@
40
40
  "LICENSE"
41
41
  ],
42
42
  "dependencies": {
43
- "@llblab/pi-actors": "0.52.1",
43
+ "@llblab/pi-actors": "0.53.0",
44
44
  "@llblab/pi-clean-room": "0.1.1",
45
45
  "@llblab/pi-codex-usage": "0.9.4",
46
46
  "@llblab/pi-grow-loop": "0.7.5",
47
- "@llblab/pi-state-flow": "0.3.0",
48
- "@llblab/pi-telegram": "0.44.0",
49
- "@llblab/skills": "1.14.1"
47
+ "@llblab/pi-state-flow": "0.6.0",
48
+ "@llblab/pi-telegram": "0.45.1",
49
+ "@llblab/skills": "1.15.0"
50
50
  },
51
51
  "bundledDependencies": [
52
52
  "@llblab/pi-actors",
@@ -69,6 +69,7 @@
69
69
  "skills": [
70
70
  "./node_modules/@llblab/pi-actors/dist/skills",
71
71
  "./node_modules/@llblab/pi-grow-loop/skills",
72
+ "./node_modules/@llblab/pi-state-flow/skills",
72
73
  "./node_modules/@llblab/pi-telegram/skills",
73
74
  "./node_modules/@llblab/skills/"
74
75
  ]