@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
package/dist/square-tools.d.ts
CHANGED
|
@@ -16,6 +16,14 @@ export declare class SquareExpressDrafts {
|
|
|
16
16
|
consume(token?: string): void;
|
|
17
17
|
retain(args: JsonObject, owner: Owner | undefined, token: string | undefined, failure: JsonObject, expressAttempted: boolean): string | undefined;
|
|
18
18
|
}
|
|
19
|
+
interface ToolBinding {
|
|
20
|
+
identity: ToolIdentity;
|
|
21
|
+
open(location: string | undefined, control: OperationControl): Promise<Square>;
|
|
22
|
+
}
|
|
23
|
+
export declare function createSquareToolRunner(): (tool: ToolDefinition, args: JsonObject, control: OperationControl, bind: () => Promise<ToolBinding> | ToolBinding, defaultLocation?: string) => Promise<{
|
|
24
|
+
text: string;
|
|
25
|
+
isError?: true;
|
|
26
|
+
}>;
|
|
19
27
|
interface ToolDefinition {
|
|
20
28
|
name: string;
|
|
21
29
|
description: string;
|
package/dist/square-tools.js
CHANGED
|
@@ -45,6 +45,45 @@ export class SquareExpressDrafts {
|
|
|
45
45
|
return created;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
export function createSquareToolRunner() {
|
|
49
|
+
const drafts = new SquareExpressDrafts();
|
|
50
|
+
return async (tool, args, control, bind, defaultLocation) => {
|
|
51
|
+
let toolArgs = args;
|
|
52
|
+
let owner;
|
|
53
|
+
let token;
|
|
54
|
+
let attempted = false;
|
|
55
|
+
let square;
|
|
56
|
+
try {
|
|
57
|
+
const binding = await bind();
|
|
58
|
+
owner = binding.identity.owner;
|
|
59
|
+
if (tool.name === 'express') {
|
|
60
|
+
const resolved = drafts.resolve(toolArgs, owner);
|
|
61
|
+
toolArgs = resolved.args;
|
|
62
|
+
token = resolved.token;
|
|
63
|
+
}
|
|
64
|
+
square = await binding.open(toolArgs.location, control);
|
|
65
|
+
let text;
|
|
66
|
+
try {
|
|
67
|
+
attempted = tool.name === 'express';
|
|
68
|
+
text = await tool.run(square, toolArgs, control, binding.identity);
|
|
69
|
+
drafts.consume(token);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await square.close();
|
|
73
|
+
}
|
|
74
|
+
return { text };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const failure = squareToolFailure(error, control.signal);
|
|
78
|
+
const draft = tool.name === 'express' ? drafts.retain(toolArgs, owner, token, failure, attempted) : undefined;
|
|
79
|
+
return { isError: true, text: squareToolFailureText(failure, {
|
|
80
|
+
location: square?.location ?? toolArgs.location ?? defaultLocation,
|
|
81
|
+
participant: typeof toolArgs.as === 'string' ? toolArgs.as : undefined,
|
|
82
|
+
draft,
|
|
83
|
+
}) };
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
48
87
|
const location = z.string().optional().describe('Which square: a .square file path. Relative paths start from the working directory. Leave it out for the configured square.');
|
|
49
88
|
const participant = z.string().optional().describe('Your name in the square. Leave it out if you hold exactly one name here.');
|
|
50
89
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo", "http"];
|
|
2
|
+
export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
|
|
3
|
+
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
4
|
+
readonly [key: string]: JsonValue;
|
|
5
|
+
};
|
|
6
|
+
export type MessageMetadata = Readonly<Record<string, JsonValue>>;
|
|
7
|
+
export declare const MESSAGE_METADATA_MAX_BYTES: number;
|
|
8
|
+
export declare const MESSAGE_METADATA_MAX_DEPTH = 256;
|
|
9
|
+
/** Validate the bounded, lossless JSON object carried by a message. */
|
|
10
|
+
export declare function validateMessageMetadata(value: unknown): MessageMetadata | undefined;
|
|
11
|
+
export declare function messageMetadataHasContent(metadata: MessageMetadata | undefined): boolean;
|
|
12
|
+
export interface WakeAddress {
|
|
13
|
+
readonly kind: WakeRouteKind;
|
|
14
|
+
readonly address: Readonly<Record<string, string>>;
|
|
15
|
+
}
|
|
16
|
+
export declare function isWakeRouteKind(value: unknown): value is WakeRouteKind;
|
|
17
|
+
export interface WakeRoute {
|
|
18
|
+
epoch?: number;
|
|
19
|
+
location: string;
|
|
20
|
+
participant: string;
|
|
21
|
+
kind: WakeRouteKind;
|
|
22
|
+
address: Record<string, string>;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function sameName(a: string, b: string): boolean;
|
|
26
|
+
export declare const NAME_GRAPHEME_SOURCE: string;
|
|
27
|
+
export declare function validateName(name: string): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { SquareError, nameKey } from './neutral.js';
|
|
2
|
+
export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo', 'http'];
|
|
3
|
+
export const MESSAGE_METADATA_MAX_BYTES = 16 * 1024;
|
|
4
|
+
export const MESSAGE_METADATA_MAX_DEPTH = 256;
|
|
5
|
+
/** Validate the bounded, lossless JSON object carried by a message. */
|
|
6
|
+
export function validateMessageMetadata(value) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
throw new SquareError('invalid_args', 'Activity metadata must be a non-null JSON object.');
|
|
11
|
+
const visiting = new Set();
|
|
12
|
+
const stack = [{ value, depth: 0 }];
|
|
13
|
+
while (stack.length > 0) {
|
|
14
|
+
const frame = stack.pop();
|
|
15
|
+
if (frame.exit !== undefined) {
|
|
16
|
+
visiting.delete(frame.exit);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const item = frame.value;
|
|
20
|
+
if (item === null || typeof item === 'boolean' || typeof item === 'string')
|
|
21
|
+
continue;
|
|
22
|
+
if (typeof item === 'number') {
|
|
23
|
+
if (!Number.isFinite(item))
|
|
24
|
+
throw new SquareError('invalid_args', 'Activity metadata must contain only finite JSON values.');
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (typeof item !== 'object')
|
|
28
|
+
throw new SquareError('invalid_args', 'Activity metadata must contain only JSON values.');
|
|
29
|
+
if (frame.depth > MESSAGE_METADATA_MAX_DEPTH)
|
|
30
|
+
throw new SquareError('invalid_args', `Activity metadata nesting cannot exceed ${MESSAGE_METADATA_MAX_DEPTH} levels.`);
|
|
31
|
+
if (visiting.has(item))
|
|
32
|
+
throw new SquareError('invalid_args', 'Activity metadata cannot contain a cycle.');
|
|
33
|
+
visiting.add(item);
|
|
34
|
+
stack.push({ value: item, depth: frame.depth, exit: item });
|
|
35
|
+
if (Array.isArray(item)) {
|
|
36
|
+
if (Object.getOwnPropertySymbols(item).length > 0 || Object.keys(item).length !== item.length)
|
|
37
|
+
throw new SquareError('invalid_args', 'Activity metadata arrays must be dense JSON arrays.');
|
|
38
|
+
const names = Object.getOwnPropertyNames(item);
|
|
39
|
+
if (names.some(name => name !== 'length' && (!/^(0|[1-9]\d*)$/.test(name) || Number(name) >= item.length)))
|
|
40
|
+
throw new SquareError('invalid_args', 'Activity metadata arrays must contain only indexed values.');
|
|
41
|
+
for (const key of Object.keys(item).reverse()) {
|
|
42
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
43
|
+
if (descriptor === undefined || !('value' in descriptor))
|
|
44
|
+
throw new SquareError('invalid_args', 'Activity metadata cannot contain accessors.');
|
|
45
|
+
stack.push({ value: descriptor.value, depth: frame.depth + 1 });
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const prototype = Object.getPrototypeOf(item);
|
|
50
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
51
|
+
throw new SquareError('invalid_args', 'Activity metadata must contain only plain JSON objects.');
|
|
52
|
+
if (Object.getOwnPropertySymbols(item).length > 0)
|
|
53
|
+
throw new SquareError('invalid_args', 'Activity metadata cannot contain symbol keys.');
|
|
54
|
+
const names = Object.getOwnPropertyNames(item);
|
|
55
|
+
if (names.some(name => !Object.prototype.propertyIsEnumerable.call(item, name)))
|
|
56
|
+
throw new SquareError('invalid_args', 'Activity metadata cannot contain non-enumerable properties.');
|
|
57
|
+
for (const name of names.reverse()) {
|
|
58
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, name);
|
|
59
|
+
if (!('value' in descriptor))
|
|
60
|
+
throw new SquareError('invalid_args', 'Activity metadata cannot contain accessors.');
|
|
61
|
+
stack.push({ value: descriptor.value, depth: frame.depth + 1 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let serialized;
|
|
65
|
+
try {
|
|
66
|
+
serialized = JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new SquareError('invalid_args', 'Activity metadata must contain only lossless JSON values.');
|
|
70
|
+
}
|
|
71
|
+
if (serialized === undefined || Buffer.byteLength(serialized, 'utf8') > MESSAGE_METADATA_MAX_BYTES)
|
|
72
|
+
throw new SquareError('invalid_args', `Activity metadata cannot exceed ${MESSAGE_METADATA_MAX_BYTES} UTF-8 bytes.`);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
export function messageMetadataHasContent(metadata) {
|
|
76
|
+
return metadata !== undefined && Object.keys(metadata).length > 0;
|
|
77
|
+
}
|
|
78
|
+
export function isWakeRouteKind(value) {
|
|
79
|
+
return typeof value === 'string' && WAKE_ROUTE_KINDS.includes(value);
|
|
80
|
+
}
|
|
81
|
+
export function sameName(a, b) { return nameKey(a) === nameKey(b); }
|
|
82
|
+
const TEXT_NAME_GRAPHEME_SOURCE = String.raw `[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]`;
|
|
83
|
+
export const NAME_GRAPHEME_SOURCE = String.raw `${TEXT_NAME_GRAPHEME_SOURCE}|\p{RGI_Emoji}`;
|
|
84
|
+
const TEXT_NAME_PATTERN = new RegExp(`^(?:${TEXT_NAME_GRAPHEME_SOURCE})+(?:/(?:${TEXT_NAME_GRAPHEME_SOURCE})+)*$`, 'v');
|
|
85
|
+
const NAME_PATTERN = new RegExp(`^(?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*$`, 'v');
|
|
86
|
+
export function validateName(name) {
|
|
87
|
+
if (!name || (!TEXT_NAME_PATTERN.test(name) && !NAME_PATTERN.test(name))) {
|
|
88
|
+
throw new SquareError('invalid_name', 'Invalid name: names must contain non-empty slash-separated segments using Unicode letters, digits, marks, hyphens, underscores, or complete RGI emoji graphemes.');
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/views.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { type ActivityId } from './square-core.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type HydratedAct } from './model.js';
|
|
3
3
|
import type { ActivityFilter, SquareRead } from './ports.js';
|
|
4
4
|
import type { Activity, ArchiveRecord, PerceivedActivity, HistoryQuery, HistoryContinuation, ParticipantsResult, EntryPresentation, StatusResult, SquareObservation, CatchPresence } from './square-facade.js';
|
|
5
5
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
6
6
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
7
7
|
export declare const NON_READ: readonly ["join", "done", "say", "listen", "ignore", "unlisten"];
|
|
8
|
-
/** The one
|
|
9
|
-
export declare function toPublicActivity(stored:
|
|
10
|
-
export declare function toPerceivedActivity(facts: SquareRead, stored:
|
|
8
|
+
/** The one stored-to-public Activity field mapping. Rejects read bookkeeping. */
|
|
9
|
+
export declare function toPublicActivity(stored: HydratedAct): Activity;
|
|
10
|
+
export declare function toPerceivedActivity(facts: SquareRead, stored: HydratedAct, viewer: string): PerceivedActivity;
|
|
11
11
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
|
12
12
|
/** History adds archive context and ordering to the shared storage-side query predicates. */
|
|
13
13
|
export declare function selectHistory(facts: SquareRead, query: HistoryQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
|
|
14
14
|
continuation?: HistoryContinuation | undefined;
|
|
15
|
-
activities: StoredAct[];
|
|
16
|
-
visibleActivities: StoredAct[];
|
|
15
|
+
activities: import("./model.js").StoredAct[];
|
|
16
|
+
visibleActivities: import("./model.js").StoredAct[];
|
|
17
17
|
totalMatches: number;
|
|
18
18
|
};
|
|
19
19
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
package/dist/views.js
CHANGED
|
@@ -11,15 +11,14 @@ const STATUS_HERE_WINDOW_MS = 30 * 60_000;
|
|
|
11
11
|
const PRESENCE_WINDOW_MS = 8 * 60 * 60_000;
|
|
12
12
|
export const NON_READ = ['join', 'done', 'say', 'listen', 'ignore', 'unlisten'];
|
|
13
13
|
const ARCHIVE = new Set(['join', 'done', 'say']);
|
|
14
|
-
/** The one
|
|
14
|
+
/** The one stored-to-public Activity field mapping. Rejects read bookkeeping. */
|
|
15
15
|
export function toPublicActivity(stored) {
|
|
16
16
|
if (stored.kind === 'read')
|
|
17
17
|
throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
|
|
18
18
|
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
19
19
|
...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}),
|
|
20
20
|
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
21
|
-
...(
|
|
22
|
-
...(stored.kind === 'say' && stored.delivery !== undefined ? { delivery: stored.delivery } : {}),
|
|
21
|
+
...('metadata' in stored && stored.metadata !== undefined ? { metadata: stored.metadata } : {}),
|
|
23
22
|
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}),
|
|
24
23
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
25
24
|
}
|
|
@@ -144,7 +143,10 @@ export function queryStatus(facts, context, viewer = '', observe = false) {
|
|
|
144
143
|
const header = facts.header(), standingCount = inSquareCount(facts);
|
|
145
144
|
const listener = participants.find((p) => p.presence !== 'never-joined' && nameKey(p.name) === nameKey(viewer));
|
|
146
145
|
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
147
|
-
const
|
|
146
|
+
const joinHeads = observe ? facts.activityHeads({ kinds: ['join'], afterIndex: latest?.index ?? -1 }, { edge: 'newest', limit: 3 }).items : [];
|
|
147
|
+
const indexes = [...new Set([...(latest === undefined ? [] : [latest.index]), ...joinHeads.map(head => head.index)])];
|
|
148
|
+
const hydrated = new Map(facts.activities(indexes).map(activity => [activity.index, activity]));
|
|
149
|
+
const latestAct = latest === undefined ? undefined : hydrated.get(latest.index);
|
|
148
150
|
const data = {
|
|
149
151
|
hardCap: header.hardCap,
|
|
150
152
|
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
@@ -155,10 +157,13 @@ export function queryStatus(facts, context, viewer = '', observe = false) {
|
|
|
155
157
|
if (!observe)
|
|
156
158
|
return data;
|
|
157
159
|
const afterIndex = latestAct?.index ?? -1;
|
|
158
|
-
const joins =
|
|
159
|
-
|
|
160
|
-
activity:
|
|
161
|
-
|
|
160
|
+
const joins = joinHeads.flatMap(head => {
|
|
161
|
+
const activity = hydrated.get(head.index);
|
|
162
|
+
return activity === undefined ? [] : [{
|
|
163
|
+
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0,
|
|
164
|
+
activity: toPublicActivity(activity)
|
|
165
|
+
}];
|
|
166
|
+
});
|
|
162
167
|
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
163
168
|
return {
|
|
164
169
|
...data,
|
|
@@ -190,7 +195,7 @@ export function streamTailProjection(facts, last = HISTORY_DEFAULT_LIMIT, recipi
|
|
|
190
195
|
throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${PAGE_SIZE}.`);
|
|
191
196
|
const heads = last === 0 ? [] : recipient === undefined
|
|
192
197
|
? facts.activityHeads({ kinds: NON_READ }, { edge: 'newest', limit: last }).items
|
|
193
|
-
: facts.directed(recipient, {}, { edge: 'newest', limit: last }).items;
|
|
198
|
+
: facts.directed(recipient, { kinds: ['say'] }, { edge: 'newest', limit: last }).items;
|
|
194
199
|
const activities = facts.activities(heads.map((head) => head.index)).map((activity) => recipient === undefined
|
|
195
200
|
? toPublicActivity(activity) : toPerceivedActivity(facts, activity, recipient));
|
|
196
201
|
return { activities, cursor: facts.header().lastActIndex, hasMore: false };
|
package/dist/wake-sink.d.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
/** One Paseo wake attempt: where it goes, what it says, and what identifies it. */
|
|
2
1
|
export interface PaseoWakeRequest {
|
|
3
2
|
agentId: string;
|
|
4
3
|
prompt: string;
|
|
5
|
-
|
|
6
|
-
participant: string;
|
|
7
|
-
activity: string;
|
|
8
|
-
attemptN?: number;
|
|
4
|
+
messageId: string;
|
|
9
5
|
}
|
|
10
|
-
/**
|
|
11
|
-
* The daemon's own dedup key for one attempt. Every retry of the same attempt
|
|
12
|
-
* derives the same id; a later attempt of the same attention derives a new one.
|
|
13
|
-
*/
|
|
14
|
-
export declare function paseoWakeMessageId(request: Pick<PaseoWakeRequest, 'location' | 'participant' | 'activity' | 'attemptN'>): string;
|
|
15
|
-
/** Steer one existing Paseo agent through the shared delivery entry. Never interrupts a turn. */
|
|
6
|
+
/** Steer one existing Paseo agent, without interrupting its turn. */
|
|
16
7
|
export declare function sendPaseoWake(request: PaseoWakeRequest, opts?: {
|
|
17
8
|
timeoutMs?: number;
|
|
9
|
+
beforeSend?: () => Promise<boolean>;
|
|
10
|
+
signal?: AbortSignal;
|
|
18
11
|
}): Promise<void>;
|
package/dist/wake-sink.js
CHANGED
|
@@ -1,37 +1,13 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { DeliveryError, connect } from './packages/agent-delivery/src/index.js';
|
|
3
|
-
/**
|
|
4
|
-
* The daemon's own dedup key for one attempt. Every retry of the same attempt
|
|
5
|
-
* derives the same id; a later attempt of the same attention derives a new one.
|
|
6
|
-
*/
|
|
7
|
-
export function paseoWakeMessageId(request) {
|
|
8
|
-
const digest = createHash('sha256')
|
|
9
|
-
.update([request.location, request.participant, request.activity, request.attemptN ?? 0].join('|'))
|
|
10
|
-
.digest('hex');
|
|
11
|
-
return `square-${digest.slice(0, 32)}`;
|
|
12
|
-
}
|
|
13
|
-
/** Steer one existing Paseo agent through the shared delivery entry. Never interrupts a turn. */
|
|
2
|
+
/** Steer one existing Paseo agent, without interrupting its turn. */
|
|
14
3
|
export async function sendPaseoWake(request, opts = {}) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const bounded = budgetMs !== undefined && Number.isFinite(budgetMs) && budgetMs > 0;
|
|
19
|
-
const deadline = bounded ? Date.now() + budgetMs : 0;
|
|
20
|
-
// An elapsed budget never reached the daemon, so it is the shared entry's own
|
|
21
|
-
// "unavailable" failure rather than a send error of its own.
|
|
22
|
-
const connectMs = bounded ? Math.max(0, deadline - Date.now()) : budgetMs;
|
|
23
|
-
if (bounded && connectMs === 0)
|
|
4
|
+
const budget = opts.timeoutMs;
|
|
5
|
+
const deadline = budget === undefined ? undefined : Date.now() + budget;
|
|
6
|
+
if (deadline !== undefined && (!Number.isFinite(budget) || budget <= 0))
|
|
24
7
|
throw new DeliveryError('unavailable');
|
|
25
|
-
const agent = await connect({
|
|
26
|
-
|
|
27
|
-
agentId: request.agentId,
|
|
28
|
-
...(connectMs === undefined ? {} : { timeoutMs: connectMs }),
|
|
29
|
-
});
|
|
30
|
-
const steerMs = bounded ? Math.max(0, deadline - Date.now()) : budgetMs;
|
|
31
|
-
if (bounded && steerMs === 0)
|
|
8
|
+
const agent = await connect({ harness: 'paseo', agentId: request.agentId, ...(budget === undefined ? {} : { timeoutMs: budget }), signal: opts.signal });
|
|
9
|
+
if (opts.signal?.aborted || (deadline !== undefined && Date.now() >= deadline))
|
|
32
10
|
throw new DeliveryError('unavailable');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
...(steerMs === undefined ? {} : { timeoutMs: steerMs }),
|
|
36
|
-
});
|
|
11
|
+
const remaining = deadline === undefined ? undefined : deadline - Date.now();
|
|
12
|
+
await agent.steer(request.prompt, { id: request.messageId, ...(remaining === undefined ? {} : { timeoutMs: remaining }), signal: opts.signal, beforeSend: opts.beforeSend });
|
|
37
13
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DeliveryTransport } from './delivery-ledger.js';
|
|
2
|
+
export type WakeTransports = readonly DeliveryTransport[];
|
|
3
|
+
/** Concrete transports share no producer-specific state. */
|
|
4
|
+
export declare function createDefaultDeliveryTransports(env?: NodeJS.ProcessEnv): Promise<WakeTransports>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Concrete transports share no producer-specific state. */
|
|
2
|
+
export async function createDefaultDeliveryTransports(env = process.env) {
|
|
3
|
+
const { createClaudeWakeTransport } = await import('./claude-transport.js');
|
|
4
|
+
const { HttpWakeTransport } = await import('./http-delivery.js');
|
|
5
|
+
const transports = [createClaudeWakeTransport(env), new HttpWakeTransport({ env })];
|
|
6
|
+
if (env.SQUARE_DISABLE_PASEO_WAKE !== '1') {
|
|
7
|
+
const { PaseoWakeTransport } = await import('./paseo-delivery.js');
|
|
8
|
+
transports.push(new PaseoWakeTransport());
|
|
9
|
+
}
|
|
10
|
+
return transports;
|
|
11
|
+
}
|
|
@@ -1,250 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
addressed: (recipient, key) => state.addresses.find(row => row.recipient === recipient && row.key === key),
|
|
32
|
-
load: keys => new Map(keys.filter(key => state.items.has(key)).map(key => [key, state.items.get(key)])),
|
|
33
|
-
count: (recipient, query, unconsumed) => tx.heads(recipient, query, { unconsumed, limit: Infinity }).length,
|
|
34
|
-
receipt: ({ recipient, item }) => state.receipts.get(recipient)?.get(item),
|
|
35
|
-
insertReceipt({ recipient, item }, at) {
|
|
36
|
-
if (!state.receipts.has(recipient)) state.receipts.set(recipient, new Map());
|
|
37
|
-
const receipts = state.receipts.get(recipient);
|
|
38
|
-
if (receipts.has(item)) return false;
|
|
39
|
-
receipts.set(item, { at });
|
|
40
|
-
return true;
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
return tx;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// A tiny illustrative consumer, NOT a storage backend or durable runtime.
|
|
47
|
-
class Domain {
|
|
48
|
-
#state = { items: new Map(), addresses: [], receipts: new Map(), series: [] };
|
|
49
|
-
|
|
50
|
-
constructor(name, nextKey) {
|
|
51
|
-
this.name = name;
|
|
52
|
-
this.nextKey = nextKey;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
transaction(change) {
|
|
56
|
-
const draft = structuredClone(this.#state);
|
|
57
|
-
const result = change(draft, receiveStore(draft));
|
|
58
|
-
if (result instanceof Promise) throw new Error('no awaits inside memory transactions');
|
|
59
|
-
// A throw above discards the draft. No capability or mutable row escapes.
|
|
60
|
-
const captured = structuredClone(result);
|
|
61
|
-
this.#state = draft;
|
|
62
|
-
return captured;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
originate(actor, mentions, text) {
|
|
66
|
-
// Attention names its route; receive stores that application route as via.
|
|
67
|
-
const audience = resolveAudience({ actor, recipients: [...directory.keys()], mentions })
|
|
68
|
-
.map(({ recipient, route }) => ({ recipient, via: route }));
|
|
69
|
-
const key = this.nextKey();
|
|
70
|
-
const data = { text, origin: { domain: this.name, key } };
|
|
71
|
-
this.transaction((_state, tx) => receive(tx, {
|
|
72
|
-
item: { key, data }, audience: audience.filter(a => directory.get(a.recipient) === this.name),
|
|
73
|
-
}));
|
|
74
|
-
const outgoing = new Map();
|
|
75
|
-
for (const address of audience) {
|
|
76
|
-
const destination = directory.get(address.recipient);
|
|
77
|
-
if (destination === this.name) continue;
|
|
78
|
-
if (!outgoing.has(destination)) outgoing.set(destination, []);
|
|
79
|
-
outgoing.get(destination).push(address);
|
|
80
|
-
}
|
|
81
|
-
return { key, data, outgoing };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
admit(data, audience) {
|
|
85
|
-
const key = this.nextKey(); // Destination owns this identity, not origin.key.
|
|
86
|
-
this.transaction((_state, tx) => receive(tx, {
|
|
87
|
-
item: { key, data }, audience,
|
|
88
|
-
}));
|
|
89
|
-
return key;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Captured item/target/purpose are application coordinates, not an encoded key.
|
|
93
|
-
// For dispatch target is a destination; for presentation it is one recipient.
|
|
94
|
-
effectPort(purpose, item, target, fullReceipts = []) {
|
|
95
|
-
const rows = state => {
|
|
96
|
-
let series = state.series.find(s => s.purpose === purpose && s.item === item && s.target === target);
|
|
97
|
-
if (!series) {
|
|
98
|
-
series = { purpose, item, target, attempts: new Map() };
|
|
99
|
-
state.series.push(series);
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
list: () => structuredClone([...series.attempts.values()].sort((a, b) => b.ordinal - a.ordinal)),
|
|
103
|
-
byToken: token => structuredClone(series.attempts.get(token)),
|
|
104
|
-
insert: row => { series.attempts.set(row.token, structuredClone(row)); },
|
|
105
|
-
replace: row => { series.attempts.set(row.token, structuredClone(row)); },
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
|
-
const current = (state, tx) => state.items.has(item) && (purpose === 'dispatch'
|
|
109
|
-
|| Boolean(tx.addressed(target, item)) && !tx.receipt({ recipient: target, item }));
|
|
110
|
-
return {
|
|
111
|
-
claim: async () => this.transaction((state, tx) => {
|
|
112
|
-
if (!current(state, tx)) return undefined;
|
|
113
|
-
const claim = claimAttempt(rows(state), {
|
|
114
|
-
purpose, token: randomUUID(), at: Date.now(), leaseMs: 30_000, data: {},
|
|
115
|
-
});
|
|
116
|
-
return claim.status === 'acquired' ? { token: claim.token, ordinal: claim.ordinal } : undefined;
|
|
117
|
-
}),
|
|
118
|
-
current: async () => this.transaction(current),
|
|
119
|
-
settle: async (claim, completion) => this.transaction((state, tx) => {
|
|
120
|
-
const recorded = settleAttempt(rows(state), {
|
|
121
|
-
token: claim.token, outcome: completion.outcome, at: Date.now(), data: completion,
|
|
122
|
-
});
|
|
123
|
-
// ONE commit: attempt completion plus captured full-consumption receipts.
|
|
124
|
-
// No post-effect cancellation/currentness veto and no re-selection.
|
|
125
|
-
if (recorded && completion.outcome === 'presented') recordConsumed(tx, fullReceipts, Date.now());
|
|
126
|
-
return recorded;
|
|
127
|
-
}),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
consume(recipient, selection) {
|
|
132
|
-
return this.transaction((_state, tx) => consumeReceived(tx, recipient, selection, Date.now()));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
pending(recipient) {
|
|
136
|
-
return this.transaction((_state, tx) => tx.heads(recipient, {}, { unconsumed: true, limit: Infinity }).map(h => h.key));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
receipts() {
|
|
140
|
-
return this.transaction(state => [...state.receipts].flatMap(([recipient, items]) =>
|
|
141
|
-
[...items].map(([item, { at }]) => ({ recipient, item, at }))));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
attempts() {
|
|
145
|
-
return this.transaction(state => state.series.map(({ purpose, item, target, attempts }) =>
|
|
146
|
-
({ purpose, item, target, outcomes: [...attempts.values()].map(a => a.outcome) })));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async present(recipient, item, sink) {
|
|
150
|
-
const payload = this.transaction((_state, tx) => {
|
|
151
|
-
if (!tx.addressed(recipient, item)) throw new Error('not addressed');
|
|
152
|
-
return tx.load([item]).get(item);
|
|
153
|
-
});
|
|
154
|
-
const prepared = await preparePresentation({
|
|
155
|
-
payload, coverage: 'full',
|
|
156
|
-
port: this.effectPort('presentation', item, recipient, [{ recipient, item }]),
|
|
157
|
-
});
|
|
158
|
-
return prepared ? presentPrepared(prepared, sink) : undefined;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export async function demonstrateSymmetricDomains() {
|
|
163
|
-
// Same implementation, truly separate stores and unrelated key spaces.
|
|
164
|
-
let westKey = -700;
|
|
165
|
-
const east = new Domain('east', () => `opaque?${randomUUID()}`);
|
|
166
|
-
const west = new Domain('west', () => westKey--);
|
|
167
|
-
const domains = new Map([['east', east], ['west', west]]);
|
|
168
|
-
const handoffs = [];
|
|
169
|
-
let online = true;
|
|
170
|
-
|
|
171
|
-
async function sendAll(source, message, mode = 'accepted') {
|
|
172
|
-
const reports = [];
|
|
173
|
-
for (const [destination, audience] of message.outgoing) {
|
|
174
|
-
const report = await dispatchEffect(source.effectPort('dispatch', message.key, destination), async context => {
|
|
175
|
-
if (!await context.beforeSend()) return { outcome: 'not-capable' };
|
|
176
|
-
// A final policy gate may finish after the runner's timeout. Never mutate
|
|
177
|
-
// the destination after our deadline or cancellation, even if it said yes.
|
|
178
|
-
if (context.signal?.aborted || Date.now() >= context.deadline) return { outcome: 'not-capable' };
|
|
179
|
-
if (!online) return { outcome: 'failed' }; // Known no-send in this in-process fixture.
|
|
180
|
-
const key = domains.get(destination).admit(message.data, audience);
|
|
181
|
-
handoffs.push({ from: source.name, to: destination, key, audience: structuredClone(audience) });
|
|
182
|
-
// Unknown deliberately happens AFTER admission. This is not a dedup protocol.
|
|
183
|
-
return { outcome: mode, data: { destinationKey: key } };
|
|
184
|
-
}, { timeoutMs: 1000 });
|
|
185
|
-
reports.push(report);
|
|
186
|
-
}
|
|
187
|
-
return reports;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const eastMessage = east.originate('e/author', ['e/reader', 'w/reader', 'w/other'], 'East says hello');
|
|
191
|
-
const westMessage = west.originate('w/author', ['w/reader', 'e/reader', 'e/other'], 'West says hello');
|
|
192
|
-
// Establish origin receipts before destination presentation: neither can overwrite the other.
|
|
193
|
-
east.consume('e/reader', { kind: 'exact', key: eastMessage.key, remainingQuery: {} });
|
|
194
|
-
west.consume('w/reader', { kind: 'exact', key: westMessage.key, remainingQuery: {} });
|
|
195
|
-
|
|
196
|
-
const exchanges = [];
|
|
197
|
-
for (const [source, destination, message, mode, first, second] of [
|
|
198
|
-
[east, west, eastMessage, 'accepted', 'w/reader', 'w/other'],
|
|
199
|
-
[west, east, westMessage, 'unknown', 'e/reader', 'e/other'],
|
|
200
|
-
]) {
|
|
201
|
-
const [report] = await sendAll(source, message, mode);
|
|
202
|
-
const destinationKey = report.settlement.completion.data.destinationKey;
|
|
203
|
-
const admission = [destination.pending(first), destination.pending(second)];
|
|
204
|
-
const originReceiptsBefore = source.receipts();
|
|
205
|
-
const output = [];
|
|
206
|
-
// The sink proves only that this example callback captured the full payload.
|
|
207
|
-
const presentation = await destination.present(first, destinationKey, payload => { output.push(payload); });
|
|
208
|
-
const afterPresentation = [destination.pending(first), destination.pending(second)];
|
|
209
|
-
const presentationReceipts = destination.receipts().filter(ref => ref.item === destinationKey);
|
|
210
|
-
const originReceiptsAfter = source.receipts();
|
|
211
|
-
const exact = { kind: 'exact', key: destinationKey, remainingQuery: {} };
|
|
212
|
-
const unmatched = destination.consume(second, { kind: 'page', query: { via: 'bell' }, limit: 1 });
|
|
213
|
-
const consumed = destination.consume(second, { kind: 'page', query: { via: 'mention' }, limit: 1 });
|
|
214
|
-
const repeated = destination.consume(second, exact);
|
|
215
|
-
const handoffsBeforeReplay = handoffs.length;
|
|
216
|
-
const [replay] = await sendAll(source, message, mode);
|
|
217
|
-
exchanges.push({
|
|
218
|
-
from: source.name, to: destination.name, sourceKey: message.key, destinationKey,
|
|
219
|
-
dispatch: report, admission, presentation, output, afterPresentation, presentationReceipts,
|
|
220
|
-
originReceiptsBefore, originReceiptsAfter, unmatched, consumed, repeated, replay,
|
|
221
|
-
handoffsBeforeReplay, handoffsAfterReplay: handoffs.length,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
online = false;
|
|
226
|
-
const handoffsBeforeOffline = handoffs.length;
|
|
227
|
-
const offline = [];
|
|
228
|
-
for (const [domain, author, recipient] of [[east, 'e/author', 'e/reader'], [west, 'w/author', 'w/reader']]) {
|
|
229
|
-
const messages = [];
|
|
230
|
-
for (const text of ['oldest', 'middle', 'newest']) {
|
|
231
|
-
const message = domain.originate(author, [recipient], text);
|
|
232
|
-
messages.push(message);
|
|
233
|
-
await sendAll(domain, message); // Local work has no remote destination at all.
|
|
234
|
-
}
|
|
235
|
-
const exact = { kind: 'exact', key: messages[1].key, remainingQuery: {} };
|
|
236
|
-
const middle = domain.consume(recipient, exact);
|
|
237
|
-
const repeated = domain.consume(recipient, exact);
|
|
238
|
-
const page = () => domain.consume(recipient, { kind: 'page', query: { via: 'mention' }, limit: 1 });
|
|
239
|
-
offline.push({ domain: domain.name, keys: messages.map(m => m.key), middle, repeated, pages: [page(), page()] });
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
exchanges, handoffs, offline, handoffsBeforeOffline, handoffsAfterOffline: handoffs.length,
|
|
243
|
-
attempts: { east: east.attempts(), west: west.attempts() },
|
|
244
|
-
receipts: { east: east.receipts(), west: west.receipts() },
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
249
|
-
console.log(JSON.stringify(await demonstrateSymmetricDomains(), null, 2));
|
|
1
|
+
// A producer notice and its receiver's ledger presentation, using a disposable square.
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Square } from '@astrosheep/square';
|
|
6
|
+
import { DeliveryLedger } from '@astrosheep/square/delivery-ledger';
|
|
7
|
+
|
|
8
|
+
const directory = await mkdtemp(path.join(os.tmpdir(), 'square-notice-example-'));
|
|
9
|
+
const ledgerPath = path.join(directory, 'delivery.sqlite');
|
|
10
|
+
const env = { ...process.env, SQUARE_DELIVERY: ledgerPath };
|
|
11
|
+
let square, ledger;
|
|
12
|
+
try {
|
|
13
|
+
square = await Square.build({ path: path.join(directory, 'example.square'), markdown: 'Example', env });
|
|
14
|
+
ledger = await DeliveryLedger.open({ location: ledgerPath });
|
|
15
|
+
await square.join('Reader', { owner: 'example:reader' });
|
|
16
|
+
const notice = await square.send({
|
|
17
|
+
from: 'Coordinator', to: 'Reader', body: 'The work is ready.', metadata: { job: { id: 7 } },
|
|
18
|
+
});
|
|
19
|
+
const session = await ledger.session('example:reader');
|
|
20
|
+
const claim = await ledger.claimPresentation('example:reader', { generation: session.generation, budgetChars: 10000 });
|
|
21
|
+
if (!claim) throw new Error('No card was available for presentation');
|
|
22
|
+
// This callback represents the receiver actually presenting the card.
|
|
23
|
+
const presented = claim.cards.map(card => card.text);
|
|
24
|
+
await claim.settle('presented');
|
|
25
|
+
console.log(JSON.stringify({ notice, presented, receipts: await ledger.receipts('example:reader') }, null, 2));
|
|
26
|
+
} finally {
|
|
27
|
+
await square?.close();
|
|
28
|
+
await ledger?.close();
|
|
29
|
+
await rm(directory, { recursive: true, force: true });
|
|
250
30
|
}
|