@astrosheep/square 0.3.94 → 0.3.96

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 (99) hide show
  1. package/dist/artifact.js +1 -1
  2. package/dist/attention-presentation.d.ts +5 -4
  3. package/dist/attention-presentation.js +18 -8
  4. package/dist/attention.d.ts +19 -0
  5. package/dist/attention.js +30 -0
  6. package/dist/boundary-presentation.js +50 -48
  7. package/dist/catch-decisions.d.ts +1 -2
  8. package/dist/catch-decisions.js +41 -21
  9. package/dist/channel.d.ts +72 -0
  10. package/dist/channel.js +86 -0
  11. package/dist/claude-delivery.js +2 -1
  12. package/dist/cli/context.js +0 -2
  13. package/dist/cli/express-presentation.js +3 -3
  14. package/dist/cli/json.js +1 -1
  15. package/dist/cli/program.js +1 -1
  16. package/dist/cli/registry.js +1 -3
  17. package/dist/cli/square-commands.d.ts +0 -2
  18. package/dist/cli/square-commands.js +21 -27
  19. package/dist/decisions.d.ts +0 -5
  20. package/dist/decisions.js +0 -13
  21. package/dist/delivery-attempts.d.ts +52 -0
  22. package/dist/delivery-attempts.js +73 -0
  23. package/dist/delivery-effects.d.ts +139 -0
  24. package/dist/delivery-effects.js +163 -0
  25. package/dist/delivery-operations.js +43 -68
  26. package/dist/delivery-primitives.d.ts +5 -0
  27. package/dist/delivery-primitives.js +3 -0
  28. package/dist/delivery.d.ts +1 -1
  29. package/dist/delivery.js +8 -7
  30. package/dist/help.js +4 -6
  31. package/dist/http-delivery.d.ts +16 -0
  32. package/dist/http-delivery.js +101 -0
  33. package/dist/inbox.js +3 -1
  34. package/dist/mcp.js +65 -63
  35. package/dist/model.d.ts +10 -6
  36. package/dist/model.js +20 -1
  37. package/dist/notifications.js +2 -0
  38. package/dist/operation-presentation.d.ts +1 -2
  39. package/dist/operation-presentation.js +12 -20
  40. package/dist/ports.d.ts +8 -14
  41. package/dist/presentation-operations.d.ts +15 -19
  42. package/dist/presentation-operations.js +47 -57
  43. package/dist/presentation.d.ts +2 -9
  44. package/dist/presentation.js +13 -48
  45. package/dist/receive.d.ts +66 -0
  46. package/dist/receive.js +54 -0
  47. package/dist/runtime.d.ts +0 -1
  48. package/dist/runtime.js +2 -3
  49. package/dist/sqlite-square.d.ts +3 -2
  50. package/dist/sqlite-square.js +83 -57
  51. package/dist/square-api.d.ts +1 -1
  52. package/dist/square-api.js +1 -1
  53. package/dist/square-core.d.ts +1 -9
  54. package/dist/square-domain.js +29 -36
  55. package/dist/square-facade.d.ts +25 -23
  56. package/dist/square-legacy-framing.d.ts +2 -0
  57. package/dist/square-legacy-framing.js +8 -0
  58. package/dist/square-migration.d.ts +3 -1
  59. package/dist/square-migration.js +46 -13
  60. package/dist/square-receive.d.ts +6 -0
  61. package/dist/square-receive.js +18 -0
  62. package/dist/square-sqlite.d.ts +5 -3
  63. package/dist/square-sqlite.js +100 -92
  64. package/dist/square-tools.d.ts +1 -0
  65. package/dist/square-tools.js +12 -12
  66. package/dist/views.d.ts +2 -3
  67. package/dist/views.js +14 -19
  68. package/dist/wakes.d.ts +0 -3
  69. package/dist/wakes.js +3 -5
  70. package/examples/symmetric-domains.mjs +250 -0
  71. package/guides/application-facade-delivery-evidence.md +5 -5
  72. package/guides/attention-primitive.md +32 -0
  73. package/guides/change-waits.md +2 -2
  74. package/guides/codex-delivery.md +1 -1
  75. package/guides/delivery-primitives.md +180 -0
  76. package/guides/mcp.md +1 -1
  77. package/guides/migration.md +12 -6
  78. package/guides/participant-ownership.md +1 -1
  79. package/guides/pi-tools.md +2 -2
  80. package/guides/primitive-catch-consolidation.md +5 -5
  81. package/guides/primitive-history-consolidation.md +2 -2
  82. package/guides/primitive-status-consolidation.md +3 -3
  83. package/guides/primitive-storage-consolidation.md +1 -1
  84. package/guides/receive-primitive.md +90 -0
  85. package/guides/row-runtime-delivery-evidence.md +1 -1
  86. package/guides/row-storage-design.md +17 -23
  87. package/guides/square-api.md +77 -10
  88. package/guides/symmetric-domains.md +92 -0
  89. package/package.json +18 -1
  90. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/plugins/claude/skills/square/SKILL.md +2 -6
  92. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  93. package/plugins/paseo/client/marble.ts +7 -0
  94. package/plugins/paseo/client/markdown.tsx +65 -9
  95. package/plugins/paseo/client/palette.ts +43 -20
  96. package/plugins/paseo/client/square-message.tsx +57 -48
  97. package/plugins/paseo/shared/message.ts +1 -2
  98. package/plugins/pi/square-pi.js +6 -4
  99. package/skills/square/SKILL.md +2 -6
