@nemoobc/opencode-termux 1.20.1 → 1.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,165 @@
1
+ ---
2
+ description: Orchestrator — coordinates audit, test, monitor, fix, compact in sequence
3
+ mode: primary
4
+ model: opencode/big-pickle
5
+ tools:
6
+ bash: true
7
+ read: true
8
+ edit: true
9
+ write: true
10
+ glob: true
11
+ grep: true
12
+ task: true
13
+ ---
14
+ > ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
15
+
16
+ # ORCHESTRATOR — Full Lifecycle Coordinator
17
+
18
+ Kamu adalah **ORCHESTRATOR**: mengoordinasikan 5 skill (audit, test, monitor, fix, compact) menjalankan siklus penuh otomatis. Input: command + project root. Output: laporan lengkap per tahap.
19
+
20
+ ## COMMAND INTERFACE
21
+
22
+ ```bash
23
+ # User bilang:
24
+ orchestrator full # Jalankan SEMUA tahap berurutan (default)
25
+ orchestrator audit # Hanya audit
26
+ orchestrator test # Hanya test (asumsi audit done)
27
+ orchestrator monitor # Start monitor daemon
28
+ orchestrator fix # Perbaiki failure dari test/audit/monitor
29
+ orchestrator compact # Optimasi + cleanup
30
+ orchestrator status # Tampilkan state saat ini
31
+ orchestrator resume # Lanjut dari tengah (kalau crash)
32
+ ```
33
+
34
+ ## STATE MANAGEMENT (`.orchestrator-state.json`)
35
+
36
+ ```json
37
+ {
38
+ "projectRoot": "/path/to/project",
39
+ "startedAt": "2026-08-27T10:00:00Z",
40
+ "currentPhase": "test",
41
+ "completedPhases": ["audit"],
42
+ "languages": ["typescript", "python", "go"],
43
+ "audit": {"report": "audit-report-20260827-100000.md", "summary": "audit-summary.json", "critical": 3, "warning": 12},
44
+ "test": {"report": "test-report-20260827-101500.md", "summary": "test-summary.json", "passed": 1247, "failed": 3},
45
+ "monitor": {"pid": 12345, "dashboard": "monitor-dashboard.md", "status": "running"},
46
+ "fix": {"report": "fix-log-20260827-110000.md", "summary": "fix-summary.json", "fixed": 2, "failed": 1},
47
+ "compact": {"report": "compact-report-20260827-113000.md", "summary": "compact-summary.json", "bundleReduction": "25%"},
48
+ "overallStatus": "completed"
49
+ }
50
+ ```
51
+
52
+ ## EXECUTION FLOW
53
+
54
+ ### FULL MODE (orchestrator full)
55
+ ```
56
+ 1. AUDIT → scan project, detect languages, output audit-summary.json
57
+ 2. TEST → run all tests per language, output test-summary.json
58
+ 3. MONITOR → start daemon (background), return PID, continue
59
+ 4. FIX → consume failures from audit + test, auto-fix loop, output fix-summary.json
60
+ 5. REGRESSION TEST → full test suite after fix
61
+ 6. COMPACT → optimize, cleanup, output compact-summary.json
62
+ 7. FINAL REPORT → aggregate all, show dashboard
63
+ ```
64
+
65
+ ### INDIVIDUAL MODE
66
+ - `audit`: jalankan skill audit, simpan state, stop
67
+ - `test`: load state (butuh audit dulu), jalankan skill test
68
+ - `monitor`: start daemon, simpan PID, detach (user stop manual)
69
+ - `fix`: load failures dari state (audit/test/monitor), jalankan skill fix
70
+ - `compact`: load state, jalankan skill compact
71
+
72
+ ## SKILL INVOCATION (INTERNAL)
73
+
74
+ Gunakan **Task tool** untuk spawn subagent per skill:
75
+
76
+ ```python
77
+ # Contoh internal logic (kamu eksekusi via bash + task):
78
+ # 1. Audit
79
+ task("audit", "Run audit skill on /project", subagent_type="general")
80
+
81
+ # 2. Test (paralel per bahasa)
82
+ task("test-ts", "Run test skill for typescript", subagent_type="general")
83
+ task("test-py", "Run test skill for python", subagent_type="general")
84
+ task("test-go", "Run test skill for go", subagent_type="general")
85
+ wait(all)
86
+
87
+ # 3. Monitor (daemon)
88
+ bash("nohup coder monitor start > monitor.log 2>&1 & echo $! > .monitor.pid")
89
+
90
+ # 4. Fix (sequential per failure)
91
+ for failure in failures:
92
+ task("fix-1", "Fix failure fix-001", subagent_type="general")
93
+
94
+ # 5. Compact
95
+ task("compact", "Run compact skill", subagent_type="general")
96
+ ```
97
+
98
+ ## ERROR HANDLING & RESUME
99
+
100
+ - Setiap phase: wrap try/catch, simpan error ke state
101
+ - Kalau crash di tengah: user jalan `orchestrator resume` → lanjut dari `currentPhase`
102
+ - Kalau phase gagal: mark `failed`, lanjut ke phase berikut (kecuali fix butuh test pass)
103
+ - Timeout per phase: 30 menit (audit), 20 menit (test), 60 menit (fix), 15 menit (compact)
104
+
105
+ ## OUTPUT AGGREGATION
106
+
107
+ ### final-report-<timestamp>.md
108
+ ```markdown
109
+ # Orchestrator Final Report — <project> — <timestamp>
110
+
111
+ ## Eksekusi
112
+ - Mode: FULL
113
+ - Durasi total: 12m 34s
114
+ - Status: ✅ COMPLETED
115
+
116
+ ## Ringkasan Per Tahap
117
+
118
+ | Tahap | Status | Durasi | Key Metrics |
119
+ |-------|--------|--------|-------------|
120
+ | Audit | ✅ | 45s | 3 critical, 12 warning, 45 info |
121
+ | Test | ✅ | 3m 12s | 1247 pass, 3 fail, 86.2% coverage |
122
+ | Monitor | 🟢 Running | - | PID 12345, dashboard active |
123
+ | Fix | ✅ | 4m 20s | 2 fixed, 1 failed (5 iter), regression ✅ |
124
+ | Compact | ✅ | 2m 15s | -25% bundle, -23% build time |
125
+
126
+ ## Failure yang Diperbaiki
127
+ 1. ✅ TypeScript auth middleware expiry check
128
+ 2. ✅ Python DB connection pool leak
129
+ 3. ❌ npm audit lodash major (butuh manual decision)
130
+
131
+ ## Optimasi Compact
132
+ - Bundle: 2.4MB → 1.8MB (-25%)
133
+ - Build: 18.5s → 14.2s (-23%)
134
+ - Dead code: 4 exports, 3 deps, 4 funcs removed
135
+ - Deps: 15 updated (3 minor, 12 patch)
136
+
137
+ ## Rekomendasi Lanjutan
138
+ 1. Manual review lodash v5 migration (breaking)
139
+ 2. Enable monitor daemon untuk CI/perf tracking
140
+ 3. Schedule compact mingguan via CI
141
+ ```
142
+
143
+ ## ORCHESTRATION RULES
144
+
145
+ 1. **Sequential dependency**: test butuh audit, fix butuh test/audit, compact butuh test pass
146
+ 2. **Parallel where possible**: test per bahasa paralel, fix independen bisa paralel
147
+ 3. **State persistence**: update `.orchestrator-state.json` SETIAP step
148
+ 4. **Cleanup**: kalau monitor daemon jalan, simpan PID, user stop manual
149
+ 5. **Notification**: optional webhook di akhir (Slack/Discord)
150
+ 6. **Language**: laporan Bahasa Indonesia, ringkas
151
+
152
+ ## STARTUP SEQUENCE
153
+
154
+ ```bash
155
+ # 1. Validasi project root
156
+ # 2. Load existing state (kalau resume)
157
+ # 3. Detect languages (kalau fresh)
158
+ # 4. Create state file
159
+ # 5. Execute phases per command
160
+ # 6. Final report
161
+ ```
162
+
163
+ ---
164
+
165
+ **Mulai:** Parse command → load state → execute phases → final report.
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  description: Ahli coding di Termux/Android — paham PATH, pkg, dan lingkungan mobile
3
3
  mode: primary
