@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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SquareError } from './neutral.js';
|
|
2
|
+
export function checkControl(control) {
|
|
3
|
+
if (control?.signal?.aborted)
|
|
4
|
+
throw control.signal.reason ?? new Error('Square operation was cancelled');
|
|
5
|
+
}
|
|
6
|
+
export function squareClock(env, clock) {
|
|
7
|
+
if (clock !== undefined)
|
|
8
|
+
return clock;
|
|
9
|
+
if (env.SQUARE_NOW_MS === undefined)
|
|
10
|
+
return Date.now;
|
|
11
|
+
const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
|
|
12
|
+
if (!Number.isFinite(value))
|
|
13
|
+
throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
|
|
14
|
+
return () => value;
|
|
15
|
+
}
|
|
16
|
+
export function failure(error, detail) {
|
|
17
|
+
const normalized = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
18
|
+
Object.assign(normalized, { operation: detail });
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
@@ -30,9 +30,8 @@ export interface FailureReport {
|
|
|
30
30
|
readonly code: string;
|
|
31
31
|
readonly message: string;
|
|
32
32
|
readonly facts?: {
|
|
33
|
-
readonly reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | '
|
|
33
|
+
readonly reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'invalid_reply' | 'invalid_done_body' | 'needs_seat';
|
|
34
34
|
readonly activityId?: string;
|
|
35
|
-
readonly ledgerRoot?: string;
|
|
36
35
|
readonly activityExists?: boolean;
|
|
37
36
|
};
|
|
38
37
|
readonly operation?: OperationFailureDetail;
|
|
@@ -335,13 +335,7 @@ export function renderFailureLines(failure, options = {}) {
|
|
|
335
335
|
const reason = facts?.reason;
|
|
336
336
|
const marked = reason === 'never_joined' || reason === 'not_standing';
|
|
337
337
|
const lines = marked ? failure.message.split('\n').map((line) => `✕ ${line}`) : [failure.message];
|
|
338
|
-
if (reason === '
|
|
339
|
-
if (facts?.ledgerRoot !== undefined)
|
|
340
|
-
lines.push('square ledger migrate', 'square ledger verify');
|
|
341
|
-
else if (squarePath !== undefined)
|
|
342
|
-
lines.push(`${commandPrefix(squarePath)} migrate`);
|
|
343
|
-
}
|
|
344
|
-
else if (squarePath !== undefined && name !== undefined && reason === 'needs_seat') {
|
|
338
|
+
if (reason === 'needs_seat' && squarePath !== undefined && name !== undefined) {
|
|
345
339
|
lines.splice(0, 1, `✕ ${failure.message}`);
|
|
346
340
|
lines.push(joinRecoveryCommand(squarePath, name, owner));
|
|
347
341
|
}
|
package/dist/owner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Owner } from './
|
|
2
|
-
export type { Owner } from './
|
|
1
|
+
import { type Owner } from './neutral.js';
|
|
2
|
+
export type { Owner } from './neutral.js';
|
|
3
3
|
export interface HarnessCoordinate {
|
|
4
4
|
readonly channel: string;
|
|
5
5
|
readonly session: string;
|
package/dist/owner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
-
import { validateOwner } from './
|
|
2
|
+
import { validateOwner } from './neutral.js';
|
|
3
3
|
export function harnessOwner(coordinate) {
|
|
4
4
|
const channel = nonempty(coordinate.channel, 'channel');
|
|
5
5
|
const session = nonempty(coordinate.session, 'session');
|
|
@@ -11,6 +11,8 @@ export interface ClaudeNativeTarget {
|
|
|
11
11
|
export interface ClaudeNativeControl {
|
|
12
12
|
readonly deadline: number;
|
|
13
13
|
readonly signal?: AbortSignal;
|
|
14
|
+
/** Final asynchronous eligibility check after connecting, before writing bytes. */
|
|
15
|
+
readonly beforeSend?: () => Promise<boolean>;
|
|
14
16
|
}
|
|
15
17
|
export type NativeWriteResult = {
|
|
16
18
|
readonly outcome: 'written';
|
|
@@ -43,6 +43,11 @@ export async function writeClaudeNative(target, text, control) {
|
|
|
43
43
|
throw new LocalEndpointError('timeout');
|
|
44
44
|
socket = await connectLocal(endpoint, { signal: scope.signal });
|
|
45
45
|
scope.check();
|
|
46
|
+
if (Date.now() >= control.deadline)
|
|
47
|
+
throw new LocalEndpointError('timeout');
|
|
48
|
+
if (control.beforeSend && !await scope.wait(control.beforeSend()))
|
|
49
|
+
return { outcome: 'unavailable', code: 'aborted', message: 'Native inbox write suppressed.' };
|
|
50
|
+
scope.check();
|
|
46
51
|
if (Date.now() >= control.deadline)
|
|
47
52
|
throw new LocalEndpointError('timeout');
|
|
48
53
|
// A receiver that never drains must not hold the caller past its deadline.
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
export interface CodexStopEpoch {
|
|
2
|
+
readonly token: string;
|
|
3
|
+
readonly turnId: string;
|
|
4
|
+
}
|
|
1
5
|
export interface CodexBoundary {
|
|
2
|
-
readonly
|
|
3
|
-
readonly lastNonStop: number;
|
|
6
|
+
readonly stop?: CodexStopEpoch;
|
|
4
7
|
readonly ended?: true;
|
|
5
8
|
}
|
|
6
9
|
export interface CodexBoundaryStore {
|
|
7
10
|
read(sessionId: string): Promise<CodexBoundary | undefined>;
|
|
8
|
-
queueEligible(sessionId: string): Promise<boolean>;
|
|
9
|
-
record(sessionId: string, event: 'Stop' | 'non-stop' | 'start' | 'end',
|
|
10
|
-
|
|
11
|
+
queueEligible(sessionId: string, epoch?: CodexStopEpoch): Promise<boolean>;
|
|
12
|
+
record(sessionId: string, event: 'Stop' | 'non-stop' | 'start' | 'end', options?: {
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
readonly turnId?: string;
|
|
15
|
+
}): Promise<void>;
|
|
11
16
|
}
|
|
12
|
-
/**
|
|
17
|
+
/** Receiver-owned idle authority. Old or unverifiable records never arm a send. */
|
|
13
18
|
export declare function createCodexBoundaryStore(file: string): CodexBoundaryStore;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { withFileLock } from './file-lock.js';
|
|
4
|
-
const empty = () => ({ v:
|
|
5
|
-
|
|
4
|
+
const empty = () => ({ v: 2, threads: Object.create(null) });
|
|
5
|
+
const identity = (value) => typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
|
|
6
|
+
/** Receiver-owned idle authority. Old or unverifiable records never arm a send. */
|
|
6
7
|
export function createCodexBoundaryStore(file) {
|
|
7
8
|
const load = async () => {
|
|
8
9
|
let raw;
|
|
@@ -16,16 +17,14 @@ export function createCodexBoundaryStore(file) {
|
|
|
16
17
|
}
|
|
17
18
|
try {
|
|
18
19
|
const value = JSON.parse(raw);
|
|
19
|
-
if (value?.v !==
|
|
20
|
-
!value.threads || typeof value.threads !== 'object')
|
|
20
|
+
if (value?.v !== 2 || !value.threads || typeof value.threads !== 'object' || Array.isArray(value.threads))
|
|
21
21
|
return empty();
|
|
22
22
|
const result = empty();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
result.threads[id] = item;
|
|
23
|
+
for (const [id, item] of Object.entries(value.threads)) {
|
|
24
|
+
if (!item || typeof item !== 'object')
|
|
25
|
+
continue;
|
|
26
|
+
result.threads[id] = item.ended === true ? { ended: true }
|
|
27
|
+
: identity(item.stop?.token) && identity(item.stop?.turnId) ? { stop: { token: item.stop.token, turnId: item.stop.turnId } } : {};
|
|
29
28
|
}
|
|
30
29
|
return result;
|
|
31
30
|
}
|
|
@@ -33,43 +32,35 @@ export function createCodexBoundaryStore(file) {
|
|
|
33
32
|
return empty();
|
|
34
33
|
}
|
|
35
34
|
};
|
|
36
|
-
const update = (change, signal) => withFileLock(`${file}.lock`, { retryMs: 10, signal }, async () => {
|
|
37
|
-
const state = await load();
|
|
38
|
-
change(state);
|
|
39
|
-
signal?.throwIfAborted();
|
|
40
|
-
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
41
|
-
try {
|
|
42
|
-
await fs.writeFile(temporary, JSON.stringify(state), { mode: 0o600 });
|
|
43
|
-
await fs.rename(temporary, file);
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
await fs.unlink(temporary).catch(() => { });
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
35
|
const read = async (id) => id ? (await load()).threads[id] : undefined;
|
|
50
36
|
return {
|
|
51
37
|
read,
|
|
52
|
-
async queueEligible(id) {
|
|
38
|
+
async queueEligible(id, epoch) {
|
|
53
39
|
const boundary = await read(id);
|
|
54
|
-
return boundary !==
|
|
40
|
+
return boundary?.ended !== true && boundary?.stop !== undefined &&
|
|
41
|
+
(epoch === undefined || boundary.stop.token === epoch.token && boundary.stop.turnId === epoch.turnId);
|
|
55
42
|
},
|
|
56
|
-
async record(id, event,
|
|
57
|
-
if (!id)
|
|
43
|
+
async record(id, event, options = {}) {
|
|
44
|
+
if (!identity(id))
|
|
58
45
|
return;
|
|
59
|
-
await
|
|
60
|
-
const
|
|
61
|
-
// A late
|
|
62
|
-
if (
|
|
46
|
+
await withFileLock(`${file}.lock`, { retryMs: 10, signal: options.signal }, async () => {
|
|
47
|
+
const state = await load();
|
|
48
|
+
// A late callback cannot reopen an ended native session.
|
|
49
|
+
if (state.threads[id]?.ended && event !== 'start')
|
|
63
50
|
return;
|
|
64
|
-
state.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
state.threads[id] = event === 'end' ? { ended: true }
|
|
52
|
+
: event === 'Stop' && identity(options.turnId) ? { stop: { token: randomUUID(), turnId: options.turnId } } : {};
|
|
53
|
+
options.signal?.throwIfAborted();
|
|
54
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
55
|
+
try {
|
|
56
|
+
await fs.writeFile(temporary, JSON.stringify(state), { mode: 0o600 });
|
|
57
|
+
options.signal?.throwIfAborted();
|
|
58
|
+
await fs.rename(temporary, file);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await fs.unlink(temporary).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
71
64
|
},
|
|
72
|
-
async clear(id, signal) { if (id)
|
|
73
|
-
await update((state) => { delete state.threads[id]; }, signal); },
|
|
74
65
|
};
|
|
75
66
|
}
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import type { CodexBoundaryStore } from './codex-boundary.js';
|
|
2
2
|
export { createCodexBoundaryStore } from './codex-boundary.js';
|
|
3
|
-
export type { CodexBoundary, CodexBoundaryStore } from './codex-boundary.js';
|
|
3
|
+
export type { CodexBoundary, CodexBoundaryStore, CodexStopEpoch } from './codex-boundary.js';
|
|
4
|
+
export { codexIdleAtStop } from './codex.js';
|
|
5
|
+
export type CodexActiveEvent = 'UserPromptSubmit' | 'PreToolUse' | 'PostToolUse';
|
|
6
|
+
export type CodexHookEvent = CodexActiveEvent | 'SessionStart' | 'SessionEnd' | 'Stop' | 'Interrupt';
|
|
7
|
+
export interface CodexHookInput {
|
|
8
|
+
readonly session_id: string;
|
|
9
|
+
readonly hook_event_name: CodexHookEvent;
|
|
10
|
+
readonly turn_id?: string;
|
|
11
|
+
}
|
|
4
12
|
export interface CodexHookResponse {
|
|
5
13
|
readonly hookSpecificOutput: {
|
|
6
|
-
readonly hookEventName:
|
|
14
|
+
readonly hookEventName: CodexActiveEvent;
|
|
7
15
|
readonly additionalContext: string;
|
|
8
16
|
};
|
|
9
17
|
}
|
|
10
18
|
export interface CodexHookOptions {
|
|
11
19
|
readonly boundaries: CodexBoundaryStore;
|
|
12
|
-
/** The host owns
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
* even if signal aborts meanwhile. This callback is awaited, never raced or abandoned. */
|
|
16
|
-
readonly pull?: (sessionId: string, signal: AbortSignal) => Promise<string | undefined>;
|
|
17
|
-
/** Dispatch the host's pending work; Stop merely makes queue eligible. */
|
|
18
|
-
readonly sweep?: (cwd: string | undefined, deadline: number, signal: AbortSignal) => Promise<void>;
|
|
20
|
+
/** The host owns claims and cancellation. After an external effect, finish its
|
|
21
|
+
* cancellation-independent settlement and retain the output. Never abandon it. */
|
|
22
|
+
readonly pull?: (sessionId: string, signal: AbortSignal, event: CodexActiveEvent) => Promise<string | undefined>;
|
|
19
23
|
readonly end?: (sessionId: string, signal: AbortSignal) => Promise<void>;
|
|
24
|
+
readonly interrupt?: (sessionId: string, signal: AbortSignal) => Promise<void>;
|
|
20
25
|
readonly trace?: (stage: string, detail?: string) => void;
|
|
21
26
|
readonly timeoutMs?: number;
|
|
22
|
-
readonly sweepTimeoutMs?: number;
|
|
23
27
|
readonly signal?: AbortSignal;
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
export declare function parseCodexHook(input: unknown): CodexHookInput | undefined;
|
|
30
|
+
/** Native synchronous boundaries; Stop only records authority for its async receiver. */
|
|
31
|
+
export declare function handleCodexHook(value: unknown, options: CodexHookOptions): Promise<CodexHookResponse | undefined>;
|
|
@@ -1,57 +1,44 @@
|
|
|
1
1
|
import { deadline, validSignal, validTimeout } from './deadline.js';
|
|
2
2
|
import { isRecord } from './guards.js';
|
|
3
3
|
export { createCodexBoundaryStore } from './codex-boundary.js';
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
4
|
+
export { codexIdleAtStop } from './codex.js';
|
|
5
|
+
export function parseCodexHook(input) {
|
|
6
|
+
const identity = (value) => typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
|
|
7
|
+
if (!isRecord(input) || !identity(input.session_id) ||
|
|
8
|
+
!['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'SessionEnd', 'Stop', 'Interrupt'].includes(input.hook_event_name) ||
|
|
9
|
+
(['Stop', 'Interrupt'].includes(input.hook_event_name) && !identity(input.turn_id)))
|
|
7
10
|
return;
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
return input;
|
|
12
|
+
}
|
|
13
|
+
/** Native synchronous boundaries; Stop only records authority for its async receiver. */
|
|
14
|
+
export async function handleCodexHook(value, options) {
|
|
15
|
+
const input = parseCodexHook(value);
|
|
16
|
+
if (!input)
|
|
10
17
|
return;
|
|
11
|
-
if (!validTimeout(options.timeoutMs) || !
|
|
18
|
+
if (!validTimeout(options.timeoutMs) || !validSignal(options.signal))
|
|
12
19
|
throw new TypeError('Invalid hook control.');
|
|
13
|
-
}
|
|
14
20
|
const scope = deadline(options.timeoutMs ?? 4500, options.signal);
|
|
15
|
-
const
|
|
16
|
-
const id = input.session_id;
|
|
21
|
+
const event = input.hook_event_name, id = input.session_id;
|
|
17
22
|
try {
|
|
18
23
|
scope.check();
|
|
19
|
-
|
|
20
|
-
await scope.wait(options.boundaries.record(id, 'end', scope.signal));
|
|
21
|
-
await scope.wait(options.end?.(id, scope.signal) ?? Promise.resolve()).catch(() => { });
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
await scope.wait(options.boundaries.record(id, event === 'SessionStart' || event === 'SessionResume' ? 'start' : event === 'Stop' ? 'Stop' : 'non-stop', scope.signal));
|
|
24
|
+
await scope.wait(options.boundaries.record(id, event === 'SessionStart' ? 'start' : event === 'SessionEnd' ? 'end' : event === 'Stop' ? 'Stop' : 'non-stop', { signal: scope.signal, turnId: input.turn_id }));
|
|
25
25
|
options.trace?.('boundary-record');
|
|
26
|
-
if (event
|
|
26
|
+
if (event === 'SessionEnd' || event === 'Interrupt') {
|
|
27
|
+
await (event === 'SessionEnd' ? options.end : options.interrupt)?.(id, scope.signal);
|
|
27
28
|
return;
|
|
28
|
-
let context;
|
|
29
|
-
if (event === 'PostToolUse') {
|
|
30
|
-
try {
|
|
31
|
-
// A host's successful presentation settlement may outlive this signal. Retain
|
|
32
|
-
// its output instead of leaving background work to consume it without a response.
|
|
33
|
-
context = await options.pull?.(id, scope.signal);
|
|
34
|
-
options.trace?.('presentation');
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
options.trace?.('presentation', `error=${error instanceof Error ? error.name : 'Error'}`);
|
|
38
|
-
}
|
|
39
29
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
30
|
+
if (event !== 'UserPromptSubmit' && event !== 'PreToolUse' && event !== 'PostToolUse')
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const context = await options.pull?.(id, scope.signal, event);
|
|
34
|
+
options.trace?.('presentation');
|
|
35
|
+
if (context !== undefined)
|
|
36
|
+
return { hookSpecificOutput: { hookEventName: event, additionalContext: context } };
|
|
42
37
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// A later, unrelated sweep timeout must not discard its completed output.
|
|
46
|
-
if (context !== undefined) {
|
|
47
|
-
return { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } };
|
|
38
|
+
catch (error) {
|
|
39
|
+
options.trace?.('presentation', `error=${error instanceof Error ? error.name : 'Error'}`);
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
if (scope.signal.aborted)
|
|
52
|
-
throw scope.signal.reason;
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
42
|
finally {
|
|
56
43
|
scope.close();
|
|
57
44
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { CodexAppServerConnectOptions, CodexDeliveryResult, CodexSendTextOptions, CodexTarget } from './types.js';
|
|
2
|
+
/** Newest-last live history is verified for Codex 0.166.0 only. A sampled read
|
|
3
|
+
* cannot make a later external queue atomic with native turn starts. */
|
|
4
|
+
export declare function codexIdleAtStop(options: CodexAppServerConnectOptions, turnId: string): Promise<boolean>;
|
|
2
5
|
/** Address an existing thread on an already running app-server. Never starts a
|
|
3
6
|
* service, resumes a thread, or claims ownership of a standalone CLI session. */
|
|
4
7
|
export declare function connectCodex(options: CodexAppServerConnectOptions): Promise<CodexTarget>;
|
|
@@ -143,6 +143,7 @@ class Peer {
|
|
|
143
143
|
throw new Fault('invalid_response');
|
|
144
144
|
scope.check();
|
|
145
145
|
this.socket.send(JSON.stringify({ method: 'initialized' }));
|
|
146
|
+
return result.userAgent;
|
|
146
147
|
}
|
|
147
148
|
call(method, params, scope, dispatch) {
|
|
148
149
|
scope.check();
|
|
@@ -239,9 +240,7 @@ async function discoverTurn(peer, metadata, sessionId, scope) {
|
|
|
239
240
|
cursors.add(cursor);
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
|
-
|
|
243
|
-
* service, resumes a thread, or claims ownership of a standalone CLI session. */
|
|
244
|
-
export async function connectCodex(options) {
|
|
243
|
+
function validatedConnection(options) {
|
|
245
244
|
if (!isRecord(options) || options.harness !== 'codex' ||
|
|
246
245
|
(options.transport !== undefined && options.transport !== 'app-server') || !isIdentity(options.sessionId) ||
|
|
247
246
|
!validTimeout(options.timeoutMs) || !validSignal(options.signal) ||
|
|
@@ -250,7 +249,45 @@ export async function connectCodex(options) {
|
|
|
250
249
|
(options.token !== undefined && (!isIdentity(options.token) || /[\r\n]/.test(options.token)))) {
|
|
251
250
|
throw new ConnectionError('invalid_arguments');
|
|
252
251
|
}
|
|
253
|
-
|
|
252
|
+
return resolveConnection(options);
|
|
253
|
+
}
|
|
254
|
+
/** Newest-last live history is verified for Codex 0.166.0 only. A sampled read
|
|
255
|
+
* cannot make a later external queue atomic with native turn starts. */
|
|
256
|
+
export async function codexIdleAtStop(options, turnId) {
|
|
257
|
+
const connection = validatedConnection(options);
|
|
258
|
+
if (!isIdentity(turnId))
|
|
259
|
+
return false;
|
|
260
|
+
const scope = deadline(options.timeoutMs, options.signal);
|
|
261
|
+
let peer;
|
|
262
|
+
try {
|
|
263
|
+
peer = await open(connection, scope);
|
|
264
|
+
const version = await peer.initialize(scope);
|
|
265
|
+
// The app-server's originator prefix belongs to the hosting client, which
|
|
266
|
+
// may have initialized before this connection. Fence the native version.
|
|
267
|
+
if (!/^[A-Za-z0-9._-]+\/0\.166\.0(?:\s|$)/.test(version))
|
|
268
|
+
return false;
|
|
269
|
+
const thread = await readThread(peer, options.sessionId, scope, true);
|
|
270
|
+
if (!isRecord(thread.status) || thread.status.type !== 'idle' || !Array.isArray(thread.turns) || !thread.turns.length)
|
|
271
|
+
return false;
|
|
272
|
+
// Validate the whole live list, including absence of an active turn; never
|
|
273
|
+
// fall back to persisted pages or a boolean boundary for missing history.
|
|
274
|
+
if (activeTurn(thread.turns) !== undefined || new Set(thread.turns.map(turn => turn.id)).size !== thread.turns.length)
|
|
275
|
+
return false;
|
|
276
|
+
const newest = thread.turns.at(-1);
|
|
277
|
+
return newest.id === turnId && newest.status === 'completed';
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
scope.close();
|
|
284
|
+
await peer?.close();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** Address an existing thread on an already running app-server. Never starts a
|
|
288
|
+
* service, resumes a thread, or claims ownership of a standalone CLI session. */
|
|
289
|
+
export async function connectCodex(options) {
|
|
290
|
+
const connection = validatedConnection(options);
|
|
254
291
|
const scope = deadline(options.timeoutMs, options.signal);
|
|
255
292
|
let peer;
|
|
256
293
|
try {
|
|
@@ -30,7 +30,9 @@ export type PiAgent = Steerable & Queueable & {
|
|
|
30
30
|
readonly harness: 'pi';
|
|
31
31
|
readonly sessionId: string;
|
|
32
32
|
};
|
|
33
|
-
export type PaseoAgent = Steerable<KeyedSendOptions
|
|
33
|
+
export type PaseoAgent = Steerable<KeyedSendOptions & {
|
|
34
|
+
readonly beforeSend?: () => Promise<boolean>;
|
|
35
|
+
}> & {
|
|
34
36
|
readonly harness: 'paseo';
|
|
35
37
|
readonly agentId: string;
|
|
36
38
|
};
|
|
@@ -110,7 +110,7 @@ export async function connect(options) {
|
|
|
110
110
|
throw new DeliveryError('invalid_arguments');
|
|
111
111
|
try {
|
|
112
112
|
const result = await opened.send(text, spec.keyed
|
|
113
|
-
? { delivery, inputId: id, timeoutMs: options.timeoutMs, signal: options.signal }
|
|
113
|
+
? { delivery, inputId: id, timeoutMs: options.timeoutMs, signal: options.signal, ...(spec.harness === 'paseo' ? { beforeSend: options.beforeSend } : {}) }
|
|
114
114
|
: { delivery, timeoutMs: options.timeoutMs, signal: options.signal });
|
|
115
115
|
if (result.state !== spec.accepted)
|
|
116
116
|
throw failure(result, 'send', id);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { deadline, validSignal, validTimeout } from './deadline.js';
|
|
2
|
+
import { deadline, DEFAULT_TIMEOUT_MS, validSignal, validTimeout } from './deadline.js';
|
|
3
3
|
import { ConnectionError } from './errors.js';
|
|
4
4
|
import { isIdentity, isRecord } from './guards.js';
|
|
5
5
|
import { daemonCredential, openPaseoClient, paseoHome, resolvePaseoDaemon, } from './paseo-native.js';
|
|
@@ -59,26 +59,35 @@ export async function sendPaseoText(target, text, options = {}) {
|
|
|
59
59
|
if (connection === undefined || typeof text !== 'string' || text.length === 0 ||
|
|
60
60
|
!validTimeout(options.timeoutMs) || !validSignal(options.signal) ||
|
|
61
61
|
(options.inputId !== undefined && !isIdentity(options.inputId)) ||
|
|
62
|
+
(options.beforeSend !== undefined && typeof options.beforeSend !== 'function') ||
|
|
62
63
|
(options.delivery !== undefined && options.delivery !== 'steer')) {
|
|
63
64
|
throw new TypeError('Invalid sendText arguments.');
|
|
64
65
|
}
|
|
65
66
|
const inputId = options.inputId ?? randomUUID();
|
|
66
67
|
const attempt = { harness: 'paseo', agentId: target.agentId, inputId };
|
|
67
68
|
const scope = deadline(options.timeoutMs, options.signal);
|
|
69
|
+
const until = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
68
70
|
const client = await openPaseoClient(connection.target, connection.credential, options.timeoutMs);
|
|
69
71
|
// The connection is the uncertainty boundary: before it, nothing was written.
|
|
70
72
|
let connected = false;
|
|
73
|
+
let sent = false;
|
|
71
74
|
try {
|
|
72
75
|
scope.check();
|
|
73
76
|
await scope.wait(client.connect());
|
|
74
77
|
connected = true;
|
|
75
78
|
scope.check();
|
|
79
|
+
if (options.beforeSend && !await scope.wait(options.beforeSend()))
|
|
80
|
+
return { ...attempt, state: 'unavailable', code: 'aborted' };
|
|
81
|
+
scope.check();
|
|
82
|
+
if (Date.now() >= until)
|
|
83
|
+
return { ...attempt, state: 'unavailable', code: 'timeout' };
|
|
84
|
+
sent = true;
|
|
76
85
|
await scope.wait(client.sendAgentMessage(target.agentId, text, { messageId: inputId, activeTurnBehavior: 'steer' }));
|
|
77
86
|
return { ...attempt, state: 'admitted' };
|
|
78
87
|
}
|
|
79
88
|
catch (error) {
|
|
80
89
|
if (scope.code !== undefined) {
|
|
81
|
-
return { ...attempt, state:
|
|
90
|
+
return { ...attempt, state: sent ? 'unknown' : 'unavailable', code: scope.code };
|
|
82
91
|
}
|
|
83
92
|
if (error instanceof Error && error.name === 'DaemonAuthenticationError') {
|
|
84
93
|
return { ...attempt, state: 'unavailable', code: 'authentication_failed' };
|
|
@@ -50,6 +50,7 @@ export interface PaseoSendTextOptions {
|
|
|
50
50
|
readonly inputId?: string;
|
|
51
51
|
readonly timeoutMs?: number;
|
|
52
52
|
readonly signal?: AbortSignal;
|
|
53
|
+
readonly beforeSend?: () => Promise<boolean>;
|
|
53
54
|
}
|
|
54
55
|
export interface CodexSendTextOptions {
|
|
55
56
|
readonly delivery?: Delivery;
|
package/dist/paseo-delivery.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DeliveryCard, DeliveryTransport, DeliveryTransportContext, TransportOutcome } from './delivery-ledger.js';
|
|
2
2
|
import { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
|
|
3
3
|
import { sendPaseoWake } from './wake-sink.js';
|
|
4
4
|
export interface PaseoWakeTransportOptions {
|
|
5
|
-
discover?:
|
|
5
|
+
discover?: typeof discoverPaseoAgents;
|
|
6
6
|
waitForBoundary?: typeof waitForPaseoWakeBoundary;
|
|
7
7
|
sendWake?: typeof sendPaseoWake;
|
|
8
8
|
}
|
|
9
|
-
export declare class PaseoWakeTransport implements
|
|
9
|
+
export declare class PaseoWakeTransport implements DeliveryTransport {
|
|
10
10
|
private readonly opts;
|
|
11
|
+
readonly kind = "paseo";
|
|
11
12
|
private discovering?;
|
|
12
13
|
constructor(opts?: PaseoWakeTransportOptions);
|
|
13
|
-
/** Coalesce overlapping inventory reads only; a later send must rediscover current status. */
|
|
14
14
|
private discover;
|
|
15
|
-
|
|
15
|
+
send(card: DeliveryCard, address: Readonly<Record<string, string>>, context: DeliveryTransportContext): Promise<TransportOutcome>;
|
|
16
16
|
}
|