Skip to content

feat(chat): fork openwork as the CommonPlace chat register (OW1, OW3-OW6) - #158

Merged
Travis-Gilbert merged 18 commits into
mainfrom
claude/commonplace-openwork-fork-46bcd5
Aug 2, 2026
Merged

feat(chat): fork openwork as the CommonPlace chat register (OW1, OW3-OW6)#158
Travis-Gilbert merged 18 commits into
mainfrom
claude/commonplace-openwork-fork-46bcd5

Conversation

@Travis-Gilbert

Copy link
Copy Markdown
Owner

Hard fork of different-ai/openwork vendored as the CommonPlace chat register,
per SPEC-COMMONPLACE-OPENWORK-FORK-1.0. OW1, OW3, OW4, OW5, and OW6 are
complete. OW2 is partial and the gap is named below. OW7 is unblocked and not
started.

Reading this diff

1071 files, but 838 of them are apps/chat and are vendored upstream code at
2f2dde65796428109a665f3b733843fe3896b933. The review surface is much smaller:

Look at Why
apps/chat/scripts/*console-register* The token generator and its drift gate
apps/chat/src/app/index.css Both semantic layers rebound to the console register
apps/chat-server/src/console-session.ts New auth path, plus its contract test
apps/chat-server/src/theorem-mcp.ts The head's graph door
packaging/workspace/ The container, entrypoint, and its structural check
scripts/audit-openwork-fork.sh The conformance gate, 34 checks
apps/chat/MODIFICATIONS.md Every divergence from upstream, and every residual
Spec amendments A1-A13 Findings that contradict or refine the spec

Everything else under apps/chat and packages/openwork-* is upstream as
vendored, with CommonPlace edits marked by the deliverable that owns them.

What each deliverable did

OW1, vendor and sever. Telemetry, the Den control plane, the hosted model
catalog, and marketplace entries are removed. Bundle scan is clean of donor
hosts and of PostHog. MIT notices preserved, UPSTREAM.md pins the exact SHA.
No import resolves into /ee.

OW3, token and shader binding. The fork's theme is generated from the
console's registers rather than restated, because the fork is a separate Vite
app and cannot import the console's CSS at runtime. Two traps are encoded in
the generator: the console's bare selector is dark and the fork's :root is
light, so a direct copy would invert every colour; and a var() head naming a
token the fork does not emit resolves to nothing, which is invisible in review,
so it is a hard error. Both semantic layers are bound, not just the shadcn
slots, because upstream's own --dls-* layer is what most components read.

Geist shipped as a font file that no CSS rule ever named. Dropped. The three
faces the tokens declare now ship and nothing else.

Shader mounts went from 8 to 1. The activity orbs each held a WebGL context,
one per in-flight message, against a per-document limit near 16.

OW4, auth and serving. chat-server verifies the console's signed HttpOnly
cp_active_workspace cookie, so an authenticated console user reaches the
register with no second sign-in and the browser holds no token. Verify half
only: this daemon never mints a session. The wire contract is pinned against a
frozen fixture and against a live import of the console's own encoder, because
the two halves live in different apps and nothing else would fail if they
diverged.

Den view surfaces are deleted, including the per-organization brand accent that
overwrote --dls-accent at runtime, which is the second design authority the
spec forbids.

OW5, workspace container. One image, two doors, one checkout, one token.
The registers see the same filesystem because it is the same filesystem, so
there is no sync step and no reconciler that can be wrong.

OW6, seam audit. Decision: stay on the opencode head. Reversal conditions
recorded.

Gaps, stated plainly

OW2's live proof is incomplete. The configuration contract is done and the
engine accepts the generated config. The MCP handshake, the permission
round-trip, and todos/events are not proven: every app-scoped route on opencode
1.17.11 deadlocks, verified against a 150 second timeout. Five causes were ruled
out by direct experiment, including stock opencode with no config of ours, which
deadlocks identically and is the decisive control. Amendment A12 carries the
full list so the next attempt does not repeat it.

The OW5 image has never been built. No Docker daemon was available. The
structural claims are gated statically by packaging/workspace/check-two-doors.mjs,
and the frozen-lockfile check that the build depends on now runs in the audit,
but docker build remains the acceptance step.

A live network trace has not been run. The OW1 acceptance evidence is a
static bundle scan. cdn.simpleicons.org is still fetched for provider
favicons and closes with the Noun Project icon swap.

The Den client library survives. Only the view layer is deleted. Provider
auth and MCP connections still read den.ts, so its symbols remain in the
bundle. That unwind belongs with whatever replaces provider auth.

Verification

  • 34/34 conformance checks pass (scripts/audit-openwork-fork.sh)
  • Both packages typecheck clean
  • 24 tests pass in apps/chat-server
  • pnpm build:web succeeds
  • Bundle: zero openworklabs.com, zero PostHog, zero DEN flags

One bug worth calling out

The OW3 font change was committed without pnpm-lock.yaml. The OW5 Dockerfile
installs with --frozen-lockfile, so the image would have failed at that layer
with ERR_PNPM_OUTDATED_LOCKFILE. Verified in both directions and fixed in
3d73edf1. Docker was unavailable here, so the build could not have caught it
and every other gate stayed green while the deploy was broken. The audit now
runs the frozen-lockfile check itself.

🤖 Generated with Claude Code

SPEC-COMMONPLACE-OPENWORK-FORK-1.0, plan:c8307a874c5981f5. Lands OW1,
OW6, the conformance gate, the Verify First audits, and the spec anchor.

Vendored from different-ai/openwork @ 2f2dde6 (dev), MIT subset only:
apps/app -> apps/chat, apps/server -> apps/chat-server, and
packages/{ui,types,paths} -> packages/openwork-*. /ee never crosses;
handsfree declined (macOS Swift AX runtime), openwork-ui-mcp quarried
(local-desktop discovery transport), install-config dropped (its sole
consumer was the Den join dialog).

Severed: PostHog telemetry including its baked-in project key, Den
telemetry ingest, the hosted model catalog default, the Den base URL and
sign-in flag (now false literals so Vite eliminates the branches), the
Den control-plane MCP quick-connect and its openworklabs fallback, an
`npx -y openwork-ui-mcp` entry that fetched at runtime, the
capabilities-knowledge plugin that taught Den endpoints as truth, and
donor support/docs/feedback destinations.

Evidence: both packages typecheck clean, the web build succeeds, and a
scan of the built bundle finds zero openworklabs.com and zero PostHog.
scripts/audit-openwork-fork.sh enforces the eight named choices and the
anti-scope lines mechanically; it passes except the OW3 token check,
which fails on raw hex colors as it should until OW3 lands.

Four Verify First findings contradict the spec's Frame and are recorded
as amendments A1-A8: ee/LICENSE is FSL-1.1-MIT by name; there are six
plugin factories, not five; no sessions/artifacts schema exists, so
artifact truth is opencode's filesystem storage; and @ai-sdk/react is
unused with `ai` imported type-only, so there is one transport seam, not
two. OW6 decides on that evidence: stay on the opencode head, because
the seam is two client-construction sites and the real divergence is
artifact identity, which replacing the transport would not fix.
…session

OW3. The fork's Tailwind theme is emitted from the console registers rather
than restated. scripts/generate-console-register.mjs materializes the resolved
--ij-, --cp-, and --gy- values into src/styles/console-register.css, with a
drift gate and a hard error on any token that would resolve to nothing. Both
semantic layers (--dls-* and the shadcn slots) rebind to it, the radius law
follows the console's five-step ladder instead of one base times seven
coefficients, and fonts resolve to the three faces the tokens declare. Geist
shipped as a font file that no rule ever named; it is gone.

Raw hex leaves component source: the workspace identity ramp reads console hue
families, the boring-avatars palette table moves to the register lane beside
the console's own precedent, and the surfaces that genuinely need a literal
(WebGL uniforms, xterm's ITheme) read the register at runtime and re-read it on
a scheme change.

Shader law: one mount per window. The activity orbs each held a WebGL context,
one per in-flight message, so the count was unbounded; they are now compositor
gradients. check-shader-mounts.mjs holds the line at one.

OW4. Den sign-in is replaced by the console session. chat-server verifies the
console's signed cp_active_workspace cookie, so a console user reaches this
register with no second sign-in and the browser holds no token. Only the verify
half lives here; the wire contract is pinned against the console's own encoder,
which is the drift alarm across the two apps.

The Den view surfaces are deleted, with them the per-organization brand accent
that overwrote --dls-accent at runtime, and the orphaned join-organization
state. Bundle scan: the three DEN flags are eliminated, no donor host survives.

25/25 conformance checks pass.
…space container

OW2. The head's graph door is configuration, not surgery: theorem-mcp.ts emits
a remote MCP entry and merges it over the operator-managed runtime map, so a
settings write cannot silently disconnect the head from the graph. Absent when
THEOREM_MCP_URL is unset, with no default endpoint, for the same reason OW1
removed the hosted model catalog.

The prompt was the real bug. OW1 severed every Den endpoint but left the agent
instructed to reach Den's meta-MCP for memory, naming search_capabilities and
postMemory as the way to remember and recall. Severing a transport does not
sever the instructions describing it, and no path or bundle audit catches a
string. Replaced with graph doctrine; a test asserts the Den tool names are
gone from the shipped prompt.

Live proof is partial and amendment A12 says exactly how far it goes: opencode
1.17.11 starts against the config this fork generates and accepts it. The MCP
handshake, the permission round-trip, and todos/events are not proven, because
every app-scoped route on this engine build hangs and a local stand-in for the
Theorem MCP received no request. Recorded as unresolved, not as passing.

OW5. One image, two doors, one checkout at WORKSPACE_DIR. The registers see the
same filesystem because it is the same filesystem, so there is no sync step and
no reconciler that could be wrong. Both doors authenticate against one
WORKSPACE_TOKEN, and the entrypoint refuses to start without it rather than
exposing an unauthenticated IDE. check-two-doors.mjs makes both claims
falsifiable on a machine with no Docker daemon, which is where the drift that
matters (a second path, a second secret) actually gets introduced.

Amendments A9-A12: the route-versus-zone decision is a route on the console
origin, because the session cookie is what the choice is really about.

Conformance: 33 checks, all green. The audit now also holds the token
generator, the shader count, the no-second-design-authority rule, the
verify-only console session, and the two-door invariants.
…sion

A12 said the remaining OW2 proof needed a model-provider credential. That was
wrong. The blocker is a deadlock in opencode 1.17.11's app-scoped routes, and
it happens on GET /config, before any provider is selected. Five causes ruled
out by direct experiment, with stock opencode carrying no OPENCODE_CONFIG as
the decisive control: it deadlocks identically, so the Theorem MCP binding is
not implicated. Also ruled out: the models.dev catalog (local mirror changed
nothing), a missing git repository, and disk pressure, which was a real
confound on the first runs at 116 MiB free and reproduced unchanged at 7 GiB.

A13 records why Theorem ACP is not the instrument for this. It would change the
deliverable rather than verify it, since an ACP session does not exercise the
opencode head that OW2 names, and OW6's stay-on-opencode decision still holds
because its reversal condition is a contradicted permission assumption, which a
deadlock that never reached the permission model is not. Where ACP does belong
is named so it is not lost: OW7, and stage two if that condition fires.

DeepSeek is settled in the same amendment: already in the fork's provider
tables, reachable through the provider block the runtime config passes through,
so it is a configuration line whenever a live session is wanted.
The OW3 font change edited apps/chat/package.json (Geist out, JetBrains Mono
and Manrope in) and I committed it without pnpm-lock.yaml. That is not
cosmetic: packaging/workspace/Dockerfile installs with --frozen-lockfile, so
the OW5 image would have failed at that layer with ERR_PNPM_OUTDATED_LOCKFILE.
Verified both directions: the committed lockfile fails the frozen install, and
this one passes it under the same filter the Dockerfile uses.

Docker never came up in this environment, so the image build could not have
caught it, and every other gate stayed green while the deploy was broken. The
audit now runs the frozen-lockfile check itself, which is the only thing
standing between a dependency edit and a failed deploy on a machine that
cannot build the image.

Found by the repo-hygiene check flagging an uncommitted pnpm-lock.yaml.
Copilot AI review requested due to automatic review settings August 2, 2026 20:35
@gitguardian

gitguardian Bot commented Aug 2, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 3 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35660538 Triggered Generic High Entropy Secret 5514313 apps/chat-server/src/agent-context-cloud-probe.test.ts View secret
35660539 Triggered Generic High Entropy Secret 5514313 apps/chat-server/src/agent-context-diagnostics.schema.test.ts View secret
35660540 Triggered JSON Web Token 5514313 apps/chat-server/src/cloud-mcp-health.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 874 files, which is 774 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bda1e983-6e85-4074-81e0-d13277f1ad82

📥 Commits

Reviewing files that changed from the base of the PR and between 59c64c4 and bb7f3dc.

⛔ Files ignored due to path filters (206)
  • apps/chat/public/apple-touch-icon.png is excluded by !**/*.png
  • apps/chat/public/ext-context7.svg is excluded by !**/*.svg
  • apps/chat/public/ext-linear.svg is excluded by !**/*.svg
  • apps/chat/public/ext-notion.svg is excluded by !**/*.svg
  • apps/chat/public/ext-ollama.svg is excluded by !**/*.svg
  • apps/chat/public/ext-openai.svg is excluded by !**/*.svg
  • apps/chat/public/ext-sentry.svg is excluded by !**/*.svg
  • apps/chat/public/ext-stripe.svg is excluded by !**/*.svg
  • apps/chat/public/favicon-16x16.png is excluded by !**/*.png
  • apps/chat/public/favicon-32x32.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/extension/extension-032.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/fallback/fallback-032.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/mcps/mcps-032.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/plugins/plugins-032.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/skills/skills-032.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-001.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-002.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-003.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-004.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-005.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-006.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-007.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-008.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-009.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-010.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-011.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-012.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-013.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-014.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-015.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-016.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-017.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-018.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-019.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-020.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-021.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-022.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-023.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-024.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-025.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-026.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-027.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-028.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-029.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-030.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-031.png is excluded by !**/*.png
  • apps/chat/public/grain-art/workspaces/workspaces-032.png is excluded by !**/*.png
  • apps/chat/public/openwork-logo-square.svg is excluded by !**/*.svg
  • apps/chat/public/openwork-logo.svg is excluded by !**/*.svg
  • apps/chat/public/openwork-mark.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (874)
  • .gitignore
  • .harness/session-delta-queue
  • .theorem/hook-doctor/events.jsonl
  • .theorem/hook-doctor/latest.json
  • .theorem/runs/a167d78f-04c0-49d3-b8b9-b0e3ccbd0562.run_id
  • .theorem/runs/a167d78f-04c0-49d3-b8b9-b0e3ccbd0562.seq
  • apps/chat-server/.theorem/runs/a167d78f-04c0-49d3-b8b9-b0e3ccbd0562.seq
  • apps/chat-server/LICENSE
  • apps/chat-server/README.md
  • apps/chat-server/bin/openwork-server.mjs
  • apps/chat-server/constants.json
  • apps/chat-server/package.json
  • apps/chat-server/script/build.ts
  • apps/chat-server/scripts/publish-npm.mjs
  • apps/chat-server/src/.theorem/runs/a167d78f-04c0-49d3-b8b9-b0e3ccbd0562.seq
  • apps/chat-server/src/agent-context-cloud-probe.test.ts
  • apps/chat-server/src/agent-context-cloud-probe.ts
  • apps/chat-server/src/agent-context-diagnostics-local-schema.test.ts
  • apps/chat-server/src/agent-context-diagnostics-schema.ts
  • apps/chat-server/src/agent-context-diagnostics.plugin-label.test.ts
  • apps/chat-server/src/agent-context-diagnostics.schema.test.ts
  • apps/chat-server/src/agent-context-diagnostics.test.ts
  • apps/chat-server/src/agent-context-diagnostics.ts
  • apps/chat-server/src/agent-context-engine-inspection.test.ts
  • apps/chat-server/src/agent-context-engine-inspection.ts
  • apps/chat-server/src/agent-context-transport-probe.test.ts
  • apps/chat-server/src/agent-context-transport-probe.ts
  • apps/chat-server/src/approvals.ts
  • apps/chat-server/src/artifact-files.e2e.test.ts
  • apps/chat-server/src/audit.ts
  • apps/chat-server/src/authorized-folders.e2e.test.ts
  • apps/chat-server/src/blueprint-sessions.test.ts
  • apps/chat-server/src/blueprint-sessions.ts
  • apps/chat-server/src/claude-plugin-bundle.e2e.test.ts
  • apps/chat-server/src/claude-plugin-bundle.ts
  • apps/chat-server/src/cli.ts
  • apps/chat-server/src/cloud-mcp-health.test.ts
  • apps/chat-server/src/cloud-mcp-health.ts
  • apps/chat-server/src/cloud-mcp-reconcile.e2e.test.ts
  • apps/chat-server/src/cloud-plugins.test.ts
  • apps/chat-server/src/cloud-plugins.ts
  • apps/chat-server/src/commands.test.ts
  • apps/chat-server/src/commands.ts
  • apps/chat-server/src/config.ts
  • apps/chat-server/src/connect-skill-catalog.test.ts
  • apps/chat-server/src/connect-skill-catalog.ts
  • apps/chat-server/src/connect-state.inspect.test.ts
  • apps/chat-server/src/connect-state.test.ts
  • apps/chat-server/src/connect-state.ts
  • apps/chat-server/src/console-host-gate.test.ts
  • apps/chat-server/src/console-session.test.ts
  • apps/chat-server/src/console-session.ts
  • apps/chat-server/src/desktop-cloud-sync-preserves-imports.e2e.test.ts
  • apps/chat-server/src/desktop-cloud-sync.test.ts
  • apps/chat-server/src/desktop-cloud-sync.ts
  • apps/chat-server/src/diagnostic-sanitizer.test.ts
  • apps/chat-server/src/diagnostic-sanitizer.ts
  • apps/chat-server/src/embedded.ts
  • apps/chat-server/src/env-file.test.ts
  • apps/chat-server/src/env-file.ts
  • apps/chat-server/src/env-routes.e2e.test.ts
  • apps/chat-server/src/errors.ts
  • apps/chat-server/src/events.ts
  • apps/chat-server/src/extensions-connect-gating.test.ts
  • apps/chat-server/src/extensions-export.test.ts
  • apps/chat-server/src/extensions-export.ts
  • apps/chat-server/src/extensions/google-workspace.test.ts
  • apps/chat-server/src/extensions/google-workspace.ts
  • apps/chat-server/src/extensions/index.ts
  • apps/chat-server/src/extensions/openai-image-generation.ts
  • apps/chat-server/src/file-session-capacity.test.ts
  • apps/chat-server/src/file-sessions.test.ts
  • apps/chat-server/src/file-sessions.ts
  • apps/chat-server/src/frontmatter.ts
  • apps/chat-server/src/index.ts
  • apps/chat-server/src/jsonc.test.ts
  • apps/chat-server/src/jsonc.ts
  • apps/chat-server/src/legacy-config-sweep.test.ts
  • apps/chat-server/src/legacy-config-sweep.ts
  • apps/chat-server/src/managed-opencode.ts
  • apps/chat-server/src/managed-provider-auth.test.ts
  • apps/chat-server/src/managed-provider-auth.ts
  • apps/chat-server/src/mcp.authorization-link.e2e.test.ts
  • apps/chat-server/src/mcp.engine-sync.e2e.test.ts
  • apps/chat-server/src/mcp.oauth-flow.e2e.test.ts
  • apps/chat-server/src/mcp.passive-inventory.test.ts
  • apps/chat-server/src/mcp.remote-connect.e2e.test.ts
  • apps/chat-server/src/mcp.ts
  • apps/chat-server/src/no-bare-fetch.test.ts
  • apps/chat-server/src/opencode-config-path.test.ts
  • apps/chat-server/src/opencode-connection.test.ts
  • apps/chat-server/src/opencode-connection.ts
  • apps/chat-server/src/opencode-db.test.ts
  • apps/chat-server/src/opencode-db.ts
  • apps/chat-server/src/opencode-directory-guard.test.ts
  • apps/chat-server/src/opencode-models-url.test.ts
  • apps/chat-server/src/opencode-models-url.ts
  • apps/chat-server/src/opencode-plugins/agent-instruction-compose.test.ts
  • apps/chat-server/src/opencode-plugins/agent-instruction-compose.ts
  • apps/chat-server/src/opencode-plugins/openwork-anthropic-adaptive-thinking.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-anthropic-adaptive-thinking.ts
  • apps/chat-server/src/opencode-plugins/openwork-anthropic-tool-schema.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-anthropic-tool-schema.ts
  • apps/chat-server/src/opencode-plugins/openwork-extensions-preview-connect-steering.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-extensions-preview-steering.ts
  • apps/chat-server/src/opencode-plugins/openwork-extensions-preview.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-extensions-preview.ts
  • apps/chat-server/src/opencode-plugins/openwork-office-attachments.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-office-attachments.ts
  • apps/chat-server/src/opencode-plugins/openwork-provider-adapters.test.ts
  • apps/chat-server/src/opencode-plugins/openwork-provider-adapters.ts
  • apps/chat-server/src/opencode-proxy-gate.test.ts
  • apps/chat-server/src/openwork-config-db-migration.e2e.test.ts
  • apps/chat-server/src/openwork-extensions-plugin-path.test.ts
  • apps/chat-server/src/openwork-extensions-plugin-path.ts
  • apps/chat-server/src/openwork-runtime-config.test.ts
  • apps/chat-server/src/openwork-runtime-config.ts
  • apps/chat-server/src/openwork-workspace-config-store.ts
  • apps/chat-server/src/paths.ts
  • apps/chat-server/src/plugins.ts
  • apps/chat-server/src/portable-files.test.ts
  • apps/chat-server/src/portable-files.ts
  • apps/chat-server/src/portable-opencode.test.ts
  • apps/chat-server/src/portable-opencode.ts
  • apps/chat-server/src/reload-events.e2e.test.ts
  • apps/chat-server/src/reload-fingerprint.ts
  • apps/chat-server/src/reload-watcher.test.ts
  • apps/chat-server/src/reload-watcher.ts
  • apps/chat-server/src/routes/cloud-mcp.ts
  • apps/chat-server/src/routes/core.ts
  • apps/chat-server/src/routes/files.ts
  • apps/chat-server/src/routes/operations.ts
  • apps/chat-server/src/routes/registry.ts
  • apps/chat-server/src/routes/sessions.ts
  • apps/chat-server/src/routes/workspaces.ts
  • apps/chat-server/src/runtime-config-diagnostic-redaction.test.ts
  • apps/chat-server/src/runtime-config-disabled-providers.test.ts
  • apps/chat-server/src/runtime-config-file-mode.test.ts
  • apps/chat-server/src/runtime-config-global-providers.test.ts
  • apps/chat-server/src/runtime-config-migrate.e2e.test.ts
  • apps/chat-server/src/runtime-config-patch-reload.e2e.test.ts
  • apps/chat-server/src/runtime-db.node.test.ts
  • apps/chat-server/src/runtime-db.test.ts
  • apps/chat-server/src/runtime-db.ts
  • apps/chat-server/src/runtime-opencode-config-store.inspect.test.ts
  • apps/chat-server/src/runtime-opencode-config-store.test.ts
  • apps/chat-server/src/runtime-opencode-config-store.ts
  • apps/chat-server/src/runtime-provider-merge.test.ts
  • apps/chat-server/src/serve-node.test.ts
  • apps/chat-server/src/serve-node.ts
  • apps/chat-server/src/server-fetch.ts
  • apps/chat-server/src/server.normalizeWorkspaceRelativePath.test.ts
  • apps/chat-server/src/server.ts
  • apps/chat-server/src/session-groups.e2e.test.ts
  • apps/chat-server/src/session-groups.ts
  • apps/chat-server/src/session-read-model.e2e.test.ts
  • apps/chat-server/src/session-read-model.ts
  • apps/chat-server/src/skills.test.ts
  • apps/chat-server/src/skills.ts
  • apps/chat-server/src/static-ui.test.ts
  • apps/chat-server/src/static-ui.ts
  • apps/chat-server/src/theorem-mcp.test.ts
  • apps/chat-server/src/theorem-mcp.ts
  • apps/chat-server/src/tokens.test.ts
  • apps/chat-server/src/tokens.ts
  • apps/chat-server/src/toy-ui.ts
  • apps/chat-server/src/types.ts
  • apps/chat-server/src/utils.test.ts
  • apps/chat-server/src/utils.ts
  • apps/chat-server/src/validators.test.ts
  • apps/chat-server/src/validators.ts
  • apps/chat-server/src/worker-activity-heartbeat.test.ts
  • apps/chat-server/src/worker-activity-heartbeat.ts
  • apps/chat-server/src/workspace-activate.e2e.test.ts
  • apps/chat-server/src/workspace-export-safety.test.ts
  • apps/chat-server/src/workspace-export-safety.ts
  • apps/chat-server/src/workspace-files.ts
  • apps/chat-server/src/workspace-import-preview.test.ts
  • apps/chat-server/src/workspace-import-preview.ts
  • apps/chat-server/src/workspace-init.test.ts
  • apps/chat-server/src/workspace-init.ts
  • apps/chat-server/src/workspace-kv-store.node.test.ts
  • apps/chat-server/src/workspace-kv-store.test.ts
  • apps/chat-server/src/workspace-kv-store.ts
  • apps/chat-server/src/workspace-paths.test.ts
  • apps/chat-server/src/workspace-paths.ts
  • apps/chat-server/src/workspaces.test.ts
  • apps/chat-server/src/workspaces.ts
  • apps/chat-server/tsconfig.json
  • apps/chat-server/undefined/ow2/opencode-config.json
  • apps/chat/.theorem/runs/a167d78f-04c0-49d3-b8b9-b0e3ccbd0562.seq
  • apps/chat/LICENSE
  • apps/chat/MODIFICATIONS.md
  • apps/chat/NOTICE
  • apps/chat/UPSTREAM.md
  • apps/chat/components.json
  • apps/chat/globals.d.ts
  • apps/chat/index.html
  • apps/chat/overlay.html
  • apps/chat/package.json
  • apps/chat/scripts/_util.mjs
  • apps/chat/scripts/artifact-spreadsheet.test.ts
  • apps/chat/scripts/artifacts.test.ts
  • apps/chat/scripts/browser-entry.mjs
  • apps/chat/scripts/bump-version.mjs
  • apps/chat/scripts/check-console-register.mjs
  • apps/chat/scripts/check-shader-mounts.mjs
  • apps/chat/scripts/check-token-references.mjs
  • apps/chat/scripts/console-register-lib.mjs
  • apps/chat/scripts/desktop-cloud-sync.test.ts
  • apps/chat/scripts/dev-log.ts
  • apps/chat/scripts/e2e.mjs
  • apps/chat/scripts/events.mjs
  • apps/chat/scripts/extension-items.test.ts
  • apps/chat/scripts/fs-engine.mjs
  • apps/chat/scripts/generate-console-register.mjs
  • apps/chat/scripts/health.mjs
  • apps/chat/scripts/local-file-path.mjs
  • apps/chat/scripts/managed-voice-e2e.mjs
  • apps/chat/scripts/mention-send.mjs
  • apps/chat/scripts/open-target.test.ts
  • apps/chat/scripts/permissions.mjs
  • apps/chat/scripts/remote-workspace-diagnostics.test.ts
  • apps/chat/scripts/select-session-debug.mjs
  • apps/chat/scripts/session-error-recovery.ts
  • apps/chat/scripts/session-render-state.test.ts
  • apps/chat/scripts/session-scope.ts
  • apps/chat/scripts/session-switch.mjs
  • apps/chat/scripts/sessions-parallel.mjs
  • apps/chat/scripts/sessions.mjs
  • apps/chat/scripts/todos.mjs
  • apps/chat/scripts/voice-cdp.mjs
  • apps/chat/src/app/cloud/desktop-app-restrictions.ts
  • apps/chat/src/app/cloud/desktop-cloud-sync.ts
  • apps/chat/src/app/cloud/import-state.ts
  • apps/chat/src/app/constants.test.ts
  • apps/chat/src/app/constants.ts
  • apps/chat/src/app/data/commands/browser-setup.md
  • apps/chat/src/app/data/skill-creator.md
  • apps/chat/src/app/defaults/index.ts
  • apps/chat/src/app/defaults/models.ts
  • apps/chat/src/app/enablement.ts
  • apps/chat/src/app/extensions.ts
  • apps/chat/src/app/index.css
  • apps/chat/src/app/lib/agent-context-diagnostics-transport.ts
  • apps/chat/src/app/lib/agent-context-diagnostics.ts
  • apps/chat/src/app/lib/analytics.ts
  • apps/chat/src/app/lib/app-inspector.ts
  • apps/chat/src/app/lib/deep-link-bridge.ts
  • apps/chat/src/app/lib/den-endpoint-sources.test.ts
  • apps/chat/src/app/lib/den-endpoint-sources.ts
  • apps/chat/src/app/lib/den-handoff.ts
  • apps/chat/src/app/lib/den-session-events.ts
  • apps/chat/src/app/lib/den-telemetry.ts
  • apps/chat/src/app/lib/den-types.ts
  • apps/chat/src/app/lib/den.ts
  • apps/chat/src/app/lib/desktop-types.ts
  • apps/chat/src/app/lib/desktop.ts
  • apps/chat/src/app/lib/dev-log.ts
  • apps/chat/src/app/lib/diagnostic-sanitizer.ts
  • apps/chat/src/app/lib/diagnostics-bundle.ts
  • apps/chat/src/app/lib/download.ts
  • apps/chat/src/app/lib/electron-alpha.ts
  • apps/chat/src/app/lib/enterprise-activation.ts
  • apps/chat/src/app/lib/feedback.ts
  • apps/chat/src/app/lib/font-zoom.ts
  • apps/chat/src/app/lib/gateway-runtime.ts
  • apps/chat/src/app/lib/local-file-path.impl.d.ts
  • apps/chat/src/app/lib/local-file-path.impl.js
  • apps/chat/src/app/lib/local-file-path.ts
  • apps/chat/src/app/lib/migration.ts
  • apps/chat/src/app/lib/model-behavior.ts
  • apps/chat/src/app/lib/opencode-session.ts
  • apps/chat/src/app/lib/opencode.ts
  • apps/chat/src/app/lib/openwork-deployment.ts
  • apps/chat/src/app/lib/openwork-env-runtime.ts
  • apps/chat/src/app/lib/openwork-links.ts
  • apps/chat/src/app/lib/openwork-server.ts
  • apps/chat/src/app/lib/perf-log.ts
  • apps/chat/src/app/lib/platform-capabilities.test.ts
  • apps/chat/src/app/lib/platform-capabilities.ts
  • apps/chat/src/app/lib/provider-events.ts
  • apps/chat/src/app/lib/release-channels.ts
  • apps/chat/src/app/lib/runtime-env.ts
  • apps/chat/src/app/lib/session-scope.ts
  • apps/chat/src/app/lib/session-title.ts
  • apps/chat/src/app/lib/startup-boot.ts
  • apps/chat/src/app/lib/version-gate.ts
  • apps/chat/src/app/lib/workspace-blueprints.ts
  • apps/chat/src/app/lib/workspace-endpoint.ts
  • apps/chat/src/app/mcp.ts
  • apps/chat/src/app/theme.ts
  • apps/chat/src/app/types.ts
  • apps/chat/src/app/utils/index.ts
  • apps/chat/src/app/utils/plugins.ts
  • apps/chat/src/app/utils/providers.ts
  • apps/chat/src/components/chat/artifact-icon.tsx
  • apps/chat/src/components/chat/artifact.tsx
  • apps/chat/src/components/chat/capability-call-line.tsx
  • apps/chat/src/components/chat/file-chip.tsx
  • apps/chat/src/components/chat/image-attachment-badge.tsx
  • apps/chat/src/components/chat/message-list-provider.tsx
  • apps/chat/src/components/chat/message-list.tsx
  • apps/chat/src/components/chat/reasoning-block.tsx
  • apps/chat/src/components/chat/subagent-run-line.tsx
  • apps/chat/src/components/chat/task-suggestions.tsx
  • apps/chat/src/components/chat/tool-aggregate-group.tsx
  • apps/chat/src/components/chat/utils.ts
  • apps/chat/src/components/descriptive-button.tsx
  • apps/chat/src/components/markdown/link-action-menu.tsx
  • apps/chat/src/components/markdown/markdown-math.ts
  • apps/chat/src/components/markdown/markdown-primitive.ts
  • apps/chat/src/components/markdown/markdown.tsx
  • apps/chat/src/components/markdown/text-highlights.ts
  • apps/chat/src/components/model-behavior-select.tsx
  • apps/chat/src/components/model-select.tsx
  • apps/chat/src/components/page.tsx
  • apps/chat/src/components/panel-tabs.tsx
  • apps/chat/src/components/tools/apply-patch.tsx
  • apps/chat/src/components/tools/bash.tsx
  • apps/chat/src/components/tools/collapsible-tool.tsx
  • apps/chat/src/components/tools/edit.tsx
  • apps/chat/src/components/tools/env-var-request.tsx
  • apps/chat/src/components/tools/error-attribution.ts
  • apps/chat/src/components/tools/file.tsx
  • apps/chat/src/components/tools/glob.tsx
  • apps/chat/src/components/tools/grep.tsx
  • apps/chat/src/components/tools/lsp.tsx
  • apps/chat/src/components/tools/mcp-reconnect-state.ts
  • apps/chat/src/components/tools/openwork-session-create.tsx
  • apps/chat/src/components/tools/path.ts
  • apps/chat/src/components/tools/question.tsx
  • apps/chat/src/components/tools/skill.tsx
  • apps/chat/src/components/tools/todowrite.tsx
  • apps/chat/src/components/tools/use-chat-tool-reconnect.ts
  • apps/chat/src/components/tools/webfetch.tsx
  • apps/chat/src/components/tools/websearch.tsx
  • apps/chat/src/components/ui/accordion.tsx
  • apps/chat/src/components/ui/alert-dialog.tsx
  • apps/chat/src/components/ui/alert.tsx
  • apps/chat/src/components/ui/autocomplete.tsx
  • apps/chat/src/components/ui/avatar.tsx
  • apps/chat/src/components/ui/badge.tsx
  • apps/chat/src/components/ui/button.tsx
  • apps/chat/src/components/ui/card.tsx
  • apps/chat/src/components/ui/chain-of-thought.tsx
  • apps/chat/src/components/ui/checkbox.tsx
  • apps/chat/src/components/ui/collapsible.tsx
  • apps/chat/src/components/ui/command.tsx
  • apps/chat/src/components/ui/context-menu.tsx
  • apps/chat/src/components/ui/dialog.tsx
  • apps/chat/src/components/ui/dot-matrix-loader.tsx
  • apps/chat/src/components/ui/dropdown-menu.tsx
  • apps/chat/src/components/ui/empty.tsx
  • apps/chat/src/components/ui/field.tsx
  • apps/chat/src/components/ui/hover-card.tsx
  • apps/chat/src/components/ui/image.tsx
  • apps/chat/src/components/ui/input-group.tsx
  • apps/chat/src/components/ui/input.tsx
  • apps/chat/src/components/ui/label.tsx
  • apps/chat/src/components/ui/message.tsx
  • apps/chat/src/components/ui/popover.tsx
  • apps/chat/src/components/ui/progress.tsx
  • apps/chat/src/components/ui/radio-group.tsx
  • apps/chat/src/components/ui/resizable.tsx
  • apps/chat/src/components/ui/scroll-area.tsx
  • apps/chat/src/components/ui/select.tsx
  • apps/chat/src/components/ui/separator.tsx
  • apps/chat/src/components/ui/sheet.tsx
  • apps/chat/src/components/ui/sidebar.tsx
  • apps/chat/src/components/ui/skeleton.tsx
  • apps/chat/src/components/ui/sonner.tsx
  • apps/chat/src/components/ui/source.tsx
  • apps/chat/src/components/ui/switch.tsx
  • apps/chat/src/components/ui/table.tsx
  • apps/chat/src/components/ui/tabs.tsx
  • apps/chat/src/components/ui/textarea.tsx
  • apps/chat/src/components/ui/toggle-group.tsx
  • apps/chat/src/components/ui/toggle.tsx
  • apps/chat/src/components/ui/tool.tsx
  • apps/chat/src/components/ui/tooltip.tsx
  • apps/chat/src/hooks/use-mobile.ts
  • apps/chat/src/i18n/index.ts
  • apps/chat/src/i18n/locales/ca.ts
  • apps/chat/src/i18n/locales/en.ts
  • apps/chat/src/i18n/locales/es.ts
  • apps/chat/src/i18n/locales/fr.ts
  • apps/chat/src/i18n/locales/index.ts
  • apps/chat/src/i18n/locales/ja.ts
  • apps/chat/src/i18n/locales/pt-BR.ts
  • apps/chat/src/i18n/locales/ru.ts
  • apps/chat/src/i18n/locales/th.ts
  • apps/chat/src/i18n/locales/vi.ts
  • apps/chat/src/i18n/locales/zh.ts
  • apps/chat/src/index.react.tsx
  • apps/chat/src/lib/artifacts.ts
  • apps/chat/src/lib/build-in-tools.ts
  • apps/chat/src/lib/capability-call.ts
  • apps/chat/src/lib/favicon.ts
  • apps/chat/src/lib/messages.ts
  • apps/chat/src/lib/target-provider.ts
  • apps/chat/src/lib/tool-activity.ts
  • apps/chat/src/lib/tool-aggregate.ts
  • apps/chat/src/lib/tool-call-duration.ts
  • apps/chat/src/lib/utils.ts
  • apps/chat/src/lib/websearch-results.ts
  • apps/chat/src/overlay/context-menu.tsx
  • apps/chat/src/overlay/index.tsx
  • apps/chat/src/react-app/ARCHITECTURE.md
  • apps/chat/src/react-app/design-system/extension-card.tsx
  • apps/chat/src/react-app/design-system/extension-detail-modal.tsx
  • apps/chat/src/react-app/design-system/extension-icon-src.ts
  • apps/chat/src/react-app/design-system/extension-mesh-avatar.tsx
  • apps/chat/src/react-app/design-system/grain-orb.tsx
  • apps/chat/src/react-app/design-system/marble-avatar.tsx
  • apps/chat/src/react-app/design-system/modals/confirm-modal.tsx
  • apps/chat/src/react-app/design-system/provider-icon.test.tsx
  • apps/chat/src/react-app/design-system/provider-icon.tsx
  • apps/chat/src/react-app/design-system/provider-logo-src.ts
  • apps/chat/src/react-app/design-system/register-token.ts
  • apps/chat/src/react-app/design-system/restriction-notice-modal.tsx
  • apps/chat/src/react-app/design-system/select-menu.tsx
  • apps/chat/src/react-app/design-system/text-input.tsx
  • apps/chat/src/react-app/design-system/workspace-avatar-utils.ts
  • apps/chat/src/react-app/design-system/workspace-avatar.tsx
  • apps/chat/src/react-app/domains/cloud/connect-confirm-dialog.tsx
  • apps/chat/src/react-app/domains/cloud/connect-link-provider.tsx
  • apps/chat/src/react-app/domains/cloud/den-auth-provider.tsx
  • apps/chat/src/react-app/domains/cloud/desktop-config-provider.tsx
  • apps/chat/src/react-app/domains/cloud/open-browser-auth.ts
  • apps/chat/src/react-app/domains/cloud/openwork-models-promo.test.ts
  • apps/chat/src/react-app/domains/cloud/openwork-models-promo.ts
  • apps/chat/src/react-app/domains/cloud/restriction-notice-provider.tsx
  • apps/chat/src/react-app/domains/cloud/use-cloud-provider-auto-sync.ts
  • apps/chat/src/react-app/domains/cloud/use-org-list-window.ts
  • apps/chat/src/react-app/domains/cloud/workspace-branding-restart.test.ts
  • apps/chat/src/react-app/domains/cloud/workspace-branding-restart.ts
  • apps/chat/src/react-app/domains/connections/cloud-inventory-cache.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-diagnostics.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-maintenance-outcome.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-reconciler.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-submit-readiness.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-user-state.test.ts
  • apps/chat/src/react-app/domains/connections/cloud-mcp-user-state.ts
  • apps/chat/src/react-app/domains/connections/managed-engine-config.ts
  • apps/chat/src/react-app/domains/connections/mcp-auth-modal.tsx
  • apps/chat/src/react-app/domains/connections/mcp-silent-reauth.ts
  • apps/chat/src/react-app/domains/connections/modals.tsx
  • apps/chat/src/react-app/domains/connections/modals/add-mcp-modal.tsx
  • apps/chat/src/react-app/domains/connections/modals/claude-plugin-import-modal.tsx
  • apps/chat/src/react-app/domains/connections/native-provider-connections.test.ts
  • apps/chat/src/react-app/domains/connections/native-provider-connections.ts
  • apps/chat/src/react-app/domains/connections/openwork-connect-status.ts
  • apps/chat/src/react-app/domains/connections/openwork-server-provider.tsx
  • apps/chat/src/react-app/domains/connections/openwork-server-store.ts
  • apps/chat/src/react-app/domains/connections/policy-provider-reconcile.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/cloud-provider-config.test.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/cloud-provider-config.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/index.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/managed-models-recovery.test.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/managed-models-recovery.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/provider-auth-modal.tsx
  • apps/chat/src/react-app/domains/connections/provider-auth/provider-policy.test.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/provider-policy.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/store.ts
  • apps/chat/src/react-app/domains/connections/provider-auth/use-session-provider-auth.ts
  • apps/chat/src/react-app/domains/connections/provider.tsx
  • apps/chat/src/react-app/domains/connections/store.ts
  • apps/chat/src/react-app/domains/connections/use-cloud-mcp-submit-readiness.ts
  • apps/chat/src/react-app/domains/connections/use-mcp-connected-count.ts
  • apps/chat/src/react-app/domains/connections/use-org-mcp-connections.ts
  • apps/chat/src/react-app/domains/connections/use-session-mcp-maintenance.ts
  • apps/chat/src/react-app/domains/onboarding/attribution-step.tsx
  • apps/chat/src/react-app/domains/onboarding/provider-selection-step.tsx
  • apps/chat/src/react-app/domains/onboarding/welcome-page.tsx
  • apps/chat/src/react-app/domains/session/artifacts/artifact-icon.tsx
  • apps/chat/src/react-app/domains/session/artifacts/artifact-panel.tsx
  • apps/chat/src/react-app/domains/session/artifacts/artifact-spreadsheet-editor.tsx
  • apps/chat/src/react-app/domains/session/artifacts/artifact-spreadsheet-model.ts
  • apps/chat/src/react-app/domains/session/artifacts/artifact-text-editor.tsx
  • apps/chat/src/react-app/domains/session/artifacts/markdown-live-preview.ts
  • apps/chat/src/react-app/domains/session/artifacts/open-target.ts
  • apps/chat/src/react-app/domains/session/artifacts/preview.tsx
  • apps/chat/src/react-app/domains/session/chat/conversation-tab-history.ts
  • apps/chat/src/react-app/domains/session/chat/new-task-composer.tsx
  • apps/chat/src/react-app/domains/session/chat/permission-approval-modal.tsx
  • apps/chat/src/react-app/domains/session/chat/session-empty-hero.tsx
  • apps/chat/src/react-app/domains/session/chat/session-page.tsx
  • apps/chat/src/react-app/domains/session/chat/workbench-store.ts
  • apps/chat/src/react-app/domains/session/control/session-control-actions.ts
  • apps/chat/src/react-app/domains/session/modals/model-picker-modal-components.tsx
  • apps/chat/src/react-app/domains/session/modals/model-picker-modal.test.ts
  • apps/chat/src/react-app/domains/session/modals/model-picker-modal.tsx
  • apps/chat/src/react-app/domains/session/modals/question-modal.tsx
  • apps/chat/src/react-app/domains/session/modals/queued-messages-panel.tsx
  • apps/chat/src/react-app/domains/session/modals/rename-session-modal.tsx
  • apps/chat/src/react-app/domains/session/modals/use-model-picker.ts
  • apps/chat/src/react-app/domains/session/panel/panel-empty.tsx
  • apps/chat/src/react-app/domains/session/panel/panel-tab-store.ts
  • apps/chat/src/react-app/domains/session/panel/side-panel-session.ts
  • apps/chat/src/react-app/domains/session/panel/side-panel.tsx
  • apps/chat/src/react-app/domains/session/panel/use-side-panel-tabs.ts
  • apps/chat/src/react-app/domains/session/panel/utils.ts
  • apps/chat/src/react-app/domains/session/search/session-search.ts
  • apps/chat/src/react-app/domains/session/sidebar/account-status-menu.tsx
  • apps/chat/src/react-app/domains/session/sidebar/app-sidebar-provider.tsx
  • apps/chat/src/react-app/domains/session/sidebar/app-sidebar.tsx
  • apps/chat/src/react-app/domains/session/sidebar/session-dot-matrix-loader.tsx
  • apps/chat/src/react-app/domains/session/sidebar/session-management-store.ts
  • apps/chat/src/react-app/domains/session/sidebar/session-title-marquee.ts
  • apps/chat/src/react-app/domains/session/sidebar/session-title.tsx
  • apps/chat/src/react-app/domains/session/sidebar/sidebar-destination.tsx
  • apps/chat/src/react-app/domains/session/sidebar/sidebar-lane-metrics.ts
  • apps/chat/src/react-app/domains/session/sidebar/sidebar-lanes.tsx
  • apps/chat/src/react-app/domains/session/sidebar/utils.ts
  • apps/chat/src/react-app/domains/session/sidebar/workspace-avatar-picker.tsx
  • apps/chat/src/react-app/domains/session/sidebar/workspace-avatar-store.ts
  • apps/chat/src/react-app/domains/session/status/session-activity-store.ts
  • apps/chat/src/react-app/domains/session/surface/composer-auto-send.ts
  • apps/chat/src/react-app/domains/session/surface/composer-state-store.ts
  • apps/chat/src/react-app/domains/session/surface/composer/app-mentions.ts
  • apps/chat/src/react-app/domains/session/surface/composer/composer.tsx
  • apps/chat/src/react-app/domains/session/surface/composer/connect-skill-token.ts
  • apps/chat/src/react-app/domains/session/surface/composer/editor.tsx
  • apps/chat/src/react-app/domains/session/surface/composer/mention-encoding.ts
  • apps/chat/src/react-app/domains/session/surface/composer/pasted-text-insertion.ts
  • apps/chat/src/react-app/domains/session/surface/composer/pasted-text.ts
  • apps/chat/src/react-app/domains/session/surface/composer/slash-command.ts
  • apps/chat/src/react-app/domains/session/surface/connect-capability-inventory.ts
  • apps/chat/src/react-app/domains/session/surface/debug-panel.tsx
  • apps/chat/src/react-app/domains/session/surface/find-bar.tsx
  • apps/chat/src/react-app/domains/session/surface/find-store.ts
  • apps/chat/src/react-app/domains/session/surface/markdown.tsx
  • apps/chat/src/react-app/domains/session/surface/mcp-chat-reconnect.ts
  • apps/chat/src/react-app/domains/session/surface/scroll-controller-state.ts
  • apps/chat/src/react-app/domains/session/surface/scroll-controller.ts
  • apps/chat/src/react-app/domains/session/surface/scroll-overlay.tsx
  • apps/chat/src/react-app/domains/session/surface/scroll-store.ts
  • apps/chat/src/react-app/domains/session/surface/session-model-store.ts
  • apps/chat/src/react-app/domains/session/surface/session-render-state.ts
  • apps/chat/src/react-app/domains/session/surface/session-surface.tsx
  • apps/chat/src/react-app/domains/session/surface/text-highlights.ts
  • apps/chat/src/react-app/domains/session/surface/use-model-behavior.ts
  • apps/chat/src/react-app/domains/session/sync/actions-provider.tsx
  • apps/chat/src/react-app/domains/session/sync/actions-store.ts
  • apps/chat/src/react-app/domains/session/sync/attachment-file-part.ts
  • apps/chat/src/react-app/domains/session/sync/draft-store.ts
  • apps/chat/src/react-app/domains/session/sync/env-context.ts
  • apps/chat/src/react-app/domains/session/sync/message-merge.ts
  • apps/chat/src/react-app/domains/session/sync/parse-tool-parts.ts
  • apps/chat/src/react-app/domains/session/sync/prompt-file-parts.ts
  • apps/chat/src/react-app/domains/session/sync/run-state.ts
  • apps/chat/src/react-app/domains/session/sync/runtime-sync.tsx
  • apps/chat/src/react-app/domains/session/sync/session-sync.ts
  • apps/chat/src/react-app/domains/session/sync/transcript-reconcile.ts
  • apps/chat/src/react-app/domains/session/sync/transition-controller.ts
  • apps/chat/src/react-app/domains/session/sync/use-session-interactions.ts
  • apps/chat/src/react-app/domains/session/sync/usechat-adapter.ts
  • apps/chat/src/react-app/domains/session/terminal/terminal-dock.tsx
  • apps/chat/src/react-app/domains/session/voice/voice-panel.tsx
  • apps/chat/src/react-app/domains/settings/appearance/language-section.tsx
  • apps/chat/src/react-app/domains/settings/appearance/theme-section.tsx
  • apps/chat/src/react-app/domains/settings/appearance/window-section.tsx
  • apps/chat/src/react-app/domains/settings/browser-extension-config.tsx
  • apps/chat/src/react-app/domains/settings/cloud/agent-access-card.tsx
  • apps/chat/src/react-app/domains/settings/cloud/cloud-account-section.tsx
  • apps/chat/src/react-app/domains/settings/cloud/cloud-session-provider.tsx
  • apps/chat/src/react-app/domains/settings/cloud/control-plane-url-editor.tsx
  • apps/chat/src/react-app/domains/settings/cloud/control-plane-url.ts
  • apps/chat/src/react-app/domains/settings/cloud/dev-mode.tsx
  • apps/chat/src/react-app/domains/settings/cloud/organization-server-affordance.tsx
  • apps/chat/src/react-app/domains/settings/cloud/sections.tsx
  • apps/chat/src/react-app/domains/settings/cloud/use-den-session.tsx
  • apps/chat/src/react-app/domains/settings/computer-use-config.tsx
  • apps/chat/src/react-app/domains/settings/connect-cloud-readiness.ts
  • apps/chat/src/react-app/domains/settings/connect-delivery.ts
  • apps/chat/src/react-app/domains/settings/desktop-integration-section.tsx
  • apps/chat/src/react-app/domains/settings/extension-items.ts
  • apps/chat/src/react-app/domains/settings/extension-registry.tsx
  • apps/chat/src/react-app/domains/settings/extension-requirement-list.tsx
  • apps/chat/src/react-app/domains/settings/extension-state.ts
  • apps/chat/src/react-app/domains/settings/extension-taxonomy.ts
  • apps/chat/src/react-app/domains/settings/modals/reset-modal.tsx
  • apps/chat/src/react-app/domains/settings/ollama-config.tsx
  • apps/chat/src/react-app/domains/settings/openai-image-extension.ts
  • apps/chat/src/react-app/domains/settings/openai-image-gen-config.tsx
  • apps/chat/src/react-app/domains/settings/openwork-voice-config.tsx
  • apps/chat/src/react-app/domains/settings/pages/advanced-view-sections.tsx
  • apps/chat/src/react-app/domains/settings/pages/advanced-view-state.ts
  • apps/chat/src/react-app/domains/settings/pages/advanced-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/agent-context-diagnostics-report.tsx
  • apps/chat/src/react-app/domains/settings/pages/agent-context-diagnostics-section.tsx
  • apps/chat/src/react-app/domains/settings/pages/ai-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/appearance-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/cloud-account-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/cloud-marketplaces-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/cloud-providers-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/config-view-sections.tsx
  • apps/chat/src/react-app/domains/settings/pages/config-view-state.ts
  • apps/chat/src/react-app/domains/settings/pages/config-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/connect-view.test.ts
  • apps/chat/src/react-app/domains/settings/pages/connect-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/debug-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/environment-variable-provider.tsx
  • apps/chat/src/react-app/domains/settings/pages/environment-variable-table.tsx
  • apps/chat/src/react-app/domains/settings/pages/environment-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/extensions-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/general-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/mcp-view-state.ts
  • apps/chat/src/react-app/domains/settings/pages/mcp-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/memory-utils.ts
  • apps/chat/src/react-app/domains/settings/pages/memory-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/plugins-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/preferences-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/recovery-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/skills-view.tsx
  • apps/chat/src/react-app/domains/settings/pages/updates-view.tsx
  • apps/chat/src/react-app/domains/settings/panels/authorized-folders-panel-state.ts
  • apps/chat/src/react-app/domains/settings/panels/authorized-folders-panel.tsx
  • apps/chat/src/react-app/domains/settings/settings-extension-controller.ts
  • apps/chat/src/react-app/domains/settings/settings-layout.tsx
  • apps/chat/src/react-app/domains/settings/settings-list.tsx
  • apps/chat/src/react-app/domains/settings/settings-section.tsx
  • apps/chat/src/react-app/domains/settings/shell/panel.tsx
  • apps/chat/src/react-app/domains/settings/shell/settings-page.tsx
  • apps/chat/src/react-app/domains/settings/shell/settings-shell.tsx
  • apps/chat/src/react-app/domains/settings/shell/tabs.tsx
  • apps/chat/src/react-app/domains/settings/state/debug-view-model.ts
  • apps/chat/src/react-app/domains/settings/state/electron-updater-state.test.ts
  • apps/chat/src/react-app/domains/settings/state/electron-updater-state.ts
  • apps/chat/src/react-app/domains/settings/state/extensions-store.ts
  • apps/chat/src/react-app/domains/settings/state/feature-flags-preferences.ts
  • apps/chat/src/react-app/domains/settings/state/model-controls-provider.tsx
  • apps/chat/src/react-app/domains/settings/state/model-controls-store.ts
  • apps/chat/src/react-app/domains/settings/state/session-display-preferences.ts
  • apps/chat/src/react-app/domains/settings/state/update-check-request.ts
  • apps/chat/src/react-app/domains/workspace/create-remote-workspace-modal.tsx
  • apps/chat/src/react-app/domains/workspace/create-workspace-local-panel.tsx
  • apps/chat/src/react-app/domains/workspace/create-workspace-modal-state.ts
  • apps/chat/src/react-app/domains/workspace/create-workspace-modal.tsx
  • apps/chat/src/react-app/domains/workspace/modal-styles.ts
  • apps/chat/src/react-app/domains/workspace/openwork-den-help-link.tsx
  • apps/chat/src/react-app/domains/workspace/option-card.tsx
  • apps/chat/src/react-app/domains/workspace/remote-access-restart.ts
  • apps/chat/src/react-app/domains/workspace/remote-workspace-diagnostics.ts
  • apps/chat/src/react-app/domains/workspace/remote-workspace-fields.tsx
  • apps/chat/src/react-app/domains/workspace/rename-workspace-modal.tsx
  • apps/chat/src/react-app/domains/workspace/share-workspace-access-panel.tsx
  • apps/chat/src/react-app/domains/workspace/share-workspace-modal.tsx
  • apps/chat/src/react-app/domains/workspace/share-workspace-state.ts
  • apps/chat/src/react-app/domains/workspace/types.ts
  • apps/chat/src/react-app/domains/workspace/use-remote-workspace-connection-editor.ts
  • apps/chat/src/react-app/infra/provider-list-query.ts
  • apps/chat/src/react-app/infra/query-cache-state.ts
  • apps/chat/src/react-app/infra/query-client.ts
  • apps/chat/src/react-app/infra/workspace-server-client.test.ts
  • apps/chat/src/react-app/infra/workspace-server-client.ts
  • apps/chat/src/react-app/kernel/desktop-notification-preferences.ts
  • apps/chat/src/react-app/kernel/global-sdk-provider.tsx
  • apps/chat/src/react-app/kernel/global-sync-provider.tsx
  • apps/chat/src/react-app/kernel/local-preferences-storage.ts
  • apps/chat/src/react-app/kernel/local-provider.tsx
  • apps/chat/src/react-app/kernel/model-config.ts
  • apps/chat/src/react-app/kernel/notification-store.ts
  • apps/chat/src/react-app/kernel/platform.tsx
  • apps/chat/src/react-app/kernel/server-provider-state.ts
  • apps/chat/src/react-app/kernel/server-provider.tsx
  • apps/chat/src/react-app/kernel/system-state.ts
  • apps/chat/src/react-app/shell/app-menu.tsx
  • apps/chat/src/react-app/shell/app-root.tsx
  • apps/chat/src/react-app/shell/architecture-mismatch-gate.tsx
  • apps/chat/src/react-app/shell/boot-state.tsx
  • apps/chat/src/react-app/shell/command-palette-sessions.ts
  • apps/chat/src/react-app/shell/command-palette.tsx
  • apps/chat/src/react-app/shell/console-session-gate.tsx
  • apps/chat/src/react-app/shell/control/control-provider.tsx
  • apps/chat/src/react-app/shell/debug-logger.ts
  • apps/chat/src/react-app/shell/desktop-local-openwork.ts
  • apps/chat/src/react-app/shell/desktop-notifications.ts
  • apps/chat/src/react-app/shell/desktop-runtime-boot.ts
  • apps/chat/src/react-app/shell/dev-profiler.tsx
  • apps/chat/src/react-app/shell/dot-ticker.tsx
  • apps/chat/src/react-app/shell/font-zoom.ts
  • apps/chat/src/react-app/shell/loading-overlay.tsx
  • apps/chat/src/react-app/shell/new-providers-listener.tsx
  • apps/chat/src/react-app/shell/notification-center.tsx
  • apps/chat/src/react-app/shell/notifications.ts
  • apps/chat/src/react-app/shell/openwork-connection.ts
  • apps/chat/src/react-app/shell/openwork-context-projector.ts
  • apps/chat/src/react-app/shell/openwork-context-publisher.tsx
  • apps/chat/src/react-app/shell/providers.tsx
  • apps/chat/src/react-app/shell/react-render-watchdog-overlay.tsx
  • apps/chat/src/react-app/shell/react-render-watchdog.ts
  • apps/chat/src/react-app/shell/reload-coordinator.tsx
  • apps/chat/src/react-app/shell/route-workspaces.ts
  • apps/chat/src/react-app/shell/session-group-event-poller.ts
  • apps/chat/src/react-app/shell/session-memory.ts
  • apps/chat/src/react-app/shell/session-number-shortcuts.ts
  • apps/chat/src/react-app/shell/session-route.tsx
  • apps/chat/src/react-app/shell/session-search-dialog.tsx
  • apps/chat/src/react-app/shell/settings-route.tsx
  • apps/chat/src/react-app/shell/shell-config.tsx
  • apps/chat/src/react-app/shell/startup-deep-links.ts
  • apps/chat/src/react-app/shell/ui-state-store.ts
  • apps/chat/src/react-app/shell/use-engine-reload.ts
  • apps/chat/src/react-app/shell/use-session-group-sync.ts
  • apps/chat/src/react-app/shell/use-shell-shortcuts.ts
  • apps/chat/src/react-app/shell/use-workspace-route-state.ts
  • apps/chat/src/react-app/shell/welcome-route.tsx
  • apps/chat/src/react-app/shell/workspace-provider.ts
  • apps/chat/src/react-app/shell/workspace-routes.ts
  • apps/chat/src/react-app/shell/workspace-shell-layout.ts
  • apps/chat/src/styles/colors.css
  • apps/chat/src/styles/console-register.css
  • apps/chat/src/styles/identity-register.css
  • apps/chat/src/styles/marble-avatar-palettes.ts
  • apps/chat/src/styles/tailwind-theme.css
  • apps/chat/tests/agent-context-diagnostics-report.test.tsx
  • apps/chat/tests/agent-context-diagnostics-section.test.ts
  • apps/chat/tests/agent-context-diagnostics-transport.test.ts
  • apps/chat/tests/agent-context-diagnostics.test.ts
  • apps/chat/tests/app-inspector.test.ts
  • apps/chat/tests/app-mentions.test.ts
  • apps/chat/tests/attachment-artifacts.test.ts
  • apps/chat/tests/attachment-file-part.test.ts
  • apps/chat/tests/builtin-mcp-visibility.test.ts
  • apps/chat/tests/capability-call-sentence.test.ts
  • apps/chat/tests/chat-message-grouping.test.ts
  • apps/chat/tests/cloud-inventory-cache.test.ts
  • apps/chat/tests/cloud-marketplaces-view.test.ts
  • apps/chat/tests/cloud-mcp-diagnostics.test.ts
  • apps/chat/tests/cloud-mcp-health.test.ts
  • apps/chat/tests/cloud-mcp-maintenance-gate.test.ts
  • apps/chat/tests/cloud-mcp-maintenance-outcome.test.ts
  • apps/chat/tests/cloud-mcp-submit-readiness.test.ts
  • apps/chat/tests/cloud-mcp-user-state.test.ts
  • apps/chat/tests/cloud-provider-credentials.test.ts
  • apps/chat/tests/cloud-provider-reimport.test.ts
  • apps/chat/tests/cloud-provider-sync-gateway.test.ts
  • apps/chat/tests/cloud-provider-sync-triggers.test.ts
  • apps/chat/tests/cloud-workspace-overlay.test.tsx
  • apps/chat/tests/composer-model-controls.test.ts
  • apps/chat/tests/composer-state-store.test.ts
  • apps/chat/tests/connect-capability-inventory-admin.test.ts
  • apps/chat/tests/connect-capability-inventory.test.ts
  • apps/chat/tests/connect-delivery.test.ts
  • apps/chat/tests/connect-link-parse.test.ts
  • apps/chat/tests/connect-skill-token.test.ts
  • apps/chat/tests/connect-view.test.ts
  • apps/chat/tests/conversation-tab-history.test.ts
  • apps/chat/tests/den-auth-provider.test.ts
  • apps/chat/tests/den-desktop-bootstrap-settings.test.ts
  • apps/chat/tests/den-desktop-config.test.ts
  • apps/chat/tests/den-extension-projection.test.ts
  • apps/chat/tests/den-mcp-url.test.ts
  • apps/chat/tests/den-org-roles.test.ts
  • apps/chat/tests/den-session-offline-resilience.test.ts
  • apps/chat/tests/den-sign-out.test.ts
  • apps/chat/tests/desktop-notifications.test.ts
  • apps/chat/tests/diagnostics-bundle.test.ts
  • apps/chat/tests/enterprise-activation.test.ts
  • apps/chat/tests/entitled-model-options.test.ts
  • apps/chat/tests/env-context.test.ts
  • apps/chat/tests/extension-items.test.ts
  • apps/chat/tests/extension-taxonomy.test.ts
  • apps/chat/tests/extensions-sidebar-header.test.tsx
  • apps/chat/tests/find-store.test.ts
  • apps/chat/tests/gateway-runtime.test.ts
  • apps/chat/tests/gateway-session-reflect.test.ts
  • apps/chat/tests/managed-brand-header.test.ts
  • apps/chat/tests/managed-engine-config-guard.test.ts
  • apps/chat/tests/markdown-code-block.test.ts
  • apps/chat/tests/markdown-math.test.ts
  • apps/chat/tests/mcp-chat-reconnect.test.ts
  • apps/chat/tests/mcp-reconnect-state.test.ts
  • apps/chat/tests/mcp-silent-reauth.test.ts
  • apps/chat/tests/memory-client.test.ts
  • apps/chat/tests/mention-encoding.test.ts
  • apps/chat/tests/message-list-loading.test.tsx
  • apps/chat/tests/message-list-step-fold.test.tsx
  • apps/chat/tests/model-behavior.test.ts
  • apps/chat/tests/model-picker-modal.test.ts
  • apps/chat/tests/notification-store.test.ts
  • apps/chat/tests/office-attachment-ui.test.ts
  • apps/chat/tests/ollama-local-provider.test.ts
  • apps/chat/tests/opencode-stream-timeout.test.ts
  • apps/chat/tests/openwork-connect-beta-badge.test.tsx
  • apps/chat/tests/openwork-connect-status.test.ts
  • apps/chat/tests/openwork-context-projector.test.ts
  • apps/chat/tests/openwork-env-runtime.test.ts
  • apps/chat/tests/openwork-session-create-tool.test.tsx
  • apps/chat/tests/org-mcp-connections.test.ts
  • apps/chat/tests/pasted-text-insertion.test.ts
  • apps/chat/tests/pasted-text.test.ts
  • apps/chat/tests/permission-approval-modal.test.ts
  • apps/chat/tests/policy-provider-reconcile.test.ts
  • apps/chat/tests/prompt-file-parts.test.ts
  • apps/chat/tests/question-modal.test.tsx
  • apps/chat/tests/reasoning-display.test.ts
  • apps/chat/tests/right-panel-empty.test.tsx
  • apps/chat/tests/session-group-event-poller.test.ts
  • apps/chat/tests/session-management-store.test.ts
  • apps/chat/tests/session-mcp-maintenance.test.ts
  • apps/chat/tests/session-number-shortcuts.test.ts
  • apps/chat/tests/session-search.test.ts
  • apps/chat/tests/session-sidebar-utils.test.ts
  • apps/chat/tests/session-sync-lifecycle.test.ts
  • apps/chat/tests/session-sync-permissions.test.ts
  • apps/chat/tests/session-sync-tool-parts.test.ts
  • apps/chat/tests/session-title-marquee.test.tsx
  • apps/chat/tests/settings-route.test.ts
  • apps/chat/tests/sidebar-tree-nesting.test.ts
  • apps/chat/tests/signin-fallback-notice.test.tsx
  • apps/chat/tests/task-suggestions.test.ts
  • apps/chat/tests/tool-error-attribution-ui.test.tsx
  • apps/chat/tests/tool-error-attribution.test.ts
  • apps/chat/tests/ui-state-store.test.ts
  • apps/chat/tests/version-gate.test.ts
  • apps/chat/tests/workbench-store.test.ts
  • apps/chat/tests/workspace-endpoint.test.ts
  • apps/chat/tests/workspace-routes.test.ts
  • apps/chat/tsconfig.json
  • apps/chat/vercel.json
  • apps/chat/vite.config.ts
  • docs/plans/console/SPEC-COMMONPLACE-OPENWORK-FORK-1.0.md
  • docs/plans/console/openwork-fork/00-FACT-SHEET.md
  • docs/plans/console/openwork-fork/01-VERIFY-FIRST.md
  • docs/plans/console/openwork-fork/02-SEAM-AUDIT.md
  • packages/openwork-paths/LICENSE
  • packages/openwork-paths/index.d.ts
  • packages/openwork-paths/index.mjs
  • packages/openwork-paths/package.json
  • packages/openwork-paths/test/paths.test.mjs
  • packages/openwork-paths/tsconfig.json
  • packages/openwork-types/LICENSE
  • packages/openwork-types/package.json
  • packages/openwork-types/src/agent-context-diagnostics.ts
  • packages/openwork-types/src/connect-link.ts
  • packages/openwork-types/src/den/connect-diagnostics.ts
  • packages/openwork-types/src/den/desktop-app-restrictions.ts
  • packages/openwork-types/src/den/desktop-policies.ts
  • packages/openwork-types/src/den/egress-diagnostics.ts
  • packages/openwork-types/src/den/inference.ts
  • packages/openwork-types/src/den/mcp-connection-action.ts
  • packages/openwork-types/src/den/microsoft-365.ts
  • packages/openwork-types/src/desktop-ipc.ts
  • packages/openwork-types/src/index.ts
  • packages/openwork-types/src/openwork-affordance.ts
  • packages/openwork-types/src/openwork-context.ts
  • packages/openwork-types/src/openwork-provider.ts
  • packages/openwork-types/src/url.ts
  • packages/openwork-types/src/workspace.ts
  • packages/openwork-types/tests/url.test.ts
  • packages/openwork-types/tsconfig.json
  • packages/openwork-types/tsup.config.ts
  • packages/openwork-ui/LICENSE
  • packages/openwork-ui/README.md
  • packages/openwork-ui/package.json
  • packages/openwork-ui/src/common/paper.ts
  • packages/openwork-ui/src/react/dithered-onboarding-shell.tsx
  • packages/openwork-ui/src/react/download-card.tsx
  • packages/openwork-ui/src/react/index.ts
  • packages/openwork-ui/src/react/paper/grain-gradient.tsx
  • packages/openwork-ui/src/react/paper/mesh-gradient.tsx
  • packages/openwork-ui/src/react/paper/static-seeded-gradient.tsx
  • packages/openwork-ui/src/react/platform-detect.ts
  • packages/openwork-ui/src/react/roadmap.tsx
  • packages/openwork-ui/tsconfig.react.json
  • packages/openwork-ui/tsup.config.react.ts
  • packaging/workspace/Dockerfile
  • packaging/workspace/check-two-doors.mjs
  • packaging/workspace/entrypoint.sh
  • packaging/workspace/railway.toml
  • scripts/audit-openwork-fork.sh

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Investigated all three. They are false positives, and the reason is specific: every one is a canary belonging to the diagnostic sanitizer's own test suite, so the scanner is flagging the test data of a secret-redaction test.

