@astrosheep/square 0.3.105 → 0.4.0

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/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +8 -12
  5. package/dist/artifact.js +66 -343
  6. package/dist/attention-presentation.d.ts +33 -7
  7. package/dist/attention-presentation.js +33 -69
  8. package/dist/boundary-presentation.d.ts +0 -2
  9. package/dist/boundary-presentation.js +17 -22
  10. package/dist/catch-decisions.d.ts +2 -2
  11. package/dist/catch-decisions.js +14 -4
  12. package/dist/catch-selection.d.ts +8 -0
  13. package/dist/catch-selection.js +5 -0
  14. package/dist/channel-artifact.d.ts +10 -0
  15. package/dist/channel-artifact.js +55 -0
  16. package/dist/channel-delivery.d.ts +11 -0
  17. package/dist/channel-delivery.js +51 -0
  18. package/dist/channel-domain.d.ts +3 -0
  19. package/dist/channel-domain.js +83 -0
  20. package/dist/channel-facade.d.ts +76 -0
  21. package/dist/channel-facade.js +1 -0
  22. package/dist/channel-internal.d.ts +126 -0
  23. package/dist/channel-internal.js +440 -0
  24. package/dist/channel-metadata.d.ts +11 -0
  25. package/dist/channel-metadata.js +1 -0
  26. package/dist/channel-ports.d.ts +241 -0
  27. package/dist/channel-ports.js +1 -0
  28. package/dist/channel-presentation.d.ts +17 -0
  29. package/dist/channel-presentation.js +22 -0
  30. package/dist/channel-sqlite-schema.d.ts +13 -0
  31. package/dist/channel-sqlite-schema.js +81 -0
  32. package/dist/channel-transport.d.ts +62 -0
  33. package/dist/channel-transport.js +1 -0
  34. package/dist/channel-values.d.ts +27 -0
  35. package/dist/channel-values.js +90 -0
  36. package/dist/channel.d.ts +31 -21
  37. package/dist/channel.js +42 -74
  38. package/dist/claude-delivery-status.js +1 -1
  39. package/dist/claude-delivery.d.ts +2 -5
  40. package/dist/claude-delivery.js +56 -103
  41. package/dist/claude-mod.js +4 -4
  42. package/dist/claude-transport.d.ts +5 -0
  43. package/dist/claude-transport.js +58 -0
  44. package/dist/cli/context.d.ts +2 -2
  45. package/dist/cli/context.js +0 -1
  46. package/dist/cli/maintenance-commands.d.ts +0 -4
  47. package/dist/cli/maintenance-commands.js +2 -29
  48. package/dist/cli/program.js +1 -1
  49. package/dist/cli/registry.js +1 -2
  50. package/dist/codex-address.d.ts +1 -1
  51. package/dist/codex-queue.d.ts +2 -2
  52. package/dist/codex-queue.js +1 -3
  53. package/dist/decisions.js +3 -3
  54. package/dist/delivery-deferral.d.ts +9 -18
  55. package/dist/delivery-deferral.js +4 -4
  56. package/dist/delivery-health.d.ts +4 -4
  57. package/dist/delivery-health.js +11 -11
  58. package/dist/delivery-operations.d.ts +15 -2
  59. package/dist/delivery-operations.js +60 -30
  60. package/dist/delivery.d.ts +44 -33
  61. package/dist/delivery.js +49 -75
  62. package/dist/help.js +1 -6
  63. package/dist/http-delivery.d.ts +2 -2
  64. package/dist/http-delivery.js +2 -4
  65. package/dist/inbox.d.ts +10 -18
  66. package/dist/inbox.js +44 -53
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/mcp.d.ts +2 -1
  70. package/dist/mcp.js +40 -70
  71. package/dist/message.d.ts +7 -0
  72. package/dist/message.js +23 -0
  73. package/dist/model.d.ts +41 -77
  74. package/dist/model.js +7 -139
  75. package/dist/neutral.d.ts +72 -0
  76. package/dist/neutral.js +27 -0
  77. package/dist/notifications.d.ts +2 -8
  78. package/dist/notifications.js +15 -50
  79. package/dist/operation-presentation.d.ts +1 -2
  80. package/dist/operation-presentation.js +1 -7
  81. package/dist/owner.d.ts +2 -2
  82. package/dist/owner.js +1 -1
  83. package/dist/paseo-delivery.d.ts +1 -1
  84. package/dist/paseo-delivery.js +2 -4
  85. package/dist/paseo-timeline.js +1 -0
  86. package/dist/plugins/opencode/delivery.js +27 -42
  87. package/dist/ports.d.ts +46 -168
  88. package/dist/presentation-operations.d.ts +1 -14
  89. package/dist/presentation-operations.js +16 -16
  90. package/dist/presentation.d.ts +5 -5
  91. package/dist/presentation.js +1 -0
  92. package/dist/runtime.js +2 -2
  93. package/dist/search.js +1 -1
  94. package/dist/sqlite-channel.d.ts +15 -0
  95. package/dist/sqlite-channel.js +600 -0
  96. package/dist/sqlite-framing.d.ts +8 -0
  97. package/dist/sqlite-framing.js +31 -0
  98. package/dist/sqlite-square.d.ts +12 -13
  99. package/dist/sqlite-square.js +192 -248
  100. package/dist/sqlite-values.d.ts +21 -0
  101. package/dist/sqlite-values.js +78 -0
  102. package/dist/square-api.d.ts +1 -1
  103. package/dist/square-api.js +1 -1
  104. package/dist/square-core.d.ts +1 -1
  105. package/dist/square-domain.d.ts +21 -7
  106. package/dist/square-domain.js +93 -94
  107. package/dist/square-facade.d.ts +12 -66
  108. package/dist/square-index.d.ts +1 -1
  109. package/dist/square-index.js +1 -1
  110. package/dist/square-location.d.ts +6 -1
  111. package/dist/square-location.js +1 -1
  112. package/dist/square-receive.d.ts +2 -3
  113. package/dist/square-sqlite.d.ts +45 -44
  114. package/dist/square-sqlite.js +308 -764
  115. package/dist/square-tools.d.ts +8 -0
  116. package/dist/square-tools.js +39 -0
  117. package/dist/views.d.ts +6 -6
  118. package/dist/views.js +15 -9
  119. package/dist/wake-preview.d.ts +7 -0
  120. package/dist/wake-preview.js +15 -0
  121. package/dist/wake-sink.d.ts +2 -2
  122. package/dist/wake-sink.js +1 -1
  123. package/dist/wake-transports.d.ts +7 -0
  124. package/dist/wake-transports.js +42 -0
  125. package/guides/claude-native.md +13 -10
  126. package/guides/codex-delivery.md +3 -1
  127. package/guides/participant-ownership.md +1 -1
  128. package/guides/row-runtime-delivery-evidence.md +2 -0
  129. package/guides/row-storage-design.md +19 -44
  130. package/guides/square-api.md +137 -60
  131. package/package.json +3 -3
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/types.d.ts +1 -1
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +28 -41
  136. package/plugins/pi/square-pi.js +28 -51
  137. package/dist/square-legacy-framing.d.ts +0 -11
  138. package/dist/square-legacy-framing.js +0 -64
  139. package/dist/square-migration-host-ledger.d.ts +0 -26
  140. package/dist/square-migration-host-ledger.js +0 -169
  141. package/dist/square-migration.d.ts +0 -30
  142. package/dist/square-migration.js +0 -599
  143. package/guides/migration.md +0 -156
