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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/config.example.json +23 -1
- package/lib/codex/binary.js +287 -0
- package/lib/codex/delivery-finalizer.js +97 -0
- package/lib/codex/diagnostics.js +483 -0
- package/lib/codex/event-normalizer.js +50 -0
- package/lib/codex/host-identity.js +103 -0
- package/lib/codex/model-catalog.js +359 -0
- package/lib/codex/retention-scheduler.js +59 -0
- package/lib/codex/runtime-controller.js +1292 -0
- package/lib/codex/runtime-profile.js +888 -0
- package/lib/codex/spawn-context.js +87 -0
- package/lib/db/auto-resume.js +77 -9
- package/lib/db/codex-reconciliation.js +490 -0
- package/lib/db/codex-retention.js +139 -0
- package/lib/db/sessions.js +102 -1
- package/lib/db.js +3418 -19
- package/lib/error/classify.js +140 -0
- package/lib/handlers/abort.js +143 -5
- package/lib/handlers/autosteer.js +256 -1
- package/lib/handlers/codex-reconciliation.js +230 -0
- package/lib/handlers/config-callback.js +324 -89
- package/lib/handlers/config-ui.js +401 -39
- package/lib/handlers/dispatcher.js +71 -8
- package/lib/handlers/edit-correction.js +11 -1
- package/lib/handlers/edit-redelivery.js +230 -4
- package/lib/handlers/gate-inbound.js +7 -0
- package/lib/handlers/replay-disposition.js +133 -11
- package/lib/handlers/slash-commands.js +443 -37
- package/lib/ipc/client.js +22 -12
- package/lib/ipc/runtime-directory.js +208 -0
- package/lib/ipc/server.js +18 -12
- package/lib/model-costs.js +4 -3
- package/lib/pm-interface.js +33 -0
- package/lib/prompt.js +15 -9
- package/lib/rewind/rewind.js +21 -1
- package/lib/runtime-config.js +605 -0
- package/lib/sdk/build-options.js +4 -1
- package/lib/sdk/callbacks.js +46 -3
- package/migrations/015-agent-runtime-sessions.sql +50 -0
- package/migrations/016-agent-turn-attempts.sql +211 -0
- package/migrations/017-config-runtime-changes.sql +46 -0
- package/ops/README.md +57 -0
- package/package.json +2 -2
- package/polygram.js +697 -48
- package/scripts/doctor.js +37 -8
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DAY_MS = 86_400_000;
|
|
4
|
+
const SETTLED_RETENTION_MS = 30 * DAY_MS;
|
|
5
|
+
const AMBIGUOUS_RETENTION_MS = 90 * DAY_MS;
|
|
6
|
+
|
|
7
|
+
function pruneCodexOperationalData(rawDb, {
|
|
8
|
+
now = Date.now(),
|
|
9
|
+
settledRetentionMs = SETTLED_RETENTION_MS,
|
|
10
|
+
ambiguousRetentionMs = AMBIGUOUS_RETENTION_MS,
|
|
11
|
+
} = {}) {
|
|
12
|
+
for (const [value, label] of [
|
|
13
|
+
[now, 'now'],
|
|
14
|
+
[settledRetentionMs, 'settledRetentionMs'],
|
|
15
|
+
[ambiguousRetentionMs, 'ambiguousRetentionMs'],
|
|
16
|
+
]) {
|
|
17
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
18
|
+
throw new TypeError(`Codex retention ${label} must be a non-negative integer`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (ambiguousRetentionMs < settledRetentionMs) {
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
'Codex ambiguous retention cannot be shorter than settled retention',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return rawDb.transaction(() => {
|
|
28
|
+
const settledCutoff = now - settledRetentionMs;
|
|
29
|
+
const ambiguousCutoff = now - ambiguousRetentionMs;
|
|
30
|
+
const attempts = rawDb.prepare(`
|
|
31
|
+
DELETE FROM codex_turn_attempts
|
|
32
|
+
WHERE (
|
|
33
|
+
(
|
|
34
|
+
recovery_state = 'settled'
|
|
35
|
+
AND settled_ts IS NOT NULL
|
|
36
|
+
AND settled_ts < @settledCutoff
|
|
37
|
+
)
|
|
38
|
+
OR
|
|
39
|
+
(
|
|
40
|
+
recovery_state = 'ambiguous'
|
|
41
|
+
AND ambiguous_ts IS NOT NULL
|
|
42
|
+
AND ambiguous_ts < @ambiguousCutoff
|
|
43
|
+
AND EXISTS (
|
|
44
|
+
SELECT 1
|
|
45
|
+
FROM codex_attempt_reconciliations r
|
|
46
|
+
WHERE r.attempt_id = codex_turn_attempts.attempt_id
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
AND NOT EXISTS (
|
|
51
|
+
SELECT 1
|
|
52
|
+
FROM codex_daemon_lease lease
|
|
53
|
+
WHERE lease.generation_id = codex_turn_attempts.generation_id
|
|
54
|
+
AND lease.status IN ('active', 'quarantined')
|
|
55
|
+
)
|
|
56
|
+
AND NOT EXISTS (
|
|
57
|
+
SELECT 1
|
|
58
|
+
FROM codex_linked_inputs linked
|
|
59
|
+
WHERE (
|
|
60
|
+
linked.attempt_id = codex_turn_attempts.attempt_id
|
|
61
|
+
OR linked.target_attempt_id = codex_turn_attempts.attempt_id
|
|
62
|
+
)
|
|
63
|
+
AND linked.state = 'linked'
|
|
64
|
+
)
|
|
65
|
+
AND NOT EXISTS (
|
|
66
|
+
SELECT 1
|
|
67
|
+
FROM codex_retry_reservations retry
|
|
68
|
+
WHERE (
|
|
69
|
+
retry.original_attempt_id = codex_turn_attempts.attempt_id
|
|
70
|
+
OR retry.retry_attempt_id = codex_turn_attempts.attempt_id
|
|
71
|
+
)
|
|
72
|
+
AND retry.state != 'retired'
|
|
73
|
+
)
|
|
74
|
+
AND NOT EXISTS (
|
|
75
|
+
SELECT 1
|
|
76
|
+
FROM codex_generations generation
|
|
77
|
+
WHERE generation.generation_id = codex_turn_attempts.generation_id
|
|
78
|
+
AND generation.state = 'containment-failed'
|
|
79
|
+
AND NOT EXISTS (
|
|
80
|
+
SELECT 1
|
|
81
|
+
FROM codex_reboot_releases release
|
|
82
|
+
WHERE release.stable_host_id = generation.stable_host_id
|
|
83
|
+
AND release.incident_boot_session_id =
|
|
84
|
+
generation.boot_session_id
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
`).run({ settledCutoff, ambiguousCutoff });
|
|
88
|
+
|
|
89
|
+
const generations = rawDb.prepare(`
|
|
90
|
+
DELETE FROM codex_generations
|
|
91
|
+
WHERE (
|
|
92
|
+
(
|
|
93
|
+
state IN ('healthy-stopped', 'retired')
|
|
94
|
+
AND settled_ts IS NOT NULL
|
|
95
|
+
AND settled_ts < @settledCutoff
|
|
96
|
+
)
|
|
97
|
+
OR
|
|
98
|
+
(
|
|
99
|
+
state = 'created'
|
|
100
|
+
AND updated_ts < @ambiguousCutoff
|
|
101
|
+
)
|
|
102
|
+
OR
|
|
103
|
+
(
|
|
104
|
+
state = 'containment-failed'
|
|
105
|
+
AND updated_ts < @ambiguousCutoff
|
|
106
|
+
AND EXISTS (
|
|
107
|
+
SELECT 1
|
|
108
|
+
FROM codex_reboot_releases release
|
|
109
|
+
WHERE release.stable_host_id = codex_generations.stable_host_id
|
|
110
|
+
AND release.incident_boot_session_id =
|
|
111
|
+
codex_generations.boot_session_id
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
AND NOT EXISTS (
|
|
116
|
+
SELECT 1
|
|
117
|
+
FROM codex_turn_attempts attempt
|
|
118
|
+
WHERE attempt.generation_id = codex_generations.generation_id
|
|
119
|
+
)
|
|
120
|
+
AND NOT EXISTS (
|
|
121
|
+
SELECT 1
|
|
122
|
+
FROM codex_daemon_lease lease
|
|
123
|
+
WHERE lease.generation_id = codex_generations.generation_id
|
|
124
|
+
AND lease.status IN ('active', 'quarantined')
|
|
125
|
+
)
|
|
126
|
+
`).run({ settledCutoff, ambiguousCutoff });
|
|
127
|
+
return {
|
|
128
|
+
deletedAttempts: attempts.changes,
|
|
129
|
+
deletedGenerations: generations.changes,
|
|
130
|
+
};
|
|
131
|
+
})();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
DAY_MS,
|
|
136
|
+
SETTLED_RETENTION_MS,
|
|
137
|
+
AMBIGUOUS_RETENTION_MS,
|
|
138
|
+
pruneCodexOperationalData,
|
|
139
|
+
};
|
package/lib/db/sessions.js
CHANGED
|
@@ -11,6 +11,26 @@ const path = require('path');
|
|
|
11
11
|
|
|
12
12
|
function now() { return Date.now(); }
|
|
13
13
|
|
|
14
|
+
const CLAUDE_INLINE_NAMESPACE = 'claude:inline';
|
|
15
|
+
const CLAUDE_CHANNELS_NAMESPACE = 'claude:channels';
|
|
16
|
+
const CODEX_APP_SERVER_NAMESPACE = 'codex:app-server';
|
|
17
|
+
const CHANNELS_CLASS = new Set(['channels', 'cli']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the compatibility namespace from canonical runtime/backend identity.
|
|
21
|
+
* Historical stored `tmux` rows are inline. Current config `pm: "tmux"` is
|
|
22
|
+
* normalized to canonical `cli` before it reaches this resolver.
|
|
23
|
+
*/
|
|
24
|
+
function sessionNamespaceFor({ runtime = 'claude', backend = null } = {}) {
|
|
25
|
+
if (runtime === 'codex') return CODEX_APP_SERVER_NAMESPACE;
|
|
26
|
+
if (runtime !== 'claude') {
|
|
27
|
+
throw new TypeError(`unsupported provider runtime: ${runtime}`);
|
|
28
|
+
}
|
|
29
|
+
return CHANNELS_CLASS.has(backend)
|
|
30
|
+
? CLAUDE_CHANNELS_NAMESPACE
|
|
31
|
+
: CLAUDE_INLINE_NAMESPACE;
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
function countSessions(db) {
|
|
15
35
|
return db.raw.prepare('SELECT COUNT(*) AS c FROM sessions').get().c;
|
|
16
36
|
}
|
|
@@ -95,6 +115,78 @@ function getClaudeSessionId(db, sessionKey) {
|
|
|
95
115
|
return row?.claude_session_id || null;
|
|
96
116
|
}
|
|
97
117
|
|
|
118
|
+
function resolveProviderSessionForSpawn(db, sessionKey, resolved = {}) {
|
|
119
|
+
const namespace = sessionNamespaceFor(resolved);
|
|
120
|
+
if (!db) {
|
|
121
|
+
return {
|
|
122
|
+
existingSessionId: null,
|
|
123
|
+
appServerSessionId: null,
|
|
124
|
+
namespace,
|
|
125
|
+
drift: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (resolved.runtime !== 'codex') {
|
|
129
|
+
const legacy = resolveSessionForSpawn(db, sessionKey, resolved);
|
|
130
|
+
const row = legacy.existingSessionId
|
|
131
|
+
? db.getProviderSession(sessionKey, namespace)
|
|
132
|
+
: null;
|
|
133
|
+
return {
|
|
134
|
+
...legacy,
|
|
135
|
+
appServerSessionId: null,
|
|
136
|
+
namespace,
|
|
137
|
+
providerSession: row ?? null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const row = db.getProviderSession(sessionKey, namespace);
|
|
142
|
+
if (!row) {
|
|
143
|
+
return {
|
|
144
|
+
existingSessionId: null,
|
|
145
|
+
appServerSessionId: null,
|
|
146
|
+
namespace,
|
|
147
|
+
drift: null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const before = {
|
|
151
|
+
agent: row.agent || null,
|
|
152
|
+
cwd: row.cwd || null,
|
|
153
|
+
model: row.model || null,
|
|
154
|
+
effort: row.effort || null,
|
|
155
|
+
};
|
|
156
|
+
const after = {
|
|
157
|
+
agent: resolved.agent || null,
|
|
158
|
+
cwd: resolved.cwd || null,
|
|
159
|
+
model: resolved.model || null,
|
|
160
|
+
effort: resolved.effort || null,
|
|
161
|
+
};
|
|
162
|
+
const fields = CODEX_SPAWN_IDENTITY_FIELDS.filter((field) => (
|
|
163
|
+
after[field] != null && after[field] !== before[field]
|
|
164
|
+
));
|
|
165
|
+
if (fields.length > 0) {
|
|
166
|
+
db.clearProviderSession(sessionKey, namespace);
|
|
167
|
+
return {
|
|
168
|
+
existingSessionId: null,
|
|
169
|
+
appServerSessionId: null,
|
|
170
|
+
namespace,
|
|
171
|
+
drift: {
|
|
172
|
+
fields,
|
|
173
|
+
before: {
|
|
174
|
+
...before,
|
|
175
|
+
provider_session_id: row.provider_session_id,
|
|
176
|
+
},
|
|
177
|
+
after,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
existingSessionId: row.provider_session_id,
|
|
183
|
+
appServerSessionId: row.app_server_session_id || null,
|
|
184
|
+
namespace,
|
|
185
|
+
drift: null,
|
|
186
|
+
providerSession: row,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
98
190
|
// ─── S2: session-config drift ────────────────────────────────────────
|
|
99
191
|
//
|
|
100
192
|
// A stored `sessions` row records the config the claude session was
|
|
@@ -136,6 +228,10 @@ function getClaudeSessionId(db, sessionKey) {
|
|
|
136
228
|
// already covers cleanly. The stored model/effort columns are
|
|
137
229
|
// informational, not identity.
|
|
138
230
|
const SPAWN_IDENTITY_FIELDS = ['agent', 'cwd'];
|
|
231
|
+
// Codex model and effort are selected per turn and remain informational on
|
|
232
|
+
// the provider-session row. Workspace drift still changes the static runtime
|
|
233
|
+
// and security identity, so cwd is the sole invalidating field.
|
|
234
|
+
const CODEX_SPAWN_IDENTITY_FIELDS = ['cwd'];
|
|
139
235
|
|
|
140
236
|
/**
|
|
141
237
|
* Decide whether a stored session can be resumed for the next spawn,
|
|
@@ -215,7 +311,6 @@ function resolveSessionForSpawn(db, sessionKey, resolved = {}) {
|
|
|
215
311
|
// before 0.12 and a row created today with pm_backend='cli' are
|
|
216
312
|
// semantically the same in terms of session-context invariants
|
|
217
313
|
// (bridge MCP server mounted, reply-tool contract enforced).
|
|
218
|
-
const CHANNELS_CLASS = new Set(['channels', 'cli']);
|
|
219
314
|
const wasChannelsClass = CHANNELS_CLASS.has(before.pm_backend);
|
|
220
315
|
const willBeChannelsClass = CHANNELS_CLASS.has(after.pm_backend);
|
|
221
316
|
if (after.pm_backend != null && wasChannelsClass !== willBeChannelsClass) {
|
|
@@ -244,4 +339,10 @@ module.exports = {
|
|
|
244
339
|
resolveSessionForSpawn,
|
|
245
340
|
countSessions,
|
|
246
341
|
SPAWN_IDENTITY_FIELDS,
|
|
342
|
+
sessionNamespaceFor,
|
|
343
|
+
resolveProviderSessionForSpawn,
|
|
344
|
+
CLAUDE_INLINE_NAMESPACE,
|
|
345
|
+
CLAUDE_CHANNELS_NAMESPACE,
|
|
346
|
+
CODEX_APP_SERVER_NAMESPACE,
|
|
347
|
+
CODEX_SPAWN_IDENTITY_FIELDS,
|
|
247
348
|
};
|