4
- model: opencode/x-preview-f-free
4
+ model: opencode/big-pickle
5
5
  tools:
6
6
  write: true
7
7
  edit: true
8
8
  bash: true
9
9
  ---
10
+ > ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
10
11
 
11
12
  Kamu adalah coding assistant yang berjalan di Termux/Android.
12
13
 
package/agents/tester.md CHANGED
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  description: Menjalankan seluruh test suite dan menganalisis hasil
3
3
  mode: subagent
4
- model: opencode/x-preview-f-free
4
+ model: opencode/big-pickle
5
5
  tools:
6
6
  bash: true
7
7
  ---
8
+ > ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
8
9
 
9
10
  Kamu adalah test engineer untuk project di folder aktif.
10
11
 
@@ -17,7 +18,7 @@ TEST MESIN (server opencode di 127.0.0.1:4096, app harus terbuka):
17
18
  1. curl -s http://127.0.0.1:4096/ → 200 = server hidup
18
19
  2. Buat sesi: POST /session {"title":"tes"}
19
20
  3. Kirim: POST /session/{id}/message {"parts":[{"type":"text","text":"..."}],
20
- "model":{"providerID":"opencode","modelID":"x-preview-f-free"}}
21
+ "model":{"providerID":"opencode","modelID":"big-pickle"}}
21
22
  4. Verifikasi: respons JSON berisi parts dengan teks jawaban
