@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.
- package/dist/api/client.d.ts +5 -1
- package/dist/api/client.js +6 -0
- package/dist/api/dto/broker.d.ts +11 -2
- package/dist/api/dto/broker.js +2 -2
- package/dist/api/dto/canvas.d.ts +53 -2
- package/dist/api/dto/nodes.d.ts +10 -2
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +46 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +6 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +7 -2
- package/dist/clients/attach/session/whip-achievements.d.ts +4 -0
- package/dist/clients/attach/session/whip-achievements.js +145 -0
- package/dist/clients/attach/slash/dispatch.js +5 -3
- package/dist/clients/attach/viewer.js +607 -606
- package/dist/commands/canvas-history/grep.js +3 -22
- package/dist/commands/canvas-history/read.js +5 -3
- package/dist/commands/canvas-history/search.js +3 -22
- package/dist/commands/canvas-history/shared.d.ts +8 -0
- package/dist/commands/canvas-history/shared.js +64 -1
- package/dist/commands/canvas-history/stats.d.ts +1 -0
- package/dist/commands/canvas-history/stats.js +68 -0
- package/dist/commands/canvas-history.js +4 -3
- package/dist/commands/node/create.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +2 -2
- package/dist/commands/pkg/plugin-manage.js +3 -3
- package/dist/commands/sys/config.js +19 -2
- package/dist/commands/sys/doctor.js +4 -3
- package/dist/commands/sys/panels/models-panel.js +3 -1
- package/dist/core/__tests__/human-deliver.test.js +1 -1
- package/dist/core/__tests__/integration/lifecycle-hooks.test.d.ts +1 -0
- package/dist/core/__tests__/integration/lifecycle-hooks.test.js +182 -0
- package/dist/core/__tests__/integration/revive.test.js +19 -17
- package/dist/core/__tests__/model-pin-durability.test.js +25 -1
- package/dist/core/__tests__/push-final-guard.test.js +1 -1
- package/dist/core/__tests__/relaunch-root.test.js +17 -0
- package/dist/core/__tests__/revive-capacity.test.js +20 -20
- package/dist/core/__tests__/revive-parked-fresh.test.js +14 -14
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +2 -2
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +19 -4
- package/dist/core/__tests__/seam/dormancy-release.test.js +7 -7
- package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +1 -1
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +19 -2
- package/dist/core/__tests__/seam/yield-refresh-transaction.test.js +2 -2
- package/dist/core/broker-client/__tests__/transport-relay.test.js +3 -1
- package/dist/core/broker-client/transport-relay.js +5 -1
- package/dist/core/canvas/__tests__/history-transcript.test.d.ts +1 -0
- package/dist/core/canvas/__tests__/history-transcript.test.js +102 -0
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +13 -10
- package/dist/core/canvas/browse/model.js +16 -1
- package/dist/core/canvas/browse/render.d.ts +23 -3
- package/dist/core/canvas/browse/render.js +429 -64
- package/dist/core/canvas/canvas.js +12 -8
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +7 -1
- package/dist/core/canvas/history.d.ts +36 -1
- package/dist/core/canvas/history.js +328 -7
- package/dist/core/canvas/labels.d.ts +2 -2
- package/dist/core/canvas/migrations.js +32 -0
- package/dist/core/canvas/node-recap.d.ts +23 -0
- package/dist/core/canvas/node-recap.js +67 -0
- package/dist/core/canvas/render-source.d.ts +33 -1
- package/dist/core/canvas/render-source.js +80 -22
- package/dist/core/canvas/types.d.ts +9 -0
- package/dist/core/command-hooks/discovery.d.ts +27 -1
- package/dist/core/command-hooks/discovery.js +52 -0
- package/dist/core/command-hooks/index.d.ts +4 -3
- package/dist/core/command-hooks/index.js +2 -1
- package/dist/core/command-hooks/lifecycle-catalog.d.ts +3 -0
- package/dist/core/command-hooks/lifecycle-catalog.js +4 -0
- package/dist/core/command-hooks/report.d.ts +11 -2
- package/dist/core/command-hooks/report.js +10 -1
- package/dist/core/command-hooks/schema.d.ts +16 -1
- package/dist/core/command-hooks/schema.js +128 -40
- package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +12 -0
- package/dist/core/command-hooks/transport/exec-lifecycle.js +152 -0
- package/dist/core/config.js +13 -5
- package/dist/core/human/feedback-companion.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/model-routes.js +1 -1
- package/dist/core/review/realize.js +1 -1
- package/dist/core/runtime/broker/auth-reload.js +4 -1
- package/dist/core/runtime/broker/engine-drive.d.ts +14 -3
- package/dist/core/runtime/broker/engine-drive.js +29 -1
- package/dist/core/runtime/broker/fault-retry.js +7 -2
- package/dist/core/runtime/broker/frame-client.js +3 -0
- package/dist/core/runtime/broker/frame-dispatch.js +7 -0
- package/dist/core/runtime/broker/inbox.d.ts +12 -2
- package/dist/core/runtime/broker/inbox.js +68 -38
- package/dist/core/runtime/broker/rebind.js +7 -0
- package/dist/core/runtime/broker-protocol.d.ts +16 -1
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +27 -3
- package/dist/core/runtime/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- package/dist/core/runtime/interactive-deliver.d.ts +8 -2
- package/dist/core/runtime/interactive-deliver.js +12 -3
- package/dist/core/runtime/launch-target.js +1 -1
- package/dist/core/runtime/launch.d.ts +12 -6
- package/dist/core/runtime/launch.js +19 -17
- package/dist/core/runtime/model-swap.d.ts +5 -4
- package/dist/core/runtime/model-swap.js +7 -5
- package/dist/core/runtime/node-read.d.ts +2 -0
- package/dist/core/runtime/node-read.js +18 -9
- package/dist/core/runtime/promote.js +2 -1
- package/dist/core/runtime/reset.js +4 -2
- package/dist/core/runtime/revive-all.d.ts +1 -1
- package/dist/core/runtime/revive-all.js +2 -2
- package/dist/core/runtime/revive.d.ts +2 -2
- package/dist/core/runtime/revive.js +71 -13
- package/dist/core/runtime/session-visibility.d.ts +10 -14
- package/dist/core/runtime/session-visibility.js +43 -30
- package/dist/core/runtime/spawn.d.ts +3 -3
- package/dist/core/runtime/spawn.js +20 -3
- package/dist/core/runtime/stamp/channel.d.ts +22 -0
- package/dist/core/runtime/stamp/channel.js +83 -0
- package/dist/core/runtime/stamp/protocol.d.ts +90 -0
- package/dist/core/runtime/stamp/protocol.js +113 -0
- package/dist/core/runtime/tmux-driver.d.ts +11 -1
- package/dist/core/runtime/tmux-driver.js +27 -11
- package/dist/core/runtime/turn-visibility.d.ts +10 -0
- package/dist/core/runtime/turn-visibility.js +31 -0
- package/dist/core/self-update.js +2 -5
- package/dist/core/tui/draw.d.ts +1 -0
- package/dist/core/tui/draw.js +5 -1
- package/dist/core/tui/markdown.d.ts +17 -0
- package/dist/core/tui/markdown.js +407 -0
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +2 -2
- package/dist/daemon/api/bridge.d.ts +3 -2
- package/dist/daemon/api/bridge.js +34 -9
- package/dist/daemon/api/handlers/attach.js +4 -4
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +62 -16
- package/dist/daemon/api/handlers/messages.js +14 -3
- package/dist/daemon/api/handlers/nodes.js +17 -7
- package/dist/daemon/api/map.js +2 -0
- package/dist/daemon/cron/sinks.js +3 -3
- package/dist/daemon/fleet.js +1 -1
- package/dist/daemon/messaging/node-message.js +16 -2
- package/dist/daemon/profile-delete.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.js +26 -9
- package/dist/daemon/reconcilers/live-obligation.d.ts +8 -1
- package/dist/daemon/reconcilers/live-obligation.js +17 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +6 -6
- package/dist/hook-authoring.d.ts +31 -1
- package/dist/hook-authoring.js +42 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +27 -2
- package/dist/pi-extensions/canvas-recap.js +4 -0
- package/dist/pi-extensions/canvas-stamp.d.ts +14 -0
- package/dist/pi-extensions/canvas-stamp.js +77 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -0
- package/dist/shared/__tests__/env-boundary.test.js +4 -2
- package/dist/shared/crtr-version.d.ts +2 -0
- package/dist/shared/crtr-version.js +18 -0
- package/dist/shared/env.d.ts +10 -0
- package/dist/shared/env.js +19 -4
- package/dist/shared/generated-context.d.ts +1 -2
- package/dist/shared/generated-context.js +3 -4
- package/dist/types.d.ts +10 -0
- package/dist/types.js +4 -0
- package/package.json +1 -1
- 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 =
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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 =
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
}
|
package/dist/core/self-update.js
CHANGED
|
@@ -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
|
-
|
|
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' });
|
package/dist/core/tui/draw.d.ts
CHANGED
package/dist/core/tui/draw.js
CHANGED
|
@@ -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[][];
|