@@ -5,6 +5,7 @@ import { asSeatRef } from './seats.js';
5
5
  import { freshWatchLease } from './runtime.js';
6
6
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
7
7
  import { leaseOwnsNotification } from './delivery.js';
8
+ import { dispatchEffect } from './delivery-effects.js';
8
9
  function routeFor(location, seat, now) {
9
10
  if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
10
11
  return undefined;
@@ -37,19 +38,6 @@ async function currentness(artifact, request, now) {
37
38
  });
38
39
  return value;
39
40
  }
40
- async function attemptWakeWithin(transport, request, timeoutMs, beforeSend) {
41
- let timer;
42
- const timeout = new Promise((resolve) => {
43
- timer = setTimeout(() => resolve({ outcome: 'unknown', diagnostic: 'transport timeout' }), timeoutMs);
44
- });
45
- try {
46
- return await Promise.race([transport.attempt(request, timeoutMs, beforeSend), timeout]);
47
- }
48
- finally {
49
- if (timer !== undefined)
50
- clearTimeout(timer);
51
- }
52
- }
53
41
  async function candidates(artifact, requested, now = Date.now()) {
54
42
  const { value } = await artifact.read((facts) => {
55
43
  const heads = requested === undefined
@@ -62,12 +50,6 @@ async function candidates(artifact, requested, now = Date.now()) {
62
50
  });
63
51
  return value;
64
52
  }
65
- function releaseClaim(artifact, seat, activity, claimToken, signature, message, diagnostic) {
66
- return artifact.completeDelivery({ seat, activity, claimToken, outcome: 'released', at: Date.now(), signature,
67
- ...(message === undefined ? {} : { message }),
68
- ...(diagnostic === undefined ? {} : { diagnostic: safeJson(diagnostic) }),
69
- }).catch(() => false);
70
- }
71
53
  function safeJson(value) {
72
54
  if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
73
55
  return value;
@@ -115,55 +97,48 @@ export async function deliverPending(input) {
115
97
  }
116
98
  const claimToken = randomUUID();
117
99
  const leaseMs = input.timeoutMs ?? 5000;
118
- const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
119
- route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
120
- if (claim.status !== 'acquired')
121
- return;
122
- const withClaim = { ...request, claimToken, attemptN: claim.attemptN };
123
- let final;
124
- let suppressed = false;
125
- const beforeSend = async () => {
126
- try {
127
- final = await currentness(input.artifact, withClaim, input.now ?? Date.now());
128
- suppressed = !final.current;
129
- }
130
- catch {
131
- suppressed = true;
132
- }
133
- return !suppressed;
134
- };
135
- let before;
136
- try {
137
- before = await currentness(input.artifact, withClaim, input.now ?? Date.now());
138
- }
139
- catch { /* unverifiable */ }
140
- if (!before?.current) {
141
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', before === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.', before);
142
- return;
143
- }
144
- let outcome;
145
- try {
146
- outcome = await attemptWakeWithin(input.transport, withClaim, leaseMs, beforeSend);
147
- }
148
- catch (error) {
149
- outcome = { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
150
- }
151
- if (suppressed) {
152
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', 'The notification changed before the final send check.', final ?? { observationAvailable: false });
153
- return;
154
- }
155
- if (outcome.outcome === 'not-capable') {
156
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'transport_not_capable', undefined, outcome.diagnostic);
157
- return;
158
- }
159
- const mapped = outcome.outcome === 'accepted' ? 'accepted'
160
- : outcome.outcome === 'unknown' ? 'unknown' : 'failed';
161
- await input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken,
162
- outcome: mapped, at: Date.now(),
163
- ...('signature' in outcome && outcome.signature !== undefined ? { signature: outcome.signature } : {}),
164
- ...('message' in outcome && outcome.message !== undefined ? { message: outcome.message } : {}),
165
- ...('diagnostic' in outcome && outcome.diagnostic !== undefined ? { diagnostic: safeJson(outcome.diagnostic) } : {}),
166
- }).catch(() => false);
100
+ let snapshot;
101
+ await dispatchEffect({
102
+ async claim() {
103
+ const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
104
+ route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
105
+ return claim.status === 'acquired' ? { token: claimToken, ordinal: claim.attemptN } : undefined;
106
+ },
107
+ async current() {
108
+ snapshot = undefined;
109
+ snapshot = await currentness(input.artifact, request, input.now ?? Date.now());
110
+ return snapshot.current;
111
+ },
112
+ async settle(claim, completion) {
113
+ let fields = {};
114
+ if (completion.reason === 'not-current')
115
+ fields = {
116
+ signature: 'pre_send_revalidation_failed',
117
+ message: completion.phase === 'final' ? 'The notification changed before the final send check.'
118
+ : snapshot === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.',
119
+ diagnostic: snapshot ?? (completion.phase === 'final' ? { observationAvailable: false } : undefined),
120
+ };
121
+ else if (completion.reason === 'timeout')
122
+ fields = { diagnostic: 'transport timeout' };
123
+ else if (completion.reason === 'threw')
124
+ fields = { diagnostic: completion.error instanceof Error ? completion.error.message : String(completion.error) };
125
+ else if (completion.reason === 'not-capable')
126
+ fields = {
127
+ signature: 'transport_not_capable', diagnostic: completion.data?.diagnostic,
128
+ };
129
+ else if (completion.reason === 'returned')
130
+ fields = completion.data;
131
+ return input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken: claim.token,
132
+ outcome: completion.outcome, at: Date.now(),
133
+ ...(fields.signature === undefined ? {} : { signature: fields.signature }),
134
+ ...(fields.message === undefined ? {} : { message: fields.message }),
135
+ ...(fields.diagnostic === undefined ? {} : { diagnostic: safeJson(fields.diagnostic) }),
136
+ }).catch(() => false);
137
+ },
138
+ }, async ({ claim, timeoutMs, beforeSend }) => {
139
+ const outcome = await input.transport.attempt({ ...request, claimToken: claim.token, attemptN: claim.ordinal }, timeoutMs, beforeSend);
140
+ return { outcome: outcome.outcome, data: outcome };
141
+ }, { timeoutMs: leaseMs });
167
142
  };
