weave-codex 0.1.0
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/LICENSES/MIT.txt +21 -0
- package/README.md +144 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +149 -0
- package/dist/cli.js.map +1 -0
- package/dist/collector.d.ts +19 -0
- package/dist/collector.js +162 -0
- package/dist/collector.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +42 -0
- package/dist/constants.js.map +1 -0
- package/dist/install.d.ts +14 -0
- package/dist/install.js +177 -0
- package/dist/install.js.map +1 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.js +67 -0
- package/dist/lock.js.map +1 -0
- package/dist/log.d.ts +4 -0
- package/dist/log.js +45 -0
- package/dist/log.js.map +1 -0
- package/dist/model.d.ts +58 -0
- package/dist/model.js +5 -0
- package/dist/model.js.map +1 -0
- package/dist/rollout/cursor.d.ts +13 -0
- package/dist/rollout/cursor.js +74 -0
- package/dist/rollout/cursor.js.map +1 -0
- package/dist/rollout/parser.d.ts +26 -0
- package/dist/rollout/parser.js +359 -0
- package/dist/rollout/parser.js.map +1 -0
- package/dist/rollout/types.d.ts +213 -0
- package/dist/rollout/types.js +33 -0
- package/dist/rollout/types.js.map +1 -0
- package/dist/semconv.d.ts +37 -0
- package/dist/semconv.js +41 -0
- package/dist/semconv.js.map +1 -0
- package/dist/spans/emit.d.ts +14 -0
- package/dist/spans/emit.js +143 -0
- package/dist/spans/emit.js.map +1 -0
- package/dist/spans/messages.d.ts +26 -0
- package/dist/spans/messages.js +38 -0
- package/dist/spans/messages.js.map +1 -0
- package/dist/spans/tracer.d.ts +20 -0
- package/dist/spans/tracer.js +43 -0
- package/dist/spans/tracer.js.map +1 -0
- package/package.json +48 -0
package/LICENSES/MIT.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 CoreWeave, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# weave-codex
|
|
2
|
+
|
|
3
|
+
W&B Weave observability for the **OpenAI Codex CLI**. It reconstructs `gen_ai`
|
|
4
|
+
OpenTelemetry spans for every Codex turn — model calls, token usage, tool calls,
|
|
5
|
+
reasoning — and ships them to [Weave](https://wandb.ai/site/weave), entirely off
|
|
6
|
+
Codex's critical path. The agent that's modeled after this is W&B's
|
|
7
|
+
[`weave-claude-code`](https://github.com/wandb/weave-claude-code); this is the
|
|
8
|
+
Codex equivalent.
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
Codex emits OTEL today, but not a clean `gen_ai` span tree. Rather than parse
|
|
13
|
+
that, `weave-codex` reconstructs spans from Codex's own **rollout session files**
|
|
14
|
+
(`~/.codex/sessions/**/rollout-*.jsonl`), which contain everything: per-turn
|
|
15
|
+
model, the full message/tool stream, and per-model-call token usage.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Codex turn ends
|
|
19
|
+
└─ Stop hook (fire-and-forget shell shim, ~ms, returns immediately)
|
|
20
|
+
└─ detached worker (off Codex's critical path)
|
|
21
|
+
├─ read new rollout lines since a per-session cursor
|
|
22
|
+
├─ reconstruct the turn → spans:
|
|
23
|
+
│ invoke_agent codex (the turn)
|
|
24
|
+
│ ├─ chat <model> (one per model call: usage, output)
|
|
25
|
+
│ └─ execute_tool <name> (one per tool: args, result)
|
|
26
|
+
└─ export via OTLP → trace.wandb.ai/agents/otel/v1/traces
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **Non-blocking by construction.** The Stop hook only spawns a detached process
|
|
30
|
+
and exits `0` (printing nothing), so Codex never waits on the network.
|
|
31
|
+
- **One trace per turn.** Turns are stitched into a conversation server-side via
|
|
32
|
+
`gen_ai.conversation.id` (the Codex session id) on every span.
|
|
33
|
+
- **Accurate timelines.** Span start/end times are backdated from the rollout
|
|
34
|
+
timestamps, so durations reflect what actually happened.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Node.js ≥ 20
|
|
39
|
+
- OpenAI Codex CLI with the hooks system (recent versions)
|
|
40
|
+
- A W&B account (`WANDB_API_KEY`) and a Weave project
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g weave-codex
|
|
46
|
+
|
|
47
|
+
# Credentials (precedence: env > settings.json > netrc)
|
|
48
|
+
wandb login # or: export WANDB_API_KEY=...
|
|
49
|
+
export WEAVE_PROJECT="entity/project"
|
|
50
|
+
|
|
51
|
+
weave-codex install # merges a Stop hook into ~/.codex/hooks.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**One-time trust step:** Codex marks newly-added hooks as _untrusted_ and will
|
|
55
|
+
not run them until you approve. On your next `codex` launch, approve the
|
|
56
|
+
`weave-codex` hook when prompted (or set `bypass_hook_trust = true` in
|
|
57
|
+
`~/.codex/config.toml`). Run `weave-codex status` to confirm everything resolves.
|
|
58
|
+
|
|
59
|
+
That's it — run Codex normally. Each completed turn appears in Weave within ~a
|
|
60
|
+
second.
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
| Setting | Env var | `settings.json` key | Default |
|
|
65
|
+
| --------------- | ----------------------------- | ------------------- | ------------------------------ |
|
|
66
|
+
| W&B API key | `WANDB_API_KEY` | `wandb_api_key` | from `wandb login` (netrc) |
|
|
67
|
+
| Weave project | `WEAVE_PROJECT` | `weave_project` | — (required, `entity/project`) |
|
|
68
|
+
| Base URL | `WANDB_BASE_URL` | `wandb_base_url` | `https://trace.wandb.ai` |
|
|
69
|
+
| Capture content | `WEAVE_CODEX_CAPTURE_CONTENT` | `capture_content` | `true` |
|
|
70
|
+
| Debug logging | `WEAVE_CODEX_DEBUG` | `debug` | off (errors always log) |
|
|
71
|
+
|
|
72
|
+
State lives in `~/.weave-codex/` (settings, the hook shim, per-session cursors,
|
|
73
|
+
and `logs/collector.log`). Set `WANDB_BASE_URL` for a dedicated/self-hosted W&B.
|
|
74
|
+
|
|
75
|
+
## Data disclosure
|
|
76
|
+
|
|
77
|
+
By default `weave-codex` captures span **content**: your prompts, the model's
|
|
78
|
+
responses and reasoning, tool-call arguments, and tool results (which include
|
|
79
|
+
shell commands, command output, and file contents). This data is sent to your
|
|
80
|
+
W&B/Weave instance. PII scrubbing and redaction are **not** implemented.
|
|
81
|
+
|
|
82
|
+
To send **structure, token usage, model, and timing only** — no prompts, code,
|
|
83
|
+
or output — disable content capture:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export WEAVE_CODEX_CAPTURE_CONTENT=0
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The structural/metric spans are always emitted regardless of this setting.
|
|
90
|
+
|
|
91
|
+
## What's captured
|
|
92
|
+
|
|
93
|
+
- `invoke_agent codex` — turn root; agent name/version, conversation id, model,
|
|
94
|
+
summed token usage, and (if enabled) the user prompt + final answer.
|
|
95
|
+
- `chat <model>` — one per model call; `gen_ai.usage.*` (input/output/cached/
|
|
96
|
+
reasoning) tokens, finish reason, `server.address`, and the assistant output
|
|
97
|
+
(text, reasoning, tool-call parts).
|
|
98
|
+
- `execute_tool <name>` — one per tool (shell, `apply_patch`, web search, MCP,
|
|
99
|
+
functions); `gen_ai.tool.*` id/arguments/result; MCP calls also carry
|
|
100
|
+
`mcp.server.name`.
|
|
101
|
+
|
|
102
|
+
All attributes follow the OpenTelemetry GenAI semantic conventions that Weave's
|
|
103
|
+
Agents ingestion reads, so traces also render in generic OTEL backends.
|
|
104
|
+
|
|
105
|
+
## Scope & limitations (v0.1)
|
|
106
|
+
|
|
107
|
+
- **Modes:** interactive `codex` (TUI) and `codex exec`. The `codex mcp` /
|
|
108
|
+
`app-server` modes are not covered (they fire no hooks).
|
|
109
|
+
- **Subagents:** a spawned subagent shows up as its `spawn_agent` tool call;
|
|
110
|
+
nesting the subagent's own model/tool calls is not yet implemented (Codex
|
|
111
|
+
writes those to a separate rollout file).
|
|
112
|
+
- **Aborted turns:** the `Stop` hook does not fire on aborted/errored turns, so
|
|
113
|
+
those are not captured.
|
|
114
|
+
- **Hook-locked orgs:** if `allow_managed_hooks_only` is set, use a `notify`
|
|
115
|
+
program as the trigger instead (see below).
|
|
116
|
+
- **Backdating shim:** the span emitter currently uses a thin OpenTelemetry
|
|
117
|
+
pipeline (`src/spans/tracer.ts`) because the Weave TS SDK's GenAI helpers can't
|
|
118
|
+
yet set historical timestamps. Once the SDK gains `startedAt`/`endedAt`, the
|
|
119
|
+
emitter moves to `weave.startTurn`/`startLLM`/`startTool`.
|
|
120
|
+
|
|
121
|
+
### `notify` fallback
|
|
122
|
+
|
|
123
|
+
In environments where you cannot add hooks, configure Codex's fire-and-forget
|
|
124
|
+
`notify` program to invoke the collector instead (turn-complete only):
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
# ~/.codex/config.toml
|
|
128
|
+
notify = ["sh", "/Users/you/.weave-codex/stop-hook.sh"]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Uninstall
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
weave-codex uninstall # removes only our entries from ~/.codex/hooks.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install
|
|
141
|
+
npm run build
|
|
142
|
+
npm test
|
|
143
|
+
npm run lint
|
|
144
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// SPDX-PackageName: weave-codex
|
|
5
|
+
/**
|
|
6
|
+
* `weave-codex` CLI: install | uninstall | status.
|
|
7
|
+
*
|
|
8
|
+
* install wires a Stop-hook into ~/.codex/hooks.json that triggers the detached
|
|
9
|
+
* collector. Credentials come from env / `wandb login` (netrc) / settings.json;
|
|
10
|
+
* we never prompt for the secret (only the non-secret project).
|
|
11
|
+
*/
|
|
12
|
+
import { createInterface } from 'node:readline/promises';
|
|
13
|
+
import { execPath, stdin, stdout } from 'node:process';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { configError, readSettings, resolveConfig, writeSettings, } from './config.js';
|
|
16
|
+
import { CODEX_HOOKS_FILE, ENV, LOG_FILE, SETTINGS_FILE, VERSION, } from './constants.js';
|
|
17
|
+
import { hooksInstalled, mergeHooks, removeHooks, writeShim } from './install.js';
|
|
18
|
+
// Absolute path to the compiled worker next to cli.js in dist/; baked into the
|
|
19
|
+
// hook shim at install so the Stop hook can run it regardless of cwd.
|
|
20
|
+
const COLLECTOR_JS = fileURLToPath(new URL('./collector.js', import.meta.url));
|
|
21
|
+
async function main() {
|
|
22
|
+
const command = process.argv[2] ?? 'help';
|
|
23
|
+
switch (command) {
|
|
24
|
+
case 'install':
|
|
25
|
+
await install();
|
|
26
|
+
break;
|
|
27
|
+
case 'uninstall':
|
|
28
|
+
await uninstall();
|
|
29
|
+
break;
|
|
30
|
+
case 'status':
|
|
31
|
+
await status();
|
|
32
|
+
break;
|
|
33
|
+
case 'help':
|
|
34
|
+
case '--help':
|
|
35
|
+
case '-h':
|
|
36
|
+
printHelp();
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
console.error(`unknown command: ${command}\n`);
|
|
40
|
+
printHelp();
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function install() {
|
|
45
|
+
const settings = await readSettings();
|
|
46
|
+
const project = await resolveProjectInput(settings);
|
|
47
|
+
if (project)
|
|
48
|
+
settings.weave_project = project;
|
|
49
|
+
if (settings.capture_content === undefined)
|
|
50
|
+
settings.capture_content = true;
|
|
51
|
+
if (settings.debug === undefined)
|
|
52
|
+
settings.debug = false;
|
|
53
|
+
settings.version = VERSION;
|
|
54
|
+
settings.installed_at = new Date().toISOString();
|
|
55
|
+
await writeSettings(settings);
|
|
56
|
+
const shim = await writeShim(execPath, COLLECTOR_JS);
|
|
57
|
+
const merged = await mergeHooksOrReport();
|
|
58
|
+
if (!merged) {
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const config = await resolveConfig();
|
|
63
|
+
const err = configError(config);
|
|
64
|
+
console.log(`weave-codex ${VERSION} installed`);
|
|
65
|
+
console.log(` shim: ${shim}`);
|
|
66
|
+
console.log(` hooks: ${CODEX_HOOKS_FILE}`);
|
|
67
|
+
console.log(` added: ${formatList(merged.added)} · already present: ${formatList(merged.alreadyPresent)}`);
|
|
68
|
+
console.log(` settings: ${SETTINGS_FILE}`);
|
|
69
|
+
console.log(` project: ${config.projectId || '(unset)'}`);
|
|
70
|
+
console.log(` api key: ${config.apiKey ? 'resolved' : '(unset)'}`);
|
|
71
|
+
if (err) {
|
|
72
|
+
console.log(`\n ⚠ ${err}`);
|
|
73
|
+
console.log(` Set ${ENV.WANDB_API_KEY} / run \`wandb login\`, and set ${ENV.WEAVE_PROJECT}.`);
|
|
74
|
+
}
|
|
75
|
+
console.log(`\n ⚠ Codex marks new hooks "untrusted". On the next \`codex\` launch, approve the` +
|
|
76
|
+
`\n weave-codex hook (or set \`bypass_hook_trust = true\` in ~/.codex/config.toml).`);
|
|
77
|
+
}
|
|
78
|
+
async function uninstall() {
|
|
79
|
+
const removed = await removeHooks();
|
|
80
|
+
console.log(`weave-codex uninstalled`);
|
|
81
|
+
console.log(` removed hooks: ${formatList(removed)}`);
|
|
82
|
+
console.log(` settings left intact at ${SETTINGS_FILE} (delete manually to fully remove)`);
|
|
83
|
+
}
|
|
84
|
+
async function status() {
|
|
85
|
+
const config = await resolveConfig();
|
|
86
|
+
const err = configError(config);
|
|
87
|
+
const installed = await hooksInstalled();
|
|
88
|
+
console.log(`weave-codex ${VERSION}`);
|
|
89
|
+
console.log(` hook installed: ${installed ? 'yes' : 'no'} (${CODEX_HOOKS_FILE})`);
|
|
90
|
+
console.log(` project: ${config.projectId || '(unset)'}`);
|
|
91
|
+
console.log(` api key: ${config.apiKey ? 'resolved' : '(unset)'}`);
|
|
92
|
+
console.log(` base url: ${config.baseUrl}`);
|
|
93
|
+
console.log(` capture content: ${config.captureContent}`);
|
|
94
|
+
console.log(` debug: ${config.debug}`);
|
|
95
|
+
console.log(` log file: ${LOG_FILE}`);
|
|
96
|
+
console.log(` status: ${err ? `not ready — ${err}` : 'ready'}`);
|
|
97
|
+
}
|
|
98
|
+
function printHelp() {
|
|
99
|
+
console.log(`weave-codex ${VERSION} — W&B Weave observability for OpenAI Codex CLI
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
weave-codex install Wire the Stop hook into ~/.codex/hooks.json
|
|
103
|
+
weave-codex uninstall Remove the weave-codex hook entries
|
|
104
|
+
weave-codex status Show resolved config and hook state
|
|
105
|
+
|
|
106
|
+
Config (precedence: env > settings.json > netrc):
|
|
107
|
+
${ENV.WANDB_API_KEY} W&B API key (or run \`wandb login\`)
|
|
108
|
+
${ENV.WEAVE_PROJECT} Weave project as "entity/project"
|
|
109
|
+
${ENV.WANDB_BASE_URL} Override for dedicated/self-hosted W&B
|
|
110
|
+
${ENV.CAPTURE_CONTENT} Set to 0 to drop prompts/code/output from spans`);
|
|
111
|
+
}
|
|
112
|
+
function formatList(items) {
|
|
113
|
+
return items.length > 0 ? items.join(', ') : 'none';
|
|
114
|
+
}
|
|
115
|
+
async function prompt(question) {
|
|
116
|
+
const readline = createInterface({ input: stdin, output: stdout });
|
|
117
|
+
try {
|
|
118
|
+
return await readline.question(question);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
readline.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function resolveProjectInput(settings) {
|
|
125
|
+
const configured = process.env[ENV.WEAVE_PROJECT] ?? settings.weave_project;
|
|
126
|
+
if (configured)
|
|
127
|
+
return configured;
|
|
128
|
+
// Prompt only for the non-secret project, and only when attended.
|
|
129
|
+
if (stdin.isTTY)
|
|
130
|
+
return (await prompt('Weave project (entity/project): ')).trim();
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
// Merge our Stop hook, or report the failure (e.g. a corrupt hooks.json) and
|
|
134
|
+
// return null so the caller aborts without overwriting anything.
|
|
135
|
+
async function mergeHooksOrReport() {
|
|
136
|
+
try {
|
|
137
|
+
return await mergeHooks();
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`\n ⚠ could not update ${CODEX_HOOKS_FILE}; nothing was changed:`);
|
|
141
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
void main().catch(err => {
|
|
146
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type ResolvedConfig } from './config.js';
|
|
3
|
+
import { type TracerHandle } from './spans/tracer.js';
|
|
4
|
+
/** The Codex Stop-hook payload (handed to the worker as JSON); only the fields we read. */
|
|
5
|
+
interface StopPayload {
|
|
6
|
+
session_id: string;
|
|
7
|
+
turn_id?: string;
|
|
8
|
+
transcript_path?: string | null;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
last_assistant_message?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reconstruct + emit the just-finished turn(s). `makeTracer` is injected so a
|
|
15
|
+
* test can drive the whole pipeline with an in-memory exporter (and assert the
|
|
16
|
+
* cursor advances only on a successful export).
|
|
17
|
+
*/
|
|
18
|
+
export declare function collect(transcriptPath: string, sessionId: string, payload: StopPayload, config: ResolvedConfig, makeTracer?: (config: ResolvedConfig) => TracerHandle): Promise<void>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// SPDX-PackageName: weave-codex
|
|
5
|
+
/**
|
|
6
|
+
* The detached worker. Invoked by the Stop-hook shim as:
|
|
7
|
+
* node dist/collector.js <payload-json-file>
|
|
8
|
+
*
|
|
9
|
+
* Reads the just-finished turn from the rollout, reconstructs spans, ships them
|
|
10
|
+
* to Weave, then exits. Fully off Codex's critical path; it never throws into
|
|
11
|
+
* the calling process and never blocks Codex (the shim has already returned).
|
|
12
|
+
*/
|
|
13
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
14
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
15
|
+
import { configError, resolveConfig } from './config.js';
|
|
16
|
+
import { acquireLock } from './lock.js';
|
|
17
|
+
import { log, setVerbose } from './log.js';
|
|
18
|
+
import { readNewLines, readOffset, writeOffset } from './rollout/cursor.js';
|
|
19
|
+
import { parseLineMaybe, reconstructTurns, } from './rollout/parser.js';
|
|
20
|
+
import { emitTurn } from './spans/emit.js';
|
|
21
|
+
import { buildTracer } from './spans/tracer.js';
|
|
22
|
+
// Rollout writer flushes per line, but the OS write can lag the Stop hook.
|
|
23
|
+
const ROLLOUT_READ_RETRY_ATTEMPTS = 5;
|
|
24
|
+
const ROLLOUT_READ_RETRY_DELAY_MS = 200;
|
|
25
|
+
async function main() {
|
|
26
|
+
const payload = await readPayload();
|
|
27
|
+
const sessionId = payload.session_id;
|
|
28
|
+
const config = await resolveConfig();
|
|
29
|
+
setVerbose(config.debug);
|
|
30
|
+
const transcriptPath = payload.transcript_path;
|
|
31
|
+
if (!transcriptPath) {
|
|
32
|
+
log('warn', 'stop payload missing transcript_path; skipping', { sessionId });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const configErr = configError(config);
|
|
36
|
+
if (configErr) {
|
|
37
|
+
log('error', `not configured: ${configErr}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Serialize per session so two workers never double-emit or rewind the cursor.
|
|
41
|
+
const release = await acquireLock(sessionId);
|
|
42
|
+
if (!release) {
|
|
43
|
+
log('info', 'another worker holds this session; skipping (a later stop catches up)', {
|
|
44
|
+
sessionId,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await collect(transcriptPath, sessionId, payload, config);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await release();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reconstruct + emit the just-finished turn(s). `makeTracer` is injected so a
|
|
57
|
+
* test can drive the whole pipeline with an in-memory exporter (and assert the
|
|
58
|
+
* cursor advances only on a successful export).
|
|
59
|
+
*/
|
|
60
|
+
export async function collect(transcriptPath, sessionId, payload, config, makeTracer = buildTracer) {
|
|
61
|
+
const fromOffset = await readOffset(sessionId);
|
|
62
|
+
let rawLines = [];
|
|
63
|
+
let result = {
|
|
64
|
+
turns: [],
|
|
65
|
+
consumedLines: 0,
|
|
66
|
+
abandonedTurns: 0,
|
|
67
|
+
};
|
|
68
|
+
// The Stop hook can fire just before Codex's background writer flushes the
|
|
69
|
+
// turn's final lines to the rollout, so the turn may be missing or incomplete
|
|
70
|
+
// on the first read. Re-read a few times until the turn is present and its
|
|
71
|
+
// final text matches the Stop payload's last_assistant_message. This runs in
|
|
72
|
+
// the detached worker, off Codex's critical path, so the wait is unnoticeable.
|
|
73
|
+
for (let attempt = 0; attempt < ROLLOUT_READ_RETRY_ATTEMPTS; attempt++) {
|
|
74
|
+
const read = await readNewLines(transcriptPath, fromOffset);
|
|
75
|
+
rawLines = read.lines;
|
|
76
|
+
result = reconstructTurns(rawLines.map(parseLineMaybe), {
|
|
77
|
+
fallbackSessionId: sessionId,
|
|
78
|
+
fallbackModel: payload.model,
|
|
79
|
+
});
|
|
80
|
+
if (targetReady(result.turns, payload))
|
|
81
|
+
break;
|
|
82
|
+
if (rawLines.length === 0)
|
|
83
|
+
break; // nothing appended yet; no in-flight turn to wait for
|
|
84
|
+
await sleep(ROLLOUT_READ_RETRY_DELAY_MS);
|
|
85
|
+
}
|
|
86
|
+
if (result.abandonedTurns > 0) {
|
|
87
|
+
log('warn', `dropped ${result.abandonedTurns} abandoned turn(s) with no task_complete`, {
|
|
88
|
+
sessionId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (result.turns.length === 0) {
|
|
92
|
+
log('warn', 'no complete turn yet; will catch it on the next stop', {
|
|
93
|
+
sessionId,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const { tracer, shutdown } = makeTracer(config);
|
|
98
|
+
let exported = false;
|
|
99
|
+
try {
|
|
100
|
+
for (const turn of result.turns) {
|
|
101
|
+
emitTurn(tracer, turn, config.captureContent);
|
|
102
|
+
}
|
|
103
|
+
// shutdown() flushes then tears down; it rejects on a transport failure, so
|
|
104
|
+
// reaching the next statement means the endpoint accepted the spans.
|
|
105
|
+
await shutdown();
|
|
106
|
+
exported = true;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
log('error', `emit/export failed: ${errMessage(err)}`);
|
|
110
|
+
await shutdown().catch(() => { }); // ensure teardown even if emitTurn threw first
|
|
111
|
+
}
|
|
112
|
+
if (!exported)
|
|
113
|
+
return; // leave the cursor untouched so the next stop retries
|
|
114
|
+
const committed = fromOffset + byteLengthOfLines(rawLines, result.consumedLines);
|
|
115
|
+
await writeOffset(sessionId, committed);
|
|
116
|
+
log('info', `emitted ${result.turns.length} turn(s)`, {
|
|
117
|
+
sessionId,
|
|
118
|
+
chats: result.turns.reduce((n, t) => n + t.chats.length, 0),
|
|
119
|
+
tools: result.turns.reduce((n, t) => n + t.tools.length, 0),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** True once the just-finished turn is present and looks fully flushed. */
|
|
123
|
+
function targetReady(turns, payload) {
|
|
124
|
+
if (turns.length === 0)
|
|
125
|
+
return false;
|
|
126
|
+
// With a turn_id, wait for that exact turn; without one, wait for the latest.
|
|
127
|
+
const target = payload.turn_id
|
|
128
|
+
? turns.find(t => t.turnId === payload.turn_id)
|
|
129
|
+
: turns[turns.length - 1];
|
|
130
|
+
if (!target)
|
|
131
|
+
return false;
|
|
132
|
+
if (!payload.last_assistant_message)
|
|
133
|
+
return true;
|
|
134
|
+
const got = (target.finalAssistantMessage ?? '').trimEnd();
|
|
135
|
+
return (got.length > 0 && got.endsWith(payload.last_assistant_message.trimEnd()));
|
|
136
|
+
}
|
|
137
|
+
function byteLengthOfLines(lines, count) {
|
|
138
|
+
let bytes = 0;
|
|
139
|
+
for (let i = 0; i < count && i < lines.length; i++) {
|
|
140
|
+
bytes += Buffer.byteLength(lines[i], 'utf8') + 1;
|
|
141
|
+
}
|
|
142
|
+
return bytes;
|
|
143
|
+
}
|
|
144
|
+
async function readPayload() {
|
|
145
|
+
const file = process.argv[2];
|
|
146
|
+
if (!file)
|
|
147
|
+
return JSON.parse(await readStdin());
|
|
148
|
+
const raw = await readFile(file, 'utf8');
|
|
149
|
+
await unlink(file).catch(() => { }); // the shim's temp file; clean up best-effort
|
|
150
|
+
return JSON.parse(raw);
|
|
151
|
+
}
|
|
152
|
+
async function readStdin() {
|
|
153
|
+
const chunks = [];
|
|
154
|
+
for await (const chunk of process.stdin)
|
|
155
|
+
chunks.push(chunk);
|
|
156
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
157
|
+
}
|
|
158
|
+
function errMessage(err) {
|
|
159
|
+
return err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
160
|
+
}
|
|
161
|
+
void main().catch(err => log('error', `unhandled: ${errMessage(err)}`));
|
|
162
|
+
//# sourceMappingURL=collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector.js","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface Settings {
|
|
2
|
+
wandb_api_key?: string;
|
|
3
|
+
weave_project?: string;
|
|
4
|
+
wandb_base_url?: string;
|
|
5
|
+
capture_content?: boolean;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
version?: string;
|
|
8
|
+
installed_at?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ResolvedConfig {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** `entity/project` — the value of the project_id ingest header. */
|
|
13
|
+
projectId: string;
|
|
14
|
+
entity: string;
|
|
15
|
+
project: string;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
captureContent: boolean;
|
|
18
|
+
debug: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function readSettings(): Promise<Settings>;
|
|
21
|
+
export declare function writeSettings(settings: Settings): Promise<void>;
|
|
22
|
+
export declare function resolveConfig(preloaded?: Settings): Promise<ResolvedConfig>;
|
|
23
|
+
export declare function configError(config: ResolvedConfig): string | undefined;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Resolve runtime config for the worker and CLI.
|
|
6
|
+
*
|
|
7
|
+
* Precedence: environment > ~/.weave-codex/settings.json > ~/.netrc. Credentials
|
|
8
|
+
* are resolved locally (no network) so the worker stays fast and self-contained,
|
|
9
|
+
* off Codex's critical path.
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { DEFAULT_TRACE_BASE_URL, ENV, SETTINGS_FILE } from './constants.js';
|
|
15
|
+
/** W&B stores the API key under this netrc machine even for trace.wandb.ai. */
|
|
16
|
+
const NETRC_MACHINE = 'api.wandb.ai';
|
|
17
|
+
/** Default when neither env nor settings specify it. */
|
|
18
|
+
const DEFAULT_CAPTURE_CONTENT = true;
|
|
19
|
+
export async function readSettings() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(await fs.readFile(SETTINGS_FILE, 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeSettings(settings) {
|
|
28
|
+
await fs.mkdir(join(SETTINGS_FILE, '..'), { recursive: true });
|
|
29
|
+
await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2), {
|
|
30
|
+
mode: 0o600,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function resolveConfig(preloaded) {
|
|
34
|
+
const settings = preloaded ?? (await readSettings());
|
|
35
|
+
const baseUrl = (process.env[ENV.WANDB_BASE_URL] ??
|
|
36
|
+
settings.wandb_base_url ??
|
|
37
|
+
DEFAULT_TRACE_BASE_URL).replace(/\/+$/, '');
|
|
38
|
+
const apiKey = process.env[ENV.WANDB_API_KEY] ??
|
|
39
|
+
settings.wandb_api_key ??
|
|
40
|
+
(await apiKeyFromNetrc()) ??
|
|
41
|
+
'';
|
|
42
|
+
const projectId = process.env[ENV.WEAVE_PROJECT] ?? settings.weave_project ?? '';
|
|
43
|
+
const { entity, project } = splitProject(projectId);
|
|
44
|
+
const captureContent = parseBool(process.env[ENV.CAPTURE_CONTENT]) ??
|
|
45
|
+
settings.capture_content ??
|
|
46
|
+
DEFAULT_CAPTURE_CONTENT;
|
|
47
|
+
const debug = parseBool(process.env[ENV.DEBUG]) ?? settings.debug ?? false;
|
|
48
|
+
return { apiKey, projectId, entity, project, baseUrl, captureContent, debug };
|
|
49
|
+
}
|
|
50
|
+
export function configError(config) {
|
|
51
|
+
if (!config.apiKey)
|
|
52
|
+
return `missing API key (set ${ENV.WANDB_API_KEY} or run \`weave-codex install\`)`;
|
|
53
|
+
if (!config.projectId)
|
|
54
|
+
return `missing project (set ${ENV.WEAVE_PROJECT} as "entity/project")`;
|
|
55
|
+
if (!config.entity ||
|
|
56
|
+
!config.project ||
|
|
57
|
+
config.projectId.indexOf('/') !== config.projectId.lastIndexOf('/')) {
|
|
58
|
+
return `project "${config.projectId}" must be in "entity/project" form (exactly one "/")`;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function splitProject(value) {
|
|
63
|
+
const slash = value.indexOf('/');
|
|
64
|
+
if (slash === -1)
|
|
65
|
+
return { entity: '', project: value };
|
|
66
|
+
return { entity: value.slice(0, slash), project: value.slice(slash + 1) };
|
|
67
|
+
}
|
|
68
|
+
function parseBool(value) {
|
|
69
|
+
if (value === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
const normalized = value.trim().toLowerCase();
|
|
72
|
+
if (normalized === '0' || normalized === 'false' || normalized === 'no')
|
|
73
|
+
return false;
|
|
74
|
+
if (normalized === '1' || normalized === 'true' || normalized === 'yes')
|
|
75
|
+
return true;
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
async function apiKeyFromNetrc() {
|
|
79
|
+
try {
|
|
80
|
+
const raw = await fs.readFile(join(homedir(), '.netrc'), 'utf8');
|
|
81
|
+
// Strip comments, then tokenize on whitespace (the common netrc form).
|
|
82
|
+
const tokens = raw.replace(/#.*$/gm, '').split(/\s+/).filter(Boolean);
|
|
83
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
84
|
+
if (tokens[i] === 'machine' && tokens[i + 1] === NETRC_MACHINE) {
|
|
85
|
+
for (let j = i + 2; j < tokens.length; j++) {
|
|
86
|
+
if (tokens[j] === 'machine' || tokens[j] === 'default')
|
|
87
|
+
break; // entry ended
|
|
88
|
+
if (tokens[j] === 'password' && j + 1 < tokens.length)
|
|
89
|
+
return tokens[j + 1];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// no netrc / unreadable → fall through
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|