CodeNomad/scripts/test-subagent-depth-settings-native.mjs
Pascal André f3246e27c3
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
fix(settings): keep Store images once working in projects with their own experimental block (#1200)
## Problem

OpenCode reads `experimental.media_blob` from the whole `experimental`
object of the highest-priority configuration document that has one
(upstream: anomalyco/opencode#60410). A project
`.opencode/opencode.jsonc` holding only
`{"experimental":{"subagent_depth":8}}` — exactly what the Missions
depth editor writes — turned **Store images once** off in that project
even though the Global box was checked. Settings only showed a caption
("Another OpenCode configuration's experimental settings take precedence
here: images stay inside each message.").

## Change

- **Images** (`opencode/image-settings.ts`): when the selected
Location's own project document (the project write target) decides
`media_blob` through its existing `experimental` object, the snapshot
reports `mediaStorage.project`, the box shows the effective value with
no caption, and saving writes `media_blob` to the Global document
**and** to that project block. A block that disappeared after reading is
never recreated (conflict). Image generation is read per field and still
writes Global only. The caption remains for deciding documents CodeNomad
does not edit.
- **Subagent depth Inherit** (`opencode/subagent-depth-settings.ts`):
removes, in one comment-preserving edit, a project `experimental` object
left empty or holding only a `media_blob` equal to the value removal
exposes. That value counts only when certain (runtime declares
`media_blob`; the next native object belongs to the Global write target
and deep-equals its fresh document); otherwise the object stays, which
hides nothing new. `removeEmptyNativeObject` takes a removability
predicate; `samePath`/`maskedBy` move to
`opencode/experimental-layers.ts`.
- The depth editor writes only the depth. Predicting the inherited
object for a newly created project block needs OpenCode's full layer
order (explicit, managed, content, normalization, watcher lag) and stays
out of scope; the root cause is upstream (#60410), and Images now shows
and fixes the per-project value in one click.
- Docs: `dev-docs/SETTINGS.md`, `dev-docs/MISSIONS.md`, help
`providers.md` and `missions.md` in 10 locales.

## Validation

- `image-settings.test.ts`, `subagent-depth-settings.test.ts` (6/6):
project write, comment preservation, disappeared-block refusal, Inherit
cleanup with BOM/CRLF comments, kept block for lagging/normalized
entries, no Global object, missing capability
- `websearch-settings.test.ts` browser test (4/4); the new Images
assertion fails without the card change
- `scripts/test-subagent-depth-settings-native.mjs` (Global image
storage cleanup) and `scripts/test-image-settings-native.mjs
--expect-supported` against an isolated OpenCode 2.2.1 CLI
- `scripts/test-help.mjs` (31/31), server and UI typecheck, `git diff
--check`
2026-10-30 03:04:15 +01:00

62 lines
4 KiB
JavaScript

// Private native schema + production document admission; no reload or shared service.
import assert from "node:assert/strict"
import { mkdir, readFile, writeFile } from "node:fs/promises"
import path from "node:path"
import { setTimeout as delay } from "node:timers/promises"
import { parse } from "jsonc-parser"
import { withProductRuntime } from "./native-product-fixture.mjs"
import { PluginControls } from "../packages/server/src/opencode/plugin-controls.ts"
import { SubagentDepthSettings } from "../packages/server/src/opencode/subagent-depth-settings.ts"
let configDirectory
await withProductRuntime(process.argv[2], async (fixture) => {
configDirectory = fixture.configDirectory
await writeFile(path.join(configDirectory, "opencode.jsonc"), '{"experimental":{"subagent_depth":2}}\n')
}, async ({ client, root, connection }) => {
await client.location.get({ location: { directory: root } })
const manager = {
get: () => ({}), getSharedServiceConnection: async () => connection,
getServiceDirectoryForPath: async (_id, directory) => directory === root ? root : undefined,
ownsLocation: async (_id, location) => location.directory === root,
getWorktreeIdentityForPath: async () => "private-fixture-root",
getServicePathStyle: () => process.platform === "win32" ? "win32" : "posix",
getServiceWslDistro: () => undefined, getHostPathForServicePath: async (_id, value) => value,
}
const controls = new PluginControls({ workspaceManager: manager,
worktreeDeletionFence: { enter: () => () => {} }, logger: {} })
const settings = new SubagentDepthSettings(controls, manager), location = { directory: root }
const initial = await settings.read("fixture", location)
assert.equal(initial.capability.minimum, 0)
assert.equal(initial.effectiveDepth, 2)
const file = initial.project.path
await mkdir(path.dirname(file), { recursive: true })
await writeFile(file, '{\n// retain private comment\n"experimental":{"foreign":true},"shell":"keep"\n}\n')
for (const value of [0, 1, 3]) {
const before = await settings.read("fixture", location)
await settings.update("fixture", location, value, before.project.expectation)
const saved = await readFile(file, "utf8")
assert.match(saved, /retain private comment/)
assert.deepEqual(parse(saved).experimental, { foreign: true, subagent_depth: value })
assert.equal(parse(saved).shell, "keep")
await assert.rejects(settings.update("fixture", location, 2, before.project.expectation), /changed/)
}
const fresh = await settings.read("fixture", location)
await settings.update("fixture", location, null, fresh.project.expectation)
assert.deepEqual(parse(await readFile(file, "utf8")).experimental, { foreign: true })
await assert.rejects(settings.update("fixture", { directory: path.dirname(root) }, 1, fresh.project.expectation))
// Inherit removes a block left with only the Global image storage once native entries match the Global document.
await writeFile(path.join(configDirectory, "opencode.jsonc"), '{"experimental":{"subagent_depth":2,"media_blob":true}}\n')
const deadline = Date.now() + 30_000
while (!(await client.config.get({ location: { directory: root } })).some(entry => entry.info?.experimental?.media_blob === true)) {
assert.ok(Date.now() < deadline, "the native watcher reloads the Global document")
await delay(100)
}
await writeFile(file, '{\n"experimental":{"subagent_depth":1,\n// retain private comment\n"media_blob":true},"shell":"keep"\n}\n')
await settings.update("fixture", location, null, (await settings.read("fixture", location)).project.expectation)
const reset = await readFile(file, "utf8")
assert.match(reset, /retain private comment/)
assert.equal(parse(reset).experimental, undefined)
console.log("PASS actual native depth capability, project JSONC save/reset, Global image storage cleanup, original-draft conflicts and foreign-location rejection")
console.log("NOT QUALIFIED: depth-0 execution semantics or watcher activation; fixture intentionally disables project discovery")
})