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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const WEAVE_CODEX_HOME: string;
|
|
2
|
+
export declare const SETTINGS_FILE: string;
|
|
3
|
+
export declare const LOG_FILE: string;
|
|
4
|
+
export declare const CODEX_HOME: string;
|
|
5
|
+
export declare const CODEX_HOOKS_FILE: string;
|
|
6
|
+
export declare const DEFAULT_TRACE_BASE_URL = "https://trace.wandb.ai";
|
|
7
|
+
export declare const GENAI_OTLP_PATH = "/agents/otel/v1/traces";
|
|
8
|
+
export declare const ENV: {
|
|
9
|
+
readonly WANDB_API_KEY: "WANDB_API_KEY";
|
|
10
|
+
readonly WANDB_BASE_URL: "WANDB_BASE_URL";
|
|
11
|
+
readonly WEAVE_PROJECT: "WEAVE_PROJECT";
|
|
12
|
+
/** Set to "0"/"false" to drop all prompt/code/output content from spans. */
|
|
13
|
+
readonly CAPTURE_CONTENT: "WEAVE_CODEX_CAPTURE_CONTENT";
|
|
14
|
+
readonly DEBUG: "WEAVE_CODEX_DEBUG";
|
|
15
|
+
};
|
|
16
|
+
export declare const VERSION: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Cross-cutting constants shared across modules: filesystem paths, environment
|
|
6
|
+
* variable names, the package version, and the Weave ingest endpoint. Constants
|
|
7
|
+
* used by only one module live at the top of that module instead.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
// --- Our own state lives under ~/.weave-codex (override for tests via env) ---
|
|
13
|
+
export const WEAVE_CODEX_HOME = process.env.WEAVE_CODEX_HOME ?? join(homedir(), '.weave-codex');
|
|
14
|
+
export const SETTINGS_FILE = join(WEAVE_CODEX_HOME, 'settings.json');
|
|
15
|
+
export const LOG_FILE = join(WEAVE_CODEX_HOME, 'logs', 'collector.log');
|
|
16
|
+
// --- Codex layout we read (rollouts) and modify (hooks.json) ---
|
|
17
|
+
export const CODEX_HOME = process.env.CODEX_HOME ?? join(homedir(), '.codex');
|
|
18
|
+
export const CODEX_HOOKS_FILE = join(CODEX_HOME, 'hooks.json');
|
|
19
|
+
// --- Weave / W&B trace ingest (external contract; kept central) ---
|
|
20
|
+
export const DEFAULT_TRACE_BASE_URL = 'https://trace.wandb.ai';
|
|
21
|
+
// Agents OTLP path (not the legacy /otel/v1/traces calls path) — feeds the Agents UI.
|
|
22
|
+
export const GENAI_OTLP_PATH = '/agents/otel/v1/traces';
|
|
23
|
+
// --- Environment variable names (referenced, never inlined as literals) ---
|
|
24
|
+
export const ENV = {
|
|
25
|
+
WANDB_API_KEY: 'WANDB_API_KEY',
|
|
26
|
+
WANDB_BASE_URL: 'WANDB_BASE_URL',
|
|
27
|
+
WEAVE_PROJECT: 'WEAVE_PROJECT',
|
|
28
|
+
/** Set to "0"/"false" to drop all prompt/code/output content from spans. */
|
|
29
|
+
CAPTURE_CONTENT: 'WEAVE_CODEX_CAPTURE_CONTENT',
|
|
30
|
+
DEBUG: 'WEAVE_CODEX_DEBUG',
|
|
31
|
+
};
|
|
32
|
+
// --- Version, read from package.json so it can never drift from a hardcoded copy ---
|
|
33
|
+
// We ship package.json with the package, so an unreadable file or a missing
|
|
34
|
+
// version is a packaging bug — let it throw loudly rather than mask it as 0.0.0.
|
|
35
|
+
function readVersion() {
|
|
36
|
+
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
37
|
+
if (!version)
|
|
38
|
+
throw new Error('weave-codex: package.json is missing a "version" field');
|
|
39
|
+
return version;
|
|
40
|
+
}
|
|
41
|
+
export const VERSION = readVersion();
|
|
42
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,+BAA+B;AAC/B,gCAAgC;AAEhC;;;;GAIG;AACH,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAE/B,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAC3B,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;AAExE,kEAAkE;AAClE,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAE/D,qEAAqE;AACrE,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAExD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,aAAa,EAAE,eAAe;IAC9B,cAAc,EAAE,gBAAgB;IAChC,aAAa,EAAE,eAAe;IAC9B,4EAA4E;IAC5E,eAAe,EAAE,6BAA6B;IAC9C,KAAK,EAAE,mBAAmB;CAClB,CAAC;AAEX,sFAAsF;AACtF,4EAA4E;AAC5E,iFAAiF;AACjF,SAAS,WAAW;IAClB,MAAM,EAAC,OAAO,EAAC,GAAG,IAAI,CAAC,KAAK,CAC1B,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC5C,CAAC;IACxB,IAAI,CAAC,OAAO;QACV,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,OAAO,OAAO,CAAC;AACjB,CAAC;AACD,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hooks.json command. Run via shell (Codex expands `~`). The trailing `#`
|
|
3
|
+
* comment is ignored on execution but gives us a stable marker to match on.
|
|
4
|
+
*/
|
|
5
|
+
export declare function hookCommand(): string;
|
|
6
|
+
export declare function writeShim(nodePath: string, collectorJs: string): Promise<string>;
|
|
7
|
+
interface MergeResult {
|
|
8
|
+
added: string[];
|
|
9
|
+
alreadyPresent: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function mergeHooks(): Promise<MergeResult>;
|
|
12
|
+
export declare function removeHooks(): Promise<string[]>;
|
|
13
|
+
export declare function hooksInstalled(): Promise<boolean>;
|
|
14
|
+
export {};
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Install/uninstall mechanics: write the detach shim and merge our Stop entry
|
|
6
|
+
* into ~/.codex/hooks.json without clobbering anything.
|
|
7
|
+
*
|
|
8
|
+
* hooks.json is array-based per event, so we APPEND our entry and remove only
|
|
9
|
+
* our own hook (not the enclosing group) on uninstall. We refuse to overwrite a
|
|
10
|
+
* corrupt hooks.json, and back it up only once (never clobbering the pristine
|
|
11
|
+
* copy). Our entry is tagged with a stable marker comment so identification
|
|
12
|
+
* survives a changed install path.
|
|
13
|
+
*/
|
|
14
|
+
import { promises as fs } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { CODEX_HOOKS_FILE, LOG_FILE, WEAVE_CODEX_HOME } from './constants.js';
|
|
17
|
+
const HOOK_SHIM_FILE = join(WEAVE_CODEX_HOME, 'stop-hook.sh');
|
|
18
|
+
/**
|
|
19
|
+
* v1 registers only Stop. SubagentStop would fire a redundant worker on the
|
|
20
|
+
* PARENT rollout (racing the parent's own Stop) — we re-add it when subagent
|
|
21
|
+
* nesting lands and reads the child transcript.
|
|
22
|
+
*/
|
|
23
|
+
const HOOK_EVENTS = ['Stop'];
|
|
24
|
+
const HOOK_TIMEOUT_SEC = 30;
|
|
25
|
+
/** Stable identity for our hook, independent of the install path. */
|
|
26
|
+
const OWNER_MARKER = 'weave-codex-hook';
|
|
27
|
+
/** POSIX single-quote escaping so apostrophes/backslashes in paths can't break the shell. */
|
|
28
|
+
function shellQuote(value) {
|
|
29
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The hooks.json command. Run via shell (Codex expands `~`). The trailing `#`
|
|
33
|
+
* comment is ignored on execution but gives us a stable marker to match on.
|
|
34
|
+
*/
|
|
35
|
+
export function hookCommand() {
|
|
36
|
+
return `sh ${shellQuote(HOOK_SHIM_FILE)} # ${OWNER_MARKER}`;
|
|
37
|
+
}
|
|
38
|
+
function shimContents(nodePath, collectorJs) {
|
|
39
|
+
return `#!/bin/sh
|
|
40
|
+
# weave-codex Stop-hook shim (generated by \`weave-codex install\`).
|
|
41
|
+
# Hands the Stop payload to a fully-detached worker and returns immediately so
|
|
42
|
+
# Codex is NEVER blocked. Emits nothing on stdout (a stdout decision payload
|
|
43
|
+
# would alter Codex's turn flow).
|
|
44
|
+
NODE=${shellQuote(nodePath)}
|
|
45
|
+
COLLECTOR=${shellQuote(collectorJs)}
|
|
46
|
+
LOG=${shellQuote(LOG_FILE)}
|
|
47
|
+
mkdir -p "$(dirname "$LOG")" 2>/dev/null
|
|
48
|
+
TMP="$(mktemp "\${TMPDIR:-/tmp}/weave-codex.XXXXXX")" || exit 0
|
|
49
|
+
cat > "$TMP"
|
|
50
|
+
# Detach: background subshell with all fds redirected away from Codex's pipes.
|
|
51
|
+
( "$NODE" "$COLLECTOR" "$TMP" >>"$LOG" 2>&1 </dev/null & ) >/dev/null 2>&1
|
|
52
|
+
exit 0
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
export async function writeShim(nodePath, collectorJs) {
|
|
56
|
+
await fs.mkdir(WEAVE_CODEX_HOME, { recursive: true });
|
|
57
|
+
await fs.writeFile(HOOK_SHIM_FILE, shimContents(nodePath, collectorJs), {
|
|
58
|
+
mode: 0o755,
|
|
59
|
+
});
|
|
60
|
+
return HOOK_SHIM_FILE;
|
|
61
|
+
}
|
|
62
|
+
export async function mergeHooks() {
|
|
63
|
+
const file = await readHooksFile(); // throws on corrupt JSON → caller aborts without writing
|
|
64
|
+
const command = hookCommand();
|
|
65
|
+
const hooks = (file.hooks ??= {});
|
|
66
|
+
const added = [];
|
|
67
|
+
const alreadyPresent = [];
|
|
68
|
+
for (const event of HOOK_EVENTS) {
|
|
69
|
+
const groups = (hooks[event] ??= []);
|
|
70
|
+
if (groups.some(isOurGroup)) {
|
|
71
|
+
alreadyPresent.push(event);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
groups.push({
|
|
75
|
+
hooks: [{ type: 'command', command, timeout: HOOK_TIMEOUT_SEC }],
|
|
76
|
+
});
|
|
77
|
+
added.push(event);
|
|
78
|
+
}
|
|
79
|
+
if (added.length > 0) {
|
|
80
|
+
await backupHooksFile();
|
|
81
|
+
await writeHooksFile(file);
|
|
82
|
+
}
|
|
83
|
+
return { added, alreadyPresent };
|
|
84
|
+
}
|
|
85
|
+
export async function removeHooks() {
|
|
86
|
+
let file;
|
|
87
|
+
try {
|
|
88
|
+
file = await readHooksFile();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return []; // missing or corrupt — nothing safe to do
|
|
92
|
+
}
|
|
93
|
+
if (!file.hooks)
|
|
94
|
+
return [];
|
|
95
|
+
const removed = [];
|
|
96
|
+
for (const event of HOOK_EVENTS) {
|
|
97
|
+
const groups = file.hooks[event];
|
|
98
|
+
if (!groups)
|
|
99
|
+
continue;
|
|
100
|
+
const nextGroups = [];
|
|
101
|
+
for (const group of groups) {
|
|
102
|
+
const original = group.hooks ?? [];
|
|
103
|
+
const kept = original.filter(h => !isOurHook(h)); // remove only OUR hook
|
|
104
|
+
if (kept.length === original.length) {
|
|
105
|
+
nextGroups.push(group); // untouched
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
removed.push(event);
|
|
109
|
+
if (kept.length > 0)
|
|
110
|
+
nextGroups.push({ ...group, hooks: kept }); // preserve co-located hooks
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (nextGroups.length > 0)
|
|
114
|
+
file.hooks[event] = nextGroups;
|
|
115
|
+
else
|
|
116
|
+
delete file.hooks[event];
|
|
117
|
+
}
|
|
118
|
+
if (removed.length > 0) {
|
|
119
|
+
await backupHooksFile();
|
|
120
|
+
await writeHooksFile(file);
|
|
121
|
+
}
|
|
122
|
+
return removed;
|
|
123
|
+
}
|
|
124
|
+
export async function hooksInstalled() {
|
|
125
|
+
try {
|
|
126
|
+
const file = await readHooksFile();
|
|
127
|
+
return HOOK_EVENTS.some(e => (file.hooks?.[e] ?? []).some(isOurGroup));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function isOurHook(handler) {
|
|
134
|
+
return (typeof handler.command === 'string' &&
|
|
135
|
+
handler.command.includes(OWNER_MARKER));
|
|
136
|
+
}
|
|
137
|
+
function isOurGroup(group) {
|
|
138
|
+
return (group.hooks ?? []).some(isOurHook);
|
|
139
|
+
}
|
|
140
|
+
async function readHooksFile() {
|
|
141
|
+
let raw;
|
|
142
|
+
try {
|
|
143
|
+
raw = await fs.readFile(CODEX_HOOKS_FILE, 'utf8');
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (err.code === 'ENOENT')
|
|
147
|
+
return {}; // no file yet — fine
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(raw);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new Error(`${CODEX_HOOKS_FILE} is not valid JSON — refusing to overwrite it. Fix or remove it, then re-run.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function writeHooksFile(file) {
|
|
158
|
+
await fs.mkdir(dirname(CODEX_HOOKS_FILE), { recursive: true });
|
|
159
|
+
await fs.writeFile(CODEX_HOOKS_FILE, JSON.stringify(file, null, 2));
|
|
160
|
+
}
|
|
161
|
+
async function backupHooksFile() {
|
|
162
|
+
const backup = `${CODEX_HOOKS_FILE}.bak`;
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(backup);
|
|
165
|
+
return; // a backup already exists; never clobber the pristine copy
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// no backup yet
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await fs.writeFile(backup, await fs.readFile(CODEX_HOOKS_FILE));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// nothing to back up
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
|
package/dist/lock.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type ReleaseLock = () => Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* Returns a release function if the lock was acquired, or null if another live
|
|
4
|
+
* worker holds it (caller should skip — a later Stop re-reads from the cursor).
|
|
5
|
+
*/
|
|
6
|
+
export declare function acquireLock(sessionId: string): Promise<ReleaseLock | null>;
|
|
7
|
+
export {};
|
package/dist/lock.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Per-session advisory lock so two collector workers never process the same
|
|
6
|
+
* session concurrently (which would double-emit spans or rewind the cursor).
|
|
7
|
+
*
|
|
8
|
+
* Uses an O_EXCL create as the lock primitive. A lock older than LOCK_STALE_MS
|
|
9
|
+
* is assumed orphaned by a crashed worker and stolen, so a killed worker can
|
|
10
|
+
* never wedge a session permanently.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { promises as fs } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { WEAVE_CODEX_HOME } from './constants.js';
|
|
16
|
+
const LOCK_DIR = join(WEAVE_CODEX_HOME, 'locks');
|
|
17
|
+
/** A lock older than this is assumed orphaned by a crashed worker and stealable. */
|
|
18
|
+
const LOCK_STALE_MS = 60_000;
|
|
19
|
+
function lockPath(sessionId) {
|
|
20
|
+
const safe = createHash('sha256')
|
|
21
|
+
.update(sessionId)
|
|
22
|
+
.digest('hex')
|
|
23
|
+
.slice(0, 32);
|
|
24
|
+
return join(LOCK_DIR, `${safe}.lock`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns a release function if the lock was acquired, or null if another live
|
|
28
|
+
* worker holds it (caller should skip — a later Stop re-reads from the cursor).
|
|
29
|
+
*/
|
|
30
|
+
export async function acquireLock(sessionId) {
|
|
31
|
+
await fs.mkdir(LOCK_DIR, { recursive: true });
|
|
32
|
+
const path = lockPath(sessionId);
|
|
33
|
+
if (await tryCreate(path))
|
|
34
|
+
return makeRelease(path);
|
|
35
|
+
if (await isStale(path)) {
|
|
36
|
+
await fs.unlink(path).catch(() => { });
|
|
37
|
+
if (await tryCreate(path))
|
|
38
|
+
return makeRelease(path);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async function tryCreate(path) {
|
|
43
|
+
try {
|
|
44
|
+
const handle = await fs.open(path, 'wx'); // 'wx' = O_CREAT | O_EXCL: fails if it exists
|
|
45
|
+
await handle.writeFile(String(process.pid));
|
|
46
|
+
await handle.close();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function isStale(path) {
|
|
54
|
+
try {
|
|
55
|
+
const { mtimeMs } = await fs.stat(path);
|
|
56
|
+
return Date.now() - mtimeMs > LOCK_STALE_MS;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return true; // vanished between checks → treat as stealable
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function makeRelease(path) {
|
|
63
|
+
return async () => {
|
|
64
|
+
await fs.unlink(path).catch(() => { });
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=lock.js.map
|
package/dist/lock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,+BAA+B;AAC/B,gCAAgC;AAEhC;;;;;;;GAOG;AACH,OAAO,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AACvC,OAAO,EAAC,QAAQ,IAAI,EAAE,EAAC,MAAM,SAAS,CAAC;AACvC,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAE/B,OAAO,EAAC,gBAAgB,EAAC,MAAM,gBAAgB,CAAC;AAEhD,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;AACjD,oFAAoF;AACpF,MAAM,aAAa,GAAG,MAAM,CAAC;AAI7B,SAAS,QAAQ,CAAC,SAAiB;IACjC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC;SAC9B,MAAM,CAAC,SAAS,CAAC;SACjB,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAiB;IAEjB,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAEjC,IAAI,MAAM,SAAS,CAAC,IAAI,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACpD,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtC,IAAI,MAAM,SAAS,CAAC,IAAI,CAAC;YAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,8CAA8C;QACxF,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAY;IACjC,IAAI,CAAC;QACH,MAAM,EAAC,OAAO,EAAC,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,aAAa,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAC9D,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/log.d.ts
ADDED
package/dist/log.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Minimal append-only JSONL logger for the worker. Errors here must never
|
|
6
|
+
* surface to Codex, so every write is best-effort. Mirrors to stderr only when
|
|
7
|
+
* WEAVE_CODEX_DEBUG is set (the detached worker's stderr goes to the log file).
|
|
8
|
+
*/
|
|
9
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
10
|
+
import { dirname } from 'node:path';
|
|
11
|
+
import { LOG_FILE } from './constants.js';
|
|
12
|
+
/**
|
|
13
|
+
* Errors always log (they're the actionable failures). info/warn are opt-in via
|
|
14
|
+
* the `debug` user setting, so the log stays quiet by default — the worker should
|
|
15
|
+
* be invisible unless something is wrong. The worker calls setVerbose() once it
|
|
16
|
+
* has resolved config.
|
|
17
|
+
*/
|
|
18
|
+
let verbose = false;
|
|
19
|
+
export function setVerbose(enabled) {
|
|
20
|
+
verbose = enabled;
|
|
21
|
+
}
|
|
22
|
+
let dirReady = false;
|
|
23
|
+
export function log(level, message, extra) {
|
|
24
|
+
if (level !== 'error' && !verbose)
|
|
25
|
+
return;
|
|
26
|
+
const entry = JSON.stringify({
|
|
27
|
+
ts: new Date().toISOString(),
|
|
28
|
+
level,
|
|
29
|
+
message,
|
|
30
|
+
...extra,
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
if (!dirReady) {
|
|
34
|
+
mkdirSync(dirname(LOG_FILE), { recursive: true });
|
|
35
|
+
dirReady = true;
|
|
36
|
+
}
|
|
37
|
+
appendFileSync(LOG_FILE, `${entry}\n`);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Logging is best-effort; never throw.
|
|
41
|
+
}
|
|
42
|
+
if (verbose)
|
|
43
|
+
process.stderr.write(`${entry}\n`);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,+BAA+B;AAC/B,gCAAgC;AAEhC;;;;GAIG;AACH,OAAO,EAAC,cAAc,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAClD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,OAAO,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAIxC;;;;;GAKG;AACH,IAAI,OAAO,GAAG,KAAK,CAAC;AACpB,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,GAAG,OAAO,CAAC;AACpB,CAAC;AAED,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,MAAM,UAAU,GAAG,CACjB,KAAY,EACZ,OAAe,EACf,KAA+B;IAE/B,IAAI,KAAK,KAAK,OAAO,IAAI,CAAC,OAAO;QAAE,OAAO;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;QAC3B,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,KAAK;QACL,OAAO;QACP,GAAG,KAAK;KACT,CAAC,CAAC;IACH,IAAI,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;YAChD,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QACD,cAAc,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;IACD,IAAI,OAAO;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;AAClD,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal domain model — the reconstructed shape of one Codex turn, decoupled
|
|
3
|
+
* from both the rollout wire format (rollout/types.ts) and the OTEL span output.
|
|
4
|
+
* camelCase here; the wire format stays snake_case. This is the seam the parser
|
|
5
|
+
* produces and the span emitter consumes.
|
|
6
|
+
*/
|
|
7
|
+
/** Normalized token usage for a single model call or a turn total. */
|
|
8
|
+
export interface NormalizedUsage {
|
|
9
|
+
inputTokens: number;
|
|
10
|
+
outputTokens: number;
|
|
11
|
+
cachedInputTokens: number;
|
|
12
|
+
reasoningOutputTokens: number;
|
|
13
|
+
}
|
|
14
|
+
export type ToolKind = 'function' | 'local_shell' | 'web_search' | 'custom' | 'mcp';
|
|
15
|
+
/** A tool execution → one `execute_tool` span. */
|
|
16
|
+
export interface ToolCall {
|
|
17
|
+
callId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
kind: ToolKind;
|
|
20
|
+
startTime: Date;
|
|
21
|
+
endTime: Date;
|
|
22
|
+
arguments?: string;
|
|
23
|
+
result?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
mcpServer?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A tool the model requested within a chat (for that chat's output.messages). */
|
|
28
|
+
export interface ChatToolRef {
|
|
29
|
+
callId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
arguments?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A single model round-trip → one `chat` span. */
|
|
34
|
+
export interface ChatCall {
|
|
35
|
+
startTime: Date;
|
|
36
|
+
endTime: Date;
|
|
37
|
+
text?: string;
|
|
38
|
+
reasoning?: string;
|
|
39
|
+
toolCalls: ChatToolRef[];
|
|
40
|
+
usage?: NormalizedUsage;
|
|
41
|
+
finishReason: string;
|
|
42
|
+
}
|
|
43
|
+
/** One completed user→agent turn → one trace rooted at an `invoke_agent` span. */
|
|
44
|
+
export interface ReconstructedTurn {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
turnId: string;
|
|
47
|
+
conversationId: string;
|
|
48
|
+
model: string;
|
|
49
|
+
startTime: Date;
|
|
50
|
+
endTime: Date;
|
|
51
|
+
cwd?: string;
|
|
52
|
+
cliVersion?: string;
|
|
53
|
+
userMessage?: string;
|
|
54
|
+
finalAssistantMessage?: string;
|
|
55
|
+
chats: ChatCall[];
|
|
56
|
+
tools: ToolCall[];
|
|
57
|
+
usageTotal: NormalizedUsage;
|
|
58
|
+
}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,+BAA+B;AAC/B,gCAAgC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function readOffset(sessionId: string): Promise<number>;
|
|
2
|
+
export declare function writeOffset(sessionId: string, offset: number): Promise<void>;
|
|
3
|
+
interface NewLinesResult {
|
|
4
|
+
lines: string[];
|
|
5
|
+
/** End of the last COMPLETE line read; commit only after successful emit. */
|
|
6
|
+
nextOffset: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Read complete (newline-terminated) lines from `fromOffset` to EOF. A trailing
|
|
10
|
+
* partial line (writer mid-flush) is left unconsumed for the next read.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readNewLines(filePath: string, fromOffset: number): Promise<NewLinesResult>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-PackageName: weave-codex
|
|
4
|
+
/**
|
|
5
|
+
* Per-session byte-offset cursor so each Stop hook reads only the rollout bytes
|
|
6
|
+
* appended since the previous turn. Rollout files are strictly append-only, so a
|
|
7
|
+
* byte offset is a safe, simple cursor.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { promises as fs } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { WEAVE_CODEX_HOME } from '../constants.js';
|
|
13
|
+
/** Per-session byte-offset cursors so each turn reads only new rollout lines. */
|
|
14
|
+
const CURSOR_DIR = join(WEAVE_CODEX_HOME, 'cursors');
|
|
15
|
+
function cursorPath(sessionId) {
|
|
16
|
+
// Session ids are UUID-ish but hash anyway so the filename is always safe.
|
|
17
|
+
const safe = createHash('sha256')
|
|
18
|
+
.update(sessionId)
|
|
19
|
+
.digest('hex')
|
|
20
|
+
.slice(0, 32);
|
|
21
|
+
return join(CURSOR_DIR, `${safe}.offset`);
|
|
22
|
+
}
|
|
23
|
+
export async function readOffset(sessionId) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await fs.readFile(cursorPath(sessionId), 'utf8');
|
|
26
|
+
const n = Number.parseInt(raw.trim(), 10);
|
|
27
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 0; // no cursor yet → read from the start of the file
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function writeOffset(sessionId, offset) {
|
|
34
|
+
await fs.mkdir(CURSOR_DIR, { recursive: true });
|
|
35
|
+
// Never move the cursor backward: a slow/racing worker must not rewind past a
|
|
36
|
+
// newer worker's commit (which would re-emit later turns).
|
|
37
|
+
const current = await readOffset(sessionId);
|
|
38
|
+
if (offset <= current)
|
|
39
|
+
return;
|
|
40
|
+
await fs.writeFile(cursorPath(sessionId), String(offset), 'utf8');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read complete (newline-terminated) lines from `fromOffset` to EOF. A trailing
|
|
44
|
+
* partial line (writer mid-flush) is left unconsumed for the next read.
|
|
45
|
+
*/
|
|
46
|
+
export async function readNewLines(filePath, fromOffset) {
|
|
47
|
+
const handle = await fs.open(filePath, 'r');
|
|
48
|
+
try {
|
|
49
|
+
const { size } = await handle.stat();
|
|
50
|
+
if (size <= fromOffset)
|
|
51
|
+
return { lines: [], nextOffset: fromOffset };
|
|
52
|
+
const length = size - fromOffset;
|
|
53
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
54
|
+
// Honor bytesRead: a short read leaves the buffer tail uninitialized, so we
|
|
55
|
+
// must only decode the bytes actually read (a rare under-read of the tail is
|
|
56
|
+
// re-read from the same offset on the next Stop).
|
|
57
|
+
const { bytesRead } = await handle.read(buffer, 0, length, fromOffset);
|
|
58
|
+
const text = buffer.subarray(0, bytesRead).toString('utf8');
|
|
59
|
+
const lastNewline = text.lastIndexOf('\n');
|
|
60
|
+
if (lastNewline === -1)
|
|
61
|
+
return { lines: [], nextOffset: fromOffset };
|
|
62
|
+
const complete = text.slice(0, lastNewline);
|
|
63
|
+
// Keep lines 1:1 with newline-delimited records (no empty filter) so the
|
|
64
|
+
// caller can map line index → byte offset exactly when committing the cursor.
|
|
65
|
+
const lines = complete.split('\n');
|
|
66
|
+
// +1 for the consumed newline byte. byteLength because offsets are bytes.
|
|
67
|
+
const nextOffset = fromOffset + Buffer.byteLength(complete, 'utf8') + 1;
|
|
68
|
+
return { lines, nextOffset };
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await handle.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/rollout/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReconstructedTurn } from '../model.js';
|
|
2
|
+
import type { RolloutLine } from './types.js';
|
|
3
|
+
/** Fallbacks for fields a read slice may lack (no session_meta/turn_context in this chunk). */
|
|
4
|
+
interface ReconstructContext {
|
|
5
|
+
fallbackSessionId: string;
|
|
6
|
+
fallbackModel?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ReconstructResult {
|
|
9
|
+
turns: ReconstructedTurn[];
|
|
10
|
+
/**
|
|
11
|
+
* Count of input lines belonging to fully-completed turns. The caller commits
|
|
12
|
+
* its cursor only this far so a not-yet-flushed trailing turn is re-read next
|
|
13
|
+
* time rather than skipped (exactly-once for complete turns, no loss).
|
|
14
|
+
*/
|
|
15
|
+
consumedLines: number;
|
|
16
|
+
/**
|
|
17
|
+
* Turns that began but never completed before a new turn started (e.g. an
|
|
18
|
+
* aborted turn). They are intentionally dropped (out of v1 scope); the count
|
|
19
|
+
* lets the caller log that something was discarded.
|
|
20
|
+
*/
|
|
21
|
+
abandonedTurns: number;
|
|
22
|
+
}
|
|
23
|
+
/** Parse one raw line, or null if malformed/blank (kept aligned by the caller). */
|
|
24
|
+
export declare function parseLineMaybe(raw: string): RolloutLine | null;
|
|
25
|
+
export declare function reconstructTurns(lines: ReadonlyArray<RolloutLine | null>, context: ReconstructContext): ReconstructResult;
|
|
26
|
+
export {};
|