codiedev 0.9.55 → 0.9.57
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/assets/tui-brand-plugin.tsx +61 -0
- package/dist/captureLog.d.ts +1 -1
- package/dist/codexLimitScan.d.ts +64 -0
- package/dist/codexLimitScan.js +466 -0
- package/dist/codie.js +33 -27
- package/dist/codieBrandInstall.d.ts +11 -0
- package/dist/codieBrandInstall.js +98 -0
- package/dist/codieLauncher.d.ts +33 -0
- package/dist/codieLauncher.js +77 -9
- package/dist/codieUsage.d.ts +57 -0
- package/dist/codieUsage.js +184 -0
- package/dist/hook.js +93 -1
- package/dist/limitEvent.js +26 -1
- package/package.json +1 -1
package/dist/hook.js
CHANGED
|
@@ -44,6 +44,7 @@ const http = __importStar(require("http"));
|
|
|
44
44
|
const child_process_1 = require("child_process");
|
|
45
45
|
const utils_1 = require("./utils");
|
|
46
46
|
const limitEventReporter_1 = require("./limitEventReporter");
|
|
47
|
+
const codexLimitScan_1 = require("./codexLimitScan");
|
|
47
48
|
const version_1 = require("./version");
|
|
48
49
|
const captureLog_1 = require("./captureLog");
|
|
49
50
|
const captureExclusions_1 = require("./captureExclusions");
|
|
@@ -143,10 +144,96 @@ async function runStopFailure(hookInput) {
|
|
|
143
144
|
await readAndPostLimitEvent(hookInput, "claude-code", true);
|
|
144
145
|
process.exit(0);
|
|
145
146
|
}
|
|
147
|
+
// Codex path. Two things differ from Claude Code:
|
|
148
|
+
//
|
|
149
|
+
// 1. There is no event that fires when a credit/rate-limit block kills a turn.
|
|
150
|
+
// Codex skips its Stop hook for exactly that turn (measured: every normal
|
|
151
|
+
// task_complete pairs with a Stop-hook run, the blocked one never does),
|
|
152
|
+
// and its HookEventName enum has no StopFailure equivalent. So the block is
|
|
153
|
+
// reported from the developer's NEXT prompt instead — see runCodexLimitSweep.
|
|
154
|
+
// 2. Rollouts run to tens of megabytes and keep growing after the block, so a
|
|
155
|
+
// fixed tail read loses the marker (in one real 44 MB rollout it sat 1.13 MB
|
|
156
|
+
// from the end). Scan backwards for it instead.
|
|
157
|
+
async function reportCodexLimitFromFile(config, transcriptPath, sessionId) {
|
|
158
|
+
const hit = (0, codexLimitScan_1.scanCodexRolloutForLimit)(transcriptPath);
|
|
159
|
+
if (!hit)
|
|
160
|
+
return false;
|
|
161
|
+
await (0, limitEventReporter_1.postLimitEvent)(config, {
|
|
162
|
+
source: "codex",
|
|
163
|
+
...hit.event,
|
|
164
|
+
sessionId: sessionId ?? (0, codexLimitScan_1.sessionIdFromRolloutPath)(transcriptPath),
|
|
165
|
+
transcriptTail: (0, codexLimitScan_1.readTailEndingAt)(transcriptPath, hit.lineEnd),
|
|
166
|
+
});
|
|
167
|
+
// Tell the prompt sweep this one is done, so it neither re-scans the
|
|
168
|
+
// rollout nor re-posts an event the server would only deduplicate.
|
|
169
|
+
(0, codexLimitScan_1.recordCodexLimitReported)(transcriptPath, hit.event.at);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
146
172
|
async function runCodexLimit(hookInput) {
|
|
147
|
-
|
|
173
|
+
const config = (0, utils_1.readConfig)();
|
|
174
|
+
const transcriptPath = hookInput.transcript_path;
|
|
175
|
+
if (config && transcriptPath) {
|
|
176
|
+
try {
|
|
177
|
+
const sent = await reportCodexLimitFromFile(config, transcriptPath, hookInput.session_id);
|
|
178
|
+
if (sent) {
|
|
179
|
+
(0, captureLog_1.appendCaptureLog)({
|
|
180
|
+
stage: "limit",
|
|
181
|
+
sessionId: hookInput.session_id,
|
|
182
|
+
outcome: "ok",
|
|
183
|
+
detail: "source=codex trigger=stop",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
(0, captureLog_1.appendCaptureLog)({
|
|
189
|
+
stage: "limit",
|
|
190
|
+
sessionId: hookInput.session_id,
|
|
191
|
+
outcome: "ERROR",
|
|
192
|
+
detail: `source=codex trigger=stop ${err.message}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
148
196
|
process.exit(0);
|
|
149
197
|
}
|
|
198
|
+
const PROMPT_LIMIT_POST_TIMEOUT_MS = 2_500;
|
|
199
|
+
// UserPromptSubmit: the first Codiedev hook that runs after a block, because
|
|
200
|
+
// Codex runs no hook at the block itself. Scans the recent rollouts for any
|
|
201
|
+
// block not yet reported. Cheap in the normal case — a per-file size cache
|
|
202
|
+
// means an unchanged, unblocked rollout costs one statSync.
|
|
203
|
+
async function runCodexLimitSweep(sessionId) {
|
|
204
|
+
const config = (0, utils_1.readConfig)();
|
|
205
|
+
if (!config)
|
|
206
|
+
return;
|
|
207
|
+
try {
|
|
208
|
+
const posted = await (0, codexLimitScan_1.sweepCodexLimits)({
|
|
209
|
+
sessionsDir: (0, codexLimitScan_1.codexSessionsDir)(),
|
|
210
|
+
// A shorter timeout than the default: this is on the user's prompt
|
|
211
|
+
// clock, and the next prompt retries anything that does not land.
|
|
212
|
+
post: (payload) => (0, limitEventReporter_1.postLimitEvent)(config, payload, PROMPT_LIMIT_POST_TIMEOUT_MS),
|
|
213
|
+
// Unchanged rollouts cost one statSync and do not spend the read
|
|
214
|
+
// budget, so in steady state this is nearly free. The module default
|
|
215
|
+
// covers a cold start; see DEFAULT_MAX_SCANS for why it is not small.
|
|
216
|
+
findOptions: { withinMs: 24 * 3600_000 },
|
|
217
|
+
scanOptions: { maxScanBytes: 4 * 1024 * 1024 },
|
|
218
|
+
});
|
|
219
|
+
if (posted > 0) {
|
|
220
|
+
(0, captureLog_1.appendCaptureLog)({
|
|
221
|
+
stage: "limit",
|
|
222
|
+
sessionId,
|
|
223
|
+
outcome: "ok",
|
|
224
|
+
detail: `source=codex trigger=prompt events=${posted}`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
(0, captureLog_1.appendCaptureLog)({
|
|
230
|
+
stage: "limit",
|
|
231
|
+
sessionId,
|
|
232
|
+
outcome: "ERROR",
|
|
233
|
+
detail: `source=codex trigger=prompt ${err.message}`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
150
237
|
// Skip-reason ping: this session is ending WITHOUT a capture, and here's why.
|
|
151
238
|
// Turns silent coverage loss into per-reason buckets on the admin Capture
|
|
152
239
|
// Health panel (patches the session's aiSessionStarts row server-side). Tiny
|
|
@@ -454,6 +541,11 @@ async function main() {
|
|
|
454
541
|
if (process.argv[2] === "discover" ||
|
|
455
542
|
process.argv[2] === "discover-codex" ||
|
|
456
543
|
process.argv[2] === "discover-codie") {
|
|
544
|
+
// A Codex block leaves no hook of its own, so this prompt is the first
|
|
545
|
+
// chance to report it. Runs before runDiscover, which exits the process.
|
|
546
|
+
if (process.argv[2] === "discover-codex") {
|
|
547
|
+
await runCodexLimitSweep(hookInput.session_id);
|
|
548
|
+
}
|
|
457
549
|
await runDiscover(hookInput.prompt);
|
|
458
550
|
return;
|
|
459
551
|
}
|
package/dist/limitEvent.js
CHANGED
|
@@ -51,11 +51,36 @@ function parseLimitEventFromTranscript(transcript) {
|
|
|
51
51
|
}
|
|
52
52
|
return found;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Codex's `rate_limit_reached_type` is exactly five values (read off the 0.154
|
|
56
|
+
* binary): `rate_limit_reached`, `workspace_{owner,member}_credits_depleted`
|
|
57
|
+
* and `workspace_{owner,member}_usage_limit_reached`.
|
|
58
|
+
*
|
|
59
|
+
* The split that matters to a reader of the dashboard is "someone must act on
|
|
60
|
+
* money" vs "this dev is throttled and it will reset" — so map on what the
|
|
61
|
+
* snapshot actually asserts, not on the word "credits":
|
|
62
|
+
*
|
|
63
|
+
* - A member out of their OWN allowance is a rate limit. Real snapshots carry
|
|
64
|
+
* `spend_control_reached: null` and `individual_limit: null` — nobody
|
|
65
|
+
* configured a budget, the window is simply used up. Calling that personal
|
|
66
|
+
* spend tells a manager the dev overspent, which is not what happened.
|
|
67
|
+
*
|
|
68
|
+
* Deliberately NOT gated on `individual_limit`: it has only ever been
|
|
69
|
+
* observed null, so its populated shape and meaning are unknown, and a
|
|
70
|
+
* wrong guess reinstates the mislabel this exists to remove. The reason
|
|
71
|
+
* string is the causal signal; `individual_limit` is at best correlative.
|
|
72
|
+
* - A member whose ADMIN capped them (`usage_limit_reached`) is personal spend:
|
|
73
|
+
* there really is a cap, and someone can raise it.
|
|
74
|
+
* - Anything owner-scoped is org spend — the org's allowance or credits are
|
|
75
|
+
* gone and somebody has to act.
|
|
76
|
+
*/
|
|
54
77
|
function codexLimitKind(reason, spendControlReached) {
|
|
55
78
|
if (spendControlReached || reason.startsWith("workspace_owner_"))
|
|
56
79
|
return "org_spend";
|
|
57
|
-
if (reason
|
|
80
|
+
if (reason === "workspace_member_usage_limit_reached")
|
|
58
81
|
return "user_spend";
|
|
82
|
+
if (reason === "workspace_member_credits_depleted")
|
|
83
|
+
return "session";
|
|
59
84
|
return reason === "rate_limit_reached" ? "session" : "other";
|
|
60
85
|
}
|
|
61
86
|
function codexResetText(rateLimits) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.57",
|
|
4
4
|
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|