@astrosheep/square 0.3.11 → 0.3.13

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 (127) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks/hooks.json +9 -0
  3. package/dist/activity-feed.d.ts +14 -0
  4. package/dist/activity-feed.js +5 -5
  5. package/dist/activity.d.ts +9 -0
  6. package/dist/activity.js +112 -96
  7. package/dist/artifact.d.ts +20 -0
  8. package/dist/artifact.js +317 -648
  9. package/dist/automatic-session.d.ts +5 -0
  10. package/dist/automatic-session.js +86 -0
  11. package/dist/boundary-presentation.d.ts +5 -0
  12. package/dist/boundary-presentation.js +6 -5
  13. package/dist/claude-hook.d.ts +9 -0
  14. package/dist/claude-hook.js +35 -3
  15. package/dist/cli/context.d.ts +35 -0
  16. package/dist/cli/context.js +26 -31
  17. package/dist/cli/harness-command.d.ts +28 -0
  18. package/dist/cli/harness-command.js +30 -15
  19. package/dist/cli/maintenance-commands.d.ts +7 -0
  20. package/dist/cli/maintenance-commands.js +12 -69
  21. package/dist/cli/meta-commands.d.ts +5 -0
  22. package/dist/cli/observation-commands.d.ts +20 -0
  23. package/dist/cli/observation-commands.js +159 -139
  24. package/dist/cli/program.d.ts +2 -0
  25. package/dist/cli/program.js +3 -12
  26. package/dist/cli/registry.d.ts +9 -0
  27. package/dist/cli/registry.js +2 -4
  28. package/dist/cli/square-commands.d.ts +34 -0
  29. package/dist/cli/square-commands.js +90 -84
  30. package/dist/cmd/notify-once.d.ts +2 -0
  31. package/dist/codex-hook.d.ts +10 -0
  32. package/dist/codex-hook.js +35 -3
  33. package/dist/decisions.d.ts +92 -0
  34. package/dist/decisions.js +93 -81
  35. package/dist/delivery-health.d.ts +21 -0
  36. package/dist/delivery-health.js +41 -34
  37. package/dist/delivery.d.ts +81 -0
  38. package/dist/delivery.js +71 -69
  39. package/dist/file-lock.d.ts +6 -0
  40. package/dist/harness-claude.d.ts +19 -0
  41. package/dist/harness-claude.js +8 -3
  42. package/dist/harness-codex.d.ts +29 -0
  43. package/dist/harness-codex.js +2 -2
  44. package/dist/harness-links.d.ts +17 -0
  45. package/dist/harness-links.js +2 -2
  46. package/dist/harness-pi.d.ts +11 -0
  47. package/dist/harness-pi.js +2 -2
  48. package/dist/harness-stage.d.ts +6 -0
  49. package/dist/harness.d.ts +20 -0
  50. package/dist/harness.js +5 -3
  51. package/dist/help.d.ts +6 -0
  52. package/dist/help.js +12 -13
  53. package/dist/identity.d.ts +13 -0
  54. package/dist/inbox.d.ts +2 -0
  55. package/dist/inbox.js +15 -19
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.js +31 -67
  58. package/dist/landing.d.ts +10 -0
  59. package/dist/landing.js +95 -0
  60. package/dist/list.d.ts +1 -0
  61. package/dist/list.js +39 -61
  62. package/dist/model.d.ts +147 -0
  63. package/dist/model.js +12 -8
  64. package/dist/notifications.d.ts +30 -0
  65. package/dist/notifications.js +83 -91
  66. package/dist/open-square.d.ts +11 -0
  67. package/dist/open-square.js +4 -0
  68. package/dist/participant-identity.d.ts +1 -0
  69. package/dist/participant-identity.js +3 -0
  70. package/dist/paseo-connection.d.ts +15 -0
  71. package/dist/paseo-delivery.d.ts +14 -0
  72. package/dist/paseo-state.d.ts +11 -0
  73. package/dist/paseo-timeline.d.ts +16 -0
  74. package/dist/presence.d.ts +3 -0
  75. package/dist/presence.js +42 -0
  76. package/dist/presentation.d.ts +91 -0
  77. package/dist/presentation.js +52 -63
  78. package/dist/presented.d.ts +11 -0
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.js +36 -3
  81. package/dist/routes.d.ts +42 -0
  82. package/dist/runtime.d.ts +45 -0
  83. package/dist/runtime.js +48 -55
  84. package/dist/search.d.ts +11 -0
  85. package/dist/square-core.d.ts +111 -0
  86. package/dist/square-core.js +63 -9
  87. package/dist/square-facade.d.ts +86 -0
  88. package/dist/square-facade.js +1 -0
  89. package/dist/square-file-adapter.d.ts +18 -0
  90. package/dist/square-file-adapter.js +70 -0
  91. package/dist/square-storage.d.ts +20 -0
  92. package/dist/square-storage.js +171 -0
  93. package/dist/square-wiring.d.ts +19 -0
  94. package/dist/square-wiring.js +70 -0
  95. package/dist/square.d.ts +2 -0
  96. package/dist/state-cell.d.ts +14 -0
  97. package/dist/state-cell.js +1 -0
  98. package/dist/stream.d.ts +3 -0
  99. package/dist/stream.js +19 -23
  100. package/dist/time.d.ts +6 -0
  101. package/dist/views.d.ts +95 -0
  102. package/dist/views.js +81 -0
  103. package/dist/wake-attempts.d.ts +39 -0
  104. package/dist/wake-attempts.js +8 -4
  105. package/dist/wake-evidence.d.ts +12 -0
  106. package/dist/wake-evidence.js +7 -10
  107. package/dist/wake-port.d.ts +20 -0
  108. package/dist/wake-sink.d.ts +12 -0
  109. package/dist/wakes.d.ts +40 -0
  110. package/dist/wakes.js +62 -0
  111. package/dist/watch.d.ts +2 -0
  112. package/dist/watch.js +89 -210
  113. package/extensions/square-opencode.js +27 -1
  114. package/extensions/square-pi.js +13 -0
  115. package/guides/participant.md +7 -174
  116. package/package.json +5 -4
  117. package/skills/brainstorm/SKILL.md +30 -30
  118. package/skills/square/.claude-plugin/plugin.json +1 -1
  119. package/skills/square/SKILL.md +56 -29
  120. package/skills/square/hooks/hooks.json +9 -0
  121. package/skills/square-feedback/SKILL.md +9 -9
  122. package/dist/compact.js +0 -26
  123. package/dist/doctor.js +0 -36
  124. package/dist/square-application.js +0 -262
  125. package/template.md +0 -4
  126. package/templates/architect.md +0 -4
  127. package/templates/brainstorm.md +0 -4
package/dist/watch.js CHANGED
@@ -1,55 +1,31 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises';
2
- import { loadSquare } from './artifact.js';
3
- import { SquareError, nameKey, } from './model.js';
4
- import { markDeliveredNotifications } from './delivery.js';
5
- import { SLEEP_MS, STALE_MS, WATCH_HEARTBEAT_MS, WATCH_STALE_MS, countSays, currentHold, doneNames, freshWatchLease, hasQuorum, inSquareCount, nowMs, touchPresenceCursor, writeWatchLease, } from './runtime.js';
6
- import { execute, withSquareLock, writeSquareDoc } from './square-application.js';
2
+ import { isSquareError, } from './model.js';
3
+ import { SLEEP_MS, STALE_MS, WATCH_HEARTBEAT_MS, nowMs, } from './runtime.js';
4
+ import { openSquare } from './square-file-adapter.js';
5
+ import { closeOpenSquare } from './open-square.js';
6
+ import { openParticipant } from './square-wiring.js';
7
+ import { resolveParticipant, watchPresentation } from './views.js';
8
+ import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
7
9
  import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
8
- import { ackPeerDelta, deliveryDelta, filteredPeerActivities, filteredRoomChanges, matchesFeedFilter, peerPublicActs, peerRoomChanges, } from './activity-feed.js';
9
- import { coreParticipants, resolveKnownName } from './decisions.js';
10
10
  import { hasAutomaticDeliveryIdentity, localParticipantOwner } from './registry.js';
