The confirm dialog painted its panel with `var(--bg-surface, #1e1e2e)`, but
`--bg-surface` is not defined by any stylesheet in the project. Every theme
therefore fell through to the hardcoded dark `#1e1e2e`.
That went unnoticed under the six dark themes, whose `--text` is light and so
still contrasted against the dark panel. The paper theme is the only light
palette: its `--text` is `#1e293b`, which put dark text on the dark panel at
roughly 1.05:1 contrast and made the title and body invisible.
Point the panel at `--bg-elevated` so it tracks the active palette, and give
the title and body explicit `--text` / `--text-secondary` colors instead of
relying on inherited color plus `opacity: 0.7`, which is equally unreliable
over a light surface. Add a `--border` outline so the now-white panel still
reads as a distinct layer above the scrim.
Verified by hand across all seven themes.
Report chain: when consecutive report cards exceed the failure limit the
parent now fails through transition_work_item (settlement, dependents,
manager visibility) instead of parking forever in AWAITING_MANAGER_REVIEW
behind a metadata hold no reconcile path could clear. The hold stamp
survives only as the quarantine fallback when the FAILED write does not
land, mirroring the attempt ledger's terminalize pattern.
Cursor prompt spill: one stable file per task (retries overwrite instead
of accumulating) and a self-ignoring .gitignore so workspace git never
picks up .opc/external_prompts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On Windows, an npm-installed `claude` resolves to a .cmd shim, so
_resolve_launch_command wraps it in `cmd.exe /d /s /c ...`. cmd.exe treats a
newline inside an argument as end-of-command, silently truncating a multiline
prompt at the first line break -- the agent received only the "## Task Brief"
heading and replied that no task was included.
Detect this case in _interactive_prompt_transport and deliver the prompt over
the existing stdin channel instead. The guard requires all three of: nt
platform, a newline in the prompt, and a command that resolves to .cmd/.bat.
It reuses _resolve_windows_command_shim so the check matches the same
resolution logic that decides whether cmd.exe wrapping happens.
Also records prompt_transport_reason in the stdin metadata to distinguish this
trigger from the pre-existing oversized-prompt path.
Verified against the real claude CLI: transport flips to stdin and the CLI
confirms all prompt lines arrive intact.
Cursor-agent puts prompts on argv, so oversized company/report handoffs hit
OS ARG_MAX, crash the card as FAILED, and reconcile kept minting new Report
attempts forever. Spill large Cursor prompts to a workspace file and hold the
report chain after consecutive failures.
Review tasks and review work items were prepended (appendleft) to the
role dispatch queue, so whenever a reviewer had more than one review
waiting, the newest submission was always claimed first. Under a
sustained flow of submissions the oldest review could be postponed
indefinitely because every new arrival jumped ahead of it. Observed on a
real run: with two reviews waiting on the same manager seat, the one
created 30 seconds later was reviewed first while the earlier one waited
another seven minutes.
The prepend was redundant for its stated purpose: review-before-regular-
work priority is already enforced at pop time, where
_pop_next_queue_entry pulls the first review entry found anywhere in the
queue. Its only net effect was inverting the order among reviews.
The role serial queue (FIFO, on by default) could not compensate:
its enqueue hook only fires on phase transitions, and review work items
are inserted directly in READY, so they never enter the serial queue.
Fix: append review entries like everything else. Reviews now drain in
arrival order among themselves while still preempting regular work,
including on the blocked-manager soft-wake path.
Tests: three regressions (pop drains a review backlog oldest-first;
enqueue_runnable_work_items preserves arrival order across batches —
the observed inversion scenario; same contract for the review-Task
path). The pre-existing queue-layout assertion that encoded the old
prepend behavior now asserts the pop-time preemption contract instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A company goal turn can hold the per-task session lock for hours while its
live dispatcher waits on AWAITING_HUMAN approval cards. The card answers are
themselves session messages, so they queued behind that same lock — a
three-way circular wait (dispatcher waits for the answer, the answer waits
for the lock, the lock waits for the dispatcher) that left late approval
clicks recorded but never delivered, and the parked branches wedged forever.
Timely clicks were unaffected because the inline-wait reply path resolves a
future without touching the lock, which is why only late approvals failed.
Three legs, all verified live on a wedged production run:
1. Lock-free answer path (ws_handler): a reply that explicitly targets a
pending task_user_input / company_work_item_gate checkpoint while the
task lock is held by a live turn is delivered straight through the
engine's checkpoint-resume channel. With a live dispatcher the engine
only persists the input, applies the approval decision, releases the
human wait, and wakes the loop — no second dispatcher, no re-entry.
When the lock is free the serialized path is kept unchanged. Failures
surface to the user instead of silently queueing behind the wedge.
2. Approval treadmill: company runtime parks persisted the blocked call
without its arguments, so the OBS-7 decision bridge could not rebuild
the allowlist context — a late approve resumed the task but recorded no
grant, and the identical command re-blocked and re-parked on a fresh
card every cycle. The runtime park artifact now persists tool_args, the
decision bridge falls back to permission_requests when
pause_request.permission_context is absent, and the legacy checkpoint
migration preserves existing permission_requests entries instead of
rebuilding them empty.
3. OPC_ESCALATION_TIMEOUT_SECONDS env override for the inline approval
wait (default unchanged) so harnesses can exercise the expire/park/
late-click cycle in seconds.
Live verification on the wedged run: both stranded cards resumed (the
second through the lock-free path while the first held the lock), a fresh
10s-expiry card answered late resumed within one second, the decision
bridge recorded the grant on reply, and the run converged to delivery.
Regression: 6 new lock-free path tests + 2 decision-bridge tests; full
suite 1932 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approving (or sending feedback on) the delivery review card runs employee
self-evolution as company work items, but the finalizer required the
turn's FINAL chat text to be bare JSON. The hardened native runtime
appends a verification status line to every final text and the manager
dispatch guard displaces the final message with a justification, so every
reflection died with invalid_self_evolution_json even when the model
produced valid patches on all three attempts, and the FAILED items
polluted the delivered run's terminal verdict.
- Add a submit_self_evolution_patches tool as the authoritative result
channel (exposed only on self-evolution work items, approval-exempt).
The text parser becomes a fallback that scans fenced blocks and
balanced JSON objects, and retry feedback now carries the concrete
parse failure plus the tool instruction.
- Settle abandoned reflections as CANCELLED (self_evolution_abandoned)
and exclude kind=self_evolution from run-lifecycle settlement so an
opt-in reflection can never dirty a delivered run.
- Claim the review card with a consuming CAS before spawning (duplicate
approve/feedback replies answer idempotently instead of re-entering),
bound the reflection run with a 40-minute deadline that cancels
leftover self-evolution items, and hand the claim back to pending when
the consumed run crashes mid-flight.
Verified live on real runs: the unfixed code failed the approve path in
90s with zero patches recorded; with the fix both the approve and the
feedback paths recorded patches end-to-end (CEO->COO and CEO->CMO
cascades, zero retries, human feedback reflected in patch content).
tests/: 1924 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OBS-11 — stop/resume killed pure-native runs over a phantom external pin.
Role templates' preferred_external_agent leaked into execution identity even
when the user requested native and execution actually ran native; on resume
the availability gate trusted the pin and failed every non-terminal item.
Root fixes across the whole chain:
- Staffing card per-role defaults are now the RESOLVED backend (explicit
session agent choice > runnable template preference > native), never a
hardcoded external default; seat enrichment and the dispatch selector's
locked branch downgrade provably unavailable externals to native and
record the wish in execution_agent_unavailable.
- The resume availability gate fails closed only when a resumable external
session actually exists; a bare pin heals to native (snapshot AND task
durable identity) and the run resumes — mirroring dispatch fallback.
- Suspend-checkpoint replies: force_resume (chat/headless spelling) is
recognized alongside ui_force_resume, and bare continuation tokens
(English and Chinese spellings) take the plain-resume path instead of
being routed to the final decider as content, which reopened the
already-approved intake card.
OBS-5 — failed runs never closed and dropped new input. The dispatcher's
convergence exit now settles terminally-failed runs (status=failed,
lifecycle=closed_failed, run_failure metadata) and emits a
company_run_failure_review card whose replies never swallow messages:
dismiss acknowledges, content falls through so normal routing starts a
fresh run. _maybe_resume_existing_company_runtime no longer re-executes a
terminally-failed tree: control replies get an honest closed status,
content-bearing input starts a new run.
OBS-6 — provider quota exhaustion terminally failed work items. Rate-limit
rejections are classified (LLMProvider.is_rate_limit_error, covering
status codes, exception types, and English/Chinese provider error text),
the agent runtime raises typed ProviderQuotaExhaustedError instead of
burning conversation-feedback retries, and the company dispatcher parks:
the item returns to READY (attempt interrupted, no terminal failure), the
member session idles, and claiming backs off exponentially (60s doubling
to a 900s cap; a quiet 30min resets the streak) before resuming
automatically.
Verified end-to-end on the real minimax-m3 campaign: same goal, same 300s
stop point, same run shape that previously killed the whole tree within
90s now resumes cleanly and completes with all items approved; staffing
defaults native for all 11 roles.
Tests: test_stop_resume_native_pin (10), test_run_failure_settlement (6),
test_provider_quota_park (9); attempt-ledger, recruiter, and
suspend-resume suites updated to the new contracts (their old assertions
pinned the defective behaviors).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OBS-4: checkpoint answers for a run whose dispatcher is still live no longer
re-enter _execute_company_mode (re-entry reset live claim registries and the
attempt ledger stamped in-flight cards interrupted until the streak limit
killed them). The executor keeps a _live_run_dispatchers refcount; task and
peer checkpoint resumes deliver the input in place and wake the dispatcher.
Runs without a live dispatcher keep the original re-entry resume semantics.
OBS-7: approval decisions expressed through the chat/checkpoint route now
reach the approval engine instead of being parsed as plain task input
(which _ask_user treated as deny, re-escalating until the card died).
normalize_escalation_reply maps decision tokens/synonyms (never silently
denies free text), escalation_context_for_blocked_tool rebuilds the
allowlist context from pause_request.permission_context, and
_resume_task_checkpoint applies the grant via
apply_deferred_escalation_decision — the same engine path as the UI card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A member session parked as blocked with focus on a terminal (or runnable, or
vanished) review card kept the dispatcher skipping its runnable work items
forever — the preempt-restore race leaves focus on an already-approved card
and every existing self-heal only recognized the runnable/missing shapes.
claim_runnable_tasks now converges such sessions to idle before the
blocked-skip branch, and the skip log carries focused/focused_phase for
forensics. (OBS-8; production-verified self-heal in the t4 campaign run.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Work items re-entering a runnable phase (ready/ready_for_rework) now have
ownership released unconditionally at the store layer: claim CAS no longer
consults metadata mirror keys (columns are the only ownership truth),
update_delegation_work_item blanks columns+mirror on any runnable-phase
write, review REJECT resolution releases ownership when the target phase is
runnable, and the startup sweep also covers runnable-phase residue. Closes
the 0011 rework livelock (stale four-field claim CAS vs. un-released claim).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Align native context management with the Claude Code / Codex model:
entry-capped tool results, history frozen below the threshold, one
high-quality summary at the wall — instead of the old pipeline that
microcompacted old messages from 60% usage and hid everything past 40
messages behind a snip marker with no summary.
- context pipeline: history below the hard threshold is never rewritten
(model quality and prompt-cache prefixes depend on byte-identical old
messages); the 60% tool-aware microcompact and the 40-message history
snip move to an emergency-only fallback used under overflow pressure
when the summarizer is unavailable or circuit-broken.
- durable compaction (was a stub): at usage >= context_guard.hard_threshold
(now 0.90, soft_threshold removed) the old span is folded into a
9-section summary via the new HistoryCompactor.summarize_runtime_history,
keeping the system head, the seed user request verbatim on every round
(injected session-memory/artifact messages shift the stale
base_prefix_len, so the fold start is structure-aware), and a
pairing-safe recent tail. A previous summary stays foldable, so exactly
one summary exists at a time and rounds chain.
- token accounting anchors on the provider-reported prompt size of the
latest request (max with the local estimate).
- reactive_compaction.circuit_breaker_failures (previously unread) now
stops repeated summarizer failures; provider overflow errors retry
through the same pipeline, summary-first.
- tool-result budget clip keeps head and tail instead of tail-chopping.
- chat-side transcripts get the same treatment: new
MemoryManager.maybe_compact_session_history wires the threshold-gated
maybe_compact_session into secretary, office_ui dispatcher, and
context_loader before prompt building, closing the unbounded-growth
path; dead no-op compactor entries (maybe_compact_after_message,
should_compact_prompt) removed.
Verified by 13 new tests (history sanctity below threshold, multi-round
single-summary/seed-verbatim/chain invariants, breaker, emergency
fallback, provider-overflow end-to-end recovery) plus a live-provider
probe: multi-round compaction with the model completing correctly from
summarized context. Full suite: 1859 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Suite went from 27 failures plus one permanent hang (never finished) to
1846 passed / 0 failed in ~85s, including under FORCE_COLOR.
- office_shutdown_lifecycle: construct WSHandler via the real __init__
(helper) instead of hand-copied __new__ stubs that drift from the
constructor (#11 added _runtime_status_sync_task and the stubs hung);
the formerly-hanging wait now has a 5s wait_for.
- import-time patch hygiene: company_recruiter / company_reorg /
engine_session_defaults replaced module-level permanent
tempfile.TemporaryDirectory monkeypatching with paired
setUpModule/tearDownModule, fixing order-dependent sqlite failures in
transcript_pagination during full runs.
- stale tests updated to current product semantics: resume stubs use
status="done" (failed is deliberately non-resumable), fix4 asserts the
native review contract through build_company_work_item_contract,
delivery fixture carries user_visible/feedback_scope=final, ownership
doc names progress_log, session compression calls
maybe_compact_session(force=True) explicitly, hard delete removes the
work item row, parallel-isolation asserts delegate rebind and stubs
_get_project_delegate, role update goes through OrgService on an
editable custom org (plus read-only rejection case), collab_rpc patches
the single os.name decision point instead of poisoning pathlib, codex
no-pty builds inside the patch, identity-guard false positives reworded.
- cli_board actions rewritten against the real OfficeServiceFactory seam
with a tempdir OPC_HOME (old direct-engine stubs were never consulted
and the tests wrote into the real OPC home).
- cli_app assertions strip ANSI via _plain_output so a color-forcing
shell (FORCE_COLOR) cannot break plain-text expectations.
- deleted never-runnable test_org_concurrency (pytest.mark.asyncio
without the plugin, stdlib-only assertions) and three dead skipped
filesystem-handoff tests.
- pyproject: dev extra (pytest, pytest-timeout) and a 300s per-test
timeout backstop so a wedged test fails instead of stalling the suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The asset was moved from .opc/config/orgs/ in pre-squash commit 501a3a36
but never git-added at the new path; an untracked local copy masked the
loss. Restored from 5d06617b (.gitignore already whitelists
.opc/config/company_orgs/**).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- delete_work_item descendant cascade now goes through transition_work_item
(audit reason, attempt-ledger settlement, phase hooks) instead of raw
store phase writes plus manual task.status mutation; task-owned audit
stamps and execution-lock release only happen when a cancellation
actually occurred, removing a desync path (task=CANCELLED under
work_item=APPROVED) in drift scenarios.
- transition_work_item gains blocked_reason/handoff_status passthrough so
callers no longer need a second store write for the same transition.
- new build_company_resume_identity_restore helper in metadata_ownership
replaces the ad-hoc delegation_seat_id/role/session literals in
engine._restore_and_pin_company_resume_execution_identity, keeping seat
identity writes inside the ownership contract module.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #9 broadened token invalidation in two spots that also fired on
non-terminal results, breaking resume continuity for parked runs:
- _persist_session cleared the canonical role token and the task's
resume pin on any result.status != DONE. An approval park
(AWAITING_HUMAN) therefore wiped continuity right before the human
approved, and the retry restarted the external thread from scratch.
Both clears are now gated on _SESSION_INVALIDATING_RESULT_STATUSES
(terminal failures only).
- _stored_provider_token_allows_resume returned False whenever the
newest row for a token was not done/suspended, so a run parked on
approval vetoed its own token at the next restore. The verdict is
now tri-state: terminally failed rows still return False (wipe),
finalized rows return True, and live-but-unfinalized rows return
None (keep the pin) — except provider_stream tokens, which keep the
strict pre-existing rule via strict=True since an unfinalized stream
row may belong to a crashed attempt.
Regression tests: approval park keeps role token and task metadata;
restore keeps a canonical token whose newest row is awaiting_human;
unfinalized provider_stream tokens are still rejected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merges xiaodong-l's hardening work: company suspend-checkpoint resume
reconciliation, external session token lifecycle fixes, Windows launch
shims, codex shell_environment_policy forwarding, Office UI i18n
(en/zh-CN), and two org presets. frontend_dist is rebuilt from the
merged sources in this commit so the #11 runtime-status fixes and the
i18n bundle coexist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
# opc/plugins/office_ui/frontend_dist/assets/index-BgyI65M_.js
# opc/plugins/office_ui/frontend_dist/index.html
Live status deltas are one-shot best-effort broadcasts: a delta lost to a
disconnect window, a project-scope drop, or a not-ready store left the UI
stuck on a stale status ("thinking" vs stopped) until a hard refresh, which
rebuilds from the always-correct snapshot. Close the class, not the sites:
- Add a low-frequency runtime_status_sync reconciliation broadcast: every
12s (lazily started, cancelled on shutdown) re-broadcast the persisted
status + in-memory tracker state of every task with a live runtime, plus
one final tick for tasks that just ended. Candidates come purely from
in-memory registries (no table scans); idle system pays nothing.
- Frontend consumes it diff-before-dispatch: a tick where nothing drifted
triggers zero store updates and zero re-renders; clearing mirrors the
mergeLiveRuntimeField semantics already used by collab_sync.
- Fix the EventAdapter tracker state machine: tool_completed returns to
REFLECTING (the turn is still running), and turn_completed/turn_failed
now transition to IDLE and emit an authoritative idle runtime update.
- Guarantee the terminal board_task_status_changed in _run_session_task's
finally: a cancelled run previously skipped it, leaving the board on
"running". The fallback mirrors persisted state read-only.
Verified: 7 new tests in test_runtime_status_sync.py; 236 backend tests
pass with zero new failures; tsc clean; frontend structural tests pass;
dist rebuilt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three defects that kept a converged run bouncing forever, found by
replaying the issue #10 timeline against the real engine:
- follow-up routing did not exclude terminal cards: a FAILED
final-decider was clobbered to PENDING and then blew up the whole
resume turn with InvalidPhaseTransition: failed -> ready. Filter
FAILED/CANCELLED at selection and refuse terminal targets in
_prepare_company_followup_target (DONE stays eligible - the
approved -> rework reopen has a dedicated legal store op).
- with every routable decider terminal, the suspend checkpoint parked
pending forever and dead-ended each message on the same error.
Degrade to a plain runtime resume so the run converges and the
checkpoint drains, with an explanatory note.
- startup reconcile classified suspend-hold residue on terminal cards
as an interruption, rebuilding a pending company_runtime_interrupted
checkpoint on every boot. Scrub the residue instead so restarts are
idempotent.
scripts/verify_issue10_e2e.py replays the reporter's full timeline
(kill -> restart -> resume cycles, all external agents disabled) against
the real engine/store/dispatcher: startup only suspends, the codex-pinned
card fails closed with zero attempts burned, restarts stay converged with
idle CPU ~0%, and the kill loop terminalizes at the ledger limit. 11/11.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A company-runtime resume checkpoint pins each task to one exact
execution backend. When that pinned external agent is disabled in the
config (issue #10: all external agents off), every resume claimed the
card, transitioned it to RUNNING, and crashed deterministically in the
dispatch-time selector ("company runtime resume requires unavailable
external agent"), feeding the restart -> resume -> crash loop.
Gate availability at resume preparation instead, before any pin or
claim: fail the single work item closed (FAILED + diagnostic
blocked_reason + progress note) while the rest of the organization
resumes normally. attempt_seq stays 0 - no dispatch attempt is burned.
A missing adapter registry means availability is unknown, so the gate
fails open and leaves the decision to the dispatch-time guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Work items whose execution kept dying without a durable verdict (crash
mid-dispatch, kill -9, cancel-before-harvest) were re-dispatched forever:
every exit path was responsible for remembering to write a terminal
phase, and any path that forgot left the card RUNNING and eligible again.
Replace that with structural accounting:
- claim CAS opens an attempt in the same UPDATE (attempt_seq+1,
attempt_settled=false) so no dispatch can start unaccounted (store.py)
- transition_work_item becomes the settlement authority: every
non-RUNNING transition settles the open attempt in the same write;
crashed/interrupted outcomes accumulate streaks, clean outcomes reset
them; claim release folds into the same write; settlement still lands
when the phase write loses a race (work_item_transition.py)
- dispatcher refuses cards over the streak limits (crash>=3,
interrupted>=5) in both is_dispatchable and _work_item_is_runnable,
and a per-tick reconcile pass back-fills dead attempts as interrupted
and terminalizes over-limit cards to FAILED with a visible
blocked_reason (dispatch_hold quarantine if even that write fails)
(phase.py, company_mode.py)
- crash exits now settle: cancellation unwind harvests coroutines that
died on a real exception before discarding them, the crashed-item
handler releases the claim and settles as crashed with a quarantine
fallback, and the timeout path settles as crashed (company_mode.py)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root-cause fix for the project-4444 class of deadlocks: any FAILED/
CANCELLED work item with downstream dependents used to block its whole
tree forever, because the advancement gate required all dependencies to
be APPROVED and nothing ever propagated or triaged failures.
Settlement mechanism (work_item_transition.py):
- compute doomed set (FAILED/CANCELLED seeds contagious through hard
deps); settlement-released cards are treated as alive
- three-way advancement gate: all-approved (unchanged) / settled-with-
failures releases the nearest decision-capable card (manager parent ->
failure-triage synthesis turn, rollup delivery/aggregate -> READY)
with an atomic dependency_settlement stamp; released cards never
oscillate back
- claim/park/resume/dispatcher-tick all honor the stamp: runnable gates
admit settled failed+stuck deps, parking excludes settled deps and
re-arms triage when a failure raced the park, engine resume no longer
re-locks released cards, the dispatcher tick releases rollup cards
created after the failure
- settlement cascade: once the settled card is APPROVED, stuck children
the manager did not rebuild are cancelled (transitive closure over
stamped stuck seeds, retried until every cancel lands)
- info-class deps never block settlement; adaptive runnable gate now
shares DEPENDENCY_CLASS_DEFAULT with the release gates
Dispatch guard (company_mode.py):
- NO_DELEGATION_JUSTIFICATION parsing tolerates markdown decoration
(bold/lists/quotes/full-width colon) and rejects placeholder echoes
across all artifact/metadata/content channels
- retries exhausted no longer FAILs the work item: dispatch is a soft
constraint, so the turn output is accepted as normal completion and
annotated via manager_dispatch_guard_unresolved; the reminder loop is
unchanged, and the mutation flag is now reset per turn so one past
delegation can never mute future reminders
- manager board context now surfaces failed/cancelled children with
their preserved output and pending-cancellation stuck list so the
triage turn can rebuild, accept partial results, or escalate
Self-produced delegation output goes through review (persisted fact,
single predicate):
- the DONE transition classifies what a dispatch/intake/plan turn
actually delivered from store ground truth (live children => delegated,
none => self_produced) and persists turn_output_kind/-source on the
WorkItem
- is_manager_reviewable_turn honors the persisted marker, so the DONE
routing, report spawn, report completion and recovery scans all read
the same fact — this closes a pre-existing hole where
NO_DELEGATION_JUSTIFICATION output auto-approved with no review at all
- escalation requires a real agent manager above; top seats reporting to
the human owner keep auto-approve (covered by final delivery's human
acceptance) instead of minting unclaimable review cards
- dispatcher tick reconciles reviewable cards stuck in
AWAITING_MANAGER_REVIEW with no live report/review card by rebuilding
the report card idempotently (legacy DBs, crash windows)
Verified: 4444 tasks.db replay unwedges end to end; real-store
report->review chain exercised without lifecycle mocks (mutation check
confirms the tests bite); full suite failure set identical to a
same-session HEAD baseline run (all remaining failures pre-existing or
environment flakes reproduced at HEAD).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two user-visible defects in company mode, root-caused via project 6666/8888
DB forensics:
Progress-row flicker (thinking preview appearing/disappearing): progress
entries were broadcast to clients before reaching the persistence buffer,
so a tool_call-triggered session_detail snapshot rebuilt from the DB erased
freshly streamed entries from the live log. Buffer now fills before the
broadcast and session_detail flushes it before reading.
Native role transcripts incomplete (thinking only at start, no narration,
no final summary — external agents unaffected):
- thinking deltas shared one stream id per conversation turn while seq
reset per iteration, collapsing all iterations into one entry and
silently dropping live thinking from iteration 2 on; now keyed per
iteration like assistant deltas
- assistant_delta events were mapped to None; company mode now surfaces
them as streaming 'assistant' progress entries (rendered as Reply cards,
merged like thinking, excluded from inline chat rows)
- thinking was persisted one row per token, flooding the 1000-entry cap
and evicting interleaved tool history; append_progress now folds
streaming deltas per (type, turn, stream) with seq dedup
- the terminal company turn was hidden at summary detail and, worse, its
id-keyed backfill merge kept the first-inserted intermediate content, so
the final reply never reached any channel; terminal turns are now
flagged company_final_turn, visible at summary detail, and carry their
own ui_message_id so they insert as fresh rows
- appendProgressEntry applied its seq guard against unrelated entries when
the stream key was absent from the log, killing the first delta of any
fresh stream; the guard now only applies within the same stream
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Empty role tools lists resolve to every registered general tool for
native agents (external agents were never restricted), letting the
model pick tools itself across all five org configs (40 roles).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Company-mode UIs became unusably slow once work items and transcripts
grew. Three presentation-layer fixes, none touching work progression:
- App.tsx: buffer assistant_delta/thinking_delta store writes per task
and flush every 80ms instead of once per token; any non-delta event
for the same task flushes first so draft/clearDraft ordering is
byte-identical. The unconditional per-event setUiTick (whole-app
re-render per websocket event) is now a 300ms trailing throttle.
- PhaserGame: display:none does not stop requestAnimationFrame, so the
office scene kept burning CPU on every other page. The loop now
sleeps when the office page is hidden and wakes (with a parent-bounds
refresh) on return; bridge writes stay synchronous so no state is lost.
- Kanban collab_sync debounce 0.2s -> 1.5s: the broadcaster is a
trailing coalescer, so the final board state still always ships; each
fire is a full-project snapshot build, which at 5/s dominated backend
CPU on large projects. CommsPanel poll 8s -> 30s (comms_state_dirty
push already drives freshness) and its interval no longer pins a
stale onRefresh closure.
Verified: tsc + vite build, App.test.tsx / workItemSessions structural
tests, backend suites (company_review_flow incl. debounce push test,
kanban_push_runtime, actor_runtime_company_mode, task_mode_contract,
work_item_transition — 119 green), and canvas_smoke e2e against a real
server with zero console errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Project 3333 forensics: env_engineer sent a blocking question to the CTO,
got a full reply 96s later, and still deadlocked the whole run — the
park half (`_park_for_blocking_comms`) had no wired consumer, so
WAITING_FOR_PEER work items could never be released.
Unpark path (root fix):
- The dispatcher loop now calls `_try_unpark_blocking_comms` each tick
for parked, non-in-flight tasks; blocking replies land as durable
inbox files, so the check is read-only until all replies are present.
- `_try_unpark_blocking_comms` accepts orphaned waits (peer_wait
stripped by the legacy resolver while the work item stayed parked)
and falls back to the park predicate itself: an empty unresolved
blocking outbox releases the task, anything pending keeps it parked.
- `resolve_task_peer_wait` no longer touches comms_blocking waits (it
flipped task.status without the work-item phase and stripped the
peer_wait evidence); `_resume_peer_checkpoint` re-enters the company
runtime for comms/orphaned waits and lets the dispatcher converge.
File tools (defaults changed at their declaration sites, honoring the
"empty tools = everything, explicit list = exactly that" contract):
- corporate builtin groups gain file_write/file_edit for coordination,
QA, and data-acquisition roles.
- all shipped org YAML role tool lists gain the missing
file_write/file_edit entries.
- coordination turn modes no longer strip file_write/file_edit at
runtime — in-context content (briefs, matrices) must be persistable
instead of getting trapped in blocking DM hand-offs.
Also includes the pending office_ui ws_handler change from the working
tree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse the dual permission stack into one policy. The runtime-side
ToolPermissionResolver (own safe lists, own grant memory, bypassed the
ApprovalEngine whenever it said ALLOW) is deleted; runtime_v2 now consults
ApprovalEngine.predict(), a synchronous fast path reading the same config
and the same persisted allowlist as the async authorize pipeline, so a
grant given anywhere is honored everywhere. permissions.py keeps only a
policy-free adapter; the duplicated permissions_v2 config fields and the
runtime grant persistence loop are removed (stale YAML keys are ignored).
New shell_safety module becomes the single source of truth for shell
classification: flag-audited read-only commands (awk/od/jq/sed -n/diff/
git subcommand table/... auto-allow; find -delete, sort -o, curl -o/-d,
rg --pre still prompt even when the bare name is config-listed),
keyword-aware compound splitting (loop/branch headers no longer poison
grants), expansion-safe $() handling, and fail-closed treatment of
anything unparseable or substitution-bearing.
Grant semantics are rebuilt around derived word-boundary prefixes:
"python3 -c" instead of token bags, interpreter -c/-m kept in the prefix,
bash/eval/sudo never grantable as prefixes, read-only segments exempt
from the every-candidate-must-match rule so a granted command chained
with ls/echo verification passes, and approve-once now records the exact
candidates as a session grant so identical re-runs stop re-prompting.
The authorize heuristic also audits the original command text instead of
the quote-dropping preview (echo "<EOF>" no longer reads as redirection).
Validated live on zz_perm_probe1 (native minimal org): awk/od/ls/cat/
sha256sum ran with zero cards, python3 -c parked once and three different
python3 -c commands then passed via the persisted prefix grant, and an
agent-issued rm -f compound correctly re-prompted showing only the
segments needing approval. Full suite failures are byte-identical to the
pre-change HEAD baseline (27 pre-existing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four coordinated fixes for company runs permanently stalling around
tool-approval parks (project 1111 forensics + live reproduction):
- company executor dispatch loop now exits convergently when nothing is
in flight and every remaining task waits on a human: immediate parked
exit when all waiters have pending checkpoints, bounded stall ticks
otherwise. Previously it polled sleep(5) forever, hanging the turn
(observed 7.5h), never answering the user, and holding claims that
blocked any later rescue turn.
- _resume_task_checkpoint routes work-item runtime tasks through the
delegation state machine: release the human wait via the legal
AWAITING_HUMAN -> READY recovery exit, clear stale claims, and hand
the item back to the dispatcher (runtime-snapshot fallback when the
checkpoint payload lacks a plan) instead of a detached single-agent
re-run that never advances the work item phase.
- startup recovery reverse self-heal: an awaiting_human task whose park
checkpoint was already resolved (human answered, resume cut off before
the phase write) is reopened for dispatch instead of being preserved
as a wait nobody can end. Runs before the metadata-plan gate since
modern runs no longer carry the plan in task metadata.
- pause checkpoints record execution_mode from the durable
work_item_runtime marker instead of volatile task metadata, which
degraded to task_mode after a first resume and misrouted the next one.
Validated end-to-end on a live native-agent minimal-org run
(park -> approve -> re-park -> approve -> complete -> deliver, plus
synthesized crash-between-resolve-and-phase-write healed on restart).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Project 000, 19:21/20:27: the engine recorded its assistant reply in
session_messages, but the post-turn transcript sync never surfaced it in the
ui_state channel, and nothing reconciled afterwards — the user watched an
empty conversation while the reply sat in the DB. And at 19:13 the same reply
was persisted twice in one channel under `<id>` and `<id>::<project>::<channel>`.
Two fixes:
- _ensure_reply_projected: after the transcript sync, if the session's newest
persisted top_level_reply row is absent from the chat store (checked by
transcript message id, so nothing user-visible is ever duplicated or leaked),
insert and broadcast it directly. Reproduced by test: with the sync disabled
the reply previously never reached the channel.
- backfill_messages: a live insert racing the backfill snapshot now merges into
the existing same-scope row (new _merge_into_same_scope_row, also used by the
IntegrityError fallback) instead of minting a `::`-scoped alias id in the
message's own channel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Typed-3-shown-5 forensics (project 000): the WS client queues session_send
payloads while disconnected and flushes the queue after a reconnect, and the
server minted a fresh row id per delivery — so one typed message could land
as several user turns, each dispatched to the engine.
Every send now carries a client-generated ui_message_id (dispatchSessionSend
injects one when the caller didn't). The handler persists the user row under
that id and answers any later delivery in the same channel with an idempotent
ack instead of inserting and dispatching again. Because the row id now equals
the optimistic bubble's ui_message_id, the echo also merges with the local
message even after the transcript sync rewrites row metadata.
Same text intentionally sent again gets a fresh id and still starts a new turn.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A task_user_input checkpoint created by an approval escalation (payload
runtime_v2.permission_requests non-empty) used to capture the next plain chat
message in the session as its answer. With deferred approval cards now staying
pending indefinitely (c9018000), that implicit capture would swallow every
later conversation message into the approval reply.
Permission prompts are decided through their approval card, whose reply always
carries an explicit response_to_checkpoint_id; a plain message now falls
through to normal turn processing and the card stays pending and clickable.
Waits without a permission request (agent asked the user a question) keep
accepting typed answers unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts the revert e1c28c38, re-landing d502d66c.
Forensics on the project 000 incident show the original revert was a
misattribution: the failures observed at 19:00-19:21 ran on a server started
before the fix was committed (19:59), and the 20:27 failure was the separate
company_work_item_plan schema collision (fixed in the previous commit), which
this change never claimed to cover.
Re-landing is also now required by c9018000: the deferred approval-card click
path rewrites the reply to target the parked AWAITING_HUMAN checkpoint and
resumes through _resume_task_checkpoint — without this change that resume hits
the empty-task-list + MULTI_AGENT value-alias bug and returns an empty reply
on company tasks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every completed company session's follow-up was answered with the canned
"Legacy company runtime run ... read-only" text (project 000, 2026-07-07).
Root cause: snapshot loaders read task.metadata work_item_runtime_plan as a
serialized run-level CompanyWorkItemRuntimePlan, but work-item tasks persist a
per-item assignment spec (projection_id/turn_type/summary/deliverables/...)
under that key. from_dict on the wrong shape silently yields an empty plan
(no projections, empty metadata), _runtime_uses_multi_team_org returns False,
and the resume path falls through to the legacy read-only branch.
Add is_serialized_company_work_item_runtime_plan (a run-level plan always
serializes with projections + runtime_model; a spec always carries
projection_id) and route all full-plan metadata reads through
serialized_company_plan_from_metadata, which skips wrong-shaped candidates so
the loaders fall back to the sample-metadata-constructed plan instead of an
empty one. Fixes existing DBs read-side; no data migration.
Verified: old path on the 000 shape classifies multi_team_org=False, new path
True; regression tests cover shape discrimination, snapshot classification,
and the follow-up never reporting legacy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approval friction (harmless commands kept prompting):
- Persist "Allow for this session" grants to approval_allowlist.yaml under a
new sessions scope (capped LRU), hydrated lazily, so they survive `opc ui`
restarts and re-entering the session instead of living only in memory.
- Safe-prefix matching now accepts compound read-only commands: every segment
must match a safe prefix, and fd-duplication / /dev/null redirections
(2>&1, 2>/dev/null) no longer disqualify a command; real write redirections
(>, >>, <) still do. Default safe prefixes gain common read-only commands
(cd, cat, head, grep, git log, ...).
- First-use approval now gates only MEDIUM+ risk; heuristically LOW actions
proceed without a card.
- Shell-substitution detection flags eval/source only at command position of a
segment (no more false positives on `grep source file`); $(...) and
backticks still flag anywhere.
Approval card timeout redesign (deferred decisions):
- The card's structured approval context (action, allowlist patterns, scopes)
now travels through the escalation event into the persisted card metadata.
- Timeout without a default action no longer marks the card timed out, and the
session-detail reconciler no longer stales deferred-capable cards: the card
stays pending and clickable indefinitely, including across restarts.
- Clicking after the inline wait expired applies the allowlist grant
(approve-once grants the exact command at session scope), resolves the card,
and rewrites the reply to target the parked AWAITING_HUMAN checkpoint so the
task resumes through the normal message pipeline and the retried command
auto-approves. With no parked checkpoint the grant still lands and a helper
reply explains the state.
Verified: approval engine suite (40) incl. new deferred-decision and
compound-command tests, ws_handler + runtime suites green, real escalated
commands from project 999 replayed against the user's config now auto-approve
while pip install / $(...) / rm -rf / write redirects still prompt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A company-mode review left a pending task_user_input checkpoint behind: the
runtime carried the paused work item forward via approval-card grants and a
fresh review attempt without ever replying through the engine checkpoint. The
orphan row then captured the user's next chat message and resumed through the
deprecated multi-agent path with an empty task list, returning an empty reply.
Invariants added:
- write side: when a task settles (done/failed/cancelled) its pending
task_user_input/task_peer_wait checkpoints are superseded
- read side: checkpoint matching lazily resolves rows whose task settled or
whose linked work item reached a terminal phase (heals existing dirty DBs)
- resume: the primary task is always part of the resumed set; the
MULTI_AGENT/COMPANY_MODE value-alias no longer routes company checkpoints
into _execute_multi_agent (which silently returned "" on empty task lists)
Perf: get_latest_pending_checkpoint_for_session is called per task on every
UI sync tick, and its parent-session resolution loaded and JSON-parsed the
entire tasks table each time (24MB with inline artifact blobs) — a full core
pegged at 100% and the event loop starved so replies never surfaced. Now a
no-live-checkpoints fast path returns immediately, and the resolution uses a
targeted session_id query backed by a new tasks(session_id) index.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deleting a project closed its store but left the delegate cached in
_project_engine_delegates; re-creating a same-name project then reused the
zombie engine and crashed in get_session. delete() now closes and evicts via
_close_project_engine_store (also covers non-active deletes), the delegate
cache self-heals when a cached store is closed, and _engine_for_project
reopens a closed store for the non-evictable root engine case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approval cards raised by company-mode internal scheduling turns (review/report
work items, session ids like `<root>:review::<wid>::vN`) were posted to the
turn's own session channel, which the UI deliberately hides. The card silently
timed out after 300s and the work item parked on AWAITING_HUMAN, so users saw
only the gate card and never the approval prompt. ws_handler now detects these
internal turns and routes their escalation cards to origin_task_id, the root
session's primary task channel, or the activity channel — never the hidden one.
Also unblocks the previously dead origin/session fallbacks in the resolver.
Unclassified LLM stream failures (e.g. provider content-filter rejections like
"input may contain sensitive information") used to hit a blind truncate-retry
loop that replayed the identical payload for a dozen-plus consecutive failures.
runtime_v2 now feeds the provider's verbatim error text back into the
conversation as a "[runtime notice]" system message so the model can adapt
(rephrase, drop quotes, change tack), bounded at 2 feedback retries (counter
resets on any successful stream) plus one context-reset attempt, then fails
honestly with the real error. The blind truncate path remains only for
classified tool-protocol errors.
Verified: new end-to-end tests for recover-after-notice and bounded-failure;
runtime_v2 + ws_handler + escalation/approval + company-mode suites all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native agent progress panel (company mode):
- ws_handler: filter runtime bookkeeping noise (turn/status/member_inbox_updated),
keep tool_completed as tool_call, thinking summary previews content,
preserve raw thinking_delta fragments (no strip; skip whitespace-only)
- frontend progressLog: summarize thinking by content preview; merge thinking
by detail only so the 'Thinking' label never splices into text
- AgentProgressBlock: add bottom "Show more (N earlier steps)" toggle
ui_state.db "database is locked" hardening:
- ws_handler: isolate engine progress/kanban/runtime-event callbacks so UI
persistence failures never crash work items
- chat_store: busy_timeout, _retry_locked backoff, idempotent insert_message
(INSERT OR REPLACE), create_channel read-before-write to stop poll writes
- server: flock single-instance guard for `opc ui` per OPC home
Approval card duplicate-click bug:
- EscalationPanel: disable buttons on click with Submitting state and 30s
reconnect fallback
- ws_handler: stale-escalation branch checks real card status (new
chat_store.get_checkpoint_message); already-resolved cards get an accurate
"already handled (decision: X)" reply without being re-marked stale;
dedup identical helper messages within 120s to stop reply spam
Company mode prompt:
- add soft guidance that the runtime monitors state and re-activates roles,
so leaders need not poll work items after delegation/review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Office canvas fixes:
- Lazy-create the Phaser game via ResizeObserver on the first non-zero
layout instead of creating it inside the display:none office page with
inline px fallback sizes. Phaser's RESIZE-mode 500ms parent poll has no
zero guard, so the old path shrank the canvas to 0x0 (blue-black screen)
and later restored it to a stale wrong size (clipped office).
- On unhide, re-measure with scale.getParentBounds() before scale.refresh();
plain scale.resize() is clobbered by the stale cached parentSize in
RESIZE mode.
- Fix whole-game freeze when clicking an office card: camera effects
resolve ease names via EaseMap, which has no 'Cubic.Out' key, leaving
effect.ease undefined and killing the RAF loop with a per-frame
TypeError. Use 'Cubic.easeOut' for cam.pan in panToOffice/resetCameraView.
- Bound GameBridge queues (latest snapshot supersedes, event queue capped)
since game creation is now deferred until the Office page is first opened.
Sidebar:
- Add a collapse/expand handle on the canvas/sidebar boundary with a 220ms
grid transition; state persists in localStorage. The canvas follows the
column change automatically through the ResizeObserver path. Stacked
(<=1024px) layout collapses the bottom panel and moves the handle to the
bottom edge.
README:
- Add Simplified Chinese translation (README.zh-CN.md) with a language
switcher in both files; fix stale TOC entries in the English README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>