mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-21 13:54:39 +00:00
Use one Attach file action for native device selection and retain project-scoped @ references in the active session worktree. Remove the duplicate upload entry and composer server browser, document the outside-project scope change, and translate attachment origins and feedback across all locales. Share serialized, bounded byte ingestion across picker, drop and clipboard. Fence open pickers and late reads across session/worktree/lifecycle changes, preserve edits and newer selections without stealing focus, release rejected-read budgets, and restore failed custom-command drafts by attachment identity. Validate project directories through existing workspace ownership before filesystem listing/search. Cover the behavior with 20 attachment and 11 session-aside browser scenarios, attachment/submission/persistence units, server budget/proxy/ownership regressions, and isolated native Windows picker fixtures. Three independent review passes corrected five P2 findings and ended at zero; the final merged-head review confirmed that result. UI checks/build, Tauri release build and native picker smoke passed. Remote CI was still in progress at the explicitly requested admin merge.
107 lines
6.3 KiB
JavaScript
107 lines
6.3 KiB
JavaScript
import assert from "node:assert/strict"
|
|
import path from "node:path"
|
|
import Fastify from "fastify"
|
|
import replyFrom from "@fastify/reply-from"
|
|
import pino from "pino"
|
|
import { OpenCode } from "@opencode/client"
|
|
import { tsImport } from "tsx/esm/api"
|
|
|
|
// Called only by the isolated native fixture; no discovery or user storage.
|
|
export async function testNativeProxy({ client, baseUrl, root, authorization, runtimeFetch, connection, exercise }) {
|
|
const { registerInstanceProxyRoutes } = await tsImport("../packages/server/src/server/http-server.ts", import.meta.url)
|
|
const { PROMPT_INLINE_FILE_LIMITS } = await tsImport("../packages/server/src/api-types.ts", import.meta.url)
|
|
const { sessionEnvironment } = await tsImport("../packages/server/src/workspaces/session-environment.ts", import.meta.url)
|
|
const { createInstanceFetch } = await tsImport("../packages/ui/src/lib/sdk-manager.ts", import.meta.url)
|
|
const app = Fastify()
|
|
await app.register(replyFrom)
|
|
const owns = candidate => path.resolve(candidate) === path.resolve(root)
|
|
registerInstanceProxyRoutes(app, {
|
|
workspaceManager: {
|
|
get: () => ({ id: "native", path: root }),
|
|
getSharedServiceEndpoint: async () => ({ url: baseUrl }),
|
|
...(connection ? { getSharedServiceConnection: async () => connection } : {}),
|
|
getInstanceAuthorizationHeader: () => authorization,
|
|
getServiceDirectory: () => root,
|
|
getSharedServiceClient: async () => client,
|
|
getSessionEnvironment: () => sessionEnvironment({}),
|
|
...(runtimeFetch ? { getSharedServiceFetch: async () => runtimeFetch } : {}),
|
|
getWorktreeIdentityForPath: async (_id, directory) => owns(directory) ? root : undefined,
|
|
ownsDirectory: async (_id, directory) => owns(directory),
|
|
ownsLocation: async (_id, location) => owns(location.directory) && location.workspaceID === undefined,
|
|
ownsPath: async (_id, candidate) => owns(candidate),
|
|
},
|
|
worktreeDeletionFence: { enter: () => () => {} },
|
|
logger: pino({ level: "silent" }),
|
|
})
|
|
await app.listen({ host: "127.0.0.1", port: 0 })
|
|
const proxyBase = `http://127.0.0.1:${app.server.address().port}/workspaces/native/instance/`
|
|
const forward = createInstanceFetch(proxyBase)
|
|
const proxy = OpenCode.make({ baseUrl: proxyBase, fetch: async (input, init) => {
|
|
const response = await forward(input, init)
|
|
if (!response.ok) assert.fail(`${init?.method} ${input}: ${response.status} ${await response.text()}`)
|
|
return response
|
|
} })
|
|
const created = []
|
|
try {
|
|
const location = { directory: root }
|
|
const info = await proxy.location.get({ location })
|
|
assert.ok(info.project.id)
|
|
assert.ok(Array.isArray(await proxy.project.list()))
|
|
for (const resource of [proxy.agent, proxy.provider, proxy.model, proxy.command, proxy.plugin, proxy.mcp]) {
|
|
assert.ok(Array.isArray((await resource.list({ location })).data))
|
|
}
|
|
assert.ok(Array.isArray((await proxy.permission.request.list({ location })).data))
|
|
for (let index = 0; index < 3; index++) created.push(await proxy.session.create({ location }))
|
|
const sessionID = created[0].id
|
|
await proxy.session.update({ sessionID, title: "Stable proxy fixture" })
|
|
assert.equal((await proxy.session.get({ sessionID })).title, "Stable proxy fixture")
|
|
const page = await proxy.session.list({ directory: root, limit: 1 })
|
|
assert.equal(page.data.length, 1)
|
|
assert.ok(page.cursor.next)
|
|
const continuation = await proxy.session.list({ cursor: page.cursor.next, limit: 1 })
|
|
assert.equal(continuation.data.length, 1)
|
|
assert.notEqual(continuation.data[0].id, page.data[0].id)
|
|
assert.equal(typeof await proxy.session.active(), "object")
|
|
await proxy.session.instructions.entry.put({ sessionID, key: "fixture", value: "Fixture instruction" })
|
|
await proxy.session.instructions.entry.remove({ sessionID, key: "fixture" })
|
|
await proxy.session.wait({ sessionID })
|
|
const header = { headers: { "x-opencode-directory": encodeURIComponent(root) } }
|
|
for (const owner of [sessionID, "global"]) {
|
|
for (const action of ["reply", "cancel"]) {
|
|
const form = await client.session.form.create({
|
|
sessionID: owner, title: "Fixture form", fields: [{ key: "answer", type: "string" }],
|
|
}, owner === "global" ? header : undefined)
|
|
const listed = await proxy.form.list({ location })
|
|
assert.ok(listed.data.some(item => item.id === form.id))
|
|
const input = { sessionID: owner, formID: form.id }
|
|
if (action === "reply") await proxy.session.form.reply({ ...input, answer: { answer: "ok" } }, header)
|
|
else await proxy.session.form.cancel(input, header)
|
|
assert.ok(!(await proxy.form.list({ location })).data.some(item => item.id === form.id))
|
|
}
|
|
}
|
|
await exercise?.(proxy, sessionID)
|
|
const inlineSessionID = created[1].id
|
|
const inlineFile = Buffer.alloc(PROMPT_INLINE_FILE_LIMITS.maxFileBytes, 0x61).toString("base64")
|
|
await proxy.session.prompt({
|
|
sessionID: inlineSessionID,
|
|
text: "Acknowledge the attached transport fixture.",
|
|
files: [{ name: "inline-limit.txt", uri: `data:text/plain;base64,${inlineFile}` }],
|
|
})
|
|
await proxy.session.wait({ sessionID: inlineSessionID }, { signal: AbortSignal.timeout(60_000) })
|
|
assert((await proxy.message.list({ sessionID: inlineSessionID, limit: 20 })).data.some(message => message.type === "user"))
|
|
console.log(`PASS: ${PROMPT_INLINE_FILE_LIMITS.maxFileBytes}-byte inline file through real proxy and OpenCode endpoint`)
|
|
// Export remains a direct native read; it is deliberately not added to the
|
|
// guarded UI allowlist merely for this fixture.
|
|
const exported = await client.session.export({ sessionID })
|
|
await proxy.session.remove({ sessionID })
|
|
const restored = await proxy.session.import({ ...exported, location })
|
|
assert.equal(restored.id, sessionID)
|
|
assert.equal((await proxy.message.list({ sessionID, limit: 100 })).data.length, exported.messages.length)
|
|
console.log("PASS: generated stable client through real proxy: catalogs, sessions, native cursors, active envelope, instructions, wait and session/global Forms")
|
|
} finally {
|
|
// Import can fail after its source has already been removed. Cleanup must
|
|
// neither replace that failure with a 404 nor leave the HTTP listener open.
|
|
try { await Promise.allSettled(created.map(session => client.session.remove({ sessionID: session.id }))) }
|
|
finally { await app.close() }
|
|
}
|
|
}
|