Conversation
…ntime Phase 0-6 complete: - Core: pure scheduling logic (graph, layering, transitions, replan, required-validator) - Core: CQRS read-model (store, projector, sql tables, migration, probe) - Schema: 15 EventV2 definitions (dag-event.ts) + durable-event-manifest registration - Opencode: Dag.Service + layer + .node (LayerNode), EventV2Bridge-backed - Opencode: runtime (scheduling, spawn, eval, recovery, worktree-manager, templates) - Opencode: workflow tool (start/extend/control) - HTTP API: DagApi group (6 endpoints) + dagHandlers + server.ts wiring - TUI: sidebar DAG indicator + full-screen inspector route - SDK regenerated Bug fixes applied: - scheduling: failed nodes no longer counted as 'done' (separate failed Set) - scheduling: parallel forkDetach for 4 event subscriptions (was serial-blocked) - handlers: Effect.die -> notFound()/InvalidRequestError (500 -> 404/400) - spawn: model resolution requires both modelID+providerID (no empty fallback) - spawn: prompt resolved via resolveTemplate (was hardcoded empty text) - Removed dead core/dag/dag.ts (duplicate Service tag, zero consumers) All packages typecheck green. 80 tests pass (49 core + 31 opencode).
Phase 0 (dag-node-completion-semantics, 17/17 tasks complete): - spawn.ts: replace failure-only catchCause with Effect.gen success path that extracts output text and publishes NodeCompleted on prompt resolution - Fixes F1: successful nodes now publish NodeCompleted, preventing the scheduling loop deadlock that froze every workflow after first layer - 4 new completion tests (success/failure/empty-output/idempotency) OpenSpec changes (architecture analysis captured as specs): - dag-node-completion-semantics: completion contract (Level 1 text output) - dag-scheduler-durability: restart recovery + saga rehydration (Phase 2) - dag-scheduling-correctness: 6 internal bugs (race/replan/pause/cleanup) (Phase 2) - dag-structured-output: Level 2 data flow (structured output/input_mapping) (Phase 2) - dag-runtime-wiring spec relaxed: permits spawn.ts completion bridge - dag-runtime-wiring proposal: marks completion-semantics as prerequisite Boundary: Level 1 output is plain text; input_mapping field references (nodeID.output.field) resolve to undefined until Level 2 lands.
Three improvements for offline / restricted-network startup that are not covered by the already-merged offline-catalog PRs (#88-#91): 1. Plugin SDK offline bundling: build.ts copies the plugin source into a vendored directory next to each binary. At runtime npm.install resolves @opencode-ai/plugin via three-tier fallback — project-local > bundled copy > registry — so plugin installs succeed without network access. 2. dependencyVersion(): only pin the version on stable latest releases (channel=latest + pure semver x.y.z). Fork/prerelease/local builds get undefined so the install doesn't request a non-existent registry version. 3. Reference offline degradation: only register a Git reference when its cache directory actually exists, and update the entry after a successful refresh. Missing references are omitted instead of carrying a stale path.
fix(core): bundle plugin SDK and degrade references for offline startup
…aboration patterns Move strategy guidance out of the tool description into a built-in skill loaded on demand. The tool description (always in context) is now a lean syntax reference; the skill body (loaded when needed) carries: - Orchestration lifecycle: 5-phase meta-workflow (explore+brainstorm → review gate → parallel execution → audit+merge → expansion decision) with worked YAML examples and iterate/extend/complete exit branches - Four collaboration patterns: staged pipeline with gate, parallel fan-out, adversarial multi-model review, diverge-converge brainstorm - Adaptive replanning, model assignment strategy, design principles Skill description includes trigger keywords (动态工作流, dynamic workflow, workflow) so the agent loads it when the user describes heavy orchestration tasks.
Correctness fixes:
- Serialize terminal-event re-evaluation via per-workflow Semaphore(1)
(evalLock) across all event handlers, closing the double-spawn race
- Isolate InvalidTransitionError in spawnNode so guard failures on
nodeCompleted/nodeFailed don't route into catchCause and deadlock
the node with zero terminal events
- Add RUNNING → SKIPPED to node transition table so nodeCancelled
succeeds from running state
- Broaden reconcileWorkflow to detect pending orphans (crash between
session create and NodeStarted), with retroactive NodeStarted
- Fix structured-output gaps: log warning on invalid JSON, validate
parsed output against declared schema, interpolate {{var}} before
appending context block
- Replace try/catch + JSON.parse with Schema.UnknownFromJsonString
Standards fixes:
- Replace for-loops with functional methods in scheduling.ts
- Replace if/else-if cascade with early returns in dag handler
- Migrate test stubs from Service.of({...}) to Layer.mock
- Replace Effect.sleep sync with Fiber.await in tests
- Export and share SUCCESS_TERMINAL/toSchedulingNodes + fixtures
Verification: typecheck clean (core + opencode), 133 tests pass.
Interactive terminal tool that: - Discovers and merges all opencode config layers (managed/inline/project/custom/global) - Generates provider config blocks from models.dev metadata - Validates provider/model consistency against enabled/disabled policies - Writes atomically with backup, file locking, and 0600 permissions Covers full config discovery (OPENCODE_CONFIG_DIR, .opencode dirs, multi-platform managed paths, macOS MDM plist), JSONC parsing with trailing commas, sensitive field redaction in TUI, cross-provider model mapping, schema-required limit/cost fields, Windows MoveFileEx atomic replace, and HOME-failure fail-closed.
feat(config): add config_assistant Go TUI for opencode config management
Persists the merged workflow config as source of truth after replan (WorkflowConfigUpdated event) so node definitions survive restarts, and re-attaches completion watchers for running nodes on recovery so crashed-and-restarted workflows don't leave nodes stuck in running. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deadline-bounded node execution with semaphore-aware timeout; durable wake/replan columns and migration; report_to_parent node-level wake delivery with per-session serialization and crash-recovery drain; replan ceiling enforcement (per-node + live-node-count total); crash recovery that preserves pending nodes and cancels stale child sessions; workflow terminal cleanup that cancels all active children under evalLock. Key correctness fixes across multiple review rounds: - execute semaphore.release as Effect in both spawn and recovery watchers - D7 orchestrator-unresponsive scoped to delivered dagIDs only - NodeSkipped/NodeCancelled projector status guards - workflow cancel/complete/fail publishes terminal node events - replan total-ceiling check moved before any event publication - /goal deprecation regex word boundary fix Verified: core + opencode typecheck, 170/170 DAG tests, git diff --check.
…ix cancel cascade - Remove goal module entirely (goal/, tool/goal.ts, GoalLoop, goal HTTP API, goal TUI sidebar). /goal and /subgoal route to /workflow with deprecation notice - Replace text-parse structured output with submit_result tool: child agent calls submit_result, payload validated against output_schema and persisted to workflow_node.captured_output column (crash-safe across restarts) - Fix C1: NodeCancelled now projects to failed status and triggers markUnsatisfied cascade, so cancelled nodes' downstream dependents are auto-failed (was inverted to markSatisfied, letting dependents run without input) - Fix C1: skip spawnReady during replan event batch; WorkflowReplanned handler does the single post-batch spawn - Fix C2: captured payload persisted to DB (captured_output column) instead of in-memory Map; all three completion paths (fresh spawn, recovery watcher, reconcile) read from node row and agree on verdict_fail for schema nodes without captured output - Fix C3: max_total_nodes counts all nodes ever registered (cumulative lifetime) instead of only non-terminal nodes, preventing cancel-N-add-N churn from bypassing the ceiling - Clean phantom config fields (use_worktree, retry, replan_policy, report_strategy) - Change condition evaluation to fail-closed (unparseable → NodeFailed) - Default max_concurrency 4 → 5 - Wake messages include act-now obligation text for node-level wakes - Add budget documentation to workflow.md and skill workflow.md - Regenerate JS SDK after route changes - Sync delta specs to main specs, archive change simplify-dag-retire-goal
Field cleanup:
- Remove unused WorkflowConfig.timeout_ms and .description
- Make max_concurrency optional (aligns type with runtime ?? 5 fallback)
- Sync WorkflowGraphSchema in workflow.ts
Dead code/column removal:
- Delete probe.ts (4 functions, zero consumers) and core/layering.ts
- Remove dead retry_count column + add DROP COLUMN migration
Correctness fixes:
- create() now validates the full graph for cycles (not just required nodes)
- NodeCompleted projector gains WHERE status guard (pending/queued/running)
preventing stale events from flipping terminal nodes
Watcher removal:
- Delete attachNodeCompletionWatcher, runPollLoop, attachAbandonedSessionWatcher
- Crash recovery now relies solely on reconcileWorkflow one-time scan
- Stale comments in recovery.ts and loop.ts corrected
Document truth source unified (BREAKING):
- Delete tool/workflow.md; description now references SkillPlugin.WorkflowContent
- Add .annotate({description}) to all workflow tool schema fields
- Append Tool Reference section to skill workflow.md
Trigger path unified (BREAKING):
- Remove /workflow, /goal, /subgoal command registrations
- Remove prompt.ts special-case dispatch branches
Tests:
- Remove 8 watcher-related tests
- Add cycle detection, terminal irrevocability, projection guard tests
- Make extend operation additive to preserve pending/queued/paused nodes - Fix replan edge-building to keep original deps for running nodes without restart - Guard NodeCompleted/Skipped and NodeFailed handlers against stale events - Add path traversal protection to template ID resolution - Improve nodeCancelled validation and remove dead code - Update projector status guards to include paused state
On a replan-restart (NodeRestarted → NodeStarted with a new child session), captured_output from the dead prior run survived, defeating the verdict_fail completion path in both consumers (spawn.ts in-process and recovery.ts crash-recovery): a restarted node that never re-called submit_result would silently complete with the previous run's payload. Add captured_output: null to the NodeStarted projector patch alongside the existing per-attempt resets (child_session_id, started_at, etc.), matching the dag-structured-output spec requirement. Late setCapturedOutput(oldSession) writes are no-ops after NodeStarted (newSession) since they key on child_session_id. Adds a real-layer integration regression test (DB + EventV2 + Projector + Store) that fails without the fix. Also clarifies the 'survives restart' comments to distinguish process-crash survival from replan-restart reset.
feat(dag): DAG workflow engine with runtime safety hardening
chore(opencode): add config.json schema stub
…rompt injection - projector: guard NodeStarted against terminal statuses (stale event resurrection) - loop/recovery: fail orphaned nodes on expired deadline; safety net uses active-fiber-aware hasRunningMatching so post-crash all-orphaned workflows no longer hang - spawn: catch terminalization race during spawn window, cancel orphaned child session, return no-op fiber (no spurious NodeFailed) - templates/sanitize: recursive sanitizeInput on resolvedMapping closes injection gap on dynamic input_mapping - types: FAILED now returns [] in transition table, aligning with isNodeTerminalStatus (iron law #2) — restart path uses RUNNING->PENDING, never FAILED - tests: cover NodeStarted guard, recovery deadlines, spawn terminalization, hasRunningMatching, nested sanitize
fix(dag): harden runtime against stale events, orphaned fibers, and prompt injection
Two dev-branch CI failures in Unit Tests (linux): 1. 'declared schema has no ungenerated migrations' — the dynamic-workflow schema changes (workflow_node.captured_output column, dropped goal_state table) were committed without running the migration generator. Generate the migration via `bun script/migration.ts` and refresh schema.json, migration.gen.ts, schema.gen.ts. 2. 'uses configured provider env vars as credentials' timed out at 5000ms — the test registers an env credential, which makes OpencodePlugin.load() issue a real HTTPS request to console.opencode.ai via fetchProviders. Network availability determined pass/fail. Stub the HttpClient to return 404 (no remote config) so the test stays on local defaults and never hits the network; the connected-server it.live test keeps the real client.
Stateless DagSummaryPublisher recomputes per-session WorkflowSummary[] from DagStore on every dag.* event and pushes an ephemeral dag.workflow.summary.updated event via GlobalBus. Adds the /dag/session/:sessionID/summary route, DagStore.getWorkflowSummaries, the WorkflowSummary schema, regenerated SDK, the TUI sync store slice with a bootstrap fallback fetch, and reuses the SDK type (DagWorkflowSummary) in plugin/tui.ts instead of a hand-duplicated interface. Control ops that fail with InvalidTransitionError/TerminalViolationError now map to 409 Conflict.
Add keyboard navigation (j/k node, h/l workflow, enter child session, escape/q close) and workflow control (p pause / r resume / x cancel) bound via useBindings with new dag_* keybind definitions. Auto-selects the first workflow/node and re-validates selection as data changes; guards stale fetch responses and cleans up event subscriptions with onCleanup. Extract the topological wave layout into dag-inspector-utils (computeWaves) with unit tests; missing dependencies from a replan are now treated as satisfied.
SyncProvider calls useExit() since #31524, but the sync test harness did not mount ExitProvider, causing 8 pre-existing test failures (sync, live hydration, undefined messages). Wrap the fixture mount with ExitProvider.
Add a TUI (packages/tui) section under Extending the Codebase covering: builtin registration, route-scoped keybinds plus Definitions/CommandMap config tables, the sync store slice with bootstrap fallback fetch and the ExitProvider dependency, ephemeral vs durable events, SDK type reuse, server-side aggregation with stateless publishers, and *-utils extraction.
fix(core): generate pending db migration and stub network in plugin test
feat(dag): complete workflow summary pipeline and TUI surface
…iscovery fails bun's ancestor-directory workspace walk dies with CouldntReadCurrentDirectory in sandboxed checkouts, failing hooks on an environment defect. The shared runner keeps 'bun typecheck' as the primary path and falls back to running each package's own typecheck script only on that specific error; real type errors still fail on either path.
…fallback tsgo -b packages (app/desktop) can fail with TS5033 'operation not permitted' writing .tsbuildinfo under sandboxed hook contexts. Tolerate a package failure only when TS5033 is the sole TS diagnostic; real type errors still fail.
fix(dag): cross-instance adoption guard, subscription defect survival, cancel-skip race
…osal contract Restructure the dag-flow orchestration manual around a tiered doctrine: advanced-tier nodes conduct and check, standard-tier nodes buy accuracy with breadth (concurrent fan-out) and depth (verdict-gated waves). Add depth ladder hard minimums, graded review profile with claim verification, verdict disposal contract at the terminal boundary, ship the 12 dag-prompts templates the manual promised, fix the arbiter dead-end example, and drop the dead dagworker template relic.
feat(core): tiered orchestration doctrine, depth ladder, verdict disposal contract
Loop handlers for NodeCompleted/NodeSkipped/NodeFailed now confirm the node row still reflects the event's terminal status before touching fibers or runtime state, so events from a previous generation (after NodeRestarted/replan) are dropped. Schema capture validation additionally enforces const/enum via structural equality.
fix(core): drop stale dag node terminal events via db status cross-check
chore(ci): enforce oxlint warning ratchet in CI and pre-commit
fix(tui): dag inspector frame alignment, padding, and wave grouping
chore: rename project to OpenCode-GraphAgent
Replaces the diff-viewer frame vocabulary with the chat transcript's: rail and panel blocks instead of box-drawing lines, two-column side padding, and a fixed 42-wide sidebar gated on the same > 120 terminal width chat uses. - Responsive sidebar: below 120 columns the sidebar squeezed node names into 4-character truncation, so it is dropped and the summary block carries the workflow position instead. - Selection now uses primary + selectedForeground() for the node cursor and backgroundElement for the secondary workflow selection, matching the select dialog's focused/unfocused semantics. backgroundElement alone meant "selected but unfocused" and read as an 8% luminance nudge. - Cursor keys only: down/up move nodes, left/right switch workflows, return opens, escape closes. j/k/h/l/tab/q are gone and pause/resume/step/cancel are unbound by default, reachable through the command palette instead, so the footer stays a single uncrowded row. - Tightens the lint ratchet to 4690: a runCommand test helper collapses ten duplicated command-context assertions into one.
fix(tui): rebuild dag inspector on the chat design language
…cabulary The previous revision transplanted chat's structure instead of learning its design language. Three chat-specific structures were imported and are now removed: - The 42-wide right sidebar. In chat that container holds ambient session metadata; here it held the workflow list, which is primary navigation. - The > 120 width gate that came with it. A threshold designed for optional metadata made primary navigation vanish on narrow terminals; the earlier "responsive" fix copied chat's breakpoint and entrenched the mistake instead of questioning the transplant. - The SplitBorder rail-and-panel block. In chat that marks one utterance in a stream; the workflow summary is persistent state, not an utterance. Structure now comes from what this screen actually is, a full-screen inspector: title row, always-present navigation pane, content pane, detail pane, footer. Navigation width is derived from the pane's own needs, clamp(18, 32, width * 0.3), so it narrows instead of disappearing. Only the vocabulary is borrowed: regions are marked by the panel surface plus a padding rhythm rather than drawn box characters (which also settles the original misaligned-rule complaint), token semantics for text/textMuted/ backgroundElement/primary, and bold reserved for identity and section headers. The width regression test now asserts the inverse invariant: the workflow list is present at 130, 100 and 70 columns.
fix(tui): restore the dag inspector's own structure, keep only the vocabulary
fix(dag): harden workflow startup controls
fix(dag): harden workflow spec handoffs
fix(dag): harden recovery and replan races
LeXwDeX
enabled auto-merge
July 30, 2026 04:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promote the validated dev integration branch to main for a formal release.
Validation already completed on dev SHA 75953f4:
This PR intentionally contains the accumulated dev release train.