@astrosheep/square 0.3.106 → 0.4.1
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/activity-feed.js +1 -1
- package/dist/artifact-store.d.ts +41 -0
- package/dist/artifact-store.js +272 -0
- package/dist/artifact.d.ts +6 -12
- package/dist/artifact.js +43 -344
- package/dist/attention-presentation.d.ts +13 -7
- package/dist/attention-presentation.js +29 -68
- package/dist/boundary-presentation.d.ts +3 -16
- package/dist/boundary-presentation.js +29 -109
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.d.ts +2 -2
- package/dist/catch-decisions.js +7 -3
- package/dist/catch-selection.d.ts +8 -0
- package/dist/catch-selection.js +5 -0
- package/dist/claude-delivery.d.ts +14 -12
- package/dist/claude-delivery.js +42 -142
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +28 -140
- package/dist/claude-transport.d.ts +5 -0
- package/dist/claude-transport.js +34 -0
- package/dist/cli/context.d.ts +2 -2
- package/dist/cli/context.js +7 -10
- package/dist/cli/maintenance-commands.d.ts +0 -4
- package/dist/cli/maintenance-commands.js +2 -29
- package/dist/cli/observation-commands.d.ts +2 -0
- package/dist/cli/observation-commands.js +12 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +4 -3
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -34
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +3 -3
- package/dist/delivery-deferral.d.ts +1 -19
- package/dist/delivery-deferral.js +1 -29
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-links.js +61 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +3 -6
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -79
- package/dist/inbox.d.ts +6 -57
- package/dist/inbox.js +24 -380
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -2
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +40 -70
- package/dist/message.d.ts +7 -0
- package/dist/message.js +23 -0
- package/dist/model.d.ts +15 -91
- package/dist/model.js +2 -140
- package/dist/neutral.d.ts +72 -0
- package/dist/neutral.js +27 -0
- package/dist/notifications.d.ts +24 -26
- package/dist/notifications.js +39 -143
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +1 -7
- package/dist/owner.d.ts +2 -2
- package/dist/owner.js +1 -1
- package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -99
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +33 -214
- package/dist/presentation.d.ts +5 -5
- package/dist/presentation.js +1 -0
- package/dist/runtime.js +2 -2
- package/dist/search.js +1 -1
- package/dist/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-framing.d.ts +8 -0
- package/dist/sqlite-framing.js +31 -0
- package/dist/sqlite-square.d.ts +16 -26
- package/dist/sqlite-square.js +199 -262
- package/dist/sqlite-values.d.ts +21 -0
- package/dist/sqlite-values.js +78 -0
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +5 -8
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +12 -10
- package/dist/square-domain.js +105 -223
- package/dist/square-facade.d.ts +25 -76
- package/dist/square-location.d.ts +6 -1
- package/dist/square-location.js +1 -1
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-receive.d.ts +2 -3
- package/dist/square-sqlite.d.ts +33 -42
- package/dist/square-sqlite.js +236 -768
- package/dist/square-tools.d.ts +8 -0
- package/dist/square-tools.js +39 -0
- package/dist/square-values.d.ts +27 -0
- package/dist/square-values.js +90 -0
- package/dist/views.d.ts +6 -6
- package/dist/views.js +14 -9
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -0
- package/dist/wake-transports.js +11 -0
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +19 -172
- package/guides/codex-delivery.md +55 -78
- package/guides/delivery-primitives.md +48 -207
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/row-runtime-delivery-evidence.md +2 -0
- package/guides/row-storage-design.md +19 -44
- package/guides/square-api.md +113 -538
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/hooks.json +1 -1
- package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +167 -451
- package/dist/channel.d.ts +0 -74
- package/dist/channel.js +0 -92
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -9
- package/dist/delivery-operations.js +0 -168
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -45
- package/dist/delivery.js +0 -106
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -238
- package/dist/presentation-operations.d.ts +0 -18
- package/dist/presentation-operations.js +0 -86
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- package/dist/square-legacy-framing.d.ts +0 -11
- package/dist/square-legacy-framing.js +0 -64
- package/dist/square-migration-host-ledger.d.ts +0 -26
- package/dist/square-migration-host-ledger.js +0 -169
- package/dist/square-migration.d.ts +0 -30
- package/dist/square-migration.js +0 -599
- package/guides/migration.md +0 -156
- package/plugins/opencode/delivery.ts +0 -211
|
@@ -1,120 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { withFileLock } from './file-lock.js';
|
|
4
|
-
import { createArtifactPort } from './artifact.js';
|
|
5
|
-
import { preparePendingPresentation } from './presentation-operations.js';
|
|
6
|
-
import { presentPreparedBatch } from './delivery-effects.js';
|
|
7
|
-
import { sessionInbox, projectBoundaryEligibility, pendingAtBoundary, pendingKey } from './inbox.js';
|
|
8
|
-
import { attentionBodyIsClipped, renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
|
|
9
|
-
export { pendingAtBoundary };
|
|
1
|
+
import { DeliveryLedger } from './delivery-ledger.js';
|
|
2
|
+
import { deliveryFrameOverhead, renderDeliveryReminder } from './delivery-frame.js';
|
|
10
3
|
const CONTEXT_MAX = 1200;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
function renderBoundary(inbox) {
|
|
15
|
-
const queued = inbox.flatMap((membership) => membership.notifications.map((notification) => ({ membership, notification })));
|
|
16
|
-
const blocks = [];
|
|
17
|
-
const agoraBlocks = [];
|
|
18
|
-
const deliveries = [];
|
|
19
|
-
let remainingAgora = queued.filter(({ notification }) => notification.delivery?.mode !== 'direct').length;
|
|
20
|
-
const entries = [];
|
|
21
|
-
let omitted = 0;
|
|
22
|
-
const omission = (count) => `${count} more ${count === 1 ? 'activity' : 'activities'} not shown.`;
|
|
23
|
-
for (const entry of queued) {
|
|
24
|
-
const { membership, notification } = entry;
|
|
25
|
-
// The omission line represents the rest of this batch too, but never consumes its bodies.
|
|
26
|
-
const coverage = { membership, actIndex: notification.actIndex, markSeen: false };
|
|
27
|
-
entries.push(coverage);
|
|
28
|
-
const block = renderAttentionPreview({
|
|
29
|
-
squarePath: membership.location,
|
|
30
|
-
actIndex: notification.actIndex,
|
|
31
|
-
recipient: membership.seat.participant,
|
|
32
|
-
actor: notification.actor,
|
|
33
|
-
route: notification.route,
|
|
34
|
-
body: notification.body,
|
|
35
|
-
delivery: notification.delivery,
|
|
36
|
-
});
|
|
37
|
-
if (notification.delivery?.mode !== 'direct') {
|
|
38
|
-
remainingAgora -= 1;
|
|
39
|
-
const omittedAfter = omitted + remainingAgora;
|
|
40
|
-
const prospective = renderSquareReminder([...agoraBlocks, block, ...(omittedAfter > 0 ? [omission(omittedAfter)] : [])]);
|
|
41
|
-
if (prospective.length > CONTEXT_MAX) {
|
|
42
|
-
omitted += 1;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
agoraBlocks.push(block);
|
|
46
|
-
}
|
|
47
|
-
blocks.push(block);
|
|
48
|
-
deliveries.push(notification.delivery);
|
|
49
|
-
coverage.markSeen = !attentionBodyIsClipped(notification.body, notification.delivery);
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
context: renderSquareReminder([...blocks, ...(omitted > 0 ? [omission(omitted)] : [])], deliveries),
|
|
53
|
-
entries,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
async function presentPendingAtBoundaryUnlocked(identity, present, lookup = sessionInbox, env = process.env, signal) {
|
|
57
|
-
const inbox = await lookup(identity, env, signal);
|
|
58
|
-
if (signal?.aborted)
|
|
59
|
-
return undefined;
|
|
60
|
-
const pending = await projectBoundaryEligibility(inbox);
|
|
61
|
-
if (pending.length === 0 || signal?.aborted)
|
|
62
|
-
return undefined;
|
|
63
|
-
const artifacts = new Map();
|
|
64
|
-
const claims = [];
|
|
4
|
+
/** The sink must resolve only after the host has actually accepted the output. */
|
|
5
|
+
export async function presentPendingAtBoundary(owner, present, env = process.env, signal) {
|
|
6
|
+
const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
|
|
65
7
|
try {
|
|
8
|
+
const session = await ledger.session(owner);
|
|
9
|
+
if (!session || session.state !== 'attached' || signal?.aborted)
|
|
10
|
+
return undefined;
|
|
11
|
+
const claim = await ledger.claimPresentation(owner, { generation: session.generation, budgetChars: CONTEXT_MAX - deliveryFrameOverhead({ tagged: true }), signal });
|
|
12
|
+
if (!claim)
|
|
13
|
+
return undefined;
|
|
14
|
+
let current;
|
|
66
15
|
try {
|
|
67
|
-
|
|
68
|
-
for (const entry of renderBoundary(pending).entries) {
|
|
69
|
-
const { location, seat } = entry.membership;
|
|
70
|
-
let artifact = artifacts.get(location);
|
|
71
|
-
if (artifact === undefined) {
|
|
72
|
-
// Keep connections alive through all signal-independent settlement/cleanup.
|
|
73
|
-
artifact = createArtifactPort(location);
|
|
74
|
-
artifacts.set(location, artifact);
|
|
75
|
-
}
|
|
76
|
-
const claim = await preparePendingPresentation({ artifact, location, participant: seat.participant,
|
|
77
|
-
activity: entry.actIndex, seat, markSeen: entry.markSeen, signal });
|
|
78
|
-
if (claim !== undefined)
|
|
79
|
-
claims.push({ key: pendingKey(entry.membership, entry.actIndex), claim });
|
|
80
|
-
}
|
|
16
|
+
current = await claim.current(signal);
|
|
81
17
|
}
|
|
82
18
|
catch (error) {
|
|
83
|
-
|
|
84
|
-
await Promise.all(claims.map(({ claim }) => claim.release()));
|
|
19
|
+
await claim.settle('released');
|
|
85
20
|
throw error;
|
|
86
21
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
22
|
+
if (!current || signal?.aborted) {
|
|
23
|
+
await claim.settle('released');
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = await present(renderDeliveryReminder(claim.cards.map(card => card.text), { tagged: true }));
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
await claim.settle('unknown');
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
await claim.settle('presented');
|
|
35
|
+
return result;
|
|
100
36
|
}
|
|
101
37
|
finally {
|
|
102
|
-
await
|
|
38
|
+
await ledger.close();
|
|
103
39
|
}
|
|
104
40
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Serialize a boundary across hook processes so one stale inbox cannot render twice.
|
|
107
|
-
*
|
|
108
|
-
* `lookup` returns the session's memberships for this boundary. It may reuse an observation it has
|
|
109
|
-
* verified against the current presence binding and artifact revision; the boundary still projects
|
|
110
|
-
* presentation evidence live. Every item is claimed and revalidated before the batch sink;
|
|
111
|
-
* after success, captured-seat commits record the winning batch, including budget omissions as
|
|
112
|
-
* clipped (no consumption receipt), without an eligibility veto. Coverage is conservative if a
|
|
113
|
-
* competing claim's removal lets a previously omitted body fit in the final preview.
|
|
114
|
-
* A source that cannot verify its reuse must read the inbox again.
|
|
115
|
-
*/
|
|
116
|
-
export async function presentPendingAtBoundary(identity, present, lookup = sessionInbox, env = process.env, signal) {
|
|
117
|
-
const indexPath = squareIndexPath(env);
|
|
118
|
-
const ownerKey = createHash('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
119
|
-
return withFileLock(`${indexPath}.boundary-${ownerKey}.lock`, { retryMs: 10, signal }, () => presentPendingAtBoundaryUnlocked(identity, present, lookup, env, signal));
|
|
120
|
-
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { isCardPresentation, type CardPresentation } from './plugins/paseo/shared/card-presentation.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HydratedAct } from './model.js';
|
|
2
2
|
import type { ActivityIndex, DirectedFilter, SquareRead, SquareWrite } from './ports.js';
|
|
3
3
|
import type { CatchOptions, CatchReason, PerceivedActivity } from './square-facade.js';
|
|
4
4
|
export interface CatchSelection {
|
|
@@ -14,7 +14,7 @@ export interface CatchSelection {
|
|
|
14
14
|
readonly hint?: string;
|
|
15
15
|
}
|
|
16
16
|
/** Capture a selected page (or an empty completion) without opening another resource. */
|
|
17
|
-
export declare function observeCatchSelection(facts: SquareRead, participant: string, activities: readonly
|
|
17
|
+
export declare function observeCatchSelection(facts: SquareRead, participant: string, activities: readonly HydratedAct[], filter: DirectedFilter, at: number, remaining?: number): CatchSelection;
|
|
18
18
|
export declare const CATCH_DEFAULT_LIMIT = 10;
|
|
19
19
|
export declare const CATCH_MAX_LIMIT = 100;
|
|
20
20
|
export declare function catchLimit(value: number | undefined): number;
|
package/dist/catch-decisions.js
CHANGED
|
@@ -33,7 +33,7 @@ function catchPageSelection(facts, requestedName, options) {
|
|
|
33
33
|
const participant = resolveKnownName(facts, requestedName);
|
|
34
34
|
const limit = catchLimit(options.limit);
|
|
35
35
|
const senders = options.from?.map(name => resolveKnownName(facts, name));
|
|
36
|
-
const filter = { unseenOnly: true, ...(senders === undefined ? {} : { senders }), ...(options.mention === true ? { attentionOnly: true } : {}) };
|
|
36
|
+
const filter = { kinds: ['say'], unseenOnly: true, ...(senders === undefined ? {} : { senders }), ...(options.mention === true ? { attentionOnly: true } : {}) };
|
|
37
37
|
return { participant, filter, selection: { kind: 'page', query: filter, limit } };
|
|
38
38
|
}
|
|
39
39
|
/** Select non-ID catch coordinates without hydrating bodies or recording receipts. */
|
|
@@ -44,7 +44,7 @@ export function selectCatchPage(facts, requestedName, options) {
|
|
|
44
44
|
/** Selection, receipts and resulting cursor belong to one artifact write transaction. */
|
|
45
45
|
export function decideCatch(facts, requestedName, options, at) {
|
|
46
46
|
let viewer;
|
|
47
|
-
let filter = { unseenOnly: true };
|
|
47
|
+
let filter = { kinds: ['say'], unseenOnly: true };
|
|
48
48
|
let selection;
|
|
49
49
|
if (options.id !== undefined) {
|
|
50
50
|
viewer = resolveKnownName(facts, requestedName);
|
|
@@ -66,6 +66,10 @@ export function decideCatch(facts, requestedName, options, at) {
|
|
|
66
66
|
try {
|
|
67
67
|
consumed = consumeReceived({
|
|
68
68
|
...squareReceiveRead(facts),
|
|
69
|
+
addressed(recipient, index) {
|
|
70
|
+
const head = facts.directed(recipient, { indexes: [index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
|
|
71
|
+
return head === undefined ? undefined : { key: head.index, via: head.route };
|
|
72
|
+
},
|
|
69
73
|
insertReceipt: (ref, time) => facts.markSeen(ref.recipient, [ref.item], time) > 0,
|
|
70
74
|
}, viewer, selection, at);
|
|
71
75
|
}
|
|
@@ -85,7 +89,7 @@ export function decideCatch(facts, requestedName, options, at) {
|
|
|
85
89
|
const delivered = consumed.items.map(item => item.data);
|
|
86
90
|
const observed = observeCatchSelection(facts, viewer, delivered, filter, at, consumed.remaining);
|
|
87
91
|
// Receipts are the durable catch count: everything directed here minus what is still unseen.
|
|
88
|
-
const caught = facts.directedCount(viewer, {}) - facts.directedCount(viewer, { unseenOnly: true });
|
|
92
|
+
const caught = facts.directedCount(viewer, { kinds: ['say'] }) - facts.directedCount(viewer, { kinds: ['say'], unseenOnly: true });
|
|
89
93
|
// An exact id catch is a targeted fetch and stays repeatable, so it carries no nudge.
|
|
90
94
|
const hint = options.id === undefined ? catchHint(caught - consumed.newlyConsumed, caught) : undefined;
|
|
91
95
|
return hint === undefined ? observed : { ...observed, hint };
|
|
@@ -4,3 +4,11 @@ export declare function matchesCatchSelection(actor: string, audience: Audience,
|
|
|
4
4
|
readonly participants?: readonly string[];
|
|
5
5
|
readonly mention?: string;
|
|
6
6
|
}, replyAuthor?: string): boolean;
|
|
7
|
+
/** One directed arrival and the upper reason it reached its recipient. */
|
|
8
|
+
export interface RoutedNotification {
|
|
9
|
+
readonly actor: string;
|
|
10
|
+
readonly route: import('./model.js').DirectedNotificationRoute;
|
|
11
|
+
readonly recipient: string;
|
|
12
|
+
}
|
|
13
|
+
/** A live catch owns every matching notification, independent of its page size. */
|
|
14
|
+
export declare function leaseOwnsNotification(lease: import('./model.js').WatchLease, notification: RoutedNotification): boolean;
|
package/dist/catch-selection.js
CHANGED
|
@@ -12,3 +12,8 @@ export function matchesCatchSelection(actor, audience, filter, replyAuthor) {
|
|
|
12
12
|
return audienceIncludes(audience, filter.mention)
|
|
13
13
|
|| (replyAuthor !== undefined && sameName(replyAuthor, filter.mention));
|
|
14
14
|
}
|
|
15
|
+
/** A live catch owns every matching notification, independent of its page size. */
|
|
16
|
+
export function leaseOwnsNotification(lease, notification) {
|
|
17
|
+
return matchesCatchSelection(notification.actor, notification.route === 'bell' ? { kind: 'bell' }
|
|
18
|
+
: { kind: 'mentions', names: notification.route === 'mention' ? [notification.recipient] : [] }, lease.filter ?? {}, notification.route === 'reply' ? notification.recipient : undefined);
|
|
19
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export
|
|
3
|
-
export declare function nativePayload(token: string, preview: string): string;
|
|
4
|
-
export declare function createClaudeWakeTransport(env?: NodeJS.ProcessEnv): WakeTransportPort;
|
|
5
|
-
/** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
|
|
6
|
-
export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv): Promise<{
|
|
7
|
-
recognized: boolean;
|
|
8
|
-
current: boolean;
|
|
9
|
-
}>;
|
|
1
|
+
import { type DeliveryHandoffRef } from './delivery-ledger.js';
|
|
2
|
+
export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
|
|
10
3
|
export type ClaudeBinding = {
|
|
11
|
-
readonly location: string;
|
|
12
|
-
readonly participant: string;
|
|
13
4
|
readonly session: string;
|
|
14
|
-
readonly generation
|
|
5
|
+
readonly generation: string;
|
|
15
6
|
};
|
|
7
|
+
/** Extract complete frames and hash the exact bytes observed by the native host. */
|
|
8
|
+
export declare function claudeFrames(text: string): readonly {
|
|
9
|
+
token: string;
|
|
10
|
+
payloadHash: string;
|
|
11
|
+
}[];
|
|
12
|
+
/** Guard pre-effect; admit and observe only the exact captured handoff after each native effect. */
|
|
13
|
+
export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv, captured?: readonly DeliveryHandoffRef[]): Promise<{
|
|
14
|
+
recognized: boolean;
|
|
15
|
+
current: boolean;
|
|
16
|
+
refs?: readonly DeliveryHandoffRef[];
|
|
17
|
+
}>;
|
package/dist/claude-delivery.js
CHANGED
|
@@ -1,155 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function nativeSupported(route) { return route.address.loaded === 'true' && !!route.address.endpoint; }
|
|
15
|
-
function hashPayload(payload) { return createHash('sha256').update(payload).digest('hex'); }
|
|
16
|
-
function previewFacts(facts, request) {
|
|
17
|
-
const index = parseActivityId(request.activity);
|
|
18
|
-
if (index === undefined || request.route.kind !== 'claude-native' || request.route.address.endpoint === undefined)
|
|
19
|
-
return undefined;
|
|
20
|
-
return nativePendingPreview(facts, { location: request.location, routeKind: 'claude-native', generation: request.seat.generation,
|
|
21
|
-
address: request.route.address,
|
|
22
|
-
participant: request.participant, actIndex: index, now: Date.now() });
|
|
23
|
-
}
|
|
24
|
-
async function preview(artifact, request) {
|
|
25
|
-
return (await artifact.read((facts) => previewFacts(facts, request))).value;
|
|
26
|
-
}
|
|
27
|
-
async function writeClaudeWake(sessionId, endpoint, messageId, payload, timeoutMs, signal) {
|
|
28
|
-
const result = await writeClaudeNative({ sessionId, endpoint, messageId }, payload, { deadline: Date.now() + timeoutMs, signal });
|
|
29
|
-
if (result.outcome === 'unavailable')
|
|
30
|
-
return { outcome: 'failed', signature: 'claude_endpoint_unavailable', message: result.message };
|
|
31
|
-
return { outcome: 'unknown', signature: 'native_admission_unconfirmed', message: result.outcome === 'written' ? 'Claude accepted the local write; native admission is unconfirmed.' : result.message };
|
|
32
|
-
}
|
|
33
|
-
export function createClaudeWakeTransport(env = process.env) {
|
|
34
|
-
return {
|
|
35
|
-
async probe(route) {
|
|
36
|
-
if (!nativeSupported(route))
|
|
37
|
-
return { outcome: 'not-capable', diagnostic: 'Claude native inbox unavailable: a loaded interactive Square mod is required.' };
|
|
38
|
-
try {
|
|
39
|
-
return (await stat(route.address.endpoint)).isSocket() || { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint is not a socket.' };
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint unavailable.' };
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
attempt: (request, timeoutMs, beforeSend) => dispatchClaude(request, timeoutMs, beforeSend, env),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
async function dispatchClaude(request, timeoutMs, beforeSend, env = process.env) {
|
|
49
|
-
void env;
|
|
50
|
-
const signal = AbortSignal.timeout(timeoutMs);
|
|
51
|
-
if (!nativeSupported(request.route) || request.artifact === undefined || request.claimToken === undefined)
|
|
52
|
-
return { outcome: 'not-capable', diagnostic: 'Claude native delivery needs a current artifact claim and loaded inbox.' };
|
|
53
|
-
if (signal.aborted || !await beforeSend())
|
|
54
|
-
return { outcome: 'unknown', signature: 'native_send_suppressed' };
|
|
55
|
-
const prepared = await preview(request.artifact, request);
|
|
56
|
-
if (prepared === undefined)
|
|
57
|
-
return { outcome: 'not-capable', diagnostic: 'Claude activity is no longer current.' };
|
|
58
|
-
const messageId = randomUUID();
|
|
59
|
-
const payload = nativePayload(request.claimToken, prepared.payload);
|
|
60
|
-
const correlation = { harness: 'claude', token: request.claimToken, messageId, payloadHash: hashPayload(payload) };
|
|
61
|
-
if (!await request.artifact.prepareDelivery({ seat: request.seat, claimToken: request.claimToken, correlation }))
|
|
62
|
-
return { outcome: 'not-capable', diagnostic: 'Claude delivery claim is no longer current.' };
|
|
63
|
-
const fresh = await preview(request.artifact, request);
|
|
64
|
-
if (fresh === undefined || nativePayload(request.claimToken, fresh.payload) !== payload)
|
|
65
|
-
return { outcome: 'not-capable', diagnostic: 'Claude activity changed before native write.' };
|
|
66
|
-
if (signal.aborted || !await beforeSend())
|
|
67
|
-
return { outcome: 'not-capable', diagnostic: 'Claude activity was consumed before native write.' };
|
|
68
|
-
const sessionId = request.route.address.sessionId;
|
|
69
|
-
if (sessionId === undefined)
|
|
70
|
-
return { outcome: 'not-capable', diagnostic: 'Claude native route has no session address.' };
|
|
71
|
-
return writeClaudeWake(sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
|
|
72
|
-
}
|
|
73
|
-
function currentPayload(facts, request) {
|
|
74
|
-
const seat = facts.seat(request.participant), evidence = facts.deliveryByToken(request.claimToken);
|
|
75
|
-
if (seat === undefined || seat.attachment !== 'attached' || evidence === undefined || seat.generation !== evidence.generation
|
|
76
|
-
|| seat.generation !== request.seat.generation || seat.owner !== request.seat.owner
|
|
77
|
-
|| evidence.activity !== request.activity || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome))
|
|
78
|
-
return undefined;
|
|
79
|
-
const native = previewFacts(facts, request);
|
|
80
|
-
if (native === undefined)
|
|
81
|
-
return undefined;
|
|
82
|
-
const payload = nativePayload(request.claimToken, native.payload);
|
|
83
|
-
return evidence.correlation?.payloadHash === hashPayload(payload) ? payload : undefined;
|
|
84
|
-
}
|
|
85
|
-
async function rowsForSession(sessionId, env, tokens) {
|
|
86
|
-
const rows = [];
|
|
87
|
-
const candidates = (await indexedSquareEntries({}, env)).filter(entry => {
|
|
88
|
-
const coordinate = decodeHarnessOwner(entry.owner);
|
|
89
|
-
return coordinate?.channel === 'claude-code' && coordinate.session === sessionId;
|
|
1
|
+
import { DeliveryLedger } from './delivery-ledger.js';
|
|
2
|
+
import { harnessOwner } from './owner.js';
|
|
3
|
+
import { renderDeliveryReminder } from './delivery-frame.js';
|
|
4
|
+
import { hashPayload, nativeTokens } from './claude-transport.js';
|
|
5
|
+
export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
|
|
6
|
+
/** Extract complete frames and hash the exact bytes observed by the native host. */
|
|
7
|
+
export function claudeFrames(text) {
|
|
8
|
+
const prefix = renderDeliveryReminder([''], { tagged: false });
|
|
9
|
+
return [...text.matchAll(/(?:^|\n)(\[square-inbox:([a-zA-Z0-9-]+)\]\n[\s\S]*?\n\[\/square-inbox:\2\])(?=\n|$)/g)]
|
|
10
|
+
.flatMap(frame => {
|
|
11
|
+
const start = frame.index + frame[0].length - frame[1].length;
|
|
12
|
+
return text.slice(start - prefix.length, start) === prefix
|
|
13
|
+
? [{ token: frame[2], payloadHash: hashPayload(prefix + frame[1]) }] : [];
|
|
90
14
|
});
|
|
91
|
-
const paths = new Set(candidates.map(candidate => candidate.location));
|
|
92
|
-
for (const location of paths) {
|
|
93
|
-
const artifact = createArtifactPort(location);
|
|
94
|
-
try {
|
|
95
|
-
const found = await artifact.read((facts) => tokens.flatMap((token) => {
|
|
96
|
-
const evidence = facts.deliveryByToken(token);
|
|
97
|
-
if (evidence?.kind !== 'wake' || evidence.correlation?.harness !== 'claude' || evidence.correlation.token !== token
|
|
98
|
-
|| evidence.route?.kind !== 'claude-native' || evidence.route.address.sessionId !== sessionId)
|
|
99
|
-
return [];
|
|
100
|
-
const row = { artifact, evidence };
|
|
101
|
-
const seat = facts.seat(evidence.participant), index = parseActivityId(evidence.activity);
|
|
102
|
-
const item = index === undefined ? undefined : facts.activity(index);
|
|
103
|
-
if (seat === undefined || item?.kind !== 'say')
|
|
104
|
-
return [row];
|
|
105
|
-
const route = { location, participant: seat.participant,
|
|
106
|
-
...evidence.route, updatedAt: evidence.at };
|
|
107
|
-
const request = { location, participant: seat.participant, activity: evidence.activity,
|
|
108
|
-
actor: item.actor, seat: asSeatRef(seat), claimToken: token, attemptN: evidence.attemptN, route, artifact };
|
|
109
|
-
return [{ ...row, request, payload: currentPayload(facts, request) }];
|
|
110
|
-
}));
|
|
111
|
-
rows.push(...found.value);
|
|
112
|
-
if (found.value.length === 0)
|
|
113
|
-
await artifact.close();
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
await artifact.close();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return rows;
|
|
120
15
|
}
|
|
121
|
-
/**
|
|
122
|
-
export async function observeClaudeDelivery(sessionId, text, operation, env = process.env) {
|
|
16
|
+
/** Guard pre-effect; admit and observe only the exact captured handoff after each native effect. */
|
|
17
|
+
export async function observeClaudeDelivery(sessionId, text, operation, env = process.env, captured) {
|
|
123
18
|
const tokens = nativeTokens(text);
|
|
124
|
-
if (tokens.length
|
|
19
|
+
if (!tokens.length)
|
|
125
20
|
return { recognized: false, current: false };
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
21
|
+
const frames = claudeFrames(text);
|
|
22
|
+
const owner = harnessOwner({ channel: 'claude-code', session: sessionId });
|
|
23
|
+
const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
|
|
129
24
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
25
|
+
const found = await Promise.all(tokens.map(token => ledger.handoff(token)));
|
|
26
|
+
const recognized = found.some(Boolean);
|
|
27
|
+
if (!recognized)
|
|
28
|
+
return { recognized: false, current: false };
|
|
29
|
+
if (frames.length !== tokens.length || new Set(tokens).size !== tokens.length || found.some(ref => !ref))
|
|
135
30
|
return { recognized, current: false };
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
31
|
+
const refs = found;
|
|
32
|
+
if (refs.some((ref, i) => ref.owner !== owner || ref.routeKind !== 'claude-native' || ref.token !== frames[i].token || ref.payloadHash !== frames[i].payloadHash))
|
|
33
|
+
return { recognized, current: false };
|
|
34
|
+
if (operation === 'admitted' && (!Array.isArray(captured) || captured.length !== refs.length || refs.some((ref, i) => {
|
|
35
|
+
const old = captured[i];
|
|
36
|
+
return !old || old.token !== ref.token || old.owner !== ref.owner || old.generation !== ref.generation || old.payloadHash !== ref.payloadHash || old.routeKind !== ref.routeKind;
|
|
37
|
+
})))
|
|
38
|
+
return { recognized, current: false };
|
|
39
|
+
if (operation === 'guard') {
|
|
40
|
+
for (const ref of refs)
|
|
41
|
+
if (!await ledger.guardHandoff(ref))
|
|
42
|
+
return { recognized, current: false };
|
|
43
|
+
return { recognized, current: true, refs };
|
|
44
|
+
}
|
|
45
|
+
for (const ref of refs) {
|
|
46
|
+
const recorded = operation === 'admitted' ? await ledger.acceptHandoff(ref) : await ledger.observePresentation(ref);
|
|
47
|
+
if (!recorded)
|
|
143
48
|
return { recognized, current: false };
|
|
144
|
-
if (operation === 'stored')
|
|
145
|
-
await presentPending({ artifact: row.artifact, location: request.location,
|
|
146
|
-
// Claude's stored event consumes even Agora previews; retain that contract for both modes.
|
|
147
|
-
participant: request.participant, activity: request.activity, seat: request.seat, observationOf: request.claimToken, markSeen: true, sink: { present() { } },
|
|
148
|
-
current: facts => currentPayload(facts, request) === payload });
|
|
149
49
|
}
|
|
150
50
|
return { recognized, current: true };
|
|
151
51
|
}
|
|
152
52
|
finally {
|
|
153
|
-
await
|
|
53
|
+
await ledger.close();
|
|
154
54
|
}
|
|
155
55
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { deliveryLedgerLocation } from './delivery-ledger.js';
|
|
4
5
|
import { z } from 'zod/v4';
|
|
5
6
|
import { withFileLock } from './file-lock.js';
|
|
6
|
-
import { squareIndexPath } from './square-index.js';
|
|
7
7
|
import { SquareError } from './model.js';
|
|
8
8
|
const LEASE_MS = 10 * 60 * 1000;
|
|
9
9
|
const text = z.string().max(4096).refine(value => value.trim().length > 0 && !/[\x00-\x1f\x7f]/u.test(value));
|
|
@@ -12,7 +12,7 @@ const coordinate = z.object({
|
|
|
12
12
|
});
|
|
13
13
|
const record = coordinate.extend({ token: z.string().regex(/^[a-f0-9]{64}$/u), expiresAt: z.number() });
|
|
14
14
|
const unavailable = () => new SquareError('unavailable', 'Claude MCP call context unavailable');
|
|
15
|
-
const storeRoot = (env) => path.join(path.dirname(
|
|
15
|
+
const storeRoot = (env) => path.join(path.dirname(deliveryLedgerLocation(env)), 'claude-mcp-calls');
|
|
16
16
|
const callFile = (root, id) => path.join(root, `${createHash('sha256').update(id).digest('hex')}.json`);
|
|
17
17
|
async function readRecord(file) {
|
|
18
18
|
try {
|