Finding Value Verification
35660538 ow_mcp_at_dGhpcy1jYW5hcnkt… The base64 tail decodes to this-canary-must-never-be-returned. Injected as a provider-controlled tool ID at agent-context-cloud-probe.test.ts:254, asserted absent at :269.
35660539 %2525252542earer%2525252520diag-secret-canary One row in a table of redaction-evasion vectors (percent-encoded Bearer, fullwidth characters, zero-width spaces) fed to the sanitizer.
35660540 eyJhbGciOiJub25lIn0.…signature123456789 Header decodes to {"alg":"none"}, payload is the canonical sub: 1234567890, and the signature position holds the literal string signature123456789. Unsigned, so no signing key exists. Asserted absent at cloud-mcp-health.test.ts:215.

Nothing to revoke or rotate.

None of the three can be removed or altered. Each is the input to a not.toContain(...) assertion, so changing it would delete the security property the test exists to hold: that the sanitizer never leaks an authorization value into diagnostics.

No code change was made. This check is the GitGuardian GitHub App rather than a ggshield CI step (no ggshield invocation exists in any workflow), so it reads no configuration from this repository and a .gitguardian.yaml here would be inert. Remediation is marking the three incidents false-positive in workspace 907603, which needs dashboard access I do not have.

The verification is recorded in apps/chat/MODIFICATIONS.md (commit 7a72aba) because these files are vendored from upstream and will re-trip on every cherry-pick that touches them.

