@hwip/cli 0.0.1 → 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/LICENSE +190 -0
- package/NOTICE +11 -0
- package/README.md +1979 -0
- package/dist/agent-list.d.ts +76 -0
- package/dist/agent-list.js +102 -0
- package/dist/agent-read.d.ts +105 -0
- package/dist/agent-read.js +309 -0
- package/dist/agent-rules-fetch.d.ts +51 -0
- package/dist/agent-rules-fetch.js +275 -0
- package/dist/agent-rules.default.d.ts +30 -0
- package/dist/agent-rules.default.js +292 -0
- package/dist/agent-session.d.ts +23 -0
- package/dist/agent-session.js +44 -0
- package/dist/agent-state.d.ts +84 -0
- package/dist/agent-state.js +188 -0
- package/dist/agent-target.d.ts +60 -0
- package/dist/agent-target.js +81 -0
- package/dist/agents.d.ts +31 -0
- package/dist/agents.js +75 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +76 -0
- package/dist/args.d.ts +74 -0
- package/dist/args.js +254 -0
- package/dist/ask-context-attach.d.ts +73 -0
- package/dist/ask-context-attach.js +151 -0
- package/dist/ask-context-screen.d.ts +21 -0
- package/dist/ask-context-screen.js +49 -0
- package/dist/ask-context-switch.d.ts +31 -0
- package/dist/ask-context-switch.js +74 -0
- package/dist/ask-context.d.ts +84 -0
- package/dist/ask-context.js +172 -0
- package/dist/ask-pending.d.ts +47 -0
- package/dist/ask-pending.js +221 -0
- package/dist/ask-seal.d.ts +120 -0
- package/dist/ask-seal.js +179 -0
- package/dist/ask.d.ts +107 -0
- package/dist/ask.js +551 -0
- package/dist/blocked-prompt-seal.d.ts +35 -0
- package/dist/blocked-prompt-seal.js +89 -0
- package/dist/blocked-prompt.d.ts +57 -0
- package/dist/blocked-prompt.js +181 -0
- package/dist/channel.d.ts +67 -0
- package/dist/channel.js +136 -0
- package/dist/check-update.d.ts +28 -0
- package/dist/check-update.js +153 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +813 -0
- package/dist/codex-config.d.ts +24 -0
- package/dist/codex-config.js +169 -0
- package/dist/codex-transcript.d.ts +59 -0
- package/dist/codex-transcript.js +373 -0
- package/dist/command-refusal.d.ts +56 -0
- package/dist/command-refusal.js +127 -0
- package/dist/command-scan.d.ts +46 -0
- package/dist/command-scan.js +154 -0
- package/dist/command-seal.d.ts +57 -0
- package/dist/command-seal.js +88 -0
- package/dist/command-seq.d.ts +30 -0
- package/dist/command-seq.js +150 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.js +392 -0
- package/dist/core-sections.d.ts +18 -0
- package/dist/core-sections.js +28 -0
- package/dist/crypto.d.ts +278 -0
- package/dist/crypto.js +698 -0
- package/dist/desktop-notify.d.ts +33 -0
- package/dist/desktop-notify.js +49 -0
- package/dist/device-directory.d.ts +36 -0
- package/dist/device-directory.js +97 -0
- package/dist/device-presence.d.ts +17 -0
- package/dist/device-presence.js +62 -0
- package/dist/downloads.d.ts +29 -0
- package/dist/downloads.js +106 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +28 -0
- package/dist/file-crypto.d.ts +94 -0
- package/dist/file-crypto.js +272 -0
- package/dist/file-view-budget.d.ts +15 -0
- package/dist/file-view-budget.js +34 -0
- package/dist/file-view-switch.d.ts +28 -0
- package/dist/file-view-switch.js +122 -0
- package/dist/file-view.d.ts +108 -0
- package/dist/file-view.js +327 -0
- package/dist/gate.d.ts +141 -0
- package/dist/gate.js +336 -0
- package/dist/gemini-transcript.d.ts +32 -0
- package/dist/gemini-transcript.js +189 -0
- package/dist/http-failure.d.ts +98 -0
- package/dist/http-failure.js +349 -0
- package/dist/hwip-core.generated.d.ts +7 -0
- package/dist/hwip-core.generated.js +14 -0
- package/dist/hwip-hook.d.ts +77 -0
- package/dist/hwip-hook.js +340 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/input-sequencer.d.ts +65 -0
- package/dist/input-sequencer.js +142 -0
- package/dist/insecure-url.d.ts +16 -0
- package/dist/insecure-url.js +76 -0
- package/dist/installer.d.ts +96 -0
- package/dist/installer.js +850 -0
- package/dist/inventory-offload.d.ts +49 -0
- package/dist/inventory-offload.js +209 -0
- package/dist/inventory-worker.d.ts +2 -0
- package/dist/inventory-worker.js +27 -0
- package/dist/jsonc-doc.d.ts +106 -0
- package/dist/jsonc-doc.js +354 -0
- package/dist/keep-awake.d.ts +32 -0
- package/dist/keep-awake.js +46 -0
- package/dist/key-changes.d.ts +48 -0
- package/dist/key-changes.js +140 -0
- package/dist/lan-auth.d.ts +140 -0
- package/dist/lan-auth.js +217 -0
- package/dist/lan-endpoint.d.ts +41 -0
- package/dist/lan-endpoint.js +115 -0
- package/dist/lan-receiver.d.ts +122 -0
- package/dist/lan-receiver.js +510 -0
- package/dist/lan-sender.d.ts +76 -0
- package/dist/lan-sender.js +184 -0
- package/dist/lan-transfer.d.ts +36 -0
- package/dist/lan-transfer.js +54 -0
- package/dist/listener-device-id.d.ts +20 -0
- package/dist/listener-device-id.js +133 -0
- package/dist/listener-log.d.ts +4 -0
- package/dist/listener-log.js +23 -0
- package/dist/listener-process.d.ts +200 -0
- package/dist/listener-process.js +470 -0
- package/dist/listener-service.d.ts +225 -0
- package/dist/listener-service.js +682 -0
- package/dist/listener-worktree-cleanup.d.ts +27 -0
- package/dist/listener-worktree-cleanup.js +62 -0
- package/dist/listener.d.ts +1325 -0
- package/dist/listener.js +5680 -0
- package/dist/login-address.d.ts +25 -0
- package/dist/login-address.js +97 -0
- package/dist/login-page.d.ts +29 -0
- package/dist/login-page.js +93 -0
- package/dist/login.d.ts +98 -0
- package/dist/login.js +388 -0
- package/dist/mcp-command.d.ts +45 -0
- package/dist/mcp-command.js +50 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.js +29 -0
- package/dist/node-support.d.ts +19 -0
- package/dist/node-support.js +38 -0
- package/dist/notify-failure.d.ts +57 -0
- package/dist/notify-failure.js +150 -0
- package/dist/opencode-transcript.d.ts +139 -0
- package/dist/opencode-transcript.js +390 -0
- package/dist/optional-features.d.ts +6 -0
- package/dist/optional-features.js +19 -0
- package/dist/pane-fit.d.ts +40 -0
- package/dist/pane-fit.js +119 -0
- package/dist/peer-key.d.ts +33 -0
- package/dist/peer-key.js +34 -0
- package/dist/pi-transcript.d.ts +26 -0
- package/dist/pi-transcript.js +255 -0
- package/dist/presence.d.ts +14 -0
- package/dist/presence.js +124 -0
- package/dist/private-files.d.ts +51 -0
- package/dist/private-files.js +218 -0
- package/dist/project-switch.d.ts +19 -0
- package/dist/project-switch.js +60 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +21 -0
- package/dist/remote-agents.d.ts +410 -0
- package/dist/remote-agents.js +1218 -0
- package/dist/remote-hook.d.ts +12 -0
- package/dist/remote-hook.js +196 -0
- package/dist/replace-file.d.ts +50 -0
- package/dist/replace-file.js +328 -0
- package/dist/schedule-command.d.ts +35 -0
- package/dist/schedule-command.js +260 -0
- package/dist/schedule-launch.d.ts +15 -0
- package/dist/schedule-launch.js +24 -0
- package/dist/schedule-ready.d.ts +44 -0
- package/dist/schedule-ready.js +71 -0
- package/dist/schedule-remote.d.ts +77 -0
- package/dist/schedule-remote.js +167 -0
- package/dist/schedule-runner.d.ts +89 -0
- package/dist/schedule-runner.js +452 -0
- package/dist/schedule.d.ts +149 -0
- package/dist/schedule.js +428 -0
- package/dist/secret-mask.d.ts +29 -0
- package/dist/secret-mask.js +245 -0
- package/dist/self-register.d.ts +45 -0
- package/dist/self-register.js +67 -0
- package/dist/send.d.ts +56 -0
- package/dist/send.js +104 -0
- package/dist/session-diff.d.ts +161 -0
- package/dist/session-diff.js +445 -0
- package/dist/session-registry.d.ts +143 -0
- package/dist/session-registry.js +216 -0
- package/dist/sessions-report.d.ts +59 -0
- package/dist/sessions-report.js +99 -0
- package/dist/shell-audit.d.ts +34 -0
- package/dist/shell-audit.js +47 -0
- package/dist/subagent-transcripts.d.ts +167 -0
- package/dist/subagent-transcripts.js +437 -0
- package/dist/subscriber-trust.d.ts +128 -0
- package/dist/subscriber-trust.js +397 -0
- package/dist/templates.d.ts +105 -0
- package/dist/templates.js +871 -0
- package/dist/tmux-install.d.ts +44 -0
- package/dist/tmux-install.js +65 -0
- package/dist/tmux-liveness.d.ts +45 -0
- package/dist/tmux-liveness.js +71 -0
- package/dist/tmux-pane.d.ts +55 -0
- package/dist/tmux-pane.js +126 -0
- package/dist/tmux-socket.d.ts +20 -0
- package/dist/tmux-socket.js +292 -0
- package/dist/todo-fold.d.ts +31 -0
- package/dist/todo-fold.js +102 -0
- package/dist/transcript-tail.d.ts +312 -0
- package/dist/transcript-tail.js +718 -0
- package/dist/trust-command.d.ts +49 -0
- package/dist/trust-command.js +200 -0
- package/dist/trust-lock.d.ts +26 -0
- package/dist/trust-lock.js +94 -0
- package/dist/trust-pins.d.ts +37 -0
- package/dist/trust-pins.js +82 -0
- package/dist/turn-recorder-wire.d.ts +70 -0
- package/dist/turn-recorder-wire.js +165 -0
- package/dist/turn-recorder.d.ts +190 -0
- package/dist/turn-recorder.js +695 -0
- package/dist/turn-ring.d.ts +93 -0
- package/dist/turn-ring.js +418 -0
- package/dist/turn-watch.d.ts +177 -0
- package/dist/turn-watch.js +630 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +55 -0
- package/dist/uninstall.js +507 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +421 -0
- package/dist/verify.d.ts +47 -0
- package/dist/verify.js +396 -0
- package/dist/worktree.d.ts +144 -0
- package/dist/worktree.js +310 -0
- package/dist/wrapper.d.ts +254 -0
- package/dist/wrapper.js +704 -0
- package/dist/ws-url.d.ts +109 -0
- package/dist/ws-url.js +202 -0
- package/package.json +78 -10
- package/bin/hwip.js +0 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The table hwip writes into a config that has none. No `env` block, unlike
|
|
2
|
+
* the Cursor/Windsurf JSON entry: codex is a terminal app, so the server
|
|
3
|
+
* inherits the shell's `HWIP_API_KEY`, and an unexpanded `"${HWIP_API_KEY}"`
|
|
4
|
+
* literal would shadow the ~/.hwip/config.json fallback with junk (the same
|
|
5
|
+
* reasoning as opencode's). */
|
|
6
|
+
export declare const CODEX_MCP_TABLE: string;
|
|
7
|
+
/**
|
|
8
|
+
* The config with hwip's entry present and current, leaving every other byte
|
|
9
|
+
* alone. Appended at the end when absent: a table header ends the table before
|
|
10
|
+
* it, so nothing above can capture the keys.
|
|
11
|
+
*/
|
|
12
|
+
export declare const upsertHwipMcpTable: (config: string) => string;
|
|
13
|
+
/** The config with hwip's entry and its sub-tables gone, or null when it was
|
|
14
|
+
* never there (so the caller can report "nothing to remove" rather than
|
|
15
|
+
* rewriting an untouched file). */
|
|
16
|
+
export declare const removeHwipMcpTable: (config: string) => string | null;
|
|
17
|
+
/**
|
|
18
|
+
* The launch argv codex has recorded for hwip, verbatim, or null when there is
|
|
19
|
+
* no entry. Same contract as `registeredMcpArgv` in verify.ts: returned
|
|
20
|
+
* unnormalized, because a stale entry is only visible if the reader does not
|
|
21
|
+
* reshape it into what it expects.
|
|
22
|
+
*/
|
|
23
|
+
export declare const readHwipMcpArgv: (config: string) => string[] | null;
|
|
24
|
+
//# sourceMappingURL=codex-config.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readHwipMcpArgv = exports.removeHwipMcpTable = exports.upsertHwipMcpTable = exports.CODEX_MCP_TABLE = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The hwip entry inside `~/.codex/config.toml`, which Codex CLI owns.
|
|
6
|
+
*
|
|
7
|
+
* Codex is the one supported agent whose MCP registry is TOML, and the file is
|
|
8
|
+
* the user's: it carries their model, their approval settings, their other
|
|
9
|
+
* servers. So this module works at the text level on the one table hwip owns
|
|
10
|
+
* (`[mcp_servers.hwip]`) and never rewrites the document — a parse-and-emit
|
|
11
|
+
* round trip through a TOML library would reorder their keys and drop their
|
|
12
|
+
* comments, which is a worse failure than the entry being missing.
|
|
13
|
+
*
|
|
14
|
+
* What that promise costs, stated once here because every function below is
|
|
15
|
+
* shaped by it:
|
|
16
|
+
*
|
|
17
|
+
* - **Sub-tables are part of the entry.** Codex writes per-tool settings as
|
|
18
|
+
* `[mcp_servers.hwip.tools.hwip_ask]` when the user picks "always allow".
|
|
19
|
+
* Removal takes them along (a child of a deleted server is dead config);
|
|
20
|
+
* re-install keeps them, since they are the user's answer, not ours.
|
|
21
|
+
* - **Only `command` and `args` are ours.** Any other key in the table —
|
|
22
|
+
* `cwd`, `startup_timeout_sec`, a comment — survives a re-install.
|
|
23
|
+
* - **Comments before the next table belong to that table**, not to ours, so
|
|
24
|
+
* the region we replace or delete stops short of them.
|
|
25
|
+
*
|
|
26
|
+
* Install, uninstall and verify all go through here so this is stated once.
|
|
27
|
+
*/
|
|
28
|
+
const mcp_command_js_1 = require("./mcp-command.js");
|
|
29
|
+
const [MCP_BIN, ...MCP_SUBCOMMAND] = mcp_command_js_1.MCP_LAUNCH_ARGV;
|
|
30
|
+
/** The table header, with or without the quotes TOML also allows. */
|
|
31
|
+
const HWIP_TABLE_HEADER = /^\[mcp_servers\.(?:hwip|"hwip")\]$/;
|
|
32
|
+
/** A sub-table of ours: `[mcp_servers.hwip.tools.hwip_ask]` and friends. */
|
|
33
|
+
const HWIP_SUBTABLE_HEADER = /^\[mcp_servers\.(?:hwip|"hwip")\..+\]$/;
|
|
34
|
+
/**
|
|
35
|
+
* The dotted/inline form — `mcp_servers.hwip = { command = "npx", … }` — which
|
|
36
|
+
* is valid TOML and which older hand-written configs use. It has to be found,
|
|
37
|
+
* because appending a header table beside it is a duplicate-key error that
|
|
38
|
+
* makes codex reject the WHOLE config, hwip's entry and the user's alike.
|
|
39
|
+
*/
|
|
40
|
+
const HWIP_INLINE_ENTRY = /^mcp_servers\.(?:hwip|"hwip")\s*=/;
|
|
41
|
+
const ANY_TABLE_HEADER = /^\[/;
|
|
42
|
+
const isHwipDefinition = (line) => HWIP_TABLE_HEADER.test(line.trim()) || HWIP_INLINE_ENTRY.test(line.trim());
|
|
43
|
+
/** The keys hwip owns; everything else in the table is the user's. */
|
|
44
|
+
const OWNED_KEY = /^(?:command|args)\s*=/;
|
|
45
|
+
const commandLine = `command = "${MCP_BIN}"`;
|
|
46
|
+
const argsLine = `args = [${MCP_SUBCOMMAND.map((a) => `"${a}"`).join(', ')}]`;
|
|
47
|
+
/** The table hwip writes into a config that has none. No `env` block, unlike
|
|
48
|
+
* the Cursor/Windsurf JSON entry: codex is a terminal app, so the server
|
|
49
|
+
* inherits the shell's `HWIP_API_KEY`, and an unexpanded `"${HWIP_API_KEY}"`
|
|
50
|
+
* literal would shadow the ~/.hwip/config.json fallback with junk (the same
|
|
51
|
+
* reasoning as opencode's). */
|
|
52
|
+
exports.CODEX_MCP_TABLE = ['[mcp_servers.hwip]', commandLine, argsLine].join('\n');
|
|
53
|
+
/** CRLF in, CRLF out — a Windows config must not come back with mixed endings. */
|
|
54
|
+
const eolOf = (config) => (config.includes('\r\n') ? '\r\n' : '\n');
|
|
55
|
+
const splitLines = (config) => config.split(/\r?\n/);
|
|
56
|
+
/** Line index of hwip's definition — header or inline — or -1. */
|
|
57
|
+
const definitionIndex = (lines) => lines.findIndex((line) => isHwipDefinition(line));
|
|
58
|
+
/** One past the last line of hwip's own key/value body (the next table header
|
|
59
|
+
* of any kind, or EOF). Meaningless for the inline form, which is one line. */
|
|
60
|
+
const bodyEnd = (lines, start) => {
|
|
61
|
+
const next = lines.slice(start + 1).findIndex((line) => ANY_TABLE_HEADER.test(line.trim()));
|
|
62
|
+
return next === -1 ? lines.length : start + 1 + next;
|
|
63
|
+
};
|
|
64
|
+
/** One past the last line of the whole entry: body plus every sub-table of
|
|
65
|
+
* ours, stopping at the first header that belongs to someone else. */
|
|
66
|
+
const entryEnd = (lines, start) => {
|
|
67
|
+
let end = bodyEnd(lines, start);
|
|
68
|
+
while (end < lines.length && HWIP_SUBTABLE_HEADER.test(lines[end].trim())) {
|
|
69
|
+
end = bodyEnd(lines, end);
|
|
70
|
+
}
|
|
71
|
+
return end;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* `end` walked back past the blank lines and comments that precede whatever
|
|
75
|
+
* comes next. A comment sitting above `[mcp_servers.graft]` documents THAT
|
|
76
|
+
* server; deleting it with our table is the exact loss this module exists to
|
|
77
|
+
* avoid.
|
|
78
|
+
*/
|
|
79
|
+
const withoutTrailingPreamble = (lines, start, end) => {
|
|
80
|
+
let cut = end;
|
|
81
|
+
while (cut > start + 1) {
|
|
82
|
+
const line = lines[cut - 1].trim();
|
|
83
|
+
if (line === '' || line.startsWith('#'))
|
|
84
|
+
cut -= 1;
|
|
85
|
+
else
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return cut;
|
|
89
|
+
};
|
|
90
|
+
/** The document, ending in exactly one newline, so writing a config that
|
|
91
|
+
* already carries the table is a no-op rather than a one-byte diff. */
|
|
92
|
+
const rejoin = (lines, eol) => `${lines.join(eol).replace(/(?:\r?\n)+$/, '')}${eol}`;
|
|
93
|
+
/**
|
|
94
|
+
* The config with hwip's entry present and current, leaving every other byte
|
|
95
|
+
* alone. Appended at the end when absent: a table header ends the table before
|
|
96
|
+
* it, so nothing above can capture the keys.
|
|
97
|
+
*/
|
|
98
|
+
const upsertHwipMcpTable = (config) => {
|
|
99
|
+
const eol = eolOf(config);
|
|
100
|
+
const lines = splitLines(config);
|
|
101
|
+
const start = definitionIndex(lines);
|
|
102
|
+
if (start === -1) {
|
|
103
|
+
const body = config.replace(/(?:\r?\n)+$/, '');
|
|
104
|
+
const table = exports.CODEX_MCP_TABLE.split('\n').join(eol);
|
|
105
|
+
return `${body ? `${body}${eol}${eol}` : ''}${table}${eol}`;
|
|
106
|
+
}
|
|
107
|
+
// The inline form is a single line and cannot hold keys of ours to keep.
|
|
108
|
+
if (HWIP_INLINE_ENTRY.test(lines[start].trim())) {
|
|
109
|
+
const table = exports.CODEX_MCP_TABLE.split('\n');
|
|
110
|
+
return rejoin([...lines.slice(0, start), ...table, ...lines.slice(start + 1)], eol);
|
|
111
|
+
}
|
|
112
|
+
const end = withoutTrailingPreamble(lines, start, bodyEnd(lines, start));
|
|
113
|
+
const kept = lines.slice(start + 1, end).filter((line) => !OWNED_KEY.test(line.trim()));
|
|
114
|
+
return rejoin([
|
|
115
|
+
...lines.slice(0, start),
|
|
116
|
+
'[mcp_servers.hwip]',
|
|
117
|
+
commandLine,
|
|
118
|
+
argsLine,
|
|
119
|
+
...kept,
|
|
120
|
+
...lines.slice(end),
|
|
121
|
+
], eol);
|
|
122
|
+
};
|
|
123
|
+
exports.upsertHwipMcpTable = upsertHwipMcpTable;
|
|
124
|
+
/** The config with hwip's entry and its sub-tables gone, or null when it was
|
|
125
|
+
* never there (so the caller can report "nothing to remove" rather than
|
|
126
|
+
* rewriting an untouched file). */
|
|
127
|
+
const removeHwipMcpTable = (config) => {
|
|
128
|
+
const eol = eolOf(config);
|
|
129
|
+
const lines = splitLines(config);
|
|
130
|
+
const start = definitionIndex(lines);
|
|
131
|
+
if (start === -1)
|
|
132
|
+
return null;
|
|
133
|
+
const end = HWIP_INLINE_ENTRY.test(lines[start].trim())
|
|
134
|
+
? start + 1
|
|
135
|
+
: withoutTrailingPreamble(lines, start, entryEnd(lines, start));
|
|
136
|
+
const kept = [...lines.slice(0, start), ...lines.slice(end)];
|
|
137
|
+
// The removed entry left its blank separator behind; collapse the run so
|
|
138
|
+
// repeated install/uninstall cycles cannot grow the file.
|
|
139
|
+
return `${kept.join(eol).replace(/(?:\r?\n){3,}/g, `${eol}${eol}`).replace(/(?:\r?\n)+$/, '')}${eol}`;
|
|
140
|
+
};
|
|
141
|
+
exports.removeHwipMcpTable = removeHwipMcpTable;
|
|
142
|
+
/**
|
|
143
|
+
* The launch argv codex has recorded for hwip, verbatim, or null when there is
|
|
144
|
+
* no entry. Same contract as `registeredMcpArgv` in verify.ts: returned
|
|
145
|
+
* unnormalized, because a stale entry is only visible if the reader does not
|
|
146
|
+
* reshape it into what it expects.
|
|
147
|
+
*/
|
|
148
|
+
const readHwipMcpArgv = (config) => {
|
|
149
|
+
const lines = splitLines(config);
|
|
150
|
+
const start = definitionIndex(lines);
|
|
151
|
+
if (start === -1)
|
|
152
|
+
return null;
|
|
153
|
+
// Joined, not scanned line by line: `args` is often written across several
|
|
154
|
+
// lines, and a line-wise reader drops it — which verify would then report
|
|
155
|
+
// as a stale registration for an entry that is in fact current.
|
|
156
|
+
const body = HWIP_INLINE_ENTRY.test(lines[start].trim())
|
|
157
|
+
? lines[start]
|
|
158
|
+
: lines.slice(start + 1, bodyEnd(lines, start)).join('\n');
|
|
159
|
+
const command = body.match(/(?:^|[\s,{])command\s*=\s*["']([^"']+)["']/)?.[1];
|
|
160
|
+
if (!command)
|
|
161
|
+
return null;
|
|
162
|
+
const rawArgs = body.match(/(?:^|[\s,{])args\s*=\s*\[([^\]]*)\]/)?.[1] ?? '';
|
|
163
|
+
const args = rawArgs
|
|
164
|
+
.split(',')
|
|
165
|
+
.map((a) => a.trim().replace(/^["']|["']$/g, ''))
|
|
166
|
+
.filter(Boolean);
|
|
167
|
+
return [command, ...args];
|
|
168
|
+
};
|
|
169
|
+
exports.readHwipMcpArgv = readHwipMcpArgv;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex's rollout format, projected into the events the timeline draws.
|
|
3
|
+
*
|
|
4
|
+
* The third projector. Like pi's, everything here is Codex's vocabulary and
|
|
5
|
+
* belongs to Codex alone — `turn-watch` reaches it through `TranscriptProjector`
|
|
6
|
+
* and knows nothing about any of it.
|
|
7
|
+
*
|
|
8
|
+
* Shape measured 2026-09-14 against `~/.codex/sessions/2026/09/14/rollout-…jsonl`
|
|
9
|
+
* (77 lines, codex-cli 0.154.0, `gpt-5.6-luna`), cross-checked against the
|
|
10
|
+
* strings in the writer itself (`~/.codex/packages/standalone/current/bin/codex`)
|
|
11
|
+
* for the entry types that session happens not to contain.
|
|
12
|
+
*
|
|
13
|
+
* Every line is `{ timestamp, ordinal, type, payload }`. Two lanes describe the
|
|
14
|
+
* same turn, and this projector reads exactly one of them:
|
|
15
|
+
*
|
|
16
|
+
* - `response_item` — what goes to the model: `message`, `reasoning`,
|
|
17
|
+
* `custom_tool_call`, `function_call` and their `_output` twins.
|
|
18
|
+
* - `event_msg` — what the UI is told: `item_completed` wrapping a typed
|
|
19
|
+
* `item`, plus `token_count` and the task markers.
|
|
20
|
+
*
|
|
21
|
+
* `event_msg` wins, for three reasons measured in that session:
|
|
22
|
+
*
|
|
23
|
+
* 1. Its `UserMessage` items are the person's messages *only*. The
|
|
24
|
+
* `response_item` lane files Codex's own injections under `role: 'user'`
|
|
25
|
+
* too — the first user-role entry there is `# AGENTS.md instructions …`
|
|
26
|
+
* followed by `<environment_context>` — and stamps no provenance on them.
|
|
27
|
+
* Four user-role `response_item`s, three `UserMessage` items, and the one
|
|
28
|
+
* left out is the injection. Codex has already made this call; pi's
|
|
29
|
+
* projector had to guess at it with a wrapper regex because pi has no
|
|
30
|
+
* equivalent lane.
|
|
31
|
+
* 2. `CommandExecution` carries `parsed_cmd`, Codex's own reading of what a
|
|
32
|
+
* shell line does (`{ type: 'read', name: 'SKILL.md', path: '…' }`), plus
|
|
33
|
+
* `exit_code`. The `response_item` twin carries a JavaScript program —
|
|
34
|
+
* `const r = await tools.exec_command({cmd:"…"})` — whose command string
|
|
35
|
+
* would have to be parsed back out of source text to label a row.
|
|
36
|
+
* 3. One lane cannot double-count. Reading both would put every tool call on
|
|
37
|
+
* the timeline twice, and the two lanes share no id to merge on.
|
|
38
|
+
*
|
|
39
|
+
* The cost is latency, and it is real: an `item_completed` is written when the
|
|
40
|
+
* work finishes, so a two-minute build shows up two minutes in rather than at
|
|
41
|
+
* its start. The `response_item` lane is written at call time but cannot say
|
|
42
|
+
* whether the call succeeded — the honest row late beats the ambiguous row
|
|
43
|
+
* early, and a timeline that showed a call and never its outcome is the worse
|
|
44
|
+
* of the two failures.
|
|
45
|
+
*
|
|
46
|
+
* An item type this file does not name draws no row at all. Codex ships types no
|
|
47
|
+
* session on this machine has produced and will ship more; each needs a sample
|
|
48
|
+
* read before it can be projected, and until then a thinner timeline is the
|
|
49
|
+
* honest one. `FileChange` and `McpToolCall` were read from samples on
|
|
50
|
+
* 2026-10-02 (1 and 7 items) and are projected; `update` / `delete` changes and
|
|
51
|
+
* the failure forms rest on the writer's strings only, and say so where used.
|
|
52
|
+
*/
|
|
53
|
+
import { type TranscriptProjector } from './transcript-tail.js';
|
|
54
|
+
/**
|
|
55
|
+
* Codex writes one line per completed item, so no event needs merging across
|
|
56
|
+
* lines — the same place this projector is simpler than Claude's.
|
|
57
|
+
*/
|
|
58
|
+
export declare const projectCodexEntries: TranscriptProjector;
|
|
59
|
+
//# sourceMappingURL=codex-transcript.d.ts.map
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Codex's rollout format, projected into the events the timeline draws.
|
|
4
|
+
*
|
|
5
|
+
* The third projector. Like pi's, everything here is Codex's vocabulary and
|
|
6
|
+
* belongs to Codex alone — `turn-watch` reaches it through `TranscriptProjector`
|
|
7
|
+
* and knows nothing about any of it.
|
|
8
|
+
*
|
|
9
|
+
* Shape measured 2026-09-14 against `~/.codex/sessions/2026/09/14/rollout-…jsonl`
|
|
10
|
+
* (77 lines, codex-cli 0.154.0, `gpt-5.6-luna`), cross-checked against the
|
|
11
|
+
* strings in the writer itself (`~/.codex/packages/standalone/current/bin/codex`)
|
|
12
|
+
* for the entry types that session happens not to contain.
|
|
13
|
+
*
|
|
14
|
+
* Every line is `{ timestamp, ordinal, type, payload }`. Two lanes describe the
|
|
15
|
+
* same turn, and this projector reads exactly one of them:
|
|
16
|
+
*
|
|
17
|
+
* - `response_item` — what goes to the model: `message`, `reasoning`,
|
|
18
|
+
* `custom_tool_call`, `function_call` and their `_output` twins.
|
|
19
|
+
* - `event_msg` — what the UI is told: `item_completed` wrapping a typed
|
|
20
|
+
* `item`, plus `token_count` and the task markers.
|
|
21
|
+
*
|
|
22
|
+
* `event_msg` wins, for three reasons measured in that session:
|
|
23
|
+
*
|
|
24
|
+
* 1. Its `UserMessage` items are the person's messages *only*. The
|
|
25
|
+
* `response_item` lane files Codex's own injections under `role: 'user'`
|
|
26
|
+
* too — the first user-role entry there is `# AGENTS.md instructions …`
|
|
27
|
+
* followed by `<environment_context>` — and stamps no provenance on them.
|
|
28
|
+
* Four user-role `response_item`s, three `UserMessage` items, and the one
|
|
29
|
+
* left out is the injection. Codex has already made this call; pi's
|
|
30
|
+
* projector had to guess at it with a wrapper regex because pi has no
|
|
31
|
+
* equivalent lane.
|
|
32
|
+
* 2. `CommandExecution` carries `parsed_cmd`, Codex's own reading of what a
|
|
33
|
+
* shell line does (`{ type: 'read', name: 'SKILL.md', path: '…' }`), plus
|
|
34
|
+
* `exit_code`. The `response_item` twin carries a JavaScript program —
|
|
35
|
+
* `const r = await tools.exec_command({cmd:"…"})` — whose command string
|
|
36
|
+
* would have to be parsed back out of source text to label a row.
|
|
37
|
+
* 3. One lane cannot double-count. Reading both would put every tool call on
|
|
38
|
+
* the timeline twice, and the two lanes share no id to merge on.
|
|
39
|
+
*
|
|
40
|
+
* The cost is latency, and it is real: an `item_completed` is written when the
|
|
41
|
+
* work finishes, so a two-minute build shows up two minutes in rather than at
|
|
42
|
+
* its start. The `response_item` lane is written at call time but cannot say
|
|
43
|
+
* whether the call succeeded — the honest row late beats the ambiguous row
|
|
44
|
+
* early, and a timeline that showed a call and never its outcome is the worse
|
|
45
|
+
* of the two failures.
|
|
46
|
+
*
|
|
47
|
+
* An item type this file does not name draws no row at all. Codex ships types no
|
|
48
|
+
* session on this machine has produced and will ship more; each needs a sample
|
|
49
|
+
* read before it can be projected, and until then a thinner timeline is the
|
|
50
|
+
* honest one. `FileChange` and `McpToolCall` were read from samples on
|
|
51
|
+
* 2026-10-02 (1 and 7 items) and are projected; `update` / `delete` changes and
|
|
52
|
+
* the failure forms rest on the writer's strings only, and say so where used.
|
|
53
|
+
*/
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.projectCodexEntries = void 0;
|
|
56
|
+
const transcript_tail_js_1 = require("./transcript-tail.js");
|
|
57
|
+
/** The item types this projector knows. Anything else draws no row — see the file header. */
|
|
58
|
+
const CODEX_USER_MESSAGE = 'UserMessage';
|
|
59
|
+
const CODEX_AGENT_MESSAGE = 'AgentMessage';
|
|
60
|
+
const CODEX_REASONING = 'Reasoning';
|
|
61
|
+
const CODEX_COMMAND = 'CommandExecution';
|
|
62
|
+
const CODEX_EXTENSION = 'Extension';
|
|
63
|
+
const CODEX_FILE_CHANGE = 'FileChange';
|
|
64
|
+
const CODEX_MCP_CALL = 'McpToolCall';
|
|
65
|
+
/**
|
|
66
|
+
* Which `parsed_cmd` field reads as "what this call is about", first match wins.
|
|
67
|
+
*
|
|
68
|
+
* Codex's own names, and a short list because `parsed_cmd` is already the
|
|
69
|
+
* vendor's summary rather than raw argv: a `read` entry carries `name` (the
|
|
70
|
+
* basename) and `path`, a search carries `query`. `cmd` — the reconstructed
|
|
71
|
+
* command line, and the field most likely to hold a token or a signed URL — is
|
|
72
|
+
* absent for the same reason `command` is absent from Claude's list.
|
|
73
|
+
*/
|
|
74
|
+
const CODEX_PARSED_KEYS = ['name', 'query', 'path'];
|
|
75
|
+
/**
|
|
76
|
+
* Codex nests JSON inside JSON: an item's `content` arrives as a string holding
|
|
77
|
+
* an encoded array. Null on anything that is not JSON, which the callers read as
|
|
78
|
+
* "nothing to take from here".
|
|
79
|
+
*/
|
|
80
|
+
const safeParse = (raw) => {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/** Prose out of a Codex content array. */
|
|
89
|
+
const textOf = (content) => {
|
|
90
|
+
const items = typeof content === 'string' ? safeParse(content) : content;
|
|
91
|
+
if (!Array.isArray(items))
|
|
92
|
+
return '';
|
|
93
|
+
let text = '';
|
|
94
|
+
for (const block of items) {
|
|
95
|
+
if (!block || typeof block !== 'object')
|
|
96
|
+
continue;
|
|
97
|
+
const item = block;
|
|
98
|
+
// `UserMessage` blocks are typed `text`, `AgentMessage` blocks `Text` —
|
|
99
|
+
// measured, not assumed. The type IS checked, because the writer's
|
|
100
|
+
// content kinds include `SkillMention`, `Image` and `Audio`, and one of
|
|
101
|
+
// those carrying an incidental `text` field would otherwise be glued
|
|
102
|
+
// into the middle of what reads as the person's own sentence.
|
|
103
|
+
if (typeof item.type !== 'string' || item.type.toLowerCase() !== 'text')
|
|
104
|
+
continue;
|
|
105
|
+
if (typeof item.text === 'string')
|
|
106
|
+
text += item.text;
|
|
107
|
+
}
|
|
108
|
+
return text;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Whether a completed command actually succeeded.
|
|
112
|
+
*
|
|
113
|
+
* `exit_code` decides when there is one, and the absence of one is NOT success:
|
|
114
|
+
* an interrupted or aborted command completes as an item without ever having
|
|
115
|
+
* exited, and coercing that to 0 draws a green row for work that never
|
|
116
|
+
* finished. `status` is what Codex itself reports then — measured as
|
|
117
|
+
* `'completed'` on every item in the sampled session, alongside `failed` and
|
|
118
|
+
* `aborted` in the writer's own strings.
|
|
119
|
+
*/
|
|
120
|
+
const succeeded = (item) => typeof item.exit_code === 'number' ? item.exit_code === 0 : item.status === 'completed';
|
|
121
|
+
const record = (value) => value && typeof value === 'object' ? value : null;
|
|
122
|
+
/**
|
|
123
|
+
* `+` / `-` lines of a unified diff, file header (`+++` / `---` before the first
|
|
124
|
+
* `@@`) excluded — after it, a removed `-- sql comment` is a line like any other.
|
|
125
|
+
* `unified_diff` is the field name in the writer's own strings; no `update`
|
|
126
|
+
* entry exists in any rollout measured here, so this reads the standard format
|
|
127
|
+
* and nothing Codex-specific.
|
|
128
|
+
*/
|
|
129
|
+
const diffLineCounts = (diff) => {
|
|
130
|
+
let add = 0;
|
|
131
|
+
let del = 0;
|
|
132
|
+
let inHunk = false;
|
|
133
|
+
for (const line of diff.split('\n')) {
|
|
134
|
+
if (line.startsWith('@@'))
|
|
135
|
+
inHunk = true;
|
|
136
|
+
else if (!inHunk && (line.startsWith('+++') || line.startsWith('---')))
|
|
137
|
+
continue;
|
|
138
|
+
else if (line.startsWith('+'))
|
|
139
|
+
add++;
|
|
140
|
+
else if (line.startsWith('-'))
|
|
141
|
+
del++;
|
|
142
|
+
}
|
|
143
|
+
return { add, del };
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* What a `FileChange` row says about its patch: the first path, how many more
|
|
147
|
+
* there were, and numbers about the lines — never the lines. An `add` is
|
|
148
|
+
* measured (its `content` is the new file); an `update` reads its diff; a
|
|
149
|
+
* `delete` carries no count because no sample shows what it holds.
|
|
150
|
+
*/
|
|
151
|
+
const fileChangeSummary = (changes) => {
|
|
152
|
+
// A list is not the path-keyed object measured in the sample; its index
|
|
153
|
+
// keys would draw as file names.
|
|
154
|
+
const entries = Array.isArray(changes) ? [] : Object.entries(record(changes) ?? {});
|
|
155
|
+
if (entries.length === 0)
|
|
156
|
+
return {};
|
|
157
|
+
let add = 0;
|
|
158
|
+
let del = 0;
|
|
159
|
+
for (const [, change] of entries) {
|
|
160
|
+
const c = record(change);
|
|
161
|
+
if (!c)
|
|
162
|
+
continue;
|
|
163
|
+
if (c.type === 'add' && typeof c.content === 'string')
|
|
164
|
+
add += (0, transcript_tail_js_1.resultLinesOf)(c.content);
|
|
165
|
+
else if (c.type === 'update' && typeof c.unified_diff === 'string') {
|
|
166
|
+
const counts = diffLineCounts(c.unified_diff);
|
|
167
|
+
add += counts.add;
|
|
168
|
+
del += counts.del;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const rest = entries.length - 1;
|
|
172
|
+
const target = (0, transcript_tail_js_1.oneLine)(entries[0][0].trim());
|
|
173
|
+
// The suffix is added after the clamp so a long path cannot cut it off.
|
|
174
|
+
const suffix = rest > 0 ? ` (+${rest} more)` : '';
|
|
175
|
+
return {
|
|
176
|
+
...(target ? { target: (0, transcript_tail_js_1.clamp)(target, transcript_tail_js_1.MAX_EVENT_FIELD_CHARS - suffix.length) + suffix } : {}),
|
|
177
|
+
...(add ? { add } : {}),
|
|
178
|
+
...(del ? { del } : {}),
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* The usage numbers on a `token_count` event.
|
|
183
|
+
*
|
|
184
|
+
* `last_token_usage` is this turn's, `total_token_usage` the session's running
|
|
185
|
+
* sum — the running one would make every row report the whole session.
|
|
186
|
+
*
|
|
187
|
+
* One event per usage tick, NOT one per assistant message: the sampled session
|
|
188
|
+
* holds 7 `token_count` events against 5 `AgentMessage` items across 3 turns.
|
|
189
|
+
* `mid` therefore means "one usage tick" here where it means "one API message"
|
|
190
|
+
* for Claude. The viewer only dedupes on it, so the finer grain costs nothing —
|
|
191
|
+
* but it is not a message id and nothing downstream may read it as one.
|
|
192
|
+
*
|
|
193
|
+
* Codex reports `input_tokens` as the full prompt with `cached_input_tokens`
|
|
194
|
+
* already inside it (measured: 16054 input of which 11904 cached, totalling 16067 with
|
|
195
|
+
* 13 output), so unlike Claude's three separate counters it is already the
|
|
196
|
+
* context number and must not be added to.
|
|
197
|
+
*/
|
|
198
|
+
const usageEvent = (payload, ordinal, index, model, at) => {
|
|
199
|
+
const info = record(payload.info);
|
|
200
|
+
const last = info && record(info.last_token_usage);
|
|
201
|
+
if (!last)
|
|
202
|
+
return null;
|
|
203
|
+
// Codex stamps no id on a usage event, so the line's own ordinal is the id:
|
|
204
|
+
// unique within a rollout and monotonic, which is all `mid` is for. A Codex
|
|
205
|
+
// release that renamed the field would otherwise drop every usage row,
|
|
206
|
+
// blanking the phone's token readout with no other symptom — so the line's
|
|
207
|
+
// timestamp and position stand in. The timestamp carries the weight: an
|
|
208
|
+
// index alone repeats across ticks (every tick has a `lines[0]`), and the
|
|
209
|
+
// viewer dedupes by `mid`, so two ticks' numbers would collapse into one.
|
|
210
|
+
const mid = typeof ordinal === 'number' ? String(ordinal) : `line-${at ?? 'unstamped'}-${index}`;
|
|
211
|
+
const reasoning = (0, transcript_tail_js_1.finiteNumber)(last.reasoning_output_tokens);
|
|
212
|
+
return {
|
|
213
|
+
kind: 'msg',
|
|
214
|
+
mid,
|
|
215
|
+
...(model ? { model: (0, transcript_tail_js_1.clamp)(model) } : {}),
|
|
216
|
+
out: (0, transcript_tail_js_1.finiteNumber)(last.output_tokens),
|
|
217
|
+
ctx: (0, transcript_tail_js_1.finiteNumber)(last.input_tokens),
|
|
218
|
+
// Codex reports reasoning as tokens, not blocks. A non-zero count is
|
|
219
|
+
// still "it thought here", which is the only thing this field draws.
|
|
220
|
+
...(reasoning ? { thinking: reasoning } : {}),
|
|
221
|
+
...(at ? { at } : {}),
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Codex writes one line per completed item, so no event needs merging across
|
|
226
|
+
* lines — the same place this projector is simpler than Claude's.
|
|
227
|
+
*/
|
|
228
|
+
const projectCodexEntries = (lines, opts = {}) => {
|
|
229
|
+
const events = [];
|
|
230
|
+
// The model is stamped on `turn_context`, once per turn, ahead of the usage
|
|
231
|
+
// events it applies to. Carried forward rather than looked up, because a
|
|
232
|
+
// tail that begins mid-session may never see one.
|
|
233
|
+
let model;
|
|
234
|
+
for (let index = 0; index < lines.length; index++) {
|
|
235
|
+
const entry = record(safeParse(lines[index]));
|
|
236
|
+
if (!entry)
|
|
237
|
+
continue;
|
|
238
|
+
const at = typeof entry.timestamp === 'string' ? entry.timestamp : undefined;
|
|
239
|
+
const stamp = at ? { at } : {};
|
|
240
|
+
if (entry.type === 'turn_context') {
|
|
241
|
+
const payload = record(entry.payload);
|
|
242
|
+
if (payload && typeof payload.model === 'string')
|
|
243
|
+
model = payload.model;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// `session_meta` is the cwd header the resolver reads, `world_state` and
|
|
247
|
+
// `token_usage_record` are bookkeeping, and `response_item` is the lane
|
|
248
|
+
// this projector deliberately does not read.
|
|
249
|
+
if (entry.type !== 'event_msg')
|
|
250
|
+
continue;
|
|
251
|
+
const payload = record(entry.payload);
|
|
252
|
+
if (!payload)
|
|
253
|
+
continue;
|
|
254
|
+
if (payload.type === 'token_count') {
|
|
255
|
+
const usage = usageEvent(payload, entry.ordinal, index, model, at);
|
|
256
|
+
if (usage)
|
|
257
|
+
events.push(usage);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (payload.type !== 'item_completed')
|
|
261
|
+
continue;
|
|
262
|
+
const item = record(payload.item);
|
|
263
|
+
if (!item || typeof item.type !== 'string' || typeof item.id !== 'string') {
|
|
264
|
+
opts.onUnknown?.('codex item without a type or id');
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (item.type === CODEX_USER_MESSAGE) {
|
|
268
|
+
const text = textOf(item.content).trim();
|
|
269
|
+
if (text)
|
|
270
|
+
events.push({ kind: 'prompt', text: (0, transcript_tail_js_1.clamp)(text, transcript_tail_js_1.MAX_EVENT_TEXT_CHARS), ...stamp });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (item.type === CODEX_AGENT_MESSAGE) {
|
|
274
|
+
const text = textOf(item.content).trim();
|
|
275
|
+
if (text)
|
|
276
|
+
events.push({ kind: 'text', text: (0, transcript_tail_js_1.clamp)(text, transcript_tail_js_1.MAX_EVENT_TEXT_CHARS), ...stamp });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
// Reasoning is what the agent considered, not what it did — dropped
|
|
280
|
+
// here exactly as thinking blocks are for Claude and pi. Its size still
|
|
281
|
+
// reaches the timeline, as the `thinking` field on the usage event.
|
|
282
|
+
if (item.type === CODEX_REASONING)
|
|
283
|
+
continue;
|
|
284
|
+
if (item.type === CODEX_COMMAND) {
|
|
285
|
+
const parsed = Array.isArray(item.parsed_cmd) ? record(item.parsed_cmd[0]) : null;
|
|
286
|
+
// Codex's own reading of the command — `read`, `search`, `unknown`.
|
|
287
|
+
// The raw `command` argv is never consulted: it is `['/bin/zsh',
|
|
288
|
+
// '-lc', <the whole script>]`, which is the shape that carries
|
|
289
|
+
// secrets.
|
|
290
|
+
const name = parsed && typeof parsed.type === 'string' ? parsed.type : 'exec';
|
|
291
|
+
const target = (0, transcript_tail_js_1.oneLine)((0, transcript_tail_js_1.targetFrom)(parsed, CODEX_PARSED_KEYS));
|
|
292
|
+
const resultLines = (0, transcript_tail_js_1.resultLinesOf)(item.aggregated_output);
|
|
293
|
+
events.push({ kind: 'tool', id: item.id, name: (0, transcript_tail_js_1.clamp)(name), ...(target ? { target } : {}), ...stamp });
|
|
294
|
+
events.push({
|
|
295
|
+
kind: 'tool_result',
|
|
296
|
+
id: item.id,
|
|
297
|
+
ok: succeeded(item),
|
|
298
|
+
...(resultLines ? { lines: resultLines } : {}),
|
|
299
|
+
...stamp,
|
|
300
|
+
});
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (item.type === CODEX_EXTENSION) {
|
|
304
|
+
// `kind` is the extension that ran (`web.search`), `query` what it
|
|
305
|
+
// was asked. A search query is the user's own words, already on the
|
|
306
|
+
// wire as the prompt that produced it.
|
|
307
|
+
const name = typeof item.kind === 'string' ? item.kind : 'extension';
|
|
308
|
+
const target = (0, transcript_tail_js_1.oneLine)(typeof item.query === 'string' ? item.query : undefined);
|
|
309
|
+
events.push({ kind: 'tool', id: item.id, name: (0, transcript_tail_js_1.clamp)(name), ...(target ? { target: (0, transcript_tail_js_1.clamp)(target) } : {}), ...stamp });
|
|
310
|
+
events.push({
|
|
311
|
+
kind: 'tool_result',
|
|
312
|
+
id: item.id,
|
|
313
|
+
// No failure field has been observed on an `Extension` item, and
|
|
314
|
+
// an empty result list is a search that found nothing rather
|
|
315
|
+
// than one that broke. Left as success until a failing sample
|
|
316
|
+
// says what a failure looks like — a guessed field reads as a
|
|
317
|
+
// red row on every successful search instead.
|
|
318
|
+
ok: true,
|
|
319
|
+
...(Array.isArray(item.results) ? { lines: item.results.length } : {}),
|
|
320
|
+
...stamp,
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (item.type === CODEX_FILE_CHANGE) {
|
|
325
|
+
// `apply_patch` is Codex's own name for the tool that writes this.
|
|
326
|
+
// `status` decides, as it does for a command with no exit code:
|
|
327
|
+
// `declined` is a patch the person refused, and it wrote nothing.
|
|
328
|
+
events.push({ kind: 'tool', id: item.id, name: 'apply_patch', ...fileChangeSummary(item.changes), ...stamp });
|
|
329
|
+
const resultLines = (0, transcript_tail_js_1.resultLinesOf)(item.stdout);
|
|
330
|
+
events.push({
|
|
331
|
+
kind: 'tool_result',
|
|
332
|
+
id: item.id,
|
|
333
|
+
ok: item.status === 'completed',
|
|
334
|
+
...(resultLines ? { lines: resultLines } : {}),
|
|
335
|
+
...stamp,
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (item.type === CODEX_MCP_CALL) {
|
|
340
|
+
// `server.tool`, the same dotted shape as `web.search`. The
|
|
341
|
+
// `arguments` are the MCP server's schema, not Codex's, and can
|
|
342
|
+
// hold anything a tool takes — a body, a URL, a token — so no
|
|
343
|
+
// target is drawn from them and neither is the result's text.
|
|
344
|
+
const server = typeof item.server === 'string' ? item.server : '';
|
|
345
|
+
const tool = typeof item.tool === 'string' ? item.tool : '';
|
|
346
|
+
const name = server && tool ? `${server}.${tool}` : tool || server || 'mcp';
|
|
347
|
+
const result = record(item.result);
|
|
348
|
+
const resultLines = (0, transcript_tail_js_1.resultLinesOf)(result?.content);
|
|
349
|
+
events.push({ kind: 'tool', id: item.id, name: (0, transcript_tail_js_1.clamp)(name), ...stamp });
|
|
350
|
+
events.push({
|
|
351
|
+
kind: 'tool_result',
|
|
352
|
+
id: item.id,
|
|
353
|
+
// An `error` beside `completed` is still a failure: Codex files
|
|
354
|
+
// a call that reached the server and was refused as completed.
|
|
355
|
+
// The field's name is the writer's (`McpToolCallError`); no
|
|
356
|
+
// failing call is in any rollout measured here. `isError` is
|
|
357
|
+
// MCP's own tool-level failure flag inside the result.
|
|
358
|
+
ok: item.status === 'completed' && item.error == null && result?.isError !== true,
|
|
359
|
+
...(resultLines ? { lines: resultLines } : {}),
|
|
360
|
+
...stamp,
|
|
361
|
+
});
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
// An item type this file has never seen falls out here, and the
|
|
365
|
+
// timeline is thinner by one row rather than wrong by one. Naming it
|
|
366
|
+
// without reading its fields would strand a call that never resolves;
|
|
367
|
+
// reading its fields without a sample is guessing at a vendor's format.
|
|
368
|
+
// Saying that it happened is neither.
|
|
369
|
+
opts.onUnknown?.(`codex item ${item.type}`);
|
|
370
|
+
}
|
|
371
|
+
return (0, transcript_tail_js_1.sliceSinceLastPrompt)(events, opts);
|
|
372
|
+
};
|
|
373
|
+
exports.projectCodexEntries = projectCodexEntries;
|