@astrosheep/square 0.3.86 → 0.3.87

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 (141) 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 +3 -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 +471 -339
  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 +14 -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.js +29 -2
  85. package/dist/stdio-server.js +4 -2
  86. package/dist/stream.d.ts +3 -2
  87. package/dist/stream.js +9 -10
  88. package/dist/views.d.ts +9 -14
  89. package/dist/views.js +50 -70
  90. package/dist/wakes.d.ts +6 -5
  91. package/dist/wakes.js +20 -17
  92. package/guides/application-facade-delivery-evidence.md +1 -1
  93. package/guides/claude-native.md +9 -9
  94. package/guides/mcp.md +1 -1
  95. package/guides/migration.md +107 -67
  96. package/guides/row-storage-design.md +1 -1
  97. package/guides/square-api.md +61 -28
  98. package/package.json +1 -1
  99. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  100. package/plugins/claude/hooks/register.js +1 -1
  101. package/plugins/claude/skills/square/SKILL.md +1 -1
  102. package/plugins/claude/types.d.ts +1 -1
  103. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  104. package/plugins/opencode/delivery.ts +170 -118
  105. package/plugins/opencode/index.ts +12 -30
  106. package/plugins/pi/square-pi.js +55 -17
  107. package/skills/square/SKILL.md +1 -1
  108. package/dist/cli/ledger-command.d.ts +0 -12
  109. package/dist/cli/ledger-command.js +0 -72
  110. package/dist/host-ledger-file-adapter.d.ts +0 -47
  111. package/dist/host-ledger-file-adapter.js +0 -384
  112. package/dist/host-ledger-migration.d.ts +0 -27
  113. package/dist/host-ledger-migration.js +0 -264
  114. package/dist/host-ledger-root.d.ts +0 -2
  115. package/dist/host-ledger-root.js +0 -9
  116. package/dist/host-ledger-sqlite.d.ts +0 -65
  117. package/dist/host-ledger-sqlite.js +0 -368
  118. package/dist/host-ledger.d.ts +0 -195
  119. package/dist/host-ledger.js +0 -1
  120. package/dist/ledger-maintenance.d.ts +0 -30
  121. package/dist/ledger-maintenance.js +0 -110
  122. package/dist/open-square.d.ts +0 -38
  123. package/dist/open-square.js +0 -81
  124. package/dist/participant-host.d.ts +0 -23
  125. package/dist/participant-host.js +0 -92
  126. package/dist/registry.d.ts +0 -55
  127. package/dist/registry.js +0 -123
  128. package/dist/routes.d.ts +0 -39
  129. package/dist/routes.js +0 -99
  130. package/dist/square-actions.d.ts +0 -56
  131. package/dist/square-actions.js +0 -353
  132. package/dist/square-projections.d.ts +0 -31
  133. package/dist/square-projections.js +0 -64
  134. package/dist/wake-attempts.d.ts +0 -10
  135. package/dist/wake-attempts.js +0 -39
  136. package/dist/wake-eligibility.d.ts +0 -53
  137. package/dist/wake-eligibility.js +0 -123
  138. package/dist/wake-evidence.d.ts +0 -33
  139. package/dist/wake-evidence.js +0 -32
  140. package/guides/host-ledger.md +0 -58
  141. package/guides/indexed-host-ledger-performance.md +0 -39
@@ -2,6 +2,7 @@ import os from 'node:os';
2
2
  import { commandUsageHint } from '../help.js';
3
3
  import { parseParticipantList, validateName } from '../model.js';
4
4
  import { Square, squareClock } from '../square-api.js';
