@astrosheep/square 0.3.86 → 0.3.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +14 -10
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +1 -0
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +8 -11
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +3 -4
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.js +8 -3
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +20 -9
- package/dist/sqlite-square.js +471 -339
- package/dist/square-api.d.ts +1 -0
- package/dist/square-api.js +3 -4
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +14 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +1 -1
- package/dist/square-stdio.js +12 -7
- package/dist/square-tools.js +29 -2
- package/dist/stdio-server.js +4 -2
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +50 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +1 -1
- package/guides/migration.md +107 -67
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +61 -28
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +1 -1
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +55 -17
- package/skills/square/SKILL.md +1 -1
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/host-ledger.js +0 -1
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -38
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -23
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
package/dist/square-api.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SquareBackend } from './square-backend.js';
|
|
|
2
2
|
import type { SquareAtInput, SquareBuildInput, OperationControl } from './square-facade.js';
|
|
3
3
|
export type * from './square-facade.js';
|
|
4
4
|
export { squareLocation } from './square-location.js';
|
|
5
|
+
export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
5
6
|
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
|
|
6
7
|
/** The same structural resource regardless of persistence or transport. */
|
|
7
8
|
export interface Square extends SquareBackend {
|
package/dist/square-api.js
CHANGED
|
@@ -2,23 +2,22 @@ import { SqliteSquare } from './sqlite-square.js';
|
|
|
2
2
|
import { squareLocation, backendConfiguration, parseSquareLocation } from './square-location.js';
|
|
3
3
|
import { openStdioSquare } from './square-stdio.js';
|
|
4
4
|
export { squareLocation } from './square-location.js';
|
|
5
|
+
export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
5
6
|
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
|
|
6
7
|
export const Square = {
|
|
7
8
|
async at(input, control) {
|
|
8
9
|
const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
|
|
9
10
|
const location = squareLocation(captured);
|
|
10
11
|
const selected = parseSquareLocation(location);
|
|
11
|
-
const backends = await backendConfiguration(captured);
|
|
12
12
|
return selected.kind === 'local' ? SqliteSquare.at({ ...captured, path: location }, control)
|
|
13
|
-
: openStdioSquare(location, selected, captured,
|
|
13
|
+
: openStdioSquare(location, selected, captured, await backendConfiguration(captured), undefined, control);
|
|
14
14
|
},
|
|
15
15
|
async build(input, control) {
|
|
16
16
|
const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
|
|
17
17
|
const location = squareLocation(captured);
|
|
18
18
|
const selected = parseSquareLocation(location);
|
|
19
|
-
const backends = await backendConfiguration(captured);
|
|
20
19
|
return selected.kind === 'local' ? SqliteSquare.build({ ...captured, path: location }, control)
|
|
21
|
-
: openStdioSquare(location, selected, captured,
|
|
20
|
+
: openStdioSquare(location, selected, captured, await backendConfiguration(captured), {
|
|
22
21
|
markdown: input.markdown, hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
|
|
23
22
|
template: input.template, force: input.force,
|
|
24
23
|
}, control);
|
package/dist/square-core.d.ts
CHANGED
|
@@ -24,7 +24,8 @@ export type Act = {
|
|
|
24
24
|
kind: 'say';
|
|
25
25
|
actor: Participant;
|
|
26
26
|
at?: number;
|
|
27
|
-
body
|
|
27
|
+
body?: string;
|
|
28
|
+
metadata?: import('./model.js').ActivityMetadata;
|
|
28
29
|
mentions?: readonly Participant[];
|
|
29
30
|
reach?: Reach;
|
|
30
31
|
reply?: number;
|
|
@@ -59,7 +60,7 @@ export type Act = {
|
|
|
59
60
|
at?: number;
|
|
60
61
|
};
|
|
61
62
|
export declare function audienceOf(say: {
|
|
62
|
-
body
|
|
63
|
+
body?: string;
|
|
63
64
|
mentions?: readonly Participant[];
|
|
64
65
|
reach?: Reach;
|
|
65
66
|
}): Audience;
|
package/dist/square-domain.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type StoredAct } from './model.js';
|
|
2
|
-
import type { AttentionFact, LandedRecipient, ParticipantFact, SquareRead, SquareWrite } from './ports.js';
|
|
1
|
+
import { type Seat, type StoredAct } from './model.js';
|
|
2
|
+
import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SquareRead, SquareWrite } from './ports.js';
|
|
3
3
|
export interface LandingEffect {
|
|
4
4
|
readonly activity: StoredAct;
|
|
5
5
|
readonly participant?: ParticipantFact;
|
|
@@ -21,8 +21,15 @@ export interface LandingFacts {
|
|
|
21
21
|
/** The single landing transition, shared by both adapters and explicit rebuild. */
|
|
22
22
|
export declare function deriveLanding(activity: StoredAct, facts: LandingFacts): LandingEffect;
|
|
23
23
|
export declare function landingFacts(read: SquareRead, activity: StoredAct): LandingFacts;
|
|
24
|
-
export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | '
|
|
24
|
+
export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | 'updateWakeAddress' | 'cancelThrough' | 'claimDelivery' | 'prepareDelivery' | 'completeDelivery' | 'observeNativeDelivery'> {
|
|
25
|
+
/** Artifact-private seat persistence used only by this gate's lifecycle transition. */
|
|
26
|
+
storeSeat(seat: Seat): boolean;
|
|
27
|
+
removeSeat(seat: import('./ports.js').SeatRef): boolean;
|
|
25
28
|
applyLanding(effect: LandingEffect): void;
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
export interface SquareWriteGate {
|
|
31
|
+
readonly facts: SquareWrite;
|
|
32
|
+
complete(): void;
|
|
33
|
+
}
|
|
34
|
+
/** The artifact transaction supplies one captured authority; every mutation checks it here. */
|
|
35
|
+
export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter, authority: ArtifactWriteAuthority): SquareWriteGate;
|
package/dist/square-domain.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SquareError, sameName } from './model.js';
|
|
1
|
+
import { SquareError, formatActivityId, parseActivityId, sameBody, sameName, validateBody, validateName } from './model.js';
|
|
2
2
|
/** The single landing transition, shared by both adapters and explicit rebuild. */
|
|
3
3
|
export function deriveLanding(activity, facts) {
|
|
4
4
|
const audience = [];
|
|
@@ -64,18 +64,174 @@ export function landingFacts(read, activity) {
|
|
|
64
64
|
replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined,
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
function validateGeneration(value) {
|
|
68
|
+
if (typeof value !== 'string' || parseActivityId(value) === undefined)
|
|
69
|
+
throw new SquareError('invalid_args', 'Artifact authority requires a valid join generation.');
|
|
70
|
+
}
|
|
71
|
+
/** The artifact transaction supplies one captured authority; every mutation checks it here. */
|
|
72
|
+
export function wrapSquareWrite(read, writes, authority) {
|
|
73
|
+
if (authority === undefined || typeof authority !== 'object' || authority === null)
|
|
74
|
+
throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
|
|
69
75
|
const { applyLanding, ...mutations } = writes;
|
|
70
|
-
|
|
76
|
+
const expectedName = authority.kind === 'participant' ? authority.seat?.participant : authority.participant;
|
|
77
|
+
if (!['participant', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
|
|
78
|
+
throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
|
|
79
|
+
validateName(expectedName);
|
|
80
|
+
if (authority.kind === 'participant') {
|
|
81
|
+
validateBody(authority.seat.body);
|
|
82
|
+
validateGeneration(authority.seat.generation);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
validateBody(authority.body);
|
|
86
|
+
if (authority.kind === 'takeover')
|
|
87
|
+
validateGeneration(authority.expectedGeneration);
|
|
88
|
+
}
|
|
89
|
+
const assertSeat = (seat, expected = authority.kind === 'participant' ? authority.seat : undefined) => {
|
|
90
|
+
if (expected === undefined || !sameName(expected.participant, seat.participant)
|
|
91
|
+
|| seat.generation !== expected.generation || !sameBody(seat.body, expected.body))
|
|
92
|
+
throw new SquareError('already_joined', 'This write needs the current participant seat.');
|
|
93
|
+
const current = read.seat(seat.participant);
|
|
94
|
+
if (current === undefined || !sameBody(current.body, expected.body) || current.generation !== expected.generation)
|
|
95
|
+
throw new SquareError('already_joined', 'This participant seat has been replaced.');
|
|
96
|
+
};
|
|
97
|
+
const assertParticipant = (name, generation) => {
|
|
98
|
+
if (authority.kind !== 'participant')
|
|
99
|
+
throw new SquareError('already_joined', 'This write needs the current participant seat.');
|
|
100
|
+
assertSeat({ ...authority.seat, participant: name, generation: generation === undefined ? authority.seat.generation : generation });
|
|
101
|
+
};
|
|
102
|
+
let reconnectSeat;
|
|
103
|
+
let takeoverPhase = authority.kind === 'takeover' ? 'ready' : undefined;
|
|
104
|
+
if (authority.kind === 'participant') {
|
|
105
|
+
assertSeat(authority.seat);
|
|
106
|
+
}
|
|
107
|
+
else if (authority.kind === 'join') {
|
|
108
|
+
const participant = read.participant(expectedName), seat = read.seat(expectedName);
|
|
109
|
+
if (participant?.joined) {
|
|
110
|
+
if (seat === undefined || !sameBody(seat.body, authority.body))
|
|
111
|
+
throw new SquareError('already_joined', `${expectedName} is held by another body; take it over explicitly`);
|
|
112
|
+
reconnectSeat = seat;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const current = read.seat(expectedName);
|
|
117
|
+
if (current === undefined || current.generation !== authority.expectedGeneration)
|
|
118
|
+
throw new SquareError('already_joined', 'The participant seat changed before takeover.');
|
|
119
|
+
}
|
|
120
|
+
let lastActivity;
|
|
121
|
+
const facts = Object.assign({}, read, {
|
|
71
122
|
land(activity) {
|
|
123
|
+
const actor = 'actor' in activity ? activity.actor : undefined;
|
|
124
|
+
if (actor === undefined)
|
|
125
|
+
throw new SquareError('invalid_args', 'Participant activities require a named actor.');
|
|
126
|
+
validateName(actor);
|
|
127
|
+
if (!sameName(actor, expectedName))
|
|
128
|
+
throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
|
|
129
|
+
if (authority.kind === 'participant') {
|
|
130
|
+
assertSeat(authority.seat);
|
|
131
|
+
if (activity.kind === 'join')
|
|
132
|
+
throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
|
|
133
|
+
}
|
|
134
|
+
else if (authority.kind === 'join') {
|
|
135
|
+
if (activity.kind !== 'join')
|
|
136
|
+
throw new SquareError('invalid_args', 'A new seat must be established by its join activity.');
|
|
137
|
+
if (read.participant(expectedName)?.joined)
|
|
138
|
+
throw new SquareError('already_joined', `${expectedName} is already standing; join cannot replace an existing seat.`);
|
|
139
|
+
}
|
|
140
|
+
else if (takeoverPhase === 'ready') {
|
|
141
|
+
const current = read.seat(expectedName);
|
|
142
|
+
if (activity.kind !== 'done' || current === undefined || current.generation !== authority.expectedGeneration)
|
|
143
|
+
throw new SquareError('already_joined', 'Takeover must first complete the captured participant.');
|
|
144
|
+
}
|
|
145
|
+
else if (authority.kind === 'takeover' && takeoverPhase === 'done') {
|
|
146
|
+
if (activity.kind !== 'join' || !read.participant(expectedName)?.done)
|
|
147
|
+
throw new SquareError('already_joined', 'Takeover must join after its captured done activity.');
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw new SquareError('invalid_args', 'Takeover permits only one done followed by one join.');
|
|
151
|
+
}
|
|
72
152
|
const index = read.header().nextActIndex;
|
|
73
|
-
if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER)
|
|
153
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER)
|
|
74
154
|
throw new SquareError('invalid_args', 'Square activity index is exhausted.');
|
|
75
|
-
|
|
155
|
+
const captured = authority.kind === 'takeover' && activity.kind === 'done'
|
|
156
|
+
? read.seat(expectedName) : undefined;
|
|
76
157
|
const stored = { ...activity, index };
|
|
77
158
|
applyLanding(deriveLanding(stored, landingFacts(read, stored)));
|
|
78
|
-
|
|
159
|
+
if (stored.kind === 'join') {
|
|
160
|
+
if (authority.kind === 'participant')
|
|
161
|
+
throw new SquareError('invalid_args', 'Participant authority cannot establish a seat.');
|
|
162
|
+
const seat = {
|
|
163
|
+
participant: stored.actor,
|
|
164
|
+
body: authority.body,
|
|
165
|
+
generation: formatActivityId(stored.index),
|
|
166
|
+
...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }),
|
|
167
|
+
};
|
|
168
|
+
mutations.storeSeat(seat);
|
|
169
|
+
const established = read.seat(seat.participant);
|
|
170
|
+
if (established === undefined || !sameBody(established.body, seat.body) || established.generation !== seat.generation)
|
|
171
|
+
throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
|
|
172
|
+
if (authority.kind === 'takeover')
|
|
173
|
+
takeoverPhase = 'joined';
|
|
174
|
+
}
|
|
175
|
+
else if (stored.kind === 'done' && authority.kind === 'participant') {
|
|
176
|
+
if (!mutations.removeSeat(authority.seat))
|
|
177
|
+
throw new SquareError('already_joined', 'The participant seat changed while it was being completed.');
|
|
178
|
+
}
|
|
179
|
+
else if (stored.kind === 'done' && authority.kind === 'takeover') {
|
|
180
|
+
if (captured === undefined || !mutations.removeSeat({ participant: expectedName, body: captured.body, generation: captured.generation }))
|
|
181
|
+
throw new SquareError('already_joined', 'The participant seat changed while takeover was completing it.');
|
|
182
|
+
takeoverPhase = 'done';
|
|
183
|
+
}
|
|
184
|
+
lastActivity = read.activity(index);
|
|
185
|
+
return lastActivity;
|
|
186
|
+
},
|
|
187
|
+
updateWakeAddress(seat, wakeAddress) {
|
|
188
|
+
assertSeat(seat, authority.kind === 'join' ? reconnectSeat : undefined);
|
|
189
|
+
return mutations.updateWakeAddress(seat, wakeAddress);
|
|
190
|
+
},
|
|
191
|
+
cancelThrough(seat, through) {
|
|
192
|
+
assertSeat(seat);
|
|
193
|
+
if (!Number.isSafeInteger(through) || through < 0 || through > read.header().lastActIndex || read.activity(through) === undefined)
|
|
194
|
+
throw new SquareError('invalid_args', 'Cancellation boundary must name an existing activity at or below the current head.');
|
|
195
|
+
return mutations.cancelThrough(seat, through);
|
|
196
|
+
},
|
|
197
|
+
markSeen(name, indexes, at) {
|
|
198
|
+
assertParticipant(name);
|
|
199
|
+
return mutations.markSeen(name, indexes, at);
|
|
200
|
+
},
|
|
201
|
+
putLease(name, lease, generation) {
|
|
202
|
+
assertParticipant(name, generation);
|
|
203
|
+
return mutations.putLease(name, lease, generation);
|
|
204
|
+
},
|
|
205
|
+
removeLease(name, leaseId, generation) {
|
|
206
|
+
assertParticipant(name, generation);
|
|
207
|
+
return mutations.removeLease(name, leaseId, generation);
|
|
208
|
+
},
|
|
209
|
+
claimDelivery(input) {
|
|
210
|
+
assertSeat(input.seat);
|
|
211
|
+
return mutations.claimDelivery(input);
|
|
212
|
+
},
|
|
213
|
+
prepareDelivery(input) {
|
|
214
|
+
assertSeat(input.seat);
|
|
215
|
+
return mutations.prepareDelivery(input);
|
|
216
|
+
},
|
|
217
|
+
completeDelivery(input) {
|
|
218
|
+
assertSeat(input.seat);
|
|
219
|
+
return mutations.completeDelivery(input);
|
|
220
|
+
},
|
|
221
|
+
observeNativeDelivery(input) {
|
|
222
|
+
assertSeat(input.seat);
|
|
223
|
+
return mutations.observeNativeDelivery(input);
|
|
79
224
|
},
|
|
80
225
|
});
|
|
226
|
+
return {
|
|
227
|
+
facts,
|
|
228
|
+
complete() {
|
|
229
|
+
if (authority.kind !== 'takeover')
|
|
230
|
+
return;
|
|
231
|
+
const seat = read.seat(expectedName);
|
|
232
|
+
if (takeoverPhase !== 'joined' || lastActivity?.kind !== 'join' || seat === undefined
|
|
233
|
+
|| !sameBody(seat.body, authority.body) || seat.generation !== formatActivityId(lastActivity.index))
|
|
234
|
+
throw new SquareError('already_joined', 'Takeover did not complete its replacement join.');
|
|
235
|
+
},
|
|
236
|
+
};
|
|
81
237
|
}
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { SquareBackends } from './square-backend.js';
|
|
2
2
|
import type { ActivityId, Perception, Reach } from './square-core.js';
|
|
3
|
-
import type { StoredAct } from './model.js';
|
|
4
|
-
import type {
|
|
3
|
+
import type { Body, StoredAct, WakeAddress, ActivityMetadata } from './model.js';
|
|
4
|
+
import type { WakeTransportPort, DeliveryResult, SquareResourceOwner } from './ports.js';
|
|
5
5
|
export type { ActivityId, Perception, Reach } from './square-core.js';
|
|
6
|
+
export type { Body, WakeAddress, ActivityMetadata, JsonValue } from './model.js';
|
|
6
7
|
/** Indexed archive event, including read and historical actorless lifecycle records. */
|
|
7
8
|
export type ArchiveRecord = Readonly<StoredAct>;
|
|
8
9
|
export interface Activity {
|
|
@@ -11,6 +12,7 @@ export interface Activity {
|
|
|
11
12
|
readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
|
|
12
13
|
readonly actor: string;
|
|
13
14
|
readonly body?: string;
|
|
15
|
+
readonly metadata?: ActivityMetadata;
|
|
14
16
|
readonly mentions: readonly string[];
|
|
15
17
|
readonly reach?: Reach;
|
|
16
18
|
readonly target?: string;
|
|
@@ -24,6 +26,7 @@ export interface ObservationOptions {
|
|
|
24
26
|
readonly observe?: boolean;
|
|
25
27
|
}
|
|
26
28
|
export interface ExpressOptions extends ObservationOptions {
|
|
29
|
+
readonly metadata?: ActivityMetadata;
|
|
27
30
|
readonly force?: boolean;
|
|
28
31
|
readonly mentions?: readonly string[];
|
|
29
32
|
readonly reach?: Reach;
|
|
@@ -31,6 +34,9 @@ export interface ExpressOptions extends ObservationOptions {
|
|
|
31
34
|
readonly noMention?: boolean;
|
|
32
35
|
readonly noWait?: boolean;
|
|
33
36
|
}
|
|
37
|
+
export interface ExpressInput extends ExpressOptions {
|
|
38
|
+
readonly body?: string;
|
|
39
|
+
}
|
|
34
40
|
export interface ActivityResult {
|
|
35
41
|
readonly activity: Activity;
|
|
36
42
|
readonly delivery?: DeliveryResult;
|
|
@@ -147,9 +153,11 @@ export interface SquareSnapshot {
|
|
|
147
153
|
}
|
|
148
154
|
export interface OpenOptions {
|
|
149
155
|
readonly clock?: () => number;
|
|
150
|
-
readonly hostLedger?: HostLedgerPort;
|
|
151
156
|
readonly wakeTransport?: WakeTransportPort;
|
|
152
|
-
|
|
157
|
+
/** Explicit execution coordinate resolved by an adapter. */
|
|
158
|
+
readonly body?: Body;
|
|
159
|
+
/** Optional wake address attached to this body. */
|
|
160
|
+
readonly wakeAddress?: WakeAddress;
|
|
153
161
|
}
|
|
154
162
|
export interface SquareLocation {
|
|
155
163
|
readonly path?: string;
|
|
@@ -181,13 +189,10 @@ export interface JoinResult {
|
|
|
181
189
|
readonly participantCount: number;
|
|
182
190
|
readonly observation?: JoinObservation;
|
|
183
191
|
}
|
|
184
|
-
/** Optional fence for owner-scoped lifecycle actions. */
|
|
185
|
-
export interface OwnershipFenceOptions {
|
|
186
|
-
readonly expectedEpoch?: number;
|
|
187
|
-
}
|
|
188
192
|
export interface Participant {
|
|
189
193
|
readonly name: string;
|
|
190
|
-
express(body: string, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
|
|
194
|
+
express(body: string | undefined, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
|
|
195
|
+
express(input: ExpressInput, control?: OperationControl): Promise<ExpressResult>;
|
|
191
196
|
listen(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
192
197
|
ignore(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
193
198
|
unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
@@ -356,7 +361,6 @@ export interface ExpressObservation extends SquareObservation, ExpressionObserva
|
|
|
356
361
|
readonly pendingPublic: readonly ArchiveRecord[];
|
|
357
362
|
readonly sayNumbers: Readonly<Record<number, number>>;
|
|
358
363
|
readonly perceptions: Readonly<Record<number, Perception>>;
|
|
359
|
-
readonly reentry?: Activity;
|
|
360
364
|
}
|
|
361
365
|
export interface CatchObservation extends SquareObservation, ExpressionObservation {
|
|
362
366
|
readonly participant: string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type VersionObserver } from './file-changes.js';
|
|
2
|
+
import { type Body } from './model.js';
|
|
3
|
+
/** A rebuildable discovery hint: the body that may have used an artifact and its path.
|
|
4
|
+
* It is deliberately not a participant, generation, ownership, suppression, or evidence row. */
|
|
5
|
+
export interface SquareIndexEntry {
|
|
6
|
+
readonly location: string;
|
|
7
|
+
readonly body: Body;
|
|
8
|
+
}
|
|
9
|
+
export interface SquareIndexQuery {
|
|
10
|
+
readonly body?: Body;
|
|
11
|
+
readonly channel?: string;
|
|
12
|
+
readonly session?: string;
|
|
13
|
+
readonly agent?: string;
|
|
14
|
+
}
|
|
15
|
+
/** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
|
|
16
|
+
export declare function squareIndexPath(env?: NodeJS.ProcessEnv): string;
|
|
17
|
+
/** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
|
|
18
|
+
export declare function indexedSquareEntries(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly SquareIndexEntry[]>;
|
|
19
|
+
export declare function indexedSquarePaths(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly string[]>;
|
|
20
|
+
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
21
|
+
export declare function rememberSquarePath(location: string, body: Body, index: string): Promise<boolean>;
|
|
22
|
+
/** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
|
|
23
|
+
export declare function observeSquareIndex(env?: NodeJS.ProcessEnv): Promise<VersionObserver<string>>;
|
|
24
|
+
export declare function bodyKey(body: Body): string;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { withFileLock } from './file-lock.js';
|
|
6
|
+
import { canonicalPath } from './canonical-path.js';
|
|
7
|
+
import { observeFileMetadata } from './file-changes.js';
|
|
8
|
+
import { sameBody, validateBody } from './model.js';
|
|
9
|
+
/** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
|
|
10
|
+
export function squareIndexPath(env = process.env) {
|
|
11
|
+
const configured = env.SQUARE_INDEX?.trim();
|
|
12
|
+
return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
|
|
13
|
+
}
|
|
14
|
+
function lockPath(index) {
|
|
15
|
+
return `${index}.lock`;
|
|
16
|
+
}
|
|
17
|
+
function validEntry(value) {
|
|
18
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return undefined;
|
|
20
|
+
const row = value;
|
|
21
|
+
if (typeof row.location !== 'string' || !path.isAbsolute(row.location)
|
|
22
|
+
|| row.body === null || typeof row.body !== 'object' || Array.isArray(row.body))
|
|
23
|
+
return undefined;
|
|
24
|
+
const raw = row.body;
|
|
25
|
+
const body = {
|
|
26
|
+
channel: raw.channel,
|
|
27
|
+
session: raw.session,
|
|
28
|
+
...(raw.agent === undefined ? {} : { agent: raw.agent }),
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
validateBody(body);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(raw).some((key) => !['channel', 'session', 'agent'].includes(key)))
|
|
37
|
+
return undefined;
|
|
38
|
+
return { location: row.location, body };
|
|
39
|
+
}
|
|
40
|
+
async function readEntries(index) {
|
|
41
|
+
let content;
|
|
42
|
+
try {
|
|
43
|
+
content = await fs.readFile(index, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error.code === 'ENOENT')
|
|
47
|
+
return [];
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
const entries = [];
|
|
51
|
+
for (const line of content.split('\n')) {
|
|
52
|
+
if (line.trim() === '')
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
const entry = validEntry(JSON.parse(line));
|
|
56
|
+
if (entry !== undefined)
|
|
57
|
+
entries.push(entry);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// The index is disposable discovery input. A malformed line is ignored rather than
|
|
61
|
+
// becoming a second authority or preventing access to valid entries beside it.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return entries;
|
|
65
|
+
}
|
|
66
|
+
function matches(entry, query) {
|
|
67
|
+
if (query.body !== undefined && !sameBody(entry.body, query.body))
|
|
68
|
+
return false;
|
|
69
|
+
if (query.channel !== undefined && entry.body.channel !== query.channel)
|
|
70
|
+
return false;
|
|
71
|
+
if (query.session !== undefined && entry.body.session !== query.session)
|
|
72
|
+
return false;
|
|
73
|
+
if (query.agent !== undefined && entry.body.agent !== query.agent)
|
|
74
|
+
return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
|
|
78
|
+
export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
79
|
+
const entries = await readEntries(squareIndexPath(env));
|
|
80
|
+
const result = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (!matches(entry, query))
|
|
84
|
+
continue;
|
|
85
|
+
let location;
|
|
86
|
+
try {
|
|
87
|
+
location = await canonicalPath(entry.location);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const key = JSON.stringify([location, entry.body.channel, entry.body.session, entry.body.agent ?? null]);
|
|
93
|
+
if (seen.has(key))
|
|
94
|
+
continue;
|
|
95
|
+
seen.add(key);
|
|
96
|
+
result.push({ location, body: entry.body });
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
export async function indexedSquarePaths(query = {}, env = process.env) {
|
|
101
|
+
return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
|
|
102
|
+
}
|
|
103
|
+
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
104
|
+
export async function rememberSquarePath(location, body, index) {
|
|
105
|
+
validateBody(body);
|
|
106
|
+
const canonical = await canonicalPath(location);
|
|
107
|
+
await fs.mkdir(path.dirname(index), { recursive: true });
|
|
108
|
+
return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
|
|
109
|
+
const entries = await readEntries(index);
|
|
110
|
+
if (entries.some((entry) => entry.location === canonical && sameBody(entry.body, body)))
|
|
111
|
+
return false;
|
|
112
|
+
const next = [...entries, { location: canonical, body }];
|
|
113
|
+
const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
|
|
114
|
+
try {
|
|
115
|
+
await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
|
|
116
|
+
await fs.rename(temporary, index);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await fs.rm(temporary, { force: true });
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}) ?? false;
|
|
123
|
+
}
|
|
124
|
+
/** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
|
|
125
|
+
export function observeSquareIndex(env = process.env) {
|
|
126
|
+
return observeFileMetadata(squareIndexPath(env));
|
|
127
|
+
}
|
|
128
|
+
export function bodyKey(body) {
|
|
129
|
+
validateBody(body);
|
|
130
|
+
return JSON.stringify([body.channel, body.session, body.agent ?? null]);
|
|
131
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
export declare const FORMAT4_ADDED_OBJECTS: ReadonlySet<string>;
|
|
3
|
+
export declare const FORMAT4_REPLACED_OBJECTS: ReadonlySet<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Read-only recognition of the exact row framing accepted by explicit migration.
|
|
6
|
+
* It inspects identity, owned schema and journal mode only; it never reads row
|
|
7
|
+
* contents (including legacy routes), changes pragmas, or performs DDL.
|
|
8
|
+
*/
|
|
9
|
+
export declare function legacyRowFramingIssue(database: DatabaseSync, applicationId: number, version: 2 | 3, location: string, currentSchemaSql: string): string | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const FORMAT4_ADDED_OBJECTS = new Set([
|
|
2
|
+
'seats', 'seat_body', 'delivery_evidence', 'delivery_claim_token', 'delivery_activity', 'delivery_cancellations',
|
|
3
|
+
]);
|
|
4
|
+
export const FORMAT4_REPLACED_OBJECTS = new Set(['routes', 'route_participant', 'route_session']);
|
|
5
|
+
const LEGACY_ROUTES_SQL = `CREATE TABLE routes (
|
|
6
|
+
position INTEGER PRIMARY KEY CHECK (position >= 0),
|
|
7
|
+
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
8
|
+
session_json TEXT COLLATE BINARY NOT NULL,
|
|
9
|
+
kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','claude-native','paseo')),
|
|
10
|
+
updated_at REAL NOT NULL,
|
|
11
|
+
payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
|
|
12
|
+
);
|
|
13
|
+
CREATE INDEX route_participant ON routes(participant_key_json, position);
|
|
14
|
+
CREATE INDEX route_session ON routes(session_json, position);`;
|
|
15
|
+
function normalizedSql(sql) {
|
|
16
|
+
return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
|
|
17
|
+
}
|
|
18
|
+
function legacyRowSchema(currentSchemaSql, version) {
|
|
19
|
+
const statements = currentSchemaSql.split(';').map((statement) => statement.trim()).filter(Boolean)
|
|
20
|
+
.filter((statement) => {
|
|
21
|
+
const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
|
|
22
|
+
return match === null || !FORMAT4_ADDED_OBJECTS.has(match[1]) && !FORMAT4_REPLACED_OBJECTS.has(match[1]);
|
|
23
|
+
});
|
|
24
|
+
const legacyStatements = statements.join(';\n')
|
|
25
|
+
.replace(' delivery_revision INTEGER NOT NULL CHECK (delivery_revision BETWEEN 0 AND 9007199254740991),\n', '')
|
|
26
|
+
.replace(' generation_json TEXT NOT NULL DEFAULT \'""\',\n', '');
|
|
27
|
+
const allStatements = [...legacyStatements.split(';').map((statement) => statement.trim()).filter(Boolean),
|
|
28
|
+
...LEGACY_ROUTES_SQL.split(';').map((statement) => statement.trim()).filter(Boolean)];
|
|
29
|
+
let schema = allStatements.join(';\n');
|
|
30
|
+
if (version === 2) {
|
|
31
|
+
schema = schema.replace("kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')", "kind IN ('join','done','say','hold','resume','read','listen','ignore')");
|
|
32
|
+
}
|
|
33
|
+
return schema.split(';').map((statement) => normalizedSql(statement)).filter(Boolean).sort();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read-only recognition of the exact row framing accepted by explicit migration.
|
|
37
|
+
* It inspects identity, owned schema and journal mode only; it never reads row
|
|
38
|
+
* contents (including legacy routes), changes pragmas, or performs DDL.
|
|
39
|
+
*/
|
|
40
|
+
export function legacyRowFramingIssue(database, applicationId, version, location, currentSchemaSql) {
|
|
41
|
+
const identity = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
|
|
42
|
+
if (identity.application_id !== applicationId)
|
|
43
|
+
return 'unrelated SQLite database.';
|
|
44
|
+
if (identity.user_version !== version)
|
|
45
|
+
return 'unsupported SQLite format version.';
|
|
46
|
+
const actual = database.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all()
|
|
47
|
+
.map((row) => typeof row.sql === 'string' ? normalizedSql(row.sql) : '')
|
|
48
|
+
.sort();
|
|
49
|
+
const expected = legacyRowSchema(currentSchemaSql, version);
|
|
50
|
+
if (actual.length !== expected.length || actual.some((sql, index) => sql !== expected[index]))
|
|
51
|
+
return 'unsupported row schema.';
|
|
52
|
+
const journalMode = database.prepare('PRAGMA journal_mode').get().journal_mode;
|
|
53
|
+
if (journalMode !== (location === ':memory:' ? 'memory' : 'delete'))
|
|
54
|
+
return 'unsupported journal mode.';
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Body } from './model.js';
|
|
2
|
+
/** Offline conversion input only; never a runtime ownership or evidence reader. */
|
|
3
|
+
export interface LegacyOwner {
|
|
4
|
+
readonly participant: string;
|
|
5
|
+
readonly body: Body;
|
|
6
|
+
}
|
|
7
|
+
export interface LegacyHostData {
|
|
8
|
+
readonly owners: readonly LegacyOwner[];
|
|
9
|
+
readonly evidence: readonly LegacyDeliveryEvidence[];
|
|
10
|
+
}
|
|
11
|
+
export interface LegacyDeliveryEvidence {
|
|
12
|
+
readonly v: 1;
|
|
13
|
+
readonly location: string;
|
|
14
|
+
readonly participant: string;
|
|
15
|
+
readonly session: string;
|
|
16
|
+
readonly activity: string;
|
|
17
|
+
readonly kind: 'wake' | 'presentation' | 'lifecycle';
|
|
18
|
+
readonly outcome: string;
|
|
19
|
+
readonly at?: number;
|
|
20
|
+
readonly expiresAt?: number;
|
|
21
|
+
readonly attemptN?: number;
|
|
22
|
+
readonly claimToken?: string;
|
|
23
|
+
readonly [key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** One frozen snapshot, latest owners only; no age, expiry, or ownership history inference. */
|
|
26
|
+
export declare function readLegacyHostData(location: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<LegacyHostData>;
|