@illuminis/comprism 0.1.3 → 0.1.4

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 (131) hide show
  1. package/README.md +61 -2
  2. package/out/agent/command.d.ts +75 -4
  3. package/out/agent/command.js +220 -25
  4. package/out/agent/render.d.ts +17 -2
  5. package/out/agent/render.js +157 -14
  6. package/out/agent/session.d.ts +141 -2
  7. package/out/agent/session.js +735 -149
  8. package/out/commands/agents.d.ts +2 -0
  9. package/out/commands/agents.js +79 -0
  10. package/out/commands/ask.d.ts +1 -1
  11. package/out/commands/ask.js +78 -11
  12. package/out/commands/commands-thin.js +79 -17
  13. package/out/commands/config.d.ts +1 -0
  14. package/out/commands/config.js +138 -0
  15. package/out/commands/cost.d.ts +1 -0
  16. package/out/commands/cost.js +167 -0
  17. package/out/commands/hooks.d.ts +1 -0
  18. package/out/commands/hooks.js +83 -0
  19. package/out/commands/install.d.ts +44 -1
  20. package/out/commands/install.js +198 -4
  21. package/out/commands/instructions.d.ts +1 -0
  22. package/out/commands/instructions.js +113 -0
  23. package/out/commands/integrations.d.ts +3 -0
  24. package/out/commands/integrations.js +215 -0
  25. package/out/commands/jobs.d.ts +5 -0
  26. package/out/commands/jobs.js +157 -0
  27. package/out/commands/login.js +188 -36
  28. package/out/commands/memory.d.ts +3 -0
  29. package/out/commands/memory.js +113 -0
  30. package/out/commands/permissions.d.ts +1 -0
  31. package/out/commands/permissions.js +94 -0
  32. package/out/commands/plugins.d.ts +4 -0
  33. package/out/commands/plugins.js +192 -0
  34. package/out/commands/privacy.d.ts +1 -0
  35. package/out/commands/privacy.js +57 -0
  36. package/out/commands/providerKey.d.ts +32 -0
  37. package/out/commands/providerKey.js +108 -0
  38. package/out/commands/repl.d.ts +8 -1
  39. package/out/commands/repl.js +1207 -118
  40. package/out/commands/report.d.ts +39 -0
  41. package/out/commands/report.js +115 -0
  42. package/out/commands/review.d.ts +5 -0
  43. package/out/commands/review.js +223 -0
  44. package/out/commands/sessions.d.ts +23 -0
  45. package/out/commands/sessions.js +115 -0
  46. package/out/commands/settings.d.ts +3 -1
  47. package/out/commands/settings.js +18 -16
  48. package/out/commands/skills.d.ts +21 -0
  49. package/out/commands/skills.js +207 -0
  50. package/out/commands/unattended.d.ts +7 -0
  51. package/out/commands/unattended.js +351 -0
  52. package/out/commands/update.d.ts +1 -0
  53. package/out/commands/update.js +123 -0
  54. package/out/commands/worktrees.d.ts +5 -0
  55. package/out/commands/worktrees.js +186 -0
  56. package/out/executor/browser.d.ts +14 -0
  57. package/out/executor/browser.js +270 -0
  58. package/out/executor/diagnostics.d.ts +2 -0
  59. package/out/executor/diagnostics.js +181 -0
  60. package/out/executor/files.js +270 -40
  61. package/out/executor/git.js +42 -29
  62. package/out/executor/hooks.d.ts +42 -58
  63. package/out/executor/hooks.js +89 -182
  64. package/out/executor/index.d.ts +21 -5
  65. package/out/executor/index.js +160 -14
  66. package/out/executor/paths.d.ts +6 -1
  67. package/out/executor/paths.js +34 -6
  68. package/out/executor/sandbox.d.ts +40 -0
  69. package/out/executor/sandbox.js +299 -0
  70. package/out/executor/shell.d.ts +49 -4
  71. package/out/executor/shell.js +302 -56
  72. package/out/executor/toolservers.d.ts +20 -0
  73. package/out/executor/toolservers.js +189 -0
  74. package/out/executor/worktree.d.ts +9 -0
  75. package/out/executor/worktree.js +119 -0
  76. package/out/graph/read-python.js +2 -1
  77. package/out/lib/attach.d.ts +56 -12
  78. package/out/lib/attach.js +230 -63
  79. package/out/lib/clipboard.d.ts +23 -0
  80. package/out/lib/clipboard.js +182 -0
  81. package/out/lib/commandlist.d.ts +20 -0
  82. package/out/lib/commandlist.js +58 -0
  83. package/out/lib/decision.d.ts +22 -0
  84. package/out/lib/decision.js +50 -0
  85. package/out/lib/fingerprint.d.ts +25 -0
  86. package/out/lib/fingerprint.js +58 -0
  87. package/out/lib/gateway.d.ts +186 -2
  88. package/out/lib/gateway.js +59 -4
  89. package/out/lib/history.d.ts +24 -0
  90. package/out/lib/history.js +137 -0
  91. package/out/lib/ide.d.ts +19 -0
  92. package/out/lib/ide.js +131 -0
  93. package/out/lib/keyboard.d.ts +95 -0
  94. package/out/lib/keyboard.js +383 -0
  95. package/out/lib/machine.d.ts +21 -0
  96. package/out/lib/machine.js +91 -0
  97. package/out/lib/notify.d.ts +4 -0
  98. package/out/lib/notify.js +52 -0
  99. package/out/lib/output.d.ts +48 -0
  100. package/out/lib/output.js +108 -0
  101. package/out/lib/project-ops.d.ts +19 -0
  102. package/out/lib/project-ops.js +146 -0
  103. package/out/lib/project.d.ts +28 -0
  104. package/out/lib/project.js +114 -0
  105. package/out/lib/prompt.js +15 -2
  106. package/out/lib/queue.d.ts +13 -0
  107. package/out/lib/queue.js +116 -0
  108. package/out/lib/readiness.d.ts +19 -0
  109. package/out/lib/readiness.js +170 -1
  110. package/out/lib/self.d.ts +23 -0
  111. package/out/lib/self.js +124 -0
  112. package/out/lib/sessions.d.ts +19 -0
  113. package/out/lib/sessions.js +221 -0
  114. package/out/lib/stdin.d.ts +32 -0
  115. package/out/lib/stdin.js +117 -0
  116. package/out/lib/store.d.ts +40 -0
  117. package/out/lib/store.js +138 -0
  118. package/out/lib/sync.d.ts +18 -0
  119. package/out/lib/sync.js +81 -0
  120. package/out/lib/ui.d.ts +2 -4
  121. package/out/lib/ui.js +31 -25
  122. package/out/lib/voice.js +24 -0
  123. package/out/postinstall.js +42 -17
  124. package/out/providers/anthropic.d.ts +22 -0
  125. package/out/providers/anthropic.js +80 -0
  126. package/out/providers/index.d.ts +8 -0
  127. package/out/providers/index.js +83 -0
  128. package/out/providers/openai.d.ts +11 -0
  129. package/out/providers/openai.js +57 -0
  130. package/out/thin.js +594 -32
  131. package/package.json +9 -49
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readOne = readOne;
4
+ exports.listOne = listOne;
5
+ exports.fetchCommandList = fetchCommandList;
6
+ /**
7
+ * The session's command and skill list, as the service decides it (manual 6.4).
8
+ *
9
+ * The service names each file or folder it needs from the project and this
10
+ * reads it, inside the project and through the executor's own checks, so a
11
+ * secrets file is never sent and nothing outside the project is read. It asks
12
+ * again with what it was given until it has what it needs. Every choice about
13
+ * what counts as a skill, what it is called and what its line says is made on
14
+ * the service.
15
+ */
16
+ const files_1 = require("../executor/files");
17
+ const gateway_1 = require("./gateway");
18
+ const ROUNDS = 6;
19
+ const MAX_CHARS = 8000;
20
+ function readOne(root, rel) {
21
+ const r = (0, files_1.performFileAction)(root, 'read_file', { path: rel });
22
+ return r.isError ? null : r.content.slice(0, MAX_CHARS);
23
+ }
24
+ function listOne(root, rel) {
25
+ const r = (0, files_1.performFileAction)(root, 'list_dir', { path: rel });
26
+ if (r.isError)
27
+ return null;
28
+ return r.content === '(empty)' ? [] : r.content.split('\n').filter(Boolean);
29
+ }
30
+ /** Ask the service for this project's list. `words` are the session words
31
+ * this tool runs. */
32
+ async function fetchCommandList(root, words) {
33
+ const files = {};
34
+ const listings = {};
35
+ for (let round = 0; round < ROUNDS; round += 1) {
36
+ const reply = await (0, gateway_1.ask)({
37
+ intent: 'commands', prompt: '',
38
+ commandsPayload: { words, round, files, listings, project: root },
39
+ });
40
+ const body = reply.commands;
41
+ if (!reply.ok || reply.served === false || !body) {
42
+ return { ok: false, items: [], needsUpdate: [], keepHistory: false };
43
+ }
44
+ if (!body.wants) {
45
+ return {
46
+ ok: true,
47
+ items: body.items ?? [],
48
+ needsUpdate: body.needs_update ?? [],
49
+ keepHistory: body.keep_history === true,
50
+ };
51
+ }
52
+ for (const p of body.wants.read ?? [])
53
+ files[p] = readOne(root, p);
54
+ for (const f of body.wants.list ?? [])
55
+ listings[f] = listOne(root, f);
56
+ }
57
+ return { ok: false, items: [], needsUpdate: [], keepHistory: false };
58
+ }
@@ -0,0 +1,22 @@
1
+ import type { WithheldReason } from './types';
2
+ export interface LocalVerdict {
3
+ recommendation: string | null;
4
+ withheld: WithheldReason | null;
5
+ withheldDetail?: string;
6
+ breakdown: Record<string, never>;
7
+ tableHashes: Record<string, never>;
8
+ baselineVersion: string | null;
9
+ policyVersion: string;
10
+ estimatorVersion: string;
11
+ /** True only on a close corpus match. Every surface must flag the rest. */
12
+ confident: boolean;
13
+ /** Which rung of the lookup answered, carried for provenance. */
14
+ rung: string | null;
15
+ }
16
+ export declare const POLICY_VERSION = "served-v1";
17
+ export declare function decide(input: {
18
+ prompt: string;
19
+ callingModel?: string;
20
+ contextTokens?: number;
21
+ freshContext?: boolean;
22
+ }): Promise<LocalVerdict>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POLICY_VERSION = void 0;
4
+ exports.decide = decide;
5
+ /**
6
+ * The decision, as this tool now gets it: by asking.
7
+ *
8
+ * ── What this replaced ────────────────────────────────────────────────────
9
+ *
10
+ * `estimator.ts` computed the decision here, on the customer's machine, from
11
+ * tables and curves that are the product's whole method. That file stays in the
12
+ * repository because our own tests read it, and it is excluded from every
13
+ * published package, because a package on a public registry is readable forever
14
+ * by anyone who installs it.
15
+ *
16
+ * So the interception paths call this instead. It asks our server and returns
17
+ * the SAME SHAPE the call sites already expect, which is why swapping it in
18
+ * changed one import line in each of them rather than rewriting three files.
19
+ *
20
+ * The maths-bearing fields come back empty on purpose. A breakdown per candidate
21
+ * and the hashes of the tables consulted are the reasoning, and the reasoning
22
+ * stays on the server where it is written into the server's own decision record.
23
+ * The client records WHAT was decided; it no longer records how.
24
+ */
25
+ const gateway_1 = require("./gateway");
26
+ exports.POLICY_VERSION = 'served-v1';
27
+ async function decide(input) {
28
+ const reply = await (0, gateway_1.ask)({
29
+ intent: 'decide',
30
+ prompt: input.prompt,
31
+ ...(input.callingModel ? { callingModel: input.callingModel } : {}),
32
+ contextTokens: input.contextTokens ?? 0,
33
+ freshContext: input.freshContext ?? true,
34
+ });
35
+ const d = reply.decision;
36
+ return {
37
+ recommendation: d.model,
38
+ // A server that could not decide is not an error here. Rule 10: the caller
39
+ // runs what it was going to run, and the reason travels with the record.
40
+ withheld: d.model ? null : 'no_decision',
41
+ ...(d.rationale ? { withheldDetail: d.rationale } : {}),
42
+ breakdown: {},
43
+ tableHashes: {},
44
+ baselineVersion: null,
45
+ policyVersion: exports.POLICY_VERSION,
46
+ estimatorVersion: exports.POLICY_VERSION,
47
+ confident: d.confident,
48
+ rung: d.rung,
49
+ };
50
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Normalization before hashing: lowercase, collapse runs of whitespace, trim.
3
+ *
4
+ * Deliberately mild. Aggressive normalization (stripping punctuation, stemming)
5
+ * would collapse genuinely different requests onto one fingerprint, and a
6
+ * fingerprint collision is a chain-linking error that is invisible afterwards.
7
+ */
8
+ export declare function normalisePrompt(prompt: string): string;
9
+ export declare function fingerprint(prompt: string): string;
10
+ /**
11
+ * A short, stable id. `crypto.randomUUID` would do, but ids appear in every
12
+ * report and a 12-character id is legible in a terminal where a UUID is noise.
13
+ */
14
+ export declare function newId(prefix: string): string;
15
+ /**
16
+ * Context size in tokens, approximated from characters.
17
+ *
18
+ * Four characters per token is the industry's usual rule of thumb and it is an
19
+ * approximation, not a measurement - which is why every figure derived from it
20
+ * carries `derivation: 'inferred'` with this method named. The provider's own
21
+ * reported token counts are used wherever they exist; this is only for the
22
+ * context estimate that has to exist *before* the call is made.
23
+ */
24
+ export declare const APPROX_CHARS_PER_TOKEN = 4;
25
+ export declare function approxTokens(text: string): number;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APPROX_CHARS_PER_TOKEN = void 0;
4
+ exports.normalisePrompt = normalisePrompt;
5
+ exports.fingerprint = fingerprint;
6
+ exports.newId = newId;
7
+ exports.approxTokens = approxTokens;
8
+ /**
9
+ * Fingerprinting: how a request is identified without being kept.
10
+ *
11
+ * Every record refers to a request by the sha256 of its normalized text. The
12
+ * text itself is never written anywhere. Two consequences the rest of the
13
+ * system depends on:
14
+ *
15
+ * - A verbatim retry produces the identical fingerprint, which is how Phase 2
16
+ * recognizes two calls as attempts at the same unit of work without ever
17
+ * comparing the words.
18
+ * - Nothing can be reversed out of the record. A fingerprint identifies a
19
+ * request to someone who already has it, and is inert to everyone else.
20
+ */
21
+ const node_crypto_1 = require("node:crypto");
22
+ /**
23
+ * Normalization before hashing: lowercase, collapse runs of whitespace, trim.
24
+ *
25
+ * Deliberately mild. Aggressive normalization (stripping punctuation, stemming)
26
+ * would collapse genuinely different requests onto one fingerprint, and a
27
+ * fingerprint collision is a chain-linking error that is invisible afterwards.
28
+ */
29
+ function normalisePrompt(prompt) {
30
+ return prompt.replace(/\s+/g, ' ').trim().toLowerCase();
31
+ }
32
+ function fingerprint(prompt) {
33
+ return (0, node_crypto_1.createHash)('sha256').update(normalisePrompt(prompt), 'utf8').digest('hex');
34
+ }
35
+ /**
36
+ * A short, stable id. `crypto.randomUUID` would do, but ids appear in every
37
+ * report and a 12-character id is legible in a terminal where a UUID is noise.
38
+ */
39
+ function newId(prefix) {
40
+ const rand = (0, node_crypto_1.createHash)('sha256')
41
+ .update(`${process.pid}:${Date.now()}:${Math.random()}`)
42
+ .digest('hex')
43
+ .slice(0, 12);
44
+ return `${prefix}_${rand}`;
45
+ }
46
+ /**
47
+ * Context size in tokens, approximated from characters.
48
+ *
49
+ * Four characters per token is the industry's usual rule of thumb and it is an
50
+ * approximation, not a measurement - which is why every figure derived from it
51
+ * carries `derivation: 'inferred'` with this method named. The provider's own
52
+ * reported token counts are used wherever they exist; this is only for the
53
+ * context estimate that has to exist *before* the call is made.
54
+ */
55
+ exports.APPROX_CHARS_PER_TOKEN = 4;
56
+ function approxTokens(text) {
57
+ return Math.ceil(text.length / exports.APPROX_CHARS_PER_TOKEN);
58
+ }
@@ -33,8 +33,36 @@ export interface CatalogProvider {
33
33
  permitted: boolean;
34
34
  models: CatalogModel[];
35
35
  }
36
+ /**
37
+ * One session's cost, as the server worked it out.
38
+ *
39
+ * Deliberately loose below the top level: the fields are named by the service
40
+ * and a client that re-declared every one of them would need a release every
41
+ * time a cost factor was added. What matters is that nothing here is computed
42
+ * on this side, so a new field arrives and prints without a new version.
43
+ */
44
+ export interface SpendReport {
45
+ session: Record<string, unknown> | null;
46
+ /** The per-session fold: the five cost factors and what they add to. */
47
+ cost_factors: Record<string, unknown>;
48
+ /** Every provider call, with its tokens, its price and the decision behind it. */
49
+ calls: Record<string, unknown>[];
50
+ /**
51
+ * What a client should DRAW: headings, labels, ordered rows, formatted values.
52
+ *
53
+ * Sent by the server so that adding a cost factor, renaming one or changing
54
+ * how a figure reads is a server change and never obliges a customer to
55
+ * install a new version of the tool. Loosely typed on purpose, for the same
56
+ * reason: a field added there must arrive here without a release.
57
+ */
58
+ presentation?: Record<string, unknown>;
59
+ decisions: Record<string, unknown>[];
60
+ units_of_work: Record<string, unknown>[];
61
+ }
36
62
  export interface GatewayReply {
37
- intent: 'decide' | 'answer' | 'catalog' | 'store_key' | 'session' | 'job_start' | 'resolve_workspace' | 'sign_in' | 'sign_out' | 'attach' | 'transcribe' | 'graph';
63
+ /** Local transport metadata, not an authentication decision. */
64
+ httpStatus?: number;
65
+ intent: 'decide' | 'answer' | 'catalog' | 'store_key' | 'session' | 'job_start' | 'resolve_workspace' | 'sign_in' | 'sign_out' | 'attach' | 'transcribe' | 'graph' | 'spend' | 'project' | 'commands' | 'transcript' | 'context' | 'compact' | 'sessions' | 'permissions' | 'jobs' | 'agents' | 'integrations' | 'skills' | 'plugins' | 'schema' | 'usage' | 'instructions' | 'config' | 'memory' | 'worktrees' | 'privacy' | 'hooks' | 'queue' | 'review' | 'release';
38
66
  client: string;
39
67
  decision: Decision;
40
68
  answer: string | null;
@@ -89,7 +117,109 @@ export interface GatewayReply {
89
117
  /** Present only for a graph request: what the map holds, what it still needs,
90
118
  * or the answer to one of the five questions. */
91
119
  graph?: Record<string, unknown>;
120
+ /**
121
+ * One session's cost, worked out term by term. Present only for a spend
122
+ * request.
123
+ *
124
+ * Shaped by the server and rendered here, never recomputed here: the whole
125
+ * point of asking the service is that the terminal and the portal quote one
126
+ * set of figures. A client that added anything up itself would be a second
127
+ * source of truth for the one number this product sells.
128
+ */
129
+ spend?: SpendReport | null;
130
+ /** The first line of a session, as the service wrote it (manual 2.2). */
131
+ project_line?: string | null;
132
+ /** The lines a project operation prints, written by the service. */
133
+ lines?: string[] | null;
134
+ data?: Record<string, unknown> | null;
135
+ /** Present only for a context or compact request: lines to print (6.13, 6.15). */
136
+ context?: {
137
+ lines: string[];
138
+ ok?: boolean;
139
+ } | null;
140
+ /** Present only for a transcript request: lines to print as sent (6.9). */
141
+ transcript?: {
142
+ job_id: string;
143
+ lines: string[];
144
+ complete: boolean;
145
+ } | null;
146
+ /** Present only for a jobs request (manual 5.14): lines to print as sent. */
147
+ jobs?: {
148
+ lines: string[];
149
+ job_id?: string;
150
+ running?: boolean;
151
+ } | null;
152
+ /** The result object a script reads (manual 10.3), built by the service. */
153
+ result?: Record<string, unknown> | null;
154
+ /** One reason word for a refusal, turned into a number by `exit_status`. */
155
+ reason?: string | null;
156
+ /** Reason word to exit number (Appendix B), as the service sends it. */
157
+ exit_status?: Record<string, number> | null;
158
+ /** Present only for a usage request: the figures and the lines to print. */
159
+ usage?: Record<string, unknown> | null;
160
+ /** Present only for a schema request: the published shape and the table. */
161
+ schema_doc?: Record<string, unknown> | null;
162
+ /** Present only for an agents request (manual 5.24): lines to print as sent. */
163
+ agents?: {
164
+ lines: string[];
165
+ helpers?: Array<Record<string, unknown>>;
166
+ wants?: {
167
+ read?: string[];
168
+ list?: string[];
169
+ } | null;
170
+ } | null;
171
+ /** Present only for a commands request (manual 6.4, 6.5). `wants` names
172
+ * the project files to send on the next round. */
173
+ commands?: {
174
+ items?: {
175
+ name: string;
176
+ description: string;
177
+ source: string;
178
+ }[];
179
+ needs_update?: string[];
180
+ keep_history?: boolean;
181
+ wants?: {
182
+ read?: string[];
183
+ list?: string[];
184
+ } | null;
185
+ } | null;
92
186
  /** Who this machine is signed in as. Present only for a session request. */
187
+ /** For sessions: `lines` to print as they are, and the records behind
188
+ * them (`sessions` for a list, the one session's fields for a resume). */
189
+ sessions?: SessionsReply | null;
190
+ /** For permissions: `lines` to print as they are (manual 4.14). */
191
+ permissions?: {
192
+ lines?: string[];
193
+ verdict?: string;
194
+ } | null;
195
+ /** For integrations: `lines` to print as they are (web manual 9.5). */
196
+ integrations?: Record<string, unknown> & {
197
+ lines?: string[];
198
+ connections?: unknown[];
199
+ } | null;
200
+ /** `comprism plugins` (manual 9.15 to 9.17), in the service's words. */
201
+ plugins?: Record<string, unknown> & {
202
+ lines?: string[];
203
+ line?: string;
204
+ error?: string;
205
+ } | null;
206
+ /** `comprism skills` (manual 9.1 to 9.4): rows to draw, or what was saved. */
207
+ skills?: {
208
+ rows?: Array<{
209
+ name: string;
210
+ description: string;
211
+ where: string;
212
+ active: boolean;
213
+ note?: string | null;
214
+ }>;
215
+ empty?: string | null;
216
+ line?: string;
217
+ error?: string;
218
+ wants?: {
219
+ read?: string[];
220
+ list?: string[];
221
+ } | null;
222
+ } | null;
93
223
  session?: SessionOut | null;
94
224
  /** Permission for one coding job. Present only for a job_start request. */
95
225
  job?: JobOut | null;
@@ -97,6 +227,9 @@ export interface GatewayReply {
97
227
  sign_in?: SignInOut | null;
98
228
  /** Present only for an attach or transcribe request. */
99
229
  upload?: UploadOut | null;
230
+ /** For attach: why the file will not be read, from the service's fixed
231
+ * list. The sentence is in `message`. */
232
+ refusal?: string | null;
100
233
  /**
101
234
  * True when the request was understood, allowed and acted on.
102
235
  *
@@ -119,6 +252,21 @@ export interface GatewayReply {
119
252
  ok: boolean;
120
253
  }
121
254
  /** Permission to send one file, and the address to send it to. */
255
+ /** One session as the service describes it (CLI manual 8.4, 8.5). */
256
+ export interface SessionRecord {
257
+ session_id: string;
258
+ number: string;
259
+ title: string;
260
+ last_job_id?: string | null;
261
+ continues_job?: string | null;
262
+ conversation_kept?: boolean;
263
+ cost_usd: number;
264
+ jobs: number;
265
+ }
266
+ export interface SessionsReply extends Partial<SessionRecord> {
267
+ lines: string[];
268
+ sessions?: SessionRecord[];
269
+ }
122
270
  export interface UploadOut {
123
271
  /** Presented at the upload address in place of the workspace credential. */
124
272
  ticket: string;
@@ -190,7 +338,7 @@ export interface SessionOut {
190
338
  can_write: boolean;
191
339
  }
192
340
  export interface Ask {
193
- intent: 'decide' | 'answer' | 'catalog' | 'store_key' | 'session' | 'job_start' | 'resolve_workspace' | 'sign_in' | 'sign_out' | 'attach' | 'transcribe' | 'graph';
341
+ intent: 'decide' | 'answer' | 'catalog' | 'store_key' | 'session' | 'job_start' | 'resolve_workspace' | 'sign_in' | 'sign_out' | 'attach' | 'transcribe' | 'graph' | 'spend' | 'project' | 'commands' | 'transcript' | 'context' | 'compact' | 'sessions' | 'permissions' | 'jobs' | 'agents' | 'integrations' | 'skills' | 'plugins' | 'schema' | 'usage' | 'instructions' | 'config' | 'memory' | 'worktrees' | 'privacy' | 'hooks' | 'queue' | 'review' | 'release';
194
342
  prompt: string;
195
343
  callingModel?: string;
196
344
  contextTokens?: number;
@@ -207,11 +355,37 @@ export interface Ask {
207
355
  resumeJobId?: string;
208
356
  /** For sign_in only. Sent once, over TLS, and written nowhere. */
209
357
  password?: string;
358
+ /** Company login (manual 1.10): the token a browser approval issued. */
359
+ deviceToken?: string;
210
360
  /** For sign_in, when a second factor is required. */
211
361
  challenge?: string;
212
362
  code?: string;
213
363
  /** For sign_in: how this machine appears where a person goes to revoke it. */
214
364
  machineLabel?: string;
365
+ /** Which session to break down. Omitted means the newest one recorded. */
366
+ spendSession?: string;
367
+ /** For project: the folder facts this machine observed (manual 2.1 to 2.3). */
368
+ project?: Record<string, unknown>;
369
+ /** For instructions, config, memory and worktrees (manual 2.5 to 2.11). */
370
+ op?: string;
371
+ payload?: Record<string, unknown>;
372
+ /** For commands: the session words this tool runs, and the project files
373
+ * the service asked for on the previous round. */
374
+ commandsPayload?: Record<string, unknown>;
375
+ sessionsPayload?: Record<string, unknown>;
376
+ /** For jobs: op (list, find, stop) and job (manual 5.14). */
377
+ jobsPayload?: Record<string, unknown>;
378
+ /** For agents: the helper definition files this machine read (manual 5.24). */
379
+ agentsPayload?: Record<string, unknown>;
380
+ /** For usage: this machine's offset from UTC in minutes (manual 10.12). */
381
+ tzMinutes?: number;
382
+ skillsPayload?: Record<string, unknown>;
383
+ integrationsPayload?: Record<string, unknown>;
384
+ pluginsPayload?: Record<string, unknown>;
385
+ /** For permissions: op, action, target and the settings files (manual 4.14). */
386
+ permissionsPayload?: Record<string, unknown>;
387
+ /** For transcript: the job whose record to print (manual 6.9). */
388
+ jobId?: string;
215
389
  /** For graph: which of the map's operations this is. */
216
390
  graphOp?: 'status' | 'stamps' | 'update' | 'finish' | 'ask' | 'forget' | 'policy';
217
391
  /** For graph: the project on this machine the map belongs to. */
@@ -226,6 +400,14 @@ export interface Ask {
226
400
  * that could carry it.
227
401
  */
228
402
  graphPayload?: Record<string, unknown>;
403
+ /** For attach: the file's name and size, asked about before its bytes are
404
+ * sent. Never its contents (manual 3.10). */
405
+ attachment?: {
406
+ filename: string;
407
+ size_bytes: number;
408
+ };
409
+ /** For answer: ids of files already attached, read before the question. */
410
+ attachments?: string[];
229
411
  }
230
412
  /**
231
413
  * The same door, at an address this machine is not signed in to yet.
@@ -237,3 +419,5 @@ export interface Ask {
237
419
  */
238
420
  export declare function askAt(url: string, input: Ask, tenant?: string): Promise<GatewayReply>;
239
421
  export declare function ask(input: Ask): Promise<GatewayReply>;
422
+ /** A sentence for a certificate failure, or null when the failure was not one. */
423
+ export declare function certificateProblem(e: unknown): string | null;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.askAt = askAt;
4
4
  exports.ask = ask;
5
+ exports.certificateProblem = certificateProblem;
5
6
  /**
6
7
  * The one door, from this side of it.
7
8
  *
@@ -21,6 +22,7 @@ exports.ask = ask;
21
22
  * the shape of the reply, which is not a secret and cannot be one.
22
23
  */
23
24
  const connection_1 = require("./connection");
25
+ const version_1 = require("./version");
24
26
  /**
25
27
  * A decision that decides nothing, for when the door cannot be reached.
26
28
  *
@@ -105,6 +107,9 @@ async function post(url, input, credential, tenant) {
105
107
  headers.authorization = `Bearer ${conn.workspaceKey}`;
106
108
  if (conn.tenant)
107
109
  headers['X-Tenant-ID'] = conn.tenant;
110
+ // Which tool is asking, so the service can say when a feature needs a newer
111
+ // one (manual 1.7). It decides; this only says what it is.
112
+ headers['X-Comprism-Version'] = version_1.COMPRISM_VERSION;
108
113
  // Forwarded, never written to disk: the customer's money stays the
109
114
  // customer's, and we hold no secret of theirs on their behalf.
110
115
  if (input.providerKey)
@@ -113,6 +118,11 @@ async function post(url, input, credential, tenant) {
113
118
  const res = await fetch(`${conn.url.replace(/\/+$/, '')}/api/v1/dev/gateway`, {
114
119
  method: 'POST',
115
120
  headers,
121
+ // Never waits forever. A service that restarts mid request, or a network
122
+ // that goes quiet, answers as unreachable (Appendix B, 6) rather than
123
+ // leaving the terminal hanging. Longer for a question a model answers.
124
+ signal: AbortSignal.timeout(['answer', 'decide', 'compact', 'transcribe'].includes(input.intent)
125
+ ? 300_000 : 120_000),
116
126
  body: JSON.stringify({
117
127
  intent: input.intent,
118
128
  client: 'cli',
@@ -128,10 +138,27 @@ async function post(url, input, credential, tenant) {
128
138
  ...(input.password ? { password: input.password } : {}),
129
139
  ...(input.challenge ? { challenge: input.challenge } : {}),
130
140
  ...(input.code ? { code: input.code } : {}),
141
+ ...(input.deviceToken ? { device_token: input.deviceToken } : {}),
131
142
  ...(input.machineLabel ? { machine_label: input.machineLabel } : {}),
143
+ ...(input.spendSession ? { spend_session: input.spendSession } : {}),
144
+ ...(input.project ? { project: input.project } : {}),
145
+ ...(input.op ? { op: input.op } : {}),
146
+ ...(input.payload ? { payload: input.payload } : {}),
147
+ ...(input.commandsPayload ? { commands_payload: input.commandsPayload } : {}),
148
+ ...(input.sessionsPayload ? { sessions_payload: input.sessionsPayload } : {}),
149
+ ...(input.jobsPayload ? { jobs_payload: input.jobsPayload } : {}),
150
+ ...(input.agentsPayload ? { agents_payload: input.agentsPayload } : {}),
151
+ ...(input.tzMinutes !== undefined ? { tz_minutes: input.tzMinutes } : {}),
152
+ ...(input.skillsPayload ? { skills_payload: input.skillsPayload } : {}),
153
+ ...(input.integrationsPayload ? { integrations_payload: input.integrationsPayload } : {}),
154
+ ...(input.pluginsPayload ? { plugins_payload: input.pluginsPayload } : {}),
155
+ ...(input.permissionsPayload ? { permissions_payload: input.permissionsPayload } : {}),
156
+ ...(input.jobId ? { job_id: input.jobId } : {}),
132
157
  ...(input.graphOp ? { graph_op: input.graphOp } : {}),
133
158
  ...(input.workspace ? { workspace: input.workspace } : {}),
134
159
  ...(input.graphPayload ? { graph_payload: input.graphPayload } : {}),
160
+ ...(input.attachment ? { attachment: input.attachment } : {}),
161
+ ...(input.attachments?.length ? { attachments: input.attachments } : {}),
135
162
  }),
136
163
  });
137
164
  if (!res.ok) {
@@ -156,16 +183,44 @@ async function post(url, input, credential, tenant) {
156
183
  // credential path was alive and would have worked. Newer builds answer
157
184
  // 200 with `served: false` and never reach this line.
158
185
  if (res.status === 422)
159
- return { ...failed, served: false };
160
- return failed;
186
+ return { ...failed, served: false, httpStatus: res.status };
187
+ return { ...failed, httpStatus: res.status };
161
188
  }
162
189
  // `ok` is this client's own marker and the server never sends it: it
163
190
  // records that the workspace answered at all. Set here, on the one path
164
191
  // where that is true, so no caller has to infer it from an empty field.
165
192
  const reply = (await res.json());
193
+ if (reply.reason === 'upgrade') {
194
+ // Said before any work starts or anything is charged, and the number is
195
+ // Appendix B's 10 (manual 1.7).
196
+ process.stderr.write(` ${String(reply.message || 'This needs a newer comprism. Run comprism update.').split('\n').join('\n ')}\n`);
197
+ process.exit(10);
198
+ }
166
199
  return { ...reply, ok: true };
167
200
  }
168
- catch {
169
- return openFailure(input.intent, input.callingModel, 'the service could not be reached');
201
+ catch (e) {
202
+ // A certificate problem is said as one, with the setting to change, never
203
+ // as "could not be reached" (manual 1.11).
204
+ return openFailure(input.intent, input.callingModel, certificateProblem(e) ?? 'the service could not be reached');
205
+ }
206
+ }
207
+ /** The certificate codes Node reports when a company's own authority is not
208
+ * trusted, or a certificate is wrong (manual 1.11). */
209
+ const CERT_CODES = new Set(["UNABLE_TO_VERIFY_LEAF_SIGNATURE", "SELF_SIGNED_CERT_IN_CHAIN",
210
+ "DEPTH_ZERO_SELF_SIGNED_CERT", "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
211
+ "CERT_HAS_EXPIRED", "ERR_TLS_CERT_ALTNAME_INVALID", "CERT_UNTRUSTED"]);
212
+ /** A sentence for a certificate failure, or null when the failure was not one. */
213
+ function certificateProblem(e) {
214
+ let cause = e;
215
+ for (let i = 0; i < 4 && cause; i += 1) {
216
+ const code = cause.code;
217
+ if (code && CERT_CODES.has(code)) {
218
+ const file = process.env.NODE_EXTRA_CA_CERTS;
219
+ return `a certificate problem (${code}): the service's certificate is not trusted on this machine. `
220
+ + (file ? `NODE_EXTRA_CA_CERTS is ${file}; check it holds your company's certificate authority.`
221
+ : "If your company uses its own certificate authority, set NODE_EXTRA_CA_CERTS to its certificate file.");
222
+ }
223
+ cause = cause.cause;
170
224
  }
225
+ return null;
171
226
  }
@@ -0,0 +1,24 @@
1
+ export declare function historyFile(): string;
2
+ export declare class History {
3
+ /** Newest first, as shown on the prompt line. Shared with the reader, which
4
+ * walks it with Up and Down. */
5
+ readonly entries: string[];
6
+ /** Newest first, as sent. What the file holds. */
7
+ private saved;
8
+ /** The company's rule: true keeps, false removes, null (the service could
9
+ * not be asked) keeps this session's requests in memory and leaves any
10
+ * file exactly as it is. */
11
+ private keep;
12
+ /** Begin a session under the company's rule. */
13
+ start(keep: boolean | null): void;
14
+ private off;
15
+ /** `history off`: nothing is remembered, in memory or on disk. */
16
+ disable(): void;
17
+ /** Remember one request. `shown` is how it looked at the prompt, paste
18
+ * labels included; `sent` is what was sent. */
19
+ add(shown: string, sent: string): void;
20
+ /** End the session. Without permission to keep, nothing is left behind. */
21
+ finish(): void;
22
+ private write;
23
+ private remove;
24
+ }