Dashboard › opencode › Session 16PhI6appvKA
16PhI6appvKAE7YWdImplement the user-approved architecture change for the OpenCode V2 pilot. Work only in /home/byk/.local/share/opencode-v2-pilot/config/opencode, /home/byk/.local/share/opencode-v2-pilot/opencode-v2.service, /home/byk/.local/share/opencode-v2-pilot/CUTOVER.md, and /tmp/opencode/pty-plugin-smoke.ts. Do not touch /home/byk/.local/share/opencode-v2-pilot/supervisor except to read it if useful. Do not install, start, stop, or restart any service. Do not modify production. Use apply_patch for edits.
Decision: abandon the root supervisor architecture and match the existing production trust model. OpenCode and model-controlled PTYs run as byk under the existing systemd service with NoNewPrivileges=yes and the account's normal supplementary groups. No root service, dedicated user, helper, readiness marker, SCM_RIGHTS, transient systemd unit, or cgroup supervisor is required. The known limitation is explicit: a malicious command running as trusted byk may deliberately daemonize/escape its original process group, so plugin cleanup cannot guarantee killing such descendants. This is accepted because model shell commands already have byk authority and matches existing production opencode-pty trust.
Tasks:
plugins/pty.ts to use the prior hardened direct Node-safe backend: node:child_process spawning /usr/bin/script (util-linux) as the current byk service process, not bun-pty, not @opencode-ai/pty, and never supervisor transport. Preserve Plugin.define API and exactly five tools in order: pty_spawn, pty_write, pty_read, pty_list, pty_kill.save: []; authorize exact decoded write bytes; Session ownership on every operation; atomic global/per-owner reservations before first await; closing/deleted admission gates and in-flight spawn cleanup; fixed safe environment with no inherited ...<skill_content name="customize-opencode"> # Skill: customize-opencode <!-- Built-in skill. Name and description are registered in code at packages/core/src/plugin/skill.ts and CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION). The body below becomes the skill's content. --> # Customizing opencode opencode validates its own config strictly and refuses to start when a field is wrong. The shapes below cover the common surface area, but they are a **summary, not the source of truth**. ## Full schema reference The authoritative list of every config option — with field types, enums, defaults, and descriptions — lives in the published JSON Schema: **<https://opencode.ai/config.json>** If a field is not documented in this skill, or you need to confirm an exact shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. Independently, every `opencode.json` should ...
I’ll first inspect the frozen pilot files and existing smoke contract, then patch only the approved paths. I will not touch production or control any service.
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode</path> <type>directory</type> <entries> bun.lock node_modules/ opencode.json package.json plugins/ service-v2-pilot.json test/ tsconfig.json types/ (9 entries) </entries>
/home/byk/.local/share/opencode-v2-pilot/config/opencode/bun.lock /home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json /home/byk/.local/share/opencode-v2-pilot/config/opencode/test/followup.test.ts /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/package.json /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/index.d.ts /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/README.md /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/LICENSE /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/lib/process.js /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/lib/filesystem.js /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/lib/elf.js /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/detect-libc/lib/detect-libc.js /home/byk/.local/share/...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import { open, realpath } from "node:fs/promises"
7: import type { FileHandle } from "node:fs/promises"
8: import { constants } from "node:fs"
9: import { assertSupervisorAvailable, MAX_BUFFER_SIZE, openSupervisorTransport } from "./pty-transport"
10: import type { SupervisorOptions, SupervisorTransport } from "./pty-transport"
11:
12: type PtyPluginOptions = SupervisorOptions & {
13: assertAvailable?: typeof assertSupervisorAvailable
14: openTransport?: typeof openSupervisorTransport
15: }
16:
17: const MAX_SESSIONS = 16
18: const MAX_SESSIONS_PER_OWNER = 5
19: const DEFAULT_READ_LIMIT = 500
20: const MAX_READ_LIMIT = ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty-transport.ts</path>
<type>file</type>
<content>
1: import { spawn } from "node:child_process"
2: import type { ChildProcess } from "node:child_process"
3: import { isUtf8 } from "node:buffer"
4: import { fstatSync } from "node:fs"
5: import { stat } from "node:fs/promises"
6: import type { FileHandle } from "node:fs/promises"
7: import { createConnection } from "node:net"
8: import type { Socket } from "node:net"
9:
10: const MAGIC = Buffer.from("OPTY")
11: const VERSION = 1
12: const HEADER_SIZE = 12
13: const MAX_FRAME_SIZE = 70 * 1024
14: const MAX_IO_PAYLOAD_SIZE = 32 * 1024
15: const TYPE_INPUT = 2
16: const TYPE_OUTPUT = 3
17: const TYPE_EXIT = 4
18: const TYPE_ERROR = 5
19: const TYPE_STARTED = 6
20: const TYPE_STOP = 7
21: const TYPE_STOPPED = 8
22: const DEFAULT_SOCKET_PATH = "/run/opencode-pty/supervisor.sock"
23: const DEFAULT_HELPER_PATH = "/usr/local/libexec/opencode-pty-client"
24: const ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: "allowImportingTsExtensions": true,
4: "lib": ["ESNext", "DOM", "DOM.Iterable"],
5: "module": "Preserve",
6: "moduleResolution": "Bundler",
7: "noEmit": true,
8: "paths": {
9: "@opencode/plugin": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/index.ts"],
10: "@opencode/plugin/*": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/*.ts"]
11: },
12: "skipLibCheck": true,
13: "strict": true,
14: "target": "ESNext",
15: "types": ["bun"]
16: },
17: "include": ["plugins/**/*.ts", "test/**/*.ts"]
18: }
(End of file - total 18 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://opencode.ai/config.json"
3: }
(End of file - total 3 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/package.json</path>
<type>file</type>
<content>
1: {
2: "private": true,
3: "type": "module",
4: "dependencies": {
5: "effect": "file:/home/byk/Code/opencode-v2-pilot/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect"
6: },
7: "devDependencies": {
8: "@types/bun": "1.2.21"
9: }
10: }
(End of file - total 10 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/opencode-v2.service</path> <type>file</type> <content> 1: [Unit] 2: Description=OpenCode V2 Server 3: After=network.target opencode-pty-supervisor.service 4: Requires=opencode-pty-supervisor.service 5: 6: [Service] 7: Type=simple 8: EnvironmentFile=/home/byk/.opencode/env 9: EnvironmentFile=/home/byk/.local/share/opencode-v2-pilot/server.env 10: Environment=OPENCODE_CONFIG_DIR=/home/byk/.local/share/opencode-v2-pilot/config/opencode 11: Environment=OPENCODE_DB=/home/byk/.local/share/opencode-v2-pilot/data/opencode/opencode.db 12: Environment=XDG_DATA_HOME=/home/byk/.local/share/opencode-v2-pilot/data 13: Environment=XDG_CACHE_HOME=/home/byk/.local/share/opencode-v2-pilot/cache 14: Environment=XDG_CONFIG_HOME=/home/byk/.local/share/opencode-v2-pilot/config 15: Environment=XDG_STATE_HOME=/home/byk/.local/share/opencode-v2-pilot/state 16: User=byk 17: WorkingDirectory=/home/byk 18: ...
<path>/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md</path> <type>file</type> <content> 1: # OpenCode V2 cutover 2: 3: The V2 executable serves the API and web UI from one origin. Do not add nginx. 4: 5: ## Preflight 6: 7: 1. Confirm production is still healthy on port 4096. 8: 2. Confirm the candidate revision and smoke build: 9: `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` 10: `/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node --version` 11: 3. Confirm the isolated candidate on port 14102: 12: `curl --fail --user opencode:$OPENCODE_PASSWORD http://127.0.0.1:14102/api/health` 13: `curl --fail http://127.0.0.1:14102/ >/dev/null` 14: 4. Confirm both local plugins are active after calling `POST /api/plugin/await-activation`. 15: 5. Create `/home/byk/.local/share/opencode-v2-pilot/server.env` with mode `0600` and one line: `OPENCODE_PASSWORD=<password>`. 16: 6. ...
File not found: /tmp/opencode/pty-plugin-smoke.ts
No files found
wrapper 3105140
3105130 3086649 3105130 3105130 \_ node \_ node -e const {spawn,spawnSync}=require("node:child_process"); const p=spawn("/usr/bin/script",["-qefc","/bin/sh -c 'read x'","/dev/null"],{detached:true,stdio:["pipe","pipe","pipe"]}); p.once("spawn",()=>{console.log("wrapper",p.pid); console.log(spawnSync("/usr/bin/ps",["-eo","pid,ppid,pgid,sid,comm,args","--forest"],{encoding:"utf8"}).stdout.split("\n").filter(x=>x.includes(String(p.pid))||x.includes("read x")||x.includes("script -qefc")).join("\n")); process.kill(-p.pid,"SIGKILL")}); p.once("close",()=>process.exit())
3105132 3105130 3105130 3105130 \_ MainThr \_ node -e const {spawn,spawnSync}=require("node:child_process"); const p=spawn("/usr/bin/script",["-qefc","/bin/sh -c 'read x'","/dev/null"],{detached:true,stdio:["pipe","pipe","pipe"]}); p.once("spawn",()=>{console.log("wrapper",p.pid); ...
before 3105828 3105829 3105828 3105829 3105829 S<s+ sh after "3105828 3105821 3105828 3105828 S<s script" "3105829" close null SIGKILL
wrapper,target 3107194 3107195 3107194 3107187 3107194 3107194 S<s script 3107195 3107194 3107195 3107195 S<s+ sh post-term "3107194 3107187 3107194 3107194 S<s script\n3107195 3107194 3107195 3107195 S<s+ sh" close 137 null
The supervisor-backed plugin still contains the hardened admission, permission, ownership, paging, and teardown logic. I’m retaining those invariants and replacing only its process boundary with a direct script(1) PTY backend.
No files found
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/followup.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import { Schema } from "effect"
3:
4: const SUBAGENT_REFUSAL =
5: "Error: scheduled follow-ups are disabled in subagents. A subagent must finish its task and return a result synchronously."
6:
7: const DelayInput = Schema.Struct({
8: delay: Schema.String.annotate({
9: description: "How long to wait, such as 30s, 2m, or 1h. A bare number means seconds.",
10: }),
11: message: Schema.String.annotate({
12: description: "The instruction to deliver when the delay expires.",
13: }),
14: })
15:
16: const EmptyInput = Schema.Struct({})
17:
18: const CancelInput = Schema.Struct({
19: id: Schema.String.annotate({ description: "The follow-up ID to cancel, or all." }),
20: })
21:
22: export default Plugin.define({
23: id: "followup",
24: async setup(ctx) {
25: const pending = new Map<
26: string,
27: ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/followup.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import followup from "../plugins/followup.ts"
3:
4: describe("followup", () => {
5: test("schedules, lists, cancels, delivers, and rejects subagents", async () => {
6: const tools = new Map<string, Tool>()
7: const prompts: Array<{ sessionID: string; text: string; delivery?: string }> = []
8: const controller = new AbortController()
9: const context = {
10: session: {
11: get: async ({ sessionID }: { sessionID: string }) =>
12: sessionID === "subagent" ? { id: sessionID, parentID: "parent" } : { id: sessionID },
13: prompt: async (input: { sessionID: string; text: string; delivery?: string }) => {
14: prompts.push(input)
15: return {}
16: },
17: },
18: tool: {
19: transform: async (callback: (editor: { add(tool: Tool): void }) => void) => {
20: callback({ ...
Found 45 matches /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/@types/node/child_process.d.ts: Line 490: interface ChildProcessByStdio<I extends null | Writable, O extends null | Readable, E extends null | Readable> Line 697: function spawn(command: string, options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams; Line 698: function spawn( Line 701: ): ChildProcessByStdio<Writable, Readable, Readable>; Line 702: function spawn( Line 705: ): ChildProcessByStdio<Writable, Readable, null>; Line 706: function spawn( Line 709: ): ChildProcessByStdio<Writable, null, Readable>; Line 710: function spawn( Line 713: ): ChildProcessByStdio<null, Readable, Readable>; Line 714: function spawn( Line 717: ): ChildProcessByStdio<Writable, null, null>; Line 718: function spawn( Line 721: ): ChildProcessByStdio<null, Readable, null>; Line 722: function spawn( ...
mi code 0
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import { open, realpath } from "node:fs/promises"
7: import type { FileHandle } from "node:fs/promises"
8: import { constants } from "node:fs"
9: import { assertSupervisorAvailable, MAX_BUFFER_SIZE, openSupervisorTransport } from "./pty-transport"
10: import type { SupervisorOptions, SupervisorTransport } from "./pty-transport"
11:
12: type PtyPluginOptions = SupervisorOptions & {
13: assertAvailable?: typeof assertSupervisorAvailable
14: openTransport?: typeof openSupervisorTransport
15: }
16:
17: const MAX_SESSIONS = 16
18: const MAX_SESSIONS_PER_OWNER = 5
19: const DEFAULT_READ_LIMIT = 500
20: const MAX_READ_LIMIT = ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins</path> <type>directory</type> <entries> followup.ts pty-transport.ts pty.ts (3 entries) </entries>
<path>/home/byk/.local/share/opencode-v2-pilot/opencode-v2.service</path> <type>file</type> <content> 1: [Unit] 2: Description=OpenCode V2 Server 3: After=network.target opencode-pty-supervisor.service 4: Requires=opencode-pty-supervisor.service 5: 6: [Service] 7: Type=simple 8: EnvironmentFile=/home/byk/.opencode/env 9: EnvironmentFile=/home/byk/.local/share/opencode-v2-pilot/server.env 10: Environment=OPENCODE_CONFIG_DIR=/home/byk/.local/share/opencode-v2-pilot/config/opencode 11: Environment=OPENCODE_DB=/home/byk/.local/share/opencode-v2-pilot/data/opencode/opencode.db 12: Environment=XDG_DATA_HOME=/home/byk/.local/share/opencode-v2-pilot/data 13: Environment=XDG_CACHE_HOME=/home/byk/.local/share/opencode-v2-pilot/cache 14: Environment=XDG_CONFIG_HOME=/home/byk/.local/share/opencode-v2-pilot/config 15: Environment=XDG_STATE_HOME=/home/byk/.local/share/opencode-v2-pilot/state 16: User=byk 17: WorkingDirectory=/home/byk 18: ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: "allowImportingTsExtensions": true,
4: "lib": ["ESNext", "DOM", "DOM.Iterable"],
5: "module": "Preserve",
6: "moduleResolution": "Bundler",
7: "noEmit": true,
8: "paths": {
9: "@opencode/plugin": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/index.ts"],
10: "@opencode/plugin/*": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/*.ts"]
11: },
12: "skipLibCheck": true,
13: "strict": true,
14: "target": "ESNext",
15: "types": ["bun"]
16: },
17: "include": ["plugins/**/*.ts", "test/**/*.ts"]
18: }
(End of file - total 18 lines)
</content>
<skill_content name="customize-opencode"> # Skill: customize-opencode <!-- Built-in skill. Name and description are registered in code at packages/core/src/plugin/skill.ts and CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION). The body below becomes the skill's content. --> # Customizing opencode opencode validates its own config strictly and refuses to start when a field is wrong. The shapes below cover the common surface area, but they are a **summary, not the source of truth**. ## Full schema reference The authoritative list of every config option — with field types, enums, defaults, and descriptions — lives in the published JSON Schema: **<https://opencode.ai/config.json>** If a field is not documented in this skill, or you need to confirm an exact shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. Independently, every `opencode.json` should ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://opencode.ai/config.json"
3: }
(End of file - total 3 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md</path> <type>file</type> <content> 1: # OpenCode V2 cutover 2: 3: The V2 executable serves the API and web UI from one origin. Do not add nginx. 4: 5: ## Preflight 6: 7: 1. Confirm production is still healthy on port 4096. 8: 2. Confirm the candidate revision and smoke build: 9: `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` 10: `/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node --version` 11: 3. Confirm the isolated candidate on port 14102: 12: `curl --fail --user opencode:$OPENCODE_PASSWORD http://127.0.0.1:14102/api/health` 13: `curl --fail http://127.0.0.1:14102/ >/dev/null` 14: 4. Confirm both local plugins are active after calling `POST /api/plugin/await-activation`. 15: 5. Create `/home/byk/.local/share/opencode-v2-pilot/server.env` with mode `0600` and one line: `OPENCODE_PASSWORD=<password>`. 16: 6. ...
I’m updating the remaining candidate-only references now: the plugin source and tests, the staged service unit, the cutover runbook, and the isolated plugin typecheck. Production stays untouched.
apply_patch verification failed: Error: Failed to find expected lines in /home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:
const session: Session = {
No files found
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/@types/node/child_process.d.ts</path>
<type>file</type>
<content>
470: */
471: ref(): void;
472: }
473: interface ChildProcess extends InternalEventEmitter<ChildProcessEventMap> {}
474: // return this object when stdio option is undefined or not specified
475: interface ChildProcessWithoutNullStreams extends ChildProcess {
476: stdin: Writable;
477: stdout: Readable;
478: stderr: Readable;
479: readonly stdio: [
480: Writable,
481: Readable,
482: Readable,
483: // stderr
484: Readable | Writable | null | undefined,
485: // extra, no modification
486: Readable | Writable | null | undefined, // extra, no modification
487: ];
488: }
489: // return this object when stdio option is a tuple of 3
490: interface ChildProcessByStdio<I extends null | ...
/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty-transport.ts /home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty.so /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty_musl.so /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty_arm64.so /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty_arm64_musl.so /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty.dylib /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/librust_pty_arm64.dylib /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/rust-pty/target/release/rust_pty...
Found 6 matches
/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/supervisor/main.go:
Line 14: ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/launcher/main.go:
Line 129: "LANG": "C.UTF-8",
Line 131: "PATH": "/usr/local/bin:/usr/bin:/bin",
Line 133: "TERM": "xterm-256color",
Line 137: return fmt.Errorf("set fixed environment: %w", err)
/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/launcher/main_test.go:
Line 80: shell = "kill -TERM $$"
<path>/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/launcher/main.go</path>
<type>file</type>
<content>
110: }
111: if index > 0 {
112: argsSize += len(item)
113: if argsSize > protocol.MaxArgsSize {
114: return nil, errors.New("aggregate arguments exceed limit")
115: }
116: }
117: decoded = append(decoded, string(item))
118: }
119: if decoded[0] == "" {
120: return nil, errors.New("command is empty")
121: }
122: return decoded, nil
123: }
124:
125: func setFixedEnvironment() error {
126: os.Clearenv()
127: for key, value := range map[string]string{
128: "HOME": "/home/byk",
129: "LANG": "C.UTF-8",
130: "LOGNAME": "byk",
131: "PATH": "/usr/local/bin:/usr/bin:/bin",
132: "SHELL": "/bin/sh",
133: "TERM": "xterm-256color",
134: "USER": "byk",
135: } {
136: if err := os.Setenv(key, value); err != nil {
137: return fmt.Errorf("set fixed environment: %w", err)
138: }
139: }
140: return nil
141: }
142:
143: ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import type { ChildProcessByStdio } from "node:child_process"
7: import { open, realpath } from "node:fs/promises"
8: import type { FileHandle } from "node:fs/promises"
9: import { constants } from "node:fs"
10: import type { Readable, Writable } from "node:stream"
11:
12: type PtyPluginOptions = {
13: openProcess?: typeof openPtyProcess
14: }
15:
16: const MAX_SESSIONS = 16
17: const MAX_SESSIONS_PER_OWNER = 5
18: const DEFAULT_READ_LIMIT = 500
19: const MAX_READ_LIMIT = 2_000
20: const MAX_READ_OFFSET = 10_000
21: const MAX_LINE_LENGTH = 2_000
22: const MAX_PATTERN_LENGTH = 500
23: const MAX_WRITE_SIZE = 65_536
24: const ...
No files found
No files found
No files found
No files found
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/launcher/main.go</path>
<type>file</type>
<content>
1: package main
2:
3: import (
4: "encoding/base64"
5: "errors"
6: "fmt"
7: "os"
8: "strings"
9: "syscall"
10:
11: "opencode-pty-supervisor/internal/protocol"
12:
13: "golang.org/x/sys/unix"
14: )
15:
16: const encodedArgumentsEnvironment = "OPENCODE_PTY_ARGV"
17:
18: const launcherFD = 3
19:
20: func main() {
21: if err := run(os.Args[1:]); err != nil {
22: fmt.Fprintln(os.Stderr, err)
23: os.Exit(1)
24: }
25: }
26:
27: func run(values []string) error {
28: if len(values) == 1 && values[0] == "--exec" {
29: return execCommand()
30: }
31: if _, err := decodeArguments(values); err != nil {
32: return err
33: }
34: if err := syscall.Fchdir(2); err != nil {
35: return fmt.Errorf("fchdir approved cwd: %w", err)
36: }
37: if err := syscall.Dup2(1, 2); err != nil {
38: return fmt.Errorf("replace cwd descriptor: %w", err)
39: }
40: if err := ...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/package.json</path>
<type>file</type>
<content>
1: {
2: "private": true,
3: "type": "module",
4: "dependencies": {
5: "effect": "file:/home/byk/Code/opencode-v2-pilot/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect"
6: },
7: "devDependencies": {
8: "@types/bun": "1.2.21"
9: }
10: }
(End of file - total 10 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: "allowImportingTsExtensions": true,
4: "lib": ["ESNext", "DOM", "DOM.Iterable"],
5: "module": "Preserve",
6: "moduleResolution": "Bundler",
7: "noEmit": true,
8: "paths": {
9: "@opencode/plugin": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/index.ts"],
10: "@opencode/plugin/*": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/*.ts"]
11: },
12: "skipLibCheck": true,
13: "strict": true,
14: "target": "ESNext",
15: "types": ["bun"]
16: },
17: "include": ["plugins/**/*.ts", "test/**/*.ts"]
18: }
(End of file - total 18 lines)
</content>
No files found
No files found
The direct backend is now in place. I’m checking it against the frozen smoke contract and candidate service/cutover files before removing the abandoned transport.
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
450: "PTY plugin cleanup timed out.",
451: ).catch((error) => {
452: console.error("[pty] plugin cleanup failed", error)
453: return []
454: })
455: cleanup.forEach((entry) => {
456: if (entry.status === "rejected") console.error("[pty] plugin cleanup failed", entry.reason)
457: })
458: }
459: },
460: })
461: }
462:
463: export default createPtyPlugin()
464:
465: function result(text: string) {
466: return { output: text, content: text }
467: }
468:
469: function requireSession(sessions: Map<string, Session>, id: string, sessionID: string) {
470: const session = sessions.get(id)
471: if (!session || session.parentSessionID !== sessionID) throw new Error(`PTY session not found: ${id}`)
472: return session
473: }
474:
475: function lineCount(input: string) {
476: if (!input) return 0
477: let count = input.endsWith("\n") ? ...
<skill_content name="customize-opencode"> # Skill: customize-opencode <!-- Built-in skill. Name and description are registered in code at packages/core/src/plugin/skill.ts and CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION). The body below becomes the skill's content. --> # Customizing opencode opencode validates its own config strictly and refuses to start when a field is wrong. The shapes below cover the common surface area, but they are a **summary, not the source of truth**. ## Full schema reference The authoritative list of every config option — with field types, enums, defaults, and descriptions — lives in the published JSON Schema: **<https://opencode.ai/config.json>** If a field is not documented in this skill, or you need to confirm an exact shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. Independently, every `opencode.json` should ...
File not found: /tmp/opencode/pty-plugin-smoke.ts
Found 1 matches /home/byk/.local/share/opencode-v2-pilot/supervisor/ROOT-ACCEPTANCE.md: Line 39: 14. Run `bun /tmp/opencode/pty-plugin-smoke.ts` ten times. Keep that reviewed harness outside the auto-discovered plugin directory. Then run it against the freshly built helper and an isolated disposable supervisor. Confirm permissions, source identity, ownership, deletion, in-flight deletion, reservations, JSON envelopes, regex bounds, UTF-8 write bounds, pre-listener output bounds, saturated STOP bounds, transport framing, helper transfer, notifications, timeouts, and cleanup.
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://opencode.ai/config.json"
3: }
(End of file - total 3 lines)
</content>
No files found
No files found
No files found
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/service-v2-pilot.json</path>
<type>file</type>
<content>
1: {
2: "hostname": "127.0.0.1",
3: "port": 14097,
4: "password": "0ZEDypl6r2V8gYMw+bfbOvabqO7NvvaG6+m/NvAHxJg="
5: }
(End of file - total 5 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/opencode-v2.service</path> <type>file</type> <content> 1: [Unit] 2: Description=OpenCode V2 Server 3: After=network.target opencode-pty-supervisor.service 4: Requires=opencode-pty-supervisor.service 5: 6: [Service] 7: Type=simple 8: EnvironmentFile=/home/byk/.opencode/env 9: EnvironmentFile=/home/byk/.local/share/opencode-v2-pilot/server.env 10: Environment=OPENCODE_CONFIG_DIR=/home/byk/.local/share/opencode-v2-pilot/config/opencode 11: Environment=OPENCODE_DB=/home/byk/.local/share/opencode-v2-pilot/data/opencode/opencode.db 12: Environment=XDG_DATA_HOME=/home/byk/.local/share/opencode-v2-pilot/data 13: Environment=XDG_CACHE_HOME=/home/byk/.local/share/opencode-v2-pilot/cache 14: Environment=XDG_CONFIG_HOME=/home/byk/.local/share/opencode-v2-pilot/config 15: Environment=XDG_STATE_HOME=/home/byk/.local/share/opencode-v2-pilot/state 16: User=byk 17: WorkingDirectory=/home/byk 18: ...
<path>/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md</path> <type>file</type> <content> 1: # OpenCode V2 cutover 2: 3: The V2 executable serves the API and web UI from one origin. Do not add nginx. 4: 5: ## Preflight 6: 7: 1. Confirm production is still healthy on port 4096. 8: 2. Confirm the candidate revision and smoke build: 9: `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` 10: `/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node --version` 11: 3. Confirm the isolated candidate on port 14102: 12: `curl --fail --user opencode:$OPENCODE_PASSWORD http://127.0.0.1:14102/api/health` 13: `curl --fail http://127.0.0.1:14102/ >/dev/null` 14: 4. Confirm both local plugins are active after calling `POST /api/plugin/await-activation`. 15: 5. Create `/home/byk/.local/share/opencode-v2-pilot/server.env` with mode `0600` and one line: `OPENCODE_PASSWORD=<password>`. 16: 6. ...
I found stale activation blockers: the candidate unit and cutover guide still require the dormant root supervisor. I’ll remove those dependencies and replace the supervisor acceptance steps with checks for the direct byk PTY model and its stated process-group limit.
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/opencode-v2.service M ../../.local/share/opencode-v2-pilot/CUTOVER.md
Found 3 matches
/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:
Line 13: openProcess?: typeof openPtyProcess
Line 237: const process = await (supervisor.openProcess ?? openPtyProcess)(binding.handle, input.command, input.args).catch(async (error) => {
Line 526: async function openPtyProcess(directory: FileHandle, command: string, args: readonly string[]) {
DATA "3134860\n" KILL 3134859 END CLOSE null SIGKILL
Found 2 matches
/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/plugin.ts:
Line 25: export interface Context {
/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/tool.ts:
Line 11: export interface ToolContext extends Omit<Tool.Context, "progress"> {
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/plugin.ts</path>
<type>file</type>
<content>
1: import type { OpenCodeClient } from "@opencode/client"
2: import type { GenerateApi, PluginApi } from "@opencode/client/promise/api"
3: import type { Location } from "@opencode/schema/location"
4: import type { PluginOptions } from "../options.js"
5: import type { App } from "../app.js"
6: import type { AgentDomain } from "./agent.js"
7: import type { AISDKDomain } from "./aisdk.js"
8: import type { CatalogDomain } from "./catalog.js"
9: import type { CommandDomain } from "./command.js"
10: import type { EventDomain } from "./event.js"
11: import type { IntegrationDomain } from "./integration.js"
12: import type { MCPDomain } from "./mcp.js"
13: import type { PermissionDomain } from "./permission.js"
14: import type { ReferenceDomain } from "./reference.js"
15: import type { RpcDomain } from "./rpc.js"
16: import type { SessionDomain } from "./session.js"
17: import type ...
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/tool.ts</path>
<type>file</type>
<content>
1: export { CallID, Error } from "@opencode/schema/tool"
2: export type { Metadata, Options, Result } from "@opencode/schema/tool"
3:
4: import { Tool } from "@opencode/schema/tool"
5: import type { Agent } from "@opencode/schema/agent"
6: import type { Session } from "@opencode/schema/session"
7: import type { SessionMessage } from "@opencode/schema/session-message"
8: import type { Types } from "effect"
9: import type { Hooks, Transform } from "./registration.js"
10:
11: export interface ToolContext extends Omit<Tool.Context, "progress"> {
12: readonly progress: (update: Tool.Metadata) => Promise<void>
13: }
14:
15: export type Info<
16: Input extends Tool.ValueSchema<any> = Tool.ValueSchema<any>,
17: Output extends Tool.ValueSchema<any> | undefined = Tool.ValueSchema<any> | undefined,
18: > = Omit<Tool.Info<Input, Output>, "execute"> & {
19: readonly execute: ...
/home/byk/.volta/bin/bun 1.3.14
/home/byk/.bun/install/cache/oxlint-tsgolint@0.21.0@@@1/bin/tsgolint.js /home/byk/.bun/install/cache/@oxlint-tsgolint/win32-x64@0.21.0@@@1/tsgolint.exe /home/byk/.bun/install/cache/@oxlint-tsgolint/darwin-arm64@0.21.0@@@1/tsgolint /home/byk/.bun/install/cache/@oxlint-tsgolint/darwin-x64@0.21.0@@@1/tsgolint /home/byk/.bun/install/cache/@oxlint-tsgolint/win32-arm64@0.21.0@@@1/tsgolint.exe /home/byk/.bun/install/cache/@oxlint-tsgolint/linux-arm64@0.21.0@@@1/tsgolint /home/byk/.bun/install/cache/@oxlint-tsgolint/linux-x64@0.21.0@@@1/tsgolint /home/byk/.bun/install/cache/@typescript/native-preview-win32-x64@7.0.0-1108584ef5254fdf@@@1/lib/tsgo.exe /home/byk/.bun/install/cache/@typescript/native-preview-darwin-arm64@7.0.0-1108584ef5254fdf@@@1/lib/tsgo /home/byk/.bun/install/cache/@typescript/native-preview-linux-arm@7.0.0-1108584ef5254fdf@@@1/lib/tsgo.sig /home/byk/.bun/install/cache/@typescript/native-preview-linux-arm@7.0.0-1108584ef5254fdf@@@1/lib/tsgo /home/byk/.bun/install/cache/@typescr...
(no output)
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/followup.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import followup from "../plugins/followup.ts"
3:
4: describe("followup", () => {
5: test("schedules, lists, cancels, delivers, and rejects subagents", async () => {
6: const tools = new Map<string, Tool>()
7: const prompts: Array<{ sessionID: string; text: string; delivery?: string }> = []
8: const controller = new AbortController()
9: const context = {
10: session: {
11: get: async ({ sessionID }: { sessionID: string }) =>
12: sessionID === "subagent" ? { id: sessionID, parentID: "parent" } : { id: sessionID },
13: prompt: async (input: { sessionID: string; text: string; delivery?: string }) => {
14: prompts.push(input)
15: return {}
16: },
17: },
18: tool: {
19: transform: async (callback: (editor: { add(tool: Tool): void }) => void) => {
20: callback({ ...
Success. Updated the following files: A ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
bun test v1.3.14 (0d9b296a)
killed 1 dangling process
test/pty.test.ts:
# Unhandled error between tests
-------------------------------
292 | }, input.timeoutSeconds * 1_000)
293 | if (typeof session.timer === "object") session.timer.unref()
294 | }
295 | if (closing || deleted.has(context.sessionID)) {
296 | await terminate(session)
297 | throw new Error(closing ? "PTY plugin is closing." : `Session has been deleted: ${context.sessionID}`)
^
error: PTY plugin is closing.
at <anonymous> (/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:297:18)
-------------------------------
[pty] plugin cleanup failed 292 | }, input.timeoutSeconds * 1_000)
293 | if (typeof session.timer === "object") session.timer.unref()
294 | }
295 | if (closing || deleted.has(context.sessionID)) {
296 | await terminate(session)
297 | throw new Error(closing ? "PTY plugin is ...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
The first regression run exposed two concrete bugs in the new backend: the command-PID pipe stayed open after its first line, blocking spawn admission, and cleanup checked the command PID as a process-group ID even though script owns the group. I’ve corrected the PID handshake; I’m correcting group cleanup before rerunning the suite.
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
600: const [wrapperPID, pid] = await Promise.all([spawned, target]).catch(async (error) => {
601: if (child.pid !== undefined) signalProcessGroup(child.pid, "SIGKILL")
602: await exited.catch(() => {})
603: throw error
604: })
605: let closing: Promise<void> | undefined
606: return {
607: pid,
608: write(data) {
609: return new Promise<void>((done, fail) => {
610: child.stdin.write(data, (error) => error ? fail(error) : done())
611: })
612: },
613: close() {
614: if (closing) return closing
615: closing = (async () => {
616: signalProcessGroup(wrapperPID, "SIGKILL")
617: signalProcessGroup(wrapperPID, "SIGTERM")
618: if (await waitForClose(exited, TERMINATE_TIMEOUT_MS) && !processGroupExists(wrapperPID)) return
619: signalProcessGroup(wrapperPID, "SIGKILL")
620: if (!await waitForClose(exited, TERMINATE_TIMEOUT_MS)) ...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import { open, realpath } from "node:fs/promises"
7: import type { FileHandle } from "node:fs/promises"
8: import { constants } from "node:fs"
9: import { Readable } from "node:stream"
10: import { StringDecoder } from "node:string_decoder"
11:
12: type PtyPluginOptions = {
13: openProcess?: typeof openPtyProcess
14: }
15:
16: const MAX_SESSIONS = 16
17: const MAX_SESSIONS_PER_OWNER = 5
18: const DEFAULT_READ_LIMIT = 500
19: const MAX_READ_LIMIT = 2_000
20: const MAX_READ_OFFSET = 10_000
21: const MAX_LINE_LENGTH = 2_000
22: const MAX_PATTERN_LENGTH = 500
23: const MAX_WRITE_SIZE = 65_536
24: const MAX_TIMEOUT_SECONDS = ...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
<path>/tmp/opencode</path> <type>directory</type> <entries> (0 entries) </entries>
bun test v1.3.14 (0d9b296a)
test/pty.test.ts:
34 | description: "argv boundaries",
35 | })
36 | const id = JSON.parse(spawned.output).session.id
37 | await waitForExit(harness, id)
38 | const read = JSON.parse((await harness.execute("pty_read", { id })).output)
39 | expect(read.lines.some((line: { text: string }) => line.text.replace(/\r$/, "") === JSON.stringify(values))).toBeTrue()
^
error: expect(received).toBeTrue()
Received: false
at <anonymous> (/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts:39:112)
(fail) pty > preserves argv without shell evaluation [63.58ms]
145 | async function waitFor(check: () => boolean | Promise<boolean>) {
146 | for (let attempt = 0; attempt < 100; attempt++) {
147 | if (await check()) return
148 | await Bun.sleep(10)
149 | }
150 | throw new Error("Condition did not become true.")
...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
bun test v1.3.14 (0d9b296a)
test/pty.test.ts:
39 | const output = read.lines.map((line: { text: string }) => line.text).join("\n")
40 | expect(output).toContain("space value")
41 | expect(output).toContain("quote'value")
42 | expect(output).toContain("$(printf injected)")
43 | expect(output).toContain("semi;colon")
44 | expect(output).toContain("é")
^
error: expect(received).toContain(expected)
Expected to contain: "é"
Received: "[\"space value\", \"quote'value\", \"$(printf injected)\", \"semi;colon\", \"\\u00e9\"]\r"
at <anonymous> (/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts:44:18)
(fail) pty > preserves argv without shell evaluation [53.91ms]
90 | args: ["-c", "while :; do sleep 1; done"],
91 | description: "deletion cleanup",
92 | })
93 | const session = JSON.parse(spawned.output).session
94 | harness.events.push({ type: "session.deleted", data: { sessionID: toolContext.sessionID } })
95 | ...
Found 38 matches
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/src/terminal.ts:
Line 50: // See: https://github.com/sursaone/bun-pty/issues/19
Line 105: join(here, "..", "bun-pty", "rust-pty", "target", "release"), // monorepo setups
Line 106: join(process.cwd(), "node_modules", "bun-pty", "rust-pty", "target", "release"),
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/src/index.ts:
Line 2: * The main export module for bun-pty.
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/README.md:
Line 1: # bun-pty
Line 3: [](https://www.npmjs.com/package/bun-pty)
Line 21: bun add bun-pty
Line 42: import { spawn } from "bun-pty";
Line 78: import { spawn } from "bun-pty";
Line 79: import type { IPty, IExitEvent, IPtyForkOptions } from "bun-pty";
Line 109: import { spawn } from "bun-pty";
Line 193: bun-pty ...
Success. Updated the following files: D ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty-transport.ts M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
bun test v1.3.14 (0d9b296a)
test/pty.test.ts:
143 | async function waitFor(check: () => boolean | Promise<boolean>) {
144 | for (let attempt = 0; attempt < 100; attempt++) {
145 | if (await check()) return
146 | await Bun.sleep(10)
147 | }
148 | throw new Error("Condition did not become true.")
^
error: Condition did not become true.
at waitFor (/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts:148:12)
at async <anonymous> (/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts:95:9)
(fail) pty > removes active sessions when their parent Session is deleted [2008.39ms]
4 pass
1 fail
12 expect() calls
Ran 5 tests across 1 file. [6.36s]
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
470: }
471:
472: export default createPtyPlugin()
473:
474: function result(text: string) {
475: return { output: text, content: text }
476: }
477:
478: function requireSession(sessions: Map<string, Session>, id: string, sessionID: string) {
479: const session = sessions.get(id)
480: if (!session || session.parentSessionID !== sessionID) throw new Error(`PTY session not found: ${id}`)
481: return session
482: }
483:
484: function lineCount(input: string) {
485: if (!input) return 0
486: let count = input.endsWith("\n") ? 0 : 1
487: for (let index = 0; index < input.length; index++) if (input.charCodeAt(index) === 10) count++
488: return count
489: }
490:
491: function lastNonblankLine(input: string) {
492: let end = input.length
493: while (end > 0) {
494: const start = input.lastIndexOf("\n", end - 1) + 1
495: const line = input.slice(start, ...
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.41s]
Found 24 matches
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/src/terminal.ts:
Line 50: // See: https://github.com/sursaone/bun-pty/issues/19
Line 105: join(here, "..", "bun-pty", "rust-pty", "target", "release"), // monorepo setups
Line 106: join(process.cwd(), "node_modules", "bun-pty", "rust-pty", "target", "release"),
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/src/index.ts:
Line 2: * The main export module for bun-pty.
/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/bun-pty/README.md:
Line 1: # bun-pty
Line 3: [](https://www.npmjs.com/package/bun-pty)
Line 21: bun add bun-pty
Line 42: import { spawn } from "bun-pty";
Line 78: import { spawn } from "bun-pty";
Line 79: import type { IPty, IExitEvent, IPtyForkOptions } from "bun-pty";
Line 109: import { spawn } from "bun-pty";
Line 193: bun-pty ...
No files found
Found 6 matches /home/byk/.local/share/opencode-v2-pilot/supervisor/opencode-pty-supervisor.service: Line 11: ExecStart=/usr/local/libexec/opencode-pty-supervisor /home/byk/.local/share/opencode-v2-pilot/supervisor/ROOT-ACCEPTANCE.md: Line 9: sudo install -o root -g root -m 0755 bin/opencode-pty-supervisor /usr/local/libexec/opencode-pty-supervisor Line 10: sudo install -o root -g root -m 0755 bin/opencode-pty-launcher /usr/local/libexec/opencode-pty-launcher Line 11: sudo install -o root -g root -m 0755 bin/opencode-pty-client /usr/local/libexec/opencode-pty-client Line 12: sudo install -o root -g root -m 0755 verify-readiness /usr/local/libexec/opencode-pty-verify-readiness Line 43: 16. Obtain fresh independent correctness and security reviews of the exact source and hashes. Run the reviewed `review-source-hash` from the unchanged source tree and require its result to equal the `pty-source:sha256` value embedded in `verify-readiness`. ...
<path>/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md</path> <type>file</type> <content> 1: # OpenCode V2 cutover 2: 3: The V2 executable serves the API and web UI from one origin. Do not add nginx. 4: 5: ## Preflight 6: 7: 1. Confirm production is still healthy on port 4096. 8: 2. Confirm the candidate revision and smoke build: 9: `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` 10: `/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node --version` 11: 3. Confirm the isolated candidate on port 14102: 12: `curl --fail --user opencode:$OPENCODE_PASSWORD http://127.0.0.1:14102/api/health` 13: `curl --fail http://127.0.0.1:14102/ >/dev/null` 14: 4. Confirm both local plugins are active after calling `POST /api/plugin/await-activation`. 15: 5. Create `/home/byk/.local/share/opencode-v2-pilot/server.env` with mode `0600` and one line: `OPENCODE_PASSWORD=<password>`. 16: 6. ...
Found 4 matches /home/byk/Code/opencode-v2-pilot/packages/cli/test/service.test.ts: Line 568: const info = await Schema.decodeUnknownPromise(Service.Info)(value) /home/byk/Code/opencode-v2-pilot/packages/cli/src/services/standalone.ts: Line 10: const decodeReady = Schema.decodeUnknownPromise(Schema.fromJsonString(Ready)) /home/byk/Code/opencode-v2-pilot/packages/cli/script/service-smoke.ts: Line 37: const info = await Schema.decodeUnknownPromise(Service.Info)(await Bun.file(registration).json()) Line 133: if (response?.ok) return Schema.decodeUnknownPromise(ServiceStatus.Health)(await response.json())
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
bun test v1.3.14 (0d9b296a) 6 pass 0 fail 21 expect() calls Ran 6 tests across 2 files. [7.53s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.38s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.34s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.39s]
<skill_content name="customize-opencode"> # Skill: customize-opencode <!-- Built-in skill. Name and description are registered in code at packages/core/src/plugin/skill.ts and CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION). The body below becomes the skill's content. --> # Customizing opencode opencode validates its own config strictly and refuses to start when a field is wrong. The shapes below cover the common surface area, but they are a **summary, not the source of truth**. ## Full schema reference The authoritative list of every config option — with field types, enums, defaults, and descriptions — lives in the published JSON Schema: **<https://opencode.ai/config.json>** If a field is not documented in this skill, or you need to confirm an exact shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. Independently, every `opencode.json` should ...
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/Config",
"$defs": {
"LogLevel": {
"type": "string",
"enum": [
"DEBUG",
"INFO",
"WARN",
"ERROR"
],
"description": "Log level"
},
"ServerConfig": {
"type": "object",
"properties": {
"port": {
"exclusiveMinimum": 0,
"type": "integer",
"maximum": 9007199254740991,
"description": "Port to listen on"
},
"hostname": {
"type": "string",
"description": "Hostname to listen on"
},
"mdns": {
"type": "boolean",
"description": "Enable mDNS service discovery"
},
"mdnsDomain": {
"type": "string",
"description": "Custom domain name for mDNS service (default: opencode.local)"
},
"cors": {
"type": "array",
"items": {
"type": "string"
...
bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.44s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [4.61s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [4.67s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.43s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.39s] bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [6.38s]
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://opencode.ai/config.json"
3: }
(End of file - total 3 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/opencode-v2.service</path> <type>file</type> <content> 1: [Unit] 2: Description=OpenCode V2 Server 3: After=network.target 4: 5: [Service] 6: Type=simple 7: EnvironmentFile=/home/byk/.opencode/env 8: EnvironmentFile=/home/byk/.local/share/opencode-v2-pilot/server.env 9: Environment=OPENCODE_CONFIG_DIR=/home/byk/.local/share/opencode-v2-pilot/config/opencode 10: Environment=OPENCODE_DB=/home/byk/.local/share/opencode-v2-pilot/data/opencode/opencode.db 11: Environment=XDG_DATA_HOME=/home/byk/.local/share/opencode-v2-pilot/data 12: Environment=XDG_CACHE_HOME=/home/byk/.local/share/opencode-v2-pilot/cache 13: Environment=XDG_CONFIG_HOME=/home/byk/.local/share/opencode-v2-pilot/config 14: Environment=XDG_STATE_HOME=/home/byk/.local/share/opencode-v2-pilot/state 15: User=byk 16: WorkingDirectory=/home/byk 17: ExecStart=/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node serve ...
The backend now passes repeated runtime tests. I’m doing a source-level security and lifecycle review of the exact candidate files next; the candidate remains inactive and production remains untouched.
Found 16 matches
/home/byk/Code/opencode-v2-pilot/packages/plugin-browser/src/connection.ts:
Line 133: Stream.filter((event) => event.type === "session.deleted" || event.type === "session.moved"),
/home/byk/Code/opencode-v2-pilot/packages/schema/test/event-manifest.test.ts:
Line 43: expect(EventManifest.Server.get("session.deleted")).toBe(SessionEvent.Deleted)
Line 113: "session.deleted.2",
/home/byk/Code/opencode-v2-pilot/packages/schema/test/legacy-event.test.ts:
Line 13: "session.deleted",
/home/byk/Code/opencode-v2-pilot/packages/schema/src/session-event.ts:
Line 158: type: "session.deleted",
/home/byk/Code/opencode-v2-pilot/packages/schema/src/v1/session.ts:
Line 589: type: "session.deleted",
/home/byk/Code/opencode-v2-pilot/packages/core/src/bus.ts:
Line 224: if (event.type === "session.deleted") updates.set(id, undefined)
Line 250: if (event.type === "session.deleted") updates.set(id, ...
<path>/home/byk/Code/opencode-v2-pilot/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
145: export type UsageRecorded = typeof UsageRecorded.Type
146:
147: export const UsageUpdated = Event.ephemeral({
148: type: "session.usage.updated",
149: schema: {
150: ...Base,
151: cost: Money.USD,
152: tokens: TokenUsage.Info,
153: },
154: })
155: export type UsageUpdated = typeof UsageUpdated.Type
156:
157: export const Deleted = Event.durable({
158: type: "session.deleted",
159: durable: {
160: aggregate: "sessionID",
161: version: 2,
162: },
163: schema: Base,
164: })
165: export type Deleted = typeof Deleted.Type
166:
167: export const Forked = Event.durable({
168: type: "session.forked",
169: durable: {
170: aggregate: "sessionID",
171: version: 2,
172: },
173: schema: {
174: ...Base,
175: parentID: SessionID,
176: boundary: SessionFork.Boundary,
177: instructions: ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/package.json</path>
<type>file</type>
<content>
1: {
2: "private": true,
3: "type": "module",
4: "dependencies": {
5: "effect": "file:/home/byk/Code/opencode-v2-pilot/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect"
6: },
7: "devDependencies": {
8: "@types/bun": "1.2.21"
9: }
10: }
(End of file - total 10 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: "allowImportingTsExtensions": true,
4: "lib": ["ESNext", "DOM", "DOM.Iterable"],
5: "module": "Preserve",
6: "moduleResolution": "Bundler",
7: "noEmit": true,
8: "paths": {
9: "@opencode/plugin": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/index.ts"],
10: "@opencode/plugin/*": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/*.ts"]
11: },
12: "skipLibCheck": true,
13: "strict": true,
14: "target": "ESNext",
15: "types": ["bun"]
16: },
17: "include": ["plugins/**/*.ts", "test/**/*.ts"]
18: }
(End of file - total 18 lines)
</content>
f2ac5193452ca7c471ec253f0c7d8759f2ac002d7015c023198603c856cf7dbf plugins/pty.ts 94a64d0171a5c6f438dea0e8bfaf9fd00cab0de8553610af08e106702a6d42f6 test/pty.test.ts 8d51037989aa2cbfa10de82b56ef9512218a07544a5308a74890d59252f43bad plugins/followup.ts 571a0730c86bde522edf63516b7fa69cb9cbf2ff8730e15e2a8ce064c33ad905 opencode.json cbe806483294fe4b7fe9c5fa98723ccbfe0baa8ab202a030b08c0cdf097af2e9 package.json 07196988cfa0e34e77927d4ec5e208a5e7ea66192b70af0dde27a9a049ee92b5 tsconfig.json byk:byk 644 regular file plugins/pty.ts byk:byk 644 regular file test/pty.test.ts byk:byk 644 regular file plugins/followup.ts byk:byk 644 regular file opencode.json byk:byk 644 regular file package.json byk:byk 644 regular file tsconfig.json
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import { open, realpath } from "node:fs/promises"
7: import type { FileHandle } from "node:fs/promises"
8: import { constants } from "node:fs"
9: import { Readable } from "node:stream"
10: import { StringDecoder } from "node:string_decoder"
11:
12: type PtyPluginOptions = {
13: openProcess?: typeof openPtyProcess
14: }
15:
16: const MAX_SESSIONS = 16
17: const MAX_SESSIONS_PER_OWNER = 5
18: const DEFAULT_READ_LIMIT = 500
19: const MAX_READ_LIMIT = 2_000
20: const MAX_READ_OFFSET = 10_000
21: const MAX_LINE_LENGTH = 2_000
22: const MAX_PATTERN_LENGTH = 500
23: const MAX_WRITE_SIZE = 65_536
24: const MAX_TIMEOUT_SECONDS = ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
451: events.abort()
452: const cleanup = await withTimeout(
453: Promise.allSettled([
454: ...pendingSpawns,
455: ...[...sessions.values()].map(remove),
456: ...pendingNotifications,
457: ]),
458: CLOSE_TIMEOUT_MS,
459: "PTY plugin cleanup timed out.",
460: ).catch((error) => {
461: console.error("[pty] plugin cleanup failed", error)
462: return []
463: })
464: cleanup.forEach((entry) => {
465: if (entry.status === "rejected") console.error("[pty] plugin cleanup failed", entry.reason)
466: })
467: }
468: },
469: })
470: }
471:
472: export default createPtyPlugin()
473:
474: function result(text: string) {
475: return { output: text, content: text }
476: }
477:
478: function requireSession(sessions: Map<string, Session>, id: string, sessionID: string) {
479: const session = sessions.get(id)
480: if ...
The exact-source review found one lifecycle gap in the direct backend: after SIGKILL, it waits for the wrapper to close but does not verify that both ordinary process groups disappeared. I’m adding the bounded post-kill check and an adversarial regression before the ten-run harness. Deliberate setsid() or daemonization remains outside this guarantee.
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, setDefaultTimeout, test } from "bun:test"
2: import { createPtyPlugin } from "../plugins/pty.ts"
3:
4: const cleanups: Array<() => Promise<void> | void> = []
5: setDefaultTimeout(15_000)
6:
7: afterEach(async () => {
8: await Promise.allSettled(cleanups.splice(0).map((cleanup) => cleanup()))
9: })
10:
11: describe("pty", () => {
12: test("runs an interactive command with a fixed environment and pinned cwd", async () => {
13: const harness = await setup()
14: const spawned = await harness.execute("pty_spawn", {
15: command: "/bin/sh",
16: args: ["-c", "printf '%s|%s|%s|%s|%s\\n' \"$HOME\" \"$LANG\" \"$PATH\" \"$TERM\" \"$PWD\"; read value; printf 'got:%s\\n' \"$value\""],
17: description: "interactive environment",
18: })
19: const id = JSON.parse(spawned.output).session.id
20: await ...
apply_patch verification failed: Error: Failed to find expected lines in /home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:
signalProcessGroup(pid, "SIGKILL")
signalProcessGroup(wrapperPID, "SIGKILL")
if (!await waitForClose(exited, TERMINATE_TIMEOUT_MS)) throw new Error(`PTY process ${pid} did not close.`)
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts M ../../.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts
Success. Updated the following files: M ../../.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts
bun test v1.3.14 (0d9b296a) 5 pass 0 fail 14 expect() calls Ran 5 tests across 1 file. [4.45s]
Found 1 matches /home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/adapter.ts: Line 471: host.tool.transform((editor) =>
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/adapter.ts</path>
<type>file</type>
<content>
440: Effect.catch((error) => Effect.die(new TypedFailure(error))),
441: ),
442: ),
443: list: adaptApiMethod(PermissionEndpoints["session.permission.list"], host.permission.list),
444: get: adaptApiMethod(PermissionEndpoints["session.permission.get"], host.permission.get),
445: reply: adaptApiMethod(PermissionEndpoints["session.permission.reply"], host.permission.reply),
446: },
447: plugin: {
448: list: adaptApiMethod(PluginEndpoints["plugin.list"], host.plugin.list),
449: },
450: reference: {
451: list: adaptApiMethod(ReferenceEndpoints["reference.list"], host.reference.list),
452: transform: transform(host.reference),
453: reload: () => run(host.reference.reload()),
454: },
455: ...
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/tool.ts</path>
<type>file</type>
<content>
1: export { CallID, Error } from "@opencode/schema/tool"
2: export type { Metadata, Options, Result } from "@opencode/schema/tool"
3:
4: import { Tool } from "@opencode/schema/tool"
5: import type { Agent } from "@opencode/schema/agent"
6: import type { Session } from "@opencode/schema/session"
7: import type { SessionMessage } from "@opencode/schema/session-message"
8: import type { Types } from "effect"
9: import type { Hooks, Transform } from "./registration.js"
10:
11: export interface ToolContext extends Omit<Tool.Context, "progress"> {
12: readonly progress: (update: Tool.Metadata) => Promise<void>
13: }
14:
15: export type Info<
16: Input extends Tool.ValueSchema<any> = Tool.ValueSchema<any>,
17: Output extends Tool.ValueSchema<any> | undefined = Tool.ValueSchema<any> | undefined,
18: > = Omit<Tool.Info<Input, Output>, "execute"> & {
19: readonly execute: ...
<path>/home/byk/.local/share/opencode-v2-pilot/supervisor/ROOT-ACCEPTANCE.md</path> <type>file</type> <content> 1: # Root-only acceptance 2: 3: Do not install, start, enable, or mark the candidate ready until every check below passes on the exact target host. Source tests never substitute for these checks. 4: 5: 1. Rebuild all three binaries twice from the reviewed source with `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags=-buildid=`. Require both build sets to match. Record the content-bound source revision, Go version, dependency sums, complete build commands, and SHA-256 hashes. ...
Your previous result was empty. Resume and complete the exact implementation request unchanged. You must either make and verify all approved edits or return the exact file/tool/error blocking you. Work only in /home/byk/.local/share/opencode-v2-pilot/config/opencode, /home/byk/.local/share/opencode-v2-pilot/opencode-v2.service, /home/byk/.local/share/opencode-v2-pilot/CUTOVER.md, and /tmp/opencode/pty-plugin-smoke.ts; never edit supervisor sources, repository tracked source, production config, or installed units. Use apply_patch. Restore the fully hardened direct Node child_process + /usr/bin/script PTY implementation under byk, remove pty-transport.ts, remove every candidate supervisor/helper/readiness dependency, update exact tests/docs, run canonical and dedicated tsgo, run the adversarial smoke 10 times, and run unprivileged systemd-analyze verify. Preserve all requirements from the previous prompt, including permissions on exact writes, ownership, atomic reservations, fixed env, cwd fd binding, bounded lifecycle/resources, JSON envelopes, and explicit escaped-descendant limitation. Return exact edits and command results. Do not install/start/stop/restart services or run the SEA build.
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect</path> <type>directory</type> <entries> AGENTS.md ai-docs/ CLAUDE.md dist/ LICENSE package.json README.md src/ (8 entries) </entries>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect/dist</path> <type>directory</type> <entries> Array.d.ts Array.d.ts.map Array.js Array.js.map BigDecimal.d.ts BigDecimal.d.ts.map BigDecimal.js BigDecimal.js.map BigInt.d.ts BigInt.d.ts.map BigInt.js BigInt.js.map Boolean.d.ts Boolean.d.ts.map Boolean.js Boolean.js.map Brand.d.ts Brand.d.ts.map Brand.js Brand.js.map Cache.d.ts Cache.d.ts.map Cache.js Cache.js.map Cause.d.ts Cause.d.ts.map Cause.js Cause.js.map Channel.d.ts Channel.d.ts.map Channel.js Channel.js.map ChannelSchema.d.ts ChannelSchema.d.ts.map ChannelSchema.js ChannelSchema.js.map Chunk.d.ts Chunk.d.ts.map Chunk.js Chunk.js.map Clock.d.ts Clock.d.ts.map Clock.js Clock.js.map Combiner.d.ts Combiner.d.ts.map Combiner.js Combiner.js.map Config.d.ts Config.d.ts.map Config.js Config.js.map ConfigProvider.d.ts ConfigProvider.d.ts.map ConfigProvider.js ConfigProvider.js.map Console.d.ts Console.d.ts.map Console.js Console.js.map Context.d.ts Context...
No files found
<skill_content name="customize-opencode"> # Skill: customize-opencode <!-- Built-in skill. Name and description are registered in code at packages/core/src/plugin/skill.ts and CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION). The body below becomes the skill's content. --> # Customizing opencode opencode validates its own config strictly and refuses to start when a field is wrong. The shapes below cover the common surface area, but they are a **summary, not the source of truth**. ## Full schema reference The authoritative list of every config option — with field types, enums, defaults, and descriptions — lives in the published JSON Schema: **<https://opencode.ai/config.json>** If a field is not documented in this skill, or you need to confirm an exact shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. Independently, every `opencode.json` should ...
I’m resuming from the current candidate files. I’ll verify the direct backend and tests first, then add the external adversarial harness, update any remaining candidate-only files, and run every requested check without touching services or production.
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts</path>
<type>file</type>
<content>
1: import { Plugin } from "@opencode/plugin"
2: import type { Context } from "@opencode/plugin/plugin"
3: import type { ToolContext } from "@opencode/plugin/tool"
4: import { Schema } from "effect"
5: import { spawn } from "node:child_process"
6: import { open, realpath } from "node:fs/promises"
7: import type { FileHandle } from "node:fs/promises"
8: import { constants } from "node:fs"
9: import { Readable } from "node:stream"
10: import { StringDecoder } from "node:string_decoder"
11:
12: type PtyPluginOptions = {
13: openProcess?: typeof openPtyProcess
14: }
15:
16: const MAX_SESSIONS = 16
17: const MAX_SESSIONS_PER_OWNER = 5
18: const DEFAULT_READ_LIMIT = 500
19: const MAX_READ_LIMIT = 2_000
20: const MAX_READ_OFFSET = 10_000
21: const MAX_LINE_LENGTH = 2_000
22: const MAX_PATTERN_LENGTH = 500
23: const MAX_WRITE_SIZE = 65_536
24: const MAX_TIMEOUT_SECONDS = ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, setDefaultTimeout, test } from "bun:test"
2: import { createPtyPlugin } from "../plugins/pty.ts"
3:
4: const cleanups: Array<() => Promise<void> | void> = []
5: setDefaultTimeout(15_000)
6:
7: afterEach(async () => {
8: await Promise.allSettled(cleanups.splice(0).map((cleanup) => cleanup()))
9: })
10:
11: describe("pty", () => {
12: test("runs an interactive command with a fixed environment and pinned cwd", async () => {
13: const harness = await setup()
14: const spawned = await harness.execute("pty_spawn", {
15: command: "/bin/sh",
16: args: ["-c", "printf '%s|%s|%s|%s|%s\\n' \"$HOME\" \"$LANG\" \"$PATH\" \"$TERM\" \"$PWD\"; read value; printf 'got:%s\\n' \"$value\""],
17: description: "interactive environment",
18: })
19: const id = JSON.parse(spawned.output).session.id
20: await ...
<path>/home/byk/.local/share/opencode-v2-pilot/opencode-v2.service</path> <type>file</type> <content> 1: [Unit] 2: Description=OpenCode V2 Server 3: After=network.target 4: 5: [Service] 6: Type=simple 7: EnvironmentFile=/home/byk/.opencode/env 8: EnvironmentFile=/home/byk/.local/share/opencode-v2-pilot/server.env 9: Environment=OPENCODE_CONFIG_DIR=/home/byk/.local/share/opencode-v2-pilot/config/opencode 10: Environment=OPENCODE_DB=/home/byk/.local/share/opencode-v2-pilot/data/opencode/opencode.db 11: Environment=XDG_DATA_HOME=/home/byk/.local/share/opencode-v2-pilot/data 12: Environment=XDG_CACHE_HOME=/home/byk/.local/share/opencode-v2-pilot/cache 13: Environment=XDG_CONFIG_HOME=/home/byk/.local/share/opencode-v2-pilot/config 14: Environment=XDG_STATE_HOME=/home/byk/.local/share/opencode-v2-pilot/state 15: User=byk 16: WorkingDirectory=/home/byk 17: ExecStart=/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node serve ...
<path>/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md</path> <type>file</type> <content> 1: # OpenCode V2 cutover 2: 3: The V2 executable serves the API and web UI from one origin. Do not add nginx. 4: 5: ## Preflight 6: 7: 1. Confirm production is still healthy on port 4096. 8: 2. Confirm the candidate revision and smoke build: 9: `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` 10: `/home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node --version` 11: 3. Confirm the isolated candidate on port 14102: 12: `curl --fail --user opencode:$OPENCODE_PASSWORD http://127.0.0.1:14102/api/health` 13: `curl --fail http://127.0.0.1:14102/ >/dev/null` 14: 4. Confirm both local plugins are active after calling `POST /api/plugin/await-activation`. 15: 5. Create `/home/byk/.local/share/opencode-v2-pilot/server.env` with mode `0600` and one line: `OPENCODE_PASSWORD=<password>`. 16: 6. ...
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: "allowImportingTsExtensions": true,
4: "lib": ["ESNext", "DOM", "DOM.Iterable"],
5: "module": "Preserve",
6: "moduleResolution": "Bundler",
7: "noEmit": true,
8: "paths": {
9: "@opencode/plugin": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/index.ts"],
10: "@opencode/plugin/*": ["/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/*.ts"]
11: },
12: "skipLibCheck": true,
13: "strict": true,
14: "target": "ESNext",
15: "types": ["bun"]
16: },
17: "include": ["plugins/**/*.ts", "test/**/*.ts"]
18: }
(End of file - total 18 lines)
</content>
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://opencode.ai/config.json"
3: }
(End of file - total 3 lines)
</content>
No files found
Found 64 matches /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/immer/src/plugins/patches.ts: Line 115: // NEW: Add resolvePath helper function /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/pure-rand/README.md: Line 141: ### Extra helpers Line 143: Some helpers are also provided in order to ease the use of `RandomGenerator` instances: Line 148: And one last helper responsible to change any function accepting an instance of `RandomGenerator` as a first argument into a pure version of it: Line 152: Each of these helpers come with its own import: `pure-rand/utils/<name>`. /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/ast-types/README.md: Line 84: Two more low-level helper functions, `eachField` and `someField`, are Line 287: // Yes, you can define arbitrary helper methods. Line 294: // And even helper helper ...
No files found
No files found
No files found
No files found
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect/dist/Schema.d.ts</path> <type>file</type> <content> 1: /** 2: * Describes data shapes and how unknown input becomes trusted values. 3: * 4: * A schema can validate input, decode it into an application type, and encode 5: * that value back to another representation. This module contains the main 6: * schema, codec, decoder, and encoder APIs, together with schemas for common 7: * JavaScript values and Effect data types. It also supports refinements, 8: * transformations, defaults, classes, JSON Schema generation, test data 9: * generation, formatting, equivalence, optics, and differs derived from schema 10: * definitions. 11: * 12: * @since 4.0.0 13: */ 14: import * as BigDecimal_ from "./BigDecimal.ts"; 15: import type * as Brand from "./Brand.ts"; 16: import * as Cause_ from "./Cause.ts"; 17: import * as Chunk_ from "./Chunk.ts"; 18: import * as DateTime from "./DateTime.ts"; 19: import type ...
No files found
No files found
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/plugin.ts</path>
<type>file</type>
<content>
1: import type { OpenCodeClient } from "@opencode/client"
2: import type { GenerateApi, PluginApi } from "@opencode/client/promise/api"
3: import type { Location } from "@opencode/schema/location"
4: import type { PluginOptions } from "../options.js"
5: import type { App } from "../app.js"
6: import type { AgentDomain } from "./agent.js"
7: import type { AISDKDomain } from "./aisdk.js"
8: import type { CatalogDomain } from "./catalog.js"
9: import type { CommandDomain } from "./command.js"
10: import type { EventDomain } from "./event.js"
11: import type { IntegrationDomain } from "./integration.js"
12: import type { MCPDomain } from "./mcp.js"
13: import type { PermissionDomain } from "./permission.js"
14: import type { ReferenceDomain } from "./reference.js"
15: import type { RpcDomain } from "./rpc.js"
16: import type { SessionDomain } from "./session.js"
17: import type ...
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect/dist/SchemaParser.d.ts</path> <type>file</type> <content> 1: import * as Effect from "./Effect.ts"; 2: import * as Exit from "./Exit.ts"; 3: import * as Option from "./Option.ts"; 4: import * as Result from "./Result.ts"; 5: import type * as Schema from "./Schema.ts"; 6: import * as SchemaAST from "./SchemaAST.ts"; 7: import * as SchemaIssue from "./SchemaIssue.ts"; 8: /** 9: * Creates an effectful maker for the schema's decoded type side. 10: * 11: * **When to use** 12: * 13: * Use to construct decoded schema values in `Effect` while preserving 14: * construction failures as `SchemaIssue.Issue` values in the error channel. 15: * 16: * **Details** 17: * 18: * The returned function accepts constructor input, applies constructor defaults, 19: * runs type-side validation unless checks are disabled, and fails with a 20: * `SchemaIssue.Issue` when construction fails. ...
No files found
No files found
<path>/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "effect",
3: "type": "module",
4: "version": "4.0.0-rc.112",
5: "license": "MIT",
6: "description": "The missing standard library for TypeScript, for writing production-grade software.",
7: "homepage": "https://effect.website",
8: "repository": {
9: "type": "git",
10: "url": "https://github.com/Effect-TS/effect.git",
11: "directory": "packages/effect"
12: },
13: "bugs": {
14: "url": "https://github.com/Effect-TS/effect/issues"
15: },
16: "tags": [
17: "typescript",
18: "error-handling",
19: "concurrency",
20: "observability"
21: ],
22: "keywords": [
23: "typescript",
24: "error-handling",
25: "concurrency",
26: "observability"
27: ],
28: "sideEffects": [],
29: "exports": {
30: "./package.json": "./package.json",
31: ".": "./dist/index.js",
32: ...
Found 1 matches /home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/effect/dist/index.d.ts: Line 436: export * as SchemaParser from "./SchemaParser.ts";
<path>/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/tool.ts</path>
<type>file</type>
<content>
1: export { CallID, Error } from "@opencode/schema/tool"
2: export type { Metadata, Options, Result } from "@opencode/schema/tool"
3:
4: import { Tool } from "@opencode/schema/tool"
5: import type { Agent } from "@opencode/schema/agent"
6: import type { Session } from "@opencode/schema/session"
7: import type { SessionMessage } from "@opencode/schema/session-message"
8: import type { Types } from "effect"
9: import type { Hooks, Transform } from "./registration.js"
10:
11: export interface ToolContext extends Omit<Tool.Context, "progress"> {
12: readonly progress: (update: Tool.Metadata) => Promise<void>
13: }
14:
15: export type Info<
16: Input extends Tool.ValueSchema<any> = Tool.ValueSchema<any>,
17: Output extends Tool.ValueSchema<any> | undefined = Tool.ValueSchema<any> | undefined,
18: > = Omit<Tool.Info<Input, Output>, "execute"> & {
19: readonly execute: ...
No files found
No files found
Found 69 matches /home/byk/Code/opencode-v2-pilot/services/updates/package.json: Line 12: "typecheck": "tsgo --noEmit" Line 24: "@typescript/native-preview": "catalog:", /home/byk/Code/opencode-v2-pilot/services/files/package.json: Line 10: "typecheck": "tsgo --noEmit" Line 15: "@typescript/native-preview": "catalog:", /home/byk/Code/opencode-v2-pilot/packages/sdk/package.json: Line 27: "typecheck": "tsgo -b", Line 45: "@typescript/native-preview": "catalog:" /home/byk/Code/opencode-v2-pilot/packages/plugin-browser/package.json: Line 26: "typecheck": "tsgo --noEmit -p tsconfig.test.json", Line 37: "@typescript/native-preview": "catalog:", /home/byk/Code/opencode-v2-pilot/packages/latex/package.json: Line 13: "typecheck": "tsgo --noEmit" Line 23: "@typescript/native-preview": "catalog:" /home/byk/Code/opencode-v2-pilot/package.json: Line 93: "@typescript/native-preview": "7.0.0-dev.20251207.1", Line ...
* 🔴 (11:36) User stated the previous implementation result was empty and required resuming the exact implementation request unchanged. * 🔴 [enforced-workflow] (11:36) User required either making and verifying every approved edit or returning the exact blocking file, tool, or error. * 🔴 (11:36) User restricted all work to `/home/byk/.local/share/opencode-v2-pilot/config/opencode`, `/home/byk/.l…
* 🟡 (11:26) `/home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/tool.ts` is exactly `69` lines and re-exports `CallID` and `Error`, plus types `Metadata`, `Options`, and `Result`, from `@opencode/schema/tool`. * 🟡 (11:26) `ToolContext` extends `Omit<Tool.Context, "progress">` and replaces `progress` with `(update: Tool.Metadata) => Promise<void>`; generic `Info<Input, Output>` replace…
* 🟡 (11:16) Candidate-config integrity snapshot recorded SHA-256 hashes: `plugins/pty.ts`=`f2ac5193452ca7c471ec253f0c7d8759f2ac002d7015c023198603c856cf7dbf`; `test/pty.test.ts`=`94a64d0171a5c6f438dea0e8bfaf9fd00cab0de8553610af08e106702a6d42f6`; `plugins/followup.ts`=`8d51037989aa2cbfa10de82b56ef9512218a07544a5308a74890d59252f43bad`; `opencode.json`=`571a0730c86bde522edf63516b7fa69cb9cbf2ff8730e1…
* 🟡 (11:07) Six additional `bun test v1.3.14 (0d9b296a)` runs each completed with exactly `5 pass`, `0 fail`, and `14 expect() calls` across `5 tests` in `1 file`; durations in order were `6.44s`, `4.61s`, `4.67s`, `6.43s`, `6.39s`, and `6.38s`. * 🟡 (11:08) `/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json` contains only `"$schema": "https://opencode.ai/config.json"` in a …
* 🔴 (11:05) User requires OpenCode V2 to serve the API and web UI from one origin; nginx must not be added. * 🔴 (11:05) User’s ordered V2 preflight procedure in `/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md` is: 1. confirm production remains healthy on port `4096`; 2. confirm candidate revision with `git -C /home/byk/Code/opencode-v2-pilot rev-parse HEAD` and smoke-build version with `/h…
* 🟡 (10:53) A further patch modified `/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts`. * 🟡 (10:53) `/tmp/opencode` was inspected and contained exactly `0 entries`. * 🟡 (10:54) After the PTY backend patch, `bun test v1.3.14 (0d9b296a)` on `test/pty.test.ts` improved to exactly `3 pass`, `2 fail`, and `8 expect() calls` across `5 tests` in `4.41s`. * 🟡 (10:54) Remaining…
* 🟡 (10:50) `bun test v1.3.14 (0d9b296a)` against `/home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts` reported exactly `1 pass`, `4 fail`, `3 errors`, and `3 expect() calls` across `5 tests` in `21.32s`; Bun also reported `killed 1 dangling process` multiple times. * 🟡 (10:50) PTY test failure 1: `pty > runs an interactive command with a fixed environment and pinned cwd`…
* 🔴 (10:37) User stated opencode `model` values always carry a provider prefix, e.g. `"anthropic/claude-sonnet-4-6"`. * 🔴 (10:37) User stated `mcp[name].command` is an array of strings, never a single string, and `type` is required. * 🔴 (10:37) User stated `skills` is an object with `paths` and/or `urls`, not an array; `references`, `agent`, and `command` are objects keyed by alias/name; `plug…
Date: Sep 8, 2026 * 🟡 (10:33) `/home/byk/.local/share/opencode-v2-pilot/supervisor/cmd/launcher/main.go` defines `encodedArgumentsEnvironment = "OPENCODE_PTY_ARGV"` and `launcherFD = 3`. `run()` accepts the special `--exec` path, otherwise validates encoded arguments, calls `syscall.Fchdir(2)`, replaces fd `2` via `syscall.Dup2(1, 2)`, installs the fixed environment, stores dot-joined arguments …
* 🟡 (10:13) `/home/byk/.local/share/opencode-v2-pilot/config/opencode/opencode.json` contains only `"$schema": "https://opencode.ai/config.json"` in a 3-line JSON object. * 🔴 (10:14) User’s `/home/byk/.local/share/opencode-v2-pilot/CUTOVER.md` states that the V2 executable serves the API and web UI from one origin and directs: “Do not add nginx.” * 🔴 (10:14) User’s V2 preflight sequence is: 1.…
* 🟡 (10:09) Search for Node child-process spawning found 45 matches in `/home/byk/.local/share/opencode-v2-pilot/config/opencode/node_modules/@types/node/child_process.d.ts`, including `spawn()` overloads at lines 697–777 and `spawnSync()` overloads at lines 1242–1257. * 🟡 (10:10) A command produced output `mi` and exited with code `0`. * 🟡 (10:10) Partial inspection of `/home/byk/.local/share…
* 🟡 (09:58) Direct-backend process-topology experiment spawned `/usr/bin/script -qefc "/bin/sh -c 'read x'" /dev/null` with `{ detached: true, stdio: ["pipe", "pipe", "pipe"] }`. The `script` wrapper had PID/PGID/SID `3105140`, while its `/bin/sh -c read x` child had PID/PGID/SID `3105141`, demonstrating that the command launched by util-linux `script(1)` enters a separate process group and sess…
Date: Sep 8, 2026 * 🔴 (09:42) User approved abandoning the root supervisor architecture for the OpenCode V2 pilot and matching the existing production trust model: OpenCode and model-controlled PTYs run as `byk` under the existing systemd service with `NoNewPrivileges=yes` and the account’s normal supplementary groups. * 🔴 (09:42) User stated the approved direct architecture requires no root se…
Date: Sep 8, 2026 * 🟡 (09:43) Inspected `/home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty-transport.ts` (319 lines), which implements a Unix-socket PTY supervisor transport using `spawn`, `createConnection`, a privileged helper, framed binary messages, buffered output, structured exits, and stop handling. * 🟡 (09:43) `pty-transport.ts` protocol constants are `MAGIC = Buffer.…
Date: Sep 8, 2026 * 🔴 (09:42) User stated opencode configuration is strictly validated and invalid fields cause startup failure; the authoritative schema is `https://opencode.ai/config.json`, and every `opencode.json` should declare `"$schema": "https://opencode.ai/config.json"`. * 🔴 (09:42) User stated opencode config is loaded once at startup and is not hot-reloaded; after changing `opencode.…