@astrosheep/square 0.3.92 → 0.3.94
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/dist/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +2 -2
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/catch-decisions.d.ts +4 -6
- package/dist/catch-decisions.js +4 -4
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -25
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +26 -14
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +2 -2
- package/dist/cli/registry.js +6 -3
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +46 -54
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -11
- package/dist/decisions.js +9 -20
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +38 -38
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +1 -1
- package/dist/harness-links.d.ts +1 -10
- package/dist/harness-links.js +144 -109
- package/dist/harness.js +4 -13
- package/dist/help.js +12 -10
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +8 -15
- package/dist/model.js +4 -7
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +6 -5
- package/dist/operation-presentation.d.ts +5 -4
- package/dist/operation-presentation.js +50 -45
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +13 -33
- package/dist/presentation-operations.js +4 -4
- package/dist/presentation.d.ts +5 -8
- package/dist/presentation.js +33 -43
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.js +0 -14
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -22
- package/dist/sqlite-square.js +138 -161
- package/dist/square-api.d.ts +15 -10
- package/dist/square-api.js +26 -22
- package/dist/square-core.d.ts +0 -5
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +56 -21
- package/dist/square-facade.d.ts +45 -87
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -69
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +2 -1
- package/dist/square-migration.js +52 -30
- package/dist/square-sqlite.d.ts +6 -2
- package/dist/square-sqlite.js +54 -28
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +22 -54
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +21 -10
- package/dist/views.js +38 -38
- package/dist/wakes.d.ts +1 -2
- package/dist/wakes.js +1 -1
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +142 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +33 -4
- package/guides/primitive-history-consolidation.md +1 -1
- package/guides/row-storage-design.md +8 -8
- package/guides/square-api.md +244 -88
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/harness-links.js
CHANGED
|
@@ -1,79 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
5
4
|
import crossSpawn from 'cross-spawn';
|
|
5
|
+
import { applyEdits, createScanner, findNodeAtLocation, modify, parse, parseTree } from 'jsonc-parser';
|
|
6
6
|
import { SQUARE_IDENTITY } from './identity.js';
|
|
7
7
|
import { truncateExternalDiagnostic } from './presentation.js';
|
|
8
|
-
function packageRoot() {
|
|
9
|
-
// Emitted modules live in dist; package assets are one level above them.
|
|
10
|
-
return fileURLToPath(new URL('../', import.meta.url));
|
|
11
|
-
}
|
|
12
|
-
export function installHarnessLinks(links, force = false) {
|
|
13
|
-
const prepared = links.map((link) => {
|
|
14
|
-
if (!fs.existsSync(link.source)) {
|
|
15
|
-
throw new Error(`Harness link source is missing: ${link.source}`);
|
|
16
|
-
}
|
|
17
|
-
const existing = (() => {
|
|
18
|
-
try {
|
|
19
|
-
return fs.lstatSync(link.target);
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (error.code === 'ENOENT')
|
|
23
|
-
return undefined;
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
})();
|
|
27
|
-
return {
|
|
28
|
-
...link,
|
|
29
|
-
existing,
|
|
30
|
-
sourceIsDirectory: fs.statSync(link.source).isDirectory(),
|
|
31
|
-
alreadyManaged: existing !== undefined && sameLink(link.source, link.target),
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
for (const { target, existing, alreadyManaged } of prepared) {
|
|
35
|
-
if (existing !== undefined && !alreadyManaged && !force)
|
|
36
|
-
throw new Error(`Refusing to overwrite existing link: ${target}\nPass -f to replace it.`);
|
|
37
|
-
}
|
|
38
|
-
for (const { source, target, existing, sourceIsDirectory, alreadyManaged } of prepared) {
|
|
39
|
-
if (alreadyManaged)
|
|
40
|
-
continue;
|
|
41
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
42
|
-
if (existing !== undefined)
|
|
43
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
44
|
-
const symlinkType = os.platform() === 'win32' && sourceIsDirectory ? 'junction' : 'file';
|
|
45
|
-
fs.symlinkSync(source, target, symlinkType);
|
|
46
|
-
}
|
|
47
|
-
return prepared.map(({ target }) => target);
|
|
48
|
-
}
|
|
49
|
-
function sameLink(source, target) {
|
|
50
|
-
try {
|
|
51
|
-
return fs.realpathSync(source) === fs.realpathSync(target);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
export function uninstallHarnessLinks(links) {
|
|
58
|
-
const removed = [];
|
|
59
|
-
for (const link of links) {
|
|
60
|
-
try {
|
|
61
|
-
if (fs.lstatSync(link.target).isSymbolicLink() && sameLink(link.source, link.target)) {
|
|
62
|
-
fs.rmSync(link.target, { force: true });
|
|
63
|
-
removed.push(link.target);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// A missing or user-owned target is preserved.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return removed;
|
|
71
|
-
}
|
|
72
|
-
export function doctorHarnessLinks(links) {
|
|
73
|
-
return links.map((link) => sameLink(link.source, link.target)
|
|
74
|
-
? `✓ Square ${link.kind ?? 'link'} ${link.target}`
|
|
75
|
-
: `○ Square ${link.kind ?? 'link'} missing ${link.target}`);
|
|
76
|
-
}
|
|
77
8
|
function runOpenCode(homeDir, args) {
|
|
78
9
|
const prefix = (() => {
|
|
79
10
|
try {
|
|
@@ -94,39 +25,150 @@ function runOpenCode(homeDir, args) {
|
|
|
94
25
|
return { status: result.status ?? 1, stdout: result.stdout || '', stderr: result.stderr || '' };
|
|
95
26
|
}
|
|
96
27
|
export function installOpenCodePlugin(homeDir, force = false, run = runOpenCode) {
|
|
97
|
-
//
|
|
28
|
+
// Explicit `square install` replaces owned pins/duplicates with one unpinned
|
|
29
|
+
// current-field entry; updating an exact old pin otherwise succeeds unchanged.
|
|
98
30
|
void force;
|
|
99
|
-
const
|
|
31
|
+
const configs = readOpenCodeConfigs(homeDir);
|
|
32
|
+
const entries = configs.flatMap((config) => config.entries);
|
|
33
|
+
const options = entries.map((entry) => entry.options).filter((value) => value !== undefined);
|
|
34
|
+
if (options.some((value) => !isDeepStrictEqual(value, options[0]))) {
|
|
35
|
+
throw new Error('Conflicting Square plugin options in OpenCode configuration; reconcile the owned plugin/plugins entries before installing.');
|
|
36
|
+
}
|
|
37
|
+
const canonical = options.length ? { package: SQUARE_IDENTITY.packageName, options: options[0] } : SQUARE_IDENTITY.packageName;
|
|
38
|
+
const targets = configs.length ? configs : [{ target: openCodeConfigPaths(homeDir)[0], source: '{}', entries: [] }];
|
|
39
|
+
const alreadyCanonical = entries.length === 1 && configs[0]?.entries[0]?.field === 'plugins'
|
|
40
|
+
&& isDeepStrictEqual(entries[0].value, canonical);
|
|
41
|
+
const changes = alreadyCanonical ? [] : targets.map(({ target, source, entries }, index) => {
|
|
42
|
+
let next = source;
|
|
43
|
+
for (const { field, index } of entries.toReversed())
|
|
44
|
+
next = removeOpenCodeEntry(next, field, index);
|
|
45
|
+
if (index === 0)
|
|
46
|
+
next = applyEdits(next, modify(next, ['plugins', -1], canonical, {}));
|
|
47
|
+
return { target, source, next };
|
|
48
|
+
}).filter(({ source, next }) => source !== next);
|
|
49
|
+
// Appending just our element avoids native add's whole-array rewrite, which
|
|
50
|
+
// drops comments belonging to unrelated plugins. Native add still owns npm.
|
|
51
|
+
for (const { target, next } of changes) {
|
|
52
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
53
|
+
fs.writeFileSync(target, next);
|
|
54
|
+
}
|
|
55
|
+
if (!entries.some(({ spec }) => spec === SQUARE_IDENTITY.packageName)) {
|
|
56
|
+
try {
|
|
57
|
+
openCodeCommand(homeDir, ['plugin', 'add', SQUARE_IDENTITY.packageName], run, 'install');
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Undo only our preparation, never files the native command/user changed.
|
|
61
|
+
for (const { target, source, next } of changes) {
|
|
62
|
+
if (fs.existsSync(target) && fs.readFileSync(target, 'utf8') === next) {
|
|
63
|
+
if (configs.length)
|
|
64
|
+
fs.writeFileSync(target, source);
|
|
65
|
+
else
|
|
66
|
+
fs.rmSync(target);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Even add can reuse an older cached package. Always request the latest.
|
|
73
|
+
openCodeCommand(homeDir, ['plugin', 'update', SQUARE_IDENTITY.packageName], run, 'update');
|
|
74
|
+
return [SQUARE_IDENTITY.packageName];
|
|
75
|
+
}
|
|
76
|
+
function openCodeCommand(homeDir, args, run, operation) {
|
|
100
77
|
const result = run(homeDir, args);
|
|
101
78
|
if (result.status !== 0)
|
|
102
|
-
throw new Error(`OpenCode
|
|
103
|
-
return
|
|
79
|
+
throw new Error(`OpenCode ${operation} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
|
|
80
|
+
return result.stdout;
|
|
104
81
|
}
|
|
105
|
-
function
|
|
82
|
+
function openCodeConfigPaths(homeDir) {
|
|
106
83
|
const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, '.config');
|
|
107
|
-
|
|
84
|
+
const root = path.resolve(configHome, 'opencode');
|
|
85
|
+
return [root, path.join(root, '.opencode')].flatMap((directory) => ['opencode.json', 'opencode.jsonc'].map((name) => path.join(directory, name)));
|
|
86
|
+
}
|
|
87
|
+
function readOpenCodeConfigs(homeDir) {
|
|
88
|
+
return openCodeConfigPaths(homeDir).flatMap((target) => {
|
|
89
|
+
let source;
|
|
90
|
+
try {
|
|
91
|
+
source = fs.readFileSync(target, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error.code === 'ENOENT')
|
|
95
|
+
return [];
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
const errors = [];
|
|
99
|
+
const config = parse(source, errors, { allowTrailingComma: true });
|
|
100
|
+
if (errors.length || typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
101
|
+
throw new Error(`Invalid OpenCode configuration: ${target}`);
|
|
102
|
+
}
|
|
103
|
+
const entries = ['plugin', 'plugins'].flatMap((field) => {
|
|
104
|
+
const plugins = config[field];
|
|
105
|
+
if (plugins === undefined)
|
|
106
|
+
return [];
|
|
107
|
+
if (!Array.isArray(plugins))
|
|
108
|
+
throw new Error(`Invalid OpenCode ${field} configuration: ${target}`);
|
|
109
|
+
return plugins.flatMap((entry, index) => {
|
|
110
|
+
const spec = openCodePluginSpec(entry);
|
|
111
|
+
const options = Array.isArray(entry) ? entry[1]
|
|
112
|
+
: typeof entry === 'object' && entry !== null ? entry.options : undefined;
|
|
113
|
+
return spec === undefined ? [] : [{ field, index, spec, options, value: entry }];
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return [{ target, source, entries }];
|
|
117
|
+
});
|
|
108
118
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
export function uninstallOpenCodePlugin(homeDir) {
|
|
120
|
+
// Native remove only handles `plugins` in one file. Delete our elements in
|
|
121
|
+
// both fields/files, from the end so indexes remain stable, keeping JSONC.
|
|
122
|
+
const changes = readOpenCodeConfigs(homeDir).filter(({ entries }) => entries.length).map(({ target, source, entries }) => {
|
|
123
|
+
for (const { field, index } of entries.reverse()) {
|
|
124
|
+
source = removeOpenCodeEntry(source, field, index);
|
|
125
|
+
}
|
|
126
|
+
return { target, source };
|
|
127
|
+
});
|
|
128
|
+
for (const { target, source } of changes)
|
|
129
|
+
fs.writeFileSync(target, source);
|
|
130
|
+
return changes.length ? [SQUARE_IDENTITY.packageName] : [];
|
|
131
|
+
}
|
|
132
|
+
// jsonc-parser's array `modify` also removes intervening comments. Use its AST
|
|
133
|
+
// and scanner to remove only the owned value and one separator, not the trivia.
|
|
134
|
+
function removeOpenCodeEntry(source, field, index) {
|
|
135
|
+
const array = findNodeAtLocation(parseTree(source), [field]);
|
|
136
|
+
const node = array.children[index];
|
|
137
|
+
const scanner = createScanner(source, true);
|
|
138
|
+
scanner.setPosition(node.offset + node.length);
|
|
139
|
+
let comma = scanner.scan() === 5 /* SyntaxKind.CommaToken */ ? scanner.getTokenOffset() : undefined;
|
|
140
|
+
if (comma === undefined && index > 0) {
|
|
141
|
+
const previous = array.children[index - 1];
|
|
142
|
+
scanner.setPosition(previous.offset + previous.length);
|
|
143
|
+
if (scanner.scan() === 5 /* SyntaxKind.CommaToken */)
|
|
144
|
+
comma = scanner.getTokenOffset();
|
|
145
|
+
}
|
|
146
|
+
return applyEdits(source, [
|
|
147
|
+
{ offset: node.offset, length: node.length, content: '' },
|
|
148
|
+
...(comma === undefined ? [] : [{ offset: comma, length: 1, content: '' }]),
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
function openCodePluginSpec(entry) {
|
|
152
|
+
const spec = typeof entry === 'string' ? entry : Array.isArray(entry) ? entry[0]
|
|
153
|
+
: typeof entry === 'object' && entry !== null && 'package' in entry ? entry.package : undefined;
|
|
154
|
+
return typeof spec === 'string' && (spec === SQUARE_IDENTITY.packageName || spec.startsWith(`${SQUARE_IDENTITY.packageName}@`))
|
|
155
|
+
? spec : undefined;
|
|
156
|
+
}
|
|
157
|
+
function parseOpenCodeConfig(stdout) {
|
|
158
|
+
let sources;
|
|
112
159
|
try {
|
|
113
|
-
|
|
160
|
+
sources = JSON.parse(stdout);
|
|
114
161
|
}
|
|
115
162
|
catch {
|
|
116
|
-
|
|
163
|
+
throw new Error('OpenCode debug config returned invalid JSON');
|
|
117
164
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
fs.writeFileSync(target, next);
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
export function uninstallOpenCodePlugin(homeDir) {
|
|
127
|
-
return removeConfiguredPlugin(homeDir) ? [SQUARE_IDENTITY.packageName] : [];
|
|
165
|
+
if (!Array.isArray(sources) || sources.some((source) => source === null || typeof source !== 'object'
|
|
166
|
+
|| !['document', 'directory'].includes(source.type))) {
|
|
167
|
+
throw new Error('OpenCode debug config returned unexpected configuration sources');
|
|
168
|
+
}
|
|
169
|
+
return sources;
|
|
128
170
|
}
|
|
129
|
-
/**
|
|
171
|
+
/** Config sources prove configuration, not that a running session loaded the plugin. */
|
|
130
172
|
export function verifyOpenCodeRuntime(homeDir, run = runOpenCode) {
|
|
131
173
|
try {
|
|
132
174
|
const result = run(homeDir, ['debug', 'config']);
|
|
@@ -134,28 +176,21 @@ export function verifyOpenCodeRuntime(homeDir, run = runOpenCode) {
|
|
|
134
176
|
const diagnostic = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
|
|
135
177
|
return `✕ OpenCode debug config failed: ${truncateExternalDiagnostic(diagnostic)}`;
|
|
136
178
|
}
|
|
137
|
-
let
|
|
179
|
+
let sources;
|
|
138
180
|
try {
|
|
139
|
-
|
|
181
|
+
sources = parseOpenCodeConfig(result.stdout);
|
|
140
182
|
}
|
|
141
|
-
catch {
|
|
142
|
-
return
|
|
183
|
+
catch (error) {
|
|
184
|
+
return `✕ ${error.message}`;
|
|
143
185
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
186
|
+
const configured = sources.some((source) => source.type === 'document'
|
|
187
|
+
&& Array.isArray(source.info?.plugins) && source.info.plugins.some((entry) => openCodePluginSpec(entry) !== undefined));
|
|
188
|
+
return configured
|
|
189
|
+
? '✓ OpenCode npm plugin configured (runtime loading not verified)'
|
|
190
|
+
: `○ OpenCode npm plugin not configured: ${SQUARE_IDENTITY.packageName}`;
|
|
149
191
|
}
|
|
150
192
|
catch (error) {
|
|
151
193
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
152
194
|
return `○ OpenCode runtime unavailable (${truncateExternalDiagnostic(diagnostic)})`;
|
|
153
195
|
}
|
|
154
196
|
}
|
|
155
|
-
export function skillLinks(homeDir = os.homedir(), parents = ['.claude', '.agents']) {
|
|
156
|
-
return parents.flatMap((parent) => ['square'].map((name) => ({
|
|
157
|
-
source: path.join(packageRoot(), 'skills', name),
|
|
158
|
-
target: path.join(homeDir, parent, 'skills', name),
|
|
159
|
-
kind: 'skill',
|
|
160
|
-
})));
|
|
161
|
-
}
|
package/dist/harness.js
CHANGED
|
@@ -5,7 +5,7 @@ import { truncateExternalDiagnostic } from './presentation.js';
|
|
|
5
5
|
import { doctorClaudePlugin, installClaudePlugin, uninstallClaudePlugin, } from './harness-claude.js';
|
|
6
6
|
import { doctorCodexPlugin, installCodexPlugin, uninstallCodexPlugin, } from './harness-codex.js';
|
|
7
7
|
import { doctorPiPackage, installPiPackage, uninstallPiPackage, } from './harness-pi.js';
|
|
8
|
-
import {
|
|
8
|
+
import { installOpenCodePlugin, uninstallOpenCodePlugin, verifyOpenCodeRuntime, } from './harness-links.js';
|
|
9
9
|
function result(lines, notes = []) {
|
|
10
10
|
return { lines, notes };
|
|
11
11
|
}
|
|
@@ -18,9 +18,6 @@ async function doctorHost(label, inspect) {
|
|
|
18
18
|
return result([`○ ${label} doctor unavailable (${truncateExternalDiagnostic(diagnostic)})`]);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
function openCodeLinks(homeDir) {
|
|
22
|
-
return skillLinks(homeDir, ['.agents']);
|
|
23
|
-
}
|
|
24
21
|
function readableSquarePath(squarePath) {
|
|
25
22
|
if (squarePath === undefined)
|
|
26
23
|
return false;
|
|
@@ -67,15 +64,9 @@ const TARGETS = [
|
|
|
67
64
|
{
|
|
68
65
|
name: 'opencode',
|
|
69
66
|
capabilities: ['install', 'uninstall', 'doctor'],
|
|
70
|
-
install:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]),
|
|
74
|
-
uninstall: ({ homeDir }) => result([
|
|
75
|
-
...uninstallOpenCodePlugin(homeDir),
|
|
76
|
-
...uninstallHarnessLinks(openCodeLinks(homeDir)),
|
|
77
|
-
]),
|
|
78
|
-
doctor: ({ homeDir }) => result([verifyOpenCodeRuntime(homeDir), ...doctorHarnessLinks(openCodeLinks(homeDir))]),
|
|
67
|
+
install: ({ homeDir, force }) => result(installOpenCodePlugin(homeDir, force)),
|
|
68
|
+
uninstall: ({ homeDir }) => result(uninstallOpenCodePlugin(homeDir)),
|
|
69
|
+
doctor: ({ homeDir }) => result([verifyOpenCodeRuntime(homeDir)]),
|
|
79
70
|
},
|
|
80
71
|
{
|
|
81
72
|
name: 'pi',
|
package/dist/help.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { JSON_COMMANDS } from './cli/json.js';
|
|
1
2
|
const COMMANDS = [
|
|
2
3
|
{ names: ['help'], usage: 'help [command]', summary: 'Show the command index or help for one command.' },
|
|
3
4
|
{ names: ['version'], usage: 'version', summary: 'Print the installed version.', hiddenFromIndex: true },
|
|
@@ -52,9 +53,9 @@ const COMMANDS = [
|
|
|
52
53
|
details: ['Options:', ' --ndjson Emit one JSON event per line.', ' --for <name> With --ndjson, emit notifications for one participant.', ' --last <N> Emit the last N eligible activities first (default 10, maximum 100).', ' --after <id> Resume after a canonical activity id (act/12).'],
|
|
53
54
|
},
|
|
54
55
|
{
|
|
55
|
-
names: ['inbox'], usage: 'inbox --for-
|
|
56
|
+
names: ['inbox'], usage: 'inbox --for-owner <owner> [--limit <N>]', hiddenFromIndex: true,
|
|
56
57
|
summary: 'Inspect a bounded machine-local notification snapshot for a native session.',
|
|
57
|
-
details: ['Options:', ' --for-
|
|
58
|
+
details: ['Options:', ' --for-owner <id> Required opaque owner.', ' --limit <N> Snapshot memberships (default 20, maximum 100).'],
|
|
58
59
|
},
|
|
59
60
|
{ names: ['codex-hook'], usage: '{command}', summary: 'Present pending attention at one native agent boundary.', hiddenFromIndex: true },
|
|
60
61
|
{ names: ['claude-mod'], usage: '{command}', summary: 'Receive a structured operation from the loaded Claude mod.', hiddenFromIndex: true },
|
|
@@ -67,7 +68,7 @@ const COMMANDS = [
|
|
|
67
68
|
{
|
|
68
69
|
names: ['participants'], usage: 'participants [--limit <count>]', usesSquare: true, group: 'host',
|
|
69
70
|
summary: 'Show the participant roster and current states.',
|
|
70
|
-
details: ['Options:', ' --limit <count> Show the first count names in
|
|
71
|
+
details: ['Options:', ' --limit <count> Show the first count names in human text (default 20, maximum 100).'],
|
|
71
72
|
},
|
|
72
73
|
{ names: ['hold'], usage: '--as <name> hold [reason | -]', usesSquare: true, group: 'participant', summary: 'Raise a hand and pause participant activity.' },
|
|
73
74
|
{ names: ['resume'], usage: '--as <name> resume', usesSquare: true, group: 'participant', summary: 'Lower the raised hand and resume activity.' },
|
|
@@ -93,7 +94,7 @@ const COMMANDS = [
|
|
|
93
94
|
{
|
|
94
95
|
names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
|
|
95
96
|
summary: 'Convert a supported older square to the current format in place.',
|
|
96
|
-
details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2
|
|
97
|
+
details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–6 migrate explicitly to format 7; format 6 attachment is preserved and Body coordinates become opaque owners. Versions 4/5 first gain attached seats; 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
|
|
97
98
|
},
|
|
98
99
|
];
|
|
99
100
|
function definitionFor(command) {
|
|
@@ -117,10 +118,11 @@ export function renderGlobalHelp() {
|
|
|
117
118
|
'',
|
|
118
119
|
]);
|
|
119
120
|
return [
|
|
120
|
-
'Usage: square [--location <square>] [--as <name>] <command> [args...]',
|
|
121
|
-
'Environment: SQUARE_LOCATION sets the CLI
|
|
122
|
-
'
|
|
123
|
-
'
|
|
121
|
+
'Usage: square [--location <square>] [--as <name>] [--owner <id>] <command> [args...]',
|
|
122
|
+
'Environment: SQUARE_LOCATION sets the CLI local artifact path; square-accessing commands require it or --location. Use --as to select a participant.',
|
|
123
|
+
'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use catch --idle.',
|
|
124
|
+
'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, hold, resume, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
|
|
125
|
+
'History emits one JSON document; stream --ndjson emits one SDK record per line.',
|
|
124
126
|
'',
|
|
125
127
|
...commandLines,
|
|
126
128
|
"Run 'square <command> --help' for command options.",
|
|
@@ -132,7 +134,7 @@ export function renderSubcommandHelp(command) {
|
|
|
132
134
|
if (definition === undefined)
|
|
133
135
|
return undefined;
|
|
134
136
|
const aliases = definition.names.filter((name) => name !== command);
|
|
135
|
-
const usage = definition.usage.replace('{command}', command);
|
|
137
|
+
const usage = definition.usage.replace('{command}', command) + (JSON_COMMANDS.has(command) ? ' [--json]' : '');
|
|
136
138
|
return [
|
|
137
139
|
`Usage: square ${definition.usesSquare ? '[--location <square>] ' : ''}${usage}`,
|
|
138
140
|
...(aliases.length > 0 ? [`Aliases: ${aliases.join(', ')}`] : []),
|
|
@@ -151,7 +153,7 @@ export function helpRequest(rawArgs) {
|
|
|
151
153
|
const args = [];
|
|
152
154
|
for (let index = 0; index < rawArgs.length; index++) {
|
|
153
155
|
const arg = rawArgs[index];
|
|
154
|
-
if (arg === '--location' || arg === '--as') {
|
|
156
|
+
if (arg === '--location' || arg === '--as' || arg === '--owner') {
|
|
155
157
|
const value = rawArgs[index + 1];
|
|
156
158
|
if (value === undefined || value.startsWith('--'))
|
|
157
159
|
return undefined;
|
package/dist/inbox.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Owner, type LocatedSeat, type PendingMembership } from './model.js';
|
|
2
2
|
import type { SquareRead } from './ports.js';
|
|
3
3
|
export interface PendingWaitOptions {
|
|
4
4
|
signal?: AbortSignal;
|
|
@@ -14,15 +14,12 @@ export declare function pendingAtBoundary(inbox: readonly PendingMembership[]):
|
|
|
14
14
|
export declare function projectBoundaryEligibility(inbox: readonly PendingMembership[]): Promise<PendingMembership[]>;
|
|
15
15
|
export interface SessionBindingCandidate {
|
|
16
16
|
readonly location: string;
|
|
17
|
-
readonly
|
|
17
|
+
readonly owner: Owner;
|
|
18
18
|
}
|
|
19
19
|
/** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
|
|
20
20
|
export declare function projectSessionCandidates(input: SessionSeatQuery): Promise<readonly SessionBindingCandidate[]>;
|
|
21
21
|
export interface SessionSeatQuery {
|
|
22
|
-
readonly
|
|
23
|
-
readonly sessionId?: string;
|
|
24
|
-
readonly channel?: string;
|
|
25
|
-
readonly agent?: string;
|
|
22
|
+
readonly owner?: Owner;
|
|
26
23
|
readonly location?: string;
|
|
27
24
|
readonly env?: NodeJS.ProcessEnv;
|
|
28
25
|
}
|
|
@@ -36,31 +33,21 @@ export interface NativePendingPreview {
|
|
|
36
33
|
}
|
|
37
34
|
export type NativePendingPreviewInput = {
|
|
38
35
|
readonly location: string;
|
|
39
|
-
readonly
|
|
36
|
+
readonly generation: import('./model.js').ActivityId;
|
|
40
37
|
readonly participant: string;
|
|
41
38
|
readonly actIndex: number;
|
|
42
39
|
readonly cancelledThrough?: number;
|
|
43
40
|
readonly now: number;
|
|
44
41
|
} & ({
|
|
45
42
|
readonly routeKind: 'claude-native';
|
|
46
|
-
readonly address:
|
|
47
|
-
readonly endpoint: string;
|
|
48
|
-
};
|
|
43
|
+
readonly address: Readonly<Record<string, string>>;
|
|
49
44
|
} | {
|
|
50
45
|
readonly routeKind: 'opencode-server';
|
|
51
|
-
readonly address:
|
|
52
|
-
readonly sessionId: string;
|
|
53
|
-
};
|
|
46
|
+
readonly address: Readonly<Record<string, string>>;
|
|
54
47
|
});
|
|
55
|
-
/**
|
|
48
|
+
/** Current-generation native attention, owned by its exact wake address and catch state. */
|
|
56
49
|
export declare function nativePendingPreview(facts: SquareRead, input: NativePendingPreviewInput): NativePendingPreview | undefined;
|
|
57
|
-
export
|
|
58
|
-
readonly channel: string;
|
|
59
|
-
readonly session: string;
|
|
60
|
-
readonly allAgents?: true;
|
|
61
|
-
}
|
|
62
|
-
export type SessionIdentity = Body | SessionIdentityQuery;
|
|
63
|
-
export declare function sessionInbox(identity: SessionIdentity, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<PendingMembership[]>;
|
|
50
|
+
export declare function sessionInbox(owner: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<PendingMembership[]>;
|
|
64
51
|
export interface SessionPendingObserver {
|
|
65
52
|
/** Read committed revisions before reusing content; filesystem hints are only for waiting. */
|
|
66
53
|
read(options?: Pick<PendingWaitOptions, 'signal' | 'excludeKeys'>): Promise<PendingMembership[]>;
|
|
@@ -68,4 +55,4 @@ export interface SessionPendingObserver {
|
|
|
68
55
|
close(): void;
|
|
69
56
|
}
|
|
70
57
|
/** Session-scoped projection over process-shared storage change detectors. */
|
|
71
|
-
export declare function observeSessionPending(
|
|
58
|
+
export declare function observeSessionPending(owner: Owner, suppliedEnv?: NodeJS.ProcessEnv): Promise<SessionPendingObserver>;
|
package/dist/inbox.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sameBody } from './model.js';
|
|
2
1
|
import { createArtifactPort, observeSquareDeliveryRevision, observeSquareRevision } from './artifact.js';
|
|
3
2
|
import { indexedSquareEntries, observeSquareIndex } from './square-index.js';
|
|
4
3
|
import { canonicalPath } from './canonical-path.js';
|
|
@@ -20,8 +19,8 @@ export function pendingAtBoundary(inbox) {
|
|
|
20
19
|
}
|
|
21
20
|
function eligibleNotifications(facts, membership, now = Date.now()) {
|
|
22
21
|
const seat = facts.seat(membership.seat.participant);
|
|
23
|
-
if (seat === undefined || seat.generation !== membership.seat.generation
|
|
24
|
-
||
|
|
22
|
+
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== membership.seat.generation
|
|
23
|
+
|| seat.owner !== membership.seat.owner)
|
|
25
24
|
return { notifications: [] };
|
|
26
25
|
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
27
26
|
let expiry = Infinity;
|
|
@@ -68,22 +67,22 @@ export async function projectBoundaryEligibility(inbox) {
|
|
|
68
67
|
/** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
|
|
69
68
|
export async function projectSessionCandidates(input) {
|
|
70
69
|
const location = input.location === undefined ? undefined : await canonicalPath(input.location);
|
|
71
|
-
const entries = await indexedSquareEntries({
|
|
70
|
+
const entries = await indexedSquareEntries({ owner: input.owner }, input.env ?? process.env);
|
|
72
71
|
const candidates = new Map();
|
|
73
72
|
for (const entry of entries) {
|
|
74
73
|
if (location !== undefined && location !== entry.location)
|
|
75
74
|
continue;
|
|
76
|
-
const key = JSON.stringify([entry.location, entry.
|
|
77
|
-
candidates.set(key, { location: entry.location,
|
|
75
|
+
const key = JSON.stringify([entry.location, entry.owner]);
|
|
76
|
+
candidates.set(key, { location: entry.location, owner: entry.owner });
|
|
78
77
|
}
|
|
79
78
|
return [...candidates.values()];
|
|
80
79
|
}
|
|
81
80
|
function groupCandidates(candidates) {
|
|
82
81
|
const grouped = new Map();
|
|
83
|
-
for (const { location,
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
grouped.set(location,
|
|
82
|
+
for (const { location, owner } of candidates) {
|
|
83
|
+
const owners = grouped.get(location) ?? [];
|
|
84
|
+
owners.push(owner);
|
|
85
|
+
grouped.set(location, owners);
|
|
87
86
|
}
|
|
88
87
|
return grouped;
|
|
89
88
|
}
|
|
@@ -97,11 +96,11 @@ function locatedSeats(facts, location, seats) {
|
|
|
97
96
|
/** Lifecycle reads resolve seats and join times, never pending activity bodies. */
|
|
98
97
|
export async function projectSessionSeats(input, signal) {
|
|
99
98
|
const seats = [];
|
|
100
|
-
for (const [location,
|
|
99
|
+
for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
|
|
101
100
|
signal?.throwIfAborted();
|
|
102
101
|
const artifact = createArtifactPort(location, undefined, signal);
|
|
103
102
|
try {
|
|
104
|
-
seats.push(...(await artifact.read(facts => locatedSeats(facts, location,
|
|
103
|
+
seats.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner))), signal)).value);
|
|
105
104
|
}
|
|
106
105
|
catch (error) {
|
|
107
106
|
if (signal?.aborted)
|
|
@@ -116,11 +115,11 @@ export async function projectSessionSeats(input, signal) {
|
|
|
116
115
|
/** Public count boundary: one coherent read per artifact, without pending body hydration. */
|
|
117
116
|
export async function sessionInboxCounts(input, signal) {
|
|
118
117
|
const rows = [];
|
|
119
|
-
for (const [location,
|
|
118
|
+
for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
|
|
120
119
|
signal?.throwIfAborted();
|
|
121
120
|
const artifact = createArtifactPort(location, undefined, signal);
|
|
122
121
|
try {
|
|
123
|
-
rows.push(...(await artifact.read(facts => locatedSeats(facts, location,
|
|
122
|
+
rows.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'))).map(({ seat }) => ({
|
|
124
123
|
name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true }),
|
|
125
124
|
})), signal)).value);
|
|
126
125
|
}
|
|
@@ -137,13 +136,13 @@ export async function sessionInboxCounts(input, signal) {
|
|
|
137
136
|
/** One authoritative read per canonical artifact, even when discovery names several bodies. */
|
|
138
137
|
async function projectPending(candidates, cache, signal) {
|
|
139
138
|
const pending = [];
|
|
140
|
-
for (const [location,
|
|
139
|
+
for (const [location, owners] of groupCandidates(candidates)) {
|
|
141
140
|
signal?.throwIfAborted();
|
|
142
141
|
const artifact = createArtifactPort(location, undefined, signal);
|
|
143
142
|
try {
|
|
144
143
|
const { value: current } = await artifact.read(facts => {
|
|
145
|
-
const seats =
|
|
146
|
-
const coordinate = JSON.stringify([
|
|
144
|
+
const seats = owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'));
|
|
145
|
+
const coordinate = JSON.stringify([owners, seats.map(seat => [seat.participant, seat.owner, seat.generation])]);
|
|
147
146
|
const header = facts.header();
|
|
148
147
|
let cached = cache?.get(location);
|
|
149
148
|
if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
|
|
@@ -177,14 +176,14 @@ async function projectPending(candidates, cache, signal) {
|
|
|
177
176
|
return pending;
|
|
178
177
|
}
|
|
179
178
|
function sameWakeAddress(address, kind, requested) {
|
|
180
|
-
if (address?.kind !== kind)
|
|
179
|
+
if (address?.kind !== kind || Object.keys(address.address).length !== Object.keys(requested).length)
|
|
181
180
|
return false;
|
|
182
181
|
return Object.entries(requested).every(([key, value]) => address.address[key] === value);
|
|
183
182
|
}
|
|
184
|
-
/**
|
|
183
|
+
/** Current-generation native attention, owned by its exact wake address and catch state. */
|
|
185
184
|
export function nativePendingPreview(facts, input) {
|
|
186
185
|
const seat = facts.seat(input.participant);
|
|
187
|
-
if (seat === undefined || seat.
|
|
186
|
+
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== input.generation
|
|
188
187
|
|| !sameWakeAddress(seat.wakeAddress, input.routeKind, input.address))
|
|
189
188
|
return undefined;
|
|
190
189
|
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
@@ -200,10 +199,8 @@ export function nativePendingPreview(facts, input) {
|
|
|
200
199
|
return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
|
|
201
200
|
actor: item.actor, route: head.route, body: item.body ?? '(structured activity)' })]), clipped: attentionBodyIsClipped(item.body ?? '(structured activity)') };
|
|
202
201
|
}
|
|
203
|
-
export async function sessionInbox(
|
|
204
|
-
|
|
205
|
-
: { body: identity, env };
|
|
206
|
-
return projectPending(await projectSessionCandidates(query), undefined, signal);
|
|
202
|
+
export async function sessionInbox(owner, env = process.env, signal) {
|
|
203
|
+
return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
|
|
207
204
|
}
|
|
208
205
|
async function capture(observer, kind, signal) {
|
|
209
206
|
const generation = observer.generation;
|
|
@@ -234,10 +231,9 @@ async function capture(observer, kind, signal) {
|
|
|
234
231
|
}
|
|
235
232
|
}
|
|
236
233
|
/** Session-scoped projection over process-shared storage change detectors. */
|
|
237
|
-
export async function observeSessionPending(
|
|
234
|
+
export async function observeSessionPending(owner, suppliedEnv = process.env) {
|
|
238
235
|
const env = { ...suppliedEnv };
|
|
239
|
-
const query =
|
|
240
|
-
: { body: identity, env };
|
|
236
|
+
const query = { owner, env };
|
|
241
237
|
const index = await observeSquareIndex(env);
|
|
242
238
|
const artifacts = new Map();
|
|
243
239
|
const cache = new Map();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
|
+
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
3
4
|
export type { WakeTransportPort, WakeRequest, WakeOutcome } from './ports.js';
|
|
4
5
|
export type { WakeTransports } from './notifications.js';
|
|
5
|
-
export { createDefaultWakeTransport } from './notifications.js';
|
|
6
|
-
export type { PresentationSinkPort } from './ports.js';
|
|
7
|
-
export type * from './square-backend.js';
|
|
8
6
|
export type * from './square-facade.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
|
-
export {
|
|
3
|
+
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type Owner } from './square-api.js';
|
|
2
3
|
import type { SquareAtInput } from './square-facade.js';
|
|
3
4
|
/** The server owns its artifact connection lifetime, so callers cannot lend one. */
|
|
4
5
|
export interface SquareMcpOptions extends Omit<SquareAtInput, 'resources'> {
|
|
6
|
+
readonly owner?: Owner;
|
|
7
|
+
readonly wakeAddress?: import('./model.js').WakeAddress;
|
|
5
8
|
}
|
|
6
9
|
export type SquareMcpServer = McpServer;
|
|
7
10
|
export declare function createSquareMcpServer(options: SquareMcpOptions): SquareMcpServer;
|