subharness 0.0.1 → 0.0.2

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 (120) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +51 -18
  3. package/dist/adapters/claude-process.d.ts +10 -0
  4. package/dist/adapters/claude-process.js +58 -0
  5. package/dist/adapters/claude-process.js.map +1 -0
  6. package/dist/adapters/claude.js +52 -10
  7. package/dist/adapters/claude.js.map +1 -1
  8. package/dist/adapters/codex-input.d.ts +2 -0
  9. package/dist/adapters/codex-input.js +14 -0
  10. package/dist/adapters/codex-input.js.map +1 -0
  11. package/dist/adapters/codex-permissions.d.ts +13 -0
  12. package/dist/adapters/codex-permissions.js +30 -0
  13. package/dist/adapters/codex-permissions.js.map +1 -0
  14. package/dist/adapters/codex.js +12 -6
  15. package/dist/adapters/codex.js.map +1 -1
  16. package/dist/adapters/fx-auth.js +3 -0
  17. package/dist/adapters/fx-auth.js.map +1 -1
  18. package/dist/adapters/fx-permissions.d.ts +3 -0
  19. package/dist/adapters/fx-permissions.js +128 -0
  20. package/dist/adapters/fx-permissions.js.map +1 -0
  21. package/dist/adapters/fx.js +4 -0
  22. package/dist/adapters/fx.js.map +1 -1
  23. package/dist/cli/args.d.ts +3 -1
  24. package/dist/cli/args.js +18 -12
  25. package/dist/cli/args.js.map +1 -1
  26. package/dist/cli/dashboard-client.d.ts +3 -0
  27. package/dist/cli/dashboard-client.js +78 -0
  28. package/dist/cli/dashboard-client.js.map +1 -0
  29. package/dist/cli/dashboard-layout.d.ts +21 -0
  30. package/dist/cli/dashboard-layout.js +211 -0
  31. package/dist/cli/dashboard-layout.js.map +1 -0
  32. package/dist/cli/dashboard-renderer.d.ts +30 -0
  33. package/dist/cli/dashboard-renderer.js +80 -0
  34. package/dist/cli/dashboard-renderer.js.map +1 -0
  35. package/dist/cli/dashboard.d.ts +31 -0
  36. package/dist/cli/dashboard.js +81 -0
  37. package/dist/cli/dashboard.js.map +1 -0
  38. package/dist/cli/help.d.ts +1 -1
  39. package/dist/cli/help.js +35 -9
  40. package/dist/cli/help.js.map +1 -1
  41. package/dist/cli/main.js +19 -6
  42. package/dist/cli/main.js.map +1 -1
  43. package/dist/cli/output.js +10 -1
  44. package/dist/cli/output.js.map +1 -1
  45. package/dist/config/access.js +4 -4
  46. package/dist/config/access.js.map +1 -1
  47. package/dist/config/loader.js +2 -2
  48. package/dist/config/loader.js.map +1 -1
  49. package/dist/runtime/client.d.ts +5 -1
  50. package/dist/runtime/client.js +91 -40
  51. package/dist/runtime/client.js.map +1 -1
  52. package/dist/runtime/coordinator.d.ts +7 -1
  53. package/dist/runtime/coordinator.js +72 -8
  54. package/dist/runtime/coordinator.js.map +1 -1
  55. package/dist/runtime/daemon.js +100 -18
  56. package/dist/runtime/daemon.js.map +1 -1
  57. package/dist/runtime/dashboard-workspace.d.ts +2 -0
  58. package/dist/runtime/dashboard-workspace.js +38 -0
  59. package/dist/runtime/dashboard-workspace.js.map +1 -0
  60. package/dist/runtime/dashboard.d.ts +28 -0
  61. package/dist/runtime/dashboard.js +19 -0
  62. package/dist/runtime/dashboard.js.map +1 -0
  63. package/dist/runtime/readiness.d.ts +4 -0
  64. package/dist/runtime/readiness.js +17 -0
  65. package/dist/runtime/readiness.js.map +1 -0
  66. package/dist/runtime/select-native.d.ts +2 -1
  67. package/dist/runtime/select-native.js +5 -3
  68. package/dist/runtime/select-native.js.map +1 -1
  69. package/dist/runtime/service.d.ts +1 -1
  70. package/dist/runtime/service.js +10 -3
  71. package/dist/runtime/service.js.map +1 -1
  72. package/dist/runtime/startup-lock.d.ts +5 -0
  73. package/dist/runtime/startup-lock.js +37 -0
  74. package/dist/runtime/startup-lock.js.map +1 -0
  75. package/dist/runtime/startup-protocol.d.ts +17 -0
  76. package/dist/runtime/startup-protocol.js +23 -0
  77. package/dist/runtime/startup-protocol.js.map +1 -0
  78. package/dist/runtime/state.d.ts +1 -2
  79. package/dist/runtime/state.js +8 -43
  80. package/dist/runtime/state.js.map +1 -1
  81. package/dist/runtime/types.d.ts +16 -2
  82. package/dist/runtime/types.js.map +1 -1
  83. package/dist/runtime/worker-client.d.ts +7 -2
  84. package/dist/runtime/worker-client.js +69 -12
  85. package/dist/runtime/worker-client.js.map +1 -1
  86. package/dist/runtime/worker-server.d.ts +7 -0
  87. package/dist/runtime/worker-server.js +101 -0
  88. package/dist/runtime/worker-server.js.map +1 -0
  89. package/dist/runtime/worker.js +2 -66
  90. package/dist/runtime/worker.js.map +1 -1
  91. package/dist/sdk/definitions.js +23 -13
  92. package/dist/sdk/definitions.js.map +1 -1
  93. package/dist/sdk/permission-validation.d.ts +4 -0
  94. package/dist/sdk/permission-validation.js +55 -0
  95. package/dist/sdk/permission-validation.js.map +1 -0
  96. package/dist/sdk/types.d.ts +14 -0
  97. package/dist/sdk/types.js.map +1 -1
  98. package/package.json +17 -17
  99. package/sdk/access-config.md +4 -2
  100. package/sdk/adapter-contract.md +14 -2
  101. package/sdk/agent-skill.md +38 -0
  102. package/sdk/agent.md +6 -2
  103. package/sdk/cli/dashboard-design.md +40 -0
  104. package/sdk/cli/index.md +47 -13
  105. package/sdk/cli/output.md +21 -3
  106. package/sdk/completion-notifications.md +9 -5
  107. package/sdk/config.md +33 -8
  108. package/sdk/distribution.md +9 -7
  109. package/sdk/evals.md +171 -0
  110. package/sdk/fx.md +4 -3
  111. package/sdk/harnesses.md +6 -0
  112. package/sdk/index.md +2 -0
  113. package/sdk/message-delivery.md +6 -2
  114. package/sdk/permissions.md +65 -0
  115. package/sdk/plugins/sub-agents.md +7 -4
  116. package/sdk/pr-integration.md +37 -0
  117. package/sdk/project-team.md +13 -8
  118. package/sdk/sessions.md +1 -1
  119. package/sdk/tools.md +2 -0
  120. package/sdk/v1-runtime.md +22 -7
