@astrosheep/square 0.3.12 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks/hooks.json +9 -0
- package/dist/activity-feed.d.ts +14 -0
- package/dist/activity-feed.js +5 -5
- package/dist/activity.d.ts +9 -0
- package/dist/activity.js +111 -90
- package/dist/artifact.d.ts +20 -0
- package/dist/artifact.js +10 -29
- package/dist/automatic-session.d.ts +5 -0
- package/dist/automatic-session.js +86 -0
- package/dist/boundary-presentation.d.ts +5 -0
- package/dist/boundary-presentation.js +5 -4
- package/dist/claude-hook.d.ts +9 -0
- package/dist/claude-hook.js +35 -3
- package/dist/cli/context.d.ts +35 -0
- package/dist/cli/context.js +25 -30
- package/dist/cli/harness-command.d.ts +28 -0
- package/dist/cli/harness-command.js +29 -14
- package/dist/cli/maintenance-commands.d.ts +7 -0
- package/dist/cli/maintenance-commands.js +3 -3
- package/dist/cli/meta-commands.d.ts +5 -0
- package/dist/cli/observation-commands.d.ts +20 -0
- package/dist/cli/observation-commands.js +155 -128
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +2 -12
- package/dist/cli/registry.d.ts +9 -0
- package/dist/cli/registry.js +1 -2
- package/dist/cli/square-commands.d.ts +34 -0
- package/dist/cli/square-commands.js +81 -74
- package/dist/cmd/notify-once.d.ts +2 -0
- package/dist/codex-hook.d.ts +10 -0
- package/dist/codex-hook.js +35 -3
- package/dist/decisions.d.ts +92 -0
- package/dist/decisions.js +83 -77
- package/dist/delivery-health.d.ts +21 -0
- package/dist/delivery-health.js +40 -34
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.js +60 -22
- package/dist/file-lock.d.ts +6 -0
- package/dist/harness-claude.d.ts +19 -0
- package/dist/harness-claude.js +8 -3
- package/dist/harness-codex.d.ts +29 -0
- package/dist/harness-codex.js +2 -2
- package/dist/harness-links.d.ts +17 -0
- package/dist/harness-links.js +2 -2
- package/dist/harness-pi.d.ts +11 -0
- package/dist/harness-pi.js +2 -2
- package/dist/harness-stage.d.ts +6 -0
- package/dist/harness.d.ts +20 -0
- package/dist/harness.js +5 -3
- package/dist/help.d.ts +6 -0
- package/dist/help.js +6 -5
- package/dist/identity.d.ts +13 -0
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +15 -19
- package/dist/index.d.ts +13 -0
- package/dist/index.js +31 -69
- package/dist/landing.d.ts +10 -0
- package/dist/landing.js +95 -0
- package/dist/list.d.ts +1 -0
- package/dist/list.js +40 -18
- package/dist/model.d.ts +147 -0
- package/dist/model.js +12 -2
- package/dist/notifications.d.ts +30 -0
- package/dist/notifications.js +75 -91
- package/dist/open-square.d.ts +11 -0
- package/dist/open-square.js +4 -0
- package/dist/participant-identity.d.ts +1 -0
- package/dist/participant-identity.js +3 -0
- package/dist/paseo-connection.d.ts +15 -0
- package/dist/paseo-delivery.d.ts +14 -0
- package/dist/paseo-state.d.ts +11 -0
- package/dist/paseo-timeline.d.ts +16 -0
- package/dist/presence.d.ts +3 -0
- package/dist/presence.js +42 -0
- package/dist/presentation.d.ts +91 -0
- package/dist/presentation.js +33 -29
- package/dist/presented.d.ts +11 -0
- package/dist/registry.d.ts +58 -0
- package/dist/registry.js +36 -3
- package/dist/routes.d.ts +42 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.js +42 -34
- package/dist/search.d.ts +11 -0
- package/dist/square-core.d.ts +111 -0
- package/dist/square-core.js +7 -0
- package/dist/square-facade.d.ts +86 -0
- package/dist/square-facade.js +1 -0
- package/dist/square-file-adapter.d.ts +18 -0
- package/dist/square-file-adapter.js +70 -0
- package/dist/square-storage.d.ts +20 -0
- package/dist/square-storage.js +171 -0
- package/dist/square-wiring.d.ts +19 -0
- package/dist/square-wiring.js +70 -0
- package/dist/square.d.ts +2 -0
- package/dist/state-cell.d.ts +14 -0
- package/dist/state-cell.js +1 -0
- package/dist/stream.d.ts +3 -0
- package/dist/stream.js +19 -23
- package/dist/time.d.ts +6 -0
- package/dist/views.d.ts +95 -0
- package/dist/views.js +81 -0
- package/dist/wake-attempts.d.ts +39 -0
- package/dist/wake-evidence.d.ts +12 -0
- package/dist/wake-evidence.js +7 -10
- package/dist/wake-port.d.ts +20 -0
- package/dist/wake-sink.d.ts +12 -0
- package/dist/wakes.d.ts +40 -0
- package/dist/wakes.js +62 -0
- package/dist/watch.d.ts +2 -0
- package/dist/watch.js +89 -210
- package/extensions/square-opencode.js +27 -1
- package/extensions/square-pi.js +13 -0
- package/package.json +5 -2
- package/skills/brainstorm/SKILL.md +26 -26
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +47 -29
- package/skills/square/hooks/hooks.json +9 -0
- package/skills/square-feedback/SKILL.md +2 -2
- package/dist/compact.js +0 -26
- package/dist/square-application.js +0 -193
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{ "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5 }] }
|
|
5
|
+
],
|
|
6
|
+
"SessionResume": [
|
|
7
|
+
{ "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5 }] }
|
|
8
|
+
],
|
|
9
|
+
"SessionEnd": [
|
|
10
|
+
{ "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5 }] }
|
|
11
|
+
],
|
|
3
12
|
"PostToolUse": [
|
|
4
13
|
{
|
|
5
14
|
"hooks": [
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type StoredAct, type SquareState, type PublicAct, type RoomChangeAct } from './model.js';
|
|
2
|
+
export interface ActivityFeedFilter {
|
|
3
|
+
participants?: string[];
|
|
4
|
+
mention?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function actDelta(acts: StoredAct[], cursor: number): StoredAct[];
|
|
7
|
+
/** Public cursor changes plus directed receipts that remain pending behind it. */
|
|
8
|
+
export declare function deliveryDelta(squareState: SquareState, name: string): StoredAct[];
|
|
9
|
+
export declare function peerRoomChanges(delta: StoredAct[], name: string): RoomChangeAct[];
|
|
10
|
+
export declare function peerPublicActs(delta: StoredAct[], name: string): PublicAct[];
|
|
11
|
+
export declare function matchesFeedFilter(act: StoredAct, filter: ActivityFeedFilter): boolean;
|
|
12
|
+
export declare function filteredPeerActivities(delta: StoredAct[], name: string, filter: ActivityFeedFilter): PublicAct[];
|
|
13
|
+
export declare function filteredRoomChanges(delta: StoredAct[], name: string, filter: ActivityFeedFilter): RoomChangeAct[];
|
|
14
|
+
export declare function ackPeerDelta(squareState: SquareState, name: string, delta: StoredAct[], at?: number): boolean;
|
package/dist/activity-feed.js
CHANGED
|
@@ -5,10 +5,10 @@ export function actDelta(acts, cursor) {
|
|
|
5
5
|
return acts.filter((act) => act.index > cursor);
|
|
6
6
|
}
|
|
7
7
|
/** Public cursor changes plus directed receipts that remain pending behind it. */
|
|
8
|
-
export function deliveryDelta(
|
|
9
|
-
const items = actDelta(
|
|
8
|
+
export function deliveryDelta(squareState, name) {
|
|
9
|
+
const items = actDelta(squareState.acts, readCursor(squareState, name));
|
|
10
10
|
const seen = new Set(items.map((act) => act.index));
|
|
11
|
-
for (const notification of deriveDeliveryModel(
|
|
11
|
+
for (const notification of deriveDeliveryModel(squareState).pendingFor(name)) {
|
|
12
12
|
if (!seen.has(notification.item.index))
|
|
13
13
|
items.push(notification.item);
|
|
14
14
|
}
|
|
@@ -39,6 +39,6 @@ export function filteredRoomChanges(delta, name, filter) {
|
|
|
39
39
|
return [];
|
|
40
40
|
return peerRoomChanges(delta, name).filter((act) => matchesParticipants(act, filter.participants));
|
|
41
41
|
}
|
|
42
|
-
export function ackPeerDelta(
|
|
43
|
-
return advanceCursor(
|
|
42
|
+
export function ackPeerDelta(squareState, name, delta, at) {
|
|
43
|
+
return advanceCursor(squareState, name, latestActIndex([...peerPublicActs(delta, name), ...peerRoomChanges(delta, name)]), at);
|
|
44
44
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ActivityOptions {
|
|
2
|
+
force?: boolean;
|
|
3
|
+
forceCommand: string;
|
|
4
|
+
noWait?: boolean;
|
|
5
|
+
reach?: import('./model.js').Reach;
|
|
6
|
+
reply?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function saveActivityDraft(squarePath: string, name: string, body: string): string;
|
|
9
|
+
export declare function cmdActivity(squarePath: string, name: string, activity: string, resolveBody: (arg: string) => string, opts: ActivityOptions): Promise<void>;
|
package/dist/activity.js
CHANGED
|
@@ -2,12 +2,15 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
|
-
import {
|
|
6
|
-
import { SquareError, validateName } from './model.js';
|
|
5
|
+
import { SquareError, isSquareError, validateName } from './model.js';
|
|
7
6
|
import { expressHintLine, renderActivityBlocked, renderActivityLimit, renderExpressNoWait, renderExpressWaiting, renderPendingFeed, withPathOutput, } from './presentation.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
7
|
+
import { nowMs, SLEEP_MS } from './runtime.js';
|
|
8
|
+
import { wakeNotifierForSquare } from './notifications.js';
|
|
9
|
+
import { openSquare } from './square-file-adapter.js';
|
|
10
|
+
import { closeOpenSquare } from './open-square.js';
|
|
11
|
+
import { Square } from './square-wiring.js';
|
|
12
|
+
import { activityPresentation, resolveParticipant } from './views.js';
|
|
13
|
+
import { formatActivityId } from './square-core.js';
|
|
11
14
|
import { formatTimestamp } from './time.js';
|
|
12
15
|
function draftDirFor(squarePath) {
|
|
13
16
|
return path.join(path.dirname(squarePath), 'drafts');
|
|
@@ -39,114 +42,132 @@ export function saveActivityDraft(squarePath, name, body) {
|
|
|
39
42
|
export async function cmdActivity(squarePath, name, activity, resolveBody, opts) {
|
|
40
43
|
validateName(name);
|
|
41
44
|
const rawInput = String(resolveBody(activity)).replace(/\r\n/g, '\n');
|
|
42
|
-
|
|
45
|
+
const reader = await openSquare(squarePath, { clock: nowMs });
|
|
46
|
+
let knownName;
|
|
43
47
|
try {
|
|
44
|
-
|
|
48
|
+
knownName = (await resolveParticipant(reader, name)).name;
|
|
45
49
|
}
|
|
46
50
|
catch (err) {
|
|
47
|
-
|
|
51
|
+
await closeOpenSquare(reader);
|
|
52
|
+
if (isSquareError(err)) {
|
|
53
|
+
if (err.code === 'unknown_participant' || err.code === 'invalid_args') {
|
|
54
|
+
const draftPath = saveActivityDraft(squarePath, name, rawInput);
|
|
55
|
+
process.stderr.write(err.message + '\n');
|
|
56
|
+
process.stderr.write(`draft kept: ${draftPath}\n`);
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
48
59
|
process.stderr.write(err.message + '\n');
|
|
49
60
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
50
61
|
}
|
|
51
62
|
throw err;
|
|
52
63
|
}
|
|
53
|
-
|
|
54
|
-
if (knownName === undefined) {
|
|
55
|
-
const draftPath = saveActivityDraft(squarePath, name, rawInput);
|
|
56
|
-
const expected = doc.acts.filter((act) => act.kind === 'join').map((act) => act.actor);
|
|
57
|
-
process.stderr.write(`Unknown participant "${name}". Expected one of: ${expected.join(', ')}.\n`);
|
|
58
|
-
process.stderr.write(`draft kept: ${draftPath}\n`);
|
|
59
|
-
process.exit(2);
|
|
60
|
-
}
|
|
64
|
+
await closeOpenSquare(reader);
|
|
61
65
|
const body = rawInput.trim();
|
|
62
66
|
const force = opts.force ?? false;
|
|
63
67
|
const noWait = opts.noWait ?? false;
|
|
64
68
|
const reach = opts.reach === 'bell' ? 'bell' : undefined;
|
|
65
69
|
let announcedWait;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const hasPending =
|
|
86
|
-
const pending = hasPending ? `\n\n${renderPendingFeed(
|
|
87
|
-
const hint = expressHintLine(
|
|
88
|
-
const
|
|
70
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
71
|
+
try {
|
|
72
|
+
const participant = await square.join(name);
|
|
73
|
+
while (true) {
|
|
74
|
+
const beforeSquare = await openSquare(squarePath, { clock: nowMs });
|
|
75
|
+
const before = await activityPresentation(beforeSquare, knownName).finally(() => closeOpenSquare(beforeSquare));
|
|
76
|
+
const pendingPublic = before.pendingPublic;
|
|
77
|
+
const pendingRoomChanges = before.pendingRoomChanges;
|
|
78
|
+
try {
|
|
79
|
+
await participant.express(body, {
|
|
80
|
+
force,
|
|
81
|
+
...(reach === undefined ? {} : { reach }),
|
|
82
|
+
...(opts.reply === undefined ? {} : { reply: formatActivityId(opts.reply) }),
|
|
83
|
+
});
|
|
84
|
+
const freshSquare = await openSquare(squarePath, { clock: nowMs });
|
|
85
|
+
const fresh = await activityPresentation(freshSquare, knownName).finally(() => closeOpenSquare(freshSquare));
|
|
86
|
+
const headerCount = fresh.participantCount;
|
|
87
|
+
const held = fresh.held;
|
|
88
|
+
const ownActCount = fresh.ownActivityCount;
|
|
89
|
+
const hasPending = pendingPublic.length > 0 || pendingRoomChanges.length > 0;
|
|
90
|
+
const pending = hasPending ? `\n\n${renderPendingFeed([...fresh.activities], [...pendingPublic], [...pendingRoomChanges], knownName)}` : '';
|
|
91
|
+
const hint = expressHintLine(ownActCount);
|
|
92
|
+
const confirmation = `● heads turn your way — #${ownActCount}`;
|
|
93
|
+
const withHint = hint ? `${confirmation}\n${hint}` : confirmation;
|
|
89
94
|
process.stdout.write(withPathOutput(squarePath, withHint + pending, { participantCount: headerCount, held }));
|
|
90
95
|
return;
|
|
91
96
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
draftPath,
|
|
101
|
-
participantCount: headerCount,
|
|
102
|
-
held,
|
|
103
|
-
}));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
case 'capped': {
|
|
108
|
-
process.stdout.write(renderActivityLimit({
|
|
109
|
-
squarePath,
|
|
110
|
-
name: knownName,
|
|
111
|
-
count: decision.count,
|
|
112
|
-
hardCap: decision.hardCap,
|
|
113
|
-
draftPath: saveActivityDraft(squarePath, name, rawInput),
|
|
114
|
-
participantCount: headerCount,
|
|
115
|
-
held,
|
|
116
|
-
}));
|
|
117
|
-
process.exit(1);
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case 'throttled': {
|
|
121
|
-
if (noWait) {
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (!(error instanceof SquareError))
|
|
99
|
+
throw error;
|
|
100
|
+
const freshSquare = await openSquare(squarePath, { clock: nowMs });
|
|
101
|
+
const fresh = await activityPresentation(freshSquare, knownName).finally(() => closeOpenSquare(freshSquare));
|
|
102
|
+
const headerCount = fresh.participantCount;
|
|
103
|
+
const held = fresh.held;
|
|
104
|
+
if (error.code === 'behind') {
|
|
122
105
|
const draftPath = saveActivityDraft(squarePath, name, rawInput);
|
|
123
|
-
process.stdout.write(
|
|
106
|
+
process.stdout.write(renderActivityBlocked({
|
|
107
|
+
squarePath,
|
|
108
|
+
name: knownName,
|
|
109
|
+
forceCommand: opts.forceCommand,
|
|
110
|
+
activitySummaries: [],
|
|
111
|
+
unreadRoomChanges: [...pendingRoomChanges],
|
|
112
|
+
draftPath,
|
|
113
|
+
participantCount: headerCount,
|
|
114
|
+
held,
|
|
115
|
+
}));
|
|
124
116
|
process.exit(1);
|
|
117
|
+
return;
|
|
125
118
|
}
|
|
126
|
-
if (
|
|
127
|
-
process.stdout.write(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
process.stdout.write(renderExpressNoWait({ squarePath, name: knownName, reason: 'held', holdReason: decision.reason, draftPath, participantCount: headerCount, held }));
|
|
119
|
+
if (error.code === 'capped') {
|
|
120
|
+
process.stdout.write(renderActivityLimit({
|
|
121
|
+
squarePath,
|
|
122
|
+
name: knownName,
|
|
123
|
+
count: fresh.ownActivityCount,
|
|
124
|
+
...(fresh.hardCap === null ? {} : { hardCap: fresh.hardCap }),
|
|
125
|
+
draftPath: saveActivityDraft(squarePath, name, rawInput),
|
|
126
|
+
participantCount: headerCount,
|
|
127
|
+
held,
|
|
128
|
+
}));
|
|
137
129
|
process.exit(1);
|
|
130
|
+
return;
|
|
138
131
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
if (error.code === 'throttled') {
|
|
133
|
+
const delayMs = error.facts?.retryAfterMs ?? SLEEP_MS;
|
|
134
|
+
if (noWait) {
|
|
135
|
+
const draftPath = saveActivityDraft(squarePath, name, rawInput);
|
|
136
|
+
process.stdout.write(renderExpressNoWait({ squarePath, name: knownName, reason: 'throttled', delayMs, draftPath, participantCount: headerCount, held }));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (announcedWait !== 'throttled') {
|
|
140
|
+
process.stdout.write(renderExpressWaiting({ reason: 'throttled', delayMs }) + '\n');
|
|
141
|
+
announcedWait = 'throttled';
|
|
142
|
+
}
|
|
143
|
+
await sleep(delayMs);
|
|
144
|
+
continue;
|
|
142
145
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
if (error.code === 'held') {
|
|
147
|
+
const holdReason = fresh.holdReason;
|
|
148
|
+
if (noWait) {
|
|
149
|
+
const draftPath = saveActivityDraft(squarePath, name, rawInput);
|
|
150
|
+
process.stdout.write(renderExpressNoWait({ squarePath, name: knownName, reason: 'held', holdReason, draftPath, participantCount: headerCount, held }));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
if (announcedWait !== 'held') {
|
|
154
|
+
process.stdout.write(renderExpressWaiting({ reason: 'held' }) + '\n');
|
|
155
|
+
announcedWait = 'held';
|
|
156
|
+
}
|
|
157
|
+
await sleep(SLEEP_MS);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (error.code === 'bell_quota') {
|
|
161
|
+
const nextAt = nowMs() + (error.facts?.retryAfterMs ?? 1);
|
|
162
|
+
process.stdout.write(withPathOutput(squarePath, [`✕ the bell stays quiet for now`, ` · you can ring it again at ${formatTimestamp(nextAt)}`].join('\n'), { participantCount: headerCount, held }));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
149
167
|
}
|
|
150
168
|
}
|
|
151
169
|
}
|
|
170
|
+
finally {
|
|
171
|
+
await square.close();
|
|
172
|
+
}
|
|
152
173
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type BuildOptions, type HardCap, type SquareState, type SquareRuntimeState } from './model.js';
|
|
2
|
+
export interface DoctorProblem {
|
|
3
|
+
kind: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DiagnoseResult {
|
|
7
|
+
unfixable?: string;
|
|
8
|
+
problems: DoctorProblem[];
|
|
9
|
+
state?: SquareState;
|
|
10
|
+
}
|
|
11
|
+
export declare function emptyRuntimeState(nextActIndex?: number): SquareRuntimeState;
|
|
12
|
+
export declare function createSquareState(options: BuildOptions & {
|
|
13
|
+
hardCap: HardCap;
|
|
14
|
+
}, snippet: string): SquareState;
|
|
15
|
+
export declare function encodeSquare(squareState: SquareState): Buffer;
|
|
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;
|
package/dist/artifact.js
CHANGED
|
@@ -3,10 +3,9 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { TextDecoder } from 'node:util';
|
|
5
5
|
import zlib from 'node:zlib';
|
|
6
|
-
import { isWakeRouteKind, nameKey, SquareError, } from './model.js';
|
|
6
|
+
import { isWakeRouteKind, InternalSquareError, nameKey, SquareError, } from './model.js';
|
|
7
7
|
import { parseActivityId } from './square-core.js';
|
|
8
8
|
const SQUARE_MAGIC = Buffer.from('SQUARE01', 'ascii');
|
|
9
|
-
const ARCHIVE_MAGIC = Buffer.from('SQARCH01', 'ascii');
|
|
10
9
|
const LENGTH_BYTES = 4;
|
|
11
10
|
const DIGEST_BYTES = 32;
|
|
12
11
|
const HEADER_BYTES = SQUARE_MAGIC.length + LENGTH_BYTES + DIGEST_BYTES;
|
|
@@ -185,7 +184,7 @@ function validateActs(value) {
|
|
|
185
184
|
}
|
|
186
185
|
return true;
|
|
187
186
|
}
|
|
188
|
-
function
|
|
187
|
+
function validateSquareState(value) {
|
|
189
188
|
if (!isObject(value)
|
|
190
189
|
|| !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute'])
|
|
191
190
|
|| !(value.hardCap === null || (Number.isSafeInteger(value.hardCap) && value.hardCap > 0))
|
|
@@ -279,7 +278,7 @@ function readGuide(name) {
|
|
|
279
278
|
throw error;
|
|
280
279
|
}
|
|
281
280
|
}
|
|
282
|
-
export function
|
|
281
|
+
export function createSquareState(options, snippet) {
|
|
283
282
|
const guides = [readGuide('participant')];
|
|
284
283
|
if (options.template !== undefined)
|
|
285
284
|
guides.push(readGuide(options.template));
|
|
@@ -292,23 +291,11 @@ export function createSquareDoc(options, snippet) {
|
|
|
292
291
|
runtime: emptyRuntimeState(),
|
|
293
292
|
};
|
|
294
293
|
}
|
|
295
|
-
export function encodeSquare(
|
|
296
|
-
return encodeEnvelope(SQUARE_MAGIC,
|
|
294
|
+
export function encodeSquare(squareState) {
|
|
295
|
+
return encodeEnvelope(SQUARE_MAGIC, validateSquareState(squareState));
|
|
297
296
|
}
|
|
298
297
|
export function decodeSquare(bytes) {
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
export function encodeArchive(acts) {
|
|
302
|
-
if (!validateActs(acts))
|
|
303
|
-
throw invalidArtifact('archive activity schema is malformed.');
|
|
304
|
-
return encodeEnvelope(ARCHIVE_MAGIC, { acts });
|
|
305
|
-
}
|
|
306
|
-
export function decodeArchive(bytes) {
|
|
307
|
-
const value = decodeEnvelope(bytes, ARCHIVE_MAGIC);
|
|
308
|
-
if (!isObject(value) || !hasExactKeys(value, ['acts']) || !validateActs(value.acts)) {
|
|
309
|
-
throw invalidArtifact('archive schema is malformed.');
|
|
310
|
-
}
|
|
311
|
-
return value.acts;
|
|
298
|
+
return validateSquareState(decodeEnvelope(bytes, SQUARE_MAGIC));
|
|
312
299
|
}
|
|
313
300
|
function readArtifact(squarePath) {
|
|
314
301
|
try {
|
|
@@ -316,7 +303,7 @@ function readArtifact(squarePath) {
|
|
|
316
303
|
}
|
|
317
304
|
catch (error) {
|
|
318
305
|
if (error.code === 'ENOENT') {
|
|
319
|
-
throw new
|
|
306
|
+
throw new InternalSquareError('not_found', `square file not found: ${squarePath}`);
|
|
320
307
|
}
|
|
321
308
|
throw error;
|
|
322
309
|
}
|
|
@@ -341,20 +328,14 @@ function atomicWrite(target, bytes) {
|
|
|
341
328
|
throw error;
|
|
342
329
|
}
|
|
343
330
|
}
|
|
344
|
-
export function writeSquareFile(squarePath,
|
|
331
|
+
export function writeSquareFile(squarePath, squareState) {
|
|
345
332
|
requireSquareExtension(squarePath);
|
|
346
|
-
atomicWrite(squarePath, encodeSquare(
|
|
333
|
+
atomicWrite(squarePath, encodeSquare(squareState));
|
|
347
334
|
}
|
|
348
335
|
export function loadSquare(squarePath) {
|
|
349
336
|
requireSquareExtension(squarePath);
|
|
350
337
|
return decodeSquare(readArtifact(squarePath));
|
|
351
338
|
}
|
|
352
|
-
export function writeArchiveFile(archivePath, acts) {
|
|
353
|
-
atomicWrite(archivePath, encodeArchive(acts));
|
|
354
|
-
}
|
|
355
|
-
export function loadArchive(archivePath) {
|
|
356
|
-
return decodeArchive(readArtifact(archivePath));
|
|
357
|
-
}
|
|
358
339
|
export function probeSquare(squarePath) {
|
|
359
340
|
if (!squarePath.endsWith('.square'))
|
|
360
341
|
return undefined;
|
|
@@ -382,7 +363,7 @@ export function probeSquare(squarePath) {
|
|
|
382
363
|
}
|
|
383
364
|
export function diagnoseSquareFile(squarePath) {
|
|
384
365
|
try {
|
|
385
|
-
return { problems: [],
|
|
366
|
+
return { problems: [], state: loadSquare(squarePath) };
|
|
386
367
|
}
|
|
387
368
|
catch (error) {
|
|
388
369
|
return {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type AutomaticProvider = 'codex' | 'claude' | 'opencode' | 'pi';
|
|
2
|
+
export declare function publicSquarePath(cwd: string): string;
|
|
3
|
+
export declare function automaticParticipant(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): string;
|
|
4
|
+
export declare function automaticSessionStart(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
5
|
+
export declare function automaticSessionEnd(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { closeOpenSquare } from './open-square.js';
|
|
6
|
+
import { Square } from './square-wiring.js';
|
|
7
|
+
import { entryPresentation } from './views.js';
|
|
8
|
+
import { canonicalSquarePath, lookupSession, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
|
|
9
|
+
import { participantIdentity, renderAmbientEvent } from './presentation.js';
|
|
10
|
+
import { validateName } from './model.js';
|
|
11
|
+
const providerEnv = {
|
|
12
|
+
codex: 'CODEX_THREAD_ID',
|
|
13
|
+
claude: 'CLAUDE_CODE_SESSION_ID',
|
|
14
|
+
opencode: 'OPENCODE_SESSION_ID',
|
|
15
|
+
pi: 'SQUARE_PI_SESSION_ID',
|
|
16
|
+
};
|
|
17
|
+
export function publicSquarePath(cwd) {
|
|
18
|
+
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
19
|
+
}
|
|
20
|
+
export function automaticParticipant(provider, sessionId, env) {
|
|
21
|
+
const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
|
|
22
|
+
if (configured) {
|
|
23
|
+
validateName(configured);
|
|
24
|
+
return configured;
|
|
25
|
+
}
|
|
26
|
+
const digest = createHash('sha256').update(sessionId, 'utf8').digest('hex').slice(0, 12);
|
|
27
|
+
return `${provider}-${digest}`;
|
|
28
|
+
}
|
|
29
|
+
export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
|
|
30
|
+
const squarePath = publicSquarePath(cwd);
|
|
31
|
+
if (!fs.existsSync(squarePath))
|
|
32
|
+
return undefined;
|
|
33
|
+
let reader;
|
|
34
|
+
try {
|
|
35
|
+
reader = await openSquare(squarePath);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
process.stderr.write(`! PUBLIC.square unreadable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const name = automaticParticipant(provider, sessionId, env);
|
|
42
|
+
const bindings = lookupSession(sessionId);
|
|
43
|
+
const before = await entryPresentation(reader, name);
|
|
44
|
+
if (bindings.some((binding) => canonicalSquarePath(binding.squarePath) === canonicalSquarePath(squarePath) && binding.name === name) && before.joined) {
|
|
45
|
+
await closeOpenSquare(reader);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
await closeOpenSquare(reader);
|
|
49
|
+
const square = await Square.at({ path: squarePath });
|
|
50
|
+
try {
|
|
51
|
+
await square.join(name);
|
|
52
|
+
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
53
|
+
recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
|
|
54
|
+
const afterSquare = await openSquare(squarePath);
|
|
55
|
+
const after = await entryPresentation(afterSquare, name, 10).finally(() => closeOpenSquare(afterSquare));
|
|
56
|
+
const activity = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
|
|
57
|
+
now: Date.now(),
|
|
58
|
+
preview: 200,
|
|
59
|
+
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
60
|
+
})).filter(Boolean).join('\n\n');
|
|
61
|
+
return [`You joined the public square as ${participantIdentity(name)}.`, after.scene, after.context ? `context\n${after.context}` : '', activity ? `recent activity\n${activity}` : ''].filter(Boolean).join('\n\n');
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await square.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
|
|
68
|
+
const squarePath = publicSquarePath(cwd);
|
|
69
|
+
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
70
|
+
const binding = lookupSessionBindings(sessionId).find((item) => canonicalSquarePath(item.squarePath) === canonicalSquarePath(squarePath) && item.channel === channel);
|
|
71
|
+
if (binding === undefined || !fs.existsSync(squarePath))
|
|
72
|
+
return;
|
|
73
|
+
const reader = await openSquare(squarePath);
|
|
74
|
+
const joined = await entryPresentation(reader, binding.name).finally(() => closeOpenSquare(reader));
|
|
75
|
+
if (!joined.joined)
|
|
76
|
+
return;
|
|
77
|
+
const square = await Square.at({ path: squarePath });
|
|
78
|
+
try {
|
|
79
|
+
const participant = await square.join(binding.name);
|
|
80
|
+
await participant.done();
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await square.close();
|
|
84
|
+
}
|
|
85
|
+
recordSessionDone(sessionId, binding.name, squarePath, channel, env);
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InboxMembership } from './model.js';
|
|
2
|
+
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
3
|
+
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
4
|
+
export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
|
|
5
|
+
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<T | undefined>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { leaseOwnsNotification, notificationMessageId } from './delivery.js';
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
|
-
import { participantCommandPrefix } from './presentation.js';
|
|
3
|
+
import { participantCommandPrefix, participantIdentity } from './presentation.js';
|
|
4
4
|
import { presentOnce } from './presented.js';
|
|
5
5
|
const BODY_MAX = 200;
|
|
6
6
|
const CONTEXT_MAX = 1200;
|
|
@@ -43,7 +43,7 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
43
43
|
const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
|
|
44
44
|
const id = notificationMessageId(membership.squarePath, notification.actIndex);
|
|
45
45
|
const block = [
|
|
46
|
-
`${id} · ${membership.squarePath}:
|
|
46
|
+
`${id} · ${membership.squarePath}: ${participantIdentity(membership.name)} from ${participantIdentity(notification.actor)} (${notification.route})`,
|
|
47
47
|
bodyPreview(notification.body),
|
|
48
48
|
`Ack with: ${command}`,
|
|
49
49
|
].join('\n');
|
|
@@ -72,6 +72,7 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
72
72
|
...footer,
|
|
73
73
|
].join('\n');
|
|
74
74
|
}
|
|
75
|
-
export function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
76
|
-
|
|
75
|
+
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
76
|
+
const inbox = await lookup(sessionId);
|
|
77
|
+
return presentOnce(sessionId, () => pendingAtBoundary(inbox), (inbox) => present(renderPendingAtBoundary(inbox)), env);
|
|
77
78
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InboxMembership } from './model.js';
|
|
2
|
+
export interface NativeHookInput {
|
|
3
|
+
session_id?: unknown;
|
|
4
|
+
hook_event_name?: unknown;
|
|
5
|
+
cwd?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function runClaudeHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
8
|
+
export declare function claudeHookResponse(input: NativeHookInput, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
|
|
9
|
+
export declare function runClaudeHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
package/dist/claude-hook.js
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
|
-
|
|
3
|
+
import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
|
|
4
|
+
export async function runClaudeHookAsync(inputText, env = process.env) {
|
|
5
|
+
let input;
|
|
6
|
+
try {
|
|
7
|
+
input = JSON.parse(inputText);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
if (input === null || typeof input !== 'object')
|
|
13
|
+
return '';
|
|
14
|
+
const value = input;
|
|
15
|
+
if (typeof value.session_id !== 'string' || typeof value.cwd !== 'string')
|
|
16
|
+
return runClaudeHook(inputText, env);
|
|
17
|
+
if (value.hook_event_name === 'SessionStart' || value.hook_event_name === 'SessionResume') {
|
|
18
|
+
try {
|
|
19
|
+
const context = await automaticSessionStart('claude', value.session_id, value.cwd, env);
|
|
20
|
+
return context === undefined ? '' : `${JSON.stringify({ hookSpecificOutput: { hookEventName: value.hook_event_name, additionalContext: context } })}\n`;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (value.hook_event_name === 'SessionEnd') {
|
|
27
|
+
try {
|
|
28
|
+
await automaticSessionEnd('claude', value.session_id, value.cwd, env);
|
|
29
|
+
}
|
|
30
|
+
catch { /* end remains bounded */ }
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
return runClaudeHook(inputText, env);
|
|
34
|
+
}
|
|
35
|
+
export async function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
4
36
|
if (typeof input.session_id !== 'string' || input.session_id === '')
|
|
5
37
|
return undefined;
|
|
6
38
|
if (input.hook_event_name !== 'PostToolBatch')
|
|
7
39
|
return undefined;
|
|
8
40
|
return presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: context } }), lookup, env);
|
|
9
41
|
}
|
|
10
|
-
export function runClaudeHook(inputText, env = process.env) {
|
|
42
|
+
export async function runClaudeHook(inputText, env = process.env) {
|
|
11
43
|
let input;
|
|
12
44
|
try {
|
|
13
45
|
input = JSON.parse(inputText);
|
|
@@ -17,6 +49,6 @@ export function runClaudeHook(inputText, env = process.env) {
|
|
|
17
49
|
}
|
|
18
50
|
if (input === null || typeof input !== 'object')
|
|
19
51
|
return '';
|
|
20
|
-
const response = claudeHookResponse(input, sessionInbox, env);
|
|
52
|
+
const response = await claudeHookResponse(input, sessionInbox, env);
|
|
21
53
|
return response === undefined ? '' : `${JSON.stringify(response)}\n`;
|
|
22
54
|
}
|