Skip to content

feat: phase 3 — HITL permissions (sub-project 4.5)#171

Merged
blove merged 16 commits into
claude/phase3-workspacefrom
claude/phase3-permissions
May 21, 2026
Merged

feat: phase 3 — HITL permissions (sub-project 4.5)#171
blove merged 16 commits into
claude/phase3-workspacefrom
claude/phase3-permissions

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 21, 2026

Summary

Sub-project 4.5 of the Dawn opinionated agent harness, building on PR #170 (workspace capability). Replaces the hard-refuse-on-path-jail-escape behavior with a human-in-the-loop interrupt prompt, and adds the same prompt-for-approval gating to runBash. Three operating modes (interactive / non-interactive / bypass) configurable in dawn.config.ts or via DAWN_PERMISSIONS_MODE env var. Persisted "always" decisions live in .dawn/permissions.json (project-local, auto-gitignored).

Spec: docs/superpowers/specs/2026-05-21-phase3-permissions-design.md
Plan: docs/superpowers/plans/2026-05-21-phase3-permissions.md

This PR is stacked on PR #170 (claude/phase3-workspace). Once that merges, change base to main and rebase.

Changes

  • New @dawn-ai/permissions package (30 unit tests): public types, prefix-matching engine, smart-default pattern inference (first 2 tokens for commands, parent dir for paths), PermissionsStore with write queue + auto-gitignore.
  • Workspace capability gates through PermissionsStore. Path tools silent for paths inside the workspace; gate when outside. runBash gates every command. Three modes encode the deployment shapes; bypass disables the path-jail and warns loudly on capability load.
  • DawnConfig + CapabilityMarkerContext extend with optional permissions field of the same shape as .dawn/permissions.json (config + runtime merge for effective permissions).
  • MemorySaver checkpointer + thread_id propagation in agent-adapter enable LangGraph's Command({resume}) to replay parked state.
  • Resume endpoint POST /threads/:thread_id/resume in the dev server. Validates interrupt_id, resolves the pending Promise, parked graph resumes via a fresh Command({resume}) invocation that yields continuation events into the same SSE response.
  • Interrupt detection in agent-adapter handles LangGraph 1.x's actual emission path: a GraphInterrupt thrown from inside a tool surfaces as on_tool_error with the interrupts JSON-stringified in event.data.error. Defensive fallback also reads __interrupt__ on on_chain_end if a future LangGraph version changes the path.
  • Chat demo seeds allow.bash: ["ls","pwd","cat","echo","head","tail","wc"] and deny.bash: ["rm -rf","sudo","chmod 777"]. Web client renders an inline permission panel with three buttons; POSTs the decision via /api/permission-resume proxy.

Test plan

  • Unit tests, 524 total: @dawn-ai/permissions (30 across pattern-matching, suggested-pattern, store), workspace capability gating (5+ new tests covering all three modes), agent-adapter interrupt propagation (5 covering stringified error, live error, fallback, threadId variants), resume endpoint (4)
  • Manual SSE smoke (end-to-end): prompt agent to git statusevent: interrupt fires with {interruptId, kind: "command", suggestedPattern: "git status"} → POST resume with decision: "once" returns {ok: true} → stream continues with tool_call/tool_result/chunks → final event: done
  • Full repo green: 524 tests / 75 files, build + lint clean

Sharp edge resolved

The plan flagged the interrupt-to-resume bridging as the highest-uncertainty piece. Empirically:

  • LangGraph 1.x emits interrupts as on_tool_error events (not on_interrupt)
  • The error message is a JSON-stringified interrupts array followed by a stack trace
  • Resume requires a MemorySaver checkpointer (added) + stable thread_id (propagated through stream pipeline)
  • Resume invocation: graph.streamEvents(new Command({resume}), {configurable: {thread_id}}) replays from the parked state

The detection code defensively handles three error shapes (live GraphInterrupt object, stringified message, bare string) so future LangGraph version drift fails open rather than crashing.

