@astrosheep/square 0.3.94 → 0.3.95
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.js +1 -1
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +1 -2
- package/dist/catch-decisions.js +41 -21
- package/dist/cli/context.js +0 -2
- package/dist/cli/express-presentation.js +3 -3
- package/dist/cli/json.js +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -3
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +21 -27
- package/dist/decisions.d.ts +0 -5
- package/dist/decisions.js +0 -13
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-operations.js +43 -68
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +6 -5
- package/dist/help.js +4 -6
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/model.d.ts +4 -6
- package/dist/model.js +1 -1
- package/dist/notifications.js +2 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +12 -20
- package/dist/ports.d.ts +5 -14
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +47 -57
- package/dist/presentation.d.ts +2 -9
- package/dist/presentation.js +13 -48
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +2 -3
- package/dist/sqlite-square.d.ts +0 -1
- package/dist/sqlite-square.js +29 -55
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +0 -9
- package/dist/square-domain.js +9 -31
- package/dist/square-facade.d.ts +5 -21
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-migration.d.ts +3 -1
- package/dist/square-migration.js +46 -13
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +5 -3
- package/dist/square-sqlite.js +89 -90
- package/dist/square-tools.js +3 -11
- package/dist/views.d.ts +2 -3
- package/dist/views.js +13 -19
- package/dist/wakes.d.ts +0 -3
- package/dist/wakes.js +3 -5
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +5 -5
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/codex-delivery.md +1 -1
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp.md +1 -1
- package/guides/migration.md +12 -6
- package/guides/participant-ownership.md +1 -1
- package/guides/pi-tools.md +2 -2
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +17 -23
- package/guides/square-api.md +77 -10
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +2 -6
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { wakePreview } from './delivery.js';
|
|
2
|
+
/** Configuration only: probing never contacts the receiver. */
|
|
3
|
+
function configuration(route, env) {
|
|
4
|
+
if (route.kind !== 'http')
|
|
5
|
+
return undefined;
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(route.address.url);
|
|
8
|
+
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password
|
|
9
|
+
|| route.address.url.includes('#'))
|
|
10
|
+
return undefined;
|
|
11
|
+
const headers = new Headers({ 'Content-Type': 'application/json' });
|
|
12
|
+
const tokenEnv = route.address.tokenEnv;
|
|
13
|
+
if (tokenEnv !== undefined) {
|
|
14
|
+
const token = tokenEnv.trim() ? env[tokenEnv] : undefined;
|
|
15
|
+
// RFC 6750 bearer credentials; whitespace/control characters cannot become headers.
|
|
16
|
+
if (!token || token !== token.trim() || !/^[A-Za-z0-9._~+\/-]+=*$/.test(token))
|
|
17
|
+
return undefined;
|
|
18
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
19
|
+
}
|
|
20
|
+
return { url: route.address.url, headers };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** HTTP accepts wake custody only; it never consumes or presents Square activity. */
|
|
27
|
+
export class HttpWakeTransport {
|
|
28
|
+
opts;
|
|
29
|
+
constructor(opts = {}) {
|
|
30
|
+
this.opts = opts;
|
|
31
|
+
}
|
|
32
|
+
async probe(route) {
|
|
33
|
+
return configuration(route, this.opts.env ?? process.env) !== undefined
|
|
34
|
+
? true : { outcome: 'not-capable', diagnostic: 'HTTP wake configuration unavailable.' };
|
|
35
|
+
}
|
|
36
|
+
async attempt(request, timeoutMs, beforeSend) {
|
|
37
|
+
const deadline = Date.now() + timeoutMs;
|
|
38
|
+
if (request.route.kind !== 'http')
|
|
39
|
+
return { outcome: 'not-capable' };
|
|
40
|
+
const config = configuration(request.route, this.opts.env ?? process.env);
|
|
41
|
+
if (config === undefined)
|
|
42
|
+
return { outcome: 'failed', signature: 'http_config_rejected', message: 'Invalid HTTP wake configuration.' };
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
let sent = false;
|
|
45
|
+
const budgetUnavailable = () => sent
|
|
46
|
+
? { outcome: 'unknown', signature: 'http_send_timeout', message: 'HTTP wake acceptance is unknown after timeout.' }
|
|
47
|
+
: { outcome: 'failed', signature: 'http_pre_send_timeout', message: 'HTTP wake budget elapsed before sending.' };
|
|
48
|
+
if (!Number.isFinite(timeoutMs) || Date.now() >= deadline)
|
|
49
|
+
return budgetUnavailable();
|
|
50
|
+
let timer;
|
|
51
|
+
const timeout = new Promise(resolve => {
|
|
52
|
+
timer = setTimeout(() => { controller.abort(); resolve(budgetUnavailable()); }, Math.max(0, deadline - Date.now()));
|
|
53
|
+
});
|
|
54
|
+
const send = async () => {
|
|
55
|
+
try {
|
|
56
|
+
const preview = request.artifact === undefined ? undefined
|
|
57
|
+
: (await request.artifact.read(facts => wakePreview(facts, request), controller.signal)).value;
|
|
58
|
+
if (preview === undefined)
|
|
59
|
+
return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
|
|
60
|
+
const body = JSON.stringify({
|
|
61
|
+
location: request.location, participant: request.participant, activity: request.activity, actor: request.actor,
|
|
62
|
+
...(request.attemptN === undefined ? {} : { attemptN: request.attemptN }), preview,
|
|
63
|
+
});
|
|
64
|
+
if (controller.signal.aborted || Date.now() >= deadline)
|
|
65
|
+
return budgetUnavailable();
|
|
66
|
+
if (!(await beforeSend()))
|
|
67
|
+
return { outcome: 'not-capable' };
|
|
68
|
+
// Preview and the final gate spend the same absolute budget as the request.
|
|
69
|
+
if (controller.signal.aborted || Date.now() >= deadline)
|
|
70
|
+
return budgetUnavailable();
|
|
71
|
+
sent = true;
|
|
72
|
+
const response = await (this.opts.fetch ?? fetch)(config.url, {
|
|
73
|
+
method: 'POST', headers: config.headers, body, redirect: 'manual', signal: controller.signal,
|
|
74
|
+
});
|
|
75
|
+
// Status is the whole acknowledgement; never wait for or buffer a receiver body.
|
|
76
|
+
void response.body?.cancel().catch(() => undefined);
|
|
77
|
+
if (response.status >= 200 && response.status < 300)
|
|
78
|
+
return { outcome: 'accepted', signature: 'http_accepted' };
|
|
79
|
+
return {
|
|
80
|
+
outcome: 'failed', signature: response.status >= 500 && response.status < 600 ? 'http_status_transient' : 'http_status_rejected',
|
|
81
|
+
message: `HTTP wake returned status ${response.status}.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// URLs, query credentials and raw fetch exceptions must never reach evidence.
|
|
86
|
+
if (controller.signal.aborted)
|
|
87
|
+
return budgetUnavailable();
|
|
88
|
+
return sent
|
|
89
|
+
? { outcome: 'unknown', signature: 'http_send_unknown', message: 'HTTP wake acceptance is unknown after a network error.' }
|
|
90
|
+
: { outcome: 'failed', signature: 'http_pre_send_failed', message: 'HTTP wake could not be prepared.' };
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
return await Promise.race([send(), timeout]);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
if (timer !== undefined)
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/model.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Act, ActivityId } from './square-core.js';
|
|
2
2
|
export { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
3
|
export type { Act, ActivityId, Audience, Reach } from './square-core.js';
|
|
4
|
-
export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo"];
|
|
4
|
+
export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo", "http"];
|
|
5
5
|
export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
|
|
6
6
|
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
7
7
|
readonly [key: string]: JsonValue;
|
|
@@ -41,7 +41,7 @@ export interface WakeRoute {
|
|
|
41
41
|
address: Record<string, string>;
|
|
42
42
|
updatedAt: number;
|
|
43
43
|
}
|
|
44
|
-
export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | '
|
|
44
|
+
export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | 'capped' | 'throttled' | 'bell_quota' | 'behind' | 'io' | 'unavailable';
|
|
45
45
|
export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reached' | 'conflict' | 'pending_peer';
|
|
46
46
|
export interface SquareErrorFacts {
|
|
47
47
|
/** Copyable recovery for a missing, detached or foreign seat. */
|
|
@@ -59,8 +59,6 @@ export interface SquareErrorFacts {
|
|
|
59
59
|
/** Whether the artifact holds that activity at all; an unknown id needs an ordinary page. */
|
|
60
60
|
activityExists?: boolean;
|
|
61
61
|
pending?: number;
|
|
62
|
-
holder?: string;
|
|
63
|
-
holdReason?: string;
|
|
64
62
|
retryAfterMs?: number;
|
|
65
63
|
}
|
|
66
64
|
declare class SquareErrorBase<Code extends string> extends Error {
|
|
@@ -100,7 +98,7 @@ export interface WatchLease {
|
|
|
100
98
|
expiresAt: number;
|
|
101
99
|
filter?: WatchLeaseFilter;
|
|
102
100
|
}
|
|
103
|
-
export type DirectedNotificationRoute = '
|
|
101
|
+
export type DirectedNotificationRoute = import('./attention.js').AudienceRoute;
|
|
104
102
|
export interface InboxNotification {
|
|
105
103
|
actIndex: number;
|
|
106
104
|
actor: string;
|
|
@@ -127,7 +125,7 @@ export type PublicAct = Extract<StoredAct, {
|
|
|
127
125
|
kind: 'say' | 'done';
|
|
128
126
|
}>;
|
|
129
127
|
export type RoomChangeAct = Extract<StoredAct, {
|
|
130
|
-
kind: 'join' | 'done' | '
|
|
128
|
+
kind: 'join' | 'done' | 'listen' | 'ignore' | 'unlisten';
|
|
131
129
|
}>;
|
|
132
130
|
export declare function formatHardCap(hardCap: HardCap): string;
|
|
133
131
|
export declare function parseParticipantList(value: string): string[];
|
package/dist/model.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Shared model and constants for Square.
|
|
2
2
|
export { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
|
-
export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo'];
|
|
3
|
+
export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo', 'http'];
|
|
4
4
|
export const ACTIVITY_METADATA_MAX_BYTES = 16 * 1024;
|
|
5
5
|
export const ACTIVITY_METADATA_MAX_DEPTH = 256;
|
|
6
6
|
/** Validate the bounded, lossless JSON object carried by a say. */
|
package/dist/notifications.js
CHANGED
|
@@ -32,6 +32,8 @@ export async function createDefaultWakeTransport(env = process.env, suppliedTran
|
|
|
32
32
|
}
|
|
33
33
|
const { createClaudeWakeTransport } = await import('./claude-delivery.js');
|
|
34
34
|
transports['claude-native'] = createClaudeWakeTransport(env);
|
|
35
|
+
const { HttpWakeTransport } = await import('./http-delivery.js');
|
|
36
|
+
transports.http = new HttpWakeTransport({ env });
|
|
35
37
|
return createWakeTransport(transports);
|
|
36
38
|
}
|
|
37
39
|
/** Route selection only: all implementations receive the same request and final gate. */
|
|
@@ -24,8 +24,7 @@ export declare function renderStatusResult(squarePath: string, data: StatusResul
|
|
|
24
24
|
export declare function renderListeningResult(squarePath: string, data: ListeningResult): string | undefined;
|
|
25
25
|
export type ListenerVerb = 'listen' | 'ignore' | 'unlisten';
|
|
26
26
|
export declare function renderListenerChange(squarePath: string, verb: ListenerVerb, data: ListenerChangeResult): string | undefined;
|
|
27
|
-
export
|
|
28
|
-
export declare function renderLifecycleResult(squarePath: string, verb: LifecycleVerb, data: LifecycleResult): string | undefined;
|
|
27
|
+
export declare function renderLifecycleResult(squarePath: string, data: LifecycleResult): string | undefined;
|
|
29
28
|
/** The refusal facts a transport has after normalizing a failure, without the live error. */
|
|
30
29
|
export interface FailureReport {
|
|
31
30
|
readonly code: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// render through these functions, so a person sees the same square either way.
|
|
3
3
|
import { sameName } from './model.js';
|
|
4
4
|
import { formatActivityId } from './square-core.js';
|
|
5
|
-
import { commandPrefix, joinRecoveryCommand, participantCommandPrefix, participantIdentity, participantsRecoveryCommand, quoteShell, renderAmbientEvent, renderAmbientEventFacts, renderEventCli, renderGrepActivitiesView, renderPresenceAnchor,
|
|
5
|
+
import { commandPrefix, joinRecoveryCommand, participantCommandPrefix, participantIdentity, participantsRecoveryCommand, quoteShell, renderAmbientEvent, renderAmbientEventFacts, renderEventCli, renderGrepActivitiesView, renderPresenceAnchor, renderWatchAlreadyActive, renderWatchReplaced, renderWatchStatus, takeoverRecoveryLines, withPathOutput, } from './presentation.js';
|
|
6
6
|
import { formatRelativeTime, formatTimestamp } from './time.js';
|
|
7
7
|
import { style } from './tty-style.js';
|
|
8
8
|
export const PARTICIPANTS_DEFAULT_LIMIT = 20;
|
|
@@ -34,7 +34,7 @@ export function renderJoinResult(data, owner) {
|
|
|
34
34
|
...(after.enrichmentUnavailable ? [' · joined; recent activity and context are unavailable right now'] : []),
|
|
35
35
|
...(isRejoin || activities === '' ? [] : ['', style('dim', 'recent activity'), activities]),
|
|
36
36
|
...fallback,
|
|
37
|
-
].join('\n'), { participantCount: after.standingCount
|
|
37
|
+
].join('\n'), { participantCount: after.standingCount });
|
|
38
38
|
}
|
|
39
39
|
function isGestureOnly(body) {
|
|
40
40
|
return /\*[^*]*\*/.test(body) && body.replace(/\*[^*]*\*/g, '').trim() === '';
|
|
@@ -63,7 +63,7 @@ export function renderExpression(squarePath, data, owner) {
|
|
|
63
63
|
: `\n○ ${participantIdentity(data.replyAuthorNotHere)} isn't in the square right now — the reply landed without reaching them`;
|
|
64
64
|
const unmatchedNotice = data.unmatchedMentions?.length
|
|
65
65
|
? `\n · ${data.unmatchedMentions.map((token) => `@${token} isn't standing here`).join('; ')} — ${activity.mentions.length === 0 && activity.reply === undefined ? 'activity landed bare' : 'left as literal text'}` : '';
|
|
66
|
-
return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount
|
|
66
|
+
return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount });
|
|
67
67
|
}
|
|
68
68
|
function continuationCommand(squarePath, name, options, owner) {
|
|
69
69
|
const args = ['--now'];
|
|
@@ -83,7 +83,7 @@ export function renderCatchOutcome(squarePath, data, options = {}, owner) {
|
|
|
83
83
|
const location = projection.location ?? squarePath;
|
|
84
84
|
const name = projection.participant;
|
|
85
85
|
const stop = data.reason === 'drained' && data.activities.length > 0 ? undefined : data.reason;
|
|
86
|
-
const header = { participantCount: projection.standingCount
|
|
86
|
+
const header = { participantCount: projection.standingCount };
|
|
87
87
|
const statusOptions = {
|
|
88
88
|
squarePath: location, name, owner, presence: projection.presence,
|
|
89
89
|
showCatchHint: !projection.deliveryAvailable,
|
|
@@ -256,11 +256,6 @@ export function renderStatusResult(squarePath, data) {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
const cap = data.hardCap === null ? 'unlimited' : `${data.hardCap} each`;
|
|
259
|
-
const held = data.held;
|
|
260
|
-
const holdTime = held?.at === undefined ? 'just now' : formatRelativeTime(held.at, data.now);
|
|
261
|
-
const hold = held !== null
|
|
262
|
-
? `· ${held.by === undefined ? 'someone' : participantIdentity(held.by)} raised a hand${held.reason ? ` — ${held.reason}` : ''}${style('dim', ` · ${holdTime}`)}`
|
|
263
|
-
: undefined;
|
|
264
259
|
const visible = data.latestActivity === undefined
|
|
265
260
|
? { text: '', bodyClipped: false }
|
|
266
261
|
: renderAmbientEventFacts(data.latestActivity, presentation.viewer, {
|
|
@@ -293,13 +288,13 @@ export function renderStatusResult(squarePath, data) {
|
|
|
293
288
|
].join(' · ');
|
|
294
289
|
const output = [
|
|
295
290
|
style('dim', counts),
|
|
296
|
-
|
|
291
|
+
'', style('dim', 'around the square'), ...people, '', style('dim', 'latest'), ...latest,
|
|
297
292
|
...(data.listening === undefined ? [] : [
|
|
298
293
|
'', style('dim', 'you listen to'),
|
|
299
294
|
...(data.listening.length === 0 ? [' ○ not turned toward anyone'] : data.listening.map((name) => ` · ${participantIdentity(name)}`)),
|
|
300
295
|
]),
|
|
301
296
|
].join('\n');
|
|
302
|
-
return withPathOutput(squarePath, output, { participantCount: presentation.standingCount
|
|
297
|
+
return withPathOutput(squarePath, output, { participantCount: presentation.standingCount });
|
|
303
298
|
}
|
|
304
299
|
export function renderListeningResult(squarePath, data) {
|
|
305
300
|
const projection = data.observation;
|
|
@@ -308,7 +303,7 @@ export function renderListeningResult(squarePath, data) {
|
|
|
308
303
|
const body = data.listening.length === 0
|
|
309
304
|
? `○ ${participantIdentity(projection.participant)} is not turned toward anyone`
|
|
310
305
|
: ['listening', ...data.listening.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
|
|
311
|
-
return withPathOutput(squarePath, body, { participantCount: projection.standingCount
|
|
306
|
+
return withPathOutput(squarePath, body, { participantCount: projection.standingCount });
|
|
312
307
|
}
|
|
313
308
|
export function renderListenerChange(squarePath, verb, data) {
|
|
314
309
|
const projection = data.observation;
|
|
@@ -324,19 +319,16 @@ export function renderListenerChange(squarePath, verb, data) {
|
|
|
324
319
|
: verb === 'ignore' ? `${actor} already ignores ${target} — only bells will reach you from them`
|
|
325
320
|
: `${actor} is not listening to ${target}`;
|
|
326
321
|
return withPathOutput(squarePath, `${data.activity === null ? '○' : '·'} ${action}`, {
|
|
327
|
-
participantCount: projection.standingCount,
|
|
322
|
+
participantCount: projection.standingCount,
|
|
328
323
|
});
|
|
329
324
|
}
|
|
330
|
-
export function renderLifecycleResult(squarePath,
|
|
325
|
+
export function renderLifecycleResult(squarePath, data) {
|
|
331
326
|
const projection = data.observation;
|
|
332
327
|
if (projection === undefined)
|
|
333
328
|
return undefined;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
const line = `${renderRoomChangeText(data.activity)} · ${data.activity.id} · just now`;
|
|
339
|
-
return withPathOutput(projection.location ?? squarePath, verb === 'hold' ? `· ${line}` : `✓ ${line}`, header);
|
|
329
|
+
return withPathOutput(projection.location ?? squarePath, `○ you stepped out — your footsteps fade · ${data.activity.id}`, {
|
|
330
|
+
participantCount: projection.standingCount,
|
|
331
|
+
});
|
|
340
332
|
}
|
|
341
333
|
/** Refusal lines with the same recovery commands the CLI prints. */
|
|
342
334
|
export function renderFailureLines(failure, options = {}) {
|
package/dist/ports.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Act, ActivityId, Owner, HardCap, Seat, StoredAct, WakeAddress, WakeRoute, WatchLease } from './model.js';
|
|
2
|
+
import type { AttemptOutcome } from './delivery-attempts.js';
|
|
2
3
|
import type { MetadataMatch, DeliveryDeferralFact } from './delivery-deferral.js';
|
|
3
4
|
export type ActivityIndex = number;
|
|
4
5
|
export interface SquareHeader {
|
|
@@ -35,7 +36,7 @@ export interface ActivityHead {
|
|
|
35
36
|
readonly index: ActivityIndex;
|
|
36
37
|
readonly at: number;
|
|
37
38
|
readonly kind: StoredAct['kind'];
|
|
38
|
-
readonly actor
|
|
39
|
+
readonly actor: string;
|
|
39
40
|
readonly reach?: 'bell';
|
|
40
41
|
readonly reply?: ActivityIndex;
|
|
41
42
|
}
|
|
@@ -47,7 +48,6 @@ export interface ActivityFilter {
|
|
|
47
48
|
readonly kinds?: readonly StoredAct['kind'][];
|
|
48
49
|
readonly actors?: readonly string[];
|
|
49
50
|
readonly actorMatch?: 'identity' | 'exact';
|
|
50
|
-
readonly actorRequired?: true;
|
|
51
51
|
readonly afterAt?: number;
|
|
52
52
|
}
|
|
53
53
|
export interface IndexPage {
|
|
@@ -71,12 +71,9 @@ export interface DirectedHead {
|
|
|
71
71
|
readonly at: number;
|
|
72
72
|
readonly actor: string;
|
|
73
73
|
readonly recipient: string;
|
|
74
|
-
readonly route: '
|
|
75
|
-
}
|
|
76
|
-
export interface LandedRecipient {
|
|
77
|
-
readonly recipient: string;
|
|
78
|
-
readonly route: DirectedHead['route'];
|
|
74
|
+
readonly route: import('./attention.js').AudienceRoute;
|
|
79
75
|
}
|
|
76
|
+
export type LandedRecipient = import('./attention.js').AudienceRecipient;
|
|
80
77
|
export interface ReceiptFacts {
|
|
81
78
|
readonly latestAt?: number;
|
|
82
79
|
readonly latestActorActivityIndex?: number;
|
|
@@ -109,7 +106,7 @@ export interface DeliveryEvidence {
|
|
|
109
106
|
readonly generation: ActivityId;
|
|
110
107
|
readonly activity: ActivityId;
|
|
111
108
|
readonly kind: 'wake' | 'presentation';
|
|
112
|
-
readonly outcome:
|
|
109
|
+
readonly outcome: AttemptOutcome;
|
|
113
110
|
readonly attemptN: number;
|
|
114
111
|
readonly at: number;
|
|
115
112
|
readonly expiresAt?: number;
|
|
@@ -178,12 +175,6 @@ export interface SquareRead {
|
|
|
178
175
|
roster(): readonly ParticipantFact[];
|
|
179
176
|
attention(listener: string): readonly AttentionFact[];
|
|
180
177
|
incomingAttention(sender: string): readonly AttentionFact[];
|
|
181
|
-
hold(): {
|
|
182
|
-
readonly active: boolean;
|
|
183
|
-
readonly at?: number;
|
|
184
|
-
readonly reason?: string;
|
|
185
|
-
readonly actor?: string;
|
|
186
|
-
};
|
|
187
178
|
activity(index: ActivityIndex): StoredAct | undefined;
|
|
188
179
|
activityHeads(filter: ActivityFilter, page: IndexPage): FactPage<ActivityHead>;
|
|
189
180
|
activities(indexes: readonly ActivityIndex[]): readonly StoredAct[];
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import type { PresentationResult, PresentPendingInput } from './ports.js';
|
|
2
2
|
/** Prepare custody before any external effect. A batch prepares every included item first. */
|
|
3
|
-
export declare function preparePendingPresentation(input: Omit<PresentPendingInput, 'sink'>): Promise<{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
eligible(): Promise<boolean>;
|
|
18
|
-
release(outcome?: "released" | "failed", message?: string): Promise<void>;
|
|
19
|
-
recordPresented(): Promise<PresentationResult>;
|
|
20
|
-
} | undefined>;
|
|
21
|
-
/** Eligibility precedes the sink; only captured ownership fences its truthful completion. */
|
|
3
|
+
export declare function preparePendingPresentation(input: Omit<PresentPendingInput, 'sink'>): Promise<import("./delivery-effects.js").PreparedPresentation<{
|
|
4
|
+
kind: "say";
|
|
5
|
+
actor: import("./square-core.js").Participant;
|
|
6
|
+
at?: number;
|
|
7
|
+
body?: string;
|
|
8
|
+
metadata?: import("./model.js").ActivityMetadata;
|
|
9
|
+
mentions?: readonly import("./square-core.js").Participant[];
|
|
10
|
+
reach?: import("./square-core.js").Reach;
|
|
11
|
+
reply?: number;
|
|
12
|
+
} & {
|
|
13
|
+
index: number;
|
|
14
|
+
at: number;
|
|
15
|
+
}> | undefined>;
|
|
16
|
+
/** Square sinks explicitly classify rejection as failed; the public effect core defaults to unknown. */
|
|
22
17
|
export declare function presentPending(input: PresentPendingInput): Promise<PresentationResult>;
|
|
@@ -4,6 +4,7 @@ import { isSquareError } from './model.js';
|
|
|
4
4
|
import { pendingHead, sameJoin } from './delivery.js';
|
|
5
5
|
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
6
6
|
import { asSeatRef } from './seats.js';
|
|
7
|
+
import { preparePresentation, presentPrepared } from './delivery-effects.js';
|
|
7
8
|
/** Prepare custody before any external effect. A batch prepares every included item first. */
|
|
8
9
|
export async function preparePendingPresentation(input) {
|
|
9
10
|
const index = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
@@ -30,67 +31,56 @@ export async function preparePendingPresentation(input) {
|
|
|
30
31
|
&& sameJoin(facts, participant);
|
|
31
32
|
};
|
|
32
33
|
const claimToken = randomUUID();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
return preparePresentation({
|
|
35
|
+
payload: item, coverage: input.markSeen === false ? 'clipped' : 'full',
|
|
36
|
+
port: {
|
|
37
|
+
async claim(signal) {
|
|
38
|
+
const claimed = await input.artifact.claimDelivery({ seat: ref, activity: formatActivityId(index), kind: 'presentation',
|
|
39
|
+
at: input.now ?? Date.now(), leaseMs: input.timeoutMs ?? 5000, claimToken }, signal).catch(() => ({ status: 'stale' }));
|
|
40
|
+
return claimed.status === 'acquired' ? { token: claimToken, ordinal: claimed.attemptN } : undefined;
|
|
41
|
+
},
|
|
42
|
+
async current(signal) {
|
|
43
|
+
return (await input.artifact.read(facts => owns(facts) && input.current?.(facts) !== false
|
|
44
|
+
&& pendingHead(facts, participant.name, index) !== undefined
|
|
45
|
+
&& deliveryDeferralExpiry(facts, seat, index, input.now ?? Date.now()) === undefined, signal ?? input.signal)).value;
|
|
46
|
+
},
|
|
47
|
+
async settle(claim, completion) {
|
|
48
|
+
const success = completion.outcome === 'presented' || completion.outcome === 'clipped';
|
|
49
|
+
const { value } = await input.artifact.settleAs({ kind: 'participant', seat: ref }, facts => {
|
|
50
|
+
// Captured ownership fences truth; post-effect policy/cancellation cannot veto it.
|
|
51
|
+
if (!owns(facts))
|
|
52
|
+
return false;
|
|
53
|
+
if (!facts.completeDelivery({ seat: ref, activity: formatActivityId(index), claimToken: claim.token,
|
|
54
|
+
outcome: completion.outcome, at: success ? input.now ?? Date.now() : Date.now(),
|
|
55
|
+
...(completion.reason === 'threw' ? { message: completion.error instanceof Error ? completion.error.message : String(completion.error) } : {}),
|
|
56
|
+
}))
|
|
57
|
+
return false;
|
|
58
|
+
if (completion.outcome === 'presented')
|
|
59
|
+
facts.markSeen(participant.name, [index], input.now ?? Date.now());
|
|
60
|
+
return true;
|
|
61
|
+
}).catch(error => {
|
|
62
|
+
if (!success || isSquareError(error) && error.code === 'already_joined')
|
|
63
|
+
return { value: false };
|
|
64
|
+
throw error;
|
|
65
|
+
});
|
|
66
|
+
return value;
|
|
67
|
+
},
|
|
53
68
|
},
|
|
54
|
-
|
|
55
|
-
// Success is an observed external fact, not another eligibility decision. Neither
|
|
56
|
-
// post-effect cancellation nor a new deferral can erase it and authorize replay.
|
|
57
|
-
effectObserved = true;
|
|
58
|
-
const outcome = input.markSeen === false ? 'clipped' : 'presented';
|
|
59
|
-
const { value: committed } = await input.artifact.settleAs({ kind: 'participant', seat: ref }, facts => {
|
|
60
|
-
if (!owns(facts))
|
|
61
|
-
return false;
|
|
62
|
-
if (!facts.completeDelivery({ seat: ref, activity: formatActivityId(index), claimToken, outcome, at: input.now ?? Date.now() }))
|
|
63
|
-
return false;
|
|
64
|
-
if (input.markSeen !== false)
|
|
65
|
-
facts.markSeen(participant.name, [index], input.now ?? Date.now());
|
|
66
|
-
return true;
|
|
67
|
-
}).catch(error => {
|
|
68
|
-
if (isSquareError(error) && error.code === 'already_joined')
|
|
69
|
-
return { value: false };
|
|
70
|
-
throw error;
|
|
71
|
-
});
|
|
72
|
-
return committed ? { presented: true, activity: item } : { presented: false };
|
|
73
|
-
},
|
|
74
|
-
};
|
|
69
|
+
}, { signal: input.signal });
|
|
75
70
|
}
|
|
76
|
-
/**
|
|
71
|
+
/** Square sinks explicitly classify rejection as failed; the public effect core defaults to unknown. */
|
|
77
72
|
export async function presentPending(input) {
|
|
78
73
|
const prepared = await preparePendingPresentation(input);
|
|
79
74
|
if (prepared === undefined)
|
|
80
75
|
return { presented: false };
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
return await prepared.recordPresented();
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
await prepared.release();
|
|
95
|
-
}
|
|
76
|
+
const report = await presentPrepared(prepared, activity => input.sink.present(activity), {
|
|
77
|
+
signal: input.signal, classifyError: () => 'failed',
|
|
78
|
+
});
|
|
79
|
+
if (report.observation.kind === 'threw')
|
|
80
|
+
throw report.observation.error;
|
|
81
|
+
const result = report.settlements[0].result;
|
|
82
|
+
if (result.persistence.status === 'error')
|
|
83
|
+
throw result.persistence.error;
|
|
84
|
+
return report.observation.kind === 'returned' && result.persistence.status === 'recorded'
|
|
85
|
+
? { presented: true, activity: prepared.payload } : { presented: false };
|
|
96
86
|
}
|
package/dist/presentation.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type { Activity, UnreadActivitySummary, ParticipantSummary } from './squa
|
|
|
5
5
|
export type WatchStatus = 'idle' | 'drained' | 'quorum' | 'capped';
|
|
6
6
|
interface HeaderOptions {
|
|
7
7
|
participantCount?: number;
|
|
8
|
-
held?: boolean;
|
|
9
8
|
/** Refusal bodies go to stderr, which stays literal even when it is a TTY. */
|
|
10
9
|
plain?: boolean;
|
|
11
10
|
}
|
|
@@ -14,7 +13,6 @@ interface ParticipantOutputOptions {
|
|
|
14
13
|
squarePath: string;
|
|
15
14
|
name: string;
|
|
16
15
|
participantCount?: number;
|
|
17
|
-
held?: boolean;
|
|
18
16
|
}
|
|
19
17
|
interface ActivityLimitOptions extends ParticipantOutputOptions {
|
|
20
18
|
count?: number;
|
|
@@ -27,17 +25,13 @@ interface ActivityBlockedOptions extends ParticipantOutputOptions {
|
|
|
27
25
|
draftPath?: string;
|
|
28
26
|
}
|
|
29
27
|
interface ExpressWaitingOptions {
|
|
30
|
-
reason: 'throttled'
|
|
28
|
+
reason: 'throttled';
|
|
31
29
|
delayMs?: number;
|
|
32
|
-
holder?: string;
|
|
33
|
-
holdReason?: string;
|
|
34
30
|
}
|
|
35
31
|
interface ExpressNoWaitOptions extends ParticipantOutputOptions {
|
|
36
|
-
reason: 'throttled'
|
|
32
|
+
reason: 'throttled';
|
|
37
33
|
retryCommand: string;
|
|
38
34
|
delayMs?: number;
|
|
39
|
-
holder?: string;
|
|
40
|
-
holdReason?: string;
|
|
41
35
|
draftPath?: string;
|
|
42
36
|
}
|
|
43
37
|
interface WatchStatusOptions extends ParticipantOutputOptions {
|
|
@@ -56,7 +50,6 @@ export declare function withPathOutput(squarePath: string, body?: string, opts?:
|
|
|
56
50
|
/** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
|
|
57
51
|
export declare function formatRefusal(squarePath: string, bodyLines: string[], opts?: {
|
|
58
52
|
participantCount?: number;
|
|
59
|
-
held?: boolean;
|
|
60
53
|
}): string;
|
|
61
54
|
export declare function refusalText(error: unknown): string;
|
|
62
55
|
export declare function quoteShell(value: string): string;
|