@astrosheep/square 0.3.27 → 0.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/artifact.d.ts +4 -4
- package/dist/artifact.js +24 -19
- package/dist/automatic-session.js +18 -7
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/cli/context.d.ts +4 -4
- package/dist/cli/context.js +11 -9
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/observation-commands.js +2 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/square-commands.js +15 -16
- package/dist/codex-boundary-state.d.ts +4 -4
- package/dist/codex-boundary-state.js +19 -19
- package/dist/codex-hook.js +3 -3
- package/dist/codex-queue.js +2 -2
- package/dist/file-lock.d.ts +1 -1
- package/dist/file-lock.js +22 -50
- package/dist/harness-links.d.ts +2 -1
- package/dist/harness-links.js +41 -12
- package/dist/harness.js +11 -5
- package/dist/inbox.js +2 -2
- package/dist/list.js +3 -3
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +10 -10
- package/dist/opencode.d.ts +19 -0
- package/dist/opencode.js +49 -0
- package/dist/presented.d.ts +5 -13
- package/dist/presented.js +48 -158
- package/dist/registry.d.ts +18 -30
- package/dist/registry.js +89 -320
- package/dist/routes.d.ts +6 -6
- package/dist/routes.js +20 -20
- package/dist/square-file-adapter.d.ts +1 -1
- package/dist/square-file-adapter.js +5 -9
- package/dist/square-storage.d.ts +3 -3
- package/dist/square-storage.js +23 -20
- package/dist/square-wiring.js +1 -1
- package/dist/wake-attempts.d.ts +6 -6
- package/dist/wake-attempts.js +39 -31
- package/dist/wake-evidence.d.ts +1 -1
- package/dist/wake-evidence.js +11 -11
- package/dist/wake-port.d.ts +1 -1
- package/dist/wake-port.js +1 -1
- package/dist/watch.js +1 -1
- package/extensions/square-pi.js +30 -3
- package/package.json +5 -1
- package/extensions/square-opencode.js +0 -48
package/dist/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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
35
|
+
await fd.writeFile(token, 'utf8');
|
|
38
36
|
return token;
|
|
39
37
|
}
|
|
40
38
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
fs.unlinkSync(lockPath);
|
|
43
|
-
}
|
|
44
|
-
catch { }
|
|
39
|
+
await fs.unlink(lockPath).catch(() => undefined);
|
|
45
40
|
throw error;
|
|
46
41
|
}
|
|
47
42
|
finally {
|
|
48
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
66
|
-
fs.
|
|
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
|
-
|
|
66
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
97
67
|
let token;
|
|
98
68
|
while (token === undefined) {
|
|
99
|
-
|
|
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/harness-links.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export type OpenCodeCommandRunner = (homeDir: string, args: string[]) => {
|
|
|
11
11
|
stdout: string;
|
|
12
12
|
stderr: string;
|
|
13
13
|
};
|
|
14
|
+
export declare function installOpenCodePlugin(homeDir: string, force?: boolean, run?: OpenCodeCommandRunner): string[];
|
|
15
|
+
export declare function uninstallOpenCodePlugin(homeDir: string): string[];
|
|
14
16
|
/** Verify that OpenCode accepts its resolved runtime configuration after links are installed. */
|
|
15
17
|
export declare function verifyOpenCodeRuntime(homeDir: string, run?: OpenCodeCommandRunner): string;
|
|
16
18
|
export declare function skillLinks(homeDir?: string, parents?: Array<'.claude' | '.agents'>): HarnessLink[];
|
|
17
|
-
export declare function opencodeExtensionLink(homeDir?: string): HarnessLink;
|
package/dist/harness-links.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import crossSpawn from 'cross-spawn';
|
|
6
|
+
import { SQUARE_IDENTITY } from './identity.js';
|
|
6
7
|
function packageRoot() {
|
|
7
8
|
// Emitted modules live in dist; package assets are one level above them.
|
|
8
9
|
return fileURLToPath(new URL('../', import.meta.url));
|
|
@@ -83,6 +84,42 @@ function runOpenCode(homeDir, args) {
|
|
|
83
84
|
throw result.error;
|
|
84
85
|
return { status: result.status ?? 1, stdout: result.stdout || '', stderr: result.stderr || '' };
|
|
85
86
|
}
|
|
87
|
+
function requireOpenCodeSuccess(result, action) {
|
|
88
|
+
if (result.status === 0)
|
|
89
|
+
return;
|
|
90
|
+
throw new Error(`OpenCode ${action} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
|
|
91
|
+
}
|
|
92
|
+
export function installOpenCodePlugin(homeDir, force = false, run = runOpenCode) {
|
|
93
|
+
const args = ['plugin', SQUARE_IDENTITY.packageName, '--global'];
|
|
94
|
+
if (force)
|
|
95
|
+
args.push('--force');
|
|
96
|
+
requireOpenCodeSuccess(run(homeDir, args), 'plugin install');
|
|
97
|
+
return [SQUARE_IDENTITY.packageName];
|
|
98
|
+
}
|
|
99
|
+
function configPath(homeDir) {
|
|
100
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, '.config');
|
|
101
|
+
return path.join(configHome, 'opencode', 'opencode.jsonc');
|
|
102
|
+
}
|
|
103
|
+
function removeConfiguredPlugin(homeDir) {
|
|
104
|
+
const target = configPath(homeDir);
|
|
105
|
+
let source;
|
|
106
|
+
try {
|
|
107
|
+
source = fs.readFileSync(target, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const escaped = SQUARE_IDENTITY.packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
113
|
+
const packageLine = new RegExp(`^\\s*"${escaped}(?:@[^"\\n]+)?"\\s*,?\\s*$`, 'm');
|
|
114
|
+
const next = source.replace(packageLine, '');
|
|
115
|
+
if (next === source)
|
|
116
|
+
return false;
|
|
117
|
+
fs.writeFileSync(target, next);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
export function uninstallOpenCodePlugin(homeDir) {
|
|
121
|
+
return removeConfiguredPlugin(homeDir) ? [SQUARE_IDENTITY.packageName] : [];
|
|
122
|
+
}
|
|
86
123
|
/** Verify that OpenCode accepts its resolved runtime configuration after links are installed. */
|
|
87
124
|
export function verifyOpenCodeRuntime(homeDir, run = runOpenCode) {
|
|
88
125
|
try {
|
|
@@ -98,10 +135,10 @@ export function verifyOpenCodeRuntime(homeDir, run = runOpenCode) {
|
|
|
98
135
|
return '✕ OpenCode debug config returned invalid JSON';
|
|
99
136
|
}
|
|
100
137
|
const plugin = config.config?.plugin;
|
|
101
|
-
const expected =
|
|
138
|
+
const expected = SQUARE_IDENTITY.packageName;
|
|
102
139
|
if (Array.isArray(plugin) && plugin.includes(expected))
|
|
103
|
-
return '✓ OpenCode
|
|
104
|
-
return `○ OpenCode plugin not loaded: ${expected}`;
|
|
140
|
+
return '✓ OpenCode npm plugin loaded';
|
|
141
|
+
return `○ OpenCode npm plugin not loaded: ${expected}`;
|
|
105
142
|
}
|
|
106
143
|
catch (error) {
|
|
107
144
|
return `○ OpenCode runtime unavailable (${error instanceof Error ? error.message : String(error)})`;
|
|
@@ -114,11 +151,3 @@ export function skillLinks(homeDir = os.homedir(), parents = ['.claude', '.agent
|
|
|
114
151
|
kind: 'skill',
|
|
115
152
|
})));
|
|
116
153
|
}
|
|
117
|
-
export function opencodeExtensionLink(homeDir = os.homedir()) {
|
|
118
|
-
const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, '.config');
|
|
119
|
-
return {
|
|
120
|
-
source: path.join(packageRoot(), 'extensions', 'square-opencode.js'),
|
|
121
|
-
target: path.join(configHome, 'opencode', 'plugins', 'square.js'),
|
|
122
|
-
kind: 'extension',
|
|
123
|
-
};
|
|
124
|
-
}
|
package/dist/harness.js
CHANGED
|
@@ -4,7 +4,7 @@ import { wakeGraceMs } from './notifications.js';
|
|
|
4
4
|
import { doctorClaudePlugin, installClaudePlugin, uninstallClaudePlugin, } from './harness-claude.js';
|
|
5
5
|
import { doctorCodexPlugin, installCodexPlugin, uninstallCodexPlugin, } from './harness-codex.js';
|
|
6
6
|
import { doctorPiPackage, installPiPackage, uninstallPiPackage, } from './harness-pi.js';
|
|
7
|
-
import { doctorHarnessLinks, installHarnessLinks,
|
|
7
|
+
import { doctorHarnessLinks, installHarnessLinks, installOpenCodePlugin, skillLinks, uninstallOpenCodePlugin, uninstallHarnessLinks, verifyOpenCodeRuntime, } from './harness-links.js';
|
|
8
8
|
function result(lines, notes = []) {
|
|
9
9
|
return { lines, notes };
|
|
10
10
|
}
|
|
@@ -17,7 +17,7 @@ async function doctorHost(label, inspect) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
function openCodeLinks(homeDir) {
|
|
20
|
-
return
|
|
20
|
+
return skillLinks(homeDir, ['.agents']);
|
|
21
21
|
}
|
|
22
22
|
function readableSquarePath(squarePath) {
|
|
23
23
|
if (squarePath === undefined)
|
|
@@ -65,9 +65,15 @@ const TARGETS = [
|
|
|
65
65
|
{
|
|
66
66
|
name: 'opencode',
|
|
67
67
|
capabilities: ['install', 'uninstall', 'doctor'],
|
|
68
|
-
install: ({ homeDir, force }) => result(
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
install: async ({ homeDir, force }) => result([
|
|
69
|
+
...installOpenCodePlugin(homeDir, force),
|
|
70
|
+
...installHarnessLinks(openCodeLinks(homeDir), force),
|
|
71
|
+
]),
|
|
72
|
+
uninstall: ({ homeDir }) => result([
|
|
73
|
+
...uninstallOpenCodePlugin(homeDir),
|
|
74
|
+
...uninstallHarnessLinks(openCodeLinks(homeDir)),
|
|
75
|
+
]),
|
|
76
|
+
doctor: ({ homeDir }) => result([verifyOpenCodeRuntime(homeDir), ...doctorHarnessLinks(openCodeLinks(homeDir))]),
|
|
71
77
|
},
|
|
72
78
|
{
|
|
73
79
|
name: 'pi',
|
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;
|
package/dist/list.js
CHANGED
|
@@ -15,12 +15,12 @@ function contextLines(lines) {
|
|
|
15
15
|
async function readSquareListItem(filePath, root) {
|
|
16
16
|
let stat;
|
|
17
17
|
try {
|
|
18
|
-
stat = fs.
|
|
18
|
+
stat = await fs.promises.stat(filePath);
|
|
19
19
|
}
|
|
20
20
|
catch {
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
|
-
const square = probeSquare(filePath);
|
|
23
|
+
const square = await probeSquare(filePath);
|
|
24
24
|
if (square === undefined)
|
|
25
25
|
return null;
|
|
26
26
|
const projection = await listPresentation(square).finally(() => closeOpenSquare(square));
|
|
@@ -38,7 +38,7 @@ async function collectSquareList(root, maxDepth) {
|
|
|
38
38
|
async function walk(dir, depth) {
|
|
39
39
|
let entries;
|
|
40
40
|
try {
|
|
41
|
-
entries = fs.
|
|
41
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
42
42
|
}
|
|
43
43
|
catch {
|
|
44
44
|
// Directory vanished or became unreadable mid-walk — skip it, don't abort the scan.
|
package/dist/notifications.d.ts
CHANGED
|
@@ -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[]>;
|
package/dist/notifications.js
CHANGED
|
@@ -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)]);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** OpenCode's server plugin entrypoint for the published Square package. */
|
|
2
|
+
export default function squareOpenCodePlugin(): Promise<{
|
|
3
|
+
event: ({ event }: {
|
|
4
|
+
event: {
|
|
5
|
+
type?: string;
|
|
6
|
+
properties?: Record<string, any>;
|
|
7
|
+
};
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
'shell.env': (input: {
|
|
10
|
+
sessionID?: string;
|
|
11
|
+
}, output: {
|
|
12
|
+
env: Record<string, string>;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
'tool.execute.after': (input: {
|
|
15
|
+
sessionID: string;
|
|
16
|
+
}, output: {
|
|
17
|
+
output: string;
|
|
18
|
+
}) => Promise<void>;
|
|
19
|
+
}>;
|
package/dist/opencode.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
2
|
+
import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
|
|
3
|
+
/** OpenCode's server plugin entrypoint for the published Square package. */
|
|
4
|
+
export default async function squareOpenCodePlugin() {
|
|
5
|
+
const joining = new Map();
|
|
6
|
+
return {
|
|
7
|
+
event: async ({ event }) => {
|
|
8
|
+
if (event.type === 'session.created' || event.type === 'session.updated') {
|
|
9
|
+
const sessionID = event.properties?.sessionID;
|
|
10
|
+
const cwd = event.properties?.info?.directory || process.cwd();
|
|
11
|
+
if (sessionID) {
|
|
12
|
+
try {
|
|
13
|
+
const context = await automaticSessionStart('opencode', sessionID, cwd);
|
|
14
|
+
if (context)
|
|
15
|
+
joining.set(sessionID, context);
|
|
16
|
+
}
|
|
17
|
+
catch { /* startup remains bounded */ }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (event.type === 'session.deleted') {
|
|
21
|
+
const sessionID = event.properties?.sessionID;
|
|
22
|
+
const cwd = event.properties?.info?.directory || process.cwd();
|
|
23
|
+
if (sessionID) {
|
|
24
|
+
joining.delete(sessionID);
|
|
25
|
+
await automaticSessionEnd('opencode', sessionID, cwd);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
'shell.env': async (input, output) => {
|
|
30
|
+
if (input.sessionID)
|
|
31
|
+
output.env.OPENCODE_SESSION_ID = input.sessionID;
|
|
32
|
+
},
|
|
33
|
+
'tool.execute.after': async (input, output) => {
|
|
34
|
+
try {
|
|
35
|
+
const joined = joining.get(input.sessionID);
|
|
36
|
+
if (joined) {
|
|
37
|
+
joining.delete(input.sessionID);
|
|
38
|
+
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${joined}`;
|
|
39
|
+
}
|
|
40
|
+
await presentPendingAtBoundary(input.sessionID, (context) => {
|
|
41
|
+
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${context}`;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// A failed admission remains available at a later boundary.
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/presented.d.ts
CHANGED
|
@@ -6,16 +6,8 @@ export interface PresentedAttention {
|
|
|
6
6
|
actIndex: number;
|
|
7
7
|
}
|
|
8
8
|
export declare function presentedPath(env?: NodeJS.ProcessEnv): string;
|
|
9
|
-
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
-
|
|
13
|
-
export declare function
|
|
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>;
|