Deferred / known limitations

  • Persistent "deny always" entries — schema accommodates them but no UI yet. Today's deny is per-call. Sub-project 4.6 territory.
  • Generalized capability-driven gating — any tool can opt into the permission flow. Builds on this PR's infrastructure. Sub-project 4.6.
  • Two-tier config (project + user-global) — single project-local file is sufficient until someone asks.
  • Race on rapid auto-clickers — if a client POSTs resume in the same event-loop tick as the SSE emits the interrupt event, the resume can arrive before setPending fires (server returns "Stale interrupt_id"). Real human reaction time avoids this; a ~200ms client-side delay (or retry-on-stale) would make automated tests robust. Not a real-world UX concern.
  • Single-process resume onlyMemorySaver is in-memory. Multi-process / horizontal scaling needs SQLite/Postgres saver. Sub-project 7 (Agent Protocol) territory.

🤖 Generated with Claude Code

blove and others added 16 commits May 21, 2026 12:06
Designs the human-in-the-loop permission system that builds on sub-
project 4's workspace capability. Path-jail escapes and every first-
occurrence bash command trigger an interrupt prompt with three
approval scopes (Once / Always-for-pattern / Deny). Persisted "always"
decisions live in .dawn/permissions.json (project-local, gitignored)
using a tool-keyed flat-string format that's forward-compatible with
future tool categories.

