codiedev 0.10.0-beta.4 → 0.10.0-beta.6
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/README.md +11 -5
- package/assets/tui-brand-plugin.tsx +14 -2
- package/dist/backup/cache.js +1 -5
- package/dist/backup/match.js +0 -6
- package/dist/backup/run.js +3 -35
- package/dist/captureExclusions.js +0 -12
- package/dist/captureLog.js +0 -28
- package/dist/captureReadiness.js +0 -58
- package/dist/captureSignals.js +68 -3
- package/dist/cli.js +75 -64
- package/dist/codexHookTrust.js +125 -6
- package/dist/codexLimitScan.js +2 -109
- package/dist/codie.js +50 -114
- package/dist/codieBrandInstall.js +0 -18
- package/dist/codieLauncher.js +20 -92
- package/dist/codieRuntime.js +19 -23
- package/dist/codieRuntimeInstall.js +108 -23
- package/dist/codieUsage.js +38 -41
- package/dist/commands/agentTemplates.generated.js +136 -0
- package/dist/commands/agents.js +1066 -0
- package/dist/commands/agentsSdk.generated.js +7 -0
- package/dist/commands/ask.js +0 -3
- package/dist/commands/capture.js +0 -2
- package/dist/commands/createTicket.js +0 -10
- package/dist/commands/doctor.js +53 -120
- package/dist/commands/evals.js +187 -0
- package/dist/commands/inbox.js +0 -2
- package/dist/commands/library.js +0 -3
- package/dist/commands/pack.js +573 -0
- package/dist/commands/ping.js +0 -2
- package/dist/commands/pull.js +6 -10
- package/dist/commands/push.js +2 -12
- package/dist/commands/search.js +0 -4
- package/dist/commands/send.js +0 -5
- package/dist/commands/share.js +0 -4
- package/dist/commands/shared.js +0 -35
- package/dist/commands/traces.js +344 -0
- package/dist/commands/tracesLabel.js +348 -0
- package/dist/configDir.js +0 -10
- package/dist/connect.js +19 -116
- package/dist/connectFlow.js +0 -9
- package/dist/copilotHooks.js +391 -0
- package/dist/copilotInstallation.js +64 -0
- package/dist/cursorHooks.js +38 -30
- package/dist/cursorLifecycle.js +109 -0
- package/dist/detection.js +0 -30
- package/dist/harnessDirs.js +77 -0
- package/dist/hook.js +182 -469
- package/dist/inboxFormat.js +0 -24
- package/dist/launchContext.js +150 -0
- package/dist/launcher.js +140 -0
- package/dist/limitEvent.js +41 -35
- package/dist/limitEventReporter.js +1 -1
- package/dist/managedPi.js +240 -0
- package/dist/mcp.js +3 -384
- package/dist/nativeSteering.js +24 -0
- package/dist/packs/codie.js +182 -0
- package/dist/packs/harnesses.js +411 -0
- package/dist/packs/hookGate.js +256 -0
- package/dist/packs/install.js +286 -0
- package/dist/packs/manifest.js +126 -0
- package/dist/packs/paths.js +81 -0
- package/dist/packs/shim.js +214 -0
- package/dist/packs/store.js +155 -0
- package/dist/packs/sync.js +170 -0
- package/dist/piHooks.js +216 -0
- package/dist/piRuntime.js +120 -0
- package/dist/repoResolver.js +0 -19
- package/dist/secret.js +0 -3
- package/dist/selfUpdate.js +376 -0
- package/dist/skillMarker.js +23 -0
- package/dist/subagents.js +135 -0
- package/dist/teamSkillContext.js +10 -0
- package/dist/transcriptModels.js +6 -8
- package/dist/upgradeNudge.js +0 -30
- package/dist/utils.js +494 -824
- package/dist/version.js +0 -10
- package/dist/versionCheck.js +0 -33
- package/package.json +8 -9
- package/dist/backup/cache.d.ts +0 -14
- package/dist/backup/match.d.ts +0 -2
- package/dist/backup/run.d.ts +0 -25
- package/dist/captureExclusions.d.ts +0 -30
- package/dist/captureLog.d.ts +0 -20
- package/dist/captureReadiness.d.ts +0 -65
- package/dist/captureSignals.d.ts +0 -19
- package/dist/cli.d.ts +0 -16
- package/dist/codexHookTrust.d.ts +0 -34
- package/dist/codexLimitScan.d.ts +0 -64
- package/dist/codie.d.ts +0 -2
- package/dist/codieBrandInstall.d.ts +0 -11
- package/dist/codieLauncher.d.ts +0 -86
- package/dist/codieRuntime.d.ts +0 -29
- package/dist/codieRuntimeInstall.d.ts +0 -28
- package/dist/codieStartup.d.ts +0 -26
- package/dist/codieStartup.js +0 -30
- package/dist/codieUsage.d.ts +0 -57
- package/dist/commands/ask.d.ts +0 -1
- package/dist/commands/capture.d.ts +0 -14
- package/dist/commands/createTicket.d.ts +0 -27
- package/dist/commands/delete.d.ts +0 -1
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/inbox.d.ts +0 -3
- package/dist/commands/library.d.ts +0 -2
- package/dist/commands/note.d.ts +0 -1
- package/dist/commands/ping.d.ts +0 -1
- package/dist/commands/promote.d.ts +0 -1
- package/dist/commands/promote.js +0 -53
- package/dist/commands/pull.d.ts +0 -2
- package/dist/commands/push.d.ts +0 -33
- package/dist/commands/react.d.ts +0 -2
- package/dist/commands/react.js +0 -40
- package/dist/commands/reverseTicket.d.ts +0 -2
- package/dist/commands/reverseTicket.js +0 -477
- package/dist/commands/search.d.ts +0 -2
- package/dist/commands/security.d.ts +0 -1
- package/dist/commands/security.js +0 -156
- package/dist/commands/send.d.ts +0 -2
- package/dist/commands/share.d.ts +0 -2
- package/dist/commands/shared.d.ts +0 -64
- package/dist/configDir.d.ts +0 -1
- package/dist/connect.d.ts +0 -1
- package/dist/connectFlow.d.ts +0 -13
- package/dist/cursorHooks.d.ts +0 -23
- package/dist/detection.d.ts +0 -30
- package/dist/hook.d.ts +0 -6
- package/dist/inboxFormat.d.ts +0 -38
- package/dist/limitEvent.d.ts +0 -16
- package/dist/limitEventReporter.d.ts +0 -21
- package/dist/mcp.d.ts +0 -12
- package/dist/prUrl.d.ts +0 -9
- package/dist/prUrl.js +0 -33
- package/dist/repoResolver.d.ts +0 -17
- package/dist/secret.d.ts +0 -1
- package/dist/security.d.ts +0 -102
- package/dist/security.js +0 -437
- package/dist/securityDeepScan.d.ts +0 -41
- package/dist/securityDeepScan.js +0 -248
- package/dist/securityDoctor.d.ts +0 -32
- package/dist/securityDoctor.js +0 -63
- package/dist/securityFeature.d.ts +0 -5
- package/dist/securityFeature.js +0 -7
- package/dist/securityInstall.d.ts +0 -63
- package/dist/securityInstall.js +0 -239
- package/dist/securityInventory.d.ts +0 -54
- package/dist/securityInventory.js +0 -556
- package/dist/securityPilot.d.ts +0 -19
- package/dist/securityPilot.js +0 -61
- package/dist/securityPolicy.d.ts +0 -33
- package/dist/securityPolicy.js +0 -188
- package/dist/securitySession.d.ts +0 -6
- package/dist/securitySession.js +0 -72
- package/dist/transcriptModels.d.ts +0 -18
- package/dist/upgradeNudge.d.ts +0 -25
- package/dist/utils.d.ts +0 -346
- package/dist/version.d.ts +0 -1
- package/dist/versionCheck.d.ts +0 -68
package/dist/codexLimitScan.js
CHANGED
|
@@ -41,49 +41,17 @@ exports.sessionIdFromRolloutPath = sessionIdFromRolloutPath;
|
|
|
41
41
|
exports.codexSessionsDir = codexSessionsDir;
|
|
42
42
|
exports.recordCodexLimitReported = recordCodexLimitReported;
|
|
43
43
|
exports.sweepCodexLimits = sweepCodexLimits;
|
|
44
|
-
// Reporting a Codex credit/rate-limit block.
|
|
45
|
-
//
|
|
46
|
-
// WHY THIS EXISTS: the `limit-codex` reporter is a Codex `Stop` hook, and
|
|
47
|
-
// Codex does NOT run Stop hooks on the turn a block kills. Measured on real
|
|
48
|
-
// rollouts: every normal `task_complete` pairs with a Stop-hook run within
|
|
49
|
-
// ~200ms, and the blocked one never does (21 turns → 20 runs; 11 → 10, short
|
|
50
|
-
// by exactly the blocked turn each time). Claude Code has a dedicated
|
|
51
|
-
// StopFailure event with a `rate_limit` matcher, which is why it reports
|
|
52
|
-
// correctly; Codex's HookEventName enum has no equivalent — there is no event
|
|
53
|
-
// at all that fires at the block.
|
|
54
|
-
//
|
|
55
|
-
// So the block can only be reported LATER, from a hook that does run. This
|
|
56
|
-
// module scans the rollout files Codex has already written and reports any
|
|
57
|
-
// block that has not been reported yet. It is driven from UserPromptSubmit
|
|
58
|
-
// (`discover-codex`), which fires on the developer's next prompt — in the
|
|
59
|
-
// blocked session or in a new one — and from Stop, which still catches the
|
|
60
|
-
// (rarer) case of a block followed by more turns.
|
|
61
|
-
//
|
|
62
|
-
// The scan reads BACKWARDS from end of file. The previous reader took a fixed
|
|
63
|
-
// 512 KB tail, which silently lost the event whenever the session kept writing
|
|
64
|
-
// after the block: in one real 44 MB rollout the marker sat 1.13 MB from the
|
|
65
|
-
// end, outside the window, and the parse returned null.
|
|
66
44
|
const fs = __importStar(require("fs"));
|
|
67
|
-
const os = __importStar(require("os"));
|
|
68
45
|
const path = __importStar(require("path"));
|
|
69
46
|
const limitEvent_1 = require("./limitEvent");
|
|
70
47
|
const configDir_1 = require("./configDir");
|
|
48
|
+
const harnessDirs_1 = require("./harnessDirs");
|
|
71
49
|
const MARKER = Buffer.from("rate_limit_reached_type");
|
|
72
50
|
const SPEND_MARKER = Buffer.from("spend_control_reached");
|
|
73
51
|
const NEWLINE = 0x0a;
|
|
74
|
-
// Read backwards in 256 KB chunks, and give up after 16 MB. Codex rollouts run
|
|
75
|
-
// to tens of megabytes, but a block is always in the session's LAST turn — the
|
|
76
|
-
// bytes after it are whatever the session did once the window reset. 16 MB
|
|
77
|
-
// covers that comfortably without ever loading a whole rollout.
|
|
78
52
|
const DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
79
53
|
const DEFAULT_MAX_SCAN_BYTES = 16 * 1024 * 1024;
|
|
80
|
-
// Tail shipped to the server so it can summarize what the developer was doing
|
|
81
|
-
// when they got blocked. Matches the server-side cap in http.ts.
|
|
82
54
|
const DEFAULT_TAIL_BYTES = 64 * 1024;
|
|
83
|
-
/**
|
|
84
|
-
* Find the LAST rate-limit/credit block recorded in a Codex rollout file.
|
|
85
|
-
* Returns null when the session was never blocked.
|
|
86
|
-
*/
|
|
87
55
|
function scanCodexRolloutForLimit(filePath, options = {}) {
|
|
88
56
|
const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;
|
|
89
57
|
const maxScanBytes = options.maxScanBytes ?? DEFAULT_MAX_SCAN_BYTES;
|
|
@@ -105,8 +73,6 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
|
|
|
105
73
|
}
|
|
106
74
|
try {
|
|
107
75
|
let pos = size;
|
|
108
|
-
// Bytes of the (earlier-read) chunk that belong to a line whose start we
|
|
109
|
-
// have not reached yet. Carried into the next, earlier chunk.
|
|
110
76
|
let carry = Buffer.alloc(0);
|
|
111
77
|
let scanned = 0;
|
|
112
78
|
while (pos > 0 && scanned < maxScanBytes) {
|
|
@@ -118,15 +84,10 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
|
|
|
118
84
|
const read = fs.readSync(fd, chunk, 0, length, pos);
|
|
119
85
|
scanned += read;
|
|
120
86
|
const window = Buffer.concat([chunk.subarray(0, read), carry]);
|
|
121
|
-
// At pos > 0 the first line in the window is partial — its start lies in
|
|
122
|
-
// an earlier chunk — so hold it back rather than parse a torn line.
|
|
123
87
|
let completeStart = 0;
|
|
124
88
|
if (pos > 0) {
|
|
125
89
|
const firstNewline = window.indexOf(NEWLINE);
|
|
126
90
|
if (firstNewline < 0) {
|
|
127
|
-
// The whole window is one unterminated line. A token_count record is
|
|
128
|
-
// ~1 KB, so a line this long is never the one we want: drop it rather
|
|
129
|
-
// than grow the carry without bound.
|
|
130
91
|
carry = window.length > maxScanBytes ? Buffer.alloc(0) : window;
|
|
131
92
|
continue;
|
|
132
93
|
}
|
|
@@ -139,8 +100,6 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
|
|
|
139
100
|
const region = window.subarray(completeStart);
|
|
140
101
|
if (region.indexOf(MARKER) < 0 && region.indexOf(SPEND_MARKER) < 0)
|
|
141
102
|
continue;
|
|
142
|
-
// Walk this window's complete lines from the end backwards; the first
|
|
143
|
-
// match going backwards is the latest one in the file.
|
|
144
103
|
const bounds = [];
|
|
145
104
|
let lineStart = 0;
|
|
146
105
|
for (let index = 0; index < region.length; index += 1) {
|
|
@@ -168,18 +127,10 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
|
|
|
168
127
|
fs.closeSync(fd);
|
|
169
128
|
}
|
|
170
129
|
catch {
|
|
171
|
-
/* best-effort */
|
|
172
130
|
}
|
|
173
131
|
}
|
|
174
132
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Read up to `maxBytes` of the rollout ENDING at `lineEnd` — the work the
|
|
177
|
-
* developer was doing when the block hit, not whatever the session did after
|
|
178
|
-
* the window reset. Resumes at a line boundary so every JSONL line parses.
|
|
179
|
-
*/
|
|
180
133
|
function readTailEndingAt(filePath, lineEnd, maxBytes = DEFAULT_TAIL_BYTES) {
|
|
181
|
-
// Clamp to the file as it is NOW: the offset came from an earlier scan, and
|
|
182
|
-
// reading past EOF would return a short, garbled tail.
|
|
183
134
|
let end = lineEnd;
|
|
184
135
|
try {
|
|
185
136
|
end = Math.min(lineEnd, fs.statSync(filePath).size);
|
|
@@ -215,13 +166,11 @@ function readTailEndingAt(filePath, lineEnd, maxBytes = DEFAULT_TAIL_BYTES) {
|
|
|
215
166
|
fs.closeSync(fd);
|
|
216
167
|
}
|
|
217
168
|
catch {
|
|
218
|
-
/* best-effort */
|
|
219
169
|
}
|
|
220
170
|
}
|
|
221
171
|
}
|
|
222
172
|
const DEFAULT_WITHIN_MS = 24 * 3600_000;
|
|
223
173
|
const DEFAULT_MAX_FILES = 12;
|
|
224
|
-
/** Codex rollout files, newest first, limited by age and count. */
|
|
225
174
|
function findRecentCodexRollouts(sessionsDir, options = {}) {
|
|
226
175
|
const withinMs = options.withinMs ?? DEFAULT_WITHIN_MS;
|
|
227
176
|
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
@@ -252,7 +201,6 @@ function findRecentCodexRollouts(sessionsDir, options = {}) {
|
|
|
252
201
|
found.push({ file: full, mtime });
|
|
253
202
|
}
|
|
254
203
|
catch {
|
|
255
|
-
/* unreadable — skip */
|
|
256
204
|
}
|
|
257
205
|
}
|
|
258
206
|
};
|
|
@@ -262,17 +210,14 @@ function findRecentCodexRollouts(sessionsDir, options = {}) {
|
|
|
262
210
|
.slice(0, maxFiles)
|
|
263
211
|
.map((entry) => entry.file);
|
|
264
212
|
}
|
|
265
|
-
/** Codex names rollouts `rollout-<ISO local time>-<session id>.jsonl`. */
|
|
266
213
|
function sessionIdFromRolloutPath(filePath) {
|
|
267
214
|
const match = path.basename(filePath).match(/^rollout-.*?T[\d-]+-(.+)\.jsonl$/);
|
|
268
215
|
return match ? match[1] : undefined;
|
|
269
216
|
}
|
|
270
|
-
/** Where Codex writes its rollout JSONL files. */
|
|
271
217
|
function codexSessionsDir() {
|
|
272
|
-
return path.join(
|
|
218
|
+
return path.join((0, harnessDirs_1.codexHome)(), "sessions");
|
|
273
219
|
}
|
|
274
220
|
exports.CODEX_LIMIT_STATE_PATH = path.join((0, configDir_1.codiedevStateDir)(), "codex-limit-state.json");
|
|
275
|
-
// Bounds the state file on a machine with a long Codex history.
|
|
276
221
|
const MAX_STATE_ENTRIES = 500;
|
|
277
222
|
function readSweepState(statePath) {
|
|
278
223
|
let parsed;
|
|
@@ -297,10 +242,6 @@ function readSweepState(statePath) {
|
|
|
297
242
|
};
|
|
298
243
|
}
|
|
299
244
|
}
|
|
300
|
-
// Migrate the 0.10.0-beta shape ({ reportedThrough, clean }). The old global
|
|
301
|
-
// watermark is DELIBERATELY dropped rather than carried over: it is exactly
|
|
302
|
-
// what could suppress an older block forever. Re-reporting is safe — the
|
|
303
|
-
// server deduplicates on (companyId, userId, at).
|
|
304
245
|
const legacyClean = parsed.clean;
|
|
305
246
|
if (legacyClean && typeof legacyClean === "object") {
|
|
306
247
|
for (const [file, size] of Object.entries(legacyClean)) {
|
|
@@ -310,9 +251,6 @@ function readSweepState(statePath) {
|
|
|
310
251
|
}
|
|
311
252
|
return { seen };
|
|
312
253
|
}
|
|
313
|
-
// A record is dropped only once its file has been gone for a while. Pruning
|
|
314
|
-
// the moment a path is missing would re-report an already-sent block after any
|
|
315
|
-
// transient rename or move.
|
|
316
254
|
const PRUNE_AFTER_MS = 7 * 24 * 3600_000;
|
|
317
255
|
function pruneSweepState(state, now) {
|
|
318
256
|
for (const [file, record] of Object.entries(state.seen)) {
|
|
@@ -329,11 +267,6 @@ function pruneSweepState(state, now) {
|
|
|
329
267
|
.slice(MAX_STATE_ENTRIES)
|
|
330
268
|
.forEach((file) => delete state.seen[file]);
|
|
331
269
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Merge our records into whatever is on disk NOW and write the result. Two
|
|
334
|
-
* Codex sessions can submit a prompt at the same time; a plain
|
|
335
|
-
* read-modify-write would silently drop the other process's records.
|
|
336
|
-
*/
|
|
337
270
|
function commitSweepState(statePath, ours, now) {
|
|
338
271
|
const current = readSweepState(statePath);
|
|
339
272
|
for (const [file, record] of Object.entries(ours))
|
|
@@ -342,9 +275,6 @@ function commitSweepState(statePath, ours, now) {
|
|
|
342
275
|
writeSweepState(statePath, current);
|
|
343
276
|
}
|
|
344
277
|
function writeSweepState(statePath, state) {
|
|
345
|
-
// Written through a temp file and renamed: Codex kills a hook that overruns
|
|
346
|
-
// its timeout, and a half-written state file would read as "nothing seen"
|
|
347
|
-
// and re-report every block in the window.
|
|
348
278
|
const temp = `${statePath}.${process.pid}.tmp`;
|
|
349
279
|
try {
|
|
350
280
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
@@ -356,7 +286,6 @@ function writeSweepState(statePath, state) {
|
|
|
356
286
|
fs.unlinkSync(temp);
|
|
357
287
|
}
|
|
358
288
|
catch {
|
|
359
|
-
/* best-effort */
|
|
360
289
|
}
|
|
361
290
|
}
|
|
362
291
|
}
|
|
@@ -369,46 +298,15 @@ function fingerprint(filePath) {
|
|
|
369
298
|
return null;
|
|
370
299
|
}
|
|
371
300
|
}
|
|
372
|
-
/**
|
|
373
|
-
* Record a block reported outside the sweep (the Stop path), so the next
|
|
374
|
-
* prompt sweep neither re-scans the rollout nor re-posts the event.
|
|
375
|
-
*/
|
|
376
301
|
function recordCodexLimitReported(filePath, at, statePath = exports.CODEX_LIMIT_STATE_PATH) {
|
|
377
302
|
const print = fingerprint(filePath);
|
|
378
303
|
if (!print)
|
|
379
304
|
return;
|
|
380
305
|
commitSweepState(statePath, { [filePath]: { ...print, at, t: Date.now() } }, Date.now());
|
|
381
306
|
}
|
|
382
|
-
// Candidates are listed generously and settled by one statSync each; only
|
|
383
|
-
// files that actually changed cost a read. The budget therefore bounds work
|
|
384
|
-
// without letting idle sessions bury an unreported block.
|
|
385
|
-
//
|
|
386
|
-
// The budget binds only on a COLD START, where nothing has been seen before,
|
|
387
|
-
// so it must be big enough to finish that first pass. A budget of 8 silently
|
|
388
|
-
// starved the older blocked rollouts on a real machine: 49 candidates in the
|
|
389
|
-
// window, the newest 8 all unblocked, nothing reported. A full pass over
|
|
390
|
-
// those 49 costs ~205ms — the backward scan stops at the marker rather than
|
|
391
|
-
// reading each file to its cap — so the budget can afford to cover them.
|
|
392
307
|
const DEFAULT_CANDIDATES = 200;
|
|
393
308
|
const DEFAULT_MAX_SCANS = 64;
|
|
394
|
-
// The budget bounds how MANY files are read; this bounds how LONG. A count is
|
|
395
|
-
// not enough on a slow or network-mounted disk, and this runs on the user's
|
|
396
|
-
// prompt clock. Whatever is not reached stays unrecorded, so the next prompt
|
|
397
|
-
// picks it up rather than skipping it.
|
|
398
309
|
const DEFAULT_DEADLINE_MS = 1_500;
|
|
399
|
-
/**
|
|
400
|
-
* Report every Codex block this machine has not reported yet. Returns how many
|
|
401
|
-
* events were posted. Never throws — it runs inside a hook.
|
|
402
|
-
*
|
|
403
|
-
* State is kept PER FILE, not as one newest-wins timestamp. A single global
|
|
404
|
-
* watermark loses data: rollouts are capped per sweep, so a block in a file
|
|
405
|
-
* outside the cap can be older than one already reported and would then be
|
|
406
|
-
* suppressed forever. A per-file record can only ever cause a re-report, which
|
|
407
|
-
* the server deduplicates on (companyId, userId, at).
|
|
408
|
-
*
|
|
409
|
-
* A file is recorded only after its POST succeeds, so a failed send is retried
|
|
410
|
-
* on the next prompt.
|
|
411
|
-
*/
|
|
412
310
|
async function sweepCodexLimits(args) {
|
|
413
311
|
const statePath = args.statePath ?? exports.CODEX_LIMIT_STATE_PATH;
|
|
414
312
|
const state = readSweepState(statePath);
|
|
@@ -426,8 +324,6 @@ async function sweepCodexLimits(args) {
|
|
|
426
324
|
if (!print)
|
|
427
325
|
continue;
|
|
428
326
|
const record = state.seen[file];
|
|
429
|
-
// Settled by the stat alone — it has not changed since the last scan, so
|
|
430
|
-
// it holds nothing new and does not spend the read budget.
|
|
431
327
|
if (record && record.size === print.size && record.mtime === print.mtime)
|
|
432
328
|
continue;
|
|
433
329
|
if (budget <= 0 || Date.now() >= deadline)
|
|
@@ -439,14 +335,11 @@ async function sweepCodexLimits(args) {
|
|
|
439
335
|
continue;
|
|
440
336
|
}
|
|
441
337
|
if (record && record.at === hit.event.at) {
|
|
442
|
-
// Same block, already reported; the file just grew.
|
|
443
338
|
touched[file] = { ...print, at: record.at, t: now };
|
|
444
339
|
continue;
|
|
445
340
|
}
|
|
446
341
|
pending.push({ file, print, hit });
|
|
447
342
|
}
|
|
448
|
-
// Posted concurrently: each file is independent, and this runs on the
|
|
449
|
-
// prompt path where a serial chain of network timeouts would be felt.
|
|
450
343
|
const results = await Promise.allSettled(pending.map(({ file, hit }) => args.post({
|
|
451
344
|
source: "codex",
|
|
452
345
|
...hit.event,
|
package/dist/codie.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
// `codie` — Codiedev's governed coding harness.
|
|
4
|
-
//
|
|
5
|
-
// Ships from the same package as `codiedev` so a developer runs one install and
|
|
6
|
-
// then picks their surface: Claude Code, Codex, and Cursor get session capture
|
|
7
|
-
// via `codiedev connect`; `codie` launches the governed harness against the
|
|
8
|
-
// same token. OpenCode is the embedded engine; all user-facing identity,
|
|
9
|
-
// credentials, models, policy, and capture belong to Codiedev.
|
|
10
|
-
//
|
|
11
|
-
// The engine is downloaded on first run (see codieRuntime.ts) so capture-only
|
|
12
|
-
// users never pay for it.
|
|
13
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
4
|
if (k2 === undefined) k2 = k;
|
|
15
5
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -55,10 +45,10 @@ const codieBrandInstall_1 = require("./codieBrandInstall");
|
|
|
55
45
|
const codieUsage_1 = require("./codieUsage");
|
|
56
46
|
const codieRuntime_1 = require("./codieRuntime");
|
|
57
47
|
const codieRuntimeInstall_1 = require("./codieRuntimeInstall");
|
|
58
|
-
const codieStartup_1 = require("./codieStartup");
|
|
59
48
|
const upgradeNudge_1 = require("./upgradeNudge");
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
const codie_1 = require("./packs/codie");
|
|
50
|
+
const paths_1 = require("./packs/paths");
|
|
51
|
+
const managedPi_1 = require("./managedPi");
|
|
62
52
|
const USAGE_SETTLE_MS = 1200;
|
|
63
53
|
function git(args) {
|
|
64
54
|
const result = (0, child_process_1.spawnSync)("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -88,8 +78,6 @@ function existingInlineConfig() {
|
|
|
88
78
|
throw new Error("OPENCODE_CONFIG_CONTENT is not valid JSON");
|
|
89
79
|
}
|
|
90
80
|
}
|
|
91
|
-
// The one credential. `codiedev connect` wrote it; the gateway validates the
|
|
92
|
-
// same userApiTokens row, so there is no second harness token to mint.
|
|
93
81
|
function connectedCodiedevConfig() {
|
|
94
82
|
try {
|
|
95
83
|
return JSON.parse(fs.readFileSync(path.join((0, configDir_1.codiedevStateDir)(), "config.json"), "utf8"));
|
|
@@ -98,14 +86,23 @@ function connectedCodiedevConfig() {
|
|
|
98
86
|
return {};
|
|
99
87
|
}
|
|
100
88
|
}
|
|
89
|
+
function withStarterPacks(existing, approved, catalog) {
|
|
90
|
+
try {
|
|
91
|
+
const packs = (0, codie_1.codiePackConfig)((0, paths_1.packsRoot)(), { approvedModels: approved, catalog });
|
|
92
|
+
if (process.env.CODIEDEV_DEBUG) {
|
|
93
|
+
for (const note of packs.notes)
|
|
94
|
+
console.error(`codie: starter pack ${note}`);
|
|
95
|
+
}
|
|
96
|
+
return (0, codie_1.mergeCodiePackConfig)(existing, packs);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`codie: skipping starter packs (${error instanceof Error ? error.message : error})`);
|
|
100
|
+
return existing;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
101
103
|
function assetPath(name) {
|
|
102
|
-
// dist/codie.js → ../assets/<name>; src/codie.ts → ../assets/<name>.
|
|
103
104
|
return path.join(__dirname, "..", "assets", name);
|
|
104
105
|
}
|
|
105
|
-
// Remove the engine's boot/exit banner wordmark in place (see
|
|
106
|
-
// patchBrandWordmark). Best-effort like the TUI plugin: any failure rolls the
|
|
107
|
-
// bytes back and the stock banner keeps working. Runs once per install; an
|
|
108
|
-
// already-suppressed binary has no known source region and is skipped.
|
|
109
106
|
function installBrandWordmark(runtime) {
|
|
110
107
|
try {
|
|
111
108
|
installBrandWordmarkUnsafe(runtime);
|
|
@@ -119,10 +116,6 @@ function installBrandWordmarkUnsafe(runtime) {
|
|
|
119
116
|
const { changed, offset, replacement } = (0, codieLauncher_1.patchBrandWordmark)(binary);
|
|
120
117
|
if (!changed || !replacement)
|
|
121
118
|
return;
|
|
122
|
-
// In-place write (not copy+rename): opencode-ai/bin/opencode.exe and the
|
|
123
|
-
// platform package's binary are hardlinks to one inode, and this keeps them
|
|
124
|
-
// consistent. Restoring the original bytes is a true rollback — identical
|
|
125
|
-
// content revalidates the existing macOS signature.
|
|
126
119
|
const writeAt = (bytes) => {
|
|
127
120
|
const fd = fs.openSync(runtime, "r+");
|
|
128
121
|
try {
|
|
@@ -135,7 +128,6 @@ function installBrandWordmarkUnsafe(runtime) {
|
|
|
135
128
|
const restore = () => writeAt(binary.subarray(offset, offset + replacement.length));
|
|
136
129
|
writeAt(replacement);
|
|
137
130
|
if (process.platform === "darwin") {
|
|
138
|
-
// A modified Mach-O needs a fresh ad-hoc signature or the OS kills it.
|
|
139
131
|
const signed = (0, child_process_1.spawnSync)("codesign", ["--force", "--sign", "-", runtime], { stdio: "ignore" });
|
|
140
132
|
if (signed.status !== 0) {
|
|
141
133
|
restore();
|
|
@@ -165,7 +157,6 @@ async function runNonInteractive(runtime, runtimeArgs, env) {
|
|
|
165
157
|
return;
|
|
166
158
|
}
|
|
167
159
|
catch {
|
|
168
|
-
// Fall back to the direct child if its process group already exited.
|
|
169
160
|
}
|
|
170
161
|
}
|
|
171
162
|
child.kill(signal);
|
|
@@ -175,73 +166,8 @@ async function runNonInteractive(runtime, runtimeArgs, env) {
|
|
|
175
166
|
process.once("SIGINT", forwardInterrupt);
|
|
176
167
|
process.once("SIGTERM", forwardTermination);
|
|
177
168
|
return await new Promise((resolve, reject) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const retainedPrefixBytes = Math.max(...errorPrefixes.map((prefix) => prefix.length)) - 1;
|
|
181
|
-
// OpenCode's generic UnknownError and the adapter's private reason can be
|
|
182
|
-
// delivered in either order — and the generic block can arrive on stdout
|
|
183
|
-
// or stderr. Gate both streams by content, retaining only a possible final
|
|
184
|
-
// error block. Ordinary output stays streaming apart from the few bytes
|
|
185
|
-
// needed to recognize a split prefix.
|
|
186
|
-
const createOutputGate = (write) => {
|
|
187
|
-
let tail = Buffer.alloc(0);
|
|
188
|
-
let possibleError;
|
|
189
|
-
return {
|
|
190
|
-
push(chunk, blocked) {
|
|
191
|
-
if (blocked) {
|
|
192
|
-
if (possibleError)
|
|
193
|
-
possibleError.push(Buffer.from(chunk));
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (possibleError) {
|
|
197
|
-
possibleError.push(Buffer.from(chunk));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const combined = Buffer.concat([tail, chunk]);
|
|
201
|
-
let errorStart = -1;
|
|
202
|
-
for (const prefix of errorPrefixes) {
|
|
203
|
-
const index = combined.indexOf(prefix);
|
|
204
|
-
if (index >= 0 && (errorStart < 0 || index < errorStart))
|
|
205
|
-
errorStart = index;
|
|
206
|
-
}
|
|
207
|
-
if (errorStart >= 0) {
|
|
208
|
-
if (errorStart > 0)
|
|
209
|
-
write(combined.subarray(0, errorStart));
|
|
210
|
-
possibleError = [combined.subarray(errorStart)];
|
|
211
|
-
tail = Buffer.alloc(0);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const flushBytes = Math.max(0, combined.length - retainedPrefixBytes);
|
|
215
|
-
if (flushBytes > 0)
|
|
216
|
-
write(combined.subarray(0, flushBytes));
|
|
217
|
-
tail = combined.subarray(flushBytes);
|
|
218
|
-
},
|
|
219
|
-
finish(blocked) {
|
|
220
|
-
if (tail.length > 0)
|
|
221
|
-
write(tail);
|
|
222
|
-
const heldError = possibleError ? Buffer.concat(possibleError).toString() : "";
|
|
223
|
-
const genericBlockError = heldError.includes("UnknownError") &&
|
|
224
|
-
heldError.includes("Unexpected server error");
|
|
225
|
-
if (heldError && (!blocked || !genericBlockError))
|
|
226
|
-
write(heldError);
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
const stdoutGate = createOutputGate((chunk) => process.stdout.write(chunk));
|
|
231
|
-
const stderrGate = createOutputGate((chunk) => process.stderr.write(chunk));
|
|
232
|
-
child.stdout?.on("data", (chunk) => {
|
|
233
|
-
stdoutGate.push(Buffer.from(chunk), Boolean(blockReason));
|
|
234
|
-
});
|
|
235
|
-
child.stderr?.on("data", (chunk) => {
|
|
236
|
-
const text = chunk.toString();
|
|
237
|
-
const reason = (0, codieLauncher_1.extractCodieSecurityBlock)(text);
|
|
238
|
-
if (reason) {
|
|
239
|
-
blockReason = reason;
|
|
240
|
-
signalChild("SIGTERM");
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
stderrGate.push(Buffer.from(chunk), Boolean(blockReason));
|
|
244
|
-
});
|
|
169
|
+
child.stdout?.on("data", (chunk) => process.stdout.write(chunk));
|
|
170
|
+
child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
245
171
|
child.once("error", (error) => {
|
|
246
172
|
if (child.stdin)
|
|
247
173
|
process.stdin.unpipe(child.stdin);
|
|
@@ -254,11 +180,6 @@ async function runNonInteractive(runtime, runtimeArgs, env) {
|
|
|
254
180
|
process.stdin.unpipe(child.stdin);
|
|
255
181
|
process.removeListener("SIGINT", forwardInterrupt);
|
|
256
182
|
process.removeListener("SIGTERM", forwardTermination);
|
|
257
|
-
stdoutGate.finish(Boolean(blockReason));
|
|
258
|
-
stderrGate.finish(Boolean(blockReason));
|
|
259
|
-
if (blockReason) {
|
|
260
|
-
console.error(`Codie Security blocked this action:\n${blockReason}`);
|
|
261
|
-
}
|
|
262
183
|
resolve(status);
|
|
263
184
|
});
|
|
264
185
|
});
|
|
@@ -272,20 +193,33 @@ async function main() {
|
|
|
272
193
|
if (!key) {
|
|
273
194
|
throw new Error("Connect your Codiedev account first with `codiedev connect`");
|
|
274
195
|
}
|
|
275
|
-
// Before anything paints: once the OpenCode engine owns stdio, a prompt is
|
|
276
|
-
// unreadable. A successful in-place upgrade relaunches this command with the
|
|
277
|
-
// new package, so the stale process must stop here.
|
|
278
196
|
const update = await (0, upgradeNudge_1.maybeOfferUpgrade)();
|
|
279
197
|
if (update.relaunched) {
|
|
280
198
|
process.exitCode = update.status ?? 0;
|
|
281
199
|
return;
|
|
282
200
|
}
|
|
283
|
-
const policyRefresh = await (0, codieStartup_1.refreshCodieSecurityPolicy)(connected);
|
|
284
|
-
if (policyRefresh.status === "cached") {
|
|
285
|
-
console.error(`codie: security policy refresh failed; using the cached policy (${policyRefresh.error})`);
|
|
286
|
-
}
|
|
287
201
|
const gatewayUrl = process.env.CODIEDEV_GATEWAY_URL || "https://gateway.codiedev.com/api/v1";
|
|
288
202
|
const catalog = await (0, codieLauncher_1.fetchApprovedCatalog)({ baseUrl: gatewayUrl, key });
|
|
203
|
+
if (process.argv[2] === "--pi") {
|
|
204
|
+
let skillPaths = [];
|
|
205
|
+
try {
|
|
206
|
+
const approved = new Set(catalog.map((model) => (0, codieLauncher_1.bareModel)(model.id)).filter(Boolean));
|
|
207
|
+
skillPaths = (0, codie_1.codiePackConfig)((0, paths_1.packsRoot)(), { approvedModels: approved, catalog }).skillPaths;
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error(`codie: skipping starter packs (${error instanceof Error ? error.message : error})`);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
process.exitCode = (0, managedPi_1.runManagedPi)({
|
|
214
|
+
args: process.argv.slice(3), key, gatewayUrl, catalog, stateDir: (0, configDir_1.codiedevStateDir)(), skillPaths,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error(`codie: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
|
+
process.exitCode = error instanceof managedPi_1.ManagedPiArgError ? 2 : 1;
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
289
223
|
const parsed = (0, codieLauncher_1.parseCodieArgs)(process.argv.slice(2));
|
|
290
224
|
if (parsed.args.length === 1 && parsed.args[0] === "models") {
|
|
291
225
|
console.log("Codiedev approved models\n");
|
|
@@ -316,8 +250,18 @@ async function main() {
|
|
|
316
250
|
return null;
|
|
317
251
|
}
|
|
318
252
|
},
|
|
253
|
+
isEngine: codieRuntimeInstall_1.isEngineOnDisk,
|
|
319
254
|
}),
|
|
320
255
|
install: (missing) => (0, codieRuntimeInstall_1.installCodieRuntimeOnDisk)(missing, stateDir),
|
|
256
|
+
repair: (unlinked) => {
|
|
257
|
+
console.error("codie: finishing the engine setup npm skipped (opencode-ai's postinstall)…");
|
|
258
|
+
const linked = (0, codieRuntimeInstall_1.linkEngineOnDisk)(unlinked.packageDir);
|
|
259
|
+
if (!linked.ok && linked.detail === codieRuntimeInstall_1.IGNORE_SCRIPTS)
|
|
260
|
+
throw new Error((0, codieRuntimeInstall_1.linkFailure)(linked, unlinked.packageDir));
|
|
261
|
+
if (!linked.ok)
|
|
262
|
+
console.error(`codie: ${(0, codieRuntimeInstall_1.linkFailure)(linked, unlinked.packageDir)} Reinstalling the engine.`);
|
|
263
|
+
return linked.ok;
|
|
264
|
+
},
|
|
321
265
|
confirmUpdate: async (outdated) => {
|
|
322
266
|
console.error("");
|
|
323
267
|
console.error(`Codie harness ${outdated.expectedVersion} is available (you're on ${outdated.currentVersion}).`);
|
|
@@ -334,7 +278,7 @@ async function main() {
|
|
|
334
278
|
(0, codieBrandInstall_1.installTuiPlugin)();
|
|
335
279
|
installBrandWordmark(runtime);
|
|
336
280
|
const config = (0, codieLauncher_1.buildCodieConfig)({
|
|
337
|
-
existing: existingInlineConfig(),
|
|
281
|
+
existing: withStarterPacks(existingInlineConfig(), new Set(catalog.map((model) => (0, codieLauncher_1.bareModel)(model.id)).filter(Boolean)), catalog),
|
|
338
282
|
catalog,
|
|
339
283
|
env: { ...process.env, ...parsed.overrides, CODIEDEV_GATEWAY_URL: gatewayUrl },
|
|
340
284
|
plugins: [capturePlugin, metadataPlugin],
|
|
@@ -350,13 +294,7 @@ async function main() {
|
|
|
350
294
|
};
|
|
351
295
|
const runtimeArgs = (0, codieLauncher_1.withCodieWorkingDirectory)(parsed.args, process.cwd());
|
|
352
296
|
const nonInteractiveRun = runtimeArgs[0] === "run";
|
|
353
|
-
if (nonInteractiveRun)
|
|
354
|
-
env.CODIEDEV_SECURITY_BLOCK_OUTPUT = "marker";
|
|
355
297
|
if (nonInteractiveRun) {
|
|
356
|
-
// The TUI paints its own live status line from the same endpoint. A
|
|
357
|
-
// non-interactive run has no TUI, so the budget arrives as two printed
|
|
358
|
-
// lines instead — and only for a human at a terminal, so a scripted run
|
|
359
|
-
// is neither slowed nor polluted.
|
|
360
298
|
const reportCost = Boolean(process.stderr.isTTY);
|
|
361
299
|
const startedAt = Date.now();
|
|
362
300
|
if (reportCost) {
|
|
@@ -366,8 +304,6 @@ async function main() {
|
|
|
366
304
|
}
|
|
367
305
|
process.exitCode = (await runNonInteractive(runtime, runtimeArgs, env)) ?? 1;
|
|
368
306
|
if (reportCost) {
|
|
369
|
-
// The gateway books a request's cost after it has answered, so a poll
|
|
370
|
-
// fired the instant the run closes can miss the last one.
|
|
371
307
|
await new Promise((resolve) => setTimeout(resolve, USAGE_SETTLE_MS));
|
|
372
308
|
const cost = (0, codieUsage_1.formatSessionCostLine)(await (0, codieUsage_1.fetchCodieUsage)({ baseUrl: gatewayUrl, key, sinceMs: startedAt }));
|
|
373
309
|
if (cost)
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Stages the Codiedev TUI plugin into the engine's config directory.
|
|
3
|
-
//
|
|
4
|
-
// TUI plugins load only from tui.json in the OpenCode config dir, so the
|
|
5
|
-
// packaged template is copied there and referenced by a relative spec. The
|
|
6
|
-
// copy also carries the status line, which needs an absolute path back into
|
|
7
|
-
// this package — the plugin sits outside it and cannot reach it any other way.
|
|
8
|
-
//
|
|
9
|
-
// Every step is best-effort: a developer who cannot write this directory still
|
|
10
|
-
// gets a working harness, just the stock logo and no status line.
|
|
11
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
3
|
if (k2 === undefined) k2 = k;
|
|
13
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -50,15 +41,8 @@ const os = __importStar(require("os"));
|
|
|
50
41
|
const path = __importStar(require("path"));
|
|
51
42
|
const codieLauncher_1 = require("./codieLauncher");
|
|
52
43
|
function assetPath(name) {
|
|
53
|
-
// dist/codieBrandInstall.js → ../assets/<name>; src/… → ../assets/<name>.
|
|
54
44
|
return path.join(__dirname, "..", "assets", name);
|
|
55
45
|
}
|
|
56
|
-
/**
|
|
57
|
-
* The usage module this install should load. The shipped package has the
|
|
58
|
-
* compiled file; a source checkout (and the test run) has the TypeScript one.
|
|
59
|
-
* Naming a path that does not exist would cost the status line silently, so a
|
|
60
|
-
* miss raises instead and the caller falls back to the plain plugin.
|
|
61
|
-
*/
|
|
62
46
|
function usageModulePath() {
|
|
63
47
|
const candidates = [
|
|
64
48
|
path.join(__dirname, "codieUsage.js"),
|
|
@@ -69,8 +53,6 @@ function usageModulePath() {
|
|
|
69
53
|
throw new Error("this install has no Codiedev usage module");
|
|
70
54
|
return found;
|
|
71
55
|
}
|
|
72
|
-
/** The engine resolves its config dir per the XDG spec, so a customized
|
|
73
|
-
* XDG_CONFIG_HOME must be honored or tui.json lands where it never reads. */
|
|
74
56
|
function openCodeConfigDir() {
|
|
75
57
|
return path.join(process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config"), "opencode");
|
|
76
58
|
}
|