@astrosheep/square 0.3.92 → 0.3.94

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 (119) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +2 -2
  4. package/dist/boundary-presentation.d.ts +2 -2
  5. package/dist/catch-decisions.d.ts +4 -6
  6. package/dist/catch-decisions.js +4 -4
  7. package/dist/claude-delivery-status.js +10 -4
  8. package/dist/claude-delivery.js +19 -11
  9. package/dist/claude-mod.js +24 -53
  10. package/dist/cli/context.d.ts +9 -4
  11. package/dist/cli/context.js +30 -25
  12. package/dist/cli/express-presentation.d.ts +3 -1
  13. package/dist/cli/express-presentation.js +26 -14
  14. package/dist/cli/json.d.ts +3 -0
  15. package/dist/cli/json.js +14 -0
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.d.ts +1 -3
  18. package/dist/cli/observation-commands.js +43 -56
  19. package/dist/cli/program.js +2 -2
  20. package/dist/cli/registry.js +6 -3
  21. package/dist/cli/square-commands.d.ts +1 -1
  22. package/dist/cli/square-commands.js +46 -54
  23. package/dist/codex-hook.d.ts +3 -2
  24. package/dist/codex-hook.js +13 -4
  25. package/dist/codex-queue.js +4 -4
  26. package/dist/decisions.d.ts +5 -11
  27. package/dist/decisions.js +9 -20
  28. package/dist/delivery-deferral.js +2 -2
  29. package/dist/delivery-health.d.ts +0 -1
  30. package/dist/delivery-health.js +1 -3
  31. package/dist/delivery-operations.d.ts +2 -2
  32. package/dist/delivery-operations.js +38 -38
  33. package/dist/delivery.d.ts +1 -1
  34. package/dist/delivery.js +1 -1
  35. package/dist/harness-links.d.ts +1 -10
  36. package/dist/harness-links.js +144 -109
  37. package/dist/harness.js +4 -13
  38. package/dist/help.js +12 -10
  39. package/dist/inbox.d.ts +9 -22
  40. package/dist/inbox.js +23 -27
  41. package/dist/index.d.ts +1 -3
  42. package/dist/index.js +1 -1
  43. package/dist/mcp.d.ts +3 -0
  44. package/dist/mcp.js +23 -24
  45. package/dist/model.d.ts +8 -15
  46. package/dist/model.js +4 -7
  47. package/dist/notifications.d.ts +1 -1
  48. package/dist/notifications.js +6 -5
  49. package/dist/operation-presentation.d.ts +5 -4
  50. package/dist/operation-presentation.js +50 -45
  51. package/dist/owner.d.ts +11 -0
  52. package/dist/owner.js +32 -0
  53. package/dist/paseo-delivery.d.ts +4 -1
  54. package/dist/paseo-delivery.js +7 -12
  55. package/dist/paseo-state.d.ts +2 -2
  56. package/dist/paseo-state.js +6 -4
  57. package/dist/plugins/opencode/delivery.js +27 -15
  58. package/dist/ports.d.ts +13 -33
  59. package/dist/presentation-operations.js +4 -4
  60. package/dist/presentation.d.ts +5 -8
  61. package/dist/presentation.js +33 -43
  62. package/dist/runtime.d.ts +1 -7
  63. package/dist/runtime.js +0 -14
  64. package/dist/seats.d.ts +2 -3
  65. package/dist/seats.js +7 -15
  66. package/dist/session-lifecycle.d.ts +6 -2
  67. package/dist/session-lifecycle.js +39 -11
  68. package/dist/sqlite-square.d.ts +36 -22
  69. package/dist/sqlite-square.js +138 -161
  70. package/dist/square-api.d.ts +15 -10
  71. package/dist/square-api.js +26 -22
  72. package/dist/square-core.d.ts +0 -5
  73. package/dist/square-core.js +0 -9
  74. package/dist/square-domain.d.ts +4 -2
  75. package/dist/square-domain.js +56 -21
  76. package/dist/square-facade.d.ts +45 -87
  77. package/dist/square-index.d.ts +5 -13
  78. package/dist/square-index.js +13 -38
  79. package/dist/square-location.d.ts +1 -14
  80. package/dist/square-location.js +4 -69
  81. package/dist/square-migration-host-ledger.d.ts +2 -2
  82. package/dist/square-migration-host-ledger.js +6 -5
  83. package/dist/square-migration.d.ts +2 -1
  84. package/dist/square-migration.js +52 -30
  85. package/dist/square-sqlite.d.ts +6 -2
  86. package/dist/square-sqlite.js +54 -28
  87. package/dist/square-tools.d.ts +7 -10
  88. package/dist/square-tools.js +22 -54
  89. package/dist/stream.js +4 -5
  90. package/dist/views.d.ts +21 -10
  91. package/dist/views.js +38 -38
  92. package/dist/wakes.d.ts +1 -2
  93. package/dist/wakes.js +1 -1
  94. package/guides/application-facade-delivery-evidence.md +1 -1
  95. package/guides/claude-native.md +18 -9
  96. package/guides/codex-delivery.md +17 -6
  97. package/guides/mcp-connection-lifetime.md +1 -1
  98. package/guides/mcp.md +3 -3
  99. package/guides/migration.md +142 -151
  100. package/guides/operation-cancellation.md +1 -1
  101. package/guides/participant-ownership.md +78 -47
  102. package/guides/pi-tools.md +33 -4
  103. package/guides/primitive-history-consolidation.md +1 -1
  104. package/guides/row-storage-design.md +8 -8
  105. package/guides/square-api.md +244 -88
  106. package/package.json +2 -1
  107. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  108. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  109. package/plugins/opencode/delivery.ts +27 -17
  110. package/plugins/paseo/client/palette.ts +18 -18
  111. package/plugins/paseo/client/square-message.tsx +17 -23
  112. package/plugins/pi/square-pi.js +17 -17
  113. package/dist/square-backend.d.ts +0 -13
  114. package/dist/square-backend.js +0 -1
  115. package/dist/square-stdio.d.ts +0 -22
  116. package/dist/square-stdio.js +0 -235
  117. package/dist/stdio-server.d.ts +0 -3
  118. package/dist/stdio-server.js +0 -212
  119. package/guides/backends.md +0 -193
