toolroll 0.9.67 → 0.9.69

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 (250) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/acp-client.js +1 -3
  3. package/dist/agent-board.d.ts +0 -1
  4. package/dist/agent-board.js +0 -1
  5. package/dist/agent-fence.d.ts +58 -0
  6. package/dist/agent-fence.js +214 -19
  7. package/dist/agentconfig.d.ts +1 -1
  8. package/dist/agentconfig.js +6 -2
  9. package/dist/batch-checks.js +14 -26
  10. package/dist/browser/workspace.css +1 -1
  11. package/dist/browser/workspace.js +31 -29
  12. package/dist/builder.d.ts +9 -0
  13. package/dist/builder.js +30 -27
  14. package/dist/canonical-json.d.ts +14 -0
  15. package/dist/canonical-json.js +36 -0
  16. package/dist/chat-actions.js +1 -2
  17. package/dist/chat-admission.d.ts +19 -0
  18. package/dist/chat-admission.js +22 -0
  19. package/dist/chat-channel.js +1 -1
  20. package/dist/chat-delivery.js +0 -1
  21. package/dist/chat-follow.d.ts +17 -0
  22. package/dist/chat-follow.js +32 -0
  23. package/dist/chat-messages.js +0 -1
  24. package/dist/chat-settings-page.d.ts +23 -0
  25. package/dist/chat-settings-page.js +41 -0
  26. package/dist/check-access.d.ts +54 -0
  27. package/dist/check-access.js +80 -0
  28. package/dist/claim.js +3 -6
  29. package/dist/claude-print.d.ts +12 -0
  30. package/dist/claude-print.js +34 -0
  31. package/dist/cli-http.d.ts +0 -7
  32. package/dist/cli-http.js +0 -21
  33. package/dist/cli.d.ts +3 -10
  34. package/dist/cli.js +15 -24
  35. package/dist/coding-provider.js +2 -1
  36. package/dist/coding-workspace.js +1 -1
  37. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  38. package/dist/connect-cli.js +331 -0
  39. package/dist/containment.d.ts +0 -2
  40. package/dist/containment.js +2 -6
  41. package/dist/contracts/chat-actions.d.ts +0 -1
  42. package/dist/contracts/chat-content.d.ts +0 -4
  43. package/dist/contracts/chat-content.js +0 -2
  44. package/dist/contracts/chat-tables.d.ts +0 -1
  45. package/dist/contracts/cli.d.ts +0 -62
  46. package/dist/contracts/cli.js +0 -4
  47. package/dist/contracts/coding-workspace.js +1 -1
  48. package/dist/contracts/console-api.d.ts +1 -0
  49. package/dist/contracts/console-api.js +1 -0
  50. package/dist/contracts/contract.js +1 -1
  51. package/dist/contracts/flow-card.d.ts +0 -1
  52. package/dist/contracts/flow-propose.d.ts +0 -1
  53. package/dist/contracts/flow-send.js +1 -1
  54. package/dist/contracts/gateway-tools.d.ts +0 -1
  55. package/dist/contracts/integration-metadata.js +1 -1
  56. package/dist/contracts/lead-tools.d.ts +0 -1
  57. package/dist/contracts/memory-pass.d.ts +0 -1
  58. package/dist/contracts/memory-pass.js +1 -1
  59. package/dist/contracts/plan.js +1 -1
  60. package/dist/contracts/project-knowledge.js +1 -1
  61. package/dist/contracts/project-skills.d.ts +0 -1
  62. package/dist/contracts/proof.js +1 -1
  63. package/dist/contracts/recipes.js +1 -1
  64. package/dist/contracts/scope.js +1 -1
  65. package/dist/contracts/scout-report.js +1 -1
  66. package/dist/contracts/teams-callback.d.ts +0 -1
  67. package/dist/converse.d.ts +0 -6
  68. package/dist/converse.js +0 -19
  69. package/dist/daemon.js +3 -2
  70. package/dist/decision.d.ts +1 -3
  71. package/dist/deprecations.d.ts +12 -4
  72. package/dist/deprecations.js +27 -4
  73. package/dist/desktop-update-recovery.js +1 -1
  74. package/dist/desktop-update.js +3 -2
  75. package/dist/discord-chat.js +4 -15
  76. package/dist/discord-settings.d.ts +2 -6
  77. package/dist/discord-settings.js +10 -38
  78. package/dist/discord.js +12 -23
  79. package/dist/evidence.d.ts +0 -1
  80. package/dist/evidence.js +0 -3
  81. package/dist/exec.d.ts +0 -9
  82. package/dist/exec.js +3 -81
  83. package/dist/flow-actions.js +0 -3
  84. package/dist/flow-draft.d.ts +1 -3
  85. package/dist/flow-draft.js +15 -28
  86. package/dist/flow-scripts.d.ts +1 -8
  87. package/dist/flow-scripts.js +0 -13
  88. package/dist/flow-send.js +1 -1
  89. package/dist/flow-share.d.ts +0 -2
  90. package/dist/flow-share.js +1 -6
  91. package/dist/flow-steps.js +1 -1
  92. package/dist/flow-triggers.d.ts +0 -1
  93. package/dist/flow-triggers.js +1 -2
  94. package/dist/flow-unattended.js +1 -12
  95. package/dist/flows.js +1 -1
  96. package/dist/health-cli.d.ts +2 -2
  97. package/dist/health-cli.js +1 -1
  98. package/dist/invoke.d.ts +39 -5
  99. package/dist/invoke.js +181 -24
  100. package/dist/is-record.d.ts +2 -0
  101. package/dist/is-record.js +2 -0
  102. package/dist/lead-doors.d.ts +1 -5
  103. package/dist/lead-doors.js +0 -4
  104. package/dist/lead-tools.js +3 -6
  105. package/dist/live-detail.d.ts +0 -1
  106. package/dist/live-detail.js +0 -6
  107. package/dist/live-terminal.js +2 -1
  108. package/dist/live.d.ts +0 -1
  109. package/dist/live.js +1 -2
  110. package/dist/mcp-core.js +0 -2
  111. package/dist/memory-pass.js +1 -1
  112. package/dist/model-catalog.js +1 -1
  113. package/dist/modes.js +1 -12
  114. package/dist/operate.d.ts +4 -0
  115. package/dist/operate.js +456 -750
  116. package/dist/password-bearer.d.ts +0 -2
  117. package/dist/password-bearer.js +0 -4
  118. package/dist/peek-cli.js +2 -1
  119. package/dist/peek.d.ts +2 -26
  120. package/dist/peek.js +2 -168
  121. package/dist/phase-routing.d.ts +0 -4
  122. package/dist/phase-routing.js +2 -21
  123. package/dist/planner-source.d.ts +2 -1
  124. package/dist/planner-source.js +2 -12
  125. package/dist/planner.js +2 -14
  126. package/dist/process-tree.d.ts +0 -2
  127. package/dist/process-tree.js +0 -10
  128. package/dist/project-delete-ui.d.ts +16 -0
  129. package/dist/project-delete-ui.js +60 -1
  130. package/dist/project-delete.d.ts +0 -2
  131. package/dist/project-delete.js +1 -3
  132. package/dist/project-learning.d.ts +0 -14
  133. package/dist/project-skills.d.ts +0 -1
  134. package/dist/project-skills.js +0 -14
  135. package/dist/project-tools.js +2 -10
  136. package/dist/provider.d.ts +0 -17
  137. package/dist/provider.js +0 -26
  138. package/dist/pulls.d.ts +0 -2
  139. package/dist/pulls.js +0 -9
  140. package/dist/release-adapters.d.ts +3 -0
  141. package/dist/release-adapters.js +15 -5
  142. package/dist/remote-exec.d.ts +3 -2
  143. package/dist/remote-exec.js +3 -2
  144. package/dist/repos.d.ts +0 -5
  145. package/dist/repos.js +2 -10
  146. package/dist/repository-context.d.ts +0 -2
  147. package/dist/repository-context.js +0 -4
  148. package/dist/restart-certification.d.ts +0 -2
  149. package/dist/restart-certification.js +1 -5
  150. package/dist/result-follow-ups.d.ts +1 -4
  151. package/dist/result-follow-ups.js +11 -21
  152. package/dist/result-review.d.ts +0 -2
  153. package/dist/result-review.js +0 -2
  154. package/dist/retention.d.ts +0 -1
  155. package/dist/retention.js +0 -1
  156. package/dist/review-context.d.ts +0 -1
  157. package/dist/review-context.js +1 -12
  158. package/dist/reviewer.d.ts +0 -16
  159. package/dist/reviewer.js +0 -18
  160. package/dist/run-closeout.js +2 -1
  161. package/dist/scope.d.ts +9 -0
  162. package/dist/scope.js +13 -18
  163. package/dist/scout-browser.js +2 -1
  164. package/dist/serve.js +11 -2
  165. package/dist/server/console.css +5 -133
  166. package/dist/server/flows.js +2 -2
  167. package/dist/server/http.d.ts +4 -0
  168. package/dist/server/http.js +5 -0
  169. package/dist/server/pages.js +2 -2
  170. package/dist/server/people-tokens.js +3 -4
  171. package/dist/server/remote.js +1 -6
  172. package/dist/server/render-chat.d.ts +9 -2
  173. package/dist/server/render-chat.js +53 -7
  174. package/dist/server/render-results.d.ts +3 -6
  175. package/dist/server/render-results.js +4 -9
  176. package/dist/server/render-tasks.js +6 -6
  177. package/dist/server/route-table.d.ts +0 -4
  178. package/dist/server/route-table.js +1 -6
  179. package/dist/server/settings.js +60 -23
  180. package/dist/server/task-views.js +1 -1
  181. package/dist/server/tasks.js +5 -3
  182. package/dist/setup-guide.d.ts +2 -2
  183. package/dist/skills.d.ts +0 -6
  184. package/dist/skills.js +0 -8
  185. package/dist/slack-chat.d.ts +1 -7
  186. package/dist/slack-chat.js +5 -18
  187. package/dist/slack-settings.d.ts +2 -6
  188. package/dist/slack-settings.js +10 -38
  189. package/dist/slack.js +12 -23
  190. package/dist/sleep.d.ts +2 -0
  191. package/dist/sleep.js +2 -0
  192. package/dist/store.d.ts +29 -75
  193. package/dist/store.js +89 -180
  194. package/dist/structured-output.d.ts +0 -1
  195. package/dist/structured-output.js +0 -1
  196. package/dist/subagent-work.d.ts +0 -2
  197. package/dist/subagent-work.js +0 -2
  198. package/dist/subagents.d.ts +1 -3
  199. package/dist/subagents.js +20 -33
  200. package/dist/subscription-chat.d.ts +1 -3
  201. package/dist/subscription-chat.js +2 -3
  202. package/dist/surface.js +2 -1
  203. package/dist/task-sizing.d.ts +1 -4
  204. package/dist/task-sizing.js +3 -10
  205. package/dist/teams-chat.js +4 -11
  206. package/dist/teams.js +12 -19
  207. package/dist/telegram-lead.js +2 -2
  208. package/dist/telegram.js +1 -1
  209. package/dist/token-notices.d.ts +0 -11
  210. package/dist/token-notices.js +1 -5
  211. package/dist/tool-secret-store.d.ts +0 -3
  212. package/dist/tool-secret-store.js +2 -11
  213. package/dist/toolroll-update.d.ts +0 -2
  214. package/dist/toolroll-update.js +2 -2
  215. package/dist/verification-evidence.d.ts +0 -1
  216. package/dist/verification-evidence.js +0 -1
  217. package/dist/webhooks.d.ts +0 -1
  218. package/dist/webhooks.js +0 -1
  219. package/dist/workspace-ui.d.ts +0 -8
  220. package/dist/workspace-ui.js +1 -6
  221. package/package.json +1 -1
  222. package/dist/contracts/native-census.d.ts +0 -42
  223. package/dist/contracts/native-census.js +0 -36
  224. package/dist/process-recovery-anchor.d.ts +0 -75
  225. package/dist/process-recovery-anchor.js +0 -188
  226. package/dist/process-recovery-coalition.d.ts +0 -25
  227. package/dist/process-recovery-coalition.js +0 -105
  228. package/dist/process-recovery-eligibility.d.ts +0 -179
  229. package/dist/process-recovery-eligibility.js +0 -174
  230. package/dist/process-recovery-native.d.ts +0 -161
  231. package/dist/process-recovery-native.js +0 -595
  232. package/dist/process-recovery-provenance.d.ts +0 -163
  233. package/dist/process-recovery-provenance.js +0 -292
  234. package/dist/process-recovery-services.d.ts +0 -56
  235. package/dist/process-recovery-services.js +0 -191
  236. package/dist/process-recovery-settlement.d.ts +0 -32
  237. package/dist/process-recovery-settlement.js +0 -133
  238. package/dist/process-recovery.d.ts +0 -41
  239. package/dist/process-recovery.js +0 -91
  240. package/dist/session-brief.d.ts +0 -8
  241. package/dist/session-brief.js +0 -24
  242. package/dist/session-cli.d.ts +0 -42
  243. package/dist/session-cli.js +0 -271
  244. package/dist/session-contract.d.ts +0 -183
  245. package/dist/session-contract.js +0 -122
  246. package/dist/session-remote.d.ts +0 -19
  247. package/dist/session-remote.js +0 -86
  248. package/dist/session-service.d.ts +0 -22
  249. package/dist/session-service.js +0 -157
  250. package/dist/team-cli.js +0 -692
