@astrosheep/square 0.3.27 → 0.3.29
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 +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/artifact.d.ts +4 -4
- package/dist/artifact.js +24 -19
- package/dist/automatic-session.js +18 -7
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/cli/context.d.ts +4 -4
- package/dist/cli/context.js +11 -9
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/observation-commands.js +2 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/square-commands.js +15 -16
- package/dist/codex-boundary-state.d.ts +4 -4
- package/dist/codex-boundary-state.js +19 -19
- package/dist/codex-hook.js +3 -3
- package/dist/codex-queue.js +2 -2
- package/dist/file-lock.d.ts +1 -1
- package/dist/file-lock.js +22 -50
- package/dist/harness-links.d.ts +2 -1
- package/dist/harness-links.js +41 -12
- package/dist/harness.js +11 -5
- package/dist/inbox.js +2 -2
- package/dist/list.js +3 -3
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +10 -10
- package/dist/opencode.d.ts +19 -0
- package/dist/opencode.js +49 -0
- package/dist/presented.d.ts +5 -13
- package/dist/presented.js +48 -158
- package/dist/registry.d.ts +18 -30
- package/dist/registry.js +89 -320
- package/dist/routes.d.ts +6 -6
- package/dist/routes.js +20 -20
- package/dist/square-file-adapter.d.ts +1 -1
- package/dist/square-file-adapter.js +5 -9
- package/dist/square-storage.d.ts +3 -3
- package/dist/square-storage.js +23 -20
- package/dist/square-wiring.js +1 -1
- package/dist/wake-attempts.d.ts +6 -6
- package/dist/wake-attempts.js +39 -31
- package/dist/wake-evidence.d.ts +1 -1
- package/dist/wake-evidence.js +11 -11
- package/dist/wake-port.d.ts +1 -1
- package/dist/wake-port.js +1 -1
- package/dist/watch.js +1 -1
- package/extensions/square-pi.js +30 -3
- package/package.json +5 -1
- package/extensions/square-opencode.js +0 -48
package/dist/artifact.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function createSquareState(options: BuildOptions & {
|
|
|
14
14
|
}, snippet: string): SquareState;
|
|
15
15
|
export declare function encodeSquare(squareState: SquareState): Buffer;
|
|
16
16
|
export declare function decodeSquare(bytes: Buffer): SquareState;
|
|
17
|
-
export declare function writeSquareFile(squarePath: string, squareState: SquareState): void
|
|
18
|
-
export declare function loadSquare(squarePath: string): SquareState
|
|
19
|
-
export declare function probeSquare(squarePath: string): SquareState | undefined
|
|
20
|
-
export declare function diagnoseSquareFile(squarePath: string): DiagnoseResult
|
|
17
|
+
export declare function writeSquareFile(squarePath: string, squareState: SquareState): Promise<void>;
|
|
18
|
+
export declare function loadSquare(squarePath: string): Promise<SquareState>;
|
|
19
|
+
export declare function probeSquare(squarePath: string): Promise<SquareState | undefined>;
|
|
20
|
+
export declare function diagnoseSquareFile(squarePath: string): Promise<DiagnoseResult>;
|
package/dist/artifact.js
CHANGED
|
@@ -10,6 +10,8 @@ const LENGTH_BYTES = 4;
|
|
|
10
10
|
const DIGEST_BYTES = 32;
|
|
11
11
|
const HEADER_BYTES = SQUARE_MAGIC.length + LENGTH_BYTES + DIGEST_BYTES;
|
|
12
12
|
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
13
|
+
const guideNames = ['participant', 'architect', 'brainstorm'];
|
|
14
|
+
const guideContents = new Map(await Promise.all(guideNames.map(async (name) => [name, (await fs.promises.readFile(new URL(`../guides/${name}.md`, import.meta.url), 'utf8')).trim()])));
|
|
13
15
|
function invalidArtifact(detail) {
|
|
14
16
|
return new SquareError('invalid_args', `Invalid square artifact: ${detail}`);
|
|
15
17
|
}
|
|
@@ -260,7 +262,10 @@ function normalizedLines(value) {
|
|
|
260
262
|
}
|
|
261
263
|
function readGuide(name) {
|
|
262
264
|
try {
|
|
263
|
-
|
|
265
|
+
const guide = guideContents.get(name);
|
|
266
|
+
if (guide === undefined)
|
|
267
|
+
throw Object.assign(new Error('missing guide'), { code: 'ENOENT' });
|
|
268
|
+
return guide;
|
|
264
269
|
}
|
|
265
270
|
catch (error) {
|
|
266
271
|
if (error.code === 'ENOENT') {
|
|
@@ -288,9 +293,9 @@ export function encodeSquare(squareState) {
|
|
|
288
293
|
export function decodeSquare(bytes) {
|
|
289
294
|
return validateSquareState(decodeEnvelope(bytes, SQUARE_MAGIC));
|
|
290
295
|
}
|
|
291
|
-
function readArtifact(squarePath) {
|
|
296
|
+
async function readArtifact(squarePath) {
|
|
292
297
|
try {
|
|
293
|
-
return fs.
|
|
298
|
+
return await fs.promises.readFile(squarePath);
|
|
294
299
|
}
|
|
295
300
|
catch (error) {
|
|
296
301
|
if (error.code === 'ENOENT') {
|
|
@@ -304,37 +309,37 @@ function requireSquareExtension(squarePath) {
|
|
|
304
309
|
throw new SquareError('invalid_args', `Square artifacts must use the .square extension: ${squarePath}`);
|
|
305
310
|
}
|
|
306
311
|
}
|
|
307
|
-
function atomicWrite(target, bytes) {
|
|
312
|
+
async function atomicWrite(target, bytes) {
|
|
308
313
|
const temporary = path.join(path.dirname(target), `.${path.basename(target)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
309
|
-
fs.
|
|
314
|
+
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
310
315
|
try {
|
|
311
|
-
fs.
|
|
312
|
-
fs.
|
|
316
|
+
await fs.promises.writeFile(temporary, bytes);
|
|
317
|
+
await fs.promises.rename(temporary, target);
|
|
313
318
|
}
|
|
314
319
|
catch (error) {
|
|
315
320
|
try {
|
|
316
|
-
fs.
|
|
321
|
+
await fs.promises.unlink(temporary);
|
|
317
322
|
}
|
|
318
323
|
catch { }
|
|
319
324
|
throw error;
|
|
320
325
|
}
|
|
321
326
|
}
|
|
322
|
-
export function writeSquareFile(squarePath, squareState) {
|
|
327
|
+
export async function writeSquareFile(squarePath, squareState) {
|
|
323
328
|
requireSquareExtension(squarePath);
|
|
324
|
-
atomicWrite(squarePath, encodeSquare(squareState));
|
|
329
|
+
await atomicWrite(squarePath, encodeSquare(squareState));
|
|
325
330
|
}
|
|
326
|
-
export function loadSquare(squarePath) {
|
|
331
|
+
export async function loadSquare(squarePath) {
|
|
327
332
|
requireSquareExtension(squarePath);
|
|
328
|
-
return decodeSquare(readArtifact(squarePath));
|
|
333
|
+
return decodeSquare(await readArtifact(squarePath));
|
|
329
334
|
}
|
|
330
|
-
export function probeSquare(squarePath) {
|
|
335
|
+
export async function probeSquare(squarePath) {
|
|
331
336
|
if (!squarePath.endsWith('.square'))
|
|
332
337
|
return undefined;
|
|
333
338
|
let descriptor;
|
|
334
339
|
try {
|
|
335
|
-
descriptor = fs.
|
|
340
|
+
descriptor = await fs.promises.open(squarePath, 'r');
|
|
336
341
|
const magic = Buffer.alloc(SQUARE_MAGIC.length);
|
|
337
|
-
if (
|
|
342
|
+
if ((await descriptor.read(magic, 0, magic.length, 0)).bytesRead !== magic.length || !magic.equals(SQUARE_MAGIC)) {
|
|
338
343
|
return undefined;
|
|
339
344
|
}
|
|
340
345
|
}
|
|
@@ -343,18 +348,18 @@ export function probeSquare(squarePath) {
|
|
|
343
348
|
}
|
|
344
349
|
finally {
|
|
345
350
|
if (descriptor !== undefined)
|
|
346
|
-
|
|
351
|
+
await descriptor.close();
|
|
347
352
|
}
|
|
348
353
|
try {
|
|
349
|
-
return loadSquare(squarePath);
|
|
354
|
+
return await loadSquare(squarePath);
|
|
350
355
|
}
|
|
351
356
|
catch {
|
|
352
357
|
return undefined;
|
|
353
358
|
}
|
|
354
359
|
}
|
|
355
|
-
export function diagnoseSquareFile(squarePath) {
|
|
360
|
+
export async function diagnoseSquareFile(squarePath) {
|
|
356
361
|
try {
|
|
357
|
-
return { problems: [], state: loadSquare(squarePath) };
|
|
362
|
+
return { problems: [], state: await loadSquare(squarePath) };
|
|
358
363
|
}
|
|
359
364
|
catch (error) {
|
|
360
365
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { openSquare } from './square-file-adapter.js';
|
|
4
4
|
import { closeOpenSquare } from './open-square.js';
|
|
@@ -16,9 +16,18 @@ const providerEnv = {
|
|
|
16
16
|
export function publicSquarePath(cwd) {
|
|
17
17
|
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
18
18
|
}
|
|
19
|
+
async function squareExists(squarePath) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(squarePath);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
|
|
20
29
|
const squarePath = publicSquarePath(cwd);
|
|
21
|
-
if (!
|
|
30
|
+
if (!await squareExists(squarePath))
|
|
22
31
|
return undefined;
|
|
23
32
|
let reader;
|
|
24
33
|
try {
|
|
@@ -29,7 +38,8 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
29
38
|
return undefined;
|
|
30
39
|
}
|
|
31
40
|
const name = automaticParticipant(provider, sessionId, env);
|
|
32
|
-
const
|
|
41
|
+
const canonicalPath = await canonicalSquarePath(squarePath);
|
|
42
|
+
const alreadyBound = (await lookupSessionBindings(sessionId)).some((binding) => binding.squarePath === canonicalPath && binding.name === name);
|
|
33
43
|
await closeOpenSquare(reader);
|
|
34
44
|
const square = await Square.at({ path: squarePath });
|
|
35
45
|
try {
|
|
@@ -37,7 +47,7 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
37
47
|
if (implicit.state === 'done' || (implicit.state === 'active' && alreadyBound))
|
|
38
48
|
return undefined;
|
|
39
49
|
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
40
|
-
recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
|
|
50
|
+
await recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
|
|
41
51
|
return undefined;
|
|
42
52
|
}
|
|
43
53
|
finally {
|
|
@@ -47,8 +57,9 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
47
57
|
export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
|
|
48
58
|
const squarePath = publicSquarePath(cwd);
|
|
49
59
|
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
50
|
-
const
|
|
51
|
-
|
|
60
|
+
const canonicalPath = await canonicalSquarePath(squarePath);
|
|
61
|
+
const binding = (await lookupSessionBindings(sessionId)).find((item) => item.squarePath === canonicalPath && item.channel === channel);
|
|
62
|
+
if (binding === undefined || !await squareExists(squarePath))
|
|
52
63
|
return;
|
|
53
64
|
const reader = await openSquare(squarePath);
|
|
54
65
|
const joined = await entryPresentation(reader, binding.name).finally(() => closeOpenSquare(reader));
|
|
@@ -62,5 +73,5 @@ export async function automaticSessionEnd(provider, sessionId, cwd, env = proces
|
|
|
62
73
|
finally {
|
|
63
74
|
await square.close();
|
|
64
75
|
}
|
|
65
|
-
recordSessionDone(sessionId, binding.name, squarePath, channel, env);
|
|
76
|
+
await recordSessionDone(sessionId, binding.name, squarePath, channel, env);
|
|
66
77
|
}
|
|
@@ -2,4 +2,4 @@ import type { InboxMembership } from './model.js';
|
|
|
2
2
|
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
3
3
|
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
4
4
|
export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
|
|
5
|
-
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T | Promise<T>, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<T | undefined>;
|
|
5
|
+
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T | Promise<T>, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
|
|
@@ -76,13 +76,13 @@ function renderBoundary(inbox) {
|
|
|
76
76
|
complete,
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
79
|
+
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env, signal) {
|
|
80
80
|
const inbox = await lookup(sessionId);
|
|
81
81
|
let delivered;
|
|
82
82
|
const result = await presentOnce(sessionId, () => pendingAtBoundary(inbox), (inbox) => {
|
|
83
83
|
delivered = renderBoundary(inbox);
|
|
84
84
|
return present(delivered.context);
|
|
85
|
-
}, env);
|
|
85
|
+
}, env, Date.now(), signal);
|
|
86
86
|
if (result !== undefined && delivered !== undefined) {
|
|
87
87
|
for (const entry of delivered.complete) {
|
|
88
88
|
await markBoundarySeen(entry.membership.squarePath, entry.membership.name, entry.membership.ownerId, entry.actIndexes);
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export interface CommandSpec<Intent = unknown, Result = void> {
|
|
|
10
10
|
execute(intent: Intent, context: CommandContext): Promise<Result> | Result;
|
|
11
11
|
present(result: Result, context: CommandContext): void;
|
|
12
12
|
}
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function resolveBody(arg: string): string
|
|
15
|
-
export declare function readPipedBodyFallback(): string | undefined
|
|
13
|
+
export declare function readStdin(): Promise<string>;
|
|
14
|
+
export declare function resolveBody(arg: string): Promise<string>;
|
|
15
|
+
export declare function readPipedBodyFallback(): Promise<string | undefined>;
|
|
16
16
|
export declare function fail(message: string, exitCode?: number): never;
|
|
17
17
|
export declare function usage(command: string): never;
|
|
18
18
|
export declare function requireValue(args: string[], index: number, flag: string): string;
|
|
@@ -29,7 +29,7 @@ export interface ParsedGlobalArgs {
|
|
|
29
29
|
name?: string;
|
|
30
30
|
args: string[];
|
|
31
31
|
}
|
|
32
|
-
export declare function parseGlobalArgs(rawArgs: string[]): ParsedGlobalArgs
|
|
32
|
+
export declare function parseGlobalArgs(rawArgs: string[]): Promise<ParsedGlobalArgs>;
|
|
33
33
|
export declare function locationIsRequired(command: string): boolean;
|
|
34
34
|
export declare function defaultContext(command: string, squarePath?: string, name?: string): CommandContext;
|
|
35
35
|
export declare function requireSquarePath(context: CommandContext): string;
|
package/dist/cli/context.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import os from 'node:os';
|
|
3
2
|
import { commandUsageHint } from '../help.js';
|
|
4
3
|
import { parseParticipantList, validateName } from '../model.js';
|
|
5
4
|
import { localParticipantName } from '../registry.js';
|
|
6
|
-
export function
|
|
5
|
+
export async function readStdin() {
|
|
7
6
|
try {
|
|
8
|
-
|
|
7
|
+
let content = '';
|
|
8
|
+
for await (const chunk of process.stdin.setEncoding('utf8'))
|
|
9
|
+
content += chunk;
|
|
10
|
+
return content;
|
|
9
11
|
}
|
|
10
12
|
catch {
|
|
11
13
|
return '';
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
export function resolveBody(arg) {
|
|
15
|
-
return arg === '-' ?
|
|
16
|
+
export async function resolveBody(arg) {
|
|
17
|
+
return arg === '-' ? readStdin() : arg;
|
|
16
18
|
}
|
|
17
|
-
export function readPipedBodyFallback() {
|
|
19
|
+
export async function readPipedBodyFallback() {
|
|
18
20
|
if (process.stdin.isTTY)
|
|
19
21
|
return undefined;
|
|
20
|
-
const content =
|
|
22
|
+
const content = await readStdin();
|
|
21
23
|
return content.trim() === '' ? undefined : content;
|
|
22
24
|
}
|
|
23
25
|
export function fail(message, exitCode = 2) {
|
|
@@ -114,7 +116,7 @@ function configuredName() {
|
|
|
114
116
|
validateName(value);
|
|
115
117
|
return value;
|
|
116
118
|
}
|
|
117
|
-
export function parseGlobalArgs(rawArgs) {
|
|
119
|
+
export async function parseGlobalArgs(rawArgs) {
|
|
118
120
|
const args = [...rawArgs];
|
|
119
121
|
let requestedPath;
|
|
120
122
|
let name;
|
|
@@ -142,7 +144,7 @@ export function parseGlobalArgs(rawArgs) {
|
|
|
142
144
|
}
|
|
143
145
|
const squarePath = requestedPath ?? configured;
|
|
144
146
|
if (name === undefined && squarePath !== undefined && command !== undefined && locationIsRequired(command)) {
|
|
145
|
-
name = localParticipantName(squarePath);
|
|
147
|
+
name = await localParticipantName(squarePath);
|
|
146
148
|
}
|
|
147
149
|
return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
|
|
148
150
|
}
|
|
@@ -8,9 +8,9 @@ export const doctorCommand = {
|
|
|
8
8
|
usage(context.command);
|
|
9
9
|
return undefined;
|
|
10
10
|
},
|
|
11
|
-
execute(_intent, context) {
|
|
11
|
+
async execute(_intent, context) {
|
|
12
12
|
const squarePath = requireSquarePath(context);
|
|
13
|
-
const diagnosis = diagnoseSquareFile(squarePath);
|
|
13
|
+
const diagnosis = await diagnoseSquareFile(squarePath);
|
|
14
14
|
if (diagnosis.unfixable !== undefined || diagnosis.state === undefined) {
|
|
15
15
|
return {
|
|
16
16
|
output: withPathOutput(squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the snapshot could not be decoded')),
|
|
@@ -12,7 +12,7 @@ import { cmdWatch } from '../watch.js';
|
|
|
12
12
|
import { openSquare } from '../square-file-adapter.js';
|
|
13
13
|
import { closeOpenSquare } from '../open-square.js';
|
|
14
14
|
import { historyPresentation, participantsPresentation, statusPresentation } from '../views.js';
|
|
15
|
-
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger,
|
|
15
|
+
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdin, requireParticipant, requireSquarePath, requireValue, usage, } from './context.js';
|
|
16
16
|
const STATUS_PARTICIPANT_PREVIEW_LIMIT = 10;
|
|
17
17
|
export const listCommand = {
|
|
18
18
|
parse: (argv) => argv,
|
|
@@ -470,7 +470,7 @@ function hookCommand(runHook) {
|
|
|
470
470
|
return {
|
|
471
471
|
parse(argv, context) { if (argv.length > 0)
|
|
472
472
|
usage(context.command); return undefined; },
|
|
473
|
-
execute: () => runHook(
|
|
473
|
+
execute: async () => runHook(await readStdin()),
|
|
474
474
|
present: (result) => process.stdout.write(result),
|
|
475
475
|
};
|
|
476
476
|
}
|
package/dist/cli/program.js
CHANGED
|
@@ -17,7 +17,7 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
17
17
|
await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help'));
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
const parsed = parseGlobalArgs(rawArgs);
|
|
20
|
+
const parsed = await parseGlobalArgs(rawArgs);
|
|
21
21
|
if (parsed.args.length === 0 || parsed.args[0] === '--help' || parsed.args[0] === '-h') {
|
|
22
22
|
await executeRegisteredCommand('help', [], defaultContext('help', parsed.squarePath, parsed.name));
|
|
23
23
|
return;
|
|
@@ -8,7 +8,7 @@ import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
|
8
8
|
import { closeOpenSquare } from '../open-square.js';
|
|
9
9
|
import { openParticipant, Square } from '../square-wiring.js';
|
|
10
10
|
import { entryPresentation, eventPresentation } from '../views.js';
|
|
11
|
-
import { fail, parseHardCap, parsePositiveInteger,
|
|
11
|
+
import { fail, parseHardCap, parsePositiveInteger, readStdin, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
|
|
12
12
|
function parseBuild(argv) {
|
|
13
13
|
const options = { force: false, hardCap: null };
|
|
14
14
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -41,16 +41,16 @@ function parseBuild(argv) {
|
|
|
41
41
|
if (options.throttlePerMinute !== undefined && options.throttlePerMinute <= 0) {
|
|
42
42
|
fail('Invalid build option: --throttle must be a positive integer.');
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
if (snippet.trim() === '')
|
|
46
|
-
fail('Missing Markdown body snippet on stdin.');
|
|
47
|
-
return { options, snippet };
|
|
44
|
+
return { options, snippet: '' };
|
|
48
45
|
}
|
|
49
46
|
export const buildCommand = {
|
|
50
47
|
parse: (argv) => parseBuild(argv),
|
|
51
48
|
async execute(intent, context) {
|
|
52
49
|
const squarePath = requireSquarePath(context);
|
|
53
|
-
await
|
|
50
|
+
const snippet = await readStdin();
|
|
51
|
+
if (snippet.trim() === '')
|
|
52
|
+
fail('Missing Markdown body snippet on stdin.');
|
|
53
|
+
await createSquare(squarePath, intent.options, snippet);
|
|
54
54
|
const cap = intent.options.hardCap === null ? 'unlimited' : formatHardCap(intent.options.hardCap);
|
|
55
55
|
const throttle = intent.options.throttlePerMinute === undefined ? [] : [` · throttle ${intent.options.throttlePerMinute}/min`];
|
|
56
56
|
return withPathOutput(squarePath, ['✓ built', ` · cap ${cap}`, ...throttle, ' · participants (none seeded — first join adds names)'].join('\n'), { participantCount: 0 });
|
|
@@ -90,7 +90,7 @@ export const joinCommand = {
|
|
|
90
90
|
const joinedName = participant.name;
|
|
91
91
|
const isRejoin = before.joined;
|
|
92
92
|
const reconnect = isRejoin
|
|
93
|
-
&& localParticipantOwner(squarePath, joinedName) !== undefined;
|
|
93
|
+
&& await localParticipantOwner(squarePath, joinedName) !== undefined;
|
|
94
94
|
if (isRejoin && !intent.kick && !reconnect) {
|
|
95
95
|
fail([
|
|
96
96
|
`✕ ${participantIdentity(joinedName)} shoos you out of the square`,
|
|
@@ -102,7 +102,7 @@ export const joinCommand = {
|
|
|
102
102
|
const afterSquare = await openSquare(squarePath, { clock: nowMs });
|
|
103
103
|
const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
|
|
104
104
|
await closeOpenSquare(afterSquare);
|
|
105
|
-
recordLocalJoin(joinedName, squarePath);
|
|
105
|
+
await recordLocalJoin(joinedName, squarePath);
|
|
106
106
|
await sweepPendingNotifications(squarePath);
|
|
107
107
|
const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
|
|
108
108
|
now: nowMs(),
|
|
@@ -164,9 +164,8 @@ function parseActivity(argv, context) {
|
|
|
164
164
|
const reach = bell ? 'bell' : undefined;
|
|
165
165
|
if (bodyArgs.length !== 1) {
|
|
166
166
|
if (bodyArgs.length === 0) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return { name: requireParticipant(context.name), activity: piped, force, noWait, reach, reply };
|
|
167
|
+
if (!process.stdin.isTTY)
|
|
168
|
+
return { name: requireParticipant(context.name), activity: '-', force, noWait, reach, reply };
|
|
170
169
|
}
|
|
171
170
|
fail("express requires a body argument (a quoted string or '-' with piped stdin)");
|
|
172
171
|
}
|
|
@@ -177,7 +176,7 @@ export const expressCommand = {
|
|
|
177
176
|
async execute(intent, context) {
|
|
178
177
|
const squarePath = requireSquarePath(context);
|
|
179
178
|
await sweepPendingNotifications(squarePath);
|
|
180
|
-
const body = resolveBody(intent.activity);
|
|
179
|
+
const body = await resolveBody(intent.activity);
|
|
181
180
|
const reachArg = intent.reach === 'bell' ? ' --bell' : '';
|
|
182
181
|
await cmdActivity(squarePath, intent.name, body, (value) => value, {
|
|
183
182
|
force: intent.force,
|
|
@@ -269,19 +268,19 @@ export const listeningCommand = {
|
|
|
269
268
|
function parseDone(argv, context) {
|
|
270
269
|
if (argv.length > 1)
|
|
271
270
|
usage(context.command);
|
|
272
|
-
return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] :
|
|
271
|
+
return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] : undefined };
|
|
273
272
|
}
|
|
274
273
|
export const doneCommand = {
|
|
275
274
|
parse: parseDone,
|
|
276
275
|
async execute(intent, context) {
|
|
277
276
|
const squarePath = requireSquarePath(context);
|
|
278
|
-
const body = resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim();
|
|
277
|
+
const body = (await resolveBody(intent.body ?? (process.stdin.isTTY ? '' : '-'))).replace(/\r\n/g, '\n').trim();
|
|
279
278
|
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
280
279
|
const participant = await square.join(intent.name);
|
|
281
280
|
const result = await participant.done(body);
|
|
282
281
|
await square.close();
|
|
283
282
|
const name = result.activity.actor;
|
|
284
|
-
recordLocalDone(name, squarePath);
|
|
283
|
+
await recordLocalDone(name, squarePath);
|
|
285
284
|
const presentation = await openSquare(squarePath, { clock: nowMs });
|
|
286
285
|
const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
|
|
287
286
|
return withPathOutput(squarePath, `○ ${participantIdentity(name)} steps out of the square — done · just now`, { participantCount });
|
|
@@ -300,7 +299,7 @@ export const holdCommand = {
|
|
|
300
299
|
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
301
300
|
try {
|
|
302
301
|
const participant = await square.join(intent.name);
|
|
303
|
-
const result = await participant.hold(resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim());
|
|
302
|
+
const result = await participant.hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
|
|
304
303
|
await square.close();
|
|
305
304
|
const presentationSquare = await openSquare(squarePath, { clock: nowMs });
|
|
306
305
|
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
@@ -2,7 +2,7 @@ export interface CodexBoundary {
|
|
|
2
2
|
lastStop: number;
|
|
3
3
|
lastNonStop: number;
|
|
4
4
|
}
|
|
5
|
-
export declare function readCodexBoundary(threadId: string, env?: NodeJS.ProcessEnv): CodexBoundary | undefined
|
|
6
|
-
export declare function codexQueueEligible(threadId: string, env?: NodeJS.ProcessEnv): boolean
|
|
7
|
-
export declare function recordCodexBoundary(threadId: string, event: 'Stop' | 'non-stop', env?: NodeJS.ProcessEnv): void
|
|
8
|
-
export declare function clearCodexBoundary(threadId: string, env?: NodeJS.ProcessEnv): void
|
|
5
|
+
export declare function readCodexBoundary(threadId: string, env?: NodeJS.ProcessEnv): Promise<CodexBoundary | undefined>;
|
|
6
|
+
export declare function codexQueueEligible(threadId: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
7
|
+
export declare function recordCodexBoundary(threadId: string, event: 'Stop' | 'non-stop', env?: NodeJS.ProcessEnv): Promise<void>;
|
|
8
|
+
export declare function clearCodexBoundary(threadId: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { withFileLock } from './file-lock.js';
|
|
5
5
|
function statePath(env = process.env) {
|
|
6
6
|
return env.SQUARE_CODEX_BOUNDARIES || path.join(os.homedir(), '.square', 'codex-boundaries.json');
|
|
7
7
|
}
|
|
@@ -11,10 +11,10 @@ function lockPath(filePath) {
|
|
|
11
11
|
function emptyFile() {
|
|
12
12
|
return { v: 1, nextSequence: 0, threads: {} };
|
|
13
13
|
}
|
|
14
|
-
function readFile(filePath) {
|
|
14
|
+
async function readFile(filePath) {
|
|
15
15
|
let raw;
|
|
16
16
|
try {
|
|
17
|
-
raw = fs.
|
|
17
|
+
raw = await fs.promises.readFile(filePath, 'utf8');
|
|
18
18
|
}
|
|
19
19
|
catch (error) {
|
|
20
20
|
if (error.code === 'ENOENT')
|
|
@@ -43,45 +43,45 @@ function readFile(filePath) {
|
|
|
43
43
|
return emptyFile();
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
function writeFile(filePath, value) {
|
|
47
|
-
fs.
|
|
46
|
+
async function writeFile(filePath, value) {
|
|
47
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
48
48
|
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
49
|
-
fs.
|
|
50
|
-
fs.
|
|
49
|
+
await fs.promises.writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
50
|
+
await fs.promises.rename(temporary, filePath);
|
|
51
51
|
}
|
|
52
|
-
export function readCodexBoundary(threadId, env = process.env) {
|
|
52
|
+
export async function readCodexBoundary(threadId, env = process.env) {
|
|
53
53
|
if (!threadId)
|
|
54
54
|
return undefined;
|
|
55
55
|
const filePath = statePath(env);
|
|
56
|
-
return readFile(filePath).threads[threadId];
|
|
56
|
+
return (await readFile(filePath)).threads[threadId];
|
|
57
57
|
}
|
|
58
|
-
export function codexQueueEligible(threadId, env = process.env) {
|
|
59
|
-
const boundary = readCodexBoundary(threadId, env);
|
|
58
|
+
export async function codexQueueEligible(threadId, env = process.env) {
|
|
59
|
+
const boundary = await readCodexBoundary(threadId, env);
|
|
60
60
|
return boundary !== undefined && boundary.lastStop > boundary.lastNonStop;
|
|
61
61
|
}
|
|
62
|
-
export function recordCodexBoundary(threadId, event, env = process.env) {
|
|
62
|
+
export async function recordCodexBoundary(threadId, event, env = process.env) {
|
|
63
63
|
if (!threadId)
|
|
64
64
|
return;
|
|
65
65
|
const filePath = statePath(env);
|
|
66
|
-
|
|
67
|
-
const value = readFile(filePath);
|
|
66
|
+
await withFileLock(lockPath(filePath), { retryMs: 10, staleMs: 30_000 }, async () => {
|
|
67
|
+
const value = await readFile(filePath);
|
|
68
68
|
value.nextSequence += 1;
|
|
69
69
|
const current = value.threads[threadId] ?? { lastStop: 0, lastNonStop: 0 };
|
|
70
70
|
value.threads[threadId] = event === 'Stop'
|
|
71
71
|
? { ...current, lastStop: value.nextSequence }
|
|
72
72
|
: { ...current, lastNonStop: value.nextSequence };
|
|
73
|
-
writeFile(filePath, value);
|
|
73
|
+
await writeFile(filePath, value);
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
export function clearCodexBoundary(threadId, env = process.env) {
|
|
76
|
+
export async function clearCodexBoundary(threadId, env = process.env) {
|
|
77
77
|
if (!threadId)
|
|
78
78
|
return;
|
|
79
79
|
const filePath = statePath(env);
|
|
80
|
-
|
|
81
|
-
const value = readFile(filePath);
|
|
80
|
+
await withFileLock(lockPath(filePath), { retryMs: 10, staleMs: 30_000 }, async () => {
|
|
81
|
+
const value = await readFile(filePath);
|
|
82
82
|
if (!(threadId in value.threads))
|
|
83
83
|
return;
|
|
84
84
|
delete value.threads[threadId];
|
|
85
|
-
writeFile(filePath, value);
|
|
85
|
+
await writeFile(filePath, value);
|
|
86
86
|
});
|
|
87
87
|
}
|
package/dist/codex-hook.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function codexHookResponse(input, lookup = sessionInbox, env = proc
|
|
|
14
14
|
const hookEventName = CODEX_HOOK_EVENTS[input.hook_event_name];
|
|
15
15
|
if (hookEventName === undefined)
|
|
16
16
|
return undefined;
|
|
17
|
-
recordCodexBoundary(input.session_id, hookEventName === 'Stop' ? 'Stop' : 'non-stop', env);
|
|
17
|
+
await recordCodexBoundary(input.session_id, hookEventName === 'Stop' ? 'Stop' : 'non-stop', env);
|
|
18
18
|
return presentPendingAtBoundary(input.session_id, (context) => hookEventName === 'Stop'
|
|
19
19
|
? { systemMessage: context }
|
|
20
20
|
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }, lookup, env);
|
|
@@ -46,7 +46,7 @@ export async function runCodexHookAsync(inputText, env = process.env) {
|
|
|
46
46
|
if (typeof value.session_id !== 'string')
|
|
47
47
|
return runCodexHook(inputText, env);
|
|
48
48
|
if (value.hook_event_name === 'SessionStart' || value.hook_event_name === 'SessionResume') {
|
|
49
|
-
recordCodexBoundary(value.session_id, 'non-stop', env);
|
|
49
|
+
await recordCodexBoundary(value.session_id, 'non-stop', env);
|
|
50
50
|
const cwd = typeof value.cwd === 'string' ? value.cwd : process.cwd();
|
|
51
51
|
try {
|
|
52
52
|
const context = await automaticSessionStart('codex', value.session_id, cwd, env);
|
|
@@ -57,7 +57,7 @@ export async function runCodexHookAsync(inputText, env = process.env) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
if (value.hook_event_name === 'SessionEnd') {
|
|
60
|
-
clearCodexBoundary(value.session_id, env);
|
|
60
|
+
await clearCodexBoundary(value.session_id, env);
|
|
61
61
|
const cwd = typeof value.cwd === 'string' ? value.cwd : process.cwd();
|
|
62
62
|
try {
|
|
63
63
|
await automaticSessionEnd('codex', value.session_id, cwd, env);
|
package/dist/codex-queue.js
CHANGED
|
@@ -39,7 +39,7 @@ export class CodexQueueAdapter {
|
|
|
39
39
|
return { outcome: 'unavailable', signature: 'invalid_address', message: 'Codex route has no thread id.' };
|
|
40
40
|
}
|
|
41
41
|
const env = this.opts.env ?? process.env;
|
|
42
|
-
if (!codexQueueEligible(threadId, env)) {
|
|
42
|
+
if (!await codexQueueEligible(threadId, env)) {
|
|
43
43
|
return {
|
|
44
44
|
outcome: 'unavailable',
|
|
45
45
|
signature: 'boundary_not_stopped',
|
|
@@ -49,7 +49,7 @@ export class CodexQueueAdapter {
|
|
|
49
49
|
}
|
|
50
50
|
if (!(await beforeSend()))
|
|
51
51
|
return { outcome: 'cancelled' };
|
|
52
|
-
if (!codexQueueEligible(threadId, env)) {
|
|
52
|
+
if (!await codexQueueEligible(threadId, env)) {
|
|
53
53
|
return {
|
|
54
54
|
outcome: 'unavailable',
|
|
55
55
|
signature: 'boundary_not_stopped',
|
package/dist/file-lock.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface FileLockOptions {
|
|
2
2
|
retryMs: number;
|
|
3
3
|
staleMs: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
4
5
|
}
|
|
5
|
-
export declare function withFileLockSync<T>(lockPath: string, options: FileLockOptions, fn: () => T): T;
|
|
6
6
|
export declare function withFileLock<T>(lockPath: string, options: FileLockOptions, fn: () => T | Promise<T>): Promise<T>;
|