@@ -0,0 +1,211 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+ import stringWidth from 'string-width';
3
+ import { dashboardTitle } from '../runtime/dashboard.js';
4
+ /** Groups a snapshot without changing coordinator-owned row order. */
5
+ export function groupDashboardRows(rows, launchWorkspace, previousFirstObserved = new Map()) {
6
+ const firstObserved = new Map(previousFirstObserved);
7
+ let nextObserved = firstObserved.size;
8
+ const byDirectory = new Map();
9
+ for (const row of rows) {
10
+ let group = byDirectory.get(row.workspace.directory);
11
+ if (!group) {
12
+ group = { workspace: row.workspace, rows: [], updatedAt: row.updatedAt };
13
+ byDirectory.set(row.workspace.directory, group);
14
+ if (!firstObserved.has(row.workspace.directory)) {
15
+ firstObserved.set(row.workspace.directory, nextObserved++);
16
+ }
17
+ }
18
+ group.rows.push(row);
19
+ group.updatedAt = Math.max(group.updatedAt, row.updatedAt);
20
+ }
21
+ const pinnedDirectory = launchWorkspace?.directory;
22
+ const groups = [...byDirectory.values()].sort((left, right) => {
23
+ const leftPinned = left.workspace.directory === pinnedDirectory;
24
+ const rightPinned = right.workspace.directory === pinnedDirectory;
25
+ if (leftPinned !== rightPinned)
26
+ return leftPinned ? -1 : 1;
27
+ if (left.updatedAt !== right.updatedAt)
28
+ return right.updatedAt - left.updatedAt;
29
+ return firstObserved.get(left.workspace.directory) - firstObserved.get(right.workspace.directory);
30
+ });
31
+ return { groups, firstObserved };
32
+ }
33
+ function sanitize(text) {
34
+ return stripVTControlCharacters(text)
35
+ .replace(/[\u0000-\u001f\u007f-\u009f]/gu, '')
36
+ .replace(/\s+/gu, ' ')
37
+ .trim();
38
+ }
39
+ function pathParts(path) {
40
+ return path.split(/[\\/]+/u).map(sanitize).filter(Boolean);
41
+ }
42
+ function suffix(parts, depth) {
43
+ const result = parts.slice(-depth).join('/');
44
+ return result || '/';
45
+ }
46
+ function distinguishingDepth(left, right) {
47
+ const limit = Math.max(left.length, right.length, 1);
48
+ for (let depth = 1; depth <= limit; depth++) {
49
+ if (suffix(left, depth) !== suffix(right, depth))
50
+ return depth;
51
+ }
52
+ return limit;
53
+ }
54
+ /** Adds parent components only when short workspace labels would collide. */
55
+ export function labelDashboardGroups(groups) {
56
+ const paths = groups.map(group => ({
57
+ directory: pathParts(group.workspace.directory),
58
+ repository: group.workspace.repository ? pathParts(group.workspace.repository) : undefined,
59
+ }));
60
+ const depths = groups.map(() => ({ directory: 1, repository: 1 }));
61
+ const baseLabels = paths.map(path => path.repository
62
+ ? `${suffix(path.repository, 1)} / ${suffix(path.directory, 1)}`
63
+ : suffix(path.directory, 1));
64
+ const collisionBuckets = new Map();
65
+ for (let index = 0; index < baseLabels.length; index++) {
66
+ const label = baseLabels[index];
67
+ const bucket = collisionBuckets.get(label);
68
+ if (bucket)
69
+ bucket.push(index);
70
+ else
71
+ collisionBuckets.set(label, [index]);
72
+ }
73
+ for (const bucket of collisionBuckets.values()) {
74
+ if (bucket.length < 2)
75
+ continue;
76
+ for (let leftIndex = 0; leftIndex < bucket.length; leftIndex++) {
77
+ for (let rightIndex = leftIndex + 1; rightIndex < bucket.length; rightIndex++) {
78
+ const left = bucket[leftIndex];
79
+ const right = bucket[rightIndex];
80
+ const leftPath = paths[left];
81
+ const rightPath = paths[right];
82
+ const leftDepth = depths[left];
83
+ const rightDepth = depths[right];
84
+ if (leftPath.repository && rightPath.repository
85
+ && groups[left].workspace.repository !== groups[right].workspace.repository) {
86
+ const depth = distinguishingDepth(leftPath.repository, rightPath.repository);
87
+ leftDepth.repository = Math.max(leftDepth.repository, depth);
88
+ rightDepth.repository = Math.max(rightDepth.repository, depth);
89
+ }
90
+ else {
91
+ const depth = distinguishingDepth(leftPath.directory, rightPath.directory);
92
+ leftDepth.directory = Math.max(leftDepth.directory, depth);
93
+ rightDepth.directory = Math.max(rightDepth.directory, depth);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return groups.map((group, index) => {
99
+ const path = paths[index];
100
+ const depth = depths[index];
101
+ const label = path.repository
102
+ ? `${suffix(path.repository, depth.repository)} / ${suffix(path.directory, depth.directory)}`
103
+ : suffix(path.directory, depth.directory);
104
+ return { ...group, label };
105
+ });
106
+ }
107
+ const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
108
+ export function clipDashboardText(text, columns) {
109
+ if (columns <= 0)
110
+ return '';
111
+ let result = '';
112
+ let width = 0;
113
+ for (const { segment } of graphemes.segment(text)) {
114
+ const next = stringWidth(segment);
115
+ if (width + next > columns)
116
+ break;
117
+ result += segment;
118
+ width += next;
119
+ }
120
+ return result;
121
+ }
122
+ function pad(text, columns) {
123
+ const clipped = clipDashboardText(text, columns);
124
+ return `${clipped}${' '.repeat(Math.max(0, columns - stringWidth(clipped)))}`;
125
+ }
126
+ function maxWidth(values) {
127
+ let result = 0;
128
+ for (const value of values)
129
+ result = Math.max(result, stringWidth(value));
130
+ return result;
131
+ }
132
+ export function dashboardElapsed(row, now) {
133
+ const end = row.state === 'failed' ? row.failedAt ?? row.finishedAt ?? now : row.finishedAt ?? now;
134
+ const seconds = row.dispatchedAt === undefined ? 0
135
+ : Math.max(0, Math.floor((end - row.dispatchedAt) / 1_000));
136
+ const minutes = Math.floor(seconds / 60);
137
+ const tail = `${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
138
+ return minutes < 60 ? tail : `${Math.floor(minutes / 60)}:${tail}`;
139
+ }
140
+ function completionAge(row, now) {
141
+ const completedAt = row.state === 'failed' ? row.failedAt ?? row.finishedAt
142
+ : row.state === 'completed' || row.state === 'cancelled' || row.state === 'interrupted'
143
+ ? row.finishedAt
144
+ : undefined;
145
+ if (completedAt === undefined)
146
+ return undefined;
147
+ const minutes = Math.floor(Math.max(0, now - completedAt) / 60_000);
148
+ return minutes === 0 ? '<1m ago' : `${minutes}m ago`;
149
+ }
150
+ function dashboardMarker(row) {
151
+ if (row.state === 'running')
152
+ return '●';
153
+ if (row.state === 'queued' || row.state === 'waiting')
154
+ return '◌';
155
+ if (row.state === 'failed' || row.state === 'interrupted')
156
+ return '!';
157
+ return ' ';
158
+ }
159
+ export function formatDashboardRows(rows, columns, now) {
160
+ const harnesses = rows.map(row => sanitize(row.harness));
161
+ const titles = rows.map(row => dashboardTitle(row.title));
162
+ const states = rows.map(row => sanitize(row.state));
163
+ const elapsed = rows.map(row => dashboardElapsed(row, now));
164
+ const ages = rows.map(row => completionAge(row, now));
165
+ const harnessWidth = maxWidth(harnesses);
166
+ const titleNaturalWidth = maxWidth(titles);
167
+ const stateWidth = maxWidth(states);
168
+ const elapsedWidth = maxWidth(elapsed);
169
+ const ageWidth = maxWidth(ages.filter((age) => age !== undefined));
170
+ const fixedWidth = 2 + 1 + 1 + harnessWidth + 2 + 2 + stateWidth + 2 + elapsedWidth;
171
+ const showAge = ageWidth > 0 && fixedWidth + titleNaturalWidth + 2 + ageWidth <= columns;
172
+ const ageSpace = showAge ? 2 + ageWidth : 0;
173
+ const titleWidth = Math.min(titleNaturalWidth, Math.max(0, columns - fixedWidth - ageSpace));
174
+ return rows.map((row, index) => {
175
+ const age = showAge && ages[index] ? ` ${ages[index]}` : '';
176
+ const line = ` ${dashboardMarker(row)} ${pad(harnesses[index], harnessWidth)} `
177
+ + `${pad(titles[index], titleWidth)} ${pad(states[index], stateWidth)} `
178
+ + `${pad(elapsed[index], elapsedWidth)}${age}`;
179
+ return clipDashboardText(line, columns);
180
+ });
181
+ }
182
+ export function layoutDashboardGroups(groups, columns, now, maxLines = Number.POSITIVE_INFINITY) {
183
+ if (maxLines <= 0)
184
+ return [];
185
+ const visible = [];
186
+ groupLoop: for (const [groupIndex, group] of groups.entries()) {
187
+ if (groupIndex > 0) {
188
+ visible.push({ kind: 'blank' });
189
+ if (visible.length >= maxLines)
190
+ break;
191
+ }
192
+ visible.push({ kind: 'label', text: group.label });
193
+ if (visible.length >= maxLines)
194
+ break;
195
+ for (const row of group.rows) {
196
+ visible.push({ kind: 'row', row });
197
+ if (visible.length >= maxLines)
198
+ break groupLoop;
199
+ }
200
+ }
201
+ const formattedRows = formatDashboardRows(visible.flatMap(slot => slot.kind === 'row' ? [slot.row] : []), columns, now);
202
+ let rowIndex = 0;
203
+ return visible.map(slot => {
204
+ if (slot.kind === 'blank')
205
+ return '';
206
+ if (slot.kind === 'label')
207
+ return clipDashboardText(sanitize(slot.text), columns);
208
+ return formattedRows[rowIndex++];
209
+ });
210
+ }
211
+ //# sourceMappingURL=dashboard-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout.js","sourceRoot":"","sources":["../../src/cli/dashboard-layout.ts"],"names":[],"mappings":"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","sourcesContent":["import { stripVTControlCharacters } from 'node:util';\nimport stringWidth from 'string-width';\nimport { dashboardTitle } from '../runtime/dashboard.js';\nimport type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';\n\nexport interface DashboardGroup {\n workspace: DashboardWorkspace;\n rows: DashboardRow[];\n updatedAt: number;\n}\n\nexport interface LabelledDashboardGroup extends DashboardGroup {\n label: string;\n}\n\nexport interface DashboardGrouping {\n groups: DashboardGroup[];\n firstObserved: ReadonlyMap<string, number>;\n}\n\n/** Groups a snapshot without changing coordinator-owned row order. */\nexport function groupDashboardRows(\n rows: DashboardRow[],\n launchWorkspace?: DashboardWorkspace,\n previousFirstObserved: ReadonlyMap<string, number> = new Map(),\n): DashboardGrouping {\n const firstObserved = new Map(previousFirstObserved);\n let nextObserved = firstObserved.size;\n const byDirectory = new Map<string, DashboardGroup>();\n\n for (const row of rows) {\n let group = byDirectory.get(row.workspace.directory);\n if (!group) {\n group = {workspace: row.workspace, rows: [], updatedAt: row.updatedAt};\n byDirectory.set(row.workspace.directory, group);\n if (!firstObserved.has(row.workspace.directory)) {\n firstObserved.set(row.workspace.directory, nextObserved++);\n }\n }\n group.rows.push(row);\n group.updatedAt = Math.max(group.updatedAt, row.updatedAt);\n }\n\n const pinnedDirectory = launchWorkspace?.directory;\n const groups = [...byDirectory.values()].sort((left, right) => {\n const leftPinned = left.workspace.directory === pinnedDirectory;\n const rightPinned = right.workspace.directory === pinnedDirectory;\n if (leftPinned !== rightPinned) return leftPinned ? -1 : 1;\n if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt;\n return firstObserved.get(left.workspace.directory)! - firstObserved.get(right.workspace.directory)!;\n });\n\n return {groups, firstObserved};\n}\n\nfunction sanitize(text: string): string {\n return stripVTControlCharacters(text)\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, '')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\n\nfunction pathParts(path: string): string[] {\n return path.split(/[\\\\/]+/u).map(sanitize).filter(Boolean);\n}\n\nfunction suffix(parts: string[], depth: number): string {\n const result = parts.slice(-depth).join('/');\n return result || '/';\n}\n\nfunction distinguishingDepth(left: string[], right: string[]): number {\n const limit = Math.max(left.length, right.length, 1);\n for (let depth = 1; depth <= limit; depth++) {\n if (suffix(left, depth) !== suffix(right, depth)) return depth;\n }\n return limit;\n}\n\n/** Adds parent components only when short workspace labels would collide. */\nexport function labelDashboardGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const paths = groups.map(group => ({\n directory: pathParts(group.workspace.directory),\n repository: group.workspace.repository ? pathParts(group.workspace.repository) : undefined,\n }));\n const depths = groups.map(() => ({directory: 1, repository: 1}));\n const baseLabels = paths.map(path => path.repository\n ? `${suffix(path.repository, 1)} / ${suffix(path.directory, 1)}`\n : suffix(path.directory, 1));\n const collisionBuckets = new Map<string, number[]>();\n for (let index = 0; index < baseLabels.length; index++) {\n const label = baseLabels[index]!;\n const bucket = collisionBuckets.get(label);\n if (bucket) bucket.push(index);\n else collisionBuckets.set(label, [index]);\n }\n\n for (const bucket of collisionBuckets.values()) {\n if (bucket.length < 2) continue;\n for (let leftIndex = 0; leftIndex < bucket.length; leftIndex++) {\n for (let rightIndex = leftIndex + 1; rightIndex < bucket.length; rightIndex++) {\n const left = bucket[leftIndex]!;\n const right = bucket[rightIndex]!;\n const leftPath = paths[left]!;\n const rightPath = paths[right]!;\n const leftDepth = depths[left]!;\n const rightDepth = depths[right]!;\n if (leftPath.repository && rightPath.repository\n && groups[left]!.workspace.repository !== groups[right]!.workspace.repository) {\n const depth = distinguishingDepth(leftPath.repository, rightPath.repository);\n leftDepth.repository = Math.max(leftDepth.repository, depth);\n rightDepth.repository = Math.max(rightDepth.repository, depth);\n } else {\n const depth = distinguishingDepth(leftPath.directory, rightPath.directory);\n leftDepth.directory = Math.max(leftDepth.directory, depth);\n rightDepth.directory = Math.max(rightDepth.directory, depth);\n }\n }\n }\n }\n\n return groups.map((group, index) => {\n const path = paths[index]!;\n const depth = depths[index]!;\n const label = path.repository\n ? `${suffix(path.repository, depth.repository)} / ${suffix(path.directory, depth.directory)}`\n : suffix(path.directory, depth.directory);\n return {...group, label};\n });\n}\n\nconst graphemes = new Intl.Segmenter(undefined, {granularity: 'grapheme'});\n\nexport function clipDashboardText(text: string, columns: number): string {\n if (columns <= 0) return '';\n let result = '';\n let width = 0;\n for (const {segment} of graphemes.segment(text)) {\n const next = stringWidth(segment);\n if (width + next > columns) break;\n result += segment;\n width += next;\n }\n return result;\n}\n\nfunction pad(text: string, columns: number): string {\n const clipped = clipDashboardText(text, columns);\n return `${clipped}${' '.repeat(Math.max(0, columns - stringWidth(clipped)))}`;\n}\n\nfunction maxWidth(values: string[]): number {\n let result = 0;\n for (const value of values) result = Math.max(result, stringWidth(value));\n return result;\n}\n\nexport function dashboardElapsed(row: DashboardRow, now: number): string {\n const end = row.state === 'failed' ? row.failedAt ?? row.finishedAt ?? now : row.finishedAt ?? now;\n const seconds = row.dispatchedAt === undefined ? 0\n : Math.max(0, Math.floor((end - row.dispatchedAt) / 1_000));\n const minutes = Math.floor(seconds / 60);\n const tail = `${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;\n return minutes < 60 ? tail : `${Math.floor(minutes / 60)}:${tail}`;\n}\n\nfunction completionAge(row: DashboardRow, now: number): string | undefined {\n const completedAt = row.state === 'failed' ? row.failedAt ?? row.finishedAt\n : row.state === 'completed' || row.state === 'cancelled' || row.state === 'interrupted'\n ? row.finishedAt\n : undefined;\n if (completedAt === undefined) return undefined;\n const minutes = Math.floor(Math.max(0, now - completedAt) / 60_000);\n return minutes === 0 ? '<1m ago' : `${minutes}m ago`;\n}\n\nfunction dashboardMarker(row: DashboardRow): string {\n if (row.state === 'running') return '●';\n if (row.state === 'queued' || row.state === 'waiting') return '◌';\n if (row.state === 'failed' || row.state === 'interrupted') return '!';\n return ' ';\n}\n\nexport function formatDashboardRows(rows: DashboardRow[], columns: number, now: number): string[] {\n const harnesses = rows.map(row => sanitize(row.harness));\n const titles = rows.map(row => dashboardTitle(row.title));\n const states = rows.map(row => sanitize(row.state));\n const elapsed = rows.map(row => dashboardElapsed(row, now));\n const ages = rows.map(row => completionAge(row, now));\n const harnessWidth = maxWidth(harnesses);\n const titleNaturalWidth = maxWidth(titles);\n const stateWidth = maxWidth(states);\n const elapsedWidth = maxWidth(elapsed);\n const ageWidth = maxWidth(ages.filter((age): age is string => age !== undefined));\n const fixedWidth = 2 + 1 + 1 + harnessWidth + 2 + 2 + stateWidth + 2 + elapsedWidth;\n const showAge = ageWidth > 0 && fixedWidth + titleNaturalWidth + 2 + ageWidth <= columns;\n const ageSpace = showAge ? 2 + ageWidth : 0;\n const titleWidth = Math.min(titleNaturalWidth, Math.max(0, columns - fixedWidth - ageSpace));\n\n return rows.map((row, index) => {\n const age = showAge && ages[index] ? ` ${ages[index]}` : '';\n const line = ` ${dashboardMarker(row)} ${pad(harnesses[index]!, harnessWidth)} `\n + `${pad(titles[index]!, titleWidth)} ${pad(states[index]!, stateWidth)} `\n + `${pad(elapsed[index]!, elapsedWidth)}${age}`;\n return clipDashboardText(line, columns);\n });\n}\n\nexport function layoutDashboardGroups(\n groups: LabelledDashboardGroup[],\n columns: number,\n now: number,\n maxLines = Number.POSITIVE_INFINITY,\n): string[] {\n if (maxLines <= 0) return [];\n const visible: Array<{kind: 'blank'} | {kind: 'label'; text: string} | {kind: 'row'; row: DashboardRow}> = [];\n groupLoop:\n for (const [groupIndex, group] of groups.entries()) {\n if (groupIndex > 0) {\n visible.push({kind: 'blank'});\n if (visible.length >= maxLines) break;\n }\n visible.push({kind: 'label', text: group.label});\n if (visible.length >= maxLines) break;\n for (const row of group.rows) {\n visible.push({kind: 'row', row});\n if (visible.length >= maxLines) break groupLoop;\n }\n }\n const formattedRows = formatDashboardRows(\n visible.flatMap(slot => slot.kind === 'row' ? [slot.row] : []),\n columns,\n now,\n );\n let rowIndex = 0;\n return visible.map(slot => {\n if (slot.kind === 'blank') return '';\n if (slot.kind === 'label') return clipDashboardText(sanitize(slot.text), columns);\n return formattedRows[rowIndex++]!;\n });\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';
2
+ export interface DashboardOutput {
3
+ columns?: number;
4
+ rows?: number;
5
+ write(text: string): boolean;
6
+ on(event: 'drain', listener: () => void): unknown;
7
+ off(event: 'drain', listener: () => void): unknown;
8
+ }
9
+ export declare function formatDashboardRow(row: DashboardRow, columns: number, now: number): string;
10
+ /** Keeps one pending snapshot when the terminal applies backpressure. */
11
+ export declare class DashboardRenderer {
12
+ private readonly output;
13
+ private readonly launchWorkspace?;
14
+ private previous;
15
+ private current;
16
+ private now;
17
+ private blocked;
18
+ private active;
19
+ private size;
20
+ private firstObserved;
21
+ private labelMembership;
22
+ private labels;
23
+ private readonly drain;
24
+ constructor(output: DashboardOutput, launchWorkspace?: DashboardWorkspace | undefined);
25
+ start(): void;
26
+ render(rows: DashboardRow[], now: number): void;
27
+ private draw;
28
+ private labelGroups;
29
+ close(): void;
30
+ }
@@ -0,0 +1,80 @@
1
+ import { formatDashboardRows, groupDashboardRows, labelDashboardGroups, layoutDashboardGroups, } from './dashboard-layout.js';
2
+ const enter = '\u001b[?1049h\u001b[?25l\u001b[2J';
3
+ const leave = '\u001b[?25h\u001b[?1049l';
4
+ export function formatDashboardRow(row, columns, now) {
5
+ return formatDashboardRows([row], columns, now)[0] ?? '';
6
+ }
7
+ /** Keeps one pending snapshot when the terminal applies backpressure. */
8
+ export class DashboardRenderer {
9
+ output;
10
+ launchWorkspace;
11
+ previous = [];
12
+ current = [];
13
+ now = 0;
14
+ blocked = false;
15
+ active = false;
16
+ size = '';
17
+ firstObserved = new Map();
18
+ labelMembership = new Map();
19
+ labels = new Map();
20
+ drain = () => { this.blocked = false; this.draw(); };
21
+ constructor(output, launchWorkspace) {
22
+ this.output = output;
23
+ this.launchWorkspace = launchWorkspace;
24
+ }
25
+ start() {
26
+ this.active = true;
27
+ this.output.on('drain', this.drain);
28
+ this.blocked = !this.output.write(enter);
29
+ }
30
+ render(rows, now) {
31
+ this.current = rows;
32
+ this.now = now;
33
+ this.draw();
34
+ }
35
+ draw() {
36
+ if (!this.active || this.blocked)
37
+ return;
38
+ const height = Math.max(0, this.output.rows ?? 24);
39
+ const width = Math.max(0, this.output.columns ?? 80);
40
+ const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);
41
+ this.firstObserved = grouping.firstObserved;
42
+ const labelled = this.labelGroups(grouping.groups);
43
+ const lines = layoutDashboardGroups(labelled, width, this.now, height);
44
+ const size = `${width}:${height}`;
45
+ let frame = this.size && this.size !== size ? '\u001b[2J' : '';
46
+ if (frame)
47
+ this.previous = [];
48
+ this.size = size;
49
+ for (let index = 0; index < Math.min(height, Math.max(lines.length, this.previous.length)); index++) {
50
+ if ((lines[index] ?? '') !== (this.previous[index] ?? '')) {
51
+ frame += `\u001b[${index + 1};1H\u001b[2K${lines[index] ?? ''}`;
52
+ }
53
+ }
54
+ this.previous = lines;
55
+ if (frame)
56
+ this.blocked = !this.output.write(frame);
57
+ }
58
+ labelGroups(groups) {
59
+ const membershipChanged = groups.length !== this.labelMembership.size
60
+ || groups.some(group => !this.labelMembership.has(group.workspace.directory)
61
+ || this.labelMembership.get(group.workspace.directory) !== group.workspace.repository);
62
+ if (membershipChanged) {
63
+ this.labelMembership = new Map(groups.map(group => [
64
+ group.workspace.directory,
65
+ group.workspace.repository,
66
+ ]));
67
+ this.labels = new Map(labelDashboardGroups(groups)
68
+ .map(group => [group.workspace.directory, group.label]));
69
+ }
70
+ return groups.map(group => ({ ...group, label: this.labels.get(group.workspace.directory) }));
71
+ }
72
+ close() {
73
+ if (!this.active)
74
+ return;
75
+ this.active = false;
76
+ this.output.off('drain', this.drain);
77
+ this.output.write(leave);
78
+ }
79
+ }
80
+ //# sourceMappingURL=dashboard-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-renderer.js","sourceRoot":"","sources":["../../src/cli/dashboard-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';\nimport {\n formatDashboardRows,\n groupDashboardRows,\n labelDashboardGroups,\n layoutDashboardGroups,\n type DashboardGroup,\n type LabelledDashboardGroup,\n} from './dashboard-layout.js';\n\nexport interface DashboardOutput {\n columns?: number;\n rows?: number;\n write(text: string): boolean;\n on(event: 'drain', listener: () => void): unknown;\n off(event: 'drain', listener: () => void): unknown;\n}\n\nconst enter = '\\u001b[?1049h\\u001b[?25l\\u001b[2J';\nconst leave = '\\u001b[?25h\\u001b[?1049l';\n\nexport function formatDashboardRow(row: DashboardRow, columns: number, now: number): string {\n return formatDashboardRows([row], columns, now)[0] ?? '';\n}\n\n/** Keeps one pending snapshot when the terminal applies backpressure. */\nexport class DashboardRenderer {\n private previous: string[] = [];\n private current: DashboardRow[] = [];\n private now = 0;\n private blocked = false;\n private active = false;\n private size = '';\n private firstObserved: ReadonlyMap<string, number> = new Map();\n private labelMembership = new Map<string, string | undefined>();\n private labels = new Map<string, string>();\n private readonly drain = () => { this.blocked = false; this.draw(); };\n\n constructor(\n private readonly output: DashboardOutput,\n private readonly launchWorkspace?: DashboardWorkspace,\n ) {}\n\n start() {\n this.active = true;\n this.output.on('drain', this.drain);\n this.blocked = !this.output.write(enter);\n }\n\n render(rows: DashboardRow[], now: number) {\n this.current = rows;\n this.now = now;\n this.draw();\n }\n\n private draw() {\n if (!this.active || this.blocked) return;\n const height = Math.max(0, this.output.rows ?? 24);\n const width = Math.max(0, this.output.columns ?? 80);\n const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);\n this.firstObserved = grouping.firstObserved;\n const labelled = this.labelGroups(grouping.groups);\n const lines = layoutDashboardGroups(labelled, width, this.now, height);\n const size = `${width}:${height}`;\n let frame = this.size && this.size !== size ? '\\u001b[2J' : '';\n if (frame) this.previous = [];\n this.size = size;\n for (let index = 0; index < Math.min(height, Math.max(lines.length, this.previous.length)); index++) {\n if ((lines[index] ?? '') !== (this.previous[index] ?? '')) {\n frame += `\\u001b[${index + 1};1H\\u001b[2K${lines[index] ?? ''}`;\n }\n }\n this.previous = lines;\n if (frame) this.blocked = !this.output.write(frame);\n }\n\n private labelGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const membershipChanged = groups.length !== this.labelMembership.size\n || groups.some(group => !this.labelMembership.has(group.workspace.directory)\n || this.labelMembership.get(group.workspace.directory) !== group.workspace.repository);\n if (membershipChanged) {\n this.labelMembership = new Map(groups.map(group => [\n group.workspace.directory,\n group.workspace.repository,\n ]));\n this.labels = new Map(labelDashboardGroups(groups)\n .map(group => [group.workspace.directory, group.label]));\n }\n return groups.map(group => ({...group, label: this.labels.get(group.workspace.directory)!}));\n }\n\n close() {\n if (!this.active) return;\n this.active = false;\n this.output.off('drain', this.drain);\n this.output.write(leave);\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { EventEmitter } from 'node:events';
2
+ import type { DashboardSnapshot } from '../runtime/dashboard.js';
3
+ import { type Endpoint } from '../runtime/state.js';
4
+ import { type DashboardOutput } from './dashboard-renderer.js';
5
+ interface Terminal extends DashboardOutput {
6
+ isTTY?: boolean;
7
+ on(event: 'drain' | 'resize' | 'error', listener: (...args: any[]) => void): unknown;
8
+ off(event: 'drain' | 'resize' | 'error', listener: (...args: any[]) => void): unknown;
9
+ }
10
+ interface Keyboard {
11
+ isTTY?: boolean;
12
+ isRaw?: boolean;
13
+ readableFlowing: boolean | null;
14
+ setRawMode(enabled: boolean): unknown;
15
+ resume(): unknown;
16
+ pause(): unknown;
17
+ on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
18
+ off(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
19
+ }
20
+ interface DashboardOptions {
21
+ cwd?: string;
22
+ output?: Terminal;
23
+ input?: Keyboard;
24
+ signals?: Pick<EventEmitter, 'on' | 'off'>;
25
+ term?: string;
26
+ intervalMs?: number;
27
+ discover?: () => Promise<Endpoint | undefined>;
28
+ snapshot?: (endpoint: Endpoint, signal: AbortSignal) => Promise<DashboardSnapshot>;
29
+ }
30
+ export declare function runDashboard(options?: DashboardOptions): Promise<number>;
31
+ export {};
@@ -0,0 +1,81 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import { AgentError } from '../errors.js';
3
+ import { resolveDashboardWorkspace } from '../runtime/dashboard-workspace.js';
4
+ import { readEndpoint, stateDirectory } from '../runtime/state.js';
5
+ import { fetchDashboardSnapshot } from './dashboard-client.js';
6
+ import { DashboardRenderer } from './dashboard-renderer.js';
7
+ export async function runDashboard(options = {}) {
8
+ const output = options.output ?? process.stdout;
9
+ if (!output.isTTY || (options.term ?? process.env.TERM) === 'dumb') {
10
+ throw new AgentError('INVALID_ARGUMENT', 'The dashboard requires terminal stdout with cursor control.');
11
+ }
12
+ const input = options.input ?? process.stdin;
13
+ const wasRaw = input.isRaw ?? false;
14
+ const wasFlowing = input.readableFlowing === true;
15
+ let inputEnabled = false;
16
+ const signals = options.signals ?? process;
17
+ const discover = options.discover ?? (() => readEndpoint(stateDirectory()));
18
+ const snapshot = options.snapshot ?? fetchDashboardSnapshot;
19
+ const interval = options.intervalMs ?? 1_000;
20
+ const abort = new AbortController();
21
+ const renderer = new DashboardRenderer(output, await resolveDashboardWorkspace(options.cwd ?? process.cwd()));
22
+ let rows = [];
23
+ let endpoint;
24
+ let exitCode = 0;
25
+ let outputError;
26
+ const interrupt = () => { exitCode = 130; abort.abort(); };
27
+ const terminate = () => { exitCode = 143; abort.abort(); };
28
+ const keys = (chunk) => {
29
+ if (typeof chunk === 'string' ? chunk.includes('\u0003') : chunk.includes(3))
30
+ interrupt();
31
+ };
32
+ const resize = () => renderer.render(rows, Date.now());
33
+ const failedOutput = (error) => { outputError = error; abort.abort(); };
34
+ signals.on('SIGINT', interrupt);
35
+ signals.on('SIGTERM', terminate);
36
+ output.on('resize', resize);
37
+ output.on('error', failedOutput);
38
+ try {
39
+ if (input.isTTY) {
40
+ input.setRawMode(true);
41
+ inputEnabled = true;
42
+ input.on('data', keys);
43
+ input.resume();
44
+ }
45
+ renderer.start();
46
+ while (!abort.signal.aborted) {
47
+ const next = Date.now() + interval;
48
+ endpoint ??= await discover();
49
+ if (abort.signal.aborted)
50
+ break;
51
+ if (endpoint)
52
+ rows = (await snapshot(endpoint, abort.signal)).sessions;
53
+ if (abort.signal.aborted)
54
+ break;
55
+ renderer.render(rows, Date.now());
56
+ await delay(Math.max(0, next - Date.now()), undefined, { signal: abort.signal });
57
+ }
58
+ }
59
+ catch (error) {
60
+ if (!abort.signal.aborted)
61
+ throw error;
62
+ }
63
+ finally {
64
+ abort.abort();
65
+ renderer.close();
66
+ if (inputEnabled) {
67
+ input.off('data', keys);
68
+ input.setRawMode(wasRaw);
69
+ if (!wasFlowing)
70
+ input.pause();
71
+ }
72
+ signals.off('SIGINT', interrupt);
73
+ signals.off('SIGTERM', terminate);
74
+ output.off('resize', resize);
75
+ output.off('error', failedOutput);
76
+ }
77
+ if (outputError)
78
+ throw new AgentError('INTERNAL', 'The dashboard terminal output failed.');
79
+ return exitCode;
80
+ }
81
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/cli/dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventEmitter } from 'node:events';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { AgentError } from '../errors.js';\nimport type { DashboardRow, DashboardSnapshot } from '../runtime/dashboard.js';\nimport { resolveDashboardWorkspace } from '../runtime/dashboard-workspace.js';\nimport { readEndpoint, stateDirectory, type Endpoint } from '../runtime/state.js';\nimport { fetchDashboardSnapshot } from './dashboard-client.js';\nimport { DashboardRenderer, type DashboardOutput } from './dashboard-renderer.js';\n\ninterface Terminal extends DashboardOutput {\n isTTY?: boolean;\n on(event: 'drain' | 'resize' | 'error', listener: (...args: any[]) => void): unknown;\n off(event: 'drain' | 'resize' | 'error', listener: (...args: any[]) => void): unknown;\n}\ninterface Keyboard {\n isTTY?: boolean;\n isRaw?: boolean;\n readableFlowing: boolean | null;\n setRawMode(enabled: boolean): unknown;\n resume(): unknown;\n pause(): unknown;\n on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;\n off(event: 'data', listener: (chunk: Buffer | string) => void): unknown;\n}\ninterface DashboardOptions {\n cwd?: string;\n output?: Terminal;\n input?: Keyboard;\n signals?: Pick<EventEmitter, 'on' | 'off'>;\n term?: string;\n intervalMs?: number;\n discover?: () => Promise<Endpoint | undefined>;\n snapshot?: (endpoint: Endpoint, signal: AbortSignal) => Promise<DashboardSnapshot>;\n}\n\nexport async function runDashboard(options: DashboardOptions = {}): Promise<number> {\n const output = options.output ?? process.stdout;\n if (!output.isTTY || (options.term ?? process.env.TERM) === 'dumb') {\n throw new AgentError('INVALID_ARGUMENT', 'The dashboard requires terminal stdout with cursor control.');\n }\n const input = options.input ?? process.stdin;\n const wasRaw = input.isRaw ?? false;\n const wasFlowing = input.readableFlowing === true;\n let inputEnabled = false;\n const signals = options.signals ?? process;\n const discover = options.discover ?? (() => readEndpoint(stateDirectory()));\n const snapshot = options.snapshot ?? fetchDashboardSnapshot;\n const interval = options.intervalMs ?? 1_000;\n const abort = new AbortController();\n const renderer = new DashboardRenderer(output, await resolveDashboardWorkspace(options.cwd ?? process.cwd()));\n let rows: DashboardRow[] = [];\n let endpoint: Endpoint | undefined;\n let exitCode = 0;\n let outputError: unknown;\n const interrupt = () => { exitCode = 130; abort.abort(); };\n const terminate = () => { exitCode = 143; abort.abort(); };\n const keys = (chunk: Buffer | string) => {\n if (typeof chunk === 'string' ? chunk.includes('\\u0003') : chunk.includes(3)) interrupt();\n };\n const resize = () => renderer.render(rows, Date.now());\n const failedOutput = (error: unknown) => { outputError = error; abort.abort(); };\n signals.on('SIGINT', interrupt);\n signals.on('SIGTERM', terminate);\n output.on('resize', resize);\n output.on('error', failedOutput);\n try {\n if (input.isTTY) {\n input.setRawMode(true);\n inputEnabled = true;\n input.on('data', keys);\n input.resume();\n }\n renderer.start();\n while (!abort.signal.aborted) {\n const next = Date.now() + interval;\n endpoint ??= await discover();\n if (abort.signal.aborted) break;\n if (endpoint) rows = (await snapshot(endpoint, abort.signal)).sessions;\n if (abort.signal.aborted) break;\n renderer.render(rows, Date.now());\n await delay(Math.max(0, next - Date.now()), undefined, {signal: abort.signal});\n }\n } catch (error) {\n if (!abort.signal.aborted) throw error;\n } finally {\n abort.abort();\n renderer.close();\n if (inputEnabled) {\n input.off('data', keys);\n input.setRawMode(wasRaw);\n if (!wasFlowing) input.pause();\n }\n signals.off('SIGINT', interrupt);\n signals.off('SIGTERM', terminate);\n output.off('resize', resize);\n output.off('error', failedOutput);\n }\n if (outputError) throw new AgentError('INTERNAL', 'The dashboard terminal output failed.');\n return exitCode;\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  type DirectHarness = 'codex' | 'claudeCode' | 'fx';
2
- export declare function renderHelp(harness?: DirectHarness): string;
2
+ export declare function renderHelp(harness?: DirectHarness, command?: 'run' | 'check'): string;
3
3
  export {};
package/dist/cli/help.js CHANGED
@@ -2,13 +2,16 @@ const GLOBAL_HELP = `subharness — coordinate external coding agents
2
2
 
3
3
  Usage:
4
4
  subharness list [--cwd <directory>]
5
- subharness run <target> [options] <prompt>
6
- subharness run <target> [options] --prompt <text>
7
- subharness run <target> [options] --prompt-file <path|->
8
- subharness send <session-id> [--delivery queue|steer|interrupt] <prompt>
9
- subharness send <session-id> [--delivery queue|steer|interrupt] --prompt <text>
10
- subharness send <session-id> [--delivery queue|steer|interrupt] --prompt-file <path|->
11
- subharness wait <task-id> --after <response-id>
5
+ subharness dashboard
6
+ subharness check <target> [--cwd <directory>] [--format text|jsonl]
7
+ subharness check <harness> [--cwd <directory>] [--model <model>] [--effort <effort>] [--format text|jsonl]
8
+ subharness run <target> [options] [--detach] <prompt>
9
+ subharness run <target> [options] [--detach] --prompt <text>
10
+ subharness run <target> [options] [--detach] --prompt-file <path|->
11
+ subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] <prompt>
12
+ subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] --prompt <text>
13
+ subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] --prompt-file <path|->
14
+ subharness wait <task-id> [--after <response-id>]
12
15
  subharness status <task-id> [--full]
13
16
  subharness queue <session-id>
14
17
  subharness cancel <task-id>
@@ -17,8 +20,11 @@ Usage:
17
20
  Direct harness targets: codex, claude, fx. These bare names are reserved; same-named specialists require repo: or global:.
18
21
  Specialist targets: repo:<name>, global:<name>, or an unambiguous bare name.
19
22
  Declared child targets: subagent:<name>, resolved only from the active managed parent's declarations.
20
- Run "subharness run <harness> --help" for harness-specific options.
23
+ Run "subharness run <harness> --help" or "subharness check <harness> --help" for harness-specific options.
21
24
  Execution commands accept --format text|jsonl; text is the default.
25
+ Omit --after to wait for the first retained response; supply it to wait for the next response.
26
+ Detach after admission with --detach, then use wait or status; steering cannot be detached.
27
+ Dashboard is a terminal-only read-only view and accepts no options.
22
28
  --help and --version do not start a coordinator or native harness.
23
29
  Use -- to pass a positional prompt beginning with a dash.
24
30
  agent is an identical compatibility alias for subharness.
@@ -40,10 +46,29 @@ const details = {
40
46
  effort: 'fx accepts a nonempty native effort identifier; model compatibility is checked by the adapter.',
41
47
  },
42
48
  };
43
- export function renderHelp(harness) {
49
+ export function renderHelp(harness, command = 'run') {
44
50
  if (!harness)
45
51
  return GLOBAL_HELP;
46
52
  const target = details[harness];
53
+ if (command === 'check')
54
+ return `subharness check ${target.name} — check direct ${target.name} startup readiness
55
+
56
+ Usage:
57
+ subharness check ${target.name} [--cwd <directory>] [--model <model>] [--effort <effort>] [--format text|jsonl]
58
+
59
+ Options:
60
+ --cwd <directory> Execution directory for the startup check
61
+ --model <model> Native model identifier
62
+ --effort <effort> Native effort identifier
63
+ --format text|jsonl Output format (text by default)
64
+
65
+ The check opens and closes an isolated native session without submitting a turn.
66
+ Readiness covers startup at that moment, not quota, task success, native permissions, or continued availability.
67
+ Omitting --model or --effort uses the verifiable native default for the selected access route.
68
+ ${target.effort}
69
+ ${target.access}
70
+ Native harness flags are not forwarded; unsupported options are errors.
71
+ `;
47
72
  return `subharness run ${target.name} — start a direct ${target.name} session
48
73
 
49
74
  Usage:
@@ -57,6 +82,7 @@ Options:
57
82
  --effort <effort> Native effort identifier
58
83
  --prompt <text> Prompt text
59
84
  --prompt-file <path|-> Read the prompt from a UTF-8 file or stdin
85
+ --detach Return after admission without observing a response
60
86
  --format text|jsonl Output format (text by default)
61
87
 
62
88
  Omitting --model or --effort uses the verifiable native default for the selected access route.
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBnB,CAAC;AAEF,MAAM,OAAO,GAA0E;IACrF,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,wEAAwE;QAChF,MAAM,EAAE,mGAAmG;KAC5G;IACD,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,8EAA8E;QACtF,MAAM,EAAE,yDAAyD;KAClE;IACD,EAAE,EAAE;QACF,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,oEAAoE;QAC5E,MAAM,EAAE,gGAAgG;KACzG;CACF,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAAuB;IAChD,IAAI,CAAC,OAAO;QAAE,OAAO,WAAW,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,OAAO,kBAAkB,MAAM,CAAC,IAAI,qBAAqB,MAAM,CAAC,IAAI;;;mBAGnD,MAAM,CAAC,IAAI;mBACX,MAAM,CAAC,IAAI;mBACX,MAAM,CAAC,IAAI;;;;;;;;;;;EAW5B,MAAM,CAAC,MAAM;EACb,MAAM,CAAC,MAAM;;CAEd,CAAC;AACF,CAAC","sourcesContent":["type DirectHarness = 'codex' | 'claudeCode' | 'fx';\n\nconst GLOBAL_HELP = `subharness — coordinate external coding agents\n\nUsage:\n subharness list [--cwd <directory>]\n subharness run <target> [options] <prompt>\n subharness run <target> [options] --prompt <text>\n subharness run <target> [options] --prompt-file <path|->\n subharness send <session-id> [--delivery queue|steer|interrupt] <prompt>\n subharness send <session-id> [--delivery queue|steer|interrupt] --prompt <text>\n subharness send <session-id> [--delivery queue|steer|interrupt] --prompt-file <path|->\n subharness wait <task-id> --after <response-id>\n subharness status <task-id> [--full]\n subharness queue <session-id>\n subharness cancel <task-id>\n subharness resume <session-id>\n\nDirect harness targets: codex, claude, fx. These bare names are reserved; same-named specialists require repo: or global:.\nSpecialist targets: repo:<name>, global:<name>, or an unambiguous bare name.\nDeclared child targets: subagent:<name>, resolved only from the active managed parent's declarations.\nRun \"subharness run <harness> --help\" for harness-specific options.\nExecution commands accept --format text|jsonl; text is the default.\n--help and --version do not start a coordinator or native harness.\nUse -- to pass a positional prompt beginning with a dash.\nagent is an identical compatibility alias for subharness.\n`;\n\nconst details: Record<DirectHarness, {name: string; access: string; effort: string}> = {\n codex: {\n name: 'codex',\n access: 'Requires an installed Codex executable and eligible configured access.',\n effort: 'Codex accepts a nonempty native effort identifier; model compatibility is checked by the adapter.',\n },\n claudeCode: {\n name: 'claude',\n access: 'Requires an installed Claude Code executable and eligible configured access.',\n effort: 'Claude effort must be low, medium, high, xhigh, or max.',\n },\n fx: {\n name: 'fx',\n access: 'Requires an installed fx executable and configured Gateway access.',\n effort: 'fx accepts a nonempty native effort identifier; model compatibility is checked by the adapter.',\n },\n};\n\nexport function renderHelp(harness?: DirectHarness): string {\n if (!harness) return GLOBAL_HELP;\n const target = details[harness];\n return `subharness run ${target.name} — start a direct ${target.name} session\n\nUsage:\n subharness run ${target.name} [--cwd <directory>] [--model <model>] [--effort <effort>] <prompt>\n subharness run ${target.name} [options] --prompt <text>\n subharness run ${target.name} [options] --prompt-file <path|->\n\nOptions:\n --cwd <directory> Execution directory for the new session\n --model <model> Native model identifier\n --effort <effort> Native effort identifier\n --prompt <text> Prompt text\n --prompt-file <path|-> Read the prompt from a UTF-8 file or stdin\n --format text|jsonl Output format (text by default)\n\nOmitting --model or --effort uses the verifiable native default for the selected access route.\n${target.effort}\n${target.access}\nNative harness flags are not forwarded; unsupported options are errors.\n`;\n}\n"]}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BnB,CAAC;AAEF,MAAM,OAAO,GAA0E;IACrF,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,wEAAwE;QAChF,MAAM,EAAE,mGAAmG;KAC5G;IACD,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,8EAA8E;QACtF,MAAM,EAAE,yDAAyD;KAClE;IACD,EAAE,EAAE;QACF,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,oEAAoE;QAC5E,MAAM,EAAE,gGAAgG;KACzG;CACF,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAAuB,EAAE,OAAO,GAAoB,KAAK;IAClF,IAAI,CAAC,OAAO;QAAE,OAAO,WAAW,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,oBAAoB,MAAM,CAAC,IAAI,mBAAmB,MAAM,CAAC,IAAI;;;qBAG1E,MAAM,CAAC,IAAI;;;;;;;;;;;EAW9B,MAAM,CAAC,MAAM;EACb,MAAM,CAAC,MAAM;;CAEd,CAAC;IACA,OAAO,kBAAkB,MAAM,CAAC,IAAI,qBAAqB,MAAM,CAAC,IAAI;;;mBAGnD,MAAM,CAAC,IAAI;mBACX,MAAM,CAAC,IAAI;mBACX,MAAM,CAAC,IAAI;;;;;;;;;;;;EAY5B,MAAM,CAAC,MAAM;EACb,MAAM,CAAC,MAAM;;CAEd,CAAC;AACF,CAAC","sourcesContent":["type DirectHarness = 'codex' | 'claudeCode' | 'fx';\n\nconst GLOBAL_HELP = `subharness — coordinate external coding agents\n\nUsage:\n subharness list [--cwd <directory>]\n subharness dashboard\n subharness check <target> [--cwd <directory>] [--format text|jsonl]\n subharness check <harness> [--cwd <directory>] [--model <model>] [--effort <effort>] [--format text|jsonl]\n subharness run <target> [options] [--detach] <prompt>\n subharness run <target> [options] [--detach] --prompt <text>\n subharness run <target> [options] [--detach] --prompt-file <path|->\n subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] <prompt>\n subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] --prompt <text>\n subharness send <session-id> [--delivery queue|steer|interrupt] [--detach] --prompt-file <path|->\n subharness wait <task-id> [--after <response-id>]\n subharness status <task-id> [--full]\n subharness queue <session-id>\n subharness cancel <task-id>\n subharness resume <session-id>\n\nDirect harness targets: codex, claude, fx. These bare names are reserved; same-named specialists require repo: or global:.\nSpecialist targets: repo:<name>, global:<name>, or an unambiguous bare name.\nDeclared child targets: subagent:<name>, resolved only from the active managed parent's declarations.\nRun \"subharness run <harness> --help\" or \"subharness check <harness> --help\" for harness-specific options.\nExecution commands accept --format text|jsonl; text is the default.\nOmit --after to wait for the first retained response; supply it to wait for the next response.\nDetach after admission with --detach, then use wait or status; steering cannot be detached.\nDashboard is a terminal-only read-only view and accepts no options.\n--help and --version do not start a coordinator or native harness.\nUse -- to pass a positional prompt beginning with a dash.\nagent is an identical compatibility alias for subharness.\n`;\n\nconst details: Record<DirectHarness, {name: string; access: string; effort: string}> = {\n codex: {\n name: 'codex',\n access: 'Requires an installed Codex executable and eligible configured access.',\n effort: 'Codex accepts a nonempty native effort identifier; model compatibility is checked by the adapter.',\n },\n claudeCode: {\n name: 'claude',\n access: 'Requires an installed Claude Code executable and eligible configured access.',\n effort: 'Claude effort must be low, medium, high, xhigh, or max.',\n },\n fx: {\n name: 'fx',\n access: 'Requires an installed fx executable and configured Gateway access.',\n effort: 'fx accepts a nonempty native effort identifier; model compatibility is checked by the adapter.',\n },\n};\n\nexport function renderHelp(harness?: DirectHarness, command: 'run' | 'check' = 'run'): string {\n if (!harness) return GLOBAL_HELP;\n const target = details[harness];\n if (command === 'check') return `subharness check ${target.name} — check direct ${target.name} startup readiness\n\nUsage:\n subharness check ${target.name} [--cwd <directory>] [--model <model>] [--effort <effort>] [--format text|jsonl]\n\nOptions:\n --cwd <directory> Execution directory for the startup check\n --model <model> Native model identifier\n --effort <effort> Native effort identifier\n --format text|jsonl Output format (text by default)\n\nThe check opens and closes an isolated native session without submitting a turn.\nReadiness covers startup at that moment, not quota, task success, native permissions, or continued availability.\nOmitting --model or --effort uses the verifiable native default for the selected access route.\n${target.effort}\n${target.access}\nNative harness flags are not forwarded; unsupported options are errors.\n`;\n return `subharness run ${target.name} — start a direct ${target.name} session\n\nUsage:\n subharness run ${target.name} [--cwd <directory>] [--model <model>] [--effort <effort>] <prompt>\n subharness run ${target.name} [options] --prompt <text>\n subharness run ${target.name} [options] --prompt-file <path|->\n\nOptions:\n --cwd <directory> Execution directory for the new session\n --model <model> Native model identifier\n --effort <effort> Native effort identifier\n --prompt <text> Prompt text\n --prompt-file <path|-> Read the prompt from a UTF-8 file or stdin\n --detach Return after admission without observing a response\n --format text|jsonl Output format (text by default)\n\nOmitting --model or --effort uses the verifiable native default for the selected access route.\n${target.effort}\n${target.access}\nNative harness flags are not forwarded; unsupported options are errors.\n`;\n}\n"]}