Three operating modes: interactive (dev default), non-interactive
(production / CI), bypass (explicit "operator knows what they're
doing"). dawn.config.ts gains a permissions field with mode, allow,
deny — same shape as the runtime file. DAWN_PERMISSIONS_MODE env var
overrides config for the session.

SSE envelope shape is Agent-Protocol-compatible so sub-project 7 can
implement the spec on top of this without refactoring.

New @dawn-ai/permissions package ships types + pattern-matching engine
+ store. Workspace capability gains a permission check between the
path-jail / bash invocation and the actual backend call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bite-sized, TDD-structured plan for sub-project 4.5. 13 tasks across
five phases: @dawn-ai/permissions package (T1-T5), config + capability
changes (T6-T7), runtime interrupt + resume (T8-T9), chat demo updates
(T10-T11), smoke + PR (T12-T13).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the package skeleton for the upcoming HITL permissions system.
No exports yet — types, pattern matching, and store land in subsequent
commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nore handling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Type-only edge to @dawn-ai/permissions. Workspace capability will read
context.permissions in a subsequent commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each tool's run() consults the optional PermissionsStore in the
capability context:

- Path tools (readFile/writeFile/listDir): silent for paths inside the
  workspace; consult the store for paths outside.
- runBash: gate every command regardless of path.

Three modes:
- interactive: unknown ops emit LangGraph interrupt() and pause the run
- non-interactive: unknown ops hard-refuse (fail-closed)
- bypass: all ops proceed (path-jail disabled), warn on capability load

The old pathJail() helper is removed — the gate now handles
out-of-workspace cases via the permission flow.

Also packs @dawn-ai/permissions in the CLI typegen install test so the
external install can resolve core's new runtime dep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
LangGraph 1.x's `interrupt()` does not emit a dedicated streamEvents v2
event; the parked state surfaces as `__interrupt__: [{value, ...}]` in
the graph's final `on_chain_end` output. Detect this and yield a
{type: "interrupt", data: payload} chunk so the SSE consumer (and the
soon-to-arrive resume endpoint) sees the workspace capability's
PermissionRequest envelope verbatim.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds POST /threads/:thread_id/resume to the dev runtime server, backed
by a module-level pending-interrupts map keyed by thread_id. The
endpoint validates interrupt_id (409 on stale, 400 on missing/invalid
decision) and invokes the registered resolve() callback before
clearing the entry.

execute-route.ts now loads the permissions config from dawn.config.ts,
honors the DAWN_PERMISSIONS_MODE env override, constructs a
PermissionsStore via createPermissionsStore, and threads it into
applyCapabilities so the workspace capability's gates have a store to
consult. streamResolvedRoute also bridges {type: "interrupt"} chunks
from the agent-adapter into the pending map when called with a
threadId.

Approach: two-call via checkpointer was the only viable option (the
in-process Deferred pattern would require the parked tool call to
yield back into Node's microtask queue, which LangGraph's
GraphInterrupt unwinds). However, Dawn does not yet wire a
LangGraph MemorySaver or propagate thread_id into createReactAgent —
that plumbing arrives with the Agent Protocol work in sub-project 7.
Until then resolve() is a no-op stub: the endpoint accepts decisions
and clears the entry, but cannot actually replay the parked graph.
The smoke test (T12) will exercise the loop end-to-end once
checkpointer support lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…chanism

The original T8/T9 implementation propagated interrupts to the SSE stream
and registered them in pendingByThread, but the resume callback was a
no-op stub because LangGraph 1.x requires a MemorySaver checkpointer +
a stable thread_id to actually replay from the parked state.

This commit:

- Wires a process-level MemorySaver into createReactAgent.
- Propagates thread_id from the request body through streamResolvedRoute
  to streamAgent to config.configurable.thread_id.
- When agent-adapter detects an interrupt, it registers a resolve
  callback in pendingByThread and awaits the user's decision.
- On resume, the adapter re-invokes the graph with new Command({resume})
  and yields the resulting events into the same SSE stream.
- Moves pending-interrupts.ts to @dawn-ai/langchain so the adapter
  imports the same map the resume endpoint writes to.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Detects 'event: interrupt' frames in the SSE stream; renders an inline
panel with Once / Always-for-pattern / Deny buttons; POSTs the decision
through /api/permission-resume to the Dawn server's resume endpoint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…runs/stream

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
LangGraph 1.x surfaces a tool-invoked interrupt() via streamEvents v2 as an
on_tool_error whose data.error is a stringified GraphInterrupt — the leading
JSON array is the interrupts list. The top-level LangGraph on_chain_end does
not carry __interrupt__ on this path. Parse the error string in on_tool_error
to surface the interrupt SSE event; keep __interrupt__-on-chain-end and live
GraphInterrupt object detection as defensive fallbacks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dawnai Building Building Preview, Comment May 21, 2026 8:25pm

Request Review

@blove blove merged commit e66860f into claude/phase3-workspace May 21, 2026
1 of 4 checks passed
@blove blove deleted the claude/phase3-permissions branch May 21, 2026 20:25
blove added a commit that referenced this pull request May 21, 2026
…t 4) (#170)

* docs: phase 3 workspace capability + pluggable backends design spec

Design for sub-project 4 of the Dawn opinionated agent harness. The
workspace tools (readFile, writeFile, listDir, runBash) become a built-
in capability auto-wired by the convention of having a workspace/
directory under a route. Filesystem and exec implementations become
pluggable via a new @dawn-ai/workspace package shipping the type
interfaces, localFilesystem/localExec defaults, a compose() helper,
and one demonstration middleware (withLogging).

dawn.config.ts switches from the existing hand-rolled string-only
parser to tsx-evaluated import so callable backend values can be
expressed naturally. Default behavior is unchanged: apps that don't
touch dawn.config.ts keep working.

Path-jail enforcement lives in the capability; backends receive
already-resolved absolute paths. Human-in-the-loop permission gating
(interrupt to ask the user about jail escapes) is deferred to a
separate sub-project (4.5) with its own brainstorm + spec + plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: implementation plan for phase 3 workspace + backends

Bite-sized, TDD-structured plan covering: @dawn-ai/workspace package
(types, localFilesystem, localExec, compose, withLogging), the
createWorkspaceMarker capability, dawn.config.ts loader switch from
hand-rolled parser to tsx import, tool-name uniqueness check inversion
for overridable tools, runtime wiring, typegen, chat example migration,
and the smoke + PR steps. 15 tasks; each commits independently.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* scaffold(workspace): empty @dawn-ai/workspace package

Adds the package skeleton (manifest, tsconfig, vitest config) for the
upcoming pluggable workspace backends. No exports yet — types, defaults,
and helpers land in subsequent commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(workspace): type interfaces for filesystem + exec backends

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(workspace): localFilesystem default backend

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(workspace): localExec default backend

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(workspace): compose() middleware helper

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(workspace): withFilesystemLogging + withExecLogging middlewares

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(core): switch dawn.config.ts loader from hand-rolled parser to tsx import

The hand-rolled parser supported only string-literal property values and
const string bindings. The upcoming workspace capability needs to express
callable backend values in dawn.config.ts, which strings can't express.
Switch to a tsx-evaluated dynamic import (same loader Dawn already uses
for route discovery and tool execution).

Existing dawn.config.ts files (just { appDir }) remain valid TS modules
and continue to load without modification.

Side-effects of the loader swap:
- Two CLI integration tests assumed the old parser's specific error
  message or its fresh-read-from-disk behavior. The verify test's
  expected error string is updated to match the runtime ReferenceError
  that the tsx import now surfaces, and the dev test that mutated
  dawn.config.ts mid-session is rewritten to start the dev process with
  the invalid config in place (Node's ESM cache prevents a re-import of
  the same module URL within one process — mid-session config edits
  will become a per-task concern as backends land).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(core): add backends field to DawnConfig + CapabilityMarkerContext

Type-only edge: @dawn-ai/core now imports FilesystemBackend/ExecBackend
types from @dawn-ai/workspace via 'import type'. No runtime weight yet
(workspace stays in devDependencies until the marker lands).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(core): createWorkspaceMarker capability

Auto-detects a route's workspace/ directory and contributes four tools
(readFile/writeFile/listDir/runBash) routed through configurable
backends. Defaults to localFilesystem + localExec when no backends are
configured in dawn.config.ts. Path-jail enforced in the capability;
backends receive resolved absolute paths.

Tools carry an `overridable: true` flag so a future uniqueness-check
inversion can let user-authored tools/<name>.ts files supersede them.

Promotes @dawn-ai/workspace to a runtime dependency of @dawn-ai/core,
and extends the cli typegen harness to pack @dawn-ai/workspace
alongside cli/core/langchain/langgraph/sdk so externally installed
dawn bin tests resolve the new transitive dep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cli): support overridable capability tools

