@north-light/crouter 0.3.263 → 0.3.270

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 (171) hide show
  1. package/dist/api/client.d.ts +5 -1
  2. package/dist/api/client.js +6 -0
  3. package/dist/api/dto/broker.d.ts +11 -2
  4. package/dist/api/dto/broker.js +2 -2
  5. package/dist/api/dto/canvas.d.ts +53 -2
  6. package/dist/api/dto/nodes.d.ts +10 -2
  7. package/dist/api/routes.d.ts +1 -0
  8. package/dist/api/routes.js +1 -0
  9. package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +2 -2
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +50 -0
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +46 -6
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +6 -2
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +7 -2
  14. package/dist/clients/attach/session/whip-achievements.d.ts +4 -0
  15. package/dist/clients/attach/session/whip-achievements.js +145 -0
  16. package/dist/clients/attach/slash/dispatch.js +5 -3
  17. package/dist/clients/attach/viewer.js +607 -606
  18. package/dist/commands/canvas-history/grep.js +3 -22
  19. package/dist/commands/canvas-history/read.js +5 -3
  20. package/dist/commands/canvas-history/search.js +3 -22
  21. package/dist/commands/canvas-history/shared.d.ts +8 -0
  22. package/dist/commands/canvas-history/shared.js +64 -1
  23. package/dist/commands/canvas-history/stats.d.ts +1 -0
  24. package/dist/commands/canvas-history/stats.js +68 -0
  25. package/dist/commands/canvas-history.js +4 -3
  26. package/dist/commands/node/create.js +2 -2
  27. package/dist/commands/pkg/plugin-inspect.js +2 -2
  28. package/dist/commands/pkg/plugin-manage.js +3 -3
  29. package/dist/commands/sys/config.js +19 -2
  30. package/dist/commands/sys/doctor.js +4 -3
  31. package/dist/commands/sys/panels/models-panel.js +3 -1
  32. package/dist/core/__tests__/human-deliver.test.js +1 -1
  33. package/dist/core/__tests__/integration/lifecycle-hooks.test.d.ts +1 -0
  34. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +182 -0
  35. package/dist/core/__tests__/integration/revive.test.js +19 -17
  36. package/dist/core/__tests__/model-pin-durability.test.js +25 -1
  37. package/dist/core/__tests__/push-final-guard.test.js +1 -1
  38. package/dist/core/__tests__/relaunch-root.test.js +17 -0
  39. package/dist/core/__tests__/revive-capacity.test.js +20 -20
  40. package/dist/core/__tests__/revive-parked-fresh.test.js +14 -14
  41. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +2 -2
  42. package/dist/core/__tests__/seam/broker-provider-retry.test.js +19 -4
  43. package/dist/core/__tests__/seam/dormancy-release.test.js +7 -7
  44. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +1 -1
  45. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +19 -2
  46. package/dist/core/__tests__/seam/yield-refresh-transaction.test.js +2 -2
  47. package/dist/core/broker-client/__tests__/transport-relay.test.js +3 -1
  48. package/dist/core/broker-client/transport-relay.js +5 -1
  49. package/dist/core/canvas/__tests__/history-transcript.test.d.ts +1 -0
  50. package/dist/core/canvas/__tests__/history-transcript.test.js +102 -0
  51. package/dist/core/canvas/browse/app.d.ts +1 -1
  52. package/dist/core/canvas/browse/app.js +13 -10
  53. package/dist/core/canvas/browse/model.js +16 -1
  54. package/dist/core/canvas/browse/render.d.ts +23 -3
  55. package/dist/core/canvas/browse/render.js +429 -64
  56. package/dist/core/canvas/canvas.js +12 -8
  57. package/dist/core/canvas/extensions.d.ts +1 -1
  58. package/dist/core/canvas/extensions.js +7 -1
  59. package/dist/core/canvas/history.d.ts +36 -1
  60. package/dist/core/canvas/history.js +328 -7
  61. package/dist/core/canvas/labels.d.ts +2 -2
  62. package/dist/core/canvas/migrations.js +32 -0
  63. package/dist/core/canvas/node-recap.d.ts +23 -0
  64. package/dist/core/canvas/node-recap.js +67 -0
  65. package/dist/core/canvas/render-source.d.ts +33 -1
  66. package/dist/core/canvas/render-source.js +80 -22
  67. package/dist/core/canvas/types.d.ts +9 -0
  68. package/dist/core/command-hooks/discovery.d.ts +27 -1
  69. package/dist/core/command-hooks/discovery.js +52 -0
  70. package/dist/core/command-hooks/index.d.ts +4 -3
  71. package/dist/core/command-hooks/index.js +2 -1
  72. package/dist/core/command-hooks/lifecycle-catalog.d.ts +3 -0
  73. package/dist/core/command-hooks/lifecycle-catalog.js +4 -0
  74. package/dist/core/command-hooks/report.d.ts +11 -2
  75. package/dist/core/command-hooks/report.js +10 -1
  76. package/dist/core/command-hooks/schema.d.ts +16 -1
  77. package/dist/core/command-hooks/schema.js +128 -40
  78. package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +12 -0
  79. package/dist/core/command-hooks/transport/exec-lifecycle.js +152 -0
  80. package/dist/core/config.js +13 -5
  81. package/dist/core/human/feedback-companion.js +1 -1
  82. package/dist/core/keybindings/catalog.d.ts +2 -2
  83. package/dist/core/keybindings/catalog.js +2 -1
  84. package/dist/core/model-routes.js +1 -1
  85. package/dist/core/review/realize.js +1 -1
  86. package/dist/core/runtime/broker/auth-reload.js +4 -1
  87. package/dist/core/runtime/broker/engine-drive.d.ts +14 -3
  88. package/dist/core/runtime/broker/engine-drive.js +29 -1
  89. package/dist/core/runtime/broker/fault-retry.js +7 -2
  90. package/dist/core/runtime/broker/frame-client.js +3 -0
  91. package/dist/core/runtime/broker/frame-dispatch.js +7 -0
  92. package/dist/core/runtime/broker/inbox.d.ts +12 -2
  93. package/dist/core/runtime/broker/inbox.js +68 -38
  94. package/dist/core/runtime/broker/rebind.js +7 -0
  95. package/dist/core/runtime/broker-protocol.d.ts +16 -1
  96. package/dist/core/runtime/broker.d.ts +1 -1
  97. package/dist/core/runtime/broker.js +27 -3
  98. package/dist/core/runtime/canvas-extensions.d.ts +1 -0
  99. package/dist/core/runtime/canvas-extensions.js +2 -0
  100. package/dist/core/runtime/interactive-deliver.d.ts +8 -2
  101. package/dist/core/runtime/interactive-deliver.js +12 -3
  102. package/dist/core/runtime/launch-target.js +1 -1
  103. package/dist/core/runtime/launch.d.ts +12 -6
  104. package/dist/core/runtime/launch.js +19 -17
  105. package/dist/core/runtime/model-swap.d.ts +5 -4
  106. package/dist/core/runtime/model-swap.js +7 -5
  107. package/dist/core/runtime/node-read.d.ts +2 -0
  108. package/dist/core/runtime/node-read.js +18 -9
  109. package/dist/core/runtime/promote.js +2 -1
  110. package/dist/core/runtime/reset.js +4 -2
  111. package/dist/core/runtime/revive-all.d.ts +1 -1
  112. package/dist/core/runtime/revive-all.js +2 -2
  113. package/dist/core/runtime/revive.d.ts +2 -2
  114. package/dist/core/runtime/revive.js +71 -13
  115. package/dist/core/runtime/session-visibility.d.ts +10 -14
  116. package/dist/core/runtime/session-visibility.js +43 -30
  117. package/dist/core/runtime/spawn.d.ts +3 -3
  118. package/dist/core/runtime/spawn.js +20 -3
  119. package/dist/core/runtime/stamp/channel.d.ts +22 -0
  120. package/dist/core/runtime/stamp/channel.js +83 -0
  121. package/dist/core/runtime/stamp/protocol.d.ts +90 -0
  122. package/dist/core/runtime/stamp/protocol.js +113 -0
  123. package/dist/core/runtime/tmux-driver.d.ts +11 -1
  124. package/dist/core/runtime/tmux-driver.js +27 -11
  125. package/dist/core/runtime/turn-visibility.d.ts +10 -0
  126. package/dist/core/runtime/turn-visibility.js +31 -0
  127. package/dist/core/self-update.js +2 -5
  128. package/dist/core/tui/draw.d.ts +1 -0
  129. package/dist/core/tui/draw.js +5 -1
  130. package/dist/core/tui/markdown.d.ts +17 -0
  131. package/dist/core/tui/markdown.js +407 -0
  132. package/dist/core/user-settings.d.ts +4 -0
  133. package/dist/core/user-settings.js +1 -0
  134. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -0
  135. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +2 -2
  136. package/dist/daemon/api/bridge.d.ts +3 -2
  137. package/dist/daemon/api/bridge.js +34 -9
  138. package/dist/daemon/api/handlers/attach.js +4 -4
  139. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  140. package/dist/daemon/api/handlers/canvas.js +62 -16
  141. package/dist/daemon/api/handlers/messages.js +14 -3
  142. package/dist/daemon/api/handlers/nodes.js +17 -7
  143. package/dist/daemon/api/map.js +2 -0
  144. package/dist/daemon/cron/sinks.js +3 -3
  145. package/dist/daemon/fleet.js +1 -1
  146. package/dist/daemon/messaging/node-message.js +16 -2
  147. package/dist/daemon/profile-delete.js +1 -1
  148. package/dist/daemon/reconcilers/broker-supervision.js +26 -9
  149. package/dist/daemon/reconcilers/live-obligation.d.ts +8 -1
  150. package/dist/daemon/reconcilers/live-obligation.js +17 -0
  151. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +6 -6
  152. package/dist/hook-authoring.d.ts +31 -1
  153. package/dist/hook-authoring.js +42 -5
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +3 -0
  156. package/dist/pi-extensions/canvas-inbox-watcher.js +27 -2
  157. package/dist/pi-extensions/canvas-recap.js +4 -0
  158. package/dist/pi-extensions/canvas-stamp.d.ts +14 -0
  159. package/dist/pi-extensions/canvas-stamp.js +77 -0
  160. package/dist/pi-extensions/canvas-stophook.js +3 -0
  161. package/dist/shared/__tests__/env-boundary.test.js +4 -2
  162. package/dist/shared/crtr-version.d.ts +2 -0
  163. package/dist/shared/crtr-version.js +18 -0
  164. package/dist/shared/env.d.ts +10 -0
  165. package/dist/shared/env.js +19 -4
  166. package/dist/shared/generated-context.d.ts +1 -2
  167. package/dist/shared/generated-context.js +3 -4
  168. package/dist/types.d.ts +10 -0
  169. package/dist/types.js +4 -0
  170. package/package.json +1 -1
  171. package/runtime.lock.json +2 -2
