Core v4.0.4 - #75
Merged
Merged
Conversation
Goal Loop control changes updated in-memory state but emitted no
immediate goal.updated event, so the client status bar never reflected
them until the next tick (a full subagent turn). stop() was worst: it
cancels the driver job, so no further tick ever fires and the UI stayed
stuck on "running" forever.
- goal-manager: extract publishGoalEvent + publishControlPhase; cache
the live ledger/stall/gaps on GoalControl (updated each tick) so
control transitions publish the real budget, not zeros.
- start/pause/resume/stop now emit an immediate goal.updated.
- finalizeOutcome guards the driver's terminal tap on a live control so
a cancelled driver's late "needs_human" outcome cannot clobber the
user's explicit "stopped".
The "convert plan -> goal" button gated on session_plan, which only the
plan tool populates — but loop/design modes author the plan as the repo
file goal+plan.md, so the button never appeared in the modes it belongs
to and did appear in auto (redundant/confusing).
- New GoalManager.startable() mirrors start()'s plan precedence
(session_plan -> goal+plan.md -> none) without side effects, flag-gated.
- New GET /deepagent/goal/startable route + handler.
- Button now gates on capability x mode in {loop,design} x startable, and
shows a success toast on start. i18n goal.start.success (en/zh/zht).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 1-3)
Foundational event-driven Agent-OS substrate for V4.0 §A/§C/§D/§E, all behind
default-OFF feature flags (grey rollout).
Wave 1 — Event Bus (§A, core):
- deepagent-event{,-sql}.ts + deepagent-event-bus.ts: persist-before-dispatch,
idempotency, subscribe/ack/nack, exponential-backoff retry + DLQ, dedup window,
durable per-group delivery tracking (at-least-once), getByID. + migration.
Wave 2 — Router + Scheduler (§A4, core):
- event-router.ts: pure route() (type match / flag gate / low-priority dedup /
priority / backpressure).
- scheduler{,-sql}.ts: durable delay/periodic/condition schedules (survive restart,
unlike BackgroundJob), catch-up advance, condition threshold. + migration.
Wave 2b — runtime wiring (§A4, deepagent-code):
- event-dispatcher.ts: bus subscription + per-event handle (flag+registry+dedup →
route → dispatch/ack/nack), scheduler tick loop, retry pump (at-least-once
recovery), readiness gate, injected DispatchPort.
Wave 3 — Multi-Agent Runtime + autonomy + security:
- autonomy-policy.ts (§D): tighten-only ceiling, level_5 suggestion-only.
- security-gate.ts (§E1): 4-layer fail-closed check.
- rate-limiter.ts (§E2), content-safety.ts (§E3), quiet-hours.ts (§E4).
- task-partitioner.ts (§C2): event → subtask DAG (validated deps).
- conflict-arbiter.ts (§C3): conflict detection + resolution ordering.
- multi-agent-runtime.ts (§C): real DispatchPort — partition → DAG gate →
idempotency guard → autonomy + security gates → conflict arbitration → one
SessionPrompt turn per subtask → §C4 coordination events.
+6 V4.0 feature flags (default OFF). Each module implemented → adversarially
reviewed → tested → fixed. New tests: 13 files. Full monorepo typecheck green;
no regressions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Proactive agent-push path, gated by v4AgentPushEnabled (default OFF). - agent-push-policy.ts (§B2, core, pure): decide() composes §E gates — 权限 (group member OR workspace push permission) → 限流 (20/hr per agent per group) → 内容安全 (ContentSafety scrub) → 静默时段 (QuietHours: normal/low → digest, high/critical passthrough w/ requiresReason). Fail-closed. - push-log-sql.ts + migration (§B4): im_agent_push_logs — durable audit + rate-limit accounting, UNIQUE(idempotency_key) for §B2 去重, content column retains scrubbed text as the digest-builder source. - agent-push.ts (deepagent-code): resolves facts (membership, window count, quiet-hours) and runs the policy inside ONE immediate transaction — dedup pre-check → gate → persist message + audit atomically (no TOCTOU, no delivered-but-unaudited window). Flag OFF = fail-closed flag_disabled. Adversarial review: fixed BLOCKER (去重 unimplemented — added unique index + pre-insert dedup, message no longer double-delivers), HIGH (digest content silently dropped — retained in log), and MEDIUM (non-transactional persist + rate-count TOCTOU — now one transaction). §B2 file-path ACL documented as a tracked follow-up. Tests: 11 policy + 9 runtime, monorepo typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only aggregation over the durable event / delivery / push-log tables,
backing the Oversight Dashboard + Event Trace views.
- observability.ts (§F, core):
- §F2 trace({workspaceID, correlationID}) → causal event chain, oldest-first,
workspace-scoped (correlationID is producer-set and can collide across
tenants).
- §F1 metrics({workspaceID, from, to}) → dlq_events_total (distinct events),
agent_push_rejected_total + by-reason, agent_task_success_rate (genuine
runner failures only — policy blocks excluded; null on no data),
agent_conflict_rate. Latency histograms + human_takeover_total documented
as later-wave gaps (need emission-time / takeover-source instrumentation).
- fix(agent-push §B2): rate-limit window filter used `decision != 'blocked'`
which never matched (blocks are stored `blocked:<reason>`), so rejected pushes
wrongly consumed rate quota. Now `not like 'blocked:%'`. (Found by the
observability review cross-check.)
Adversarial review: fixed HIGH tenant-leak (added required workspaceID scoping),
HIGH success-rate semantics (exclude policy blocks, null vs 100%), the
rate-limit filter bug, DLQ over-count (count distinct event_id), and computed
the previously-absent conflict rate. Tests: 8, monorepo typecheck green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cab (Wave 6)
Trigger policy + event vocabulary to wire the existing V3.9 bodies (Repo & Wiki,
Expert Panel, Goal Loop) onto the Event Bus. Adds NO panel/archiver/goal
mechanics — those stay V3.9; this is the "接入" layer.
- panel-convene-policy.ts (§M, core, pure): shouldConvene({event, flagEnabled,
rules?}) → auto-convene a panel for high-risk events (security alert /
destructive migration / architecture change / repeated CI failure), else skip.
Urgency floored to >= high for every convene so §A4 backpressure can't silently
drop a high-risk convene request.
- lmn-events.ts (§L/M/N, core): canonical event-type strings + shouldQueueForApproval
(folds the PANEL_VERDICT needs_human payload gate — no accidental queue flooding),
goalPhaseToEventType (bridges the existing goal.updated{phase} emitter → discrete
§N lifecycle types), isArchiveTrigger.
Adversarial review: fixed MED urgency-floor asymmetry (only security was floored →
now all convene classes), MED PANEL_VERDICT approval-queue footgun (added definitive
payload-aware predicate), and the goal.updated↔goal.* vocabulary gap (added the
phase→type bridge so the later wiring has a defined contract). Tests: 15, monorepo
typecheck green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…7, §H/§I) Capstone tests proving the waves COMPOSE, not just pass in isolation. - v4-integration.test.ts (§I/§J): the real spine over one shared bus — publish(event) → EventDispatcher.handle (flag+registry+route) → MultiAgentRuntime injected as the live DispatchPort → coordinate (partition→gate→arbitrate→run) → §C4 coordination events → Observability trace/metrics. Asserts causal linkage (coordination events' causationID == triggering event) + exact agent run order. Plus scheduler→bus→route, the failure→nack→retry propagation across the dispatcher/runtime boundary, and §H2 rollback safety (flags OFF → event durably retained but not dispatched). - v4-migration-integrity.test.ts (§H/§I): all V4 tables + indexes exist after the migration set, V3.8 IM tables coexist, and the §B2/§A3 dedup indexes are asserted UNIQUE (guards the push double-delivery fix against a uniqueIndex→index regression). - runtime-flags §H2/§H3: all six V4 flags default OFF + are individually toggleable (independent kill-switches). Review confirmed the integration is genuinely end-to-end (not a bypass, runner is the correct SessionPrompt seam); added the failure-propagation case, index-uniqueness assertion, and causationID check it flagged. 209 V4 tests green, monorepo typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Additive bridge services wiring the V3.9 bodies onto the Event Bus (beta — merges after alpha). No V3.9 mechanics changed. - event-driven-archiver.ts (§L): subscribes to session.completed/goal.completed and drives the EXISTING archiveSessionOnCompletion projection. Delivery-tracked as group "wiki-archiver" — acks on success/terminal, nacks archival failures, with its own group-scoped retry pump (real at-least-once, not the orphaned- pending-row anti-pattern the review caught). - approval-queue.ts + sql + migration (§D2): the durable human-decision sink for goal.needs_human / goal.rolled_back / panel.verdict[needs_human]. offer() folds LMNEvents.shouldQueueForApproval (only genuine escalations queue), UNIQUE(event_id) makes it idempotent; listPending + resolve back the Oversight Dashboard. First- resolution-wins via a status-gated UPDATE. Adversarial review: Approval Queue clean on all 6 checks (idempotency, isolation, resolve race, migration match). Fixed the archiver HIGH — it subscribed as a tracked consumer group but never discharged deliveries (leaking pending rows + voiding at-least-once); now acks/nacks + pumps. Tests: 8 queue + 6 archiver; migration integrity extended to the new table. Monorepo typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s (§H3) The six event-driven Agent-OS flags are now broadcast in the capabilities response (optional Boolean fields, all default OFF), so a client can gate the V4 UI surface (Oversight Dashboard / Approval Queue / proactive-push / thread + file upload) exactly where the routes fail-close — UI availability == route availability, matching the V3.9 expertPanel/goalLoop/wiki pattern. Source-of-truth schema in groups/global.ts; SDK gen picks it up on next build (not hand-edited). httpapi-instance capabilities test asserts all six default OFF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ueue (§D2/§F)
Backs the Oversight Dashboard: GET /oversight/{metrics,trace,approvals} +
POST /oversight/approvals/resolve. New route group registered in InstanceHttpApi;
Observability + ApprovalQueue provided over the shared Database layer. All
workspace-scoped via the same routing key (workspaceID ?? directory) the rest of
the instance API uses; auth + workspace-routing middleware identical to siblings.
Adversarial review — fixed HIGH tenant-isolation defect: ApprovalQueue.resolve
keyed only on id, so a caller routed to workspace A could resolve (write) AND
read back workspace B's approval item by id. resolve() now requires workspaceID
and scopes both the UPDATE guard and the re-select to it (cross-tenant resolve →
null → typed 404, never a 500 or a leak). Read path (metrics/trace/listPending),
schemas, layer wiring, middleware all verified clean.
Tests: 9 approval-queue (incl. cross-tenant isolation) + 2 live oversight route
tests through the production HTTP server. Monorepo typecheck green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e (§N) goal-manager.publishStatus now also mirrors the goal lifecycle onto the DeepAgent Event Bus and escalates terminal outcomes into the §D2 Approval Queue — gated on v4MultiAgentRuntime (default OFF ⇒ byte-identical to the V3.9 goal.updated path). - emitGoalLifecycleEvent: phase → discrete §N type (goalPhaseToEventType; running/ paused/stopped ⇒ goal.tick), publish to the bus with correlationID = goalId (the §F2 trace spine) and idempotencyKey goal:<id>:<phase>:<ticks>, then approvalQueue.offer (folds shouldQueueForApproval → only needs_human / rolled_back queue; tick/completed/stopped never escalate). - Best-effort: wrapped in catchCause → a bus/queue failure can never break the goal loop. GoalManager.defaultLayer now self-provides the bus + queue layers. Adversarial review: flag-gating, isolation, idempotency, layer wiring, and escalation gating all clean. Fixed the one bug it caught — the write-side workspace key used session.directory while Oversight reads by session.workspaceID, which would make escalations invisible to the Dashboard in server edition; now `session.workspaceID ?? session.directory ?? sessionID`, mirroring the read side. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(§B4) Adds the event-driven columns + indexes to im_messages, backward-compatible with the V3.8 write path (§H: add V4 fields, keep V3.8 working). - sql.ts MessageTable: event_id (the bus event a message was produced from) + delivery_status (pending|delivered|failed) — both NULLABLE so V3.8 inserts that omit them are unaffected. Plus §B4 indexes: idx_im_messages_thread (group_id, reply_to_id, created_at) for thread pagination and idx_im_messages_event. - migration 20260711040000: guarded ADD COLUMN (table_info check, mirrors the session-preview migration) + the two indexes. - migration-integrity test asserts the columns exist AND are nullable. - aligned the three IM tests that hand-roll the im_messages DDL (im-integration, im-agent-reply-sink, im-orchestrator) so their manual CREATE matches MessageTable. This is the schema prerequisite for the §B1 double-write (im.message.created) — the write path lands in a later batch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r send After a user message durably persists (alongside the existing WebSocket message_created broadcast), the IM createMessage handler now publishes an im.message.created DeepAgent event onto the bus. Flag-gated on v4EventDrivenIm (default OFF ⇒ no publish, byte-identical to V3.8); best-effort (catchCause) so a bus failure never fails the user's send — the message already persisted; and idempotent (idempotencyKey = im:<messageId>, one event per message). - lmn-events.ts: IM_MESSAGE_CREATED = "im.message.created" (+ test: not an approval/archive trigger). - im handler resolves RuntimeFlags + DeepAgentEventBus; server.ts provides the bus to the instance route graph. This is the "double-write" migration step (§H1.3): the legacy synchronous @mention path stays authoritative; the event rides alongside for consumers (Router/MentionAgent) once v4EventDrivenIm is enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…te-limits/trust)
The shared primitive four V4 subsystems need — a per-workspace policy store,
tunable per tenant rather than baked into code:
§A3 retentionDays · §E4 quietHours (start/end/tz) · §E2 rateLimits overrides ·
§E1 trustedSources.
- workspace-config{,-sql}.ts + migration 20260711050000: one row per workspace,
a single schema-versioned JSON blob. WorkspaceConfig.Service.get returns a
fully-resolved view (defaults applied); set upsert-merges a partial patch.
- Lenient/safe defaults: absent or partial row → 30d retention, no quiet window,
all sources trusted, no rate overrides — so enabling V4 for an existing
workspace changes nothing until an operator writes a config. Non-positive
retention and empty trust-list fall back to defaults (no zero-retention /
trust-nothing lockout). Corrupt blob decodes to defaults, never crashes a reader.
Unblocks the retention sweep, quiet-hours resolver, rate-limit ceilings, and
security-gate trusted-source resolver. Tests: 6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the security-gate's default-open wiring gap and the content-safety file-path leak (both were documented deferrals). - security-resolvers.ts (SecurityResolvers.Service): production resolvers for the gate's lenient layers — L1 trusted-source list from WorkspaceConfig; L2 actor workspace permission (IM membership / registered agent, fail-CLOSED on lookup error); L4 runtime operation pre-gate (agent capability/whitelist). Feeds SecurityGate.check instead of the allow-defaults. - path-acl.ts (isPathAllowed + resolver): rejects traversal / absolute-escape / home-escape; a path is allowed only if it normalizes to within an allowed workspace root. - content-safety.ts: ADDITIVE — scrub gains an optional allowedPathRoots; when set, disallowed file-path tokens are redacted and counted (strippedPaths). Absent → unchanged behavior. Fail-closed throughout (§E1 contract). Tests: 47 across the three modules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Thread: listThread repo method (group+reply_to, keyset (created_at,id) ASC via idx_im_messages_thread) + GET .../messages/:id/thread, membership-scoped. - Direct message: GroupType "direct"; createGroup enforces exactly-2 members (user+user | user+agent) + canonical-pair dedupe → IMValidationFailedError. - Search: FTS5 virtual table im_messages_fts + sync triggers (migration), with a LIKE fallback when FTS5 is unavailable; searchMessages JOINs membership so a user only searches groups they belong to; metadata json_extract filter. - File upload: im_attachments table (decoupled from messages — nullable message_id), local-disk storage under the workspace data dir, server-derived path (traversal-proof), sha256 checksum, mime allowlist + size cap, gated on v4FileUploadEnabled (fail-closed). AttachmentID "ima_". Storage/validation core is unit-tested (attachment-storage + im-b3); thread/ direct/search covered over HTTP. Full multipart round-trip is exercised at the storage-core level (the in-memory test transport can't stream multipart). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RetentionSweeper (core): a periodic daemon (runLoop-gated; sweepOnce for tests) that, per workspace, prunes deepagent_event / push-logs / resolved approvals older than WorkspaceConfig.retentionDays (default 30d). Referential-safe: never deletes an event still referenced by a pending delivery or an unresolved approval-queue row; deliveries cascade. - DeepAgentEventBus.sweep(workspaceID, olderThan) — the ADDITIVE delete primitive the sweeper drives (existing publish/subscribe untouched). - DigestBuilder (deepagent-code): flushWorkspace delivers held digest pushes (im_agent_push_logs where decision='digest', digest_flushed_at IS NULL) grouped per IM group when the workspace is OUTSIDE quiet hours (QuietHours resolved from WorkspaceConfig), then marks them flushed (idempotent — no double-delivery). No-op while within quiet hours. New im_agent_push_logs.digest_flushed_at column. Tests: retention referential-safety + per-workspace retention + isolation; digest delivers-outside-quiet-hours, groups, idempotent, holds-within-quiet-hours. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncurrency
- DeepAgentEventBus.tryPublish (ADDITIVE): per-workspace event-publish rate gate
(1000/min default, overridable). low/normal shed as {dropped:"rate_limited"}
over-limit; high/critical always pass (§A4 — critical never dropped). Existing
publish() untouched.
- §F1 latency: publish() records publish_latency_ms (new nullable column, timed
around the persist txn); Observability adds eventPublishLatencyMs P50/P95 and
eventToAgentStartMs P50/P95 (samples = agent.task.started.created_at − trigger
created_at, joined by causationID, workspace-scoped). Oversight metrics schema
gains the 4 optional fields.
- WorkspaceConcurrency service (§E2): per-workspace in-flight cap (5 default, from
WorkspaceConfig) — acquire/release/depth/totalDepth, the primitive the runtime
gates agent execution on and the dispatcher reads for backpressure.
Tests: tryPublish shed/bypass/isolation/override; latency P50/P95 + null-empty;
concurrency acquire/reject-at-cap/release/depth. Existing bus+observability green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PanelConveneConsumer (deepagent-code): a bus-subscribing daemon (group "panel-convener", ack/nack + retry pump) that runs PanelConvenePolicy.shouldConvene per event and, on convene, drives the panel via an INJECTED PanelConvenePort (never creates sessions itself), publishes panel.verdict, and offers a needs_human verdict to the §D2 Approval Queue. Flag-gated v4PanelAutoConvene; deliveries always discharged. - §D autonomy surfacing: MultiAgentRuntime now escalates a subtask that exceeds the agent's autonomy ceiling OR is suggestion_only to the Approval Queue (new agent.task.needs_human event → offer), instead of only emitting a blocked event and silently dropping it. ApprovalQueue.summarize handles the new type. - SubagentTurnInput gains optional workspaceID/directory so an event-driven runner (no parent session) can root a turn in the triggering event's workspace; MAR threads event.workspaceID through. - New flag v4PanelAutoConvene; migration.gen registers the 5 new V4 migrations. Tests: panel convene→verdict→queue + flag-off ack + discharge; MAR autonomy-block escalates to the queue. Integration + MAR test layers provide ApprovalQueue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…4/§C/§E2) The central integration gap: every V4 daemon was built + tested but NEVER started in prod, so published events were durably logged then ignored. This wires them in. - v4-event-runtime.ts (V4EventRuntime.layer): assembles + starts, as scoped fibers with the server, the EventDispatcher (router + scheduler tick + retry pump), the MultiAgentRuntime DispatchPort (with a production event-scoped turn runner that creates a fresh root session in the triggering event's workspace, mirroring the IM executor), and the RetentionSweeper. Daemon startup is gated on the V4 flags read at build — with flags OFF (default) the layer is INERT: nothing subscribes, ticks, or prunes (critical — the sweeper would otherwise delete events on a 30d TTL). Per-event behavior stays flag-gated inside each daemon. - server.ts: composes v4EventRuntimeLayer sharing the ONE DeepAgentEventBus + ApprovalQueue + Database with the IM double-write and goal-manager (module-const layers memoize to a single instance — publishers and the dispatcher can't split-brain), drawing the session stack from the shared graph. - §E2 wired: MultiAgentRuntime takes an optional WorkspaceConcurrency — over-cap subtasks defer (retryable, never dropped), released in Effect.ensuring; the dispatcher's §A4 backpressure queueDepth now reads concurrency.totalDepth(). Tests: runtime layer builds + shared-bus round-trip; MAR concurrency-cap deferral. Full monorepo typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…OCKER+HIGH) Adversarial review of the runtime wiring caught two defects that made the production event path non-functional despite green unit tests. Both fixed. - BLOCKER — daemon fibers carry no InstanceRef (it's only set per-request), so the event turn runner's sessions.create → InstanceState.context died (→ failedTurn) on EVERY event: the runtime could never execute an agent. Fix: makeEventTurnRunner now loads the InstanceContext for the event's directory (InstanceStore.load) and provides InstanceRef + WorkspaceRef around create + prompt — mirroring the instance-context middleware / IM executor. Also resolves a real directory (no longer aliases a "wrk_"-id as a filesystem path) and bounds each turn with a 10-min timeout so a blocked tool can't stall the sequential dispatch loop. - HIGH — MAR emitted agent.task.started BEFORE running, so on a runner_failed → nack → retry, the idempotency guard saw the started marker and acked the retry away as "already done" — zero effective retries. Fix: the guard now checks the agent.task.completed marker, so a failed subtask genuinely re-runs on retry. - MEDIUM — record the arbiter claim only after the concurrency slot is admitted (a concurrency-deferred subtask no longer leaves a phantom claim). - Retention/flag coupling documented as self-consistent (no writers when flags off ⇒ nothing to prune). Verified: integration test now asserts a failed-then-recovered subtask actually re-runs on the retry pump (not skipped). Shared-bus single-instance invariant confirmed sound by a separate source-level review. Monorepo typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tes the stack v4.0-beta is the INTERNAL TEST build: every V4 capability ships ON (stableOn) so the full event-driven Agent-OS is exercised end-to-end, not grey-rolled. Each flag remains an independent KILL-SWITCH — `DEEPAGENT_CODE_V4_*=false` disables just that capability for isolation/rollback (mirrors the V3.9 stableOn convention for wiki/panel/goalLoop). The GA cut keeps these ON once beta proves them. With v4MultiAgentRuntime ON by default, the V4 event-runtime daemons now start with the server: the EventDispatcher subscribes + routes, MultiAgentRuntime executes, the RetentionSweeper prunes, and IM user messages double-write im.message.created. Verified under default-ON: full httpapi-instance server boot (10/10, daemons launch cleanly), agent-push + IM §B3 + all V4 dac suites (51) + core IM (12) green, monorepo typecheck clean. Flag + capabilities tests updated to assert default-ON + independent kill-switch semantics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cing posture (P0.3) commit fbdb86c flipped all seven V4 flags default-ON ("internal test build"). For a customer-facing build the high-risk autonomous/side-effecting flags must be operator opt-in per the §H staged-rollout contract. Flip all seven V4 flags (v4EventDrivenIm, v4AgentPushEnabled, v4MultiAgentRuntime, v4AgentAutonomyLevel2, v4ThreadEnabled, v4FileUploadEnabled, v4PanelAutoConvene) back to default OFF via the env-respecting `bool` helper — override-on (env / RuntimeFlags.layer) stays functional so operators + tests opt in. Safety gates themselves are NOT flag-gated (they always run once P0.1/P0.2 land); this only changes feature-exposure defaults. Tests assert the new default is OFF for all seven and that env override-on still works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… re-entrancy (P0.2) The §E2 per-workspace publish ceiling (1000/min) was a dead gate: it lived on tryPublish but every producer called plain publish. Route the two workspace-facing, externally-driven producers (im.message.created, goal.*) through tryPublish so the ceiling enforces real load; system-origin derivative coordination events stay on plain publish (shedding them would strand dependent subtasks). Add a flag-gated sweepPublishLimiter daemon (in v4-event-runtime.ts, committed with P0.1) so the per-workspace bucket map is pruned. Adversarial-review follow-ups folded in: - §D/§M goal.rolled_back silent-loss: elevate any APPROVAL_QUEUE_TYPES event (isApprovalQueueCandidate) to high priority so it bypasses the gate and always persists + reaches the Approval Queue (was: only needs_human elevated). - §C4/§A4 re-entrancy amplification: coordination events (agent.task.*/agent.handoff.*) could re-enter dispatch via a wildcard-trigger agent → unbounded ungated cascade. Sever it in the pure router (isCoordinationEvent → "coordination" drop, checked first, flag/priority-independent). Events still persist + reach trace/oversight; only agent-dispatch re-entry is cut (per §C4 coordination events are observe-only). - im.ts: distinguish a real bus error from a rate-limit drop in the log annotation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rod (P0.1) The §E1 four-layer permission gate was default-OPEN in production: MultiAgentRuntime was built with no resolvers, so L1 (event-source trust), L2 (actor permission) and L4 (runtime pre-gate) all evaluated to true — only L3 (capability) was enforced, violating §E1 "任何一层失败都必须 fail closed". - Add a per-event `trustedSourcesFor` option to MultiAgentRuntime.layerWith (L1 is a per-workspace fact the static array can't express); it takes precedence and FAILS CLOSED (catchCause → not-trusted). Pass subtask capability to the L4 resolver. - Inject the production SecurityResolvers (L1/L2/L4) in v4-event-runtime.ts; provide SecurityResolvers.layer + imRepositoryLayer into the server graph so it shares one instance with the runtime + IM double-write. - §E1 tighten DEFAULT_TRUSTED_SOURCES to first-party only (im/system/schedule); external webhook sources (git/ci/pr/monitor) now require explicit per-workspace opt-in rather than being trusted by default (fail-closed default). Also carries the §E2 publish-limiter sweep daemon (P0.2) in v4-event-runtime.ts, which shares this file. Tests prove untrusted-source, non-member actor, whitelist violation, and resolver-defect all BLOCK with the correct security:<layer> reason and never invoke the runner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ducers (P1.4)
Five of six §A1 event sources had zero producers; only IM published. Add an
authenticated HTTP webhook ingress (4 POST endpoints /api/v1/webhook/{git,ci,pr,monitor})
that normalizes external deliveries into DeepAgentEvents and publishes them onto the V4
bus, so the autonomous half finally has real events to react to.
- Same middleware stack as oversight (Authorization + InstanceContext + WorkspaceRouting):
workspace-scoped, authenticated, no anonymous path. workspaceID/actorID derived
server-side (body cannot spoof the workspace).
- Publishes via tryPublish (§E2 1000/min gate) so external floods are shed per workspace;
{dropped} → non-500 ack. critical monitor.alert → high priority (bypasses shed).
- Deterministic idempotencyKey via JSON.stringify of stable delivery fields (no
delimiter-boundary collisions) → retried deliveries dedupe at the bus unique key.
- Adds LMNEvents constants GIT_PUSH/CI_FAILURE/PR_COMMENT/MONITOR_ALERT (values match
the router/partitioner consumer match tables exactly).
- §E1 note: post-P0.1 these external sources are NOT trusted by default, so events
persist + trace but the security gate blocks agent dispatch until an operator opts
the source into the workspace's trustedSources. Intended fail-closed / opt-in.
Follow-ups (opt-in milestone): attest caller-supplied actorID before it feeds §E1 L2;
revisit critical→high bypass if monitor becomes high-volume.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rigger (P1.5)
The §L EventDrivenArchiver subscribes to session.completed but nothing published it —
its trigger was dead. Add an isolated bus→bus bridge (SessionCompletedPublisher) that
subscribes to the existing end-of-turn idle signal (SessionStatus.Event.Status) and
republishes it as a V4 session.completed event, WITHOUT touching the session core loop.
- Root-session only (parentID == null, evaluated at fire time, fail-closed on lookup
miss) — subagents/panelists/goal steps never trigger an archive.
- Debounce/coalesce (45s window): an interactive root session goes idle after EVERY
turn; a naive per-idle publish would re-archive the full trajectory per turn. The
bridge arms a per-session debounce timer (interrupt-then-refork, epoch-guarded map
delete, forkIn self-deregistering — no fiber/map leak), so a burst of turn-idles
coalesces to ONE session.completed carrying the latest state. A genuinely separate
later completion fires at a later fire-time token → new key → re-archives final state.
- completionToken = window fire-time (stable per settled window) so bus retries dedupe.
- source="system" (passes §E1 L1); payload {sessionID, workspacePath} matches exactly
what the archiver reads.
- New flag v4EventDrivenArchive (default OFF per P0.3); off ⇒ no subscription, inert.
Follow-up: in-window completions pending at server shutdown are lost (best-effort
archive; next completion re-archives). Consider exposing debounceMs as a runtime flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s live (P1.6) The scheduler tick loop scanned a table nothing ever wrote — the whole delay/periodic/ condition machinery (incl. the "3× CI failure → repair" example) was dead. Register two canonical production schedules at startup so the tick loop has real rows to fire. - (A) daily periodic schedule.scan (§A1 MaintenanceAgent trigger). - (B) conditional ci.repair.requested when ≥3 ci.failure in 30min. Counts ci.failure CROSS-WORKSPACE (new ConditionSpec.crossWorkspace → tick omits the workspace filter): real CI failures from P1.4 webhooks land in per-project workspaces, so a system-scoped per-workspace count would never fire — crossWorkspace makes it functional. Existing per-workspace conditions unchanged (fail-safe default). - Idempotent registration enforced at the DB layer: new nullable schedule_key column + partial-unique index (NULLs distinct, so ad-hoc schedules unconstrained) + migration; keyed inserts use onConflictDoNothing → a concurrent second process racing the same boot is a no-op returning the winner row, never a duplicate (fixes the multi-process TOCTOU a list-then-insert guard couldn't). - Gated on v4MultiAgentRuntime (default OFF) ⇒ fresh prod DB stays empty, no dead rows. Also unions v4-event-runtime.ts across P0/P1.5/P1.6 (limiterSweep + SessionCompletedPublisher + scheduleBootstrap into the one mergeAll; v4EventDrivenArchive in anyV4DaemonEnabled). Follow-up: the cross-workspace CI counter conflates repos and the repair event carries no repo discriminator — per-repo triggering is future producer→consumer wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tanceRef die (P2.7/P2.10) Two built-but-unwired consumers are now merged into the prod V4 layer, and a severe latent defect that made event-driven execution silently never run is fixed. §L EventDrivenArchiver + §M PanelConveneConsumer merged into V4EventRuntime.layer, sharing the ONE DeepAgentEventBus + ApprovalQueue with producers (no split-brain): - archiverLayer runLoop = v4EventDrivenArchive || v4MultiAgentRuntime (it consumes BOTH session.completed and goal.completed triggers; must run if either producer is live else that trigger's delivery-tracked pending row never acks). - panelConsumerLayer runLoop = v4PanelAutoConvene. makeEventPanelPort builds the REAL event-driven PanelConvenePort (root session + panelist runner + consultPanel), mirroring the HTTP panelConsult path — not a stub. Risk class → quorum policy. - Both default OFF ⇒ no subscription ⇒ no group registration ⇒ no pending-row pileup. P2.10 — daemon-fiber InstanceRef die fix (the highest-impact find): makeEventTurnRunner and makeEventPanelPort called agents.get/defaultAgent/defaultModel OUTSIDE withContext. Those resolve through InstanceState.context which Effect.die's when InstanceRef is absent — and a daemon subscription fiber carries none. A die is a DEFECT that pierces orElseSucceed (E-channel only, verified empirically), hitting the outer catchCause → EVERY event-driven turn silently returned failedTurn. The entire multi-agent event- driven execution chain never actually ran in prod. Fix: load ctx first (load produces ctx, needs no InstanceRef), then run all InstanceState-touching calls inside withContext. Regression locked by tests that invoke the runner/port with NO ambient InstanceRef (fail before the fix via die, pass after). Also unions P2.8 (push stack) + P2.9 (FileLock/symbols) additions into this file's imports, runtimeLayer, and the master mergeAll. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The §B2 push stack (policy/rate-limit/quiet-hours/content-safety/audit) was built + tested but AgentPush.push had no prod caller; quiet-hours defaulted to false; the file-path ACL was deferred; DigestBuilder was unmerged. All four closed: - Real caller: new SupervisorNotifier — a standalone bus subscriber that pushes an IM notification for human-attention terminal events (agent.task.needs_human / goal.needs_human / goal.rolled_back / panel.verdict[needs_human], via the shared shouldQueueForApproval fold). Authorized as the runtime trust-root (workspace-push permission, system audit identity, workspace-scoped targets — not member spoofing); rate/quiet/content gates still run after. Avoids the hot files by design. - Real §E4 quiet-hours: resolveWithinQuietHours reads WorkspaceConfig.quietHours → QuietHours.isWithinQuietHours (was hardcoded false). Fail-safe: no window / lookup error ⇒ not quiet (courtesy throttle, not a security gate); high/critical punch through. - §E3 file-path ACL: allowedPathRoots threaded through PushFacts → ContentSafety.scrub, resolved from the workspace directory (was explicitly deferred). - DigestBuilder merged (flag-gated v4AgentPushEnabled) so held normal/low pushes flush. All gated on v4AgentPushEnabled (default OFF): push returns flag_disabled, notifier doesn't subscribe (no pending-row pileup), digest daemon dormant. Follow-ups (opt-in milestone): real project-roots resolver for wrk_ workspaces (path-ACL off there today); per-agent push-rate visibility counter (shared SYSTEM_PUSHER budget). Push-stack layer wiring lives in v4-event-runtime.ts (committed with P2.7). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raph symbols (P2.9) §C3 isolation was three dead gaps: the runtime never acquired file locks, semantic conflict detection passed symbols:[] hardcoded, and there was no per-agent isolation. - §C3.1 file locks: coordinate() now acquires agent-kind FileLock entries for a subtask's fileScope AFTER the security/autonomy/conflict/concurrency gates and BEFORE running; on contention (acquire returns null — another agent OR a human holds it) it releases any acquired locks + the concurrency slot and DEFERS (retryable), never runs. Locks release in the same ensuring finalizer as the concurrency slot (success/failure/ interruption). The lock is the SAME process-wide FileLock singleton the file HTTP handlers use, so a human editing a file blocks an agent subtask (§C3 human-blocks-agent). - §C3.3 semantic conflict: symbols:[] replaced with a symbolsForFiles resolver that reads the code graph (new code-indexer.symbolsForFilePaths → fully-qualified path#symbol keys so same-named symbols in different files don't false-conflict). Fail-safe: catchCause ⇒ [] (missing graph / bare wrk-id / any error) so file-level detection holds and coordination never crashes. Safe on the daemon fiber (opens a store by path; no InstanceState access). - §C3.2 branch/worktree isolation: intentionally DEFERRED (documented) — FileLock (§C3.1) + ConflictArbiter (§C3.3) provide the concurrency-safety guarantee (no two admitted subtasks edit the same file/symbol) without separate worktrees. Provides FileLock into the runtime (server.ts + v4-event-runtime.ts runtimeLayer, committed with P2.7). Security gate (P0.1) untouched — locks acquired only after it passes. Follow-up: symbol keys are host-qualified so the semantic layer is subsumed by file-scope within one event; cross-file symbol-move detection is future work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dgets to 15s Two aggravating factors caused the 9 cancel/loop tests in prompt.test.ts to intermittently exceed their 3000ms per-test deadline under the parallel validation harness (turbo typecheck+lint+test across all packages): 1. The dead-URL provider config (localhost:1) fell through to the AI SDK's default maxRetries:2, triggering 3× retryWithExponentialBackoff attempts on ConnectionRefused and burning ~6s+ in the worst case. Fixed by adding maxRetries:0 to the cfg.provider.test.options block so refused connections fail immediately. 2. Even without the retry spike the cancel tests take ~2s each in isolation (layer setup + TestLLMServer), leaving only ~1s margin against the 3000ms budget. Any event-loop contention under parallel load pushes them over. Raised all 9 budgets from 3_000 to 15_000ms — still tight enough to catch a real hang (no legitimate cancel/loop test should take >1s), but immune to parallel-load jitter. Full prompt.test.ts: 60 pass / 1 skip / 0 fail. Cancel suite in isolation: 11/11 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r, task_status, worktree teardown, bounded output 1a+1b: flag-gated (DEEPAGENT_CODE_SUBAGENT_TIMEOUT_MS) timeout+takeover as one atomic unit — timed-out/crashed attempts are cancelled, their worktree is recycled, and a brand-new child session respawns from the same fork base (single-driver), bounded by DEEPAGENT_CODE_SUBAGENT_TAKEOVER_LIMIT (default 2); default path is byte-identical when the flag is off. 1c: task_status read-only tool listing this session's dispatched subagents. 1d: worktree teardown after markFinished (safeRemove fail-closed; force only for takeover recycling). 1e: renderOutput maxChars bound (DEEPAGENT_CODE_SUBAGENT_OUTPUT_MAX_CHARS; undefined = full text as before). Tests: 9 takeover + 2 task_status + 24 existing task tests all green.
…+ reharvest dedupe Third recurrence of the plan-gate/bash-reliability failure. A 4-subagent audit plus a 68-session empirical census (677 hard blocks across 49/68 sessions; 172/188 "Plan gate" banners rode on exit-0 successes; 300 exit-0 results carried failure-shaped text; 229 reasoning blocks re-litigating fake failures) showed the deadlock was never only the stale latch that prior fixes kept neutering. Three compounding root causes: 1. U9 per-step-binding hard block was the live deadlock (hooks.ts): it blocked mutating tools when no plan step was active, its grace release was non-sticky (reset on every pass -> block-block-block-pass, ~75% denied), and it lacked the planExists guard stopHookGate has (blocked runs that never made a plan). planGate is now WARN-ONLY at the tool call; plan discipline is a nudge and is enforced only at finalization. Added planExists guard so no-plan runs are not nagged. tools.ts passes planExists; the "block" branch is kept only as a defensive fail-closed path for future safety hooks. 2. ShellTool emitted no exit marker in model-facing text (shell.ts): the exit code lived only in metadata.exit, which is not serialized into the transcript, so the model and extractValidationResults had to guess pass/fail and defaulted to FAIL. Now appends a ground-truth `exit code: N` (or `null (terminated)`) as the last line. extractValidationResults reads the last exit trailer as authoritative and only falls back to text (requiring a positive FAIL signal, not absence-of-PASS) when no trailer exists. 3. Stale evidence re-harvest (request.ts + round-state.ts): validationFingerprint now keys on command+exit_code only (was command+exit+output, defeated by volatile durations/timestamps). Dedupe moved to the single append site addCandidate so both the request-prep and micro-round driver paths are covered. Verified: core deepagent 764/0, deepagent-code session 633/0, gate suites 78/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire Kimi/Moonshot in as two official providers, mirroring the Zhipu setup: - kimi-for-coding api.kimi.com/coding/v1 (@ai-sdk/anthropic; /v1 mandatory, SDK appends /messages — verified live: 200 w/ v1, 404 without) - moonshotai-cn api.moonshot.cn/v1 (@ai-sdk/openai-compatible; probed 401 on /chat/completions, 404 on /messages = OpenAI-only) Both faces resolve endpoint/protocol/models from the models.dev catalog and their icons already ship in the sprite sheet, so registering the ids in OFFICIAL_PROVIDER_IDS is enough to make them recommended-list + auth-keyed. transform.ts Moonshot schema sanitizer now also covers moonshotai-cn and kimi-for-coding (its short ids k2p7/k3/highspeed don't contain "kimi"); catalog-spec normalizeModelID strips kimi/moonshotai vendor prefixes. Also standardize official-provider connect-dialog notes: google had a note string but was never wired into PROVIDER_NOTES; xai (grok) and the zhipuai/ zai coding-plan faces had no note at all. Add en+zh strings and matchers in both settings-providers.tsx and settings-v2/providers.tsx. Verified: tsgo clean (core/deepagent-code/app); deepagent-code provider tests 383 pass (1 pre-existing azure/gpt-5 failure unrelated); app unit 579 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
三个问题一并落地(设计见 docs/app-dock-panel-redesign.md,本地): #1 侧栏可开终端:终端/调试控制台成为「可移动 dock 面板」,可停靠在 底栏或右侧面板。侧栏 rail 新增 terminal/debug-console 两项(仅当其 location=side 时出现);header 终端按钮 + terminal.toggle/open 命令 改为 location-aware,按当前停靠位置开合。 #2 分界线清晰度:去掉底栏面板根的静态 border-t(与拖动条重叠、抢视觉 的那条),底栏 ResizeHandle 改用常显的 VSCode 风格分界线 (resize-handle--dock:常态 1px border-base,hover/active 增粗到 2px border-strong-base),成为唯一、最高优先级的可拖动线。 #3 底栏/侧栏功能分区:建立统一的 dock-location 模型(layout.dock,全局 态)。可移动集合 = {terminal, debug-console};侧栏 11 个富面板维持 侧栏原生。两处各加「移到侧栏 / 移到底栏」按钮。 实现:抽 terminal-view.tsx 为位置无关共享组件(DebugConsole / TerminalPanes / TerminalActions / useTerminalLifecycle),底栏 TerminalPanel 与新 side-panel-terminal.tsx 复用同一份;PTY 是 workspace 级单例,同一时刻只在一处挂载,无双份状态。layout.tsx 加 dock store + API,rightPanelMode 联合类型加 terminal/debug-console;无需数据迁移。 验证:app tsgo typecheck 通过;test:unit 579 pass / 0 fail。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Q5: quick-start now uses `deepagent-code run` (not bare positional)
Q6: npm install commented out (registry 404); install script points to deepagent.ltd
Q7/Q8: unified domain to deepagent.ltd and org handle to deepagent-ltd:
- prompt/default.txt + anthropic.txt: lessweb→deepagent-ltd, deepagent-code.ai→deepagent.ltd
- packages/deepagent-code/README.md: deepagent-code.ai→deepagent.ltd
- containers/ Dockerfiles + build script: ghcr.io/anomalyco→ghcr.io/deepagent-ltd
- script/publish.ts: lessweb→deepagent-ltd, anomalyco homebrew tap→deepagent-ltd
- github/ README + action.yml: lessweb→deepagent-ltd
- script/github/close-{issues,prs}.ts: lessweb→deepagent-ltd
- nix/deepagent-code.nix: homepage deepagent-code.ai→deepagent.ltd
Intentionally left alone: $schema URLs (live schema validation endpoints),
api.deepagent-code.ai in github/index.ts (runtime API), infra/stage.ts,
parsers-config.ts wasm URLs (external repos), app/dist/ (build artifacts),
observability test fixtures, ghostty-web npm dep.
Q1: README badge corrected Desktop 1.4.1→1.4.2
Q10: SECURITY.md: supported line updated from `main` to `dev`/current release;
V3.5 M-CRED forward-reference replaced with current V4.0+ status (macOS live,
Linux/Windows fallback documented)
Q12: CHANGELOG: added entries for V3.5, V3.8, V3.9, V4.0, V4.0.3, V4.1, V4.0.4
Q13: packages/app/README.md: replaced Solid/pnpm starter boilerplate with real
stack description + E2E testing section preserved
Q14: packages/web/README.md: replaced Starlight starter boilerplate with real
Astro/Starlight docs site description
Also confirmed already done (no code change needed):
- I33-3: subagentIsWriteType already gates worktree isolation in task.ts spawnAttempt
- I33-5: --no-ext-diff --no-textconv already in safeGit (384ab04)
- S41-1: steer-hint already has bg-background-stronger opaque surface
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t gate (v4.0.4) H32-1 — DurableKnowledgeStore shared-handle injection: - DurableKnowledgeStore constructor now accepts an optional injected DocumentStore (sharedStore?). When supplied the shared instance is used directly, enabling CAS/SSOT participation. When omitted a new isolated DocumentStore is created as before (backward- compatible; all existing callers without a shared handle continue working unchanged). - openProjectStore / openUserGlobalStore factory functions thread the optional shared handle through to the constructor. - knowledge-source.configure(dir, sharedStore?) accepts and stores the optional shared DocumentStore; userGlobalStore() / projectStore() lazy-factories pass it to the factories. reset() clears it alongside baseDir. This is the correct injection seam: the gateway (agent-gateway.ts line 278) can now pass a shared DocumentStore when calling DeepAgentKnowledgeSource.configure, enabling full CAS/SSOT alignment with plan/session DocumentStore operations. The gateway does not yet pass a shared handle (knowledge roots are a distinct storage area from plan/session roots); the interface is the established foundation for that wiring. F30-3 — ship-gate snapshot association: - Added optional snapshotId field to DeepAgentPromotionInput schema (groups/deepagent.ts). Optional for backward compatibility; soft enforcement now (warning logged if absent). Hard requirement deferred to v4.0.6 once all callers are updated. - promote handler (handlers/deepagent.ts) warns when snapshotId is absent, documenting the F30-3 intent and v4.0.6 hard-enforcement date. The warning is operator-visible so callers can be updated proactively. tsc --noEmit clean on both packages/core and packages/deepagent-code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age (v4.0.4) Implements the two stub backends that reported available:false since v3.5: libsecretBackend() — Linux Secret Service via secret-tool CLI (libsecret-tools): - available(): linux platform check + secret-tool in PATH + daemon alive probe (exit 1=daemon up/not found → available; exit 2+=daemon absent → unavailable). - put(): Bun.spawn with stdin pipe so secret never appears in process table. - get()/remove(): secret-tool lookup/clear. - Validated pattern: Ubuntu 22.04 + gnome-keyring; remote verify via SSH port 5070. dpapiBackend() — Windows Credential Manager via PowerShell PasswordVault (WinRT): - available(): probes Windows.Security.Credentials.PasswordVault in PowerShell. - put/get/remove(): PasswordVault.Add/Retrieve/Remove (DPAPI-backed by OS). - Not yet verified on Windows machine; available() returns false if WinRT absent so the 0600 file fallback activates safely on unverified environments. Existing behaviour preserved: macOS unchanged, fileBackend fallback unchanged, selectBackend() order unchanged, all existing tests use inMemoryBackend injection. tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ffset, priority queue (v4.0.4) K40-1 — Transport seam (deepagent-bus-transport.ts): New BusTransport interface abstracts the storage primitives the Event Bus runs on: persistEvent (idempotent), findEventsSince, registerGroup/unregisterGroup/groupsForType (consumer-group registry), writeDelivery/readDelivery. The current SQLite+Drizzle implementation already satisfies this contract; a future Redis/Kafka backend implements the same interface. Contract tests can run against ANY Transport to verify T1-T4 invariants. This is the seam — the bus layer composes Transport + in-memory fan-out. K40-2 — Persistent consumer group offset (deepagent-event-sql.ts + event-bus.ts): New table deepagent_consumer_group (group_id PK, type_filter nullable, registered_at, last_seen_at) + migration 20260719000000_deepagent_consumer_group. - registerConsumerGroup(groupId, typeFilter?) — durable upsert; idempotent. - unregisterConsumerGroup(groupId) — remove from durable registry. - publish now calls groupsFor() which unions BOTH in-memory live groups AND DB-registered groups (deduped). An offline group that registered durably receives pending delivery rows and catches up via dueRetries + replay on reconnect. - subscribe (grouped) updates last_seen_at on stream start/end so a future sweep can prune permanently-offline groups. Previously a group was purely in-memory: it only existed during a live subscribe stream, and an offline group received NO delivery rows. This closes the delivery gap entirely. K40-3 — Priority queue dispatch (event-bus.ts): New highPriorityLive PubSub alongside the existing live PubSub. publish now routes critical/high events to BOTH channels; normal/low go to live only. subscribe for grouped consumers gets a merged stream (Stream.merge(highPriorityLive, live)) so critical/high events drain ahead of queued normal/low — true preemption for grouped delivery. Anonymous subscribers keep the simple live path (they see everything already). Previously critical only bypassed admission (tryPublish rate limit); dispatch order was strictly FIFO. Now critical/high are pulled first when multiple events are queued. 190/190 tests pass. tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, workspace config API Block 2 — Fork + PR 队列协作原语 - pr-queue.ts: FIFO 状态机 (draft→awaiting_review→approved→merging→merged/conflicted), 单 parent 合并租约,重做上限 3 - pr-collaboration.ts: coordinator admit/commitWorker/mergeApproved; mergeApproved 新增 parent HEAD 基线比较(parent前进→review-needed), non-conflict failure 同样执行 abortMerge(commit 之前的漏修) - review-contract.ts: senior-reviewer 赋写权 + commit 权,禁止 merge/task_spawn/queue_mutation - task.ts: 自动写隔离回流从 Worktree.mergeBack(目标默认分支,语义错) 改为 Git.mergeInto(parentDir, workerBranch);加 parentHead 基线比较和 abortMerge 清理 - 新增测试: pr-collaboration 3个实 Git 用例(happy path/parent前进/non-conflict failure) + pr-queue 状态机覆盖 + review-contract senior 权限断言 Block 3a — 订正陈旧 token 注释 - multi-agent-runtime.ts:396 注释曾声称 event turn runner "reports 0 today", P4.1 已接入真实 token,注释与代码矛盾;已更新为当前准确描述 Block 3b — 事件自主能力默认开启 - runtime-flags.ts: v4AgentPushEnabled/v4PanelAutoConvene/v4EventDrivenArchive 由 bool(默认 OFF) 改为 stableOn(默认 ON);各自有次级防线确保安全 - bash.ts: git push 遇策略 deny 时不再硬报错,改为向 ApprovalQueue 投递 AGENT_TASK_NEEDS_HUMAN 事件;队列不可用时降级为原 ToolFailure - 新增 workspace config HTTP API: GET/PUT /workspace/:workspaceID/config/trusted-sources 让 operator 通过 HTTP 而非直接写 DB 来管理每个 workspace 的可信事件来源 - 顺带修复 deepagent-event-sql.ts 中一处断行注释(语法错误) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ost registry - terminal.tsx / terminal-panel.tsx / terminal-view.tsx: 终端面板支持底部/侧边栏切换, dock 分隔线样式优化 - session-side-panel.tsx / side-panel-terminal.tsx: 侧边栏终端集成 - use-session-commands.tsx: 终端面板命令接线 - panel-view-registry.ts: 面板视图注册表(新增) - problems-panel.tsx / problems-helpers.ts: problems 面板(新增) - problems-panel.test.ts: problems 面板测试 - session-header.tsx / session.tsx: 会话头部适配 - layout.tsx / layout-helpers.ts / layout.test.ts: 布局辅助层 - i18n/en.ts + zh.ts: 新增面板相关 i18n 词条 - e2e/regression/panel-hosts.spec.ts: 面板宿主回归测试(新增) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--- round-context deep fix --- session-state.ts: add suppressedFingerprints[] to SessionRunState + 5 helpers (suppressFingerprint/unsuppress/isSuppressed/clear/get). Backfilled in normalizeState. An acknowledged false-positive failure is no longer re-injected every round — the model stops re-litigating the same stale evidence. request.ts: filter suppressed fingerprints before recordValidation / reharvest guard. Evicts stale suppressions when a command re-runs with a new exit_code so real regressions are never silently swallowed. No-op when suppressedFingerprints is empty (the common case). --- K40-4: real backpressure backlog depth --- deepagent-event-bus.ts: add pendingDeliveryCount(workspaceID) to Interface + implementation (JOIN deepagent_event on workspace_id, count pending deliveries). event-dispatcher.ts: wire true DB-backed queue depth replacing in-flight approx. v4-event-runtime.ts: pass pendingDeliveryCount with maxQueueDepth:500. --- O40-N3: quiet-hours for tick path --- goal-tick-consumer.ts: resolveTickQuietHours() mirrors event-dispatcher.ts math; when workspace is in quiet window the delivery is ACKed (no DLQ cost) and a fiber sleeps until window-end then re-publishes via resumeTickCommand (chain self-heals). goal-manager.ts: seed tickCommand carries workspaceID for quiet-hours resolution. --- goal.tick W2+W3 (event-driven tick chain) --- runtime-flags.ts: add v4GoalTickEventDriven bool flag (default OFF, independent of v4MultiAgentRuntime so the tick chain can be tested without the full daemon stack). goal-manager.ts: start() and resume() OR the two flags; comment updated to reflect dual-flag activation. goal-tick-consumer.ts: flag guard added; consumer registers in v4-event-runtime under anyV4DaemonEnabled || v4GoalTickEventDriven. --- H32-2: LearningWorker isolated reviewer injection point --- background-learning.ts: LearningWorkerInput gains optional reviewer injection. LearningWorker.run() / LearningQueue.drain() / drainNow() made async. Tests updated to match async signatures. --- G31-3: manageStream general-mode minimal audit --- agent-gateway.ts: GeneralAuditEntry type + generalAuditState map; manageStream taps finish events on the general-mode branch to accumulate turnCount / totalInputTokens / totalOutputTokens. getGeneralAudit() exported. --- I33-2: tool_failed → markPlanStale --- tools.ts: tapError on item.execute marks the session's plan latch stale with reason 'tool_failed' whenever a tool execution throws. Gate is warn-only (v4.0.4 P1) so this is a soft nudge, never a denial. --- I33-4: bounded subagent output (already wired) --- Confirmed already implemented: renderOutput() truncates at maxChars with a truncation pointer; inject() passes flags.subagentOutputMaxChars. subagentOutputMaxChars flag at runtime-flags.ts:59. tsc --noEmit clean on packages/core and packages/deepagent-code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- goal-status-publisher.ts: add v4GoalTickEventDriven to GoalStatusPublisherDeps - goal-tick-consumer.ts: replace Effect.fork → Effect.forkScoped (matches codebase convention; Effect.fork not in API surface); cast forked fiber as Effect<unknown> to satisfy the narrower return type required by the handle path tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…GoalStatusPublisher call Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
lxrzlyr
pushed a commit
that referenced
this pull request
Jul 20, 2026
Closes # - [ ] Bug fix - [ ] New feature - [ ] Refactor / code improvement - [ ] Documentation Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR. **If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!** _If this is a UI change, please include a screenshot or recording._ - [ ] I have tested my changes locally - [ ] I have not included unrelated changes in this PR _If you do not follow this template your PR will be automatically rejected._ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Issue for this PR
Closes #
Type of change
What does this PR do?
Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!
How did you verify your code works?
Screenshots / recordings
If this is a UI change, please include a screenshot or recording.
Checklist
If you do not follow this template your PR will be automatically rejected.