@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.
Files changed (156) hide show
  1. package/CHANGELOG.md +30 -3
  2. package/CONTRIBUTING.md +104 -0
  3. package/README.md +43 -31
  4. package/SECURITY.md +42 -0
  5. package/dist/claude/model-picker.d.ts.map +1 -1
  6. package/dist/claude/model-picker.js +4 -3
  7. package/dist/claude/model-picker.js.map +1 -1
  8. package/dist/claude/model-switch-confirmation.js +9 -1
  9. package/dist/claude/model-switch-confirmation.js.map +1 -1
  10. package/dist/claude/screen-table.js +1 -1
  11. package/dist/claude/screen-table.js.map +1 -1
  12. package/dist/claude/session/build.d.ts.map +1 -1
  13. package/dist/claude/session/build.js +3 -0
  14. package/dist/claude/session/build.js.map +1 -1
  15. package/dist/claude/session/start-or-resume.js +1 -0
  16. package/dist/claude/session/start-or-resume.js.map +1 -1
  17. package/dist/cli/help.d.ts +1 -1
  18. package/dist/cli/help.d.ts.map +1 -1
  19. package/dist/cli/help.js +13 -10
  20. package/dist/cli/help.js.map +1 -1
  21. package/dist/cli/interactive/spawn.d.ts.map +1 -1
  22. package/dist/cli/interactive/spawn.js +30 -9
  23. package/dist/cli/interactive/spawn.js.map +1 -1
  24. package/dist/cli/main.d.ts.map +1 -1
  25. package/dist/cli/main.js +9 -0
  26. package/dist/cli/main.js.map +1 -1
  27. package/dist/cli/models/combined.d.ts +6 -0
  28. package/dist/cli/models/combined.d.ts.map +1 -0
  29. package/dist/cli/models/combined.js +92 -0
  30. package/dist/cli/models/combined.js.map +1 -0
  31. package/dist/cli/models/index.d.ts +2 -0
  32. package/dist/cli/models/index.d.ts.map +1 -1
  33. package/dist/cli/models/index.js +21 -7
  34. package/dist/cli/models/index.js.map +1 -1
  35. package/dist/cli/models/render.d.ts +4 -0
  36. package/dist/cli/models/render.d.ts.map +1 -1
  37. package/dist/cli/models/render.js +28 -1
  38. package/dist/cli/models/render.js.map +1 -1
  39. package/dist/cli/models/result.d.ts +13 -0
  40. package/dist/cli/models/result.d.ts.map +1 -0
  41. package/dist/cli/models/result.js +2 -0
  42. package/dist/cli/models/result.js.map +1 -0
  43. package/dist/cli/models/settings.d.ts +61 -0
  44. package/dist/cli/models/settings.d.ts.map +1 -0
  45. package/dist/cli/models/settings.js +24 -0
  46. package/dist/cli/models/settings.js.map +1 -0
  47. package/dist/cli/models/signals.d.ts +11 -0
  48. package/dist/cli/models/signals.d.ts.map +1 -0
  49. package/dist/cli/models/signals.js +21 -0
  50. package/dist/cli/models/signals.js.map +1 -0
  51. package/dist/cli/request/index.js +1 -1
  52. package/dist/cli/request/index.js.map +1 -1
  53. package/dist/cli/request/layers.d.ts +2 -0
  54. package/dist/cli/request/layers.d.ts.map +1 -1
  55. package/dist/cli/request/layers.js +9 -5
  56. package/dist/cli/request/layers.js.map +1 -1
  57. package/dist/cli/request/validation.d.ts.map +1 -1
  58. package/dist/cli/request/validation.js +0 -4
  59. package/dist/cli/request/validation.js.map +1 -1
  60. package/dist/cli/run/output.d.ts.map +1 -1
  61. package/dist/cli/run/output.js +3 -6
  62. package/dist/cli/run/output.js.map +1 -1
  63. package/dist/cli/sessions/index.d.ts +9 -2
  64. package/dist/cli/sessions/index.d.ts.map +1 -1
  65. package/dist/cli/sessions/index.js +2 -6
  66. package/dist/cli/sessions/index.js.map +1 -1
  67. package/dist/cli/sessions/settings.d.ts +11 -0
  68. package/dist/cli/sessions/settings.d.ts.map +1 -0
  69. package/dist/cli/sessions/settings.js +26 -0
  70. package/dist/cli/sessions/settings.js.map +1 -0
  71. package/dist/codex/model-picker.d.ts.map +1 -1
  72. package/dist/codex/model-picker.js +3 -2
  73. package/dist/codex/model-picker.js.map +1 -1
  74. package/dist/codex/session/build.d.ts.map +1 -1
  75. package/dist/codex/session/build.js +5 -7
  76. package/dist/codex/session/build.js.map +1 -1
  77. package/dist/codex/session/start-or-resume.js +1 -0
  78. package/dist/codex/session/start-or-resume.js.map +1 -1
  79. package/dist/core/input/index.d.ts.map +1 -1
  80. package/dist/core/input/index.js +15 -3
  81. package/dist/core/input/index.js.map +1 -1
  82. package/dist/core/models/input.d.ts +3 -0
  83. package/dist/core/models/input.d.ts.map +1 -0
  84. package/dist/core/models/input.js +9 -0
  85. package/dist/core/models/input.js.map +1 -0
  86. package/dist/core/models/picker.d.ts +2 -1
  87. package/dist/core/models/picker.d.ts.map +1 -1
  88. package/dist/core/models/picker.js +6 -5
  89. package/dist/core/models/picker.js.map +1 -1
  90. package/dist/core/models/tui-screen.d.ts +1 -1
  91. package/dist/core/models/tui-screen.d.ts.map +1 -1
  92. package/dist/core/models/tui-screen.js +26 -3
  93. package/dist/core/models/tui-screen.js.map +1 -1
  94. package/dist/runtime/session/base.d.ts.map +1 -1
  95. package/dist/runtime/session/base.js +4 -1
  96. package/dist/runtime/session/base.js.map +1 -1
  97. package/dist/runtime/session/commands.d.ts +2 -1
  98. package/dist/runtime/session/commands.d.ts.map +1 -1
  99. package/dist/runtime/session/commands.js +10 -4
  100. package/dist/runtime/session/commands.js.map +1 -1
  101. package/dist/runtime/session/lifecycle.d.ts +3 -4
  102. package/dist/runtime/session/lifecycle.d.ts.map +1 -1
  103. package/dist/runtime/session/lifecycle.js +6 -5
  104. package/dist/runtime/session/lifecycle.js.map +1 -1
  105. package/dist/state/directories.d.ts +4 -0
  106. package/dist/state/directories.d.ts.map +1 -0
  107. package/dist/state/directories.js +41 -0
  108. package/dist/state/directories.js.map +1 -0
  109. package/dist/state/files.d.ts.map +1 -1
  110. package/dist/state/files.js +25 -11
  111. package/dist/state/files.js.map +1 -1
  112. package/dist/state/private-session.d.ts.map +1 -1
  113. package/dist/state/private-session.js +2 -0
  114. package/dist/state/private-session.js.map +1 -1
  115. package/dist/state/store.d.ts.map +1 -1
  116. package/dist/state/store.js +7 -1
  117. package/dist/state/store.js.map +1 -1
  118. package/docs/cli-behavior.md +1 -1
  119. package/docs/cli-output.md +40 -10
  120. package/docs/cli.md +3 -2
  121. package/docs/codex/cli.md +57 -0
  122. package/docs/codex/config.md +47 -0
  123. package/docs/codex/hooks.md +70 -0
  124. package/examples/README.md +56 -0
  125. package/examples/full.ts +113 -0
  126. package/examples/minimal.ts +15 -0
  127. package/examples/stream.ts +39 -0
  128. package/examples/support.ts +71 -0
  129. package/package.json +10 -5
  130. package/prd/01-overview.md +21 -0
  131. package/prd/02-goals.md +47 -0
  132. package/prd/03-terminology.md +32 -0
  133. package/prd/04-architecture.md +121 -0
  134. package/prd/05-api/5.1-starting-claude.md +108 -0
  135. package/prd/05-api/5.2-resuming-claude.md +86 -0
  136. package/prd/05-api/5.3-claude-session-api.md +658 -0
  137. package/prd/05-api/5.4-events.md +145 -0
  138. package/prd/05-api/5.5-starting-codex.md +106 -0
  139. package/prd/05-api/5.6-resuming-codex.md +48 -0
  140. package/prd/05-api/5.7-codex-session-api.md +425 -0
  141. package/prd/05-api/5.8-session-api.md +122 -0
  142. package/prd/05-api/5.9-loops.md +170 -0
  143. package/prd/05-api/README.md +19 -0
  144. package/prd/06-claude-hook-bridge.md +164 -0
  145. package/prd/07-claude-policy.md +15 -0
  146. package/prd/07a-codex-hook-bridge.md +76 -0
  147. package/prd/08-state.md +127 -0
  148. package/prd/09-lifecycle.md +222 -0
  149. package/prd/10-errors.md +55 -0
  150. package/prd/11-test-app.md +53 -0
  151. package/prd/12-e2e.md +23 -0
  152. package/prd/12a-cli.md +402 -0
  153. package/prd/13-latitude.md +12 -0
  154. package/prd/14-conformance.md +333 -0
  155. package/prd/15-open-notes.md +8 -0
  156. package/prd/README.md +45 -0
@@ -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(() => rmSync(dir, { recursive: true, force: true }), firstError);
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),
@@ -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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"}
@@ -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 `PRD.md`
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
@@ -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. Warnings and diagnostics go to stderr,
9
- never into the answer on stdout. `--verbose` adds compact elapsed phase,
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` starts the agent briefly to read its own model picker, then
114
- tears the session down:
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
- Failures use the same `error` document and statuses as a run. Useful recipes:
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`. The built-in non-interactive posture
87
- is Claude `dontAsk`, or Codex `workspace-write` with approval policy `never`.
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`.
@@ -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
+ }