@astrosheep/square 0.3.26 → 0.3.28
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/cli/context.d.ts +3 -3
- package/dist/cli/context.js +9 -7
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/observation-commands.js +2 -2
- package/dist/cli/square-commands.js +12 -13
- package/dist/harness-links.d.ts +2 -1
- package/dist/harness-links.js +41 -12
- package/dist/harness.js +11 -5
- package/dist/list.js +3 -3
- package/dist/opencode.d.ts +19 -0
- package/dist/opencode.js +49 -0
- 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/extensions/square-pi.js +33 -4
- package/package.json +5 -1
- package/extensions/square-opencode.js +0 -48
package/dist/artifact.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function createSquareState(options: BuildOptions & {
|
|
|
14
14
|
}, snippet: string): SquareState;
|
|
15
15
|
export declare function encodeSquare(squareState: SquareState): Buffer;
|
|
16
16
|
export declare function decodeSquare(bytes: Buffer): SquareState;
|
|
17
|
-
export declare function writeSquareFile(squarePath: string, squareState: SquareState): void
|
|
18
|
-
export declare function loadSquare(squarePath: string): SquareState
|
|
19
|
-
export declare function probeSquare(squarePath: string): SquareState | undefined
|
|
20
|
-
export declare function diagnoseSquareFile(squarePath: string): DiagnoseResult
|
|
17
|
+
export declare function writeSquareFile(squarePath: string, squareState: SquareState): Promise<void>;
|
|
18
|
+
export declare function loadSquare(squarePath: string): Promise<SquareState>;
|
|
19
|
+
export declare function probeSquare(squarePath: string): Promise<SquareState | undefined>;
|
|
20
|
+
export declare function diagnoseSquareFile(squarePath: string): Promise<DiagnoseResult>;
|
package/dist/artifact.js
CHANGED
|
@@ -10,6 +10,8 @@ const LENGTH_BYTES = 4;
|
|
|
10
10
|
const DIGEST_BYTES = 32;
|
|
11
11
|
const HEADER_BYTES = SQUARE_MAGIC.length + LENGTH_BYTES + DIGEST_BYTES;
|
|
12
12
|
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
13
|
+
const guideNames = ['participant', 'architect', 'brainstorm'];
|
|
14
|
+
const guideContents = new Map(await Promise.all(guideNames.map(async (name) => [name, (await fs.promises.readFile(new URL(`../guides/${name}.md`, import.meta.url), 'utf8')).trim()])));
|
|
13
15
|
function invalidArtifact(detail) {
|
|
14
16
|
return new SquareError('invalid_args', `Invalid square artifact: ${detail}`);
|
|
15
17
|
}
|
|
@@ -260,7 +262,10 @@ function normalizedLines(value) {
|
|
|
260
262
|
}
|
|
261
263
|
function readGuide(name) {
|
|
262
264
|
try {
|
|
263
|
-
|
|
265
|
+
const guide = guideContents.get(name);
|
|
266
|
+
if (guide === undefined)
|
|
267
|
+
throw Object.assign(new Error('missing guide'), { code: 'ENOENT' });
|
|
268
|
+
return guide;
|
|
264
269
|
}
|
|
265
270
|
catch (error) {
|
|
266
271
|
if (error.code === 'ENOENT') {
|
|
@@ -288,9 +293,9 @@ export function encodeSquare(squareState) {
|
|
|
288
293
|
export function decodeSquare(bytes) {
|
|
289
294
|
return validateSquareState(decodeEnvelope(bytes, SQUARE_MAGIC));
|
|
290
295
|
}
|
|
291
|
-
function readArtifact(squarePath) {
|
|
296
|
+
async function readArtifact(squarePath) {
|
|
292
297
|
try {
|
|
293
|
-
return fs.
|
|
298
|
+
return await fs.promises.readFile(squarePath);
|
|
294
299
|
}
|
|
295
300
|
catch (error) {
|
|
296
301
|
if (error.code === 'ENOENT') {
|
|
@@ -304,37 +309,37 @@ function requireSquareExtension(squarePath) {
|
|
|
304
309
|
throw new SquareError('invalid_args', `Square artifacts must use the .square extension: ${squarePath}`);
|
|
305
310
|
}
|
|
306
311
|
}
|
|
307
|
-
function atomicWrite(target, bytes) {
|
|
312
|
+
async function atomicWrite(target, bytes) {
|
|
308
313
|
const temporary = path.join(path.dirname(target), `.${path.basename(target)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
309
|
-
fs.
|
|
314
|
+
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
310
315
|
try {
|
|
311
|
-
fs.
|
|
312
|
-
fs.
|
|
316
|
+
await fs.promises.writeFile(temporary, bytes);
|
|
317
|
+
await fs.promises.rename(temporary, target);
|
|
313
318
|
}
|
|
314
319
|
catch (error) {
|
|
315
320
|
try {
|
|
316
|
-
fs.
|
|
321
|
+
await fs.promises.unlink(temporary);
|
|
317
322
|
}
|
|
318
323
|
catch { }
|
|
319
324
|
throw error;
|
|
320
325
|
}
|
|
321
326
|
}
|
|
322
|
-
export function writeSquareFile(squarePath, squareState) {
|
|
327
|
+
export async function writeSquareFile(squarePath, squareState) {
|
|
323
328
|
requireSquareExtension(squarePath);
|
|
324
|
-
atomicWrite(squarePath, encodeSquare(squareState));
|
|
329
|
+
await atomicWrite(squarePath, encodeSquare(squareState));
|
|
325
330
|
}
|
|
326
|
-
export function loadSquare(squarePath) {
|
|
331
|
+
export async function loadSquare(squarePath) {
|
|
327
332
|
requireSquareExtension(squarePath);
|
|
328
|
-
return decodeSquare(readArtifact(squarePath));
|
|
333
|
+
return decodeSquare(await readArtifact(squarePath));
|
|
329
334
|
}
|
|
330
|
-
export function probeSquare(squarePath) {
|
|
335
|
+
export async function probeSquare(squarePath) {
|
|
331
336
|
if (!squarePath.endsWith('.square'))
|
|
332
337
|
return undefined;
|
|
333
338
|
let descriptor;
|
|
334
339
|
try {
|
|
335
|
-
descriptor = fs.
|
|
340
|
+
descriptor = await fs.promises.open(squarePath, 'r');
|
|
336
341
|
const magic = Buffer.alloc(SQUARE_MAGIC.length);
|
|
337
|
-
if (
|
|
342
|
+
if ((await descriptor.read(magic, 0, magic.length, 0)).bytesRead !== magic.length || !magic.equals(SQUARE_MAGIC)) {
|
|
338
343
|
return undefined;
|
|
339
344
|
}
|
|
340
345
|
}
|
|
@@ -343,18 +348,18 @@ export function probeSquare(squarePath) {
|
|
|
343
348
|
}
|
|
344
349
|
finally {
|
|
345
350
|
if (descriptor !== undefined)
|
|
346
|
-
|
|
351
|
+
await descriptor.close();
|
|
347
352
|
}
|
|
348
353
|
try {
|
|
349
|
-
return loadSquare(squarePath);
|
|
354
|
+
return await loadSquare(squarePath);
|
|
350
355
|
}
|
|
351
356
|
catch {
|
|
352
357
|
return undefined;
|
|
353
358
|
}
|
|
354
359
|
}
|
|
355
|
-
export function diagnoseSquareFile(squarePath) {
|
|
360
|
+
export async function diagnoseSquareFile(squarePath) {
|
|
356
361
|
try {
|
|
357
|
-
return { problems: [], state: loadSquare(squarePath) };
|
|
362
|
+
return { problems: [], state: await loadSquare(squarePath) };
|
|
358
363
|
}
|
|
359
364
|
catch (error) {
|
|
360
365
|
return {
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export interface CommandSpec<Intent = unknown, Result = void> {
|
|
|
10
10
|
execute(intent: Intent, context: CommandContext): Promise<Result> | Result;
|
|
11
11
|
present(result: Result, context: CommandContext): void;
|
|
12
12
|
}
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function resolveBody(arg: string): string
|
|
15
|
-
export declare function readPipedBodyFallback(): string | undefined
|
|
13
|
+
export declare function readStdin(): Promise<string>;
|
|
14
|
+
export declare function resolveBody(arg: string): Promise<string>;
|
|
15
|
+
export declare function readPipedBodyFallback(): Promise<string | undefined>;
|
|
16
16
|
export declare function fail(message: string, exitCode?: number): never;
|
|
17
17
|
export declare function usage(command: string): never;
|
|
18
18
|
export declare function requireValue(args: string[], index: number, flag: string): string;
|
package/dist/cli/context.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import os from 'node:os';
|
|
3
2
|
import { commandUsageHint } from '../help.js';
|
|
4
3
|
import { parseParticipantList, validateName } from '../model.js';
|
|
5
4
|
import { localParticipantName } from '../registry.js';
|
|
6
|
-
export function
|
|
5
|
+
export async function readStdin() {
|
|
7
6
|
try {
|
|
8
|
-
|
|
7
|
+
let content = '';
|
|
8
|
+
for await (const chunk of process.stdin.setEncoding('utf8'))
|
|
9
|
+
content += chunk;
|
|
10
|
+
return content;
|
|
9
11
|
}
|
|
10
12
|
catch {
|
|
11
13
|
return '';
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
export function resolveBody(arg) {
|
|
15
|
-
return arg === '-' ?
|
|
16
|
+
export async function resolveBody(arg) {
|
|
17
|
+
return arg === '-' ? readStdin() : arg;
|
|
16
18
|
}
|
|
17
|
-
export function readPipedBodyFallback() {
|
|
19
|
+
export async function readPipedBodyFallback() {
|
|
18
20
|
if (process.stdin.isTTY)
|
|
19
21
|
return undefined;
|
|
20
|
-
const content =
|
|
22
|
+
const content = await readStdin();
|
|
21
23
|
return content.trim() === '' ? undefined : content;
|
|
22
24
|
}
|
|
23
25
|
export function fail(message, exitCode = 2) {
|
|
@@ -8,9 +8,9 @@ export const doctorCommand = {
|
|
|
8
8
|
usage(context.command);
|
|
9
9
|
return undefined;
|
|
10
10
|
},
|
|
11
|
-
execute(_intent, context) {
|
|
11
|
+
async execute(_intent, context) {
|
|
12
12
|
const squarePath = requireSquarePath(context);
|
|
13
|
-
const diagnosis = diagnoseSquareFile(squarePath);
|
|
13
|
+
const diagnosis = await diagnoseSquareFile(squarePath);
|
|
14
14
|
if (diagnosis.unfixable !== undefined || diagnosis.state === undefined) {
|
|
15
15
|
return {
|
|
16
16
|
output: withPathOutput(squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the snapshot could not be decoded')),
|
|
@@ -12,7 +12,7 @@ import { cmdWatch } from '../watch.js';
|
|
|
12
12
|
import { openSquare } from '../square-file-adapter.js';
|
|
13
13
|
import { closeOpenSquare } from '../open-square.js';
|
|
14
14
|
import { historyPresentation, participantsPresentation, statusPresentation } from '../views.js';
|
|
15
|
-
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger,
|
|
15
|
+
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdin, requireParticipant, requireSquarePath, requireValue, usage, } from './context.js';
|
|
16
16
|
const STATUS_PARTICIPANT_PREVIEW_LIMIT = 10;
|
|
17
17
|
export const listCommand = {
|
|
18
18
|
parse: (argv) => argv,
|
|
@@ -470,7 +470,7 @@ function hookCommand(runHook) {
|
|
|
470
470
|
return {
|
|
471
471
|
parse(argv, context) { if (argv.length > 0)
|
|
472
472
|
usage(context.command); return undefined; },
|
|
473
|
-
execute: () => runHook(
|
|
473
|
+
execute: async () => runHook(await readStdin()),
|
|
474
474
|
present: (result) => process.stdout.write(result),
|
|
475
475
|
};
|
|
476
476
|
}
|
|
@@ -8,7 +8,7 @@ import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
|
8
8
|
import { closeOpenSquare } from '../open-square.js';
|
|
9
9
|
import { openParticipant, Square } from '../square-wiring.js';
|
|
10
10
|
import { entryPresentation, eventPresentation } from '../views.js';
|
|
11
|
-
import { fail, parseHardCap, parsePositiveInteger,
|
|
11
|
+
import { fail, parseHardCap, parsePositiveInteger, readStdin, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
|
|
12
12
|
function parseBuild(argv) {
|
|
13
13
|
const options = { force: false, hardCap: null };
|
|
14
14
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -41,16 +41,16 @@ function parseBuild(argv) {
|
|
|
41
41
|
if (options.throttlePerMinute !== undefined && options.throttlePerMinute <= 0) {
|
|
42
42
|
fail('Invalid build option: --throttle must be a positive integer.');
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
if (snippet.trim() === '')
|
|
46
|
-
fail('Missing Markdown body snippet on stdin.');
|
|
47
|
-
return { options, snippet };
|
|
44
|
+
return { options, snippet: '' };
|
|
48
45
|
}
|
|
49
46
|
export const buildCommand = {
|
|
50
47
|
parse: (argv) => parseBuild(argv),
|
|
51
48
|
async execute(intent, context) {
|
|
52
49
|
const squarePath = requireSquarePath(context);
|
|
53
|
-
await
|
|
50
|
+
const snippet = await readStdin();
|
|
51
|
+
if (snippet.trim() === '')
|
|
52
|
+
fail('Missing Markdown body snippet on stdin.');
|
|
53
|
+
await createSquare(squarePath, intent.options, snippet);
|
|
54
54
|
const cap = intent.options.hardCap === null ? 'unlimited' : formatHardCap(intent.options.hardCap);
|
|
55
55
|
const throttle = intent.options.throttlePerMinute === undefined ? [] : [` · throttle ${intent.options.throttlePerMinute}/min`];
|
|
56
56
|
return withPathOutput(squarePath, ['✓ built', ` · cap ${cap}`, ...throttle, ' · participants (none seeded — first join adds names)'].join('\n'), { participantCount: 0 });
|
|
@@ -164,9 +164,8 @@ function parseActivity(argv, context) {
|
|
|
164
164
|
const reach = bell ? 'bell' : undefined;
|
|
165
165
|
if (bodyArgs.length !== 1) {
|
|
166
166
|
if (bodyArgs.length === 0) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return { name: requireParticipant(context.name), activity: piped, force, noWait, reach, reply };
|
|
167
|
+
if (!process.stdin.isTTY)
|
|
168
|
+
return { name: requireParticipant(context.name), activity: '-', force, noWait, reach, reply };
|
|
170
169
|
}
|
|
171
170
|
fail("express requires a body argument (a quoted string or '-' with piped stdin)");
|
|
172
171
|
}
|
|
@@ -177,7 +176,7 @@ export const expressCommand = {
|
|
|
177
176
|
async execute(intent, context) {
|
|
178
177
|
const squarePath = requireSquarePath(context);
|
|
179
178
|
await sweepPendingNotifications(squarePath);
|
|
180
|
-
const body = resolveBody(intent.activity);
|
|
179
|
+
const body = await resolveBody(intent.activity);
|
|
181
180
|
const reachArg = intent.reach === 'bell' ? ' --bell' : '';
|
|
182
181
|
await cmdActivity(squarePath, intent.name, body, (value) => value, {
|
|
183
182
|
force: intent.force,
|
|
@@ -269,13 +268,13 @@ export const listeningCommand = {
|
|
|
269
268
|
function parseDone(argv, context) {
|
|
270
269
|
if (argv.length > 1)
|
|
271
270
|
usage(context.command);
|
|
272
|
-
return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] :
|
|
271
|
+
return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] : undefined };
|
|
273
272
|
}
|
|
274
273
|
export const doneCommand = {
|
|
275
274
|
parse: parseDone,
|
|
276
275
|
async execute(intent, context) {
|
|
277
276
|
const squarePath = requireSquarePath(context);
|
|
278
|
-
const body = resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim();
|
|
277
|
+
const body = (await resolveBody(intent.body ?? (process.stdin.isTTY ? '' : '-'))).replace(/\r\n/g, '\n').trim();
|
|
279
278
|
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
280
279
|
const participant = await square.join(intent.name);
|
|
281
280
|
const result = await participant.done(body);
|
|
@@ -300,7 +299,7 @@ export const holdCommand = {
|
|
|
300
299
|
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
301
300
|
try {
|
|
302
301
|
const participant = await square.join(intent.name);
|
|
303
|
-
const result = await participant.hold(resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim());
|
|
302
|
+
const result = await participant.hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
|
|
304
303
|
await square.close();
|
|
305
304
|
const presentationSquare = await openSquare(squarePath, { clock: nowMs });
|
|
306
305
|
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
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/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.
|
|
@@ -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
|
+
}
|
|
@@ -13,7 +13,7 @@ export interface SquareBuildOptions {
|
|
|
13
13
|
notifier?: WakeNotifier;
|
|
14
14
|
}
|
|
15
15
|
export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'notifier'>): Promise<OpenSquare>;
|
|
16
|
-
export declare function probeSquare(squarePath: string): OpenSquare | undefined
|
|
16
|
+
export declare function probeSquare(squarePath: string): Promise<OpenSquare | undefined>;
|
|
17
17
|
export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
|
|
18
18
|
export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
|
|
19
19
|
/** Wait for any bound artifact to change; delivery callers re-project after the edge. */
|
|
@@ -2,17 +2,13 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { createSquareState, probeSquareFile, writeSquareSnapshot, withSquareFileLock, openSquareCell, createMemoryCell, } from './square-storage.js';
|
|
3
3
|
import { InternalSquareError, SquareError, } from './model.js';
|
|
4
4
|
import { closeOpenSquare } from './open-square.js';
|
|
5
|
-
/** The current CLI file mutation boundary. */
|
|
6
|
-
function writeSquareState(squarePath, squareState) {
|
|
7
|
-
writeSquareSnapshot(squarePath, squareState);
|
|
8
|
-
}
|
|
9
5
|
/** File-owned artifact creation for the CLI and path-backed public facade. */
|
|
10
6
|
export async function createSquare(squarePath, options, snippet) {
|
|
11
|
-
await withSquareFileLock(squarePath, () => {
|
|
12
|
-
if (fs.
|
|
7
|
+
await withSquareFileLock(squarePath, async () => {
|
|
8
|
+
if (await fs.promises.access(squarePath).then(() => true, () => false) && !options.force) {
|
|
13
9
|
throw new InternalSquareError('conflict', `Refusing to overwrite existing square: ${squarePath}\nPass -f to overwrite.`);
|
|
14
10
|
}
|
|
15
|
-
writeSquareSnapshot(squarePath, createSquareState(options, snippet));
|
|
11
|
+
await writeSquareSnapshot(squarePath, await createSquareState(options, snippet));
|
|
16
12
|
});
|
|
17
13
|
}
|
|
18
14
|
function validateBuildOptions(options) {
|
|
@@ -39,8 +35,8 @@ export async function openSquare(squarePath, options = {}) {
|
|
|
39
35
|
throw error;
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
|
-
export function probeSquare(squarePath) {
|
|
43
|
-
const state = probeSquareFile(squarePath);
|
|
38
|
+
export async function probeSquare(squarePath) {
|
|
39
|
+
const state = await probeSquareFile(squarePath);
|
|
44
40
|
return state === undefined ? undefined : { cell: createMemoryCell(state), clock: Date.now, location: squarePath };
|
|
45
41
|
}
|
|
46
42
|
export async function buildSquare(squarePath, options) {
|
package/dist/square-storage.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { type SquareState } from './model.js';
|
|
|
7
7
|
* protects it.
|
|
8
8
|
*/
|
|
9
9
|
export { createSquareState, };
|
|
10
|
-
export declare function readSquareFile(squarePath: string): SquareState
|
|
11
|
-
export declare function probeSquareFile(squarePath: string): SquareState | undefined
|
|
10
|
+
export declare function readSquareFile(squarePath: string): Promise<SquareState>;
|
|
11
|
+
export declare function probeSquareFile(squarePath: string): Promise<SquareState | undefined>;
|
|
12
12
|
export declare function diagnoseSquareFile(squarePath: string): ReturnType<typeof diagnoseArtifactFile>;
|
|
13
|
-
export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): void
|
|
13
|
+
export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): Promise<void>;
|
|
14
14
|
export declare function withSquareFileLock<T>(squarePath: string, fn: () => T | Promise<T>): Promise<T>;
|
|
15
15
|
/** In-process cell for fast application tests and embedded consumers. */
|
|
16
16
|
export declare function createMemoryCell(initial: SquareState): StateCell;
|
package/dist/square-storage.js
CHANGED
|
@@ -9,17 +9,17 @@ import { LOCK_RETRY_MS, LOCK_STALE_MS } from './runtime.js';
|
|
|
9
9
|
* protects it.
|
|
10
10
|
*/
|
|
11
11
|
export { createSquareState, };
|
|
12
|
-
export function readSquareFile(squarePath) {
|
|
12
|
+
export async function readSquareFile(squarePath) {
|
|
13
13
|
return loadSquare(squarePath);
|
|
14
14
|
}
|
|
15
|
-
export function probeSquareFile(squarePath) {
|
|
15
|
+
export async function probeSquareFile(squarePath) {
|
|
16
16
|
return probeSquare(squarePath);
|
|
17
17
|
}
|
|
18
|
-
export function diagnoseSquareFile(squarePath) {
|
|
18
|
+
export async function diagnoseSquareFile(squarePath) {
|
|
19
19
|
return diagnoseArtifactFile(squarePath);
|
|
20
20
|
}
|
|
21
|
-
export function writeSquareSnapshot(squarePath, squareState) {
|
|
22
|
-
writeSquareFile(squarePath, squareState);
|
|
21
|
+
export async function writeSquareSnapshot(squarePath, squareState) {
|
|
22
|
+
await writeSquareFile(squarePath, squareState);
|
|
23
23
|
}
|
|
24
24
|
export function withSquareFileLock(squarePath, fn) {
|
|
25
25
|
return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, fn);
|
|
@@ -102,9 +102,9 @@ export function createMemoryCell(initial) {
|
|
|
102
102
|
};
|
|
103
103
|
return cell;
|
|
104
104
|
}
|
|
105
|
-
function fileFingerprint(squarePath) {
|
|
105
|
+
async function fileFingerprint(squarePath) {
|
|
106
106
|
try {
|
|
107
|
-
const stat = fs.
|
|
107
|
+
const stat = await fs.promises.stat(squarePath);
|
|
108
108
|
return `${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
109
109
|
}
|
|
110
110
|
catch {
|
|
@@ -115,36 +115,39 @@ function fileFingerprint(squarePath) {
|
|
|
115
115
|
export function createFileCell(squarePath) {
|
|
116
116
|
let closed = false;
|
|
117
117
|
let version = 0;
|
|
118
|
-
let fingerprint
|
|
118
|
+
let fingerprint;
|
|
119
119
|
let cached;
|
|
120
|
-
function observe() {
|
|
121
|
-
const next = fileFingerprint(squarePath);
|
|
122
|
-
if (
|
|
120
|
+
async function observe() {
|
|
121
|
+
const next = await fileFingerprint(squarePath);
|
|
122
|
+
if (fingerprint === undefined) {
|
|
123
|
+
fingerprint = next;
|
|
124
|
+
}
|
|
125
|
+
else if (next !== fingerprint) {
|
|
123
126
|
fingerprint = next;
|
|
124
127
|
cached = undefined;
|
|
125
128
|
version += 1;
|
|
126
129
|
}
|
|
127
130
|
return fingerprint;
|
|
128
131
|
}
|
|
129
|
-
function currentState() {
|
|
130
|
-
const observed = observe();
|
|
132
|
+
async function currentState() {
|
|
133
|
+
const observed = await observe();
|
|
131
134
|
if (cached?.fingerprint === observed)
|
|
132
135
|
return cloneState(cached.state);
|
|
133
|
-
const decoded = readSquareFile(squarePath);
|
|
136
|
+
const decoded = await readSquareFile(squarePath);
|
|
134
137
|
cached = { fingerprint: observed, state: cloneState(decoded) };
|
|
135
138
|
return cloneState(cached.state);
|
|
136
139
|
}
|
|
137
140
|
return {
|
|
138
141
|
async transact(fn) {
|
|
139
142
|
assertCellOpen(closed);
|
|
140
|
-
return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, () => {
|
|
143
|
+
return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, async () => {
|
|
141
144
|
assertCellOpen(closed);
|
|
142
|
-
const current = currentState();
|
|
145
|
+
const current = await currentState();
|
|
143
146
|
const working = cloneState(current);
|
|
144
147
|
const outcome = fn(working, version);
|
|
145
148
|
if (outcome.state !== undefined) {
|
|
146
|
-
writeSquareSnapshot(squarePath, outcome.state);
|
|
147
|
-
fingerprint = fileFingerprint(squarePath);
|
|
149
|
+
await writeSquareSnapshot(squarePath, outcome.state);
|
|
150
|
+
fingerprint = await fileFingerprint(squarePath);
|
|
148
151
|
cached = { fingerprint, state: cloneState(outcome.state) };
|
|
149
152
|
version += 1;
|
|
150
153
|
}
|
|
@@ -153,13 +156,13 @@ export function createFileCell(squarePath) {
|
|
|
153
156
|
},
|
|
154
157
|
async read() {
|
|
155
158
|
assertCellOpen(closed);
|
|
156
|
-
return { state: currentState(), version };
|
|
159
|
+
return { state: await currentState(), version };
|
|
157
160
|
},
|
|
158
161
|
async changed(sinceVersion, timeoutMs) {
|
|
159
162
|
assertCellOpen(closed);
|
|
160
163
|
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
161
164
|
while (true) {
|
|
162
|
-
observe();
|
|
165
|
+
await observe();
|
|
163
166
|
if (version > sinceVersion)
|
|
164
167
|
return true;
|
|
165
168
|
const remaining = deadline - Date.now();
|
package/extensions/square-pi.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
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';
|
|
5
|
+
|
|
6
|
+
const PI_SEND_TIMEOUT_MS = 5_000;
|
|
4
7
|
|
|
5
8
|
export function pendingInbox(inbox) {
|
|
6
9
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
@@ -52,8 +55,22 @@ export default function squarePiExtension(pi) {
|
|
|
52
55
|
watcherAbort = undefined;
|
|
53
56
|
};
|
|
54
57
|
|
|
58
|
+
const pause = (signal, delayMs) => new Promise((resolve) => {
|
|
59
|
+
const finish = () => {
|
|
60
|
+
signal.removeEventListener('abort', finish);
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
resolve();
|
|
63
|
+
};
|
|
64
|
+
const timer = setTimeout(finish, delayMs);
|
|
65
|
+
signal.addEventListener('abort', finish, { once: true });
|
|
66
|
+
});
|
|
67
|
+
|
|
55
68
|
const wake = async (piContext, token, signal) => {
|
|
56
69
|
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
70
|
+
if (lookupSessionBindings(sessionId).length === 0) {
|
|
71
|
+
await pause(signal, 1_000);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
57
74
|
const deferredRetry = retryAfterChange;
|
|
58
75
|
const pending = await waitForSessionPending(sessionId, 30_000, {
|
|
59
76
|
signal,
|
|
@@ -78,10 +95,21 @@ export default function squarePiExtension(pi) {
|
|
|
78
95
|
const delivered = await presentPendingAtBoundary(
|
|
79
96
|
sessionId,
|
|
80
97
|
async (content) => {
|
|
81
|
-
|
|
98
|
+
const send = Promise.resolve(pi.sendMessage(
|
|
82
99
|
{ customType: 'square', content, display: true },
|
|
83
100
|
{ deliverAs: 'steer', triggerTurn: true },
|
|
84
|
-
);
|
|
101
|
+
));
|
|
102
|
+
let timer;
|
|
103
|
+
try {
|
|
104
|
+
await Promise.race([
|
|
105
|
+
send,
|
|
106
|
+
new Promise((_, reject) => {
|
|
107
|
+
timer = setTimeout(() => reject(new Error('Pi native injection timed out')), PI_SEND_TIMEOUT_MS);
|
|
108
|
+
}),
|
|
109
|
+
]);
|
|
110
|
+
} finally {
|
|
111
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
112
|
+
}
|
|
85
113
|
return true;
|
|
86
114
|
},
|
|
87
115
|
);
|
|
@@ -106,7 +134,8 @@ export default function squarePiExtension(pi) {
|
|
|
106
134
|
sessionCwd = ctx.cwd || process.cwd();
|
|
107
135
|
previousSessionId = process.env.SQUARE_PI_SESSION_ID;
|
|
108
136
|
process.env.SQUARE_PI_SESSION_ID = sessionId;
|
|
109
|
-
|
|
137
|
+
joiningContext = undefined;
|
|
138
|
+
void automaticSessionStart('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
110
139
|
watcherAbort = new AbortController();
|
|
111
140
|
const token = generation;
|
|
112
141
|
watcher = wake(ctx, token, watcherAbort.signal).catch(() => undefined);
|
|
@@ -138,7 +167,7 @@ export default function squarePiExtension(pi) {
|
|
|
138
167
|
stopWatcher();
|
|
139
168
|
for (const waiter of settledWaiters) waiter.resolve();
|
|
140
169
|
settledWaiters = [];
|
|
141
|
-
if (sessionId && sessionCwd)
|
|
170
|
+
if (sessionId && sessionCwd) void automaticSessionEnd('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
142
171
|
if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
|
|
143
172
|
if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
|
|
144
173
|
else process.env.SQUARE_PI_SESSION_ID = previousSessionId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
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": {
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"./paseo": {
|
|
53
53
|
"types": "./dist/paseo.d.ts",
|
|
54
54
|
"default": "./dist/paseo.js"
|
|
55
|
+
},
|
|
56
|
+
"./server": {
|
|
57
|
+
"types": "./dist/opencode.d.ts",
|
|
58
|
+
"default": "./dist/opencode.js"
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
61
|
"main": "dist/index.js",
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { presentPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
|
-
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
3
|
-
|
|
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) joining.set(sessionID, context);
|
|
15
|
-
} catch { /* startup remains bounded */ }
|
|
16
|
-
}
|
|
17
|
-
} else if (event.type === 'session.deleted') {
|
|
18
|
-
const sessionID = event.properties?.sessionID;
|
|
19
|
-
const cwd = event.properties?.info?.directory || process.cwd();
|
|
20
|
-
if (sessionID) {
|
|
21
|
-
joining.delete(sessionID);
|
|
22
|
-
await automaticSessionEnd('opencode', sessionID, cwd);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
'shell.env': async (input, output) => {
|
|
27
|
-
if (input.sessionID) output.env.OPENCODE_SESSION_ID = input.sessionID;
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
'tool.execute.after': async (input, output) => {
|
|
31
|
-
try {
|
|
32
|
-
const joined = joining.get(input.sessionID);
|
|
33
|
-
if (joined) {
|
|
34
|
-
joining.delete(input.sessionID);
|
|
35
|
-
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${joined}`;
|
|
36
|
-
}
|
|
37
|
-
await presentPendingAtBoundary(
|
|
38
|
-
input.sessionID,
|
|
39
|
-
(context) => {
|
|
40
|
-
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${context}`;
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
} catch {
|
|
44
|
-
// A failed admission remains available at a later boundary.
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|