@astrosheep/square 0.3.93 → 0.3.95
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/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +3 -3
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +5 -8
- package/dist/catch-decisions.js +45 -25
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -27
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +28 -16
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +3 -3
- package/dist/cli/registry.js +7 -6
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +30 -44
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -16
- package/dist/decisions.js +9 -33
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +76 -101
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +7 -6
- package/dist/help.js +14 -14
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +12 -21
- package/dist/model.js +5 -8
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +8 -5
- package/dist/operation-presentation.d.ts +6 -6
- package/dist/operation-presentation.js +55 -58
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +18 -47
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +51 -61
- package/dist/presentation.d.ts +7 -17
- package/dist/presentation.js +46 -91
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +2 -17
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -23
- package/dist/sqlite-square.js +162 -211
- package/dist/square-api.d.ts +16 -11
- package/dist/square-api.js +27 -23
- package/dist/square-core.d.ts +0 -14
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +65 -52
- package/dist/square-facade.d.ts +47 -105
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +4 -1
- package/dist/square-migration.js +93 -38
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +9 -3
- package/dist/square-sqlite.js +139 -114
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +21 -61
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +23 -13
- package/dist/views.js +47 -53
- package/dist/wakes.d.ts +1 -5
- package/dist/wakes.js +4 -6
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +6 -6
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +148 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +34 -5
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +22 -28
- package/guides/square-api.md +314 -91
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/skills/square/SKILL.md +2 -6
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { formatHardCap } from '../model.js';
|
|
2
2
|
import { commandPrefix, participantCommandPrefix, withPathOutput, } from '../presentation.js';
|
|
3
3
|
import { renderJoinResult, renderLifecycleResult, renderListenerChange, renderListeningResult, } from '../operation-presentation.js';
|
|
4
|
-
import { Square } from '../square-api.js';
|
|
4
|
+
import { Square, requireOwner, requireJoinName, selectOwnSeat } from '../square-api.js';
|
|
5
5
|
import { executeExpression } from './express-presentation.js';
|
|
6
|
-
import {
|
|
6
|
+
import { jsonResult } from './json.js';
|
|
7
|
+
import { squareInput, withSquare, fail, parseBoundedLimit, parseHardCap, parsePositiveInteger, readStdin, requireSquarePath, requireValue, resolveBody, usage, commandIdentity, } from './context.js';
|
|
7
8
|
const JOIN_DEFAULT_LAST = 10;
|
|
8
9
|
const JOIN_MAX_LAST = 100;
|
|
9
10
|
export const buildCommand = {
|
|
@@ -53,7 +54,7 @@ export const buildCommand = {
|
|
|
53
54
|
const cap = data.hardCap === null ? 'unlimited' : formatHardCap(data.hardCap);
|
|
54
55
|
const throttle = data.throttlePerMinute === undefined ? [] : [` · throttle ${data.throttlePerMinute}/min`];
|
|
55
56
|
return withPathOutput(square.location, ['· the square stands ready', ` · cap ${cap}`, ...throttle].join('\n'), {
|
|
56
|
-
participantCount: 0,
|
|
57
|
+
participantCount: 0,
|
|
57
58
|
});
|
|
58
59
|
},
|
|
59
60
|
present: (result) => process.stdout.write(result),
|
|
@@ -69,14 +70,17 @@ export const joinCommand = {
|
|
|
69
70
|
}
|
|
70
71
|
else if (argv[index] === '--kick')
|
|
71
72
|
kick = true;
|
|
72
|
-
else
|
|
73
|
+
else if (argv[index] !== '--json')
|
|
73
74
|
usage(context.command);
|
|
74
75
|
}
|
|
75
76
|
return { lastN, kick };
|
|
76
77
|
},
|
|
77
78
|
async execute(intent, context) {
|
|
78
|
-
const
|
|
79
|
-
|
|
79
|
+
const { owner, wakeAddress } = commandIdentity(context.env, context.owner);
|
|
80
|
+
const data = await withSquare(context, (square) => square.join(requireJoinName(context.name), {
|
|
81
|
+
owner: requireOwner(owner), wakeAddress, takeover: intent.kick, last: intent.lastN, ...(context.json ? {} : { observe: true }),
|
|
82
|
+
}));
|
|
83
|
+
return context.json ? jsonResult(data) : renderJoinResult(data, context.owner ?? context.env.SQUARE_OWNER);
|
|
80
84
|
},
|
|
81
85
|
present: (result) => process.stdout.write(result),
|
|
82
86
|
};
|
|
@@ -92,6 +96,8 @@ function parseActivity(argv, context) {
|
|
|
92
96
|
const bodyArgs = [];
|
|
93
97
|
for (let index = 0; index < argv.length; index++) {
|
|
94
98
|
const argument = argv[index];
|
|
99
|
+
if (argument === '--json')
|
|
100
|
+
continue;
|
|
95
101
|
if (argument === '-f' || argument === '--force')
|
|
96
102
|
force = true;
|
|
97
103
|
else if (argument === '--no-wait')
|
|
@@ -141,23 +147,25 @@ export const expressCommand = {
|
|
|
141
147
|
await executeExpression((control) => Square.at(squareInput(context), control), requireSquarePath(context), context.name, rawInput, {
|
|
142
148
|
force: intent.force, noWait: intent.noWait, noMention: intent.noMention,
|
|
143
149
|
mentions: intent.mentions, reach: intent.reach, reply: intent.reply, metadata: intent.metadata,
|
|
150
|
+
recoveryOwner: context.owner ?? context.env.SQUARE_OWNER,
|
|
144
151
|
metadataJson: intent.metadataJson, bodyProvided: intent.activity !== undefined,
|
|
145
|
-
});
|
|
152
|
+
}, context.json, () => commandIdentity(context.env, context.owner).owner);
|
|
146
153
|
},
|
|
147
154
|
present: () => { },
|
|
148
155
|
};
|
|
149
156
|
function parseListener(argv, context, targetRequired) {
|
|
150
|
-
|
|
157
|
+
const args = argv.filter((arg) => arg !== '--json');
|
|
158
|
+
if (targetRequired ? args.length !== 1 : args.length !== 0)
|
|
151
159
|
usage(context.command);
|
|
152
|
-
return targetRequired ? { target:
|
|
160
|
+
return targetRequired ? { target: args[0] } : {};
|
|
153
161
|
}
|
|
154
162
|
function listenerCommand(verb) {
|
|
155
163
|
return {
|
|
156
164
|
parse(argv, context) { return parseListener(argv, context, true); },
|
|
157
165
|
async execute(intent, context) {
|
|
158
166
|
const squarePath = requireSquarePath(context);
|
|
159
|
-
const data = await withSquare(context, async (square) => (await square
|
|
160
|
-
return renderListenerChange(squarePath, verb, data);
|
|
167
|
+
const data = await withSquare(context, async (square) => (await selectOwnSeat(square, commandIdentity(context.env, context.owner).owner, context.name))[verb](intent.target, context.json ? {} : { observe: true }));
|
|
168
|
+
return context.json ? jsonResult(data) : renderListenerChange(squarePath, verb, data);
|
|
161
169
|
},
|
|
162
170
|
present: (result) => process.stdout.write(result),
|
|
163
171
|
};
|
|
@@ -169,50 +177,28 @@ export const listeningCommand = {
|
|
|
169
177
|
parse(argv, context) { return parseListener(argv, context, false); },
|
|
170
178
|
async execute(_intent, context) {
|
|
171
179
|
const squarePath = requireSquarePath(context);
|
|
172
|
-
const data = await withSquare(context, async (square) => (await square
|
|
173
|
-
return renderListeningResult(squarePath, data);
|
|
180
|
+
const data = await withSquare(context, async (square) => (await selectOwnSeat(square, commandIdentity(context.env, context.owner).owner, context.name)).listening(context.json ? {} : { observe: true }));
|
|
181
|
+
return context.json ? jsonResult(data) : renderListeningResult(squarePath, data);
|
|
174
182
|
},
|
|
175
183
|
present: (result) => process.stdout.write(result),
|
|
176
184
|
};
|
|
177
185
|
export const doneCommand = {
|
|
178
186
|
parse(argv, context) {
|
|
179
|
-
|
|
187
|
+
const args = argv.filter((arg) => arg !== '--json');
|
|
188
|
+
if (args.length > 1)
|
|
180
189
|
usage(context.command);
|
|
181
|
-
return { body:
|
|
190
|
+
return { body: args[0] };
|
|
182
191
|
},
|
|
183
192
|
async execute(intent, context) {
|
|
184
193
|
const body = intent.body === '-' || ((intent.body === undefined || intent.body === '') && !process.stdin.isTTY)
|
|
185
194
|
? await readStdin() : intent.body;
|
|
186
195
|
const squarePath = requireSquarePath(context);
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
parse(argv, context) {
|
|
194
|
-
if (argv.length > 1)
|
|
195
|
-
usage(context.command);
|
|
196
|
-
return { body: argv[0] };
|
|
197
|
-
},
|
|
198
|
-
async execute(intent, context) {
|
|
199
|
-
const body = (await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim();
|
|
200
|
-
const squarePath = requireSquarePath(context);
|
|
201
|
-
const result = await withSquare(context, async (square) => (await square.participant(context.name)).hold(body, { observe: true }));
|
|
202
|
-
return renderLifecycleResult(squarePath, 'hold', result);
|
|
203
|
-
},
|
|
204
|
-
present: (result) => process.stdout.write(result),
|
|
205
|
-
};
|
|
206
|
-
export const resumeCommand = {
|
|
207
|
-
parse(argv, context) {
|
|
208
|
-
if (argv.length !== 0)
|
|
209
|
-
usage(context.command);
|
|
210
|
-
return {};
|
|
211
|
-
},
|
|
212
|
-
async execute(_intent, context) {
|
|
213
|
-
const squarePath = requireSquarePath(context);
|
|
214
|
-
const result = await withSquare(context, async (square) => (await square.participant(context.name)).resume({ observe: true }));
|
|
215
|
-
return renderLifecycleResult(squarePath, 'resume', result);
|
|
196
|
+
const result = await withSquare(context, async (square) => {
|
|
197
|
+
const participant = await selectOwnSeat(square, commandIdentity(context.env, context.owner).owner, context.name);
|
|
198
|
+
const options = context.json ? {} : { observe: true };
|
|
199
|
+
return participant.done(body, options);
|
|
200
|
+
});
|
|
201
|
+
return context.json ? jsonResult(result) : renderLifecycleResult(squarePath, result);
|
|
216
202
|
},
|
|
217
203
|
present: (result) => process.stdout.write(result),
|
|
218
204
|
};
|
package/dist/codex-hook.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Owner } from './owner.js';
|
|
2
|
+
import type { PendingMembership } from './model.js';
|
|
2
3
|
import type { WakeTransports } from './notifications.js';
|
|
3
4
|
export interface CodexHookInput {
|
|
4
5
|
session_id?: unknown;
|
|
@@ -8,6 +9,6 @@ export interface CodexHookInput {
|
|
|
8
9
|
}
|
|
9
10
|
export type HookTrace = (line: string) => void;
|
|
10
11
|
export declare const CODEX_HOOK_BUDGET_MS = 4500;
|
|
11
|
-
export declare function codexHookResponse(input: CodexHookInput, lookup?: (identity:
|
|
12
|
+
export declare function codexHookResponse(input: CodexHookInput, lookup?: (identity: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<PendingMembership[]> | PendingMembership[], env?: NodeJS.ProcessEnv, deliveryTransports?: WakeTransports, trace?: HookTrace): Promise<object | undefined>;
|
|
12
13
|
export declare function runCodexHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
13
14
|
export declare const runCodexHookAsync: typeof runCodexHook;
|
package/dist/codex-hook.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { handleCodexHook } from './packages/agent-delivery/src/codex-hook.js';
|
|
2
2
|
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
3
3
|
import { sessionInbox } from './inbox.js';
|
|
4
|
-
import {
|
|
4
|
+
import { harnessOwner } from './owner.js';
|
|
5
|
+
import { detachSessionSeats, restoreSessionSeats } from './session-lifecycle.js';
|
|
5
6
|
import { codexBoundaryStore } from './codex-boundary-state.js';
|
|
7
|
+
import { codexWakeAddress } from './codex-address.js';
|
|
6
8
|
import { PRIVILEGED_HOOK_BUDGET_MS, sweepPrivilegedPending } from './notifications.js';
|
|
7
9
|
export const CODEX_HOOK_BUDGET_MS = 4500;
|
|
8
10
|
const defaultTrace = (line) => { process.stderr.write(`${line}\n`); };
|
|
@@ -10,13 +12,20 @@ export async function codexHookResponse(input, lookup = sessionInbox, env = proc
|
|
|
10
12
|
const configured = Number(env.SQUARE_CODEX_HOOK_BUDGET_MS ?? CODEX_HOOK_BUDGET_MS);
|
|
11
13
|
const startedAt = Date.now();
|
|
12
14
|
let mark = startedAt;
|
|
15
|
+
const timeoutMs = Number.isFinite(configured) && configured > 0 ? configured : CODEX_HOOK_BUDGET_MS;
|
|
16
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
17
|
+
if ((input.hook_event_name === 'SessionStart' || input.hook_event_name === 'SessionResume')
|
|
18
|
+
&& typeof input.session_id === 'string' && input.session_id.trim()) {
|
|
19
|
+
await restoreSessionSeats(harnessOwner({ channel: 'codex', session: input.session_id }), env, codexWakeAddress(input.session_id, env), signal);
|
|
20
|
+
}
|
|
13
21
|
return handleCodexHook(input, {
|
|
14
22
|
boundaries: codexBoundaryStore(env),
|
|
15
|
-
timeoutMs
|
|
23
|
+
timeoutMs,
|
|
24
|
+
signal,
|
|
16
25
|
sweepTimeoutMs: PRIVILEGED_HOOK_BUDGET_MS,
|
|
17
|
-
pull: (id, signal) => presentPendingAtBoundary({ channel: 'codex', session: id }, (context) => context, lookup, env, signal),
|
|
26
|
+
pull: (id, signal) => presentPendingAtBoundary(harnessOwner({ channel: 'codex', session: id }), (context) => context, lookup, env, signal),
|
|
18
27
|
sweep: (cwd, deadline, signal) => sweepPrivilegedPending(cwd ?? process.cwd(), env, deliveryTransports, deadline, signal),
|
|
19
|
-
end: async (id) => { await
|
|
28
|
+
end: async (id) => { await detachSessionSeats('codex', id, env); },
|
|
20
29
|
trace: (name, detail = '') => {
|
|
21
30
|
const now = Date.now();
|
|
22
31
|
const durationMs = now - mark;
|
package/dist/codex-queue.js
CHANGED
|
@@ -12,7 +12,7 @@ export class CodexQueueTransport {
|
|
|
12
12
|
const { address } = request.route;
|
|
13
13
|
const threadId = address.threadId?.trim();
|
|
14
14
|
if (!threadId) {
|
|
15
|
-
return { outcome: 'failed',
|
|
15
|
+
return { outcome: 'failed', signature: 'invalid_address', message: 'Codex route has no thread id.' };
|
|
16
16
|
}
|
|
17
17
|
const env = this.opts.env ?? process.env;
|
|
18
18
|
const payload = request.artifact === undefined ? undefined
|
|
@@ -28,15 +28,15 @@ export class CodexQueueTransport {
|
|
|
28
28
|
});
|
|
29
29
|
const remaining = deadline - Date.now();
|
|
30
30
|
if (remaining <= 0) {
|
|
31
|
-
return { outcome: 'failed',
|
|
31
|
+
return { outcome: 'failed', signature: 'dispatch_budget_exhausted', message: 'The wake dispatch budget elapsed before queueing.' };
|
|
32
32
|
}
|
|
33
33
|
await agent.queue(payload, { timeoutMs: remaining });
|
|
34
34
|
return { outcome: 'accepted' };
|
|
35
35
|
}
|
|
36
36
|
catch (error) {
|
|
37
37
|
if (error instanceof DeliveryError && error.code === 'session_inactive') {
|
|
38
|
-
return { outcome: 'failed',
|
|
39
|
-
message: 'The Codex thread has no current Stop boundary for queueing.'
|
|
38
|
+
return { outcome: 'failed', signature: 'boundary_not_stopped',
|
|
39
|
+
message: 'The Codex thread has no current Stop boundary for queueing.' };
|
|
40
40
|
}
|
|
41
41
|
const message = error instanceof Error ? error.message : String(error);
|
|
42
42
|
if (!(error instanceof DeliveryError) || error.maybeDelivered)
|
package/dist/decisions.d.ts
CHANGED
|
@@ -5,21 +5,14 @@ type TimedAct<K extends Act['kind'] = Act['kind']> = Extract<Act, {
|
|
|
5
5
|
}> & {
|
|
6
6
|
at: number;
|
|
7
7
|
};
|
|
8
|
-
import type { ExpressResult, ParticipantSummary
|
|
9
|
-
export type { ParticipantSummary as ParticipantStatus, ParticipantPresence as CorePresenceState, UnreadActivitySummary, UnreadActivityPreview } from './square-facade.js';
|
|
8
|
+
import type { ExpressResult, ParticipantSummary, UnreadActivitySummary } from './square-facade.js';
|
|
10
9
|
export declare function resolveKnownName(facts: SquareRead, name: string): string;
|
|
11
10
|
export interface DecideJoinResult {
|
|
12
11
|
joinedName: string;
|
|
13
12
|
addParticipant: boolean;
|
|
14
13
|
joinAct?: TimedAct<'join'>;
|
|
15
14
|
}
|
|
16
|
-
export interface DecideImplicitJoinResult {
|
|
17
|
-
joinedName: string;
|
|
18
|
-
state: 'joined' | 'active' | 'done';
|
|
19
|
-
joinAct?: TimedAct<'join'>;
|
|
20
|
-
}
|
|
21
15
|
export declare function decideJoin(facts: SquareRead, name: string, now: number): DecideJoinResult;
|
|
22
|
-
export declare function decideImplicitJoin(facts: SquareRead, name: string, now: number): DecideImplicitJoinResult;
|
|
23
16
|
export type ActDecision = ({
|
|
24
17
|
type: 'sent';
|
|
25
18
|
act: TimedAct;
|
|
@@ -33,14 +26,11 @@ export type ActDecision = ({
|
|
|
33
26
|
} | {
|
|
34
27
|
type: 'throttled';
|
|
35
28
|
delayMs: number;
|
|
36
|
-
} | {
|
|
37
|
-
type: 'held';
|
|
38
|
-
reason: string | undefined;
|
|
39
29
|
} | {
|
|
40
30
|
type: 'bell_quota';
|
|
41
31
|
nextAt: number;
|
|
42
32
|
};
|
|
43
|
-
/** Count the directed suffix
|
|
33
|
+
/** Count the directed suffix and hydrate only its last activity by index per spelling. */
|
|
44
34
|
export declare function unreadActivitySummaries(facts: SquareRead, name: string, now: number): UnreadActivitySummary[];
|
|
45
35
|
export declare function decideAct(facts: SquareRead, input: {
|
|
46
36
|
name: string;
|
|
@@ -59,7 +49,6 @@ export declare function coreListen(facts: SquareRead, actor: string, target: str
|
|
|
59
49
|
export declare function coreIgnore(facts: SquareRead, actor: string, target: string, now: number): TimedAct<"listen" | "ignore" | "unlisten"> | undefined;
|
|
60
50
|
export declare function coreUnlisten(facts: SquareRead, actor: string, target: string, now: number): TimedAct<"listen" | "ignore" | "unlisten"> | undefined;
|
|
61
51
|
export declare function coreListening(facts: SquareRead, actor: string): readonly string[];
|
|
62
|
-
export declare function
|
|
63
|
-
export declare function
|
|
64
|
-
export
|
|
65
|
-
export declare function coreParticipants(facts: SquareRead, now: number): ParticipantStatus[];
|
|
52
|
+
export declare function compareParticipantActivity(a: Pick<ParticipantSummary, 'name' | 'lastActiveAt'>, b: Pick<ParticipantSummary, 'name' | 'lastActiveAt'>): number;
|
|
53
|
+
export declare function coreParticipants(facts: SquareRead, now: number): ParticipantSummary[];
|
|
54
|
+
export {};
|
package/dist/decisions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SquareError, isSquareError, sameName,
|
|
1
|
+
import { SquareError, isSquareError, sameName, validateName, validateActivityMetadata, activityMetadataHasContent, } from './model.js';
|
|
2
2
|
import { participantIdentity } from './participant-identity.js';
|
|
3
3
|
import { UNREAD_BLOCK_GRACE_MS, freshWatchLease, readCursor, FACT_PAGE_SIZE, THROTTLE_WINDOW_MS } from './runtime.js';
|
|
4
4
|
import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
@@ -17,12 +17,6 @@ export function decideJoin(facts, name, now) {
|
|
|
17
17
|
return current?.joined ? { joinedName, addParticipant: false }
|
|
18
18
|
: { joinedName, addParticipant: current === undefined, joinAct: { kind: 'join', actor: joinedName, at: now } };
|
|
19
19
|
}
|
|
20
|
-
export function decideImplicitJoin(facts, name, now) {
|
|
21
|
-
validateName(name);
|
|
22
|
-
const current = facts.participant(name);
|
|
23
|
-
return current === undefined ? { joinedName: name, state: 'joined', joinAct: { kind: 'join', actor: name, at: now } }
|
|
24
|
-
: { joinedName: current.name, state: current.joined ? 'active' : 'done' };
|
|
25
|
-
}
|
|
26
20
|
function validateActivityName(name) {
|
|
27
21
|
try {
|
|
28
22
|
validateName(name);
|
|
@@ -54,34 +48,28 @@ function requireStanding(current) {
|
|
|
54
48
|
if (!current.joined)
|
|
55
49
|
throw new SquareError('not_joined', `${participantIdentity(current.name)} has not joined this square`);
|
|
56
50
|
}
|
|
57
|
-
/** Count the directed suffix
|
|
51
|
+
/** Count the directed suffix and hydrate only its last activity by index per spelling. */
|
|
58
52
|
export function unreadActivitySummaries(facts, name, now) {
|
|
59
53
|
let afterIndex = readCursor(facts, name);
|
|
60
|
-
const numbers = new Map();
|
|
61
54
|
const summaries = new Map();
|
|
62
55
|
while (true) {
|
|
63
56
|
const page = facts.directed(name, { afterIndex }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
|
|
64
57
|
for (const item of page.items) {
|
|
65
|
-
const
|
|
66
|
-
const number = (numbers.get(key) ?? 0) + 1;
|
|
67
|
-
numbers.set(key, number);
|
|
68
|
-
const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, previews: [] };
|
|
58
|
+
const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, lastIndex: item.index };
|
|
69
59
|
summary.count++;
|
|
70
60
|
summary.latestAt = Math.max(summary.latestAt, item.at);
|
|
71
|
-
summary.
|
|
72
|
-
if (summary.previews.length > 3)
|
|
73
|
-
summary.previews.shift();
|
|
61
|
+
summary.lastIndex = item.index;
|
|
74
62
|
summaries.set(item.actor, summary);
|
|
75
63
|
}
|
|
76
64
|
if (!page.hasMore)
|
|
77
65
|
break;
|
|
78
66
|
afterIndex = page.items.at(-1).index;
|
|
79
67
|
}
|
|
80
|
-
const selected = facts.activities([...summaries.values()].
|
|
68
|
+
const selected = facts.activities([...summaries.values()].map((item) => item.lastIndex));
|
|
81
69
|
const byIndex = new Map(selected.map((item) => [item.index, item]));
|
|
82
70
|
return [...summaries].map(([name, summary]) => ({
|
|
83
71
|
name, count: summary.count, latestActivityAgeMs: Math.max(0, now - summary.latestAt),
|
|
84
|
-
|
|
72
|
+
preview: byIndex.get(summary.lastIndex),
|
|
85
73
|
})).sort((a, b) => a.latestActivityAgeMs - b.latestActivityAgeMs || a.name.localeCompare(b.name));
|
|
86
74
|
}
|
|
87
75
|
export function decideAct(facts, input) {
|
|
@@ -142,9 +130,6 @@ export function decideAct(facts, input) {
|
|
|
142
130
|
throw activityRefusal('already_done', `${name} is already done`);
|
|
143
131
|
if (!current.joined)
|
|
144
132
|
throw activityRefusal('not_joined', `${name} has not joined this square`);
|
|
145
|
-
const hold = facts.hold();
|
|
146
|
-
if (hold.active)
|
|
147
|
-
return { type: 'held', reason: hold.reason };
|
|
148
133
|
if (header.hardCap !== null && current.sayCount >= header.hardCap)
|
|
149
134
|
return { type: 'capped', count: current.sayCount, hardCap: header.hardCap };
|
|
150
135
|
if (reach === 'bell') {
|
|
@@ -197,16 +182,6 @@ export function coreListening(facts, actor) {
|
|
|
197
182
|
const current = resolveStanding(facts, actor);
|
|
198
183
|
return facts.attention(current.name).filter((item) => item.kind === 'listen').map((item) => item.target);
|
|
199
184
|
}
|
|
200
|
-
export function coreHold(facts, actor, body, now) {
|
|
201
|
-
const current = resolveStanding(facts, actor);
|
|
202
|
-
requireStanding(current);
|
|
203
|
-
return { kind: 'hold', actor: current.name, at: now, body: body.replace(/\r\n/g, '\n').trim() };
|
|
204
|
-
}
|
|
205
|
-
export function coreResume(facts, actor, now) {
|
|
206
|
-
const current = resolveStanding(facts, actor);
|
|
207
|
-
requireStanding(current);
|
|
208
|
-
return { kind: 'resume', actor: current.name, at: now };
|
|
209
|
-
}
|
|
210
185
|
export function compareParticipantActivity(a, b) {
|
|
211
186
|
return (b.lastActiveAt ?? -Infinity) - (a.lastActiveAt ?? -Infinity) || a.name.localeCompare(b.name);
|
|
212
187
|
}
|
|
@@ -219,10 +194,11 @@ export function coreParticipants(facts, now) {
|
|
|
219
194
|
lastActiveAt = Math.max(lastActiveAt ?? -Infinity, seenAt);
|
|
220
195
|
}
|
|
221
196
|
return {
|
|
222
|
-
name: current.name,
|
|
197
|
+
name: current.name, deliveryAvailable: facts.seat(current.name)?.wakeAddress !== undefined,
|
|
223
198
|
listening: facts.attention(current.name).filter((item) => item.kind === 'listen').map((item) => item.target),
|
|
224
199
|
activityCount: current.sayCount, lastActiveAt,
|
|
225
|
-
presence: current.done ? 'done' : !current.joined ? 'never-joined' :
|
|
200
|
+
presence: current.done ? 'done' : !current.joined ? 'never-joined' : facts.seat(current.name)?.attachment === 'detached' ? 'away'
|
|
201
|
+
: freshWatchLease(facts, current.name, now) !== undefined ? 'watching' : 'active',
|
|
226
202
|
unreadActivityCount: current.joined ? facts.directedCount(current.name, { afterIndex: readCursor(facts, current.name) }) : 0,
|
|
227
203
|
pendingMentionCount: current.joined ? facts.directedCount(current.name, { afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
|
|
228
204
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type DeliveryPurpose = 'dispatch' | 'presentation';
|
|
2
|
+
export type AttemptOutcome = 'dispatching' | 'accepted' | 'failed' | 'unknown' | 'released' | 'presented' | 'clipped';
|
|
3
|
+
export interface Attempt<D> {
|
|
4
|
+
readonly purpose: DeliveryPurpose;
|
|
5
|
+
readonly token: string;
|
|
6
|
+
readonly ordinal: number;
|
|
7
|
+
readonly outcome: AttemptOutcome;
|
|
8
|
+
readonly at: number;
|
|
9
|
+
readonly expiresAt?: number;
|
|
10
|
+
readonly data: D;
|
|
11
|
+
}
|
|
12
|
+
/** One item/recipient/authority/purpose series, bound inside the caller's transaction.
|
|
13
|
+
* The caller owns token uniqueness, isolation and rollback; replacements retain coordinates.
|
|
14
|
+
*/
|
|
15
|
+
export interface AttemptRows<D> {
|
|
16
|
+
list(): readonly Attempt<D>[];
|
|
17
|
+
byToken(token: string): Attempt<D> | undefined;
|
|
18
|
+
insert(attempt: Attempt<D>): void;
|
|
19
|
+
replace(attempt: Attempt<D>): void;
|
|
20
|
+
}
|
|
21
|
+
export type AttemptClaim<D> = {
|
|
22
|
+
readonly status: 'acquired';
|
|
23
|
+
readonly token: string;
|
|
24
|
+
readonly ordinal: number;
|
|
25
|
+
} | {
|
|
26
|
+
readonly status: 'busy' | 'terminal';
|
|
27
|
+
readonly evidence: Attempt<D>;
|
|
28
|
+
readonly interrupted?: true;
|
|
29
|
+
};
|
|
30
|
+
export declare function claimAttempt<D>(rows: AttemptRows<D>, input: {
|
|
31
|
+
purpose: DeliveryPurpose;
|
|
32
|
+
token: string;
|
|
33
|
+
at: number;
|
|
34
|
+
leaseMs: number;
|
|
35
|
+
data: D;
|
|
36
|
+
}): AttemptClaim<D>;
|
|
37
|
+
export declare function prepareAttempt<D>(rows: AttemptRows<D>, input: {
|
|
38
|
+
token: string;
|
|
39
|
+
data: D;
|
|
40
|
+
}): boolean;
|
|
41
|
+
export declare function settleAttempt<D>(rows: AttemptRows<D>, input: {
|
|
42
|
+
token: string;
|
|
43
|
+
outcome: Exclude<AttemptOutcome, 'dispatching'>;
|
|
44
|
+
at: number;
|
|
45
|
+
data: D;
|
|
46
|
+
}): boolean;
|
|
47
|
+
type Custody = Pick<Attempt<unknown>, 'purpose' | 'outcome' | 'expiresAt'>;
|
|
48
|
+
/** Presentation custody suppresses dispatch independently of dispatch acceptance. */
|
|
49
|
+
export declare function presentationBlocksDispatch(attempts: readonly Custody[], now: number): boolean;
|
|
50
|
+
/** An expired dispatch is uncertain, not permission for automatic replay. */
|
|
51
|
+
export declare function automaticDispatchEligible(attempts: readonly Custody[], now: number): boolean;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
function finite(value) {
|
|
2
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
3
|
+
throw new RangeError('time must be finite');
|
|
4
|
+
}
|
|
5
|
+
function token(value) {
|
|
6
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
7
|
+
throw new TypeError('token must be nonempty');
|
|
8
|
+
}
|
|
9
|
+
export function claimAttempt(rows, input) {
|
|
10
|
+
if (input.purpose !== 'dispatch' && input.purpose !== 'presentation')
|
|
11
|
+
throw new TypeError('unsupported purpose');
|
|
12
|
+
token(input.token);
|
|
13
|
+
finite(input.at);
|
|
14
|
+
if (!Number.isSafeInteger(input.leaseMs) || input.leaseMs <= 0)
|
|
15
|
+
throw new RangeError('leaseMs must be a positive safe integer');
|
|
16
|
+
const expiresAt = input.at + input.leaseMs;
|
|
17
|
+
finite(expiresAt);
|
|
18
|
+
const attempts = rows.list();
|
|
19
|
+
const terminal = attempts.find(row => row.outcome === 'unknown'
|
|
20
|
+
|| row.outcome === (input.purpose === 'dispatch' ? 'accepted' : 'presented'));
|
|
21
|
+
if (terminal !== undefined)
|
|
22
|
+
return { status: 'terminal', evidence: terminal };
|
|
23
|
+
const dispatching = attempts.find(row => row.outcome === 'dispatching');
|
|
24
|
+
if (dispatching !== undefined) {
|
|
25
|
+
if (dispatching.expiresAt === undefined || dispatching.expiresAt > input.at)
|
|
26
|
+
return { status: 'busy', evidence: dispatching };
|
|
27
|
+
const { expiresAt: _expiry, ...previous } = dispatching;
|
|
28
|
+
const evidence = { ...previous, outcome: 'unknown', at: input.at };
|
|
29
|
+
rows.replace(evidence);
|
|
30
|
+
return { status: 'terminal', evidence, interrupted: true };
|
|
31
|
+
}
|
|
32
|
+
const ordinal = (attempts[0]?.ordinal ?? -1) + 1;
|
|
33
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 0)
|
|
34
|
+
throw new RangeError('attempt ordinal exhausted');
|
|
35
|
+
rows.insert({ purpose: input.purpose, token: input.token, ordinal, outcome: 'dispatching', at: input.at, expiresAt, data: input.data });
|
|
36
|
+
return { status: 'acquired', token: input.token, ordinal };
|
|
37
|
+
}
|
|
38
|
+
export function prepareAttempt(rows, input) {
|
|
39
|
+
token(input.token);
|
|
40
|
+
const previous = rows.byToken(input.token);
|
|
41
|
+
if (previous?.outcome !== 'dispatching')
|
|
42
|
+
return false;
|
|
43
|
+
rows.replace({ ...previous, data: input.data });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
export function settleAttempt(rows, input) {
|
|
47
|
+
token(input.token);
|
|
48
|
+
finite(input.at);
|
|
49
|
+
if (!['accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(input.outcome))
|
|
50
|
+
throw new TypeError('unsupported completion');
|
|
51
|
+
const previous = rows.byToken(input.token);
|
|
52
|
+
if (previous === undefined)
|
|
53
|
+
return false;
|
|
54
|
+
if (previous.outcome === 'accepted' && input.outcome === 'accepted')
|
|
55
|
+
return true;
|
|
56
|
+
if (previous.outcome !== 'dispatching' && !(previous.outcome === 'unknown' && input.outcome === 'accepted'))
|
|
57
|
+
return false;
|
|
58
|
+
const { expiresAt: _expiry, ...retained } = previous;
|
|
59
|
+
rows.replace({ ...retained, outcome: input.outcome, at: input.at, data: input.data });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/** Presentation custody suppresses dispatch independently of dispatch acceptance. */
|
|
63
|
+
export function presentationBlocksDispatch(attempts, now) {
|
|
64
|
+
finite(now);
|
|
65
|
+
return attempts.some(row => row.purpose === 'presentation'
|
|
66
|
+
&& (row.outcome === 'presented' || row.outcome === 'clipped' || row.outcome === 'unknown'
|
|
67
|
+
|| row.outcome === 'dispatching' && (row.expiresAt === undefined || row.expiresAt > now)));
|
|
68
|
+
}
|
|
69
|
+
/** An expired dispatch is uncertain, not permission for automatic replay. */
|
|
70
|
+
export function automaticDispatchEligible(attempts, now) {
|
|
71
|
+
return !presentationBlocksDispatch(attempts, now) && !attempts.some(row => row.purpose === 'dispatch'
|
|
72
|
+
&& (row.outcome === 'accepted' || row.outcome === 'unknown' || row.outcome === 'dispatching'));
|
|
73
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SquareError
|
|
1
|
+
import { SquareError } from './model.js';
|
|
2
2
|
export const MAX_DELIVERY_DEFERRAL_MS = 24 * 60 * 60 * 1000;
|
|
3
3
|
export function validateMetadataMatch(value) {
|
|
4
4
|
const fail = () => { throw new SquareError('invalid_args', 'Invalid metadata match.'); };
|
|
@@ -69,7 +69,7 @@ export function matchingDeferrals(deferrals, metadata, now) {
|
|
|
69
69
|
}
|
|
70
70
|
export function deliveryDeferralExpiry(facts, captured, index, now) {
|
|
71
71
|
const seat = facts.seat(captured.participant);
|
|
72
|
-
if (seat === undefined || seat.generation !== captured.generation ||
|
|
72
|
+
if (seat === undefined || seat.generation !== captured.generation || seat.owner !== captured.owner)
|
|
73
73
|
return undefined;
|
|
74
74
|
const leases = facts.deliveryDeferrals(seat.participant, seat.generation);
|
|
75
75
|
if (!leases.some(lease => lease.expiresAt > now))
|