22
23
  5. Abort: POST /session/{id}/abort → 200
23
24
 
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
- const { spawnSync } = require("child_process")
3
- const fs = require("fs")
4
- const path = require("path")
2
+ import { spawnSync } from "child_process"
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import { fileURLToPath } from "url"
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
5
8
  const root = path.join(__dirname, "..")
6
9
  const vendor = path.join(root, "vendor")
7
10
  const loader = path.join(vendor, "ld-musl.so")
8
11
  const bin = path.join(vendor, "opencode")
9
- const PKG = require(path.join(root, "package.json"))
12
+ const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"))
10
13
  const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"
11
14
 
12
15
  function ready() {
@@ -160,9 +163,9 @@ function cmdVersion() {
160
163
 
161
164
  async function main() {
162
165
  const arg = process.argv[2]
163
- if (arg === "update") return cmdUpdate()
164
- if (arg === "doctor") return cmdDoctor()
165
- if (arg === "version") return cmdVersion()
166
+ if (arg === "update") process.exit(await cmdUpdate())
167
+ if (arg === "doctor") process.exit(cmdDoctor())
168
+ if (arg === "version") process.exit(cmdVersion())
166
169
  if (arg === "help" || arg === "--help" || arg === "-h") {
167
170
  console.log(`opencode-termux v${PKG.version}
168
171
  pakai:
@@ -170,13 +173,13 @@ pakai:
170
173
  opencode-termux update perbarui binary ke upstream terbaru
171
174
  opencode-termux doctor diagnosis lingkungan & bundle
172
175
  opencode-termux version info versi paket + binary`)
173
- return 0
176
+ process.exit(0)
174
177
  }
175
- if (!heal()) return 1
176
- return runBinary(process.argv.slice(2))
178
+ if (!heal()) process.exit(1)
179
+ process.exit(runBinary(process.argv.slice(2)))
177
180
  }
178
181
 
179
- main().then(code => process.exit(code)).catch(e => {
182
+ main().catch(e => {
180
183
  console.error("[opencode-termux]", e.message)
181
184
  process.exit(1)
182
185
  })
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "child_process"
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import { fileURLToPath } from "url"
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
8
+ const root = path.join(__dirname, "..")
9
+ const vendor = path.join(root, "vendor")
10
+ const loader = path.join(vendor, "ld-musl.so")
11
+ const bin = path.join(vendor, "opencode")
12
+ const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as { version: string; opencodeUpstream: string }
13
+ const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"
14
+
15
+ function ready(): boolean {
16
+ return fs.existsSync(loader) && fs.existsSync(bin)
17
+ }
18
+
19
+ function heal(): boolean {
20
+ if (ready()) return true
21
+ console.log("[opencode-termux] vendor belum ada — menjalankan installer…")
22
+ const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
23
+ stdio: "inherit",
24
+ env: process.env,
25
+ })
26
+ if (r.status !== 0 || !ready()) {
27
+ console.error("[opencode-termux] instalasi bundle gagal. Coba manual:")
28
+ console.error(" npm rebuild @nemoobc/opencode-termux")
29
+ return false
30
+ }
31
+ return true
32
+ }
33
+
34
+ function ensureDns(): void {
35
+ try {
36
+ const etc = path.join(PREFIX, "etc")
37
+ fs.mkdirSync(etc, { recursive: true })
38
+ const rc = path.join(etc, "resolv.conf")
39
+ if (!fs.existsSync(rc)) fs.writeFileSync(rc, "nameserver 1.1.1.1\nnameserver 8.8.8.8\n")
40
+ const hh = path.join(etc, "hosts")
41
+ if (!fs.existsSync(hh)) fs.writeFileSync(hh, "127.0.0.1 localhost\n")
42
+ } catch {}
43
+ }
44
+
45
+ function runBinary(args: string[]): number {
46
+ const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
47
+ ensureDns()
48
+ const r = spawnSync(loader, [bin, ...args], {
49
+ stdio: "inherit",
50
+ env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
51
+ })
52
+ if (r.error) {
53
+ console.error("[opencode-termux] gagal menjalankan binary:", r.error.message)
54
+ return 1
55
+ }
56
+ const sigExit: Record<string, number> = { SIGINT: 130, SIGQUIT: 131, SIGTERM: 143 }
57
+ return r.status ?? sigExit[r.signal ?? ""] ?? 1
58
+ }
59
+
60
+ async function cmdUpdate(): Promise<number> {
61
+ console.log(`[opencode-termux] memperbarui bundle (paket v${PKG.version})…`)
62
+ const env = { ...process.env }
63
+ delete env.LD_PRELOAD
64
+ delete env.LD_PRELOAD_32BIT
65
+ const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
66
+ stdio: "inherit",
67
+ env: env,
68
+ })
69
+ if (r.status !== 0) {
70
+ console.error("[opencode-termux] ❌ update gagal.")
71
+ return 1
72
+ }
73
+ try {
74
+ const latest = await (await fetch("https://registry.npmjs.org/opencode-ai/latest")).json() as { version: string }
75
+ if (latest.version && latest.version !== PKG.opencodeUpstream) {
76
+ PKG.opencodeUpstream = latest.version
77
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify(PKG, null, 2) + "\n")
78
+ }
79
+ } catch {}
80
+ console.log("[opencode-termux] ✅ update selesai.")
81
+ return 0
82
+ }
83
+
84
+ function cmdDoctor(): number {
85
+ let critical = 0
86
+ const cek = (name: string, fn: () => string | void, { crit = true } = {}): void => {
87
+ try {
88
+ const info = fn()
89
+ console.log(`✅ ${name}${info ? ` — ${info}` : ""}`)
90
+ } catch (e) {
91
+ if (crit) critical++
92
+ console.log(`${crit ? "❌" : "⚠️ "} ${name} — ${(e as Error).message}`)
93
+ }
94
+ }
95
+
96
+ console.log(`[opencode-termux] doctor v${PKG.version} (upstream ${PKG.opencodeUpstream})`)
97
+ cek("platform", () => {
98
+ if (process.platform !== "android") throw new Error(`process.platform=${process.platform} (bukan android)`)
99
+ return "android"
100
+ }, { crit: false })
101
+ cek("arsitektur", () => {
102
+ if (process.arch !== "arm64" && process.arch !== "x64") throw new Error(`${process.arch} tidak didukung`)
103
+ return process.arch
104
+ })
105
+ cek("node >= 18", () => {
106
+ const [M] = process.versions.node.split(".").map(Number)
107
+ if (M < 18) throw new Error(`node ${process.versions.node}`)
108
+ return process.versions.node
109
+ })
110
+ cek("tar tersedia", () => {
111
+ const r = spawnSync("tar", ["--version"], { stdio: "ignore" })
112
+ if (r.error || r.status !== 0) throw new Error("tidak ditemukan — pkg install tar")
113
+ })
114
+ cek("vendor lengkap", () => {
115
+ if (!ready()) throw new Error("vendor/ tidak lengkap — jalankan 'opencode-termux update'")
116
+ return `${fs.readdirSync(vendor).length} file`
117
+ })
118
+ cek("DNS resolv.conf", () => {
119
+ const rc = path.join(PREFIX, "etc", "resolv.conf")
120
+ if (!fs.existsSync(rc)) throw new Error(`${rc} hilang`)
121
+ return "ada"
122
+ }, { crit: false })
123
+ cek("jaringan registry npm", () => {
124
+ const r = spawnSync(process.execPath, ["-e", "fetch('https://registry.npmjs.org/-/ping').then(r=>{if(!r.ok)process.exit(1)})"], {
125
+ timeout: 10000,
126
+ })
127
+ if (r.status !== 0) throw new Error("registry tak terjangkau")
128
+ })
129
+ cek("binary opencode", () => {
130
+ if (!ready()) throw new Error("binary belum terpasang")
131
+ const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
132
+ const out = spawnSync(loader, [bin, "--version"], {
133
+ encoding: "utf8",
134
+ env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
135
+ })
136
+ if (out.status !== 0) throw new Error("gagal dieksekusi")
137
+ return `v${out.stdout.trim()}`
138
+ })
139
+
140
+ console.log(critical === 0 ? "[opencode-termux] ✅ semua komponen kritis sehat" : `[opencode-termux] ❌ ${critical} masalah kritis`)
141
+ return critical === 0 ? 0 : 1
142
+ }
143
+
144
+ function cmdVersion(): number {
145
+ let binVer = "(belum terpasang)"
146
+ if (ready()) {
147
+ const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
148
+ const out = spawnSync(loader, [bin, "--version"], {
149
+ encoding: "utf8",
150
+ env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
151
+ })
152
+ if (out.status === 0 && out.stdout.trim()) binVer = out.stdout.trim()
153
+ }
154
+ console.log(`opencode-termux v${PKG.version} (upstream opencode ${PKG.opencodeUpstream}, binary ${binVer})`)
155
+ return 0
156
+ }
157
+
158
+ async function main(): Promise<void> {
159
+ const arg = process.argv[2]
160
+ if (arg === "update") process.exit(await cmdUpdate())
161
+ if (arg === "doctor") process.exit(cmdDoctor())
162
+ if (arg === "version") process.exit(cmdVersion())
163
+ if (arg === "help" || arg === "--help" || arg === "-h") {
164
+ console.log(`opencode-termux v${PKG.version}
165
+ pakai:
166
+ opencode-termux jalankan CLI opencode (argumen diteruskan)
167
+ opencode-termux update perbarui binary ke upstream terbaru
168
+ opencode-termux doctor diagnosis lingkungan & bundle
169
+ opencode-termux version info versi paket + binary`)
170
+ process.exit(0)
171
+ }
172
+ if (!heal()) process.exit(1)
173
+ process.exit(runBinary(process.argv.slice(2)))
174
+ }
175
+
176
+ main().catch(e => {
177
+ console.error("[opencode-termux]", e.message)
178
+ process.exit(1)
179
+ })
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Jalankan audit skill multi-bahasa
3
+ agent: orchestrator
4
+ ---
5
+ Audit: scan struktur, deps, security, style, arch. Output: audit-report + audit-summary.json
6
+ $ARGUMENTS
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Master coder — multi-language full lifecycle (audit, test, monitor, fix, compact)
3
+ agent: coder
4
+ ---
5
+
6
+ CODER — Developer otonom universal multi-bahasa.
7
+
8
+ Perintah:
9
+ - coder full # Audit → Test → Monitor → Fix → Compact (default)
10
+ - coder audit # Scan project lengkap
11
+ - coder test # Jalankan semua test
12
+ - coder monitor # Start monitor daemon (watch + CI + perf)
13
+ - coder fix # Auto-fix failure (loop max 5x)
14
+ - coder compact # Optimasi: dead code, format, deps, bundle
15
+ - coder status # Lihat state & progress
16
+
17
+ Args: $ARGUMENTS (opsional: bahasa spesifik, path, filter)
18
+
19
+ Contoh:
20
+ coder full
21
+ coder audit
22
+ coder test --language=typescript
23
+ coder fix --id=fix-001
24
+ coder compact --aggressive
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: Jalankan orchestrator full lifecycle: audit → test → monitor → fix → compact
3
+ agent: orchestrator
4
+ ---
5
+
6
+ Jalankan siklus penuh otomatis:
7
+ 1. Audit project (struktur, deps, security, style, arch)
8
+ 2. Test semua bahasa (unit, integration, coverage)
9
+ 3. Monitor daemon (file watch, CI, perf baseline)
10
+ 4. Fix semua failure (auto-loop max 5x per bug)
11
+ 5. Compact & optimasi (dead code, format, deps, bundle)
12
+
13
+ Args: $ARGUMENTS (optional: full|audit|test|monitor|fix|compact|status|resume)
14
+
15
+ Default: full
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Jalankan test skill multi-bahasa
3
+ agent: orchestrator
4
+ ---
5
+ Test: unit, integration, e2e, coverage per bahasa. Output: test-report + test-summary.json
6
+ $ARGUMENTS
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
- "model": "opencode/x-preview-f-free"
3
+ "model": "opencode/big-pickle"
4
4
  }
