@astrosheep/square 0.3.58 → 0.3.62
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 +5 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.d.ts +6 -2
- package/dist/activity.js +170 -133
- package/dist/artifact.d.ts +4 -0
- package/dist/artifact.js +43 -14
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +21 -19
- package/dist/automatic-session.js +16 -26
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/catch-decisions.js +1 -1
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +17 -33
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +2 -0
- package/dist/cli/mcp-server-command.js +13 -0
- package/dist/cli/observation-commands.js +132 -64
- package/dist/cli/program.js +36 -9
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +103 -133
- package/dist/decisions.d.ts +4 -0
- package/dist/decisions.js +82 -34
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +1 -4
- package/dist/file-changes.d.ts +12 -0
- package/dist/file-changes.js +235 -0
- package/dist/help.js +4 -8
- package/dist/host-ledger-file-adapter.d.ts +15 -13
- package/dist/host-ledger-file-adapter.js +131 -79
- package/dist/host-ledger-root.d.ts +2 -0
- package/dist/host-ledger-root.js +9 -0
- package/dist/host-ledger.d.ts +4 -24
- package/dist/inbox.d.ts +10 -5
- package/dist/inbox.js +152 -67
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/list.js +12 -9
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +122 -0
- package/dist/mention-parse.d.ts +8 -0
- package/dist/mention-parse.js +107 -0
- package/dist/model.d.ts +5 -0
- package/dist/model.js +4 -1
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.js +25 -20
- package/dist/participant-host.js +2 -2
- package/dist/ports.d.ts +2 -9
- package/dist/presentation.d.ts +19 -1
- package/dist/presentation.js +111 -73
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +43 -76
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +39 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +203 -87
- package/dist/square-application.d.ts +58 -0
- package/dist/square-application.js +183 -0
- package/dist/square-facade.d.ts +24 -9
- package/dist/square-file-adapter.d.ts +5 -12
- package/dist/square-file-adapter.js +12 -64
- package/dist/square-projections.d.ts +3 -2
- package/dist/square-projections.js +12 -3
- package/dist/square-storage.js +27 -34
- package/dist/square-wiring.d.ts +12 -7
- package/dist/square-wiring.js +36 -32
- package/dist/state-cell.d.ts +1 -1
- package/dist/stream.js +31 -27
- package/dist/tty-style.d.ts +7 -0
- package/dist/tty-style.js +13 -0
- package/dist/views.d.ts +3 -3
- package/dist/views.js +10 -9
- package/dist/wake-attempts.js +2 -1
- package/dist/wake-evidence.js +2 -8
- package/dist/wakes.d.ts +1 -0
- package/dist/wakes.js +5 -5
- package/dist/watch.d.ts +6 -1
- package/dist/watch.js +43 -28
- package/extensions/square-pi.js +107 -109
- package/guides/application-boundary.md +32 -0
- package/guides/change-waits.md +28 -0
- package/guides/host-ledger.md +9 -0
- package/guides/mcp.md +9 -0
- package/guides/operation-cancellation.md +17 -0
- package/package.json +10 -4
- package/skills/square/SKILL.md +5 -7
package/dist/square-wiring.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { closeOpenSquare } from './open-square.js';
|
|
2
2
|
import { buildMemorySquare, buildSquare, openSquare } from './square-file-adapter.js';
|
|
3
|
-
import { catchUp, done, endOwnedSession, express, hold, ignore, implicitJoin, join, takeover, listen, listening, resume, } from './square-actions.js';
|
|
3
|
+
import { catchUp, done, endOwnedSession, express, hold, ignore, implicitJoin, join, takeover, listen, listening, resume, retireEndedSessionRoutes, } from './square-actions.js';
|
|
4
4
|
import { markBoundarySeen as recordBoundarySeen } from './presence.js';
|
|
5
5
|
import { history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
|
|
6
6
|
import { currentParticipant } from './views.js';
|
|
7
7
|
import { projectSessionBindings } from './square-projections.js';
|
|
8
|
-
import { reconcileBinding as reconcileBindingOperation } from './delivery-operations.js';
|
|
9
8
|
class ParticipantHandle {
|
|
10
9
|
name;
|
|
11
10
|
square;
|
|
@@ -15,32 +14,32 @@ class ParticipantHandle {
|
|
|
15
14
|
this.square = square;
|
|
16
15
|
this.context = context;
|
|
17
16
|
}
|
|
18
|
-
express(body, options) {
|
|
19
|
-
return express(this.context, this.name, body, options);
|
|
17
|
+
express(body, options, control) {
|
|
18
|
+
return express(this.context, this.name, body, options, control);
|
|
20
19
|
}
|
|
21
|
-
listen(target) {
|
|
22
|
-
return listen(this.context, this.name, target);
|
|
20
|
+
listen(target, control) {
|
|
21
|
+
return listen(this.context, this.name, target, control);
|
|
23
22
|
}
|
|
24
|
-
ignore(target) {
|
|
25
|
-
return ignore(this.context, this.name, target);
|
|
23
|
+
ignore(target, control) {
|
|
24
|
+
return ignore(this.context, this.name, target, control);
|
|
26
25
|
}
|
|
27
|
-
listening() {
|
|
28
|
-
return listening(this.context, this.name);
|
|
26
|
+
listening(control) {
|
|
27
|
+
return listening(this.context, this.name, control);
|
|
29
28
|
}
|
|
30
|
-
catch(options) {
|
|
31
|
-
return catchUp(this.context, this.name, options);
|
|
29
|
+
catch(options, control) {
|
|
30
|
+
return catchUp(this.context, this.name, options, undefined, control);
|
|
32
31
|
}
|
|
33
|
-
history(query) {
|
|
34
|
-
return participantHistory(this.square, this.name, query);
|
|
32
|
+
history(query, control) {
|
|
33
|
+
return participantHistory(this.square, this.name, query, control);
|
|
35
34
|
}
|
|
36
|
-
hold(reason) {
|
|
37
|
-
return hold(this.context, this.name, reason);
|
|
35
|
+
hold(reason, control) {
|
|
36
|
+
return hold(this.context, this.name, reason, control);
|
|
38
37
|
}
|
|
39
|
-
resume() {
|
|
40
|
-
return resume(this.context, this.name);
|
|
38
|
+
resume(control) {
|
|
39
|
+
return resume(this.context, this.name, control);
|
|
41
40
|
}
|
|
42
|
-
done(body) {
|
|
43
|
-
return done(this.context, this.name, body);
|
|
41
|
+
done(body, control) {
|
|
42
|
+
return done(this.context, this.name, body, {}, control);
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
export class Square {
|
|
@@ -61,27 +60,33 @@ export class Square {
|
|
|
61
60
|
static inMemory(input) {
|
|
62
61
|
return new Square('memory', buildMemorySquare(input));
|
|
63
62
|
}
|
|
64
|
-
async join(name) {
|
|
65
|
-
const joined = await join(this.context, name);
|
|
63
|
+
async join(name, control) {
|
|
64
|
+
const joined = await join(this.context, name, control);
|
|
66
65
|
return new ParticipantHandle(joined.name, this.square, this.context);
|
|
67
66
|
}
|
|
68
|
-
async joinWithActivity(name) {
|
|
69
|
-
const joined = await join(this.context, name);
|
|
67
|
+
async joinWithActivity(name, control) {
|
|
68
|
+
const joined = await join(this.context, name, control);
|
|
70
69
|
return { participant: new ParticipantHandle(joined.name, this.square, this.context), activity: joined.activity };
|
|
71
70
|
}
|
|
72
|
-
async
|
|
73
|
-
const result = await takeover(this.context, name);
|
|
74
|
-
return new ParticipantHandle(result.name, this.square, this.context);
|
|
71
|
+
async takeoverWithActivity(name, control) {
|
|
72
|
+
const result = await takeover(this.context, name, [], control);
|
|
73
|
+
return { participant: new ParticipantHandle(result.name, this.square, this.context), activities: result.activities };
|
|
75
74
|
}
|
|
76
|
-
async
|
|
77
|
-
|
|
75
|
+
async takeover(name, control) {
|
|
76
|
+
return (await this.takeoverWithActivity(name, control)).participant;
|
|
77
|
+
}
|
|
78
|
+
doneOwnedSession(name, body, expectedEpoch, control) {
|
|
79
|
+
return done(this.context, name, body, { expectedEpoch }, control);
|
|
80
|
+
}
|
|
81
|
+
async implicitJoin(name, control) {
|
|
82
|
+
const joined = await implicitJoin(this.context, name, control);
|
|
78
83
|
return joined.state === 'done'
|
|
79
84
|
? { state: joined.state }
|
|
80
85
|
: { state: joined.state, participant: new ParticipantHandle(joined.name, this.square, this.context) };
|
|
81
86
|
}
|
|
82
87
|
participants() { return participants(this.square); }
|
|
83
88
|
snapshot() { return snapshot(this.square); }
|
|
84
|
-
history(query) { return history(this.square, query); }
|
|
89
|
+
history(query, control) { return history(this.square, query, control); }
|
|
85
90
|
async recognize(env) {
|
|
86
91
|
if (this.square.hostLedger === undefined)
|
|
87
92
|
return null;
|
|
@@ -93,7 +98,6 @@ export class Square {
|
|
|
93
98
|
hostLedger: this.square.hostLedger,
|
|
94
99
|
location: this.square.location,
|
|
95
100
|
sessionId,
|
|
96
|
-
scopes: ['user', 'local'],
|
|
97
101
|
})))).flat();
|
|
98
102
|
if (candidates.length !== 1)
|
|
99
103
|
return null;
|
|
@@ -101,8 +105,8 @@ export class Square {
|
|
|
101
105
|
return canonicalName === undefined ? null : new ParticipantHandle(canonicalName, this.square, this.context);
|
|
102
106
|
}
|
|
103
107
|
close() { return closeOpenSquare(this.square); }
|
|
104
|
-
reconcileBinding() { return reconcileBindingOperation({ artifact: this.square.artifact, hostLedger: this.square.hostLedger, location: this.location }); }
|
|
105
108
|
endOwnedSession(name, sessionId, expectedEpoch) { return endOwnedSession(this.context, name, sessionId, expectedEpoch); }
|
|
109
|
+
retireEndedSessionRoutes(sessionId) { return retireEndedSessionRoutes(this.context, sessionId); }
|
|
106
110
|
}
|
|
107
111
|
export function markBoundarySeen(squarePath, name, actIndexes, at) {
|
|
108
112
|
return recordBoundarySeen(squarePath, name, actIndexes, at);
|
package/dist/state-cell.d.ts
CHANGED
|
@@ -9,6 +9,6 @@ export interface StateCell {
|
|
|
9
9
|
state: SquareState;
|
|
10
10
|
version: number;
|
|
11
11
|
}>;
|
|
12
|
-
changed(sinceVersion: number, timeoutMs: number): Promise<boolean>;
|
|
12
|
+
changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
13
13
|
close(): Promise<void>;
|
|
14
14
|
}
|
package/dist/stream.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { once } from 'node:events';
|
|
4
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
4
|
import { quoteShell } from './presentation.js';
|
|
6
|
-
import {
|
|
7
|
-
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { openSquare, observeSquareChanges } from './square-file-adapter.js';
|
|
8
6
|
import { closeOpenSquare } from './open-square.js';
|
|
9
7
|
import { streamProjection, streamTailProjection } from './views.js';
|
|
10
8
|
async function writeNdjson(value) {
|
|
@@ -20,38 +18,44 @@ export async function cmdStreamNdjson(squarePath, recipient, start = { kind: 'ta
|
|
|
20
18
|
}
|
|
21
19
|
let cursor = start.kind === 'after' ? start.after : -1;
|
|
22
20
|
let initial = start.kind === 'tail' ? start : undefined;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const observer = await observeSquareChanges(squarePath);
|
|
22
|
+
try {
|
|
23
|
+
while (true) {
|
|
24
|
+
const generation = observer.generation;
|
|
26
25
|
try {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
const baseline = await observer.read();
|
|
27
|
+
const square = await openSquare(squarePath);
|
|
28
|
+
let hasMore;
|
|
29
|
+
try {
|
|
30
|
+
const projection = initial === undefined
|
|
31
|
+
? await streamProjection(square, cursor, recipient)
|
|
32
|
+
: await streamTailProjection(square, initial.last, recipient);
|
|
33
|
+
for (const item of projection.activities) {
|
|
34
|
+
await writeNdjson({ seq: item.activity.index, square: squarePath, ...item.activity,
|
|
35
|
+
...(item.route === undefined ? {} : { route: item.route }) });
|
|
36
|
+
}
|
|
37
|
+
cursor = projection.cursor;
|
|
38
|
+
initial = undefined;
|
|
39
|
+
hasMore = projection.hasMore;
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
finally {
|
|
42
|
+
await closeOpenSquare(square);
|
|
43
|
+
}
|
|
44
|
+
if (!hasMore)
|
|
45
|
+
await observer.changed(baseline, Infinity);
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
catch {
|
|
48
|
+
// Repair/replacement is a storage hint, not a reason to scan history on a timer.
|
|
49
|
+
await observer.hinted(generation, Infinity);
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
await sleep(SLEEP_MS);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
observer.close();
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
export async function cmdStream(squarePath) {
|
|
54
58
|
process.stderr.write('✕ interactive stream was removed\n');
|
|
55
|
-
process.stderr.write(
|
|
59
|
+
process.stderr.write(`square --location ${quoteShell(path.resolve(squarePath))} stream --ndjson\n`);
|
|
56
60
|
process.exitCode = 2;
|
|
57
61
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const PALETTE = {
|
|
2
|
+
blocked: '\x1b[38;5;203m',
|
|
3
|
+
release: '\x1b[38;5;114m',
|
|
4
|
+
changed: '\x1b[38;5;179m',
|
|
5
|
+
dim: '\x1b[38;5;244m',
|
|
6
|
+
match: '\x1b[38;5;222m\x1b[1m',
|
|
7
|
+
};
|
|
8
|
+
export function style(role, text, opts = {}) {
|
|
9
|
+
if (!(opts.stream ?? process.stdout).isTTY || (opts.env ?? process.env).NO_COLOR !== undefined || text === '') {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
return `${PALETTE[role]}${text}\x1b[0m`;
|
|
13
|
+
}
|
package/dist/views.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { coreParticipants, coreStatus } from './decisions.js';
|
|
|
3
3
|
import { type DeliveryModel, type PlannedNotification } from './delivery.js';
|
|
4
4
|
import { type ActivitiesOptions, type ActivityObservation, type InboxNotification, type PublicAct, type RoomChangeAct, type SquareState, type StoredAct } from './model.js';
|
|
5
5
|
import type { OpenSquare } from './open-square.js';
|
|
6
|
-
import type { Activity, HistoryQuery, ParticipantStatus, SquareSnapshot } from './square-facade.js';
|
|
6
|
+
import type { Activity, HistoryQuery, OperationControl, ParticipantStatus, SquareSnapshot } from './square-facade.js';
|
|
7
7
|
export interface ActivityPresentation {
|
|
8
8
|
readonly name: string;
|
|
9
9
|
readonly roster: readonly string[];
|
|
@@ -71,8 +71,8 @@ export interface PendingDeliveryProjection {
|
|
|
71
71
|
readonly recipient: string;
|
|
72
72
|
readonly notifications: readonly PlannedNotification[];
|
|
73
73
|
}
|
|
74
|
-
export declare function history(square: OpenSquare, query?: HistoryQuery): Promise<Activity[]>;
|
|
75
|
-
export declare function participantHistory(square: OpenSquare, _name: string, query?: HistoryQuery): Promise<Activity[]>;
|
|
74
|
+
export declare function history(square: OpenSquare, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
|
|
75
|
+
export declare function participantHistory(square: OpenSquare, _name: string, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
|
|
76
76
|
export declare function resolveParticipant(square: OpenSquare, name: string): Promise<{
|
|
77
77
|
readonly name: string;
|
|
78
78
|
readonly roster: readonly string[];
|
package/dist/views.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
1
|
+
import { formatActivityId, parseActivityId, replayLandedAudiences } from './square-core.js';
|
|
2
2
|
import { deliveryDelta, directedPeerSays } from './activity-feed.js';
|
|
3
3
|
import { coreActivities, coreParticipants, coreStatus, resolveKnownName } from './decisions.js';
|
|
4
4
|
import { deriveDeliveryModel, isActivitySeen } from './delivery.js';
|
|
5
5
|
import { SquareError, nameKey } from './model.js';
|
|
6
|
-
import { countSays, currentHold, foldedState, freshWatchLease, inSquareCount, isCurrentlyJoined, resolveRosterName, rosterNames, watchTerminalStatus } from './runtime.js';
|
|
6
|
+
import { countSays, currentHold, foldedState, freshWatchLease, inSquareCount, isCurrentlyJoined, presenceAnchor, resolveRosterName, rosterNames, watchTerminalStatus } from './runtime.js';
|
|
7
7
|
function expose(stored) {
|
|
8
8
|
if (stored.kind === 'read' || stored.actor === undefined)
|
|
9
9
|
throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
|
|
10
|
-
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor, ...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}), mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}), ...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
10
|
+
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor, ...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}), ...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}), mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}), ...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
11
11
|
}
|
|
12
12
|
function parseRequiredActivityId(id) {
|
|
13
13
|
const index = parseActivityId(id);
|
|
@@ -20,7 +20,7 @@ function historyOptions(query) {
|
|
|
20
20
|
throw new SquareError('invalid_args', 'History cannot combine before and after cursors');
|
|
21
21
|
const afterIndex = query.after === undefined ? undefined : parseRequiredActivityId(query.after);
|
|
22
22
|
const beforeIndex = query.before === undefined ? undefined : parseRequiredActivityId(query.before);
|
|
23
|
-
return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.mention === undefined ? {} : { mention: query.mention }), ...(afterIndex === undefined ? {} : { afterIndex }), ...(beforeIndex === undefined ? {} : { beforeIndex }), order: 'asc' };
|
|
23
|
+
return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.fixed === undefined ? {} : { fixed: query.fixed }), ...(query.mention === undefined ? {} : { mention: query.mention }), ...(afterIndex === undefined ? {} : { afterIndex }), ...(beforeIndex === undefined ? {} : { beforeIndex }), order: 'asc' };
|
|
24
24
|
}
|
|
25
25
|
function selectHistory(stored, query) {
|
|
26
26
|
let selected = stored.filter((activity) => activity.kind !== 'read');
|
|
@@ -40,10 +40,11 @@ function statuses(square, state) {
|
|
|
40
40
|
}
|
|
41
41
|
function anchors(state, delivery) {
|
|
42
42
|
const result = {};
|
|
43
|
+
const landed = replayLandedAudiences(state.acts);
|
|
43
44
|
for (const name of delivery.participants()) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
result[
|
|
45
|
+
const anchor = presenceAnchor(state, name, landed);
|
|
46
|
+
if (anchor >= 0)
|
|
47
|
+
result[anchor] = [...(result[anchor] ?? []), name];
|
|
47
48
|
}
|
|
48
49
|
return result;
|
|
49
50
|
}
|
|
@@ -58,8 +59,8 @@ function sayNumbers(state) {
|
|
|
58
59
|
}
|
|
59
60
|
return result;
|
|
60
61
|
}
|
|
61
|
-
export async function history(square, query = {}) { const { state } = await square.artifact.read(); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
|
|
62
|
-
export async function participantHistory(square, _name, query = {}) { const { state } = await square.artifact.read(); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(expose); }
|
|
62
|
+
export async function history(square, query = {}, control) { const { state } = await square.artifact.read(control?.signal); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
|
|
63
|
+
export async function participantHistory(square, _name, query = {}, control) { const { state } = await square.artifact.read(control?.signal); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(expose); }
|
|
63
64
|
export async function resolveParticipant(square, name) { const { state } = await square.artifact.read(); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
|
|
64
65
|
export async function currentParticipant(square, name) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
|
|
65
66
|
export async function participants(square) { const { state } = await square.artifact.read(); return statuses(square, state); }
|
package/dist/wake-attempts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { isWakeRouteKind, nameKey } from './model.js';
|
|
4
5
|
import { canonicalSquarePath } from './registry.js';
|
|
@@ -9,7 +10,7 @@ import { redactDiagnostic } from './diagnostic-redaction.js';
|
|
|
9
10
|
export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
|
|
10
11
|
const VALID_OUTCOMES = new Set(['accepted', 'unknown', 'failed']);
|
|
11
12
|
function ledger(env) {
|
|
12
|
-
return createHostLedgerPort({
|
|
13
|
+
return createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
|
|
13
14
|
}
|
|
14
15
|
export function wakeAttemptsPath(env = process.env) {
|
|
15
16
|
return env.SQUARE_WAKE_ATTEMPTS || path.join(os.homedir(), '.square', 'wake-attempts.ndjsonl');
|
package/dist/wake-evidence.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
2
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
3
3
|
import { openSquare } from './square-file-adapter.js';
|
|
4
4
|
import { closeOpenSquare } from './open-square.js';
|
|
5
5
|
import { projectWakeEvidenceFromState, wakeIsEligible, } from './square-projections.js';
|
|
6
6
|
export { wakeIsEligible };
|
|
7
7
|
function hostLedgerForEnv(env) {
|
|
8
|
-
|
|
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
|
-
});
|
|
8
|
+
return createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
|
|
15
9
|
}
|
|
16
10
|
/** Adapter entry: open the artifact, assemble host ports, and project wake evidence. */
|
|
17
11
|
export async function wakeEvidenceProjection(squarePath, now, env) {
|
package/dist/wakes.d.ts
CHANGED
package/dist/wakes.js
CHANGED
|
@@ -21,15 +21,15 @@ export async function pulseWatchLease(square, name, leaseId, options, heartbeatD
|
|
|
21
21
|
const lease = freshWatchLease(state, known, at);
|
|
22
22
|
if (lease?.leaseId !== leaseId)
|
|
23
23
|
return { result: { type: 'replaced' } };
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const terminal = watchTerminalStatus(state, known);
|
|
24
|
+
const held = currentHold(state.acts).active;
|
|
25
|
+
const terminal = held ? undefined : watchTerminalStatus(state, known);
|
|
27
26
|
if (terminal !== undefined)
|
|
28
27
|
return { result: { type: 'terminal', status: terminal } };
|
|
28
|
+
const type = held ? 'held' : 'sleep';
|
|
29
29
|
if (!heartbeatDue)
|
|
30
|
-
return { result: { type
|
|
30
|
+
return { result: { type } };
|
|
31
31
|
writeWatchLease(state, known, { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
|
|
32
|
-
return { state, result: { type
|
|
32
|
+
return { state, result: { type, heartbeatAt: at } };
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
export async function releaseWatchLease(square, name, leaseId) {
|
package/dist/watch.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type WatchOptions } from './model.js';
|
|
2
|
+
interface WatchCallerContext {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly env: NodeJS.ProcessEnv;
|
|
5
|
+
}
|
|
2
6
|
/** `false` is reserved for a quiet --now; idle completion preserves its existing sweep boundary. */
|
|
3
|
-
export declare function cmdWatch(squarePath: string, name: string, opts: WatchOptions): Promise<boolean | undefined>;
|
|
7
|
+
export declare function cmdWatch(squarePath: string, name: string, opts: WatchOptions, caller?: WatchCallerContext): Promise<boolean | undefined>;
|
|
8
|
+
export {};
|
package/dist/watch.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
1
|
import { isSquareError, } from './model.js';
|
|
3
|
-
import {
|
|
2
|
+
import { STALE_MS, WATCH_HEARTBEAT_MS, inSquareCount, nowMs, } from './runtime.js';
|
|
4
3
|
import { openSquare } from './square-file-adapter.js';
|
|
5
4
|
import { closeOpenSquare } from './open-square.js';
|
|
6
|
-
import {
|
|
5
|
+
import { createSquareApplication } from './square-application.js';
|
|
7
6
|
import { resolveParticipant, watchPresentation } from './views.js';
|
|
8
7
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
9
|
-
import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchReplaceMissing, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, joinRecoveryCommand, participantsRecoveryCommand, withPathOutput, } from './presentation.js';
|
|
8
|
+
import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchReplaceMissing, renderWatchOutput, renderWatchReplaced, renderWatchStatus, formatRefusal, participantCommandPrefix, joinRecoveryCommand, participantsRecoveryCommand, withPathOutput, } from './presentation.js';
|
|
10
9
|
import { hasAutomaticDeliveryIdentity } from './registry.js';
|
|
11
10
|
import { parseActivityId } from './square-core.js';
|
|
12
11
|
function watchStatusExitCode(status) {
|
|
@@ -37,7 +36,7 @@ function watchOutputResult(squarePath, presentation, name, caught, opts = {}) {
|
|
|
37
36
|
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
38
37
|
perceptions,
|
|
39
38
|
}) + (caught.remaining > 0
|
|
40
|
-
? `\n○ ${caught.remaining} matching ${caught.remaining === 1 ? 'activity remains' : 'activities remain'}\n
|
|
39
|
+
? `\n○ ${caught.remaining} matching ${caught.remaining === 1 ? 'activity remains' : 'activities remain'}\n${catchContinuationCommand(squarePath, name, opts)}`
|
|
41
40
|
: ''),
|
|
42
41
|
remaining: caught.remaining,
|
|
43
42
|
...(opts.status ? { status: opts.status } : {}),
|
|
@@ -61,7 +60,7 @@ function writeWatchOutput(squarePath, name, presentation, stdout, remaining, sta
|
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
const fallback = showCatchHint && remaining === 0
|
|
64
|
-
?
|
|
63
|
+
? `${participantCommandPrefix(squarePath, name)} catch --idle 30m\n stay available for new activity`
|
|
65
64
|
: '';
|
|
66
65
|
process.stdout.write(withPathOutput(squarePath, [stdout.trimEnd(), fallback].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
|
|
67
66
|
}
|
|
@@ -109,7 +108,7 @@ function installWatchInterruptHandler(square, squarePath, name, currentLeaseId)
|
|
|
109
108
|
const onInterrupt = () => {
|
|
110
109
|
void (async () => {
|
|
111
110
|
await endWatch(square, name, currentLeaseId());
|
|
112
|
-
process.stdout.write(withPathOutput(squarePath, '
|
|
111
|
+
process.stdout.write(withPathOutput(squarePath, '○ you step back — catch stopped'));
|
|
113
112
|
process.exit(130);
|
|
114
113
|
})().catch((error) => {
|
|
115
114
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -121,11 +120,11 @@ function installWatchInterruptHandler(square, squarePath, name, currentLeaseId)
|
|
|
121
120
|
process.off('SIGINT', onInterrupt);
|
|
122
121
|
};
|
|
123
122
|
}
|
|
124
|
-
async function cmdWatchNow(squarePath, name, opts) {
|
|
125
|
-
const square = await openSquare(squarePath, { clock: nowMs });
|
|
126
|
-
const
|
|
123
|
+
async function cmdWatchNow(squarePath, name, opts, caller) {
|
|
124
|
+
const square = await openSquare(squarePath, { clock: nowMs, env: caller.env });
|
|
125
|
+
const application = createSquareApplication({ cwd: caller.cwd, env: caller.env, squarePath, participant: name, clock: nowMs });
|
|
127
126
|
try {
|
|
128
|
-
const caught = await
|
|
127
|
+
const caught = await application.catch({
|
|
129
128
|
...(opts.id === undefined ? {} : { id: opts.id }),
|
|
130
129
|
...(opts.participants === undefined ? {} : { from: opts.participants }),
|
|
131
130
|
...(opts.mention === undefined ? {} : { mention: true }),
|
|
@@ -140,23 +139,33 @@ async function cmdWatchNow(squarePath, name, opts) {
|
|
|
140
139
|
return caught.activities.length > 0;
|
|
141
140
|
}
|
|
142
141
|
finally {
|
|
143
|
-
await facade.close();
|
|
144
142
|
await closeOpenSquare(square);
|
|
145
143
|
}
|
|
146
144
|
}
|
|
145
|
+
async function countParticipants(square) {
|
|
146
|
+
try {
|
|
147
|
+
return inSquareCount((await square.artifact.read()).state);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
147
153
|
/** `false` is reserved for a quiet --now; idle completion preserves its existing sweep boundary. */
|
|
148
|
-
export async function cmdWatch(squarePath, name, opts) {
|
|
154
|
+
export async function cmdWatch(squarePath, name, opts, caller = { cwd: process.cwd(), env: { ...process.env } }) {
|
|
149
155
|
let square;
|
|
156
|
+
let participantCount;
|
|
150
157
|
try {
|
|
151
|
-
square = await openSquare(squarePath, { clock: nowMs });
|
|
158
|
+
square = await openSquare(squarePath, { clock: nowMs, env: caller.env });
|
|
159
|
+
participantCount = inSquareCount((await square.artifact.read()).state);
|
|
152
160
|
name = (await resolveParticipant(square, name)).name;
|
|
153
161
|
}
|
|
154
162
|
catch (err) {
|
|
155
163
|
if (isSquareError(err)) {
|
|
156
|
-
|
|
164
|
+
const bodyLines = [err.message];
|
|
157
165
|
// The caller's own name has never joined; only a join admits it.
|
|
158
|
-
if (
|
|
159
|
-
|
|
166
|
+
if (/^✕ .* has never stepped into/.test(err.message))
|
|
167
|
+
bodyLines.push(joinRecoveryCommand(squarePath, name));
|
|
168
|
+
process.stderr.write(formatRefusal(squarePath, bodyLines, participantCount === undefined ? {} : { participantCount }));
|
|
160
169
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
161
170
|
}
|
|
162
171
|
throw err;
|
|
@@ -169,19 +178,21 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
catch (err) {
|
|
181
|
+
const participantCount = await countParticipants(square);
|
|
172
182
|
await closeOpenSquare(square).catch(() => undefined);
|
|
173
183
|
if (isSquareError(err)) {
|
|
174
|
-
|
|
184
|
+
const bodyLines = [err.message];
|
|
175
185
|
// A filter target is unknown, not the caller; the roster is the useful next read.
|
|
176
|
-
if (
|
|
177
|
-
|
|
186
|
+
if (/^✕ .*(has never stepped into|is not standing in this square)/.test(err.message))
|
|
187
|
+
bodyLines.push(participantsRecoveryCommand(squarePath));
|
|
188
|
+
process.stderr.write(formatRefusal(squarePath, bodyLines, participantCount === undefined ? {} : { participantCount }));
|
|
178
189
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
179
190
|
}
|
|
180
191
|
throw err;
|
|
181
192
|
}
|
|
182
193
|
if (opts.now) {
|
|
183
194
|
await closeOpenSquare(square);
|
|
184
|
-
return cmdWatchNow(squarePath, name, opts);
|
|
195
|
+
return cmdWatchNow(squarePath, name, opts, caller);
|
|
185
196
|
}
|
|
186
197
|
const start = await beginWatch(square, squarePath, name, opts);
|
|
187
198
|
if (start.type === 'active') {
|
|
@@ -191,6 +202,7 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
191
202
|
process.exit(1);
|
|
192
203
|
}
|
|
193
204
|
let staleSince = nowMs();
|
|
205
|
+
let wasHeld = false;
|
|
194
206
|
let currentLeaseId = start.leaseId;
|
|
195
207
|
let nextHeartbeatAt = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
196
208
|
if (opts.replace) {
|
|
@@ -201,16 +213,18 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
201
213
|
}
|
|
202
214
|
const idleMs = opts.idleMs ?? STALE_MS;
|
|
203
215
|
const removeInterruptHandler = installWatchInterruptHandler(square, squarePath, name, () => currentLeaseId);
|
|
204
|
-
const
|
|
216
|
+
const application = createSquareApplication({ cwd: caller.cwd, env: caller.env, squarePath, participant: name, clock: nowMs });
|
|
205
217
|
try {
|
|
206
218
|
while (true) {
|
|
219
|
+
// Capture before evaluation: changes during catch/lease checks must not be lost.
|
|
220
|
+
const baseline = (await square.artifact.read()).version;
|
|
207
221
|
const leaseState = await pulseWatchLease(square, name, currentLeaseId, opts, nowMs() >= nextHeartbeatAt);
|
|
208
|
-
if (leaseState.type === 'sleep' && leaseState.heartbeatAt !== undefined) {
|
|
222
|
+
if ((leaseState.type === 'sleep' || leaseState.type === 'held') && leaseState.heartbeatAt !== undefined) {
|
|
209
223
|
nextHeartbeatAt = leaseState.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
210
224
|
}
|
|
211
225
|
let result = leaseState;
|
|
212
226
|
if (result.type === 'sleep') {
|
|
213
|
-
const caught = await
|
|
227
|
+
const caught = await application.catch({
|
|
214
228
|
...(opts.participants === undefined ? {} : { from: opts.participants }),
|
|
215
229
|
...(opts.mention === undefined ? {} : { mention: true }),
|
|
216
230
|
...(opts.limit === undefined ? {} : { limit: opts.limit }),
|
|
@@ -223,9 +237,10 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
223
237
|
currentLeaseId = undefined;
|
|
224
238
|
return;
|
|
225
239
|
}
|
|
226
|
-
if (result.type === 'held')
|
|
240
|
+
if (result.type === 'held' || wasHeld)
|
|
227
241
|
staleSince = nowMs();
|
|
228
|
-
|
|
242
|
+
wasHeld = result.type === 'held';
|
|
243
|
+
if (!wasHeld && nowMs() - staleSince >= idleMs) {
|
|
229
244
|
const result = !await ownsWatchLease(square, name, currentLeaseId)
|
|
230
245
|
? { type: 'replaced' }
|
|
231
246
|
: { type: 'terminal', status: 'stale' };
|
|
@@ -234,11 +249,11 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
234
249
|
return;
|
|
235
250
|
}
|
|
236
251
|
}
|
|
237
|
-
|
|
252
|
+
const nextDeadline = Math.min(nextHeartbeatAt, wasHeld ? Infinity : staleSince + idleMs);
|
|
253
|
+
await square.artifact.changed(baseline, Math.max(0, nextDeadline - nowMs()));
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
finally {
|
|
241
|
-
await facade.close();
|
|
242
257
|
await closeOpenSquare(square);
|
|
243
258
|
removeInterruptHandler();
|
|
244
259
|
}
|