@astrosheep/square 0.3.29 → 0.3.31

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 (77) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/claude-plugin/hooks/hooks.json +0 -3
  3. package/claude-plugin/skills/square/SKILL.md +8 -7
  4. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  5. package/dist/activity.js +7 -3
  6. package/dist/artifact.js +7 -50
  7. package/dist/automatic-session.js +31 -14
  8. package/dist/boundary-presentation.d.ts +1 -1
  9. package/dist/boundary-presentation.js +58 -14
  10. package/dist/catch-decisions.d.ts +17 -0
  11. package/dist/catch-decisions.js +53 -0
  12. package/dist/claude-hook.d.ts +1 -1
  13. package/dist/cli/context.js +10 -2
  14. package/dist/cli/observation-commands.d.ts +5 -1
  15. package/dist/cli/observation-commands.js +68 -33
  16. package/dist/cli/square-commands.js +17 -10
  17. package/dist/codex-hook.d.ts +1 -1
  18. package/dist/decisions.js +2 -2
  19. package/dist/delivery-health.d.ts +1 -1
  20. package/dist/delivery-operations.d.ts +25 -0
  21. package/dist/delivery-operations.js +124 -0
  22. package/dist/help.js +1 -1
  23. package/dist/host-ledger-file-adapter.d.ts +34 -0
  24. package/dist/host-ledger-file-adapter.js +165 -0
  25. package/dist/host-ledger.d.ts +160 -0
  26. package/dist/host-ledger.js +1 -0
  27. package/dist/inbox.d.ts +2 -2
  28. package/dist/inbox.js +22 -14
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +1 -0
  31. package/dist/landing.d.ts +18 -14
  32. package/dist/landing.js +29 -113
  33. package/dist/model.d.ts +6 -17
  34. package/dist/notifications.d.ts +6 -10
  35. package/dist/notifications.js +71 -192
  36. package/dist/open-square.d.ts +4 -4
  37. package/dist/open-square.js +1 -1
  38. package/dist/ports.d.ts +127 -0
  39. package/dist/ports.js +1 -0
  40. package/dist/presence.d.ts +1 -2
  41. package/dist/presence.js +13 -46
  42. package/dist/presentation-operations.d.ts +3 -0
  43. package/dist/presentation-operations.js +51 -0
  44. package/dist/presentation.d.ts +2 -2
  45. package/dist/presentation.js +11 -7
  46. package/dist/presented.d.ts +1 -12
  47. package/dist/presented.js +6 -75
  48. package/dist/registry.d.ts +10 -10
  49. package/dist/registry.js +48 -113
  50. package/dist/routes.d.ts +6 -33
  51. package/dist/routes.js +6 -170
  52. package/dist/runtime.d.ts +1 -1
  53. package/dist/runtime.js +3 -4
  54. package/dist/square-actions.d.ts +32 -0
  55. package/dist/square-actions.js +167 -0
  56. package/dist/square-facade.d.ts +7 -7
  57. package/dist/square-file-adapter.d.ts +4 -3
  58. package/dist/square-file-adapter.js +22 -5
  59. package/dist/square-projections.d.ts +68 -0
  60. package/dist/square-projections.js +87 -0
  61. package/dist/square-storage.d.ts +2 -2
  62. package/dist/square-storage.js +14 -9
  63. package/dist/square-wiring.d.ts +3 -3
  64. package/dist/square-wiring.js +44 -29
  65. package/dist/views.d.ts +8 -2
  66. package/dist/views.js +28 -24
  67. package/dist/wake-attempts.d.ts +29 -22
  68. package/dist/wake-attempts.js +36 -119
  69. package/dist/wake-evidence.d.ts +6 -18
  70. package/dist/wake-evidence.js +17 -80
  71. package/dist/wakes.d.ts +2 -16
  72. package/dist/wakes.js +7 -27
  73. package/dist/watch.js +2 -3
  74. package/extensions/square-pi.js +7 -2
  75. package/package.json +1 -1
  76. package/skills/brainstorm/SKILL.md +2 -2
  77. package/skills/square/SKILL.md +8 -7
@@ -1,112 +1,48 @@
1
- import fs from 'node:fs';
2
1
  import os from 'node:os';
3
2
  import path from 'node:path';
4
- import { withFileLock } from './file-lock.js';
5
3
  import { isWakeRouteKind, nameKey } from './model.js';
6
4
  import { canonicalSquarePath } from './registry.js';
7
5
  import { formatActivityId, parseActivityId } from './square-core.js';
