@with-logic/elwood 0.1.0 → 0.1.2
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 +182 -3
- package/CONTRIBUTING.md +104 -0
- package/LICENSE +1 -1
- package/README.md +295 -26
- package/SECURITY.md +42 -0
- package/dist/claude/command.d.ts +9 -2
- package/dist/claude/command.d.ts.map +1 -1
- package/dist/claude/command.js +23 -16
- package/dist/claude/command.js.map +1 -1
- 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/index.d.ts.map +1 -1
- package/dist/claude/session/index.js +5 -2
- package/dist/claude/session/index.js.map +1 -1
- package/dist/claude/session/resume.d.ts +1 -1
- package/dist/claude/session/resume.d.ts.map +1 -1
- package/dist/claude/session/resume.js +5 -1
- package/dist/claude/session/resume.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/claude/simple.d.ts.map +1 -1
- package/dist/claude/simple.js +4 -0
- package/dist/claude/simple.js.map +1 -1
- package/dist/cli/args/index.d.ts +3 -1
- package/dist/cli/args/index.d.ts.map +1 -1
- package/dist/cli/args/index.js +10 -2
- package/dist/cli/args/index.js.map +1 -1
- package/dist/cli/args/options.d.ts.map +1 -1
- package/dist/cli/args/options.js +2 -0
- package/dist/cli/args/options.js.map +1 -1
- package/dist/cli/args-commands.d.ts +10 -0
- package/dist/cli/args-commands.d.ts.map +1 -0
- package/dist/cli/args-commands.js +113 -0
- package/dist/cli/args-commands.js.map +1 -0
- package/dist/cli/command-types.d.ts +26 -0
- package/dist/cli/command-types.d.ts.map +1 -0
- package/dist/cli/command-types.js +9 -0
- package/dist/cli/command-types.js.map +1 -0
- package/dist/cli/config/codec-primitives.d.ts +20 -0
- package/dist/cli/config/codec-primitives.d.ts.map +1 -0
- package/dist/cli/config/codec-primitives.js +51 -0
- package/dist/cli/config/codec-primitives.js.map +1 -0
- package/dist/cli/config/codec.d.ts.map +1 -1
- package/dist/cli/config/codec.js +4 -46
- package/dist/cli/config/codec.js.map +1 -1
- package/dist/cli/config/effective.d.ts.map +1 -1
- package/dist/cli/config/effective.js +1 -0
- package/dist/cli/config/effective.js.map +1 -1
- package/dist/cli/config/keys.d.ts +1 -1
- package/dist/cli/config/keys.d.ts.map +1 -1
- package/dist/cli/config/keys.js +1 -0
- package/dist/cli/config/keys.js.map +1 -1
- package/dist/cli/entry.d.ts +4 -2
- package/dist/cli/entry.d.ts.map +1 -1
- package/dist/cli/entry.js +2 -1
- package/dist/cli/entry.js.map +1 -1
- package/dist/cli/help.d.ts +2 -2
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +40 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interactive/index.d.ts +26 -0
- package/dist/cli/interactive/index.d.ts.map +1 -0
- package/dist/cli/interactive/index.js +33 -0
- package/dist/cli/interactive/index.js.map +1 -0
- package/dist/cli/interactive/launch.d.ts +14 -0
- package/dist/cli/interactive/launch.d.ts.map +1 -0
- package/dist/cli/interactive/launch.js +66 -0
- package/dist/cli/interactive/launch.js.map +1 -0
- package/dist/cli/interactive/spawn.d.ts +18 -0
- package/dist/cli/interactive/spawn.d.ts.map +1 -0
- package/dist/cli/interactive/spawn.js +53 -0
- package/dist/cli/interactive/spawn.js.map +1 -0
- package/dist/cli/main-failure.d.ts +28 -0
- package/dist/cli/main-failure.d.ts.map +1 -0
- package/dist/cli/main-failure.js +78 -0
- package/dist/cli/main-failure.js.map +1 -0
- package/dist/cli/main.d.ts +12 -4
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +27 -73
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/models/index.d.ts +20 -0
- package/dist/cli/models/index.d.ts.map +1 -0
- package/dist/cli/models/index.js +63 -0
- package/dist/cli/models/index.js.map +1 -0
- package/dist/cli/models/render.d.ts +9 -0
- package/dist/cli/models/render.d.ts.map +1 -0
- package/dist/cli/models/render.js +33 -0
- package/dist/cli/models/render.js.map +1 -0
- package/dist/cli/request/index.d.ts.map +1 -1
- package/dist/cli/request/index.js +8 -2
- 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/posture.d.ts +28 -0
- package/dist/cli/request/posture.d.ts.map +1 -0
- package/dist/cli/request/posture.js +70 -0
- package/dist/cli/request/posture.js.map +1 -0
- package/dist/cli/request/resolution-types.d.ts +1 -0
- package/dist/cli/request/resolution-types.d.ts.map +1 -1
- package/dist/cli/request/settings.d.ts +2 -12
- package/dist/cli/request/settings.d.ts.map +1 -1
- package/dist/cli/request/settings.js +2 -25
- package/dist/cli/request/settings.js.map +1 -1
- package/dist/cli/request/validation.d.ts +12 -0
- package/dist/cli/request/validation.d.ts.map +1 -1
- package/dist/cli/request/validation.js +30 -10
- package/dist/cli/request/validation.js.map +1 -1
- package/dist/cli/request/values.d.ts +1 -0
- package/dist/cli/request/values.d.ts.map +1 -1
- package/dist/cli/request/values.js +1 -0
- package/dist/cli/request/values.js.map +1 -1
- package/dist/cli/run/index.d.ts.map +1 -1
- package/dist/cli/run/index.js +5 -41
- package/dist/cli/run/index.js.map +1 -1
- package/dist/cli/run/subscribe.d.ts +19 -0
- package/dist/cli/run/subscribe.d.ts.map +1 -0
- package/dist/cli/run/subscribe.js +47 -0
- package/dist/cli/run/subscribe.js.map +1 -0
- package/dist/cli/session/launch.d.ts +3 -0
- package/dist/cli/session/launch.d.ts.map +1 -1
- package/dist/cli/session/launch.js +11 -0
- package/dist/cli/session/launch.js.map +1 -1
- package/dist/cli/sessions/index.d.ts +30 -0
- package/dist/cli/sessions/index.d.ts.map +1 -0
- package/dist/cli/sessions/index.js +61 -0
- package/dist/cli/sessions/index.js.map +1 -0
- package/dist/cli/sessions/list.d.ts +28 -0
- package/dist/cli/sessions/list.d.ts.map +1 -0
- package/dist/cli/sessions/list.js +63 -0
- package/dist/cli/sessions/list.js.map +1 -0
- 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/cli/table.d.ts +7 -0
- package/dist/cli/table.d.ts.map +1 -0
- package/dist/cli/table.js +14 -0
- package/dist/cli/table.js.map +1 -0
- package/dist/cli/types.d.ts +5 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/codex/command.d.ts +18 -2
- package/dist/codex/command.d.ts.map +1 -1
- package/dist/codex/command.js +39 -24
- package/dist/codex/command.js.map +1 -1
- 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/index.d.ts.map +1 -1
- package/dist/codex/session/index.js +5 -2
- package/dist/codex/session/index.js.map +1 -1
- package/dist/codex/session/resume.d.ts +1 -1
- package/dist/codex/session/resume.d.ts.map +1 -1
- package/dist/codex/session/resume.js +5 -1
- package/dist/codex/session/resume.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/codex/session/types.d.ts +3 -0
- package/dist/codex/session/types.d.ts.map +1 -1
- package/dist/codex/simple.d.ts.map +1 -1
- package/dist/codex/simple.js +4 -0
- package/dist/codex/simple.js.map +1 -1
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/high-trust.d.ts +41 -0
- package/dist/core/high-trust.d.ts.map +1 -0
- package/dist/core/high-trust.js +50 -0
- package/dist/core/high-trust.js.map +1 -0
- 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/core/trust/allowlist.d.ts +56 -0
- package/dist/core/trust/allowlist.d.ts.map +1 -0
- package/dist/core/{trust-prompts.js → trust/allowlist.js} +21 -38
- package/dist/core/trust/allowlist.js.map +1 -0
- package/dist/core/trust/prompts.d.ts +4 -46
- package/dist/core/trust/prompts.d.ts.map +1 -1
- package/dist/core/trust/prompts.js +2 -73
- package/dist/core/trust/prompts.js.map +1 -1
- package/dist/core/trust/responder.d.ts +2 -0
- package/dist/core/trust/responder.d.ts.map +1 -1
- package/dist/core/trust/responder.js +4 -0
- package/dist/core/trust/responder.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/runtime/launch-arguments.d.ts +12 -0
- package/dist/runtime/launch-arguments.d.ts.map +1 -0
- package/dist/runtime/launch-arguments.js +15 -0
- package/dist/runtime/launch-arguments.js.map +1 -0
- 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 +89 -1
- package/docs/cli-output.md +60 -0
- package/docs/cli.md +10 -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 +17 -10
- 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 +375 -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/claude/hook-dispatch.d.ts +0 -9
- package/dist/claude/hook-dispatch.d.ts.map +0 -1
- package/dist/claude/hook-dispatch.js +0 -12
- package/dist/claude/hook-dispatch.js.map +0 -1
- package/dist/claude/hook-events-extra.d.ts +0 -81
- package/dist/claude/hook-events-extra.d.ts.map +0 -1
- package/dist/claude/hook-events-extra.js +0 -6
- package/dist/claude/hook-events-extra.js.map +0 -1
- package/dist/claude/hook-events.d.ts +0 -101
- package/dist/claude/hook-events.d.ts.map +0 -1
- package/dist/claude/hook-events.js +0 -6
- package/dist/claude/hook-events.js.map +0 -1
- package/dist/claude/hook-names.d.ts +0 -24
- package/dist/claude/hook-names.d.ts.map +0 -1
- package/dist/claude/hook-names.js +0 -45
- package/dist/claude/hook-names.js.map +0 -1
- package/dist/claude/hooks.d.ts +0 -78
- package/dist/claude/hooks.d.ts.map +0 -1
- package/dist/claude/hooks.js +0 -6
- package/dist/claude/hooks.js.map +0 -1
- package/dist/claude/session-bridge.d.ts +0 -11
- package/dist/claude/session-bridge.d.ts.map +0 -1
- package/dist/claude/session-bridge.js +0 -18
- package/dist/claude/session-bridge.js.map +0 -1
- package/dist/claude/session-build.d.ts +0 -15
- package/dist/claude/session-build.d.ts.map +0 -1
- package/dist/claude/session-build.js +0 -147
- package/dist/claude/session-build.js.map +0 -1
- package/dist/claude/session-hook-handler.d.ts +0 -34
- package/dist/claude/session-hook-handler.d.ts.map +0 -1
- package/dist/claude/session-hook-handler.js +0 -42
- package/dist/claude/session-hook-handler.js.map +0 -1
- package/dist/claude/session-instance.d.ts +0 -42
- package/dist/claude/session-instance.d.ts.map +0 -1
- package/dist/claude/session-instance.js +0 -146
- package/dist/claude/session-instance.js.map +0 -1
- package/dist/claude/session-interface.d.ts +0 -49
- package/dist/claude/session-interface.d.ts.map +0 -1
- package/dist/claude/session-interface.js +0 -10
- package/dist/claude/session-interface.js.map +0 -1
- package/dist/claude/session-resume.d.ts +0 -8
- package/dist/claude/session-resume.d.ts.map +0 -1
- package/dist/claude/session-resume.js +0 -47
- package/dist/claude/session-resume.js.map +0 -1
- package/dist/claude/session-runtime.d.ts +0 -43
- package/dist/claude/session-runtime.d.ts.map +0 -1
- package/dist/claude/session-runtime.js +0 -105
- package/dist/claude/session-runtime.js.map +0 -1
- package/dist/claude/session-transcript.d.ts +0 -62
- package/dist/claude/session-transcript.d.ts.map +0 -1
- package/dist/claude/session-transcript.js +0 -88
- package/dist/claude/session-transcript.js.map +0 -1
- package/dist/claude/session.d.ts +0 -18
- package/dist/claude/session.d.ts.map +0 -1
- package/dist/claude/session.js +0 -58
- package/dist/claude/session.js.map +0 -1
- package/dist/claude/start-or-resume.d.ts +0 -12
- package/dist/claude/start-or-resume.d.ts.map +0 -1
- package/dist/claude/start-or-resume.js +0 -32
- package/dist/claude/start-or-resume.js.map +0 -1
- package/dist/claude/tool-hook-events.d.ts +0 -10
- package/dist/claude/tool-hook-events.d.ts.map +0 -1
- package/dist/claude/tool-hook-events.js +0 -18
- package/dist/claude/tool-hook-events.js.map +0 -1
- package/dist/claude/tool-types.d.ts +0 -158
- package/dist/claude/tool-types.d.ts.map +0 -1
- package/dist/claude/tool-types.js +0 -6
- package/dist/claude/tool-types.js.map +0 -1
- package/dist/claude/validate-input.d.ts +0 -8
- package/dist/claude/validate-input.d.ts.map +0 -1
- package/dist/claude/validate-input.js +0 -137
- package/dist/claude/validate-input.js.map +0 -1
- package/dist/claude/validate-permission-update.d.ts +0 -10
- package/dist/claude/validate-permission-update.d.ts.map +0 -1
- package/dist/claude/validate-permission-update.js +0 -72
- package/dist/claude/validate-permission-update.js.map +0 -1
- package/dist/claude/validate-result.d.ts +0 -7
- package/dist/claude/validate-result.d.ts.map +0 -1
- package/dist/claude/validate-result.js +0 -118
- package/dist/claude/validate-result.js.map +0 -1
- package/dist/claude/validate-shapes.d.ts +0 -22
- package/dist/claude/validate-shapes.d.ts.map +0 -1
- package/dist/claude/validate-shapes.js +0 -15
- package/dist/claude/validate-shapes.js.map +0 -1
- package/dist/claude/validate-tool-update.d.ts +0 -15
- package/dist/claude/validate-tool-update.d.ts.map +0 -1
- package/dist/claude/validate-tool-update.js +0 -122
- package/dist/claude/validate-tool-update.js.map +0 -1
- package/dist/cli/agent-detect.d.ts +0 -44
- package/dist/cli/agent-detect.d.ts.map +0 -1
- package/dist/cli/agent-detect.js +0 -66
- package/dist/cli/agent-detect.js.map +0 -1
- package/dist/cli/arg-errors.d.ts +0 -6
- package/dist/cli/arg-errors.d.ts.map +0 -1
- package/dist/cli/arg-errors.js +0 -44
- package/dist/cli/arg-errors.js.map +0 -1
- package/dist/cli/arg-options.d.ts +0 -26
- package/dist/cli/arg-options.d.ts.map +0 -1
- package/dist/cli/arg-options.js +0 -50
- package/dist/cli/arg-options.js.map +0 -1
- package/dist/cli/args.d.ts +0 -7
- package/dist/cli/args.d.ts.map +0 -1
- package/dist/cli/args.js +0 -70
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/lifecycle-outcome.d.ts +0 -13
- package/dist/cli/lifecycle-outcome.d.ts.map +0 -1
- package/dist/cli/lifecycle-outcome.js +0 -20
- package/dist/cli/lifecycle-outcome.js.map +0 -1
- package/dist/cli/lifecycle.d.ts +0 -61
- package/dist/cli/lifecycle.d.ts.map +0 -1
- package/dist/cli/lifecycle.js +0 -152
- package/dist/cli/lifecycle.js.map +0 -1
- package/dist/cli/request-finalize.d.ts +0 -10
- package/dist/cli/request-finalize.d.ts.map +0 -1
- package/dist/cli/request-finalize.js +0 -135
- package/dist/cli/request-finalize.js.map +0 -1
- package/dist/cli/request-layers.d.ts +0 -30
- package/dist/cli/request-layers.d.ts.map +0 -1
- package/dist/cli/request-layers.js +0 -44
- package/dist/cli/request-layers.js.map +0 -1
- package/dist/cli/request-settings.d.ts +0 -34
- package/dist/cli/request-settings.d.ts.map +0 -1
- package/dist/cli/request-settings.js +0 -66
- package/dist/cli/request-settings.js.map +0 -1
- package/dist/cli/request-sources.d.ts +0 -13
- package/dist/cli/request-sources.d.ts.map +0 -1
- package/dist/cli/request-sources.js +0 -23
- package/dist/cli/request-sources.js.map +0 -1
- package/dist/cli/request-validation.d.ts +0 -18
- package/dist/cli/request-validation.d.ts.map +0 -1
- package/dist/cli/request-validation.js +0 -84
- package/dist/cli/request-validation.js.map +0 -1
- package/dist/cli/request-values.d.ts +0 -33
- package/dist/cli/request-values.d.ts.map +0 -1
- package/dist/cli/request-values.js +0 -73
- package/dist/cli/request-values.js.map +0 -1
- package/dist/cli/request.d.ts +0 -12
- package/dist/cli/request.d.ts.map +0 -1
- package/dist/cli/request.js +0 -103
- package/dist/cli/request.js.map +0 -1
- package/dist/cli/resolution-types.d.ts +0 -35
- package/dist/cli/resolution-types.d.ts.map +0 -1
- package/dist/cli/resolution-types.js +0 -6
- package/dist/cli/resolution-types.js.map +0 -1
- package/dist/cli/run-output-lines.d.ts +0 -14
- package/dist/cli/run-output-lines.d.ts.map +0 -1
- package/dist/cli/run-output-lines.js +0 -42
- package/dist/cli/run-output-lines.js.map +0 -1
- package/dist/cli/run-output.d.ts +0 -48
- package/dist/cli/run-output.d.ts.map +0 -1
- package/dist/cli/run-output.js +0 -150
- package/dist/cli/run-output.js.map +0 -1
- package/dist/cli/run.d.ts +0 -20
- package/dist/cli/run.d.ts.map +0 -1
- package/dist/cli/run.js +0 -126
- package/dist/cli/run.js.map +0 -1
- package/dist/cli/session-launch.d.ts +0 -23
- package/dist/cli/session-launch.d.ts.map +0 -1
- package/dist/cli/session-launch.js +0 -57
- package/dist/cli/session-launch.js.map +0 -1
- package/dist/cli/session.d.ts +0 -68
- package/dist/cli/session.d.ts.map +0 -1
- package/dist/cli/session.js +0 -124
- package/dist/cli/session.js.map +0 -1
- package/dist/codex/attach-images.d.ts +0 -20
- package/dist/codex/attach-images.d.ts.map +0 -1
- package/dist/codex/attach-images.js +0 -66
- package/dist/codex/attach-images.js.map +0 -1
- package/dist/codex/clipboard-lock.d.ts +0 -15
- package/dist/codex/clipboard-lock.d.ts.map +0 -1
- package/dist/codex/clipboard-lock.js +0 -16
- package/dist/codex/clipboard-lock.js.map +0 -1
- package/dist/codex/clipboard.d.ts +0 -32
- package/dist/codex/clipboard.d.ts.map +0 -1
- package/dist/codex/clipboard.js +0 -92
- package/dist/codex/clipboard.js.map +0 -1
- package/dist/codex/config-lock.d.ts +0 -21
- package/dist/codex/config-lock.d.ts.map +0 -1
- package/dist/codex/config-lock.js +0 -22
- package/dist/codex/config-lock.js.map +0 -1
- package/dist/codex/config-restore.d.ts +0 -33
- package/dist/codex/config-restore.d.ts.map +0 -1
- package/dist/codex/config-restore.js +0 -90
- package/dist/codex/config-restore.js.map +0 -1
- package/dist/codex/config-transaction.d.ts +0 -26
- package/dist/codex/config-transaction.d.ts.map +0 -1
- package/dist/codex/config-transaction.js +0 -47
- package/dist/codex/config-transaction.js.map +0 -1
- package/dist/codex/hook-dispatch.d.ts +0 -9
- package/dist/codex/hook-dispatch.d.ts.map +0 -1
- package/dist/codex/hook-dispatch.js +0 -14
- package/dist/codex/hook-dispatch.js.map +0 -1
- package/dist/codex/hook-names.d.ts +0 -19
- package/dist/codex/hook-names.d.ts.map +0 -1
- package/dist/codex/hook-names.js +0 -17
- package/dist/codex/hook-names.js.map +0 -1
- package/dist/codex/hooks.d.ts +0 -99
- package/dist/codex/hooks.d.ts.map +0 -1
- package/dist/codex/hooks.js +0 -6
- package/dist/codex/hooks.js.map +0 -1
- package/dist/codex/initial-ready.d.ts +0 -19
- package/dist/codex/initial-ready.d.ts.map +0 -1
- package/dist/codex/initial-ready.js +0 -19
- package/dist/codex/initial-ready.js.map +0 -1
- package/dist/codex/serialize.d.ts +0 -8
- package/dist/codex/serialize.d.ts.map +0 -1
- package/dist/codex/serialize.js +0 -48
- package/dist/codex/serialize.js.map +0 -1
- package/dist/codex/session-bridge.d.ts +0 -14
- package/dist/codex/session-bridge.d.ts.map +0 -1
- package/dist/codex/session-bridge.js +0 -20
- package/dist/codex/session-bridge.js.map +0 -1
- package/dist/codex/session-build.d.ts +0 -15
- package/dist/codex/session-build.d.ts.map +0 -1
- package/dist/codex/session-build.js +0 -144
- package/dist/codex/session-build.js.map +0 -1
- package/dist/codex/session-cleanup.d.ts +0 -12
- package/dist/codex/session-cleanup.d.ts.map +0 -1
- package/dist/codex/session-cleanup.js +0 -15
- package/dist/codex/session-cleanup.js.map +0 -1
- package/dist/codex/session-hooks.d.ts +0 -13
- package/dist/codex/session-hooks.d.ts.map +0 -1
- package/dist/codex/session-hooks.js +0 -54
- package/dist/codex/session-hooks.js.map +0 -1
- package/dist/codex/session-instance.d.ts +0 -50
- package/dist/codex/session-instance.d.ts.map +0 -1
- package/dist/codex/session-instance.js +0 -117
- package/dist/codex/session-instance.js.map +0 -1
- package/dist/codex/session-resume.d.ts +0 -7
- package/dist/codex/session-resume.d.ts.map +0 -1
- package/dist/codex/session-resume.js +0 -44
- package/dist/codex/session-resume.js.map +0 -1
- package/dist/codex/session-runtime.d.ts +0 -7
- package/dist/codex/session-runtime.d.ts.map +0 -1
- package/dist/codex/session-runtime.js +0 -10
- package/dist/codex/session-runtime.js.map +0 -1
- package/dist/codex/session-transcript.d.ts +0 -30
- package/dist/codex/session-transcript.d.ts.map +0 -1
- package/dist/codex/session-transcript.js +0 -53
- package/dist/codex/session-transcript.js.map +0 -1
- package/dist/codex/session-types.d.ts +0 -87
- package/dist/codex/session-types.d.ts.map +0 -1
- package/dist/codex/session-types.js +0 -6
- package/dist/codex/session-types.js.map +0 -1
- package/dist/codex/session-warnings.d.ts +0 -27
- package/dist/codex/session-warnings.d.ts.map +0 -1
- package/dist/codex/session-warnings.js +0 -54
- package/dist/codex/session-warnings.js.map +0 -1
- package/dist/codex/session.d.ts +0 -16
- package/dist/codex/session.d.ts.map +0 -1
- package/dist/codex/session.js +0 -61
- package/dist/codex/session.js.map +0 -1
- package/dist/codex/start-or-resume.d.ts +0 -12
- package/dist/codex/start-or-resume.d.ts.map +0 -1
- package/dist/codex/start-or-resume.js +0 -31
- package/dist/codex/start-or-resume.js.map +0 -1
- package/dist/codex/transcript/cursor-io.d.ts +0 -7
- package/dist/codex/transcript/cursor-io.d.ts.map +0 -1
- package/dist/codex/transcript/cursor-io.js +0 -7
- package/dist/codex/transcript/cursor-io.js.map +0 -1
- package/dist/codex/transcript/cursor.d.ts +0 -8
- package/dist/codex/transcript/cursor.d.ts.map +0 -1
- package/dist/codex/transcript/cursor.js +0 -8
- package/dist/codex/transcript/cursor.js.map +0 -1
- package/dist/codex/transcript/drops.d.ts +0 -9
- package/dist/codex/transcript/drops.d.ts.map +0 -1
- package/dist/codex/transcript/drops.js +0 -9
- package/dist/codex/transcript/drops.js.map +0 -1
- package/dist/codex/transcript.d.ts +0 -12
- package/dist/codex/transcript.d.ts.map +0 -1
- package/dist/codex/transcript.js +0 -10
- package/dist/codex/transcript.js.map +0 -1
- package/dist/codex/validate-result.d.ts +0 -7
- package/dist/codex/validate-result.d.ts.map +0 -1
- package/dist/codex/validate-result.js +0 -84
- package/dist/codex/validate-result.js.map +0 -1
- package/dist/codex/validate.d.ts +0 -11
- package/dist/codex/validate.d.ts.map +0 -1
- package/dist/codex/validate.js +0 -88
- package/dist/codex/validate.js.map +0 -1
- package/dist/core/activity-lifecycle.d.ts +0 -21
- package/dist/core/activity-lifecycle.d.ts.map +0 -1
- package/dist/core/activity-lifecycle.js +0 -45
- package/dist/core/activity-lifecycle.js.map +0 -1
- package/dist/core/activity-meta.d.ts +0 -13
- package/dist/core/activity-meta.d.ts.map +0 -1
- package/dist/core/activity-meta.js +0 -67
- package/dist/core/activity-meta.js.map +0 -1
- package/dist/core/activity-transcript.d.ts +0 -19
- package/dist/core/activity-transcript.d.ts.map +0 -1
- package/dist/core/activity-transcript.js +0 -60
- package/dist/core/activity-transcript.js.map +0 -1
- package/dist/core/activity.d.ts +0 -51
- package/dist/core/activity.d.ts.map +0 -1
- package/dist/core/activity.js +0 -81
- package/dist/core/activity.js.map +0 -1
- package/dist/core/control-queue-state.d.ts +0 -29
- package/dist/core/control-queue-state.d.ts.map +0 -1
- package/dist/core/control-queue-state.js +0 -95
- package/dist/core/control-queue-state.js.map +0 -1
- package/dist/core/control-queue-traits.d.ts +0 -54
- package/dist/core/control-queue-traits.d.ts.map +0 -1
- package/dist/core/control-queue-traits.js +0 -118
- package/dist/core/control-queue-traits.js.map +0 -1
- package/dist/core/control-queue-types.d.ts +0 -45
- package/dist/core/control-queue-types.d.ts.map +0 -1
- package/dist/core/control-queue-types.js +0 -7
- package/dist/core/control-queue-types.js.map +0 -1
- package/dist/core/control-queue.d.ts +0 -21
- package/dist/core/control-queue.d.ts.map +0 -1
- package/dist/core/control-queue.js +0 -98
- package/dist/core/control-queue.js.map +0 -1
- package/dist/core/list-models.d.ts +0 -44
- package/dist/core/list-models.d.ts.map +0 -1
- package/dist/core/list-models.js +0 -73
- package/dist/core/list-models.js.map +0 -1
- package/dist/core/model-picker.d.ts +0 -22
- package/dist/core/model-picker.d.ts.map +0 -1
- package/dist/core/model-picker.js +0 -58
- package/dist/core/model-picker.js.map +0 -1
- package/dist/core/model-rows.d.ts +0 -21
- package/dist/core/model-rows.d.ts.map +0 -1
- package/dist/core/model-rows.js +0 -57
- package/dist/core/model-rows.js.map +0 -1
- package/dist/core/probe-state.d.ts +0 -21
- package/dist/core/probe-state.d.ts.map +0 -1
- package/dist/core/probe-state.js +0 -39
- package/dist/core/probe-state.js.map +0 -1
- package/dist/core/session-input-abort.d.ts +0 -10
- package/dist/core/session-input-abort.d.ts.map +0 -1
- package/dist/core/session-input-abort.js +0 -44
- package/dist/core/session-input-abort.js.map +0 -1
- package/dist/core/session-input.d.ts +0 -37
- package/dist/core/session-input.d.ts.map +0 -1
- package/dist/core/session-input.js +0 -154
- package/dist/core/session-input.js.map +0 -1
- package/dist/core/session-warnings.d.ts +0 -20
- package/dist/core/session-warnings.d.ts.map +0 -1
- package/dist/core/session-warnings.js +0 -32
- package/dist/core/session-warnings.js.map +0 -1
- package/dist/core/simple/loop-controls.d.ts +0 -7
- package/dist/core/simple/loop-controls.d.ts.map +0 -1
- package/dist/core/simple/loop-controls.js +0 -7
- package/dist/core/simple/loop-controls.js.map +0 -1
- package/dist/core/startup-automation.d.ts +0 -54
- package/dist/core/startup-automation.d.ts.map +0 -1
- package/dist/core/startup-automation.js +0 -40
- package/dist/core/startup-automation.js.map +0 -1
- package/dist/core/startup-frame.d.ts +0 -39
- package/dist/core/startup-frame.d.ts.map +0 -1
- package/dist/core/startup-frame.js +0 -60
- package/dist/core/startup-frame.js.map +0 -1
- package/dist/core/startup-write.d.ts +0 -47
- package/dist/core/startup-write.d.ts.map +0 -1
- package/dist/core/startup-write.js +0 -73
- package/dist/core/startup-write.js.map +0 -1
- package/dist/core/trust-blocking.d.ts +0 -22
- package/dist/core/trust-blocking.d.ts.map +0 -1
- package/dist/core/trust-blocking.js +0 -38
- package/dist/core/trust-blocking.js.map +0 -1
- package/dist/core/trust-prompts.d.ts +0 -135
- package/dist/core/trust-prompts.d.ts.map +0 -1
- package/dist/core/trust-prompts.js.map +0 -1
- package/dist/core/trust-responder.d.ts +0 -48
- package/dist/core/trust-responder.d.ts.map +0 -1
- package/dist/core/trust-responder.js +0 -119
- package/dist/core/trust-responder.js.map +0 -1
- package/dist/core/tui-screen.d.ts +0 -21
- package/dist/core/tui-screen.d.ts.map +0 -1
- package/dist/core/tui-screen.js +0 -56
- package/dist/core/tui-screen.js.map +0 -1
- package/dist/core/update-warning.d.ts +0 -25
- package/dist/core/update-warning.d.ts.map +0 -1
- package/dist/core/update-warning.js +0 -33
- package/dist/core/update-warning.js.map +0 -1
- package/dist/core/warning-lifecycle.d.ts +0 -23
- package/dist/core/warning-lifecycle.d.ts.map +0 -1
- package/dist/core/warning-lifecycle.js +0 -6
- package/dist/core/warning-lifecycle.js.map +0 -1
- package/dist/core/warning-reasons.d.ts +0 -62
- package/dist/core/warning-reasons.d.ts.map +0 -1
- package/dist/core/warning-reasons.js +0 -121
- package/dist/core/warning-reasons.js.map +0 -1
- package/dist/core/warnings.d.ts +0 -145
- package/dist/core/warnings.d.ts.map +0 -1
- package/dist/core/warnings.js +0 -12
- package/dist/core/warnings.js.map +0 -1
- package/dist/runtime/cleanup-latch.d.ts +0 -27
- package/dist/runtime/cleanup-latch.d.ts.map +0 -1
- package/dist/runtime/cleanup-latch.js +0 -43
- package/dist/runtime/cleanup-latch.js.map +0 -1
- package/dist/runtime/initial-ready-advance.d.ts +0 -22
- package/dist/runtime/initial-ready-advance.d.ts.map +0 -1
- package/dist/runtime/initial-ready-advance.js +0 -39
- package/dist/runtime/initial-ready-advance.js.map +0 -1
- package/dist/runtime/initial-ready-fallback.d.ts +0 -12
- package/dist/runtime/initial-ready-fallback.d.ts.map +0 -1
- package/dist/runtime/initial-ready-fallback.js +0 -20
- package/dist/runtime/initial-ready-fallback.js.map +0 -1
- package/dist/runtime/initial-ready.d.ts +0 -49
- package/dist/runtime/initial-ready.d.ts.map +0 -1
- package/dist/runtime/initial-ready.js +0 -81
- package/dist/runtime/initial-ready.js.map +0 -1
- package/dist/runtime/reap-tree.d.ts +0 -62
- package/dist/runtime/reap-tree.d.ts.map +0 -1
- package/dist/runtime/reap-tree.js +0 -108
- package/dist/runtime/reap-tree.js.map +0 -1
- package/dist/runtime/session-base-types.d.ts +0 -36
- package/dist/runtime/session-base-types.d.ts.map +0 -1
- package/dist/runtime/session-base-types.js +0 -14
- package/dist/runtime/session-base-types.js.map +0 -1
- package/dist/runtime/session-base.d.ts +0 -48
- package/dist/runtime/session-base.d.ts.map +0 -1
- package/dist/runtime/session-base.js +0 -76
- package/dist/runtime/session-base.js.map +0 -1
- package/dist/runtime/session-commands.d.ts +0 -43
- package/dist/runtime/session-commands.d.ts.map +0 -1
- package/dist/runtime/session-commands.js +0 -52
- package/dist/runtime/session-commands.js.map +0 -1
- package/dist/runtime/session-exit.d.ts +0 -16
- package/dist/runtime/session-exit.d.ts.map +0 -1
- package/dist/runtime/session-exit.js +0 -32
- package/dist/runtime/session-exit.js.map +0 -1
- package/dist/runtime/session-image-attach.d.ts +0 -45
- package/dist/runtime/session-image-attach.d.ts.map +0 -1
- package/dist/runtime/session-image-attach.js +0 -82
- package/dist/runtime/session-image-attach.js.map +0 -1
- package/dist/runtime/session-lifecycle.d.ts +0 -61
- package/dist/runtime/session-lifecycle.d.ts.map +0 -1
- package/dist/runtime/session-lifecycle.js +0 -164
- package/dist/runtime/session-lifecycle.js.map +0 -1
- package/dist/runtime/session-loops.d.ts +0 -32
- package/dist/runtime/session-loops.d.ts.map +0 -1
- package/dist/runtime/session-loops.js +0 -62
- package/dist/runtime/session-loops.js.map +0 -1
- package/dist/runtime/session-readiness.d.ts +0 -18
- package/dist/runtime/session-readiness.d.ts.map +0 -1
- package/dist/runtime/session-readiness.js +0 -22
- package/dist/runtime/session-readiness.js.map +0 -1
- package/dist/runtime/session-reap.d.ts +0 -28
- package/dist/runtime/session-reap.d.ts.map +0 -1
- package/dist/runtime/session-reap.js +0 -51
- package/dist/runtime/session-reap.js.map +0 -1
- package/dist/runtime/session-resize.d.ts +0 -20
- package/dist/runtime/session-resize.d.ts.map +0 -1
- package/dist/runtime/session-resize.js +0 -26
- package/dist/runtime/session-resize.js.map +0 -1
- package/dist/runtime/session-shutdown-binding.d.ts +0 -30
- package/dist/runtime/session-shutdown-binding.d.ts.map +0 -1
- package/dist/runtime/session-shutdown-binding.js +0 -37
- package/dist/runtime/session-shutdown-binding.js.map +0 -1
- package/dist/runtime/session-shutdown.d.ts +0 -60
- package/dist/runtime/session-shutdown.d.ts.map +0 -1
- package/dist/runtime/session-shutdown.js +0 -140
- package/dist/runtime/session-shutdown.js.map +0 -1
- package/dist/runtime/session-status-wiring.d.ts +0 -38
- package/dist/runtime/session-status-wiring.d.ts.map +0 -1
- package/dist/runtime/session-status-wiring.js +0 -41
- package/dist/runtime/session-status-wiring.js.map +0 -1
- package/dist/runtime/session-status.d.ts +0 -7
- package/dist/runtime/session-status.d.ts.map +0 -1
- package/dist/runtime/session-status.js +0 -22
- package/dist/runtime/session-status.js.map +0 -1
- package/dist/runtime/shutdown-coordinator.d.ts +0 -57
- package/dist/runtime/shutdown-coordinator.d.ts.map +0 -1
- package/dist/runtime/shutdown-coordinator.js +0 -77
- package/dist/runtime/shutdown-coordinator.js.map +0 -1
- package/dist/runtime/startup-buffer.d.ts +0 -33
- package/dist/runtime/startup-buffer.d.ts.map +0 -1
- package/dist/runtime/startup-buffer.js +0 -53
- package/dist/runtime/startup-buffer.js.map +0 -1
- package/dist/runtime/startup-cleanup.d.ts +0 -43
- package/dist/runtime/startup-cleanup.d.ts.map +0 -1
- package/dist/runtime/startup-cleanup.js +0 -89
- package/dist/runtime/startup-cleanup.js.map +0 -1
- package/dist/runtime/startup.d.ts +0 -17
- package/dist/runtime/startup.d.ts.map +0 -1
- package/dist/runtime/startup.js +0 -47
- package/dist/runtime/startup.js.map +0 -1
- package/dist/runtime/status-emit.d.ts +0 -14
- package/dist/runtime/status-emit.d.ts.map +0 -1
- package/dist/runtime/status-emit.js +0 -15
- package/dist/runtime/status-emit.js.map +0 -1
- package/dist/runtime/teardown.d.ts +0 -15
- package/dist/runtime/teardown.d.ts.map +0 -1
- package/dist/runtime/teardown.js +0 -40
- package/dist/runtime/teardown.js.map +0 -1
- package/dist/runtime/terminate.d.ts +0 -22
- package/dist/runtime/terminate.d.ts.map +0 -1
- package/dist/runtime/terminate.js +0 -129
- package/dist/runtime/terminate.js.map +0 -1
- package/dist/runtime/update-lock.d.ts +0 -19
- package/dist/runtime/update-lock.d.ts.map +0 -1
- package/dist/runtime/update-lock.js +0 -183
- package/dist/runtime/update-lock.js.map +0 -1
- package/dist/runtime/update-once.d.ts +0 -40
- package/dist/runtime/update-once.d.ts.map +0 -1
- package/dist/runtime/update-once.js +0 -88
- package/dist/runtime/update-once.js.map +0 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
## 14. Conformance
|
|
2
|
+
|
|
3
|
+
This section defines what it means for an Elwood implementation to satisfy this
|
|
4
|
+
PRD. Every criterion below should have one or more tests. Test names should
|
|
5
|
+
include the criterion ID.
|
|
6
|
+
|
|
7
|
+
### 14.1 Criteria Structure
|
|
8
|
+
|
|
9
|
+
Each criterion has:
|
|
10
|
+
|
|
11
|
+
- an ID such as `C-API-01`;
|
|
12
|
+
- a reference to the section it validates;
|
|
13
|
+
- a concrete externally observable behavior.
|
|
14
|
+
|
|
15
|
+
### 14.2 Criteria
|
|
16
|
+
|
|
17
|
+
#### C-API: Public API (§5)
|
|
18
|
+
|
|
19
|
+
| ID | Section | Criterion |
|
|
20
|
+
|---|---:|---|
|
|
21
|
+
| C-API-01 | §5.1 | `startClaude({ cwd })` returns a `ClaudeSessionApi` with a stable non-empty `elwoodSessionId`. |
|
|
22
|
+
| C-API-02 | §5.1 | `startClaude` accepts launch-time hook handlers before Claude starts. |
|
|
23
|
+
| C-API-03 | §5.2 | `resumeClaude({ elwoodSessionId })` resumes using Elwood metadata without requiring a Claude session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. |
|
|
24
|
+
| C-API-04 | §5.3 | `ClaudeSessionApi` exposes `sendPrompt`, `sendKeys`, `resize`, `stop`, `kill`, and `teardown`. |
|
|
25
|
+
| C-API-05 | §5.3 | `sendPrompt` writes through the PTY input path, not through Claude print mode or SDK APIs. |
|
|
26
|
+
| C-API-06 | §5.3 | `sendPrompt` supports multi-line prompt text as one submitted user prompt. |
|
|
27
|
+
| C-API-07 | §5.3 | Calling `sendPrompt` while Claude is busy writes input immediately rather than waiting for turn readiness, serializes against queue-backed prompt/message/command submissions, and resolves after its submitting Enter is dispatched; terminated sessions may still fail with `session_not_running`. |
|
|
28
|
+
| C-API-08 | §5.4 | Consumers can subscribe and unsubscribe from typed session events. |
|
|
29
|
+
| C-API-09 | §5.5 | `startCodex({ cwd })` returns a `CodexSessionApi` with a stable non-empty `elwoodSessionId`. |
|
|
30
|
+
| C-API-10 | §5.6 | `resumeCodex({ elwoodSessionId })` resumes using Elwood metadata without requiring a Codex session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. |
|
|
31
|
+
| C-API-11 | §5.7 | `CodexSessionApi` exposes the same terminal control and lifecycle methods as `ClaudeSessionApi`. |
|
|
32
|
+
| C-API-12 | §5.4 | Claude and Codex sessions emit adapter-neutral `activity` events for common lifecycle, message, tool, transcript, and hook-error observations, with normalized metadata for common timeline rendering. |
|
|
33
|
+
| C-API-13 | §5.3 | Claude and Codex sessions expose `sendMessage` as the adapter-neutral queued message submission API. |
|
|
34
|
+
| C-API-14 | §5.7 | Sessions emit typed `warning` events (and projected `warning` activities) for non-fatal environment issues. Warnings are LIVE-ONLY: emitted once when observed, never persisted, never replayed to a late subscriber, and never accumulated into a snapshot or running count. A warning known BEFORE the session is returned (the version/preflight warning) is delivered on a deferred tick AFTER `startClaude`/`startCodex` resolves, so a caller that attaches a `warning` listener in the same turn it receives the session still observes it — without restoring general late-subscriber replay. |
|
|
35
|
+
| C-API-15 | §5.3 | Sessions expose a typed headless xterm terminal handle with snapshot and underlying xterm access. |
|
|
36
|
+
| C-API-16 | §5.1 | Omitted `initialSize` defaults to a 189 column by 48 row terminal. |
|
|
37
|
+
| C-API-17 | §5.4 | Hook dispatch emits adapter-neutral hook-result activity, and hook failures also appear in the activity stream. |
|
|
38
|
+
| C-API-18 | §5.1 | Claude and Codex start/resume options expose `autotrust` for opt-in workspace trust prompt automation. |
|
|
39
|
+
| C-API-19 | §5.3 | Calling `sendMessage` while the session is alive but not ready queues the message until the next ready transition and rejects only if the session terminates first. |
|
|
40
|
+
| C-API-20 | §5.7 | Codex transcript activity is flushed before terminal exit and terminal lifecycle status are emitted. |
|
|
41
|
+
| C-API-21 | §5.1 §5.5 | A caller-provided `persona` is submitted as the session's first user message on the first ready transition, ahead of caller-queued messages; it is not persisted and not re-sent on resume. |
|
|
42
|
+
| C-API-22 | §5.3 §5.7 | `compact()` submits the adapter's `/compact` command through the readiness queue, resolves on the adapter's `PostCompact` hook, rejects with `compact_failed` on timeout, and rejects with `session_not_running` if the session terminates first. |
|
|
43
|
+
| C-API-23 | §5.3 §5.7 | `listModels()` parses the adapter's rendered model picker into typed options with current/default markers, cancels with Escape, and leaves the session model unchanged. |
|
|
44
|
+
| C-API-24 | §5.3 §5.7 | `setModel(id)` switches the session model through cursor navigation; Elwood itself never persists a new default into user-owned configuration (Claude uses the session-only key; the Codex CLI persists its own picker selection, documented as a §4.5 deviation) and unknown ids reject with `model_automation_failed` listing available ids. When Claude interposes its built-in model- or effort-cache warning, Elwood selects the affirmative row from one bottom-most contiguous live dialog across numbered/unnumbered layouts, revalidates it before confirmation, and resolves only after the idle composer returns; transcript text cannot participate and a `PreModelSwitch` hook confirmation remains human-controlled. |
|
|
45
|
+
| C-API-35 | §5.3 §5.7 | `listModels()`/`setModel()` dispatch the picker command even while a turn is in flight (they do not wait for `ready`), so picker automation is not stalled by an in-flight turn such as an MCP-server boot spinner; `compact` and messages still wait for the active turn. Ordering is FIFO except for the documented overtaking of a readiness-waiting head by an immediate operation (a picker command, a `sendPrompt`, or already-ready running-turn guidance); same-class and post-ready ordering stays FIFO, and no two submissions interleave. |
|
|
46
|
+
| C-API-36 | §5.3 | A Claude session requested below 100 columns bootstraps at 100 columns and holds only the PHYSICAL pre-ready resize while recording each pre-ready requested size in memory (multiple pre-ready resizes leave the last one as the latest requested size; terminal size is never persisted, §8.2); it restores the latest requested size before its initial ready queue drains, so narrow visible terminals do not lose their first prompt. |
|
|
47
|
+
| C-API-37 | §5.3 §5.7 | Claude and Codex expose `sendGuidance(message)`: before first readiness and while blocked it queues safely like `sendMessage`; during a post-ready running turn it overtakes readiness-waiting operations and enters the TUI immediately. Guidance serializes with queue-backed prompt/message/command submissions and resolves only after the submitting Enter is dispatched; raw `sendKeys` intentionally bypasses that queue. |
|
|
48
|
+
| C-API-38 | §5.3 §5.7 | `interrupt()` bypasses the control queue and writes a single Escape immediately while the session is `running` or `blocked` AND has reached initial readiness at least once, resolving when the session next reaches `ready`; with no turn in flight — an idle `ready` session or the pre-readiness startup `running` window — it resolves without writing anything; concurrent calls coalesce into one Escape; it rejects with `interrupt_failed` after `timeoutMs` (default 10000 ms) and with `session_not_running` when the session terminates first or is already terminal. |
|
|
49
|
+
| C-API-39 | §5.3 §5.7 | When restoring the latest requested size at Claude's initial-ready transition fails with a real (non-closed) native PTY resize error, Elwood keeps the safe bootstrap width, does not report the resize as applied, and surfaces a typed `resize_restore_failed` warning (content-free: only the requested size and an allowlisted error code), while still releasing the queued persona/caller message so input is never starved; a closed-fd resize at that transition stays a silent no-op. |
|
|
50
|
+
| C-API-25 | §5.3 | Promise-returning session methods called after a terminal status reject with `session_not_running` instead of throwing synchronously. |
|
|
51
|
+
| C-API-26 | §5.2 §5.6 | `startOrResumeClaude`/`startOrResumeCodex` resume when possible, fall back to a fresh start only on `state_not_found`, `resume_unavailable`, or `adapter_mismatch`, rethrow all other errors, and report `resumed` in the result. |
|
|
52
|
+
| C-API-27 | §5.7 | `ElwoodAgentSession` is exported and both `ClaudeSessionApi` and `CodexSessionApi` (the raw live sessions the `ClaudeSession`/`CodexSession` classes wrap) are assignable to it, covering common events, io, commands, and lifecycle. |
|
|
53
|
+
| C-API-28 | §5.3 | Initial readiness fires from the adapter's pre-input readiness hook — Codex's `SessionStart`, Claude's `InstructionsLoaded`. On a COLD start the rendered composer is never a readiness signal — the boot-time composer placeholder alone never releases the first queued message (it is painted before the input loop is live and would be swallowed). On a RESUME the CLI reattaches to an existing conversation without re-firing its pre-input hook and its input loop is live when the composer paints, so a resumed session fires initial readiness on the FIRST rendered composer marker (or on the readiness hook if it re-fires, whichever is first), reaching readiness in ~1s instead of the full deadline. A blocking dialog's option caret is byte-identical to the composer marker, so the resume-composer path does NOT fire while a blocking dialog is on screen — readiness waits for the dialog to clear, so a draining queued message can never approve it. This dialog gate applies to EVERY readiness source, not just the resume-composer path: a blocking dialog on screen — including one that first rendered while the session was still `starting`, before it could latch the `blocked` status — defers a hook OR deadline readiness mark too, so no readiness source ever drains the queue into a dialog. The deferred mark is reconciled the moment a rendered frame shows the dialog cleared (readiness is neither drained into the dialog nor permanently starved by it). A bounded deadline after the first rendered frame is the ultimate fallback on BOTH adapters and BOTH start/resume, so a missing/failed readiness hook (and, on cold start, an absent composer signal) cannot starve queued persona/messages forever. Readiness fires exactly once (idempotent): whichever of hook, resume-composer signal, or deadline comes first releases the first queued message, and the others are no-ops. |
|
|
54
|
+
| C-TURN-01 | §5.3 | While a turn runs the session is `running`; when the turn ends by any means — completion, Escape interrupt, or otherwise — the session transitions to `ready` and emits the corresponding `status` activity, on both adapters. |
|
|
55
|
+
| C-TURN-02 | §5.3 | An Escape interrupt of a running turn produces the `ready` transition from rendered TUI state alone, with no dependency on a `Stop` hook. |
|
|
56
|
+
| C-TURN-03 | §5.3 | Turn-state detection uses documented per-adapter indicator constants over `snapshot().text`; redundant edges are idempotent, and screens showing neither indicator hold state. Watching activates only after initial readiness. On resume, rendered edges remain suppressed through transcript replay until the first quiet, non-blocking composer; explicit running evidence releases that suppression immediately and synchronizes the watcher, and the CURRENT frame is still evaluated so the real turn's rendered end is retained even when it is the first frame after release (the turn ended exactly as the evidence arrived). |
|
|
57
|
+
| C-TURN-04 | §5.3 | The interrupt `ready` transition fires at any terminal width: via the working-token edge where the footer fits, and via the adapter's interrupt end banner (which Claude's footer elision below ~66 columns makes necessary) on narrow screens. |
|
|
58
|
+
| C-TURN-05 | §5.3 | Turn-state detection also consumes the OSC window title as a width-independent working signal: both adapters render a braille-spinner glyph (U+2800–U+28FF) in the title while a turn runs, so a turn `started` edge can fire from the title alone when the on-screen footer token is elided. The title is exposed on the terminal handle and is not persisted. |
|
|
59
|
+
| C-ATTN-01 | §5.3 | A tool permission dialog rendered mid-turn transitions the session to `blocked` and emits an `attention` activity whose label names the matched screen-fact rule ids, on both adapters. |
|
|
60
|
+
| C-ATTN-02 | §5.3 | A session enters `blocked` from either `running` or `ready` when a blocking dialog appears, and settles to `ready` when the dialog resolves; blocking evidence never moves a session out of a terminal status. |
|
|
61
|
+
| C-ATTN-03 | §5.3 | Startup prompts Elwood answers automatically never produce `blocked`; a workspace trust prompt left unanswered because `autotrust` is false does. |
|
|
62
|
+
| C-ATTN-04 | §5.3 | Claude's model/effort switch confirmation dialogs are blocking prompts even though their selection caret resembles the composer marker. The built-in cache warning is automatically confirmed only as part of `setModel`; hook-requested confirmations remain blocked for a human decision. |
|
|
63
|
+
| C-API-33 | §5.3 | `statusDecisions()` returns a live-only log of at most the 50 most recent status decisions, oldest first, each recording the evidence, the from/to statuses (`to` undefined when ignored), and a human-readable reason; it is never persisted and carries no prompt, terminal, or hook payload content. |
|
|
64
|
+
| C-API-34 | §5.3 | `waitForStatus`/`waitForActivity` resolve from current state or the next matching event, reject with `wait_timeout` after the timeout (default 60000 ms), and reject with `session_not_running` when the session reaches an unwaited terminal status first; both unsubscribe on settle. |
|
|
65
|
+
| C-API-29 | §5.2 §5.6 | Resume accepts the same launch-policy options as start and forwards them into the relaunched command: `resumeClaude` forwards `permissionMode`, `allowedTools`, `disallowedTools`, `tools`, and `reasoningEffort`; `resumeCodex` forwards `sandbox`, `approvalPolicy`, and `reasoningEffort`. A resumed agent stays exactly as privileged and as tool-restricted as it started, and applies a re-supplied reasoning effort (which Elwood does not persist across resume). |
|
|
66
|
+
| C-API-32 | §5.2 §5.6 | Resume defaults launch-policy options from the record's persisted posture; explicit resume options override field by field, and the effective posture is re-persisted. |
|
|
67
|
+
| C-API-30 | §5.4 | `tool_call` activity carries the tool's input as a serialized `toolInput` and `tool_result` activity carries the tool's output as a serialized `toolOutput`, sourced from the committed transcript for Claude (`tool_use.input`/`tool_result.content`, per C-CLAUDE-15) and from the transcript for Codex (`arguments`/`output`); absent sources leave the field absent. |
|
|
68
|
+
| C-API-31 | §5.3 | The submitting Enter is a separate PTY write after a settle delay, and bounded re-Enters fire while the rendered composer still shows the staged paste, so a first long prompt cannot be left staged-but-unsubmitted. |
|
|
69
|
+
| C-API-40 | §5.3 | `sendPrompt`/`sendMessage`/`sendGuidance` text is sanitized before it is framed as a bracketed paste: the `ESC [ 200~`/`ESC [ 201~` sentinels and all other C0/C1 control characters except tab, newline, and carriage return are removed, so caller/model text cannot escape paste mode and inject live keystrokes (e.g. an Enter that confirms a permission dialog). `sendKeys` is the raw escape hatch and is never sanitized. |
|
|
70
|
+
| C-API-41 | §5.3 | `listClaudeModels(options)`/`listCodexModels(options)` return the available models WITHOUT a caller-held session: each starts a throwaway session with `autotrust: true` (a documented §5.1 exception — the probe only opens/cancels the picker and never runs a turn), waits for first readiness, calls `listModels`, and ALWAYS removes ALL probe state afterward — on success, on a readiness/picker failure, AND on an adapter start failure that has already allocated a state directory or runtime files — so no probe session, process tree, socket, or state directory is leaked. A readiness/picker error is the error surfaced to the caller; teardown still runs, and when teardown ITSELF fails (which can leave a probe process tree alive) a bounded diagnostic naming the teardown failure — an allowlisted errno or the error's name, never a raw message — is attached to the surfaced error's `cause`, so a leaked probe is never silently invisible. The rows are exactly `listModels`' output and the probe leaves the user's saved MODEL default untouched (the picker is opened and cancelled, never applied), just as `listModels` does on a live session. |
|
|
71
|
+
| C-API-42 | §5.3 §5.7 | When recording a session's one-shot initial-ready transition throws (a lifecycle-event listener) — on EITHER adapter — Elwood still releases the control queue directly FIRST so queued persona/caller input is never starved, then surfaces a typed, content-free `initial_ready_fallback` warning (`agent: "claude" \| "codex"`) whose delivery is itself isolated so a rogue warning sink cannot re-starve the queue. |
|
|
72
|
+
| C-API-43 | §5.3 | `ClaudeSession.login(options)` drives the interactive `/login` re-authentication flow to recover a session whose login lapsed (per C-CLAUDE-18) without a caller-held restart. It submits `/login` as a picker command, selects the requested login `method` (default `claudeai`) from the "Select login method" list if that list renders, and scrapes the authorization URL off screen and reports it via the optional `onAuthUrl` callback. Because the default account flow completes with a human-entered authorization code, Elwood awaits the CLI's "paste code" prompt and, when it renders, calls the required `provideCode` callback and submits the returned code as raw input; a flow that self-completes without prompting for a code never calls `provideCode`. `login` resolves once the CLI reports login success AND the session is usable again, and rejects with `login_failed` on an explicit failure/invalid-code banner or with `login_timeout` after `timeoutMs` (default 300000 ms); like every other command method it rejects with `session_not_running` when the session is terminal. It serializes with other queued commands and never sends raw keystrokes into a blocking dialog. |
|
|
73
|
+
| C-API-44 | §5.3 | `sendPrompt`/`sendMessage`/`sendGuidance` accept an optional `images` list (file paths or `{data,format}` bytes) attached to the submission. Images have no position within the text, but the list is ORDERED (array order = attachment/chip order). Elwood attaches every image through the CLI's own native ingestion path, as part of the SAME serialized queue operation that submits the text and BEFORE that text's submitting Enter, so one call yields one agent turn carrying text and all images. Inputs are validated and bounded before any temp file is written or any input reaches the composer: at most 16 images, ≤25 MiB per image and ≤50 MiB total; an unsupported/empty `format`, a `path` that is not a readable regular file (its readability checked, not merely its existence), or any count/size past the limits rejects with `invalid_image`. A non-array `images`, an entry that is not exactly one of `{path}`/`{data,format}`, a non-string `path`, or non-`Uint8Array` `data` also rejects with `invalid_image` (never a raw type error). Every input is defensively SNAPSHOTTED — byte buffers cloned, paths absolutized — SYNCHRONOUSLY at the public send boundary, BEFORE the op is enqueued, so a caller that mutates its buffer (or changes `cwd`) after the call cannot change what is attached; the shape, format, count, and byte-size limits are enforced in that same synchronous pass, so a malformed or over-limit input rejects with `invalid_image` before it ever occupies a queue slot. The remaining filesystem checks that need I/O — a path's readability, regular-file-ness, and size against the per-image and shared aggregate ceilings — run inside the queued op at dispatch (so a path made unreadable after the call still rejects, and no temp file is written for a still-queued submission). The synchronous snapshot completes before the queue insertion, so an image submission is still enqueued in call order and a later plain submission cannot overtake it. Beyond the per-submission 50 MiB ceiling, a PER-SESSION aggregate ceiling (200 MiB) bounds the cloned image bytes held across ALL not-yet-attached queued submissions at once: a submission whose clone would push the session past that ceiling rejects with `invalid_image`, and each submission's reserved bytes are released when it settles (attach done, reject, or the session closing), so a slow paste/confirmation cannot let a caller retain unbounded queued-clone memory. Because a send on a TERMINAL session is guarded first, a malformed/over-limit `images` on a stopped/killed/torn-down session rejects with `session_not_running` (the terminal contract), not `invalid_image`. The lifecycle turn transition is deferred until AFTER a successful attach, so an attach rejection leaves readiness untouched and never wedges the queue; a raw materialization failure (e.g. disk-full) is surfaced as the typed `image_attach_failed`, not a raw platform error. Each image's attach is CONFIRMED by its `[Image #N]` chip on the composer prompt-glyph line (so unrelated screen text cannot spoof it); an unconfirmed image (chip not seen within 10 s) or a clipboard read/write failure rejects with `image_attach_failed`, submits no text, and best-effort clears the composer draft so staged images do not leak into a later turn (a session that terminates mid-attach rejects with `session_not_running`). Bytes are written to a short-lived temp file Elwood attempts to remove after the submission is attached (best-effort; a persistently failing removal falls to OS temp reaping). |
|
|
74
|
+
| C-API-45 | §5.3 | Claude attaches an image by bracketed-pasting its ABSOLUTE path into the composer (the delivery a terminal produces on drag-and-drop); Claude reads and encodes the file itself and shows an `[Image #N]` chip. This is pure PTY text and works on every platform. |
|
|
75
|
+
| C-API-46 | §5.3 | Codex attaches an image from the OS clipboard: Elwood snapshots the user's clipboard ONCE (rejecting with `image_attach_failed` if the snapshot fails, before mutating anything), then for each image in order writes it onto the macOS `NSPasteboard` as a native image (`public.tiff`), sends Ctrl+V, and waits for the `[Image #N]` chip; after all images it restores the single snapshotted clipboard (best-effort, text contents). The whole snapshot/set/paste/confirm/restore sequence holds a process-wide clipboard lock so concurrent Codex sessions cannot cross-attach. A pasted path is NOT an image on Codex. Codex image attachment is macOS-only: `images` on a non-macOS Codex session rejects with `unsupported_platform` and submits nothing. |
|
|
76
|
+
| C-API-47 | §5.8 | The ergonomic `ClaudeSession`/`CodexSession` construct synchronously with the same options as `startClaude`/`startCodex` (with `cwd` defaulting to `process.cwd()`), do NOT start the underlying session at construction, and start it lazily on the first `send`/`stream` or on an explicit `start()`. `start()` is idempotent and concurrent-safe (a second `start()`, or a `send`/`stream` during startup, awaits the same in-flight start), and a startup failure rejects the triggering call with the same typed `ElwoodError` the factory would throw. The started underlying session is exposed via a read-only `session` accessor (undefined until started). |
|
|
77
|
+
| C-API-48 | §5.8 | `stream(prompt, options?)` returns an async iterable that yields the SIMPLIFIED typed events of exactly one turn in arrival order — `{type:"text"}` for `assistant_message`, `{type:"thinking"}` for `reasoning`, `{type:"tool_call",name,input?,toolCallId?}` for `tool_call`, `{type:"tool_result",name?,output?,toolCallId?}` for `tool_result` — and no other activity kind. A tool event carries the adapter's normalized `toolUseId` as `toolCallId` when available so calls and results can be correlated. The facade subscribes to `activity` BEFORE submitting the prompt (no early event of the turn is missed); turn isolation rests on SERIALIZATION — the serialized slot is held until the agent reaches its real boundary (not until the consumer stops reading) so a prior turn's activity never bleeds into the next — with `turnId` binding as an additional refinement where the adapter tags it (Codex; Claude transcript activity has none). An idle `ready` may complete the turn only after positive acceptance from `UserPromptSubmit`, turn content, or `Stop`; an unaccepted idle transition waits the no-oracle quiet window, replays the same submission at most twice, and then fails with `wait_timeout` instead of reporting an empty success. The iterator ends via the completeness oracle: once an accepted `ready` is observed, it ends the instant the collected assistant text contains the `Stop` hook's `last_assistant_message` (used as a completeness signal only, never displayed); with no such signal it ends after a bounded quiet window; a terminal status ends it IMMEDIATELY. There is NO whole-turn timeout by default (a live turn may run for hours); a caller may pass an opt-in `timeoutMs` ceiling, ARMED ONLY AFTER submission (a turn begins on submission, so the ceiling never rejects a prompt still queued behind readiness that then submits), and a `catchUpMs` cap (default 10000 ms) armed once an accepted `ready` fires throws `wait_timeout` if the transcript never catches up — including when a NON-EMPTY expected text never appears (a promised completion that never arrives is a failure, not a quiet settle). |
|
|
78
|
+
| C-API-49 | §5.8 | `send(prompt, options?)` resolves with the turn's assistant text: the `text` of every `type:"text"` stream event, in order, joined by `\n\n` between distinct assistant messages, and NOTHING else (no thinking or tool text). A turn with no assistant text resolves to the empty string. `send` and `stream` share one turn boundary, so a `send` resolves exactly when the equivalent `stream` iterator ends. |
|
|
79
|
+
| C-API-50 | §5.8 | Ergonomic turns are SERIALIZED: overlapping `send`/`stream` calls queue and run one at a time in call order, so one turn's yielded/collected activity never interleaves with another's. |
|
|
80
|
+
| C-API-51 | §5.8 | `close()` stops the underlying session (falling back to `kill` on a stop failure) and is a no-op when the session never started, so it is safe to call in a `finally`. When a lazy start is IN FLIGHT, `close()` awaits that same start and stops the resulting session (never orphaning a session whose launch resolves after `close()` returned); a launch that REJECTS leaves nothing to close. When BOTH stop and kill fail, `close()` throws `termination_failed` carrying BOTH the stop `cause` and the kill `killCause` (neither diagnostic is lost). |
|
|
81
|
+
| C-API-52 | §5.8 | `ClaudeSession`/`CodexSession` expose the FULL control surface in addition to `send`/`stream`, in THREE categories. (1) OPERATIONAL methods — `sendMessage`, `sendPrompt`, `sendGuidance`, `sendKeys`, `resize`, `interrupt`, `compact`, `listModels`, `setModel`, `waitForStatus`, `waitForActivity`, and Claude's `login` — lazy-start the underlying session on first use and delegate to it. (2) `on`/`off` BUFFER before start (attached on start, so subscribing never forces a start). (3) SHUTDOWN methods — `stop`/`kill`/`teardown` — only delegate when a live session already exists and are no-ops before start (so `close()` is safe in a `finally`). `status` reads `starting` until the session exists. Only `send`/`stream` serialize; operational control methods go through immediately (not queued behind a running turn). `startClaude`/`startCodex` are deprecated in favor of the class but remain functional. |
|
|
82
|
+
| C-API-53 | §5.8 | An ergonomic turn's buffered state is BOUNDED even with no whole-turn timeout: the oracle matches a rolling window of recent assistant text (bounded even when the `Stop` hook's expected text is itself large, and large enough BEFORE the oracle is installed to retain text that arrives ahead of a lagging `Stop` hook), and UNCONSUMED events are capped by BOTH count and total UTF-8 bytes. A turn whose pending backlog exceeds either cap fails with `wait_timeout` rather than growing without limit; the byte cap is required because a single event may carry an arbitrarily large agent-controlled payload that the count cap alone would not bound. |
|
|
83
|
+
| C-API-54 | §5.1 §5.2 §5.5 §5.6 | `highTrust: true` on `startClaude`/`resumeClaude`/`ClaudeSession` expands to `permissionMode: "bypassPermissions"`, and on `startCodex`/`resumeCodex`/`CodexSession` to `sandbox: "danger-full-access"` with `approvalPolicy: "never"`, before the posture is persisted and the process spawned (the record and launch command carry the expanded values). Combining it with an explicit `permissionMode` (Claude) or `sandbox`/`approvalPolicy` (Codex) rejects before spawn with `claude_high_trust_conflict` / `codex_high_trust_conflict`; the lazy session classes reject at construction. |
|
|
84
|
+
|
|
85
|
+
#### C-LOOP: Recurring Session Loops (§5.9, §8, §9, §10)
|
|
86
|
+
|
|
87
|
+
| ID | Section | Criterion |
|
|
88
|
+
|---|---:|---|
|
|
89
|
+
| C-LOOP-01 | §5.9 | Raw Claude/Codex sessions, `ElwoodAgentSession`, and both lazy classes expose async `createLoop`, `listLoops`, and `cancelLoop` with adapter-neutral behavior and no native scheduler command. |
|
|
90
|
+
| C-LOOP-02 | §5.9 | `ElwoodLoopRequest` is a readonly fixed/idle union; `parseLoopCommand` is explicit and package-root exported, while `sendMessage`/`sendPrompt` preserve literal `/loop` text. |
|
|
91
|
+
| C-LOOP-03 | §5.9 | The parser recognizes only a positive integer plus `s`, `m`, `h`, or `d` as the first-token interval; every other remainder is an idle message. |
|
|
92
|
+
| C-LOOP-04 | §5.9 | Fixed intervals are `>= 60,000` and `< 604,800,000` ms, messages are 1..65,536 UTF-8 bytes, and a fifty-first unexpired loop fails without eviction. |
|
|
93
|
+
| C-LOOP-05 | §5.9 | A fixed loop first becomes due after its full interval plus delay-only jitter and submits only through the ordinary readiness-safe path. |
|
|
94
|
+
| C-LOOP-06 | §5.9 | Idle loops use five continuous non-loop-activity-free minutes plus jitter; non-loop activity resets all, while a completed loop turn rearms only its origin and leaves peers eligible. |
|
|
95
|
+
| C-LOOP-07 | §5.9 | Stable jitter is derived from ID at creation, falls in `0..min(10% cadence, 30s)`, never advances a due time, and is persisted as restore authority. |
|
|
96
|
+
| C-LOOP-08 | §5.9 | A due loop blocked by liveness/readiness submits at most once at the next ready transition; missed occurrences do not accumulate and stop/exit discards transient due state. |
|
|
97
|
+
| C-LOOP-09 | §5.9 | Due loops commit serially, one per ready transition, ordered by due time then ID; fixed clocks restart from submission and idle origin clocks from completing readiness. |
|
|
98
|
+
| C-LOOP-10 | §5.9 | Live scheduling/submission failure emits redacted failure evidence and rearms fresh unless the session is non-live, when the persisted definition waits for resume. |
|
|
99
|
+
| C-LOOP-11 | §8.2 | The loop sidecar stores stable identity, cadence, exact prompt, jitter, creation, and expiry but no live timer or transient scheduling state. |
|
|
100
|
+
| C-LOOP-12 | §5.9 §9.3 | Resume discards missed runs and starts fresh clocks for surviving definitions from restored readiness. |
|
|
101
|
+
| C-LOOP-13 | §5.9 | Loops expire seven wall-clock days after original creation; expiry beats pending submission, emits live only, and is pruned silently during resume or stopped/exited management. |
|
|
102
|
+
| C-LOOP-14 | §5.9 §8.4 | Stop, unexpected exit, and process failure preserve loops; kill clears them permanently; teardown removes them with session state. |
|
|
103
|
+
| C-LOOP-15 | §5.9 | `listLoops` orders immutable snapshots by creation then ID and exposes exact message, cadence, interval when fixed, jitter, times, state, and only a scheduled next-due time. |
|
|
104
|
+
| C-LOOP-16 | §5.4 §5.9 | `loop` events match the specified `created`/`fired`/`cancelled`/`expired`/`failed` union and never contain a stored message or serialized request. |
|
|
105
|
+
| C-LOOP-17 | §5.9 | Cancelling a known loop removes its definition and all future submissions without interrupting an already-committed active turn. |
|
|
106
|
+
| C-LOOP-18 | §5.9 | Cancelling an unknown or finished loop fails with `loop_not_found` and cannot affect a different loop. |
|
|
107
|
+
| C-LOOP-19 | §5.9 §10 | Loop validation, capacity, cancellation, persistence, and scheduling/submission use the five stable loop error names; diagnostics identify only loop ID. |
|
|
108
|
+
| C-LOOP-20 | §5.9 | Listing/cancellation work on stopped/exited raw sessions, creation requires live state, lazy methods retain lazy-start semantics, and delivery uses current readiness/security posture. |
|
|
109
|
+
| C-LOOP-21 | §8.2 | Core schema-version-1 records remain readable; absent sidecar means no loops, while the versioned `0600`, current-user-owned sidecar rejects malformed/unsafe reads as `state_corrupt`. |
|
|
110
|
+
|
|
111
|
+
#### C-PTY: Terminal Process Behavior (§4, §9)
|
|
112
|
+
|
|
113
|
+
| ID | Section | Criterion |
|
|
114
|
+
|---|---:|---|
|
|
115
|
+
| C-PTY-01 | §4.1 | Claude runs inside a real PTY rather than a plain pipe subprocess. |
|
|
116
|
+
| C-PTY-02 | §4.2 | The launched process sees environment from the user's normal interactive macOS shell startup. |
|
|
117
|
+
| C-PTY-03 | §4.1 | Raw PTY output is emitted through `terminal:data` for live rendering, and late subscribers receive a bounded live-only replay of earlier process-lifetime PTY output. |
|
|
118
|
+
| C-PTY-04 | §5.3 | `sendKeys` writes through the headless xterm input path, which emits PTY input. |
|
|
119
|
+
| C-PTY-05 | §5.3 | `resize({ cols, rows })` resizes both the underlying PTY and the headless terminal model, while closed-fd resize races during process exit are ignored. |
|
|
120
|
+
| C-PTY-06 | §9.4 | Process exit emits a terminal/process exit event and updates session status. |
|
|
121
|
+
| C-PTY-07 | §4.1 | Cursor-addressed PTY output renders into the headless xterm snapshot before startup prompt detection runs. |
|
|
122
|
+
| C-PTY-08 | §4.2 §9.2 | Non-PTY CLI probes (`--version`, `update`, `--help`) and the agent PTY run through the same interactive login shell, so every lifecycle operation resolves the same CLI installation. |
|
|
123
|
+
|
|
124
|
+
#### C-PERF: Startup Performance (§9)
|
|
125
|
+
|
|
126
|
+
| ID | PRD | Criterion |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| C-PERF-01 | §9.2 | Version/capability probes run asynchronously and never block the host process's event loop, so a roster of concurrent spawns does not serialize on a single thread. |
|
|
129
|
+
| C-PERF-02 | §9.2 | Each adapter's `--version` is read at most once per parent process; concurrent first reads share a single subprocess, later reads reuse the cached result, and the cache is invalidated after every coordinated `autoupdate` attempt, including a failed or peer-owned attempt. |
|
|
130
|
+
| C-PERF-03 | §9.2 | Any non-PTY probe (`--version`, `--help`, `update`) that exceeds the default runtime timeout (15s) or per-stream byte cap (1,000,000 bytes) is killed. A killed/failed `--version`/`--help` probe surfaces through `*_start_failed` with `cause`/`errno` (`ETIMEDOUT`/`E2BIG`) preserved in `details`; a killed/failed `update` probe is best-effort and instead surfaces the live `agent_update_failed` warning (same bounded diagnostics) without blocking startup. Probes that exit or fail to spawn on their own are not signaled. |
|
|
131
|
+
| C-PERF-04 | §9.2 | With `autoupdate`, concurrent callers in one parent process share one update, and separate Elwood parent processes for the same macOS user and adapter serialize through an atomic lease in a stable account cache independent of `TMPDIR`. A contender waits asynchronously for the active updater, skips its duplicate attempt, invalidates its local caches, and validates the installed version; none races a second global update. The lease records owner identity and a unique generation: a live owner is never evicted solely because the stale bound elapsed, dead-owner recovery is serialized, and cleanup cannot remove a successor generation. |
|
|
132
|
+
|
|
133
|
+
#### C-CLAUDE: Claude Startup And Settings (§4, §7, §9)
|
|
134
|
+
|
|
135
|
+
| ID | Section | Criterion |
|
|
136
|
+
|---|---:|---|
|
|
137
|
+
| C-CLAUDE-01 | §4.3 | Elwood does not mutate `.claude/settings.local.json` during normal startup. |
|
|
138
|
+
| C-CLAUDE-02 | §4.3 | Elwood launches Claude with session-scoped generated settings passed through Claude's settings override mechanism. |
|
|
139
|
+
| C-CLAUDE-03 | §7 | `permissionMode`, `allowedTools`, and `disallowedTools` options are reflected in Claude launch policy. |
|
|
140
|
+
| C-CLAUDE-04 | §9.2 | Startup checks Claude Code version and fails with `claude_version_unsupported` when below the configured minimum. |
|
|
141
|
+
| C-CLAUDE-05 | §10 | Missing `claude` fails with `claude_not_found` and a useful message. |
|
|
142
|
+
| C-CLAUDE-06 | §10 | An immediately failing or unusable Claude process fails with `claude_start_failed` or a more specific typed error. |
|
|
143
|
+
| C-CLAUDE-07 | §5.1 | `autoupdate: true` runs `claude update` before spawning Claude. |
|
|
144
|
+
| C-CLAUDE-08 | §5.2 | `resumeClaude` fails explicitly when Elwood has not persisted a Claude resume id. |
|
|
145
|
+
| C-CLAUDE-09 | §9.2 | `autoupdate: true` rechecks the Claude version after running `claude update`. |
|
|
146
|
+
| C-CLAUDE-10 | §5.1 | `autotrust: true` answers Claude's workspace trust prompt through PTY input and emits `startup_prompt` activity, supporting both older numbered-option layouts and current cursor-selectable layouts. |
|
|
147
|
+
| C-CLAUDE-11 | §5.1 | Claude's browser tools onboarding prompt is declined through PTY input regardless of `autotrust`, with `startup_prompt` activity emitted under the `browser_tools` label. |
|
|
148
|
+
| C-CLAUDE-12 | §5.1 | `startClaude` forwards `model` to Claude's `--model` launch flag. |
|
|
149
|
+
| C-CLAUDE-13 | §4.3 | `tools` emits Claude's `--tools` allowlist flag as one comma-separated value, with an empty array encoding `--tools ""` (all tools disabled); it is forwarded across resume like the other tool options. |
|
|
150
|
+
| C-CLAUDE-14 | §5.1 | Under `autotrust`, Claude's allowlisted skill/plugin/MCP trust prompts are each answered once and emit `startup_prompt` activity under their `skill_trust`/`plugin_trust`/`mcp_trust` labels. Recognition is the only guard: a prompt is recognized solely by its HEADER wording outside the numbered or cursor-selectable option region (so an option-only trust phrase cannot spoof one), and once recognized Elwood sends or navigates to the first affirmative option in the current frame — the agent is never left waiting. When a recognized prompt's affirmative option has not rendered in the current frame yet, Elwood emits a fire-once transient `attention` activity and keeps watching so a later frame carrying the option is still answered; this render-delay state is TRANSIENT and no warning is emitted for it. An off-allowlist first-run prompt is never auto-answered. Per the say-yes policy there is deliberately no per-dialog region binding, so a second stacked dialog's affirmative in the same frame is an accepted consequence, not a defended boundary. |
|
|
151
|
+
| C-CLAUDE-15 | §5.4 | Claude `assistant_message`, `tool_call`, and `tool_result` activities are sourced from the committed transcript the CLI writes at `transcript_path`, never from the `Stop` hook's `last_assistant_message`; an un-sent ghost-text / composer draft therefore never becomes an `assistant_message`. |
|
|
152
|
+
| C-CLAUDE-16 | §5.1 §5.4 §5.7 | A Claude startup prompt Elwood auto-answers is marked settled and emits its `startup_prompt` activity only after its PTY `sendInput` write fulfills. A rejected write emits NO `startup_prompt` activity, leaves the prompt un-settled so a later frame re-attempts it, and surfaces a bounded, content-free `startup_prompt_write_failed` warning carrying only the prompt label. |
|
|
153
|
+
| C-CLAUDE-17 | §5.1 | A Claude startup banner showing a lapsed, revoked, or absent login that only directs the user to re-run `/login` (`Login expired`, `Session expired`, `OAuth token revoked`, or `Not logged in`, each paired with a `run /login` recovery hint) is treated as an authentication failure and rejects `startClaude` with `claude_not_authenticated`, exactly like the explicit `not authenticated` banners — the session is torn down rather than reported as usable. Matching is anchored on the `/login` recovery directive so an unrelated mention of "login" does not trip it. |
|
|
154
|
+
| C-CLAUDE-18 | §5.3 §5.7 | When a lapsed/revoked-login banner (per C-CLAUDE-17) appears on a Claude session that has ALREADY reached readiness — i.e. login expires mid-session — Elwood surfaces a typed, content-free `login_expired` warning (and its `warning` activity) carrying only a bounded `recoveryCommand` of `/login`. Detection is edge-based so a banner persisting across many frames does not re-emit, so a session warns at most once for its login expiring; the session is left ALIVE (no forced terminal transition) so the caller can recover in place via `session.login()`, tear down, or re-authenticate out of band. |
|
|
155
|
+
| C-CLAUDE-19 | §5.4 §7A.4 | A committed Claude assistant `thinking` content block surfaces its plaintext extended-thinking as a `reasoning` activity carrying that text, sourced from the committed transcript exactly like `assistant_message` (C-CLAUDE-15) — no hook exposes it. Only ASSISTANT thinking is surfaced and empty thinking is dropped; a `redacted_thinking` block (opaque encrypted `data`, no readable text) produces no activity. |
|
|
156
|
+
| C-CLAUDE-20 | §5.1 §5.2 | `reasoningEffort`, when supplied to `startClaude`/`resumeClaude`, is validated against the `ClaudeReasoningEffort` enum (`low`, `medium`, `high`, `xhigh`, `max`) before spawn — an out-of-enum value rejects with `claude_invalid_reasoning_effort` (message listing the valid values) and no process is spawned — and a valid value is forwarded to Claude's `--effort` launch flag. It is independent of `model`, applies to the launched session only, and is not persisted across resume (a resume must re-supply it). |
|
|
157
|
+
| C-CLAUDE-21 | §5.1 §5.4 | Claude's one-time bypass-permissions acceptance dialog (header wording `Claude Code running in Bypass Permissions mode`, affirmative `Yes, I accept`, decline `No, exit`) is an allowlisted trust prompt: under `autotrust` it is recognized by its header wording outside the option region, answered once by selecting the affirmative in numbered or cursor layouts, and reported as `startup_prompt` activity with label `bypass_permissions`; with `autotrust` off it is a blocking prompt. The persistent `bypass permissions on` footer of a running session never matches, and a CLI refusal of bypass mode surfaces as the ordinary start failure. |
|
|
158
|
+
|
|
159
|
+
#### C-CODEX: Codex Startup And Config (§4, §7A, §9)
|
|
160
|
+
|
|
161
|
+
| ID | Section | Criterion |
|
|
162
|
+
|---|---:|---|
|
|
163
|
+
| C-CODEX-01 | §4.4 | Elwood does not mutate `.codex/config.toml`, `.codex/hooks.json`, or user Codex config during normal startup. |
|
|
164
|
+
| C-CODEX-02 | §4.4 | Elwood launches Codex with session-scoped hook config passed through `--config` overrides. |
|
|
165
|
+
| C-CODEX-03 | §5.5 | `model`, `profile`, `sandbox`, `approvalPolicy`, and `configOverrides` options are reflected in Codex launch policy. |
|
|
166
|
+
| C-CODEX-04 | §9.2 | Startup checks Codex CLI version and fails with `codex_version_unsupported` when below the configured minimum. |
|
|
167
|
+
| C-CODEX-05 | §10 | Missing `codex` fails with `codex_not_found` and a useful message. |
|
|
168
|
+
| C-CODEX-06 | §4.4 | Codex hooks are trusted by `hookTrust="trust-all"`, the bypass flag when supported, or by answering the allowlisted `Hooks need review` TUI prompt once regardless of `autotrust` (hook trust is Elwood's own integration; see C-CODEX-15). |
|
|
169
|
+
| C-CODEX-07 | §5.6 | `resumeCodex` fails explicitly when Elwood has not persisted a Codex resume id. |
|
|
170
|
+
| C-CODEX-08 | §5.5 | `autoupdate: true` runs `codex update` before spawning Codex. |
|
|
171
|
+
| C-CODEX-09 | §5.7 | Codex MCP startup warnings are parsed from terminal output into typed warning events with server names and recovery commands. |
|
|
172
|
+
| C-CODEX-10 | §9.2 | `autoupdate: true` rechecks the Codex version after running `codex update`. |
|
|
173
|
+
| C-CODEX-11 | §5.5 | `autotrust: true` answers Codex's directory trust prompt through PTY input and emits `startup_prompt` activity. |
|
|
174
|
+
| C-CODEX-12 | §5.5 | If Codex still shows an interactive update prompt inside the TUI, Elwood selects the skip/continue-without-updating option by label. The skip is EDGE-triggered: a persistent update screen starts one bounded response operation (not one per rendered frame), but that operation re-reads the current rendered frame before every retry and writes only when the original first-party prompt generation remains active and the frame exposes its safe numbered choice. It stops without writing when the generation clears or changes and fails boundedly if that prompt loses its safe choice or exhausts retries; the CLI converts an unanswerable blocked update into `blocked_prompt` even without a whole-run timeout. The skip RE-ARMS once the update screen leaves the frame, so an update prompt that REAPPEARS after Codex restarts (e.g. the update did not take and the same screen returns) is skipped again rather than leaving the session stuck on it. Every recognized update screen is input-blocking until its rendered frame clears: a captured first-party banner blocks before its options render, and a following safe-option-only continuation frame stays latched as the same prompt. Generic agent prose containing "update available" and Codex's passive installation notice do not activate the blocker. Readiness and queued persona/caller input therefore cannot select the default "Update now" action. |
|
|
175
|
+
| C-CODEX-13 | §10 | An immediately failing or unusable Codex process fails with `codex_start_failed` or a more specific typed error. |
|
|
176
|
+
| C-CODEX-14 | §5.3 | `setModel` on Codex restores the user's prior `config.toml` default via compare-and-swap after the CLI persists its picker selection, skipping with the `codex_default_model_persisted` warning instead of clobbering concurrent edits. The restore is written atomically (sibling temp file + rename, mode preserved, a symlinked config written through to its target) so a crash mid-restore can never leave the user's config truncated. When no `config.toml` existed before the switch and the CLI created one, there is no snapshot to restore: the new file is left in place and the same warning code carries a message saying so (not a false "changed in other ways"). |
|
|
177
|
+
| C-CODEX-15 | §5.5 | Codex's directory-trust prompt is answered only under `autotrust` (blocking on the human when off); Codex hook trust — Elwood's own integration — is answered regardless of `autotrust` and is NOT classified as blocking. Each is recognized only by its HEADER wording on a non-option line (so an option-only phrase cannot spoof it) and, once recognized, answered from the frame's affirmative option — the agent is never left waiting. When a recognized prompt's affirmative option has not rendered yet, a fire-once transient `attention` activity is emitted and Elwood keeps watching so a later frame answers it; this render-delay state is TRANSIENT and no warning is emitted for it. Each is answered once, from the shared allowlist. |
|
|
178
|
+
| C-CODEX-16 | §5.1 | Codex `assistant_message`, `tool_call`, and `tool_result` activities are sourced only from the committed transcript, never re-projected from the `Stop`/`PreToolUse`/`PostToolUse` hook payloads; those hooks emit plain `hook` activity, so a single reply or tool step is surfaced exactly once (mirrors C-CLAUDE-15). |
|
|
179
|
+
| C-CODEX-17 | §5.4 §5.5 §5.7 | A Codex startup prompt Elwood auto-answers (directory/hook trust, `update` skip) is marked settled and emits its `startup_prompt` activity only after its PTY `sendInput` write fulfills. A rejected write emits NO `startup_prompt` activity, leaves the prompt un-settled so a later frame re-attempts it, and surfaces a bounded, content-free `startup_prompt_write_failed` warning carrying only the prompt label (mirrors C-CLAUDE-16). |
|
|
180
|
+
| C-CODEX-18 | §5.4 §7A.4 | A committed Codex `reasoning` transcript item surfaces its human-readable text on the `reasoning` activity's `text` field: the `text` of every `summary[]` entry of type `summary_text`, or — when the reasoning is un-summarized — every `content[]` entry of type `reasoning_text`, joined by newlines. The always-present `encrypted_content` blob is never readable and is never surfaced. When neither carries prose (the common case with reasoning summaries disabled), the activity carries no `text`, exactly as a bare reasoning marker. |
|
|
181
|
+
| C-CODEX-19 | §5.4 §7A.4 | A committed Codex shell/exec transcript item surfaces the ACTUAL command it ran as `tool_call` activity with its `toolInput`, and its output as `tool_result` activity with its `toolOutput`, across every representation the CLI emits — and the command is UNWRAPPED from the CLI's invocation machinery so consumers see the bare command, not the harness. The modern `exec` tool is a `custom_tool_call` whose `input` is a JavaScript snippet wrapping the call (e.g. `const r = await tools.exec_command({"cmd":"echo hi","workdir":…,"yield_time_ms":…}); text(r.output);`): Elwood extracts the inner `cmd`/`command` string (a `command` array is joined with spaces) and surfaces THAT as `toolInput`, discarding the JS wrapper and the non-command fields (`workdir`, `yield_time_ms`, `max_output_tokens`). A `function_call` (`shell`/`exec_command`) whose command is a JSON string in `arguments` is likewise unwrapped to its `command`/`cmd`. When the wrapper cannot be parsed, the raw `input`/`arguments` is surfaced unchanged rather than dropped. A `custom_tool_call_output` is classified as `tool_result` (not `other`), and a result's `output` — a plain string OR an array of `{ type: "input_text", text }` entries — is surfaced as `toolOutput` with the array entries' text joined; the `tool_result` `label` carries the `call_id` correlating it to its `tool_call`. So the real command and its output are surfaced as a clean paired call/result, never the JS harness and never a content-free `other` row. |
|
|
182
|
+
| C-CODEX-21 | §5.5 §5.6 | `reasoningEffort`, when supplied to `startCodex`/`resumeCodex`, is validated against the `CodexReasoningEffort` enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) before spawn — an out-of-enum value rejects with `codex_invalid_reasoning_effort` (message listing the valid values) and no process is spawned — and a valid value is forwarded as the reserved `-c model_reasoning_effort=<value>` override, applied AFTER caller `configOverrides` so it wins over a hand-rolled duplicate. It is independent of `model`, applies to the launched session only, and is not persisted across resume (a resume must re-supply it). |
|
|
183
|
+
| C-CODEX-20 | §5.4 | The Codex transcript reader is BOUNDED like Claude's: it reads in fixed-size chunks, discards any un-terminated record past a max-pending ceiling as an `"oversized"` drop, streams a large backlog across poll ticks under a per-scan chunk budget, and at PTY-exit drains only within a bounded chunk budget and wall-clock slice — accounting leftover bytes as an `"unread_backlog"` drop — so a hundreds-of-MiB transcript never OOMs or blocks the event loop. An unblocked `Stop` reads the committed turn with the per-pass scan budget, never the PTY-exit drain budget, so an arbitrarily long session (hundreds of turns) never exhausts that budget into false `unread_backlog` drops. Lost data is surfaced as the content-free, count-free `transcript_records_dropped` warning (a live event, cause-tagged, not persisted or counted) and a scan that throws is contained as `transcript_poll_stopped` (both `agent: "codex" | "claude"`). `finish()` is idempotent and terminal — a late `observe()` never restarts polling past `terminal:exit`. |
|
|
184
|
+
|
|
185
|
+
#### C-HOOK: Hook Bridge Coverage And Semantics (§6)
|
|
186
|
+
|
|
187
|
+
| ID | Section | Criterion |
|
|
188
|
+
|---|---:|---|
|
|
189
|
+
| C-HOOK-01 | §6.1 | Generated Claude settings include bridge handlers for every supported Claude hook event. |
|
|
190
|
+
| C-HOOK-02 | §6.2 | Hook bridge invocations route to the correct Elwood session using session-scoped identity. |
|
|
191
|
+
| C-HOOK-03 | §6.3 | A hook event with no registered parent handler returns no decision to Claude. |
|
|
192
|
+
| C-HOOK-04 | §6.3 | A timed-out hook handler returns no decision to Claude and emits `hookError`. |
|
|
193
|
+
| C-HOOK-05 | §6.3 | A throwing/rejecting hook handler returns no decision to Claude and emits `hookError`. |
|
|
194
|
+
| C-HOOK-06 | §6.3 | An invalid handler result returns no decision to Claude and emits `hookError`. |
|
|
195
|
+
| C-HOOK-07 | §6.4 | Hook IPC input is runtime-validated before dispatch to parent handlers. |
|
|
196
|
+
| C-HOOK-08 | §6.4 | `hook_event_name` narrows valid handler response types at compile time. |
|
|
197
|
+
| C-HOOK-09 | §6.4 | Known Claude built-in tool names narrow `tool_input` types at compile time. |
|
|
198
|
+
| C-HOOK-10 | §6.4 | Unknown or MCP tool names are represented through a safe extensibility path. |
|
|
199
|
+
| C-HOOK-11 | §6.5 | `Stop` marks the session ready only when the Stop event is not blocked. |
|
|
200
|
+
| C-HOOK-12 | §7A.1 | Generated Codex config includes bridge handlers for every supported Codex hook event. |
|
|
201
|
+
| C-HOOK-13 | §7A.2 | Codex hook IPC input is runtime-validated before dispatch to parent handlers. |
|
|
202
|
+
| C-HOOK-14 | §7A.2 | Codex `hook_event_name` narrows valid handler response types at compile time. |
|
|
203
|
+
| C-HOOK-15 | §7A.3 | Codex `Stop` marks the session ready only when the Stop event is not blocked. |
|
|
204
|
+
| C-HOOK-16 | §6.2 | Hook IPC waits for a complete framed request before dispatching and fails open on malformed complete requests. A single request is capped at exactly 8 MiB (8,388,608 bytes) measured on the ENCODED wire envelope (`{token, elwoodSessionId, input}` JSON plus its framing newline) — the identical byte count in the child bridge script (before it connects) and the parent server (as bytes arrive), so an escape-heavy input whose JSON encoding expands past the cap is rejected by both, never accepted by one and dropped by the other. An over-cap request fails open before auth/parse (child exits with no decision; server responds no-decision and closes) and does not emit a `hookError`. |
|
|
205
|
+
| C-HOOK-17 | §6.4 | Claude hook IPC input validates event-specific payload fields and known tool input schemas before dispatch. |
|
|
206
|
+
|
|
207
|
+
#### C-HRESP: Hook Response Mapping (§6, §7)
|
|
208
|
+
|
|
209
|
+
| ID | Section | Criterion |
|
|
210
|
+
|---|---:|---|
|
|
211
|
+
| C-HRESP-01 | §6.4 | `PreToolUse` handlers can return allow, deny, ask, defer, updated input, and additional context according to Claude's schema. |
|
|
212
|
+
| C-HRESP-02 | §7 | `AskUserQuestion` input is strongly typed and can be answered through `updatedInput.answers`. |
|
|
213
|
+
| C-HRESP-03 | §6.4 | `Stop` handlers can block stopping with a reason. |
|
|
214
|
+
| C-HRESP-04 | §6.4 | Observe-only events such as `Notification` cannot return blocking decisions through the public type API. |
|
|
215
|
+
| C-HRESP-05 | §6.4 | `Elicitation` and `ElicitationResult` support typed accept/decline/cancel response shapes. |
|
|
216
|
+
| C-HRESP-06 | §6.4 | Hook responses serialize to Claude-compatible JSON or no-output results. |
|
|
217
|
+
| C-HRESP-07 | §7A.2 | Codex `PreToolUse` handlers can deny, allow, allow with rewritten input, or add context according to Codex's schema. |
|
|
218
|
+
| C-HRESP-08 | §7A.2 | Codex `PermissionRequest` handlers can allow or deny and cannot return future-only invalid fields. |
|
|
219
|
+
| C-HRESP-09 | §7A.2 | Codex `Stop` and `SubagentStop` handlers can request continuation with a reason. |
|
|
220
|
+
| C-HRESP-10 | §7A.2 | Codex hook responses serialize to Codex-compatible JSON or no-output results. |
|
|
221
|
+
|
|
222
|
+
#### C-STATE: State And Resume (§8, §9)
|
|
223
|
+
|
|
224
|
+
| ID | Section | Criterion |
|
|
225
|
+
|---|---:|---|
|
|
226
|
+
| C-STATE-01 | §8.1 | Default state is written under `<cwd>/.elwood/`; planted state-directory and generated-file links are rejected before changing external contents or modes (§8.1/8.2). |
|
|
227
|
+
| C-STATE-02 | §8.1 | Passing `stateDir` stores state under the caller-provided directory. |
|
|
228
|
+
| C-STATE-03 | §8.2 | Session metadata is written atomically. |
|
|
229
|
+
| C-STATE-04 | §8.2 | Session metadata contains enough information to resume after parent process restart. |
|
|
230
|
+
| C-STATE-05 | §8.3 | Raw PTY input/output is not persisted by default. |
|
|
231
|
+
| C-STATE-06 | §8.3 | Hook payloads and responses are not persisted by default. |
|
|
232
|
+
| C-STATE-07 | §8.4 | `stop()` preserves session metadata and generated files. |
|
|
233
|
+
| C-STATE-08 | §8.4 | `teardown()` removes Elwood-owned session metadata and generated files. |
|
|
234
|
+
| C-STATE-09 | §8.4 | `teardown()` does not remove Claude-owned auth, transcripts, or user/project settings. |
|
|
235
|
+
| C-STATE-10 | §5.2, §5.6 | Resume APIs reject session records whose persisted adapter does not match the requested adapter. |
|
|
236
|
+
| C-STATE-11 | §8.1 | Elwood does not overwrite an existing `.elwood/.gitignore` or create gitignore files in custom `stateDir` directories. |
|
|
237
|
+
| C-STATE-13 | §8.2 | The session record persists the resolved launch posture (privilege and tool policy) at start, validates it on read, and resume updates it to the effective values. |
|
|
238
|
+
| C-STATE-12 | §8.1 | Sessions start successfully with arbitrarily long `stateDir` paths because the hook bridge socket binds in a short Elwood-owned temp home. The home is STABLE per session (a bounded fingerprint of the session's full identity — `stateDir`, adapter, and id), so every start/resume resolves the same one while a shared explicit id in a different state dir resolves a DISTINCT home; each launch binds a fresh socket FILE inside it, and teardown removes the whole home so no per-launch socket leaks across restart/resume. |
|
|
239
|
+
| C-LIFE-09 | §9.2 | `autoupdate` runs the adapter's update command at most once per parent process per adapter, so fleet spawns do not race N concurrent same-binary updates. Separate Elwood parent processes for the same macOS user and adapter also hold an atomic cross-process lease around that global update; contenders wait asynchronously, skip the duplicate update, invalidate their own caches, and validate the installed binary after the owner settles. The stable per-account lease records owner identity and generation, never evicts a live owner solely for age, serializes dead-owner recovery, and cannot be removed by an old owner after a successor claims it. A FAILED in-process shared update is shared once too — concurrent callers observe the same failure and proceed through the compatibility gate — and is not retried per start nor retained as a rejected result that poisons later starts. |
|
|
240
|
+
| C-LIFE-11 | §9.2 | Autoupdate is BEST-EFFORT: when the update command fails but the installed CLI still meets the minimum version, startup emits a live `agent_update_failed` warning (safe diagnostics only) and reaches `ready` from the installed binary; startup fails only when the installed version is below the minimum. Version and capability caches are invalidated even after a failed attempt because the installer may have partially changed the binary. No per-process cached probe (version read, capability detection, update) retains a rejected result — a later caller re-attempts rather than inheriting a prior failure. |
|
|
241
|
+
| C-ERR-08 | §10 | Startup failures carry the underlying cause, and errno/syscall/path details when the underlying error provides them. |
|
|
242
|
+
|
|
243
|
+
#### C-LIFE: Lifecycle Controls (§5, §9)
|
|
244
|
+
|
|
245
|
+
| ID | Section | Criterion |
|
|
246
|
+
|---|---:|---|
|
|
247
|
+
| C-LIFE-01 | §9.1 | Startup initializes state, generated settings, IPC, PTY, and Claude before resolving `startClaude`. |
|
|
248
|
+
| C-LIFE-02 | §5.3 | `stop()` attempts graceful termination and updates status. |
|
|
249
|
+
| C-LIFE-03 | §5.3 | `kill()` force-terminates the Claude process and updates status. |
|
|
250
|
+
| C-LIFE-04 | §9.3 | `resumeClaude` restores hook routing and PTY control for a saved Elwood session. |
|
|
251
|
+
| C-LIFE-05 | §5.3 | A live `ClaudeSessionApi` (the started session) owns exactly one main Claude process; the lazy `ClaudeSession` wrapper (§5.8) owns none before start and that one after. |
|
|
252
|
+
| C-LIFE-10 | §5.3 | `stop()`, `kill()`, and `teardown()` reap the full PTY process tree by SIGKILLing the leader's process group, so a descendant reparented to PID 1 after the leader exits (e.g. the CLI-spawned `hook-bridge.mjs`) is still killed and no process tree leaks across launch/quit cycles. |
|
|
253
|
+
| C-LIFE-11 | §5.3 | A `teardown()` that rejects in-flight control-queue operations still completes: the rejected `sendMessage`/`compact`/model operation does not skip the process-group reap or remaining teardown steps. |
|
|
254
|
+
|
|
255
|
+
#### C-ERR: Error Model (§10)
|
|
256
|
+
|
|
257
|
+
| ID | Section | Criterion |
|
|
258
|
+
|---|---:|---|
|
|
259
|
+
| C-ERR-01 | §10 | Public failures use stable typed error names. |
|
|
260
|
+
| C-ERR-02 | §10 | Unsupported platforms fail with `unsupported_platform`. |
|
|
261
|
+
| C-ERR-03 | §10 | Missing state for resume/teardown fails with `state_not_found`. |
|
|
262
|
+
| C-ERR-04 | §10 | Corrupt state fails with `state_corrupt`. |
|
|
263
|
+
| C-ERR-05 | §10 | PTY startup failure fails with `pty_start_failed`. |
|
|
264
|
+
| C-ERR-06 | §10 | Hook IPC initialization failure fails with `hook_bridge_failed`. |
|
|
265
|
+
| C-ERR-07 | §9.2 | Unparseable non-strict CLI versions produce a typed `version_unparseable` warning. |
|
|
266
|
+
|
|
267
|
+
#### C-APP: Local Test App (§11)
|
|
268
|
+
|
|
269
|
+
| ID | Section | Criterion |
|
|
270
|
+
|---|---:|---|
|
|
271
|
+
| C-APP-01 | §11 | The test app can start a Claude session for a selected cwd. |
|
|
272
|
+
| C-APP-02 | §11 | The test app can resume a saved Elwood session ID. |
|
|
273
|
+
| C-APP-03 | §11 | The test app renders live PTY output in a terminal view. |
|
|
274
|
+
| C-APP-04 | §11 | The test app can send single-line and multi-line prompts. |
|
|
275
|
+
| C-APP-05 | §11 | The test app shows a chronological live hook/event log. |
|
|
276
|
+
| C-APP-06 | §11 | The event log summarizes handler result semantics: no decision, allow, deny, block, context, or error. |
|
|
277
|
+
| C-APP-07 | §11 | The test app can resize the terminal and the Claude TUI responds. |
|
|
278
|
+
| C-APP-08 | §11 | The web dev app immediately force-kills itself and child processes on SIGINT, and cleans up owned resources and child processes on SIGTERM/SIGHUP/job-control signals without reading from stdin. |
|
|
279
|
+
| C-APP-09 | §11 | The web dev app emits structured debugger entries for hooks, activity, warnings, hook errors, lifecycle status, terminal exits, and runtime errors. |
|
|
280
|
+
| C-APP-10 | §11 | The web dev app renders filterable, visually delineated debugger rows with a formatted JSON detail inspector. |
|
|
281
|
+
| C-APP-11 | §11 | Runnable example package scripts execute PTY-owning TypeScript examples under a Node supervisor, remain invokable through `npm run`, and do not mirror raw wrapped-agent terminal output into the caller's shell. |
|
|
282
|
+
|
|
283
|
+
#### C-CLI: Headless Command-Line Interface (§12A)
|
|
284
|
+
|
|
285
|
+
| ID | Section | Criterion |
|
|
286
|
+
|---|---:|---|
|
|
287
|
+
| C-CLI-01 | §12A | Local, git, and packed-tarball installs expose an executable `elwood` bin and an importable library entry backed only by emitted JavaScript and declarations under `dist`, on Node.js 24 or newer. The published package declares NO install scripts of its own (`prepare`, `install`, `postinstall`): `dist` is built before publish and shipped in the tarball, so `npm install -g @with-logic/elwood` yields a working executable without building and without an install-script approval prompt for Elwood. |
|
|
288
|
+
| C-CLI-02 | §12A.1 | Direct and explicit `run` forms are equivalent; an argument-free invocation, top-level/config help, and version complete without stdin/config reads or agent launch; `elwood run` still requires input; and reserved command words are commands only in first-argument position before `--`. |
|
|
289
|
+
| C-CLI-03 | §12A.1 | New sessions default to the invocation cwd and, when nothing selects an agent, to the first available of Claude then Codex (C-CLI-21); explicit cwd overrides the workspace, an unavailable selected adapter never falls back, and resume uses its validated stored adapter and cwd exactly while rejecting `--cwd`. |
|
|
290
|
+
| C-CLI-04 | §12A.1 | Positional and piped input compose with one blank line, terminal stdin is not read, whitespace-only input fails before launch, UTF-8 input is incrementally limited to 8 MiB, and ordered image flags attach on the user turn. |
|
|
291
|
+
| C-CLI-05 | §12A.2 | A CLI turn preserves Elwood's real interactive environment and auto-authorizes only its documented trust classes; disabled or unrecognized trust automation fails as `blocked_prompt` without submitting into the dialog. |
|
|
292
|
+
| C-CLI-06 | §12A.2 | Built-in and selected per-agent permission, sandbox, approval, model, effort, and persona settings validate against the effective adapter before launch. |
|
|
293
|
+
| C-CLI-07 | §12A.2 | An optional duration bounds launch, setup, and turn; first and repeated `SIGINT` perform interrupt then force-kill behavior; timeout and interruption exit 124 and 130 respectively. |
|
|
294
|
+
| C-CLI-08 | §12A.2 §12A.5 | New Elwood state tears down unless kept, resumed state preserves unless ephemeral, exact resume never falls back, teardown does not undo workspace/agent-owned state, and persona is one output-discarded but side-effect-capable new-session setup turn that is rejected on resume. |
|
|
295
|
+
| C-CLI-09 | §12A.2 | Premature agent exit and cleanup failure retain collected response data; cleanup runs once, never replaces a primary failure, and changes otherwise-successful execution to status 1. |
|
|
296
|
+
| C-CLI-10 | §12A.3 | Default text output contains only the combined observed assistant messages with exact separator/newline semantics; text/JSON warnings surface concisely on stderr; verbose emits concise elapsed progress without assistant/thinking duplication; debug detail stays on stderr; and streamed text is emitted once with partial output retained on failure. |
|
|
297
|
+
| C-CLI-11 | §12A.3 | JSON emits one version-1 terminal document and JSONL emits ordered normalized records plus exactly one terminal record, including structured validation errors when explicitly selected; every JSONL record has `elapsedMs` and tool records carry `toolCallId` when available. An error record emitted before any adapter was selected reports `agent: null`; otherwise `agent` is the selected or running adapter. |
|
|
298
|
+
| C-CLI-12 | §12A.3 | Output excludes terminal and secret-bearing internals, honors backpressure, and treats downstream `EPIPE` as graceful consumer closure followed by cleanup. |
|
|
299
|
+
| C-CLI-13 | §12A.4 | Config path resolution follows explicit, absolute XDG, then home fallback order; config is strict version 1 with only documented keys, and project-local config is ignored. |
|
|
300
|
+
| C-CLI-14 | §12A.4 | Flag, environment, config, and built-in precedence is deterministic, environment booleans are strict, negative flags reverse inherited stream/verbose/high-trust values, `--no-defaults` bypasses Elwood run defaults, and config path/show/get/set/unset/effective never launches an agent (agent auto-detection only asks the login shell whether a command resolves). |
|
|
301
|
+
| C-CLI-15 | §12A.4 §12A.5 | Config and state reads and writes enforce private ownership, regular-file, symlink-safety, and atomicity constraints. |
|
|
302
|
+
| C-CLI-16 | §12A.5 | CLI state defaults to the absolute XDG state base or `~/.local/state/elwood`, never the workspace, and one session identity has at most one live owner. |
|
|
303
|
+
| C-CLI-17 | §12A.5 | Success, agent/cleanup failure, usage/config failure, timeout, and interruption map to statuses 0, 1, 2, 124, and 130 without cleanup masking a primary status. |
|
|
304
|
+
| C-CLI-18 | §12A.6 | `--head` fails before launch without terminal stdin/stderr and rejects stream/verbose/debug/JSONL; otherwise it mirrors ordered raw PTY bytes (including full-screen VT/ANSI control sequences) only to terminal stderr, uses and follows its size, keeps final text/JSON stdout clean, treats raw Ctrl-C like SIGINT, and restores terminal input/display modes before final output on every handled outcome. Outstanding mirror work is capped at 4 MiB or 1,024 frames; overflow fails the run after draining accepted bytes and restoring the terminal instead of growing memory without bound. A terminal-gone error from an input handle already closed by its host is contained because only that host can then restore the terminal, and it does not replace a completed result. |
|
|
305
|
+
| C-CLI-19 | §12A.4 | `config effective` prints validated effective launch/output values (including `head` and the full Claude tool posture) with config location/load state and per-setting provenance (including `auto-detected` for a probed agent), supports exact stored resume inspection using the same posture merge as launch, reads no prompt input, and starts no agent. |
|
|
306
|
+
| C-CLI-20 | §12A.5 | Text validation errors use user-facing language, identify relevant paths and inherited-setting sources, suggest an unambiguous nearby long option, and retain the stable structured error code. |
|
|
307
|
+
| C-CLI-21 | §12A.1 §12A.4 | When no flag, environment variable, or config key selects an agent, a new session (and `config effective`) probes the user's login shell for `claude` and `codex` concurrently and uses the first in that order whose command resolves, reporting source `auto-detected`; the probe never runs an agent, resume never probes, and when neither resolves the invocation fails before launch with code `no_agent_found` and status 2, naming both agents, an install hint, and the `--agent`/`ELWOOD_AGENT`/config selection paths. A probe that cannot run (missing or broken login shell, probe timeout) fails with the same code and status but names the shell and underlying error rather than claiming no agent is installed; adapter-option conflicts with an auto-detected agent say so and offer `--agent` for the other adapter. |
|
|
308
|
+
| C-CLI-22 | §12A.2 §12A.4 | `--high-trust` / `--no-high-trust`, `ELWOOD_HIGH_TRUST` (strict boolean), and the `highTrust` config key layer with normal precedence and expand, for the effective adapter, to Claude `bypassPermissions` or Codex `danger-full-access` + `never`; an effective flag/environment `true` combined with an explicit per-agent posture flag or variable is a usage error naming both sources on new and resumed runs; saved per-agent posture keys are overridden by high trust while a saved `highTrust` yields field by field to explicit per-agent flags/variables; and `config effective` reports `highTrust` plus posture values whose source is the deciding high-trust source. |
|
|
309
|
+
| C-CLI-23 | §12A.7 | `elwood resume <id> [options] [prompt...]` is a parse-time rewrite of `elwood run --resume <id>` sharing one resolution/execution path: stored adapter and workspace loading, `--cwd`/explicit-adapter/`--keep`/persona/`--ephemeral` rules, and positional-plus-piped prompt composition are identical; a missing id or an additional `--resume` is a usage error. |
|
|
310
|
+
| C-CLI-24 | §12A.8 | `elwood sessions` lists private, owner-matched session records under the effective state directory without launching an agent, reporting id, adapter, workspace, ISO-8601 `createdAt`/`lastUsedAt`, `resumable`, and socket-presence `live`, ordered by most recent use; unreadable records are skipped with a stderr warning, an empty or absent state directory is a status-0 empty listing (stderr notice in text, empty array in JSON), text is an aligned header table, JSON is one version-1 `sessions` document, and JSONL, prompt words, or other run options are usage errors. |
|
|
311
|
+
| C-CLI-25 | §12A.9 | `elwood interactive [id]` spawns the selected agent's own CLI in the foreground with inherited stdio, no PTY/bridge/automation/state, `SIGINT` passed through, and the agent's exit status; it fails with status 2 before spawning without terminal stdin and stdout or with `--output json|jsonl`, `--stream`, `--verbose`, `--debug`, `--head`, `--timeout`, `--persona`, `--image`, `--keep`, `--ephemeral`, `--resume`, or extra positionals; explicitly selected agent, model, effort, workspace, and posture map through the adapters' shared launch-argument builders while built-in posture defaults are omitted; and `[id]` loads the stored record with `--resume` semantics, merges stored posture field by field, and passes the agent's own conversation id or fails as `resume_unavailable`. |
|
|
312
|
+
| C-CLI-26 | §12A.10 | `elwood models` starts a fresh headless session with normal setting precedence, lists models through the public picker operation without changing the model, always tears the session and its state down, prints an aligned table (`*` current, `(default)`) or one version-1 `models` document of `AgentModelOption` rows, maps timeout/interruption/blocked-prompt/agent-exit/picker failures to the existing error conventions and statuses, and rejects stream, head, persona, image, keep, resume, ephemeral, positional, and JSONL inputs as usage errors. |
|
|
313
|
+
|
|
314
|
+
#### C-E2E: Real Adapter Flows (§12)
|
|
315
|
+
|
|
316
|
+
| ID | Section | Criterion |
|
|
317
|
+
|---|---:|---|
|
|
318
|
+
| C-E2E-01 | §12 | `npm run test:e2e` runs real public API session flows outside the default `npm run check` gate. |
|
|
319
|
+
| C-E2E-02 | §12 | Claude e2e coverage starts a real Claude PTY, observes terminal data, sends input/message text, observes hooks/activity, resizes, stops, resumes when possible, and tears down Elwood-owned state. |
|
|
320
|
+
| C-E2E-03 | §12 | Codex e2e coverage starts a real Codex PTY, observes terminal data, sends input/message text, observes hooks/activity/transcript where available, resizes, stops, resumes when possible, and tears down Elwood-owned state. |
|
|
321
|
+
| C-E2E-04 | §12 | Real adapter e2e tests skip only for local prerequisite failures such as a missing CLI; they do not replace adapter flows with fake PTYs, fake CLIs, or fake hook bridges. |
|
|
322
|
+
| C-E2E-05 | §12 | `npm run check:all` runs the default `npm run check` gate followed by `npm run test:e2e`, without changing the composition of `npm run check`. |
|
|
323
|
+
| C-E2E-06 | §4.5 | A real session started in a project with its own agent configuration loads that configuration additively: project-defined hooks fire alongside Elwood's bridge hooks and project instruction files are loaded by the agent. |
|
|
324
|
+
| C-E2E-07 | §5.4 | A real committed Claude turn surfaces an `assistant_message` with `source: "transcript"` and a `transcriptPath`, and no `assistant_message` is emitted with `source: "hook"` (C-CLAUDE-15). |
|
|
325
|
+
| C-E2E-08 | §5.4 | A real Claude tool turn surfaces `tool_call` and `tool_result` with `source: "transcript"`, correlated `toolUseId`s, and serialized input/output, and no `tool_call`/`tool_result` is emitted with `source: "hook"` (C-CLAUDE-15). |
|
|
326
|
+
| C-E2E-10 | §5.1 | A real committed Codex turn surfaces exactly one `assistant_message` (with `source: "transcript"`), never a second copy from the `Stop` hook (C-CODEX-16). |
|
|
327
|
+
| C-E2E-11 | §5.3 | A real Codex session with a queued initial persona delivers that message to Codex (a `UserPromptSubmit` with the persona text is observed) rather than swallowing it — the queue is released on `SessionStart`, not on the boot-time composer placeholder (C-API-28). |
|
|
328
|
+
| C-E2E-12 | §5.3 | A real Claude session attaches an image supplied via `sendMessage(message, { images })` — pasting the absolute path drives the CLI to show its `[Image #N]` chip in the rendered composer — verified against the installed CLI (C-API-45). |
|
|
329
|
+
| C-E2E-13 | §5.3 | A real Codex session on macOS attaches an image supplied via `sendMessage(message, { images })` — the clipboard-injection + Ctrl+V path drives the CLI to show its `[Image #N]` chip in the rendered composer, and the user's prior clipboard is restored afterward — verified against the installed CLI (C-API-46). |
|
|
330
|
+
| C-E2E-09 | §5.1 | The trust-prompt allowlist recognizes and answers the REAL folder-trust frame the installed Claude CLI renders in a fresh untrusted directory (header-anchored recognition + numbered or cursor affirmative selection), verifies the trust screen clears before readiness, and fails if a fully rendered trust frame is visible but unanswerable; it skips loudly only when no trust frame renders (for example, an already trusted directory). |
|
|
331
|
+
| C-E2E-14 | §5.8 | Against a REAL Claude (or Codex) CLI, a lazily-started `ClaudeSession`/`CodexSession` answers two sequential `send` calls: the first returns non-empty assistant text, and the second — referring back to the first — returns text consistent with retained conversation context, proving `send` collects a turn's assistant text and the ergonomic layer preserves multi-turn context (C-API-47, C-API-49). |
|
|
332
|
+
| C-E2E-15 | §5.8 | Against a REAL CLI, `stream(prompt)` for a task that DETERMINISTICALLY uses a workspace tool (reading a planted file) yields the turn's simplified typed events in arrival order — a `tool_call` FOLLOWED by its `tool_result`, and at least one `text` — and ends when the turn settles, verified against the installed CLI (C-API-48). |
|
|
333
|
+
| C-E2E-16 | §5.1 §5.5 | Against the REAL CLIs, a session started with `highTrust: true` reaches `ready` on both adapters: Claude's rendered composer shows its bypass-permissions footer with no acceptance dialog left visible (answered when the installed CLI renders one), and Codex's persisted launch posture is `danger-full-access` / `never` (C-API-54, C-CLAUDE-21). |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## 15. Open Implementation Notes
|
|
2
|
+
|
|
3
|
+
- The preferred PTY implementation is still to be selected. The public behavior
|
|
4
|
+
requires a real PTY; the library choice is not part of this PRD.
|
|
5
|
+
- The hook bridge should start with Unix domain sockets on macOS unless an
|
|
6
|
+
implementation spike shows a better local-only IPC path.
|
|
7
|
+
- A future PRD revision should define the adapter interface for non-Claude tools
|
|
8
|
+
after the Claude adapter proves the core model.
|
package/prd/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Elwood — Product Requirements Document
|
|
2
|
+
|
|
3
|
+
**A headless control layer for real interactive agentic coding CLIs.**
|
|
4
|
+
|
|
5
|
+
Status: Draft specification, ready for implementation planning
|
|
6
|
+
Platform: macOS for MVP
|
|
7
|
+
Primary surface: TypeScript library
|
|
8
|
+
First adapter: Claude Code CLI
|
|
9
|
+
Second adapter: OpenAI Codex CLI
|
|
10
|
+
|
|
11
|
+
This document specifies Elwood in enough detail that an engineer can implement
|
|
12
|
+
the first version without losing product intent. Anything observable by a
|
|
13
|
+
library consumer belongs here: public APIs, lifecycle behavior, hook semantics,
|
|
14
|
+
state layout, failure modes, and conformance criteria. Internal implementation
|
|
15
|
+
choices are left open where they do not change the public contract.
|
|
16
|
+
|
|
17
|
+
This specification is split across the files below. Each file keeps its
|
|
18
|
+
original section number, so a reference like §12A.7 or C-CLI-23 still
|
|
19
|
+
resolves exactly as it did when this was one document.
|
|
20
|
+
|
|
21
|
+
| Section | Topic | File |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| §1 | Overview | [01-overview.md](01-overview.md) |
|
|
24
|
+
| §2 | Goals and non-goals | [02-goals.md](02-goals.md) |
|
|
25
|
+
| §3 | Terminology | [03-terminology.md](03-terminology.md) |
|
|
26
|
+
| §4 | Product architecture | [04-architecture.md](04-architecture.md) |
|
|
27
|
+
| §5 | Public TypeScript API | [05-api/README.md](05-api/README.md) |
|
|
28
|
+
| §6 | Claude hook bridge | [06-claude-hook-bridge.md](06-claude-hook-bridge.md) |
|
|
29
|
+
| §7 | Claude tool and permission policy | [07-claude-policy.md](07-claude-policy.md) |
|
|
30
|
+
| §7A | Codex hook bridge and policy | [07a-codex-hook-bridge.md](07a-codex-hook-bridge.md) |
|
|
31
|
+
| §8 | State and persistence | [08-state.md](08-state.md) |
|
|
32
|
+
| §9 | Process lifecycle | [09-lifecycle.md](09-lifecycle.md) |
|
|
33
|
+
| §10 | Error model | [10-errors.md](10-errors.md) |
|
|
34
|
+
| §11 | Local test app | [11-test-app.md](11-test-app.md) |
|
|
35
|
+
| §12 | End-to-end testing | [12-e2e.md](12-e2e.md) |
|
|
36
|
+
| §12A | Headless command-line interface | [12a-cli.md](12a-cli.md) |
|
|
37
|
+
| §13 | Implementation latitude | [13-latitude.md](13-latitude.md) |
|
|
38
|
+
| §14 | Conformance | [14-conformance.md](14-conformance.md) |
|
|
39
|
+
| §15 | Open implementation notes | [15-open-notes.md](15-open-notes.md) |
|
|
40
|
+
|
|
41
|
+
## Conformance criteria
|
|
42
|
+
|
|
43
|
+
Every criterion ID (`C-API-*`, `C-CLI-*`, `C-CLAUDE-*`, `C-E2E-*`, and the
|
|
44
|
+
rest) is defined in [14-conformance.md](14-conformance.md). Tests name the
|
|
45
|
+
criterion they cover in their titles.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude hook handler dispatch: the shared fail-open dispatcher bound to the
|
|
3
|
-
* Claude result validator, plus block detection for `Stop`.
|
|
4
|
-
* Implements PRD §6.3 and §6.4.
|
|
5
|
-
*/
|
|
6
|
-
import type { ClaudeHookEvent, ClaudeHookResult } from "./hooks.ts";
|
|
7
|
-
export declare const requestHook: import("../core/hook-dispatch.ts").HookDispatcher<ClaudeHookEvent, ClaudeHookResult>;
|
|
8
|
-
export declare function isBlock(result: ClaudeHookResult): boolean;
|
|
9
|
-
//# sourceMappingURL=hook-dispatch.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hook-dispatch.d.ts","sourceRoot":"","sources":["../../src/claude/hook-dispatch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGpE,eAAO,MAAM,WAAW,sFAGvB,CAAC;AAEF,wBAAgB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAEzD"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude hook handler dispatch: the shared fail-open dispatcher bound to the
|
|
3
|
-
* Claude result validator, plus block detection for `Stop`.
|
|
4
|
-
* Implements PRD §6.3 and §6.4.
|
|
5
|
-
*/
|
|
6
|
-
import { createHookDispatcher } from "../core/hook-dispatch.js";
|
|
7
|
-
import { isClaudeHookResult } from "./validate-result.js";
|
|
8
|
-
export const requestHook = createHookDispatcher("claude", isClaudeHookResult);
|
|
9
|
-
export function isBlock(result) {
|
|
10
|
-
return Boolean(result && "decision" in result && result.decision === "block");
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=hook-dispatch.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hook-dispatch.js","sourceRoot":"","sources":["../../src/claude/hook-dispatch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAC7C,QAAQ,EACR,kBAAkB,CACnB,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,MAAwB;IAC9C,OAAO,OAAO,CAAC,MAAM,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Less central Claude hook event payload fragments.
|
|
3
|
-
* Implements PRD §6.
|
|
4
|
-
*/
|
|
5
|
-
import type { ClaudeStopFailureError, ClaudeStopFields, ClaudeTaskFields } from "./hook-events.ts";
|
|
6
|
-
import type { ClaudeCommonHookFields } from "./hook-names.ts";
|
|
7
|
-
export type TaskCreatedEvent = ClaudeCommonHookFields & ClaudeTaskFields & {
|
|
8
|
-
readonly hook_event_name: "TaskCreated";
|
|
9
|
-
};
|
|
10
|
-
export type TaskCompletedEvent = ClaudeCommonHookFields & ClaudeTaskFields & {
|
|
11
|
-
readonly hook_event_name: "TaskCompleted";
|
|
12
|
-
};
|
|
13
|
-
export type StopEvent = ClaudeCommonHookFields & ClaudeStopFields & {
|
|
14
|
-
readonly hook_event_name: "Stop";
|
|
15
|
-
};
|
|
16
|
-
export type StopFailureEvent = ClaudeCommonHookFields & {
|
|
17
|
-
readonly hook_event_name: "StopFailure";
|
|
18
|
-
readonly error: ClaudeStopFailureError | string;
|
|
19
|
-
readonly error_details?: string;
|
|
20
|
-
readonly last_assistant_message?: string;
|
|
21
|
-
};
|
|
22
|
-
export type TeammateIdleEvent = ClaudeCommonHookFields & {
|
|
23
|
-
readonly hook_event_name: "TeammateIdle";
|
|
24
|
-
readonly teammate_name: string;
|
|
25
|
-
readonly team_name: string;
|
|
26
|
-
};
|
|
27
|
-
export type ConfigChangeEvent = ClaudeCommonHookFields & {
|
|
28
|
-
readonly hook_event_name: "ConfigChange";
|
|
29
|
-
readonly source: "user_settings" | "project_settings" | "local_settings" | "policy_settings" | "skills" | string;
|
|
30
|
-
readonly file_path?: string;
|
|
31
|
-
};
|
|
32
|
-
export type CwdChangedEvent = ClaudeCommonHookFields & {
|
|
33
|
-
readonly hook_event_name: "CwdChanged";
|
|
34
|
-
readonly old_cwd: string;
|
|
35
|
-
readonly new_cwd: string;
|
|
36
|
-
};
|
|
37
|
-
export type FileChangedEvent = ClaudeCommonHookFields & {
|
|
38
|
-
readonly hook_event_name: "FileChanged";
|
|
39
|
-
readonly file_path: string;
|
|
40
|
-
readonly event: "change" | "add" | "unlink";
|
|
41
|
-
};
|
|
42
|
-
export type WorktreeCreateEvent = ClaudeCommonHookFields & {
|
|
43
|
-
readonly hook_event_name: "WorktreeCreate";
|
|
44
|
-
readonly name: string;
|
|
45
|
-
};
|
|
46
|
-
export type WorktreeRemoveEvent = ClaudeCommonHookFields & {
|
|
47
|
-
readonly hook_event_name: "WorktreeRemove";
|
|
48
|
-
readonly worktree_path: string;
|
|
49
|
-
};
|
|
50
|
-
export type PreCompactEvent = ClaudeCommonHookFields & {
|
|
51
|
-
readonly hook_event_name: "PreCompact";
|
|
52
|
-
readonly trigger: "manual" | "auto";
|
|
53
|
-
readonly custom_instructions?: string | null;
|
|
54
|
-
};
|
|
55
|
-
export type PostCompactEvent = ClaudeCommonHookFields & {
|
|
56
|
-
readonly hook_event_name: "PostCompact";
|
|
57
|
-
readonly trigger: "manual" | "auto";
|
|
58
|
-
readonly compact_summary: string;
|
|
59
|
-
};
|
|
60
|
-
export type SessionEndEvent = ClaudeCommonHookFields & {
|
|
61
|
-
readonly hook_event_name: "SessionEnd";
|
|
62
|
-
readonly reason: "clear" | "resume" | "logout" | "prompt_input_exit" | "bypass_permissions_disabled" | "other" | string;
|
|
63
|
-
};
|
|
64
|
-
export type ElicitationEvent = ClaudeCommonHookFields & {
|
|
65
|
-
readonly hook_event_name: "Elicitation";
|
|
66
|
-
readonly mcp_server_name: string;
|
|
67
|
-
readonly message: string;
|
|
68
|
-
readonly mode?: "form" | "url" | string;
|
|
69
|
-
readonly url?: string;
|
|
70
|
-
readonly elicitation_id?: string;
|
|
71
|
-
readonly requested_schema?: Readonly<Record<string, unknown>>;
|
|
72
|
-
};
|
|
73
|
-
export type ElicitationResultEvent = ClaudeCommonHookFields & {
|
|
74
|
-
readonly hook_event_name: "ElicitationResult";
|
|
75
|
-
readonly mcp_server_name: string;
|
|
76
|
-
readonly action: string;
|
|
77
|
-
readonly mode?: "form" | "url" | string;
|
|
78
|
-
readonly elicitation_id?: string;
|
|
79
|
-
readonly content?: Readonly<Record<string, unknown>>;
|
|
80
|
-
};
|
|
81
|
-
//# sourceMappingURL=hook-events-extra.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hook-events-extra.d.ts","sourceRoot":"","sources":["../../src/claude/hook-events-extra.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACnG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GACnD,gBAAgB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GACrD,gBAAgB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,SAAS,GAAG,sBAAsB,GAC5C,gBAAgB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG;IACtD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAAC;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,GAAG;IACvD,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,GAAG;IACvD,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,MAAM,EACX,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,QAAQ,GACR,MAAM,CAAC;IACX,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG;IACrD,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG;IACtD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG;IACzD,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG;IACzD,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG;IACrD,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAEpC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG;IACtD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG;IACrD,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC;IACvC,QAAQ,CAAC,MAAM,EACX,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,mBAAmB,GACnB,6BAA6B,GAC7B,OAAO,GACP,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG;IACtD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC/D,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG;IAC5D,QAAQ,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD,CAAC"}
|