@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.
Files changed (119) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +2 -2
  4. package/dist/boundary-presentation.d.ts +2 -2
  5. package/dist/catch-decisions.d.ts +4 -6
  6. package/dist/catch-decisions.js +4 -4
  7. package/dist/claude-delivery-status.js +10 -4
  8. package/dist/claude-delivery.js +19 -11
  9. package/dist/claude-mod.js +24 -53
  10. package/dist/cli/context.d.ts +9 -4
  11. package/dist/cli/context.js +30 -25
  12. package/dist/cli/express-presentation.d.ts +3 -1
  13. package/dist/cli/express-presentation.js +26 -14
  14. package/dist/cli/json.d.ts +3 -0
  15. package/dist/cli/json.js +14 -0
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.d.ts +1 -3
  18. package/dist/cli/observation-commands.js +43 -56
  19. package/dist/cli/program.js +2 -2
  20. package/dist/cli/registry.js +6 -3
  21. package/dist/cli/square-commands.d.ts +1 -1
  22. package/dist/cli/square-commands.js +46 -54
  23. package/dist/codex-hook.d.ts +3 -2
  24. package/dist/codex-hook.js +13 -4
  25. package/dist/codex-queue.js +4 -4
  26. package/dist/decisions.d.ts +5 -11
  27. package/dist/decisions.js +9 -20
  28. package/dist/delivery-deferral.js +2 -2
  29. package/dist/delivery-health.d.ts +0 -1
  30. package/dist/delivery-health.js +1 -3
  31. package/dist/delivery-operations.d.ts +2 -2
  32. package/dist/delivery-operations.js +38 -38
  33. package/dist/delivery.d.ts +1 -1
  34. package/dist/delivery.js +1 -1
  35. package/dist/harness-links.d.ts +1 -10
  36. package/dist/harness-links.js +144 -109
  37. package/dist/harness.js +4 -13
  38. package/dist/help.js +12 -10
  39. package/dist/inbox.d.ts +9 -22
  40. package/dist/inbox.js +23 -27
  41. package/dist/index.d.ts +1 -3
  42. package/dist/index.js +1 -1
  43. package/dist/mcp.d.ts +3 -0
  44. package/dist/mcp.js +23 -24
  45. package/dist/model.d.ts +8 -15
  46. package/dist/model.js +4 -7
  47. package/dist/notifications.d.ts +1 -1
  48. package/dist/notifications.js +6 -5
  49. package/dist/operation-presentation.d.ts +5 -4
  50. package/dist/operation-presentation.js +50 -45
  51. package/dist/owner.d.ts +11 -0
  52. package/dist/owner.js +32 -0
  53. package/dist/paseo-delivery.d.ts +4 -1
  54. package/dist/paseo-delivery.js +7 -12
  55. package/dist/paseo-state.d.ts +2 -2
  56. package/dist/paseo-state.js +6 -4
  57. package/dist/plugins/opencode/delivery.js +27 -15
  58. package/dist/ports.d.ts +13 -33
  59. package/dist/presentation-operations.js +4 -4
  60. package/dist/presentation.d.ts +5 -8
  61. package/dist/presentation.js +33 -43
  62. package/dist/runtime.d.ts +1 -7
  63. package/dist/runtime.js +0 -14
  64. package/dist/seats.d.ts +2 -3
  65. package/dist/seats.js +7 -15
  66. package/dist/session-lifecycle.d.ts +6 -2
  67. package/dist/session-lifecycle.js +39 -11
  68. package/dist/sqlite-square.d.ts +36 -22
  69. package/dist/sqlite-square.js +138 -161
  70. package/dist/square-api.d.ts +15 -10
  71. package/dist/square-api.js +26 -22
  72. package/dist/square-core.d.ts +0 -5
  73. package/dist/square-core.js +0 -9
  74. package/dist/square-domain.d.ts +4 -2
  75. package/dist/square-domain.js +56 -21
  76. package/dist/square-facade.d.ts +45 -87
  77. package/dist/square-index.d.ts +5 -13
  78. package/dist/square-index.js +13 -38
  79. package/dist/square-location.d.ts +1 -14
  80. package/dist/square-location.js +4 -69
  81. package/dist/square-migration-host-ledger.d.ts +2 -2
  82. package/dist/square-migration-host-ledger.js +6 -5
  83. package/dist/square-migration.d.ts +2 -1
  84. package/dist/square-migration.js +52 -30
  85. package/dist/square-sqlite.d.ts +6 -2
  86. package/dist/square-sqlite.js +54 -28
  87. package/dist/square-tools.d.ts +7 -10
  88. package/dist/square-tools.js +22 -54
  89. package/dist/stream.js +4 -5
  90. package/dist/views.d.ts +21 -10
  91. package/dist/views.js +38 -38
  92. package/dist/wakes.d.ts +1 -2
  93. package/dist/wakes.js +1 -1
  94. package/guides/application-facade-delivery-evidence.md +1 -1
  95. package/guides/claude-native.md +18 -9
  96. package/guides/codex-delivery.md +17 -6
  97. package/guides/mcp-connection-lifetime.md +1 -1
  98. package/guides/mcp.md +3 -3
  99. package/guides/migration.md +142 -151
  100. package/guides/operation-cancellation.md +1 -1
  101. package/guides/participant-ownership.md +78 -47
  102. package/guides/pi-tools.md +33 -4
  103. package/guides/primitive-history-consolidation.md +1 -1
  104. package/guides/row-storage-design.md +8 -8
  105. package/guides/square-api.md +244 -88
  106. package/package.json +2 -1
  107. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  108. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  109. package/plugins/opencode/delivery.ts +27 -17
  110. package/plugins/paseo/client/palette.ts +18 -18
  111. package/plugins/paseo/client/square-message.tsx +17 -23
  112. package/plugins/pi/square-pi.js +17 -17
  113. package/dist/square-backend.d.ts +0 -13
  114. package/dist/square-backend.js +0 -1
  115. package/dist/square-stdio.d.ts +0 -22
  116. package/dist/square-stdio.js +0 -235
  117. package/dist/stdio-server.d.ts +0 -3
  118. package/dist/stdio-server.js +0 -212
  119. package/guides/backends.md +0 -193