package/dist/channel.d.ts CHANGED
@@ -1,9 +1,18 @@
1
- import { type HarnessSessionChannel } from './participant-identity.js';
2
- import type { SquareResourceOwner, WakeTransportPort } from './ports.js';
3
- import type { ActivityId, ChannelSendInput, ChannelSendResult, DeliveryDeferral, HeldSeat, MetadataMatch, OperationControl, Owner, Participant, WakeAddress, PendingOptions, PendingResult, AckResult } from './square-facade.js';
1
+ import type { WakeAddress } from './channel-values.js';
2
+ import type { Owner, OperationControl } from './neutral.js';
3
+ import type { HarnessSessionChannel } from './participant-identity.js';
4
+ import type { WakeTransportPort } from './channel-transport.js';
5
+ import type { MetadataMatch, DeliveryDeferral } from './channel-metadata.js';
6
+ import type { MessageId } from './message.js';
7
+ import type { AckResult, ChannelEndpointHold, ChannelEndpointSummary, ChannelHistoryQuery, ChannelHistoryResult, ChannelPendingOptions, ChannelPendingResult, ChannelReceiveResult, ChannelSendInput, ChannelSendResult } from './channel-facade.js';
4
8
  export { harnessOwner, decodeHarnessOwner, localUserOwner } from './owner.js';