@@ -1,271 +0,0 @@
1
- import { open } from 'node:fs/promises';
2
- import { envelopeJson } from './envelope.js';
3
- import { SESSION_DESCRIPTORS, SESSION_PROMPT_BYTES, isSessionResponse, sessionCapabilities, sessionDescriptor, validateSessionRequest, } from './session-contract.js';
4
- import { centralProfile, UsageError } from './team-cli.js';
5
- import { postCli } from './remote-exec.js';
6
- import { TOKEN_REPLACEMENT } from './password-bearer.js';
7
- /** The files key the prompt travels under: the server reads no paths, only what the request carries. */
8
- export const SESSION_PROMPT_FILE = 'prompt';
9
- export const SESSION_CLI_ACTIONS = ['capabilities', ...SESSION_DESCRIPTORS.map(one => one.operation)];
10
- /** Before D7 the session client signed in with an operator password on every request. */
11
- const RETIRED_FLAGS = new Set(['url', 'as', 'token-env', 'token-file']);
12
- export const SESSION_TOKEN_REQUIRED = `Session commands use your saved API token. ${TOKEN_REPLACEMENT}, then save it with \`toolroll connect <origin> --as <account> --token-stdin\`.`;
13
- /** The flags an operation takes: on this computer (where the prompt may come from a file or stdin, and a saved
14
- * profile is chosen), or on the server (where the prompt is only ever the request's own file). */
15
- export function sessionCliFlags(spec, side = 'client') {
16
- const prompt = spec.operation === 'start' || spec.operation === 'send';
17
- return { json: 'flag', help: 'flag', ...(side === 'client' ? { profile: 'value' } : {}),
18
- ...Object.fromEntries(Object.keys(spec.flags).map(name => [name, 'value'])),
19
- ...(prompt ? { file: 'value', ...(side === 'client' ? { stdin: 'flag' } : {}) } : {}) };
20
- }
21
- function help(spec) {
22
- if (!spec)
23
- return `toolroll session <operation>\n\n${SESSION_DESCRIPTORS.map(one => ` ${one.operation.padEnd(12)} ${one.synopsis}`).join('\n')}\n capabilities Show this client's schemas and authority requirements\n\nUse session <operation> --help for required flags. Sessions run on the server you connected to with your API token (toolroll connect); they need instance operator access.`;
24
- const fields = spec.inputSchema.required.filter(one => !['version', 'sessionId', 'prompt'].includes(one));
25
- const required = fields.map(field => Object.entries(spec.flags).find(([, value]) => value.field === field)?.[0]).filter(Boolean);
26
- return `toolroll session ${spec.operation}${'sessionId' in spec.inputSchema.properties ? ' <session-id>' : ''}\n${spec.synopsis}.\n\n${required.length > 0 ? `Required:\n${required.map(name => ` --${name} <value>`).join('\n')}\n` : ''}${'prompt' in spec.inputSchema.properties ? ' --file <path> or --stdin (complete prompt, up to 64 KB)\n' : ''}${'expectedRevision' in spec.inputSchema.properties ? '\nUse the revision, nativeThreadId and turnId from session show. Use none for a null thread or turn.\n' : ''}\nOptions: ${Object.keys(sessionCliFlags(spec)).map(name => `--${name}`).join(' ')}\nRuns on the saved connection (or --profile <name>) over /api/cli with your API token. Mutations are never retried automatically.`;
27
- }
28
- /** One parser for both sides of /api/cli: `<operation> [<session-id>] --flag value …`. */
29
- export function parseSessionArguments(argv, side) {
30
- const operation = argv[0] ?? '';
31
- const spec = sessionDescriptor(operation);
32
- if (!spec)
33
- return { problem: `Unknown session operation. Use: ${SESSION_CLI_ACTIONS.join(', ')}.` };
34
- const allowed = sessionCliFlags(spec, side);
35
- const flags = {};
36
- const positionals = [];
37
- for (let index = 1; index < argv.length; index++) {
38
- const argument = argv[index];
39
- if (!argument.startsWith('-')) {
40
- positionals.push(argument);
41
- continue;
42
- }
43
- const name = argument === '-h' ? 'help' : argument.startsWith('--') ? argument.slice(2) : '';
44
- if (side === 'client' && RETIRED_FLAGS.has(name))
45
- return { problem: SESSION_TOKEN_REQUIRED };
46
- if (!Object.hasOwn(allowed, name))
47
- return { problem: `Unknown option ${argument} for session ${operation}.` };
48
- if (Object.hasOwn(flags, name))
49
- return { problem: `Option --${name} was repeated.` };
50
- if (allowed[name] === 'flag')
51
- flags[name] = true;
52
- else {
53
- const value = argv[++index];
54
- if (!value || value.startsWith('-'))
55
- return { problem: `--${name} requires a value.` };
56
- flags[name] = value;
57
- }
58
- }
59
- return { spec, flags, positionals };
60
- }
61
- /** The validated request the parsed arguments and prompt spell. */
62
- export function sessionRequestOf({ spec, flags, positionals }, prompt) {
63
- const request = { version: 1 };
64
- if ('sessionId' in spec.inputSchema.properties) {
65
- if (positionals.length !== 1)
66
- return { ok: false, message: 'Name exactly one session ID.' };
67
- request['sessionId'] = positionals[0];
68
- }
69
- else if (positionals.length)
70
- return { ok: false, message: 'This operation takes no positional arguments.' };
71
- for (const [name, field] of Object.entries(spec.flags)) {
72
- const raw = flags[name];
73
- if (typeof raw !== 'string')
74
- continue;
75
- if (field.kind === 'integer' && !/^\d+$/.test(raw))
76
- return { ok: false, message: `--${name} must be a whole number.` };
77
- request[field.field] = field.kind === 'integer' ? Number(raw) : field.kind === 'nullable-id' && raw === 'none' ? null : raw;
78
- }
79
- if (prompt !== undefined)
80
- request['prompt'] = prompt;
81
- return validateSessionRequest(spec.operation, request);
82
- }
83
- /** The exact argv the server runs for a validated request: always --json, the prompt as the request's one file. */
84
- export function sessionArgv(spec, request) {
85
- const fields = request;
86
- const argv = ['session', spec.operation];
87
- if (typeof fields['sessionId'] === 'string')
88
- argv.push(fields['sessionId']);
89
- for (const [name, field] of Object.entries(spec.flags)) {
90
- const value = fields[field.field];
91
- if (value !== undefined)
92
- argv.push(`--${name}`, value === null ? 'none' : String(value));
93
- }
94
- if (typeof fields['prompt'] === 'string')
95
- argv.push('--file', SESSION_PROMPT_FILE);
96
- argv.push('--json');
97
- return argv;
98
- }
99
- async function readBoundedFile(path, limit, label) {
100
- let file;
101
- try {
102
- file = await open(path, 'r');
103
- const stat = await file.stat();
104
- if (!stat.isFile() || stat.size > limit)
105
- throw new Error();
106
- const buffer = Buffer.alloc(limit + 1);
107
- let offset = 0;
108
- while (offset <= limit) {
109
- const { bytesRead } = await file.read(buffer, offset, buffer.length - offset, null);
110
- if (!bytesRead)
111
- break;
112
- offset += bytesRead;
113
- }
114
- if (offset > limit)
115
- throw new Error();
116
- return new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, offset));
117
- }
118
- catch {
119
- throw new UsageError(`${label} must be a readable UTF-8 regular file of at most ${limit} bytes.`);
120
- }
121
- finally {
122
- await file?.close();
123
- }
124
- }
125
- async function readStdin() {
126
- if (process.stdin.isTTY)
127
- throw new UsageError('Pipe the complete prompt into --stdin, or use --file.');
128
- const chunks = [];
129
- let bytes = 0;
130
- for await (const chunk of process.stdin) {
131
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
132
- bytes += buffer.length;
133
- if (bytes > SESSION_PROMPT_BYTES)
134
- throw new UsageError('The prompt exceeds 64 KB.');
135
- chunks.push(buffer);
136
- }
137
- try {
138
- return new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks));
139
- }
140
- catch {
141
- throw new UsageError('The prompt must be UTF-8 text.');
142
- }
143
- }
144
- function unconfirmed(spec, request) {
145
- const sessionId = 'sessionId' in request ? request.sessionId : undefined;
146
- return { version: 1, operation: spec.operation, ok: false, status: spec.mutation ? 'uncertain' : 'rejected',
147
- delivery: spec.mutation ? 'unknown' : 'not-sent', retry: spec.mutation ? 'inspect-first' : 'safe-read',
148
- reason: spec.mutation ? 'delivery-unknown' : 'service-unavailable',
149
- message: spec.mutation ? 'The service response could not be confirmed. Inspect the saved session before continuing; the request was not retried.' : 'The service response could not be read. Check the service address and connection.',
150
- nextActions: [{ operation: sessionId ? 'show' : 'list', label: sessionId ? 'Inspect session' : 'List sessions', ...(sessionId ? { sessionId } : {}) }] };
151
- }
152
- /** One request over /api/cli, never retried. Only an answer whose identity matches the request counts as delivered. */
153
- async function perform(spec, request, profile, options) {
154
- const files = typeof request.prompt === 'string' ? { [SESSION_PROMPT_FILE]: request.prompt } : {};
155
- const outcome = await postCli(profile.origin, profile.token, { argv: sessionArgv(spec, request), files }, options);
156
- if (outcome.kind === 'unconfirmed')
157
- return unconfirmed(spec, request);
158
- if (outcome.kind === 'refused') {
159
- // /api/cli refuses before it runs anything, except a command that stopped on the server: that one may have acted.
160
- if (outcome.status >= 500 && outcome.status !== 501 && outcome.status !== 503)
161
- return unconfirmed(spec, request);
162
- return { version: 1, operation: spec.operation, ok: false, status: 'rejected', delivery: 'not-sent', retry: spec.mutation ? 'never' : 'safe-read',
163
- reason: /^[a-z][a-z0-9-]*$/.test(outcome.code) ? outcome.code : 'refused', message: outcome.message, nextActions: [] };
164
- }
165
- let payload;
166
- try {
167
- payload = JSON.parse(outcome.answer.stdout);
168
- }
169
- catch {
170
- return unconfirmed(spec, request);
171
- }
172
- if (!isSessionResponse(payload, spec.operation))
173
- return unconfirmed(spec, request);
174
- if ('sessionId' in request && payload.result?.session && payload.result.session.id !== request.sessionId)
175
- return unconfirmed(spec, request);
176
- if (payload.ok && 'key' in request && payload.result?.receipt?.key !== request.key)
177
- return unconfirmed(spec, request);
178
- if (payload.ok && 'expectedThreadId' in request && request.expectedThreadId !== null && payload.result?.session?.nativeThreadId !== request.expectedThreadId)
179
- return unconfirmed(spec, request);
180
- return payload;
181
- }
182
- const terminalText = (value) => value.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
183
- /** The plain-text answer, the same on either side of the transport. */
184
- export function renderSessionResponse(response) {
185
- const result = response.result;
186
- const lines = response.ok && response.operation === 'show' && result?.brief ? [] : [response.message];
187
- for (const session of result?.sessions ?? [])
188
- lines.push(`${session.id} ${session.status} ${session.title}`);
189
- if (result?.session) {
190
- const session = result.session;
191
- lines.push(result?.brief?.summary ?? `${session.title} · ${session.status}`, `Session: ${session.id}`, `Revision: ${session.revision} · Thread: ${session.nativeThreadId ?? 'none'} · Turn: ${session.turnId ?? 'none'}`);
192
- if (session.error && !result?.brief)
193
- lines.push(session.error);
194
- }
195
- for (const item of result?.items ?? [])
196
- lines.push(`\n${item.type}: ${item.text}`);
197
- for (const request of result?.requests ?? [])
198
- lines.push(`\n${request.title}\n${request.detail}`);
199
- if (result?.changes)
200
- lines.push(result.changes.status, result.changes.diff, ...(result.changes.truncated ? ['Changes were truncated. Open the workspace for the full diff.'] : []));
201
- if (result?.truncated)
202
- lines.push('The response is shortened. Open the workspace for complete activity.');
203
- for (const action of response.nextActions)
204
- lines.push(`${action.label}: ${action.operation === 'open-ui' ? 'open the coding workspace' : `toolroll session ${action.operation}${action.sessionId ? ` ${action.sessionId}` : ''}`}`);
205
- return terminalText(lines.join('\n'));
206
- }
207
- /** 0 delivered · 1 unknown (inspect first) · 3 refused. */
208
- export const sessionExitCode = (response) => response.ok ? 0 : response.status === 'uncertain' || response.reason === 'service-unavailable' ? 1 : 3;
209
- export async function runSessionCommand(argv, write, options = {}) {
210
- const json = argv.includes('--json');
211
- const stderr = options.stderr ?? (line => process.stderr.write(`${line}\n`));
212
- const operation = argv[0];
213
- const command = operation ? `session ${operation}` : 'session';
214
- const fail = (message) => {
215
- const payload = { ok: false, command, reason: 'usage', status: 'rejected', delivery: 'not-sent', retry: 'never', message };
216
- if (json)
217
- write(envelopeJson(payload));
218
- else
219
- stderr(message);
220
- return 2;
221
- };
222
- if (!operation || operation === '--help' || operation === '-h') {
223
- if (json)
224
- write(envelopeJson({ ok: true, command: 'session', ...sessionCapabilities() }));
225
- else
226
- write(help());
227
- return 0;
228
- }
229
- if (operation === 'capabilities') {
230
- if (argv.slice(1).some(arg => !['--json', '--help', '-h'].includes(arg)))
231
- return fail('session capabilities accepts only --json or --help.');
232
- write(json ? envelopeJson({ ok: true, command, ...sessionCapabilities() }) : help());
233
- return 0;
234
- }
235
- const parsed = parseSessionArguments(argv, 'client');
236
- if ('problem' in parsed)
237
- return fail(parsed.problem);
238
- const { spec, flags } = parsed;
239
- if (flags['help']) {
240
- write(json ? envelopeJson({ ok: true, command, descriptor: spec, help: help(spec) }) : help(spec));
241
- return 0;
242
- }
243
- let request;
244
- let profile;
245
- try {
246
- let prompt;
247
- if ('prompt' in spec.inputSchema.properties) {
248
- if (Boolean(flags['file']) === Boolean(flags['stdin']))
249
- throw new UsageError('Choose exactly one prompt source: --file <path> or --stdin.');
250
- prompt = typeof flags['file'] === 'string' ? await readBoundedFile(flags['file'], SESSION_PROMPT_BYTES, 'The prompt file') : await (options.readStdin ?? readStdin)();
251
- }
252
- const validated = sessionRequestOf(parsed, prompt);
253
- if (!validated.ok)
254
- throw new UsageError(validated.message);
255
- request = validated.request;
256
- profile = centralProfile(options, typeof flags['profile'] === 'string' ? flags['profile'] : undefined);
257
- }
258
- catch (error) {
259
- return fail(error instanceof UsageError ? error.message : 'The session request could not be prepared. Check the prompt and saved connection.');
260
- }
261
- if (profile === null || !profile.apiToken)
262
- return fail(SESSION_TOKEN_REQUIRED);
263
- const response = await perform(spec, request, profile, options);
264
- if (json)
265
- write(envelopeJson({ ...response, command, ...('key' in request ? { key: request.key } : {}) }));
266
- else if (response.ok)
267
- write(renderSessionResponse(response));
268
- else
269
- stderr(renderSessionResponse(response));
270
- return sessionExitCode(response);
271
- }
@@ -1,183 +0,0 @@
1
- /** The session transport is a client of the running owner, never a catalog owner.
2
- * These descriptors drive request validation, CLI parsing, help and discovery. */
3
- export declare const SESSION_PROTOCOL_VERSION = 1;
4
- export declare const SESSION_PROMPT_BYTES = 64000;
5
- export declare const SESSION_REQUEST_BYTES = 128000;
6
- export declare const SESSION_RESPONSE_BYTES = 4000000;
7
- export type SessionOperation = 'list' | 'show' | 'changes' | 'start' | 'send' | 'stop' | 'resume' | 'recover';
8
- export type SessionExpected = {
9
- sessionId: string;
10
- key: string;
11
- expectedRevision: number;
12
- expectedThreadId: string | null;
13
- expectedTurnId: string | null;
14
- };
15
- export type SessionRequests = {
16
- list: {
17
- version: 1;
18
- project?: string;
19
- limit?: number;
20
- };
21
- show: {
22
- version: 1;
23
- sessionId: string;
24
- view?: 'brief' | 'activity';
25
- };
26
- changes: {
27
- version: 1;
28
- sessionId: string;
29
- };
30
- start: {
31
- version: 1;
32
- project: string;
33
- title: string;
34
- prompt: string;
35
- model?: string;
36
- key: string;
37
- };
38
- send: {
39
- version: 1;
40
- prompt: string;
41
- } & SessionExpected;
42
- stop: {
43
- version: 1;
44
- } & SessionExpected;
45
- resume: {
46
- version: 1;
47
- } & SessionExpected;
48
- recover: {
49
- version: 1;
50
- } & SessionExpected;
51
- };
52
- export type SessionRequest = SessionRequests[SessionOperation];
53
- export type SessionView = {
54
- id: string;
55
- repo: string;
56
- title: string;
57
- status: string;
58
- nativeThreadId: string | null;
59
- turnId: string | null;
60
- revision: number;
61
- branch?: string;
62
- base?: string;
63
- error?: string | null;
64
- deliveryReviewRequired?: boolean;
65
- updatedAt?: string;
66
- };
67
- export type SessionBrief = {
68
- summary: string;
69
- nextAction: string;
70
- sourceRevision: number;
71
- sourceItemIds?: string[];
72
- partialHistory?: boolean;
73
- };
74
- export type SessionResult = {
75
- session?: SessionView;
76
- sessions?: SessionView[];
77
- items?: {
78
- id: string;
79
- type: string;
80
- text: string;
81
- status: string | null;
82
- clientId?: string;
83
- }[];
84
- requests?: {
85
- id: string;
86
- kind: string;
87
- title: string;
88
- detail: string;
89
- }[];
90
- changes?: {
91
- head: string;
92
- status: string;
93
- diff: string;
94
- truncated: boolean;
95
- };
96
- receipt?: {
97
- key: string;
98
- status: string;
99
- };
100
- brief?: SessionBrief;
101
- truncated?: boolean;
102
- };
103
- export type SessionNextAction = {
104
- operation: SessionOperation | 'open-ui';
105
- label: string;
106
- sessionId?: string;
107
- };
108
- export type SessionResponse = {
109
- version: 1;
110
- operation: SessionOperation;
111
- ok: boolean;
112
- status: 'succeeded' | 'pending' | 'rejected' | 'uncertain';
113
- delivery: 'confirmed' | 'not-sent' | 'unknown';
114
- retry: 'safe-read' | 'inspect-first' | 'never';
115
- message: string;
116
- nextActions: SessionNextAction[];
117
- reason?: string;
118
- result?: SessionResult;
119
- };
120
- type Field = {
121
- type: 'string' | 'integer' | readonly ['string', 'null'];
122
- minLength?: number;
123
- maxLength?: number;
124
- pattern?: string;
125
- minimum?: number;
126
- maximum?: number;
127
- maxBytes?: number;
128
- nonblank?: boolean;
129
- const?: number;
130
- };
131
- export type SessionDescriptor = {
132
- operation: SessionOperation;
133
- synopsis: string;
134
- mutation: boolean;
135
- audience: 'operator';
136
- agentMayInvoke: false;
137
- retry: 'safe-read' | 'inspect-first';
138
- inputSchema: {
139
- type: 'object';
140
- additionalProperties: false;
141
- required: readonly string[];
142
- properties: Record<string, Field>;
143
- };
144
- flags: Readonly<Record<string, {
145
- field: string;
146
- kind: 'string' | 'integer' | 'nullable-id';
147
- }>>;
148
- };
149
- export declare const SESSION_DESCRIPTORS: readonly SessionDescriptor[];
150
- export declare const SESSION_OPERATIONS: SessionOperation[];
151
- export declare function sessionDescriptor(operation: string): SessionDescriptor | undefined;
152
- export type SessionValidation = {
153
- ok: true;
154
- request: SessionRequest;
155
- } | {
156
- ok: false;
157
- message: string;
158
- };
159
- export declare function validateSessionRequest(operation: string, value: unknown): SessionValidation;
160
- /** Validate before claiming success. A broken response to a mutation is unknown
161
- * delivery, not proof that the owner rejected it. Additional fields are allowed. */
162
- export declare function isSessionResponse(value: unknown, operation: SessionOperation): value is SessionResponse;
163
- export declare function sessionCapabilities(): {
164
- version: number;
165
- transport: string;
166
- authentication: string;
167
- authority: string;
168
- operations: readonly SessionDescriptor[];
169
- brief: {
170
- supported: boolean;
171
- operation: string;
172
- defaultView: string;
173
- source: string;
174
- };
175
- unsupported: string[];
176
- limits: {
177
- promptBytes: number;
178
- requestBytes: number;
179
- responseBytes: number;
180
- };
181
- retry: string;
182
- };
183
- export {};
@@ -1,122 +0,0 @@
1
- /** The session transport is a client of the running owner, never a catalog owner.
2
- * These descriptors drive request validation, CLI parsing, help and discovery. */
3
- export const SESSION_PROTOCOL_VERSION = 1;
4
- export const SESSION_PROMPT_BYTES = 64_000;
5
- export const SESSION_REQUEST_BYTES = 128_000;
6
- export const SESSION_RESPONSE_BYTES = 4_000_000;
7
- const text = (maxLength) => ({ type: 'string', minLength: 1, maxLength, nonblank: true });
8
- const identity = { ...text(200), pattern: '^[A-Za-z0-9_.:-]+$' };
9
- const nullableIdentity = { ...identity, type: ['string', 'null'] };
10
- const key = { type: 'string', pattern: '^[A-Za-z0-9_-]{16,100}$', minLength: 16, maxLength: 100 };
11
- const prompt = { ...text(SESSION_PROMPT_BYTES), maxBytes: SESSION_PROMPT_BYTES };
12
- const model = { ...text(128), pattern: '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' };
13
- const expected = { sessionId: identity, key, expectedRevision: { type: 'integer', minimum: 0 }, expectedThreadId: nullableIdentity, expectedTurnId: nullableIdentity };
14
- const expectedFlags = {
15
- key: { field: 'key', kind: 'string' }, revision: { field: 'expectedRevision', kind: 'integer' },
16
- thread: { field: 'expectedThreadId', kind: 'nullable-id' }, turn: { field: 'expectedTurnId', kind: 'nullable-id' },
17
- };
18
- function descriptor(operation, synopsis, mutation, fields, required, flags) {
19
- return { operation, synopsis, mutation, audience: 'operator', agentMayInvoke: false, retry: mutation ? 'inspect-first' : 'safe-read',
20
- inputSchema: { type: 'object', additionalProperties: false, required: ['version', ...required], properties: { version: { type: 'integer', const: 1 }, ...fields } }, flags };
21
- }
22
- export const SESSION_DESCRIPTORS = [
23
- descriptor('list', 'List your native coding sessions', false, { project: text(4096), limit: { type: 'integer', minimum: 1, maximum: 100 } }, [], { project: { field: 'project', kind: 'string' }, limit: { field: 'limit', kind: 'integer' } }),
24
- descriptor('show', 'Inspect the saved brief and exact identity; request activity when needed', false, { sessionId: identity, view: { type: 'string', pattern: '^(brief|activity)$' } }, ['sessionId'], { view: { field: 'view', kind: 'string' } }),
25
- descriptor('changes', 'Read changes from the saved session checkout', false, { sessionId: identity }, ['sessionId'], {}),
26
- descriptor('start', 'Start a native coding session in a project', true, { project: text(4096), title: text(160), prompt, model, key }, ['project', 'title', 'prompt', 'key'], { project: { field: 'project', kind: 'string' }, title: { field: 'title', kind: 'string' }, model: { field: 'model', kind: 'string' }, key: { field: 'key', kind: 'string' } }),
27
- descriptor('send', 'Send a message to the exact saved session and turn', true, { ...expected, prompt }, [...Object.keys(expected), 'prompt'], expectedFlags),
28
- descriptor('stop', 'Stop the exact running turn', true, expected, Object.keys(expected), expectedFlags),
29
- descriptor('resume', 'Reconnect the saved conversation', true, expected, Object.keys(expected), expectedFlags),
30
- descriptor('recover', 'Check process exit and reconcile saved delivery receipts', true, expected, Object.keys(expected), expectedFlags),
31
- ];
32
- export const SESSION_OPERATIONS = SESSION_DESCRIPTORS.map(one => one.operation);
33
- export function sessionDescriptor(operation) { return SESSION_DESCRIPTORS.find(one => one.operation === operation); }
34
- const record = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
35
- export function validateSessionRequest(operation, value) {
36
- const spec = sessionDescriptor(operation);
37
- const bad = (message) => ({ ok: false, message });
38
- if (!spec)
39
- return bad('Choose a supported session operation.');
40
- if (!record(value))
41
- return bad('The session request must be a JSON object.');
42
- for (const field of Object.keys(value))
43
- if (!Object.hasOwn(spec.inputSchema.properties, field))
44
- return bad(`Unknown session field: ${field}.`);
45
- for (const field of spec.inputSchema.required)
46
- if (!Object.hasOwn(value, field))
47
- return bad(`Missing session field: ${field}.`);
48
- for (const [name, valueAtField] of Object.entries(value)) {
49
- const field = spec.inputSchema.properties[name];
50
- if (Array.isArray(field.type) && valueAtField === null)
51
- continue;
52
- if (field.type === 'integer') {
53
- if (!Number.isSafeInteger(valueAtField) || typeof valueAtField !== 'number' || (field.minimum !== undefined && valueAtField < field.minimum) || (field.maximum !== undefined && valueAtField > field.maximum) || (field.const !== undefined && valueAtField !== field.const))
54
- return bad(`Invalid ${name}.`);
55
- }
56
- else {
57
- if (typeof valueAtField !== 'string' || (field.nonblank && !valueAtField.trim()) || (field.minLength !== undefined && valueAtField.length < field.minLength) || (field.maxLength !== undefined && valueAtField.length > field.maxLength) || (field.maxBytes !== undefined && Buffer.byteLength(valueAtField) > field.maxBytes) || (field.pattern !== undefined && !new RegExp(field.pattern).test(valueAtField)))
58
- return bad(`Invalid ${name}.`);
59
- }
60
- }
61
- if (Buffer.byteLength(JSON.stringify(value)) > SESSION_REQUEST_BYTES)
62
- return bad('The session request is too large.');
63
- return { ok: true, request: value };
64
- }
65
- /** Validate before claiming success. A broken response to a mutation is unknown
66
- * delivery, not proof that the owner rejected it. Additional fields are allowed. */
67
- export function isSessionResponse(value, operation) {
68
- if (!record(value) || value['version'] !== 1 || value['operation'] !== operation || typeof value['ok'] !== 'boolean' || typeof value['message'] !== 'string' || !Array.isArray(value['nextActions']))
69
- return false;
70
- if (!['succeeded', 'pending', 'rejected', 'uncertain'].includes(String(value['status'])) || !['confirmed', 'not-sent', 'unknown'].includes(String(value['delivery'])) || !['safe-read', 'inspect-first', 'never'].includes(String(value['retry'])))
71
- return false;
72
- if ((value['ok'] === true) !== ['succeeded', 'pending'].includes(String(value['status'])))
73
- return false;
74
- if (value['status'] === 'uncertain' && (value['delivery'] !== 'unknown' || value['retry'] !== 'inspect-first'))
75
- return false;
76
- if (value['status'] === 'rejected' && value['delivery'] !== 'not-sent')
77
- return false;
78
- if (value['ok'] === true && value['delivery'] !== 'confirmed')
79
- return false;
80
- if (value['reason'] !== undefined && (typeof value['reason'] !== 'string' || !/^[a-z][a-z0-9-]*$/.test(value['reason'])))
81
- return false;
82
- for (const action of value['nextActions'])
83
- if (!record(action) || typeof action['label'] !== 'string' || ![...SESSION_OPERATIONS, 'open-ui'].includes(String(action['operation'])) || (action['sessionId'] !== undefined && typeof action['sessionId'] !== 'string'))
84
- return false;
85
- if (value['result'] === undefined)
86
- return value['ok'] === false;
87
- const result = value['result'];
88
- if (!record(result))
89
- return false;
90
- const session = (item) => record(item) && ['id', 'repo', 'title', 'status'].every(key => typeof item[key] === 'string') && (typeof item['nativeThreadId'] === 'string' || item['nativeThreadId'] === null) && (typeof item['turnId'] === 'string' || item['turnId'] === null) && Number.isSafeInteger(item['revision']) && Number(item['revision']) >= 0;
91
- if (result['session'] !== undefined && !session(result['session']))
92
- return false;
93
- if (result['sessions'] !== undefined && (!Array.isArray(result['sessions']) || !result['sessions'].every(session)))
94
- return false;
95
- if (value['ok'] === true && (operation === 'list' ? !Array.isArray(result['sessions']) : !session(result['session'])))
96
- return false;
97
- if (result['changes'] !== undefined) {
98
- const changes = result['changes'];
99
- if (!record(changes) || !['head', 'status', 'diff'].every(key => typeof changes[key] === 'string') || typeof changes['truncated'] !== 'boolean')
100
- return false;
101
- }
102
- if (value['ok'] === true && operation === 'changes' && result['changes'] === undefined)
103
- return false;
104
- if (result['items'] !== undefined && (!Array.isArray(result['items']) || !result['items'].every(item => record(item) && ['id', 'type', 'text'].every(key => typeof item[key] === 'string') && (item['status'] === null || typeof item['status'] === 'string'))))
105
- return false;
106
- if (result['requests'] !== undefined && (!Array.isArray(result['requests']) || !result['requests'].every(item => record(item) && ['id', 'kind', 'title', 'detail'].every(key => typeof item[key] === 'string'))))
107
- return false;
108
- if (result['receipt'] !== undefined && (!record(result['receipt']) || typeof result['receipt']['key'] !== 'string' || typeof result['receipt']['status'] !== 'string'))
109
- return false;
110
- if (result['truncated'] !== undefined && typeof result['truncated'] !== 'boolean')
111
- return false;
112
- if (result['brief'] !== undefined && (!record(result['brief']) || typeof result['brief']['summary'] !== 'string' || typeof result['brief']['nextAction'] !== 'string' || !Number.isSafeInteger(result['brief']['sourceRevision'])))
113
- return false;
114
- return true;
115
- }
116
- export function sessionCapabilities() {
117
- return { version: SESSION_PROTOCOL_VERSION, transport: 'POST /api/cli with argv session <operation> (the prompt travels as the request\'s one file)', authentication: 'API token: Authorization Bearer so_… (toolroll tokens create, saved by toolroll connect)',
118
- authority: 'An instance operator\'s API token without a project limit reads sessions; changing one needs an act token. Lead and coordinator credentials have no session access.',
119
- operations: SESSION_DESCRIPTORS, brief: { supported: true, operation: 'show', defaultView: 'brief', source: 'Saved state and labeled agent reports, pinned to a revision; no additional model call.' },
120
- unsupported: ['events', 'review', 'continue'], limits: { promptBytes: SESSION_PROMPT_BYTES, requestBytes: SESSION_REQUEST_BYTES, responseBytes: SESSION_RESPONSE_BYTES },
121
- retry: 'Never automatically retry a mutation. Inspect saved state after a lost response; keep the original key.' };
122
- }
@@ -1,19 +0,0 @@
1
- import type { Store } from './store.js';
2
- import type { CodingWorkspace } from './coding-workspace.js';
3
- import { type Principal } from './operate-remote.js';
4
- /**
5
- * The server half of `toolroll session …` over `POST /api/cli` (D7, replacing the separate /api/sessions transport and
6
- * its password sign-in). /api/cli has already proved the API token and charged its budget; this binds the token's
7
- * person to the existing session owner, which rechecks the account and project on every operation. Native sessions
8
- * stay installation-wide operator authority: only an instance operator's act token, limited to no projects, may
9
- * change one. Every request is in the action ledger like any remote command, and every answer is one session
10
- * envelope: a mutation whose outcome is unknown says so, and is never retried here.
11
- */
12
- export type SessionRunner = (principal: Principal, argv: readonly string[], files: Readonly<Record<string, string>>, write: (line: string) => void) => Promise<number>;
13
- export declare function createSessionRunner(options: {
14
- store: Store;
15
- workspace: CodingWorkspace | null;
16
- projects: () => readonly string[];
17
- projectAllowed: (repo: string) => boolean;
18
- clock?: () => Date;
19
- }): SessionRunner;