polygram 0.22.0 → 0.23.1

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 (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -3
  3. package/config.example.json +23 -1
  4. package/lib/codex/binary.js +287 -0
  5. package/lib/codex/delivery-finalizer.js +97 -0
  6. package/lib/codex/diagnostics.js +483 -0
  7. package/lib/codex/event-normalizer.js +50 -0
  8. package/lib/codex/host-identity.js +103 -0
  9. package/lib/codex/model-catalog.js +359 -0
  10. package/lib/codex/retention-scheduler.js +59 -0
  11. package/lib/codex/runtime-controller.js +1292 -0
  12. package/lib/codex/runtime-profile.js +888 -0
  13. package/lib/codex/spawn-context.js +87 -0
  14. package/lib/db/auto-resume.js +77 -9
  15. package/lib/db/codex-reconciliation.js +490 -0
  16. package/lib/db/codex-retention.js +139 -0
  17. package/lib/db/sessions.js +102 -1
  18. package/lib/db.js +3418 -19
  19. package/lib/error/classify.js +140 -0
  20. package/lib/handlers/abort.js +143 -5
  21. package/lib/handlers/autosteer.js +256 -1
  22. package/lib/handlers/codex-reconciliation.js +230 -0
  23. package/lib/handlers/config-callback.js +324 -89
  24. package/lib/handlers/config-ui.js +401 -39
  25. package/lib/handlers/dispatcher.js +71 -8
  26. package/lib/handlers/edit-correction.js +11 -1
  27. package/lib/handlers/edit-redelivery.js +230 -4
  28. package/lib/handlers/gate-inbound.js +7 -0
  29. package/lib/handlers/replay-disposition.js +133 -11
  30. package/lib/handlers/slash-commands.js +443 -37
  31. package/lib/ipc/client.js +22 -12
  32. package/lib/ipc/runtime-directory.js +208 -0
  33. package/lib/ipc/server.js +18 -12
  34. package/lib/model-costs.js +4 -3
  35. package/lib/pm-interface.js +33 -0
  36. package/lib/prompt.js +15 -9
  37. package/lib/rewind/rewind.js +21 -1
  38. package/lib/runtime-config.js +605 -0
  39. package/lib/sdk/build-options.js +4 -1
  40. package/lib/sdk/callbacks.js +46 -3
  41. package/migrations/015-agent-runtime-sessions.sql +50 -0
  42. package/migrations/016-agent-turn-attempts.sql +211 -0
  43. package/migrations/017-config-runtime-changes.sql +46 -0
  44. package/ops/README.md +57 -0
  45. package/package.json +2 -2
  46. package/polygram.js +697 -48
  47. package/scripts/doctor.js +37 -8
@@ -0,0 +1,359 @@
1
+ 'use strict';
2
+
3
+ const { createHash } = require('node:crypto');
4
+
5
+ const MAX_ID_BYTES = 512;
6
+ const MAX_MODELS = 1_000;
7
+ const MAX_EFFORTS = 32;
8
+ const SHA256_RE = /^[a-f0-9]{64}$/;
9
+ const PREFLIGHT_ACTIONS = Object.freeze({
10
+ CODEX_AUTH_UNAVAILABLE:
11
+ 'Authenticate the pinned CLI in the dedicated deployment CODEX_HOME, then rerun preflight.',
12
+ CODEX_MODEL_UNAVAILABLE:
13
+ 'Choose a model returned by authenticated Codex model preflight.',
14
+ CODEX_EFFORT_UNAVAILABLE:
15
+ 'Choose a reasoning effort supported by the selected Codex model.',
16
+ CODEX_BINARY_MISMATCH:
17
+ 'Restore the reviewed pinned Codex executable, then rerun preflight.',
18
+ CODEX_CONFIG_MISMATCH:
19
+ 'Restore the owned Codex configuration and credential-home permissions, then rerun preflight.',
20
+ });
21
+
22
+ class CodexCatalogError extends Error {
23
+ constructor(message, code, action, options) {
24
+ super(message, options);
25
+ this.name = 'CodexCatalogError';
26
+ this.code = code;
27
+ this.action = action;
28
+ }
29
+ }
30
+
31
+ function deepFreeze(value) {
32
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) {
33
+ return value;
34
+ }
35
+ for (const child of Object.values(value)) deepFreeze(child);
36
+ return Object.freeze(value);
37
+ }
38
+
39
+ function canonical(value) {
40
+ if (Array.isArray(value)) return value.map(canonical);
41
+ if (value && typeof value === 'object') {
42
+ return Object.fromEntries(
43
+ Object.keys(value).sort().map((key) => [key, canonical(value[key])]),
44
+ );
45
+ }
46
+ return value;
47
+ }
48
+
49
+ function digest(value) {
50
+ return createHash('sha256')
51
+ .update(JSON.stringify(canonical(value)))
52
+ .digest('hex');
53
+ }
54
+
55
+ function boundedString(value, label, maxBytes = MAX_ID_BYTES) {
56
+ if (
57
+ typeof value !== 'string'
58
+ || value.length === 0
59
+ || Buffer.byteLength(value) > maxBytes
60
+ ) {
61
+ throw new CodexCatalogError(
62
+ `Codex ${label} must be a bounded non-empty string.`,
63
+ label === 'authenticated deployment identity'
64
+ ? 'CODEX_DEPLOYMENT_IDENTITY_INVALID'
65
+ : 'CODEX_PREFLIGHT_INVALID',
66
+ 'Rerun Codex preflight with the exact configured runtime profile.',
67
+ );
68
+ }
69
+ return value;
70
+ }
71
+
72
+ function deploymentKey(identity) {
73
+ return digest({
74
+ authenticatedDeploymentIdentity: boundedString(
75
+ identity,
76
+ 'authenticated deployment identity',
77
+ ),
78
+ });
79
+ }
80
+
81
+ function actionablePreflightError(error) {
82
+ if (error?.action || !PREFLIGHT_ACTIONS[error?.code]) return error;
83
+ return new CodexCatalogError(
84
+ error.message,
85
+ error.code,
86
+ PREFLIGHT_ACTIONS[error.code],
87
+ { cause: error },
88
+ );
89
+ }
90
+
91
+ function cloneModel(entry) {
92
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
93
+ throw new CodexCatalogError(
94
+ 'Codex preflight returned a malformed model catalog.',
95
+ 'CODEX_PREFLIGHT_INVALID',
96
+ 'Rerun authenticated Codex model preflight.',
97
+ );
98
+ }
99
+ const supportedReasoningEfforts = entry.supportedReasoningEfforts;
100
+ if (
101
+ !Array.isArray(supportedReasoningEfforts)
102
+ || supportedReasoningEfforts.length === 0
103
+ || supportedReasoningEfforts.length > MAX_EFFORTS
104
+ ) {
105
+ throw new CodexCatalogError(
106
+ 'Codex preflight returned malformed reasoning efforts.',
107
+ 'CODEX_PREFLIGHT_INVALID',
108
+ 'Rerun authenticated Codex model preflight.',
109
+ );
110
+ }
111
+ const efforts = supportedReasoningEfforts.map((effort) => (
112
+ boundedString(effort, 'reasoning effort')
113
+ ));
114
+ if (new Set(efforts).size !== efforts.length) {
115
+ throw new CodexCatalogError(
116
+ 'Codex preflight returned duplicate reasoning efforts.',
117
+ 'CODEX_PREFLIGHT_INVALID',
118
+ 'Rerun authenticated Codex model preflight.',
119
+ );
120
+ }
121
+ if (
122
+ typeof entry.isDefault !== 'boolean'
123
+ || !efforts.includes(entry.defaultReasoningEffort)
124
+ ) {
125
+ throw new CodexCatalogError(
126
+ 'Codex preflight returned an inconsistent default effort.',
127
+ 'CODEX_PREFLIGHT_INVALID',
128
+ 'Rerun authenticated Codex model preflight.',
129
+ );
130
+ }
131
+ return {
132
+ id: boundedString(entry.id, 'model ID'),
133
+ model: boundedString(entry.model, 'model name'),
134
+ displayName: boundedString(entry.displayName, 'model display name'),
135
+ defaultReasoningEffort: entry.defaultReasoningEffort,
136
+ supportedReasoningEfforts: efforts,
137
+ isDefault: entry.isDefault,
138
+ };
139
+ }
140
+
141
+ function projectPreflight(result) {
142
+ if (
143
+ !result
144
+ || typeof result !== 'object'
145
+ || Array.isArray(result)
146
+ || result.runtime !== 'codex'
147
+ || !SHA256_RE.test(result.spawnProfileId)
148
+ || result.auth?.authenticated !== true
149
+ || result.auth?.accountType !== 'chatgpt'
150
+ || typeof result.auth?.requiresOpenaiAuth !== 'boolean'
151
+ || !Array.isArray(result.models)
152
+ || result.models.length === 0
153
+ || result.models.length > MAX_MODELS
154
+ || !Array.isArray(result.efforts)
155
+ ) {
156
+ throw new CodexCatalogError(
157
+ 'Codex preflight returned an invalid authenticated catalog.',
158
+ 'CODEX_PREFLIGHT_INVALID',
159
+ 'Rerun authenticated Codex preflight with the reviewed Orchestra runtime.',
160
+ );
161
+ }
162
+
163
+ const models = result.models.map(cloneModel);
164
+ if (
165
+ new Set(models.map(({ id }) => id)).size !== models.length
166
+ || new Set(models.map(({ model }) => model)).size !== models.length
167
+ ) {
168
+ throw new CodexCatalogError(
169
+ 'Codex preflight returned duplicate model identities.',
170
+ 'CODEX_PREFLIGHT_INVALID',
171
+ 'Rerun authenticated Codex model preflight.',
172
+ );
173
+ }
174
+ const selectedModel = boundedString(result.selected?.model, 'selected model');
175
+ const selectedEffort = boundedString(
176
+ result.selected?.effort,
177
+ 'selected reasoning effort',
178
+ );
179
+ const selected = models.filter(({ model }) => model === selectedModel);
180
+ if (
181
+ selected.length !== 1
182
+ || !selected[0].supportedReasoningEfforts.includes(selectedEffort)
183
+ || JSON.stringify(result.efforts)
184
+ !== JSON.stringify(selected[0].supportedReasoningEfforts)
185
+ ) {
186
+ throw new CodexCatalogError(
187
+ 'Codex preflight selected model or effort is inconsistent.',
188
+ 'CODEX_PREFLIGHT_INVALID',
189
+ 'Select a model and effort returned by authenticated model preflight.',
190
+ );
191
+ }
192
+
193
+ return {
194
+ runtimeVersion: boundedString(result.runtimeVersion, 'runtime version'),
195
+ schemaVersion: boundedString(result.schemaVersion, 'schema version'),
196
+ spawnProfileId: result.spawnProfileId,
197
+ auth: {
198
+ authenticated: true,
199
+ accountType: 'chatgpt',
200
+ requiresOpenaiAuth: result.auth.requiresOpenaiAuth,
201
+ },
202
+ models,
203
+ efforts: [...selected[0].supportedReasoningEfforts],
204
+ selected: {
205
+ model: selectedModel,
206
+ effort: selectedEffort,
207
+ },
208
+ };
209
+ }
210
+
211
+ function resolveOrchestra(orchestra) {
212
+ const loaded = orchestra ?? require('@shumkov/orchestra');
213
+ for (const method of [
214
+ 'preflightCodexRuntime',
215
+ 'createCodexSpawnProfile',
216
+ 'assertCodexSpawnProfile',
217
+ ]) {
218
+ if (typeof loaded?.[method] !== 'function') {
219
+ throw new CodexCatalogError(
220
+ `Installed Orchestra does not expose ${method}.`,
221
+ 'CODEX_PREFLIGHT_UNWIRED',
222
+ 'Install the exact reviewed Orchestra Codex runtime before enabling Codex.',
223
+ );
224
+ }
225
+ }
226
+ return loaded;
227
+ }
228
+
229
+ class CodexModelCatalog {
230
+ constructor({ orchestra = null } = {}) {
231
+ this.orchestra = orchestra;
232
+ this.cache = new Map();
233
+ this.currentKeyByDeployment = new Map();
234
+ this.generationByDeployment = new Map();
235
+ }
236
+
237
+ _invalidateKey(key) {
238
+ const current = this.currentKeyByDeployment.get(key);
239
+ if (current) this.cache.delete(current);
240
+ this.currentKeyByDeployment.delete(key);
241
+ }
242
+
243
+ invalidate({ deploymentIdentity } = {}) {
244
+ if (deploymentIdentity === undefined) {
245
+ this.cache.clear();
246
+ this.currentKeyByDeployment.clear();
247
+ this.generationByDeployment.clear();
248
+ return;
249
+ }
250
+ const key = deploymentKey(deploymentIdentity);
251
+ this._invalidateKey(key);
252
+ this.generationByDeployment.set(
253
+ key,
254
+ (this.generationByDeployment.get(key) ?? 0) + 1,
255
+ );
256
+ }
257
+
258
+ getCached({ deploymentIdentity, spawnProfileId } = {}) {
259
+ const key = deploymentKey(deploymentIdentity);
260
+ boundedString(spawnProfileId, 'spawn profile ID');
261
+ if (!SHA256_RE.test(spawnProfileId)) {
262
+ throw new CodexCatalogError(
263
+ 'Codex spawn profile ID must be a lowercase SHA-256.',
264
+ 'CODEX_PREFLIGHT_INVALID',
265
+ 'Use the exact spawn profile returned by authenticated preflight.',
266
+ );
267
+ }
268
+ const cacheKey = digest({ deploymentKey: key, spawnProfileId });
269
+ return this.cache.get(cacheKey) ?? null;
270
+ }
271
+
272
+ /**
273
+ * `deploymentIdentity` is an opaque fingerprint of the provisioned
274
+ * credential deployment, not credential contents. It must change when that
275
+ * deployment is replaced so an authenticated catalog cannot cross stores.
276
+ */
277
+ async preflight(expectedStaticProfile, { deploymentIdentity } = {}) {
278
+ const key = deploymentKey(deploymentIdentity);
279
+ const generation = (this.generationByDeployment.get(key) ?? 0) + 1;
280
+ this.generationByDeployment.set(key, generation);
281
+ this._invalidateKey(key);
282
+
283
+ let orchestra;
284
+ try {
285
+ orchestra = resolveOrchestra(this.orchestra);
286
+ } catch (error) {
287
+ if (this.generationByDeployment.get(key) === generation) {
288
+ this._invalidateKey(key);
289
+ }
290
+ throw error;
291
+ }
292
+
293
+ let result;
294
+ try {
295
+ result = await orchestra.preflightCodexRuntime(expectedStaticProfile);
296
+ } catch (error) {
297
+ if (this.generationByDeployment.get(key) === generation) {
298
+ this._invalidateKey(key);
299
+ }
300
+ throw actionablePreflightError(error);
301
+ }
302
+ if (this.generationByDeployment.get(key) !== generation) {
303
+ throw new CodexCatalogError(
304
+ 'Codex preflight result was superseded by a newer request.',
305
+ 'CODEX_PREFLIGHT_SUPERSEDED',
306
+ 'Use the newest completed Codex preflight result.',
307
+ );
308
+ }
309
+
310
+ try {
311
+ const projection = projectPreflight(result);
312
+ const spawnProfile = orchestra.createCodexSpawnProfile(
313
+ expectedStaticProfile,
314
+ result,
315
+ );
316
+ const asserted = orchestra.assertCodexSpawnProfile(spawnProfile);
317
+ if (
318
+ asserted !== spawnProfile
319
+ || spawnProfile.runtime !== 'codex'
320
+ || spawnProfile.spawnProfileId !== projection.spawnProfileId
321
+ ) {
322
+ throw new CodexCatalogError(
323
+ 'Orchestra returned an inconsistent Codex spawn-profile receipt.',
324
+ 'CODEX_PREFLIGHT_RECEIPT_INVALID',
325
+ 'Rerun preflight with the exact selected static profile.',
326
+ );
327
+ }
328
+
329
+ const cacheIdentity = digest({
330
+ deploymentKey: key,
331
+ ...projection,
332
+ });
333
+ const available = deepFreeze({
334
+ state: 'available',
335
+ cacheIdentity,
336
+ ...projection,
337
+ spawnProfile,
338
+ });
339
+ const cacheKey = digest({
340
+ deploymentKey: key,
341
+ spawnProfileId: projection.spawnProfileId,
342
+ });
343
+ this._invalidateKey(key);
344
+ this.cache.set(cacheKey, available);
345
+ this.currentKeyByDeployment.set(key, cacheKey);
346
+ return available;
347
+ } catch (error) {
348
+ if (this.generationByDeployment.get(key) === generation) {
349
+ this._invalidateKey(key);
350
+ }
351
+ throw error;
352
+ }
353
+ }
354
+ }
355
+
356
+ module.exports = {
357
+ CodexCatalogError,
358
+ CodexModelCatalog,
359
+ };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_INTERVAL_MS = 24 * 3_600_000;
4
+
5
+ function scheduleCodexRetention({
6
+ db,
7
+ logEvent,
8
+ logger = console,
9
+ intervalMs = DEFAULT_INTERVAL_MS,
10
+ scheduleImmediate = setImmediate,
11
+ scheduleInterval = setInterval,
12
+ } = {}) {
13
+ if (
14
+ typeof db?.pruneCodexOperationalData !== 'function'
15
+ || typeof logEvent !== 'function'
16
+ || !Number.isSafeInteger(intervalMs)
17
+ || intervalMs <= 0
18
+ || typeof scheduleImmediate !== 'function'
19
+ || typeof scheduleInterval !== 'function'
20
+ ) {
21
+ throw new TypeError('Codex retention scheduler dependencies are invalid');
22
+ }
23
+
24
+ const run = (trigger) => {
25
+ try {
26
+ const result = db.pruneCodexOperationalData();
27
+ const deletedAttempts = result?.deletedAttempts ?? 0;
28
+ const deletedGenerations = result?.deletedGenerations ?? 0;
29
+ if (deletedAttempts > 0 || deletedGenerations > 0) {
30
+ logger.log?.(
31
+ `[codex-retention] pruned ${deletedAttempts} attempts and `
32
+ + `${deletedGenerations} generations (${trigger})`,
33
+ );
34
+ logEvent('codex-operational-data-pruned', {
35
+ trigger,
36
+ deleted_attempts: deletedAttempts,
37
+ deleted_generations: deletedGenerations,
38
+ });
39
+ }
40
+ return result;
41
+ } catch (error) {
42
+ logger.error?.(
43
+ `[codex-retention] prune failed (${trigger}): `
44
+ + `${error?.code || error?.name || 'unknown'}`,
45
+ );
46
+ return null;
47
+ }
48
+ };
49
+
50
+ scheduleImmediate(() => run('boot'));
51
+ const interval = scheduleInterval(() => run('interval'), intervalMs);
52
+ interval?.unref?.();
53
+ return { run, interval };
54
+ }
55
+
56
+ module.exports = {
57
+ DEFAULT_INTERVAL_MS,
58
+ scheduleCodexRetention,
59
+ };