@astrosheep/square 0.3.63 → 0.3.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +3 -2
- package/claude-plugin/hooks/hooks.json +1 -22
- package/claude-plugin/hooks/register.js +119 -0
- package/claude-plugin/types.d.ts +7 -0
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +1 -10
- package/dist/activity-feed.js +1 -27
- package/dist/activity.js +57 -25
- package/dist/artifact.d.ts +1 -1
- package/dist/artifact.js +24 -25
- package/dist/attention-presentation.js +1 -0
- package/dist/automatic-session.d.ts +3 -1
- package/dist/automatic-session.js +21 -34
- package/dist/boundary-presentation.js +4 -13
- package/dist/canonical-path.d.ts +11 -0
- package/dist/canonical-path.js +47 -0
- package/dist/catch-decisions.js +14 -10
- package/dist/catch-selection.d.ts +6 -0
- package/dist/catch-selection.js +14 -0
- package/dist/claude-delivery.d.ts +11 -0
- package/dist/claude-delivery.js +145 -0
- package/dist/claude-mod.d.ts +8 -0
- package/dist/claude-mod.js +164 -0
- package/dist/cli/context.d.ts +0 -1
- package/dist/cli/context.js +9 -21
- package/dist/cli/harness-command.d.ts +0 -1
- package/dist/cli/harness-command.js +0 -6
- package/dist/cli/observation-commands.d.ts +1 -1
- package/dist/cli/observation-commands.js +31 -58
- package/dist/cli/program.js +20 -18
- package/dist/cli/registry.js +2 -2
- package/dist/cli/square-commands.js +10 -6
- package/dist/codex-boundary-state.d.ts +2 -2
- package/dist/codex-boundary-state.js +4 -4
- package/dist/codex-hook.js +12 -15
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +7 -5
- package/dist/decisions.d.ts +2 -6
- package/dist/decisions.js +16 -17
- package/dist/delivery-health.d.ts +1 -2
- package/dist/delivery-health.js +4 -5
- package/dist/delivery-operations.d.ts +2 -5
- package/dist/delivery-operations.js +77 -214
- package/dist/delivery.d.ts +25 -47
- package/dist/delivery.js +14 -46
- package/dist/diagnostic-redaction.js +7 -9
- package/dist/file-changes.d.ts +0 -1
- package/dist/file-changes.js +2 -19
- package/dist/harness-claude.js +8 -6
- package/dist/harness-codex.d.ts +0 -2
- package/dist/harness-codex.js +0 -2
- package/dist/harness-links.js +13 -17
- package/dist/harness.js +3 -7
- package/dist/help.js +4 -3
- package/dist/host-ledger-file-adapter.d.ts +17 -8
- package/dist/host-ledger-file-adapter.js +164 -80
- package/dist/host-ledger.d.ts +55 -50
- package/dist/inbox.d.ts +0 -3
- package/dist/inbox.js +88 -50
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +1 -4
- package/dist/mcp.js +24 -41
- package/dist/mention-parse.js +3 -9
- package/dist/model.d.ts +11 -2
- package/dist/model.js +1 -1
- package/dist/notifications.d.ts +3 -16
- package/dist/notifications.js +41 -122
- package/dist/opencode-delivery.d.ts +14 -0
- package/dist/opencode-delivery.js +171 -0
- package/dist/opencode.d.ts +39 -18
- package/dist/opencode.js +145 -40
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
- package/dist/packages/agent-delivery/src/claude-auth.js +75 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
- package/dist/packages/agent-delivery/src/claude-native.js +68 -0
- package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
- package/dist/packages/agent-delivery/src/claude.js +66 -0
- package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
- package/dist/packages/agent-delivery/src/deadline.js +41 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
- package/dist/packages/agent-delivery/src/errors.js +41 -0
- package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
- package/dist/packages/agent-delivery/src/guards.js +6 -0
- package/dist/packages/agent-delivery/src/index.d.ts +83 -0
- package/dist/packages/agent-delivery/src/index.js +110 -0
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
- package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
- package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
- package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
- package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
- package/dist/packages/agent-delivery/src/opencode.js +127 -0
- package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
- package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
- package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
- package/dist/packages/agent-delivery/src/paseo.js +106 -0
- package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
- package/dist/packages/agent-delivery/src/pi-client.js +106 -0
- package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
- package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
- package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
- package/dist/packages/agent-delivery/src/pi-send.js +4 -0
- package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
- package/dist/packages/agent-delivery/src/pi.js +209 -0
- package/dist/packages/agent-delivery/src/types.d.ts +120 -0
- package/dist/participant-host.js +12 -13
- package/dist/participant-identity.d.ts +21 -0
- package/dist/participant-identity.js +32 -11
- package/dist/paseo-delivery.d.ts +2 -2
- package/dist/paseo-delivery.js +35 -27
- package/dist/paseo-timeline.js +25 -21
- package/dist/paseo.d.ts +2 -2
- package/dist/paseo.js +1 -2
- package/dist/ports.d.ts +7 -20
- package/dist/presentation-operations.js +16 -4
- package/dist/presentation.d.ts +19 -10
- package/dist/presentation.js +99 -177
- package/dist/registry.d.ts +15 -66
- package/dist/registry.js +45 -123
- package/dist/routes.d.ts +1 -28
- package/dist/routes.js +22 -104
- package/dist/runtime.d.ts +3 -9
- package/dist/runtime.js +14 -67
- package/dist/search.d.ts +0 -2
- package/dist/search.js +3 -10
- package/dist/square-actions.js +48 -72
- package/dist/square-application.js +32 -30
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +22 -5
- package/dist/square-facade.d.ts +2 -0
- package/dist/square-file-adapter.d.ts +2 -1
- package/dist/square-file-adapter.js +6 -9
- package/dist/square-projections.d.ts +40 -45
- package/dist/square-projections.js +60 -74
- package/dist/square-state.js +1 -4
- package/dist/square-storage.d.ts +5 -8
- package/dist/square-storage.js +13 -36
- package/dist/square-wiring.d.ts +2 -7
- package/dist/square-wiring.js +6 -24
- package/dist/stream.js +4 -6
- package/dist/time.js +2 -7
- package/dist/views.d.ts +6 -18
- package/dist/views.js +14 -21
- package/dist/wake-attempts.d.ts +2 -56
- package/dist/wake-attempts.js +15 -110
- package/dist/wake-eligibility.d.ts +43 -0
- package/dist/wake-eligibility.js +106 -0
- package/dist/wake-evidence.d.ts +32 -9
- package/dist/wake-evidence.js +29 -24
- package/dist/wake-sink.d.ts +13 -9
- package/dist/wake-sink.js +35 -54
- package/dist/watch.js +13 -21
- package/extensions/square-pi.js +27 -13
- package/guides/change-waits.md +1 -1
- package/guides/claude-native.md +101 -0
- package/guides/host-ledger.md +3 -1
- package/guides/participant.md +14 -7
- package/guides/pi-native-delivery.md +49 -0
- package/package.json +4 -3
- package/dist/claude-hook.d.ts +0 -10
- package/dist/claude-hook.js +0 -60
- package/dist/paseo-connection.d.ts +0 -15
- package/dist/paseo-connection.js +0 -141
- package/dist/presence.d.ts +0 -2
- package/dist/presence.js +0 -24
- package/dist/presented.d.ts +0 -2
- package/dist/presented.js +0 -10
- package/dist/state-cell.d.ts +0 -14
- package/dist/wake-port.d.ts +0 -23
- package/dist/wake-port.js +0 -27
- /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type SquareState, type WakeRoute } from './model.js';
|
|
2
|
+
import { deriveDeliveryModel } from './delivery.js';
|
|
3
|
+
import type { EvidenceRecord, PresenceRecord } from './host-ledger.js';
|
|
4
|
+
import type { PresentationEvidenceProjection, WakeRequest } from './ports.js';
|
|
5
|
+
import { type WakeAttempt } from './wake-evidence.js';
|
|
6
|
+
export interface WakeSnapshot {
|
|
7
|
+
readonly state: SquareState;
|
|
8
|
+
/** Canonicalized by the caller; every identity in this snapshot is square-local. */
|
|
9
|
+
readonly location: string;
|
|
10
|
+
readonly owners: readonly PresenceRecord[];
|
|
11
|
+
readonly wakeRecords: readonly EvidenceRecord[];
|
|
12
|
+
readonly presentations: readonly PresentationEvidenceProjection[];
|
|
13
|
+
readonly now: number;
|
|
14
|
+
readonly delivery?: ReturnType<typeof deriveDeliveryModel>;
|
|
15
|
+
}
|
|
16
|
+
export interface OwnedWakeRoute {
|
|
17
|
+
readonly route: WakeRoute;
|
|
18
|
+
readonly owner: PresenceRecord;
|
|
19
|
+
}
|
|
20
|
+
export interface WakeCurrentness {
|
|
21
|
+
readonly current: boolean;
|
|
22
|
+
readonly activityPending: boolean;
|
|
23
|
+
readonly sessionBound: boolean;
|
|
24
|
+
readonly routePublished: boolean;
|
|
25
|
+
readonly selectedOwner: boolean;
|
|
26
|
+
readonly cancelled: boolean;
|
|
27
|
+
readonly presented: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface WakeEvidence {
|
|
30
|
+
readonly delivered: boolean;
|
|
31
|
+
/** All selected, uncancelled routes have session-local presentation evidence. */
|
|
32
|
+
readonly presented: boolean;
|
|
33
|
+
readonly attempts: readonly WakeAttempt[];
|
|
34
|
+
readonly terminal?: WakeAttempt;
|
|
35
|
+
readonly attemptableRoutes: readonly WakeRoute[];
|
|
36
|
+
}
|
|
37
|
+
export interface WakeEligibility {
|
|
38
|
+
evidence(recipient: string, actIndex: number): WakeEvidence;
|
|
39
|
+
currentness(request: WakeRequest): WakeCurrentness;
|
|
40
|
+
}
|
|
41
|
+
/** One strict ownership join, latest selection, and request fence over primary evidence only. */
|
|
42
|
+
export declare function projectWakeEligibility(input: WakeSnapshot): WakeEligibility;
|
|
43
|
+
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { nameKey } from './model.js';
|
|
2
|
+
import { deriveDeliveryModel } from './delivery.js';
|
|
3
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
4
|
+
import { decodeWakeEvidence } from './wake-evidence.js';
|
|
5
|
+
function identity(...parts) { return JSON.stringify(parts); }
|
|
6
|
+
/** One strict ownership join, latest selection, and request fence over primary evidence only. */
|
|
7
|
+
export function projectWakeEligibility(input) {
|
|
8
|
+
const delivery = input.delivery ?? deriveDeliveryModel(input.state);
|
|
9
|
+
const ownersBySession = new Map();
|
|
10
|
+
for (const owner of input.owners) {
|
|
11
|
+
if (owner.location !== input.location)
|
|
12
|
+
continue;
|
|
13
|
+
const key = identity(nameKey(owner.participant), owner.session);
|
|
14
|
+
const rows = ownersBySession.get(key) ?? [];
|
|
15
|
+
rows.push(owner);
|
|
16
|
+
ownersBySession.set(key, rows);
|
|
17
|
+
}
|
|
18
|
+
const ownerFor = (route) => route.location !== input.location ? undefined
|
|
19
|
+
: ownersBySession.get(identity(nameKey(route.participant), route.sessionId))?.find((row) => route.epoch === undefined || row.epoch === route.epoch);
|
|
20
|
+
const ownedRoutes = [];
|
|
21
|
+
const latest = new Map();
|
|
22
|
+
for (const route of input.state.routes ?? []) {
|
|
23
|
+
const participant = nameKey(route.participant);
|
|
24
|
+
const owner = ownerFor(route);
|
|
25
|
+
if (owner === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
ownedRoutes.push({ route, owner });
|
|
28
|
+
latest.set(participant, Math.max(latest.get(participant) ?? Number.NEGATIVE_INFINITY, route.updatedAt));
|
|
29
|
+
}
|
|
30
|
+
const selectedByRecipient = new Map();
|
|
31
|
+
for (const owned of ownedRoutes) {
|
|
32
|
+
const key = nameKey(owned.route.participant);
|
|
33
|
+
if (owned.route.updatedAt !== latest.get(key))
|
|
34
|
+
continue;
|
|
35
|
+
const rows = selectedByRecipient.get(key) ?? [];
|
|
36
|
+
rows.push(owned);
|
|
37
|
+
selectedByRecipient.set(key, rows);
|
|
38
|
+
}
|
|
39
|
+
const pending = new Set();
|
|
40
|
+
for (const recipient of delivery.joinedRecipients()) {
|
|
41
|
+
for (const notification of delivery.pendingFor(recipient))
|
|
42
|
+
pending.add(identity(nameKey(recipient), notification.item.index));
|
|
43
|
+
}
|
|
44
|
+
const attemptsByAttention = new Map();
|
|
45
|
+
for (const record of input.wakeRecords) {
|
|
46
|
+
if (record.location !== input.location)
|
|
47
|
+
continue;
|
|
48
|
+
const decoded = decodeWakeEvidence(record, input.now);
|
|
49
|
+
if (decoded?.kind !== 'attempt')
|
|
50
|
+
continue;
|
|
51
|
+
const attempt = decoded.value;
|
|
52
|
+
const key = identity(nameKey(attempt.attention.recipient), attempt.attention.actIndex);
|
|
53
|
+
const rows = attemptsByAttention.get(key) ?? [];
|
|
54
|
+
rows.push(attempt);
|
|
55
|
+
attemptsByAttention.set(key, rows);
|
|
56
|
+
}
|
|
57
|
+
const presentedSessions = new Set();
|
|
58
|
+
for (const row of input.presentations) {
|
|
59
|
+
if (row.location !== input.location || (row.outcome !== 'presented' && row.outcome !== 'clipped'))
|
|
60
|
+
continue;
|
|
61
|
+
presentedSessions.add(identity(nameKey(row.participant), row.activity, row.sessionId));
|
|
62
|
+
}
|
|
63
|
+
const isPresented = (route, actIndex) => presentedSessions.has(identity(nameKey(route.participant), formatActivityId(actIndex), route.sessionId));
|
|
64
|
+
const isCancelled = (owner, actIndex) => actIndex <= (owner.cancelledThrough ?? -1);
|
|
65
|
+
return {
|
|
66
|
+
evidence(recipient, actIndex) {
|
|
67
|
+
const key = nameKey(recipient);
|
|
68
|
+
// Accepted/unknown exclusion belongs to attention, not to a surviving owner or route kind.
|
|
69
|
+
const attempts = attemptsByAttention.get(identity(key, actIndex)) ?? [];
|
|
70
|
+
const terminal = attempts.findLast((attempt) => attempt.outcome === 'accepted')
|
|
71
|
+
?? attempts.findLast((attempt) => attempt.outcome === 'unknown');
|
|
72
|
+
const routes = (selectedByRecipient.get(key) ?? []).filter(({ owner }) => !isCancelled(owner, actIndex));
|
|
73
|
+
const presented = routes.length > 0 && routes.every(({ route }) => isPresented(route, actIndex));
|
|
74
|
+
return {
|
|
75
|
+
delivered: delivery.isSeen(recipient, actIndex), presented, attempts,
|
|
76
|
+
...(terminal === undefined ? {} : { terminal }),
|
|
77
|
+
attemptableRoutes: terminal === undefined && pending.has(identity(key, actIndex))
|
|
78
|
+
? routes.filter(({ route }) => !isPresented(route, actIndex)).map(({ route }) => route) : [],
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
currentness(request) {
|
|
82
|
+
const actIndex = parseActivityId(request.activity);
|
|
83
|
+
const route = request.route;
|
|
84
|
+
const sameRecipient = nameKey(request.participant) === nameKey(route.participant);
|
|
85
|
+
const sameLocation = request.location === input.location && route.location === input.location;
|
|
86
|
+
const activityPending = actIndex !== undefined && request.location === input.location
|
|
87
|
+
&& pending.has(identity(nameKey(request.participant), actIndex));
|
|
88
|
+
const owner = sameLocation && sameRecipient ? ownerFor(route) : undefined;
|
|
89
|
+
const sessionBound = owner !== undefined;
|
|
90
|
+
const matchesRequest = (published) => published.location === route.location
|
|
91
|
+
&& nameKey(published.participant) === nameKey(route.participant) && published.sessionId === route.sessionId
|
|
92
|
+
&& published.kind === route.kind && published.epoch === route.epoch && Object.keys(published.address).length === Object.keys(route.address).length && Object.keys(published.address).every((key) => published.address[key] === route.address[key]);
|
|
93
|
+
const routePublished = sameLocation && sameRecipient && (input.state.routes ?? []).some(matchesRequest);
|
|
94
|
+
const selectedOwner = sameLocation && sameRecipient
|
|
95
|
+
&& (selectedByRecipient.get(nameKey(route.participant)) ?? []).some(({ route: published }) => matchesRequest(published));
|
|
96
|
+
const cancelled = actIndex !== undefined && owner !== undefined && isCancelled(owner, actIndex);
|
|
97
|
+
const presented = actIndex !== undefined && sameLocation && sameRecipient && isPresented(route, actIndex);
|
|
98
|
+
// Dispatching evidence deliberately does not fence the request that owns the atomic claim.
|
|
99
|
+
return { current: activityPending && sessionBound && routePublished && selectedOwner && !cancelled && !presented,
|
|
100
|
+
activityPending, sessionBound, routePublished, selectedOwner, cancelled, presented };
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function wakeIsEligible(evidence) {
|
|
105
|
+
return !evidence.delivered && !evidence.presented && evidence.terminal === undefined && evidence.attemptableRoutes.length > 0;
|
|
106
|
+
}
|
package/dist/wake-evidence.d.ts
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import type { EvidenceRecord, WakeAttention } from './host-ledger.js';
|
|
2
|
+
import type { WakeRouteKind } from './model.js';
|
|
3
|
+
export interface WakeAttempt {
|
|
4
|
+
readonly at: number;
|
|
5
|
+
readonly attention: WakeAttention;
|
|
6
|
+
readonly routeKind: WakeRouteKind;
|
|
7
|
+
readonly outcome: 'accepted' | 'unknown' | 'failed';
|
|
8
|
+
readonly signature?: string;
|
|
9
|
+
readonly attemptN: number;
|
|
10
|
+
readonly session?: string;
|
|
11
|
+
readonly message?: string;
|
|
12
|
+
readonly diagnostic?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface WakeReleaseDiagnostic {
|
|
15
|
+
readonly at: number;
|
|
16
|
+
readonly attention: WakeAttention;
|
|
17
|
+
readonly routeKind?: WakeRouteKind;
|
|
18
|
+
readonly attemptN?: number;
|
|
19
|
+
readonly session?: string;
|
|
20
|
+
readonly signature?: string;
|
|
21
|
+
readonly message?: string;
|
|
22
|
+
readonly diagnostic?: unknown;
|
|
23
|
+
}
|
|
24
|
+
export type DecodedWakeEvidence = {
|
|
25
|
+
readonly kind: 'attempt';
|
|
26
|
+
readonly value: WakeAttempt;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'release';
|
|
29
|
+
readonly value: WakeReleaseDiagnostic;
|
|
30
|
+
};
|
|
31
|
+
/** Decode primary attempts and diagnostic-only releases without mixing their evidence. */
|
|
32
|
+
export declare function decodeWakeEvidence(record: EvidenceRecord, now: number): DecodedWakeEvidence | undefined;
|
package/dist/wake-evidence.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { parseActivityId } from './square-core.js';
|
|
2
|
+
/** Decode primary attempts and diagnostic-only releases without mixing their evidence. */
|
|
3
|
+
export function decodeWakeEvidence(record, now) {
|
|
4
|
+
if (record.kind !== 'wake')
|
|
5
|
+
return undefined;
|
|
6
|
+
const actIndex = parseActivityId(record.activity);
|
|
7
|
+
if (actIndex === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const value = {
|
|
10
|
+
at: record.at ?? now,
|
|
11
|
+
attention: { squarePath: record.location, recipient: record.participant, actIndex },
|
|
12
|
+
...(record.session === undefined ? {} : { session: record.session }),
|
|
13
|
+
...(record.signature === undefined ? {} : { signature: record.signature }),
|
|
14
|
+
...(record.message === undefined ? {} : { message: record.message }),
|
|
15
|
+
...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }),
|
|
16
|
+
};
|
|
17
|
+
if (record.outcome === 'released') {
|
|
18
|
+
if (record.at === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
return { kind: 'release', value: {
|
|
21
|
+
...value,
|
|
22
|
+
...(record.routeKind === undefined ? {} : { routeKind: record.routeKind }),
|
|
23
|
+
...(record.attemptN === undefined ? {} : { attemptN: record.attemptN }),
|
|
24
|
+
} };
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return (await wakeEvidenceProjection(squarePath, now, env)).evidence(recipient, actIndex);
|
|
26
|
+
if (record.outcome !== 'accepted' && record.outcome !== 'unknown' && record.outcome !== 'failed')
|
|
27
|
+
return undefined;
|
|
28
|
+
if (record.routeKind === undefined || typeof record.attemptN !== 'number')
|
|
29
|
+
return undefined;
|
|
30
|
+
return { kind: 'attempt', value: { ...value, routeKind: record.routeKind, attemptN: record.attemptN, outcome: record.outcome } };
|
|
26
31
|
}
|
package/dist/wake-sink.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
/** One Paseo wake attempt: where it goes, what it says, and what identifies it. */
|
|
1
2
|
export interface PaseoWakeRequest {
|
|
2
3
|
agentId: string;
|
|
3
4
|
prompt: string;
|
|
5
|
+
location: string;
|
|
6
|
+
participant: string;
|
|
7
|
+
activity: string;
|
|
8
|
+
attemptN?: number;
|
|
4
9
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
bin?: string;
|
|
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. */
|
|
16
|
+
export declare function sendPaseoWake(request: PaseoWakeRequest, opts?: {
|
|
13
17
|
timeoutMs?: number;
|
|
14
|
-
}): void
|
|
18
|
+
}): Promise<void>;
|
package/dist/wake-sink.js
CHANGED
|
@@ -1,56 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
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)}`;
|
|
9
12
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
function configuredArguments(name) {
|
|
35
|
-
try {
|
|
36
|
-
const parsed = JSON.parse(process.env[name] ?? '[]');
|
|
37
|
-
return Array.isArray(parsed) && parsed.every((item) => typeof item === 'string') ? parsed : [];
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export function sendPaseoWake({ agentId, prompt }, opts = {}) {
|
|
44
|
-
const result = spawnSync(opts.bin ?? process.env.SQUARE_PASEO_BIN ?? 'paseo', [...(opts.args ?? configuredArguments('SQUARE_PASEO_BIN_ARGS')), 'send', agentId, '--prompt', prompt, '--no-wait', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: opts.timeoutMs ?? 5000, env: process.env });
|
|
45
|
-
if (result.error) {
|
|
46
|
-
const code = result.error.code;
|
|
47
|
-
const kind = code === 'ENOENT' || code === 'ECONNREFUSED' ? 'transient' : 'unknown';
|
|
48
|
-
throw new PaseoWakeSendError(result.error.message, kind);
|
|
49
|
-
}
|
|
50
|
-
if (result.status === 0)
|
|
51
|
-
return;
|
|
52
|
-
const output = `${result.stderr ?? ''}${result.stdout ?? ''}`;
|
|
53
|
-
const failure = commandError(output);
|
|
54
|
-
const message = redactUriPassword(failure?.message || output.trim() || `paseo send exited with ${result.status ?? 'no status'}`);
|
|
55
|
-
throw new PaseoWakeSendError(message, classifyCommandFailure(failure?.code, message));
|
|
13
|
+
/** Steer one existing Paseo agent through the shared delivery entry. Never interrupts a turn. */
|
|
14
|
+
export async function sendPaseoWake(request, opts = {}) {
|
|
15
|
+
const budgetMs = opts.timeoutMs;
|
|
16
|
+
// A usable budget becomes one deadline both phases share; anything else is an
|
|
17
|
+
// argument error, so it is handed to the entry untouched to classify.
|
|
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)
|
|
24
|
+
throw new DeliveryError('unavailable');
|
|
25
|
+
const agent = await connect({
|
|
26
|
+
harness: 'paseo',
|
|
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)
|
|
32
|
+
throw new DeliveryError('unavailable');
|
|
33
|
+
await agent.steer(request.prompt, {
|
|
34
|
+
id: paseoWakeMessageId(request),
|
|
35
|
+
...(steerMs === undefined ? {} : { timeoutMs: steerMs }),
|
|
36
|
+
});
|
|
56
37
|
}
|
package/dist/watch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isSquareError, } from './model.js';
|
|
2
|
-
import { STALE_MS, WATCH_HEARTBEAT_MS, inSquareCount, nowMs, } from './runtime.js';
|
|
2
|
+
import { STALE_MS, WATCH_HEARTBEAT_MS, countSays, inSquareCount, nowMs, } from './runtime.js';
|
|
3
3
|
import { openSquare } from './square-file-adapter.js';
|
|
4
4
|
import { closeOpenSquare } from './open-square.js';
|
|
5
5
|
import { createSquareApplication } from './square-application.js';
|
|
@@ -20,20 +20,14 @@ function watchOutputResult(squarePath, presentation, name, caught, opts = {}) {
|
|
|
20
20
|
const stored = index === undefined ? undefined : presentation.activities.find((item) => item.index === index);
|
|
21
21
|
return stored === undefined ? [] : [{ activity: stored, perception: activity.perception }];
|
|
22
22
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.filter((item) => item.kind === 'say' || item.kind === 'done');
|
|
26
|
-
const roomChanges = delivered
|
|
27
|
-
.map(({ activity }) => activity)
|
|
28
|
-
.filter((item) => item.kind === 'join' || item.kind === 'done' || item.kind === 'hold' || item.kind === 'resume');
|
|
23
|
+
// Catch delivers only directed say activities, so there is no lifecycle list to split off.
|
|
24
|
+
const publicItems = delivered.map(({ activity }) => activity).filter((item) => item.kind === 'say' || item.kind === 'done');
|
|
29
25
|
const perceptions = new Map(delivered.map(({ activity, perception }) => [activity.index, perception]));
|
|
30
26
|
return {
|
|
31
27
|
type: 'output',
|
|
32
|
-
stdout: renderWatchOutput([...presentation.activities], publicItems,
|
|
28
|
+
stdout: renderWatchOutput([...presentation.activities], publicItems, {
|
|
33
29
|
...opts,
|
|
34
|
-
squarePath,
|
|
35
30
|
viewer: name,
|
|
36
|
-
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
37
31
|
perceptions,
|
|
38
32
|
}) + (caught.remaining > 0
|
|
39
33
|
? `\n○ ${caught.remaining} matching ${caught.remaining === 1 ? 'activity remains' : 'activities remain'}\n${catchContinuationCommand(squarePath, name, opts)}`
|
|
@@ -72,6 +66,8 @@ function writeWatchTerminal(squarePath, name, presentation, status, idleMs) {
|
|
|
72
66
|
...(idleMs === undefined ? {} : { idleMs }),
|
|
73
67
|
presence: presentation.presence,
|
|
74
68
|
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
69
|
+
ownActivityCount: countSays(presentation.state.acts, name),
|
|
70
|
+
hardCap: presentation.state.hardCap,
|
|
75
71
|
}), { participantCount: presentation.participantCount }));
|
|
76
72
|
}
|
|
77
73
|
function writeWatchReplaced(squarePath, name, presentation) {
|
|
@@ -142,14 +138,6 @@ async function cmdWatchNow(squarePath, name, opts, caller) {
|
|
|
142
138
|
await closeOpenSquare(square);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
|
-
async function countParticipants(square) {
|
|
146
|
-
try {
|
|
147
|
-
return inSquareCount((await square.artifact.read()).state);
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
141
|
/** `false` is reserved for a quiet --now; idle completion preserves its existing sweep boundary. */
|
|
154
142
|
export async function cmdWatch(squarePath, name, opts, caller = { cwd: process.cwd(), env: { ...process.env } }) {
|
|
155
143
|
let square;
|
|
@@ -163,7 +151,7 @@ export async function cmdWatch(squarePath, name, opts, caller = { cwd: process.c
|
|
|
163
151
|
if (isSquareError(err)) {
|
|
164
152
|
const bodyLines = [err.message];
|
|
165
153
|
// The caller's own name has never joined; only a join admits it.
|
|
166
|
-
if (
|
|
154
|
+
if (err.facts?.reason === 'never_joined')
|
|
167
155
|
bodyLines.push(joinRecoveryCommand(squarePath, name));
|
|
168
156
|
process.stderr.write(formatRefusal(squarePath, bodyLines, participantCount === undefined ? {} : { participantCount }));
|
|
169
157
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
@@ -178,12 +166,16 @@ export async function cmdWatch(squarePath, name, opts, caller = { cwd: process.c
|
|
|
178
166
|
}
|
|
179
167
|
}
|
|
180
168
|
catch (err) {
|
|
181
|
-
|
|
169
|
+
let participantCount;
|
|
170
|
+
try {
|
|
171
|
+
participantCount = inSquareCount((await square.artifact.read()).state);
|
|
172
|
+
}
|
|
173
|
+
catch { /* the header count is best effort */ }
|
|
182
174
|
await closeOpenSquare(square).catch(() => undefined);
|
|
183
175
|
if (isSquareError(err)) {
|
|
184
176
|
const bodyLines = [err.message];
|
|
185
177
|
// A filter target is unknown, not the caller; the roster is the useful next read.
|
|
186
|
-
if (
|
|
178
|
+
if (err.facts?.reason === 'never_joined' || err.facts?.reason === 'not_standing')
|
|
187
179
|
bodyLines.push(participantsRecoveryCommand(squarePath));
|
|
188
180
|
process.stderr.write(formatRefusal(squarePath, bodyLines, participantCount === undefined ? {} : { participantCount }));
|
|
189
181
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
package/extensions/square-pi.js
CHANGED
|
@@ -2,6 +2,7 @@ import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/bound
|
|
|
2
2
|
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
3
3
|
import { sessionInbox, observeSessionPending } from '../dist/inbox.js';
|
|
4
4
|
import { renderAttentionDescription } from '../dist/attention-presentation.js';
|
|
5
|
+
import { createPiReceiver, sendPiMessage } from '../dist/packages/agent-delivery/src/pi.js';
|
|
5
6
|
|
|
6
7
|
class PiDeliveryDroppedError extends Error {
|
|
7
8
|
constructor() {
|
|
@@ -42,6 +43,10 @@ export function summarizePendingForNotify(pending) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export default function squarePiExtension(pi) {
|
|
46
|
+
pi.registerFlag('agent-delivery-socket', { type: 'string', description: 'Explicit private local delivery socket' });
|
|
47
|
+
createPiReceiver(pi, {
|
|
48
|
+
get endpoint() { return pi.getFlag('agent-delivery-socket'); },
|
|
49
|
+
});
|
|
45
50
|
let sessionId;
|
|
46
51
|
let sessionCwd;
|
|
47
52
|
let ui;
|
|
@@ -115,12 +120,12 @@ export default function squarePiExtension(pi) {
|
|
|
115
120
|
// Pi owns PI_SESSION_ID and several SDK sessions may coexist in one process.
|
|
116
121
|
const sessionEnv = (id) => ({ ...process.env, ...(id === undefined ? {} : { PI_SESSION_ID: id }) });
|
|
117
122
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
123
|
+
// Aborting releases native landing waits; retain cleanup until the observer
|
|
124
|
+
// has closed, including when cancellation precedes a lifecycle hook.
|
|
120
125
|
const stopWatcher = () => {
|
|
121
126
|
watcherAbort?.abort();
|
|
122
|
-
watcher = undefined;
|
|
123
127
|
watcherAbort = undefined;
|
|
128
|
+
return watcher;
|
|
124
129
|
};
|
|
125
130
|
|
|
126
131
|
const pause = (signal, delayMs) => new Promise((resolve) => {
|
|
@@ -201,10 +206,10 @@ export default function squarePiExtension(pi) {
|
|
|
201
206
|
const messageContent = framePiMessage(content);
|
|
202
207
|
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
203
208
|
try {
|
|
204
|
-
|
|
209
|
+
sendPiMessage(pi,
|
|
205
210
|
{ customType: 'square', content: messageContent, display: false },
|
|
206
211
|
{ deliverAs: 'steer', triggerTurn: true },
|
|
207
|
-
)
|
|
212
|
+
);
|
|
208
213
|
} catch (error) {
|
|
209
214
|
landing.ack.settle(error);
|
|
210
215
|
}
|
|
@@ -224,6 +229,7 @@ export default function squarePiExtension(pi) {
|
|
|
224
229
|
env,
|
|
225
230
|
signal,
|
|
226
231
|
);
|
|
232
|
+
if (signal.aborted || token !== generation) return;
|
|
227
233
|
for (const key of keys) handledPending.add(key);
|
|
228
234
|
} catch (error) {
|
|
229
235
|
if (signal.aborted || token !== generation) return;
|
|
@@ -247,7 +253,7 @@ export default function squarePiExtension(pi) {
|
|
|
247
253
|
cancelledRun = false;
|
|
248
254
|
generation += 1;
|
|
249
255
|
failAcks(new Error('Pi session replaced'));
|
|
250
|
-
stopWatcher();
|
|
256
|
+
const stopped = stopWatcher();
|
|
251
257
|
handledPending.clear();
|
|
252
258
|
observedPending.clear();
|
|
253
259
|
retryAfterChange = false;
|
|
@@ -259,29 +265,31 @@ export default function squarePiExtension(pi) {
|
|
|
259
265
|
sessionId = ctx.sessionManager.getSessionId();
|
|
260
266
|
sessionCwd = ctx.cwd || process.cwd();
|
|
261
267
|
const token = generation;
|
|
268
|
+
await stopped;
|
|
269
|
+
if (token !== generation) return;
|
|
262
270
|
watcherAbort = new AbortController();
|
|
263
271
|
const signal = watcherAbort.signal;
|
|
264
|
-
|
|
272
|
+
const startup = automaticSessionStart('pi', sessionId, sessionCwd, sessionEnv(sessionId)).then((context) => {
|
|
265
273
|
if (context === undefined || sessionId === undefined || token !== generation) return;
|
|
266
274
|
const messageContent = framePiMessage(context);
|
|
267
275
|
const landing = waitForLanding(messageContent, signal, 'nextTurn');
|
|
268
276
|
try {
|
|
269
|
-
|
|
277
|
+
sendPiMessage(pi,
|
|
270
278
|
{ customType: 'square', content: messageContent, display: true },
|
|
271
279
|
{ deliverAs: 'nextTurn' },
|
|
272
|
-
)
|
|
280
|
+
);
|
|
273
281
|
} catch {
|
|
274
282
|
// Joining context is advisory; an unavailable Pi transport does not block startup.
|
|
275
283
|
landing.ack.settle(new Error('Pi joining-context injection failed'));
|
|
276
284
|
}
|
|
277
285
|
void landing.promise.catch(() => undefined);
|
|
278
286
|
}).catch(() => undefined);
|
|
279
|
-
watcher = wake(token, signal).
|
|
287
|
+
watcher = Promise.allSettled([startup, wake(token, signal)]).then(() => undefined);
|
|
280
288
|
});
|
|
281
289
|
|
|
282
290
|
pi.on('agent_start', async (_event, ctx) => {
|
|
283
291
|
detachRunAbort?.();
|
|
284
|
-
currentRunSignal = ctx?.
|
|
292
|
+
currentRunSignal = ctx?.signal;
|
|
285
293
|
if (ctx?.mode !== 'tui' || currentRunSignal === undefined) return;
|
|
286
294
|
const runSignal = currentRunSignal;
|
|
287
295
|
detachRunAbort = () => runSignal.removeEventListener('abort', cancelRunDelivery);
|
|
@@ -329,6 +337,8 @@ export default function squarePiExtension(pi) {
|
|
|
329
337
|
const token = generation;
|
|
330
338
|
// Cancel the entire pending batch, including entries not yet sent to Pi.
|
|
331
339
|
// This is local suppression, never evidence that the model saw the activity.
|
|
340
|
+
await stopWatcher();
|
|
341
|
+
if (token !== generation || sessionId === undefined) return;
|
|
332
342
|
const cancelled = await sessionInbox(sessionId, sessionEnv(sessionId));
|
|
333
343
|
if (token !== generation || sessionId === undefined) return;
|
|
334
344
|
for (const key of inboxKeys(cancelled)) handledPending.add(key);
|
|
@@ -343,13 +353,17 @@ export default function squarePiExtension(pi) {
|
|
|
343
353
|
cancelledRun = false;
|
|
344
354
|
generation += 1;
|
|
345
355
|
failAcks(new Error('Pi session ended'));
|
|
346
|
-
stopWatcher();
|
|
356
|
+
const stopped = stopWatcher();
|
|
347
357
|
ui = undefined;
|
|
348
|
-
|
|
358
|
+
const endedId = sessionId;
|
|
359
|
+
const endedCwd = sessionCwd;
|
|
360
|
+
const endedEnv = sessionEnv(endedId);
|
|
349
361
|
sessionId = undefined;
|
|
350
362
|
sessionCwd = undefined;
|
|
351
363
|
retryWait = undefined;
|
|
352
364
|
activeTurn = undefined;
|
|
353
365
|
currentRunSignal = undefined;
|
|
366
|
+
await stopped;
|
|
367
|
+
if (endedId && endedCwd) await automaticSessionEnd('pi', endedId, endedCwd, endedEnv).catch(() => undefined);
|
|
354
368
|
});
|
|
355
369
|
}
|
package/guides/change-waits.md
CHANGED
|
@@ -4,7 +4,7 @@ Square's artifact and host-ledger adapters own change detection. Notifications a
|
|
|
4
4
|
|
|
5
5
|
- A process shares a revision reader per canonical artifact and a filesystem watcher per containing directory. Subscribers have independent baselines, deadlines, and cancellation. The final release closes the watcher, reconciliation timer, and any outstanding read.
|
|
6
6
|
- Directory notifications invalidate lightweight reads. A two-second reconciliation checks revisions/ledger metadata even when notifications are missing. Missing directories, unsupported watches, and watcher failures retain that fallback; directory replacement is detected during reconciliation. Normal notification latency is not a hard guarantee: a missed hint may wait for reconciliation, plus scheduling and lock contention.
|
|
7
|
-
- The artifact revision remains authoritative. No new sidecar, broker, or persistent cache is introduced. Only changed projections load historical state. Ledger metadata invalidates bindings and presentation projections; it is never treated as evidence of delivery.
|
|
7
|
+
- The artifact revision remains authoritative. No new sidecar, broker, or persistent cache is introduced. Only changed projections load historical state. A missing artifact is a stable observation, not a repeated change: reconciliation remains quiet while it stays absent and detects its return without deleting its session bindings. Strict artifact reads still report absence as an error. Ledger metadata invalidates bindings and presentation projections; it is never treated as evidence of delivery.
|
|
8
8
|
- Each Pi session owns a long-lived pending observer, including when it has no bindings yet. Presence and evidence changes update its subscriptions. There is no repeated thirty-second restart and no per-second binding scan. A failed injection waits for a substantive change; its own failed evidence write does not trigger a retry loop.
|
|
9
9
|
- Blocking CLI catch waits for a revision or its next heartbeat/quiet deadline. A held catch keeps its lease alive and pauses quiet expiry. Core catch, NDJSON stream, and delivery-receipt waits use the same change detector. Held express also waits for changes; throttling still waits for its eligibility time.
|
|
10
10
|
- Catch lease expiry, ledger retention/claim expiry, and cancellation are independent wake conditions. No filesystem write is required for a time-based eligibility change.
|