168
143
  const groups = receivers.values();
169
144
  const settled = await Promise.allSettled(Array.from({ length: Math.min(4, receivers.size) }, async () => {
@@ -0,0 +1,5 @@
1
+ /** Storage-independent attempt transitions and bound effect lifecycles. */
2
+ export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
+ export type { DeliveryPurpose, AttemptOutcome, Attempt, AttemptRows, AttemptClaim } from './delivery-attempts.js';
4
+ export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
5
+ export type { EffectClaim, EffectPort, Persistence, Settlement, DispatchOutcome, DispatchCompletion, DispatchContext, DispatchReport, PresentationCompletion, PresentationWork, PreparedPresentation, PresentationControl, PresentationReport } from './delivery-effects.js';
@@ -0,0 +1,3 @@
1
+ /** Storage-independent attempt transitions and bound effect lifecycles. */
2
+ export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
+ export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
@@ -40,7 +40,7 @@ export declare function pendingPages(artifact: SquareArtifactPort, scan: Pending
40
40
  at: number;
41
41
  actor: string;
42
42
  recipient: string;
43
- route: "bell" | "mention" | "reply" | "attention";
43
+ route: import("./attention.js").AudienceRoute;
44
44
  }[], void, unknown>;
45
45
  /** A pass retains only its captured joins; routes and individual receipts are fresh. */
46
46
  export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
package/dist/delivery.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
2
2
  import { parseActivityId } from './square-core.js';
3
3
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
4
+ import { automaticDispatchEligible, presentationBlocksDispatch } from './delivery-attempts.js';
4
5
  import { matchesCatchSelection } from './catch-selection.js';
5
6
  /** Exact stored activity projection for transports that send an awareness reminder. */
6
7
  export function wakePreview(facts, request) {
@@ -13,19 +14,19 @@ export function wakePreview(facts, request) {
13
14
  return undefined;
14
15
  return renderSquareReminder([renderAttentionPreview({
15
16
  squarePath: request.location, recipient: request.participant, actIndex: item.index,
16
- actor: item.actor, route: head.route, body: item.body ?? '(structured activity)',
17
- })]);
17
+ actor: item.actor, route: head.route, body: item.body ?? '(structured activity)', delivery: item.delivery,
18
+ })], [item.delivery]);
19
+ }
20
+ function custody(evidence) {
21
+ return evidence.map(row => ({ purpose: row.kind === 'wake' ? 'dispatch' : 'presentation', outcome: row.outcome, expiresAt: row.expiresAt }));
18
22
  }