Tools marked overridable on a capability contribution can be shadowed
by a user-authored tool with the same name. Used by the workspace
capability so authors can override readFile/writeFile/listDir/runBash
by dropping a file in tools/. Non-overridable capability tools
(writeTodos, readSkill, task) retain the collision error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: biome auto-fixes (import order) on workspace marker + tests

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cli): register workspace capability + thread backends from dawn.config

Registers createWorkspaceMarker in the capability registry. Loads
dawn.config.ts at the start of prepareRouteExecution and threads
config.backends into the CapabilityMarkerContext so the workspace
marker uses the configured backends (defaulting to localFilesystem +
localExec when none are configured).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cli): typegen surfaces workspace tools for routes with workspace/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(examples/chat): migrate to workspace capability

Delete the hand-rolled readFile/writeFile/listDir/runBash tool files
(and their workspace-path helpers) from both the /chat route and the
research subagent. The workspace capability auto-contributes these
tools when the route has a workspace/ directory, so add empty
workspace/ dirs (with .gitkeep) under both routes to opt in.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(core,cli): workspace capability uses cwd-relative root, matching agents-md

T13's migration of the chat example surfaced a mismatch: the workspace
capability was resolving to <routeDir>/workspace/ while the agents-md
capability (and the prior hand-rolled tools) used
<process.cwd()>/workspace/. Result: post-migration, the chat agent's
memory file and its workspace tools pointed at completely different
directories.