5
+ import { harnessSessionSources } from '../participant-identity.js';
5
6
  export async function readStdin() {
6
7
  try {
7
8
  let content = '';
@@ -139,7 +140,30 @@ export function defaultContext(command, squarePath, name) {
139
140
  return { command, cwd: process.cwd(), env: { ...process.env }, squarePath, name, homeDir: os.homedir() };
140
141
  }
141
142
  export function squareInput(context) {
142
- return { cwd: context.cwd, env: context.env, path: context.squarePath, participant: context.name };
143
+ const { body, wakeAddress } = commandIdentity(context.env);
144
+ return { cwd: context.cwd, env: context.env, path: context.squarePath, participant: context.name,
145
+ ...(body === undefined ? {} : { body }), ...(wakeAddress === undefined ? {} : { wakeAddress }) };
146
+ }
147
+ function commandIdentity(env) {
148
+ const coordinates = harnessSessionSources.flatMap(({ variable, channel, provider }) => {
149
+ const session = env[variable]?.trim();
150
+ return session === undefined || session === '' ? [] : [{ channel, session, native: provider !== undefined }];
151
+ });
152
+ const native = coordinates.filter((coordinate) => coordinate.native);
153
+ if (native.length > 1)
154
+ throw new Error('Multiple harness execution coordinates are present; select one explicit body.');
155
+ const paseo = coordinates.find((coordinate) => coordinate.channel === 'paseo');
156
+ // A child marker without a reliable agent id must never inherit the parent or Paseo identity.
157
+ if (env.CLAUDE_CODE_CHILD_SESSION === '1')
158
+ return {};
159
+ const selected = native[0] ?? paseo;
160
+ if (selected === undefined)
161
+ return {};
162
+ const body = { channel: selected.channel, session: selected.session };
163
+ const wakeAddress = selected.channel === 'codex'
164
+ ? { kind: 'codex-queue', address: { threadId: selected.session } }
165
+ : paseo === undefined ? undefined : { kind: 'paseo', address: { agentId: paseo.session } };
166
+ return { body, ...(wakeAddress === undefined ? {} : { wakeAddress }) };
143
167
  }
144
168
  /** Adapter lifetime only; each callback uses the actual public resource API. */
145
169
  export async function withSquare(context, run, control) {
@@ -3,9 +3,11 @@ import type { OperationControl, ExpressOptions } from '../square-facade.js';
3
3
  export { renderExpression } from '../operation-presentation.js';
4
4
  export interface ActivityOptions extends Omit<ExpressOptions, 'reply'> {
5
5
  readonly reply?: string;
6
+ readonly metadataJson?: string;
7
+ readonly bodyProvided?: boolean;
6
8
  }
7
9
  export declare function saveActivityDraft(squarePath: string, name: string, body: string, now?: number): string;
8
10
  /** Retry choices are transport syntax, never a second admission decision. */
9
11
  export declare function activityRetryCommand(squarePath: string, name: string, opts: ActivityOptions, force?: boolean): string;
10
12
  /** Body adaptation, terminal IO and drafts only; Participant owns every attempt and wait. */
11
- export declare function executeExpression(open: (control?: OperationControl) => Promise<Square>, squarePath: string, requestedName: string | undefined, rawInput: string, opts: ActivityOptions): Promise<void>;
13
+ export declare function executeExpression(open: (control?: OperationControl) => Promise<Square>, squarePath: string, requestedName: string | undefined, rawInput: string | undefined, opts: ActivityOptions): Promise<void>;
@@ -37,7 +37,9 @@ export function activityRetryCommand(squarePath, name, opts, force = opts.force
37
37
  const reach = (opts.reach === 'bell' ? ' --bell' : '')
38
38
  + (opts.mentions ?? []).map((target) => ` --mention ${quoteShell(target)}`).join('')
39
39
  + (opts.noMention === true ? ' --no-mention' : '');
40
- return `${participantCommandPrefix(squarePath, name)} express${force ? ' --force' : ''}${opts.noWait ? ' --no-wait' : ''}${reach}${replyArgument(opts.reply)} -`;
40
+ const metadata = opts.metadataJson === undefined ? '' : ` --metadata ${quoteShell(opts.metadataJson)}`;
41
+ const body = opts.bodyProvided === false ? '' : ' -';
42
+ return `${participantCommandPrefix(squarePath, name)} express${force ? ' --force' : ''}${opts.noWait ? ' --no-wait' : ''}${reach}${metadata}${replyArgument(opts.reply)}${body}`;
41
43
  }
42
44
  /** Body adaptation, terminal IO and drafts only; Participant owns every attempt and wait. */
43
45
  export async function executeExpression(open, squarePath, requestedName, rawInput, opts) {
@@ -54,8 +56,9 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
54
56
  const participant = await square.participant(requestedName);
55
57
  name = participant.name;
56
58
  attempted = true;
57
- const outcome = await participant.express(rawInput.replace(/\r\n/g, '\n').trim(), {
58
- ...opts, reply: opts.reply, observe: true,
59
+ const { metadataJson: _metadataJson, bodyProvided: _bodyProvided, ...expressOptions } = opts;
60
+ const outcome = await participant.express(rawInput === undefined ? undefined : rawInput.replace(/\r\n/g, '\n').trim(), {
61
+ ...expressOptions, reply: opts.reply, observe: true,
59
62
  }, {
60
63
  signal: controller.signal,
61
64
  onProgress(progress) {
@@ -75,7 +78,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
75
78
  const detail = error?.operation;
76
79
  name = detail?.participant ?? name ?? 'participant';
77
80
  // This MUST precede recovery: even an unreadable artifact cannot eat the raw body.
78
- const draftPath = saveActivityDraft(squarePath, name, rawInput);
81
+ const draftPath = saveActivityDraft(squarePath, name, rawInput ?? '');
79
82
  if (square === undefined)
80
83
  square = await open().catch(() => undefined);
81
84
  const fresh = await square?.recovery(name).catch(() => undefined);
@@ -85,6 +88,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
85
88
  && detail?.expressEffect !== 'landed' && detail?.expressEffect !== 'unknown'
86
89
  && (!attempted || detail?.expressEffect === 'unsent' || (isSquareError(error) && error.facts?.activityUnsent === true));
87
90
  const retryCommand = activityRetryCommand(squarePath, name, opts);
91
+ const retryInput = opts.bodyProvided === false ? retryCommand : `${retryCommand} < ${quoteShell(draftPath)}`;
88
92
  const exitCode = (code) => { process.exitCode = controller.signal.aborted ? 130 : code; };
89
93
  if (!confirmedUnsent) {
90
94
  process.stderr.write(renderActivityUncertain({ ...output, landedId: landedId ?? detail?.landedId,
@@ -94,7 +98,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
94
98
  }
95
99
  if (isSquareError(error)) {
96
100
  if (error.code === 'behind' && fresh?.activitySummaries !== undefined) {
97
- process.stdout.write(renderActivityBlocked({ ...output, retryCommand,
101
+ process.stdout.write(renderActivityBlocked({ ...output, retryCommand: retryInput,
98
102
  activitySummaries: fresh.activitySummaries.map((summary) => ({ ...summary, previews: [...summary.previews] })),
99
103
  }));
100
104
  exitCode(1);
@@ -108,7 +112,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
108
112
  return;
109
113
  }
110
114
  if (error.code === 'held' || error.code === 'throttled') {
111
- process.stdout.write(renderExpressNoWait({ ...output, reason: error.code, retryCommand,
115
+ process.stdout.write(renderExpressNoWait({ ...output, reason: error.code, retryCommand: retryInput,
112
116
  delayMs: error.facts?.retryAfterMs ?? 1, holder: error.facts?.holder, holdReason: error.facts?.holdReason,
113
117
  }));
114
118
  exitCode(1);
@@ -117,7 +121,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
117
121
  if (error.code === 'bell_quota') {
118
122
  process.stdout.write(withPathOutput(squarePath, [
119
123
  '✕ the bell stays quiet for now', ` · you can ring it again in ${formatDuration(error.facts?.retryAfterMs ?? 1)}`,
120
- `· draft kept: ${draftPath}`, ' · retry when the bell is ready:', `${retryCommand} < ${quoteShell(draftPath)}`,
124
+ `· draft kept: ${draftPath}`, ' · retry when the bell is ready:', retryInput,
121
125
  ].join('\n'), output));
122
126
  exitCode(1);
123
127
  return;
@@ -143,7 +147,7 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
143
147
  if (repair) {
144
148
  lines.push(participantsRecoveryCommand(squarePath), reason === 'bell_mention_conflict'
145
149
  ? ' · ring the bell without explicit mentions:'
146
- : ' · retry without explicit recipients:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })} < ${quoteShell(draftPath)}`);
150
+ : ' · retry without explicit recipients:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })}${opts.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
147
151
  }
148
152
  else if (error.code === 'invalid_args' || error.code === 'invalid_name') {
149
153
  if (opts.mentions?.length)
@@ -154,10 +158,10 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
154
158
  }
155
159
  if (reason === 'invalid_reply') {
156
160
  const withoutReply = { ...opts, reply: undefined };
157
- lines.push(`${commandPrefix(squarePath)} history --limit 10`, ' · choose a valid --reply from history, or send without a reply using this repair:', `${activityRetryCommand(squarePath, name, withoutReply)} < ${quoteShell(draftPath)}`);
161
+ lines.push(`${commandPrefix(squarePath)} history --limit 10`, ' · choose a valid --reply from history, or send without a reply using this repair:', `${activityRetryCommand(squarePath, name, withoutReply)}${withoutReply.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
158
162
  }
159
163
  else if (!repair) {
160
- lines.push(' · correct the problem above before retrying; keep your intended recipients and reply:', `${retryCommand} < ${quoteShell(draftPath)}`);
164
+ lines.push(' · correct the problem above before retrying; keep your intended recipients and reply:', `${retryInput}`);
161
165
  }
162
166
  process.stderr.write(formatRefusal(squarePath, lines, output));
163
167
  exitCode(2);
@@ -1,4 +1,4 @@
1
- import { fail } from './context.js';
1
+ import { fail, squareInput } from './context.js';
2
2
  export const mcpServerCommand = {
3
3
  parse(argv) {
4
4
  if (argv.length > 0)
@@ -7,7 +7,7 @@ export const mcpServerCommand = {
7
7
  },
8
8
  async execute(_intent, context) {
9
9
  const { runSquareMcpStdio } = await import('../mcp.js');
10
- await runSquareMcpStdio({ cwd: context.cwd, env: context.env, path: context.squarePath, participant: context.name });
10
+ await runSquareMcpStdio(squareInput(context));
11
11
  },
12
12
  present() { },
13
13
  };
@@ -408,6 +408,7 @@ function jsonLine(sayNumbers, item) {
408
408
  body: 'body' in act && typeof act.body === 'string' ? act.body : '',
409
409
  number: act.kind === 'say' ? sayNumbers[act.index] : null,
410
410
  mentions: act.kind === 'say' ? [...(act.mentions ?? [])] : [],
411
+ metadata: act.kind === 'say' ? act.metadata ?? null : null,
411
412
  reach: act.kind === 'say' ? act.reach ?? null : null,
412
413
  reply: act.kind === 'say' && act.reply !== undefined ? formatActivityId(act.reply) : null,
413
414
  });
@@ -1,6 +1,5 @@
1
1
  import { buildCommand, doneCommand, expressCommand, holdCommand, ignoreCommand, joinCommand, listenCommand, listeningCommand, resumeCommand, unlistenCommand } from './square-commands.js';
2
2
  import { doctorCommand, migrateCommand } from './maintenance-commands.js';
3
- import { ledgerCommand } from './ledger-command.js';
4
3
  import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
5
4
  import { helpCommand, versionCommand } from './meta-commands.js';
6
5
  import { mcpServerCommand } from './mcp-server-command.js';
@@ -28,7 +27,6 @@ export const commandRegistry = [
28
27
  { names: ['harness'], spec: harnessCommand },
29
28
  { names: ['doctor'], spec: doctorCommand },
30
29
  { names: ['migrate'], spec: migrateCommand },
31
- { names: ['ledger'], spec: ledgerCommand },
32
30
  { names: ['history'], spec: historyCommand },
33
31
  { names: ['status'], spec: statusCommand },
34
32
  { names: ['participants'], spec: participantsCommand },
@@ -1,5 +1,5 @@
1
1
  import { type BuildOptions, type HardCap } from '../model.js';
2
- import type { Reach } from '../square-facade.js';
2
+ import type { Reach, ActivityMetadata } from '../square-facade.js';
3
3
  import { type CommandSpec } from './context.js';
4
4
  interface BuildIntent {
5
5
  options: BuildOptions & {
@@ -11,13 +11,15 @@ interface JoinIntent {
11
11
  kick: boolean;
12
12
  }
13
13
  interface ActivityIntent {
14
- activity: string;
14
+ activity?: string;
15
15
  force: boolean;
16
16
  noWait: boolean;
17
17
  noMention: boolean;
18
18
  mentions: string[];
19
19
  reach?: Reach;
20
20
  reply?: string;
21
+ metadata?: ActivityMetadata;
22
+ metadataJson?: string;
21
23
  }
22
24
  interface BodyIntent {
23
25
  body?: string;
@@ -87,6 +87,8 @@ function parseActivity(argv, context) {
87
87
  let noMention = false;
88
88
  const mentions = [];
89
89
  let reply;
90
+ let metadata;
91
+ let metadataJson;
90
92
  const bodyArgs = [];
91
93
  for (let index = 0; index < argv.length; index++) {
92
94
  const argument = argv[index];
@@ -108,25 +110,38 @@ function parseActivity(argv, context) {
108
110
  reply = requireValue(argv, index, argument);
109
111
  index += 1;
110
112
  }
113
+ else if (argument === '--metadata') {
114
+ metadataJson = requireValue(argv, index, argument);
115
+ try {
116
+ metadata = JSON.parse(metadataJson);
117
+ }
118
+ catch {
119
+ fail('✕ invalid --metadata: expected JSON');
120
+ }
121
+ index += 1;
122
+ }
111
123
  else
112
124
  bodyArgs.push(argument);
113
125
  }
114
126
  const reach = bell ? 'bell' : undefined;
115
- if (bodyArgs.length !== 1) {
116
- if (bodyArgs.length === 0 && !process.stdin.isTTY)
127
+ if (bodyArgs.length > 1)
128
+ fail("express accepts one body argument (a quoted string or '-')");
129
+ if (bodyArgs.length === 0 && metadataJson === undefined) {
130
+ if (!process.stdin.isTTY)
117
131
  bodyArgs.push('-');
118
132
  else
119
- fail("express requires a body argument (a quoted string or '-' with piped stdin)");
133
+ fail("express requires a body argument or --metadata <json>");
120
134
  }
121
- return { activity: bodyArgs[0], force, noWait, noMention, mentions, reach, reply };
135
+ return { activity: bodyArgs[0], force, noWait, noMention, mentions, reach, reply, metadata, metadataJson };
122
136
  }
123
137
  export const expressCommand = {
124
138
  parse: parseActivity,
125
139
  async execute(intent, context) {
126
- const rawInput = await resolveBody(intent.activity);
140
+ const rawInput = intent.activity === undefined ? undefined : await resolveBody(intent.activity);
127
141
  await executeExpression((control) => Square.at(squareInput(context), control), requireSquarePath(context), context.name, rawInput, {
128
142
  force: intent.force, noWait: intent.noWait, noMention: intent.noMention,
129
- mentions: intent.mentions, reach: intent.reach, reply: intent.reply,
143
+ mentions: intent.mentions, reach: intent.reach, reply: intent.reply, metadata: intent.metadata,
144
+ metadataJson: intent.metadataJson, bodyProvided: intent.activity !== undefined,
130
145
  });
131
146
  },
132
147
  present: () => { },
@@ -1,5 +1,5 @@
1
- import type { InboxMembership } from './model.js';
2
- import type { WakeAdapter } from './delivery.js';
1
+ import type { Body, PendingMembership } from './model.js';
2
+ import type { WakeTransports } from './notifications.js';
3
3
  export interface CodexHookInput {
4
4
  session_id?: unknown;
5
5
  hook_event_name?: unknown;
@@ -10,6 +10,6 @@ export type HookTrace = (line: string) => void;
10
10
  /** Overall hook budget: Codex kills the hook at its configured 5s timeout, so the hook must
11
11
  * bound every wait/retry itself and exit with a readable stage trace before that. */
12
12
  export declare const CODEX_HOOK_BUDGET_MS = 4500;
13
- export declare function codexHookResponse(input: CodexHookInput, lookup?: (sessionId: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv, deliveryAdapters?: WakeAdapter[], trace?: HookTrace): Promise<object | undefined>;
13
+ export declare function codexHookResponse(input: CodexHookInput, lookup?: (identity: Body, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<PendingMembership[]> | PendingMembership[], env?: NodeJS.ProcessEnv, deliveryTransports?: WakeTransports, trace?: HookTrace): Promise<object | undefined>;
14
14
  export declare function runCodexHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
15
15
  export declare function runCodexHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
@@ -15,7 +15,7 @@ function hookBudgetMs(env) {
15
15
  const configured = Number(env.SQUARE_CODEX_HOOK_BUDGET_MS ?? CODEX_HOOK_BUDGET_MS);
16
16
  return Number.isFinite(configured) && configured > 0 ? configured : CODEX_HOOK_BUDGET_MS;
17
17
  }
18
- export async function codexHookResponse(input, lookup = sessionInbox, env = process.env, deliveryAdapters, trace = defaultTrace) {
18
+ export async function codexHookResponse(input, lookup = sessionInbox, env = process.env, deliveryTransports, trace = defaultTrace) {
19
19
  if (typeof input.session_id !== 'string' || input.session_id === '')
20
20
  return undefined;
21
21
  if (typeof input.hook_event_name !== 'string')
@@ -38,20 +38,20 @@ export async function codexHookResponse(input, lookup = sessionInbox, env = proc
38
38
  await recordCodexBoundary(input.session_id, hookEventName === 'Stop' ? 'Stop' : 'non-stop', env, signal);
39
39
  stage('boundary-record');
40
40
  if (hookEventName === 'Stop') {
41
- await sweepPrivilegedPending(cwd, env, deliveryAdapters, sweepDeadline, signal).catch(() => undefined);
41
+ await sweepPrivilegedPending(cwd, env, deliveryTransports, sweepDeadline, signal).catch(() => undefined);
42
42
  stage('sweep', signal.aborted ? 'aborted=true' : '');
43
43
  return undefined;
44
44
  }
45
45
  let response;
46
46
  try {
47
- response = await presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }), lookup, env, signal);
47
+ response = await presentPendingAtBoundary({ channel: 'codex', session: input.session_id }, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }), lookup, env, signal);
48
48
  stage('presentation', signal.aborted ? 'aborted=true' : '');
49
49
  }
50
50
  catch (error) {
51
51
  stage('presentation', `error=${error instanceof Error ? error.name : String(error)}`);
52
52
  response = undefined;
53
53
  }
54
- await sweepPrivilegedPending(cwd, env, deliveryAdapters, sweepDeadline, signal).catch(() => undefined);
54
+ await sweepPrivilegedPending(cwd, env, deliveryTransports, sweepDeadline, signal).catch(() => undefined);
55
55
  stage('sweep', signal.aborted ? 'aborted=true' : '');
56
56
  return response;
57
57
  }
@@ -1,4 +1,4 @@
1
- import type { WakeAdapter, WakeAdapterResult } from './delivery.js';
1
+ import type { WakeRequest, WakeOutcome, WakeTransportPort } from './ports.js';
2
2
  import { type WakeRoute } from './model.js';
3
3
  export interface CodexQueueRequest {
4
4
  threadId: string;
@@ -15,14 +15,13 @@ export declare function sendCodexQueue({ threadId, message }: CodexQueueRequest,
15
15
  env?: NodeJS.ProcessEnv;
16
16
  timeoutMs?: number;
17
17
  }): void;
18
- export interface CodexQueueAdapterOptions {
18
+ export interface CodexQueueTransportOptions {
19
19
  env?: NodeJS.ProcessEnv;
20
20
  sendQueue?: typeof sendCodexQueue;
21
21
  }
22
- export declare class CodexQueueAdapter implements WakeAdapter {
22
+ export declare class CodexQueueTransport implements WakeTransportPort {
23
23
  private readonly opts;
24
- readonly kind: "codex-queue";
25
- constructor(opts?: CodexQueueAdapterOptions);
26
- dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>, timeoutMs?: number): Promise<WakeAdapterResult>;
24
+ constructor(opts?: CodexQueueTransportOptions);
25
+ attempt(request: WakeRequest, timeoutMs: number, beforeSend: () => Promise<boolean>): Promise<WakeOutcome>;
27
26
  }
28
27
  export type { WakeRoute };
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { codexQueueEligible } from './codex-boundary-state.js';
3
+ import { wakePreview } from './delivery.js';
3
4
  export class CodexQueueSendError extends Error {
4
5
  kind;
5
6
  constructor(message, kind) {
@@ -30,16 +31,15 @@ export function sendCodexQueue({ threadId, message }, opts = {}) {
30
31
  const messageText = output || `codex queue exited with ${result.status ?? 'no status'}`;
31
32
  throw new CodexQueueSendError(messageText, classifyFailure(messageText));
32
33
  }
33
- export class CodexQueueAdapter {
34
+ export class CodexQueueTransport {
34
35
  opts;
35
- kind = 'codex-queue';
36
36
  constructor(opts = {}) {
37
37
  this.opts = opts;
38
38
  }
39
- async dispatch(address, payload, beforeSend, timeoutMs = 5000) {
39
+ async attempt(request, timeoutMs, beforeSend) {
40
40
  const deadline = Date.now() + timeoutMs;
41
41
  const remainingMs = () => Math.max(0, deadline - Date.now());
42
- const threadId = address.threadId?.trim();
42
+ const threadId = request.route.address.threadId?.trim();
43
43
  if (!threadId) {
44
44
  return { outcome: 'failed', unavailable: true, signature: 'invalid_address', message: 'Codex route has no thread id.', routeStale: true };
45
45
  }
@@ -53,8 +53,12 @@ export class CodexQueueAdapter {
53
53
  retainRoute: true,
54
54
  };
55
55
  }
56
+ const payload = request.artifact === undefined ? undefined
57
+ : (await request.artifact.read((facts) => wakePreview(facts, request))).value;
58
+ if (payload === undefined)
59
+ return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
56
60
  if (!(await beforeSend()))
57
- return { outcome: 'gate-rejected' };
61
+ return { outcome: 'not-capable' };
58
62
  if (!await codexQueueEligible(threadId, env)) {
59
63
  return {
60
64
  outcome: 'failed',
@@ -1,4 +1,4 @@
1
- import { type Act, type Reach } from './model.js';
1
+ import { type Act, type Reach, type ActivityMetadata } from './model.js';
2
2
  import type { SquareRead } from './ports.js';
3
3
  type TimedAct<K extends Act['kind'] = Act['kind']> = Extract<Act, {
4
4
  kind: K;
@@ -44,9 +44,10 @@ export type ActDecision = ({
44
44
  export declare function unreadActivitySummaries(facts: SquareRead, name: string, now: number): UnreadActivitySummary[];
45
45
  export declare function decideAct(facts: SquareRead, input: {
46
46
  name: string;
47
- body: string;
47
+ body?: string;
48
48
  force: boolean;
49
49
  now: number;
50
+ metadata?: ActivityMetadata;
50
51
  mentions?: readonly string[];
51
52
  reach?: Reach;
52
53
  reply?: number;
package/dist/decisions.js CHANGED
@@ -1,4 +1,4 @@
1
- import { SquareError, isSquareError, sameName, nameKey, validateName, } from './model.js';
1
+ import { SquareError, isSquareError, sameName, nameKey, validateName, validateActivityMetadata, activityMetadataHasContent, } from './model.js';
2
2
  import { participantIdentity } from './participant-identity.js';
3
3
  import { UNREAD_BLOCK_GRACE_MS, freshWatchLease, readCursor, FACT_PAGE_SIZE, THROTTLE_WINDOW_MS } from './runtime.js';
4
4
  import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
@@ -85,11 +85,23 @@ export function unreadActivitySummaries(facts, name, now) {
85
85
  })).sort((a, b) => a.latestActivityAgeMs - b.latestActivityAgeMs || a.name.localeCompare(b.name));
86
86
  }
87
87
  export function decideAct(facts, input) {
88
- const { now, force, body, reach, reply } = input;
88
+ const { now, force, reach, reply } = input;
89
+ let metadata;
90
+ try {
91
+ metadata = validateActivityMetadata(input.metadata);
92
+ }
93
+ catch (error) {
94
+ if (isSquareError(error))
95
+ error.facts = { ...error.facts, activityUnsent: true };
96
+ throw error;
97
+ }
98
+ if (input.body !== undefined && typeof input.body !== 'string')
99
+ throw activityRefusal('invalid_args', 'express body must be a string');
100
+ const body = input.body?.trim() === '' ? undefined : input.body;
101
+ if (body === undefined && !activityMetadataHasContent(metadata))
102
+ throw activityRefusal('invalid_args', 'express requires a non-empty body or metadata');
89
103
  const current = resolveStanding(facts, input.name, true);
90
104
  const name = current.name;
91
- if (body.trim() === '')
92
- throw activityRefusal('invalid_args', 'express body cannot be empty');
93
105
  const roster = facts.roster();
94
106
  const joinedNames = roster.filter((participant) => participant.joined).map((participant) => participant.name);
95
107
  if (reach === 'bell' && input.mentions?.length)
@@ -109,7 +121,7 @@ export function decideAct(facts, input) {
109
121
  throw activityRefusal('invalid_args', `${participantIdentity(requested)} is not standing in this square`, { reason: 'not_standing', targets: [requested] });
110
122
  addMention(resolved);
111
123
  }
112
- const bodyMentions = reach === 'bell' || input.noMention === true ? [] : scanMentionCandidates(body);
124
+ const bodyMentions = reach === 'bell' || input.noMention === true ? [] : scanMentionCandidates(body ?? '');
113
125
  const unmatchedMentions = [];
114
126
  for (const token of bodyMentions) {
115
127
  const resolved = resolveMention(token, joinedNames);
@@ -150,7 +162,7 @@ export function decideAct(facts, input) {
150
162
  if (activitySummaries.length > 0 && activitySummaries[0].latestActivityAgeMs > UNREAD_BLOCK_GRACE_MS)
151
163
  return { type: 'blocked', activitySummaries };
152
164
  }
153
- return { type: 'sent', act: { kind: 'say', actor: name, at: now, body, mentions: mentionNames,
165
+ return { type: 'sent', act: { kind: 'say', actor: name, at: now, ...(body === undefined ? {} : { body }), ...(metadata === undefined ? {} : { metadata }), mentions: mentionNames,
154
166
  ...(reach === undefined ? {} : { reach }), ...(reply === undefined ? {} : { reply }) },
155
167
  ...(unmatchedMentions.length === 0 ? {} : { unmatchedMentions }),
156
168
  ...(replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere }) };
@@ -1,5 +1,35 @@
1
1
  import { type DirectedNotificationRoute } from './delivery.js';
2
- import { type WakeAttempt, type WakeReleaseDiagnostic } from './wake-evidence.js';
2
+ import type { DeliveryEvidence } from './ports.js';
3
+ export interface WakeAttempt {
4
+ readonly nativeObservations?: readonly NonNullable<DeliveryEvidence['nativeObservations']>[number][];
5
+ readonly at: number;
6
+ readonly attention: {
7
+ readonly squarePath: string;
8
+ readonly recipient: string;
9
+ readonly actIndex: number;
10
+ };
11
+ readonly routeKind: import('./model.js').WakeRouteKind;
12
+ readonly outcome: 'accepted' | 'unknown' | 'failed';
13
+ readonly signature?: string;
14
+ readonly attemptN: number;
15
+ readonly session?: string;
16
+ readonly message?: string;
17
+ readonly diagnostic?: unknown;
18
+ }
19
+ export interface WakeReleaseDiagnostic {
20
+ readonly at: number;
21
+ readonly attention: {
22
+ readonly squarePath: string;
23
+ readonly recipient: string;
24
+ readonly actIndex: number;
25
+ };
26
+ readonly routeKind?: import('./model.js').WakeRouteKind;
27
+ readonly attemptN?: number;
28
+ readonly session?: string;
29
+ readonly signature?: string;
30
+ readonly message?: string;
31
+ readonly diagnostic?: unknown;
32
+ }
3
33
  export type DeliveryHealthKind = 'awaiting' | 'wake-accepted' | 'wake-unknown' | 'presented-not-delivered' | 'unreachable';
4
34
  export interface DeliveryHealthItem {
5
35
  squarePath: string;
@@ -12,7 +42,7 @@ export interface DeliveryHealthItem {
12
42
  kind: DeliveryHealthKind;
13
43
  attempt?: WakeAttempt;
14
44
  }
15
- /** Purely classify current pending attention from the artifact and durable ledgers. */
45
+ /** Classify current pending attention from the artifact's seats and delivery evidence. */
16
46
  export declare function classifyDeliveryHealth(squarePath: string, opts: {
17
47
  graceMs: number;
18
48
  now?: number;