@astrosheep/square 0.3.30 → 0.3.31
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/hooks/hooks.json +0 -3
- package/claude-plugin/skills/square/SKILL.md +5 -4
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +7 -3
- package/dist/artifact.js +7 -50
- package/dist/automatic-session.js +31 -14
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +58 -14
- package/dist/catch-decisions.d.ts +17 -0
- package/dist/catch-decisions.js +53 -0
- package/dist/claude-hook.d.ts +1 -1
- package/dist/cli/context.js +10 -2
- package/dist/cli/observation-commands.d.ts +5 -1
- package/dist/cli/observation-commands.js +68 -33
- package/dist/cli/square-commands.js +17 -10
- package/dist/codex-hook.d.ts +1 -1
- package/dist/decisions.js +2 -2
- package/dist/delivery-health.d.ts +1 -1
- package/dist/delivery-operations.d.ts +25 -0
- package/dist/delivery-operations.js +124 -0
- package/dist/help.js +1 -1
- package/dist/host-ledger-file-adapter.d.ts +34 -0
- package/dist/host-ledger-file-adapter.js +165 -0
- package/dist/host-ledger.d.ts +160 -0
- package/dist/host-ledger.js +1 -0
- package/dist/inbox.d.ts +2 -2
- package/dist/inbox.js +22 -14
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/landing.d.ts +18 -14
- package/dist/landing.js +29 -113
- package/dist/model.d.ts +6 -17
- package/dist/notifications.d.ts +6 -10
- package/dist/notifications.js +71 -192
- package/dist/open-square.d.ts +4 -4
- package/dist/open-square.js +1 -1
- package/dist/ports.d.ts +127 -0
- package/dist/ports.js +1 -0
- package/dist/presence.d.ts +1 -2
- package/dist/presence.js +13 -46
- package/dist/presentation-operations.d.ts +3 -0
- package/dist/presentation-operations.js +51 -0
- package/dist/presentation.d.ts +2 -2
- package/dist/presentation.js +11 -7
- package/dist/presented.d.ts +1 -12
- package/dist/presented.js +6 -75
- package/dist/registry.d.ts +10 -10
- package/dist/registry.js +48 -113
- package/dist/routes.d.ts +6 -33
- package/dist/routes.js +6 -170
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3 -4
- package/dist/square-actions.d.ts +32 -0
- package/dist/square-actions.js +167 -0
- package/dist/square-facade.d.ts +7 -7
- package/dist/square-file-adapter.d.ts +4 -3
- package/dist/square-file-adapter.js +22 -5
- package/dist/square-projections.d.ts +68 -0
- package/dist/square-projections.js +87 -0
- package/dist/square-storage.d.ts +2 -2
- package/dist/square-storage.js +2 -2
- package/dist/square-wiring.d.ts +3 -3
- package/dist/square-wiring.js +44 -29
- package/dist/views.d.ts +8 -2
- package/dist/views.js +28 -24
- package/dist/wake-attempts.d.ts +29 -22
- package/dist/wake-attempts.js +36 -119
- package/dist/wake-evidence.d.ts +6 -18
- package/dist/wake-evidence.js +17 -80
- package/dist/wakes.d.ts +2 -16
- package/dist/wakes.js +7 -27
- package/dist/watch.js +2 -3
- package/extensions/square-pi.js +7 -2
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +2 -2
- package/skills/square/SKILL.md +5 -4
package/dist/wake-attempts.js
CHANGED
|
@@ -1,112 +1,48 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { withFileLock } from './file-lock.js';
|
|
5
3
|
import { isWakeRouteKind, nameKey } from './model.js';
|
|
6
4
|
import { canonicalSquarePath } from './registry.js';
|
|
7
5
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
7
|
+
import { terminalWakeEvidence } from './square-projections.js';
|
|
8
|
+
export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
|
|
11
9
|
const VALID_OUTCOMES = new Set(['accepted', 'unknown', 'failed']);
|
|
10
|
+
function ledger(env) {
|
|
11
|
+
return createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? path.dirname(wakeAttemptsPath(env)), readableScopes: ['user'], writableScope: 'user' });
|
|
12
|
+
}
|
|
12
13
|
export function wakeAttemptsPath(env = process.env) {
|
|
13
14
|
return env.SQUARE_WAKE_ATTEMPTS || path.join(os.homedir(), '.square', 'wake-attempts.ndjsonl');
|
|
14
15
|
}
|
|
15
16
|
export async function wakeAttentionKey(attention) {
|
|
16
17
|
return JSON.stringify([await canonicalSquarePath(attention.squarePath), formatActivityId(attention.actIndex), nameKey(attention.recipient)]);
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
value = JSON.parse(raw);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
if (value === null || typeof value !== 'object')
|
|
27
|
-
return undefined;
|
|
28
|
-
const row = value;
|
|
29
|
-
if (row.v !== 1 || typeof row.ts !== 'number' || !Number.isFinite(row.ts) || row.ts > now || now - row.ts > RETENTION_MS ||
|
|
30
|
-
row.attention === undefined || typeof row.attention.square_path !== 'string' || row.attention.square_path === '' ||
|
|
31
|
-
typeof row.attention.act_id !== 'string' || parseActivityId(row.attention.act_id) === undefined ||
|
|
32
|
-
typeof row.attention.recipient !== 'string' || row.attention.recipient === '' ||
|
|
33
|
-
typeof row.outcome !== 'string' || !VALID_OUTCOMES.has(row.outcome) ||
|
|
34
|
-
typeof row.attempt_n !== 'number' || !Number.isInteger(row.attempt_n) || row.attempt_n <= 0 ||
|
|
35
|
-
!isWakeRouteKind(row.route_kind) ||
|
|
36
|
-
(row.signature !== undefined && typeof row.signature !== 'string') ||
|
|
37
|
-
(row.outcome !== 'accepted' && (typeof row.signature !== 'string' || row.signature === '')) ||
|
|
38
|
-
(row.message !== undefined && typeof row.message !== 'string'))
|
|
39
|
-
return undefined;
|
|
40
|
-
return row;
|
|
19
|
+
export async function claimWakeDispatch(attention, leaseId, leaseMs, env = process.env, at = Date.now(), session) {
|
|
20
|
+
return ledger(env).claimWakeDispatch({ attention, leaseId, leaseMs, at, session });
|
|
41
21
|
}
|
|
42
|
-
async function
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
raw = await fs.promises.readFile(filePath, 'utf8');
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
if (error.code === 'ENOENT')
|
|
49
|
-
return [];
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
return raw.split('\n').filter(Boolean).map((line) => parseRow(line, now)).filter((row) => row !== undefined);
|
|
22
|
+
export async function transitionWakeDispatch(attention, leaseId, phase, leaseMs, routeKind, attemptN, session, env = process.env, at = Date.now()) {
|
|
23
|
+
return ledger(env).transitionWakeDispatch({ attention, leaseId, phase, leaseMs, routeKind, attemptN, session, at });
|
|
53
24
|
}
|
|
54
|
-
async function
|
|
55
|
-
|
|
25
|
+
export async function releaseWakeDispatch(attention, leaseId, env = process.env, at = Date.now()) {
|
|
26
|
+
await ledger(env).releaseWakeDispatch({ attention, leaseId, at });
|
|
56
27
|
}
|
|
57
|
-
async function
|
|
58
|
-
await
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
28
|
+
async function readRows(env, now) {
|
|
29
|
+
const records = await ledger(env).listWakeAttempts({ now });
|
|
30
|
+
return records.flatMap((record) => {
|
|
31
|
+
const actIndex = parseActivityId(record.activity);
|
|
32
|
+
if (actIndex === undefined || !isWakeRouteKind(record.routeKind) || !VALID_OUTCOMES.has(record.outcome) || typeof record.attemptN !== 'number')
|
|
33
|
+
return [];
|
|
34
|
+
return [{ at: record.at ?? now, attention: { squarePath: record.location, actIndex, recipient: record.participant }, routeKind: record.routeKind, outcome: record.outcome, attemptN: record.attemptN, ...(record.signature === undefined ? {} : { signature: record.signature }), ...(record.session === undefined ? {} : { session: record.session }), ...(record.message === undefined ? {} : { message: record.message }), ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }) }];
|
|
62
35
|
});
|
|
63
|
-
await fs.promises.rename(temporary, filePath);
|
|
64
|
-
}
|
|
65
|
-
async function fromRow(row) {
|
|
66
|
-
const actIndex = parseActivityId(row.attention.act_id);
|
|
67
|
-
if (actIndex === undefined)
|
|
68
|
-
throw new Error(`Invalid wake activity id: ${row.attention.act_id}`);
|
|
69
|
-
return {
|
|
70
|
-
at: row.ts,
|
|
71
|
-
attention: {
|
|
72
|
-
squarePath: await canonicalSquarePath(row.attention.square_path),
|
|
73
|
-
actIndex,
|
|
74
|
-
recipient: row.attention.recipient,
|
|
75
|
-
},
|
|
76
|
-
routeKind: row.route_kind,
|
|
77
|
-
outcome: row.outcome,
|
|
78
|
-
...(row.signature === undefined ? {} : { signature: row.signature }),
|
|
79
|
-
attemptN: row.attempt_n,
|
|
80
|
-
...(row.message === undefined ? {} : { message: row.message }),
|
|
81
|
-
...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
|
|
82
|
-
};
|
|
83
36
|
}
|
|
84
37
|
export async function readWakeAttempts(opts = {}) {
|
|
85
38
|
const now = opts.now ?? Date.now();
|
|
86
39
|
const expected = opts.attention === undefined ? undefined : await wakeAttentionKey(opts.attention);
|
|
87
|
-
const attempts = await
|
|
40
|
+
const attempts = await readRows(opts.env ?? process.env, now);
|
|
41
|
+
const scoped = opts.sessionId === undefined ? attempts : attempts.filter((attempt) => attempt.session === opts.sessionId);
|
|
88
42
|
if (expected === undefined)
|
|
89
|
-
return
|
|
90
|
-
const keys = await Promise.all(
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
export function terminalWakeEvidence(attempts) {
|
|
94
|
-
return attempts.findLast((attempt) => attempt.outcome === 'accepted' || attempt.outcome === 'unknown');
|
|
95
|
-
}
|
|
96
|
-
export async function terminalWakeAttempt(attention, opts = {}) {
|
|
97
|
-
return terminalWakeEvidence(await readWakeAttempts({ attention, ...opts }));
|
|
98
|
-
}
|
|
99
|
-
export function isWakeRouteAttemptable(route, attempts) {
|
|
100
|
-
if (terminalWakeEvidence(attempts) !== undefined)
|
|
101
|
-
return false;
|
|
102
|
-
const failed = attempts.findLast((attempt) => attempt.routeKind === route.kind && attempt.outcome === 'failed');
|
|
103
|
-
return failed === undefined || route.updatedAt > failed.at;
|
|
104
|
-
}
|
|
105
|
-
export function hasAttemptableWakeRoute(routes, attempts) {
|
|
106
|
-
return routes.some((route) => isWakeRouteAttemptable(route, attempts));
|
|
107
|
-
}
|
|
108
|
-
export async function nextWakeAttemptNumber(attention, opts = {}) {
|
|
109
|
-
return (await readWakeAttempts({ attention, ...opts })).reduce((highest, attempt) => Math.max(highest, attempt.attemptN), 0) + 1;
|
|
43
|
+
return scoped;
|
|
44
|
+
const keys = await Promise.all(scoped.map((attempt) => wakeAttentionKey(attempt.attention)));
|
|
45
|
+
return scoped.filter((_, index) => keys[index] === expected);
|
|
110
46
|
}
|
|
111
47
|
function redact(value, secret) {
|
|
112
48
|
if (typeof value === 'string') {
|
|
@@ -120,24 +56,6 @@ function redact(value, secret) {
|
|
|
120
56
|
}
|
|
121
57
|
return value;
|
|
122
58
|
}
|
|
123
|
-
async function toRow(attempt, env) {
|
|
124
|
-
const safe = redact(attempt, env.PASEO_PASSWORD);
|
|
125
|
-
return {
|
|
126
|
-
v: 1,
|
|
127
|
-
ts: safe.at,
|
|
128
|
-
attention: {
|
|
129
|
-
square_path: await canonicalSquarePath(safe.attention.squarePath),
|
|
130
|
-
act_id: formatActivityId(safe.attention.actIndex),
|
|
131
|
-
recipient: safe.attention.recipient,
|
|
132
|
-
},
|
|
133
|
-
route_kind: safe.routeKind,
|
|
134
|
-
outcome: safe.outcome,
|
|
135
|
-
...(safe.signature === undefined ? {} : { signature: safe.signature }),
|
|
136
|
-
attempt_n: safe.attemptN,
|
|
137
|
-
...(safe.message === undefined ? {} : { message: safe.message }),
|
|
138
|
-
...(safe.diagnostic === undefined ? {} : { diagnostic: safe.diagnostic }),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
59
|
export async function recordWakeAttempt(attempt, env = process.env) {
|
|
142
60
|
const value = { ...attempt, at: attempt.at ?? Date.now() };
|
|
143
61
|
if (!isWakeRouteKind(value.routeKind))
|
|
@@ -145,25 +63,24 @@ export async function recordWakeAttempt(attempt, env = process.env) {
|
|
|
145
63
|
if (value.outcome !== 'accepted' && !value.signature) {
|
|
146
64
|
throw new Error(`${value.outcome} wake attempts require a transport signature.`);
|
|
147
65
|
}
|
|
148
|
-
const
|
|
149
|
-
await
|
|
150
|
-
await writeRows(file, [...await readRowsFromFile(file, value.at), await toRow(value, env)]);
|
|
151
|
-
});
|
|
66
|
+
const safe = redact(value, env.PASEO_PASSWORD);
|
|
67
|
+
await ledger(env).appendWakeAttempt({ location: safe.attention.squarePath, participant: safe.attention.recipient, session: safe.session ?? safe.signature ?? `route:${safe.routeKind}`, activity: formatActivityId(safe.attention.actIndex), kind: 'wake', outcome: safe.outcome, routeKind: safe.routeKind, signature: safe.signature, attemptN: safe.attemptN, message: safe.message, diagnostic: safe.diagnostic, at: safe.at });
|
|
152
68
|
return value;
|
|
153
69
|
}
|
|
154
70
|
export async function recordRecoveredUnknown(attention, lease, env = process.env, at = Date.now()) {
|
|
155
71
|
const routeKind = lease.routeKind;
|
|
156
72
|
if (lease.attemptN === undefined || !isWakeRouteKind(routeKind))
|
|
157
73
|
return undefined;
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const rows = await readRowsFromFile(file, at);
|
|
74
|
+
const rows = await readRows(env, at);
|
|
75
|
+
{
|
|
161
76
|
const expected = await wakeAttentionKey(attention);
|
|
162
77
|
const attempts = [];
|
|
163
|
-
for (const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
78
|
+
for (const attempt of rows) {
|
|
79
|
+
if (await wakeAttentionKey(attempt.attention) !== expected)
|
|
80
|
+
continue;
|
|
81
|
+
if (lease.session !== undefined && attempt.session !== lease.session)
|
|
82
|
+
continue;
|
|
83
|
+
attempts.push(attempt);
|
|
167
84
|
}
|
|
168
85
|
const terminal = terminalWakeEvidence(attempts);
|
|
169
86
|
if (terminal !== undefined)
|
|
@@ -177,7 +94,7 @@ export async function recordRecoveredUnknown(attention, lease, env = process.env
|
|
|
177
94
|
attemptN: lease.attemptN,
|
|
178
95
|
message: 'The notification worker ended after dispatch began; transport acceptance is unknown.',
|
|
179
96
|
};
|
|
180
|
-
await
|
|
97
|
+
await ledger(env).appendWakeAttempt({ location: attention.squarePath, participant: attention.recipient, session: lease.session ?? value.signature, activity: formatActivityId(attention.actIndex), kind: 'wake', outcome: 'unknown', routeKind, signature: value.signature, attemptN: value.attemptN, message: value.message, at });
|
|
181
98
|
return value;
|
|
182
|
-
}
|
|
99
|
+
}
|
|
183
100
|
}
|
package/dist/wake-evidence.d.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
notified: boolean;
|
|
8
|
-
presented: boolean;
|
|
9
|
-
attempts: WakeAttempt[];
|
|
10
|
-
terminal?: WakeAttempt;
|
|
11
|
-
attemptableRoutes: WakeRoute[];
|
|
12
|
-
}
|
|
13
|
-
export interface WakeEvidenceProjection {
|
|
14
|
-
evidence(recipient: string, actIndex: number): WakeEvidence;
|
|
15
|
-
}
|
|
16
|
-
/** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
|
|
1
|
+
import type { SquareState } from './model.js';
|
|
2
|
+
import { wakeIsEligible, type WakeEvidence, type WakeEvidenceProjection } from './square-projections.js';
|
|
3
|
+
import type { DeliveryModel } from './delivery.js';
|
|
4
|
+
export type { WakeEvidence, WakeEvidenceProjection };
|
|
5
|
+
export { wakeIsEligible };
|
|
6
|
+
/** Adapter entry: open the artifact, assemble host ports, and project wake evidence. */
|
|
17
7
|
export declare function wakeEvidenceProjection(squarePath: string, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidenceProjection>;
|
|
18
8
|
export declare function wakeEvidenceProjectionFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, delivery?: DeliveryModel): Promise<WakeEvidenceProjection>;
|
|
19
|
-
/** Project every wake decision from the same primary evidence. */
|
|
20
9
|
export declare function wakeEvidence(squarePath: string, recipient: string, actIndex: number, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidence>;
|
|
21
|
-
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
package/dist/wake-evidence.js
CHANGED
|
@@ -1,95 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { readPresentedAttentions } from './presented.js';
|
|
4
|
-
import { canonicalSquarePath, readActiveBindings } from './registry.js';
|
|
5
|
-
import { readWakeRoutes } from './routes.js';
|
|
6
|
-
import { isWakeRouteAttemptable, readWakeAttempts, terminalWakeEvidence, } from './wake-attempts.js';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
7
3
|
import { openSquare } from './square-file-adapter.js';
|
|
8
4
|
import { closeOpenSquare } from './open-square.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { projectWakeEvidenceFromState, wakeIsEligible, } from './square-projections.js';
|
|
6
|
+
export { wakeIsEligible };
|
|
7
|
+
function hostLedgerForEnv(env) {
|
|
8
|
+
const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
9
|
+
return createHostLedgerPort({
|
|
10
|
+
userPath: env.SQUARE_HOST_LEDGER_USER ?? root,
|
|
11
|
+
localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root,
|
|
12
|
+
readableScopes: ['user'],
|
|
13
|
+
writableScope: 'user',
|
|
14
|
+
});
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
const canonicalPath = await canonicalSquarePath(squarePath);
|
|
16
|
-
const owners = new Map();
|
|
17
|
-
for (const binding of await readActiveBindings(now)) {
|
|
18
|
-
if (binding.squarePath !== canonicalPath)
|
|
19
|
-
continue;
|
|
20
|
-
const key = nameKey(binding.name);
|
|
21
|
-
const recipientOwners = owners.get(key) ?? new Set();
|
|
22
|
-
recipientOwners.add(binding.ownerId);
|
|
23
|
-
owners.set(key, recipientOwners);
|
|
24
|
-
}
|
|
25
|
-
const routesByOwner = new Map();
|
|
26
|
-
for (const route of await readWakeRoutes({ freshOnly: true, now, env })) {
|
|
27
|
-
const routes = routesByOwner.get(route.ownerId) ?? [];
|
|
28
|
-
routes.push(route);
|
|
29
|
-
routesByOwner.set(route.ownerId, routes);
|
|
30
|
-
}
|
|
31
|
-
const attemptsByAttention = new Map();
|
|
32
|
-
for (const attempt of await readWakeAttempts({ env, now })) {
|
|
33
|
-
const key = await attentionKey(attempt.attention.squarePath, attempt.attention.recipient, attempt.attention.actIndex);
|
|
34
|
-
const attempts = attemptsByAttention.get(key) ?? [];
|
|
35
|
-
attempts.push(attempt);
|
|
36
|
-
attemptsByAttention.set(key, attempts);
|
|
37
|
-
}
|
|
38
|
-
const presentedByAttention = new Map();
|
|
39
|
-
for (const presented of await readPresentedAttentions(env, now)) {
|
|
40
|
-
const key = await attentionKey(presented.squarePath, presented.name, presented.actIndex);
|
|
41
|
-
const presentedOwners = presentedByAttention.get(key) ?? new Set();
|
|
42
|
-
presentedOwners.add(presented.ownerId);
|
|
43
|
-
presentedByAttention.set(key, presentedOwners);
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
evidence(recipient, actIndex) {
|
|
47
|
-
const recipientOwners = owners.get(nameKey(recipient)) ?? new Set();
|
|
48
|
-
const key = JSON.stringify([canonicalPath, nameKey(recipient), actIndex]);
|
|
49
|
-
const attempts = attemptsByAttention.get(key) ?? [];
|
|
50
|
-
const terminal = terminalWakeEvidence(attempts);
|
|
51
|
-
const routes = [...recipientOwners].flatMap((ownerId) => routesByOwner.get(ownerId) ?? []);
|
|
52
|
-
const presented = [...(presentedByAttention.get(key) ?? [])]
|
|
53
|
-
.some((ownerId) => recipientOwners.has(ownerId));
|
|
54
|
-
return {
|
|
55
|
-
delivered: delivery.isSeen(recipient, actIndex),
|
|
56
|
-
notified: (() => {
|
|
57
|
-
const observation = observationFor(state, recipient, actIndex);
|
|
58
|
-
return observation?.state === 'notified'
|
|
59
|
-
&& observation.ownerId !== undefined
|
|
60
|
-
&& recipientOwners.has(observation.ownerId);
|
|
61
|
-
})(),
|
|
62
|
-
presented,
|
|
63
|
-
attempts,
|
|
64
|
-
...(terminal === undefined ? {} : { terminal }),
|
|
65
|
-
attemptableRoutes: terminal === undefined
|
|
66
|
-
? routes.filter((route) => isWakeRouteAttemptable(route, attempts))
|
|
67
|
-
: [],
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
|
|
16
|
+
/** Adapter entry: open the artifact, assemble host ports, and project wake evidence. */
|
|
73
17
|
export async function wakeEvidenceProjection(squarePath, now, env) {
|
|
74
|
-
const square = await openSquare(squarePath, { clock: () => now });
|
|
18
|
+
const square = await openSquare(squarePath, { clock: () => now, hostLedger: hostLedgerForEnv(env), env });
|
|
75
19
|
try {
|
|
76
|
-
const { state } = await
|
|
77
|
-
return
|
|
20
|
+
const { state } = await square.artifact.read();
|
|
21
|
+
return projectWakeEvidenceFromState({ location: squarePath, state, hostLedger: square.hostLedger, now });
|
|
78
22
|
}
|
|
79
23
|
finally {
|
|
80
24
|
await closeOpenSquare(square);
|
|
81
25
|
}
|
|
82
26
|
}
|
|
83
27
|
export function wakeEvidenceProjectionFromState(squarePath, state, now, env, delivery) {
|
|
84
|
-
return
|
|
28
|
+
return projectWakeEvidenceFromState({ location: squarePath, state, hostLedger: hostLedgerForEnv(env), now, delivery });
|
|
85
29
|
}
|
|
86
|
-
/** Project every wake decision from the same primary evidence. */
|
|
87
30
|
export async function wakeEvidence(squarePath, recipient, actIndex, now, env) {
|
|
88
31
|
return (await wakeEvidenceProjection(squarePath, now, env)).evidence(recipient, actIndex);
|
|
89
32
|
}
|
|
90
|
-
export function wakeIsEligible(evidence) {
|
|
91
|
-
return !evidence.delivered
|
|
92
|
-
&& !evidence.notified
|
|
93
|
-
&& evidence.terminal === undefined
|
|
94
|
-
&& evidence.attemptableRoutes.length > 0;
|
|
95
|
-
}
|
package/dist/wakes.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type WatchLease, type WatchOptions } from './model.js';
|
|
2
2
|
import type { OpenSquare } from './open-square.js';
|
|
3
3
|
export type WatchLeaseStart = {
|
|
4
4
|
readonly type: 'started';
|
|
@@ -20,21 +20,7 @@ export type WatchLeasePulse = {
|
|
|
20
20
|
readonly type: 'sleep';
|
|
21
21
|
readonly heartbeatAt?: number;
|
|
22
22
|
};
|
|
23
|
-
export
|
|
24
|
-
readonly type: 'delivered';
|
|
25
|
-
} | {
|
|
26
|
-
readonly type: 'busy';
|
|
27
|
-
} | {
|
|
28
|
-
readonly type: 'ambiguous';
|
|
29
|
-
readonly lease: NotifyLease;
|
|
30
|
-
} | {
|
|
31
|
-
readonly type: 'acquired';
|
|
32
|
-
readonly leaseId: string;
|
|
33
|
-
};
|
|
34
|
-
export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, ownerId?: string): Promise<WatchLeaseStart>;
|
|
23
|
+
export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions): Promise<WatchLeaseStart>;
|
|
35
24
|
export declare function pulseWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, heartbeatDue: boolean): Promise<WatchLeasePulse>;
|
|
36
25
|
export declare function releaseWatchLease(square: OpenSquare, name: string, leaseId: string | undefined): Promise<void>;
|
|
37
26
|
export declare function ownsWatchLease(square: OpenSquare, name: string, leaseId: string): Promise<boolean>;
|
|
38
|
-
export declare function claimNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string, leaseMs: number): Promise<NotifyLeaseClaim>;
|
|
39
|
-
export declare function transitionNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string, phase: NotifyLease['phase'], leaseMs: number, routeKind?: WakeRouteKind, attemptN?: number): Promise<boolean>;
|
|
40
|
-
export declare function releaseNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string): Promise<void>;
|
package/dist/wakes.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { formatActivityId } from './square-core.js';
|
|
2
|
-
import { isActivitySeen } from './delivery.js';
|
|
3
|
-
import { nameKey } from './model.js';
|
|
4
1
|
import { resolveKnownName } from './decisions.js';
|
|
5
2
|
import { WATCH_STALE_MS, currentHold, freshWatchLease, removeWatchLease, watchTerminalStatus, writeWatchLease } from './runtime.js';
|
|
6
3
|
function filter(options) {
|
|
7
4
|
return { ...(options.participants === undefined ? {} : { participants: [...options.participants] }), ...(options.mention === undefined ? {} : { mention: options.mention }) };
|
|
8
5
|
}
|
|
9
|
-
function
|
|
10
|
-
export async function acquireWatchLease(square, name, leaseId, options, ownerId) {
|
|
6
|
+
export async function acquireWatchLease(square, name, leaseId, options) {
|
|
11
7
|
const at = square.clock();
|
|
12
|
-
return square.
|
|
8
|
+
return square.artifact.transact((state) => {
|
|
13
9
|
const known = resolveKnownName(state, name);
|
|
14
10
|
const existing = freshWatchLease(state, known, at);
|
|
15
11
|
if (existing !== undefined && !options.replace)
|
|
16
12
|
return { result: { type: 'active', lease: existing } };
|
|
17
|
-
writeWatchLease(state, known, { leaseId,
|
|
13
|
+
writeWatchLease(state, known, { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
|
|
18
14
|
return { state, result: { type: 'started', leaseId, replaced: existing !== undefined, heartbeatAt: at } };
|
|
19
15
|
});
|
|
20
16
|
}
|
|
21
17
|
export async function pulseWatchLease(square, name, leaseId, options, heartbeatDue) {
|
|
22
18
|
const at = square.clock();
|
|
23
|
-
return square.
|
|
19
|
+
return square.artifact.transact((state) => {
|
|
24
20
|
const known = resolveKnownName(state, name);
|
|
25
21
|
const lease = freshWatchLease(state, known, at);
|
|
26
22
|
if (lease?.leaseId !== leaseId)
|
|
@@ -32,29 +28,13 @@ export async function pulseWatchLease(square, name, leaseId, options, heartbeatD
|
|
|
32
28
|
return { result: { type: 'terminal', status: terminal } };
|
|
33
29
|
if (!heartbeatDue)
|
|
34
30
|
return { result: { type: 'sleep' } };
|
|
35
|
-
writeWatchLease(state, known, { leaseId,
|
|
31
|
+
writeWatchLease(state, known, { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
|
|
36
32
|
return { state, result: { type: 'sleep', heartbeatAt: at } };
|
|
37
33
|
});
|
|
38
34
|
}
|
|
39
35
|
export async function releaseWatchLease(square, name, leaseId) {
|
|
40
36
|
if (leaseId === undefined)
|
|
41
37
|
return;
|
|
42
|
-
await square.
|
|
38
|
+
await square.artifact.transact((state) => { const known = resolveKnownName(state, name); return removeWatchLease(state, known, leaseId) ? { state, result: undefined } : { result: undefined }; });
|
|
43
39
|
}
|
|
44
|
-
export async function ownsWatchLease(square, name, leaseId) { const { state } = await square.
|
|
45
|
-
export async function claimNotificationLease(square, recipient, actIndex, leaseId, leaseMs) {
|
|
46
|
-
const at = square.clock();
|
|
47
|
-
const key = notificationKey(recipient, actIndex);
|
|
48
|
-
return square.cell.transact((state) => { const known = resolveKnownName(state, recipient); if (isActivitySeen(state, known, actIndex))
|
|
49
|
-
return { result: { type: 'delivered' } }; const existing = state.runtime.notifyLeases[key]; if (existing !== undefined && existing.expiresAt > at)
|
|
50
|
-
return { result: { type: 'busy' } }; if (existing?.phase === 'dispatching')
|
|
51
|
-
return { result: { type: 'ambiguous', lease: existing } }; state.runtime.notifyLeases[key] = { leaseId, expiresAt: at + leaseMs, phase: 'claimed' }; return { state, result: { type: 'acquired', leaseId } }; });
|
|
52
|
-
}
|
|
53
|
-
export async function transitionNotificationLease(square, recipient, actIndex, leaseId, phase, leaseMs, routeKind, attemptN) {
|
|
54
|
-
const at = square.clock();
|
|
55
|
-
const key = notificationKey(recipient, actIndex);
|
|
56
|
-
return square.cell.transact((state) => { if (state.runtime.notifyLeases[key]?.leaseId !== leaseId)
|
|
57
|
-
return { result: false }; state.runtime.notifyLeases[key] = { leaseId, expiresAt: at + leaseMs, phase, ...(routeKind === undefined ? {} : { routeKind }), ...(attemptN === undefined ? {} : { attemptN }) }; return { state, result: true }; });
|
|
58
|
-
}
|
|
59
|
-
export async function releaseNotificationLease(square, recipient, actIndex, leaseId) { const key = notificationKey(recipient, actIndex); await square.cell.transact((state) => { if (state.runtime.notifyLeases[key]?.leaseId !== leaseId)
|
|
60
|
-
return { result: undefined }; delete state.runtime.notifyLeases[key]; return { state, result: undefined }; }); }
|
|
40
|
+
export async function ownsWatchLease(square, name, leaseId) { const { state } = await square.artifact.read(); return freshWatchLease(state, resolveKnownName(state, name), square.clock())?.leaseId === leaseId; }
|
package/dist/watch.js
CHANGED
|
@@ -7,7 +7,7 @@ import { openParticipant } from './square-wiring.js';
|
|
|
7
7
|
import { resolveParticipant, watchPresentation } from './views.js';
|
|
8
8
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
9
9
|
import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
|
|
10
|
-
import { hasAutomaticDeliveryIdentity
|
|
10
|
+
import { hasAutomaticDeliveryIdentity } from './registry.js';
|
|
11
11
|
import { parseActivityId } from './square-core.js';
|
|
12
12
|
function watchStatusExitCode(status) {
|
|
13
13
|
return status === 'capped' ? 1 : 0;
|
|
@@ -87,8 +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
|
-
|
|
91
|
-
return acquireWatchLease(square, name, id, opts, ownerId);
|
|
90
|
+
return acquireWatchLease(square, name, id, opts);
|
|
92
91
|
}
|
|
93
92
|
async function endWatch(square, name, id) {
|
|
94
93
|
await releaseWatchLease(square, name, id);
|
package/extensions/square-pi.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
2
|
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
3
3
|
import { waitForSessionPending } from '../dist/inbox.js';
|
|
4
|
-
import {
|
|
4
|
+
import { projectSessionBindings } from '../dist/square-projections.js';
|
|
5
|
+
import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
|
|
5
6
|
|
|
6
7
|
const PI_SEND_TIMEOUT_MS = 5_000;
|
|
7
8
|
const DEFAULT_PI_BOUNDARY_TIMEOUT_MS = 2_000;
|
|
8
9
|
|
|
10
|
+
function sessionBindings(sessionId) {
|
|
11
|
+
return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
function piBoundaryTimeoutMs() {
|
|
10
15
|
const configured = Number.parseInt(process.env.SQUARE_PI_BOUNDARY_TIMEOUT_MS || '', 10);
|
|
11
16
|
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PI_BOUNDARY_TIMEOUT_MS;
|
|
@@ -84,7 +89,7 @@ export default function squarePiExtension(pi) {
|
|
|
84
89
|
|
|
85
90
|
const wake = async (piContext, token, signal) => {
|
|
86
91
|
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
87
|
-
if ((await
|
|
92
|
+
if ((await sessionBindings(sessionId)).length === 0) {
|
|
88
93
|
await pause(signal, 1_000);
|
|
89
94
|
continue;
|
|
90
95
|
}
|
package/package.json
CHANGED
|
@@ -53,7 +53,7 @@ square --location <square> --as <name> done - <<'EOF'
|
|
|
53
53
|
...
|
|
54
54
|
EOF
|
|
55
55
|
|
|
56
|
-
For complete history
|
|
56
|
+
For complete history, follow the activity-id continuation commands printed by `history`.
|
|
57
57
|
|
|
58
58
|
Every activity must address at least one participant with @name. Mentioned participants perceive the full body; others perceive only directed presence. Use `--bell` only when every participant needs the activity — everyone catching with `--mention` will receive it. Precise history queries may still read original archive bodies.
|
|
59
59
|
|
|
@@ -117,7 +117,7 @@ While held, participant expression and catch pause. Join, done, status, and hist
|
|
|
117
117
|
When participants are done, collect the public activities:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
square --location <square> history --
|
|
120
|
+
square --location <square> history --no-truncate # expand preview bodies
|
|
121
121
|
square --location <square> status
|
|
122
122
|
```
|
|
123
123
|
|
package/skills/square/SKILL.md
CHANGED
|
@@ -61,7 +61,7 @@ The ownership boundary belongs here. @bob, does this match your read?
|
|
|
61
61
|
EOF
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
**Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary —
|
|
64
|
+
**Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary — `history` is a read-only archive with stable activity-id cursors.
|
|
65
65
|
|
|
66
66
|
**Discipline.** Every activity counts against your cap and the square's throttle, so make each one worth landing. Keep private progress and tool chatter out — express only when another participant needs the thought, question, or decision.
|
|
67
67
|
|
|
@@ -98,13 +98,14 @@ square --location <square> --as <name> ignore <participant>
|
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
square history --limit 5 # most recent 5, oldest to newest
|
|
101
|
+
square history --before act/12 --limit 5 # the page before act/12
|
|
102
|
+
square history --after act/12 --limit 5 # the page after act/12
|
|
101
103
|
square history --limit 5 --order desc # newest first
|
|
102
|
-
square history --
|
|
103
|
-
square history --full # expand bodies in range
|
|
104
|
+
square history --no-truncate # expand preview bodies
|
|
104
105
|
square history --grep 'term' # search
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly
|
|
108
|
+
See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly. Bodies are previews by default; follow the printed activity-id command to continue page by page.
|
|
108
109
|
|
|
109
110
|
## Hold
|
|
110
111
|
|