@astrosheep/square 0.4.4 → 0.4.5
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-store.d.ts +1 -1
- package/dist/artifact-store.js +32 -15
- package/dist/artifact.d.ts +1 -4
- package/dist/artifact.js +0 -28
- package/dist/attention-presentation.js +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/claude-boundary-state.d.ts +6 -6
- package/dist/claude-boundary-state.js +13 -13
- package/dist/claude-hook.js +25 -24
- package/dist/claude-mcp-context.d.ts +2 -2
- package/dist/claude-mcp-context.js +6 -6
- package/dist/cli/context.js +3 -6
- package/dist/cli/json.js +1 -1
- package/dist/cli/mcp-server-command.js +3 -3
- package/dist/cli/observation-commands.d.ts +2 -2
- package/dist/cli/observation-commands.js +23 -51
- package/dist/cli/perceive-presentation.d.ts +3 -0
- package/dist/cli/{catch-presentation.js → perceive-presentation.js} +3 -3
- package/dist/cli/registry.js +2 -2
- package/dist/codex-address.d.ts +3 -0
- package/dist/codex-address.js +7 -0
- package/dist/codex-hook.d.ts +1 -1
- package/dist/codex-hook.js +11 -8
- package/dist/codex-queue.d.ts +3 -2
- package/dist/codex-queue.js +8 -11
- package/dist/codex-watch.d.ts +6 -1
- package/dist/codex-watch.js +27 -29
- package/dist/delivery-ledger-sqlite.d.ts +25 -20
- package/dist/delivery-ledger-sqlite.js +76 -61
- package/dist/delivery-ledger.d.ts +24 -19
- package/dist/delivery-ledger.js +150 -81
- package/dist/delivery-lifetime.d.ts +2 -2
- package/dist/delivery-lifetime.js +6 -18
- package/dist/help.js +12 -12
- package/dist/hints.d.ts +2 -2
- package/dist/hints.js +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.d.ts +2 -2
- package/dist/list.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +8 -32
- package/dist/neutral.d.ts +5 -5
- package/dist/notifications.js +12 -6
- package/dist/operation-presentation.d.ts +8 -3
- package/dist/operation-presentation.js +50 -12
- package/dist/packages/agent-delivery/src/file-lock.js +4 -1
- package/dist/perceive-decisions.d.ts +28 -0
- package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
- package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
- package/dist/{catch-selection.js → perceive-selection.js} +3 -3
- package/dist/plugins/opencode/index.js +4 -4
- package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
- package/dist/plugins/opencode-shared/delivery.js +27 -21
- package/dist/plugins/opencode-v1/runtime.js +3 -3
- package/dist/ports.d.ts +0 -4
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +13 -13
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +11 -11
- package/dist/sqlite-square.d.ts +2 -4
- package/dist/sqlite-square.js +47 -54
- package/dist/sqlite-transaction.d.ts +2 -0
- package/dist/sqlite-transaction.js +25 -3
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-domain.js +1 -1
- package/dist/square-facade.d.ts +15 -13
- package/dist/square-location.js +1 -1
- package/dist/square-posting.d.ts +5 -5
- package/dist/square-posting.js +39 -38
- package/dist/square-tools.d.ts +15 -4
- package/dist/square-tools.js +35 -17
- package/dist/views.d.ts +7 -3
- package/dist/views.js +6 -3
- package/guides/architect.md +2 -2
- package/guides/change-waits.md +1 -1
- package/guides/codex-delivery.md +40 -35
- package/guides/delivery-primitives.md +22 -7
- package/guides/mcp.md +4 -2
- package/guides/operation-cancellation.md +11 -3
- package/guides/pi-native-delivery.md +1 -1
- package/guides/pi-tools.md +11 -6
- package/guides/receive-primitive.md +3 -3
- package/guides/square-api.md +15 -8
- package/package.json +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +10 -10
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/hooks/hooks.json +3 -0
- package/plugins/opencode/index.ts +4 -4
- package/plugins/opencode-shared/delivery.ts +27 -21
- package/plugins/opencode-v1/runtime.ts +3 -3
- package/plugins/pi/square-pi.js +18 -12
- package/skills/square/SKILL.md +10 -10
- package/dist/catch-decisions.d.ts +0 -28
- package/dist/cli/catch-presentation.d.ts +0 -3
- package/guides/mcp-connection-lifetime.md +0 -22
|
@@ -4,12 +4,11 @@ import { runClaudeHook, runClaudeWatch } from '../claude-hook.js';
|
|
|
4
4
|
import { runCodexHook, runCodexInterrupt } from '../codex-hook.js';
|
|
5
5
|
import { runCodexWatch } from '../codex-watch.js';
|
|
6
6
|
import { isSquareError } from '../model.js';
|
|
7
|
-
import { commandPrefix,
|
|
8
|
-
import { PARTICIPANTS_DEFAULT_LIMIT, PARTICIPANTS_MAX_LIMIT, renderHistoryResult, renderParticipantsResult, renderStatusResult, } from '../operation-presentation.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { renderCatchOutcome, renderCatchProgress } from './catch-presentation.js';
|
|
7
|
+
import { commandPrefix, truncateChars, withPathOutput, quoteShell, } from '../presentation.js';
|
|
8
|
+
import { PARTICIPANTS_DEFAULT_LIMIT, PARTICIPANTS_MAX_LIMIT, renderHistoryResult, renderParticipantsResult, renderStatusResult, renderSquareList, } from '../operation-presentation.js';
|
|
9
|
+
import { formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
10
|
+
import { discover, inbox, formatActivityId, parseActivityId, PERCEIVE_DEFAULT_LIMIT, PERCEIVE_MAX_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from '../square-api.js';
|
|
11
|
+
import { renderPerceiveOutcome, renderPerceiveProgress } from './perceive-presentation.js';
|
|
13
12
|
import { selectOwnSeat } from '../square-api.js';
|
|
14
13
|
import { jsonResult } from './json.js';
|
|
15
14
|
import { withSquare, squareInput, now, fail, parseBoundedLimit, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdin, requireSquarePath, requireValue, usage, commandIdentity, } from './context.js';
|
|
@@ -20,38 +19,6 @@ function inboxDisplay(value) {
|
|
|
20
19
|
const truncated = truncateChars(value, INBOX_DISPLAY_CHARS - 1);
|
|
21
20
|
return truncated.remaining === 0 ? truncated.text : `${truncated.text}…`;
|
|
22
21
|
}
|
|
23
|
-
function renderSquareList(data, projection) {
|
|
24
|
-
const { items, stopped, remaining, next } = data;
|
|
25
|
-
if (data.total === 0) {
|
|
26
|
-
return ['(no squares found)', ...(stopped ? [style('dim', '○ the walk stopped early — deeper squares may still be hiding')] : [])].join('\n') + '\n';
|
|
27
|
-
}
|
|
28
|
-
const lines = ['squares'];
|
|
29
|
-
for (const item of items) {
|
|
30
|
-
const glyph = item.activities > 0 ? '●' : style('dim', '○');
|
|
31
|
-
lines.push(`${glyph} ${inboxDisplay(item.path)}${style('dim', ` · ${formatRelativeTime(item.lastActiveAt, projection.now)} · ${item.participants.length} in the square · ${item.activities} ${item.activities === 1 ? 'activity' : 'activities'}`)}`);
|
|
32
|
-
const context = item.context.slice(0, 2);
|
|
33
|
-
if (context.length === 0)
|
|
34
|
-
lines.push(` ${style('dim', 'context')} · (none)`);
|
|
35
|
-
else {
|
|
36
|
-
context.forEach((line, index) => lines.push(` ${index === 0 ? style('dim', 'context') : ' '} · ${inboxDisplay(line)}`));
|
|
37
|
-
const hidden = item.context.length - context.length;
|
|
38
|
-
if (hidden > 0)
|
|
39
|
-
lines.push(style('dim', ` · … ${hidden} more ${hidden === 1 ? 'line' : 'lines'}`));
|
|
40
|
-
}
|
|
41
|
-
const participants = item.participants.slice(0, 3);
|
|
42
|
-
const hidden = item.participants.length - participants.length;
|
|
43
|
-
const names = participants.length === 0 ? 'nobody' : participants.map((name) => participantIdentity(inboxDisplay(name))).join(' · ');
|
|
44
|
-
lines.push(` ${style('dim', 'participants')} · ${names}${hidden > 0 ? style('dim', ` · … ${hidden} more`) : ''}`);
|
|
45
|
-
}
|
|
46
|
-
if (remaining > 0) {
|
|
47
|
-
lines.push(style('dim', stopped ? ' … more squares' : ` … ${remaining} more ${remaining === 1 ? 'square' : 'squares'}`));
|
|
48
|
-
if (next !== undefined)
|
|
49
|
-
lines.push(`square list --depth ${projection.depth} --limit ${projection.limit} --after ${quoteShell(next)}`);
|
|
50
|
-
}
|
|
51
|
-
if (stopped)
|
|
52
|
-
lines.push(style('dim', '○ the walk stopped early — deeper squares may still be hiding'));
|
|
53
|
-
return lines.join('\n') + '\n';
|
|
54
|
-
}
|
|
55
22
|
export const listCommand = {
|
|
56
23
|
parse(argv, context) {
|
|
57
24
|
let depth = DEFAULT_LIST_DEPTH;
|
|
@@ -81,7 +48,12 @@ export const listCommand = {
|
|
|
81
48
|
return { depth, limit, after };
|
|
82
49
|
},
|
|
83
50
|
async execute(intent, context) {
|
|
84
|
-
|
|
51
|
+
let owner;
|
|
52
|
+
try {
|
|
53
|
+
owner = commandIdentity(context.env, context.owner).owner;
|
|
54
|
+
}
|
|
55
|
+
catch { /* Discovery stays available without a complete harness identity. */ }
|
|
56
|
+
const result = await discover(squareInput(context), { ...intent, owner });
|
|
85
57
|
return renderSquareList(result, { now: now(context), depth: intent.depth ?? DEFAULT_LIST_DEPTH, limit: intent.limit ?? DEFAULT_LIST_LIMIT });
|
|
86
58
|
},
|
|
87
59
|
present: (result) => process.stdout.write(result),
|
|
@@ -156,7 +128,7 @@ export const streamCommand = {
|
|
|
156
128
|
},
|
|
157
129
|
present: () => { },
|
|
158
130
|
};
|
|
159
|
-
export const
|
|
131
|
+
export const perceiveCommand = {
|
|
160
132
|
parse(argv, context) {
|
|
161
133
|
let idle;
|
|
162
134
|
let mention;
|
|
@@ -168,10 +140,10 @@ export const catchCommand = {
|
|
|
168
140
|
for (let index = 0; index < argv.length; index++) {
|
|
169
141
|
if (argv[index] === '--id') {
|
|
170
142
|
if (id !== undefined)
|
|
171
|
-
fail('✕
|
|
143
|
+
fail('✕ perceive accepts one --id\nsquare perceive --help');
|
|
172
144
|
const value = requireValue(argv, index, '--id');
|
|
173
145
|
if (parseActivityId(value) === undefined)
|
|
174
|
-
fail('✕ invalid --id: expected an activity id like act/12\nsquare
|
|
146
|
+
fail('✕ invalid --id: expected an activity id like act/12\nsquare perceive --help');
|
|
175
147
|
id = value;
|
|
176
148
|
index += 1;
|
|
177
149
|
}
|
|
@@ -187,7 +159,7 @@ export const catchCommand = {
|
|
|
187
159
|
mention = true;
|
|
188
160
|
}
|
|
189
161
|
else if (argv[index] === '--limit') {
|
|
190
|
-
limit = parseBoundedLimit(argv[index + 1], '--limit',
|
|
162
|
+
limit = parseBoundedLimit(argv[index + 1], '--limit', PERCEIVE_MAX_LIMIT, `${commandPrefix(requireSquarePath(context))} perceive --limit ${PERCEIVE_MAX_LIMIT}`);
|
|
191
163
|
index += 1;
|
|
192
164
|
}
|
|
193
165
|
else if (argv[index] === '--replace')
|
|
@@ -195,22 +167,22 @@ export const catchCommand = {
|
|
|
195
167
|
else if (argv[index] === '--now')
|
|
196
168
|
now = true;
|
|
197
169
|
else if (argv[index] !== '--json')
|
|
198
|
-
fail(`✕
|
|
170
|
+
fail(`✕ perceive does not know ${argv[index]}\nsquare perceive --help`);
|
|
199
171
|
}
|
|
200
172
|
if (id !== undefined) {
|
|
201
173
|
if (argv.some((flag) => ['--idle', '--from', '--mention', '--limit', '--replace'].includes(flag))) {
|
|
202
|
-
fail('✕ --id cannot be combined with --idle, --from, --mention, --limit or --replace\nsquare
|
|
174
|
+
fail('✕ --id cannot be combined with --idle, --from, --mention, --limit or --replace\nsquare perceive --help');
|
|
203
175
|
}
|
|
204
176
|
return { id };
|
|
205
177
|
}
|
|
206
178
|
if (idle !== undefined && now)
|
|
207
|
-
fail('
|
|
179
|
+
fail('perceive accepts --now or --idle, not both.');
|
|
208
180
|
if (replace && idle === undefined)
|
|
209
181
|
fail('--replace can only be used with --idle.');
|
|
210
182
|
return {
|
|
211
183
|
...(participants.length > 0 ? { from: participants } : {}),
|
|
212
184
|
...(mention === undefined ? {} : { mention }),
|
|
213
|
-
limit: limit ??
|
|
185
|
+
limit: limit ?? PERCEIVE_DEFAULT_LIMIT,
|
|
214
186
|
...(idle === undefined ? {} : { idle }),
|
|
215
187
|
...(replace ? { replace } : {}),
|
|
216
188
|
};
|
|
@@ -221,10 +193,10 @@ export const catchCommand = {
|
|
|
221
193
|
const interrupt = () => controller.abort();
|
|
222
194
|
process.once('SIGINT', interrupt);
|
|
223
195
|
try {
|
|
224
|
-
const outcome = await withSquare(context, async (square) => (await selectOwnSeat(square, commandIdentity(context.env, context.owner).owner, context.name)).
|
|
196
|
+
const outcome = await withSquare(context, async (square) => (await selectOwnSeat(square, commandIdentity(context.env, context.owner).owner, context.name)).perceive({ ...intent, ...(context.json ? {} : { observe: true }) }, {
|
|
225
197
|
signal: controller.signal,
|
|
226
198
|
onProgress(progress) {
|
|
227
|
-
const line =
|
|
199
|
+
const line = renderPerceiveProgress(squarePath, progress);
|
|
228
200
|
if (line !== undefined)
|
|
229
201
|
process.stderr.write(line);
|
|
230
202
|
},
|
|
@@ -234,7 +206,7 @@ export const catchCommand = {
|
|
|
234
206
|
process.exitCode = outcome.reason === 'already-active' || outcome.reason === 'capped' ? 1 : 0;
|
|
235
207
|
}
|
|
236
208
|
else {
|
|
237
|
-
const rendered =
|
|
209
|
+
const rendered = renderPerceiveOutcome(squarePath, outcome, intent, context.owner ?? context.env.SQUARE_OWNER);
|
|
238
210
|
process.stdout.write(rendered.stdout);
|
|
239
211
|
process.exitCode = rendered.exitCode;
|
|
240
212
|
}
|
|
@@ -242,7 +214,7 @@ export const catchCommand = {
|
|
|
242
214
|
catch (error) {
|
|
243
215
|
if (!controller.signal.aborted)
|
|
244
216
|
throw error;
|
|
245
|
-
process.stdout.write(withPathOutput(squarePath, '○ you step back —
|
|
217
|
+
process.stdout.write(withPathOutput(squarePath, '○ you step back — perceive stopped'));
|
|
246
218
|
process.exitCode = 130;
|
|
247
219
|
}
|
|
248
220
|
finally {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { renderWatchForceTakeover, renderWatchReplaceMissing } from '../presentation.js';
|
|
2
|
-
export {
|
|
3
|
-
export function
|
|
4
|
-
if (progress.kind !== '
|
|
2
|
+
export { renderPerceiveOutcome } from '../operation-presentation.js';
|
|
3
|
+
export function renderPerceiveProgress(squarePath, progress) {
|
|
4
|
+
if (progress.kind !== 'perceive-started' || !progress.replacementRequested)
|
|
5
5
|
return undefined;
|
|
6
6
|
const options = { squarePath, name: progress.participant };
|
|
7
7
|
return (progress.replaced
|
package/dist/cli/registry.js
CHANGED
|
@@ -4,7 +4,7 @@ import { harnessCommand, installCommand, uninstallCommand } from './harness-comm
|
|
|
4
4
|
import { helpCommand, versionCommand } from './meta-commands.js';
|
|
5
5
|
import { mcpServerCommand } from './mcp-server-command.js';
|
|
6
6
|
import { JSON_COMMANDS } from './json.js';
|
|
7
|
-
import {
|
|
7
|
+
import { perceiveCommand, claudeHookCommand, claudeWatchCommand, codexHookCommand, codexWatchCommand, codexInterruptCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, } from './observation-commands.js';
|
|
8
8
|
/** Every public command is an executable adapter, including aliases and utility commands. */
|
|
9
9
|
export const commandRegistry = [
|
|
10
10
|
{ names: ['build'], spec: buildCommand },
|
|
@@ -17,7 +17,7 @@ export const commandRegistry = [
|
|
|
17
17
|
{ names: ['codex-hook'], spec: codexHookCommand },
|
|
18
18
|
{ names: ['codex-watch'], spec: codexWatchCommand },
|
|
19
19
|
{ names: ['codex-interrupt'], spec: codexInterruptCommand },
|
|
20
|
-
{ names: ['
|
|
20
|
+
{ names: ['perceive'], spec: perceiveCommand },
|
|
21
21
|
{ names: ['express'], spec: expressCommand },
|
|
22
22
|
{ names: ['listen'], spec: listenCommand },
|
|
23
23
|
{ names: ['ignore'], spec: ignoreCommand },
|
package/dist/codex-address.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { type CodexBoundaryStore } from './packages/agent-delivery/src/codex-boundary.js';
|
|
1
2
|
import type { DeliveryRoute } from './delivery-ledger.js';
|
|
3
|
+
/** Local Stop/activity epochs shared by native hooks and async watchers. No RPC. */
|
|
4
|
+
export declare function codexBoundaryStore(env: NodeJS.ProcessEnv): CodexBoundaryStore;
|
|
2
5
|
/** Capture the receiver's CLI configuration when its seat is established, not at send time. */
|
|
3
6
|
export declare function codexWakeAddress(threadId: string, env: NodeJS.ProcessEnv): DeliveryRoute;
|
package/dist/codex-address.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { resolveCodexHome } from './packages/agent-delivery/src/codex-endpoint.js';
|
|
2
|
+
import { createCodexBoundaryStore } from './packages/agent-delivery/src/codex-boundary.js';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/** Local Stop/activity epochs shared by native hooks and async watchers. No RPC. */
|
|
6
|
+
export function codexBoundaryStore(env) {
|
|
7
|
+
return createCodexBoundaryStore(env.SQUARE_CODEX_BOUNDARIES ?? path.join(os.homedir(), '.square', 'codex-boundaries.json'));
|
|
8
|
+
}
|
|
2
9
|
/** Capture the receiver's CLI configuration when its seat is established, not at send time. */
|
|
3
10
|
export function codexWakeAddress(threadId, env) {
|
|
4
11
|
const endpoint = env.SQUARE_CODEX_ENDPOINT?.trim();
|
package/dist/codex-hook.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CodexHookInput } from './packages/agent-delivery/src/codex-hook.js';
|
|
2
2
|
export type HookTrace = (line: string) => void;
|
|
3
3
|
export declare const CODEX_HOOK_BUDGET_MS = 4500;
|
|
4
|
-
/** Active delivery uses PostToolUse
|
|
4
|
+
/** Active delivery uses UserPromptSubmit and PostToolUse; each async Stop owns its own wait. */
|
|
5
5
|
export declare function codexHookResponse(value: unknown, sink: ((text: string) => void | Promise<void>) | undefined, env?: NodeJS.ProcessEnv, trace?: HookTrace): Promise<void>;
|
|
6
6
|
export declare function parseCodexHookText(text: string): CodexHookInput | undefined;
|
|
7
7
|
/** The caller owns stdout; no receipt is written when no output sink is supplied. */
|
package/dist/codex-hook.js
CHANGED
|
@@ -3,10 +3,10 @@ import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
|
3
3
|
import { DeliveryLedger } from './delivery-ledger.js';
|
|
4
4
|
import { harnessOwner } from './owner.js';
|
|
5
5
|
import { attachDeliverySessionRoutes, detachDeliverySession } from './session-lifecycle.js';
|
|
6
|
-
import { codexWakeAddress } from './codex-address.js';
|
|
6
|
+
import { codexWakeAddress, codexBoundaryStore } from './codex-address.js';
|
|
7
7
|
export const CODEX_HOOK_BUDGET_MS = 4500;
|
|
8
8
|
const defaultTrace = line => { process.stderr.write(`${line}\n`); };
|
|
9
|
-
/** Active delivery uses PostToolUse
|
|
9
|
+
/** Active delivery uses UserPromptSubmit and PostToolUse; each async Stop owns its own wait. */
|
|
10
10
|
export async function codexHookResponse(value, sink, env = process.env, trace = defaultTrace) {
|
|
11
11
|
const input = parseCodexHook(value);
|
|
12
12
|
if (!input)
|
|
@@ -15,28 +15,31 @@ export async function codexHookResponse(value, sink, env = process.env, trace =
|
|
|
15
15
|
const timeoutMs = Math.min(input.hook_event_name === 'Interrupt' || input.hook_event_name === 'SessionEnd' ? 2500 : CODEX_HOOK_BUDGET_MS, Number.isFinite(configured) && configured > 0 ? configured : CODEX_HOOK_BUDGET_MS);
|
|
16
16
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
17
17
|
const owner = harnessOwner({ channel: 'codex', session: input.session_id });
|
|
18
|
+
// Active native events disarm idle queueing; Stop arms its own async watcher.
|
|
19
|
+
// This file state is the only activity gate: no app-server sampling.
|
|
20
|
+
await codexBoundaryStore(env).record(input.session_id, input.hook_event_name === 'SessionStart' ? 'start' : input.hook_event_name === 'SessionEnd' ? 'end' : 'non-stop', { signal, turnId: input.turn_id });
|
|
18
21
|
if (input.hook_event_name === 'SessionStart') {
|
|
19
22
|
await attachDeliverySessionRoutes(owner, env, [codexWakeAddress(input.session_id, env)], signal);
|
|
20
23
|
}
|
|
21
24
|
else if (input.hook_event_name === 'SessionEnd' || input.hook_event_name === 'Interrupt') {
|
|
22
|
-
const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
|
|
25
|
+
const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY, signal });
|
|
23
26
|
try {
|
|
24
|
-
const captured = await ledger.session(owner);
|
|
27
|
+
const captured = await ledger.session(owner, signal);
|
|
25
28
|
if (!captured || signal.aborted)
|
|
26
29
|
return;
|
|
27
30
|
if (input.hook_event_name === 'SessionEnd')
|
|
28
|
-
await detachDeliverySession(owner, captured.generation, env);
|
|
31
|
+
await detachDeliverySession(owner, captured.generation, env, signal);
|
|
29
32
|
else
|
|
30
|
-
await ledger.cancel({ owner, generation: captured.generation });
|
|
33
|
+
await ledger.cancel({ owner, generation: captured.generation }, signal);
|
|
31
34
|
}
|
|
32
35
|
finally {
|
|
33
36
|
await ledger.close();
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
else if (input.hook_event_name === 'PostToolUse' && sink) {
|
|
39
|
+
else if ((input.hook_event_name === 'UserPromptSubmit' || input.hook_event_name === 'PostToolUse') && sink) {
|
|
37
40
|
try {
|
|
38
41
|
await presentPendingAtBoundary(owner, async (context) => {
|
|
39
|
-
await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName:
|
|
42
|
+
await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName: input.hook_event_name, additionalContext: context } })}\n`);
|
|
40
43
|
}, env, signal);
|
|
41
44
|
trace('square-codex-hook: stage=presentation');
|
|
42
45
|
}
|
package/dist/codex-queue.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { DeliveryCard, DeliveryTransport, DeliveryTransportContext, TransportOutcome } from './delivery-ledger.js';
|
|
2
2
|
export interface CodexQueueTransportOptions {
|
|
3
|
-
readonly turnId: string;
|
|
4
3
|
readonly address: Readonly<Record<string, string>>;
|
|
5
4
|
readonly env?: NodeJS.ProcessEnv;
|
|
6
5
|
}
|
|
7
|
-
/** Instantiated only by the receiver
|
|
6
|
+
/** Instantiated only by the receiver. The native queue store is shared across
|
|
7
|
+
* processes: the hosting Codex app-server polls it and starts queued input only
|
|
8
|
+
* when the thread is idle, so admission needs no remote idle proof. */
|
|
8
9
|
export declare class CodexQueueTransport implements DeliveryTransport {
|
|
9
10
|
private readonly opts;
|
|
10
11
|
readonly kind = "codex-queue";
|
package/dist/codex-queue.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { connect, DeliveryError } from './packages/agent-delivery/src/index.js';
|
|
2
|
-
import { codexIdleAtStop } from './packages/agent-delivery/src/codex.js';
|
|
3
2
|
import { codexQueueEndpoint } from './packages/agent-delivery/src/codex-endpoint.js';
|
|
4
3
|
import { renderDeliveryReminder } from './delivery-frame.js';
|
|
5
|
-
/** Instantiated only by the receiver
|
|
4
|
+
/** Instantiated only by the receiver. The native queue store is shared across
|
|
5
|
+
* processes: the hosting Codex app-server polls it and starts queued input only
|
|
6
|
+
* when the thread is idle, so admission needs no remote idle proof. */
|
|
6
7
|
export class CodexQueueTransport {
|
|
7
8
|
opts;
|
|
8
9
|
kind = 'codex-queue';
|
|
@@ -15,18 +16,14 @@ export class CodexQueueTransport {
|
|
|
15
16
|
!Object.entries(this.opts.address).every(([key, value]) => address[key] === value))
|
|
16
17
|
return 'not-capable';
|
|
17
18
|
const env = this.opts.env ?? process.env;
|
|
18
|
-
const turnId = this.opts.turnId;
|
|
19
19
|
try {
|
|
20
|
-
// Receiver delivery addresses the
|
|
21
|
-
//
|
|
20
|
+
// Receiver delivery addresses the shared queue store explicitly; disappearing
|
|
21
|
+
// native custody must not trigger CLI service startup.
|
|
22
22
|
const endpoint = address.endpoint ?? codexQueueEndpoint({ CODEX_HOME: address.codexHome });
|
|
23
23
|
const agent = await connect({ harness: 'codex', transport: 'cli', sessionId: threadId,
|
|
24
|
-
boundaries: { queueEligible: async (
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
if (!await codexIdleAtStop({ harness: 'codex', sessionId: id, codexHome: address.codexHome,
|
|
28
|
-
endpoint, timeoutMs: Math.max(1, context.deadline - Date.now()), signal: context.signal }, turnId))
|
|
29
|
-
return false;
|
|
24
|
+
boundaries: { queueEligible: async () => {
|
|
25
|
+
// Only ledger fences remain: currentness, suppression, cancellation,
|
|
26
|
+
// attachment generation, signal and deadline immediately before launch.
|
|
30
27
|
return await context.beforeSend()
|
|
31
28
|
&& !context.signal.aborted && Date.now() < context.deadline;
|
|
32
29
|
} },
|
package/dist/codex-watch.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
/** One async Stop waits only for its captured native turn; it never adopts another Stop.
|
|
1
|
+
/** One async Stop waits only for its captured native turn; it never adopts another Stop.
|
|
2
|
+
* Queue admission writes the shared native queue store through the CLI; the hosting
|
|
3
|
+
* Codex process starts queued input only when idle, so no remote idle sampling.
|
|
4
|
+
* Local boundary epochs retire this watcher when a newer Stop or any active native
|
|
5
|
+
* event (UserPromptSubmit/PostToolUse/Interrupt) arrives, keeping active-turn
|
|
6
|
+
* presentation on the synchronous hooks. */
|
|
2
7
|
export declare function runCodexWatch(inputText: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<string>;
|
package/dist/codex-watch.js
CHANGED
|
@@ -6,16 +6,19 @@ import { retainDeliveryLedger } from './delivery-lifetime.js';
|
|
|
6
6
|
import { harnessOwner } from './owner.js';
|
|
7
7
|
import { withFileLock } from './file-lock.js';
|
|
8
8
|
import { canonicalPath } from './canonical-path.js';
|
|
9
|
-
import { codexWakeAddress } from './codex-address.js';
|
|
9
|
+
import { codexWakeAddress, codexBoundaryStore } from './codex-address.js';
|
|
10
10
|
import { parseCodexHookText } from './codex-hook.js';
|
|
11
11
|
import { CodexQueueTransport } from './codex-queue.js';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/** One async Stop waits only for its captured native turn; it never adopts another Stop.
|
|
13
|
+
* Queue admission writes the shared native queue store through the CLI; the hosting
|
|
14
|
+
* Codex process starts queued input only when idle, so no remote idle sampling.
|
|
15
|
+
* Local boundary epochs retire this watcher when a newer Stop or any active native
|
|
16
|
+
* event (UserPromptSubmit/PostToolUse/Interrupt) arrives, keeping active-turn
|
|
17
|
+
* presentation on the synchronous hooks. */
|
|
14
18
|
export async function runCodexWatch(inputText, env = process.env, signal) {
|
|
15
19
|
const input = parseCodexHookText(inputText);
|
|
16
20
|
if (input?.hook_event_name !== 'Stop' || signal?.aborted)
|
|
17
21
|
return '';
|
|
18
|
-
const turnId = input.turn_id;
|
|
19
22
|
const controller = new AbortController();
|
|
20
23
|
const stop = () => controller.abort();
|
|
21
24
|
const timer = setTimeout(stop, 86_400_000);
|
|
@@ -26,26 +29,35 @@ export async function runCodexWatch(inputText, env = process.env, signal) {
|
|
|
26
29
|
try {
|
|
27
30
|
const route = codexWakeAddress(input.session_id, env);
|
|
28
31
|
const home = await canonicalPath(route.address.codexHome);
|
|
29
|
-
const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env) });
|
|
32
|
+
const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal: combined });
|
|
30
33
|
lifetime = retainDeliveryLedger(ledger);
|
|
31
34
|
const owner = harnessOwner({ channel: 'codex', session: input.session_id });
|
|
32
|
-
const captured = await ledger.session(owner);
|
|
35
|
+
const captured = await ledger.session(owner, combined);
|
|
33
36
|
if (captured?.state !== 'attached')
|
|
34
37
|
return '';
|
|
35
38
|
// Same-turn duplicates cannot overlap; a later Stop need not wait for an old
|
|
36
39
|
// watcher or a slow late settlement to release a session-wide lock.
|
|
37
|
-
const coordinate = [input.session_id, route.address.endpoint ?? home, captured.generation,
|
|
40
|
+
const coordinate = [input.session_id, route.address.endpoint ?? home, captured.generation, input.turn_id];
|
|
38
41
|
const root = path.join(path.dirname(deliveryLedgerLocation(env)), 'codex-watchers');
|
|
39
42
|
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
40
43
|
const lock = path.join(root, createHash('sha256').update(JSON.stringify(coordinate)).digest('hex') + '.lock');
|
|
41
44
|
const file = await fs.open(lock, 'a', 0o600);
|
|
42
45
|
await file.close();
|
|
43
46
|
await withFileLock(lock, { retryMs: 10, skipIfBusy: true, signal: combined }, async () => {
|
|
47
|
+
const boundaries = codexBoundaryStore(env);
|
|
48
|
+
// Record only after winning the lock: a duplicate Stop must not replace the
|
|
49
|
+
// live watcher's epoch and retire both. A newer Stop or active event does.
|
|
50
|
+
await boundaries.record(input.session_id, 'Stop', { turnId: input.turn_id, signal: combined });
|
|
51
|
+
const epoch = (await boundaries.read(input.session_id))?.stop;
|
|
52
|
+
if (!epoch)
|
|
53
|
+
return;
|
|
44
54
|
const observer = await ledger.observe(owner);
|
|
45
55
|
try {
|
|
46
56
|
while (!combined.aborted) {
|
|
47
57
|
const version = await observer.read(combined);
|
|
48
|
-
|
|
58
|
+
if (!await boundaries.queueEligible(input.session_id, epoch))
|
|
59
|
+
return;
|
|
60
|
+
const session = await ledger.session(owner, combined);
|
|
49
61
|
if (session?.state !== 'attached' || session.generation !== captured.generation)
|
|
50
62
|
return;
|
|
51
63
|
const registered = session.routes.find(candidate => candidate.kind === 'codex-queue');
|
|
@@ -53,29 +65,15 @@ export async function runCodexWatch(inputText, env = process.env, signal) {
|
|
|
53
65
|
registered.address.endpoint !== route.address.endpoint ||
|
|
54
66
|
!registered.address.codexHome || await canonicalPath(registered.address.codexHome) !== home)
|
|
55
67
|
return;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
timeoutMs:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (state === 'unavailable') {
|
|
64
|
-
if (!combined.aborted)
|
|
65
|
-
console.warn('Square Codex idle delivery unavailable: requires a reachable hosting app-server endpoint and supported native turn history. PostToolUse delivery remains available.');
|
|
68
|
+
const held = new Set((await ledger.handoffs(owner, { generation: captured.generation }, combined))
|
|
69
|
+
.filter(attempt => !['failed', 'released'].includes(attempt.outcome)).map(attempt => attempt.seq));
|
|
70
|
+
const card = (await ledger.pending(owner, { generation: captured.generation }, combined)).find(card => !held.has(card.seq));
|
|
71
|
+
if (card) {
|
|
72
|
+
lifetime.track(await ledger.dispatchHandoff(owner, card.key, new CodexQueueTransport({ env, address: registered.address }), { generation: captured.generation, timeoutMs: 5000, signal: combined }));
|
|
73
|
+
// One handoff attempt ends this wait, including unknown/refused
|
|
74
|
+
// outcomes. Never replay or turn our own writes into a retry loop.
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
|
-
if (state === 'idle') {
|
|
69
|
-
const held = new Set((await ledger.handoffs(owner, { generation: captured.generation }))
|
|
70
|
-
.filter(attempt => !['failed', 'released'].includes(attempt.outcome)).map(attempt => attempt.seq));
|
|
71
|
-
const card = (await ledger.pending(owner, { generation: captured.generation })).find(card => !held.has(card.seq));
|
|
72
|
-
if (card) {
|
|
73
|
-
lifetime.track(await ledger.dispatchHandoff(owner, card.key, new CodexQueueTransport({ turnId, env, address: registered.address }), { generation: captured.generation, timeoutMs: 5000, signal: combined }));
|
|
74
|
-
// One handoff attempt ends this wait, including unknown/refused
|
|
75
|
-
// outcomes. Never replay or turn our own writes into a retry loop.
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
77
|
await observer.changed(version, 1000, combined);
|
|
80
78
|
}
|
|
81
79
|
}
|
|
@@ -8,8 +8,13 @@ export declare class LedgerStore {
|
|
|
8
8
|
private readonly clock;
|
|
9
9
|
private pending;
|
|
10
10
|
private closed;
|
|
11
|
+
private readonly cancellation;
|
|
12
|
+
private readonly ordinaryCancellation;
|
|
13
|
+
private closing?;
|
|
11
14
|
private constructor();
|
|
12
|
-
static open(location: string, clock: () => number): Promise<LedgerStore>;
|
|
15
|
+
static open(location: string, clock: () => number, signal?: AbortSignal): Promise<LedgerStore>;
|
|
16
|
+
/** Stop ordinary work while captured effects retain access to settlement. */
|
|
17
|
+
retire(): void;
|
|
13
18
|
close(): Promise<void>;
|
|
14
19
|
private transaction;
|
|
15
20
|
private getSession;
|
|
@@ -20,49 +25,49 @@ export declare class LedgerStore {
|
|
|
20
25
|
private normalize;
|
|
21
26
|
private deleteCard;
|
|
22
27
|
private gc;
|
|
23
|
-
session(owner: string): Promise<DeliverySession | undefined>;
|
|
24
|
-
sessions(): Promise<readonly DeliverySession[]>;
|
|
25
|
-
pendingCards(owner: string, generation: string): Promise<readonly DeliveryCard[]>;
|
|
26
|
-
handoffs(owner: string, generation: string): Promise<readonly DeliveryAttempt[]>;
|
|
28
|
+
session(owner: string, signal?: AbortSignal): Promise<DeliverySession | undefined>;
|
|
29
|
+
sessions(signal?: AbortSignal): Promise<readonly DeliverySession[]>;
|
|
30
|
+
pendingCards(owner: string, generation: string, signal?: AbortSignal): Promise<readonly DeliveryCard[]>;
|
|
31
|
+
handoffs(owner: string, generation: string, signal?: AbortSignal): Promise<readonly DeliveryAttempt[]>;
|
|
27
32
|
attach(input: {
|
|
28
33
|
owner: string;
|
|
29
34
|
expectedGeneration: string | null;
|
|
30
35
|
routes: readonly DeliveryRoute[];
|
|
31
|
-
}): Promise<DeliverySession | false>;
|
|
36
|
+
}, signal?: AbortSignal): Promise<DeliverySession | false>;
|
|
32
37
|
updateRoutes(input: {
|
|
33
38
|
owner: string;
|
|
34
39
|
generation: string;
|
|
35
40
|
routes: readonly DeliveryRoute[];
|
|
36
|
-
}): Promise<boolean>;
|
|
41
|
+
}, signal?: AbortSignal): Promise<boolean>;
|
|
37
42
|
lifecycle(input: {
|
|
38
43
|
owner: string;
|
|
39
44
|
generation: string;
|
|
40
|
-
}, state: 'detached' | 'deleted'): Promise<boolean>;
|
|
45
|
+
}, state: 'detached' | 'deleted', signal?: AbortSignal): Promise<boolean>;
|
|
41
46
|
post(input: {
|
|
42
47
|
owner: string;
|
|
43
48
|
key: string;
|
|
44
49
|
text: string;
|
|
45
50
|
details: string | null;
|
|
46
|
-
}): Promise<PostResult>;
|
|
51
|
+
}, signal?: AbortSignal): Promise<PostResult>;
|
|
47
52
|
suppress(input: {
|
|
48
53
|
owner: string;
|
|
49
54
|
key: string;
|
|
50
55
|
scope: 'exact' | 'prefix';
|
|
51
56
|
expiresAt?: number;
|
|
52
|
-
}): Promise<SuppressionResult>;
|
|
57
|
+
}, signal?: AbortSignal): Promise<SuppressionResult>;
|
|
53
58
|
renewSuppression(input: {
|
|
54
59
|
owner: string;
|
|
55
60
|
id: string;
|
|
56
61
|
expiresAt: number;
|
|
57
|
-
}): Promise<boolean>;
|
|
62
|
+
}, signal?: AbortSignal): Promise<boolean>;
|
|
58
63
|
releaseSuppression(input: {
|
|
59
64
|
owner: string;
|
|
60
65
|
id: string;
|
|
61
|
-
}): Promise<boolean>;
|
|
66
|
+
}, signal?: AbortSignal): Promise<boolean>;
|
|
62
67
|
cancel(input: {
|
|
63
68
|
owner: string;
|
|
64
69
|
generation: string;
|
|
65
|
-
}): Promise<boolean>;
|
|
70
|
+
}, signal?: AbortSignal): Promise<boolean>;
|
|
66
71
|
private isSuppressed;
|
|
67
72
|
private eligible;
|
|
68
73
|
private insertAttempt;
|
|
@@ -79,21 +84,21 @@ export declare class LedgerStore {
|
|
|
79
84
|
prepare(token: string, value: DeliveryCorrelation, signal?: AbortSignal): Promise<boolean>;
|
|
80
85
|
private receipt;
|
|
81
86
|
private confirm;
|
|
82
|
-
settle(tokens: readonly string[], outcome: Exclude<DeliveryOutcome, 'dispatching'
|
|
87
|
+
settle(tokens: readonly string[], outcome: Exclude<DeliveryOutcome, 'dispatching'>, signal?: AbortSignal): Promise<boolean>;
|
|
83
88
|
private handoffFacts;
|
|
84
89
|
private matchingHandoff;
|
|
85
90
|
private currentHandoff;
|
|
86
|
-
handoff(token: string): Promise<(DeliveryHandoffRef & {
|
|
91
|
+
handoff(token: string, signal?: AbortSignal): Promise<(DeliveryHandoffRef & {
|
|
87
92
|
routeKind: string;
|
|
88
93
|
}) | undefined>;
|
|
89
|
-
guardHandoff(ref: DeliveryHandoffRef): Promise<boolean>;
|
|
90
|
-
acceptHandoff(ref: DeliveryHandoffRef): Promise<boolean>;
|
|
94
|
+
guardHandoff(ref: DeliveryHandoffRef, signal?: AbortSignal): Promise<boolean>;
|
|
95
|
+
acceptHandoff(ref: DeliveryHandoffRef, signal?: AbortSignal): Promise<boolean>;
|
|
91
96
|
observePresentation(handoff: string | DeliveryHandoffRef, options?: {
|
|
92
97
|
readonly requireCurrent?: boolean;
|
|
93
|
-
}): Promise<boolean>;
|
|
98
|
+
}, signal?: AbortSignal): Promise<boolean>;
|
|
94
99
|
receipts(owner: string, options: {
|
|
95
100
|
keys?: readonly string[];
|
|
96
101
|
since?: number;
|
|
97
|
-
}): Promise<readonly DeliveryReceipt[]>;
|
|
98
|
-
health(owner?: string): Promise<DeliveryHealth>;
|
|
102
|
+
}, signal?: AbortSignal): Promise<readonly DeliveryReceipt[]>;
|
|
103
|
+
health(owner?: string, signal?: AbortSignal): Promise<DeliveryHealth>;
|
|
99
104
|
}
|