@astrosheep/square 0.3.30 → 0.3.32
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 +8 -3
- package/dist/artifact.js +10 -51
- package/dist/automatic-session.js +49 -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 +2 -1
- package/dist/claude-hook.js +5 -2
- 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 +18 -12
- package/dist/codex-hook.d.ts +2 -1
- package/dist/codex-hook.js +5 -2
- package/dist/codex-queue.js +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 +207 -0
- package/dist/delivery.d.ts +1 -0
- package/dist/help.js +1 -1
- package/dist/host-ledger-file-adapter.d.ts +34 -0
- package/dist/host-ledger-file-adapter.js +156 -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 +9 -17
- package/dist/model.js +2 -2
- package/dist/notifications.d.ts +12 -10
- package/dist/notifications.js +122 -190
- package/dist/open-square.d.ts +5 -4
- package/dist/open-square.js +1 -1
- package/dist/paseo-delivery.js +2 -0
- package/dist/ports.d.ts +138 -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 +47 -113
- package/dist/routes.d.ts +27 -30
- package/dist/routes.js +88 -151
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3 -4
- package/dist/square-actions.d.ts +33 -0
- package/dist/square-actions.js +204 -0
- package/dist/square-facade.d.ts +10 -7
- package/dist/square-file-adapter.d.ts +5 -3
- package/dist/square-file-adapter.js +23 -5
- package/dist/square-projections.d.ts +68 -0
- package/dist/square-projections.js +92 -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/wake-port.js +1 -1
- 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
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }
|
|
6
6
|
],
|
|
7
|
-
"SessionResume": [
|
|
8
|
-
{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }
|
|
9
|
-
],
|
|
10
7
|
"SessionEnd": [
|
|
11
8
|
{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }
|
|
12
9
|
],
|
|
@@ -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
|
|
package/dist/activity.js
CHANGED
|
@@ -5,13 +5,14 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
5
5
|
import { SquareError, isSquareError, validateName } from './model.js';
|
|
6
6
|
import { expressHintLine, renderActivityBlocked, renderActivityLimit, renderExpressNoWait, renderExpressWaiting, renderPendingFeed, withPathOutput, } from './presentation.js';
|
|
7
7
|
import { nowMs, SLEEP_MS } from './runtime.js';
|
|
8
|
-
import { wakeNotifierForSquare } from './notifications.js';
|
|
9
8
|
import { openSquare } from './square-file-adapter.js';
|
|
10
9
|
import { closeOpenSquare } from './open-square.js';
|
|
11
10
|
import { Square } from './square-wiring.js';
|
|
12
11
|
import { activityPresentation, resolveParticipant } from './views.js';
|
|
13
12
|
import { formatActivityId } from './square-core.js';
|
|
14
13
|
import { formatTimestamp } from './time.js';
|
|
14
|
+
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
15
|
+
import { createDefaultWakeTransport } from './notifications.js';
|
|
15
16
|
function draftDirFor(squarePath) {
|
|
16
17
|
return path.join(path.dirname(squarePath), 'drafts');
|
|
17
18
|
}
|
|
@@ -67,7 +68,9 @@ export async function cmdActivity(squarePath, name, activity, resolveBody, opts)
|
|
|
67
68
|
const noWait = opts.noWait ?? false;
|
|
68
69
|
const reach = opts.reach === 'bell' ? 'bell' : undefined;
|
|
69
70
|
let announcedWait;
|
|
70
|
-
const
|
|
71
|
+
const ledgerRoot = process.env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(process.env.SQUARE_REGISTRY);
|
|
72
|
+
const hostLedger = createHostLedgerPort({ userPath: process.env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot, localPath: process.env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot });
|
|
73
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, hostLedger, wakeTransport: await createDefaultWakeTransport(hostLedger, nowMs) });
|
|
71
74
|
try {
|
|
72
75
|
const participant = await square.join(name);
|
|
73
76
|
while (true) {
|
|
@@ -76,11 +79,13 @@ export async function cmdActivity(squarePath, name, activity, resolveBody, opts)
|
|
|
76
79
|
const pendingPublic = before.pendingPublic;
|
|
77
80
|
const pendingRoomChanges = before.pendingRoomChanges;
|
|
78
81
|
try {
|
|
79
|
-
await participant.express(body, {
|
|
82
|
+
const result = await participant.express(body, {
|
|
80
83
|
force,
|
|
81
84
|
...(reach === undefined ? {} : { reach }),
|
|
82
85
|
...(opts.reply === undefined ? {} : { reply: formatActivityId(opts.reply) }),
|
|
83
86
|
});
|
|
87
|
+
if (result.delivery?.notCapable)
|
|
88
|
+
process.stderr.write(`! wake not-capable: ${result.delivery.notCapable}\n`);
|
|
84
89
|
const freshSquare = await openSquare(squarePath, { clock: nowMs });
|
|
85
90
|
const fresh = await activityPresentation(freshSquare, knownName).finally(() => closeOpenSquare(freshSquare));
|
|
86
91
|
const headerCount = fresh.participantCount;
|
package/dist/artifact.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { TextDecoder } from 'node:util';
|
|
5
5
|
import zlib from 'node:zlib';
|
|
6
|
-
import { isWakeRouteKind, InternalSquareError,
|
|
6
|
+
import { isWakeRouteKind, InternalSquareError, SquareError, } from './model.js';
|
|
7
7
|
import { parseActivityId } from './square-core.js';
|
|
8
8
|
const SQUARE_MAGIC = Buffer.from('SQUARE01', 'ascii');
|
|
9
9
|
const LENGTH_BYTES = 4;
|
|
@@ -37,16 +37,14 @@ function isStringArray(value) {
|
|
|
37
37
|
}
|
|
38
38
|
function validateObservation(value) {
|
|
39
39
|
return isObject(value)
|
|
40
|
-
&& hasExactKeys(value, ['state', 'at']
|
|
41
|
-
&&
|
|
42
|
-
&& isFiniteNumber(value.at)
|
|
43
|
-
&& (value.ownerId === undefined || isNonblankString(value.ownerId));
|
|
40
|
+
&& hasExactKeys(value, ['state', 'at'])
|
|
41
|
+
&& value.state === 'seen'
|
|
42
|
+
&& isFiniteNumber(value.at);
|
|
44
43
|
}
|
|
45
44
|
function validateWatchLease(value) {
|
|
46
45
|
if (!isObject(value)
|
|
47
|
-
|| !hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['
|
|
46
|
+
|| !hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['filter'])
|
|
48
47
|
|| !isNonblankString(value.leaseId)
|
|
49
|
-
|| (value.ownerId !== undefined && !isNonblankString(value.ownerId))
|
|
50
48
|
|| !isFiniteNumber(value.heartbeatAt)
|
|
51
49
|
|| !isFiniteNumber(value.expiresAt)
|
|
52
50
|
|| value.expiresAt < value.heartbeatAt)
|
|
@@ -58,49 +56,16 @@ function validateWatchLease(value) {
|
|
|
58
56
|
&& (value.filter.participants === undefined || isStringArray(value.filter.participants))
|
|
59
57
|
&& (value.filter.mention === undefined || typeof value.filter.mention === 'string');
|
|
60
58
|
}
|
|
61
|
-
function validateNotifyLease(value) {
|
|
62
|
-
if (!isObject(value)
|
|
63
|
-
|| !hasExactKeys(value, ['leaseId', 'expiresAt', 'phase'], ['attemptN', 'routeKind'])
|
|
64
|
-
|| !isNonblankString(value.leaseId)
|
|
65
|
-
|| !isFiniteNumber(value.expiresAt)
|
|
66
|
-
|| (value.phase !== 'claimed' && value.phase !== 'dispatching')
|
|
67
|
-
|| (value.attemptN !== undefined && (!Number.isSafeInteger(value.attemptN) || value.attemptN <= 0))
|
|
68
|
-
|| (value.routeKind !== undefined && !isWakeRouteKind(value.routeKind)))
|
|
69
|
-
return false;
|
|
70
|
-
return value.phase !== 'dispatching' || (value.attemptN !== undefined && value.routeKind !== undefined);
|
|
71
|
-
}
|
|
72
59
|
function validateRecord(value, item) {
|
|
73
60
|
return isObject(value)
|
|
74
61
|
&& Object.entries(value).every(([key, candidate]) => key.length > 0 && item(candidate));
|
|
75
62
|
}
|
|
76
|
-
function parseNotifyLeaseKey(key) {
|
|
77
|
-
let parsed;
|
|
78
|
-
try {
|
|
79
|
-
parsed = JSON.parse(key);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
if (!Array.isArray(parsed) || parsed.length !== 2)
|
|
85
|
-
return undefined;
|
|
86
|
-
const [id, name] = parsed;
|
|
87
|
-
if (typeof id !== 'string' || typeof name !== 'string' || name.length === 0 || nameKey(name) !== name) {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
const index = parseActivityId(id);
|
|
91
|
-
if (index === undefined)
|
|
92
|
-
return undefined;
|
|
93
|
-
if (JSON.stringify([id, name]) !== key)
|
|
94
|
-
return undefined;
|
|
95
|
-
return { index, name };
|
|
96
|
-
}
|
|
97
63
|
function validateRuntime(value) {
|
|
98
64
|
if (!isObject(value)
|
|
99
|
-
|| !hasExactKeys(value, ['nextActIndex', 'observations', 'leases'
|
|
65
|
+
|| !hasExactKeys(value, ['nextActIndex', 'observations', 'leases'])
|
|
100
66
|
|| !isNonNegativeInteger(value.nextActIndex)
|
|
101
67
|
|| !validateRecord(value.observations, (candidate) => isObject(candidate) && Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && validateObservation(observation)))
|
|
102
|
-
|| !validateRecord(value.leases, validateWatchLease)
|
|
103
|
-
|| !validateRecord(value.notifyLeases, validateNotifyLease))
|
|
68
|
+
|| !validateRecord(value.leases, validateWatchLease))
|
|
104
69
|
return false;
|
|
105
70
|
return true;
|
|
106
71
|
}
|
|
@@ -115,13 +80,6 @@ function validateAssignedRuntimeReferences(runtime) {
|
|
|
115
80
|
return 'future';
|
|
116
81
|
}
|
|
117
82
|
}
|
|
118
|
-
for (const key of Object.keys(runtime.notifyLeases)) {
|
|
119
|
-
const parsed = parseNotifyLeaseKey(key);
|
|
120
|
-
if (parsed === undefined)
|
|
121
|
-
return 'malformed';
|
|
122
|
-
if (parsed.index >= bound)
|
|
123
|
-
return 'future';
|
|
124
|
-
}
|
|
125
83
|
return 'ok';
|
|
126
84
|
}
|
|
127
85
|
function validateActor(value, required) {
|
|
@@ -180,13 +138,14 @@ function validateActs(value) {
|
|
|
180
138
|
}
|
|
181
139
|
function validateSquareState(value) {
|
|
182
140
|
if (!isObject(value)
|
|
183
|
-
|| !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute'])
|
|
141
|
+
|| !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute', 'routes'])
|
|
184
142
|
|| !(value.hardCap === null || (Number.isSafeInteger(value.hardCap) && value.hardCap > 0))
|
|
185
143
|
|| (value.throttlePerMinute !== undefined
|
|
186
144
|
&& (!Number.isSafeInteger(value.throttlePerMinute) || value.throttlePerMinute <= 0))
|
|
187
145
|
|| !isStringArray(value.preamble)
|
|
188
146
|
|| !isStringArray(value.warmup)
|
|
189
147
|
|| !validateActs(value.acts)
|
|
148
|
+
|| (value.routes !== undefined && (!Array.isArray(value.routes) || !value.routes.every((route) => isObject(route) && isWakeRouteKind(route.kind) && typeof route.location === 'string' && typeof route.participant === 'string' && typeof route.sessionId === 'string' && typeof route.channel === 'string' && isObject(route.address) && Object.values(route.address).every((item) => typeof item === 'string') && typeof route.updatedAt === 'number')))
|
|
190
149
|
|| !validateRuntime(value.runtime)) {
|
|
191
150
|
throw invalidArtifact('snapshot schema is malformed.');
|
|
192
151
|
}
|
|
@@ -253,7 +212,6 @@ export function emptyRuntimeState(nextActIndex = 0) {
|
|
|
253
212
|
nextActIndex,
|
|
254
213
|
observations: {},
|
|
255
214
|
leases: {},
|
|
256
|
-
notifyLeases: {},
|
|
257
215
|
};
|
|
258
216
|
}
|
|
259
217
|
function normalizedLines(value) {
|
|
@@ -284,6 +242,7 @@ export function createSquareState(options, snippet) {
|
|
|
284
242
|
preamble: normalizedLines(snippet),
|
|
285
243
|
warmup: normalizedLines(guides.join('\n\n')),
|
|
286
244
|
acts: [],
|
|
245
|
+
routes: [],
|
|
287
246
|
runtime: emptyRuntimeState(),
|
|
288
247
|
};
|
|
289
248
|
}
|
|
@@ -4,8 +4,10 @@ import { openSquare } from './square-file-adapter.js';
|
|
|
4
4
|
import { closeOpenSquare } from './open-square.js';
|
|
5
5
|
import { Square } from './square-wiring.js';
|
|
6
6
|
import { entryPresentation } from './views.js';
|
|
7
|
-
import { canonicalSquarePath, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
|
|
8
7
|
import { automaticParticipant } from './participant-identity.js';
|
|
8
|
+
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
9
|
+
import { projectSessionBindings } from './square-projections.js';
|
|
10
|
+
import { publishWakeRoute, retireWakeRouteFromArtifact, resolvePrimaryWakeRoute, defaultWakeRouteCapabilities } from './routes.js';
|
|
9
11
|
export { automaticParticipant } from './participant-identity.js';
|
|
10
12
|
const providerEnv = {
|
|
11
13
|
codex: 'CODEX_THREAD_ID',
|
|
@@ -13,6 +15,16 @@ const providerEnv = {
|
|
|
13
15
|
opencode: 'OPENCODE_SESSION_ID',
|
|
14
16
|
pi: 'SQUARE_PI_SESSION_ID',
|
|
15
17
|
};
|
|
18
|
+
function operationEnv(provider, sessionId, env) {
|
|
19
|
+
return { ...env, [providerEnv[provider]]: sessionId };
|
|
20
|
+
}
|
|
21
|
+
function hostLedgerForEnv(env) {
|
|
22
|
+
const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
23
|
+
return createHostLedgerPort({
|
|
24
|
+
userPath: env.SQUARE_HOST_LEDGER_USER ?? root,
|
|
25
|
+
localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
16
28
|
export function publicSquarePath(cwd) {
|
|
17
29
|
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
18
30
|
}
|
|
@@ -38,16 +50,25 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
38
50
|
return undefined;
|
|
39
51
|
}
|
|
40
52
|
const name = automaticParticipant(provider, sessionId, env);
|
|
41
|
-
const canonicalPath = await canonicalSquarePath(squarePath);
|
|
42
|
-
const alreadyBound = (await lookupSessionBindings(sessionId)).some((binding) => binding.squarePath === canonicalPath && binding.name === name);
|
|
43
53
|
await closeOpenSquare(reader);
|
|
44
|
-
const
|
|
54
|
+
const scopedEnv = operationEnv(provider, sessionId, env);
|
|
55
|
+
const square = await Square.at({ path: squarePath, hostLedger: hostLedgerForEnv(scopedEnv), env: scopedEnv });
|
|
45
56
|
try {
|
|
46
57
|
const implicit = await square.implicitJoin(name);
|
|
47
|
-
if (implicit.state === 'done'
|
|
58
|
+
if (implicit.state === 'done')
|
|
48
59
|
return undefined;
|
|
49
|
-
const
|
|
50
|
-
|
|
60
|
+
const route = resolvePrimaryWakeRoute({ location: squarePath, participant: name, sessionId, provider }, env, await defaultWakeRouteCapabilities(hostLedgerForEnv(env)));
|
|
61
|
+
if (route !== undefined) {
|
|
62
|
+
const publisher = await openSquare(squarePath, { hostLedger: hostLedgerForEnv(scopedEnv), env: scopedEnv });
|
|
63
|
+
try {
|
|
64
|
+
await publishWakeRoute(publisher.artifact, route, { at: Date.now() });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await closeOpenSquare(publisher);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await hostLedgerForEnv(env).ensurePresence({ location: squarePath, participant: name, session: sessionId, channel: provider === 'claude' ? 'claude-code' : provider, updatedAt: Date.now() }, 'user');
|
|
71
|
+
await square.reconcileBinding();
|
|
51
72
|
return undefined;
|
|
52
73
|
}
|
|
53
74
|
finally {
|
|
@@ -56,22 +77,36 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
56
77
|
}
|
|
57
78
|
export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
|
|
58
79
|
const squarePath = publicSquarePath(cwd);
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
80
|
+
const scopedEnv = operationEnv(provider, sessionId, env);
|
|
81
|
+
const hostLedger = hostLedgerForEnv(scopedEnv);
|
|
82
|
+
const probe = await openSquare(squarePath, { hostLedger, env: scopedEnv });
|
|
83
|
+
const binding = (await projectSessionBindings({
|
|
84
|
+
hostLedger,
|
|
85
|
+
sessionId,
|
|
86
|
+
location: probe.location,
|
|
87
|
+
scopes: ['user', 'local'],
|
|
88
|
+
}))[0];
|
|
89
|
+
await closeOpenSquare(probe);
|
|
62
90
|
if (binding === undefined || !await squareExists(squarePath))
|
|
63
91
|
return;
|
|
64
92
|
const reader = await openSquare(squarePath);
|
|
65
|
-
const joined = await entryPresentation(reader, binding.
|
|
93
|
+
const joined = await entryPresentation(reader, binding.participant).finally(() => closeOpenSquare(reader));
|
|
66
94
|
if (!joined.joined)
|
|
67
95
|
return;
|
|
68
|
-
const square = await Square.at({ path: squarePath });
|
|
96
|
+
const square = await Square.at({ path: squarePath, hostLedger: hostLedgerForEnv(scopedEnv), env: scopedEnv });
|
|
69
97
|
try {
|
|
70
|
-
const participant = await square.join(binding.
|
|
98
|
+
const participant = await square.join(binding.participant);
|
|
71
99
|
await participant.done();
|
|
100
|
+
await square.reconcileBinding();
|
|
101
|
+
const cleanup = await openSquare(squarePath);
|
|
102
|
+
try {
|
|
103
|
+
await retireWakeRouteFromArtifact(cleanup.artifact, { location: squarePath, participant: binding.participant, sessionId });
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await closeOpenSquare(cleanup);
|
|
107
|
+
}
|
|
72
108
|
}
|
|
73
109
|
finally {
|
|
74
110
|
await square.close();
|
|
75
111
|
}
|
|
76
|
-
await recordSessionDone(sessionId, binding.name, squarePath, channel, env);
|
|
77
112
|
}
|
|
@@ -2,4 +2,4 @@ import type { InboxMembership } from './model.js';
|
|
|
2
2
|
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
3
3
|
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
4
4
|
export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
|
|
5
|
-
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T | Promise<T>, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
|
|
5
|
+
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T | Promise<T>, lookup?: (sessionId: string, env?: NodeJS.ProcessEnv) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { leaseOwnsNotification } from './delivery.js';
|
|
2
|
-
import {
|
|
2
|
+
import { openSquare } from './square-file-adapter.js';
|
|
3
|
+
import { closeOpenSquare } from './open-square.js';
|
|
4
|
+
import { presentPending } from './presentation-operations.js';
|
|
3
5
|
import { sessionInbox } from './inbox.js';
|
|
4
6
|
import { ATTENTION_BODY_MAX, renderAttentionPreview } from './attention-presentation.js';
|
|
5
|
-
import { presentOnce } from './presented.js';
|
|
6
7
|
const CONTEXT_MAX = 1200;
|
|
8
|
+
const presentationLocks = new Map();
|
|
7
9
|
function pendingCount(inbox) {
|
|
8
10
|
return inbox.reduce((total, membership) => total + membership.notifications.length, 0);
|
|
9
11
|
}
|
|
@@ -60,9 +62,11 @@ function renderBoundary(inbox) {
|
|
|
60
62
|
continue;
|
|
61
63
|
}
|
|
62
64
|
blocks.push(block);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
complete.push({
|
|
66
|
+
membership,
|
|
67
|
+
actIndexes: [notification.actIndex],
|
|
68
|
+
markSeen: notification.body.replace(/\r\n/g, '\n').length <= ATTENTION_BODY_MAX,
|
|
69
|
+
});
|
|
66
70
|
}
|
|
67
71
|
return {
|
|
68
72
|
context: [
|
|
@@ -77,15 +81,55 @@ function renderBoundary(inbox) {
|
|
|
77
81
|
};
|
|
78
82
|
}
|
|
79
83
|
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env, signal) {
|
|
80
|
-
const inbox = await lookup(sessionId);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
const inbox = await lookup(sessionId, env);
|
|
85
|
+
if (signal?.aborted)
|
|
86
|
+
return undefined;
|
|
87
|
+
const delivered = renderBoundary(pendingAtBoundary(inbox));
|
|
88
|
+
if (delivered.context === '')
|
|
89
|
+
return undefined;
|
|
90
|
+
let result;
|
|
91
|
+
let rendered = false;
|
|
92
|
+
const renderOnce = async () => { if (!rendered) {
|
|
93
|
+
rendered = true;
|
|
94
|
+
result = await present(delivered.context);
|
|
95
|
+
} };
|
|
96
|
+
for (const entry of delivered.complete) {
|
|
97
|
+
let square;
|
|
98
|
+
try {
|
|
99
|
+
try {
|
|
100
|
+
square = await openSquare(entry.membership.squarePath, { env });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
for (const index of entry.actIndexes) {
|
|
106
|
+
const key = `${entry.membership.squarePath}\u0000${entry.membership.name.toLocaleLowerCase()}\u0000${index}`;
|
|
107
|
+
const prior = presentationLocks.get(key);
|
|
108
|
+
if (prior !== undefined) {
|
|
109
|
+
await prior;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const work = presentPending({ artifact: square.artifact, location: entry.membership.squarePath, participant: entry.membership.name, activity: index, hostLedger: square.hostLedger, session: sessionId, sink: { present: renderOnce }, markSeen: entry.markSeen, now: Date.now() }).then(async (outcome) => {
|
|
113
|
+
// A stale projection can outlive its activity; still surface the bounded preview,
|
|
114
|
+
// but there is no artifact observation to commit.
|
|
115
|
+
if (!outcome.presented) {
|
|
116
|
+
const snapshot = await square.artifact.read().catch(() => undefined);
|
|
117
|
+
if (snapshot?.state.acts.every((activity) => activity.index !== index))
|
|
118
|
+
await renderOnce();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
presentationLocks.set(key, work);
|
|
122
|
+
try {
|
|
123
|
+
await work;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
presentationLocks.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
if (square !== undefined)
|
|
132
|
+
await closeOpenSquare(square);
|
|
89
133
|
}
|
|
90
134
|
}
|
|
91
135
|
return result;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Perception } from './square-core.js';
|
|
2
|
+
import { type SquareState, type StoredAct } from './model.js';
|
|
3
|
+
import type { CatchOptions } from './square-facade.js';
|
|
4
|
+
export interface CatchDecision {
|
|
5
|
+
readonly viewer: string;
|
|
6
|
+
readonly delivered: readonly StoredAct[];
|
|
7
|
+
readonly perceptions: ReadonlyMap<number, Perception>;
|
|
8
|
+
readonly consumedThrough: string | null;
|
|
9
|
+
readonly changed: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CatchProjection {
|
|
12
|
+
cursorFor(name: string): number;
|
|
13
|
+
directedTo(activity: StoredAct, name: string): boolean;
|
|
14
|
+
perceive(activity: StoredAct, name: string): Perception;
|
|
15
|
+
}
|
|
16
|
+
/** Pure catch selection and acknowledgement over one SquareState snapshot. */
|
|
17
|
+
export declare function decideCatch(state: SquareState, requestedName: string, options: CatchOptions, at: number, project?: (state: SquareState) => CatchProjection): CatchDecision;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { audienceOf, formatActivityId, replayLandedAudiences } from './square-core.js';
|
|
2
|
+
import { matchesMentionTarget, readCursor, recordObservation } from './runtime.js';
|
|
3
|
+
import { participantIdentity } from './participant-identity.js';
|
|
4
|
+
import { SquareError, sameName, validateName } from './model.js';
|
|
5
|
+
import { resolveRosterName, rosterNames } from './runtime.js';
|
|
6
|
+
function defaultProjection(state) {
|
|
7
|
+
const landed = replayLandedAudiences(state.acts);
|
|
8
|
+
return {
|
|
9
|
+
cursorFor: (name) => readCursor(state, name, landed),
|
|
10
|
+
directedTo: (activity, name) => activity.kind === 'say' && landed.includes(activity, name),
|
|
11
|
+
perceive: (activity, name) => activity.kind !== 'say' || sameName(activity.actor, name) || landed.includes(activity, name) ? 'full' : 'presence',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function resolveCatchName(state, requestedName) {
|
|
15
|
+
validateName(requestedName);
|
|
16
|
+
const known = resolveRosterName(state, requestedName);
|
|
17
|
+
if (known === undefined) {
|
|
18
|
+
throw new SquareError('invalid_args', `Unknown participant "${participantIdentity(requestedName)}". Expected one of: ${rosterNames(state).map(participantIdentity).join(', ')}.`);
|
|
19
|
+
}
|
|
20
|
+
return known;
|
|
21
|
+
}
|
|
22
|
+
/** Pure catch selection and acknowledgement over one SquareState snapshot. */
|
|
23
|
+
export function decideCatch(state, requestedName, options, at, project = defaultProjection) {
|
|
24
|
+
const viewer = resolveCatchName(state, requestedName);
|
|
25
|
+
const delivery = project(state);
|
|
26
|
+
const cursor = delivery.cursorFor(viewer);
|
|
27
|
+
const from = options.from;
|
|
28
|
+
const mentionOnly = options.mention === true;
|
|
29
|
+
const delivered = [];
|
|
30
|
+
const perceptions = new Map();
|
|
31
|
+
for (const activity of state.acts) {
|
|
32
|
+
if (activity.index <= cursor || activity.kind !== 'say' || !delivery.directedTo(activity, viewer))
|
|
33
|
+
continue;
|
|
34
|
+
if (from !== undefined && !from.some((participant) => sameName(participant, activity.actor)))
|
|
35
|
+
continue;
|
|
36
|
+
if (mentionOnly && audienceOf(activity).kind !== 'bell' && !matchesMentionTarget(activity, viewer))
|
|
37
|
+
continue;
|
|
38
|
+
delivered.push(activity);
|
|
39
|
+
perceptions.set(activity.index, delivery.perceive(activity, viewer));
|
|
40
|
+
}
|
|
41
|
+
let changed = false;
|
|
42
|
+
for (const activity of delivered) {
|
|
43
|
+
changed = recordObservation(state, viewer, activity.index, 'seen', at) || changed;
|
|
44
|
+
}
|
|
45
|
+
const consumed = delivery.cursorFor(viewer);
|
|
46
|
+
return {
|
|
47
|
+
viewer,
|
|
48
|
+
delivered,
|
|
49
|
+
perceptions,
|
|
50
|
+
consumedThrough: consumed < 0 ? null : formatActivityId(consumed),
|
|
51
|
+
changed,
|
|
52
|
+
};
|
|
53
|
+
}
|
package/dist/claude-hook.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { InboxMembership } from './model.js';
|
|
2
|
+
import type { WakeAdapter } from './delivery.js';
|
|
2
3
|
export interface NativeHookInput {
|
|
3
4
|
session_id?: unknown;
|
|
4
5
|
hook_event_name?: unknown;
|
|
5
6
|
cwd?: unknown;
|
|
6
7
|
}
|
|
7
8
|
export declare function runClaudeHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
8
|
-
export declare function claudeHookResponse(input: NativeHookInput, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
|
|
9
|
+
export declare function claudeHookResponse(input: NativeHookInput, lookup?: (sessionId: string, env?: NodeJS.ProcessEnv) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv, deliveryAdapters?: WakeAdapter[]): Promise<object | undefined>;
|
|
9
10
|
export declare function runClaudeHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
package/dist/claude-hook.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
3
|
import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
|
|
4
|
+
import { sweepPrivilegedPending } from './notifications.js';
|
|
4
5
|
export async function runClaudeHookAsync(inputText, env = process.env) {
|
|
5
6
|
let input;
|
|
6
7
|
try {
|
|
@@ -34,12 +35,14 @@ export async function runClaudeHookAsync(inputText, env = process.env) {
|
|
|
34
35
|
}
|
|
35
36
|
return runClaudeHook(inputText, env);
|
|
36
37
|
}
|
|
37
|
-
export async function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
38
|
+
export async function claudeHookResponse(input, lookup = sessionInbox, env = process.env, deliveryAdapters) {
|
|
38
39
|
if (typeof input.session_id !== 'string' || input.session_id === '')
|
|
39
40
|
return undefined;
|
|
40
41
|
if (input.hook_event_name !== 'PostToolBatch')
|
|
41
42
|
return undefined;
|
|
42
|
-
|
|
43
|
+
const response = await presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: context } }), lookup, env);
|
|
44
|
+
await sweepPrivilegedPending(typeof input.cwd === 'string' ? input.cwd : process.cwd(), env, deliveryAdapters).catch(() => undefined);
|
|
45
|
+
return response;
|
|
43
46
|
}
|
|
44
47
|
export async function runClaudeHook(inputText, env = process.env) {
|
|
45
48
|
let input;
|
package/dist/cli/context.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { commandUsageHint } from '../help.js';
|
|
3
3
|
import { parseParticipantList, validateName } from '../model.js';
|
|
4
|
-
import {
|
|
4
|
+
import { createHostLedgerPort } from '../host-ledger-file-adapter.js';
|
|
5
|
+
import { projectSessionBindings, sessionIdsFromEnvironment } from '../square-projections.js';
|
|
5
6
|
export async function readStdin() {
|
|
6
7
|
try {
|
|
7
8
|
let content = '';
|
|
@@ -144,7 +145,14 @@ export async function parseGlobalArgs(rawArgs) {
|
|
|
144
145
|
}
|
|
145
146
|
const squarePath = requestedPath ?? configured;
|
|
146
147
|
if (name === undefined && squarePath !== undefined && command !== undefined && locationIsRequired(command)) {
|
|
147
|
-
|
|
148
|
+
const bindings = (await Promise.all(sessionIdsFromEnvironment().map((sessionId) => projectSessionBindings({
|
|
149
|
+
hostLedger: createHostLedgerPort(),
|
|
150
|
+
location: squarePath,
|
|
151
|
+
sessionId,
|
|
152
|
+
scopes: ['user', 'local'],
|
|
153
|
+
})))).flat();
|
|
154
|
+
const names = new Set(bindings.map((binding) => binding.participant));
|
|
155
|
+
name = names.size === 1 ? [...names][0] : undefined;
|
|
148
156
|
}
|
|
149
157
|
return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
|
|
150
158
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type ActivitiesOptions, type WatchOptions } from '../model.js';
|
|
2
2
|
import { type CommandSpec } from './context.js';
|
|
3
|
+
interface HistoryCommandOptions extends ActivitiesOptions {
|
|
4
|
+
noTruncate: boolean;
|
|
5
|
+
continuationArgs: string[];
|
|
6
|
+
}
|
|
3
7
|
export declare const listCommand: CommandSpec<string[]>;
|
|
4
8
|
interface StreamIntent {
|
|
5
9
|
ndjson: boolean;
|
|
@@ -7,7 +11,7 @@ interface StreamIntent {
|
|
|
7
11
|
}
|
|
8
12
|
export declare const streamCommand: CommandSpec<StreamIntent>;
|
|
9
13
|
export declare const catchCommand: CommandSpec<WatchOptions>;
|
|
10
|
-
export declare const historyCommand: CommandSpec<
|
|
14
|
+
export declare const historyCommand: CommandSpec<HistoryCommandOptions, string>;
|
|
11
15
|
export declare const participantsCommand: CommandSpec<undefined, string>;
|
|
12
16
|
export declare const statusCommand: CommandSpec<undefined, string>;
|
|
13
17
|
interface InboxIntent {
|