Align the workspace capability with the existing convention:
process.cwd()/workspace/. Same trigger as agents-md; same root as the
deleted hand-rolled tools. The chat example's pre-existing
examples/chat/server/workspace/ directory (with AGENTS.md) now serves
as the workspace for both /chat and the research subagent.

Removes the empty per-route workspace/ stubs T13 created.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(examples/chat): update prompt + README for workspace capability

- system-prompt: runBash signature is { command } now (no timeoutSeconds);
  returns { stdout, stderr, exitCode } instead of a formatted string
- README: status reflects shipped subagents + workspace capabilities;
  layout shows current file structure (no tools/, no workspace-path.ts);
  deferred list updated to flag HITL permission gating (sub-project 4.5)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: phase 3 — HITL permissions (sub-project 4.5) (#171)

* docs: phase 3 HITL permissions design spec (sub-project 4.5)

Designs the human-in-the-loop permission system that builds on sub-
project 4's workspace capability. Path-jail escapes and every first-
occurrence bash command trigger an interrupt prompt with three
approval scopes (Once / Always-for-pattern / Deny). Persisted "always"
decisions live in .dawn/permissions.json (project-local, gitignored)
using a tool-keyed flat-string format that's forward-compatible with
future tool categories.

Three operating modes: interactive (dev default), non-interactive
(production / CI), bypass (explicit "operator knows what they're
doing"). dawn.config.ts gains a permissions field with mode, allow,
deny — same shape as the runtime file. DAWN_PERMISSIONS_MODE env var
overrides config for the session.

SSE envelope shape is Agent-Protocol-compatible so sub-project 7 can
implement the spec on top of this without refactoring.

New @dawn-ai/permissions package ships types + pattern-matching engine
+ store. Workspace capability gains a permission check between the
path-jail / bash invocation and the actual backend call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: implementation plan for phase 3 HITL permissions

Bite-sized, TDD-structured plan for sub-project 4.5. 13 tasks across
five phases: @dawn-ai/permissions package (T1-T5), config + capability
changes (T6-T7), runtime interrupt + resume (T8-T9), chat demo updates
(T10-T11), smoke + PR (T12-T13).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* scaffold(permissions): empty @dawn-ai/permissions package

Adds the package skeleton for the upcoming HITL permissions system.
No exports yet — types, pattern matching, and store land in subsequent
commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(permissions): public types

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(permissions): suggested-pattern helpers

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(permissions): pattern-matching engine

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(permissions): PermissionsStore with file I/O, write queue, gitignore handling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(core): extend DawnConfig + CapabilityMarkerContext with permissions

Type-only edge to @dawn-ai/permissions. Workspace capability will read
context.permissions in a subsequent commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(core): workspace capability gates through PermissionsStore

Each tool's run() consults the optional PermissionsStore in the
capability context:

- Path tools (readFile/writeFile/listDir): silent for paths inside the
  workspace; consult the store for paths outside.
- runBash: gate every command regardless of path.

Three modes:
- interactive: unknown ops emit LangGraph interrupt() and pause the run
- non-interactive: unknown ops hard-refuse (fail-closed)
- bypass: all ops proceed (path-jail disabled), warn on capability load

The old pathJail() helper is removed — the gate now handles
out-of-workspace cases via the permission flow.

Also packs @dawn-ai/permissions in the CLI typegen install test so the
external install can resolve core's new runtime dep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(langchain): propagate LangGraph interrupt events to the SSE stream

LangGraph 1.x's `interrupt()` does not emit a dedicated streamEvents v2
event; the parked state surfaces as `__interrupt__: [{value, ...}]` in
the graph's final `on_chain_end` output. Detect this and yield a
{type: "interrupt", data: payload} chunk so the SSE consumer (and the
soon-to-arrive resume endpoint) sees the workspace capability's
PermissionRequest envelope verbatim.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cli): resume endpoint + PermissionsStore wiring

Adds POST /threads/:thread_id/resume to the dev runtime server, backed
by a module-level pending-interrupts map keyed by thread_id. The
endpoint validates interrupt_id (409 on stale, 400 on missing/invalid
decision) and invokes the registered resolve() callback before
clearing the entry.

execute-route.ts now loads the permissions config from dawn.config.ts,
honors the DAWN_PERMISSIONS_MODE env override, constructs a
PermissionsStore via createPermissionsStore, and threads it into
applyCapabilities so the workspace capability's gates have a store to
consult. streamResolvedRoute also bridges {type: "interrupt"} chunks
from the agent-adapter into the pending map when called with a
threadId.

Approach: two-call via checkpointer was the only viable option (the
in-process Deferred pattern would require the parked tool call to
yield back into Node's microtask queue, which LangGraph's
GraphInterrupt unwinds). However, Dawn does not yet wire a
LangGraph MemorySaver or propagate thread_id into createReactAgent —
that plumbing arrives with the Agent Protocol work in sub-project 7.
Until then resolve() is a no-op stub: the endpoint accepts decisions
and clears the entry, but cannot actually replay the parked graph.
The smoke test (T12) will exercise the loop end-to-end once
checkpointer support lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(langchain,cli): wire checkpointer + thread_id; complete resume mechanism

The original T8/T9 implementation propagated interrupts to the SSE stream
and registered them in pendingByThread, but the resume callback was a
no-op stub because LangGraph 1.x requires a MemorySaver checkpointer +
a stable thread_id to actually replay from the parked state.

This commit:

- Wires a process-level MemorySaver into createReactAgent.
- Propagates thread_id from the request body through streamResolvedRoute
  to streamAgent to config.configurable.thread_id.
- When agent-adapter detects an interrupt, it registers a resolve
  callback in pendingByThread and awaits the user's decision.
- On resume, the adapter re-invokes the graph with new Command({resume})
  and yields the resulting events into the same SSE stream.
- Moves pending-interrupts.ts to @dawn-ai/langchain so the adapter
  imports the same map the resume endpoint writes to.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(examples/chat): seed permissions allow/deny in dawn.config.ts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(examples/chat-web): inline permission panel + resume proxy

Detects 'event: interrupt' frames in the SSE stream; renders an inline
panel with Once / Always-for-pattern / Deny buttons; POSTs the decision
through /api/permission-resume to the Dawn server's resume endpoint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(langchain): bind streamEvents to its Pregel instance to restore /runs/stream

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(langchain): detect LangGraph interrupts at correct event/data path

LangGraph 1.x surfaces a tool-invoked interrupt() via streamEvents v2 as an
on_tool_error whose data.error is a stringified GraphInterrupt — the leading
JSON array is the interrupts list. The top-level LangGraph on_chain_end does
not carry __interrupt__ on this path. Parse the error string in on_tool_error
to surface the interrupt SSE event; keep __interrupt__-on-chain-end and live
GraphInterrupt object detection as defensive fallbacks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* style(langchain): rename escape→escaped + biome auto-format

CI lint failed on two issues:
- Shadow of global `escape` in the interrupt-error JSON parser
- Line-length formatting in a test helper signature

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(harness): pack @dawn-ai/workspace + @dawn-ai/permissions in framework verify

Adds the two new workspace packages (introduced in sub-projects 4 and
4.5) to the framework-verification harness's pack list, override maps,
and fixture snapshots so the generated-app contract tests can install
them locally instead of trying the npm registry (404).

Also extends create-dawn-ai-app's internal-mode overrides to point
@dawn-ai/permissions and @dawn-ai/workspace at the in-repo packages so
the contributor-local lifecycle resolves transitive workspace:* deps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(harness): pack workspace + permissions in runtime contract verify

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(harness): pack workspace + permissions in runtime smoke verify

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant