@astrosheep/square 0.4.4 → 0.4.5
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.
- package/dist/artifact-store.d.ts +1 -1
- package/dist/artifact-store.js +32 -15
- package/dist/artifact.d.ts +1 -4
- package/dist/artifact.js +0 -28
- package/dist/attention-presentation.js +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/claude-boundary-state.d.ts +6 -6
- package/dist/claude-boundary-state.js +13 -13
- package/dist/claude-hook.js +25 -24
- package/dist/claude-mcp-context.d.ts +2 -2
- package/dist/claude-mcp-context.js +6 -6
- package/dist/cli/context.js +3 -6
- package/dist/cli/json.js +1 -1
- package/dist/cli/mcp-server-command.js +3 -3
- package/dist/cli/observation-commands.d.ts +2 -2
- package/dist/cli/observation-commands.js +23 -51
- package/dist/cli/perceive-presentation.d.ts +3 -0
- package/dist/cli/{catch-presentation.js → perceive-presentation.js} +3 -3
- package/dist/cli/registry.js +2 -2
- package/dist/codex-address.d.ts +3 -0
- package/dist/codex-address.js +7 -0
- package/dist/codex-hook.d.ts +1 -1
- package/dist/codex-hook.js +11 -8
- package/dist/codex-queue.d.ts +3 -2
- package/dist/codex-queue.js +8 -11
- package/dist/codex-watch.d.ts +6 -1
- package/dist/codex-watch.js +27 -29
- package/dist/delivery-ledger-sqlite.d.ts +25 -20
- package/dist/delivery-ledger-sqlite.js +76 -61
- package/dist/delivery-ledger.d.ts +24 -19
- package/dist/delivery-ledger.js +150 -81
- package/dist/delivery-lifetime.d.ts +2 -2
- package/dist/delivery-lifetime.js +6 -18
- package/dist/help.js +12 -12
- package/dist/hints.d.ts +2 -2
- package/dist/hints.js +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.d.ts +2 -2
- package/dist/list.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +8 -32
- package/dist/neutral.d.ts +5 -5
- package/dist/notifications.js +12 -6
- package/dist/operation-presentation.d.ts +8 -3
- package/dist/operation-presentation.js +50 -12
- package/dist/packages/agent-delivery/src/file-lock.js +4 -1
- package/dist/perceive-decisions.d.ts +28 -0
- package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
- package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
- package/dist/{catch-selection.js → perceive-selection.js} +3 -3
- package/dist/plugins/opencode/index.js +4 -4
- package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
- package/dist/plugins/opencode-shared/delivery.js +27 -21
- package/dist/plugins/opencode-v1/runtime.js +3 -3
- package/dist/ports.d.ts +0 -4
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +13 -13
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +11 -11
- package/dist/sqlite-square.d.ts +2 -4
- package/dist/sqlite-square.js +47 -54
- package/dist/sqlite-transaction.d.ts +2 -0
- package/dist/sqlite-transaction.js +25 -3
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-domain.js +1 -1
- package/dist/square-facade.d.ts +15 -13
- package/dist/square-location.js +1 -1
- package/dist/square-posting.d.ts +5 -5
- package/dist/square-posting.js +39 -38
- package/dist/square-tools.d.ts +15 -4
- package/dist/square-tools.js +35 -17
- package/dist/views.d.ts +7 -3
- package/dist/views.js +6 -3
- package/guides/architect.md +2 -2
- package/guides/change-waits.md +1 -1
- package/guides/codex-delivery.md +40 -35
- package/guides/delivery-primitives.md +22 -7
- package/guides/mcp.md +4 -2
- package/guides/operation-cancellation.md +11 -3
- package/guides/pi-native-delivery.md +1 -1
- package/guides/pi-tools.md +11 -6
- package/guides/receive-primitive.md +3 -3
- package/guides/square-api.md +15 -8
- package/package.json +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +10 -10
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/hooks/hooks.json +3 -0
- package/plugins/opencode/index.ts +4 -4
- package/plugins/opencode-shared/delivery.ts +27 -21
- package/plugins/opencode-v1/runtime.ts +3 -3
- package/plugins/pi/square-pi.js +18 -12
- package/skills/square/SKILL.md +10 -10
- package/dist/catch-decisions.d.ts +0 -28
- package/dist/cli/catch-presentation.d.ts +0 -3
- package/guides/mcp-connection-lifetime.md +0 -22
package/dist/delivery-ledger.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { validateOwner } from './neutral.js';
|
|
4
4
|
import { observeFileMetadata } from './file-changes.js';
|
|
5
5
|
import { LedgerStore } from './delivery-ledger-sqlite.js';
|
|
6
|
+
import { redactCurrentDiagnostic } from './diagnostic-redaction.js';
|
|
6
7
|
function nonempty(value) {
|
|
7
8
|
if (typeof value !== 'string' || value.length === 0)
|
|
8
9
|
throw new TypeError('Expected a nonempty string');
|
|
@@ -59,6 +60,8 @@ function encodeDetails(details) {
|
|
|
59
60
|
export class DeliveryLedger {
|
|
60
61
|
store;
|
|
61
62
|
leaseMs;
|
|
63
|
+
settlements = new Set();
|
|
64
|
+
retired = false;
|
|
62
65
|
constructor(store, leaseMs) {
|
|
63
66
|
this.store = store;
|
|
64
67
|
this.leaseMs = leaseMs;
|
|
@@ -69,79 +72,103 @@ export class DeliveryLedger {
|
|
|
69
72
|
throw new TypeError('Delivery location must be absolute');
|
|
70
73
|
const leaseMs = options.leaseMs ?? 30_000;
|
|
71
74
|
positive(leaseMs);
|
|
72
|
-
return new DeliveryLedger(await LedgerStore.open(location, options.clock ?? Date.now), leaseMs);
|
|
75
|
+
return new DeliveryLedger(await LedgerStore.open(location, options.clock ?? Date.now, options.signal), leaseMs);
|
|
73
76
|
}
|
|
74
|
-
close() {
|
|
75
|
-
|
|
77
|
+
close() {
|
|
78
|
+
this.retired = true;
|
|
79
|
+
this.store.retire();
|
|
80
|
+
return this.settlements.size ? Promise.resolve() : this.store.close();
|
|
81
|
+
}
|
|
82
|
+
retainSettlement(handle) {
|
|
83
|
+
this.settlements.add(handle);
|
|
84
|
+
void handle.then(() => {
|
|
85
|
+
this.settlements.delete(handle);
|
|
86
|
+
if (this.retired && !this.settlements.size)
|
|
87
|
+
void this.store.close().catch(() => this.diagnose('delivery-ledger-deferred-close-error'));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
diagnose(event, token, persistence) {
|
|
91
|
+
try {
|
|
92
|
+
const diagnostic = redactCurrentDiagnostic({ event, token, persistence });
|
|
93
|
+
try {
|
|
94
|
+
console.warn(diagnostic);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
process.stderr.write(`${JSON.stringify(diagnostic)}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { /* Diagnostics cannot prevent resource retirement. */ }
|
|
101
|
+
}
|
|
102
|
+
attach(input, signal) {
|
|
76
103
|
validateOwner(input.owner);
|
|
77
104
|
routes(input.routes);
|
|
78
105
|
if (input.expectedGeneration !== null)
|
|
79
106
|
nonempty(input.expectedGeneration);
|
|
80
|
-
return this.store.attach(input);
|
|
107
|
+
return this.store.attach(input, signal);
|
|
81
108
|
}
|
|
82
|
-
updateRoutes(input) {
|
|
109
|
+
updateRoutes(input, signal) {
|
|
83
110
|
validateOwner(input.owner);
|
|
84
111
|
nonempty(input.generation);
|
|
85
112
|
routes(input.routes);
|
|
86
|
-
return this.store.updateRoutes(input);
|
|
113
|
+
return this.store.updateRoutes(input, signal);
|
|
87
114
|
}
|
|
88
|
-
detach(input) { return this.lifecycle(input, 'detached'); }
|
|
89
|
-
markDeleted(input) { return this.lifecycle(input, 'deleted'); }
|
|
90
|
-
lifecycle(input, state) {
|
|
115
|
+
detach(input, signal) { return this.lifecycle(input, 'detached', signal); }
|
|
116
|
+
markDeleted(input, signal) { return this.lifecycle(input, 'deleted', signal); }
|
|
117
|
+
lifecycle(input, state, signal) {
|
|
91
118
|
validateOwner(input.owner);
|
|
92
119
|
nonempty(input.generation);
|
|
93
|
-
return this.store.lifecycle(input, state);
|
|
120
|
+
return this.store.lifecycle(input, state, signal);
|
|
94
121
|
}
|
|
95
|
-
session(owner) { validateOwner(owner); return this.store.session(owner); }
|
|
96
|
-
sessions() { return this.store.sessions(); }
|
|
97
|
-
pending(owner, options) {
|
|
122
|
+
session(owner, signal) { validateOwner(owner); return this.store.session(owner, signal); }
|
|
123
|
+
sessions(signal) { return this.store.sessions(signal); }
|
|
124
|
+
pending(owner, options, signal) {
|
|
98
125
|
validateOwner(owner);
|
|
99
126
|
nonempty(options.generation);
|
|
100
|
-
return this.store.pendingCards(owner, options.generation);
|
|
127
|
+
return this.store.pendingCards(owner, options.generation, signal);
|
|
101
128
|
}
|
|
102
|
-
handoffs(owner, options) {
|
|
129
|
+
handoffs(owner, options, signal) {
|
|
103
130
|
validateOwner(owner);
|
|
104
131
|
nonempty(options.generation);
|
|
105
|
-
return this.store.handoffs(owner, options.generation);
|
|
132
|
+
return this.store.handoffs(owner, options.generation, signal);
|
|
106
133
|
}
|
|
107
|
-
post(input) {
|
|
134
|
+
post(input, signal) {
|
|
108
135
|
validateOwner(input.owner);
|
|
109
136
|
nonempty(input.key);
|
|
110
137
|
if (typeof input.text !== 'string')
|
|
111
138
|
throw new TypeError('Expected card text');
|
|
112
|
-
return this.store.post({ ...input, details: input.details === undefined ? null : encodeDetails(input.details) });
|
|
139
|
+
return this.store.post({ ...input, details: input.details === undefined ? null : encodeDetails(input.details) }, signal);
|
|
113
140
|
}
|
|
114
|
-
suppress(input) {
|
|
141
|
+
suppress(input, signal) {
|
|
115
142
|
validateOwner(input.owner);
|
|
116
143
|
nonempty(input.key);
|
|
117
144
|
if (!['exact', 'prefix'].includes(input.scope) || input.expiresAt !== undefined && !Number.isFinite(input.expiresAt))
|
|
118
145
|
throw new TypeError('Invalid suppression');
|
|
119
|
-
return this.store.suppress(input);
|
|
146
|
+
return this.store.suppress(input, signal);
|
|
120
147
|
}
|
|
121
|
-
renewSuppression(input) {
|
|
148
|
+
renewSuppression(input, signal) {
|
|
122
149
|
validateOwner(input.owner);
|
|
123
150
|
nonempty(input.id);
|
|
124
151
|
if (!Number.isFinite(input.expiresAt))
|
|
125
152
|
throw new TypeError('Expected finite suppression expiry');
|
|
126
|
-
return this.store.renewSuppression(input);
|
|
153
|
+
return this.store.renewSuppression(input, signal);
|
|
127
154
|
}
|
|
128
|
-
releaseSuppression(input) {
|
|
155
|
+
releaseSuppression(input, signal) {
|
|
129
156
|
validateOwner(input.owner);
|
|
130
157
|
nonempty(input.id);
|
|
131
|
-
return this.store.releaseSuppression(input);
|
|
158
|
+
return this.store.releaseSuppression(input, signal);
|
|
132
159
|
}
|
|
133
|
-
cancel(input) {
|
|
160
|
+
cancel(input, signal) {
|
|
134
161
|
validateOwner(input.owner);
|
|
135
162
|
nonempty(input.generation);
|
|
136
|
-
return this.store.cancel(input);
|
|
163
|
+
return this.store.cancel(input, signal);
|
|
137
164
|
}
|
|
138
|
-
receipts(owner, options = {}) {
|
|
165
|
+
receipts(owner, options = {}, signal) {
|
|
139
166
|
validateOwner(owner);
|
|
140
167
|
if (options.keys)
|
|
141
168
|
options.keys.forEach(nonempty);
|
|
142
169
|
if (options.since !== undefined && !Number.isFinite(options.since))
|
|
143
170
|
throw new TypeError('Expected finite receipt time');
|
|
144
|
-
return this.store.receipts(owner, options);
|
|
171
|
+
return this.store.receipts(owner, options, signal);
|
|
145
172
|
}
|
|
146
173
|
async claimPresentation(owner, options) {
|
|
147
174
|
validateOwner(owner);
|
|
@@ -158,27 +185,27 @@ export class DeliveryLedger {
|
|
|
158
185
|
settle: outcome => {
|
|
159
186
|
if (!['presented', 'failed', 'unknown', 'released'].includes(outcome))
|
|
160
187
|
throw new TypeError('Invalid presentation outcome');
|
|
161
|
-
return this.store.settle(tokens, outcome);
|
|
188
|
+
return this.store.settle(tokens, outcome, AbortSignal.timeout(5000));
|
|
162
189
|
},
|
|
163
190
|
};
|
|
164
191
|
}
|
|
165
192
|
/** Resolve the original prepared handoff, including its stored route kind. */
|
|
166
|
-
handoff(token) {
|
|
193
|
+
handoff(token, signal) {
|
|
167
194
|
nonempty(token);
|
|
168
|
-
return this.store.handoff(token);
|
|
195
|
+
return this.store.handoff(token, signal);
|
|
169
196
|
}
|
|
170
|
-
guardHandoff(ref) { return this.store.guardHandoff(ref); }
|
|
171
|
-
acceptHandoff(ref) { return this.store.acceptHandoff(ref); }
|
|
197
|
+
guardHandoff(ref, signal) { return this.store.guardHandoff(ref, signal); }
|
|
198
|
+
acceptHandoff(ref, signal) { return this.store.acceptHandoff(ref, signal); }
|
|
172
199
|
/** Confirms the exact existing external effect; never sends or returns text. */
|
|
173
|
-
observePresentation(handoff, options) {
|
|
200
|
+
observePresentation(handoff, options, signal) {
|
|
174
201
|
if (typeof handoff === 'string')
|
|
175
202
|
nonempty(handoff);
|
|
176
|
-
return this.store.observePresentation(handoff, options);
|
|
203
|
+
return this.store.observePresentation(handoff, options, signal);
|
|
177
204
|
}
|
|
178
|
-
health(owner) {
|
|
205
|
+
health(owner, signal) {
|
|
179
206
|
if (owner !== undefined)
|
|
180
207
|
validateOwner(owner);
|
|
181
|
-
return this.store.health(owner);
|
|
208
|
+
return this.store.health(owner, signal);
|
|
182
209
|
}
|
|
183
210
|
/** Subscribe first; read the returned version, inspect state, then await changed(version,...). */
|
|
184
211
|
observe(owner) {
|
|
@@ -193,76 +220,118 @@ export class DeliveryLedger {
|
|
|
193
220
|
if (options.signal?.aborted)
|
|
194
221
|
return { outcome: 'not-invoked' };
|
|
195
222
|
nonempty(transport.kind);
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
223
|
+
const lifetime = Promise.withResolvers();
|
|
224
|
+
this.retainSettlement(lifetime.promise);
|
|
225
|
+
// One deadline includes acquisition, validation, preparation and the external effect.
|
|
226
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
200
227
|
const controller = new AbortController();
|
|
201
228
|
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
202
|
-
const
|
|
203
|
-
let
|
|
204
|
-
|
|
229
|
+
const timer = setTimeout(() => controller.abort(new Error('Delivery deadline elapsed')), options.timeoutMs);
|
|
230
|
+
let onAbort;
|
|
231
|
+
try {
|
|
232
|
+
let claim;
|
|
205
233
|
try {
|
|
206
|
-
|
|
234
|
+
claim = await this.store.claimHandoff(owner, key, options.generation, transport.kind, Math.max(this.leaseMs, options.timeoutMs), signal);
|
|
207
235
|
}
|
|
208
236
|
catch (error) {
|
|
209
|
-
|
|
237
|
+
if (signal.aborted && error === signal.reason)
|
|
238
|
+
return { outcome: 'not-invoked' };
|
|
210
239
|
throw error;
|
|
211
240
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
if (!claim)
|
|
242
|
+
return { outcome: 'not-invoked' };
|
|
243
|
+
const token = claim.attempt.token;
|
|
244
|
+
let persistenceFailure;
|
|
245
|
+
const persist = async (operation) => {
|
|
246
|
+
try {
|
|
247
|
+
return await operation;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (!signal.aborted || error !== signal.reason)
|
|
251
|
+
persistenceFailure = { error };
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const current = () => persist(this.store.current([token], signal, claim.route));
|
|
256
|
+
const finish = async (outcome) => {
|
|
257
|
+
if (!['accepted', 'presented', 'failed', 'unknown', 'not-capable', 'released'].includes(outcome))
|
|
258
|
+
throw new TypeError('Invalid transport outcome');
|
|
259
|
+
const recorded = await this.store.settle([token], outcome === 'not-capable' ? 'released' : outcome, AbortSignal.timeout(5000));
|
|
260
|
+
return { outcome, token, persistence: recorded ? 'recorded' : 'not-recorded' };
|
|
261
|
+
};
|
|
262
|
+
let eligible;
|
|
263
|
+
try {
|
|
264
|
+
eligible = await current();
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
const released = await finish('released');
|
|
268
|
+
if (signal.aborted && error === signal.reason)
|
|
269
|
+
return released;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
if (this.retired || !eligible || signal.aborted || Date.now() >= deadline)
|
|
273
|
+
return await finish('released');
|
|
227
274
|
const interrupted = new Promise(resolve => {
|
|
228
275
|
onAbort = () => resolve('unknown');
|
|
229
276
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
230
|
-
|
|
277
|
+
if (signal.aborted)
|
|
278
|
+
onAbort();
|
|
231
279
|
});
|
|
232
|
-
//
|
|
233
|
-
const effect = (async () => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
beforeSend: current,
|
|
238
|
-
prepare: value => { correlation(value); return persist(this.store.prepare(token, value, signal)); },
|
|
239
|
-
});
|
|
240
|
-
})().catch(() => 'unknown');
|
|
280
|
+
// No await separates the final fence from invocation. Adapters fence their own awaits.
|
|
281
|
+
const effect = (async () => transport.send(claim.card, claim.route.address, { token, deadline, signal,
|
|
282
|
+
beforeSend: current,
|
|
283
|
+
prepare: value => { correlation(value); return persist(this.store.prepare(token, value, signal)); },
|
|
284
|
+
}))().catch(() => 'unknown');
|
|
241
285
|
const first = await Promise.race([
|
|
242
286
|
effect.then(outcome => ({ outcome, interrupted: false })),
|
|
243
287
|
interrupted.then(outcome => ({ outcome, interrupted: true })),
|
|
244
288
|
]);
|
|
245
|
-
if (!first.interrupted)
|
|
246
|
-
|
|
289
|
+
if (!first.interrupted) {
|
|
290
|
+
const result = await finish(first.outcome);
|
|
291
|
+
if (persistenceFailure)
|
|
292
|
+
throw persistenceFailure.error;
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
// Register the effect followup even if the initial unknown write fails. Its
|
|
296
|
+
// captured connection survives caller close; no new send authority is granted.
|
|
247
297
|
const unknown = finish('unknown');
|
|
248
|
-
|
|
249
|
-
const
|
|
298
|
+
let unknownRecorded = false;
|
|
299
|
+
const initial = unknown.then(result => { unknownRecorded = result.persistence === 'recorded'; return result; });
|
|
300
|
+
const lateSettlement = initial.catch(() => undefined).then(async () => {
|
|
250
301
|
const outcome = await effect;
|
|
251
302
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
303
|
+
// A late negative cannot release an interrupted effect whose unknown
|
|
304
|
+
// evidence failed to persist. Keep conservative custody in that case.
|
|
305
|
+
const negative = ['failed', 'not-capable', 'released'].includes(outcome);
|
|
306
|
+
const result = await finish(!unknownRecorded && negative ? 'unknown' : outcome);
|
|
307
|
+
return { outcome, persistence: { status: !unknownRecorded && negative ? 'not-recorded' : result.persistence } };
|
|
254
308
|
}
|
|
255
309
|
catch (error) {
|
|
256
310
|
return { outcome, persistence: { status: 'error', error } };
|
|
257
311
|
}
|
|
258
|
-
}
|
|
259
|
-
|
|
312
|
+
});
|
|
313
|
+
// The ledger owns retention on rejection too: callers cannot track a result
|
|
314
|
+
// they never received. Surface the original error while retaining late evidence.
|
|
315
|
+
this.retainSettlement(lateSettlement);
|
|
316
|
+
try {
|
|
317
|
+
const result = await initial;
|
|
318
|
+
if (persistenceFailure)
|
|
319
|
+
throw persistenceFailure.error;
|
|
320
|
+
return { ...result, lateSettlement };
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
void lateSettlement.then(result => {
|
|
324
|
+
if (result.persistence.status !== 'recorded')
|
|
325
|
+
this.diagnose('delivery-late-settlement', token, result.persistence.status);
|
|
326
|
+
});
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
260
329
|
}
|
|
261
330
|
finally {
|
|
262
|
-
|
|
263
|
-
clearTimeout(timer);
|
|
331
|
+
clearTimeout(timer);
|
|
264
332
|
if (onAbort)
|
|
265
333
|
signal.removeEventListener('abort', onAbort);
|
|
334
|
+
lifetime.resolve();
|
|
266
335
|
}
|
|
267
336
|
}
|
|
268
337
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DeliveryLedger, HandoffResult } from './delivery-ledger.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Report late evidence; DeliveryLedger alone owns connection retirement. */
|
|
3
3
|
export declare function retainDeliveryLedger(ledger: DeliveryLedger): {
|
|
4
4
|
track(result: HandoffResult): void;
|
|
5
|
-
close()
|
|
5
|
+
close: () => Promise<void>;
|
|
6
6
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { redactCurrentDiagnostic } from './diagnostic-redaction.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Report late evidence; DeliveryLedger alone owns connection retirement. */
|
|
3
3
|
export function retainDeliveryLedger(ledger) {
|
|
4
|
-
const
|
|
5
|
-
let
|
|
4
|
+
const tracked = new WeakSet();
|
|
5
|
+
let closing;
|
|
6
6
|
const diagnose = (event, result, detail) => {
|
|
7
7
|
try {
|
|
8
8
|
const diagnostic = redactCurrentDiagnostic({ event, token: result?.token, outcome: result?.outcome, ...detail });
|
|
@@ -15,31 +15,19 @@ export function retainDeliveryLedger(ledger) {
|
|
|
15
15
|
}
|
|
16
16
|
catch { /* Closed diagnostic sinks cannot prevent settlement or resource retirement. */ }
|
|
17
17
|
};
|
|
18
|
-
const closeOnce = () => closing ??= ledger.close();
|
|
19
|
-
const settled = (handle) => {
|
|
20
|
-
pending.delete(handle);
|
|
21
|
-
if (retired && !pending.size)
|
|
22
|
-
void closeOnce().catch(() => diagnose('delivery-ledger-deferred-close-error'));
|
|
23
|
-
};
|
|
24
18
|
return {
|
|
25
19
|
track(result) {
|
|
26
20
|
const handle = result.lateSettlement;
|
|
27
|
-
if (!handle ||
|
|
21
|
+
if (!handle || tracked.has(handle))
|
|
28
22
|
return;
|
|
29
|
-
|
|
23
|
+
tracked.add(handle);
|
|
30
24
|
void handle.then(late => {
|
|
31
25
|
if (late.persistence.status !== 'recorded')
|
|
32
26
|
diagnose('delivery-late-settlement', result, { lateOutcome: late.outcome, persistence: late.persistence.status });
|
|
33
|
-
settled(handle);
|
|
34
27
|
}, () => {
|
|
35
28
|
diagnose('delivery-late-settlement-rejected', result);
|
|
36
|
-
settled(handle);
|
|
37
29
|
});
|
|
38
30
|
},
|
|
39
|
-
|
|
40
|
-
retired = true;
|
|
41
|
-
if (!pending.size)
|
|
42
|
-
await closeOnce();
|
|
43
|
-
},
|
|
31
|
+
close: () => closing ??= ledger.close(),
|
|
44
32
|
};
|
|
45
33
|
}
|
package/dist/help.js
CHANGED
|
@@ -2,14 +2,14 @@ import { JSON_COMMANDS } from './cli/json.js';
|
|
|
2
2
|
const COMMANDS = [
|
|
3
3
|
{ names: ['help'], usage: 'help [command]', summary: 'Show the command index or help for one command.' },
|
|
4
4
|
{ names: ['version'], usage: 'version', summary: 'Print the installed version.', hiddenFromIndex: true },
|
|
5
|
-
{ names: ['mcp-server'], usage: 'mcp-server', summary: 'Start the MCP stdio server.',
|
|
5
|
+
{ names: ['mcp-server'], usage: 'mcp-server', summary: 'Start the MCP stdio server.', hiddenFromIndex: true, details: ['Use list to find nearby squares; single-square tools require location on each call.', 'Participant tools accept as on each call; join requires an explicit name.', 'Session identity comes from request _meta.threadId (Codex) or _meta["ai.opencode/sessionID"] (OpenCode), falling back to the startup environment.'] },
|
|
6
6
|
{
|
|
7
7
|
names: ['build'], usage: 'build [--cap <N|unlimited>] [--template <name>] [--throttle N] [-f] < body.md', usesSquare: true, group: 'host',
|
|
8
8
|
summary: 'Create a square from Markdown on stdin.',
|
|
9
9
|
details: ['Options:', ' --cap <N|unlimited> Set a per-participant activity cap (default unlimited).', ' --template <name> Append a packaged activity guide.', ' --throttle <N> Allow at most N public activities per minute.', ' -f, --force Replace an existing artifact.'],
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
|
-
names: ['list', 'ls'], usage: '{command} [--depth N] [--limit N] [--after path]', summary: 'List nearby squares
|
|
12
|
+
names: ['list', 'ls'], usage: '{command} [--depth N] [--limit N] [--after path]', summary: 'List nearby squares and your joined names with waiting activity counts.', group: 'host',
|
|
13
13
|
details: ['Options:', ' --depth <N> Descend through at most N directory levels (default 4; maximum 16; 0 scans only the current directory).', ' --limit <N> Show one page (default 20, maximum 100).', ' --after <path> Resume after a relative path from a previous page.'],
|
|
14
14
|
},
|
|
15
15
|
{
|
|
@@ -31,7 +31,7 @@ const COMMANDS = [
|
|
|
31
31
|
' Bare activities are delivered only to participants listening to the sender.',
|
|
32
32
|
'',
|
|
33
33
|
'Options:',
|
|
34
|
-
' -f, --force Express without first
|
|
34
|
+
' -f, --force Express without first perceiving unread activity or satisfying attention etiquette.',
|
|
35
35
|
' --no-wait If throttled, save a draft and return instead of waiting.',
|
|
36
36
|
' --metadata <json> Attach a bounded JSON object (at most 16 KiB UTF-8 and 256 nesting levels); it may be the only content.',
|
|
37
37
|
' Metadata is returned in structured activity results but is not shown in human text or searchable.',
|
|
@@ -42,14 +42,14 @@ const COMMANDS = [
|
|
|
42
42
|
{ names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: 'Stop hearing one participant\'s future activity, except bells.' },
|
|
43
43
|
{ names: ['listening'], usage: '--as <name> listening', usesSquare: true, group: 'participant', summary: 'Show who you are turned toward.', details: ['Listening applies to future activity. What you could hear when an activity landed does not change afterward.'] },
|
|
44
44
|
{
|
|
45
|
-
names: ['
|
|
46
|
-
summary: '
|
|
47
|
-
details: ['Modes:', ' --id <activity-id>
|
|
45
|
+
names: ['perceive'], usage: '--as <name> perceive [--id <activity-id>] [--now | --idle <duration>] [--from <names>] [--mention] [--limit <count>] [--replace]', usesSquare: true, group: 'participant',
|
|
46
|
+
summary: 'Perceive directed conversation since you last looked.',
|
|
47
|
+
details: ['Modes:', ' --id <activity-id> Perceive exactly one activity addressed to you, with its full body; implies --now.', ' Marks only this activity seen; repeats return it again. No filters, limit, idle or replace.', ' history stays read-only; perceive --id consumes the selected activity.', ' --now Take in activity immediately; bare perceive already does this.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Attention:', ' Mentions arrive when you are addressed; bells arrive for everyone; bare says require listen.', ' listen and ignore are future-only and fixed when each say lands.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention Match direct attention for your current participant (and bells).', ' --limit <count> Return one page (default 10, maximum 100).', '', 'Recovery:', ' --replace Replace another active perceive for this participant.'],
|
|
48
48
|
},
|
|
49
49
|
{ names: ['done'], usage: '--as <name> done', usesSquare: true, group: 'participant', summary: 'Step out of the square.', details: ['done takes no message. Use express before leaving to send one.'] },
|
|
50
50
|
{
|
|
51
51
|
names: ['stream'], usage: 'stream [--ndjson [--for <name>] [--last <N> | --after <id>]]', usesSquare: true, hiddenFromIndex: true,
|
|
52
|
-
summary: 'Follow activity without marking it
|
|
52
|
+
summary: 'Follow activity without marking it perceived.',
|
|
53
53
|
details: ['Options:', ' --ndjson Emit one JSON event per line.', ' --for <name> With --ndjson, emit notifications for one participant.', ' --last <N> Emit the last N eligible activities first (default 10, maximum 100).', ' --after <id> Resume after a canonical activity id (act/12).'],
|
|
54
54
|
},
|
|
55
55
|
{
|
|
@@ -64,7 +64,7 @@ const COMMANDS = [
|
|
|
64
64
|
{ names: ['claude-watch'], usage: '{command}', summary: 'Observe pending attention from a native async Stop hook.', hiddenFromIndex: true },
|
|
65
65
|
{
|
|
66
66
|
names: ['history'], usage: 'history [filters] [output]', usesSquare: true, group: 'participant',
|
|
67
|
-
summary: 'Read or search the archive without changing what you have
|
|
67
|
+
summary: 'Read or search the archive without changing what you have perceived.',
|
|
68
68
|
details: ['Optional --as is accepted but does not change the archive or claim a name.', '', 'Filters:', ' --from <names> Match activities from participants.', ' --since <time> Match activities after a time.', ' --grep <regex> | --fixed <s> Search activity ids, participants, and original bodies.', ' --mention <name> Match direct attention for a participant.', ' --at <ids> Center on comma-separated activity ids; may repeat.', ' -B, -A, -C <N> Set non-negative context around every --at coordinate.', ' --before <id> Read the page immediately before an activity.', ' --after <id> Read the page immediately after an activity.', '', 'Results:', ' --limit <N> Page size (default 10, maximum 100).', ' --order <asc|desc> Set display order (default oldest first).', '', 'Output:', ' --no-truncate --json --format <fields>', ' One result shows its full body; multiple results use previews.', ' --no-truncate shows every original body.'],
|
|
69
69
|
},
|
|
70
70
|
{ names: ['status'], usage: '[--as <name>] status', usesSquare: true, group: 'participant', summary: 'Show who is present and what happened most recently.' },
|
|
@@ -101,7 +101,7 @@ function isHelpFlag(value) {
|
|
|
101
101
|
}
|
|
102
102
|
export function renderGlobalHelp() {
|
|
103
103
|
const groups = [
|
|
104
|
-
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', '
|
|
104
|
+
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'perceive', 'history', 'status', 'done'] },
|
|
105
105
|
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
|
|
106
106
|
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'] },
|
|
107
107
|
];
|
|
@@ -115,9 +115,9 @@ export function renderGlobalHelp() {
|
|
|
115
115
|
]);
|
|
116
116
|
return [
|
|
117
117
|
'Usage: square [--location <square>] [--as <name>] [--owner <id>] <command> [args...]',
|
|
118
|
-
'
|
|
119
|
-
'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use
|
|
120
|
-
'Output: --json on join, express,
|
|
118
|
+
'Location: square-accessing commands require --location <square>. Use square list to find nearby squares and your joined names; use --as to select a participant.',
|
|
119
|
+
'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use perceive --idle.',
|
|
120
|
+
'Output: --json on join, express, perceive, history, listen, ignore, unlisten, listening, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
|
|
121
121
|
'History emits one JSON document; stream --ndjson emits one SDK record per line.',
|
|
122
122
|
'',
|
|
123
123
|
...commandLines,
|
package/dist/hints.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** First activity, then every fifth. */
|
|
2
2
|
export declare function expressHint(ownActivityCount: number): string | undefined;
|
|
3
|
-
/** First
|
|
4
|
-
export declare function
|
|
3
|
+
/** First perceived activity, then each time the perceived total crosses another ten. */
|
|
4
|
+
export declare function perceiveHint(perceivedBefore: number, perceivedAfter: number): string | undefined;
|
package/dist/hints.js
CHANGED
|
@@ -4,9 +4,9 @@ const EXPRESS_HINTS = [
|
|
|
4
4
|
"answer someone's actual words — they're standing right there",
|
|
5
5
|
"say the half-shaped thing — that's what the square is for",
|
|
6
6
|
];
|
|
7
|
-
const
|
|
7
|
+
const PERCEIVE_HINTS = [
|
|
8
8
|
'want to follow one voice? listen to them; unlisten once you have heard enough',
|
|
9
|
-
"nothing to add? quiet is fine —
|
|
9
|
+
"nothing to add? quiet is fine — perceiving is listening too",
|
|
10
10
|
"no need to reply just to say you’ve read a message — Square already tracks that",
|
|
11
11
|
'reply to an activity to reach the person who spoke',
|
|
12
12
|
];
|
|
@@ -16,10 +16,10 @@ export function expressHint(ownActivityCount) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
return EXPRESS_HINTS[Math.floor(ownActivityCount / 5) % EXPRESS_HINTS.length];
|
|
18
18
|
}
|
|
19
|
-
/** First
|
|
20
|
-
export function
|
|
21
|
-
if (
|
|
19
|
+
/** First perceived activity, then each time the perceived total crosses another ten. */
|
|
20
|
+
export function perceiveHint(perceivedBefore, perceivedAfter) {
|
|
21
|
+
if (perceivedAfter <= perceivedBefore)
|
|
22
22
|
return undefined;
|
|
23
|
-
const crossed =
|
|
24
|
-
return crossed ?
|
|
23
|
+
const crossed = perceivedBefore === 0 || Math.floor(perceivedAfter / 10) > Math.floor(perceivedBefore / 10);
|
|
24
|
+
return crossed ? PERCEIVE_HINTS[Math.floor(perceivedAfter / 10) % PERCEIVE_HINTS.length] : undefined;
|
|
25
25
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Square, squareLocation, squareClock,
|
|
1
|
+
export { Square, squareLocation, squareClock, discover, inbox, diagnoseSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
3
|
export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
4
|
export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Square, squareLocation, squareClock,
|
|
1
|
+
export { Square, squareLocation, squareClock, discover, inbox, diagnoseSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
3
|
export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
4
|
export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
|
package/dist/list.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { DiscoveryResult, DiscoveryOptions,
|
|
1
|
+
import type { DiscoveryResult, DiscoveryOptions, OperationControl } from './square-facade.js';
|
|
2
2
|
export declare const DEFAULT_LIST_DEPTH = 4;
|
|
3
3
|
export declare const MAX_LIST_DEPTH = 16;
|
|
4
4
|
export declare const DEFAULT_LIST_LIMIT = 20;
|
|
5
5
|
export declare const MAX_LIST_LIMIT = 100;
|
|
6
6
|
/** Bounded deterministic discovery for the public resource API. */
|
|
7
|
-
export declare function discoverSquares(root: string, options?: DiscoveryOptions, control?: OperationControl
|
|
7
|
+
export declare function discoverSquares(root: string, options?: DiscoveryOptions, control?: OperationControl): Promise<DiscoveryResult>;
|
package/dist/list.js
CHANGED
|
@@ -18,7 +18,7 @@ async function readSquareListItem(filePath, root, options, control) {
|
|
|
18
18
|
const artifact = createArtifactPort(filePath, undefined, control?.signal);
|
|
19
19
|
let projection;
|
|
20
20
|
try {
|
|
21
|
-
projection = (await artifact.read(listPresentation, control?.signal)).value;
|
|
21
|
+
projection = (await artifact.read(facts => listPresentation(facts, options.owner), control?.signal)).value;
|
|
22
22
|
}
|
|
23
23
|
finally {
|
|
24
24
|
await artifact.close();
|
|
@@ -29,6 +29,7 @@ async function readSquareListItem(filePath, root, options, control) {
|
|
|
29
29
|
context: [...projection.context].map((line) => line.trim()).filter(Boolean),
|
|
30
30
|
participants: [...projection.participants],
|
|
31
31
|
activities: projection.activities,
|
|
32
|
+
...(projection.joined === undefined ? {} : { joined: projection.joined }),
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
catch (error) {
|
|
@@ -38,7 +39,7 @@ async function readSquareListItem(filePath, root, options, control) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
/** Bounded deterministic discovery for the public resource API. */
|
|
41
|
-
export async function discoverSquares(root, options = {}, control
|
|
42
|
+
export async function discoverSquares(root, options = {}, control) {
|
|
42
43
|
const depth = options.depth ?? DEFAULT_LIST_DEPTH;
|
|
43
44
|
const limit = options.limit ?? DEFAULT_LIST_LIMIT;
|
|
44
45
|
if (!Number.isSafeInteger(depth) || depth < 0 || depth > MAX_LIST_DEPTH
|
|
@@ -74,7 +75,7 @@ export async function discoverSquares(root, options = {}, control, openOptions =
|
|
|
74
75
|
await walk(fullPath, level + 1);
|
|
75
76
|
}
|
|
76
77
|
else if (entry.isFile()) {
|
|
77
|
-
const item = await readSquareListItem(fullPath, root,
|
|
78
|
+
const item = await readSquareListItem(fullPath, root, options, control);
|
|
78
79
|
if (item !== null)
|
|
79
80
|
items.push(item);
|
|
80
81
|
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { type Owner } from './square-api.js';
|
|
3
3
|
import type { SquareAtInput } from './square-facade.js';
|
|
4
4
|
import type { WakeAddress } from './model.js';
|
|
5
|
-
/**
|
|
6
|
-
export interface SquareMcpOptions extends Omit<SquareAtInput, '
|
|
5
|
+
/** Each tool call chooses its square and owns its connection lifetime. */
|
|
6
|
+
export interface SquareMcpOptions extends Omit<SquareAtInput, 'path'> {
|
|
7
7
|
readonly owner?: Owner;
|
|
8
8
|
readonly wakeAddress?: WakeAddress;
|
|
9
9
|
}
|