5
- export type { ActivityDelivery, ActivityMetadata, ActivityQuery, ActivitySearch, ActivityCursor, ChannelTruncate, PerceivedActivity, PendingOptions, PendingResult, AckResult } from './square-facade.js';
6
- export type { ActivityId, ChannelSendInput, ChannelSendResult, DeliveryDeferral, HeldSeat, MetadataMatch, OperationControl, Owner, WakeAddress, SquareResourceOwner, WakeTransportPort, HarnessSessionChannel };
9
+ export type { MessageMetadata, WakeAddress } from './channel-values.js';
10
+ export type { MessageId } from './message.js';
11
+ export type { MessageCursor, MessageQuery, MessageSearch, ChannelTruncate, ChannelSendInput, ChannelSendResult, ChannelMessage, ChannelPendingOptions, ChannelPendingResult, ChannelReceiveResult, ChannelHistoryQuery, ChannelHistoryResult, AckResult, ChannelEndpointSummary, ChannelEndpointHold } from './channel-facade.js';
12
+ export type { MetadataMatch, DeliveryDeferral } from './channel-metadata.js';
13
+ export type { Owner, OperationControl } from './neutral.js';
14
+ export type { WakeTransportPort } from './channel-transport.js';
15
+ export type { HarnessSessionChannel } from './participant-identity.js';
7
16
  export interface ResolvedHarnessSession {
8
17
  readonly channel: HarnessSessionChannel;
9
18
  readonly sessionId: string;
@@ -18,7 +27,6 @@ export interface ChannelOpenInput {
18
27
  readonly env?: NodeJS.ProcessEnv;
19
28
  readonly clock?: () => number;
20
29
  readonly wakeTransport?: WakeTransportPort | false;
21
- readonly resources?: SquareResourceOwner;
22
30
  }
23
31
  export interface ChannelCreateInput extends ChannelOpenInput {
24
32
  readonly force?: boolean;
@@ -30,16 +38,16 @@ type EndpointOptions = {
30
38
  };
31
39
  type EndpointIdentity = {
32
40
  readonly owner: Owner;
33
- readonly generation: ActivityId;
41
+ readonly generation: string;
34
42
  };
35
- /** A scoped direct-messaging vocabulary over the shared Square resource. */
43
+ /** A trusted handle to one independent message channel. */
36
44
  export declare class Channel {
37
- private readonly square;
45
+ #private;
38
46
  private constructor();
39
- get location(): string;
40
47
  static open(input: ChannelOpenInput, control?: OperationControl): Promise<Channel>;
41
48
  static create(input: ChannelCreateInput, control?: OperationControl): Promise<Channel>;
42
- static inMemory(input?: Omit<ChannelCreateInput, 'path' | 'resources' | 'force'>): Channel;
49
+ static inMemory(input?: Omit<ChannelCreateInput, 'path' | 'force'>): Channel;
50
+ get location(): string;
43
51
  attach(name: string, options: EndpointOptions & {
44
52
  readonly takeover?: boolean;
45
53
  }, control?: OperationControl): Promise<ChannelEndpoint>;
@@ -47,23 +55,24 @@ export declare class Channel {
47
55
  endpoint(name: string, options: {
48
56
  readonly owner: Owner;
49
57
  }): Promise<ChannelEndpoint>;
50
- endpointsOf(owner: Owner): Promise<readonly HeldSeat[]>;
58
+ endpointsOf(owner: Owner): Promise<readonly ChannelEndpointHold[]>;
59
+ listEndpoints(control?: OperationControl): Promise<readonly ChannelEndpointSummary[]>;
51
60
  detach(name: string, identity: EndpointIdentity): Promise<true | null>;
61
+ cancelDeliveries(name: string, identity: EndpointIdentity, control?: OperationControl): Promise<boolean>;
52
62
  remove(name: string, identity: EndpointIdentity): Promise<boolean>;
53
- cancelDeliveries(name: string, identity: EndpointIdentity): Promise<boolean>;
54
63
  send(input: ChannelSendInput, control?: OperationControl): Promise<ChannelSendResult>;
64
+ history(query?: ChannelHistoryQuery, control?: OperationControl): Promise<ChannelHistoryResult>;
55
65
  close(): Promise<void>;
56
66
  }
57
- /** Captures one recipient generation through its existing Participant handle. */
67
+ /** Fenced to one attached endpoint generation. */
58
68
  export declare class ChannelEndpoint {
59
- private readonly channel;
60
- private readonly participant;
61
- constructor(channel: Channel, participant: Participant);
69
+ #private;
70
+ private constructor();
62
71
  get name(): string;
63
- get generation(): ActivityId;
64
- pending(options?: PendingOptions, control?: OperationControl): Promise<PendingResult>;
65
- ack(ids: readonly ActivityId[], control?: OperationControl): Promise<AckResult>;
66
- leave(control?: OperationControl): Promise<void>;
72
+ get generation(): string;
73
+ pending(options?: ChannelPendingOptions, control?: OperationControl): Promise<ChannelPendingResult>;
74
+ ack(ids: readonly MessageId[], control?: OperationControl): Promise<AckResult>;
75
+ receive(options?: ChannelPendingOptions, control?: OperationControl): Promise<ChannelReceiveResult>;
67
76
  deferDelivery(input: Readonly<{
68
77
  match: MetadataMatch;
69
78
  durationMs: number;
@@ -71,4 +80,5 @@ export declare class ChannelEndpoint {
71
80
  renewDeliveryDeferral(id: string, durationMs: number, control?: OperationControl): Promise<DeliveryDeferral | null>;
72
81
  releaseDeliveryDeferral(id: string, control?: OperationControl): Promise<boolean>;
73
82
  send(input: Omit<ChannelSendInput, 'from'>, control?: OperationControl): Promise<ChannelSendResult>;
83
+ leave(control?: OperationControl): Promise<void>;
74
84
  }
package/dist/channel.js CHANGED
@@ -1,92 +1,60 @@
1
- import { codexWakeAddress } from './codex-address.js';
2
- import { SquareError } from './model.js';
3
- import { harnessOwner } from './owner.js';
4
- import { harnessSessionSources } from './participant-identity.js';
5
- import { Square } from './square-api.js';
6
- import { squareLocation } from './square-location.js';
1
+ import { Channel as ChannelCore, resolveHarnessSession as resolveSession } from './channel-internal.js';
7
2
  export { harnessOwner, decodeHarnessOwner, localUserOwner } from './owner.js';
8
3
  export function resolveHarnessSession(env = process.env) {
9
- for (const { variable, channel } of harnessSessionSources) {
10
- const sessionId = env[variable]?.trim();
11
- if (!sessionId)
12
- continue;
13
- const agentId = channel === 'claude-code' && env.CLAUDE_CODE_CHILD_SESSION?.trim() === '1'
14
- ? env.CLAUDE_CODE_AGENT_ID?.trim() || undefined : undefined;
15
- const wakeAddress = channel === 'codex' ? codexWakeAddress(sessionId, env)
16
- : channel === 'paseo' ? { kind: 'paseo', address: { agentId: sessionId } } : undefined;
17
- return { channel, sessionId, ...(agentId === undefined ? {} : { agentId }),
18
- owner: harnessOwner({ channel, session: sessionId, ...(agentId === undefined ? {} : { agent: agentId }) }),
19
- ...(wakeAddress === undefined ? {} : { wakeAddress }) };
20
- }
21
- return undefined;
22
- }
23
- function explicitLocation(input) {
24
- if (typeof input?.path !== 'string' || input.path.trim() === '')
25
- throw new SquareError('invalid_args', 'Channel path is required.');
26
- return squareLocation(input);
4
+ return resolveSession(env);
27
5
  }
28
- /** A scoped direct-messaging vocabulary over the shared Square resource. */
6
+ const endpointKey = Symbol('ChannelEndpoint');
7
+ let wrapEndpoint;
8
+ /** A trusted handle to one independent message channel. */
29
9
  export class Channel {
30
- square;
31
- constructor(square) {
32
- this.square = square;
33
- }
34
- get location() { return this.square.location; }
10
+ #core;
11
+ constructor(core) { this.#core = core; }
35
12
  static async open(input, control) {
36
- const path = explicitLocation(input);
37
- return new Channel(await Square.at({ ...input, path }, control));
13
+ return new Channel(await ChannelCore.open(input, control));
38
14
  }
39
15
  static async create(input, control) {
40
- const path = explicitLocation(input);
41
- return new Channel(await Square.build({ ...input, path, hardCap: null, markdown: input.description ?? '# Channel\n' }, control));
16
+ return new Channel(await ChannelCore.create(input, control));
42
17
  }
43
18
  static inMemory(input = {}) {
44
- return new Channel(Square.inMemory({ ...input, hardCap: null, markdown: input.description ?? '# Channel\n' }));
19
+ return new Channel(ChannelCore.inMemory(input));
45
20
  }
21
+ get location() { return this.#core.location; }
46
22
  async attach(name, options, control) {
47
- return new ChannelEndpoint(this, (await this.square.join(name, options, control)).participant);
23
+ return wrapEndpoint(await this.#core.attach(name, options, control));
48
24
  }
49
25
  async reattach(name, options, control) {
50
- const participant = await this.square.reattach(name, options, control);
51
- return participant === null ? null : new ChannelEndpoint(this, participant);
26
+ const endpoint = await this.#core.reattach(name, options, control);
27
+ return endpoint === null ? null : wrapEndpoint(endpoint);
52
28
  }
53
29
  async endpoint(name, options) {
54
- return new ChannelEndpoint(this, await this.square.participant(name, options));
55
- }
56
- endpointsOf(owner) { return this.square.seatsOf(owner); }
57
- detach(name, identity) { return this.square.detach(name, identity); }
58
- async remove(name, identity) { return await this.square.endHeld(name, identity) !== null; }
59
- cancelDeliveries(name, identity) { return this.square.cancelDeliveries(name, identity); }
60
- send(input, control) { return this.square.send(input, control); }
61
- close() { return this.square.close(); }
30
+ return wrapEndpoint(await this.#core.endpoint(name, options));
31
+ }
32
+ endpointsOf(owner) { return this.#core.endpointsOf(owner); }
33
+ listEndpoints(control) { return this.#core.listEndpoints(control); }
34
+ detach(name, identity) { return this.#core.detach(name, identity); }
35
+ cancelDeliveries(name, identity, control) { return this.#core.cancelDeliveries(name, identity, control); }
36
+ remove(name, identity) { return this.#core.remove(name, identity); }
37
+ send(input, control) { return this.#core.send(input, control); }
38
+ history(query, control) { return this.#core.history(query, control); }
39
+ close() { return this.#core.close(); }
62
40
  }
63
- /** Captures one recipient generation through its existing Participant handle. */
41
+ /** Fenced to one attached endpoint generation. */
64
42
  export class ChannelEndpoint {
65
- channel;
66
- participant;
67
- constructor(channel, participant) {
68
- this.channel = channel;
69
- this.participant = participant;
70
- }
71
- get name() { return this.participant.name; }
72
- get generation() { return this.participant.generation; }
73
- pending(options, control) {
74
- return this.participant.pending(options, control);
75
- }
76
- ack(ids, control) {
77
- return this.participant.ack(ids, control);
78
- }
79
- async leave(control) { await this.participant.done(undefined, {}, control); }
80
- deferDelivery(input, control) {
81
- return this.participant.deferDelivery(input, control);
82
- }
83
- renewDeliveryDeferral(id, durationMs, control) {
84
- return this.participant.renewDeliveryDeferral(id, durationMs, control);
85
- }
86
- releaseDeliveryDeferral(id, control) {
87
- return this.participant.releaseDeliveryDeferral(id, control);
88
- }
89
- send(input, control) {
90
- return this.channel.send({ ...input, from: this.name }, control);
91
- }
43
+ #core;
44
+ constructor(core, key) {
45
+ if (key !== endpointKey)
46
+ throw new TypeError('Channel endpoints are obtained from a Channel.');
47
+ this.#core = core;
48
+ }
49
+ static { wrapEndpoint = core => new ChannelEndpoint(core, endpointKey); }
50
+ get name() { return this.#core.name; }
51
+ get generation() { return this.#core.generation; }
52
+ pending(options, control) { return this.#core.pending(options, control); }
53
+ ack(ids, control) { return this.#core.ack(ids, control); }
54
+ receive(options, control) { return this.#core.receive(options, control); }
55
+ deferDelivery(input, control) { return this.#core.deferDelivery(input, control); }
56
+ renewDeliveryDeferral(id, durationMs, control) { return this.#core.renewDeliveryDeferral(id, durationMs, control); }
57
+ releaseDeliveryDeferral(id, control) { return this.#core.releaseDeliveryDeferral(id, control); }
58
+ send(input, control) { return this.#core.send(input, control); }
59
+ leave(control) { return this.#core.leave(control); }
92
60
  }
@@ -33,7 +33,7 @@ export async function recordClaudeDeliveryStatus(input, env = process.env) {
33
33
  continue;
34
34
  const { seat, evidence } = match[0];
35
35
  const ref = { participant: seat.participant, owner: seat.owner, generation: seat.generation };
36
- return (await artifact.transactAs({ kind: 'participant', seat: ref }, (facts) => facts.observeNativeDelivery({ seat: ref, claimToken: evidence.claimToken, observation: {
36
+ return (await artifact.transactAs({ kind: 'participant', seat: ref }, (facts) => facts.channel.observeNativeDelivery({ seat: ref, claimToken: evidence.claimToken, observation: {
37
37
  eventId: input.eventId, messageId: input.messageId, state: input.state,
38
38
  ...(input.reason === undefined ? {} : { reason: input.reason }), observedAt: input.observedAt,
39
39
  } }))).value;
@@ -1,7 +1,4 @@
1
- import type { WakeTransportPort } from './ports.js';
2
- export declare function nativeTokens(text: string): string[];
3
- export declare function nativePayload(token: string, preview: string): string;
4
- export declare function createClaudeWakeTransport(env?: NodeJS.ProcessEnv): WakeTransportPort;
1
+ export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
5
2
  /** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
6
3
  export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv): Promise<{
7
4
  recognized: boolean;
@@ -11,5 +8,5 @@ export type ClaudeBinding = {
11
8
  readonly location: string;
12
9
  readonly participant: string;
13
10
  readonly session: string;
14
- readonly generation?: import('./square-core.js').ActivityId;
11
+ readonly generation?: string;
15
12
  };
@@ -1,87 +1,11 @@
1
- import { stat } from 'node:fs/promises';
2
- import { createHash, randomUUID } from 'node:crypto';
3
1
  import { createArtifactPort } from './artifact.js';
4
2
  import { presentPending } from './presentation-operations.js';
5
- import { nativePendingPreview } from './inbox.js';
6
- import { parseActivityId } from './square-core.js';
3
+ import { nativePendingHead } from './delivery.js';
7
4
  import { decodeHarnessOwner } from './owner.js';
8
5
  import { indexedSquareEntries } from './square-index.js';
9
- import { asSeatRef } from './seats.js';
10
- import { writeClaudeNative } from './packages/agent-delivery/src/claude-native.js';
11
- const marker = /(?:^|\n)\[square-inbox:([a-zA-Z0-9-]+)\](?:\n|$)/g;
12
- export function nativeTokens(text) { return [...text.matchAll(marker)].map((match) => match[1]); }
13
- export function nativePayload(token, preview) { return `[square-inbox:${token}]\n${preview}\n[/square-inbox:${token}]`; }
14
- function nativeSupported(route) { return route.address.loaded === 'true' && !!route.address.endpoint; }
15
- function hashPayload(payload) { return createHash('sha256').update(payload).digest('hex'); }
16
- function previewFacts(facts, request) {
17
- const index = parseActivityId(request.activity);
18
- if (index === undefined || request.route.kind !== 'claude-native' || request.route.address.endpoint === undefined)
19
- return undefined;
20
- return nativePendingPreview(facts, { location: request.location, routeKind: 'claude-native', generation: request.seat.generation,
21
- address: request.route.address,
22
- participant: request.participant, actIndex: index, now: Date.now() });
23
- }
24
- async function preview(artifact, request) {
25
- return (await artifact.read((facts) => previewFacts(facts, request))).value;
26
- }
27
- async function writeClaudeWake(sessionId, endpoint, messageId, payload, timeoutMs, signal) {
28
- const result = await writeClaudeNative({ sessionId, endpoint, messageId }, payload, { deadline: Date.now() + timeoutMs, signal });
29
- if (result.outcome === 'unavailable')
30
- return { outcome: 'failed', signature: 'claude_endpoint_unavailable', message: result.message };
31
- return { outcome: 'unknown', signature: 'native_admission_unconfirmed', message: result.outcome === 'written' ? 'Claude accepted the local write; native admission is unconfirmed.' : result.message };
32
- }
33
- export function createClaudeWakeTransport(env = process.env) {
34
- return {
35
- async probe(route) {
36
- if (!nativeSupported(route))
37
- return { outcome: 'not-capable', diagnostic: 'Claude native inbox unavailable: a loaded interactive Square mod is required.' };
38
- try {
39
- return (await stat(route.address.endpoint)).isSocket() || { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint is not a socket.' };
40
- }
41
- catch {
42
- return { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint unavailable.' };
43
- }
44
- },
45
- attempt: (request, timeoutMs, beforeSend) => dispatchClaude(request, timeoutMs, beforeSend, env),
46
- };
47
- }
48
- async function dispatchClaude(request, timeoutMs, beforeSend, env = process.env) {
49
- void env;
50
- const signal = AbortSignal.timeout(timeoutMs);
51
- if (!nativeSupported(request.route) || request.artifact === undefined || request.claimToken === undefined)
52
- return { outcome: 'not-capable', diagnostic: 'Claude native delivery needs a current artifact claim and loaded inbox.' };
53
- if (signal.aborted || !await beforeSend())
54
- return { outcome: 'unknown', signature: 'native_send_suppressed' };
55
- const prepared = await preview(request.artifact, request);
56
- if (prepared === undefined)
57
- return { outcome: 'not-capable', diagnostic: 'Claude activity is no longer current.' };
58
- const messageId = randomUUID();
59
- const payload = nativePayload(request.claimToken, prepared.payload);
60
- const correlation = { harness: 'claude', token: request.claimToken, messageId, payloadHash: hashPayload(payload) };
61
- if (!await request.artifact.prepareDelivery({ seat: request.seat, claimToken: request.claimToken, correlation }))
62
- return { outcome: 'not-capable', diagnostic: 'Claude delivery claim is no longer current.' };
63
- const fresh = await preview(request.artifact, request);
64
- if (fresh === undefined || nativePayload(request.claimToken, fresh.payload) !== payload)
65
- return { outcome: 'not-capable', diagnostic: 'Claude activity changed before native write.' };
66
- if (signal.aborted || !await beforeSend())
67
- return { outcome: 'not-capable', diagnostic: 'Claude activity was consumed before native write.' };
68
- const sessionId = request.route.address.sessionId;
69
- if (sessionId === undefined)
70
- return { outcome: 'not-capable', diagnostic: 'Claude native route has no session address.' };
71
- return writeClaudeWake(sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
72
- }
73
- function currentPayload(facts, request) {
74
- const seat = facts.seat(request.participant), evidence = facts.deliveryByToken(request.claimToken);
75
- if (seat === undefined || seat.attachment !== 'attached' || evidence === undefined || seat.generation !== evidence.generation
76
- || seat.generation !== request.seat.generation || seat.owner !== request.seat.owner
77
- || evidence.activity !== request.activity || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome))
78
- return undefined;
79
- const native = previewFacts(facts, request);
80
- if (native === undefined)
81
- return undefined;
82
- const payload = nativePayload(request.claimToken, native.payload);
83
- return evidence.correlation?.payloadHash === hashPayload(payload) ? payload : undefined;
84
- }
6
+ import { parseMessageId } from './message.js';
7
+ export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
8
+ import { nativeTokens, hashPayload } from './claude-transport.js';
85
9
  async function rowsForSession(sessionId, env, tokens) {
86
10
  const rows = [];
87
11
  const candidates = (await indexedSquareEntries({}, env)).filter(entry => {
@@ -97,16 +21,9 @@ async function rowsForSession(sessionId, env, tokens) {
97
21
  if (evidence?.kind !== 'wake' || evidence.correlation?.harness !== 'claude' || evidence.correlation.token !== token
98
22
  || evidence.route?.kind !== 'claude-native' || evidence.route.address.sessionId !== sessionId)
99
23
  return [];
100
- const row = { artifact, evidence };
101
- const seat = facts.seat(evidence.participant), index = parseActivityId(evidence.activity);
102
- const item = index === undefined ? undefined : facts.activity(index);
103
- if (seat === undefined || item?.kind !== 'say')
104
- return [row];
105
- const route = { location, participant: seat.participant,
106
- ...evidence.route, updatedAt: evidence.at };
107
- const request = { location, participant: seat.participant, activity: evidence.activity,
108
- actor: item.actor, seat: asSeatRef(seat), claimToken: token, attemptN: evidence.attemptN, route, artifact };
109
- return [{ ...row, request, payload: currentPayload(facts, request) }];
24
+ const seat = facts.seat(evidence.participant);
25
+ return [{ artifact, evidence, location, index: parseMessageId(evidence.message),
26
+ ...(seat === undefined ? {} : { seat: { participant: evidence.participant, owner: seat.owner, generation: evidence.generation } }) }];
110
27
  }));
111
28
  rows.push(...found.value);
112
29
  if (found.value.length === 0)
@@ -118,6 +35,28 @@ async function rowsForSession(sessionId, env, tokens) {
118
35
  }
119
36
  return rows;
120
37
  }
38
+ /** The token, participant, activity and captured generation must still be the exact wake. */
39
+ function sameCapturedEvidence(facts, row) {
40
+ const token = row.evidence.claimToken;
41
+ const evidence = token === undefined ? undefined : facts.deliveryByToken(token);
42
+ return token !== undefined && evidence !== undefined && row.seat !== undefined && row.seat.participant === row.evidence.participant
43
+ && row.seat.generation === row.evidence.generation && evidence.kind === 'wake' && evidence.claimToken === token
44
+ && evidence.participant === row.evidence.participant && evidence.message === row.evidence.message
45
+ && evidence.generation === row.evidence.generation
46
+ && evidence.correlation?.harness === 'claude' && evidence.correlation.token === token
47
+ && evidence.correlation.payloadHash === row.evidence.correlation?.payloadHash
48
+ && ['dispatching', 'unknown', 'accepted'].includes(evidence.outcome);
49
+ }
50
+ function capturedInput(row, now) {
51
+ if (row.seat === undefined || row.index === undefined || row.evidence.route === undefined)
52
+ return undefined;
53
+ return { location: row.location, participant: row.evidence.participant, generation: row.seat.generation,
54
+ owner: row.seat.owner, index: row.index, route: row.evidence.route, now };
55
+ }
56
+ function currentRow(facts, row, now) {
57
+ const input = capturedInput(row, now);
58
+ return input !== undefined && sameCapturedEvidence(facts, row) && nativePendingHead(facts, input) !== undefined;
59
+ }
121
60
  /** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
122
61
  export async function observeClaudeDelivery(sessionId, text, operation, env = process.env) {
123
62
  const tokens = nativeTokens(text);
@@ -127,25 +66,39 @@ export async function observeClaudeDelivery(sessionId, text, operation, env = pr
127
66
  const recognized = rows.length > 0;
128
67
  const frames = [...text.matchAll(/(?:^|\n)(\[square-inbox:([a-zA-Z0-9-]+)\]\n[\s\S]*?\n\[\/square-inbox:\2\])(?=\n|$)/g)];
129
68
  try {
130
- if (!recognized || rows.some(row => {
131
- const matching = frames.filter(frame => frame[2] === row.evidence.claimToken);
132
- return row.payload === undefined || matching.length !== tokens.filter(token => token === row.evidence.claimToken).length
133
- || matching.some(frame => frame[1] !== row.payload);
134
- }))
69
+ // Every recognized token is vetted before any row can write: actual bytes against the
70
+ // stored correlation hash, then independent current eligibility.
71
+ const contentCurrent = rows.every(row => {
72
+ const token = row.evidence.claimToken, hash = row.evidence.correlation?.payloadHash;
73
+ if (token === undefined || hash === undefined || row.seat === undefined || row.index === undefined
74
+ || !['dispatching', 'unknown', 'accepted'].includes(row.evidence.outcome))
75
+ return false;
76
+ const matching = frames.filter(frame => frame[2] === token);
77
+ return matching.length === tokens.filter(candidate => candidate === token).length
78
+ && matching.every(frame => hashPayload(frame[1]) === hash);
79
+ });
80
+ if (!recognized || !contentCurrent)
135
81
  return { recognized, current: false };
82
+ const now = Date.now();
83
+ for (const row of rows) {
84
+ if (!(await row.artifact.read(facts => currentRow(facts, row, now))).value)
85
+ return { recognized, current: false };
86
+ }
136
87
  for (const row of rows) {
137
88
  if (operation === 'guard')
138
89
  continue;
139
- const request = row.request, payload = row.payload;
140
- const admitted = await row.artifact.transactAs({ kind: 'participant', seat: request.seat }, facts => currentPayload(facts, request) === payload && facts.completeDelivery({ seat: request.seat,
141
- activity: row.evidence.activity, claimToken: request.claimToken, outcome: 'accepted', at: Date.now() })).catch(() => ({ value: false }));
90
+ const seat = row.seat;
91
+ const current = (facts) => currentRow(facts, row, Date.now());
92
+ const admitted = await row.artifact.transactAs({ kind: 'participant', seat }, facts => current(facts) && facts.channel.completeDelivery({ seat, message: row.evidence.message,
93
+ claimToken: row.evidence.claimToken, outcome: 'accepted', at: Date.now() })).catch(() => ({ value: false }));
142
94
  if (!admitted.value)
143
95
  return { recognized, current: false };
144
- if (operation === 'stored')
145
- await presentPending({ artifact: row.artifact, location: request.location,
146
- // Claude's stored event consumes even Agora previews; retain that contract for both modes.
147
- participant: request.participant, activity: request.activity, seat: request.seat, observationOf: request.claimToken, markSeen: true, sink: { present() { } },
148
- current: facts => currentPayload(facts, request) === payload });
96
+ if (operation === 'stored') {
97
+ await presentPending({ artifact: row.artifact, location: row.location,
98
+ // Persisted coverage decides consumption; the current renderer never guesses.
99
+ participant: row.evidence.participant, message: row.evidence.message, seat, observationOf: row.evidence.claimToken,
100
+ markSeen: row.evidence.coverage !== 'clipped', sink: { present() { } }, current });
101
+ }
149
102
  }
150
103
  return { recognized, current: true };
151
104
  }
@@ -1,5 +1,5 @@
1
1
  import { operationEnv } from './participant-identity.js';
2
- import { createArtifactPort } from './artifact.js';
2
+ import { channelArtifact, createArtifactPort } from './artifact.js';
3
3
  import { recordClaudeDeliveryStatus } from './claude-delivery-status.js';
4
4
  import { Square } from './square-api.js';
5
5
  import { observeClaudeDelivery } from './claude-delivery.js';
@@ -104,9 +104,9 @@ export async function runClaudeMod(inputText, env = process.env) {
104
104
  let beforeIndex;
105
105
  for (;;) {
106
106
  const page = facts.activityHeads({ beforeIndex }, { edge: 'newest', limit: 100 });
107
- const last = [...page.items].reverse().find((activity) => activity.at <= cutoff);
107
+ const last = page.items.find((activity) => activity.at <= cutoff && activity.messageIndex !== undefined);
108
108
  if (last !== undefined)
109
- return last.index;
109
+ return last.messageIndex;
110
110
  if (!page.hasMore)
111
111
  return -1;
112
112
  beforeIndex = page.items[0].index;
@@ -148,7 +148,7 @@ export async function runClaudeMod(inputText, env = process.env) {
148
148
  const artifact = createArtifactPort(location, undefined, signal);
149
149
  let indices;
150
150
  try {
151
- indices = await sweepPending({ artifact, location, now: Date.now(), graceMs: 0, limit: 8 });
151
+ indices = await sweepPending({ artifact: channelArtifact(artifact), location, now: Date.now(), graceMs: 0, limit: 8 });
152
152
  }
153
153
  catch (error) {
154
154
  if (error instanceof InternalSquareError && error.code === 'not_found')
@@ -0,0 +1,5 @@
1
+ import type { WakeTransportPort } from './channel-transport.js';
2
+ export declare function nativeTokens(text: string): string[];
3
+ export declare function nativePayload(token: string, preview: string): string;
4
+ export declare function hashPayload(payload: string): string;
5
+ export declare function createClaudeWakeTransport(env?: NodeJS.ProcessEnv): WakeTransportPort;
@@ -0,0 +1,58 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { writeClaudeNative } from './packages/agent-delivery/src/claude-native.js';
4
+ const marker = /(?:^|\n)\[square-inbox:([a-zA-Z0-9-]+)\](?:\n|$)/g;
5
+ export function nativeTokens(text) { return [...text.matchAll(marker)].map((match) => match[1]); }
6
+ export function nativePayload(token, preview) { return `[square-inbox:${token}]\n${preview}\n[/square-inbox:${token}]`; }
7
+ function nativeSupported(route) { return route.address.loaded === 'true' && !!route.address.endpoint; }
8
+ export function hashPayload(payload) { return createHash('sha256').update(payload).digest('hex'); }
9
+ async function preview(request) {
10
+ return request.presentation;
11
+ }
12
+ async function writeClaudeWake(sessionId, endpoint, messageId, payload, timeoutMs, signal) {
13
+ const result = await writeClaudeNative({ sessionId, endpoint, messageId }, payload, { deadline: Date.now() + timeoutMs, signal });
14
+ if (result.outcome === 'unavailable')
15
+ return { outcome: 'failed', signature: 'claude_endpoint_unavailable', message: result.message };
16
+ return { outcome: 'unknown', signature: 'native_admission_unconfirmed', message: result.outcome === 'written' ? 'Claude accepted the local write; native admission is unconfirmed.' : result.message };
17
+ }
18
+ export function createClaudeWakeTransport(env = process.env) {
19
+ return {
20
+ async probe(route) {
21
+ if (!nativeSupported(route))
22
+ return { outcome: 'not-capable', diagnostic: 'Claude native inbox unavailable: a loaded interactive Square mod is required.' };
23
+ try {
24
+ return (await stat(route.address.endpoint)).isSocket() || { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint is not a socket.' };
25
+ }
26
+ catch {
27
+ return { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint unavailable.' };
28
+ }
29
+ },
30
+ attempt: (request, timeoutMs, beforeSend) => dispatchClaude(request, timeoutMs, beforeSend, env),
31
+ };
32
+ }
33
+ async function dispatchClaude(request, timeoutMs, beforeSend, env = process.env) {
34
+ void env;
35
+ const signal = AbortSignal.timeout(timeoutMs);
36
+ if (!nativeSupported(request.route) || request.claimToken === undefined)
37
+ return { outcome: 'not-capable', diagnostic: 'Claude native delivery needs a current artifact claim and loaded inbox.' };
38
+ if (signal.aborted || !await beforeSend())
39
+ return { outcome: 'unknown', signature: 'native_send_suppressed' };
40
+ const prepared = await preview(request);
41
+ if (prepared === undefined)
42
+ return { outcome: 'not-capable', diagnostic: 'Claude activity is no longer current.' };
43
+ const messageId = randomUUID();
44
+ const payload = nativePayload(request.claimToken, prepared.payload);
45
+ const correlation = { harness: 'claude', token: request.claimToken, messageId, payloadHash: hashPayload(payload) };
46
+ if (!await request.channel.prepare({ correlation, coverage: prepared.coverage,
47
+ ...(prepared.description === undefined ? {} : { description: prepared.description }) }))
48
+ return { outcome: 'not-capable', diagnostic: 'Claude delivery claim is no longer current.' };
49
+ const fresh = await preview(request);
50
+ if (fresh === undefined || nativePayload(request.claimToken, fresh.payload) !== payload)
51
+ return { outcome: 'not-capable', diagnostic: 'Claude activity changed before native write.' };
52
+ if (signal.aborted || !await beforeSend())
53
+ return { outcome: 'not-capable', diagnostic: 'Claude activity was consumed before native write.' };
54
+ const sessionId = request.route.address.sessionId;
55
+ if (sessionId === undefined)
56
+ return { outcome: 'not-capable', diagnostic: 'Claude native route has no session address.' };
57
+ return writeClaudeWake(sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
58
+ }
@@ -1,5 +1,5 @@
1
1
  import { type Owner } from '../square-api.js';
2
- import { type HardCap } from '../model.js';
2
+ import { type HardCap, type WakeAddress } from '../model.js';
3
3
  import { Square } from '../square-api.js';
4
4
  import type { OperationControl } from '../square-facade.js';
5
5
  export interface CommandContext {
@@ -46,7 +46,7 @@ export declare function squareInput(context: CommandContext): {
46
46
  };
47
47
  export declare function commandIdentity(env: NodeJS.ProcessEnv, explicit?: Owner): {
48
48
  owner: Owner;
49
- wakeAddress?: import('../model.js').WakeAddress;
49
+ wakeAddress?: WakeAddress;
50
50
  };
51
51
  /** Adapter lifetime only; each callback uses the actual public resource API. */
52
52
  export declare function withSquare<T>(context: CommandContext, run: (square: Square) => Promise<T>, control?: OperationControl): Promise<T>;
@@ -103,7 +103,6 @@ const LOCATION_REQUIRED_COMMANDS = new Set([
103
103
  'status',
104
104
  'participants',
105
105
  'doctor',
106
- 'migrate',
107
106
  ]);
108
107
  export async function parseGlobalArgs(rawArgs) {
109
108
  const args = [...rawArgs];
@@ -3,9 +3,5 @@ interface DoctorResult {
3
3
  output: string;
4
4
  exitCode?: number;
5
5
  }
6
- interface MigrateResult {
7
- output: string;
8
- }
9
- export declare const migrateCommand: CommandSpec<undefined, MigrateResult>;
10
6
  export declare const doctorCommand: CommandSpec<undefined, DoctorResult>;
11
7
  export {};
@@ -1,30 +1,6 @@
1
- import { diagnoseSquare, migrateSquare } from '../square-api.js';
2
- import { commandPrefix, renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
1
+ import { diagnoseSquare } from '../square-api.js';
2
+ import { renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
3
3
  import { requireSquarePath, squareInput, usage } from './context.js';
4
- export const migrateCommand = {
5
- parse(argv, context) {
6
- if (argv.length > 0)
7
- usage(context.command);
8
- return undefined;
9
- },
10
- async execute(_intent, context) {
11
- const squarePath = requireSquarePath(context);
12
- const outcome = await migrateSquare(squareInput(context));
13
- const body = outcome.status === 'migrated'
14
- ? [
15
- '✓ migrated to current row storage',
16
- ...(outcome.counts === undefined ? [] : [
17
- ` · ${outcome.counts.activities} activities · ${outcome.counts.receipts} receipts · ${outcome.counts.leases} leases · ${outcome.counts.routes} routes`,
18
- ` · ${outcome.counts.recoveredSeats} recovered seats · ${outcome.counts.unownedStanding} unowned standing · ${outcome.counts.sharedBodySeats} shared-body seats · ${outcome.counts.skippedEvidence} skipped evidence`,
19
- ]),
20
- ].join('\n')
21
- : '· already current format — nothing changed';
22
- return { output: withPathOutput(squarePath, body, { participantCount: outcome.standing }) };
23
- },
24
- present(result) {
25
- process.stdout.write(result.output);
26
- },
27
- };
28
4
  export const doctorCommand = {
29
5
  parse(argv, context) {
30
6
  if (argv.length > 0)
@@ -34,9 +10,6 @@ export const doctorCommand = {
34
10
  async execute(_intent, context) {
35
11
  const squarePath = requireSquarePath(context);
36
12
  const diagnosis = await diagnoseSquare(squareInput(context));
37
- if (diagnosis.migrationRequired !== undefined) {
38
- return { output: withPathOutput(squarePath, `${diagnosis.migrationRequired}\n${commandPrefix(squarePath)} migrate\n`), exitCode: 2 };
39
- }
40
13
  if (diagnosis.unfixable !== undefined || diagnosis.standingCount === undefined) {
41
14
  return {
42
15
  output: withPathOutput(squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the artifact could not be decoded')),