19
23
  /** Presentation custody blocks a wake, independently of native wake acceptance. */
20
24
  export function presentationBlocksWake(evidence, now) {
21
- return evidence.some((row) => row.kind === 'presentation'
22
- && (row.outcome === 'presented' || row.outcome === 'clipped' || row.outcome === 'unknown'
23
- || row.outcome === 'dispatching' && (row.expiresAt === undefined || row.expiresAt > now)));
25
+ return presentationBlocksDispatch(custody(evidence), now);
24
26
  }
25
27
  /** Preselection only: an expired wake dispatch is uncertain, never safe automatic replay. */
26
28
  export function automaticWakeEligible(evidence, now) {
27
- return !presentationBlocksWake(evidence, now) && !evidence.some((row) => row.kind === 'wake'
28
- && (row.outcome === 'accepted' || row.outcome === 'unknown' || row.outcome === 'dispatching'));
29
+ return automaticDispatchEligible(custody(evidence), now);
29
30
  }
30
31
  /** Notification pending is not cursor suffix or historical catch membership. */
31
32
  export function pendingHead(facts, name, index) {
package/dist/help.js CHANGED
@@ -32,7 +32,7 @@ const COMMANDS = [
32
32
  '',
33
33
  'Options:',
34
34
  ' -f, --force Express without first catching unread activity or satisfying attention etiquette.',
35
- ' --no-wait If held or throttled, save a draft and return instead of waiting. A held wait gives up on its own after 10 minutes.',
35
+ ' --no-wait If throttled, save a draft and return instead of waiting.',
36
36
  ' --metadata <json> Attach a bounded JSON object (at most 16 KiB UTF-8 and 256 nesting levels); it may be the only content.',
37
37
  ' Metadata is returned in structured activity results but is not shown in human text or searchable.',
38
38
  ],
@@ -70,8 +70,6 @@ const COMMANDS = [
70
70
  summary: 'Show the participant roster and current states.',
71
71
  details: ['Options:', ' --limit <count> Show the first count names in human text (default 20, maximum 100).'],
72
72
  },
73
- { names: ['hold'], usage: '--as <name> hold [reason | -]', usesSquare: true, group: 'participant', summary: 'Raise a hand and pause participant activity.' },
74
- { names: ['resume'], usage: '--as <name> resume', usesSquare: true, group: 'participant', summary: 'Lower the raised hand and resume activity.' },
75
73
  {
76
74
  names: ['install'], usage: 'install (--all | <target>...) [-f]', group: 'maintenance',
77
75
  summary: 'Install Square support for one or more agent hosts.',
@@ -94,7 +92,7 @@ const COMMANDS = [
94
92
  {
95
93
  names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
96
94
  summary: 'Convert a supported older square to the current format in place.',
97
- details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–6 migrate explicitly to format 7; format 6 attachment is preserved and Body coordinates become opaque owners. Versions 4/5 first gain attached seats; 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
95
+ details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–7 migrate explicitly to format 8. Format 7 migration deletes hold/resume activities and their keyed rows, clears replies to those activities, and preserves surviving activity ids with gaps. Format 6 attachment and opaque-owner conversion are preserved; versions 4/5 first gain attached seats, and 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
98
96
  },
99
97
  ];
100
98
  function definitionFor(command) {
@@ -105,7 +103,7 @@ function isHelpFlag(value) {
105
103
  }
106
104
  export function renderGlobalHelp() {
107
105
  const groups = [
108
- { key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
106
+ { key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'catch', 'history', 'status', 'done'] },
109
107
  { key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
110
108
  { key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor', 'migrate'] },
111
109
  ];
@@ -121,7 +119,7 @@ export function renderGlobalHelp() {
121
119
  'Usage: square [--location <square>] [--as <name>] [--owner <id>] <command> [args...]',
122
120
  'Environment: SQUARE_LOCATION sets the CLI local artifact path; square-accessing commands require it or --location. Use --as to select a participant.',
123
121
  'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use catch --idle.',
124
- 'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, hold, resume, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
122
+ 'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
125
123
  'History emits one JSON document; stream --ndjson emits one SDK record per line.',
126
124
  '',
127
125
  ...commandLines,
@@ -0,0 +1,16 @@
1
+ import type { WakeRoute } from './model.js';
2
+ import type { WakeOutcome, WakeRequest, WakeTransportPort } from './ports.js';
3
+ export interface HttpWakeTransportOptions {
4
+ fetch?: typeof fetch;
5
+ env?: NodeJS.ProcessEnv;
6
+ }
7
+ /** HTTP accepts wake custody only; it never consumes or presents Square activity. */
8
+ export declare class HttpWakeTransport implements WakeTransportPort {
9
+ private readonly opts;
10
+ constructor(opts?: HttpWakeTransportOptions);
11
+ probe(route: WakeRoute): Promise<boolean | {
12
+ outcome: 'not-capable';
13
+ diagnostic: string;
14
+ }>;
15
+ attempt(request: WakeRequest, timeoutMs: number, beforeSend: () => Promise<boolean>): Promise<WakeOutcome>;
16
+ }
@@ -0,0 +1,101 @@
1
+ import { wakePreview } from './delivery.js';
2
+ /** Configuration only: probing never contacts the receiver. */
3
+ function configuration(route, env) {
4
+ if (route.kind !== 'http')
5
+ return undefined;
6
+ try {
7
+ const url = new URL(route.address.url);
8
+ if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password
9
+ || route.address.url.includes('#'))
10
+ return undefined;
11
+ const headers = new Headers({ 'Content-Type': 'application/json' });
12
+ const tokenEnv = route.address.tokenEnv;
13
+ if (tokenEnv !== undefined) {
14
+ const token = tokenEnv.trim() ? env[tokenEnv] : undefined;
15
+ // RFC 6750 bearer credentials; whitespace/control characters cannot become headers.
16
+ if (!token || token !== token.trim() || !/^[A-Za-z0-9._~+\/-]+=*$/.test(token))
17
+ return undefined;
18
+ headers.set('Authorization', `Bearer ${token}`);
19
+ }
20
+ return { url: route.address.url, headers };
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ /** HTTP accepts wake custody only; it never consumes or presents Square activity. */
27
+ export class HttpWakeTransport {
28
+ opts;
29
+ constructor(opts = {}) {
30
+ this.opts = opts;
31
+ }
32
+ async probe(route) {
33
+ return configuration(route, this.opts.env ?? process.env) !== undefined
34
+ ? true : { outcome: 'not-capable', diagnostic: 'HTTP wake configuration unavailable.' };
35
+ }
36
+ async attempt(request, timeoutMs, beforeSend) {
37
+ const deadline = Date.now() + timeoutMs;
38
+ if (request.route.kind !== 'http')
39
+ return { outcome: 'not-capable' };
40
+ const config = configuration(request.route, this.opts.env ?? process.env);
41
+ if (config === undefined)
42
+ return { outcome: 'failed', signature: 'http_config_rejected', message: 'Invalid HTTP wake configuration.' };
43
+ const controller = new AbortController();
44
+ let sent = false;
45
+ const budgetUnavailable = () => sent
46
+ ? { outcome: 'unknown', signature: 'http_send_timeout', message: 'HTTP wake acceptance is unknown after timeout.' }
47
+ : { outcome: 'failed', signature: 'http_pre_send_timeout', message: 'HTTP wake budget elapsed before sending.' };
48
+ if (!Number.isFinite(timeoutMs) || Date.now() >= deadline)
49
+ return budgetUnavailable();
50
+ let timer;
51
+ const timeout = new Promise(resolve => {
52
+ timer = setTimeout(() => { controller.abort(); resolve(budgetUnavailable()); }, Math.max(0, deadline - Date.now()));
53
+ });
54
+ const send = async () => {
55
+ try {
56
+ const preview = request.artifact === undefined ? undefined
57
+ : (await request.artifact.read(facts => wakePreview(facts, request), controller.signal)).value;
58
+ if (preview === undefined)
59
+ return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
60
+ const body = JSON.stringify({
61
+ location: request.location, participant: request.participant, activity: request.activity, actor: request.actor,
62
+ ...(request.attemptN === undefined ? {} : { attemptN: request.attemptN }), preview,
63
+ });
64
+ if (controller.signal.aborted || Date.now() >= deadline)
65
+ return budgetUnavailable();
66
+ if (!(await beforeSend()))
67
+ return { outcome: 'not-capable' };
68
+ // Preview and the final gate spend the same absolute budget as the request.
69
+ if (controller.signal.aborted || Date.now() >= deadline)
70
+ return budgetUnavailable();
71
+ sent = true;
72
+ const response = await (this.opts.fetch ?? fetch)(config.url, {
73
+ method: 'POST', headers: config.headers, body, redirect: 'manual', signal: controller.signal,
74
+ });
75
+ // Status is the whole acknowledgement; never wait for or buffer a receiver body.
76
+ void response.body?.cancel().catch(() => undefined);
77
+ if (response.status >= 200 && response.status < 300)
78
+ return { outcome: 'accepted', signature: 'http_accepted' };
79
+ return {
80
+ outcome: 'failed', signature: response.status >= 500 && response.status < 600 ? 'http_status_transient' : 'http_status_rejected',
81
+ message: `HTTP wake returned status ${response.status}.`,
82
+ };
83
+ }
84
+ catch {
85
+ // URLs, query credentials and raw fetch exceptions must never reach evidence.
86
+ if (controller.signal.aborted)
87
+ return budgetUnavailable();
88
+ return sent
89
+ ? { outcome: 'unknown', signature: 'http_send_unknown', message: 'HTTP wake acceptance is unknown after a network error.' }
90
+ : { outcome: 'failed', signature: 'http_pre_send_failed', message: 'HTTP wake could not be prepared.' };
91
+ }
92
+ };
93
+ try {
94
+ return await Promise.race([send(), timeout]);
95
+ }
96
+ finally {
97
+ if (timer !== undefined)
98
+ clearTimeout(timer);
99
+ }
100
+ }
101
+ }
package/dist/inbox.js CHANGED
@@ -149,6 +149,7 @@ async function projectPending(candidates, cache, signal) {
149
149
  cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => ({
150
150
  ...membership, notifications: pendingNotifications(facts, membership.seat.participant).map(({ item, route }) => ({
151
151
  actIndex: item.index, actor: item.actor, at: item.at, body: item.body ?? '(structured activity)', route,
152
+ ...(item.delivery === undefined ? {} : { delivery: item.delivery }),
152
153
  })),
153
154
  })) };
154
155
  }
@@ -197,7 +198,8 @@ export function nativePendingPreview(facts, input) {
197
198
  if (item?.kind !== 'say' || deliveryDeferralExpiry(facts, seat, input.actIndex, input.now) !== undefined)
198
199
  return undefined;
199
200
  return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
200
- actor: item.actor, route: head.route, body: item.body ?? '(structured activity)' })]), clipped: attentionBodyIsClipped(item.body ?? '(structured activity)') };
201
+ actor: item.actor, route: head.route, body: item.body ?? '(structured activity)', delivery: item.delivery })], [item.delivery]),
202
+ clipped: attentionBodyIsClipped(item.body ?? '(structured activity)', item.delivery) };
201
203
  }
