@with-logic/elwood 0.1.1 → 0.1.3
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/CHANGELOG.md +30 -3
- package/CONTRIBUTING.md +104 -0
- package/README.md +43 -31
- package/SECURITY.md +42 -0
- package/dist/claude/model-picker.d.ts.map +1 -1
- package/dist/claude/model-picker.js +4 -3
- package/dist/claude/model-picker.js.map +1 -1
- package/dist/claude/model-switch-confirmation.js +9 -1
- package/dist/claude/model-switch-confirmation.js.map +1 -1
- package/dist/claude/screen-table.js +1 -1
- package/dist/claude/screen-table.js.map +1 -1
- package/dist/claude/session/build.d.ts.map +1 -1
- package/dist/claude/session/build.js +3 -0
- package/dist/claude/session/build.js.map +1 -1
- package/dist/claude/session/start-or-resume.js +1 -0
- package/dist/claude/session/start-or-resume.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +13 -10
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interactive/spawn.d.ts.map +1 -1
- package/dist/cli/interactive/spawn.js +30 -9
- package/dist/cli/interactive/spawn.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +9 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/models/combined.d.ts +6 -0
- package/dist/cli/models/combined.d.ts.map +1 -0
- package/dist/cli/models/combined.js +92 -0
- package/dist/cli/models/combined.js.map +1 -0
- package/dist/cli/models/index.d.ts +2 -0
- package/dist/cli/models/index.d.ts.map +1 -1
- package/dist/cli/models/index.js +21 -7
- package/dist/cli/models/index.js.map +1 -1
- package/dist/cli/models/render.d.ts +4 -0
- package/dist/cli/models/render.d.ts.map +1 -1
- package/dist/cli/models/render.js +28 -1
- package/dist/cli/models/render.js.map +1 -1
- package/dist/cli/models/result.d.ts +13 -0
- package/dist/cli/models/result.d.ts.map +1 -0
- package/dist/cli/models/result.js +2 -0
- package/dist/cli/models/result.js.map +1 -0
- package/dist/cli/models/settings.d.ts +61 -0
- package/dist/cli/models/settings.d.ts.map +1 -0
- package/dist/cli/models/settings.js +24 -0
- package/dist/cli/models/settings.js.map +1 -0
- package/dist/cli/models/signals.d.ts +11 -0
- package/dist/cli/models/signals.d.ts.map +1 -0
- package/dist/cli/models/signals.js +21 -0
- package/dist/cli/models/signals.js.map +1 -0
- package/dist/cli/request/index.js +1 -1
- package/dist/cli/request/index.js.map +1 -1
- package/dist/cli/request/layers.d.ts +2 -0
- package/dist/cli/request/layers.d.ts.map +1 -1
- package/dist/cli/request/layers.js +9 -5
- package/dist/cli/request/layers.js.map +1 -1
- package/dist/cli/request/validation.d.ts.map +1 -1
- package/dist/cli/request/validation.js +0 -4
- package/dist/cli/request/validation.js.map +1 -1
- package/dist/cli/run/output.d.ts.map +1 -1
- package/dist/cli/run/output.js +3 -6
- package/dist/cli/run/output.js.map +1 -1
- package/dist/cli/sessions/index.d.ts +9 -2
- package/dist/cli/sessions/index.d.ts.map +1 -1
- package/dist/cli/sessions/index.js +2 -6
- package/dist/cli/sessions/index.js.map +1 -1
- package/dist/cli/sessions/settings.d.ts +11 -0
- package/dist/cli/sessions/settings.d.ts.map +1 -0
- package/dist/cli/sessions/settings.js +26 -0
- package/dist/cli/sessions/settings.js.map +1 -0
- package/dist/codex/model-picker.d.ts.map +1 -1
- package/dist/codex/model-picker.js +3 -2
- package/dist/codex/model-picker.js.map +1 -1
- package/dist/codex/session/build.d.ts.map +1 -1
- package/dist/codex/session/build.js +5 -7
- package/dist/codex/session/build.js.map +1 -1
- package/dist/codex/session/start-or-resume.js +1 -0
- package/dist/codex/session/start-or-resume.js.map +1 -1
- package/dist/core/input/index.d.ts.map +1 -1
- package/dist/core/input/index.js +15 -3
- package/dist/core/input/index.js.map +1 -1
- package/dist/core/models/input.d.ts +3 -0
- package/dist/core/models/input.d.ts.map +1 -0
- package/dist/core/models/input.js +9 -0
- package/dist/core/models/input.js.map +1 -0
- package/dist/core/models/picker.d.ts +2 -1
- package/dist/core/models/picker.d.ts.map +1 -1
- package/dist/core/models/picker.js +6 -5
- package/dist/core/models/picker.js.map +1 -1
- package/dist/core/models/tui-screen.d.ts +1 -1
- package/dist/core/models/tui-screen.d.ts.map +1 -1
- package/dist/core/models/tui-screen.js +26 -3
- package/dist/core/models/tui-screen.js.map +1 -1
- package/dist/runtime/session/base.d.ts.map +1 -1
- package/dist/runtime/session/base.js +4 -1
- package/dist/runtime/session/base.js.map +1 -1
- package/dist/runtime/session/commands.d.ts +2 -1
- package/dist/runtime/session/commands.d.ts.map +1 -1
- package/dist/runtime/session/commands.js +10 -4
- package/dist/runtime/session/commands.js.map +1 -1
- package/dist/runtime/session/lifecycle.d.ts +3 -4
- package/dist/runtime/session/lifecycle.d.ts.map +1 -1
- package/dist/runtime/session/lifecycle.js +6 -5
- package/dist/runtime/session/lifecycle.js.map +1 -1
- package/dist/state/directories.d.ts +4 -0
- package/dist/state/directories.d.ts.map +1 -0
- package/dist/state/directories.js +41 -0
- package/dist/state/directories.js.map +1 -0
- package/dist/state/files.d.ts.map +1 -1
- package/dist/state/files.js +25 -11
- package/dist/state/files.js.map +1 -1
- package/dist/state/private-session.d.ts.map +1 -1
- package/dist/state/private-session.js +2 -0
- package/dist/state/private-session.js.map +1 -1
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +7 -1
- package/dist/state/store.js.map +1 -1
- package/docs/cli-behavior.md +1 -1
- package/docs/cli-output.md +40 -10
- package/docs/cli.md +3 -2
- package/docs/codex/cli.md +57 -0
- package/docs/codex/config.md +47 -0
- package/docs/codex/hooks.md +70 -0
- package/examples/README.md +56 -0
- package/examples/full.ts +113 -0
- package/examples/minimal.ts +15 -0
- package/examples/stream.ts +39 -0
- package/examples/support.ts +71 -0
- package/package.json +10 -5
- package/prd/01-overview.md +21 -0
- package/prd/02-goals.md +47 -0
- package/prd/03-terminology.md +32 -0
- package/prd/04-architecture.md +121 -0
- package/prd/05-api/5.1-starting-claude.md +108 -0
- package/prd/05-api/5.2-resuming-claude.md +86 -0
- package/prd/05-api/5.3-claude-session-api.md +658 -0
- package/prd/05-api/5.4-events.md +145 -0
- package/prd/05-api/5.5-starting-codex.md +106 -0
- package/prd/05-api/5.6-resuming-codex.md +48 -0
- package/prd/05-api/5.7-codex-session-api.md +425 -0
- package/prd/05-api/5.8-session-api.md +122 -0
- package/prd/05-api/5.9-loops.md +170 -0
- package/prd/05-api/README.md +19 -0
- package/prd/06-claude-hook-bridge.md +164 -0
- package/prd/07-claude-policy.md +15 -0
- package/prd/07a-codex-hook-bridge.md +76 -0
- package/prd/08-state.md +127 -0
- package/prd/09-lifecycle.md +222 -0
- package/prd/10-errors.md +55 -0
- package/prd/11-test-app.md +53 -0
- package/prd/12-e2e.md +23 -0
- package/prd/12a-cli.md +402 -0
- package/prd/13-latitude.md +12 -0
- package/prd/14-conformance.md +333 -0
- package/prd/15-open-notes.md +8 -0
- package/prd/README.md +45 -0
package/dist/state/store.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { existsSync, rmSync } from "node:fs";
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
10
10
|
import { elwoodError, toError } from "../core/errors.js";
|
|
11
|
+
import { assertStatePath } from "./directories.js";
|
|
11
12
|
import { safeSessionDir, secureMkdir, sharedMkdir, writePrivateFileAtomic, writeSharedFile, } from "./files.js";
|
|
12
13
|
import { currentFileOwner, readPrivateFile } from "./private-read.js";
|
|
13
14
|
import { removeSocketHome } from "./socket-home.js";
|
|
@@ -29,6 +30,8 @@ export function createSessionRecord(input) {
|
|
|
29
30
|
}
|
|
30
31
|
export function prepareStateDir(stateDir, options = {}) {
|
|
31
32
|
const root = resolve(stateDir);
|
|
33
|
+
assertStatePath(root);
|
|
34
|
+
assertStatePath(join(root, "sessions"));
|
|
32
35
|
if (options.gitignore === true) {
|
|
33
36
|
sharedMkdir(root);
|
|
34
37
|
}
|
|
@@ -79,7 +82,10 @@ export function removeSessionFiles(input) {
|
|
|
79
82
|
// both ran, so teardown removes everything it can and still surfaces the fault.
|
|
80
83
|
let firstError;
|
|
81
84
|
firstError = tryRemove(() => removeSocketHome(input.socketHome), firstError);
|
|
82
|
-
firstError = tryRemove(() =>
|
|
85
|
+
firstError = tryRemove(() => {
|
|
86
|
+
assertStatePath(dir);
|
|
87
|
+
rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
}, firstError);
|
|
83
89
|
if (firstError !== undefined) {
|
|
84
90
|
throw elwoodError("teardown_failed", "Could not remove Elwood session files.", {
|
|
85
91
|
cause: firstError instanceof Error ? firstError.message : String(firstError),
|
package/dist/state/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"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"}
|
package/docs/cli-behavior.md
CHANGED
|
@@ -17,7 +17,7 @@ sources are:
|
|
|
17
17
|
|
|
18
18
|
Each fact below was verified against a real CLI (versions noted where relevant).
|
|
19
19
|
When a fact drives an implementation decision, the code and the matching PRD
|
|
20
|
-
conformance criterion are cited. If you change behavior here, update `
|
|
20
|
+
conformance criterion are cited. If you change behavior here, update `prd/`
|
|
21
21
|
first (see `CLAUDE.md` / `AGENTS.md`), then this file.
|
|
22
22
|
|
|
23
23
|
## Readiness
|
package/docs/cli-output.md
CHANGED
|
@@ -5,9 +5,9 @@ exit codes, and safe shell recipes. Start with [cli.md](cli.md) for usage.
|
|
|
5
5
|
|
|
6
6
|
## Text, verbose, and stream
|
|
7
7
|
|
|
8
|
-
Text is the default output protocol.
|
|
9
|
-
|
|
10
|
-
tool-name, warning, and cleanup progress; it does not repeat assistant or
|
|
8
|
+
Text is the default output protocol. Live session warnings are quiet by default,
|
|
9
|
+
including during `elwood models`. Errors remain visible. `--verbose` adds compact
|
|
10
|
+
elapsed phase, tool-name, warning, and cleanup progress on stderr; it does not repeat assistant or
|
|
11
11
|
thinking text. `--debug` adds full sanitized event details. `--stream` emits
|
|
12
12
|
assistant text as it arrives and is valid only with text output; use
|
|
13
13
|
`--no-stream` when config enabled streaming but a script needs JSON.
|
|
@@ -26,7 +26,9 @@ a downstream pipe closing early triggers cleanup without an uncaught `EPIPE`.
|
|
|
26
26
|
|
|
27
27
|
`--output json` emits exactly one version-1 document. Its stable fields are
|
|
28
28
|
`schemaVersion`, `type`, `agent`, `response`, `sessionId`, `durationMs`,
|
|
29
|
-
`cleanup`, and, on failure, `error
|
|
29
|
+
`cleanup`, and, on failure, `error`. The examples below use `--ephemeral`, so
|
|
30
|
+
cleanup removes session state and `sessionId` is null. By default, CLI turns
|
|
31
|
+
preserve state and return a resumable session ID once one is available:
|
|
30
32
|
|
|
31
33
|
```json
|
|
32
34
|
{
|
|
@@ -66,7 +68,8 @@ a usage error or `no_agent_found`) reports `"agent": null`.
|
|
|
66
68
|
`--output jsonl` emits sequenced version-1 `text`, `thinking`, `tool`,
|
|
67
69
|
`status`, and `warning` records followed by exactly one `result` or `error`
|
|
68
70
|
record. Every record has `sequence` and `elapsedMs`; tool calls and results
|
|
69
|
-
share `toolCallId` when the agent supplies one
|
|
71
|
+
share `toolCallId` when the agent supplies one. This example also uses
|
|
72
|
+
`--ephemeral`:
|
|
70
73
|
|
|
71
74
|
```jsonl
|
|
72
75
|
{"schemaVersion":1,"type":"status","status":"running","sequence":1,"elapsedMs":114}
|
|
@@ -77,7 +80,9 @@ share `toolCallId` when the agent supplies one:
|
|
|
77
80
|
```
|
|
78
81
|
|
|
79
82
|
JSONL warning records stay on stdout so the machine protocol remains ordered;
|
|
80
|
-
text and JSON runs surface warnings on stderr
|
|
83
|
+
text and JSON runs surface session warnings on stderr only with `--verbose`
|
|
84
|
+
or `--debug`. `ELWOOD_VERBOSE=true` or config `verbose: true` also enables them;
|
|
85
|
+
`--no-verbose` overrides inherited verbosity (but not explicit `--debug`).
|
|
81
86
|
|
|
82
87
|
## Session and model listings
|
|
83
88
|
|
|
@@ -108,10 +113,35 @@ in JSON mode, and `sessions` never starts an agent:
|
|
|
108
113
|
check, so a force-killed owner can leave a stale "live" until the session is
|
|
109
114
|
next started or torn down. An empty state directory yields an empty `sessions`
|
|
110
115
|
array (text mode prints a notice on stderr and nothing on stdout). Unreadable
|
|
111
|
-
records are skipped with one stderr warning each
|
|
116
|
+
records are skipped with one stderr warning each, even without verbosity,
|
|
117
|
+
because the listing omits those results.
|
|
112
118
|
|
|
113
|
-
`elwood models`
|
|
114
|
-
|
|
119
|
+
`elwood models` probes Claude then Codex, regardless of saved or environment
|
|
120
|
+
agent defaults. Text includes an `AGENT` column. JSON contains successful
|
|
121
|
+
catalogs and canonical per-agent error records in one document:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"schemaVersion": 1,
|
|
126
|
+
"type": "models",
|
|
127
|
+
"agents": [
|
|
128
|
+
{ "agent": "claude", "models": [] },
|
|
129
|
+
{ "agent": "codex", "models": [] }
|
|
130
|
+
],
|
|
131
|
+
"errors": []
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each probe opens and cancels its model picker and always removes its session
|
|
136
|
+
state. An empty `models` array means that probe succeeded with no rows. If an
|
|
137
|
+
adapter fails, its catalog is absent from `agents` and its canonical error
|
|
138
|
+
record appears in `errors`, including `agent`, `error`, `durationMs`, and
|
|
139
|
+
`cleanup`. Available catalogs remain present. Incomplete results exit nonzero:
|
|
140
|
+
130 for interruption, then 124 for timeout, 2 for usage errors, or 1 for other
|
|
141
|
+
failures, in that precedence order. `--timeout` is a whole-command budget; no
|
|
142
|
+
next probe starts after timeout or Ctrl-C.
|
|
143
|
+
|
|
144
|
+
Explicit `elwood models --agent claude` retains its single-agent document:
|
|
115
145
|
|
|
116
146
|
```json
|
|
117
147
|
{
|
|
@@ -125,7 +155,7 @@ tears the session down:
|
|
|
125
155
|
}
|
|
126
156
|
```
|
|
127
157
|
|
|
128
|
-
|
|
158
|
+
Single-agent failures use the same `error` document and statuses as a run. Useful recipes:
|
|
129
159
|
|
|
130
160
|
```sh
|
|
131
161
|
id=$(elwood sessions --output json | jq -r '.sessions[0].id')
|
package/docs/cli.md
CHANGED
|
@@ -83,8 +83,9 @@ Boolean environment values are exactly `true` or `false`.
|
|
|
83
83
|
Run `elwood --help` for the full flag list. Launch controls include `--model`,
|
|
84
84
|
`--reasoning-effort`, `--persona`, `--claude-permission-mode`,
|
|
85
85
|
`--codex-sandbox`, `--codex-approval-policy`, `--state-dir`, `--verbose`,
|
|
86
|
-
`--debug`, and `--trust` / `--no-trust`.
|
|
87
|
-
|
|
86
|
+
`--debug`, and `--trust` / `--no-trust`. Live session warnings are quiet by
|
|
87
|
+
default; use `--verbose` or `--debug` to see them, including for `elwood models`.
|
|
88
|
+
The built-in non-interactive posture is Claude `dontAsk`, or Codex `workspace-write` with approval policy `never`.
|
|
88
89
|
|
|
89
90
|
For an unattended run that must never stop on a permission prompt, prefer the
|
|
90
91
|
agent-neutral `--high-trust` over per-agent posture flags. It selects Claude
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Codex CLI Notes
|
|
2
|
+
|
|
3
|
+
Sources researched:
|
|
4
|
+
|
|
5
|
+
- https://developers.openai.com/codex/cli/features
|
|
6
|
+
- https://developers.openai.com/codex/cli/reference
|
|
7
|
+
- local `codex --help` and `codex --version` output
|
|
8
|
+
|
|
9
|
+
## Interactive Mode
|
|
10
|
+
|
|
11
|
+
`codex` launches an interactive full-screen terminal UI. That is the mode Elwood
|
|
12
|
+
must wrap. The parent app should control Codex through PTY input and observe it
|
|
13
|
+
through hooks, not through `codex exec`, the SDK, or app-server APIs.
|
|
14
|
+
|
|
15
|
+
Codex accepts an optional initial prompt, but Elwood starts without one by
|
|
16
|
+
default and sends prompts later through the terminal input path.
|
|
17
|
+
|
|
18
|
+
Useful CLI capabilities for Elwood:
|
|
19
|
+
|
|
20
|
+
- `codex` starts a new interactive session.
|
|
21
|
+
- `codex resume <SESSION_ID>` resumes a specific interactive session.
|
|
22
|
+
- `codex resume --last` resumes the most recent interactive session for the
|
|
23
|
+
current working directory.
|
|
24
|
+
- `--cd <DIR>` tells Codex which project root to use.
|
|
25
|
+
- `--config key=value` applies one-off TOML configuration overrides.
|
|
26
|
+
- `--model <MODEL>`, `--profile <NAME>`, `--sandbox <MODE>`, and
|
|
27
|
+
`--ask-for-approval <POLICY>` map naturally to adapter launch options.
|
|
28
|
+
- Some Codex versions expose `--dangerously-bypass-hook-trust`, which lets
|
|
29
|
+
generated Elwood hooks run for one invocation without requiring a persisted
|
|
30
|
+
trust prompt. Elwood must detect support before passing it because other Codex
|
|
31
|
+
versions reject unknown flags. Detection is cached for the lifetime of the
|
|
32
|
+
Elwood process.
|
|
33
|
+
- Codex can also render interactive trust and update prompts before the session
|
|
34
|
+
is usable. Elwood should select `Trust all and continue` for hook prompts, and
|
|
35
|
+
skip TUI update prompts because API-level `autoupdate` runs `codex update`
|
|
36
|
+
before launch when requested. Detection should use Elwood's headless xterm.js
|
|
37
|
+
screen snapshot, not raw ANSI/PTY byte parsing.
|
|
38
|
+
- Codex may print non-fatal environment warnings before the session is ready,
|
|
39
|
+
such as MCP servers that are not logged in. Elwood should preserve raw
|
|
40
|
+
terminal output and also project recognized warnings into typed `warning`
|
|
41
|
+
events with remediation commands.
|
|
42
|
+
|
|
43
|
+
## Resume Implications
|
|
44
|
+
|
|
45
|
+
Codex stores transcripts locally under Codex-owned state. Elwood should persist
|
|
46
|
+
only the Codex session id needed to call `codex resume <SESSION_ID>`. If that id
|
|
47
|
+
has not arrived through hooks yet, Elwood fails with `resume_unavailable`.
|
|
48
|
+
Elwood must not silently fall back to `codex resume --last` because that can
|
|
49
|
+
attach the caller's Elwood session id to the wrong Codex conversation. A future
|
|
50
|
+
API may add an explicit opt-in fallback, but it is not part of the current
|
|
51
|
+
contract.
|
|
52
|
+
|
|
53
|
+
## Shell Implications
|
|
54
|
+
|
|
55
|
+
The process should still be launched from the user's normal interactive login
|
|
56
|
+
shell so `codex` resolution, auth environment, and user shell startup behavior
|
|
57
|
+
match opening Terminal.app manually.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Codex Configuration Notes
|
|
2
|
+
|
|
3
|
+
Sources researched:
|
|
4
|
+
|
|
5
|
+
- https://developers.openai.com/codex/config-basic
|
|
6
|
+
- https://developers.openai.com/codex/config-advanced
|
|
7
|
+
- https://developers.openai.com/codex/config-reference
|
|
8
|
+
- https://developers.openai.com/codex/hooks
|
|
9
|
+
|
|
10
|
+
## Layers
|
|
11
|
+
|
|
12
|
+
Codex reads configuration from CLI flags and `--config` overrides first, then
|
|
13
|
+
profiles, trusted project `.codex/config.toml` files, user config, system config,
|
|
14
|
+
and built-in defaults.
|
|
15
|
+
|
|
16
|
+
Project-local `.codex` layers are only loaded when Codex trusts the project.
|
|
17
|
+
Because Elwood should be minimally invasive, it should not edit user or project
|
|
18
|
+
Codex config by default.
|
|
19
|
+
|
|
20
|
+
## Elwood Strategy
|
|
21
|
+
|
|
22
|
+
Elwood should generate session-scoped hook bridge files under `.elwood/` and
|
|
23
|
+
inject Codex hook configuration through `--config` one-off overrides. This keeps
|
|
24
|
+
existing `.codex/config.toml` and `~/.codex/config.toml` intact while still
|
|
25
|
+
letting Codex merge user defaults and policies normally.
|
|
26
|
+
Elwood applies `hookTrust="trust-all"` after caller overrides so parent apps
|
|
27
|
+
cannot accidentally create an unusable wrapper session.
|
|
28
|
+
|
|
29
|
+
The generated command should enable or configure:
|
|
30
|
+
|
|
31
|
+
- hooks for every supported Codex hook event;
|
|
32
|
+
- the bridge command and timeout for each event;
|
|
33
|
+
- `hookTrust="trust-all"`, because Elwood cannot observe or control Codex
|
|
34
|
+
without trusted hooks;
|
|
35
|
+
- optional launch policy such as model, profile, sandbox, approval policy, and
|
|
36
|
+
caller-supplied raw config overrides;
|
|
37
|
+
- `--dangerously-bypass-hook-trust` when the installed Codex CLI advertises it,
|
|
38
|
+
because the generated hooks are ephemeral Elwood-owned bridge commands. Elwood
|
|
39
|
+
must omit this flag for Codex versions that do not support it.
|
|
40
|
+
|
|
41
|
+
If Codex still renders a `Hooks need review` TUI prompt, Elwood should select
|
|
42
|
+
`Trust all and continue` through the headless xterm.js input path so the session
|
|
43
|
+
is usable headlessly.
|
|
44
|
+
|
|
45
|
+
If a managed Codex requirement disallows unmanaged hooks, Codex may ignore or
|
|
46
|
+
reject Elwood hook config. Elwood should surface startup/bridge failures through
|
|
47
|
+
typed errors where possible and hook runtime failures through `hookError`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Codex Hooks Notes
|
|
2
|
+
|
|
3
|
+
Sources researched:
|
|
4
|
+
|
|
5
|
+
- https://developers.openai.com/codex/hooks
|
|
6
|
+
- https://github.com/openai/codex/tree/main/docs
|
|
7
|
+
- local `codex features list` output
|
|
8
|
+
|
|
9
|
+
## Events
|
|
10
|
+
|
|
11
|
+
The current documented Codex hook events relevant to Elwood are:
|
|
12
|
+
|
|
13
|
+
- `SessionStart`
|
|
14
|
+
- `SubagentStart`
|
|
15
|
+
- `PreToolUse`
|
|
16
|
+
- `PermissionRequest`
|
|
17
|
+
- `PostToolUse`
|
|
18
|
+
- `PreCompact`
|
|
19
|
+
- `PostCompact`
|
|
20
|
+
- `UserPromptSubmit`
|
|
21
|
+
- `SubagentStop`
|
|
22
|
+
- `Stop`
|
|
23
|
+
|
|
24
|
+
`SessionStart`, `PreToolUse`, `PermissionRequest`, `PostToolUse`,
|
|
25
|
+
`UserPromptSubmit`, `SubagentStart`, `SubagentStop`, and `Stop` include a Codex
|
|
26
|
+
permission mode field. Turn-scoped hooks include `turn_id`.
|
|
27
|
+
|
|
28
|
+
## Matchers
|
|
29
|
+
|
|
30
|
+
Hook matchers are regex strings. Tool hooks match `tool_name`; compaction hooks
|
|
31
|
+
match `manual` or `auto`; `SessionStart` matches the source such as startup or
|
|
32
|
+
resume; subagent hooks match agent type. `UserPromptSubmit` and `Stop` currently
|
|
33
|
+
ignore matchers.
|
|
34
|
+
|
|
35
|
+
For file edits, Codex reports the canonical tool name `apply_patch`, while
|
|
36
|
+
matchers may also use `Edit` or `Write`.
|
|
37
|
+
|
|
38
|
+
Tool hooks currently cover `Bash`, `apply_patch`, and MCP tool calls. They do
|
|
39
|
+
not cover every TUI-visible activity. In particular, Codex docs state that web
|
|
40
|
+
search and other non-shell/non-MCP tool calls do not trigger `PreToolUse` or
|
|
41
|
+
`PostToolUse` today. Reasoning/status lines shown in the TUI are also not hook
|
|
42
|
+
events.
|
|
43
|
+
|
|
44
|
+
Elwood therefore treats hooks as the control/approval path and uses live-only
|
|
45
|
+
transcript observation as a secondary rendering path for parent apps that need
|
|
46
|
+
to display reasoning phases, web search calls, assistant messages, and other
|
|
47
|
+
Codex-visible activity.
|
|
48
|
+
|
|
49
|
+
## Response Model
|
|
50
|
+
|
|
51
|
+
No output with exit code `0` means no decision and Codex continues.
|
|
52
|
+
|
|
53
|
+
Codex accepts event-specific JSON output. Important shapes for Elwood:
|
|
54
|
+
|
|
55
|
+
- `PreToolUse` can deny a supported tool call with `permissionDecision: "deny"`
|
|
56
|
+
and a reason, add additional context, or allow a rewritten tool input.
|
|
57
|
+
- `PermissionRequest` can decide `allow` or `deny` with `{ hookSpecificOutput:
|
|
58
|
+
{ hookEventName, decision: { behavior, message? } } }`. It must not return
|
|
59
|
+
updated input or permission edits.
|
|
60
|
+
- `Stop` and `SubagentStop` can return `decision: "block"` with a reason to ask
|
|
61
|
+
Codex to continue instead of marking the turn done.
|
|
62
|
+
- `PostToolUse`, `UserPromptSubmit`, `SubagentStart`, `PreCompact`, and
|
|
63
|
+
`PostCompact` are observe-only in Elwood until Codex documents a stable
|
|
64
|
+
response contract for them.
|
|
65
|
+
|
|
66
|
+
Elwood should fail open when parent handlers are missing, time out, throw, or
|
|
67
|
+
return an invalid response for the event.
|
|
68
|
+
|
|
69
|
+
Handlers should return `undefined` for no decision. Empty objects are invalid so
|
|
70
|
+
accidentally empty responses surface as `hookError` and fail open.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Elwood Examples
|
|
2
|
+
|
|
3
|
+
## Minimal: ask and answer (`minimal.ts`)
|
|
4
|
+
|
|
5
|
+
The smallest possible use of Elwood. Construct a session, `send` a message, get the
|
|
6
|
+
assistant's reply back as a string, ask a follow-up. The session starts lazily on the
|
|
7
|
+
first `send`, so there is no start ceremony and no manual "wait for ready".
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm run example:minimal
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const session = new CodexSession({ autotrust: true });
|
|
15
|
+
const joke = await session.send("Tell me a joke that involves a dog.");
|
|
16
|
+
const rewrite = await session.send("Now rewrite it to be about a cat.");
|
|
17
|
+
await session.close();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Streaming: watch the work (`stream.ts`)
|
|
21
|
+
|
|
22
|
+
When you want the intermediate steps, `stream` yields typed events (`thinking`,
|
|
23
|
+
`tool_call`, `tool_result`, `text`) as they arrive. `send` is just this generator
|
|
24
|
+
drained for the `text`, so the two share one turn boundary.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm run example:stream
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
for await (const event of session.stream("List the files, then summarize the project.")) {
|
|
32
|
+
if (event.type === "text") process.stdout.write(event.text);
|
|
33
|
+
// event.type is also "thinking" | "tool_call" | "tool_result"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Full: the raw control surface (`full.ts`)
|
|
38
|
+
|
|
39
|
+
`full.ts` drops below the ergonomic facade to the low-level API: `startClaude`/
|
|
40
|
+
`startCodex`, raw `activity`/`warning`/`terminal:exit` events, an explicit
|
|
41
|
+
`waitForSettled` helper (from `support.ts`) that watches `status` and
|
|
42
|
+
`terminal:exit`, and CLI flags for agent/cwd/prompt/timeout. Reach for this when you
|
|
43
|
+
need full control over the session lifecycle; most callers want `send`/`stream` above.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm run example:full
|
|
47
|
+
npm run example:full -- --agent codex --cwd . --prompt "Summarize this repo in one paragraph."
|
|
48
|
+
npm run example:full -- --agent claude --cwd . --prompt "Summarize this repo in one paragraph."
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
All examples use `autotrust: true` so embedded sessions can move past known workspace
|
|
52
|
+
trust prompts. The package scripts run the TypeScript examples under a small Node
|
|
53
|
+
supervisor because `node-pty` owns real PTYs more reliably there and the supervisor can
|
|
54
|
+
kill the example process tree on Ctrl-C. The examples import from `../src/index.ts`
|
|
55
|
+
so they run from a checkout without a build; in your own project import the same
|
|
56
|
+
symbols from `@with-logic/elwood`.
|
package/examples/full.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full runnable Elwood example for starting one agent session, sending one
|
|
3
|
+
* message, logging rich events, and cleaning up the process.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { startClaude, startCodex } from "../src/index.ts";
|
|
8
|
+
import {
|
|
9
|
+
type Agent,
|
|
10
|
+
cleanupSession,
|
|
11
|
+
type ExampleSession,
|
|
12
|
+
errorMessage,
|
|
13
|
+
isTerminalStatus,
|
|
14
|
+
textSuffix,
|
|
15
|
+
waitForSettled,
|
|
16
|
+
} from "./support.ts";
|
|
17
|
+
|
|
18
|
+
type Options = {
|
|
19
|
+
readonly agent: Agent;
|
|
20
|
+
readonly cwd: string;
|
|
21
|
+
readonly keep: boolean;
|
|
22
|
+
readonly prompt: string;
|
|
23
|
+
readonly timeoutMs: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const defaultPrompt = "Say hello from Elwood in one short sentence, then stop.";
|
|
27
|
+
const options = parseArgs(process.argv.slice(2));
|
|
28
|
+
let session: ExampleSession | undefined;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
session = await startSession(options);
|
|
32
|
+
wireLogs(session);
|
|
33
|
+
process.stdout.write(`started ${options.agent} session ${session.elwoodSessionId}\n`);
|
|
34
|
+
await session.sendMessage(options.prompt);
|
|
35
|
+
if (!(await waitForSettled(session, options.timeoutMs))) {
|
|
36
|
+
throw new Error(`Timed out after ${options.timeoutMs}ms waiting for the agent to finish.`);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
process.stderr.write(`${errorMessage(error)}\n`);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
} finally {
|
|
42
|
+
if (session && !options.keep && !isTerminalStatus(session.status)) await cleanupSession(session);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function startSession(options: Options): Promise<ExampleSession> {
|
|
46
|
+
const common = { cwd: options.cwd, autotrust: true, initialSize: { cols: 120, rows: 40 } };
|
|
47
|
+
return options.agent === "claude" ? await startClaude(common) : await startCodex(common);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function wireLogs(session: ExampleSession): void {
|
|
51
|
+
session.on("activity", (event) => {
|
|
52
|
+
process.stdout.write(
|
|
53
|
+
`[activity] ${event.agent}:${event.kind}:${event.label}${textSuffix(event.text)}\n`,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
session.on("warning", (event) => {
|
|
57
|
+
process.stdout.write(`[warning] ${event.agent}:${event.code} ${event.message}\n`);
|
|
58
|
+
});
|
|
59
|
+
session.on("hookError", (event) => {
|
|
60
|
+
process.stdout.write(`[hookError] ${event.hookEventName}:${event.category} ${event.message}\n`);
|
|
61
|
+
});
|
|
62
|
+
session.on("terminal:exit", (event) => {
|
|
63
|
+
process.stdout.write(`[terminal:exit] code=${event.exitCode}\n`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseArgs(args: readonly string[]): Options {
|
|
68
|
+
let agent: Agent = "codex";
|
|
69
|
+
let cwd = process.cwd();
|
|
70
|
+
let keep = false;
|
|
71
|
+
let prompt = defaultPrompt;
|
|
72
|
+
let timeoutMs = 120_000;
|
|
73
|
+
let index = 0;
|
|
74
|
+
while (index < args.length) {
|
|
75
|
+
const arg = args[index];
|
|
76
|
+
if (arg === "--help") return printUsageAndExit();
|
|
77
|
+
if (arg === "--agent")
|
|
78
|
+
[agent, index] = [parseAgent(requireValue(args, index + 1, arg)), index + 2];
|
|
79
|
+
else if (arg === "--cwd")
|
|
80
|
+
[cwd, index] = [resolve(requireValue(args, index + 1, arg)), index + 2];
|
|
81
|
+
else if (arg === "--prompt") [prompt, index] = [requireValue(args, index + 1, arg), index + 2];
|
|
82
|
+
else if (arg === "--timeout-ms")
|
|
83
|
+
[timeoutMs, index] = [Number(requireValue(args, index + 1, arg)), index + 2];
|
|
84
|
+
else if (arg === "--keep") [keep, index] = [true, index + 1];
|
|
85
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
86
|
+
}
|
|
87
|
+
if (!prompt) throw new Error("--prompt must not be empty.");
|
|
88
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0)
|
|
89
|
+
throw new Error("--timeout-ms must be positive.");
|
|
90
|
+
return { agent, cwd: resolve(cwd), keep, prompt, timeoutMs };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function requireValue(args: readonly string[], index: number, flag: string): string {
|
|
94
|
+
const value = args[index];
|
|
95
|
+
if (!value) throw new Error(`Missing value for ${flag}.`);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseAgent(value: string): Agent {
|
|
100
|
+
if (value === "claude" || value === "codex") return value;
|
|
101
|
+
throw new Error("--agent must be claude or codex.");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function printUsageAndExit(): never {
|
|
105
|
+
process.stdout.write(
|
|
106
|
+
[
|
|
107
|
+
"Usage: npm run example:full",
|
|
108
|
+
' npm run example:full -- --agent codex --cwd . --prompt "Summarize this repo."',
|
|
109
|
+
"",
|
|
110
|
+
].join("\n"),
|
|
111
|
+
);
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The smallest Elwood example: ask a question, get the answer, ask a follow-up.
|
|
3
|
+
* The ergonomic facade starts Codex lazily on the first `send` and gives back the
|
|
4
|
+
* assistant's text as a string (PRD §5.8).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CodexSession } from "../src/index.ts";
|
|
8
|
+
|
|
9
|
+
const session = new CodexSession({ autotrust: true });
|
|
10
|
+
try {
|
|
11
|
+
process.stdout.write(`${await session.send("Tell me a joke that involves a dog.")}\n`);
|
|
12
|
+
process.stdout.write(`${await session.send("Now rewrite it to be about a cat.")}\n`);
|
|
13
|
+
} finally {
|
|
14
|
+
await session.close();
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ergonomic streaming: watch a turn's typed events — thinking, tool calls/results, and
|
|
3
|
+
* assistant text — as they arrive, instead of waiting for the final string (PRD §5.8).
|
|
4
|
+
* `send` is just this generator drained for text; `stream` is the same turn, live.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ClaudeSession } from "../src/index.ts";
|
|
8
|
+
|
|
9
|
+
/** Compile-time exhaustiveness guard: reaching this with a real value means a case is missing. */
|
|
10
|
+
function assertNever(value: never): never {
|
|
11
|
+
throw new Error(`unhandled turn event: ${JSON.stringify(value)}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const session = new ClaudeSession({ autotrust: true });
|
|
15
|
+
try {
|
|
16
|
+
for await (const event of session.stream("List the files here, then summarize the project.")) {
|
|
17
|
+
switch (event.type) {
|
|
18
|
+
case "thinking":
|
|
19
|
+
process.stderr.write(` (thinking) ${event.text}\n`);
|
|
20
|
+
break;
|
|
21
|
+
case "tool_call":
|
|
22
|
+
process.stdout.write(`→ ${event.name}${event.input ? ` ${event.input}` : ""}\n`);
|
|
23
|
+
break;
|
|
24
|
+
case "tool_result":
|
|
25
|
+
process.stdout.write(`← ${event.output ?? ""}\n`);
|
|
26
|
+
break;
|
|
27
|
+
case "text":
|
|
28
|
+
process.stdout.write(event.text);
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
// Exhaustiveness: adding a new TurnEvent variant makes this fail to compile, so the
|
|
32
|
+
// canonical example can never silently ignore a newly-surfaced event kind.
|
|
33
|
+
assertNever(event);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
process.stdout.write("\n");
|
|
37
|
+
} finally {
|
|
38
|
+
await session.close();
|
|
39
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for runnable Elwood examples.
|
|
3
|
+
* Implements PRD §11 example behavior.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ElwoodActivityEvent,
|
|
8
|
+
ElwoodSessionStatus,
|
|
9
|
+
ElwoodWarningEvent,
|
|
10
|
+
HookErrorEvent,
|
|
11
|
+
Unsubscribe,
|
|
12
|
+
} from "../src/index.ts";
|
|
13
|
+
|
|
14
|
+
export type Agent = "claude" | "codex";
|
|
15
|
+
export type ExampleSession = {
|
|
16
|
+
readonly elwoodSessionId: string;
|
|
17
|
+
readonly status: ElwoodSessionStatus;
|
|
18
|
+
on(event: "activity", handler: (event: ElwoodActivityEvent) => void): Unsubscribe;
|
|
19
|
+
on(event: "warning", handler: (event: ElwoodWarningEvent) => void): Unsubscribe;
|
|
20
|
+
on(event: "hookError", handler: (event: HookErrorEvent) => void): Unsubscribe;
|
|
21
|
+
on(
|
|
22
|
+
event: "status",
|
|
23
|
+
handler: (event: { readonly status: ElwoodSessionStatus }) => void,
|
|
24
|
+
): Unsubscribe;
|
|
25
|
+
on(event: "terminal:exit", handler: (event: { readonly exitCode: number }) => void): Unsubscribe;
|
|
26
|
+
sendMessage(message: string): Promise<void>;
|
|
27
|
+
stop(): Promise<void>;
|
|
28
|
+
kill(): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function waitForSettled(session: ExampleSession, timeoutMs: number): Promise<boolean> {
|
|
32
|
+
if (session.status === "ready" || isTerminalStatus(session.status)) return Promise.resolve(true);
|
|
33
|
+
return new Promise((resolveWait) => {
|
|
34
|
+
const done = (result: boolean) => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
for (const unsubscribe of unsubs) unsubscribe();
|
|
37
|
+
resolveWait(result);
|
|
38
|
+
};
|
|
39
|
+
const timer = setTimeout(() => done(false), timeoutMs);
|
|
40
|
+
const unsubs: Unsubscribe[] = [
|
|
41
|
+
session.on("status", (event) => {
|
|
42
|
+
if (event.status === "ready" || isTerminalStatus(event.status)) done(true);
|
|
43
|
+
}),
|
|
44
|
+
session.on("terminal:exit", () => done(true)),
|
|
45
|
+
];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function cleanupSession(session: ExampleSession): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
await session.stop();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
process.stderr.write(`stop failed, killing session: ${errorMessage(error)}\n`);
|
|
54
|
+
await session.kill();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isTerminalStatus(status: ElwoodSessionStatus): boolean {
|
|
59
|
+
return (
|
|
60
|
+
status === "stopped" || status === "exited" || status === "killed" || status === "torn_down"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function textSuffix(text: string | undefined): string {
|
|
65
|
+
if (!text) return "";
|
|
66
|
+
return ` ${text.replace(/\s+/g, " ").slice(0, 120)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function errorMessage(error: unknown): string {
|
|
70
|
+
return error instanceof Error ? error.message : String(error);
|
|
71
|
+
}
|