@@ -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 { fileURLToPath } from 'node:url';
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
- // OpenCode v2's `plugin add` is idempotent and has no --global/--force flags.
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 args = ['plugin', 'add', SQUARE_IDENTITY.packageName];
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 plugin install failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
103
- return [SQUARE_IDENTITY.packageName];
79
+ throw new Error(`OpenCode ${operation} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
80
+ return result.stdout;
104
81
  }
105
- function configPath(homeDir) {
82
+ function openCodeConfigPaths(homeDir) {
106
83
  const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, '.config');
107
- return path.join(configHome, 'opencode', 'opencode.jsonc');
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 removeConfiguredPlugin(homeDir) {
110
- const target = configPath(homeDir);
111
- let source;
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
- source = fs.readFileSync(target, 'utf8');
160
+ sources = JSON.parse(stdout);
114
161
  }
115
162
  catch {
116
- return false;
163
+ throw new Error('OpenCode debug config returned invalid JSON');
117
164
  }
118
- const escaped = SQUARE_IDENTITY.packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
119
- const packageLine = new RegExp(`^\\s*"${escaped}(?:@[^"\\n]+)?"\\s*,?\\s*$`, 'm');
120
- const next = source.replace(packageLine, '');
121
- if (next === source)
122
- return false;
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
- /** Verify that OpenCode accepts its resolved runtime configuration after links are installed. */
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 config;
179
+ let sources;
138
180
  try {
139
- config = JSON.parse(result.stdout);
181
+ sources = parseOpenCodeConfig(result.stdout);
140
182
  }