11
- function catchDelta(doc, name) {
12
- return deliveryDelta(doc, name);
13
- }
11
+ import { parseActivityId } from './square-core.js';
14
12
  function watchStatusExitCode(status) {
15
13
  return status === 'capped' ? 1 : 0;
16
14
  }
17
15
  function leaseId() {
18
16
  return `watch_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
19
17
  }
20
- function leaseFilter(opts) {
21
- const filter = {
22
- ...(opts.participants !== undefined ? { participants: opts.participants } : {}),
23
- ...(opts.mention !== undefined ? { mention: opts.mention } : {}),
24
- };
25
- return Object.keys(filter).length === 0 ? undefined : filter;
26
- }
27
- function setLease(doc, name, id, at, opts, ownerId) {
28
- const filter = leaseFilter(opts);
29
- writeWatchLease(doc, name, {
30
- leaseId: id,
31
- ...(ownerId === undefined ? {} : { ownerId }),
32
- heartbeatAt: at,
33
- expiresAt: at + WATCH_STALE_MS,
34
- ...(filter ? { filter } : {}),
18
+ function watchOutputResult(squarePath, presentation, name, caught, opts = {}) {
19
+ const delivered = caught.activities.flatMap((activity) => {
20
+ const index = parseActivityId(activity.id);
21
+ const stored = index === undefined ? undefined : presentation.activities.find((item) => item.index === index);
22
+ return stored === undefined ? [] : [stored];
35
23
  });
36
- }
37
- function sameLease(doc, name, id, at = nowMs()) {
38
- return freshWatchLease(doc, name, at)?.leaseId === id;
39
- }
40
- function consumeDelta(doc, name, delta, delivered, at) {
41
- const consumed = ackPeerDelta(doc, name, delta);
42
- const receipts = markDeliveredNotifications(doc, name, delivered, at);
43
- return consumed || receipts;
44
- }
45
- function watchOutputResult(squarePath, doc, name, delta, opts = {}) {
46
- const publicItems = peerPublicActs(delta, name).filter((item) => matchesFeedFilter(item, opts));
47
- const roomChanges = filteredRoomChanges(delta, name, opts);
48
- consumeDelta(doc, name, delta, publicItems, nowMs());
49
- writeSquareDoc(squarePath, doc);
24
+ const publicItems = delivered.filter((item) => item.kind === 'say' || item.kind === 'done');
25
+ const roomChanges = delivered.filter((item) => item.kind === 'join' || item.kind === 'done' || item.kind === 'hold' || item.kind === 'resume');
50
26
  return {
51
27
  type: 'output',
52
- stdout: renderWatchOutput(doc.acts, publicItems, roomChanges, {
28
+ stdout: renderWatchOutput([...presentation.activities], publicItems, roomChanges, {
53
29
  ...opts,
54
30
  squarePath,
55
31
  viewer: name,
@@ -58,30 +34,11 @@ function watchOutputResult(squarePath, doc, name, delta, opts = {}) {
58
34
  ...(opts.status ? { status: opts.status } : {}),
59
35
  };
60
36
  }
61
- function loadPresence(squarePath) {
62
- try {
63
- const doc = loadSquare(squarePath);
64
- const now = nowMs();
65
- return { participants: coreParticipants(doc, now), now };
66
- }
67
- catch {
68
- return undefined;
69
- }
70
- }
71
- function loadHeaderCount(squarePath) {
72
- try {
73
- return inSquareCount(loadSquare(squarePath));
74
- }
75
- catch {
76
- return undefined;
77
- }
78
- }
79
- function writeWatchOutput(squarePath, name, stdout, status, idleMs) {
80
- const presence = loadPresence(squarePath);
81
- const headerOpts = { participantCount: loadHeaderCount(squarePath) };
37
+ function writeWatchOutput(squarePath, name, presentation, stdout, status, idleMs) {
38
+ const headerOpts = { participantCount: presentation.participantCount };
82
39
  const showCatchHint = !hasAutomaticDeliveryIdentity();
83
40
  if (status) {
84
- process.stdout.write(withPathOutput(squarePath, [renderWatchStatus({ status, squarePath, name, idleMs, presence, showCatchHint }), stdout.trimEnd()].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
41
+ process.stdout.write(withPathOutput(squarePath, [renderWatchStatus({ status, squarePath, name, idleMs, presence: presentation.presence, showCatchHint }), stdout.trimEnd()].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
85
42
  return;
86
43
  }
87
44
  const fallback = showCatchHint
@@ -89,67 +46,51 @@ function writeWatchOutput(squarePath, name, stdout, status, idleMs) {
89
46
  : '';
90
47
  process.stdout.write(withPathOutput(squarePath, [stdout.trimEnd(), fallback].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
91
48
  }
92
- function writeWatchTerminal(squarePath, name, status, idleMs) {
93
- const presence = loadPresence(squarePath);
49
+ function writeWatchTerminal(squarePath, name, presentation, status, idleMs) {
94
50
  process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
95
51
  status,
96
52
  squarePath,
97
53
  name,
98
54
  ...(idleMs === undefined ? {} : { idleMs }),
99
- presence,
55
+ presence: presentation.presence,
100
56
  showCatchHint: !hasAutomaticDeliveryIdentity(),
101
- }), { participantCount: loadHeaderCount(squarePath) }));
57
+ }), { participantCount: presentation.participantCount }));
102
58
  }
103
- function writeWatchReplaced(squarePath, name) {
104
- process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
59
+ function writeWatchReplaced(squarePath, name, presentation) {
60
+ process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: presentation.participantCount }));
105
61
  }
106
- async function finishWatchResult(squarePath, name, result, leaseId, idleMs) {
62
+ async function finishWatchResult(square, squarePath, name, result, leaseId, idleMs) {
107
63
  if (result.type === 'output') {
108
- await endWatch(squarePath, name, leaseId);
109
- writeWatchOutput(squarePath, name, result.stdout, result.status);
64
+ await endWatch(square, name, leaseId);
65
+ writeWatchOutput(squarePath, name, await watchPresentation(square, name), result.stdout, result.status);
110
66
  process.exitCode = watchStatusExitCode(result.status);
111
67
  return true;
112
68
  }
113
69
  if (result.type === 'terminal') {
114
- await endWatch(squarePath, name, leaseId);
115
- writeWatchTerminal(squarePath, name, result.status, idleMs);
70
+ await endWatch(square, name, leaseId);
71
+ writeWatchTerminal(squarePath, name, await watchPresentation(square, name), result.status, idleMs);
116
72
  process.exitCode = watchStatusExitCode(result.status);
117
73
  return true;
118
74
  }
119
75
  if (result.type === 'replaced') {
120
- writeWatchReplaced(squarePath, name);
76
+ writeWatchReplaced(squarePath, name, await watchPresentation(square, name));
121
77
  process.exitCode = 0;
122
78
  return true;
123
79
  }
124
80
  return false;
125
81
  }
126
- async function beginWatch(squarePath, name, opts) {
127
- const at = nowMs();
82
+ async function beginWatch(square, squarePath, name, opts) {
128
83
  const id = leaseId();
129
84
  const ownerId = localParticipantOwner(squarePath, name);
130
- const committed = await execute(squarePath, {
131
- type: 'lease',
132
- name,
133
- leaseId: id,
134
- ...(ownerId === undefined ? {} : { ownerId }),
135
- at,
136
- expiresAt: at + WATCH_STALE_MS,
137
- force: opts.replace,
138
- filter: leaseFilter(opts),
139
- });
140
- if (committed.result.type === 'active')
141
- return committed.result;
142
- return { type: 'started', leaseId: id, replaced: committed.result.replaced, heartbeatAt: at };
85
+ return acquireWatchLease(square, name, id, opts, ownerId);
143
86
  }
144
- async function endWatch(squarePath, name, id) {
145
- if (id === undefined)
146
- return;
147
- await execute(squarePath, { type: 'release-lease', name, leaseId: id });
87
+ async function endWatch(square, name, id) {
88
+ await releaseWatchLease(square, name, id);
148
89
  }
149
- function installWatchInterruptHandler(squarePath, name, currentLeaseId) {
90
+ function installWatchInterruptHandler(square, squarePath, name, currentLeaseId) {
150
91
  const onInterrupt = () => {
151
92
  void (async () => {
152
- await endWatch(squarePath, name, currentLeaseId());
93
+ await endWatch(square, name, currentLeaseId());
153
94
  process.stdout.write(withPathOutput(squarePath, '✕ catch stopped'));
154
95
  process.exit(130);
155
96
  })().catch((error) => {
@@ -162,157 +103,93 @@ function installWatchInterruptHandler(squarePath, name, currentLeaseId) {
162
103
  process.off('SIGINT', onInterrupt);
163
104
  };
164
105
  }
165
- function terminalStatus(doc, name) {
166
- const count = countSays(doc.acts, name);
167
- if (doc.hardCap !== null && count >= doc.hardCap)
168
- return 'capped';
169
- const done = doneNames(doc.acts);
170
- done.delete(nameKey(name));
171
- if (hasQuorum(doc, name, done))
172
- return 'quorum';
173
- return undefined;
174
- }
175
106
  async function cmdWatchNow(squarePath, name, opts) {
176
- const result = await withSquareLock(squarePath, () => {
177
- const doc = loadSquare(squarePath);
178
- const at = nowMs();
179
- const touched = touchPresenceCursor(doc, name, at);
180
- const delta = catchDelta(doc, name);
181
- const peerPublic = peerPublicActs(delta, name);
182
- const roomChanges = peerRoomChanges(delta, name);
183
- const filteredActivities = filteredPeerActivities(delta, name, opts);
184
- const matchingRoomChanges = filteredRoomChanges(delta, name, opts);
185
- const hasDeliverable = peerPublic.length > 0 || roomChanges.length > 0;
186
- const hasFilteredDeliverable = filteredActivities.length > 0 || matchingRoomChanges.length > 0;
187
- const status = terminalStatus(doc, name);
188
- if (hasDeliverable && hasFilteredDeliverable) {
189
- return watchOutputResult(squarePath, doc, name, delta, {
190
- participants: opts.participants,
191
- mention: opts.mention,
192
- ...(status ? { status } : {}),
193
- });
194
- }
195
- if (hasDeliverable) {
196
- const acked = ackPeerDelta(doc, name, delta);
197
- if (acked || touched)
198
- writeSquareDoc(squarePath, doc);
199
- }
200
- else if (touched) {
201
- writeSquareDoc(squarePath, doc);
202
- }
203
- if (status)
204
- return { type: 'terminal', status };
205
- return { type: 'terminal', status: 'empty-now' };
206
- });
207
- await finishWatchResult(squarePath, name, result, undefined);
107
+ const square = await openSquare(squarePath, { clock: nowMs });
108
+ const facade = await openParticipant({ path: squarePath, clock: nowMs }, name);
109
+ try {
110
+ const caught = await facade.participant.catch({
111
+ ...(opts.participants === undefined ? {} : { from: opts.participants }),
112
+ ...(opts.mention === undefined ? {} : { mention: true }),
113
+ });
114
+ const presentation = await watchPresentation(square, name);
115
+ const status = presentation.terminalStatus;
116
+ const result = caught.activities.length > 0
117
+ ? watchOutputResult(squarePath, presentation, name, caught, { mention: opts.mention, ...(status ? { status } : {}) })
118
+ : { type: 'terminal', status: status ?? 'empty-now' };
119
+ await finishWatchResult(square, squarePath, name, result, undefined);
120
+ }
121
+ finally {
122
+ await facade.close();
123
+ await closeOpenSquare(square);
124
+ }
208
125
  }
209
126
  export async function cmdWatch(squarePath, name, opts) {
210
- let initialDoc;
127
+ let square;
211
128
  try {
212
- initialDoc = loadSquare(squarePath);
213
- name = resolveKnownName(initialDoc, name);
129
+ square = await openSquare(squarePath, { clock: nowMs });
130
+ name = (await resolveParticipant(square, name)).name;
214
131
  if (opts.mention !== undefined)
215
- opts = { ...opts, mention: resolveKnownName(initialDoc, opts.mention) };
132
+ opts = { ...opts, mention: (await resolveParticipant(square, opts.mention)).name };
216
133
  if (opts.participants !== undefined && opts.participants.length > 0) {
217
- opts = { ...opts, participants: opts.participants.map((p) => resolveKnownName(initialDoc, p)) };
134
+ opts = { ...opts, participants: await Promise.all(opts.participants.map(async (participant) => (await resolveParticipant(square, participant)).name)) };
218
135
  }
219
136
  }
220
137
  catch (err) {
221
- if (err instanceof SquareError) {
138
+ if (isSquareError(err)) {
222
139
  process.stderr.write(err.message + '\n');
223
140
  process.exit(err.code === 'not_found' ? 1 : 2);
224
141
  }
225
142
  throw err;
226
143
  }
227
144
  if (opts.now) {
145
+ await closeOpenSquare(square);
228
146
  await cmdWatchNow(squarePath, name, opts);
229
147
  return;
230
148
  }
231
- const start = await beginWatch(squarePath, name, opts);
149
+ const start = await beginWatch(square, squarePath, name, opts);
232
150
  if (start.type === 'active') {
233
- process.stdout.write(withPathOutput(squarePath, renderWatchAlreadyActive({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
151
+ const presentation = await watchPresentation(square, name);
152
+ process.stdout.write(withPathOutput(squarePath, renderWatchAlreadyActive({ squarePath, name }), { participantCount: presentation.participantCount }));
153
+ await closeOpenSquare(square);
234
154
  process.exit(1);
235
155
  }
236
156
  let staleSince = nowMs();
237
157
  let currentLeaseId = start.leaseId;
238
158
  let nextHeartbeatAt = start.heartbeatAt + WATCH_HEARTBEAT_MS;
239
159
  if (start.replaced) {
240
- process.stdout.write(withPathOutput(squarePath, renderWatchForceTakeover({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
160
+ const presentation = await watchPresentation(square, name);
161
+ process.stdout.write(withPathOutput(squarePath, renderWatchForceTakeover({ squarePath, name }), { participantCount: presentation.participantCount }));
241
162
  }
242
163
  const idleMs = opts.idleMs ?? STALE_MS;
243
- const removeInterruptHandler = installWatchInterruptHandler(squarePath, name, () => currentLeaseId);
164
+ const removeInterruptHandler = installWatchInterruptHandler(square, squarePath, name, () => currentLeaseId);
165
+ const facade = await openParticipant({ path: squarePath, clock: nowMs }, name);
244
166
  try {
245
167
  while (true) {
246
- const result = await withSquareLock(squarePath, () => {
247
- const doc = loadSquare(squarePath);
248
- const at = nowMs();
249
- const lease = freshWatchLease(doc, name, at);
250
- if (lease === undefined || lease.leaseId !== currentLeaseId)
251
- return { type: 'replaced' };
252
- let mutated = false;
253
- if (at >= nextHeartbeatAt) {
254
- setLease(doc, name, currentLeaseId, at, opts, lease.ownerId);
255
- mutated = touchPresenceCursor(doc, name, at) || mutated;
256
- nextHeartbeatAt = at + WATCH_HEARTBEAT_MS;
257
- mutated = true;
258
- }
259
- const delta = catchDelta(doc, name);
260
- const peerPublic = peerPublicActs(delta, name);
261
- const roomChanges = peerRoomChanges(delta, name);
262
- const filteredActivities = filteredPeerActivities(delta, name, opts);
263
- const matchingRoomChanges = filteredRoomChanges(delta, name, opts);
264
- const hasDeliverable = peerPublic.length > 0 || roomChanges.length > 0;
265
- const hasFilteredDeliverable = filteredActivities.length > 0 || matchingRoomChanges.length > 0;
266
- const status = terminalStatus(doc, name);
267
- if (currentHold(doc.acts).active) {
268
- if (mutated)
269
- writeSquareDoc(squarePath, doc);
270
- return { type: 'held' };
271
- }
272
- if (hasDeliverable &&
273
- hasFilteredDeliverable &&
274
- (filteredActivities.length > 0 || matchingRoomChanges.length > 0 || status !== undefined)) {
275
- return watchOutputResult(squarePath, doc, name, delta, {
276
- participants: opts.participants,
277
- mention: opts.mention,
278
- ...(status ? { status } : {}),
279
- });
280
- }
281
- if (hasDeliverable && !hasFilteredDeliverable)
282
- mutated = ackPeerDelta(doc, name, delta) || mutated;
283
- if (status) {
284
- if (mutated)
285
- writeSquareDoc(squarePath, doc);
286
- return { type: 'terminal', status };
168
+ const leaseState = await pulseWatchLease(square, name, currentLeaseId, opts, nowMs() >= nextHeartbeatAt);
169
+ if (leaseState.type === 'sleep' && leaseState.heartbeatAt !== undefined) {
170
+ nextHeartbeatAt = leaseState.heartbeatAt + WATCH_HEARTBEAT_MS;
171
+ }
172
+ let result = leaseState;
173
+ if (result.type === 'sleep') {
174
+ const caught = await facade.participant.catch({
175
+ ...(opts.participants === undefined ? {} : { from: opts.participants }),
176
+ ...(opts.mention === undefined ? {} : { mention: true }),
177
+ });
178
+ if (caught.activities.length > 0) {
179
+ result = watchOutputResult(squarePath, await watchPresentation(square, name), name, caught, { mention: opts.mention });
287
180
  }
288
- if (mutated)
289
- writeSquareDoc(squarePath, doc);
290
- return { type: 'sleep' };
291
- });
292
- if (await finishWatchResult(squarePath, name, result, currentLeaseId)) {
181
+ }
182
+ if (await finishWatchResult(square, squarePath, name, result, currentLeaseId)) {
293
183
  currentLeaseId = undefined;
294
184
  return;
295
185
  }
296
186
  if (result.type === 'held')
297
187
  staleSince = nowMs();
298
188
  if (nowMs() - staleSince >= idleMs) {
299
- const result = await withSquareLock(squarePath, () => {
300
- const doc = loadSquare(squarePath);
301
- if (!sameLease(doc, name, currentLeaseId))
302
- return { type: 'replaced' };
303
- const delta = catchDelta(doc, name);
304
- const filteredActivities = filteredPeerActivities(delta, name, opts);
305
- if (filteredActivities.length > 0) {
306
- return watchOutputResult(squarePath, doc, name, delta, {
307
- stalePartial: true,
308
- participants: opts.participants,
309
- mention: opts.mention,
310
- idleMs,
311
- });
312
- }
313
- return { type: 'terminal', status: 'stale' };
314
- });
315
- if (await finishWatchResult(squarePath, name, result, currentLeaseId, idleMs)) {
189
+ const result = !await ownsWatchLease(square, name, currentLeaseId)
190
+ ? { type: 'replaced' }
191
+ : { type: 'terminal', status: 'stale' };
192
+ if (await finishWatchResult(square, squarePath, name, result, currentLeaseId, idleMs)) {
316
193
  currentLeaseId = undefined;
317
194
  return;
318
195
  }
@@ -321,6 +198,8 @@ export async function cmdWatch(squarePath, name, opts) {
321
198
  }
322
199
  }
323
200
  finally {
201
+ await facade.close();
202
+ await closeOpenSquare(square);
324
203
  removeInterruptHandler();
325
204
  }
326
205
  }
@@ -1,14 +1,40 @@
1
1
  import { presentPendingAtBoundary } from '../dist/boundary-presentation.js';
2
+ import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
2
3
 
3
4
  export default async function squareOpenCodePlugin() {
5
+ const joining = new Map();
4
6
  return {
7
+ event: async ({ event }) => {
8
+ if (event.type === 'session.created' || event.type === 'session.updated') {
9
+ const sessionID = event.properties?.sessionID;
10
+ const cwd = event.properties?.info?.directory || process.cwd();
11
+ if (sessionID) {
12
+ try {
13
+ const context = await automaticSessionStart('opencode', sessionID, cwd);
14
+ if (context) joining.set(sessionID, context);
15
+ } catch { /* startup remains bounded */ }
16
+ }
17
+ } else if (event.type === 'session.deleted') {
18
+ const sessionID = event.properties?.sessionID;
19
+ const cwd = event.properties?.info?.directory || process.cwd();
20
+ if (sessionID) {
21
+ joining.delete(sessionID);
22
+ await automaticSessionEnd('opencode', sessionID, cwd);
23
+ }
24
+ }
25
+ },
5
26
  'shell.env': async (input, output) => {
6
27
  if (input.sessionID) output.env.OPENCODE_SESSION_ID = input.sessionID;
7
28
  },
8
29
 
9
30
  'tool.execute.after': async (input, output) => {
10
31
  try {
11
- presentPendingAtBoundary(
32
+ const joined = joining.get(input.sessionID);
33
+ if (joined) {
34
+ joining.delete(input.sessionID);
35
+ output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${joined}`;
36
+ }
37
+ await presentPendingAtBoundary(
12
38
  input.sessionID,
13
39
  (context) => {
14
40
  output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${context}`;
@@ -1,4 +1,5 @@
1
1
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
2
+ import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
2
3
 
3
4
  export function pendingInbox(inbox) {
4
5
  return inbox.filter((item) => item.notifications?.length > 0);
@@ -16,17 +17,26 @@ export function renderPiInbox(inbox) {
16
17
 
17
18
  export default function squarePiExtension(pi) {
18
19
  let sessionId;
20
+ let sessionCwd;
21
+ let joiningContext;
19
22
  let previousSessionId;
20
23
  const present = (deliver) => sessionId === undefined ? undefined : presentPendingAtBoundary(sessionId, deliver);
21
24
 
22
25
  pi.on('session_start', async (_event, ctx) => {
23
26
  sessionId = ctx.sessionManager.getSessionId();
27
+ sessionCwd = ctx.cwd || process.cwd();
24
28
  previousSessionId = process.env.SQUARE_PI_SESSION_ID;
25
29
  process.env.SQUARE_PI_SESSION_ID = sessionId;
30
+ try { joiningContext = await automaticSessionStart('pi', sessionId, sessionCwd); } catch { joiningContext = undefined; }
26
31
  });
27
32
 
28
33
  pi.on('before_agent_start', async () => {
29
34
  try {
35
+ if (joiningContext) {
36
+ const context = joiningContext;
37
+ joiningContext = undefined;
38
+ return { message: { customType: 'square', content: context, display: true } };
39
+ }
30
40
  return present((context) => ({ message: { customType: 'square', content: context, display: true } }));
31
41
  } catch {
32
42
  return undefined;
@@ -34,10 +44,13 @@ export default function squarePiExtension(pi) {
34
44
  });
35
45
 
36
46
  pi.on('session_shutdown', async () => {
47
+ if (sessionId && sessionCwd) await automaticSessionEnd('pi', sessionId, sessionCwd);
37
48
  if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
38
49
  if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
39
50
  else process.env.SQUARE_PI_SESSION_ID = previousSessionId;
40
51
  }
41
52
  sessionId = undefined;
53
+ sessionCwd = undefined;
54
+ joiningContext = undefined;
42
55
  });
43
56
  }