@astrosheep/square 0.3.86 → 0.3.88

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 (143) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +14 -10
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +1 -0
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +8 -11
  36. package/dist/inbox.d.ts +35 -9
  37. package/dist/inbox.js +228 -144
  38. package/dist/index.d.ts +3 -4
  39. package/dist/index.js +1 -3
  40. package/dist/list.js +9 -4
  41. package/dist/mcp.js +12 -1
  42. package/dist/model.d.ts +39 -5
  43. package/dist/model.js +84 -0
  44. package/dist/notifications.d.ts +8 -6
  45. package/dist/notifications.js +56 -143
  46. package/dist/operation-presentation.js +7 -4
  47. package/dist/participant-identity.d.ts +0 -3
  48. package/dist/participant-identity.js +0 -31
  49. package/dist/paseo-delivery.d.ts +5 -6
  50. package/dist/paseo-delivery.js +13 -8
  51. package/dist/paseo.d.ts +1 -1
  52. package/dist/paseo.js +1 -1
  53. package/dist/plugins/opencode/delivery.d.ts +8 -3
  54. package/dist/plugins/opencode/delivery.js +187 -132
  55. package/dist/plugins/opencode/index.js +12 -38
  56. package/dist/ports.d.ts +112 -40
  57. package/dist/presentation-operations.d.ts +1 -1
  58. package/dist/presentation-operations.js +51 -35
  59. package/dist/presentation.js +8 -3
  60. package/dist/seats.d.ts +7 -0
  61. package/dist/seats.js +27 -0
  62. package/dist/session-lifecycle.d.ts +2 -2
  63. package/dist/session-lifecycle.js +10 -12
  64. package/dist/sqlite-square.d.ts +20 -9
  65. package/dist/sqlite-square.js +476 -341
  66. package/dist/square-api.d.ts +1 -0
  67. package/dist/square-api.js +3 -4
  68. package/dist/square-core.d.ts +3 -2
  69. package/dist/square-domain.d.ts +12 -5
  70. package/dist/square-domain.js +163 -7
  71. package/dist/square-facade.d.ts +16 -10
  72. package/dist/square-index.d.ts +24 -0
  73. package/dist/square-index.js +131 -0
  74. package/dist/square-legacy-framing.d.ts +9 -0
  75. package/dist/square-legacy-framing.js +56 -0
  76. package/dist/square-migration-host-ledger.d.ts +26 -0
  77. package/dist/square-migration-host-ledger.js +168 -0
  78. package/dist/square-migration.d.ts +5 -5
  79. package/dist/square-migration.js +373 -71
  80. package/dist/square-sqlite.d.ts +9 -9
  81. package/dist/square-sqlite.js +329 -79
  82. package/dist/square-stdio.d.ts +1 -1
  83. package/dist/square-stdio.js +12 -7
  84. package/dist/square-tools.d.ts +1 -1
  85. package/dist/square-tools.js +80 -27
  86. package/dist/stdio-server.js +4 -2
  87. package/dist/stream.d.ts +3 -2
  88. package/dist/stream.js +9 -10
  89. package/dist/views.d.ts +9 -14
  90. package/dist/views.js +52 -70
  91. package/dist/wakes.d.ts +6 -5
  92. package/dist/wakes.js +20 -17
  93. package/guides/application-facade-delivery-evidence.md +1 -1
  94. package/guides/claude-native.md +9 -9
  95. package/guides/mcp.md +2 -2
  96. package/guides/migration.md +107 -67
  97. package/guides/pi-tools.md +1 -1
  98. package/guides/row-storage-design.md +1 -1
  99. package/guides/square-api.md +61 -28
  100. package/package.json +1 -1
  101. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  102. package/plugins/claude/hooks/register.js +1 -1
  103. package/plugins/claude/skills/square/SKILL.md +4 -4
  104. package/plugins/claude/types.d.ts +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +170 -118
  107. package/plugins/opencode/index.ts +12 -30
  108. package/plugins/pi/square-pi.js +56 -18
  109. package/skills/square/SKILL.md +4 -4
  110. package/dist/cli/ledger-command.d.ts +0 -12
  111. package/dist/cli/ledger-command.js +0 -72
  112. package/dist/host-ledger-file-adapter.d.ts +0 -47
  113. package/dist/host-ledger-file-adapter.js +0 -384
  114. package/dist/host-ledger-migration.d.ts +0 -27
  115. package/dist/host-ledger-migration.js +0 -264
  116. package/dist/host-ledger-root.d.ts +0 -2
  117. package/dist/host-ledger-root.js +0 -9
  118. package/dist/host-ledger-sqlite.d.ts +0 -65
  119. package/dist/host-ledger-sqlite.js +0 -368
  120. package/dist/host-ledger.d.ts +0 -195
  121. package/dist/host-ledger.js +0 -1
  122. package/dist/ledger-maintenance.d.ts +0 -30
  123. package/dist/ledger-maintenance.js +0 -110
  124. package/dist/open-square.d.ts +0 -38
  125. package/dist/open-square.js +0 -81
  126. package/dist/participant-host.d.ts +0 -23
  127. package/dist/participant-host.js +0 -92
  128. package/dist/registry.d.ts +0 -55
  129. package/dist/registry.js +0 -123
  130. package/dist/routes.d.ts +0 -39
  131. package/dist/routes.js +0 -99
  132. package/dist/square-actions.d.ts +0 -56
  133. package/dist/square-actions.js +0 -353
  134. package/dist/square-projections.d.ts +0 -31
  135. package/dist/square-projections.js +0 -64
  136. package/dist/wake-attempts.d.ts +0 -10
  137. package/dist/wake-attempts.js +0 -39
  138. package/dist/wake-eligibility.d.ts +0 -53
  139. package/dist/wake-eligibility.js +0 -123
  140. package/dist/wake-evidence.d.ts +0 -33
  141. package/dist/wake-evidence.js +0 -32
  142. package/guides/host-ledger.md +0 -58
  143. package/guides/indexed-host-ledger-performance.md +0 -39