141
- catch {
142
- return '✕ OpenCode debug config returned invalid JSON';
183
+ catch (error) {
184
+ return `✕ ${error.message}`;
143
185
  }
144
- const plugin = config.config?.plugin;
145
- const expected = SQUARE_IDENTITY.packageName;
146
- if (Array.isArray(plugin) && plugin.includes(expected))
147
- return '✓ OpenCode npm plugin loaded';
148
- return `○ OpenCode npm plugin not loaded: ${expected}`;
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 { doctorHarnessLinks, installHarnessLinks, installOpenCodePlugin, skillLinks, uninstallOpenCodePlugin, uninstallHarnessLinks, verifyOpenCodeRuntime, } from './harness-links.js';
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: async ({ homeDir, force }) => result([
71
- ...installOpenCodePlugin(homeDir, force),
72
- ...installHarnessLinks(openCodeLinks(homeDir), force),
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-session <session-id> [--limit <N>] [--json]', hiddenFromIndex: true,
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-session <id> Required harness session id.', ' --limit <N> Snapshot memberships (default 20, maximum 100).', ' --json Emit the bounded snapshot as structured JSON.'],
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 roster order (default 20, maximum 100).'],
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, 3 and 4 are accepted; version 1, unrelated or unsupported artifacts are refused without a write. Names whose own owner and route evidence agrees may share a body; names with missing or conflicting ownership stay standing without write or delivery authority. Migration reports recovered seats, unowned standing names, shared-body seats and skipped evidence. There is no --check, --backup or --force.'],
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 location (local artifact path or stdio://name/key); square-accessing commands require it or --location. Use --as to select a participant.',
122
- 'Backends: SQUARE_BACKENDS_CONFIG selects {backends:{name:{command,args?,cwd?,env?}}}; default ~/.config/square/backends.json. See guides/backends.md.',
123
- 'CLI flags override environment values. Participants join explicitly; no square is joined automatically.',
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 Body, type LocatedSeat, type PendingMembership } from './model.js';
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 body: Body;
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 body?: Body;
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 sessionId: string;
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
- /** First matching current seat, exact body/generation/address and catch ownership. */
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 interface SessionIdentityQuery {
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(identity: SessionIdentity, suppliedEnv?: NodeJS.ProcessEnv): Promise<SessionPendingObserver>;
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
- || !sameBody(seat.body, membership.seat.body))
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({ body: input.body, session: input.sessionId, channel: input.channel, agent: input.agent }, input.env ?? process.env);
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.body.channel, entry.body.session, entry.body.agent ?? null]);
77
- candidates.set(key, { location: entry.location, body: entry.body });
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, body } of candidates) {
84
- const bodies = grouped.get(location) ?? [];
85
- bodies.push(body);
86
- grouped.set(location, bodies);
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, bodies] of groupCandidates(await projectSessionCandidates(input))) {
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, bodies.flatMap(body => facts.seatsForBody(body))), signal)).value);
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, bodies] of groupCandidates(await projectSessionCandidates(input))) {
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, bodies.flatMap(body => facts.seatsForBody(body))).map(({ seat }) => ({
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, bodies] of groupCandidates(candidates)) {
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 = bodies.flatMap(body => facts.seatsForBody(body));
146
- const coordinate = JSON.stringify([bodies, seats.map(seat => [seat.participant, seat.body, seat.generation])]);
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
- /** First matching current seat, exact body/generation/address and catch ownership. */
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.body.session !== input.sessionId
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(identity, env = process.env, signal) {
204
- const query = 'allAgents' in identity ? { channel: identity.channel, sessionId: identity.session, env }
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(identity, suppliedEnv = process.env) {
234
+ export async function observeSessionPending(owner, suppliedEnv = process.env) {
238
235
  const env = { ...suppliedEnv };
239
- const query = 'allAgents' in identity ? { channel: identity.channel, sessionId: identity.session, env }
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 { createDefaultWakeTransport } from './notifications.js';
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;