202
204
  export async function sessionInbox(owner, env = process.env, signal) {
203
205
  return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
package/dist/mcp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure, squareToolFailureText } from './square-tools.js';
3
+ import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolsEnabled, squareToolFailure, squareToolFailureText } from './square-tools.js';
4
4
  import { operationEnv } from './participant-identity.js';
5
5
  import { codexWakeAddress } from './codex-address.js';
6
6
  import { readClaudeMcpCall } from './claude-mcp-context.js';
@@ -10,76 +10,78 @@ import { harnessOwner } from './square-api.js';
10
10
  export function createSquareMcpServer(options) {
11
11
  const { owner: configuredOwner, wakeAddress, ...resourceOptions } = options;
12
12
  const baseEnv = { ...(resourceOptions.env ?? process.env) };
13
+ const toolsEnabled = squareToolsEnabled(baseEnv);
13
14
  const resources = createSquareResourceOwner();
14
15
  // Calls to the configured square borrow one server-owned connection.
15
16
  const inFlight = new Set();
16
17
  const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
17
- instructions: SQUARE_TOOL_INSTRUCTIONS,
18
+ ...(toolsEnabled ? { instructions: SQUARE_TOOL_INSTRUCTIONS } : {}),
18
19
  });
19
- for (const tool of SQUARE_TOOLS) {
20
- server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
21
- const call = (async () => {
22
- let square;
23
- try {
24
- const { location } = args;
25
- const threadId = extra._meta?.threadId;
26
- const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
27
- // Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
28
- const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
29
- ? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
30
- : undefined;
31
- const owner = claude !== undefined
32
- ? harnessOwner({ channel: 'claude-code', session: claude.sessionId, ...(claude.agentId === undefined ? {} : { agent: claude.agentId }) })
33
- : typeof threadId === 'string' && threadId.trim()
34
- ? harnessOwner({ channel: 'codex', session: threadId })
35
- : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
36
- ? harnessOwner({ channel: 'opencode', session: opencodeSessionId })
37
- : configuredOwner;
38
- const env = claude !== undefined
39
- ? { ...operationEnv('claude', claude.sessionId, baseEnv),
40
- // Preserve Claude's session + child flag semantics; an agent loop ID is not a session ID.
41
- CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
42
- : typeof threadId === 'string' && threadId.trim()
43
- ? operationEnv('codex', threadId, baseEnv)
44
- : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
45
- ? operationEnv('opencode', opencodeSessionId, baseEnv)
46
- : baseEnv;
47
- square = await Square.at({
48
- ...resourceOptions, env, cwd: claude?.cwd ?? resourceOptions.cwd,
49
- path: location ?? options.path,
50
- // The server owner is bound to its configured square; overrides close after each call.
51
- resources: location === undefined && claude === undefined ? resources : undefined,
52
- }, { signal: extra.signal });
53
- const callWake = wakeAddress !== undefined && (owner === configuredOwner
54
- || wakeAddress.kind === 'paseo' && wakeAddress.address.agentId === baseEnv.PASEO_AGENT_ID
55
- || wakeAddress.kind === 'claude-native' && wakeAddress.address.sessionId === claude?.sessionId
56
- || wakeAddress.kind === 'opencode-server' && wakeAddress.address.sessionId === opencodeSessionId)
57
- ? wakeAddress : undefined;
58
- let text;
20
+ if (toolsEnabled)
21
+ for (const tool of SQUARE_TOOLS) {
22
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
23
+ const call = (async () => {
24
+ let square;
59
25
  try {
60
- text = await tool.run(square, args, { signal: extra.signal }, {
61
- owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
62
- ? codexWakeAddress(threadId, env) : callWake,
63
- });
26
+ const { location } = args;
27
+ const threadId = extra._meta?.threadId;
28
+ const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
29
+ // Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
30
+ const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
31
+ ? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
32
+ : undefined;
33
+ const owner = claude !== undefined
34
+ ? harnessOwner({ channel: 'claude-code', session: claude.sessionId, ...(claude.agentId === undefined ? {} : { agent: claude.agentId }) })
35
+ : typeof threadId === 'string' && threadId.trim()
36
+ ? harnessOwner({ channel: 'codex', session: threadId })
37
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
38
+ ? harnessOwner({ channel: 'opencode', session: opencodeSessionId })
39
+ : configuredOwner;
40
+ const env = claude !== undefined
41
+ ? { ...operationEnv('claude', claude.sessionId, baseEnv),
42
+ // Preserve Claude's session + child flag semantics; an agent loop ID is not a session ID.
43
+ CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
44
+ : typeof threadId === 'string' && threadId.trim()
45
+ ? operationEnv('codex', threadId, baseEnv)
46
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
47
+ ? operationEnv('opencode', opencodeSessionId, baseEnv)
48
+ : baseEnv;
49
+ square = await Square.at({
50
+ ...resourceOptions, env, cwd: claude?.cwd ?? resourceOptions.cwd,
51
+ path: location ?? options.path,
52
+ // The server owner is bound to its configured square; overrides close after each call.
53
+ resources: location === undefined && claude === undefined ? resources : undefined,
54
+ }, { signal: extra.signal });
55
+ const callWake = wakeAddress !== undefined && (owner === configuredOwner
56
+ || wakeAddress.kind === 'paseo' && wakeAddress.address.agentId === baseEnv.PASEO_AGENT_ID
57
+ || wakeAddress.kind === 'claude-native' && wakeAddress.address.sessionId === claude?.sessionId
58
+ || wakeAddress.kind === 'opencode-server' && wakeAddress.address.sessionId === opencodeSessionId)
59
+ ? wakeAddress : undefined;
60
+ let text;
61
+ try {
62
+ text = await tool.run(square, args, { signal: extra.signal }, {
63
+ owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
64
+ ? codexWakeAddress(threadId, env) : callWake,
65
+ });
66
+ }
67
+ finally {
68
+ await square.close();
69
+ }
70
+ return { content: [{ type: 'text', text }] };
64
71
  }
65
- finally {
66
- await square.close();
72
+ catch (error) {
73
+ const failure = squareToolFailure(error, extra.signal);
74
+ const participant = args.as;
75
+ return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, {
76
+ location: square?.location ?? options.path,
77
+ participant: typeof participant === 'string' ? participant : undefined,
78
+ }) }] };
67
79
  }
68
- return { content: [{ type: 'text', text }] };
69
- }
70
- catch (error) {
71
- const failure = squareToolFailure(error, extra.signal);
72
- const participant = args.as;
73
- return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, {
74
- location: square?.location ?? options.path,
75
- participant: typeof participant === 'string' ? participant : undefined,
76
- }) }] };
77
- }
78
- })();
79
- inFlight.add(call);
80
- return call.finally(() => { inFlight.delete(call); });
81
- });
82
- }
80
+ })();
81
+ inFlight.add(call);
82
+ return call.finally(() => { inFlight.delete(call); });
83
+ });
84
+ }
83
85
  const close = server.close.bind(server);