@@ -1,110 +0,0 @@
1
- import { createArtifactPort } from './artifact.js';
2
- import { canonicalPath } from './canonical-path.js';
3
- import { formatActivityId, parseActivityId } from './square-core.js';
4
- import { validateName } from './model.js';
5
- import { hostLedgerForEnv } from './registry.js';
6
- /**
7
- * Explicit host-ledger evidence maintenance.
8
- *
9
- * Old wake/presentation rows are retired only when a typed artifact snapshot proves the
10
- * recipient's attention is permanently closed: the activity reached them historically, and
11
- * either catch recorded a receipt or the recipient joined again past it. Everything else is
12
- * retained. Artifacts are read directly, one snapshot per canonical location, outside any
13
- * ledger lock; retirement is the ledger's exact-record compare-and-swap. No SQL lives here.
14
- */
15
- /** A row is old enough to consider once its landing time is at least this far behind now. */
16
- const RETIREMENT_AGE_MS = 7 * 24 * 60 * 60 * 1000;
17
- /** The canonical activity coordinate the row names; leading zeros and other spellings are invalid. */
18
- function activityIndex(activity) {
19
- const index = parseActivityId(activity);
20
- return index === undefined || formatActivityId(index) !== activity ? undefined : index;
21
- }
22
- /** Old, durably closed-looking wake/presentation rows with usable coordinates. */
23
- function isCandidate(record, now) {
24
- if (record.kind !== 'wake' && record.kind !== 'presentation')
25
- return false;
26
- if (record.outcome === 'dispatching')
27
- return false;
28
- if (typeof record.at !== 'number' || !Number.isFinite(record.at) || record.at > now - RETIREMENT_AGE_MS)
29
- return false;
30
- if (typeof record.location !== 'string' || record.location.length === 0)
31
- return false;
32
- try {
33
- validateName(record.participant);
34
- }
35
- catch {
36
- return false;
37
- }
38
- return true;
39
- }
40
- /**
41
- * One artifact snapshot for one location either proves each candidate closed or reports the
42
- * whole location unavailable. A missing, unreadable, or non-artifact path returns undefined
43
- * and its rows are retained.
44
- */
45
- async function provenClosable(location, group) {
46
- const artifact = createArtifactPort(location);
47
- try {
48
- return (await artifact.read((facts) => group.filter((candidate) => {
49
- const participant = candidate.record.participant;
50
- if (facts.directedAt(participant, candidate.index) === undefined)
51
- return false;
52
- if (facts.receipt(participant, candidate.index) !== undefined)
53
- return true;
54
- const lastJoin = facts.participant(participant)?.lastJoinIndex;
55
- return lastJoin !== undefined && lastJoin > candidate.index;
56
- }))).value;
57
- }
58
- catch {
59
- return undefined;
60
- }
61
- finally {
62
- await artifact.close();
63
- }
64
- }
65
- /** Maintenance entry: root selection uses the caller's captured environment. */
66
- export function gcHostLedger(options = {}) {
67
- return maintainHostLedger(hostLedgerForEnv(options.env ?? process.env), options);
68
- }
69
- export async function maintainHostLedger(ledger, options = {}) {
70
- const dryRun = options.dryRun === true;
71
- const now = options.now ?? Date.now();
72
- const records = await ledger.listEvidence({ includeReleased: true, includeExpired: true, now });
73
- const groups = new Map();
74
- for (const record of records) {
75
- if (!isCandidate(record, now))
76
- continue;
77
- const index = activityIndex(record.activity);
78
- if (index === undefined)
79
- continue;
80
- const location = await canonicalPath(record.location).catch(() => undefined);
81
- if (location === undefined)
82
- continue;
83
- const group = groups.get(location);
84
- if (group === undefined)
85
- groups.set(location, [{ record, index }]);
86
- else
87
- group.push({ record, index });
88
- }
89
- let eligible = 0;
90
- let retired = 0;
91
- let casMisses = 0;
92
- const unavailable = new Set();
93
- for (const location of [...groups.keys()].sort()) {
94
- const closable = await provenClosable(location, groups.get(location));
95
- if (closable === undefined) {
96
- unavailable.add(location);
97
- continue;
98
- }
99
- eligible += closable.length;
100
- if (dryRun)
101
- continue;
102
- for (const candidate of closable) {
103
- if (await ledger.retireEvidenceIfUnchanged(candidate.record))
104
- retired += 1;
105
- else
106
- casMisses += 1;
107
- }
108
- }
109
- return { dryRun, eligible, retired, casMisses, unavailableArtifacts: [...unavailable].sort() };
110
- }
@@ -1,38 +0,0 @@
1
- import type { HostLedgerPort, SquareArtifactPort, SquareResourceOwner, WakeTransportPort } from './ports.js';
2
- import type { OperationControl } from './square-facade.js';
3
- /** Host-bound resource: artifact, captured environment, clock, ledger and transport. */
4
- export interface OpenSquare {
5
- readonly artifact: SquareArtifactPort;
6
- readonly clock: () => number;
7
- readonly location: string;
8
- readonly hostLedger?: HostLedgerPort;
9
- readonly wakeTransport?: WakeTransportPort;
10
- readonly env?: NodeJS.ProcessEnv;
11
- /** False when a long-lived owner lent the connection; closing this view leaves it open. */
12
- readonly ownsArtifact: boolean;
13
- readonly automaticDelivery?: boolean;
14
- }
15
- export interface SquareBuildOptions {
16
- markdown: string;
17
- hardCap?: number | null;
18
- throttlePerMinute?: number;
19
- clock?: () => number;
20
- hostLedger?: HostLedgerPort;
21
- wakeTransport?: WakeTransportPort;
22
- env?: NodeJS.ProcessEnv;
23
- /** One caller deadline propagated into artifact busy retries. */
24
- signal?: AbortSignal;
25
- }
26
- export interface SquareOpenOptions extends Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'> {
27
- /** A caller-owned connection to reuse; this view never closes it. */
28
- readonly artifact?: SquareArtifactPort;
29
- readonly automaticDelivery?: boolean;
30
- }
31
- export declare function openSquare(squarePath: string, options?: SquareOpenOptions, control?: OperationControl): Promise<OpenSquare>;
32
- export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
33
- export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
34
- export declare function closeOpenSquare(square: OpenSquare): Promise<void>;
35
- /** Explicit-lifetime holder of one reused artifact connection. The first request
36
- * binds the location; a request for any other location is refused instead of
37
- * silently sharing the wrong square. The owner closes the connection once. */
38
- export declare function createSquareResourceOwner(): SquareResourceOwner;
@@ -1,81 +0,0 @@
1
- import path from 'node:path';
2
- import { createArtifactPort, createSquare, prepareSquareBuild } from './artifact.js';
3
- import { InternalSquareError, SquareError } from './model.js';
4
- import { hostLedgerRoot } from './host-ledger-root.js';
5
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
6
- export async function openSquare(squarePath, options = {}, control) {
7
- const env = { ...(options.env ?? process.env) };
8
- const borrowed = options.artifact;
9
- // A borrowed connection was opened by its owner without a request signal; this
10
- // request may only abort its own read, never the shared connection's lifetime.
11
- const artifact = borrowed ?? createArtifactPort(squarePath, undefined, options.signal);
12
- try {
13
- await artifact.read(facts => facts.header(), control?.signal);
14
- return {
15
- artifact, automaticDelivery: options.automaticDelivery, clock: options.clock ?? Date.now, location: squarePath, env, ownsArtifact: borrowed === undefined,
16
- hostLedger: options.hostLedger ?? createHostLedgerPort({ rootPath: hostLedgerRoot(env) }),
17
- wakeTransport: options.wakeTransport,
18
- };
19
- }
20
- catch (error) {
21
- if (borrowed === undefined)
22
- await artifact.close();
23
- if (error instanceof InternalSquareError && error.code === 'not_found')
24
- throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
25
- throw error;
26
- }
27
- }
28
- export async function buildSquare(squarePath, options) {
29
- try {
30
- await createSquare(squarePath, { ...options, force: false }, options.markdown, options.signal);
31
- }
32
- catch (error) {
33
- if (error instanceof InternalSquareError && error.code === 'conflict')
34
- throw new SquareError('io', `Cannot build over an existing square at ${squarePath}`);
35
- throw error;
36
- }
37
- return openSquare(squarePath, { ...options, signal: undefined });
38
- }
39
- export function buildMemorySquare(options) {
40
- return {
41
- artifact: createArtifactPort(':memory:', prepareSquareBuild(options, options.markdown)),
42
- clock: options.clock ?? Date.now, location: 'memory',
43
- hostLedger: options.hostLedger, wakeTransport: options.wakeTransport,
44
- ownsArtifact: true,
45
- };
46
- }
47
- export function closeOpenSquare(square) {
48
- // A borrowed connection belongs to its owner; only owned views close it here.
49
- return square.ownsArtifact === false ? Promise.resolve() : square.artifact.close();
50
- }
51
- /** Explicit-lifetime holder of one reused artifact connection. The first request
52
- * binds the location; a request for any other location is refused instead of
53
- * silently sharing the wrong square. The owner closes the connection once. */
54
- export function createSquareResourceOwner() {
55
- let held;
56
- let closed = false;
57
- return {
58
- artifact(location) {
59
- if (closed)
60
- throw new SquareError('unavailable', 'Square resource owner is closed');
61
- const key = path.resolve(location);
62
- if (held === undefined) {
63
- // No external signal: a request must never become the shared connection's lifetime.
64
- held = { location: key, artifact: createArtifactPort(key) };
65
- }
66
- if (held.location !== key) {
67
- throw new SquareError('unavailable', `Square resource owner already holds ${held.location}; it cannot also open ${key}`);
68
- }
69
- return held.artifact;
70
- },
71
- async close() {
72
- if (closed)
73
- return;
74
- closed = true;
75
- const artifact = held?.artifact;
76
- held = undefined;
77
- if (artifact !== undefined)
78
- await artifact.close();
79
- },
80
- };
81
- }
@@ -1,23 +0,0 @@
1
- import type { HostLedgerPort, PresenceChannel, SquareArtifactPort } from './ports.js';
2
- import { type WakeRoute } from './routes.js';
3
- export interface HostContext {
4
- readonly artifact: SquareArtifactPort;
5
- readonly automaticDelivery?: boolean;
6
- readonly clock: () => number;
7
- readonly location?: string;
8
- readonly hostLedger?: HostLedgerPort;
9
- readonly env?: NodeJS.ProcessEnv;
10
- }
11
- export declare function processIdentity(env: NodeJS.ProcessEnv): {
12
- session: string;
13
- channel: PresenceChannel;
14
- };
15
- export declare function identityRouteDraft(context: HostContext, participant: string): Promise<Omit<WakeRoute, 'updatedAt'> | undefined>;
16
- /** One postcommit host observation; presence and route publication fail independently. */
17
- export declare function publishCommittedIdentity(context: HostContext, participant: string, epoch?: number): Promise<void>;
18
- export declare function retireIdentityRoute(context: HostContext, participant: string, expectedEpoch?: number): Promise<void>;
19
- export declare function assertLiveOwner(context: HostContext, participant: string, expectedEpoch?: number): Promise<boolean>;
20
- /** Best-effort postcommit presence; a returned epoch also witnesses a readable host observation. */
21
- export declare function ensureLocalPresence(context: HostContext, participant: string, epoch?: number): Promise<{
22
- readonly epoch?: number;
23
- } | undefined>;
@@ -1,92 +0,0 @@
1
- import { currentOwner, presenceEpoch, sessionOwnsParticipant } from './registry.js';
2
- import { firstHarnessSession } from './participant-identity.js';
3
- import { canonicalPath } from './canonical-path.js';
4
- import { defaultWakeRouteCapabilities, publishWakeRoute, retireWakeRouteFromArtifact, resolvePrimaryWakeRoute, ROUTE_FRESH_MS } from './routes.js';
5
- export function processIdentity(env) {
6
- const found = firstHarnessSession(env);
7
- return found === undefined ? { session: `process:${process.pid}`, channel: 'unknown' } : { session: found.sessionId, channel: found.channel };
8
- }
9
- export async function identityRouteDraft(context, participant) {
10
- if (context.automaticDelivery === false || context.location === undefined || context.location === 'memory')
11
- return undefined;
12
- const identity = processIdentity(context.env ?? process.env);
13
- if (identity.channel === 'unknown')
14
- return undefined;
15
- const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
16
- const boundary = { location: context.location, participant, sessionId: identity.session, provider };
17
- const env = context.env ?? process.env;
18
- // No adapter can add a route when the host has no candidate in the first place.
19
- if (context.hostLedger === undefined || resolvePrimaryWakeRoute(boundary, env, { canUse: () => true }) === undefined)
20
- return undefined;
21
- return resolvePrimaryWakeRoute(boundary, env, await defaultWakeRouteCapabilities(context.hostLedger));
22
- }
23
- /** One postcommit host observation; presence and route publication fail independently. */
24
- export async function publishCommittedIdentity(context, participant, epoch) {
25
- const observed = await ensureLocalPresence(context, participant, epoch).catch(() => undefined);
26
- if (observed === undefined)
27
- return;
28
- try {
29
- const route = await identityRouteDraft(context, participant);
30
- if (route !== undefined)
31
- await publishWakeRoute(context.artifact, { ...route, ...observed }, { at: context.clock(), requireCurrentSession: true });
32
- }
33
- catch { /* Optional route enrichment cannot undo the committed activity. */ }
34
- }
35
- export async function retireIdentityRoute(context, participant, expectedEpoch) {
36
- if (context.location === undefined || context.location === 'memory')
37
- return;
38
- const identity = processIdentity(context.env ?? process.env);
39
- await retireWakeRouteFromArtifact(context.artifact, { location: context.location, participant, sessionId: identity.session }, expectedEpoch === undefined ? {} : { expectedEpoch }).catch(() => undefined);
40
- }
41
- export async function assertLiveOwner(context, participant, expectedEpoch) {
42
- const hostLedger = context.hostLedger;
43
- if (hostLedger === undefined || context.location === undefined || context.location === 'memory')
44
- return true;
45
- const identity = processIdentity(context.env ?? process.env);
46
- // Library callers without a harness session are not ownership-fenced unless an epoch was supplied.
47
- if (identity.channel === 'unknown' && expectedEpoch === undefined)
48
- return true;
49
- return sessionOwnsParticipant(context.location, participant, identity.session, hostLedger, expectedEpoch);
50
- }
51
- /** Best-effort postcommit presence; a returned epoch also witnesses a readable host observation. */
52
- export async function ensureLocalPresence(context, participant, epoch) {
53
- if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
54
- return;
55
- const identity = processIdentity(context.env ?? process.env);
56
- // Sessionless library operations do not establish host presence or ownership.
57
- if (identity.channel === 'unknown')
58
- return;
59
- const now = Date.now();
60
- let existing;
61
- try {
62
- existing = await context.hostLedger.listPresence({ location: await canonicalPath(context.location), participant, now });
63
- }
64
- catch (error) {
65
- process.stderr.write(`! host presence degraded: ${error instanceof Error ? error.message : String(error)}\n`);
66
- return;
67
- }
68
- const owner = currentOwner(existing);
69
- const ownerEpoch = epoch ?? (owner === undefined ? undefined : presenceEpoch(owner));
70
- const observed = ownerEpoch === undefined ? {} : { epoch: ownerEpoch };
71
- // A fresh exact local binding at the observed (or explicit claim) generation already stands.
72
- if (existing.some((row) => row.session === identity.session && row.channel === identity.channel
73
- && now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
74
- && (ownerEpoch === undefined || presenceEpoch(row) === ownerEpoch)))
75
- return observed;
76
- try {
77
- const result = await context.hostLedger.ensurePresence({
78
- location: context.location,
79
- participant,
80
- session: identity.session,
81
- channel: identity.channel,
82
- updatedAt: Date.now(),
83
- ...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
84
- });
85
- if (result.status === 'degraded')
86
- throw result.error;
87
- }
88
- catch (error) {
89
- process.stderr.write(`! host presence degraded: ${error instanceof Error ? error.message : String(error)}\n`);
90
- }
91
- return observed;
92
- }
@@ -1,55 +0,0 @@
1
- /** Participant ownership and local session identity on the host ledger. */
2
- import { type FileHostLedgerPort } from './host-ledger-file-adapter.js';
3
- import type { HostLedgerPort, PresenceChannel, PresenceRecord } from './host-ledger.js';
4
- export declare function presenceEpoch(record: PresenceRecord | undefined): number;
5
- /** The current owner: the highest epoch wins, then the most recently updated row. */
6
- export declare function currentOwner(rows: readonly PresenceRecord[]): PresenceRecord | undefined;
7
- export declare function localParticipantName(squarePath: string, hostLedger: HostLedgerPort, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
8
- export declare function squareAssignedParticipantName(env?: NodeJS.ProcessEnv): string | undefined;
9
- export type OwnershipClaim = {
10
- readonly status: 'acquired' | 'owned';
11
- readonly record: PresenceRecord;
12
- readonly sessionId: string;
13
- readonly epoch: number;
14
- } | undefined;
15
- export type TakeoverClaimToken = {
16
- readonly sessionId: string;
17
- readonly epoch: number;
18
- };
19
- export type TakeoverRunResult<T> = {
20
- readonly status: 'acquired';
21
- readonly sessionId: string;
22
- readonly epoch: number;
23
- readonly result: T;
24
- } | {
25
- readonly status: 'busy';
26
- readonly epoch: number;
27
- };
28
- export declare function readParticipantOwner(squarePath: string, name: string, hostLedger: HostLedgerPort): Promise<PresenceRecord | undefined>;
29
- export declare function sessionOwnsParticipant(squarePath: string, name: string, sessionId: string, hostLedger: HostLedgerPort, expectedEpoch?: number): Promise<boolean>;
30
- export declare function releaseSessionParticipantClaim(hostLedger: HostLedgerPort, claim: OwnershipClaim): Promise<void>;
31
- export declare function claimSessionParticipant(squarePath: string, name: string, hostLedger: HostLedgerPort, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<OwnershipClaim>;
32
- /**
33
- * Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
34
- * claim lock, so no second takeover can interleave between claim and commit. The claim
35
- * persists nothing until the lifecycle commits: standing owner rows stay authoritative through
36
- * the lifecycle (a self-takeover can therefore never clobber its own standing row). On success
37
- * the standing rows are replaced by the new owner's row at the claim epoch; on refusal, rows
38
- * carrying the claim token (session + channel + claim epoch) are withdrawn and the captured
39
- * standing rows are restored exactly.
40
- */
41
- export declare function claimSessionTakeover<T>(squarePath: string, name: string, hostLedger: HostLedgerPort, env: NodeJS.ProcessEnv | undefined, opts: {
42
- readonly expectedEpoch?: number;
43
- readonly expectedSession?: string;
44
- } | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T>, signal?: AbortSignal): Promise<TakeoverRunResult<T>>;
45
- export declare function releaseSessionParticipant(squarePath: string, name: string, hostLedger: HostLedgerPort, env?: NodeJS.ProcessEnv): Promise<void>;
46
- export interface LocalSessionIdentity {
47
- sessionId: string;
48
- channel: PresenceChannel;
49
- child: boolean;
50
- paseoAgentId?: string;
51
- }
52
- export declare function localSessionIdentities(env?: NodeJS.ProcessEnv): LocalSessionIdentity[];
53
- export declare function hasAutomaticDeliveryIdentity(env?: NodeJS.ProcessEnv): boolean;
54
- /** Bind the host ledger to the caller's captured environment. */
55
- export declare function hostLedgerForEnv(env?: NodeJS.ProcessEnv): FileHostLedgerPort;
package/dist/registry.js DELETED
@@ -1,123 +0,0 @@
1
- /** Participant ownership and local session identity on the host ledger. */
2
- import { SquareError } from './model.js';
3
- import { harnessSessionSources, squareAssignedParticipantName as computeSquareAssignedParticipantName } from './participant-identity.js';
4
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
5
- import { hostLedgerRoot } from './host-ledger-root.js';
6
- import { canonicalPath } from './canonical-path.js';
7
- export function presenceEpoch(record) {
8
- return typeof record?.epoch === 'number' && Number.isSafeInteger(record.epoch) && record.epoch > 0 ? record.epoch : 0;
9
- }
10
- /** The current owner: the highest epoch wins, then the most recently updated row. */
11
- export function currentOwner(rows) {
12
- return [...rows].sort((left, right) => presenceEpoch(right) - presenceEpoch(left) || (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
13
- }
14
- export async function localParticipantName(squarePath, hostLedger, env = process.env) { const canonical = await canonicalPath(squarePath); const names = new Set((await Promise.all(localSessionIdentities(env).map(async (identity) => (await hostLedger.listPresence({ location: canonical, session: identity.sessionId })).map((row) => row.participant)))).flat()); return names.size === 1 ? [...names][0] : undefined; }
15
- export function squareAssignedParticipantName(env = process.env) { return computeSquareAssignedParticipantName(env); }
16
- export async function readParticipantOwner(squarePath, name, hostLedger) {
17
- // The registry cache this read replaces reported an unreadable cache as "no visible owner";
18
- // an ownership read stays equally tolerant so a degraded ledger cannot fail an operation.
19
- const rows = await participantPresence(squarePath, name, hostLedger).catch(() => []);
20
- return currentOwner(rows);
21
- }
22
- async function participantPresence(squarePath, name, hostLedger) {
23
- const canonical = await canonicalPath(squarePath);
24
- return hostLedger.listPresence({ location: canonical, participant: name, now: Date.now() });
25
- }
26
- export async function sessionOwnsParticipant(squarePath, name, sessionId, hostLedger, expectedEpoch) {
27
- const owner = await readParticipantOwner(squarePath, name, hostLedger);
28
- if (owner === undefined || owner.session !== sessionId)
29
- return false;
30
- return expectedEpoch === undefined || presenceEpoch(owner) === expectedEpoch;
31
- }
32
- export async function releaseSessionParticipantClaim(hostLedger, claim) {
33
- if (claim?.status !== 'acquired')
34
- return;
35
- await hostLedger.removePresenceIfUnchanged(claim.record);
36
- }
37
- export async function claimSessionParticipant(squarePath, name, hostLedger, env = process.env, signal) {
38
- const identity = localSessionIdentities(env)[0];
39
- if (identity === undefined)
40
- return undefined;
41
- if (signal?.aborted)
42
- throw signal.reason ?? new Error('Operation aborted');
43
- const location = await canonicalPath(squarePath);
44
- const result = await hostLedger.claimPresence({
45
- location,
46
- participant: name,
47
- session: identity.sessionId,
48
- channel: identity.channel,
49
- updatedAt: Date.now(),
50
- epoch: 1,
51
- }, signal);
52
- if (result.status === 'busy')
53
- throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`);
54
- if (result.status === 'degraded')
55
- throw result.error;
56
- const record = result.record;
57
- return { status: result.status, record, sessionId: record.session, epoch: presenceEpoch(record) };
58
- }
59
- /**
60
- * Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
61
- * claim lock, so no second takeover can interleave between claim and commit. The claim
62
- * persists nothing until the lifecycle commits: standing owner rows stay authoritative through
63
- * the lifecycle (a self-takeover can therefore never clobber its own standing row). On success
64
- * the standing rows are replaced by the new owner's row at the claim epoch; on refusal, rows
65
- * carrying the claim token (session + channel + claim epoch) are withdrawn and the captured
66
- * standing rows are restored exactly.
67
- */
68
- export async function claimSessionTakeover(squarePath, name, hostLedger, env = process.env, opts = {}, lifecycle, signal) {
69
- const identity = localSessionIdentities(env)[0];
70
- if (identity === undefined)
71
- throw new SquareError('invalid_args', 'No local session identity for takeover');
72
- const location = await canonicalPath(squarePath);
73
- return hostLedger.withClaimLock(async () => {
74
- const standing = await hostLedger.listPresence({ location, participant: name });
75
- const owner = currentOwner(standing);
76
- const currentEpoch = presenceEpoch(owner);
77
- const epochMatches = opts.expectedEpoch === undefined || currentEpoch === opts.expectedEpoch;
78
- const sessionMatches = opts.expectedSession === undefined
79
- || (opts.expectedSession === '' ? owner === undefined : owner?.session === opts.expectedSession);
80
- if (!epochMatches || !sessionMatches) {
81
- return { status: 'busy', epoch: currentEpoch };
82
- }
83
- const epoch = currentEpoch + 1;
84
- let result;
85
- try {
86
- result = await lifecycle({ sessionId: identity.sessionId, epoch });
87
- }
88
- catch (error) {
89
- // Withdraw: the claim itself persisted nothing. Remove only rows carrying this exact claim
90
- // token (session + channel + epoch) — whatever the lifecycle itself ensured — then restore
91
- // the captured standing rows so the old owner survives byte-consistently. Foreign rows and
92
- // later owners are never touched.
93
- const tokenRows = await hostLedger.listPresence({ location, participant: name, session: identity.sessionId });
94
- for (const row of tokenRows) {
95
- if (row.channel === identity.channel && presenceEpoch(row) === epoch)
96
- await hostLedger.removePresence({ location: row.location, participant: row.participant, session: row.session, channel: row.channel });
97
- }
98
- for (const row of standing)
99
- await hostLedger.ensurePresence(row);
100
- throw error;
101
- }
102
- // Finalize: the lifecycle committed. Replace the standing owner with the new owner's row at
103
- // the claim epoch where the owner was visible, so exactly one current owner
104
- // remains — also when the takeover is a self-takeover (same session as the standing owner).
105
- for (const row of standing)
106
- await hostLedger.removePresence({ location: row.location, participant: row.participant, session: row.session, channel: row.channel });
107
- await hostLedger.ensurePresence({ location, participant: name, session: identity.sessionId, channel: identity.channel, updatedAt: Date.now(), epoch });
108
- return { status: 'acquired', sessionId: identity.sessionId, epoch, result };
109
- }, signal);
110
- }
111
- export async function releaseSessionParticipant(squarePath, name, hostLedger, env = process.env) {
112
- const identity = localSessionIdentities(env)[0];
113
- if (identity === undefined)
114
- return;
115
- await hostLedger.removePresence({ location: squarePath, participant: name, session: identity.sessionId, channel: identity.channel });
116
- }
117
- function addLocalSession(identities, sessionId, channel, child, paseoAgentId) { if (!sessionId || identities.some((identity) => identity.sessionId === sessionId))
118
- return; identities.push({ sessionId, channel, child, ...(paseoAgentId ? { paseoAgentId } : {}) }); }
119
- export function localSessionIdentities(env = process.env) { const paseoAgentId = env.PASEO_AGENT_ID?.trim() || undefined; const identities = []; for (const source of harnessSessionSources)
120
- addLocalSession(identities, env[source.variable]?.trim(), source.channel, source.childVariable !== undefined && env[source.childVariable] === '1', paseoAgentId); return identities; }
121
- export function hasAutomaticDeliveryIdentity(env = process.env) { return localSessionIdentities(env).length > 0; }
122
- /** Bind the host ledger to the caller's captured environment. */
123
- export function hostLedgerForEnv(env = process.env) { return createHostLedgerPort({ rootPath: hostLedgerRoot(env) }); }
package/dist/routes.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import { type WakeRoute, type WakeRouteKind } from './model.js';
2
- import type { SquareRead, SquareWrite } from './ports.js';
3
- export { WAKE_ROUTE_KINDS } from './model.js';
4
- export type { WakeRoute, WakeRouteKind } from './model.js';
5
- export declare const ROUTE_FRESH_MS: number;
6
- export type WakeBoundaryProvider = 'codex' | 'claude' | 'opencode' | 'pi' | 'paseo';
7
- export interface WakeBoundary {
8
- readonly location: string;
9
- readonly participant: string;
10
- readonly sessionId: string;
11
- readonly provider: WakeBoundaryProvider;
12
- }
13
- export interface WakeRouteCapabilities {
14
- readonly canUse: (kind: WakeRouteKind, address: Readonly<Record<string, string>>) => boolean;
15
- }
16
- export declare function defaultWakeRouteCapabilities(hostLedger?: import('./host-ledger.js').HostLedgerPort): Promise<WakeRouteCapabilities>;
17
- export declare function resolvePrimaryWakeRoute(boundary: WakeBoundary, env: NodeJS.ProcessEnv, capabilities: WakeRouteCapabilities): Omit<WakeRoute, 'updatedAt'> | undefined;
18
- export declare function routeIdentityKey(route: Pick<WakeRoute, 'location' | 'participant' | 'sessionId'>, location?: string): string;
19
- export type RouteEpoch = {
20
- readonly epoch?: number;
21
- };
22
- export declare function applyWakeRoute(facts: SquareWrite, route: Omit<WakeRoute, 'updatedAt'> & RouteEpoch, at?: number): void;
23
- export declare function dropSessionWakeRoutes(facts: SquareWrite, location: string, sessionId: string, expectedEpoch?: number): void;
24
- /** Keep an in-flight same-session rejoin; unknown host evidence is not a reason to retire it. */
25
- export declare function dropEndedSessionWakeRoutes(facts: SquareWrite, location: string, sessionId: string, opts: {
26
- readonly expectedEpoch?: number;
27
- readonly liveParticipants?: ReadonlySet<string>;
28
- readonly force: boolean;
29
- readonly participant?: string;
30
- }): void;
31
- export declare function dropParticipantWakeRoutes(facts: SquareWrite, location: string, participant: string): void;
32
- export declare function sessionCanEndParticipant(facts: SquareRead, location: string, participant: string, sessionId: string, sessionIdNow?: string): boolean;
33
- export declare function publishWakeRoute(artifact: import('./ports.js').SquareArtifactPort, route: Omit<WakeRoute, 'updatedAt'> & RouteEpoch, opts?: {
34
- at?: number;
35
- requireCurrentSession?: boolean;
36
- }): Promise<void>;
37
- export declare function retireWakeRouteFromArtifact(artifact: import('./ports.js').SquareArtifactPort, route: Pick<WakeRoute, 'location' | 'participant' | 'sessionId'>, opts?: {
38
- readonly expectedEpoch?: number;
39
- }): Promise<void>;