@astrosheep/square 0.3.28 → 0.3.30

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
4
4
  "description": "Native Claude Code turn-boundary delivery for Square participants",
5
5
  "author": {
6
6
  "name": "Square"
@@ -74,11 +74,11 @@ EOF
74
74
  ```bash
75
75
  square --location <square> --as <name> catch --now # take in what is pending
76
76
  square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
77
- square --location <square> --as <name> catch --mention # filter to mentions
78
- square --location <square> --as <name> catch --from <names>
77
+ square --location <square> --as <name> catch --now --mention # take in pending mentions
78
+ square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
79
79
  ```
80
80
 
81
- Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
81
+ Every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
82
82
 
83
83
  ## Listen
84
84
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -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);
@@ -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;
@@ -116,7 +116,7 @@ function configuredName() {
116
116
  validateName(value);
117
117
  return value;
118
118
  }
119
- export function parseGlobalArgs(rawArgs) {
119
+ export async function parseGlobalArgs(rawArgs) {
120
120
  const args = [...rawArgs];
121
121
  let requestedPath;
122
122
  let name;
@@ -144,7 +144,7 @@ export function parseGlobalArgs(rawArgs) {
144
144
  }
145
145
  const squarePath = requestedPath ?? configured;
146
146
  if (name === undefined && squarePath !== undefined && command !== undefined && locationIsRequired(command)) {
147
- name = localParticipantName(squarePath);
147
+ name = await localParticipantName(squarePath);
148
148
  }
149
149
  return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
150
150
  }
@@ -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;
@@ -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(),
@@ -280,7 +280,7 @@ export const doneCommand = {
280
280
  const result = await participant.done(body);
281
281
  await square.close();
282
282
  const name = result.activity.actor;
283
- recordLocalDone(name, squarePath);
283
+ await recordLocalDone(name, squarePath);
284
284
  const presentation = await openSquare(squarePath, { clock: nowMs });
285
285
  const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
286
286
  return withPathOutput(squarePath, `○ ${participantIdentity(name)} steps out of the square — done · just now`, { participantCount });
@@ -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>;
package/dist/file-lock.js CHANGED
@@ -1,13 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import fs from 'node:fs';
2
+ import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { setTimeout as sleep } from 'node:timers/promises';
5
- const lockWait = new Int32Array(new SharedArrayBuffer(4));
6
- const heldSyncLocks = new Set();
7
- function ownerState(lockPath) {
5
+ async function ownerState(lockPath) {
8
6
  let pid;
9
7
  try {
10
- pid = Number.parseInt(fs.readFileSync(lockPath, 'utf8').split('\n')[0], 10);
8
+ pid = Number.parseInt((await fs.readFile(lockPath, 'utf8')).split('\n')[0], 10);
11
9
  }
12
10
  catch {
13
11
  return 'unknown';
@@ -22,10 +20,10 @@ function ownerState(lockPath) {
22
20
  return error.code === 'ESRCH' ? 'dead' : 'alive';
23
21
  }
24
22
  }
25
- function createLock(lockPath) {
23
+ async function createLock(lockPath) {
26
24
  let fd;
27
25
  try {
28
- fd = fs.openSync(lockPath, 'wx', 0o600);
26
+ fd = await fs.open(lockPath, 'wx', 0o600);
29
27
  }
30
28
  catch (error) {
31
29
  if (error.code === 'EEXIST')
@@ -34,79 +32,53 @@ function createLock(lockPath) {
34
32
  }
35
33
  const token = `${process.pid}\n${Date.now()}\n${randomUUID()}\n`;
36
34
  try {
37
- fs.writeFileSync(fd, token, 'utf8');
35
+ await fd.writeFile(token, 'utf8');
38
36
  return token;
39
37
  }
40
38
  catch (error) {
41
- try {
42
- fs.unlinkSync(lockPath);
43
- }
44
- catch { }
39
+ await fs.unlink(lockPath).catch(() => undefined);
45
40
  throw error;
46
41
  }
47
42
  finally {
48
- fs.closeSync(fd);
43
+ await fd.close();
49
44
  }
50
45
  }
51
- function reclaimLock(lockPath, staleMs) {
46
+ async function reclaimLock(lockPath, staleMs) {
52
47
  try {
53
- const stale = Date.now() - fs.statSync(lockPath).mtimeMs > staleMs;
54
- if (ownerState(lockPath) !== 'dead' && !stale)
48
+ const stale = Date.now() - (await fs.stat(lockPath)).mtimeMs > staleMs;
49
+ if (await ownerState(lockPath) !== 'dead' && !stale)
55
50
  return false;
56
- fs.unlinkSync(lockPath);
51
+ await fs.unlink(lockPath);
57
52
  return true;
58
53
  }
59
54
  catch (error) {
60
55
  return error.code === 'ENOENT';
61
56
  }
62
57
  }
63
- function releaseLock(lockPath, token) {
58
+ async function releaseLock(lockPath, token) {
64
59
  try {
65
- if (fs.readFileSync(lockPath, 'utf8') === token)
66
- fs.unlinkSync(lockPath);
60
+ if (await fs.readFile(lockPath, 'utf8') === token)
61
+ await fs.unlink(lockPath);
67
62
  }
68
63
  catch { }
69
64
  }
70
- function prepare(lockPath) {
71
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
72
- }
73
- export function withFileLockSync(lockPath, options, fn) {
74
- if (heldSyncLocks.has(lockPath))
75
- throw new Error(`Reentrant file lock: ${lockPath}`);
76
- prepare(lockPath);
77
- let token;
78
- while (token === undefined) {
79
- token = createLock(lockPath);
80
- if (token !== undefined)
81
- break;
82
- if (reclaimLock(lockPath, options.staleMs))
83
- continue;
84
- Atomics.wait(lockWait, 0, 0, options.retryMs);
85
- }
86
- heldSyncLocks.add(lockPath);
87
- try {
88
- return fn();
89
- }
90
- finally {
91
- heldSyncLocks.delete(lockPath);
92
- releaseLock(lockPath, token);
93
- }
94
- }
95
65
  export async function withFileLock(lockPath, options, fn) {
96
- prepare(lockPath);
66
+ await fs.mkdir(path.dirname(lockPath), { recursive: true });
97
67
  let token;
98
68
  while (token === undefined) {
99
- token = createLock(lockPath);
69
+ if (options.signal?.aborted)
70
+ throw options.signal.reason ?? new Error('File lock acquisition aborted');
71
+ token = await createLock(lockPath);
100
72
  if (token !== undefined)
101
73
  break;
102
- if (reclaimLock(lockPath, options.staleMs))
74
+ if (await reclaimLock(lockPath, options.staleMs))
103
75
  continue;
104
- await sleep(options.retryMs);
76
+ await sleep(options.retryMs, undefined, { signal: options.signal });
105
77
  }
106
78
  try {
107
79
  return await fn();
108
80
  }
109
81
  finally {
110
- releaseLock(lockPath, token);
82
+ await releaseLock(lockPath, token);
111
83
  }
112
84
  }
package/dist/inbox.js CHANGED
@@ -18,7 +18,7 @@ function withoutExcluded(inbox, excludeKeys) {
18
18
  }
19
19
  export async function sessionInbox(sessionId) {
20
20
  const inbox = [];
21
- for (const binding of lookupSessionBindings(sessionId)) {
21
+ for (const binding of await lookupSessionBindings(sessionId)) {
22
22
  let square;
23
23
  try {
24
24
  square = await openSquare(binding.squarePath);
@@ -53,7 +53,7 @@ export async function waitForSessionPending(sessionId, timeoutMs, options = {})
53
53
  }
54
54
  if (timeoutMs <= 0 || options.signal?.aborted)
55
55
  return [];
56
- const bindings = lookupSessionBindings(sessionId);
56
+ const bindings = await lookupSessionBindings(sessionId);
57
57
  const paths = [...new Set(bindings.map((binding) => binding.squarePath))];
58
58
  let aborted = false;
59
59
  let projectAfterReady = !options.skipImmediate;
@@ -28,6 +28,6 @@ export interface SweepPendingNotificationsOptions extends WorkerLaunchOptions {
28
28
  limit?: number;
29
29
  }
30
30
  /** Select sweep candidates from one frozen snapshot and one delivery replay. */
31
- export declare function pendingNotificationSweepFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, limit: number, deriveDelivery?: (snapshot: SquareState) => DeliveryModel): number[];
31
+ export declare function pendingNotificationSweepFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, limit: number, deriveDelivery?: (snapshot: SquareState) => DeliveryModel): Promise<number[]>;
32
32
  /** Reconsider old pending attention at a bounded action boundary using the existing worker. */
33
33
  export declare function sweepPendingNotifications(squarePath: string, opts?: SweepPendingNotificationsOptions): Promise<number[]>;
@@ -53,7 +53,7 @@ function renderWakePayload(request, body, kind) {
53
53
  ].join('\n');
54
54
  }
55
55
  async function waitForCatch(route, request, body) {
56
- const binding = lookupParticipant(request.squarePath, request.recipient)
56
+ const binding = (await lookupParticipant(request.squarePath, request.recipient))
57
57
  .find((item) => item.ownerId === route.ownerId);
58
58
  const activeCatch = binding && (await sessionInbox(binding.sessionId))
59
59
  .find((item) => item.name === request.recipient)?.catchLease;
@@ -68,7 +68,7 @@ async function waitForCatch(route, request, body) {
68
68
  while (Date.now() < deadline) {
69
69
  if (await hasDeliveredNotification(request.squarePath, request.recipient, request.actIndex))
70
70
  return true;
71
- const currentBinding = lookupParticipant(request.squarePath, request.recipient)
71
+ const currentBinding = (await lookupParticipant(request.squarePath, request.recipient))
72
72
  .find((item) => item.ownerId === route.ownerId);
73
73
  const lease = currentBinding && (await sessionInbox(currentBinding.sessionId))
74
74
  .find((item) => item.name === request.recipient)?.catchLease;
@@ -101,7 +101,7 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
101
101
  try {
102
102
  const recipient = (await resolveParticipant(square, name)).name;
103
103
  const index = notificationIndex(ref);
104
- return await notificationDelivered(square, recipient, index) || hasPresentedAttention(squarePath, recipient, index, env);
104
+ return await notificationDelivered(square, recipient, index) || await hasPresentedAttention(squarePath, recipient, index, env);
105
105
  }
106
106
  finally {
107
107
  await closeOpenSquare(square);
@@ -162,7 +162,7 @@ async function processNotification(squarePath, notification, opts) {
162
162
  return;
163
163
  }
164
164
  if (claim.type === 'ambiguous') {
165
- const recovered = recordRecoveredUnknown(attention, claim.lease, env);
165
+ const recovered = await recordRecoveredUnknown(attention, claim.lease, env);
166
166
  if (recovered !== undefined) {
167
167
  await releaseNotifyLease(square, notification.recipient, notification.item.index, claim.lease.leaseId);
168
168
  }
@@ -185,7 +185,7 @@ async function processNotification(squarePath, notification, opts) {
185
185
  route: notification.route,
186
186
  };
187
187
  await port.dispatch(evidence.attemptableRoutes, (route) => renderWakePayload(request, notification.item.body, route.kind), {
188
- nextAttemptN: () => nextWakeAttemptNumber(attention, { env, now: now() }),
188
+ nextAttemptN: async () => nextWakeAttemptNumber(attention, { env, now: now() }),
189
189
  beforeSend: async (route, attemptN) => {
190
190
  if (await waitForCatch(route, request, notification.item.body))
191
191
  return false;
@@ -207,7 +207,7 @@ async function processNotification(squarePath, notification, opts) {
207
207
  await transitionNotifyLease(square, notification.recipient, notification.item.index, leaseId, 'claimed');
208
208
  releaseLease = true;
209
209
  }
210
- recordWakeAttempt({
210
+ await recordWakeAttempt({
211
211
  attention,
212
212
  routeKind: route.kind,
213
213
  outcome: outcome.outcome,
@@ -224,7 +224,7 @@ async function processNotification(squarePath, notification, opts) {
224
224
  releaseLease = true;
225
225
  },
226
226
  invalidate: async (route) => {
227
- retireWakeRoute(route, { env, at: now() });
227
+ await retireWakeRoute(route, { env, at: now() });
228
228
  },
229
229
  });
230
230
  }
@@ -257,10 +257,10 @@ export function wakeNotifierForSquare(squarePath, env = process.env) {
257
257
  };
258
258
  }
259
259
  /** Select sweep candidates from one frozen snapshot and one delivery replay. */
260
- export function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery = deriveDeliveryModel) {
260
+ export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery = deriveDeliveryModel) {
261
261
  const delivery = deriveDelivery(state);
262
262
  const pending = pendingDeliveriesFromState(state, delivery);
263
- const evidence = wakeEvidenceProjectionFromState(squarePath, state, now, env, delivery);
263
+ const evidence = await wakeEvidenceProjectionFromState(squarePath, state, now, env, delivery);
264
264
  const indexes = new Set();
265
265
  for (const recipient of pending) {
266
266
  for (const note of recipient.notifications) {
@@ -288,7 +288,7 @@ export async function sweepPendingNotifications(squarePath, opts = {}) {
288
288
  finally {
289
289
  await closeOpenSquare(square);
290
290
  }
291
- const selected = pendingNotificationSweepFromState(squarePath, state, now, env, limit);
291
+ const selected = await pendingNotificationSweepFromState(squarePath, state, now, env, limit);
292
292
  const workerPath = fileURLToPath(new URL('./cmd/notify-once.js', import.meta.url));
293
293
  for (const actIndex of selected) {
294
294
  (opts.launchWorker ?? launchWorker)(workerPath, ['--location', squarePath, '--act-index', String(actIndex)]);
@@ -6,16 +6,8 @@ export interface PresentedAttention {
6
6
  actIndex: number;
7
7
  }
8
8
  export declare function presentedPath(env?: NodeJS.ProcessEnv): string;
9
- /** Read the current presentation facts once for a derived evidence projection. */
10
- export declare function readPresentedAttentions(env?: NodeJS.ProcessEnv, now?: number): PresentedAttention[];
11
- export declare function hasPresentedForOwner(ownerId: string, squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): boolean;
12
- /** True when any current participant owner has already received this attention. */
13
- export declare function hasPresentedAttention(squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): boolean;
14
- /** Record presentation by a transport that delivered the bounded attention body. */
15
- export declare function recordPresentedForOwner(ownerId: string, squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, at?: number): void;
16
- /**
17
- * Serialize presentation only for the affected participants. Delivery runs
18
- * outside the short ledger-write lock, so unrelated owners never wait on an
19
- * adapter. A throwing or rejecting callback leaves no row and remains unpresented.
20
- */
21
- export declare function presentOnce<T>(sessionId: string, lookup: (sessionId: string) => InboxMembership[] | Promise<InboxMembership[]>, deliver: (inbox: InboxMembership[]) => T | Promise<T>, env?: NodeJS.ProcessEnv, at?: number): Promise<T | undefined>;
9
+ export declare function readPresentedAttentions(env?: NodeJS.ProcessEnv, now?: number): Promise<PresentedAttention[]>;
10
+ export declare function hasPresentedForOwner(ownerId: string, squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): Promise<boolean>;
11
+ export declare function hasPresentedAttention(squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): Promise<boolean>;
12
+ export declare function recordPresentedForOwner(ownerId: string, squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, at?: number): Promise<void>;
13
+ export declare function presentOnce<T>(sessionId: string, lookup: (sessionId: string) => InboxMembership[] | Promise<InboxMembership[]>, deliver: (inbox: InboxMembership[]) => T | Promise<T>, env?: NodeJS.ProcessEnv, at?: number, signal?: AbortSignal): Promise<T | undefined>;