84
86
  // One idempotent shutdown for both close paths: an explicit server.close() and a
85
87
  // transport-initiated disconnect, where the SDK protocol fires onclose by itself.
package/dist/model.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import type { Act, ActivityId } from './square-core.js';
2
2
  export { formatActivityId, parseActivityId } from './square-core.js';
3
3
  export type { Act, ActivityId, Audience, Reach } from './square-core.js';
4
- export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo"];
4
+ export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo", "http"];
5
5
  export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
6
6
  export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
7
7
  readonly [key: string]: JsonValue;
8
8
  };
9
9
  export type ActivityMetadata = Readonly<Record<string, JsonValue>>;
10
+ export interface ActivityDelivery {
11
+ readonly mode: 'direct';
12
+ readonly preview?: string;
13
+ }
14
+ export declare function validateActivityDelivery(value: unknown): ActivityDelivery | undefined;
10
15
  export declare const ACTIVITY_METADATA_MAX_BYTES: number;
11
16
  export declare const ACTIVITY_METADATA_MAX_DEPTH = 256;
12
17
  /** Validate the bounded, lossless JSON object carried by a say. */
@@ -41,7 +46,7 @@ export interface WakeRoute {
41
46
  address: Record<string, string>;
42
47
  updatedAt: number;
43
48
  }
