@astrosheep/square 0.3.28 → 0.3.30
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +3 -3
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/automatic-session.js +18 -7
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +2 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/square-commands.js +3 -3
- package/dist/codex-boundary-state.d.ts +4 -4
- package/dist/codex-boundary-state.js +19 -19
- package/dist/codex-hook.js +3 -3
- package/dist/codex-queue.js +2 -2
- package/dist/file-lock.d.ts +1 -1
- package/dist/file-lock.js +22 -50
- package/dist/inbox.js +2 -2
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +10 -10
- package/dist/presented.d.ts +5 -13
- package/dist/presented.js +48 -158
- package/dist/registry.d.ts +18 -30
- package/dist/registry.js +89 -320
- package/dist/routes.d.ts +6 -6
- package/dist/routes.js +20 -20
- package/dist/square-storage.js +12 -7
- package/dist/square-wiring.js +1 -1
- package/dist/wake-attempts.d.ts +6 -6
- package/dist/wake-attempts.js +39 -31
- package/dist/wake-evidence.d.ts +1 -1
- package/dist/wake-evidence.js +11 -11
- package/dist/wake-port.d.ts +1 -1
- package/dist/wake-port.js +1 -1
- package/dist/watch.js +1 -1
- package/extensions/square-pi.js +30 -3
- package/package.json +1 -1
- package/skills/square/SKILL.md +3 -3
package/dist/square-storage.js
CHANGED
|
@@ -10,13 +10,15 @@ import { LOCK_RETRY_MS, LOCK_STALE_MS } from './runtime.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export { createSquareState, };
|
|
12
12
|
export async function readSquareFile(squarePath) {
|
|
13
|
-
return loadSquare(squarePath);
|
|
13
|
+
return withSquareFileLock(squarePath, () => loadSquare(squarePath));
|
|
14
14
|
}
|
|
15
15
|
export async function probeSquareFile(squarePath) {
|
|
16
|
-
|
|
16
|
+
if (!squarePath.endsWith('.square'))
|
|
17
|
+
return undefined;
|
|
18
|
+
return withSquareFileLock(squarePath, () => probeSquare(squarePath));
|
|
17
19
|
}
|
|
18
20
|
export async function diagnoseSquareFile(squarePath) {
|
|
19
|
-
return diagnoseArtifactFile(squarePath);
|
|
21
|
+
return withSquareFileLock(squarePath, () => diagnoseArtifactFile(squarePath));
|
|
20
22
|
}
|
|
21
23
|
export async function writeSquareSnapshot(squarePath, squareState) {
|
|
22
24
|
await writeSquareFile(squarePath, squareState);
|
|
@@ -129,11 +131,11 @@ export function createFileCell(squarePath) {
|
|
|
129
131
|
}
|
|
130
132
|
return fingerprint;
|
|
131
133
|
}
|
|
132
|
-
async function
|
|
134
|
+
async function currentStateUnderLock() {
|
|
133
135
|
const observed = await observe();
|
|
134
136
|
if (cached?.fingerprint === observed)
|
|
135
137
|
return cloneState(cached.state);
|
|
136
|
-
const decoded = await
|
|
138
|
+
const decoded = await loadSquare(squarePath);
|
|
137
139
|
cached = { fingerprint: observed, state: cloneState(decoded) };
|
|
138
140
|
return cloneState(cached.state);
|
|
139
141
|
}
|
|
@@ -142,7 +144,7 @@ export function createFileCell(squarePath) {
|
|
|
142
144
|
assertCellOpen(closed);
|
|
143
145
|
return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
|
|
144
146
|
assertCellOpen(closed);
|
|
145
|
-
const current = await
|
|
147
|
+
const current = await currentStateUnderLock();
|
|
146
148
|
const working = cloneState(current);
|
|
147
149
|
const outcome = fn(working, version);
|
|
148
150
|
if (outcome.state !== undefined) {
|
|
@@ -156,7 +158,10 @@ export function createFileCell(squarePath) {
|
|
|
156
158
|
},
|
|
157
159
|
async read() {
|
|
158
160
|
assertCellOpen(closed);
|
|
159
|
-
return
|
|
161
|
+
return withSquareFileLock(squarePath, async () => {
|
|
162
|
+
assertCellOpen(closed);
|
|
163
|
+
return { state: await currentStateUnderLock(), version };
|
|
164
|
+
});
|
|
160
165
|
},
|
|
161
166
|
async changed(sinceVersion, timeoutMs) {
|
|
162
167
|
assertCellOpen(closed);
|
package/dist/square-wiring.js
CHANGED
|
@@ -73,7 +73,7 @@ export class Square {
|
|
|
73
73
|
snapshot() { return snapshot(this.square); }
|
|
74
74
|
history(query) { return history(this.square, query); }
|
|
75
75
|
async recognize(env) {
|
|
76
|
-
const registered = localParticipantName(this.square.location, env);
|
|
76
|
+
const registered = await localParticipantName(this.square.location, env);
|
|
77
77
|
if (registered === undefined)
|
|
78
78
|
return null;
|
|
79
79
|
const canonicalName = await currentParticipant(this.square, registered);
|
package/dist/wake-attempts.d.ts
CHANGED
|
@@ -16,24 +16,24 @@ export interface WakeAttempt {
|
|
|
16
16
|
diagnostic?: unknown;
|
|
17
17
|
}
|
|
18
18
|
export declare function wakeAttemptsPath(env?: NodeJS.ProcessEnv): string;
|
|
19
|
-
export declare function wakeAttentionKey(attention: WakeAttention): string
|
|
19
|
+
export declare function wakeAttentionKey(attention: WakeAttention): Promise<string>;
|
|
20
20
|
export declare function readWakeAttempts(opts?: {
|
|
21
21
|
attention?: WakeAttention;
|
|
22
22
|
now?: number;
|
|
23
23
|
env?: NodeJS.ProcessEnv;
|
|
24
|
-
}): WakeAttempt[]
|
|
24
|
+
}): Promise<WakeAttempt[]>;
|
|
25
25
|
export declare function terminalWakeEvidence(attempts: readonly WakeAttempt[]): WakeAttempt | undefined;
|
|
26
26
|
export declare function terminalWakeAttempt(attention: WakeAttention, opts?: {
|
|
27
27
|
now?: number;
|
|
28
28
|
env?: NodeJS.ProcessEnv;
|
|
29
|
-
}): WakeAttempt | undefined
|
|
29
|
+
}): Promise<WakeAttempt | undefined>;
|
|
30
30
|
export declare function isWakeRouteAttemptable(route: Pick<WakeRoute, 'kind' | 'updatedAt'>, attempts: readonly WakeAttempt[]): boolean;
|
|
31
31
|
export declare function hasAttemptableWakeRoute(routes: readonly Pick<WakeRoute, 'kind' | 'updatedAt'>[], attempts: readonly WakeAttempt[]): boolean;
|
|
32
32
|
export declare function nextWakeAttemptNumber(attention: WakeAttention, opts?: {
|
|
33
33
|
now?: number;
|
|
34
34
|
env?: NodeJS.ProcessEnv;
|
|
35
|
-
}): number
|
|
35
|
+
}): Promise<number>;
|
|
36
36
|
export declare function recordWakeAttempt(attempt: Omit<WakeAttempt, 'at'> & {
|
|
37
37
|
at?: number;
|
|
38
|
-
}, env?: NodeJS.ProcessEnv): WakeAttempt
|
|
39
|
-
export declare function recordRecoveredUnknown(attention: WakeAttention, lease: Pick<NotifyLease, 'attemptN' | 'routeKind'>, env?: NodeJS.ProcessEnv, at?: number): WakeAttempt | undefined
|
|
38
|
+
}, env?: NodeJS.ProcessEnv): Promise<WakeAttempt>;
|
|
39
|
+
export declare function recordRecoveredUnknown(attention: WakeAttention, lease: Pick<NotifyLease, 'attemptN' | 'routeKind'>, env?: NodeJS.ProcessEnv, at?: number): Promise<WakeAttempt | undefined>;
|
package/dist/wake-attempts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { withFileLock } from './file-lock.js';
|
|
5
5
|
import { isWakeRouteKind, nameKey } from './model.js';
|
|
6
6
|
import { canonicalSquarePath } from './registry.js';
|
|
7
7
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
@@ -12,8 +12,8 @@ const VALID_OUTCOMES = new Set(['accepted', 'unknown', 'failed']);
|
|
|
12
12
|
export function wakeAttemptsPath(env = process.env) {
|
|
13
13
|
return env.SQUARE_WAKE_ATTEMPTS || path.join(os.homedir(), '.square', 'wake-attempts.ndjsonl');
|
|
14
14
|
}
|
|
15
|
-
export function wakeAttentionKey(attention) {
|
|
16
|
-
return JSON.stringify([canonicalSquarePath(attention.squarePath), formatActivityId(attention.actIndex), nameKey(attention.recipient)]);
|
|
15
|
+
export async function wakeAttentionKey(attention) {
|
|
16
|
+
return JSON.stringify([await canonicalSquarePath(attention.squarePath), formatActivityId(attention.actIndex), nameKey(attention.recipient)]);
|
|
17
17
|
}
|
|
18
18
|
function parseRow(raw, now) {
|
|
19
19
|
let value;
|
|
@@ -39,10 +39,10 @@ function parseRow(raw, now) {
|
|
|
39
39
|
return undefined;
|
|
40
40
|
return row;
|
|
41
41
|
}
|
|
42
|
-
function readRowsFromFile(filePath, now) {
|
|
42
|
+
async function readRowsFromFile(filePath, now) {
|
|
43
43
|
let raw;
|
|
44
44
|
try {
|
|
45
|
-
raw = fs.
|
|
45
|
+
raw = await fs.promises.readFile(filePath, 'utf8');
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
48
|
if (error.code === 'ENOENT')
|
|
@@ -51,25 +51,25 @@ function readRowsFromFile(filePath, now) {
|
|
|
51
51
|
}
|
|
52
52
|
return raw.split('\n').filter(Boolean).map((line) => parseRow(line, now)).filter((row) => row !== undefined);
|
|
53
53
|
}
|
|
54
|
-
function readRows(env, now) {
|
|
54
|
+
async function readRows(env, now) {
|
|
55
55
|
return readRowsFromFile(wakeAttemptsPath(env), now);
|
|
56
56
|
}
|
|
57
|
-
function writeRows(filePath, rows) {
|
|
58
|
-
fs.
|
|
57
|
+
async function writeRows(filePath, rows) {
|
|
58
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
59
59
|
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
60
|
-
fs.
|
|
60
|
+
await fs.promises.writeFile(temporary, rows.map((row) => JSON.stringify(row)).join('\n') + (rows.length ? '\n' : ''), {
|
|
61
61
|
mode: 0o600,
|
|
62
62
|
});
|
|
63
|
-
fs.
|
|
63
|
+
await fs.promises.rename(temporary, filePath);
|
|
64
64
|
}
|
|
65
|
-
function fromRow(row) {
|
|
65
|
+
async function fromRow(row) {
|
|
66
66
|
const actIndex = parseActivityId(row.attention.act_id);
|
|
67
67
|
if (actIndex === undefined)
|
|
68
68
|
throw new Error(`Invalid wake activity id: ${row.attention.act_id}`);
|
|
69
69
|
return {
|
|
70
70
|
at: row.ts,
|
|
71
71
|
attention: {
|
|
72
|
-
squarePath: canonicalSquarePath(row.attention.square_path),
|
|
72
|
+
squarePath: await canonicalSquarePath(row.attention.square_path),
|
|
73
73
|
actIndex,
|
|
74
74
|
recipient: row.attention.recipient,
|
|
75
75
|
},
|
|
@@ -81,18 +81,20 @@ function fromRow(row) {
|
|
|
81
81
|
...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
export function readWakeAttempts(opts = {}) {
|
|
84
|
+
export async function readWakeAttempts(opts = {}) {
|
|
85
85
|
const now = opts.now ?? Date.now();
|
|
86
|
-
const expected = opts.attention === undefined ? undefined : wakeAttentionKey(opts.attention);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
const expected = opts.attention === undefined ? undefined : await wakeAttentionKey(opts.attention);
|
|
87
|
+
const attempts = await Promise.all((await readRows(opts.env ?? process.env, now)).map(fromRow));
|
|
88
|
+
if (expected === undefined)
|
|
89
|
+
return attempts;
|
|
90
|
+
const keys = await Promise.all(attempts.map((attempt) => wakeAttentionKey(attempt.attention)));
|
|
91
|
+
return attempts.filter((_, index) => keys[index] === expected);
|
|
90
92
|
}
|
|
91
93
|
export function terminalWakeEvidence(attempts) {
|
|
92
94
|
return attempts.findLast((attempt) => attempt.outcome === 'accepted' || attempt.outcome === 'unknown');
|
|
93
95
|
}
|
|
94
|
-
export function terminalWakeAttempt(attention, opts = {}) {
|
|
95
|
-
return terminalWakeEvidence(readWakeAttempts({ attention, ...opts }));
|
|
96
|
+
export async function terminalWakeAttempt(attention, opts = {}) {
|
|
97
|
+
return terminalWakeEvidence(await readWakeAttempts({ attention, ...opts }));
|
|
96
98
|
}
|
|
97
99
|
export function isWakeRouteAttemptable(route, attempts) {
|
|
98
100
|
if (terminalWakeEvidence(attempts) !== undefined)
|
|
@@ -103,8 +105,8 @@ export function isWakeRouteAttemptable(route, attempts) {
|
|
|
103
105
|
export function hasAttemptableWakeRoute(routes, attempts) {
|
|
104
106
|
return routes.some((route) => isWakeRouteAttemptable(route, attempts));
|
|
105
107
|
}
|
|
106
|
-
export function nextWakeAttemptNumber(attention, opts = {}) {
|
|
107
|
-
return readWakeAttempts({ attention, ...opts }).reduce((highest, attempt) => Math.max(highest, attempt.attemptN), 0) + 1;
|
|
108
|
+
export async function nextWakeAttemptNumber(attention, opts = {}) {
|
|
109
|
+
return (await readWakeAttempts({ attention, ...opts })).reduce((highest, attempt) => Math.max(highest, attempt.attemptN), 0) + 1;
|
|
108
110
|
}
|
|
109
111
|
function redact(value, secret) {
|
|
110
112
|
if (typeof value === 'string') {
|
|
@@ -118,13 +120,13 @@ function redact(value, secret) {
|
|
|
118
120
|
}
|
|
119
121
|
return value;
|
|
120
122
|
}
|
|
121
|
-
function toRow(attempt, env) {
|
|
123
|
+
async function toRow(attempt, env) {
|
|
122
124
|
const safe = redact(attempt, env.PASEO_PASSWORD);
|
|
123
125
|
return {
|
|
124
126
|
v: 1,
|
|
125
127
|
ts: safe.at,
|
|
126
128
|
attention: {
|
|
127
|
-
square_path: canonicalSquarePath(safe.attention.squarePath),
|
|
129
|
+
square_path: await canonicalSquarePath(safe.attention.squarePath),
|
|
128
130
|
act_id: formatActivityId(safe.attention.actIndex),
|
|
129
131
|
recipient: safe.attention.recipient,
|
|
130
132
|
},
|
|
@@ -136,7 +138,7 @@ function toRow(attempt, env) {
|
|
|
136
138
|
...(safe.diagnostic === undefined ? {} : { diagnostic: safe.diagnostic }),
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
|
-
export function recordWakeAttempt(attempt, env = process.env) {
|
|
141
|
+
export async function recordWakeAttempt(attempt, env = process.env) {
|
|
140
142
|
const value = { ...attempt, at: attempt.at ?? Date.now() };
|
|
141
143
|
if (!isWakeRouteKind(value.routeKind))
|
|
142
144
|
throw new Error('Wake attempts require a real adapter route kind.');
|
|
@@ -144,19 +146,25 @@ export function recordWakeAttempt(attempt, env = process.env) {
|
|
|
144
146
|
throw new Error(`${value.outcome} wake attempts require a transport signature.`);
|
|
145
147
|
}
|
|
146
148
|
const file = wakeAttemptsPath(env);
|
|
147
|
-
|
|
148
|
-
writeRows(file, [...readRowsFromFile(file, value.at), toRow(value, env)]);
|
|
149
|
+
await withFileLock(`${file}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
|
|
150
|
+
await writeRows(file, [...await readRowsFromFile(file, value.at), await toRow(value, env)]);
|
|
149
151
|
});
|
|
150
152
|
return value;
|
|
151
153
|
}
|
|
152
|
-
export function recordRecoveredUnknown(attention, lease, env = process.env, at = Date.now()) {
|
|
154
|
+
export async function recordRecoveredUnknown(attention, lease, env = process.env, at = Date.now()) {
|
|
153
155
|
const routeKind = lease.routeKind;
|
|
154
156
|
if (lease.attemptN === undefined || !isWakeRouteKind(routeKind))
|
|
155
157
|
return undefined;
|
|
156
158
|
const file = wakeAttemptsPath(env);
|
|
157
|
-
return
|
|
158
|
-
const rows = readRowsFromFile(file, at);
|
|
159
|
-
const
|
|
159
|
+
return withFileLock(`${file}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
|
|
160
|
+
const rows = await readRowsFromFile(file, at);
|
|
161
|
+
const expected = await wakeAttentionKey(attention);
|
|
162
|
+
const attempts = [];
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const attempt = await fromRow(row);
|
|
165
|
+
if (await wakeAttentionKey(attempt.attention) === expected)
|
|
166
|
+
attempts.push(attempt);
|
|
167
|
+
}
|
|
160
168
|
const terminal = terminalWakeEvidence(attempts);
|
|
161
169
|
if (terminal !== undefined)
|
|
162
170
|
return terminal;
|
|
@@ -169,7 +177,7 @@ export function recordRecoveredUnknown(attention, lease, env = process.env, at =
|
|
|
169
177
|
attemptN: lease.attemptN,
|
|
170
178
|
message: 'The notification worker ended after dispatch began; transport acceptance is unknown.',
|
|
171
179
|
};
|
|
172
|
-
writeRows(file, [...rows, toRow(value, env)]);
|
|
180
|
+
await writeRows(file, [...rows, await toRow(value, env)]);
|
|
173
181
|
return value;
|
|
174
182
|
});
|
|
175
183
|
}
|
package/dist/wake-evidence.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface WakeEvidenceProjection {
|
|
|
15
15
|
}
|
|
16
16
|
/** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
|
|
17
17
|
export declare function wakeEvidenceProjection(squarePath: string, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidenceProjection>;
|
|
18
|
-
export declare function wakeEvidenceProjectionFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, delivery?: DeliveryModel): WakeEvidenceProjection
|
|
18
|
+
export declare function wakeEvidenceProjectionFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, delivery?: DeliveryModel): Promise<WakeEvidenceProjection>;
|
|
19
19
|
/** Project every wake decision from the same primary evidence. */
|
|
20
20
|
export declare function wakeEvidence(squarePath: string, recipient: string, actIndex: number, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidence>;
|
|
21
21
|
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
package/dist/wake-evidence.js
CHANGED
|
@@ -8,13 +8,13 @@ import { openSquare } from './square-file-adapter.js';
|
|
|
8
8
|
import { closeOpenSquare } from './open-square.js';
|
|
9
9
|
import { observationFor } from './runtime.js';
|
|
10
10
|
import { entryPresentation } from './views.js';
|
|
11
|
-
function attentionKey(squarePath, recipient, actIndex) {
|
|
12
|
-
return JSON.stringify([canonicalSquarePath(squarePath), nameKey(recipient), actIndex]);
|
|
11
|
+
async function attentionKey(squarePath, recipient, actIndex) {
|
|
12
|
+
return JSON.stringify([await canonicalSquarePath(squarePath), nameKey(recipient), actIndex]);
|
|
13
13
|
}
|
|
14
|
-
function projectionFromState(squarePath, state, now, env, delivery = deriveDeliveryModel(state)) {
|
|
15
|
-
const canonicalPath = canonicalSquarePath(squarePath);
|
|
14
|
+
async function projectionFromState(squarePath, state, now, env, delivery = deriveDeliveryModel(state)) {
|
|
15
|
+
const canonicalPath = await canonicalSquarePath(squarePath);
|
|
16
16
|
const owners = new Map();
|
|
17
|
-
for (const binding of readActiveBindings(now)) {
|
|
17
|
+
for (const binding of await readActiveBindings(now)) {
|
|
18
18
|
if (binding.squarePath !== canonicalPath)
|
|
19
19
|
continue;
|
|
20
20
|
const key = nameKey(binding.name);
|
|
@@ -23,21 +23,21 @@ function projectionFromState(squarePath, state, now, env, delivery = deriveDeliv
|
|
|
23
23
|
owners.set(key, recipientOwners);
|
|
24
24
|
}
|
|
25
25
|
const routesByOwner = new Map();
|
|
26
|
-
for (const route of readWakeRoutes({ freshOnly: true, now, env })) {
|
|
26
|
+
for (const route of await readWakeRoutes({ freshOnly: true, now, env })) {
|
|
27
27
|
const routes = routesByOwner.get(route.ownerId) ?? [];
|
|
28
28
|
routes.push(route);
|
|
29
29
|
routesByOwner.set(route.ownerId, routes);
|
|
30
30
|
}
|
|
31
31
|
const attemptsByAttention = new Map();
|
|
32
|
-
for (const attempt of readWakeAttempts({ env, now })) {
|
|
33
|
-
const key = attentionKey(attempt.attention.squarePath, attempt.attention.recipient, attempt.attention.actIndex);
|
|
32
|
+
for (const attempt of await readWakeAttempts({ env, now })) {
|
|
33
|
+
const key = await attentionKey(attempt.attention.squarePath, attempt.attention.recipient, attempt.attention.actIndex);
|
|
34
34
|
const attempts = attemptsByAttention.get(key) ?? [];
|
|
35
35
|
attempts.push(attempt);
|
|
36
36
|
attemptsByAttention.set(key, attempts);
|
|
37
37
|
}
|
|
38
38
|
const presentedByAttention = new Map();
|
|
39
|
-
for (const presented of readPresentedAttentions(env, now)) {
|
|
40
|
-
const key = attentionKey(presented.squarePath, presented.name, presented.actIndex);
|
|
39
|
+
for (const presented of await readPresentedAttentions(env, now)) {
|
|
40
|
+
const key = await attentionKey(presented.squarePath, presented.name, presented.actIndex);
|
|
41
41
|
const presentedOwners = presentedByAttention.get(key) ?? new Set();
|
|
42
42
|
presentedOwners.add(presented.ownerId);
|
|
43
43
|
presentedByAttention.set(key, presentedOwners);
|
|
@@ -45,7 +45,7 @@ function projectionFromState(squarePath, state, now, env, delivery = deriveDeliv
|
|
|
45
45
|
return {
|
|
46
46
|
evidence(recipient, actIndex) {
|
|
47
47
|
const recipientOwners = owners.get(nameKey(recipient)) ?? new Set();
|
|
48
|
-
const key =
|
|
48
|
+
const key = JSON.stringify([canonicalPath, nameKey(recipient), actIndex]);
|
|
49
49
|
const attempts = attemptsByAttention.get(key) ?? [];
|
|
50
50
|
const terminal = terminalWakeEvidence(attempts);
|
|
51
51
|
const routes = [...recipientOwners].flatMap((ownerId) => routesByOwner.get(ownerId) ?? []);
|
package/dist/wake-port.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WakeAdapter, WakeDispatchResult } from './delivery.js';
|
|
2
2
|
import type { WakeRoute } from './model.js';
|
|
3
3
|
export interface WakePortHooks {
|
|
4
|
-
nextAttemptN(): number
|
|
4
|
+
nextAttemptN(): number | Promise<number>;
|
|
5
5
|
beforeSend(route: WakeRoute, attemptN: number): Promise<boolean>;
|
|
6
6
|
record(route: WakeRoute, attemptN: number, result: Exclude<WakeDispatchResult, {
|
|
7
7
|
outcome: 'cancelled' | 'unavailable';
|
package/dist/wake-port.js
CHANGED
|
@@ -9,7 +9,7 @@ export class WakePort {
|
|
|
9
9
|
const adapter = this.adapters.get(route.kind);
|
|
10
10
|
if (adapter === undefined)
|
|
11
11
|
continue;
|
|
12
|
-
const attemptN = hooks.nextAttemptN();
|
|
12
|
+
const attemptN = await hooks.nextAttemptN();
|
|
13
13
|
const result = await adapter.dispatch(route.address, typeof payload === 'function' ? payload(route) : payload, () => hooks.beforeSend(route, attemptN));
|
|
14
14
|
if (result.outcome === 'cancelled')
|
|
15
15
|
return result;
|
package/dist/watch.js
CHANGED
|
@@ -87,7 +87,7 @@ async function finishWatchResult(square, squarePath, name, result, leaseId, idle
|
|
|
87
87
|
}
|
|
88
88
|
async function beginWatch(square, squarePath, name, opts) {
|
|
89
89
|
const id = leaseId();
|
|
90
|
-
const ownerId = localParticipantOwner(squarePath, name);
|
|
90
|
+
const ownerId = await localParticipantOwner(squarePath, name);
|
|
91
91
|
return acquireWatchLease(square, name, id, opts, ownerId);
|
|
92
92
|
}
|
|
93
93
|
async function endWatch(square, name, id) {
|
package/extensions/square-pi.js
CHANGED
|
@@ -4,6 +4,12 @@ import { waitForSessionPending } from '../dist/inbox.js';
|
|
|
4
4
|
import { lookupSessionBindings } from '../dist/registry.js';
|
|
5
5
|
|
|
6
6
|
const PI_SEND_TIMEOUT_MS = 5_000;
|
|
7
|
+
const DEFAULT_PI_BOUNDARY_TIMEOUT_MS = 2_000;
|
|
8
|
+
|
|
9
|
+
function piBoundaryTimeoutMs() {
|
|
10
|
+
const configured = Number.parseInt(process.env.SQUARE_PI_BOUNDARY_TIMEOUT_MS || '', 10);
|
|
11
|
+
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PI_BOUNDARY_TIMEOUT_MS;
|
|
12
|
+
}
|
|
7
13
|
|
|
8
14
|
export function pendingInbox(inbox) {
|
|
9
15
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
@@ -32,7 +38,18 @@ export default function squarePiExtension(pi) {
|
|
|
32
38
|
let settledWaiters = [];
|
|
33
39
|
const handledPending = new Set();
|
|
34
40
|
let retryAfterChange = false;
|
|
35
|
-
const present = (deliver) => sessionId === undefined ? undefined : presentPendingAtBoundary(sessionId, deliver);
|
|
41
|
+
const present = (deliver, signal) => sessionId === undefined ? undefined : presentPendingAtBoundary(sessionId, deliver, undefined, undefined, signal);
|
|
42
|
+
|
|
43
|
+
const presentAtBoundary = (deliver) => {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timer = setTimeout(() => controller.abort(new Error('Pi boundary presentation timed out')), piBoundaryTimeoutMs());
|
|
46
|
+
const pending = present(deliver, controller.signal);
|
|
47
|
+
if (pending === undefined) {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return pending.finally(() => clearTimeout(timer));
|
|
52
|
+
};
|
|
36
53
|
|
|
37
54
|
const waitForSettled = (serial, signal) => {
|
|
38
55
|
if (settledSerial !== serial) return Promise.resolve();
|
|
@@ -67,7 +84,7 @@ export default function squarePiExtension(pi) {
|
|
|
67
84
|
|
|
68
85
|
const wake = async (piContext, token, signal) => {
|
|
69
86
|
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
70
|
-
if (lookupSessionBindings(sessionId).length === 0) {
|
|
87
|
+
if ((await lookupSessionBindings(sessionId)).length === 0) {
|
|
71
88
|
await pause(signal, 1_000);
|
|
72
89
|
continue;
|
|
73
90
|
}
|
|
@@ -103,6 +120,13 @@ export default function squarePiExtension(pi) {
|
|
|
103
120
|
try {
|
|
104
121
|
await Promise.race([
|
|
105
122
|
send,
|
|
123
|
+
new Promise((_, reject) => {
|
|
124
|
+
if (signal.aborted) {
|
|
125
|
+
reject(signal.reason || new Error('Pi native injection aborted'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
signal.addEventListener('abort', () => reject(signal.reason || new Error('Pi native injection aborted')), { once: true });
|
|
129
|
+
}),
|
|
106
130
|
new Promise((_, reject) => {
|
|
107
131
|
timer = setTimeout(() => reject(new Error('Pi native injection timed out')), PI_SEND_TIMEOUT_MS);
|
|
108
132
|
}),
|
|
@@ -112,6 +136,9 @@ export default function squarePiExtension(pi) {
|
|
|
112
136
|
}
|
|
113
137
|
return true;
|
|
114
138
|
},
|
|
139
|
+
undefined,
|
|
140
|
+
undefined,
|
|
141
|
+
signal,
|
|
115
142
|
);
|
|
116
143
|
if (delivered === true || delivered === undefined) {
|
|
117
144
|
for (const key of keys) handledPending.add(key);
|
|
@@ -156,7 +183,7 @@ export default function squarePiExtension(pi) {
|
|
|
156
183
|
return { message: { customType: 'square', content: context, display: true } };
|
|
157
184
|
}
|
|
158
185
|
if (presenting) return undefined;
|
|
159
|
-
return
|
|
186
|
+
return await presentAtBoundary((context) => ({ message: { customType: 'square', content: context, display: true } }));
|
|
160
187
|
} catch {
|
|
161
188
|
return undefined;
|
|
162
189
|
}
|
package/package.json
CHANGED
package/skills/square/SKILL.md
CHANGED
|
@@ -74,11 +74,11 @@ EOF
|
|
|
74
74
|
```bash
|
|
75
75
|
square --location <square> --as <name> catch --now # take in what is pending
|
|
76
76
|
square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
|
|
77
|
-
square --location <square> --as <name> catch --mention
|
|
78
|
-
square --location <square> --as <name> catch --from <names>
|
|
77
|
+
square --location <square> --as <name> catch --now --mention # take in pending mentions
|
|
78
|
+
square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
|
|
81
|
+
Every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
|
|
82
82
|
|
|
83
83
|
## Listen
|
|
84
84
|
|