8
- const RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
9
- const LOCK_STALE_MS = 5 * 60 * 1000;
10
- const LOCK_RETRY_MS = 10;
6
+ import { createHostLedgerPort } from './host-ledger-file-adapter.js';
7
+ import { terminalWakeEvidence } from './square-projections.js';
8
+ export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
11
9
  const VALID_OUTCOMES = new Set(['accepted', 'unknown', 'failed']);
10
+ function ledger(env) {
11
+ return createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? path.dirname(wakeAttemptsPath(env)), readableScopes: ['user'], writableScope: 'user' });
12
+ }
12
13
  export function wakeAttemptsPath(env = process.env) {
13
14
  return env.SQUARE_WAKE_ATTEMPTS || path.join(os.homedir(), '.square', 'wake-attempts.ndjsonl');
14
15
  }
15
16
  export async function wakeAttentionKey(attention) {
16
17
  return JSON.stringify([await canonicalSquarePath(attention.squarePath), formatActivityId(attention.actIndex), nameKey(attention.recipient)]);
17
18
  }
18
- function parseRow(raw, now) {
19
- let value;
20
- try {
21
- value = JSON.parse(raw);
22
- }
23
- catch {
24
- return undefined;
25
- }
26
- if (value === null || typeof value !== 'object')
27
- return undefined;
28
- const row = value;
29
- if (row.v !== 1 || typeof row.ts !== 'number' || !Number.isFinite(row.ts) || row.ts > now || now - row.ts > RETENTION_MS ||
30
- row.attention === undefined || typeof row.attention.square_path !== 'string' || row.attention.square_path === '' ||
31
- typeof row.attention.act_id !== 'string' || parseActivityId(row.attention.act_id) === undefined ||
32
- typeof row.attention.recipient !== 'string' || row.attention.recipient === '' ||
33
- typeof row.outcome !== 'string' || !VALID_OUTCOMES.has(row.outcome) ||
34
- typeof row.attempt_n !== 'number' || !Number.isInteger(row.attempt_n) || row.attempt_n <= 0 ||
35
- !isWakeRouteKind(row.route_kind) ||
36
- (row.signature !== undefined && typeof row.signature !== 'string') ||
37
- (row.outcome !== 'accepted' && (typeof row.signature !== 'string' || row.signature === '')) ||
38
- (row.message !== undefined && typeof row.message !== 'string'))
39
- return undefined;
40
- return row;
19
+ export async function claimWakeDispatch(attention, leaseId, leaseMs, env = process.env, at = Date.now(), session) {
20
+ return ledger(env).claimWakeDispatch({ attention, leaseId, leaseMs, at, session });
41
21
  }
42
- async function readRowsFromFile(filePath, now) {
43
- let raw;
44
- try {
45
- raw = await fs.promises.readFile(filePath, 'utf8');
46
- }
47
- catch (error) {
48
- if (error.code === 'ENOENT')
49
- return [];
50
- throw error;
51
- }
52
- return raw.split('\n').filter(Boolean).map((line) => parseRow(line, now)).filter((row) => row !== undefined);
22
+ export async function transitionWakeDispatch(attention, leaseId, phase, leaseMs, routeKind, attemptN, session, env = process.env, at = Date.now()) {
23
+ return ledger(env).transitionWakeDispatch({ attention, leaseId, phase, leaseMs, routeKind, attemptN, session, at });
53
24
  }
54
- async function readRows(env, now) {
55
- return readRowsFromFile(wakeAttemptsPath(env), now);
25
+ export async function releaseWakeDispatch(attention, leaseId, env = process.env, at = Date.now()) {
26
+ await ledger(env).releaseWakeDispatch({ attention, leaseId, at });
56
27
  }
57
- async function writeRows(filePath, rows) {
58
- await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
59
- const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
60
- await fs.promises.writeFile(temporary, rows.map((row) => JSON.stringify(row)).join('\n') + (rows.length ? '\n' : ''), {
61
- mode: 0o600,
28
+ async function readRows(env, now) {
29
+ const records = await ledger(env).listWakeAttempts({ now });
30
+ return records.flatMap((record) => {
31
+ const actIndex = parseActivityId(record.activity);
32
+ if (actIndex === undefined || !isWakeRouteKind(record.routeKind) || !VALID_OUTCOMES.has(record.outcome) || typeof record.attemptN !== 'number')
33
+ return [];
34
+ return [{ at: record.at ?? now, attention: { squarePath: record.location, actIndex, recipient: record.participant }, routeKind: record.routeKind, outcome: record.outcome, attemptN: record.attemptN, ...(record.signature === undefined ? {} : { signature: record.signature }), ...(record.session === undefined ? {} : { session: record.session }), ...(record.message === undefined ? {} : { message: record.message }), ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }) }];
62
35
  });
63
- await fs.promises.rename(temporary, filePath);
64
- }
65
- async function fromRow(row) {
66
- const actIndex = parseActivityId(row.attention.act_id);
67
- if (actIndex === undefined)
68
- throw new Error(`Invalid wake activity id: ${row.attention.act_id}`);
69
- return {
70
- at: row.ts,
71
- attention: {
72
- squarePath: await canonicalSquarePath(row.attention.square_path),
73
- actIndex,
74
- recipient: row.attention.recipient,
75
- },
76
- routeKind: row.route_kind,
77
- outcome: row.outcome,
78
- ...(row.signature === undefined ? {} : { signature: row.signature }),
79
- attemptN: row.attempt_n,
80
- ...(row.message === undefined ? {} : { message: row.message }),
81
- ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
82
- };
83
36
  }
84
37
  export async function readWakeAttempts(opts = {}) {
85
38
  const now = opts.now ?? Date.now();
86
39
  const expected = opts.attention === undefined ? undefined : await wakeAttentionKey(opts.attention);
87
- const attempts = await Promise.all((await readRows(opts.env ?? process.env, now)).map(fromRow));
40
+ const attempts = await readRows(opts.env ?? process.env, now);
41
+ const scoped = opts.sessionId === undefined ? attempts : attempts.filter((attempt) => attempt.session === opts.sessionId);
88
42
  if (expected === undefined)
89
- return attempts;
90
- const keys = await Promise.all(attempts.map((attempt) => wakeAttentionKey(attempt.attention)));
91
- return attempts.filter((_, index) => keys[index] === expected);
92
- }
93
- export function terminalWakeEvidence(attempts) {
94
- return attempts.findLast((attempt) => attempt.outcome === 'accepted' || attempt.outcome === 'unknown');
95
- }
96
- export async function terminalWakeAttempt(attention, opts = {}) {
97
- return terminalWakeEvidence(await readWakeAttempts({ attention, ...opts }));
98
- }
99
- export function isWakeRouteAttemptable(route, attempts) {
100
- if (terminalWakeEvidence(attempts) !== undefined)
101
- return false;
102
- const failed = attempts.findLast((attempt) => attempt.routeKind === route.kind && attempt.outcome === 'failed');
103
- return failed === undefined || route.updatedAt > failed.at;
104
- }
105
- export function hasAttemptableWakeRoute(routes, attempts) {
106
- return routes.some((route) => isWakeRouteAttemptable(route, attempts));
107
- }
108
- export async function nextWakeAttemptNumber(attention, opts = {}) {
109
- return (await readWakeAttempts({ attention, ...opts })).reduce((highest, attempt) => Math.max(highest, attempt.attemptN), 0) + 1;
43
+ return scoped;
44
+ const keys = await Promise.all(scoped.map((attempt) => wakeAttentionKey(attempt.attention)));
45
+ return scoped.filter((_, index) => keys[index] === expected);
110
46
  }
111
47
  function redact(value, secret) {
112
48
  if (typeof value === 'string') {
@@ -120,24 +56,6 @@ function redact(value, secret) {
120
56
  }
121
57
  return value;
122
58
  }
123
- async function toRow(attempt, env) {
124
- const safe = redact(attempt, env.PASEO_PASSWORD);
125
- return {
126
- v: 1,
127
- ts: safe.at,
128
- attention: {
129
- square_path: await canonicalSquarePath(safe.attention.squarePath),
130
- act_id: formatActivityId(safe.attention.actIndex),
131
- recipient: safe.attention.recipient,
132
- },
133
- route_kind: safe.routeKind,
134
- outcome: safe.outcome,
135
- ...(safe.signature === undefined ? {} : { signature: safe.signature }),
136
- attempt_n: safe.attemptN,
137
- ...(safe.message === undefined ? {} : { message: safe.message }),
138
- ...(safe.diagnostic === undefined ? {} : { diagnostic: safe.diagnostic }),
139
- };
140
- }
141
59
  export async function recordWakeAttempt(attempt, env = process.env) {
142
60
  const value = { ...attempt, at: attempt.at ?? Date.now() };
143
61
  if (!isWakeRouteKind(value.routeKind))
@@ -145,25 +63,24 @@ export async function recordWakeAttempt(attempt, env = process.env) {
145
63
  if (value.outcome !== 'accepted' && !value.signature) {
146
64
  throw new Error(`${value.outcome} wake attempts require a transport signature.`);
147
65
  }
148
- const file = wakeAttemptsPath(env);
149
- await withFileLock(`${file}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
150
- await writeRows(file, [...await readRowsFromFile(file, value.at), await toRow(value, env)]);
151
- });
66
+ const safe = redact(value, env.PASEO_PASSWORD);
67
+ await ledger(env).appendWakeAttempt({ location: safe.attention.squarePath, participant: safe.attention.recipient, session: safe.session ?? safe.signature ?? `route:${safe.routeKind}`, activity: formatActivityId(safe.attention.actIndex), kind: 'wake', outcome: safe.outcome, routeKind: safe.routeKind, signature: safe.signature, attemptN: safe.attemptN, message: safe.message, diagnostic: safe.diagnostic, at: safe.at });
152
68
  return value;
153
69
  }
154
70
  export async function recordRecoveredUnknown(attention, lease, env = process.env, at = Date.now()) {
155
71
  const routeKind = lease.routeKind;
156
72
  if (lease.attemptN === undefined || !isWakeRouteKind(routeKind))
157
73
  return undefined;
158
- const file = wakeAttemptsPath(env);
159
- return withFileLock(`${file}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
160
- const rows = await readRowsFromFile(file, at);
74
+ const rows = await readRows(env, at);
75
+ {
161
76
  const expected = await wakeAttentionKey(attention);
162
77
  const attempts = [];
163
- for (const row of rows) {
164
- const attempt = await fromRow(row);
165
- if (await wakeAttentionKey(attempt.attention) === expected)
166
- attempts.push(attempt);
78
+ for (const attempt of rows) {
79
+ if (await wakeAttentionKey(attempt.attention) !== expected)
80
+ continue;
81
+ if (lease.session !== undefined && attempt.session !== lease.session)
82
+ continue;
83
+ attempts.push(attempt);
167
84
  }
168
85
  const terminal = terminalWakeEvidence(attempts);
169
86
  if (terminal !== undefined)
@@ -177,7 +94,7 @@ export async function recordRecoveredUnknown(attention, lease, env = process.env
177
94
  attemptN: lease.attemptN,
178
95
  message: 'The notification worker ended after dispatch began; transport acceptance is unknown.',
179
96
  };
180
- await writeRows(file, [...rows, await toRow(value, env)]);
97
+ await ledger(env).appendWakeAttempt({ location: attention.squarePath, participant: attention.recipient, session: lease.session ?? value.signature, activity: formatActivityId(attention.actIndex), kind: 'wake', outcome: 'unknown', routeKind, signature: value.signature, attemptN: value.attemptN, message: value.message, at });
181
98
  return value;
182
- });
99
+ }
183
100
  }
@@ -1,21 +1,9 @@
1
- import { type WakeRoute } from './model.js';
2
- import { type DeliveryModel } from './delivery.js';
3
- import { type SquareState } from './model.js';
4
- import { type WakeAttempt } from './wake-attempts.js';
5
- export interface WakeEvidence {
6
- delivered: boolean;
7
- notified: boolean;
8
- presented: boolean;
9
- attempts: WakeAttempt[];
10
- terminal?: WakeAttempt;
11
- attemptableRoutes: WakeRoute[];
12
- }
13
- export interface WakeEvidenceProjection {
14
- evidence(recipient: string, actIndex: number): WakeEvidence;
15
- }
16
- /** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
1
+ import type { SquareState } from './model.js';
2
+ import { wakeIsEligible, type WakeEvidence, type WakeEvidenceProjection } from './square-projections.js';
3
+ import type { DeliveryModel } from './delivery.js';
4
+ export type { WakeEvidence, WakeEvidenceProjection };
5
+ export { wakeIsEligible };
6
+ /** Adapter entry: open the artifact, assemble host ports, and project wake evidence. */
17
7
  export declare function wakeEvidenceProjection(squarePath: string, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidenceProjection>;
18
8
  export declare function wakeEvidenceProjectionFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, delivery?: DeliveryModel): Promise<WakeEvidenceProjection>;
19
- /** Project every wake decision from the same primary evidence. */
20
9
  export declare function wakeEvidence(squarePath: string, recipient: string, actIndex: number, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidence>;
21
- export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
@@ -1,95 +1,32 @@
1
- import { deriveDeliveryModel } from './delivery.js';
2
- import { nameKey } from './model.js';
3
- import { readPresentedAttentions } from './presented.js';
4
- import { canonicalSquarePath, readActiveBindings } from './registry.js';
5
- import { readWakeRoutes } from './routes.js';
6
- import { isWakeRouteAttemptable, readWakeAttempts, terminalWakeEvidence, } from './wake-attempts.js';
1
+ import path from 'node:path';
2
+ import { createHostLedgerPort } from './host-ledger-file-adapter.js';
7
3
  import { openSquare } from './square-file-adapter.js';
8
4
  import { closeOpenSquare } from './open-square.js';
9
- import { observationFor } from './runtime.js';
10
- import { entryPresentation } from './views.js';
11
- async function attentionKey(squarePath, recipient, actIndex) {
12
- return JSON.stringify([await canonicalSquarePath(squarePath), nameKey(recipient), actIndex]);
5
+ import { projectWakeEvidenceFromState, wakeIsEligible, } from './square-projections.js';
6
+ export { wakeIsEligible };
7
+ function hostLedgerForEnv(env) {
8
+ const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
9
+ return createHostLedgerPort({
10
+ userPath: env.SQUARE_HOST_LEDGER_USER ?? root,
11
+ localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root,
12
+ readableScopes: ['user'],
13
+ writableScope: 'user',
14
+ });
13
15
  }
14
- async function projectionFromState(squarePath, state, now, env, delivery = deriveDeliveryModel(state)) {
15
- const canonicalPath = await canonicalSquarePath(squarePath);
16
- const owners = new Map();
17
- for (const binding of await readActiveBindings(now)) {
18
- if (binding.squarePath !== canonicalPath)
19
- continue;
20
- const key = nameKey(binding.name);
21
- const recipientOwners = owners.get(key) ?? new Set();
22
- recipientOwners.add(binding.ownerId);
23
- owners.set(key, recipientOwners);
24
- }
25
- const routesByOwner = new Map();
26
- for (const route of await readWakeRoutes({ freshOnly: true, now, env })) {
27
- const routes = routesByOwner.get(route.ownerId) ?? [];
28
- routes.push(route);
29
- routesByOwner.set(route.ownerId, routes);
30
- }
31
- const attemptsByAttention = new Map();
32
- for (const attempt of await readWakeAttempts({ env, now })) {
33
- const key = await attentionKey(attempt.attention.squarePath, attempt.attention.recipient, attempt.attention.actIndex);
34
- const attempts = attemptsByAttention.get(key) ?? [];
35
- attempts.push(attempt);
36
- attemptsByAttention.set(key, attempts);
37
- }
38
- const presentedByAttention = new Map();
39
- for (const presented of await readPresentedAttentions(env, now)) {
40
- const key = await attentionKey(presented.squarePath, presented.name, presented.actIndex);
41
- const presentedOwners = presentedByAttention.get(key) ?? new Set();
42
- presentedOwners.add(presented.ownerId);
43
- presentedByAttention.set(key, presentedOwners);
44
- }
45
- return {
46
- evidence(recipient, actIndex) {
47
- const recipientOwners = owners.get(nameKey(recipient)) ?? new Set();
48
- const key = JSON.stringify([canonicalPath, nameKey(recipient), actIndex]);
49
- const attempts = attemptsByAttention.get(key) ?? [];
50
- const terminal = terminalWakeEvidence(attempts);
51
- const routes = [...recipientOwners].flatMap((ownerId) => routesByOwner.get(ownerId) ?? []);
52
- const presented = [...(presentedByAttention.get(key) ?? [])]
53
- .some((ownerId) => recipientOwners.has(ownerId));
54
- return {
55
- delivered: delivery.isSeen(recipient, actIndex),
56
- notified: (() => {
57
- const observation = observationFor(state, recipient, actIndex);
58
- return observation?.state === 'notified'
59
- && observation.ownerId !== undefined
60
- && recipientOwners.has(observation.ownerId);
61
- })(),
62
- presented,
63
- attempts,
64
- ...(terminal === undefined ? {} : { terminal }),
65
- attemptableRoutes: terminal === undefined
66
- ? routes.filter((route) => isWakeRouteAttemptable(route, attempts))
67
- : [],
68
- };
69
- },
70
- };
71
- }
72
- /** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
16
+ /** Adapter entry: open the artifact, assemble host ports, and project wake evidence. */
73
17
  export async function wakeEvidenceProjection(squarePath, now, env) {
74
- const square = await openSquare(squarePath, { clock: () => now });
18
+ const square = await openSquare(squarePath, { clock: () => now, hostLedger: hostLedgerForEnv(env), env });
75
19
  try {
76
- const { state } = await entryPresentation(square, '');
77
- return projectionFromState(squarePath, state, now, env);
20
+ const { state } = await square.artifact.read();
21
+ return projectWakeEvidenceFromState({ location: squarePath, state, hostLedger: square.hostLedger, now });
78
22
  }
79
23
  finally {
80
24
  await closeOpenSquare(square);
81
25
  }
82
26
  }
83
27
  export function wakeEvidenceProjectionFromState(squarePath, state, now, env, delivery) {
84
- return projectionFromState(squarePath, state, now, env, delivery);
28
+ return projectWakeEvidenceFromState({ location: squarePath, state, hostLedger: hostLedgerForEnv(env), now, delivery });
85
29
  }
86
- /** Project every wake decision from the same primary evidence. */
87
30
  export async function wakeEvidence(squarePath, recipient, actIndex, now, env) {
88
31
  return (await wakeEvidenceProjection(squarePath, now, env)).evidence(recipient, actIndex);
89
32
  }
90
- export function wakeIsEligible(evidence) {
91
- return !evidence.delivered
92
- && !evidence.notified
93
- && evidence.terminal === undefined
94
- && evidence.attemptableRoutes.length > 0;
95
- }
package/dist/wakes.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type NotifyLease, type WakeRouteKind, type WatchLease, type WatchOptions } from './model.js';
1
+ import { type WatchLease, type WatchOptions } from './model.js';
2
2
  import type { OpenSquare } from './open-square.js';
3
3
  export type WatchLeaseStart = {
4
4
  readonly type: 'started';
@@ -20,21 +20,7 @@ export type WatchLeasePulse = {
20
20
  readonly type: 'sleep';
21
21
  readonly heartbeatAt?: number;
22
22
  };
23
- export type NotifyLeaseClaim = {
24
- readonly type: 'delivered';
25
- } | {
26
- readonly type: 'busy';
27
- } | {
28
- readonly type: 'ambiguous';
29
- readonly lease: NotifyLease;
30
- } | {
31
- readonly type: 'acquired';
32
- readonly leaseId: string;
33
- };
34
- export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, ownerId?: string): Promise<WatchLeaseStart>;
23
+ export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions): Promise<WatchLeaseStart>;
35
24
  export declare function pulseWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, heartbeatDue: boolean): Promise<WatchLeasePulse>;
36
25
  export declare function releaseWatchLease(square: OpenSquare, name: string, leaseId: string | undefined): Promise<void>;
37
26
  export declare function ownsWatchLease(square: OpenSquare, name: string, leaseId: string): Promise<boolean>;
38
- export declare function claimNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string, leaseMs: number): Promise<NotifyLeaseClaim>;
39
- export declare function transitionNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string, phase: NotifyLease['phase'], leaseMs: number, routeKind?: WakeRouteKind, attemptN?: number): Promise<boolean>;
40
- export declare function releaseNotificationLease(square: OpenSquare, recipient: string, actIndex: number, leaseId: string): Promise<void>;
package/dist/wakes.js CHANGED
@@ -1,26 +1,22 @@
1
- import { formatActivityId } from './square-core.js';
2
- import { isActivitySeen } from './delivery.js';
3
- import { nameKey } from './model.js';
4
1
  import { resolveKnownName } from './decisions.js';
5
2
  import { WATCH_STALE_MS, currentHold, freshWatchLease, removeWatchLease, watchTerminalStatus, writeWatchLease } from './runtime.js';
6
3
  function filter(options) {
7
4
  return { ...(options.participants === undefined ? {} : { participants: [...options.participants] }), ...(options.mention === undefined ? {} : { mention: options.mention }) };
8
5
  }
9
- function notificationKey(recipient, index) { return JSON.stringify([formatActivityId(index), nameKey(recipient)]); }
10
- export async function acquireWatchLease(square, name, leaseId, options, ownerId) {
6
+ export async function acquireWatchLease(square, name, leaseId, options) {
11
7
  const at = square.clock();
12
- return square.cell.transact((state) => {
8
+ return square.artifact.transact((state) => {
13
9
  const known = resolveKnownName(state, name);
14
10
  const existing = freshWatchLease(state, known, at);
15
11
  if (existing !== undefined && !options.replace)
16
12
  return { result: { type: 'active', lease: existing } };
17
- writeWatchLease(state, known, { leaseId, ...(ownerId === undefined ? {} : { ownerId }), heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
13
+ writeWatchLease(state, known, { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
18
14
  return { state, result: { type: 'started', leaseId, replaced: existing !== undefined, heartbeatAt: at } };
19
15
  });
20
16
  }
21
17
  export async function pulseWatchLease(square, name, leaseId, options, heartbeatDue) {
22
18
  const at = square.clock();
23
- return square.cell.transact((state) => {
19
+ return square.artifact.transact((state) => {
24
20
  const known = resolveKnownName(state, name);
25
21
  const lease = freshWatchLease(state, known, at);
26
22
  if (lease?.leaseId !== leaseId)
@@ -32,29 +28,13 @@ export async function pulseWatchLease(square, name, leaseId, options, heartbeatD
32
28
  return { result: { type: 'terminal', status: terminal } };
33
29
  if (!heartbeatDue)
34
30
  return { result: { type: 'sleep' } };
35
- writeWatchLease(state, known, { leaseId, ...(lease.ownerId === undefined ? {} : { ownerId: lease.ownerId }), heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
31
+ writeWatchLease(state, known, { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter(options)).length === 0 ? {} : { filter: filter(options) }) });
36
32
  return { state, result: { type: 'sleep', heartbeatAt: at } };
37
33
  });
38
34
  }
39
35
  export async function releaseWatchLease(square, name, leaseId) {
40
36
  if (leaseId === undefined)
41
37
  return;
42
- await square.cell.transact((state) => { const known = resolveKnownName(state, name); return removeWatchLease(state, known, leaseId) ? { state, result: undefined } : { result: undefined }; });
38
+ await square.artifact.transact((state) => { const known = resolveKnownName(state, name); return removeWatchLease(state, known, leaseId) ? { state, result: undefined } : { result: undefined }; });
43
39
  }
44
- export async function ownsWatchLease(square, name, leaseId) { const { state } = await square.cell.read(); return freshWatchLease(state, resolveKnownName(state, name), square.clock())?.leaseId === leaseId; }
45
- export async function claimNotificationLease(square, recipient, actIndex, leaseId, leaseMs) {
46
- const at = square.clock();
47
- const key = notificationKey(recipient, actIndex);
48
- return square.cell.transact((state) => { const known = resolveKnownName(state, recipient); if (isActivitySeen(state, known, actIndex))
49
- return { result: { type: 'delivered' } }; const existing = state.runtime.notifyLeases[key]; if (existing !== undefined && existing.expiresAt > at)
50
- return { result: { type: 'busy' } }; if (existing?.phase === 'dispatching')
51
- return { result: { type: 'ambiguous', lease: existing } }; state.runtime.notifyLeases[key] = { leaseId, expiresAt: at + leaseMs, phase: 'claimed' }; return { state, result: { type: 'acquired', leaseId } }; });
52
- }
53
- export async function transitionNotificationLease(square, recipient, actIndex, leaseId, phase, leaseMs, routeKind, attemptN) {
54
- const at = square.clock();
55
- const key = notificationKey(recipient, actIndex);
56
- return square.cell.transact((state) => { if (state.runtime.notifyLeases[key]?.leaseId !== leaseId)
57
- return { result: false }; state.runtime.notifyLeases[key] = { leaseId, expiresAt: at + leaseMs, phase, ...(routeKind === undefined ? {} : { routeKind }), ...(attemptN === undefined ? {} : { attemptN }) }; return { state, result: true }; });
58
- }
59
- export async function releaseNotificationLease(square, recipient, actIndex, leaseId) { const key = notificationKey(recipient, actIndex); await square.cell.transact((state) => { if (state.runtime.notifyLeases[key]?.leaseId !== leaseId)
60
- return { result: undefined }; delete state.runtime.notifyLeases[key]; return { state, result: undefined }; }); }
40
+ export async function ownsWatchLease(square, name, leaseId) { const { state } = await square.artifact.read(); return freshWatchLease(state, resolveKnownName(state, name), square.clock())?.leaseId === leaseId; }
package/dist/watch.js CHANGED
@@ -7,7 +7,7 @@ import { openParticipant } from './square-wiring.js';
7
7
  import { resolveParticipant, watchPresentation } from './views.js';
8
8
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
9
9
  import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
10
- import { hasAutomaticDeliveryIdentity, localParticipantOwner } from './registry.js';
10
+ import { hasAutomaticDeliveryIdentity } from './registry.js';
11
11
  import { parseActivityId } from './square-core.js';
12
12
  function watchStatusExitCode(status) {
13
13
  return status === 'capped' ? 1 : 0;
@@ -87,8 +87,7 @@ async function finishWatchResult(square, squarePath, name, result, leaseId, idle
87
87
  }
88
88
  async function beginWatch(square, squarePath, name, opts) {
89
89
  const id = leaseId();
90
- const ownerId = await localParticipantOwner(squarePath, name);
91
- return acquireWatchLease(square, name, id, opts, ownerId);
90
+ return acquireWatchLease(square, name, id, opts);
92
91
  }
93
92
  async function endWatch(square, name, id) {
94
93
  await releaseWatchLease(square, name, id);
@@ -1,11 +1,16 @@
1
1
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
2
2
  import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
3
3
  import { waitForSessionPending } from '../dist/inbox.js';
4
- import { lookupSessionBindings } from '../dist/registry.js';
4
+ import { projectSessionBindings } from '../dist/square-projections.js';
5
+ import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
5
6
 
6
7
  const PI_SEND_TIMEOUT_MS = 5_000;
7
8
  const DEFAULT_PI_BOUNDARY_TIMEOUT_MS = 2_000;
8
9
 
10
+ function sessionBindings(sessionId) {
11
+ return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
12
+ }
13
+
9
14
  function piBoundaryTimeoutMs() {
10
15
  const configured = Number.parseInt(process.env.SQUARE_PI_BOUNDARY_TIMEOUT_MS || '', 10);
11
16
  return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PI_BOUNDARY_TIMEOUT_MS;
@@ -84,7 +89,7 @@ export default function squarePiExtension(pi) {
84
89
 
85
90
  const wake = async (piContext, token, signal) => {
86
91
  while (sessionId !== undefined && token === generation && !signal.aborted) {
87
- if ((await lookupSessionBindings(sessionId)).length === 0) {
92
+ if ((await sessionBindings(sessionId)).length === 0) {
88
93
  await pause(signal, 1_000);
89
94
  continue;
90
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,7 +53,7 @@ square --location <square> --as <name> done - <<'EOF'
53
53
  ...
54
54
  EOF
55
55
 
56
- For complete history: square --location <square> history --all --full
56
+ For complete history, follow the activity-id continuation commands printed by `history`.
57
57
 
58
58
  Every activity must address at least one participant with @name. Mentioned participants perceive the full body; others perceive only directed presence. Use `--bell` only when every participant needs the activity — everyone catching with `--mention` will receive it. Precise history queries may still read original archive bodies.
59
59
 
@@ -117,7 +117,7 @@ While held, participant expression and catch pause. Join, done, status, and hist
117
117
  When participants are done, collect the public activities:
118
118
 
119
119
  ```bash
120
- square --location <square> history --all --full # complete public history
120
+ square --location <square> history --no-truncate # expand preview bodies
121
121
  square --location <square> status
122
122
  ```
123
123
 
@@ -61,7 +61,7 @@ The ownership boundary belongs here. @bob, does this match your read?
61
61
  EOF
62
62
  ```
63
63
 
64
- **Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary — precise `history` queries and `history --all --full` read original bodies.
64
+ **Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary — `history` is a read-only archive with stable activity-id cursors.
65
65
 
66
66
  **Discipline.** Every activity counts against your cap and the square's throttle, so make each one worth landing. Keep private progress and tool chatter out — express only when another participant needs the thought, question, or decision.
67
67
 
@@ -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
 
@@ -98,13 +98,14 @@ square --location <square> --as <name> ignore <participant>
98
98
 
99
99
  ```bash
100
100
  square history --limit 5 # most recent 5, oldest to newest
101
+ square history --before act/12 --limit 5 # the page before act/12
102
+ square history --after act/12 --limit 5 # the page after act/12
101
103
  square history --limit 5 --order desc # newest first
102
- square history --all # every entry
103
- square history --full # expand bodies in range
104
+ square history --no-truncate # expand preview bodies
104
105
  square history --grep 'term' # search
105
106
  ```
106
107
 
107
- See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly; use `history --all --full` for the complete record.
108
+ See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly. Bodies are previews by default; follow the printed activity-id command to continue page by page.
108
109
 
109
110
  ## Hold
110
111