@@ -0,0 +1,83 @@
1
+ // The in-process announcement channel every delivery seam uses to tell the
2
+ // stamp writer where the next user message came from.
3
+ //
4
+ // crouter's delivery seams straddle a loader boundary: the broker is native
5
+ // ESM, while pi extensions (the inbox watcher, the stophook, the stamp writer
6
+ // itself) are Jiti-loaded and therefore CANNOT share a module singleton with
7
+ // it — the same reason held-deferred-inbox.ts exists. A process-global
8
+ // registered symbol carries the channel across that boundary, so a seam
9
+ // announces through the same two calls whichever side it lives on.
10
+ //
11
+ // Announcements are claimed by CONTENT, not by arrival order: each carries a
12
+ // signature of the text about to be sent, and the writer claims the one whose
13
+ // signature matches the message pi actually persisted. A send that never lands
14
+ // (a delivery the engine refused) therefore cannot leak its origin onto a later
15
+ // message; it simply ages out of the bounded queue.
16
+ import { createHash } from 'node:crypto';
17
+ import { setTurnVisibility } from '../turn-visibility.js';
18
+ const STAMP_CHANNEL = Symbol.for('@crouton-kit/crtr:stamp-channel');
19
+ /** Unclaimed announcements held at once. The queue is a correlation buffer, not
20
+ * a work queue: one pending announcement is the normal state. */
21
+ const MAX_PENDING = 16;
22
+ function channel() {
23
+ const host = globalThis;
24
+ const existing = host[STAMP_CHANNEL];
25
+ if (existing !== undefined)
26
+ return existing;
27
+ const created = { announcements: [], ext: [] };
28
+ host[STAMP_CHANNEL] = created;
29
+ return created;
30
+ }
31
+ /** The correlation key for one message body. Announcers pass the text they are
32
+ * about to send; the writer passes the text pi persisted. */
33
+ export function messageSignature(text) {
34
+ return createHash('sha1').update(text.trim()).digest('hex');
35
+ }
36
+ /** Announce the origin and visibility of the user message `text` that is about
37
+ * to be sent. Every ordinary announcement promotes the run to visible. */
38
+ export function announceMessageOrigin(text, origin, visibility) {
39
+ setTurnVisibility(visibility ?? 'visible');
40
+ const state = channel();
41
+ state.announcements.push({
42
+ signature: messageSignature(text),
43
+ origin,
44
+ ...(visibility === undefined ? {} : { visibility }),
45
+ });
46
+ while (state.announcements.length > MAX_PENDING)
47
+ state.announcements.shift();
48
+ }
49
+ /** Take the announced metadata for a persisted message body, or null when no
50
+ * seam announced this message. */
51
+ export function claimMessageAnnouncement(text) {
52
+ const state = channel();
53
+ const signature = messageSignature(text);
54
+ const index = state.announcements.findIndex((entry) => entry.signature === signature);
55
+ if (index < 0)
56
+ return null;
57
+ const [claimed] = state.announcements.splice(index, 1);
58
+ return {
59
+ origin: claimed.origin,
60
+ ...(claimed.visibility === undefined ? {} : { visibility: claimed.visibility }),
61
+ };
62
+ }
63
+ /** Contribute namespaced fields to the next stamp the writer emits. Unknown
64
+ * namespaces pass through untouched; the core writer never interprets them. */
65
+ export function contributeStampExt(namespace, fields) {
66
+ channel().ext.push({ namespace, fields });
67
+ }
68
+ /** Whether any contribution is waiting — an assistant message is stamped only
69
+ * when one is, so an ordinary assistant turn costs no line. */
70
+ export function hasPendingStampExt() {
71
+ return channel().ext.length > 0;
72
+ }
73
+ /** Take every buffered contribution, merged per namespace. */
74
+ export function drainStampExt() {
75
+ const state = channel();
76
+ if (state.ext.length === 0)
77
+ return undefined;
78
+ const merged = {};
79
+ for (const { namespace, fields } of state.ext.splice(0)) {
80
+ merged[namespace] = { ...merged[namespace], ...fields };
81
+ }
82
+ return merged;
83
+ }
@@ -0,0 +1,90 @@
1
+ export declare const CRTR_STAMP_CUSTOM_TYPE = "crtr-stamp";
2
+ /** Envelope version. Readers switch on `(kind, v)`. */
3
+ export declare const STAMP_SCHEMA_VERSION = 1;
4
+ export interface StampEnvelope<K extends string = string, P = object> {
5
+ v: number;
6
+ kind: K;
7
+ payload: P;
8
+ /** Plugin/contributor fields, namespaced. The core writer never interprets them. */
9
+ ext?: Record<string, Record<string, unknown>>;
10
+ }
11
+ /** One session file belongs to one node: which node, and which crtr wrote it.
12
+ * Makes the file self-describing off-box, where canvas.db is unavailable. */
13
+ export interface SessionStampPayload {
14
+ node: string;
15
+ crtr: string;
16
+ }
17
+ /** Transport a person's words arrived on. crouter core knows the attach viewer;
18
+ * other front doors announce their own channel. */
19
+ export interface HumanOrigin {
20
+ type: 'human';
21
+ channel?: {
22
+ type: string;
23
+ };
24
+ }
25
+ /** The runtime kinds mirror the `<runtime kind="…">` card vocabulary. */
26
+ export type RuntimeOriginKind = 'inbox' | 'wake' | 'revive' | 'restart-continuation' | 'stop-guard' | 'park' | 'recovery' | 'context-nudge' | 'kickoff';
27
+ /** One durable inbox entry's attribution, as delivered. */
28
+ export interface InboxOriginEntry {
29
+ from?: string;
30
+ fromName?: string;
31
+ kind: string;
32
+ tier: string;
33
+ ref?: string;
34
+ }
35
+ export interface RuntimeOrigin {
36
+ type: 'runtime';
37
+ kind: RuntimeOriginKind;
38
+ /** `wake` carries the fired cron's `WakeOrigin` under this key rather than
39
+ * spread flat: WakeOrigin has its own `kind` (`node_birth` /
40
+ * `schedule_revive`), which flat would overwrite the origin kind. */
41
+ wake?: {
42
+ kind: string;
43
+ ownerId: string | null;
44
+ ownerName?: string;
45
+ armedAt: string;
46
+ recur?: string | null;
47
+ };
48
+ /** `inbox` carries one element per delivered entry. */
49
+ entries?: InboxOriginEntry[];
50
+ /** Other kind-specific structured fields — the structure the seam holds,
51
+ * where the card carries only prose. */
52
+ [field: string]: unknown;
53
+ }
54
+ export type MessageOrigin = HumanOrigin | RuntimeOrigin;
55
+ export type MessageVisibility = 'visible' | 'internal';
56
+ export interface MessageStampPayload {
57
+ /** The target message's own `timestamp` (unix ms) — the join key. */
58
+ msgTs: number;
59
+ /** Absent means the message arrived with no announced origin (unknown),
60
+ * never a guess. */
61
+ origin?: MessageOrigin;
62
+ /** Absent means visible. Internal messages remain in the raw session and
63
+ * carry their classification through transcript projections. */
64
+ visibility?: MessageVisibility;
65
+ }
66
+ export type SessionStamp = StampEnvelope<'session', SessionStampPayload>;
67
+ export type MessageStamp = StampEnvelope<'message', MessageStampPayload>;
68
+ /** A kickoff prompt is composed by the daemon but sent by the broker, in a
69
+ * different process, so its origin rides the broker's spawn env instead of the
70
+ * in-process announcement channel. */
71
+ export declare const KICKOFF_ORIGIN_ENV = "CRTR_KICKOFF_ORIGIN";
72
+ export declare function encodeKickoffOrigin(origin: MessageOrigin): string;
73
+ /** The kickoff origin the spawner recorded, or null when this launch carried
74
+ * none (an idle broker, or a launch predating the variable). */
75
+ export declare function decodeKickoffOrigin(raw: string | undefined): MessageOrigin | null;
76
+ /** The stamp envelope carried by one session entry, or null when the entry is
77
+ * not a well-formed crtr-stamp. */
78
+ export declare function readStampEntry(entry: unknown): StampEnvelope | null;
79
+ /** Every well-formed stamp in `entries`, in file order. */
80
+ export declare function readStamps(entries: readonly unknown[]): StampEnvelope[];
81
+ /** The session stamp in force at the end of `entries` — a fork carries the
82
+ * source node's stamp until the new node appends its own, so "latest" is what
83
+ * a reader wants. */
84
+ export declare function latestSessionStamp(entries: readonly unknown[]): SessionStamp | null;
85
+ /** The stamp for the message persisted at `msgTs`. Late or revised metadata is
86
+ * a second stamp with the same key, so the last one wins. */
87
+ export declare function messageStampFor(entries: readonly unknown[], msgTs: number): MessageStamp | null;
88
+ /** Who authored the message persisted at `msgTs`, or null when nothing
89
+ * announced an origin for it. */
90
+ export declare function originFor(entries: readonly unknown[], msgTs: number): MessageOrigin | null;
@@ -0,0 +1,113 @@
1
+ // crtr-stamp — the durable per-message metadata crouter hangs on a pi session
2
+ // jsonl. One customType carries every record; the record shape is discriminated
3
+ // inside `data` by `kind`.
4
+ //
5
+ // A stamp is a pi CustomEntry: a standalone jsonl line that pi's
6
+ // buildSessionContext ignores, so nothing here ever reaches the LLM. Stamps are
7
+ // correlated to messages by the target message's own `timestamp` (`msgTs`), not
8
+ // by tree position — an extension's message_end handler runs BEFORE pi persists
9
+ // the message, so a stamp is physically the message's parent, never its child.
10
+ //
11
+ // Readers MUST ignore unknown envelope keys: that is what makes adding an
12
+ // optional field later non-breaking. `v` bumps only for a rename, a removal, or
13
+ // a change of meaning.
14
+ //
15
+ // The writer is `src/pi-extensions/canvas-stamp.ts` and nothing else.
16
+ export const CRTR_STAMP_CUSTOM_TYPE = 'crtr-stamp';
17
+ /** Envelope version. Readers switch on `(kind, v)`. */
18
+ export const STAMP_SCHEMA_VERSION = 1;
19
+ /** A kickoff prompt is composed by the daemon but sent by the broker, in a
20
+ * different process, so its origin rides the broker's spawn env instead of the
21
+ * in-process announcement channel. */
22
+ export const KICKOFF_ORIGIN_ENV = 'CRTR_KICKOFF_ORIGIN';
23
+ export function encodeKickoffOrigin(origin) {
24
+ return JSON.stringify(origin);
25
+ }
26
+ /** The kickoff origin the spawner recorded, or null when this launch carried
27
+ * none (an idle broker, or a launch predating the variable). */
28
+ export function decodeKickoffOrigin(raw) {
29
+ if (raw === undefined || raw.trim() === '')
30
+ return null;
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(raw);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ if (!isRecord(parsed))
39
+ return null;
40
+ if (parsed['type'] !== 'human' && parsed['type'] !== 'runtime')
41
+ return null;
42
+ return parsed;
43
+ }
44
+ function isRecord(value) {
45
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
46
+ }
47
+ /** The stamp envelope carried by one session entry, or null when the entry is
48
+ * not a well-formed crtr-stamp. */
49
+ export function readStampEntry(entry) {
50
+ if (!isRecord(entry))
51
+ return null;
52
+ const candidate = entry;
53
+ if (candidate.type !== 'custom' || candidate.customType !== CRTR_STAMP_CUSTOM_TYPE)
54
+ return null;
55
+ const data = candidate.data;
56
+ if (!isRecord(data))
57
+ return null;
58
+ if (typeof data['v'] !== 'number' || typeof data['kind'] !== 'string')
59
+ return null;
60
+ if (!isRecord(data['payload']))
61
+ return null;
62
+ const ext = data['ext'];
63
+ if (ext !== undefined && !isRecord(ext))
64
+ return null;
65
+ return data;
66
+ }
67
+ /** Every well-formed stamp in `entries`, in file order. */
68
+ export function readStamps(entries) {
69
+ const stamps = [];
70
+ for (const entry of entries) {
71
+ const stamp = readStampEntry(entry);
72
+ if (stamp !== null)
73
+ stamps.push(stamp);
74
+ }
75
+ return stamps;
76
+ }
77
+ function isSessionStamp(stamp) {
78
+ if (stamp.kind !== 'session' || stamp.v !== STAMP_SCHEMA_VERSION)
79
+ return false;
80
+ const payload = stamp.payload;
81
+ return typeof payload.node === 'string' && typeof payload.crtr === 'string';
82
+ }
83
+ function isMessageStamp(stamp) {
84
+ if (stamp.kind !== 'message' || stamp.v !== STAMP_SCHEMA_VERSION)
85
+ return false;
86
+ return typeof stamp.payload.msgTs === 'number';
87
+ }
88
+ /** The session stamp in force at the end of `entries` — a fork carries the
89
+ * source node's stamp until the new node appends its own, so "latest" is what
90
+ * a reader wants. */
91
+ export function latestSessionStamp(entries) {
92
+ let latest = null;
93
+ for (const stamp of readStamps(entries)) {
94
+ if (isSessionStamp(stamp))
95
+ latest = stamp;
96
+ }
97
+ return latest;
98
+ }
99
+ /** The stamp for the message persisted at `msgTs`. Late or revised metadata is
100
+ * a second stamp with the same key, so the last one wins. */
101
+ export function messageStampFor(entries, msgTs) {
102
+ let match = null;
103
+ for (const stamp of readStamps(entries)) {
104
+ if (isMessageStamp(stamp) && stamp.payload.msgTs === msgTs)
105
+ match = stamp;
106
+ }
107
+ return match;
108
+ }
109
+ /** Who authored the message persisted at `msgTs`, or null when nothing
110
+ * announced an origin for it. */
111
+ export function originFor(entries, msgTs) {
112
+ return messageStampFor(entries, msgTs)?.payload.origin ?? null;
113
+ }
@@ -170,6 +170,14 @@ export declare function paneSize(pane: string): {
170
170
  * current client when `pane` is omitted). `ms` sets the display duration. Used