@@ -1,6 +1,4 @@
1
1
  import type { SquareRead } from './ports.js';
2
- import type { StoredAct } from './model.js';
3
- /** Directed suffix includes seen rows beyond an earlier hole, unlike notification pending. */
4
- export declare function directedSuffix(facts: SquareRead, name: string): Extract<StoredAct, {
5
- kind: 'say';
6
- }>[];
2
+ import type { ExpressContext } from './square-facade.js';
3
+ /** A bounded, nonconsuming glimpse; seen rows beyond a cursor hole must not reappear. */
4
+ export declare function expressionContext(facts: SquareRead, name: string): ExpressContext;
@@ -1,15 +1,17 @@
1
- import { readCursor } from './runtime.js';
2
- /** Directed suffix includes seen rows beyond an earlier hole, unlike notification pending. */
3
- export function directedSuffix(facts, name) {
4
- const result = [];
5
- let afterIndex = readCursor(facts, name);
6
- while (true) {
7
- const page = facts.directed(name, { afterIndex }, { edge: 'oldest', limit: 100 });
8
- for (const act of facts.activities(page.items.map((head) => head.index)))
9
- if (act.kind === 'say')
10
- result.push(act);
11
- if (!page.hasMore)
12
- return result;
13
- afterIndex = page.items.at(-1).index;
14
- }
1
+ import { formatActivityId } from './square-core.js';
2
+ const CONTEXT_LIMIT = 5;
3
+ const PREVIEW_CHARS = 400;
4
+ /** A bounded, nonconsuming glimpse; seen rows beyond a cursor hole must not reappear. */
5
+ export function expressionContext(facts, name) {
6
+ const filter = { afterIndex: facts.participant(name)?.lastJoinIndex ?? -1, unseenOnly: true };
7
+ const page = facts.directed(name, filter, { edge: 'newest', limit: CONTEXT_LIMIT });
8
+ const activities = facts.activities(page.items.map(head => head.index)).filter(act => act.kind === 'say')
9
+ .sort((a, b) => a.index - b.index).map(act => {
10
+ const body = act.body ?? '(structured activity)';
11
+ // Keep the boundary between Unicode code points, including non-BMP characters.
12
+ const chars = Array.from(body.slice(0, PREVIEW_CHARS * 2));
13
+ const preview = chars.slice(0, PREVIEW_CHARS).join('');
14
+ return { id: formatActivityId(act.index), actor: act.actor, preview, truncated: preview.length < body.length };
15
+ });
16
+ return { activities, remaining: Math.max(0, facts.directedCount(name, filter) - activities.length) };
15
17
  }
package/dist/artifact.js CHANGED
@@ -120,10 +120,10 @@ const readMethods = [
120
120
  'header', 'participant', 'roster', 'attention', 'incomingAttention', 'hold',
121
121
  'activity', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
122
122
  'audience', 'directed', 'directedAt', 'directedCount', 'pendingAudience',
123
- 'receipt', 'receiptFacts', 'receiptAssignments', 'lease', 'participantCounts', 'seat', 'seatsForBody', 'completedSeatsForBody',
123
+ 'receipt', 'receiptFacts', 'receiptAssignments', 'lease', 'participantCounts', 'seat', 'seatsForOwner',
124
124
  'deliveryEvidence', 'deliveryByToken', 'cancelledThrough', 'deliveryDeferrals',
125
125
  ];
126
- const writeMethods = ['applyLanding', 'markSeen', 'putLease', 'removeLease', 'storeSeat', 'removeSeat', 'updateWakeAddress', 'cancelThrough', 'claimDelivery', 'prepareDelivery', 'completeDelivery', 'observeNativeDelivery', 'putDeliveryDeferral', 'renewDeliveryDeferral', 'releaseDeliveryDeferral'];
126
+ const writeMethods = ['applyLanding', 'markSeen', 'putLease', 'removeLease', 'storeSeat', 'removeSeat', 'updateWakeAddress', 'reattachSeat', 'detachSeat', 'cancelThrough', 'claimDelivery', 'prepareDelivery', 'completeDelivery', 'observeNativeDelivery', 'putDeliveryDeferral', 'renewDeliveryDeferral', 'releaseDeliveryDeferral'];
127
127
  /** One connection, ordered operations, guarded synchronous transaction capabilities. */
128
128
  export function createArtifactPort(location, initial, externalSignal) {
129
129
  let database;
@@ -1,5 +1,5 @@
1
1
  import { pendingAtBoundary } from './inbox.js';
2
- import type { Body, PendingMembership } from './model.js';
2
+ import type { Owner, PendingMembership } from './model.js';
3
3
  export { pendingAtBoundary };
4
4
  export declare function renderPendingAtBoundary(inbox: PendingMembership[]): string;
5
5
  /**
@@ -11,4 +11,4 @@ export declare function renderPendingAtBoundary(inbox: PendingMembership[]): str
11
11
  * after success, captured-seat commits record only its included items without an eligibility veto.
12
12
  * A source that cannot verify its reuse must read the inbox again.
13
13
  */
14
- export declare function presentPendingAtBoundary<T>(identity: Body, present: (context: string) => T | Promise<T>, lookup?: (identity: Body, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<PendingMembership[]> | PendingMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
14
+ export declare function presentPendingAtBoundary<T>(identity: Owner, present: (context: string) => T | Promise<T>, lookup?: (identity: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<PendingMembership[]> | PendingMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
@@ -1,19 +1,17 @@
1
- import { type ActivityId } from './square-core.js';
2
1
  import { type StoredAct } from './model.js';
3
2
  import type { ActivityIndex, DirectedFilter, SquareRead, SquareWrite } from './ports.js';
4
- import type { CatchOptions } from './square-facade.js';
3
+ import type { CatchOptions, CatchReason, PerceivedActivity } from './square-facade.js';
5
4
  export interface CatchSelection {
6
5
  readonly participant: string;
7
- readonly activities: readonly StoredAct[];
8
- readonly consumedThrough: ActivityId | null;
6
+ readonly activities: readonly PerceivedActivity[];
9
7
  readonly remaining: number;
10
- readonly sayNumbers: Readonly<Record<number, number>>;
11
8
  readonly standingCount: number;
12
9
  readonly held: boolean;
10
+ readonly deliveryAvailable: boolean;
13
11
  readonly now: number;
14
12
  readonly ownActivityCount: number;
15
13
  readonly hardCap: number | null;
16
- readonly terminalStatus: 'capped' | 'quorum' | undefined;
14
+ readonly terminalStatus: Extract<CatchReason, 'capped' | 'quorum'> | undefined;
17
15
  readonly hint?: string;
18
16
  }
19
17
  /** Capture a selected page (or an empty completion) without opening another resource. */
@@ -1,19 +1,19 @@
1
1
  import { formatActivityId, parseActivityId } from './square-core.js';
2
- import { readCursor, terminalStatusFor } from './runtime.js';
2
+ import { terminalStatusFor } from './runtime.js';
3
3
  import { SquareError } from './model.js';
4
4
  import { resolveKnownName } from './decisions.js';
5
+ import { toPerceivedActivity } from './views.js';
5
6
  import { catchHint } from './hints.js';
6
7
  /** Capture a selected page (or an empty completion) without opening another resource. */
7
8
  export function observeCatchSelection(facts, participant, activities, filter, at) {
8
9
  if (typeof at !== 'number' || !Number.isFinite(at))
9
10
  throw new SquareError('invalid_args', 'Catch time must be a finite number.');
10
- const cursor = readCursor(facts, participant);
11
11
  const counts = facts.participantCounts(), header = facts.header(), current = facts.participant(participant);
12
12
  return {
13
- participant, activities, consumedThrough: cursor < 0 ? null : formatActivityId(cursor),
13
+ participant, activities: activities.map(activity => toPerceivedActivity(facts, activity, participant)),
14
14
  remaining: facts.directedCount(participant, filter),
15
- sayNumbers: activities.length === 0 ? {} : facts.sayOrdinals(activities.map((item) => item.index)),
16
15
  standingCount: counts.joined, held: facts.hold().active, now: at,
16
+ deliveryAvailable: facts.seat(participant)?.wakeAddress !== undefined,
17
17
  ownActivityCount: current.sayCount, hardCap: header.hardCap,
18
18
  terminalStatus: terminalStatusFor(current, header.hardCap, counts),
19
19
  };
@@ -1,4 +1,5 @@
1
1
  import { createArtifactPort } from './artifact.js';
2
+ import { decodeHarnessOwner } from './owner.js';
2
3
  import { indexedSquareEntries } from './square-index.js';
3
4
  const holdReasons = new Set(['explicit-setting', 'managed-setting', 'repo-setting', 'invalid-setting', 'bypass-default', 'mode-unknown', 'mode-mismatch', 'no-mode-asserted']);
4
5
  const dropReasons = new Set(['expired', 'refused', 'dropped', 'discarded']);
@@ -13,12 +14,17 @@ export async function recordClaudeDeliveryStatus(input, env = process.env) {
13
14
  || (input.state === 'held' ? !holdReasons.has(input.reason ?? '')
14
15
  : input.state === 'dropped' ? !dropReasons.has(input.reason ?? '') : input.reason !== undefined))
15
16
  return false;
16
- for (const entry of await indexedSquareEntries({ channel: 'claude-code', session: input.sessionId }, env)) {
17
- const artifact = createArtifactPort(entry.location);
17
+ const paths = new Set((await indexedSquareEntries({}, env)).filter(entry => {
18
+ const coordinate = decodeHarnessOwner(entry.owner);
19
+ return coordinate?.channel === 'claude-code' && coordinate.session === input.sessionId;
20
+ }).map(entry => entry.location));
21
+ for (const location of paths) {
22
+ const artifact = createArtifactPort(location);
18
23
  try {
19
24
  const { value: match } = await artifact.read((facts) => facts.roster().flatMap((participant) => {
20
25
  const seat = facts.seat(participant.name);
21
- if (seat === undefined || seat.body.session !== input.sessionId || seat.body.channel !== 'claude-code')
26
+ if (seat === undefined || seat.attachment !== 'attached' || seat.wakeAddress?.kind !== 'claude-native'
27
+ || seat.wakeAddress.address.sessionId !== input.sessionId)
22
28
  return [];
23
29
  const evidence = facts.deliveryEvidence(seat.participant, seat.generation).find((row) => row.correlation?.harness === 'claude' && row.correlation.messageId === input.messageId && row.claimToken !== undefined);
24
30
  return evidence === undefined ? [] : [{ seat, evidence }];
@@ -26,7 +32,7 @@ export async function recordClaudeDeliveryStatus(input, env = process.env) {
26
32
  if (match.length === 0)
27
33
  continue;
28
34
  const { seat, evidence } = match[0];
29
- const ref = { participant: seat.participant, body: seat.body, generation: seat.generation };
35
+ const ref = { participant: seat.participant, owner: seat.owner, generation: seat.generation };
30
36
  return (await artifact.transactAs({ kind: 'participant', seat: ref }, (facts) => facts.observeNativeDelivery({ seat: ref, claimToken: evidence.claimToken, observation: {
31
37
  eventId: input.eventId, messageId: input.messageId, state: input.state,
32
38
  ...(input.reason === undefined ? {} : { reason: input.reason }), observedAt: input.observedAt,
@@ -2,9 +2,10 @@ import { stat } from 'node:fs/promises';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { createArtifactPort } from './artifact.js';
4
4
  import { presentPending } from './presentation-operations.js';
5
- import { projectSessionCandidates, nativePendingPreview } from './inbox.js';
6
- import { sameBody } from './model.js';
5
+ import { nativePendingPreview } from './inbox.js';
7
6
  import { parseActivityId } from './square-core.js';
7
+ import { decodeHarnessOwner } from './owner.js';
8
+ import { indexedSquareEntries } from './square-index.js';
8
9
  import { asSeatRef } from './seats.js';
9
10
  import { writeClaudeNative } from './packages/agent-delivery/src/claude-native.js';
10
11
  const marker = /(?:^|\n)\[square-inbox:([a-zA-Z0-9-]+)\](?:\n|$)/g;
@@ -22,8 +23,9 @@ function previewFacts(facts, request) {
22
23
  const index = parseActivityId(request.activity);
23
24
  if (index === undefined || request.route.kind !== 'claude-native' || request.route.address.endpoint === undefined)
24
25
  return undefined;
25
- return nativePendingPreview(facts, { location: request.location, routeKind: 'claude-native', sessionId: request.route.sessionId,
26
- participant: request.participant, actIndex: index, address: { endpoint: request.route.address.endpoint }, now: Date.now() });
26
+ return nativePendingPreview(facts, { location: request.location, routeKind: 'claude-native', generation: request.seat.generation,
27
+ address: request.route.address,
28
+ participant: request.participant, actIndex: index, now: Date.now() });
27
29
  }
28
30
  async function preview(artifact, request) {
29
31
  return (await artifact.read((facts) => previewFacts(facts, request))).value;
@@ -31,7 +33,7 @@ async function preview(artifact, request) {
31
33
  async function writeClaudeWake(sessionId, endpoint, messageId, payload, timeoutMs, signal) {
32
34
  const result = await writeClaudeNative({ sessionId, endpoint, messageId }, payload, { deadline: Date.now() + timeoutMs, signal });
33
35
  if (result.outcome === 'unavailable')
34
- return { outcome: 'failed', unavailable: true, retainRoute: true, signature: 'claude_endpoint_unavailable', message: result.message };
36
+ return { outcome: 'failed', signature: 'claude_endpoint_unavailable', message: result.message };
35
37
  return { outcome: 'unknown', signature: 'native_admission_unconfirmed', message: result.outcome === 'written' ? 'Claude accepted the local write; native admission is unconfirmed.' : result.message };
36
38
  }
37
39
  export function createClaudeWakeTransport(env = process.env) {
@@ -69,13 +71,15 @@ async function dispatchClaude(request, timeoutMs, beforeSend, env = process.env)
69
71
  return { outcome: 'not-capable', diagnostic: 'Claude activity changed before native write.' };
70
72
  if (signal.aborted || !await beforeSend())
71
73
  return { outcome: 'not-capable', diagnostic: 'Claude activity was consumed before native write.' };
72
- return writeClaudeWake(request.route.sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
74
+ const sessionId = request.route.address.sessionId;
75
+ if (sessionId === undefined)
76
+ return { outcome: 'not-capable', diagnostic: 'Claude native route has no session address.' };
77
+ return writeClaudeWake(sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
73
78
  }
74
79
  function currentPayload(facts, request) {
75
80
  const seat = facts.seat(request.participant), evidence = facts.deliveryByToken(request.claimToken);
76
- if (seat === undefined || evidence === undefined || seat.generation !== evidence.generation
77
- || seat.generation !== request.seat.generation || !sameBody(seat.body, request.seat.body)
78
- || seat.body.channel !== 'claude-code' || seat.body.session !== request.route.sessionId
81
+ if (seat === undefined || seat.attachment !== 'attached' || evidence === undefined || seat.generation !== evidence.generation
82
+ || seat.generation !== request.seat.generation || seat.owner !== request.seat.owner
79
83
  || evidence.activity !== request.activity || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome)
80
84
  || seat.wakeAddress?.kind !== 'claude-native' || !sameAddress(seat.wakeAddress.address, request.route.address))
81
85
  return undefined;
@@ -87,7 +91,11 @@ function currentPayload(facts, request) {
87
91
  }
88
92
  async function rowsForSession(sessionId, env, tokens) {
89
93
  const rows = [];
90
- const paths = new Set((await projectSessionCandidates({ sessionId, channel: 'claude-code', env })).map(candidate => candidate.location));
94
+ const candidates = (await indexedSquareEntries({}, env)).filter(entry => {
95
+ const coordinate = decodeHarnessOwner(entry.owner);
96
+ return coordinate?.channel === 'claude-code' && coordinate.session === sessionId;
97
+ });
98
+ const paths = new Set(candidates.map(candidate => candidate.location));
91
99
  for (const location of paths) {
92
100
  const artifact = createArtifactPort(location);
93
101
  try {
@@ -101,7 +109,7 @@ async function rowsForSession(sessionId, env, tokens) {
101
109
  const item = index === undefined ? undefined : facts.activity(index);
102
110
  if (seat === undefined || item?.kind !== 'say')
103
111
  return [row];
104
- const route = { location, participant: seat.participant, sessionId, channel: 'claude-code',
112
+ const route = { location, participant: seat.participant,
105
113
  ...evidence.route, updatedAt: evidence.at };
106
114
  const request = { location, participant: seat.participant, activity: evidence.activity,
107
115
  actor: item.actor, seat: asSeatRef(seat), claimToken: token, attemptN: evidence.attemptN, route, artifact };
@@ -7,11 +7,13 @@ import { processActNotificationsOnce } from './notifications.js';
7
7
  import { sweepPending } from './delivery-operations.js';
8
8
  import { beginClaudeMcpCall, endClaudeMcpCall } from './claude-mcp-context.js';
9
9
  import { asSeatRef } from './seats.js';
10
- import { projectSessionSeats, projectSessionCandidates } from './inbox.js';
11
- import { InternalSquareError, SquareError, sameBody } from './model.js';
10
+ import { projectSessionSeats } from './inbox.js';
11
+ import { restoreSessionSeats } from './session-lifecycle.js';
12
+ import { harnessOwner } from './owner.js';
13
+ import { InternalSquareError, SquareError } from './model.js';
12
14
  /** Persisted hook state keeps its wire coordinate; internal consumers keep the located seat. */
13
- function claudeBinding({ location, seat }) {
14
- return { location, participant: seat.participant, session: seat.body.session, generation: seat.generation };
15
+ function claudeBinding({ location, seat }, sessionId) {
16
+ return { location, participant: seat.participant, session: sessionId, generation: seat.generation };
15
17
  }
16
18
  /** JSON stdin bridge. Identity is explicit per operation; process.env is never rewritten. */
17
19
  export async function runClaudeMod(inputText, env = process.env) {
@@ -33,29 +35,29 @@ export async function runClaudeMod(inputText, env = process.env) {
33
35
  }
34
36
  const scoped = operationEnv('claude', input.sessionId, env);
35
37
  const signal = AbortSignal.timeout(2500);
36
- const body = { channel: 'claude-code', session: input.sessionId, ...(input.agentId === undefined ? {} : { agent: input.agentId }) };
38
+ const owner = harnessOwner({ channel: 'claude-code', session: input.sessionId, ...(input.agentId === undefined ? {} : { agent: input.agentId }) });
37
39
  const supported = process.platform === 'darwin' && typeof input.endpoint === 'string' && input.endpoint.startsWith('/');
38
- const sessionSeats = () => projectSessionSeats({ body, env }, signal);
40
+ const sessionSeats = async () => (await projectSessionSeats({ owner, env }, signal)).filter(member => member.seat.attachment === 'attached');
39
41
  if (input.operation === 'end') {
40
42
  const retired = [];
41
43
  const captured = [...input.bindings ?? []];
42
44
  // Discover committed late joins at the pinned end boundary, never later owners.
43
45
  if (input.endedAt !== undefined)
44
- for (const member of await sessionSeats()) {
46
+ for (const member of await projectSessionSeats({ owner, env }, signal)) {
45
47
  if (member.joinedAt > input.endedAt || captured.some((item) => item.location === member.location
46
48
  && item.participant === member.seat.participant && item.generation === member.seat.generation))
47
49
  continue;
48
- captured.push(claudeBinding(member));
50
+ captured.push(claudeBinding(member, input.sessionId));
49
51
  }
50
52
  for (const binding of captured) {
51
53
  if (binding.session !== input.sessionId)
52
54
  continue;
53
- const square = await Square.at({ path: binding.location, env: scoped, body,
54
- participant: binding.participant });
55
+ const square = await Square.at({ path: binding.location, env: scoped });
55
56
  try {
56
- const ended = await square.endOwnedSession(binding.participant, input.sessionId, binding.generation, body);
57
- if (ended?.activity)
58
- retired.push({ ...binding, generation: binding.generation });
57
+ const detached = binding.generation === undefined ? null
58
+ : await square.detach(binding.participant, { owner, generation: binding.generation });
59
+ if (detached)
60
+ retired.push(binding);
59
61
  }
60
62
  finally {
61
63
  await square.close();
@@ -67,41 +69,10 @@ export async function runClaudeMod(inputText, env = process.env) {
67
69
  }
68
70
  const recovery = new Set();
69
71
  const cancelledBindings = [...input.cancelledBindings ?? []];
70
- if (input.operation === 'start' && input.resume) {
71
- // Resume is proven by the artifact's latest participant lifecycle, while HOME
72
- // contributes only candidate paths. A stale candidate never establishes a seat.
73
- const candidates = await projectSessionCandidates({ body, env });
74
- const seen = new Set();
75
- for (const candidate of candidates) {
76
- if (signal.aborted)
77
- break;
78
- const probe = createArtifactPort(candidate.location, undefined, signal);
79
- let names;
80
- try {
81
- names = (await probe.read(facts => facts.completedSeatsForBody(body).map(seat => seat.participant), signal)).value;
82
- }
83
- catch (error) {
84
- if (error instanceof InternalSquareError && error.code === 'not_found')
85
- throw new SquareError('unavailable', `Square is unavailable at ${candidate.location}`);
86
- throw error;
87
- }
88
- finally {
89
- await probe.close();
90
- }
91
- for (const participant of names) {
92
- const key = `${candidate.location}\0${participant}`;
93
- if (seen.has(key))
94
- continue;
95
- seen.add(key);
96
- const square = await Square.at({ path: candidate.location, env: scoped, body, participant });
97
- try {
98
- await square.join(participant);
99
- }
100
- finally {
101
- await square.close();
102
- }
103
- }
104
- }
72
+ if (input.operation === 'start') {
73
+ await restoreSessionSeats(owner, scoped, supported ? {
74
+ kind: 'claude-native', address: { sessionId: input.sessionId, endpoint: input.endpoint, version: input.version, platform: 'darwin', loaded: 'true' },
75
+ } : undefined, signal);
105
76
  }
106
77
  if (input.operation === 'start' || input.operation === 'reconcile' || input.operation === 'cancel' || input.cancelAt !== undefined) {
107
78
  for (const member of await sessionSeats()) {
@@ -109,21 +80,21 @@ export async function runClaudeMod(inputText, env = process.env) {
109
80
  break;
110
81
  const { location, seat: capturedSeat, joinedAt } = member;
111
82
  const matches = (item) => item !== undefined
112
- && item.location === location && item.participant === capturedSeat.participant && item.session === capturedSeat.body.session
83
+ && item.location === location && item.participant === capturedSeat.participant && item.session === input.sessionId
113
84
  && (item.generation === undefined || item.generation === capturedSeat.generation);
114
85
  const expected = input.bindings?.find(matches);
115
86
  const cancelled = input.cancelledBindings?.find(matches) ?? (input.operation === 'cancel' ? expected : undefined);
116
87
  const lateCancelledJoin = input.operation === 'cancel' && input.cancelAt !== undefined
117
88
  && joinedAt <= input.cancelAt && expected === undefined;
118
89
  if (lateCancelledJoin && !cancelledBindings.some(matches))
119
- cancelledBindings.push(claudeBinding(member));
90
+ cancelledBindings.push(claudeBinding(member, input.sessionId));
120
91
  if (input.operation !== 'start' && input.operation !== 'reconcile' && !lateCancelledJoin && expected === undefined)
121
92
  continue;
122
93
  const artifact = createArtifactPort(location, undefined, signal);
123
94
  try {
124
95
  const { value: currentSeat } = await artifact.read((facts) => facts.seat(capturedSeat.participant), signal);
125
- if (currentSeat === undefined || currentSeat.generation !== capturedSeat.generation
126
- || !sameBody(currentSeat.body, capturedSeat.body))
96
+ if (currentSeat === undefined || currentSeat.attachment !== 'attached' || currentSeat.generation !== capturedSeat.generation
97
+ || currentSeat.owner !== capturedSeat.owner)
127
98
  continue;
128
99
  const seat = asSeatRef(currentSeat);
129
100
  const existing = currentSeat.wakeAddress?.kind === 'claude-native' ? currentSeat.wakeAddress : undefined;
@@ -193,7 +164,7 @@ export async function runClaudeMod(inputText, env = process.env) {
193
164
  await processActNotificationsOnce(location, index, { env: { ...env, SQUARE_NOTIFY_DELIVERY_WAIT_MS: '200' } });
194
165
  }
195
166
  }
196
- return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude with an interactive loaded mod and its real endpoint.', bindings: members.map(claudeBinding), cancelledBindings })}\n`;
167
+ return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude with an interactive loaded mod and its real endpoint.', bindings: members.map(member => claudeBinding(member, input.sessionId)), cancelledBindings })}\n`;
197
168
  }
198
169
  if (!['guard', 'admitted', 'stored'].includes(input.operation) || typeof input.text !== 'string')
199
170
  throw new Error('Unknown Claude bridge operation.');
@@ -1,13 +1,16 @@
1
+ import { type Owner } from '../square-api.js';
1
2
  import { type HardCap } from '../model.js';
2
3
  import { Square } from '../square-api.js';
3
- import type { Body, OperationControl } from '../square-facade.js';
4
+ import type { OperationControl } from '../square-facade.js';
4
5
  export interface CommandContext {
5
6
  cwd: string;
6
7
  env: NodeJS.ProcessEnv;
7
8
  squarePath?: string;
8
9
  name?: string;
10
+ owner?: Owner;
9
11
  homeDir: string;
10
12
  command: string;
13
+ json?: boolean;
11
14
  }
12
15
  export interface CommandSpec<Intent = unknown, Result = void> {
13
16
  parse(argv: string[], context: CommandContext): Intent;
@@ -30,18 +33,20 @@ export interface ParsedGlobalArgs {
30
33
  explicitSquarePath: boolean;
31
34
  multipleSquares: boolean;
32
35
  name?: string;
36
+ owner?: Owner;
33
37
  args: string[];
34
38
  }
35
39
  export declare function parseGlobalArgs(rawArgs: string[]): Promise<ParsedGlobalArgs>;
36
40
  export declare function locationIsRequired(command: string): boolean;
37
41
  export declare function defaultContext(command: string, squarePath?: string, name?: string): CommandContext;
38
42
  export declare function squareInput(context: CommandContext): {
39
- wakeAddress?: import("../model.js").WakeAddress | undefined;
40
- body?: Body | undefined;
41
43
  cwd: string;
42
44
  env: NodeJS.ProcessEnv;
43
45
  path: string | undefined;
44
- participant: string | undefined;
46
+ };
47
+ export declare function commandIdentity(env: NodeJS.ProcessEnv, explicit?: Owner): {
48
+ owner: Owner;
49
+ wakeAddress?: import('../model.js').WakeAddress;
45
50
  };
46
51
  /** Adapter lifetime only; each callback uses the actual public resource API. */
47
52
  export declare function withSquare<T>(context: CommandContext, run: (square: Square) => Promise<T>, control?: OperationControl): Promise<T>;
@@ -1,9 +1,10 @@
1
1
  import os from 'node:os';
2
+ import { harnessOwner, localUserOwner } from '../square-api.js';
3
+ import { harnessSessionSources } from '../participant-identity.js';
2
4
  import { codexWakeAddress } from '../codex-address.js';
3
5
  import { commandUsageHint } from '../help.js';
4
6
  import { parseParticipantList, validateName } from '../model.js';
5
7
  import { Square, squareClock } from '../square-api.js';
6
- import { harnessSessionSources } from '../participant-identity.js';
7
8
  export async function readStdin() {
8
9
  try {
9
10
  let content = '';
@@ -110,12 +111,18 @@ export async function parseGlobalArgs(rawArgs) {
110
111
  const args = [...rawArgs];
111
112
  let requestedPath;
112
113
  let name;
114
+ let owner;
113
115
  for (let index = 0; index < args.length; index++) {
114
116
  if (args[index] === '--location') {
115
117
  requestedPath = requireValue(args, index, args[index]);
116
118
  args.splice(index, 2);
117
119
  index -= 1;
118
120
  }
121
+ else if (args[index] === '--owner') {
122
+ owner = requireValue(args, index, args[index]);
123
+ args.splice(index, 2);
124
+ index -= 1;
125
+ }
119
126
  else if (args[index] === '--as') {
120
127
  name = requireValue(args, index, args[index]);
121
128
  args.splice(index, 2);
@@ -132,39 +139,37 @@ export async function parseGlobalArgs(rawArgs) {
132
139
  fail(`✕ ${command} needs a square location\nsquare ls`);
133
140
  }
134
141
  const squarePath = requestedPath ?? configured;
135
- return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
142
+ return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, owner, args };
136
143
  }
137
144
  export function locationIsRequired(command) {
138
145
  return LOCATION_REQUIRED_COMMANDS.has(command);
139
146
  }
140
147
  export function defaultContext(command, squarePath, name) {
141
- return { command, cwd: process.cwd(), env: { ...process.env }, squarePath, name, homeDir: os.homedir() };
148
+ return { command, cwd: process.cwd(), env: { ...process.env }, squarePath, name, owner: undefined, homeDir: os.homedir() };
142
149
  }
143
150
  export function squareInput(context) {
144
- const { body, wakeAddress } = commandIdentity(context.env);
145
- return { cwd: context.cwd, env: context.env, path: context.squarePath, participant: context.name,
146
- ...(body === undefined ? {} : { body }), ...(wakeAddress === undefined ? {} : { wakeAddress }) };
147
- }
148
- function commandIdentity(env) {
149
- const coordinates = harnessSessionSources.flatMap(({ variable, channel, provider }) => {
150
- const session = env[variable]?.trim();
151
- return session === undefined || session === '' ? [] : [{ channel, session, native: provider !== undefined }];
152
- });
153
- const native = coordinates.filter((coordinate) => coordinate.native);
154
- if (native.length > 1)
155
- throw new Error('Multiple harness execution coordinates are present; select one explicit body.');
156
- const paseo = coordinates.find((coordinate) => coordinate.channel === 'paseo');
157
- // A child marker without a reliable agent id must never inherit the parent or Paseo identity.
151
+ return { cwd: context.cwd, env: context.env, path: context.squarePath };
152
+ }
153
+ export function commandIdentity(env, explicit) {
154
+ if (explicit !== undefined)
155
+ return { owner: explicit };
156
+ const configured = env.SQUARE_OWNER;
157
+ if (configured !== undefined)
158
+ return { owner: configured };
159
+ const selected = harnessSessionSources.flatMap(({ variable, channel }) => env[variable]?.trim() ? [{ channel, session: env[variable].trim() }] : []);
160
+ const native = selected.filter(source => source.channel !== 'paseo');
158
161
  if (env.CLAUDE_CODE_CHILD_SESSION === '1')
159
- return {};
160
- const selected = native[0] ?? paseo;
161
- if (selected === undefined)
162
- return {};
163
- const body = { channel: selected.channel, session: selected.session };
164
- const wakeAddress = selected.channel === 'codex'
165
- ? codexWakeAddress(selected.session, env)
162
+ throw new Error('Harness identity is incomplete; provide --owner explicitly.');
163
+ if (native.length > 1)
164
+ throw new Error('Multiple harness execution coordinates are present; select one explicit owner.');
165
+ const paseo = selected.find(source => source.channel === 'paseo');
166
+ const coordinate = native[0] ?? paseo;
167
+ if (coordinate === undefined)
168
+ return { owner: localUserOwner() };
169
+ const owner = harnessOwner({ channel: coordinate.channel, session: coordinate.session });
170
+ const wakeAddress = coordinate.channel === 'codex' ? codexWakeAddress(coordinate.session, env)
166
171
  : paseo === undefined ? undefined : { kind: 'paseo', address: { agentId: paseo.session } };
167
- return { body, ...(wakeAddress === undefined ? {} : { wakeAddress }) };
172
+ return { owner, ...(wakeAddress === undefined ? {} : { wakeAddress }) };
168
173
  }
169
174
  /** Adapter lifetime only; each callback uses the actual public resource API. */
170
175
  export async function withSquare(context, run, control) {
@@ -1,7 +1,9 @@
1
1
  import { type Square } from '../square-api.js';
2
+ import type { Owner } from '../square-api.js';
2
3
  import type { OperationControl, ExpressOptions } from '../square-facade.js';
3
4
  export { renderExpression } from '../operation-presentation.js';
4
5
  export interface ActivityOptions extends Omit<ExpressOptions, 'reply'> {
6
+ readonly recoveryOwner?: Owner;
5
7
  readonly reply?: string;
6
8
  readonly metadataJson?: string;
7
9
  readonly bodyProvided?: boolean;
@@ -10,4 +12,4 @@ export declare function saveActivityDraft(squarePath: string, name: string, body
10
12
  /** Retry choices are transport syntax, never a second admission decision. */
11
13
  export declare function activityRetryCommand(squarePath: string, name: string, opts: ActivityOptions, force?: boolean): string;
12
14
  /** Body adaptation, terminal IO and drafts only; Participant owns every attempt and wait. */
13
- export declare function executeExpression(open: (control?: OperationControl) => Promise<Square>, squarePath: string, requestedName: string | undefined, rawInput: string | undefined, opts: ActivityOptions): Promise<void>;
15
+ export declare function executeExpression(open: (control?: OperationControl) => Promise<Square>, squarePath: string, requestedName: string | undefined, rawInput: string | undefined, opts: ActivityOptions, json?: boolean, resolveOwner?: Owner | (() => Owner)): Promise<void>;