44
- export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | 'held' | 'capped' | 'throttled' | 'bell_quota' | 'behind' | 'io' | 'unavailable';
49
+ export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | 'capped' | 'throttled' | 'bell_quota' | 'behind' | 'io' | 'unavailable';
45
50
  export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reached' | 'conflict' | 'pending_peer';
46
51
  export interface SquareErrorFacts {
47
52
  /** Copyable recovery for a missing, detached or foreign seat. */
@@ -59,8 +64,6 @@ export interface SquareErrorFacts {
59
64
  /** Whether the artifact holds that activity at all; an unknown id needs an ordinary page. */
60
65
  activityExists?: boolean;
61
66
  pending?: number;
62
- holder?: string;
63
- holdReason?: string;
64
67
  retryAfterMs?: number;
65
68
  }
66
69
  declare class SquareErrorBase<Code extends string> extends Error {
@@ -100,13 +103,14 @@ export interface WatchLease {
100
103
  expiresAt: number;
101
104
  filter?: WatchLeaseFilter;
102
105
  }
103
- export type DirectedNotificationRoute = 'mention' | 'reply' | 'attention' | 'bell';
106
+ export type DirectedNotificationRoute = import('./attention.js').AudienceRoute;
104
107
  export interface InboxNotification {
105
108
  actIndex: number;
106
109
  actor: string;
107
110
  at: number;
108
111
  route: DirectedNotificationRoute;
109
112
  body: string;
113
+ readonly delivery?: ActivityDelivery;
110
114
  }
111
115
  export interface LocatedSeat {
112
116
  readonly location: string;
@@ -127,7 +131,7 @@ export type PublicAct = Extract<StoredAct, {
127
131
  kind: 'say' | 'done';
128
132
  }>;
129
133
  export type RoomChangeAct = Extract<StoredAct, {
130
- kind: 'join' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
134
+ kind: 'join' | 'done' | 'listen' | 'ignore' | 'unlisten';
131
135
  }>;
132
136
  export declare function formatHardCap(hardCap: HardCap): string;
133
137
  export declare function parseParticipantList(value: string): string[];
package/dist/model.js CHANGED
@@ -1,6 +1,25 @@
1
1
  // Shared model and constants for Square.
2
2
  export { formatActivityId, parseActivityId } from './square-core.js';
3
- export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo'];
3
+ export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo', 'http'];
4
+ export function validateActivityDelivery(value) {
5
+ if (value === undefined)
6
+ return undefined;
7
+ if (value !== null && typeof value === 'object'
8
+ && [Object.prototype, null].includes(Object.getPrototypeOf(value))
9
+ && Object.getOwnPropertySymbols(value).length === 0) {
10
+ const keys = Object.getOwnPropertyNames(value);
11
+ const mode = Object.getOwnPropertyDescriptor(value, 'mode');
12
+ const preview = Object.getOwnPropertyDescriptor(value, 'preview');
13
+ if (mode?.enumerable && 'value' in mode && mode.value === 'direct') {
14
+ if (keys.length === 1)
15
+ return { mode: 'direct' };
16
+ if (keys.length === 2 && preview?.enumerable && 'value' in preview
17
+ && typeof preview.value === 'string' && preview.value.trim() !== '')
18
+ return { mode: 'direct', preview: preview.value };
19
+ }
20
+ }
21
+ throw new SquareError('invalid_args', "Activity delivery must be { mode: 'direct', preview?: string }.");
22
+ }
4
23
  export const ACTIVITY_METADATA_MAX_BYTES = 16 * 1024;
5
24
  export const ACTIVITY_METADATA_MAX_DEPTH = 256;
6
25
  /** Validate the bounded, lossless JSON object carried by a say. */