171
171
  * by `crtr canvas issue new` to confirm a capture from inside its popup.
172
172
  * Best-effort; never throws. */
173
+ /** Show a transient toast in the target client's status line.
174
+ *
175
+ * `-C` is load-bearing, not decoration: without it tmux STOPS painting that
176
+ * client's panes for the whole delay, so every crtr toast freezes a live
177
+ * viewer mid-turn until the message expires or a keypress dismisses it. The
178
+ * flag is newer than the tmux versions crouter still runs on, and a tmux that
179
+ * rejects it prints usage and shows nothing — so a rejected call retries in
180
+ * the plain form, where the toast is worth more than the paint stall. */
173
181
  export declare function displayMessage(message: string, pane?: string, ms?: number): boolean;
174
182
  /** Set a PANE-scoped tmux option (`tmux set-option -p -t <pane> <name> <value>`).
175
183
  * Best-effort; never throws. */
@@ -200,7 +208,9 @@ export declare function liveViewerPanes(socket?: string): LiveViewerPane[];
200
208
  * `-l` bypasses tmux key-table lookup, so delivery cannot re-trigger a root
201
209
  * binding that initiated it. */
202
210
  export declare function sendAttachControl(socket: string | undefined, pane: string, actionId: AttachBindingId): boolean;
203
- /** Show a tmux status-line message on a specific client when one is known. */
211
+ /** Show a tmux status-line message on a specific client when one is known.
212
+ * `-C` for the same reason as `displayMessage`: the client's panes must keep
213
+ * painting while the message sits in its status line. */
204
214
  export declare function displayClientMessage(socket: string | undefined, client: string | undefined, text: string): boolean;
