@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.
Files changed (49) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  3. package/dist/artifact.d.ts +4 -4
  4. package/dist/artifact.js +24 -19
  5. package/dist/automatic-session.js +18 -7
  6. package/dist/boundary-presentation.d.ts +1 -1
  7. package/dist/boundary-presentation.js +2 -2
  8. package/dist/cli/context.d.ts +4 -4
  9. package/dist/cli/context.js +11 -9
  10. package/dist/cli/maintenance-commands.js +2 -2
  11. package/dist/cli/observation-commands.js +2 -2
  12. package/dist/cli/program.js +1 -1
  13. package/dist/cli/square-commands.js +15 -16
  14. package/dist/codex-boundary-state.d.ts +4 -4
  15. package/dist/codex-boundary-state.js +19 -19
  16. package/dist/codex-hook.js +3 -3
  17. package/dist/codex-queue.js +2 -2
  18. package/dist/file-lock.d.ts +1 -1
  19. package/dist/file-lock.js +22 -50
  20. package/dist/harness-links.d.ts +2 -1
  21. package/dist/harness-links.js +41 -12
  22. package/dist/harness.js +11 -5
  23. package/dist/inbox.js +2 -2
  24. package/dist/list.js +3 -3
  25. package/dist/notifications.d.ts +1 -1
  26. package/dist/notifications.js +10 -10
  27. package/dist/opencode.d.ts +19 -0
  28. package/dist/opencode.js +49 -0
  29. package/dist/presented.d.ts +5 -13
  30. package/dist/presented.js +48 -158
  31. package/dist/registry.d.ts +18 -30
  32. package/dist/registry.js +89 -320
  33. package/dist/routes.d.ts +6 -6
  34. package/dist/routes.js +20 -20
  35. package/dist/square-file-adapter.d.ts +1 -1
  36. package/dist/square-file-adapter.js +5 -9
  37. package/dist/square-storage.d.ts +3 -3
  38. package/dist/square-storage.js +23 -20
  39. package/dist/square-wiring.js +1 -1
  40. package/dist/wake-attempts.d.ts +6 -6
  41. package/dist/wake-attempts.js +39 -31
  42. package/dist/wake-evidence.d.ts +1 -1
  43. package/dist/wake-evidence.js +11 -11
  44. package/dist/wake-port.d.ts +1 -1
  45. package/dist/wake-port.js +1 -1
  46. package/dist/watch.js +1 -1
  47. package/extensions/square-pi.js +30 -3
  48. package/package.json +5 -1
  49. package/extensions/square-opencode.js +0 -48
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "description": "Native Claude Code turn-boundary delivery for Square participants",
5
5
  "author": {
6
6
  "name": "Square"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -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
- return fs.readFileSync(new URL(`../guides/${name}.md`, import.meta.url), 'utf8').trim();
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.readFileSync(squarePath);
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.mkdirSync(path.dirname(target), { recursive: true });
314
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
310
315
  try {
311
- fs.writeFileSync(temporary, bytes);
312
- fs.renameSync(temporary, target);
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.unlinkSync(temporary);
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.openSync(squarePath, 'r');
340
+ descriptor = await fs.promises.open(squarePath, 'r');
336
341
  const magic = Buffer.alloc(SQUARE_MAGIC.length);
337
- if (fs.readSync(descriptor, magic, 0, magic.length, 0) !== magic.length || !magic.equals(SQUARE_MAGIC)) {
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
- fs.closeSync(descriptor);
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 (!fs.existsSync(squarePath))
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 alreadyBound = lookupSessionBindings(sessionId).some((binding) => canonicalSquarePath(binding.squarePath) === canonicalSquarePath(squarePath) && binding.name === name);
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 binding = lookupSessionBindings(sessionId).find((item) => canonicalSquarePath(item.squarePath) === canonicalSquarePath(squarePath) && item.channel === channel);
51
- if (binding === undefined || !fs.existsSync(squarePath))
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);
@@ -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 readStdinSync(): string;
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;
@@ -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 readStdinSync() {
5
+ export async function readStdin() {
7
6
  try {
8
- return fs.readFileSync(0, 'utf8');
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 === '-' ? readStdinSync() : 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 = readStdinSync();
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, readStdinSync, requireParticipant, requireSquarePath, requireValue, usage, } from './context.js';
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(readStdinSync()),
473
+ execute: async () => runHook(await readStdin()),
474
474
  present: (result) => process.stdout.write(result),
475
475
  };
476
476
  }
@@ -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, readPipedBodyFallback, readStdinSync, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
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
- const snippet = readStdinSync();
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 createSquare(squarePath, intent.options, intent.snippet);
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
- const piped = readPipedBodyFallback();
168
- if (piped !== undefined)
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] : readPipedBodyFallback() };
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 { withFileLockSync } from './file-lock.js';
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.readFileSync(filePath, 'utf8');
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.mkdirSync(path.dirname(filePath), { recursive: true });
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.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
50
- fs.renameSync(temporary, filePath);
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
- withFileLockSync(lockPath(filePath), { retryMs: 10, staleMs: 30_000 }, () => {
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
- withFileLockSync(lockPath(filePath), { retryMs: 10, staleMs: 30_000 }, () => {
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
  }
@@ -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);
@@ -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',
@@ -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>;