mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-17 07:09:53 +02:00
agent/lambda/768b92e0
15 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6cc553e5a3 |
fix(daemon): isolate Codex sessions per task to unblock initialize (MUL-4424) (#5360)
* fix(daemon): isolate Codex sessions per task to unblock initialize (MUL-4424) Codex 0.143+ backfills a per-home session-state DB by enumerating every rollout visible under sessions/ during `initialize`. The per-task CODEX_HOME symlinked the shared ~/.codex/sessions in, so a machine with a large accumulated history (one reporter: ~2000 rollouts / ~22 GiB) stalled `initialize` for tens of seconds — the app-server started but the task produced no output before it was cancelled (github #5273). Give each task its own local sessions/ directory instead: - Fresh task: create an empty local sessions/ so backfill is trivial. - Reused task with a real sessions/ dir: it is authoritative — leave it. - Reused task still holding a legacy symlink (older build): migrate in place. Replace the symlink with a real dir; when resuming, symlink only the single rollout being resumed (never copy — a rollout can be GiB and this is on initialize's critical path); and drop the stale, rebuildable session-state DB (state_*.sqlite*, session_index.jsonl) so Codex re-indexes the task-local sessions. Unrelated per-task DBs (goals_*, logs_*, memories_*) are left intact. Also point the token-usage fallback scan at the backend's per-task CODEX_HOME instead of the daemon-global home, so usage isn't lost now that sessions are isolated there. Complements the #5319 handshake watchdog (which turns a silent stall into a loud, phased timeout); this removes the underlying cause. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): address Codex session isolation review (MUL-4424) Resolves the three blockers from Elon's review of #5360: 1. local_directory context loss. local_directory tasks get a fresh codex-home per task ID (the daemon never reuses their workdir), so task-local isolation stranded every follow-up run with an empty sessions dir and silently restarted the conversation. Their only stable, GC-safe cross-task store is the user's own ~/.codex/sessions (a persistent store under WorkspacesRoot would be orphan-GC'd), so keep the shared-sessions symlink for them (IsLocalDirectory). Managed tasks stay isolated. 2. Migration resume robustness. - Rollout lookup now covers the flat layout and background-compressed .jsonl.zst rollouts, not just nested YYYY/MM/DD *.jsonl — both are legitimate Codex 0.144 history that were previously judged "not found", silently dropping resume. - Exposure hard-links first, then symlinks, never copies — hard links need no privilege and work on Windows within a volume, so the zero-copy path is exercised identically on CI. - The daemon now verifies the rollout is actually present in the task CODEX_HOME (execenv.CodexResumeRolloutPresent) before the brief is generated; if absent it clears the resume from both the backend and the brief instead of telling the agent it is continuing a lost thread. 3. session_index.jsonl is no longer deleted during migration — Codex uses it as the authoritative thread-id -> name store (not rebuildable from rollouts). Only the rebuildable state_*.sqlite* is reset. Tests: 2-round local_directory resume across task IDs; compressed/flat lookup; hard-link zero-copy (os.SameFile); session_index preserved; CodexResumeRolloutPresent + the daemon gate helper (present keeps / absent drops / non-codex + empty no-op). Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): scope Codex sessions to a per-issue store; disclose lost resumes (MUL-4424) Addresses the three blockers from Elon's second review of #5360. 1. local_directory still enumerated the whole machine history. The prior fix re-linked the entire ~/.codex/sessions into every fresh local_directory codex-home, so Codex still backfilled from thousands of unrelated rollouts on `initialize` (measured ~8.3s with 3450 rollouts; the reporter's 22 GiB could exceed the 30s watchdog). Point sessions/ at a persistent, per-(agent, issue) store under the shared Codex home (multica-sessions/<agent>/<issue>) that holds only this issue's rollouts. It is keyed stably across task IDs and lives outside the task-scoped envRoot the GC reclaims, so follow-up runs resume it while `initialize` only ever sees this issue's history. 2. Windows cross-volume resume was lost. Exposing a single rollout by hard-link (same-volume only) then file symlink (needs Windows privilege) can't cross a volume boundary. The store now lives on the shared Codex volume, so the resume rollout is hard-linked there zero-copy, and sessions/ is exposed to the task home via a directory link — a symlink on Unix, a junction on Windows — which crosses volumes without privilege and never copies a (possibly GiB) rollout on initialize's critical path. There is no remaining per-file cross-volume link. 3. An unavailable resume was a silent downgrade. Both resume gates (gateResumeToReusedWorkdir, gateCodexResumeToRolloutPresence) now set PriorSessionResumeUnavailable, and the runtime brief renders a Session Continuity Notice telling the agent to disclose to the user, up front in its reply, that the previous conversation context could not be restored and this run starts fresh — turning a silent restart into a user-visible one. The task is not failed: it can still do useful work without the prior context. Managed fresh / reused-real-dir tasks keep their task-local, GC-collected sessions dir unchanged; only the legacy-symlink migration with a resume routes through the store (cross-volume-safe), and a home already linked to the store is treated as authoritative on reuse. Tests: local_directory per-issue store (only this issue's history, no whole- machine leak); no-key fallback to an empty dir; two-round resume across task IDs through the store; legacy migration routed through the store with a zero-copy hard link; reused store link stays authoritative; both gates set the resume-unavailable flag; brief renders the continuity notice only when a resume was lost. execenv + daemon + pkg/agent packages, go vet, and gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): disclose live resume-RPC loss; bound Codex session store lifecycle (MUL-4424) Addresses the two blockers from Elon's third review of #5360. 1. A real thread/resume failure was still a silent new session. The brief's Session Continuity Notice only covers losses the daemon detects before launch (workdir not reused, rollout absent). But when the rollout is present yet Codex rejects the live thread/resume (corrupt/incompatible rollout, server-side thread GC, schema drift), startOrResumeThread falls back to thread/start and the run succeeds on a fresh thread with no user-facing signal. Carry the original resume intent into the backend as ExecOptions.ResumeExpected (set from the post-gate PriorSessionID, so a pre-flight drop still routes through the brief and never double-notifies), and when a resume was expected but the backend landed on a fresh thread, prepend the same continuity notice to the first turn/start input. This also covers the daemon's transport-error fresh-session retry, which clears ResumeSessionID but not ResumeExpected. 2. The persistent per-issue store had no data lifecycle. multica-sessions stores live outside the task-scoped envRoot the GC reclaims (so resume survives across task IDs), which meant a done/abandoned issue's prompts and full rollouts (one reporter: a single 1.5 GiB rollout) accumulated forever and were never freed on issue/agent/workspace deletion. Add PruneCodexSessionStores: the daemon GC loop reclaims any store untouched for GCCodexSessionTTL (default 14 days, configurable via MULTICA_GC_CODEX_SESSION_TTL, 0 disables). A store's newest rollout mtime is its last activity, so an active or recently-resumed task keeps its store fresh and is never reclaimed, while a deleted issue's store ages out — an eventual reclamation guarantee without needing deletion events. Tests: codexTurnInput discloses on resume fallback and stays silent on success / fresh start (paired with the existing live-RPC fallback test); store pruning reclaims aged stores, keeps recent ones, isolates issues, cleans empty agent dirs, and is disable-able. execenv / daemon / pkg/agent, go vet, gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): protect a reopened Codex session store from GC mid-mount (MUL-4424) Addresses Elon's fourth-review blocker: reopening an issue idle past GCCodexSessionTTL could lose context, because mounting its per-issue session store (MkdirAll + rollout lookup + task-home link) never refreshed the store's mtime, so a GC cycle firing before the resumed turn wrote its first rollout saw a >TTL-old store and reclaimed it — a stat->remove race with no in-use guard. Two complementary defenses: - Activity refresh: linkCodexSessionsToStore now os.Chtimes the store to now after linking, so codexStoreStat (which reads the newest mtime as last activity) sees a just-used store. This fixes the sequential repro — a mount immediately followed by a prune keeps the store. - In-process active-store guard: the daemon marks the per-issue store in-use (execenv.CodexSessionStorePath) from before Prepare/Reuse mounts it until the task ends, and PruneCodexSessionStores now takes an isActive predicate and skips any store a live task holds. Because prepare and prune run in the same process, this closes the remaining concurrent stat->remove window the mtime refresh alone cannot. Reference-counted, mirroring the env-root guard. Tests: a reopened >TTL store survives a GC cycle after remount and stays resumable; an idle-on-disk store marked active is skipped, then reclaimed once inactive; the existing idle-reclaim / isolation / disable / empty-agent-dir cases still pass. execenv + daemon, go vet, gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): make Codex store delete atomic with mark-active (MUL-4424) Addresses Elon's fifth-review blocker: the active-store guard's check and delete were not one atomic step. PruneCodexSessionStores called isActive (which locked, read, and unlocked) and only then RemoveAll'd, leaving a window where a task could markActiveCodexStore between the check and the removal and still lose its store — the exact mark-then-delete interleaving Elon reproduced. Replace the point-in-time isActive predicate with a reserve-for-deletion protocol that shares one lock with mark-active: - reserveCodexStoreForDeletion(store) atomically refuses when a live task holds the store (or another delete already reserved it) and otherwise marks it reserved, all under one activeCodexStoresMu acquisition. PruneCodexSessionStores reserves before RemoveAll and commits after, so confirm-inactive and remove are effectively atomic against a concurrent mark. - markActiveCodexStore now waits (on a sync.Cond) while a store is reserved, so a task never mounts a store mid-removal; committing the removal wakes it and the store is recreated fresh by Prepare (with the continuity notice). So mark-before-reserve keeps the store (reserve refused); reserve-before-mark removes it and blocks the late mark until the removal commits. The genuinely idle case still reclaims. Tests (daemon, run under -race): mark-then-reserve is refused; reserve blocks a concurrent mark until commit then the store reads active; a second reserve is refused mid-flight. The execenv prune tests move to the reserve seam; the activity-refresh / reopen-then-prune / isolation / disable cases still pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): namespace Codex session stores per profile for cross-daemon safety (MUL-4424) Addresses Elon's sixth-review blocker: the in-process reservation guard cannot span processes, but Multica supports multiple profile daemons on one machine (e.g. production + staging) that share the same ~/.codex. Each daemon's GC scanned the whole multica-sessions root, so a staging daemon could reclaim a store a production task was actively resuming — its reservation lived only in the other process's memory. Isolate by profile instead of trying to lock across processes: - Store path is now <shared>/multica-sessions/<namespace>/<agent>/<issue>, where namespace is the daemon's profile (empty -> "default"). PrepareParams/ReuseParams carry Profile; codexSessionStoreKey and CodexSessionStorePath fold it in. - PruneCodexSessionStores takes the profile and scans ONLY that namespace, so a daemon never even sees another profile's stores, let alone deletes them. The per-profile trees are disjoint, so the in-process guard is sufficient within a namespace (profiles get separate daemon state, so no two daemons share one). Test: a "staging"-owned idle store is untouched by a default-profile prune and reclaimed only by staging's own prune. Existing prune/guard/reopen tests move under the namespace. execenv + daemon under -race, go vet, gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): make the Codex store profile→namespace map injective (MUL-4424) Addresses Elon's seventh-review blocker: the per-profile namespace was derived by dropping unsafe characters, which is not injective. The CLI treats the empty (default) profile and a profile literally named "default" as separate daemons, yet both mapped to namespace "default"; likewise "staging.prod" and "stagingprod" both mapped to "stagingprod". Two distinct daemons then shared one store tree, so one could again reclaim the other's live session — the cross-process blocker reopened for those profile names. Make codexSessionStoreNamespace injective: the empty profile gets a reserved bare literal "default", and every named profile is hex-encoded (bijective, filesystem-safe) under a "p_" prefix a bare literal can never collide with. So "" -> "default" while "default" -> "p_64656661756c74", and "staging.prod" / "stagingprod" get distinct hex segments. sanitizeCodexPathSegment stays for the UUID agent/issue segments (injective for real UUIDs); only the user-controlled profile needed the encoding. Tests: codexSessionStoreNamespace is distinct for "" vs "default", punctuation variants, case variants, and an encoded-looking name; and end-to-end, pruning one profile never reclaims the other's store for the "" vs "default" and "staging.prod" vs "stagingprod" pairs. execenv + daemon under -race, go vet, gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): fixed-length Codex store namespace so long profiles fit (MUL-4424) Addresses Elon's eighth-review blocker: hex-encoding the full profile doubled the namespace segment length. A profile can be as long as a filesystem segment allows (~255 bytes) and the CLI persists it as its own config dir, but the store namespace "p_" + hex(profile) reached 2 + 127*2 = 256 bytes at 127 chars, overflowing the 255-byte single-segment limit — the profile's own dir created fine, then the session store failed with "file name too long". Derive the namespace from a fixed-length hash instead: a named profile is now "p_" + hex(sha256(profile)) — a constant 64 hex chars (66 with the prefix), filesystem-safe and collision-resistant. The empty (default) profile keeps its reserved bare literal "default", which the "p_"-prefixed 66-char segment can never equal. Still injective across the CLI's distinct-daemon cases; just no longer length-expanding. Test: the namespace stays <=255 bytes and creatable for profiles up to the 255-byte segment limit (127- and 255-char cases that overflowed under hex); the prior injectivity and cross-profile prune-isolation tests still hold. execenv + daemon under -race, go vet, gofmt all pass. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
65269ef922 |
fix(daemon): copy Codex model catalog into task home
Fixes #4825 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
6bc3d14eb3 |
fix(daemon/execenv): refresh stale Codex config copies across env reuse (MUL-2646) (#3268)
* fix(daemon/execenv): refresh stale Codex config copies across env reuse (MUL-2646)
`copyFileIfExists` previously short-circuited whenever the per-task
`codex-home/{config.toml,config.json,instructions.md}` already existed,
so once the files were seeded at first Prepare they were never refreshed
again — even though `Reuse()` calls `prepareCodexHomeWithOpts` on every
resume. A user who rotated their Codex `~/.codex/config.toml` between
runs (e.g. switching the active `[model_providers.X]` `base_url`, or
pointing `env_key` at a freshly rotated API key) kept reading the stale
per-task copy on session resume. Codex then issued requests to the new
URL using the old key and the API rejected the token.
Treat any existing `dst` as something to drop and re-copy from the
current shared source, mirroring the symlink path that already refreshes
`auth.json` (#2126). The daemon-managed sandbox / multi-agent / memory
blocks are applied via marker-bracketed idempotent passes after the
copy, so a re-copy + re-ensure cycle preserves them.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon/execenv): drop per-task Codex copy when shared source removed (MUL-2646)
Extend the MUL-2646 fix to the deletion arm of "sync the shared source":
`syncCopiedFile` (renamed from `copyFileIfExists`) now also removes the
per-task `dst` when the shared `src` is absent. The prior version
short-circuited on missing src and left `config.toml` / `config.json` /
`instructions.md` from the previous Prepare lingering in the per-task
home — so a user who removed a provider by deleting `~/.codex/config.toml`,
or pulled `config.json` / `instructions.md` out of the shared home, would
keep replaying the stale copy on session resume.
For `config.toml` the subsequent `ensureCodex{Sandbox,MultiAgent,Memory}Config`
passes recreate the file with only the daemon-managed default blocks, so
removing the shared file cleanly drops every user-managed
`[model_providers.X]` / `model_provider` line. For `config.json` and
`instructions.md` there is no daemon default, so they disappear in
lockstep with the shared source.
Adds `TestPrepareCodexHome_DropsCopiedConfigWhenSharedSourceRemoved`
covering the new path, and extends the refresh-arm test to assert the
multi-agent / memory marker blocks are still present after the copy is
refreshed.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
cd71b0fe05 |
fix(daemon): disable Codex native auto-memory in per-task config.toml (#3202)
Codex CLI's auto-memory subsystem writes summaries to `$CODEX_HOME/memories/raw_memories.md` and `state_*.sqlite`, then reads them back on the next turn. The daemon never cleared these files across Reuse(), and Codex CLI may also pull from user-level `~/.codex/memories/` entirely outside the per-task isolation. Either path leaks unrelated context into new Multica tasks — multica#3130 saw `D:\Project\MoHaYu\ WowChat` Raw Memories injected into a brand-new issue's first turn. Write a daemon-managed block into the per-task `config.toml` that sets `features.memories = false`, `memories.generate_memories = false`, and `memories.use_memories = false`. Codex then neither writes nor reads its memory subsystem regardless of where the residual files live. The user's global `~/.codex/config.toml` is never touched. Pattern mirrors `ensureCodexMultiAgentConfig`: idempotent managed-block upsert, two TOML layout variants (root dotted-key vs. inside a `[features]` / `[memories]` table) to satisfy strict toml-rs parsing, and a `MULTICA_CODEX_MEMORY` env-var escape hatch. MUL-2598 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
144661e68f |
fix(daemon/execenv): refresh stale Codex auth.json across env reuse (#2126)
`ensureSymlink` previously short-circuited whenever `dst` already existed
as a regular file ("Regular file exists — don't overwrite"). On Windows
that branch is reachable via the createFileLink copy fallback that fires
when `os.Symlink` is unavailable, so once a per-task `codex-home/auth.json`
was written as a copy it would never be refreshed by subsequent
Prepare/Reuse calls. If the shared `~/.codex/auth.json` rotated (e.g.
Codex Desktop refreshed the token in the background), the daemon kept
handing Codex a now-revoked refresh_token, which the OAuth server
rejected with `refresh_token_reused` / `token_expired`. Renaming the
workspace directory was the only recovery path.
Treat any non-matching dst — wrong-target symlink, broken symlink, or
stale regular file — as something to delete and re-create via
createFileLink, so each Prepare/Reuse mirrors the current shared source.
Add a `logCodexAuthState` info log (file kind, link target, size, mtime —
never contents) so operators chasing the same symptom can see at a glance
whether the per-task home is tracking the shared auth or has drifted.
Tests cover: stale regular-file dst is replaced, copy-fallback dst is
refreshed when the shared source rotates, and a high-level
prepareCodexHome regression simulating the Windows + token-rotation
scenario from issue #2081.
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
bd82607645 |
fix(execenv): default-disable Codex native multi-agent in per-task config (#1845)
* fix(execenv): default-disable Codex native multi-agent in per-task config Recent Codex app-server releases enable features.multi_agent by default, exposing spawn_agent / wait / close_agent tools that let a parent thread spawn nested subagents. The daemon currently models only the parent thread, so the parent's turn/completed is treated as task completion even when spawned children are still running — leading to premature task completion and dropped child output. Disable features.multi_agent by default in the per-task CODEX_HOME/config.toml so Multica's task lifecycle is the only orchestration layer in play. Strip both the dotted-key form (features.multi_agent) at TOML root and the multi_agent key inside a [features] table; siblings and unrelated tables are preserved. Honor MULTICA_CODEX_MULTI_AGENT=1 as an opt-out for users who explicitly want Codex native subagents inside a Multica task. The user's global ~/.codex/config.toml is never modified — only the daemon's isolated per-task copy. Also widen managedBlockRe to consume `\n*` rather than `\n?` so reruns don't accumulate blank lines when both the sandbox and multi-agent managed blocks coexist. * fix(execenv): inject managed multi_agent inside existing [features] table Per PR review (codex_multi_agent.go:77-83 vs :112-115): when the user's config.toml already has a top-level `[features]` table, writing `features.multi_agent = false` at the TOML root implicitly redefines the same `features` table. The strict TOML parser used by Codex (`toml-rs`) rejects that with `table 'features' already exists`, so Codex would fail to load the per-task config and refuse to start the thread. Verified the strict-parser failure with pelletier/go-toml/v2; the previous BurntSushi/toml-based regression test was permissive enough to miss it. Detect a root-level `[features]` header and place the managed block inside that table (`multi_agent = false` with marker comments). When no such header exists, keep the existing root-level dotted-key form. The managed-block regex matches both layouts so reruns and layout transitions stay idempotent. A `[features.experimental]` sub-table without a bare `[features]` header still uses the root dotted-key form, which is spec-valid (no explicit redefinition). Tests now use pelletier/go-toml/v2 to actually parse the output and assert features.multi_agent decodes to false; the regression case from the PR review is covered explicitly. * fix(execenv): recognize feature table header variants --------- Co-authored-by: Devv <devv@Devvs-Mac-mini.local> |
||
|
|
e4103f6ad7 |
fix(execenv): strip [[skills.config]] from per-task codex config.toml (#1816)
Codex Desktop writes one [[skills.config]] entry per known skill into ~/.codex/config.toml. File-backed entries get path = "...", but plugin-backed entries (e.g. name = "superpowers:brainstorming") only get a name. Codex CLI 0.114's TOML deserializer treats path as required, so it rejects the plugin entries with "missing field path" and fails thread/start. The daemon copies ~/.codex/config.toml verbatim into each task's isolated codex-home, which propagated those broken entries into the per-task config and blocked every Codex agent run for affected users. Strip the whole [[skills.config]] array on copy. Multica writes the agent's currently assigned skills directly to codex-home/skills/ and Codex auto-discovers them from there, so the user-level skill registry is redundant for a per-task run. Closes #1753 |
||
|
|
12e6ca9906 |
refactor(execenv): collapse codex plugin cache stale-link branches (#1697)
Merge the two symlink removal branches in exposeSharedCodexPluginCache — they shared the same os.Remove + recreate path with only the error label differing. The branch is now keyed off Lstat's ModeSymlink bit, with Readlink reused only to fast-path an already-correct link. Behaviour is unchanged; just less duplicated code. |
||
|
|
25b393df17 |
fix(execenv): hydrate Codex skill sources (#1668)
Expose the shared Codex plugin cache inside each per-task CODEX_HOME before launch so plugin-provided skills are available on the first session. Refresh agent-assigned workspace skills for both newly prepared and reused Codex environments, and cover plugin cache plus reuse behavior with focused execenv tests. |
||
|
|
b5de04da59 |
fix(daemon): platform-aware Codex sandbox config to unbreak macOS network (MUL-963) (#1246)
* fix(daemon): platform-aware Codex sandbox config to unbreak macOS network On macOS, Codex's Seatbelt sandbox in workspace-write mode silently ignores '[sandbox_workspace_write] network_access = true' (see openai/codex#10390). That blocks DNS inside the sandbox, so 'multica issue get' and other CLI calls fail with 'dial tcp: lookup ...: no such host' — this is what caused MUL-963. Changes: - New server/internal/daemon/execenv/codex_sandbox.go: picks a sandbox policy based on runtime.GOOS and the detected Codex CLI version. Non-darwin or darwin with a known-fixed version keeps workspace-write + network_access=true; older darwin falls back to danger-full-access and logs a warn with upgrade hint. The fix-version threshold is a single constant (CodexDarwinNetworkAccessFixedVersion) so it's easy to bump once upstream ships. - Per-task config.toml now gets a 'multica-managed' marker block (BEGIN/END comments) rewritten idempotently; user-owned keys outside the markers are preserved. Legacy inline sandbox directives from earlier daemon versions are stripped on migration. - execenv.PrepareParams gains CodexVersion; execenv.Reuse takes a codexVersion arg; daemon.go caches detected versions at registration and threads them through to Prepare/Reuse. - Replaces the old ensureCodexNetworkAccess tests with platform-parameterised coverage (linux vs darwin, idempotency, legacy-migration, policy matrix). - docs/codex-sandbox-troubleshooting.md: symptom fingerprint table, decision matrix, self-check commands, trade-offs. Refs: MUL-963 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(daemon): hoist managed sandbox block above user tables (MUL-963) Review on #1246 flagged that upsertMulticaManagedBlock appended the managed block to EOF. If the user's config.toml ends inside a TOML table (e.g. [permissions.multica] or [profiles.foo]), a trailing bare sandbox_mode = "..." is parsed as a key of that preceding table, so Codex silently ignores the policy the daemon meant to apply. Two changes make the block position-independent: - renderMulticaManagedBlock now emits only top-level key=value lines and uses TOML dotted-key form (sandbox_workspace_write.network_access = true) instead of opening a [sandbox_workspace_write] header. The block therefore neither inherits from nor leaks into any surrounding table. - upsertMulticaManagedBlock always hoists the block to the top of the file (stripping any previously written managed block first), so the sandbox_mode line is always at the TOML root regardless of what the user put below it. This also migrates configs written by the original PR #1246 logic where the block was trapped behind a user table. Added tests for the regression scenario (pre-existing [permissions.*] table) and the legacy-trailing-block migration; updated the existing Linux default test and the troubleshooting runbook to reflect the dotted-key form. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: CC-Girl <cc-girl@multica.ai> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
6c92108b09 |
fix: replace hardcoded Unix path separators with filepath.Join and os.TempDir (#860)
- cmd_daemon.go: use filepath.Join for PID/log file paths instead of string concat with "/" - codex_home.go: use os.TempDir() instead of hardcoded "/tmp" for cross-platform fallback Co-authored-by: Devv <devv@Devvs-Mac-mini.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
526e336081 |
feat(execenv): add Windows fallback for symlink operations (#859)
On Windows, os.Symlink requires Developer Mode or admin privileges. Extract symlink creation into platform-specific files: on non-Windows, behavior is unchanged (os.Symlink). On Windows, try os.Symlink first, then fall back to directory junctions (mklink /J) for dirs and file copy for files. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
47eb6cb612 |
fix(agent): enable network access for Codex sandbox so Multica CLI can reach API
Codex tasks running in workspace-write sandbox mode could not resolve api.multica.ai because the hardcoded sandbox parameter in thread/start overrode any config.toml settings, and the default sandbox policy blocks network access. Changes: - Remove hardcoded `sandbox: "workspace-write"` from thread/start RPC — let Codex read sandbox config from its own config.toml instead - Auto-generate config.toml in per-task CODEX_HOME with `sandbox_mode = "workspace-write"` and `network_access = true`, preserving any existing user settings - Fix Reuse() to restore CodexHome for Codex provider on workdir reuse Closes #368 |
||
|
|
2c1d1d989c |
fix(daemon): symlink Codex sessions dir to shared home for discoverability (#627)
Per-task CODEX_HOME isolated session logs in per-task directories, making them invisible from the global ~/.codex/sessions/ where users expect to find them. Symlink the sessions directory back to the shared home so Codex writes session logs to the global location while keeping skills isolated per task. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
46144646c5 |
feat(daemon): inject skills into agent-native directories
Write skills to provider-native paths so agents discover them
automatically instead of relying on manual path references in
CLAUDE.md/AGENTS.md.
- Claude: write to {workDir}/.claude/skills/ (native discovery)
- Codex: write to per-task CODEX_HOME/skills/ with auth/config
seeded from ~/.codex/ (symlink auth.json, copy config files)
- Fallback: keep .agent_context/skills/ for unknown providers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|