205
215
  export interface RespawnPaneOpts {
206
216
  /** Target pane id (e.g. `%3`) — the pane to re-exec in place. */
@@ -338,12 +338,23 @@ export function paneSize(pane) {
338
338
  * current client when `pane` is omitted). `ms` sets the display duration. Used
339
339
  * by `crtr canvas issue new` to confirm a capture from inside its popup.
340
340
  * Best-effort; never throws. */
341
+ /** Show a transient toast in the target client's status line.
342
+ *
343
+ * `-C` is load-bearing, not decoration: without it tmux STOPS painting that
344
+ * client's panes for the whole delay, so every crtr toast freezes a live
345
+ * viewer mid-turn until the message expires or a keypress dismisses it. The
346
+ * flag is newer than the tmux versions crouter still runs on, and a tmux that
347
+ * rejects it prints usage and shows nothing — so a rejected call retries in
348
+ * the plain form, where the toast is worth more than the paint stall. */
341
349
  export function displayMessage(message, pane, ms = 4000) {
342
- const args = ['display-message', '-d', String(ms)];
343
- if (pane !== undefined && pane !== '')
344
- args.push('-t', pane);
345
- args.push(message);
346
- return tmux(args).ok;
350
+ const args = (paneKeepsPainting) => {
351
+ const argv = ['display-message', ...(paneKeepsPainting ? ['-C'] : []), '-d', String(ms)];
352
+ if (pane !== undefined && pane !== '')
353
+ argv.push('-t', pane);
354
+ argv.push(message);
355
+ return argv;
356
+ };
357
+ return tmux(args(true)).ok || tmux(args(false)).ok;
347
358
  }
348
359
  /** Set a PANE-scoped tmux option (`tmux set-option -p -t <pane> <name> <value>`).
349
360
  * Best-effort; never throws. */
@@ -464,13 +475,18 @@ export function liveViewerPanes(socket) {
464
475
  export function sendAttachControl(socket, pane, actionId) {
465
476
  return tmuxOnSocket(socket, ['send-keys', '-l', '-t', pane, encodeAttachControlInput(actionId)]).ok;
466
477
  }
467
- /** Show a tmux status-line message on a specific client when one is known. */
478
+ /** Show a tmux status-line message on a specific client when one is known.
479
+ * `-C` for the same reason as `displayMessage`: the client's panes must keep
480
+ * painting while the message sits in its status line. */
468
481
  export function displayClientMessage(socket, client, text) {
469
- const args = ['display-message'];
470
- if (client !== undefined && client !== '')
471
- args.push('-c', client);
472
- args.push(text);
473
- return tmuxOnSocket(socket, args).ok;
482
+ const args = (paneKeepsPainting) => {
483
+ const argv = ['display-message', ...(paneKeepsPainting ? ['-C'] : [])];
484
+ if (client !== undefined && client !== '')
485
+ argv.push('-c', client);
486
+ argv.push(text);
487
+ return argv;
488
+ };
489
+ return tmuxOnSocket(socket, args(true)).ok || tmuxOnSocket(socket, args(false)).ok;
474
490
  }
475
491
  /** The `respawn-pane -k` argv for `opts`. `-k` kills the pane's current process
476
492
  * (e.g. a yielding pi) and re-execs `command` in the SAME pane, preserving its
@@ -0,0 +1,10 @@
1
+ import type { MessageVisibility } from './stamp/protocol.js';
2
+ export type TurnVisibility = MessageVisibility;
3
+ /** The presentation classification of the current agent run. */
4
+ export declare function currentTurnVisibility(): TurnVisibility;
5
+ /** Whether the current agent run is structurally internal to crouter. */
6
+ export declare function isTurnInternal(): boolean;
7
+ /** Set visibility before handing an admitted input to pi. */
8
+ export declare function setTurnVisibility(visibility: TurnVisibility): void;
9
+ /** Return to the fail-open state after the complete agent run settles. */
10
+ export declare function clearTurnVisibility(): void;
@@ -0,0 +1,31 @@
1
+ // Process-global visibility for the agent run currently owned by the broker.
2
+ //
3
+ // The broker is native ESM while guest and crouter pi extensions are Jiti-loaded,
4
+ // so ordinary module state splits at the loader boundary. A registered symbol
5
+ // keeps crouter's decision visible to every in-process extension.
6
+ const TURN_VISIBILITY = Symbol.for('@crouton-kit/crtr:turn-visibility');
7
+ function state() {
8
+ const host = globalThis;
9
+ const existing = host[TURN_VISIBILITY];
10
+ if (existing !== undefined)
11
+ return existing;
12
+ const created = { value: 'visible' };
13
+ host[TURN_VISIBILITY] = created;
14
+ return created;
15
+ }
16
+ /** The presentation classification of the current agent run. */
17
+ export function currentTurnVisibility() {
18
+ return state().value;
19
+ }
20
+ /** Whether the current agent run is structurally internal to crouter. */
21
+ export function isTurnInternal() {
22
+ return currentTurnVisibility() === 'internal';
23
+ }
24
+ /** Set visibility before handing an admitted input to pi. */
25
+ export function setTurnVisibility(visibility) {
26
+ state().value = visibility;
27
+ }
28
+ /** Return to the fail-open state after the complete agent run settles. */
29
+ export function clearTurnVisibility() {
30
+ state().value = 'visible';
31
+ }
@@ -4,20 +4,17 @@
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { join, dirname } from 'node:path';
7
- import { readFileSync } from 'node:fs';
8
7
  import { listAllPlugins, listAllMarketplaces } from './resolver.js';
8
+ import { crtrVersion } from '../shared/crtr-version.js';
9
9
  import { fetch, currentSha, remoteSha } from './git.js';
10
10
  import { general } from './errors.js';
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
13
  // src/core/self-update.ts → up to src/ → up to pkg root
14
14
  const PKG_ROOT = join(__dirname, '..', '..');
15
- const PACKAGE_JSON_PATH = join(PKG_ROOT, 'package.json');
16
15
  export const SELF_UPDATE_SCRIPT = join(PKG_ROOT, 'scripts', 'self-update.mjs');
17
16
  export function currentVersion() {
18
- const raw = readFileSync(PACKAGE_JSON_PATH, 'utf8');
19
- const parsed = JSON.parse(raw);
20
- return parsed.version;
17
+ return crtrVersion();
21
18
  }
22
19
  export function selfUpdate() {
23
20
  const res = spawnSync(process.execPath, [SELF_UPDATE_SCRIPT], { stdio: 'inherit' });
@@ -13,6 +13,7 @@ export interface Style {
13
13
  bg?: string;
14
14
  bold?: boolean;
15
15
  dim?: boolean;
16
+ italic?: boolean;
16
17
  reverse?: boolean;
17
18
  }
18
19
  export interface Span {
@@ -21,6 +21,7 @@ const RESET = `${ESC}0m`;
21
21
  const REVERSE = `${ESC}7m`;
22
22
  const DIM = `${ESC}2m`;
23
23
  const BOLD = `${ESC}1m`;
24
+ const ITALIC = `${ESC}3m`;
24
25
  /** An SGR fg/bg parameter is digits and semicolons only (e.g. '32', '1;36', '236').
25
26
  * Guards styleSpan against a non-numeric value (a color name) producing a broken
26
27
  * escape sequence. */
@@ -39,6 +40,8 @@ export function styleSpan(text, style, color, lineBase) {
39
40
  pre += DIM;
40
41
  if (style?.bold)
41
42
  pre += BOLD;
43
+ if (style?.italic)
44
+ pre += ITALIC;
42
45
  if (style?.reverse)
43
46
  pre += REVERSE;
44
47
  // fg is an SGR parameter string (e.g. '32' or '1;36'); bg is a 256-color index.
@@ -104,6 +107,7 @@ function mergeStyle(base, top) {
104
107
  bg: top.bg ?? base.bg,
105
108
  bold: top.bold || base.bold,
106
109
  dim: top.dim || base.dim,
110
+ italic: top.italic || base.italic,
107
111
  reverse: top.reverse || base.reverse,
108
112
  };
109
113
  }
@@ -113,7 +117,7 @@ function sameStyle(a, b) {
113
117
  if (!a || !b)
114
118
  return false;
115
119
  return a.fg === b.fg && a.bg === b.bg
116
- && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.reverse === !!b.reverse;
120
+ && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.italic === !!b.italic && !!a.reverse === !!b.reverse;
117
121
  }
118
122
  /** Create a screen-sized cell buffer + the absolute-cell Draw API over it. */
119
123
  export function createDraw(size, caps) {
@@ -0,0 +1,17 @@
1
+ import { type Span, type Style } from './draw.js';
2
+ /** Parse inline markup left to right, recursing on delimited content with the
3
+ * base style extended. Unclosed or implausible delimiters stay literal, which is
4
+ * the right failure mode for a preview: never eat the user's text. */
5
+ export declare function inlineSpans(src: string, base?: Style): Span[];
6
+ /**
7
+ * Render `src` as markdown into at most `maxLines` span groups, each already
8
+ * wrapped to `width` visible columns.
9
+ *
10
+ * `spacing` inserts a blank line between blocks — true where the panel has room
11
+ * to breathe, false in a cramped one where every line counts. Consecutive list
12
+ * items never get one, so a tight list stays tight.
13
+ *
14
+ * `base` is the style every rendered run inherits (the caller's block style,
15
+ * e.g. the dim of a report body); markdown's own styles layer over it.
16
+ */
17
+ export declare function renderMarkdownSpans(src: string, width: number, maxLines: number, spacing?: boolean, base?: Style): Span[][];