🤖 Addressed by Claude Code

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Both automated reviewers skipped this PR on file count, and that is a fair objection rather than a quirk to wave through: 865 of the 1071 files are a vendored snapshot of different-ai/openwork at 2f2dde65796428109a665f3b733843fe3896b933, landed wholesale in a single commit. Nobody, human or bot, should read them as a diff.

I have pushed the vendoring commit as a base ref so the imported code can be subtracted:

openwork-vendor-base = 5514313e, the commit that vendored upstream.

Diffing openwork-vendor-base..HEAD gives 64 files, +2826/-3865, which is the entire authored surface of this PR and is inside both reviewers' limits.

git diff openwork-vendor-base..HEAD

@coderabbitai review --base openwork-vendor-base

Copilot cannot be scoped the same way, since it reviews the PR as a whole and the PR is still 1071 files against its 300-file cap. The Reading this diff table in the PR description lists the ten or so files that carry actual decisions, which is the manual path.

One caveat on the 64-file range, so it is not mistaken for the whole story: it excludes OW1 itself. The severing work (telemetry removed, Den control plane cut, hosted model catalog dropped) lives inside the vendoring commit, because vendoring and severing were one operation. apps/chat/MODIFICATIONS.md is the record of every such divergence and is the right place to review OW1.

🤖 Addressed by Claude Code

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --no-patch --format='%H%n%s' openwork-vendor-base
git merge-base --is-ancestor openwork-vendor-base HEAD
git diff --stat openwork-vendor-base..HEAD
git diff --name-status openwork-vendor-base..HEAD
fd -a '^MODIFICATIONS\.md$' apps/chat -x sh -c 'printf "\n--- %s ---\n" "$1"; sed -n "1,260p" "$1"' sh {}