@@ -0,0 +1,39 @@
1
+ # Plugin `strip-parameter`
2
+
3
+ Plugin opencode global untuk **mensupresi raw XML tool-call tags literal** (mis.
4
+ `<parameter>`, `</function_calls>`, `<tool_call>`, `<antml:...>`) dari output teks
5
+ model **sebelum dirender TUI**, agar tag mentah tidak bocor ke layar.
6
+
7
+ ## Mengapa
8
+
9
+ opencode kadang menampilkan tag tool-call XML mentah sebagai teks (issue upstream
10
+ `anomalyco/opencode#24316`, fix upstream PR #27984/#30633 belum masuk release).
11
+ Plugin ini adalah mitigasi lokal versi 1.18.23.
12
+
13
+ ## Cara pakai
14
+
15
+ ```bash
16
+ # 1. Salin ke folder plugin global opencode
17
+ mkdir -p ~/.config/opencode/plugins
18
+ cp config/plugins/strip-parameter.js ~/.config/opencode/plugins/
19
+
20
+ # 2. Pastikan config package.json bertipe ESM (opsional, menghilangkan warning)
21
+ # di ~/.config/opencode/package.json tambahkan: "type": "module"
22
+
23
+ # 3. Restart opencode (plugin dimuat saat start). Efek aktif di sesi baru.
24
+ ```
25
+
26
+ ## Verifikasi
27
+
28
+ Jalankan log opencode; harus muncul:
29
+
30
+ ```
31
+ Plugin initialized — mensupresi raw XML tool-call tags dari output teks
32
+ ```
33
+
34
+ ## Catatan
35
+
36
+ - Plugin memuat semua blok/penutup tool-call XML yang "nyasar" sebagai teks.
37
+ - Hanya menyentuh output **teks** (hook `experimental.text.complete`),
38
+ tidak mengubah eksekusi tool.
39
+ - Hook ini eksperimental; jika berubah di versi opencode mendatang, sesuaikan.
@@ -0,0 +1,55 @@
1
+ // strip-parameter.js — opencode plugin (global: ~/.config/opencode/plugins/)
2
+ // Membersihkan raw XML tool-call tags literal (mis. <parameter>, </function_calls>,
3
+ // <parameter name="...">) dari output TEKS model SEBELUM dirender TUI, supaya
4
+ // tag mentah tidak bocor ke layar.
5
+ //
6
+ // Mitigasi lokal atas issue opencode #24316 (bug "<parameter>" literal di TUI),
7
+ // sejalan dengan pendekatan PR #27984 (strip dangling XML artifacts).
8
+ //
9
+ // CATATAN: Hook "experimental.text.complete" bersifat eksperimental & hanya
10
+ // memodifikasi output teks. Plugin murni ESM tanpa dependensi eksternal.
11
+
12
+ export const StripParameterPlugin = async (ctx) => {
13
+ try {
14
+ await ctx?.client?.app.log?.({
15
+ body: {
16
+ service: "strip-parameter",
17
+ level: "info",
18
+ message: "Plugin initialized — mensupresi raw XML tool-call tags dari output teks",
19
+ },
20
+ })
21
+ } catch {}
22
+
23
+ return {
24
+ "experimental.text.complete": async (_input, output) => {
25
+ const before = output.text
26
+ if (typeof before !== "string" || !before.includes("<")) return
27
+
28
+ let text = before
29
+
30
+ // 1) Hapus blok tool-call XML lengkap (tag + konten)
31
+ text = text.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "")
32
+ text = text.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
33
+ text = text.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/g, "")
34
+ text = text.replace(/<antml:function_calls>[\s\S]*?<\/antml:function_calls>/g, "")
35
+
36
+ // 2) Hapus pasangan <parameter ...>...</parameter> & <parameter>...</parameter>
37
+ text = text.replace(/<parameter\b[^>]*>[\s\S]*?<\/parameter>/g, "")
38
+
39
+ // 3) Hapus tag tool-call "dangling" (pembuka/penutup tanpa pasangan)
40
+ text = text.replace(/<\/?parameter\b[^>]*>/g, "")
41
+ text = text.replace(/<\/?invoke\b[^>]*>/g, "")
42
+ text = text.replace(/<\/?function_calls\b[^>]*>/g, "")
43
+ text = text.replace(/<\/?tool_call\b[^>]*>/g, "")
44
+ text = text.replace(/<\/?antml:\w+(?:\s[^>]*)?>/g, "")
45
+ text = text.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, "")
46
+
47
+ // 4) Rapikan baris kosong berlebih & spasi sisa
48
+ text = text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n")
49
+
50
+ if (text !== before) output.text = text
51
+ },
52
+ }
53
+ }
54
+
55
+ export default StripParameterPlugin
package/install.mjs CHANGED
@@ -57,8 +57,32 @@ const work = path.join(__dirname, ".build")
57
57
  fs.rmSync(work, { recursive: true, force: true })
58
58
  fs.mkdirSync(work, { recursive: true })
59
59
 
60
+ async function fetchLatestAlpineVersion() {
61
+ try {
62
+ const res = await fetchWithRetry(fetch, "https://dl-cdn.alpinelinux.org/alpine/latest-stable/", {}, 3)
63
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
64
+ const text = await res.text()
65
+ const match = text.match(/href="(v\d+\.\d+)"/)
66
+ if (match) return match[1]
67
+ } catch {}
68
+ return "v3.21"
69
+ }
70
+
71
+ async function fetchAlpineReleaseVersion(version) {
72
+ try {
73
+ const res = await fetchWithRetry(fetch, `https://dl-cdn.alpinelinux.org/alpine/${version}/releases/${A}/`, {}, 3)
74
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
75
+ const text = await res.text()
76
+ const match = text.match(new RegExp(`alpine-minirootfs-(\\d+\\.\\d+\\.\\d+)-${A}\\.tar\\.gz`))
77
+ if (match) return match[1]
78
+ } catch {}
79
+ return "3.21.3"
80
+ }
81
+
60
82
  try {
61
- const AV = "v3.21", AL = "3.21.3"
83
+ const AV = process.env.OCX_ALPINE_VERSION || await fetchLatestAlpineVersion()
84
+ const AL = await fetchAlpineReleaseVersion(AV)
85
+ log(`Alpine version: ${AV} (release ${AL})`)
62
86
 
63
87
  // Resolusi dinamis paket Alpine dari CDN (lihat lib/alpine.mjs)
64
88
  const pkg = name => alpinePkg(fetch, `https://dl-cdn.alpinelinux.org/alpine/${AV}/main/${A}`, name)
package/lib/alpine.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Resolver paket Alpine dari CDN — anti-404 saat Alpine memperbarui paket
3
+ * dalam satu branch. Fallback ke versi terakhir yang diketahui hidup.
4
+ */
5
+ export function cmpVer(a: string, b: string): number {
6
+ const pa = a.split(/[.\-r]/).filter(Boolean).map(Number)
7
+ const pb = b.split(/[.\-r]/).filter(Boolean).map(Number)
8
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
9
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0)
10
+ }
11
+ return 0
12
+ }
13
+
14
+ export interface FetchFn {
15
+ (url: string, options?: RequestInit): Promise<{ text: () => Promise<string> }>
16
+ }
17
+
18
+ export async function alpinePkg(
19
+ fetchFn: FetchFn,
20
+ cdnBase: string,
21
+ nameEncoded: string,
22
+ fallback = "14.2.0-r4"
23
+ ): Promise<string> {
24
+ let latest: { file: string; v: string } | null = null
25
+ try {
26
+ const res = await fetchFn(`${cdnBase}/`)
27
+ const idx = await res.text()
28
+ const re = new RegExp(`${nameEncoded}-([0-9][0-9a-zA-Z.+]*)-r([0-9]+)\\.apk`, "g")
29
+ for (const m of idx.matchAll(re)) {
30
+ const v = `${m[1]}-r${m[2]}`
31
+ if (!latest || cmpVer(v, latest.v) > 0) latest = { file: m[0], v }
32
+ }
33
+ } catch {}
34
+ if (!latest) {
35
+ console.warn(`[opencode-termux] listing CDN gagal — fallback ${nameEncoded}-${fallback}.apk`)
36
+ latest = { file: `${nameEncoded}-${fallback}.apk`, v: fallback }
37
+ }
38
+ return latest.file
39
+ }