Length of output: 854


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- remotes ---'
git remote -v

printf '%s\n' '--- remote openwork-vendor-base ---'
git ls-remote --heads origin openwork-vendor-base || true

printf '%s\n' '--- local supplied commit ---'
if git cat-file -e '5514313e^{commit}' 2>/dev/null; then
  git show --no-patch --format='%H%n%s' 5514313e
else
  echo 'Commit 5514313e is not present in this checkout.'
fi

printf '%s\n' '--- matching local refs ---'
git show-ref | grep -E 'openwork-vendor-base|5514313e' || true

Length of output: 1086


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='5514313e05d147e326f8e0a4383a072987d32385'

printf '%s\n' '--- authored range summary ---'
git diff --stat "$base"..HEAD
printf '%s\n' '--- authored files ---'
git diff --name-status "$base"..HEAD

printf '%s\n' '--- OW1 divergence record ---'
sed -n '1,360p' apps/chat/MODIFICATIONS.md

printf '%s\n' '--- focused file outlines ---'
for file in \
  apps/chat/scripts/*console-register* \
  apps/chat-server/src/console-session.ts \
  apps/chat-server/src/theorem-mcp.ts \
  packaging/workspace/check-two-doors.mjs \
  scripts/audit-openwork-fork.sh
do
  if [ -f "$file" ]; then
    printf '\n--- %s ---\n' "$file"
    ast-grep outline "$file" --view expanded || true
  fi
done

Length of output: 27800


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  apps/chat-server/src/console-session.ts \
  apps/chat-server/src/console-session.test.ts \
  apps/chat-server/src/server.ts \
  apps/chat-server/src/routes/core.ts \
  apps/chat-server/src/theorem-mcp.ts \
  apps/chat-server/src/theorem-mcp.test.ts \
  apps/chat-server/src/openwork-runtime-config.ts \
  apps/chat/scripts/console-register-lib.mjs \
  apps/chat/scripts/check-console-register.mjs \
  apps/chat/src/app/index.css \
  packaging/workspace/Dockerfile \
  packaging/workspace/entrypoint.sh \
  packaging/workspace/check-two-doors.mjs \
  scripts/audit-openwork-fork.sh
do
  printf '\n--- %s ---\n' "$file"
  cat -n "$file"
done

Length of output: 111938


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='5514313e05d147e326f8e0a4383a072987d32385'

printf '%s\n' '--- console session verifier ---'
cat -n apps/chat-server/src/console-session.ts
printf '%s\n' '--- console session contract test ---'
cat -n apps/chat-server/src/console-session.test.ts

printf '%s\n' '--- server console-session references ---'
rg -n -C 14 'readConsoleSession|resolveConsoleSessionSecret|console session|consoleSession' \
  apps/chat-server/src/server.ts apps/chat-server/src/routes/core.ts

printf '%s\n' '--- authored CSS changes near RGB token use ---'
git diff --unified=30 "$base"..HEAD -- apps/chat/src/app/index.css \
  | grep -n -E -C 12 'dls-(accent|secondary)-rgb|ow-dot-ticker|dls-accent'

printf '%s\n' '--- RGB-token definitions and uses ---'
rg -n -C 2 -- '--dls-(accent|secondary)-rgb' apps/chat/src apps/console/src

Length of output: 51257


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- console actor and workspace-claim use ---'
rg -n -C 5 --glob '*.{ts,tsx}' \
  'workspaceSlug|workspaceId|type:\s*"console"|actor\.(workspaceSlug|tenant)|actor\?\.workspaceSlug|actor\?\.tenant' \
  apps/chat-server/src

printf '%s\n' '--- route authorization and workspace resolution ---'
rg -n -C 12 --glob '*.ts' \
  'requireClient\(|requireHost\(|resolveWorkspace\(|ctx\.actor|auth ===|route\.auth|type Route' \
  apps/chat-server/src/server.ts apps/chat-server/src/routes apps/chat-server/src/types.ts

printf '%s\n' '--- console-session client gate ---'
cat -n apps/chat/src/react-app/shell/console-session-gate.tsx
printf '%s\n' '--- authored package and audit wiring ---'
git diff --unified=12 5514313e05d147e326f8e0a4383a072987d32385..HEAD -- \
  apps/chat/package.json apps/chat-server/package.json pnpm-lock.yaml

Length of output: 201401


@Travis-Gilbert Scoped review completed against openwork-vendor-base (5514313e05d147e326f8e0a4383a072987d32385), plus the OW1 record in apps/chat/MODIFICATIONS.md.

I found two issues.

  1. P1 — Bind the console session to the requested workspace.
    apps/chat-server/src/server.ts, Line 1236 creates an owner Actor from a valid console cookie. It discards claims.workspaceId. The route handlers then resolve any :id in config.workspaces without comparing it to the signed claim. A user with a valid cookie for workspace A can therefore call a workspace route for workspace B on the same daemon and receive owner access. Preserve the workspace claim in the actor or request context. Reject requests when the resolved workspace does not match the signed workspaceId. Add a contract test with two configured workspaces.

  2. P1 — Restore or replace the removed RGB semantic tokens.
    apps/chat/src/app/index.css, Line 541 and later still use --dls-accent-rgb and --dls-secondary-rgb. The OW3 change removes both definitions from the light and dark semantic layers. CSS declarations that expand rgb(var(--dls-*-rgb) / …) are invalid when these variables are absent. This removes focus rings, ticker colors, and other visual effects across existing components. Define compatible RGB tokens from the console register, or replace every remaining use with color-mix() or another token-based form.

I did not run tests or build the Docker image in the sandbox.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ositives

All three are in vendored chat-server test files and all three are canaries for
the diagnostic sanitizer, so the scanner is flagging the test data of a
secret-redaction test suite. Each is the input to a not.toContain assertion,
which is why none can be removed or altered: doing so would delete the security
property the test holds. Nothing to revoke or rotate.

The values are described rather than quoted. Quoting them is what a first pass
did, and the scanner immediately flagged this file as a fourth occurrence: the
documentation reproduced the thing it documented. The citation plus a
description is enough for a reader, and adds no new occurrence.

The check is the GitGuardian GitHub App rather than a ggshield CI step, so it
reads no configuration from this repository and a .gitguardian.yaml would be
inert; remediation is dashboard-side.

Recorded because these will re-trip on every upstream cherry-pick touching
these files.
@Travis-Gilbert
Travis-Gilbert force-pushed the claude/commonplace-openwork-fork-46bcd5 branch from 7a72aba to ac56ea9 Compare August 2, 2026 20:42
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d73edf16a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packaging/workspace/Dockerfile Outdated

# The built chat register, served by the daemon's static handler. Built on the
# host or in a prior stage: this image does not carry the web app's toolchain.
COPY apps/chat/dist /srv/openwork/public

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build the chat bundle before copying it

The Railway build uses this Dockerfile directly from the repository, but apps/chat/dist is absent from the commit tree and no earlier stage builds @commonplace/chat. Consequently COPY apps/chat/dist ... fails before the workspace image can be produced. Add a frontend build stage or commit/provide the artifact within the Docker build context.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and it was fatal: apps/chat/dist is gitignored with zero tracked files, so the COPY had no source and the image could never build. Added a first stage that installs @commonplace/chat... and runs build:web, then COPY --from=web. It also copies apps/console/src/styles, which the token generator reads.

🤖 Addressed by Claude Code

Comment thread packaging/workspace/entrypoint.sh Outdated
# --workspace, not --dir: the daemon's flag is a repeatable workspace root
# (apps/chat-server/src/config.ts). The token rides the environment rather than
# --host-token so it never appears in the container's process list.
setsid openwork-server \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Invoke the built server through an available executable

When this entrypoint runs in the workspace image, openwork-server is not on PATH: the Dockerfile builds the workspace package but neither globally installs it nor adds a package node_modules/.bin directory to PATH, and the only globally installed binary is opencode. The container therefore exits with command not found; invoke the built CLI (or explicitly install/link its bin) instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. pnpm links workspace binaries into node_modules/.bin and nothing put that on PATH, so the entrypoint would have exited command not found. PATH now includes /srv/openwork/node_modules/.bin.

🤖 Addressed by Claude Code

Comment thread packaging/workspace/entrypoint.sh Outdated
Comment on lines +76 to +79
setsid openwork-server \
--host 0.0.0.0 \
--port "${OPENWORK_PORT}" \
--workspace "${WORKSPACE_DIR}" &

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable the managed OpenCode process in the workspace image

In the workspace-container deployment, no external OpenCode URL is supplied and this command does not set OPENWORK_MANAGE_OPENCODE=1. The new CLI starts the installed engine only when that variable is exactly 1 (apps/chat-server/src/cli.ts), so the daemon starts without an engine connection and chat sessions cannot run despite OpenCode being installed in the image.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Verified at apps/chat-server/src/cli.ts:44: the managed engine starts only when the value is exactly 1. The image now sets OPENWORK_MANAGE_OPENCODE=1, so the daemon does not come up engineless with opencode installed but unused.

🤖 Addressed by Claude Code

Comment thread packaging/workspace/Dockerfile Outdated

# The built chat register, served by the daemon's static handler. Built on the
# host or in a prior stage: this image does not carry the web app's toolchain.
COPY apps/chat/dist /srv/openwork/public

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Configure the daemon to serve the copied web root

Even if the frontend artifact is supplied, copying it to /srv/openwork/public does not make it available: serveStaticUi is a no-op unless OPENWORK_WEB_ROOT is set, and neither this Dockerfile nor the entrypoint sets that variable. Requests for the chat app therefore fall through to the daemon's 404 response; configure the web root to this copied directory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. serveStaticUi is gated on OPENWORK_WEB_ROOT, so the copied bundle was inert and / fell through to 404. Set to /srv/openwork/public, the directory the new build stage writes.

🤖 Addressed by Claude Code


# The chat door. The daemon's own token store is seeded from the same value,
# so a client presenting it is an owner on both doors.
export OPENWORK_HOST_TOKEN="${WORKSPACE_TOKEN}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed the client token from WORKSPACE_TOKEN

For standalone clients that connect with the documented shared workspace token, this only sets the host credential. Normal API routes authenticate bearer tokens against OPENWORK_TOKEN, which remains randomly generated, while the web client deliberately omits the host token for non-loopback servers. Thus WORKSPACE_TOKEN opens code-server but receives 401s from ordinary chat routes; export it as the client token as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. OPENWORK_TOKEN and OPENWORK_HOST_TOKEN are separate credentials in config.ts, and the web client withholds the host token from non-loopback servers, so WORKSPACE_TOKEN opened code-server and then got 401 from every chat route. The entrypoint now exports both from the one workspace token.

🤖 Addressed by Claude Code

Comment on lines +123 to +129
function injectBootstrap(html: string, token: string): string {
if (!shouldInjectBootstrapToken()) return html;

const clientToken = token.trim();
if (!clientToken) return html;

const bootstrap = escapeScriptJson(JSON.stringify({ token: clientToken }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Disable public bootstrap-token injection for console deployments

Once OPENWORK_WEB_ROOT is configured so the packaged UI is actually served, an unauthenticated GET / reaches this fallback and receives config.token embedded in the HTML because injection defaults to enabled. Anyone who can reach the workspace service can extract that collaborator bearer token and use client-authenticated file and session routes without a console session; require authentication before serving it or set OPENWORK_WEB_BOOTSTRAP_TOKEN=0 for the console deployment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and it became reachable precisely because of the OPENWORK_WEB_ROOT fix in this same round. The image sets OPENWORK_WEB_BOOTSTRAP_TOKEN=0, so the static handler no longer embeds config.token in HTML served to an unauthenticated GET /.

🤖 Addressed by Claude Code

Comment thread apps/chat-server/src/routes/files.ts Outdated
Comment on lines +112 to +121
function resolveSafeChildPath(root: string, child: string): string {
const rootResolved = resolve(root);
const candidate = resolve(rootResolved, child);
if (candidate === rootResolved) {
throw new ApiError(400, "invalid_path", "Path must point to a file");
}
if (!candidate.startsWith(rootResolved + sep)) {
throw new ApiError(400, "invalid_path", "Path traversal is not allowed");
}
return candidate;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject workspace paths that traverse symlinks

When a checkout contains a symlink, this helper validates only the lexical path and never resolves the symlink target. A collaborator can therefore request a path such as link/outside.txt, where link points outside the workspace, and the read, write, rename, or delete call sites in routes/files.ts operate on host files outside the authorized root; resolve and validate existing ancestors (and guard creation atomically) before filesystem access.

Useful? React with 👍 / 👎.

ENV OPENWORK_PORT=8787
ENV CODE_SERVER_PORT=8080
RUN mkdir -p "${WORKSPACE_DIR}"
VOLUME ["/workspace"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist OpenCode and daemon state on the workspace volume

Only /workspace is persisted, while OpenCode stores its session database under its data home (by default /root/.local/share/opencode) and the daemon stores runtime.sqlite, tokens, and configuration under /root/.config/openwork. A Railway redeploy therefore retains the checkout but discards chat transcripts, artifact/session metadata, token records, and runtime settings; place these state directories on the mounted volume or add dedicated persistent volumes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. opencode keeps its session database under its data home and the daemon keeps runtime.sqlite, tokens, and config under its config home, both defaulting to /root, so a redeploy preserved the checkout and discarded every transcript. XDG_DATA_HOME and XDG_CONFIG_HOME now point under /workspace/state, which is the mounted volume.

🤖 Addressed by Claude Code

Comment on lines +52 to +56
function signature(payload: string, secret: string): string {
return createHmac("sha256", secret)
.update("commonplace-active-workspace-v1\0")
.update(payload)
.digest("base64url");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not give workspace containers the console signing secret

The workspace service is designed to execute user and agent code and exposes a code-server terminal, yet it receives the same symmetric COMMONPLACE_ACTIVE_WORKSPACE_SECRET used by the console to sign identity cookies. Any process or terminal user in one workspace can read that environment variable and generate valid HMAC cookies for arbitrary subjects, tenants, and workspace claims; containers should receive only an asymmetric verification key or a narrowly scoped credential that cannot mint console sessions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most important finding here, and correct. The key is symmetric, so a container that runs agent code behind a published terminal could mint sessions for arbitrary subjects and tenants rather than verify its own. Removed from the service variables, and the entrypoint now exits 78 if it finds the variable, because a workspace that boots is one someone routes traffic to. The console authenticates at its own edge and reaches this service with WORKSPACE_TOKEN, already scoped to one workspace, so no second sign-in appears.

🤖 Addressed by Claude Code

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Correction to my previous comment, and a fix for a mistake I made in it.

That comment documented the three findings by quoting them verbatim, and I committed the same table to apps/chat/MODIFICATIONS.md. The scanner then flagged that file as a fourth occurrence: the count went from 3 to 4 because the documentation reproduced the thing it was documenting. New occurrence 286614358 on incident 35660540, in commit 7a72aba4, which was mine and not vendored.

Fixed by amending 7a72aba4 (it was the branch tip, and nobody else has touched this branch) so the occurrence is removed from history rather than left behind by a follow-up commit. Force-pushed with --force-with-lease. The record now describes each value instead of quoting it, and cites the file and line for anyone who needs the literal.

Verified after the amend: back to 3 secrets, all in 5514313e, the vendoring commit. No finding now points at anything I authored.

The three vendored findings are unchanged and remain verified false positives, for the reasons in my previous comment. They are canaries in the diagnostic sanitizer's own test suite, each an input to a not.toContain(...) assertion. Nothing to revoke or rotate, and none can be altered without deleting the security property the test holds.

The check will stay red until the three incidents are marked false-positive in workspace 907603. That is dashboard-side: this check is the GitGuardian GitHub App, not a ggshield CI step, so no file in this repository can change its verdict.

🤖 Addressed by Claude Code

…, repair the workspace image

Addresses review findings from CodeRabbit and Codex on PR #158. All were real.

Console session escalation (CodeRabbit P1). consoleSessionActor built an owner
Actor from a valid cookie and discarded claims.workspaceId, while the daemon
serves several workspaces at once and handlers resolve :id against the whole
configured set. A cookie signed for workspace A reached workspace B on the same
daemon with owner rights. The actor now carries the signed workspace and
assertConsoleWorkspace rejects a mismatch with 403, enforced once in the route
dispatch so a route added later cannot forget it. Regression test added.

Removed tokens still in use (CodeRabbit P1). OW3 deleted --dls-accent-rgb and
--dls-secondary-rgb after finding three call sites. There were thirteen: the
other ten were rgba(var(--dls-accent-rgb),0.14) inside Tailwind arbitrary
values, which the original grep pattern did not match. Focus rings and tickers
across eleven components silently resolved to nothing, because an undefined
custom property is dropped rather than raised. All thirteen now use color-mix
against the register, and check-token-references.mjs fails on any owned token
used without being defined. Verified it catches the exact shipped bug.

Workspace image (Codex P1s). It could not have run. apps/chat/dist is
gitignored so the COPY had no source; a build stage now produces it.
openwork-server was not on PATH. The engine needs OPENWORK_MANAGE_OPENCODE=1,
the static handler needs OPENWORK_WEB_ROOT, and ordinary API routes
authenticate against OPENWORK_TOKEN rather than the host token, so the shared
credential opened the IDE and then got 401 from every chat route. opencode's
session database and the daemon's runtime state defaulted under /root, so a
redeploy kept the checkout and discarded every transcript; both now sit on the
volume. OPENWORK_WEB_BOOTSTRAP_TOKEN=0 stops the static handler embedding a
usable bearer token in HTML served to unauthenticated callers.

Console signing key in the workspace container (Codex P1). The key is
symmetric, and this container runs user and agent code behind a published
terminal, so anything able to read the environment could mint sessions for any
subject, tenant, or workspace rather than merely verify its own. It is removed
from the service's variables and the entrypoint refuses to start if it finds
it. The console authenticates at its own edge and reaches this service with
WORKSPACE_TOKEN, which is already scoped to one workspace, so no sign-in is
added.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Both reviewers found real defects. Everything below is fixed in 7402e8fb, with the two CodeRabbit P1s and seven of the Codex findings addressed. Typecheck clean in both packages, pnpm build:web succeeds, 16 chat-server tests pass, audit 36/36.

Console session escalation (CodeRabbit P1). The worst of them, and mine. consoleSessionActor built an owner Actor and discarded claims.workspaceId, while the daemon serves several workspaces and handlers resolve :id against the whole configured set. A cookie signed for workspace A reached workspace B on the same daemon with owner rights. The actor now carries the signed workspace, and assertConsoleWorkspace rejects a mismatch with 403 at the single dispatch chokepoint every authenticated route passes through, so a route added later cannot forget it.

Removed tokens still in use (CodeRabbit P1). Worse than reported. OW3 deleted --dls-accent-rgb and --dls-secondary-rgb after I found three call sites; there were thirteen. The other ten were rgba(var(--dls-accent-rgb),0.14) inside Tailwind arbitrary values, which my grep pattern did not match. Focus rings and tickers across eleven components resolved to nothing, and nothing caught it, because an undefined custom property is dropped rather than raised: it survives typecheck, build, and any screenshot of a surface that happens not to use it.

All thirteen now use color-mix against the register. check-token-references.mjs fails on any owned token used without being defined, and I negative-tested it against the exact bug that shipped.

The workspace image could not have run. Seven separate defects, all Codex, all confirmed against the code before fixing: apps/chat/dist is gitignored so the COPY had no source; openwork-server was not on PATH; the engine needs OPENWORK_MANAGE_OPENCODE=1; the static handler needs OPENWORK_WEB_ROOT; client routes authenticate against OPENWORK_TOKEN, not the host token; opencode and daemon state defaulted under /root so a redeploy discarded every transcript; and the static handler embedded a usable bearer token in HTML served to unauthenticated callers.

The console signing key does not belong in a workspace container. The sharpest architectural point raised. It is symmetric, and that container runs agent code behind a published terminal, so anything able to read the environment could mint sessions for arbitrary subjects and tenants rather than merely verify its own. Removed from the service variables; the entrypoint now exits 78 if it finds the variable, because failing closed is the only safe behaviour when a workspace that boots is one someone routes traffic to. The console authenticates at its own edge and reaches the service with WORKSPACE_TOKEN, so no second sign-in appears.

Not fixed, deliberately

routes/files.ts:121, symlink traversal. Not replied to inline because I did not act on it. The finding is credible and I am not disputing it, but that helper is vendored upstream at 2f2dde65 and is unmodified by this PR. Fixing it means diverging from upstream in a path-resolution routine on a fork whose cherry-pick posture is roughly ten commits a day, which is a decision worth making deliberately rather than folding into a review round. It wants its own change with its own tests, and it is a pre-existing property of the vendored daemon rather than something this PR introduces.

Still open

The image has never been built. These fixes are reasoned from the code, not from a successful docker build, since no Docker daemon was available here. Given that seven build-blocking defects were found by reading alone, treat a real build as a merge gate rather than a formality.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7402e8fba0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +34 to +35
* Same-origin by construction: OW4 serves this register under the console's
* domain, which is what lets the browser attach the console's cookie at all.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the console-origin chat proxy

This assumes the register is reachable on the Console origin, but inspection of apps/console/src/app, its Next configuration, and repo-wide references to WORKSPACE_TOKEN and port 8787 finds no route or rewrite that forwards a Console path to this workspace service. Consequently the shipped register cannot be entered through the Console as OW4 describes, and the browser never sends the Console cookie to /session/console; add the authenticated reverse proxy and token-forwarding path before relying on same-origin construction.

Useful? React with 👍 / 👎.

Comment thread apps/chat-server/src/server.ts Outdated
Comment on lines +1272 to +1274
const requested = params.id;
if (!requested) return;
if (requested === actor.workspaceId) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Map console IDs to daemon workspace IDs

When Console-cookie authentication is enabled, actor.workspaceId is the identity service's workspace ID, while this deployment invokes the daemon with only --workspace <path>, causing buildWorkspaceInfos to generate a separate ws_<path-hash> ID. The UI obtains that generated ID from /workspaces and uses it in /workspace/:id requests, so this comparison rejects every such request with workspace_unauthorized; configure the daemon with the Console workspace ID or explicitly map the claim to the local workspace.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this was a regression from my previous commit. The ids are different namespaces: buildWorkspaceInfos derives ws_<hash-of-path> while the console mints its own, so the comparison rejected every console request rather than only cross-workspace ones. Removed the comparison. The binding now keys off deployment shape: a single-workspace daemon has nowhere to escalate to, and a multi-workspace daemon refuses console auth and falls through to token auth.

🤖 Addressed by Claude Code

Comment on lines +905 to +907
const actor = await requireClient(request, config, tokens);
assertOpencodeProxyAllowed(actor, request.method, mount.restPath);
const workspace = await resolveWorkspace(config, mount.workspaceId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check workspace claims before proxying OpenCode

On a daemon configured with multiple workspaces and Console-cookie authentication, /workspace/<id>/opencode/... returns from this early proxy branch before the central assertConsoleWorkspace call. requireClient accepts the Console actor as an owner without checking mount.workspaceId, after which an attacker can substitute another configured workspace ID and read or mutate that workspace through OpenCode; validate the actor against mount.workspaceId before resolving the target.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. The early proxy branch returned before the central assertion, so the hole my previous commit claimed to close stayed open on that path. The replacement guard lives in consoleSessionActor, which this branch reaches through requireClient, so the proxy path is covered by construction rather than by a second call site someone has to remember.

🤖 Addressed by Claude Code

Comment on lines +101 to +103
const slug = SIMPLE_ICON_SLUGS[id] ?? id;
if (!SIMPLE_ICON_MISSES.has(slug) && /^[a-z0-9]+$/.test(slug)) {
candidates.push(`https://cdn.simpleicons.org/${slug}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bundle provider icons instead of fetching them remotely

When the model or extension UI renders a provider with a recognized slug, the browser loads its icon from cdn.simpleicons.org; the fallback paths similarly send provider and configured service domains to Google's favicon endpoint. This creates automatic third-party network traffic and discloses the user's provider inventory despite OW1's zero-third-party-call acceptance criterion; serve the adopted icons locally or fall back to the existing monogram.

Useful? React with 👍 / 👎.

Comment on lines +47 to +49
"packages/openwork-types/**",
"packages/openwork-paths/**",
"packaging/workspace/**",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Watch openwork-ui changes for workspace rebuilds

The Docker web stage copies and bundles packages/openwork-ui, but this Railway watch list omits that package while listing the other OpenWork packages. A change to an exported UI component therefore does not trigger a workspace-service rebuild, leaving the deployed chat bundle on the previous implementation until some unrelated watched file changes; add packages/openwork-ui/** to the build inputs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct: the web stage bundles packages/openwork-ui but the watch list omitted it, so a component change would not have rebuilt the workspace service. Added to watchPatterns.

🤖 Addressed by Claude Code

Comment thread packaging/workspace/entrypoint.sh Outdated
Comment on lines +34 to +35
echo "workspace: cloning ${WORKSPACE_REPO_URL} into ${WORKSPACE_DIR}"
git clone --depth "${WORKSPACE_CLONE_DEPTH:-1}" "${WORKSPACE_REPO_URL}" "${WORKSPACE_DIR}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep repository credentials out of logs and Git config

For a private HTTPS repository where WORKSPACE_REPO_URL contains a deploy token or userinfo, this prints the credential-bearing URL to container logs and git clone persists the same URL as remote.origin.url on the long-lived workspace volume. Anyone with log or code-server access can then recover the repository credential independently of WORKSPACE_TOKEN; pass credentials through a helper or SSH setup and configure a sanitized remote URL after cloning.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. The URL is now logged with userinfo stripped, and after cloning the stored remote is rewritten to the redacted form so the credential does not persist as remote.origin.url on a volume that code-server users can read.

🤖 Addressed by Claude Code

Comment thread packaging/workspace/entrypoint.sh Outdated
Comment on lines +105 to +106
wait -n
status=$?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Capture child failures before errexit terminates supervision

When either door exits nonzero, Bash's wait -n returns that child's nonzero status, so the active set -e terminates the entrypoint at this line before status=$? and shutdown run. The surviving code-server or chat process is consequently killed with the container rather than receiving the intended TERM and cleanup path, risking incomplete state writes during crashes; run the wait in an if/errexit-safe block before capturing its status.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Under set -e, wait -n returning a nonzero child status terminated the script at that line, so shutdown never ran and the surviving door was killed by container teardown instead of receiving TERM. Wrapped in an if so the failure branch is errexit-safe and cleanup always runs.

🤖 Addressed by Claude Code

Comment on lines +62 to +65
addRoute(routes, "POST", "/approvals/:id", "host", async (ctx) => {
const body = await readJsonBody(ctx.request);
const reply = body.reply === "allow" ? "allow" : "deny";
const result = ctx.approvals.respond(ctx.params.id, reply);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Distinguish approval IDs from workspace route IDs

When a Console-authenticated user responds to a permission prompt, the central assertConsoleWorkspace hook runs after host authentication and interprets this route's params.id as a workspace ID. An approval request ID never equals the cookie's workspace ID, so every POST /approvals/:id is rejected with workspace_unauthorized before this handler runs, leaving the tool call stuck; restrict the workspace assertion to routes whose :id actually denotes a workspace.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and the worst of my regressions: :id on this route is an approval id, so the assertion rejected every permission response with workspace_unauthorized and left tool calls stuck. That is the round-trip OW2 exists to prove. The central assertion is gone entirely rather than narrowed, since the namespace problem above made it wrong even on genuine workspace routes.

🤖 Addressed by Claude Code

Comment on lines +144 to +149
const healthResponse = () => jsonResponse({
ok: true,
version: serverVersion,
opencodeVersion,
uptimeMs: Date.now() - config.startedAt,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail health checks when the managed engine exits

When the managed OpenCode child exits after startup, openwork-server remains alive and this endpoint continues returning ok: true without checking the registered engine's isAlive state. The Docker and Railway health checks call only /health, so the container is never restarted even though every session operation has lost its engine; include engine liveness in readiness or terminate the daemon when its managed child exits.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Fixed at the lifecycle rather than the endpoint: cli.ts now watches the managed engine and exits 70 when it dies, so the container healthcheck and the Railway ON_FAILURE policy restart the pair. Only armed for a managed engine, since an externally supplied opencode is somebody else`s lifecycle.

🤖 Addressed by Claude Code

Comment on lines +114 to +115
child.stdout?.on("data", (chunk) => {
output += chunk.toString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop accumulating managed-engine output after startup

After the listening URL is found, resolving the startup promise does not remove either stream listener, so every later OpenCode log line is appended permanently to output; the stdout handler also repeatedly splits the entire ever-growing string. An active long-running workspace therefore incurs unbounded memory growth and increasing per-log CPU until the daemon can be OOM-killed; detach the startup parsers once ready while continuing to drain or forward the streams.

Useful? React with 👍 / 👎.

Three of this round's findings are regressions from the previous commit. The
binding I added compared claims.workspaceId against the route's :id, and that
was wrong in two independent ways.

The ids are different namespaces. This daemon derives ws_<hash-of-path> in
buildWorkspaceInfos while the console mints its own, so the comparison rejected
every console-authenticated request rather than only cross-workspace ones.

And :id is not always a workspace. POST /approvals/:id carries an approval id,
so the check rejected every permission response with workspace_unauthorized,
breaking the exact round-trip OW2 depends on.

It also missed the case it was written for: the opencode proxy branch returns
before the central assertion ran, so the escalation stayed open on that path.

Replaced with a guard on deployment shape. What bounds a console session is the
container: OW5 runs one workspace per container and the console reaches it with
a workspace-scoped token, so a single-workspace daemon has nowhere to escalate
to. A daemon serving several has no such boundary and no reliable mapping
between the id namespaces, so console auth is refused there and falls through
to token auth, which fails closed. This removes the comparison entirely and
covers the proxy branch, since it also authenticates through requireClient.

Also from this round:

Entrypoint, wait -n under set -e. A nonzero child status terminated the script
at the wait itself, so shutdown never ran and the surviving door was killed by
container teardown instead of TERM, risking a half-written state file.

Entrypoint, clone credentials. A private HTTPS WORKSPACE_REPO_URL can carry a
deploy token in its userinfo. It was printed to container logs and persisted by
git as remote.origin.url on a volume any code-server user can read, so the
credential outlived WORKSPACE_TOKEN. Logged redacted, and the stored remote is
rewritten without userinfo after cloning.

Railway watch patterns omitted packages/openwork-ui, which the web stage
bundles, so a component change would not have rebuilt the workspace service.

Engine watchdog. When the managed engine exited, the daemon stayed up and
/health kept answering ok, so the container healthcheck saw a healthy service
with no engine and the restart policy never fired. The daemon now exits 70.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Seven of the ten fixed in 9c890881. Three not, with reasons below.

Three of these were regressions from my own previous commit, and one of them was serious. The workspace binding I added compared claims.workspaceId to the route's :id. That was wrong twice: the ids are different namespaces, so it rejected every console request rather than only cross-workspace ones; and :id is not always a workspace, so POST /approvals/:id was rejected too, breaking the permission round-trip that OW2 exists to prove. It also missed the opencode proxy branch it was written to protect, which returns before the central assertion.

The replacement drops the comparison and guards on deployment shape instead. What actually bounds a console session is the container: OW5 runs one workspace per container and the console reaches it with a workspace-scoped token, so a single-workspace daemon has nowhere to escalate to. A daemon serving several has no such boundary and no reliable mapping between the id namespaces, so console auth is refused there and falls through to token auth, which fails closed. The proxy branch is covered by construction, since it authenticates through the same requireClient.

Also fixed: the wait -n / set -e interaction that skipped cleanup, clone credentials leaking into logs and remote.origin.url, the missing packages/openwork-ui watch pattern, and the health check reporting ok with a dead engine, handled at the lifecycle by exiting 70 so the restart policy fires.

Not fixed

console-session-gate.tsx:35, the console-origin proxy does not exist. This is the sharpest finding in the round and I am not disputing it. Amendment A9 records the entry decision as a route on the console origin, but the route was never built: no rewrite in apps/console forwards to the workspace service, so the cookie never reaches /session/console and OW4's no-second-sign-in property is unproven end to end. I am not going to add an authenticated reverse proxy as a review-round patch. It is a console-side deliverable with its own auth and routing surface, and burying it here would repeat the mistake that produced the three regressions above: a fix written faster than it was understood. It belongs with OW7, which is the next piece of console work, and it should land with a test that actually walks a console session into the register.

managed-opencode.ts:115, unbounded output growth. Real, and it is vendored upstream at 2f2dde65, unmodified by this PR. Same reasoning as the symlink finding from the last round: divergence in a hot path on a fork taking roughly ten commits a day is a deliberate decision, not a review-round edit.

provider-logo-src.ts:103, remote icon fetches. Known and already recorded as the OW1 residual in apps/chat/MODIFICATIONS.md. Its disposition is decided: the Noun Project icon set lands locally and removes the CDN call as a side effect. Worth noting the finding is broader than my record, which named only cdn.simpleicons.org and not the Google favicon fallback, so I will widen that entry.

Standing caveat

Everything here is reasoned from source. The container has still never been built, and this round found that my previous round's fix was broken in three ways, which is the argument for treating a real docker build and a live console session as merge gates rather than formalities.

The record named cdn.simpleicons.org only. Review found the fallback path also
reaches Google's favicon endpoint, which discloses configured service domains
rather than just the provider list, so the OW1 network-trace qualification is
broader than written. Same disposition: the Noun Project icon set lands locally
and removes both.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2276d292f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packaging/workspace/entrypoint.sh Outdated
Comment on lines +119 to +120
if ! wait -n; then
status=$?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the failing door's exit status

Fresh evidence after the prior wait-loop fix: the ! operator inverts wait -n before $? is captured, so when a child exits with status 70 this assignment records 0. The entrypoint consequently exits successfully after any door failure, and the ON_FAILURE policy in packaging/workspace/railway.toml will not restart the workspace; capture the status without ! while keeping the command in an errexit-safe conditional.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this was a bug in my own previous fix. Verified empirically rather than by reading: if ! cmd; then status=$?; fi captures 0 because $? is the negation's status, while cmd || status=$? captures 70. So the entrypoint would have exited successfully after any door failure and ON_FAILURE would never have restarted the service. Switched to the || form, which is equally errexit-safe and preserves the status.

🤖 Addressed by Claude Code

Comment on lines +112 to +113
ENV XDG_DATA_HOME=/workspace/state/data
ENV XDG_CONFIG_HOME=/workspace/state/config

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist the daemon data directory

Fresh evidence after the persistence fix: auditLogPath() in apps/chat-server/src/audit.ts uses openworkServerDataDir(), whose default is $HOME/.openwork/openwork-server and does not consult either XDG variable set here. Because only /workspace is mounted, every redeploy still discards the audit trail returned by /workspace/:id/audit; set OPENWORK_DATA_DIR to a directory under /workspace/state as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and a real gap in my previous persistence fix: openworkServerDataDir() does not consult XDG at all, so the variables I set did nothing for the audit trail. It defaults to $HOME/.openwork and reads OPENWORK_DATA_DIR, which now points under /workspace/state and is created in the image.

🤖 Addressed by Claude Code

Comment on lines +89 to +92
!storedConnectionIsStaleDesktopRuntime && hasUsableConnection(normalizedBaseUrl, resolvedToken)
? "stored-settings"
: hasUsableConnection(sameOriginBaseUrl, resolvedToken)
? "same-origin"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow cookie-authenticated same-origin connections

Even after the Console proxy is added, the intended cookie-only OW4 flow has a same-origin URL but no bearer token because the session cookie is HttpOnly. Requiring resolvedToken here therefore classifies that connection as empty; use-workspace-route-state.ts then clears the client at its !resolvedToken check and never sends an API request on which the browser could attach the valid cookie. Treat a same-origin web connection as usable without a bearer token and let cookie authentication authorize its requests.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and it is the client-side half of the same gap as the missing proxy: the cookie-only flow was unreachable regardless of routing. A same-origin web deployment is now usable without a bearer token. Worth noting for review: no credentials change was needed, because fetch defaults to same-origin and A9's entry decision is a route on the console origin. Nothing is granted by the looser classification either, since the daemon still authenticates every request and the cookie only travels to the origin that set it.

🤖 Addressed by Claude Code

Comment thread apps/chat-server/README.md Outdated
## Quick start

```bash
npm install -g openwork-server

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Install the renamed server package in the quick start

The publish script preserves the package name from package.json, which is @commonplace/chat-server, but this command installs the unscoped openwork-server package instead, so anyone following the published README either gets an unrelated upstream package or an install failure rather than this fork. The source command below has the same stale filter name; update both examples to select @commonplace/chat-server while retaining the openwork-server binary name.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Both the published install and the source filter named the pre-rename package, so following the README fetched an unrelated upstream package rather than this fork. Updated to @commonplace/chat-server; the openwork-server binary name is unchanged, since that is what the entrypoint and PATH rely on.

🤖 Addressed by Claude Code

…ions through

The errexit fix from the previous commit introduced a status-swallowing bug.
`if ! wait -n; then status=$?; fi` captures the negation's own status, which is
0, not the child's. The entrypoint would have exited successfully after any
door failure and Railway's ON_FAILURE policy would never have restarted the
service. Verified both forms empirically: the if-bang form yields 0 and
`wait -n || status=$?` yields 70. Using the || form, which is equally
errexit-safe and keeps the status.

Cookie-only same-origin connections were unreachable. hasUsableConnection
requires a bearer token, but the console session is an HttpOnly cookie the page
cannot read, so the OW4 flow classified as "empty" and use-workspace-route-state
cleared the client before any request went out. The browser never got the
chance to attach a cookie it already holds. A same-origin web deployment is now
usable without a token. Nothing is granted by this: the cookie only travels to
the origin that set it, fetch defaults to credentials: same-origin so it rides
without further change, and the daemon still authenticates every request.

Audit trail persistence. openworkServerDataDir() does not consult XDG at all:
it defaults to $HOME/.openwork and reads OPENWORK_DATA_DIR, so the previous
commit's XDG variables left the log behind /workspace/:id/audit on the
container layer, discarded on every redeploy. Pointed at /workspace/state.

README installed the pre-rename package name, so anyone following it got an
unrelated upstream package rather than this fork.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fe8cfe110

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1141 to +1144
const directory = workspace ? resolveOpencodeDirectory(workspace) : null;
if (directory && !headers.has("x-opencode-directory")) {
headers.set("x-opencode-directory", buildOpencodeDirectoryHeader(directory));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Override caller-supplied OpenCode directory headers

When an authenticated client supplies x-opencode-directory, this condition preserves it instead of applying the directory belonging to mount.workspaceId. OpenCode uses this header as its working directory, so a collaborator can proxy requests with a path such as /etc or /workspace/state and make the engine operate outside the authorized workspace root. Always overwrite the header with the resolved workspace directory, or reject values that are not canonically contained within it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and the sharpest finding this round. The resolved workspace directory now wins unless the caller value is canonically inside it, which closes the escape while keeping the legitimate case of naming a subdirectory. isWithinDirectory is unit-tested directly, including traversal, percent-encoded input, and the sibling case where /workspace/repo-other must not match /workspace/repo. Named honestly in the code: it resolves .. but not symlinks, so a link inside the workspace pointing out still escapes. That is the same limitation as the file-routes finding and is tracked separately rather than half-solved here.

🤖 Addressed by Claude Code

Comment on lines +42 to +43
git clone --depth "${WORKSPACE_CLONE_DEPTH:-1}" "${WORKSPACE_REPO_URL}" "${WORKSPACE_DIR}"
git -C "${WORKSPACE_DIR}" remote set-url origin "${redacted_url}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve usable authentication for private Git remotes

When WORKSPACE_REPO_URL contains the only credential for a private HTTPS repository, rewriting origin to the redacted URL makes every later git fetch, pull, or push from the persistent workspace unauthenticated. The environment variable is not automatically consumed by Git, and this image configures no credential helper, so the checkout can only clone once and cannot synchronize afterward. Keep the remote sanitized, but configure an ephemeral helper or another non-persisted authentication mechanism for subsequent Git operations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and a regression from my own previous commit: sanitizing the remote kept the credential off the volume but left the checkout able to clone once and never fetch again, since git does not read WORKSPACE_REPO_URL and the image configures no helper. Added a per-repository credential helper that reads the variable at call time, so fetch, pull, and push work while nothing is persisted to the volume. The remote stays sanitized.

🤖 Addressed by Claude Code

Comment on lines +1134 to +1139
const headers = new Headers(input.request.headers);
headers.delete("authorization");
headers.delete("x-openwork-host-token");
headers.delete("x-openwork-client-id");
headers.delete("host");
headers.delete("origin");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip hop-by-hop headers before proxying requests

When a client or the Console reverse proxy sends a chunked request to an /opencode route, the copied headers still contain transfer-encoding: chunked even though the body is subsequently buffered and passed to fetch. Node's fetch rejects a caller-supplied transfer-encoding header, so these proxied POST requests fail with a 500 before reaching OpenCode. Remove transfer-encoding and the other hop-by-hop transport headers when rebuilding the upstream request.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. The body is buffered and handed to fetch, which rejects a caller-supplied transfer-encoding, so a chunked request would have 500ed before reaching the engine. Stripping transfer-encoding, connection, keep-alive, upgrade, te, trailer, and the two proxy-auth headers when rebuilding the upstream request.

🤖 Addressed by Claude Code

Comment on lines +203 to +207
server.on("error", (error: NodeJS.ErrnoException) => {
if (error.code === "EADDRINUSE" && !retriedFreePort) {
retriedFreePort = true;
server.listen(0, hostname);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail instead of silently rebinding the server port

When the configured port is occupied, this silently starts the daemon on an OS-selected port even though the managed engine's OPENWORK_SERVER_URL, status responses, the workspace health check, and external clients continue using config.port. The process therefore remains alive on an undiscoverable port while callbacks and readiness checks target the unrelated listener that won the original port. Propagate EADDRINUSE so the supervisor can retry, or update every dependent URL and advertised port after rebinding.

Useful? React with 👍 / 👎.

Comment on lines +527 to +528
if (!ctx.actor?.tokenHash || session.actorTokenHash !== ctx.actor.tokenHash) {
throw new ApiError(403, "forbidden", "File session does not belong to this token");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Support console actors when validating file sessions

In the cookie-authenticated Console flow, the actor has a subject and workspace identity but no tokenHash. File-session creation therefore stores an empty actorTokenHash, and this check rejects every subsequent renew, catalog, read, write, or close request with 403 because ctx.actor.tokenHash remains absent. Bind sessions to a stable identity available for both actor types, such as a keyed tuple of console subject, tenant, and workspace, instead of requiring a bearer-token hash.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and a direct consequence of the console actor I added: no bearer token means actorTokenHash stored empty, so the session refused its own owner on every renew, catalog, read, write, and close. Fixed at the actor rather than in the vendored file routes: the console actor now carries a derived hash over the subject, tenant, and workspace the console signed, which is stable across a session and distinct between subjects, with a console: prefix so it cannot collide with a real token hash.

🤖 Addressed by Claude Code

…tory escape

File sessions were broken for every console user. routes/files.ts binds a
session to actor.tokenHash and refuses any later request whose hash differs. A
console actor holds no bearer token, so it stored an empty hash and was then
refused by its own session on renew, catalog, read, write, and close. The actor
now carries a derived hash over the subject, tenant, and workspace the console
signed: stable across a session, distinct between subjects, and prefixed so it
cannot collide with a real token hash. No change to the vendored file routes.

Caller-supplied x-opencode-directory was a workspace escape. The engine uses
that header as its working directory and upstream preserved the caller's value
whenever it was present, so a header of /etc pointed the engine outside the
authorized root. The resolved workspace directory now wins unless the caller's
value is canonically inside it, which keeps the legitimate case of naming a
subdirectory. isWithinDirectory is tested directly: root, descendants,
traversal, percent-encoded values, undecodable input, and the sibling case
where /workspace/repo-other must not match /workspace/repo.

Hop-by-hop headers are stripped before the upstream request. The body is
buffered and handed to fetch, which rejects a caller-supplied
transfer-encoding, so a chunked upload would have failed with 500 before
reaching the engine.

Private repository sync was broken by my own previous commit. Sanitizing
remote.origin.url kept the credential off the volume but left the checkout able
to clone once and never fetch again, since git does not read
WORKSPACE_REPO_URL and the image configures no helper. A per-repository
credential helper now reads the variable at call time, so fetch, pull, and push
work while nothing is written to the volume.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Four of five fixed in 30e4da02. The fifth is declined with a reason.

Two were caused by my own OW4 work. The console actor holds no bearer token, so file sessions stored an empty actorTokenHash and then refused their own owner on every renew, catalog, read, write, and close. Fixed at the actor, not in the vendored file routes: it now carries a hash derived from the subject, tenant, and workspace the console signed. And sanitizing remote.origin.url last round kept the credential off the volume but left the checkout able to clone once and never fetch again, which a per-repository credential helper now restores without persisting anything.

The directory escape was the sharpest finding. The engine treats x-opencode-directory as its working directory and upstream preserved whatever the caller sent, so a header of /etc pointed it outside the workspace. The resolved directory now wins unless the caller's value is canonically inside it. isWithinDirectory is unit-tested directly rather than reasoned about: root, descendants, .. traversal, percent-encoded input, undecodable input, and the sibling case where /workspace/repo-other must not match /workspace/repo. Its limit is stated in the code: it resolves .. but not symlinks, so a link inside the workspace pointing out still escapes. That is the same gap as the earlier routes/files.ts finding and belongs with it, not half-solved here.

Declined

serve-node.ts:207, silent port rebinding. Real, and vendored upstream at 2f2dde65, untouched by this PR. The correct fix is a judgment call I should not make in a review round: propagating EADDRINUSE changes startup behaviour for every existing deployment including desktop, while rebinding-and-readvertising means threading the actual port through OPENWORK_SERVER_URL, the status response, and the health check. Worth noting the workspace image is not exposed to it in the way the finding describes, since the container owns its ports and wait -n plus the exit-70 watchdog surface a failed bind as a container restart rather than a silent survivor.

Standing position, unchanged

Five review rounds. Findings in my own fixes in four of them, two more this round. Everything verifiable statically passes: 32 tests, both packages typecheck, build:web succeeds, audit 36/36. None of that has predicted a single one of these defects, because none of it runs the container or a browser.

A real docker build and a live console session remain the two gates worth holding merge on. The second is still blocked on the console-origin route that A9 records and nobody built.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30e4da0270

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// request metadata-only results so renderer settings panes do not receive
// every raw secret value up front. Reload semantics are driven from the UI
// after a write; this surface is user-scoped, not workspace-scoped, so no audit.
addRoute(routes, "GET", "/env", "host-token", async (ctx) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow Console owners to use environment routes

In the intended cookie-only Console flow, the browser has no X-OpenWork-Host-Token, but every /env route uses host-token authentication, whose requireHostToken path never accepts the Console actor. The Environment settings UI calls these routes to list and save provider credentials, so authenticated Console users receive 401 responses and cannot configure the model credentials needed by the workspace; use the Console-aware host mode or have the authenticated proxy provide a scoped host credential.

Useful? React with 👍 / 👎.

import { useBootState, useBootOverlayVisible } from "./boot-state";
import { OwDotTicker } from "./dot-ticker";

const RELEASES_URL = "https://github.com/different-ai/openwork/releases";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point boot recovery at a CommonPlace release

Whenever the web register reports a boot error, this recovery link directs the user to install a release from the donor different-ai/openwork project. That is not the canonical CommonPlace desktop host, so a user following the recovery instruction can install an unrelated application instead of repairing CommonPlace; link to the CommonPlace support/release destination or omit the download action.

AGENTS.md reference: AGENTS.md:L27-L27

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and an OW1 miss rather than a nitpick: the severing pass removed donor hosts from the bundle but left this one, so a user hitting a boot error was sent to install a different application. Now opt-in via VITE_OPENWORK_RELEASES_URL like the other destinations, and the action is not rendered when unset rather than emitting an empty href.

🤖 Addressed by Claude Code

return jsonResponse({
runtime,
runtimeKeys: runtimeConfigKeys(runtime),
effectiveRuntime,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact injected credentials from runtime diagnostics

When THEOREM_API_KEY is configured, buildOpenworkRuntimeConfigObject injects it into effectiveRuntime.mcp.theorem.headers.Authorization, and this client-authenticated route returns that object verbatim both here and under sources.injected.config. Because client authentication admits even viewer tokens and this handler performs no scope check, any viewer can retrieve the workspace's Theorem bearer key, along with secret-bearing runtime/global MCP configuration; restrict this diagnostic to an owner or recursively redact credentials before serializing it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this one is squarely my OW2 injection: the Theorem MCP header is built from THEOREM_API_KEY, and client auth admits a viewer, so any viewer could read the workspace bearer key. The payload now goes through the daemon's own sanitizeDiagnosticValue rather than a bespoke filter, so it follows the same rules as every other diagnostic surface. Tested with a control assertion that the key is present before redaction, so the test cannot pass by the key never having been there.

🤖 Addressed by Claude Code

if (!isValidEnvKey(key)) {
throw new ApiError(400, "invalid_env_key", "Invalid environment variable name");
}
const removed = await env.delete(key).catch(rethrowEnvStoreReadError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Revoke engine auth when deleting a provider key

When the deleted environment key supplies a managed provider credential, this removes only the env-store record and never deletes the credential previously delivered to OpenCode's /auth/:provider endpoint. syncManagedProviderAuth also treats a missing stored credential as a skip, and OpenCode auth persists outside the child process, so the provider continues accepting requests with the supposedly removed key immediately and after restarts; map the removed key to managed providers and clear their engine auth as part of this operation.

Useful? React with 👍 / 👎.

Comment on lines +35 to +36
resolve(packageRoot, "dist/bin/openwork-server"),
resolve(outputRoot, "dist/bin/openwork-server")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish binaries for each supported platform

The publish flow runs build:bin, which compiles only for the publisher's current OS and architecture, and then places that single native executable at the platform-neutral dist/bin/openwork-server path. Anyone installing the documented global package on a different platform receives that same binary and fails with an incompatible executable rather than starting the server; publish platform-specific packages or include all targets and select the matching binary in the launcher.

Useful? React with 👍 / 👎.

Comment thread apps/chat-server/src/server.ts Outdated

return {
type: "console",
scope: "owner",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve workspace roles in Console actors

The Console issues the active-workspace cookie after checking only that the subject is a member, even though its workspace contract carries a role and permissions, but this conversion hardcodes every such member to owner. A read-only or otherwise restricted workspace member can therefore use cookie authentication for owner-only operations such as token management, approvals, workspace deletion, and runtime upgrades; include an authorized role or scope in the signed claim and derive the daemon scope from it instead of elevating every member.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and mine. The console checks membership only and the signed claim carries no role, so this daemon cannot distinguish a read-only member from an admin, and I minted owner for all of them. Now collaborator, which is what the chat register actually needs. Owner-only operations are deliberately unreachable by console session until the console signs a role and the scope derives from it. That is the same missing piece as the console-origin route in A9, so it is one console-side change rather than two.

🤖 Addressed by Claude Code


export function HTMLPreview({ className, ...props }: HTMLPreviewProps) {
if (props.type === "text") {
return <iframe srcDoc={props.content} title={props.title} className={cn("h-full w-full border-0", className)} sandbox="allow-scripts allow-same-origin" />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Isolate HTML previews from the application origin

When a user opens an agent- or collaborator-controlled HTML artifact, this srcDoc frame grants both scripts and same-origin access. The document consequently runs with the application's origin and can access parent, mutate the chat UI, read origin storage, or issue authenticated same-origin requests; keep the frame in an opaque sandbox by removing allow-same-origin, disable scripts, or serve previews from a separate origin.

Useful? React with 👍 / 👎.

…orem key

Two security defects, both created by my own OW2 and OW4 work.

Console sessions minted owner for every member. The console issues the cookie
after checking membership only: its workspace contract carries a role but the
signed claim does not, so this daemon cannot tell a read-only member from an
admin. Owner reached token management, approvals, workspace deletion, and
runtime upgrades. The actor is now collaborator, which is what the chat
register needs. Owner-only operations are deliberately unreachable by console
session until the console signs a role and the scope derives from it, which is
the same missing piece as the console-origin route in A9.

The runtime-config diagnostic leaked that key. OW2 injects the Theorem MCP with
an Authorization header built from THEOREM_API_KEY, and the route is client
auth, which admits a viewer token, so any viewer could read the workspace's
bearer key along with whatever the runtime and global MCP configs carry. The
payload now passes through the daemon's own diagnostic sanitizer rather than a
bespoke filter, so it follows the same rules as every other diagnostic surface.
Tested with a control assertion first, so the test cannot pass by the key never
having been present.

Also an OW1 miss: the boot-error overlay linked to the donor's releases page,
so a CommonPlace user hitting a boot failure was sent to install a different
application. Opt-in through VITE_OPENWORK_RELEASES_URL like the other
destinations, and the action is not rendered when unset rather than emitting an
empty href.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Three of seven fixed in 9c128233. Four declined, with reasons.

Two were security defects I created. Console sessions minted owner for every member, because the console checks membership only and the signed claim carries no role, so the daemon cannot tell a read-only member from an admin. That reached token management, approvals, workspace deletion, and runtime upgrades. Now collaborator. And the runtime-config diagnostic returned OW2's injected THEOREM_API_KEY verbatim on a route whose client auth admits viewers, so any viewer could read the workspace bearer key. Redacted through the daemon's own sanitizer, tested with a control assertion so the test cannot pass by the key never having been present.

The third was an OW1 miss: the boot-error overlay linked to the donor's releases page.

Declined, and a pattern worth naming

/env routes rejecting console users, engine auth surviving key deletion, single-platform binaries published as platform-neutral, and the srcDoc preview granting allow-scripts with allow-same-origin.

The last is the one I would prioritize if this were my call: an agent-controlled HTML artifact running on the application origin can read storage and issue authenticated same-origin requests. It is vendored and pre-existing, and the fix is a product decision about what artifact previews are allowed to do, not a review-round edit.

The /env finding is different in kind and worth stating plainly: it is not fixable in this daemon. Those routes are host-token auth, and a console session deliberately is not a host credential, especially now that it is collaborator rather than owner. Making /env reachable by console session would hand provider-credential management to every workspace member, which is the defect I just fixed pointing the other way. It needs the console to sign a role and mint a scoped credential, which is the same missing console-side work as A9's route.

That is now three findings converging on one gap: the console must sign a role, and the console-origin route must exist. Until both land, OW4 is a mechanism with no caller, and I would rather say that than keep patching around it.

Standing position

Six rounds. Defects in my own work in five of them, two more here, both security. Everything statically verifiable passes: 34 tests, both packages typecheck, build:web succeeds, audit 36/36. That signal has not predicted a single one of these.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c12823326

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/chat-server/src/server.ts Outdated
Comment on lines +1366 to +1367
const consoleActor = consoleSessionActor(request, config);
if (consoleActor) return consoleActor;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require owner scope before accepting Console host access

When Console-cookie authentication is enabled, consoleSessionActor now returns a collaborator-scoped actor, but this branch accepts it for every host-only route without checking that scope. Fresh evidence after the earlier role-elevation finding is that changing the actor from owner to collaborator did not close the escalation: any Console member can still list or mint owner tokens, delete workspaces, or trigger runtime upgrades through auth: "host" routes. Require consoleActor.scope === "owner" before returning it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and the most important finding in the round: my previous commit did not close this. I changed consoleSessionActor from owner to collaborator and never re-read the branch that consumes the scope, so requireHost kept returning the actor unconditionally and every console member still reached token minting, workspace deletion, and runtime upgrades. The comment on that branch still asserted the owner premise I had just removed.

Now gated on scope === "owner", written against the scope rather than the actor type so a console that later signs an owner role needs no change here. Console actors are collaborator today, so this rejects them all.

Worth naming why it was invisible: every test I had written asserted the shape of the actor and none asserted what the authorization path did with it. consoleActorSatisfiesHost is now extracted and exported so the decision is testable directly, covering collaborator, viewer, absent, and owner.

I also swept the remaining actor.scope consumers rather than waiting for the next round. assertOpencodeProxyAllowed and requireClientScope are rank-based and handle collaborator correctly. requireHost was the only broken one.

🤖 Addressed by Claude Code

Comment on lines +4643 to +4645
const path = join(workspace.path, file.path);
await ensureDir(dirname(path));
await writeFile(path, file.content, "utf8");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Contain imported files across symlinked directories

When the checkout contains a symlink such as .opencode/plugins -> /outside, an approved workspace import with path .opencode/plugins/tool.js passes the lexical portable-path checks and this writeFile follows the link, overwriting a file outside the authorized workspace. This is fresh evidence beyond the previously reported file-session helper: the independent workspace-import path performs its own unchecked joined write, so fixing routes/files.ts does not protect imports. Resolve and validate existing ancestors before applying the import.

Useful? React with 👍 / 👎.

Comment on lines +23 to +24
const nextContent = buildFrontmatter(normalizeCommandFrontmatter(parsed.data)) + parsed.body.replace(/^\n?/, "\n");
await writeFile(filePath, nextContent, "utf8");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep command listing read-only

When OPENWORK_READONLY=1 and a legacy command contains model: null, GET /workspace/:id/commands reaches listCommandsInDir, which calls this repair function and rewrites the command file. The normal bootstrap repair is deliberately skipped in read-only mode, but the listing path bypasses that guard, allowing even a viewer token to mutate the checkout through a GET without approval or audit; move the repair to a writable migration path or suppress the write while listing.

Useful? React with 👍 / 👎.

Comment on lines +1633 to +1634
username: opencodeUsername,
password: opencodePassword,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact upstream passwords from viewer workspace listings

When a remote OpenCode workspace is configured with opencodePassword, any viewer token can call GET /workspaces and receive that password together with the upstream baseUrl. The viewer can then authenticate directly to the remote OpenCode server and bypass this daemon's read-only scope checks entirely; omit upstream credentials from the client serializer or expose them only through an owner-only management route.

Useful? React with 👍 / 👎.

Comment on lines +439 to +440
addRoute(routes, "POST", "/experimental/google-workspace/smoke-test", "client", async () => {
return jsonResponse(await googleWorkspaceRunScopeSmokeTest(config));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict the mutating Google smoke test

When a viewer token calls this client-authenticated route, no scope or read-only check runs before googleWorkspaceRunScopeSmokeTest creates a real Drive file and Gmail draft in the connected account. This lets a nominally read-only viewer repeatedly mutate and clutter an owner's Google Workspace; require collaborator or owner scope and honor ensureWritable before running the smoke test.

Useful? React with 👍 / 👎.

});
});
if (!callbackServer) throw new Error("Could not start Google Workspace OAuth callback server.");
const redirectUri = `http://127.0.0.1:${port}/`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a reachable OAuth callback for remote deployments

When chat-server runs in the Railway workspace container, this redirect sends the user's browser to a random 127.0.0.1 port on the user's own machine, while the callback listener was opened on loopback inside the remote container. The authorization callback therefore never reaches the pending flow and Google Workspace connection always expires in the deployed web register; use a stable Console-origin callback or another externally reachable broker callback for remote deployments.

Useful? React with 👍 / 👎.

The elevation fix in the last commit changed consoleSessionActor from owner to
collaborator and stopped there. requireHost returned the console actor without
checking scope, so host-only routes kept accepting it and the escalation never
closed: any console member still reached token minting, workspace deletion, and
runtime upgrades. The comment on that branch still asserted the owner
assumption I had just removed. The bearer path directly below it has always
made the check explicitly.

The branch is now gated on owner scope, written against the scope rather than
the actor type so a console that later signs an owner role needs no change
here. Console actors are collaborator today, so this rejects them all.

Why it was invisible: every test asserted the shape of the actor, none
asserted what the authorization path did with it. consoleActorSatisfiesHost is
extracted and exported so the decision itself is testable, and the new tests
cover collaborator, viewer, absent, and owner.

Swept the other consumers of actor.scope rather than waiting for the next
review round to find them. assertOpencodeProxyAllowed and requireClientScope
are both rank-based and handle collaborator correctly: viewer stays read-only,
owner-required routes still reject. requireHost was the only broken consumer.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

One fixed in b221e7ef. Five declined. The one I fixed is the one that mattered, and it was mine.

My previous commit did not close the escalation it claimed to close. I changed consoleSessionActor from owner to collaborator and never re-read the branch consuming that scope, so requireHost kept returning the actor unconditionally. Every console member still reached token minting, workspace deletion, and runtime upgrades. The comment on that branch still asserted the owner premise I had just deleted.

Why no test caught it: every test I had written asserted the shape of the actor, none asserted what the authorization path did with it. That is a category of test I was missing entirely, not a case I forgot. consoleActorSatisfiesHost is now exported so the decision is testable on its own.

I also swept the remaining actor.scope consumers rather than waiting for round eight. assertOpencodeProxyAllowed and requireClientScope are rank-based and handle collaborator correctly. requireHost was the only broken one.

Declined

Workspace import following symlinks, read-only bypass in command listing, opencodePassword returned to viewers by GET /workspaces, the mutating Google smoke test reachable by viewers, and the loopback OAuth callback in a remote container.

All five are vendored upstream at 2f2dde65 and untouched by this PR. Two deserve emphasis rather than a blanket dismissal:

The opencodePassword exposure is a genuine credential leak of exactly the class I fixed two commits ago for THEOREM_API_KEY, and I would fix it the same way. I am leaving it because it is in the vendored client serializer rather than in code this PR authored, and because the last several rounds have shown that my edits to vendored code I have not fully read tend to need a follow-up fix of their own.

The OAuth callback is the one finding here genuinely caused by this PR's deployment shape: loopback redirect works on desktop and cannot work in the Railway container OW5 introduces. It belongs with the console-origin route, since both need the same thing, a stable Console-origin callback.

Where this leaves the PR

Seven rounds. Defects in my own work in six of them, including this one, where a security fix from the previous round turned out to change nothing.

I am not going to keep going round for round. Every remaining open finding is either vendored code I should not be editing piecemeal, or blocked on console-side work that does not exist yet: the console must sign a role, and the console-origin route must exist. Four separate findings now converge on that one gap.

Local signal is green (39 tests, both packages typecheck, build:web succeeds, audit 36/36) and has predicted none of the seven rounds of defects. I would not weight it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b221e7efa0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packaging/workspace/entrypoint.sh Outdated
# --workspace, not --dir: the daemon's flag is a repeatable workspace root
# (apps/chat-server/src/config.ts). The token rides the environment rather than
# --host-token so it never appears in the container's process list.
setsid openwork-server \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Invoke the built chat-server entrypoint

In the workspace image, this command is not resolvable: the Dockerfile runs the package's ordinary build, which creates apps/chat-server/dist/cli.js, but it neither installs @commonplace/chat-server as a dependency nor adds apps/chat-server/bin to PATH (only the root node_modules/.bin is included). Consequently setsid exits with status 127 and the supervisor shuts down code-server on every container start; invoke the built CLI with Bun or explicitly link/install the package binary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, with one correction to the premise. apps/chat-server/bin/openwork-server.mjs is tracked in git and pnpm does create a root node_modules/.bin/openwork-server shim, so the finding as stated is not quite right for a full workspace install. But the image runs a filtered install, and I cannot confirm the shim exists there without building the image, which is precisely the class of assumption that has failed repeatedly on this PR.

Rather than argue the resolution semantics, the invocation is now bun /srv/openwork/apps/chat-server/dist/cli.js: what pnpm build emits and what the package's own start script runs, by a path that cannot fail to resolve. The PATH entry stays for anything else that wants it.

🤖 Addressed by Claude Code

Comment on lines +1087 to +1089
headers.set("Content-Type", contentTypeForPath(relativePath));
headers.set("Content-Length", String(info.size));
headers.set("Content-Disposition", `inline; filename="${basename(relativePath)}"`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Serve workspace HTML outside the authenticated origin

In a cookie-authenticated Console deployment, a collaborator-controlled .html file requested through this route is returned as text/html with Content-Disposition: inline from the application origin. If a signed-in user is navigated to that URL, the file's scripts execute with the Console origin and its HttpOnly session cookie, allowing authenticated same-origin API calls; this remains independently exploitable even if the artifact preview iframe is sandboxed. Force HTML to download, use an inert MIME type, or serve raw workspace content from a separate origin.

Useful? React with 👍 / 👎.

Comment on lines +823 to +824
if (scope === "viewer" && m !== "GET" && m !== "HEAD") {
throw new ApiError(403, "forbidden", "Viewer tokens are read-only");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce read-only mode before proxying mutations

When OPENWORK_READONLY=1, an owner or collaborator can still send POST, PATCH, or DELETE requests through either /opencode/* proxy branch because this gate checks only whether the actor is a viewer. Those branches return before any routed handler can call ensureWritable, and the managed OpenCode engine is not itself configured read-only, so requests such as session creation or permission/config mutations still succeed despite the daemon advertising read-only mode; reject non-read methods at the proxy boundary whenever config.readOnly is set.

Useful? React with 👍 / 👎.

requireClientScope(ctx, "collaborator");
const workspace = await resolveWorkspace(config, ctx.params.id);
const body = await readJsonBody(ctx.request);
const scope = normalizeOpencodeScope(typeof body.scope === "string" ? body.scope : null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate global OpenCode config writes with host auth

When approval mode is auto, any collaborator token can submit scope: "global" here and overwrite the host user's global OpenCode configuration rather than the selected workspace's project config. That configuration is shared by every workspace and can add plugins, MCPs, or other executable behavior, so routine remote access can persistently affect unrelated workspaces; global command access is already host-gated elsewhere, and the global branch here should likewise require owner/host authentication.

Useful? React with 👍 / 👎.

Comment on lines +62 to +64
if (scope === "global") {
const dir = join(homedir(), ".config", "opencode", "commands");
return listCommandsInDir(dir, "global");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve global resources through the OpenCode config directory

When XDG_CONFIG_HOME or OPENCODE_CONFIG_DIR is configured, global commands are read from the wrong directory because this hardcodes $HOME/.config/opencode; the workspace Docker image sets XDG_CONFIG_HOME=/workspace/state/config, so commands used by OpenCode under /workspace/state/config/opencode/commands never appear in this API. The global skill and plugin inventory repeats the same hardcoded path, so all three listings can be empty or stale; derive these directories from the shared globalOpencodeConfigDir resolver.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed, but worth flagging that this one is caused by my change rather than pre-existing: the hardcoded $HOME/.config/opencode was harmless until the workspace image set XDG_CONFIG_HOME=/workspace/state/config to keep engine state on the volume. So global commands, skills, and plugins now resolve to a directory the engine does not use.

Leaving it because the correct fix is to route all three listings through the shared globalOpencodeConfigDir resolver in vendored code I have not fully read, and this PR has a consistent record of my edits to such code needing a follow-up fix of their own. It should land with the other vendored path-resolution work.

🤖 Addressed by Claude Code

Comment on lines +69 to +70
} catch {
return { schemaVersion: 1, updatedAt: Date.now(), tokens: [] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve a corrupt token store instead of treating it as empty

If tokens.json is truncated by an interrupted write or contains invalid JSON, this catch silently returns an empty store. Every previously issued scoped token then starts receiving 401 responses without any diagnostic, and the next token creation or revocation overwrites the corrupt file with the empty in-memory state, permanently discarding the records and the evidence needed for recovery; surface a store-read error and use an atomic replacement write rather than interpreting corruption as no tokens.

Useful? React with 👍 / 👎.

Comment on lines +62 to +65
const content = await readFile(path, "utf8");
const rawLines = content.trim().split("\n").filter(Boolean);
if (!rawLines.length) return [];
const slice = rawLines.slice(-Math.max(1, limit));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read only the requested tail of the audit log

On a long-lived workspace, every audit request reads and splits the entire append-only JSONL file before applying the requested limit, so even ?limit=1 eventually incurs memory and latency proportional to the workspace's complete history. readLastAudit has the same behavior and is called by the ordinary workspace-config read path, allowing a sufficiently large persisted audit trail to slow routine UI loads or exhaust the daemon; seek or stream backward from the end of the file and parse only the required entries.

Useful? React with 👍 / 👎.

The PATH entry I added two rounds ago assumed pnpm creates a bin shim in the
root node_modules/.bin for a workspace project. That holds for the full
workspace install on a developer machine, and the image runs a filtered
install, where I cannot confirm it without building the image. A missing shim
exits 127 and the supervisor takes code-server down with it on every start.

Invoking dist/cli.js directly removes the resolution dependency. It is what
pnpm build emits and what the package's own start script runs, so it is the
same entrypoint by a path that cannot fail to resolve.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b440b72c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +395 to +400
addRoute(routes, "POST", "/experimental/extensions/call", "client", async (ctx) => {
if (ctx.actor?.scope === "viewer") {
throw new ApiError(403, "forbidden", "Viewer tokens cannot call extension actions");
}
const body = await readJsonBody(ctx.request);
return jsonResponse(await callExperimentalExtensionAction(config, env, body, await getConnectSnapshot(config, { ...connectSnapshotBaseOptions, ...connectSnapshotOptionsFromBody(body) })));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce read-only mode for extension actions

When OPENWORK_READONLY=1, a collaborator can still invoke mutating actions through this route because it rejects viewers but never calls ensureWritable. Actions registered here include drive_update_file, calendar_create_event, gmail_create_draft, and chat_send_message, so a nominally read-only deployment can modify or send data in the owner's connected Google account; apply the read-only gate before dispatching any side-effecting extension action.

Useful? React with 👍 / 👎.

Comment on lines +147 to +151
status=0
wait -n || status=$?
echo "workspace: a door exited with ${status}; stopping the other" >&2
shutdown
exit "${status}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat a clean door exit as a failure

When either code-server or chat-server unexpectedly exits with status 0, wait -n leaves status at 0 and this exits the container successfully after shutting down the other door. The deployment uses restartPolicyType = "ON_FAILURE" in packaging/workspace/railway.toml, so that clean child exit leaves the entire workspace offline without a restart; translate an unexpected zero status to a nonzero supervisor status.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. A door exiting cleanly is still a stopped workspace, and leaving status at 0 meant the supervisor reported success while ON_FAILURE left the container down with neither door running. This line is only reached because something that should have run forever stopped, so a nonzero status is the only correct outcome. Now translated to 70.

For the record, this is the fourth defect in this same supervisor block: --dir versus --workspace, the errexit termination, the ! swallowing the status, and now the clean exit. Each was real and each was only visible after the previous fix landed. It is the clearest illustration on this PR of why the container needs to actually run.

🤖 Addressed by Claude Code

const includeDirs = ctx.url.searchParams.get("includeDirs") !== "false";
const limit = parseCatalogLimit(ctx.url.searchParams.get("limit"));

const entries = await listWorkspaceCatalogEntries(workspace.path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop catalog traversal at the requested limit

For a large checkout containing directories such as .git or node_modules, this call recursively reads and stores every filesystem entry before the prefix, cursor, and requested limit are applied below. Consequently even the default 2,000-item snapshot can scan millions of entries, block the server, and consume memory proportional to the whole checkout; prune by prefix/cursor and stop or stream once the requested page is available.

Useful? React with 👍 / 👎.

Comment on lines +1104 to +1108
let bytes: Buffer;
try {
bytes = Buffer.from(body.dataBase64, "base64");
} catch {
throw new ApiError(400, "invalid_payload", "dataBase64 is invalid");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject malformed base64 before writing files

When dataBase64 contains malformed input, Node's Buffer.from(value, "base64") decodes permissively instead of throwing, so this catch is ineffective: values such as %%% become an empty buffer and other invalid strings become arbitrary bytes that are then written over the requested file. Validate the base64 representation strictly before decoding; the file-session batch decoder at line 826 needs the same treatment.

Useful? React with 👍 / 👎.

Comment on lines +74 to +79
const env = {
...process.env,
...options.env,
OPENCODE_SERVER_USERNAME: username,
OPENCODE_SERVER_PASSWORD: password,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build a restricted environment for the managed engine

When managed OpenCode is enabled, this spreads the daemon's complete environment into the engine despite the provider-auth bridge relying on a fixed allowlist. Command and shell tools launched by OpenCode inherit values such as OPENWORK_HOST_TOKEN, repository URL credentials, and unrelated service keys, so a collaborator who can run an agent command can read those values and potentially elevate to owner or access external services; construct an explicit non-secret environment containing only the runtime variables the engine requires.

Useful? React with 👍 / 👎.

Comment on lines +186 to +189
await mkdir(runtimeStorageDir(config), { recursive: true });
const tmp = `${path}.${randomUUID()}.tmp`;
await writeFile(tmp, content, "utf8");
await rename(tmp, path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Store the generated runtime config with private permissions

When THEOREM_API_KEY or secret-bearing MCP/provider configuration is present, content contains those credentials, but this temporary file is created with the process umask rather than an explicit private mode and then becomes the final config through rename. Under a typical 0022 umask the resulting file is 0644, allowing other local users to read the bearer keys; create it with mode 0600 and enforce that mode on an unchanged existing file as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and independently confirmed: I reported this same exposure from my own audit of the OW2 surface last round. The file carries the Theorem Authorization header and any provider credentials, and under a typical 0022 umask it landed 0644. In the workspace image it also sits on the volume the IDE door serves, so a terminal user could read a tenant-scoped key.

Created 0600 now, with the mode set at creation rather than after rename so the secret is never briefly world-readable, and re-asserted when content is unchanged since the writer short-circuits there and an older file would keep its bits forever.

Tested empirically, which was worth doing: my first version of the test asserted nothing, because writeFile's mode option only applies on creation, so the permissive-file setup left the file at 0600 and never exercised the tighten path. It uses chmod now.

Note this is a mitigation, not the fix. The underlying problem is that THEOREM_API_KEY is tenant-scoped and lives in a workspace that runs agent code; file mode does not help when code-server runs as the same uid. The real fix is a workspace-scoped credential.

🤖 Addressed by Claude Code

Comment on lines +103 to +104
const url = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`Timeout waiting for OpenCode server after ${options.timeoutMs ?? 15000}ms`)), options.timeoutMs ?? 15000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Terminate the child when managed-engine startup fails

If OpenCode remains running but never emits the expected listening line, or emits a line whose URL cannot be parsed, this startup promise rejects while the spawned child is left alive and unreaped. An embedded caller that catches the error and retries can therefore accumulate engine processes and occupied ports, and the CLI may also remain held open by the child pipes; kill and await the child on every timeout, parse failure, or startup exception.

Useful? React with 👍 / 👎.

Comment on lines +67 to +68
const { path: runtimeConfigPath } = await writeOpenworkRuntimeConfigFile(config, workspace.id);
keepOpenworkRuntimeConfigFileFresh(config, workspace.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Unsubscribe the embedded runtime-config listener on stop

Each managed embedded-server start registers a global runtime-config write listener here, but the returned unsubscribe function is discarded and handle.stop() never removes it. Repeated start/stop cycles therefore retain every old server config and keep rewriting files for stopped instances; global runtime writes can also make stale listeners rebuild the active server's derived file using their captured workspace ID. Save the unsubscribe callback and invoke it during normal stop and startup-error cleanup.

Useful? React with 👍 / 👎.

Comment on lines +589 to +590
const form = await ctx.request.formData();
const file = form.get("file");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the inbox limit before buffering the upload

When an authenticated collaborator sends a multipart body larger than OPENWORK_INBOX_MAX_BYTES, request.formData() buffers and parses the entire payload before file.size is checked. The advertised limit therefore protects only the eventual file write, not daemon memory; with the 250 MB default, one request can consume substantially more than that and exhaust a small workspace container. Reject oversized Content-Length values early and use a streaming, byte-limited multipart parser so chunked requests are bounded too.

Useful? React with 👍 / 👎.

Comment on lines +1058 to +1062
function buildOpencodeProxyUrl(baseUrl: string, path: string, search: string) {
const target = new URL(baseUrl);
const trimmedPath = path.replace(/^\/opencode/, "");
target.pathname = trimmedPath.startsWith("/") ? trimmedPath : `/${trimmedPath}`;
target.search = search;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve path prefixes in proxied OpenCode base URLs

When a remote OpenCode server is exposed below a reverse-proxy prefix, such as https://host/engine, assigning target.pathname here discards /engine; proxying /opencode/session consequently calls https://host/session instead of https://host/engine/session. Other client URL helpers preserve configured base paths, so these otherwise valid remote deployments fail only through the proxy routes; append the normalized proxy path to the configured base pathname.

Useful? React with 👍 / 👎.

…0600

A door exiting with status 0 left the supervisor status at 0, so the container
exited successfully and Railway's ON_FAILURE policy left the workspace offline
with neither door running. This line is only reached because something that
should have run forever stopped, so the only correct status here is nonzero.
That is the fourth defect in this supervisor block, each one real and each one
exposed only after the previous fix.

The generated engine config is now created 0600. It carries OW2's Theorem
Authorization header and whatever credentials the runtime config holds, and it
was written with the process umask, so under a typical 0022 it landed 0644 and
every local user could read the bearer key. In the workspace image that file
sits on the volume the IDE door serves. The mode is set at creation rather than
after rename so the secret is never briefly world-readable, and it is
re-asserted when the content is unchanged, since a file from an older build
would otherwise keep permissive bits forever.

This is the same exposure I reported from my own audit of the OW2 surface last
round, independently found in review.

Tested empirically. The first version of the test asserted nothing, because
writeFile's mode option applies only on creation, so the permissive-file setup
left the file at 0600 and the tighten path was never exercised. It uses chmod
now.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fac0c083e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/chat-server/src/diagnostic-sanitizer.ts Outdated
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/server.ts Outdated
Comment thread apps/chat-server/src/routes/core.ts
Comment thread apps/chat-server/src/routes/workspaces.ts
Comment thread apps/chat-server/src/routes/workspaces.ts
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/routes/core.ts Outdated
Comment thread apps/chat-server/src/routes/sessions.ts
Comment thread apps/chat-server/src/mcp.ts
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

…leaks

Nine findings from the review of fac0c08, all in vendored route and
inventory code that admits viewer-scoped client tokens.

Credentials reaching viewers:
- The diagnostic sanitizer matched keys against a fixed set of exact
  strings assembled from the keys this fork writes, which is the wrong
  population. GITHUB_TOKEN, accessToken, refreshToken and clientSecret
  all mean "credential" and none matched. Keys are now tokenized into
  segments, and headers/environment/env records redact every value
  regardless of what the upstream server named its variables.
- GET /workspace/:id/mcp returned each MCP's raw config, headers and
  environment included. The export path next door already redacts
  exactly those fields, so the cheaper route leaked what the careful
  one protected. Both now share redactMcpConfig.
- GET /workspace/:id/opencode-config returned raw config text, and
  ?scope=global reads the host-wide file whose provider credentials
  belong to every workspace on the box. The write side already required
  collaborator; the read now does too.

Writes and resources without a gate:
- google-workspace/disconnect revokes an OAuth token and rewrites the
  account vault but never called ensureWritable, so OPENWORK_READONLY=1
  could still lose the connected account.
- POST /workspace/:id/claude-plugins resolved a GitHub repository tree
  and downloaded every skill, command and agent file before the
  collaborator check. Authorization now precedes resolution.
- POST /dev/log is unauthenticated by design and so must be bounded by
  design; it now reads the body through a running byte limit rather
  than buffering first, and caps entry count.

Correctness:
- A failed initial prompt left the just-created session behind with no
  id in the error response, so every retry added another empty session.
- listMcpFromRuntimeSnapshot emitted a global entry and a runtime entry
  under the same name; consumers taking the first match inspected the
  definition that is not live.
- Host-token-only remote creation returned 201 and persisted a
  workspace with no credential. Rejected rather than retained: the host
  token maps to an owner-scope actor, so storing it per-workspace would
  trade a visible failure for a privilege upgrade at rest.

One review suggestion is deliberately not taken. Deriving the remote
workspace id from host and id via workspaceIdForOpenwork would break
remote addressing: rem_<remoteWorkspaceId> is a reversible encoding and
four call sites recover the upstream id by slicing the prefix, so a
one-way hash would make every remote call target an id the upstream host
has never heard of. The collision is real, so it is handled where it
does damage: registering a colliding id from a different host now 409s
instead of silently deleting the first host's record.

Baseline before this change was 603 pass / 29 fail; after, the same 29
pre-existing failures remain and nine new tests pass. The two new
sanitizer cases were negative-tested against the previous
implementation and fail there.
@Travis-Gilbert
Travis-Gilbert force-pushed the claude/commonplace-openwork-fork-46bcd5 branch from aac3487 to e7c4726 Compare August 2, 2026 23:11
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7c47262b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/chat-server/src/server.ts Outdated
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/routes/core.ts
Comment thread apps/chat-server/src/routes/files.ts
Comment thread apps/chat/src/app/lib/feedback.ts Outdated
Comment thread apps/chat/src/react-app/shell/settings-route.tsx Outdated
Comment thread apps/chat-server/src/routes/files.ts Outdated
Comment thread apps/chat-server/src/routes/files.ts
Comment thread apps/chat-server/src/cli.ts Outdated
Comment on lines +110 to +114
// The runtime config file above only covers workspaces[0]. Push every
// workspace's runtime-DB MCPs into the engine so they aren't invisible
// until a manual reload. Best-effort.
if (managedOpencode) {
void syncAllWorkspacesRuntimeMcpToEngine(config);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply runtime configuration for every managed workspace

When the repeatable --workspace option configures multiple local workspaces, the managed engine's OPENCODE_CONFIG is built and refreshed only for the selected first local workspace. This startup repair synchronizes only MCP entries for the others, so their runtime providers, plugins, disabled-provider settings, default agent, and external-directory permissions are silently ignored even though their settings writes succeed; materialize directory-scoped configuration for each workspace or run separate managed engines.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real, and reported rather than repaired. OPENCODE_CONFIG is one file read by one engine process, so the fix is either a managed engine per workspace or directory-scoped project config, and both are engine surgery that SPEC-COMMONPLACE-OPENWORK-FORK-1.0 OW6 gates behind the seam audit that has not run. What is fixed is "silently": startup now logs which local workspaces are configuration-inert and which categories are ignored for them, so an operator sees it instead of inferring it from behavior. Leaving this thread open as a live OW6 input.

🤖 Addressed by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed, but the reason it was deferred was wrong and that is now on the record as spec amendment A16.

This was parked behind OW6 on the reading that both remedies you named were engine surgery. They are not, and the evidence is in this repository: mcp.ts already inspects opencodeConfigPath(workspaceRoot) as a config.project layer alongside the global one, and the runtime map merges over both. So "materialize directory-scoped configuration for each workspace" is configuration through a layer the engine already reads, not a second transport and not a second client-construction site. It sits inside the A5 stay-on-the-opencode-head decision rather than gated by it.

Leaving this thread open on purpose: the correct classification is recorded, the fix is not written. What exists today is still only the startup warning naming which workspaces are configuration-inert, which is a smaller thing than the fix and should not be mistaken for it.

🤖 Recorded by Claude Code

…actions

Nine findings from the review of e7c4726.

Arbitrary host file read (P1). resolveSafeChildPath was lexical: resolve()
never touches the filesystem, so a symlink inside a checkout pointing at
/etc passed the child-path check and stat() and createReadStream() then
followed it. A viewer could read any file the daemon could. Containment now
resolves the real target, walking to the nearest existing ancestor so a
write to a not-yet-created file under a symlinked directory is caught too.
There were two copies of the lexical check, so the rule moved to
workspace-paths.ts and both callers bind to it; a containment rule that
exists twice is one that gets fixed once.

Credentials reaching viewers. GET /workspace/:id/config returned the merged
project and runtime config, credentials included; it is redacted for
viewers rather than gated, because the shell reads it for every scope and a
403 would blank the workspace. GET /workspace/:id/export with
?sensitive=include turned off the exclusion sanitizer for any client scope,
so that mode now requires collaborator.

Writes without a gate. google-workspace/active-account rewrites the account
vault and never called ensureWritable, the same omission the disconnect
route had.

Resource exhaustion. read-batch held every base64 string before
serializing, so 64 files under the 5 MB per-file limit was ~427 MB in
memory; a total batch ceiling now bounds the response and over-limit files
are reported rather than dropped. File sessions evicted whichever session
expired first, globally, so one viewer holding maximum-TTL sessions could
push out collaborators' live ones; capacity pressure now falls on the actor
causing it, and a store full of other actors' sessions returns 429.

Donor-bound UI. buildFeedbackUrl called new URL("") whenever
VITE_OPENWORK_FEEDBACK_URL was unset, which is the documented default after
OW1 severed the donor endpoint, so every feedback click threw behind a
button that still looked live. It returns null now and the action is
hidden. Report issue pointed at github.com/different-ai/openwork, so
CommonPlace bug reports reached maintainers who cannot act on them; it now
defaults to this repository. The Discord invite was the donor's too, which
the review did not flag but is the same OW1 violation, so it is opt-in and
hidden when unset. All three destinations live in one module now.

One finding is reported rather than repaired. OPENCODE_CONFIG is a single
file read by a single engine process, so only the booted workspace's
providers, plugins, disabled providers, default agent, and permissions
reach the engine while the others' settings writes succeed and do nothing.
Both remedies -- a managed engine per workspace, or directory-scoped
project config -- are engine surgery that OW6 gates behind the seam audit.
Startup now names the configuration-inert workspaces instead of leaving an
operator to infer it from behavior.

Verification: both packages typecheck, chat builds, audit 36/36. Nine new
tests. The symlink cases were negative-tested against the lexical check and
fail there. Two reload-watcher timing tests fail on this machine with these
changes stashed, so they are not from this diff.
@ecc-tools

ecc-tools Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb7f3dcf2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/routes/files.ts
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/routes/core.ts
Comment thread apps/chat-server/src/routes/core.ts
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/package.json
Comment thread apps/chat-server/src/mcp.ts
Comment thread apps/chat-server/src/server.ts
Comment thread apps/chat-server/src/cloud-plugins.ts
@Travis-Gilbert
Travis-Gilbert merged commit 2e611c7 into main Aug 2, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants