@wenrwa/marketplace-cli 0.7.8 → 0.8.1
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.
- package/dist/__tests__/bridge-activity-tail.test.js +16 -0
- package/dist/__tests__/bridge-attach-paste-engine.test.d.ts +1 -0
- package/dist/__tests__/bridge-attach-paste-engine.test.js +108 -0
- package/dist/__tests__/bridge-bundle-installer-opencode.test.d.ts +1 -0
- package/dist/__tests__/bridge-bundle-installer-opencode.test.js +69 -0
- package/dist/__tests__/bridge-context-engine.test.d.ts +1 -0
- package/dist/__tests__/bridge-context-engine.test.js +112 -0
- package/dist/__tests__/bridge-context-manifest-frame.test.js +19 -0
- package/dist/__tests__/bridge-crew-translator.test.d.ts +1 -0
- package/dist/__tests__/bridge-crew-translator.test.js +103 -0
- package/dist/__tests__/bridge-install-bundle-frame.test.js +24 -0
- package/dist/__tests__/bridge-loopback-opencode-open.test.d.ts +1 -0
- package/dist/__tests__/bridge-loopback-opencode-open.test.js +51 -0
- package/dist/__tests__/bridge-opencode-launch.test.d.ts +1 -0
- package/dist/__tests__/bridge-opencode-launch.test.js +179 -0
- package/dist/__tests__/bridge-relay-pucc14.test.js +28 -0
- package/dist/__tests__/bridge-server.test.js +32 -0
- package/dist/__tests__/opencode-config-parity.test.d.ts +1 -0
- package/dist/__tests__/opencode-config-parity.test.js +85 -0
- package/dist/__tests__/opencode-plugin.test.d.ts +1 -0
- package/dist/__tests__/opencode-plugin.test.js +197 -0
- package/dist/__tests__/opencode-title.test.d.ts +1 -0
- package/dist/__tests__/opencode-title.test.js +99 -0
- package/dist/__tests__/opencode-transcript-reader.test.d.ts +1 -0
- package/dist/__tests__/opencode-transcript-reader.test.js +156 -0
- package/dist/bridge/activityTail.d.ts +7 -0
- package/dist/bridge/activityTail.js +1 -0
- package/dist/bridge/agentLaunch.d.ts +28 -0
- package/dist/bridge/agentLaunch.js +49 -0
- package/dist/bridge/attachmentWriter.d.ts +16 -0
- package/dist/bridge/attachmentWriter.js +24 -1
- package/dist/bridge/bundleInstaller.d.ts +3 -0
- package/dist/bridge/bundleInstaller.js +22 -8
- package/dist/bridge/claudeTranscriptReader.d.ts +7 -1
- package/dist/bridge/claudeTranscriptReader.js +12 -2
- package/dist/bridge/contextFile.d.ts +20 -0
- package/dist/bridge/contextFile.js +30 -0
- package/dist/bridge/contextWriter.d.ts +10 -1
- package/dist/bridge/contextWriter.js +29 -9
- package/dist/bridge/crewTranslator.d.ts +41 -0
- package/dist/bridge/crewTranslator.js +129 -0
- package/dist/bridge/launchSupervisor.d.ts +4 -0
- package/dist/bridge/opencodeConfig.d.ts +41 -0
- package/dist/bridge/opencodeConfig.js +107 -0
- package/dist/bridge/opencodePlugin.d.ts +13 -0
- package/dist/bridge/opencodePlugin.js +54 -0
- package/dist/bridge/opencodeTranscriptReader.d.ts +24 -0
- package/dist/bridge/opencodeTranscriptReader.js +178 -0
- package/dist/bridge/relayClient.d.ts +27 -1
- package/dist/bridge/relayClient.js +24 -7
- package/dist/bridge/screenshotAttach.d.ts +2 -0
- package/dist/bridge/screenshotAttach.js +3 -1
- package/dist/bridge/server.d.ts +6 -0
- package/dist/bridge/server.js +9 -1
- package/dist/bridge/sessionManager.d.ts +5 -0
- package/dist/bridge/sessionManager.js +21 -1
- package/dist/commands/bridge.js +36 -9
- package/hooks/opencode-plugin.mjs +186 -0
- package/package.json +1 -1
|
@@ -66,6 +66,7 @@ exports.writeContext = writeContext;
|
|
|
66
66
|
* never a sibling workspace. An unsafe workspaceId writes NOTHING.
|
|
67
67
|
*/
|
|
68
68
|
const path = __importStar(require("path"));
|
|
69
|
+
const contextFile_1 = require("./contextFile");
|
|
69
70
|
const bundleInstaller_1 = require("./bundleInstaller");
|
|
70
71
|
const workGraphPointer_1 = require("./workGraphPointer");
|
|
71
72
|
const memoryPointer_1 = require("./memoryPointer");
|
|
@@ -119,13 +120,32 @@ function writeContext(input, deps) {
|
|
|
119
120
|
return { workspaceRoot: null, claudeMdPath: null, action: 'skipped' };
|
|
120
121
|
}
|
|
121
122
|
const workspaceRoot = path.posix.resolve(deps.reposBaseDir, input.workspaceId);
|
|
122
|
-
const
|
|
123
|
-
|
|
123
|
+
const fileAt = (name) => path.posix.resolve(workspaceRoot, name);
|
|
124
|
+
const targets = (0, contextFile_1.contextTargets)(input.engine, (f) => deps.exists(fileAt(f)));
|
|
125
|
+
// Belt-and-suspenders: prove containment (the names are fixed segments, so
|
|
124
126
|
// this can only fail if isSafeWorkspaceId ever regresses).
|
|
125
|
-
if (
|
|
127
|
+
if (targets.some((f) => fileAt(f) !== workspaceRoot + '/' + f)) {
|
|
126
128
|
return { workspaceRoot: null, claudeMdPath: null, action: 'skipped' };
|
|
127
129
|
}
|
|
128
|
-
const
|
|
130
|
+
const paths = targets.map(fileAt);
|
|
131
|
+
// A file that does not exist yet is seeded from the other one, so a first
|
|
132
|
+
// AGENTS.md carries the user's own CLAUDE.md guidance instead of hiding it
|
|
133
|
+
// (OpenCode ignores CLAUDE.md once AGENTS.md exists).
|
|
134
|
+
const seed = paths.find((p) => deps.exists(p)) ?? null;
|
|
135
|
+
let primary = 'noop';
|
|
136
|
+
paths.forEach((target, i) => {
|
|
137
|
+
const action = writeContextFile(input, target, seed, workspaceRoot, deps);
|
|
138
|
+
if (i === 0)
|
|
139
|
+
primary = action;
|
|
140
|
+
});
|
|
141
|
+
return { workspaceRoot, claudeMdPath: paths[0], contextPaths: paths, action: primary };
|
|
142
|
+
}
|
|
143
|
+
function writeContextFile(input, claudeMdPath, seedPath, workspaceRoot, deps) {
|
|
144
|
+
const existing = deps.exists(claudeMdPath)
|
|
145
|
+
? deps.readFile(claudeMdPath)
|
|
146
|
+
: seedPath && deps.exists(seedPath)
|
|
147
|
+
? deps.readFile(seedPath)
|
|
148
|
+
: null;
|
|
129
149
|
// wgcp-01 — the pointer rides IN the region, ahead of the arena's pushed
|
|
130
150
|
// file content, but is decided entirely by the caller (workGraphAvailable):
|
|
131
151
|
// absent/false is byte-identical to pre-wgcp-01 behavior (the pointer never
|
|
@@ -178,7 +198,7 @@ function writeContext(input, deps) {
|
|
|
178
198
|
// ── Empty content → remove the region cleanly (or no-op). ──
|
|
179
199
|
if (isEmpty) {
|
|
180
200
|
if (!hasRegion) {
|
|
181
|
-
return
|
|
201
|
+
return 'noop';
|
|
182
202
|
}
|
|
183
203
|
// Tidy only the seam we own: drop the trailing newlines of the head and the
|
|
184
204
|
// leading newlines of the tail (the blank line inserted on append), never
|
|
@@ -189,22 +209,22 @@ function writeContext(input, deps) {
|
|
|
189
209
|
if (out.length > 0)
|
|
190
210
|
out = out.replace(/\n*$/, '\n');
|
|
191
211
|
deps.writeFile(claudeMdPath, out);
|
|
192
|
-
return
|
|
212
|
+
return 'removed';
|
|
193
213
|
}
|
|
194
214
|
const region = regionBlock(content);
|
|
195
215
|
// ── Region already present → replace in place (idempotent). ──
|
|
196
216
|
if (hasRegion) {
|
|
197
217
|
deps.writeFile(claudeMdPath, before + region + after);
|
|
198
|
-
return
|
|
218
|
+
return 'replaced';
|
|
199
219
|
}
|
|
200
220
|
deps.mkdirp?.(workspaceRoot);
|
|
201
221
|
// ── No CLAUDE.md yet → create it holding just the region. ──
|
|
202
222
|
if (existing === null) {
|
|
203
223
|
deps.writeFile(claudeMdPath, region + '\n');
|
|
204
|
-
return
|
|
224
|
+
return 'created';
|
|
205
225
|
}
|
|
206
226
|
// ── CLAUDE.md present, no region → append the region after user content. ──
|
|
207
227
|
const sep = existing.length === 0 ? '' : (existing.endsWith('\n') ? '\n' : '\n\n');
|
|
208
228
|
deps.writeFile(claudeMdPath, existing + sep + region + '\n');
|
|
209
|
-
return
|
|
229
|
+
return 'appended';
|
|
210
230
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oc-18 — translate a forged Claude Code crew bundle into OpenCode's files.
|
|
3
|
+
* Pure: FileMap in, FileMap out, no I/O.
|
|
4
|
+
*
|
|
5
|
+
* Verified against opencode-ai@1.18.35 (oc-18 notes in issues/oc-18-*.md):
|
|
6
|
+
* - agents load from `.opencode/agent/<name>.md` (filename = name); they are
|
|
7
|
+
* NOT read from `.claude/agents`, so each subagent must be translated;
|
|
8
|
+
* - frontmatter: description, mode (primary|subagent|all), permission; the
|
|
9
|
+
* body is the prompt. `tools:` still works but is the legacy spelling, so
|
|
10
|
+
* we emit `permission`;
|
|
11
|
+
* - `.claude/skills/**\/SKILL.md` is read natively -> passthrough;
|
|
12
|
+
* - `AGENTS.md` is the project rules file (CLAUDE.md is only a fallback when
|
|
13
|
+
* AGENTS.md is absent) -> CLAUDE.md content moves there verbatim, so the
|
|
14
|
+
* HIVEOP fence markers survive unchanged.
|
|
15
|
+
*
|
|
16
|
+
* `model` is DROPPED on purpose. An OpenCode agent's provider list is pinned to
|
|
17
|
+
* the single gateway-whitelisted model its owner chose (ADR-0013); a subagent
|
|
18
|
+
* with no model inherits its caller's, so it bills at that model. A Claude
|
|
19
|
+
* alias like `sonnet` would name a model the gateway refuses.
|
|
20
|
+
*/
|
|
21
|
+
export type FileMap = Record<string, string>;
|
|
22
|
+
export declare const OPENCODE_AGENT_DIR = ".opencode/agent";
|
|
23
|
+
/**
|
|
24
|
+
* Claude's `tools:` is an allowlist (absent = inherit all); OpenCode's default
|
|
25
|
+
* is allow, so the allowlist becomes `deny` for each gated tool not listed.
|
|
26
|
+
* Read/Grep/Glob/LS are never gated. `mcp__*` entries and unknown names are
|
|
27
|
+
* ignored: OpenCode cannot deny "all MCP" without naming servers, and the
|
|
28
|
+
* agent's own allow-all setting governs MCP anyway.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mapClaudeToolsToPermission(tools: string | string[] | undefined): Record<string, 'deny'> | undefined;
|
|
31
|
+
export interface Frontmatter {
|
|
32
|
+
data: Record<string, string>;
|
|
33
|
+
body: string;
|
|
34
|
+
/** True when the source used CRLF line endings. */
|
|
35
|
+
crlf: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Minimal YAML-subset reader: `key: value`, quoted values, `|`/`>` blocks. */
|
|
38
|
+
export declare function parseFrontmatter(src: string): Frontmatter;
|
|
39
|
+
/** One Claude subagent file -> one OpenCode agent file. */
|
|
40
|
+
export declare function translateClaudeAgentToOpencode(src: string): string;
|
|
41
|
+
export declare function translateCrewBundleForOpencode(bundle: FileMap): FileMap;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* oc-18 — translate a forged Claude Code crew bundle into OpenCode's files.
|
|
4
|
+
* Pure: FileMap in, FileMap out, no I/O.
|
|
5
|
+
*
|
|
6
|
+
* Verified against opencode-ai@1.18.35 (oc-18 notes in issues/oc-18-*.md):
|
|
7
|
+
* - agents load from `.opencode/agent/<name>.md` (filename = name); they are
|
|
8
|
+
* NOT read from `.claude/agents`, so each subagent must be translated;
|
|
9
|
+
* - frontmatter: description, mode (primary|subagent|all), permission; the
|
|
10
|
+
* body is the prompt. `tools:` still works but is the legacy spelling, so
|
|
11
|
+
* we emit `permission`;
|
|
12
|
+
* - `.claude/skills/**\/SKILL.md` is read natively -> passthrough;
|
|
13
|
+
* - `AGENTS.md` is the project rules file (CLAUDE.md is only a fallback when
|
|
14
|
+
* AGENTS.md is absent) -> CLAUDE.md content moves there verbatim, so the
|
|
15
|
+
* HIVEOP fence markers survive unchanged.
|
|
16
|
+
*
|
|
17
|
+
* `model` is DROPPED on purpose. An OpenCode agent's provider list is pinned to
|
|
18
|
+
* the single gateway-whitelisted model its owner chose (ADR-0013); a subagent
|
|
19
|
+
* with no model inherits its caller's, so it bills at that model. A Claude
|
|
20
|
+
* alias like `sonnet` would name a model the gateway refuses.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.OPENCODE_AGENT_DIR = void 0;
|
|
24
|
+
exports.mapClaudeToolsToPermission = mapClaudeToolsToPermission;
|
|
25
|
+
exports.parseFrontmatter = parseFrontmatter;
|
|
26
|
+
exports.translateClaudeAgentToOpencode = translateClaudeAgentToOpencode;
|
|
27
|
+
exports.translateCrewBundleForOpencode = translateCrewBundleForOpencode;
|
|
28
|
+
exports.OPENCODE_AGENT_DIR = '.opencode/agent';
|
|
29
|
+
const CLAUDE_AGENTS_PREFIX = '.claude/agents/';
|
|
30
|
+
/** Claude tool name -> OpenCode permission key. Read-class tools are never denied. */
|
|
31
|
+
const TOOL_TO_PERMISSION = {
|
|
32
|
+
Write: 'edit', Edit: 'edit', MultiEdit: 'edit', NotebookEdit: 'edit',
|
|
33
|
+
Bash: 'bash',
|
|
34
|
+
WebFetch: 'webfetch',
|
|
35
|
+
WebSearch: 'websearch',
|
|
36
|
+
Task: 'task',
|
|
37
|
+
TodoWrite: 'todowrite',
|
|
38
|
+
};
|
|
39
|
+
/** Order is the emitted order (golden-tested). */
|
|
40
|
+
const GATED = ['edit', 'bash', 'webfetch', 'websearch', 'task', 'todowrite'];
|
|
41
|
+
/**
|
|
42
|
+
* Claude's `tools:` is an allowlist (absent = inherit all); OpenCode's default
|
|
43
|
+
* is allow, so the allowlist becomes `deny` for each gated tool not listed.
|
|
44
|
+
* Read/Grep/Glob/LS are never gated. `mcp__*` entries and unknown names are
|
|
45
|
+
* ignored: OpenCode cannot deny "all MCP" without naming servers, and the
|
|
46
|
+
* agent's own allow-all setting governs MCP anyway.
|
|
47
|
+
*/
|
|
48
|
+
function mapClaudeToolsToPermission(tools) {
|
|
49
|
+
const list = (Array.isArray(tools) ? tools : (tools ?? '').split(','))
|
|
50
|
+
.map((t) => t.trim())
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
if (list.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
const allowed = new Set(list.map((t) => TOOL_TO_PERMISSION[t]).filter(Boolean));
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const key of GATED)
|
|
57
|
+
if (!allowed.has(key))
|
|
58
|
+
out[key] = 'deny';
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** Minimal YAML-subset reader: `key: value`, quoted values, `|`/`>` blocks. */
|
|
62
|
+
function parseFrontmatter(src) {
|
|
63
|
+
const crlf = src.includes('\r\n');
|
|
64
|
+
const text = src.replace(/\r\n/g, '\n');
|
|
65
|
+
const m = /^---\n([\s\S]*?)\n---[ \t]*(?:\n|$)/.exec(text);
|
|
66
|
+
if (!m)
|
|
67
|
+
return { data: {}, body: text, crlf };
|
|
68
|
+
const data = {};
|
|
69
|
+
const lines = m[1].split('\n');
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i]);
|
|
72
|
+
if (!kv)
|
|
73
|
+
continue;
|
|
74
|
+
let v = kv[2].trim();
|
|
75
|
+
if (/^[|>][+-]?$/.test(v)) {
|
|
76
|
+
const block = [];
|
|
77
|
+
while (i + 1 < lines.length && /^\s+\S|^\s*$/.test(lines[i + 1]))
|
|
78
|
+
block.push(lines[++i].trim());
|
|
79
|
+
v = block.join(v.startsWith('|') ? '\n' : ' ').trim();
|
|
80
|
+
}
|
|
81
|
+
else if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) {
|
|
82
|
+
try {
|
|
83
|
+
v = JSON.parse(v);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
v = v.slice(1, -1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) {
|
|
90
|
+
v = v.slice(1, -1).replace(/''/g, "'");
|
|
91
|
+
}
|
|
92
|
+
data[kv[1]] = v;
|
|
93
|
+
}
|
|
94
|
+
return { data, body: text.slice(m[0].length), crlf };
|
|
95
|
+
}
|
|
96
|
+
/** One Claude subagent file -> one OpenCode agent file. */
|
|
97
|
+
function translateClaudeAgentToOpencode(src) {
|
|
98
|
+
const { data, body, crlf } = parseFrontmatter(src);
|
|
99
|
+
const head = ['---'];
|
|
100
|
+
if (data.description)
|
|
101
|
+
head.push(`description: ${JSON.stringify(data.description)}`);
|
|
102
|
+
head.push('mode: subagent');
|
|
103
|
+
const perm = mapClaudeToolsToPermission(data.tools);
|
|
104
|
+
if (perm) {
|
|
105
|
+
head.push('permission:');
|
|
106
|
+
for (const [k, v] of Object.entries(perm))
|
|
107
|
+
head.push(` ${k}: ${v}`);
|
|
108
|
+
}
|
|
109
|
+
head.push('---');
|
|
110
|
+
// A frontmatter-less source keeps its text; with frontmatter the body already
|
|
111
|
+
// carries its own leading newline(s), which is why the join adds just one.
|
|
112
|
+
const out = head.join('\n') + '\n' + body;
|
|
113
|
+
return crlf ? out.replace(/\n/g, '\r\n') : out;
|
|
114
|
+
}
|
|
115
|
+
function translateCrewBundleForOpencode(bundle) {
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const [rel, content] of Object.entries(bundle ?? {})) {
|
|
118
|
+
if (rel.startsWith(CLAUDE_AGENTS_PREFIX) && rel.endsWith('.md') && !rel.slice(CLAUDE_AGENTS_PREFIX.length).includes('/')) {
|
|
119
|
+
out[`${exports.OPENCODE_AGENT_DIR}/${rel.slice(CLAUDE_AGENTS_PREFIX.length)}`] = translateClaudeAgentToOpencode(content);
|
|
120
|
+
}
|
|
121
|
+
else if (rel === 'CLAUDE.md') {
|
|
122
|
+
out['AGENTS.md'] = content;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
out[rel] = content;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
* `No conversation found with session ID: <uuid>` (rendered by the TUI).
|
|
26
26
|
* Both arrive on the PTY, so matching the combined output catches both.
|
|
27
27
|
*
|
|
28
|
+
* Claude Code ONLY (oc-08): SessionManager never wraps an `opencode` launch
|
|
29
|
+
* here — these regexes are Claude's wording. OpenCode resumes with `-s <captured
|
|
30
|
+
* id>` and a stale id shows OpenCode's own error in the pane.
|
|
31
|
+
*
|
|
28
32
|
* Both lanes: the local bridge (loopback + crew relay on a laptop) spawns
|
|
29
33
|
* `claude` through here. The hosted container does NOT — it spawns $SHELL and
|
|
30
34
|
* infra/terminal-image/autolaunch.sh runs `claude`, so the same rules live
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oc-08 — the CANONICAL OpenCode launch config (docs/prd/opencode-engine.md,
|
|
3
|
+
* ADR-0013, shape verified in oc-04). Pure: no I/O, no process.env.
|
|
4
|
+
*
|
|
5
|
+
* The hosted lane (infra/terminal-image/opencode/build-config.js, oc-05) must
|
|
6
|
+
* emit the SAME JSON for the same inputs; a parity test on each side should pin
|
|
7
|
+
* it. The OpenRouter key is NEVER an input here: the only credential OpenCode
|
|
8
|
+
* ever holds is the per-agent gateway token, and it is passed by reference
|
|
9
|
+
* (`{env:HIVEOP_GATEWAY_TOKEN}`) so it never appears inside the JSON.
|
|
10
|
+
*/
|
|
11
|
+
/** Oldest OpenCode the local lane accepts. 1.18.35 is the version oc-04 verified
|
|
12
|
+
* (`permission.asked` / `session.idle` events, `-s <id>` resume, quiet env). */
|
|
13
|
+
export declare const OPENCODE_MIN_VERSION = "1.18.35";
|
|
14
|
+
export declare const OPENCODE_INSTALL_HINT = "Install OpenCode: npm i -g opencode-ai@>=1.18.35";
|
|
15
|
+
/** Every outbound call OpenCode makes at startup besides the gateway (oc-04 s5). */
|
|
16
|
+
export declare const OPENCODE_QUIET_ENV: Readonly<Record<string, string>>;
|
|
17
|
+
export declare const OPENCODE_PROVIDER_ID = "hiveop";
|
|
18
|
+
export declare const OPENCODE_GATEWAY_TOKEN_ENV = "HIVEOP_GATEWAY_TOKEN";
|
|
19
|
+
export interface OpencodeConfigInput {
|
|
20
|
+
/** Chosen model id (the gateway's catalogue id, e.g. an OpenRouter slug). */
|
|
21
|
+
model: string;
|
|
22
|
+
/** Full OpenAI-compatible base URL, i.e. `<api>/api/v1/opencode/v1`. */
|
|
23
|
+
gatewayBaseUrl: string;
|
|
24
|
+
/** permission "allow" when true (autonomous agent), OpenCode's default otherwise. */
|
|
25
|
+
autonomous?: boolean;
|
|
26
|
+
/** Add the wenrwa-marketplace MCP server (token read from env, not inlined). */
|
|
27
|
+
withMcp?: boolean;
|
|
28
|
+
/** argv of the MCP server; defaults to npx. */
|
|
29
|
+
mcpCommand?: string[];
|
|
30
|
+
/** Extra env var NAMES the MCP server needs, passed by reference (`{env:NAME}`), never inlined. */
|
|
31
|
+
mcpEnvPassthrough?: string[];
|
|
32
|
+
/** Project-relative files OpenCode loads as extra instructions. The local lane passes the
|
|
33
|
+
* bridge's `.hiveop/context.md` (oc-23): OpenCode has no SessionStart hook to inject it. */
|
|
34
|
+
instructions?: string[];
|
|
35
|
+
}
|
|
36
|
+
export declare function buildOpencodeConfig(input: OpencodeConfigInput): Record<string, unknown>;
|
|
37
|
+
/** OpenCode session ids are generated by OpenCode (`ses_...`); anything else
|
|
38
|
+
* (e.g. the uuid oc-01 pre-mints on the row) would fail "Session not found". */
|
|
39
|
+
export declare const isCapturedOpencodeSessionId: (id: unknown) => id is string;
|
|
40
|
+
/** True iff `versionOutput` (from `opencode --version`) is >= the minimum. */
|
|
41
|
+
export declare function opencodeVersionOk(versionOutput: string | null | undefined, min?: string): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* oc-08 — the CANONICAL OpenCode launch config (docs/prd/opencode-engine.md,
|
|
4
|
+
* ADR-0013, shape verified in oc-04). Pure: no I/O, no process.env.
|
|
5
|
+
*
|
|
6
|
+
* The hosted lane (infra/terminal-image/opencode/build-config.js, oc-05) must
|
|
7
|
+
* emit the SAME JSON for the same inputs; a parity test on each side should pin
|
|
8
|
+
* it. The OpenRouter key is NEVER an input here: the only credential OpenCode
|
|
9
|
+
* ever holds is the per-agent gateway token, and it is passed by reference
|
|
10
|
+
* (`{env:HIVEOP_GATEWAY_TOKEN}`) so it never appears inside the JSON.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.isCapturedOpencodeSessionId = exports.OPENCODE_GATEWAY_TOKEN_ENV = exports.OPENCODE_PROVIDER_ID = exports.OPENCODE_QUIET_ENV = exports.OPENCODE_INSTALL_HINT = exports.OPENCODE_MIN_VERSION = void 0;
|
|
14
|
+
exports.buildOpencodeConfig = buildOpencodeConfig;
|
|
15
|
+
exports.opencodeVersionOk = opencodeVersionOk;
|
|
16
|
+
/** Oldest OpenCode the local lane accepts. 1.18.35 is the version oc-04 verified
|
|
17
|
+
* (`permission.asked` / `session.idle` events, `-s <id>` resume, quiet env). */
|
|
18
|
+
exports.OPENCODE_MIN_VERSION = '1.18.35';
|
|
19
|
+
exports.OPENCODE_INSTALL_HINT = `Install OpenCode: npm i -g opencode-ai@>=${exports.OPENCODE_MIN_VERSION}`;
|
|
20
|
+
/** Every outbound call OpenCode makes at startup besides the gateway (oc-04 s5). */
|
|
21
|
+
exports.OPENCODE_QUIET_ENV = Object.freeze({
|
|
22
|
+
OPENCODE_DISABLE_MODELS_FETCH: '1',
|
|
23
|
+
OPENCODE_DISABLE_AUTOUPDATE: '1',
|
|
24
|
+
OPENCODE_DISABLE_SHARE: '1',
|
|
25
|
+
OPENCODE_DISABLE_LSP_DOWNLOAD: '1',
|
|
26
|
+
// oc-17: the HiveOp plugin sets the pane title itself (a bare "OpenCode" would clobber the auto-title).
|
|
27
|
+
OPENCODE_DISABLE_TERMINAL_TITLE: '1',
|
|
28
|
+
npm_config_offline: 'true',
|
|
29
|
+
});
|
|
30
|
+
exports.OPENCODE_PROVIDER_ID = 'hiveop';
|
|
31
|
+
exports.OPENCODE_GATEWAY_TOKEN_ENV = 'HIVEOP_GATEWAY_TOKEN';
|
|
32
|
+
function buildOpencodeConfig(input) {
|
|
33
|
+
const { model } = input;
|
|
34
|
+
const config = {
|
|
35
|
+
$schema: 'https://opencode.ai/config.json',
|
|
36
|
+
autoupdate: false,
|
|
37
|
+
share: 'disabled',
|
|
38
|
+
enabled_providers: [exports.OPENCODE_PROVIDER_ID],
|
|
39
|
+
model: `${exports.OPENCODE_PROVIDER_ID}/${model}`,
|
|
40
|
+
// OpenCode makes a second, billed "title" call on the small model.
|
|
41
|
+
small_model: `${exports.OPENCODE_PROVIDER_ID}/${model}`,
|
|
42
|
+
provider: {
|
|
43
|
+
[exports.OPENCODE_PROVIDER_ID]: {
|
|
44
|
+
npm: '@ai-sdk/openai-compatible',
|
|
45
|
+
name: 'HiveOp',
|
|
46
|
+
options: {
|
|
47
|
+
baseURL: input.gatewayBaseUrl,
|
|
48
|
+
apiKey: `{env:${exports.OPENCODE_GATEWAY_TOKEN_ENV}}`,
|
|
49
|
+
},
|
|
50
|
+
whitelist: [model],
|
|
51
|
+
models: {
|
|
52
|
+
[model]: {
|
|
53
|
+
name: model,
|
|
54
|
+
attachment: true,
|
|
55
|
+
tool_call: true,
|
|
56
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
57
|
+
limit: { context: 200000, output: 8192 },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
if (input.instructions && input.instructions.length > 0)
|
|
64
|
+
config.instructions = [...input.instructions];
|
|
65
|
+
if (input.autonomous)
|
|
66
|
+
config.permission = 'allow';
|
|
67
|
+
if (input.withMcp) {
|
|
68
|
+
// oc-07: OPENCODE_QUIET_ENV sets npm_config_offline=true for the pane; a bare
|
|
69
|
+
// npx MCP server inherits it and cannot fetch itself on a cold npm cache
|
|
70
|
+
// ("MCP error -32000: Connection closed"). Override it for npx only.
|
|
71
|
+
const cmd = input.mcpCommand ?? ['npx', '@wenrwa/marketplace-mcp'];
|
|
72
|
+
const usesNpx = /(^|[\\/])npx(\.cmd)?$/.test(cmd[0] ?? '');
|
|
73
|
+
config.mcp = {
|
|
74
|
+
'wenrwa-marketplace': {
|
|
75
|
+
type: 'local',
|
|
76
|
+
command: cmd,
|
|
77
|
+
environment: {
|
|
78
|
+
WENRWA_CONNECT_TOKEN: '{env:WENRWA_CONNECT_TOKEN}',
|
|
79
|
+
...Object.fromEntries((input.mcpEnvPassthrough ?? []).map((n) => [n, `{env:${n}}`])),
|
|
80
|
+
...(usesNpx ? { npm_config_offline: 'false' } : {}),
|
|
81
|
+
},
|
|
82
|
+
enabled: true,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
/** OpenCode session ids are generated by OpenCode (`ses_...`); anything else
|
|
89
|
+
* (e.g. the uuid oc-01 pre-mints on the row) would fail "Session not found". */
|
|
90
|
+
const isCapturedOpencodeSessionId = (id) => typeof id === 'string' && /^ses_[A-Za-z0-9]+$/.test(id);
|
|
91
|
+
exports.isCapturedOpencodeSessionId = isCapturedOpencodeSessionId;
|
|
92
|
+
const parseVersion = (v) => {
|
|
93
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
94
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
95
|
+
};
|
|
96
|
+
/** True iff `versionOutput` (from `opencode --version`) is >= the minimum. */
|
|
97
|
+
function opencodeVersionOk(versionOutput, min = exports.OPENCODE_MIN_VERSION) {
|
|
98
|
+
const got = parseVersion(versionOutput ?? '');
|
|
99
|
+
const want = parseVersion(min);
|
|
100
|
+
if (!got)
|
|
101
|
+
return false;
|
|
102
|
+
for (let i = 0; i < 3; i++) {
|
|
103
|
+
if (got[i] !== want[i])
|
|
104
|
+
return got[i] > want[i];
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oc-06 — the HiveOp OpenCode activity plugin (cli/hooks/opencode-plugin.mjs,
|
|
3
|
+
* shipped in the npm package beside the Claude hooks). The local launch
|
|
4
|
+
* resolver (oc-08) merges `buildOpenCodePluginConfig()` into the agent's
|
|
5
|
+
* OPENCODE_CONFIG_CONTENT; OpenCode loads plugins from `plugin: [file URL]`
|
|
6
|
+
* (the path the oc-04 spike verified). Hosted uses the copy under
|
|
7
|
+
* /opt/hiveop/opencode/ via the same key.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveOpenCodePluginPath(hooksDir?: string): string;
|
|
10
|
+
/** Config fragment to merge into OpenCode's config. pathToFileURL encodes spaces. */
|
|
11
|
+
export declare function buildOpenCodePluginConfig(pluginPath?: string): {
|
|
12
|
+
plugin: string[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveOpenCodePluginPath = resolveOpenCodePluginPath;
|
|
37
|
+
exports.buildOpenCodePluginConfig = buildOpenCodePluginConfig;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const url_1 = require("url");
|
|
40
|
+
/**
|
|
41
|
+
* oc-06 — the HiveOp OpenCode activity plugin (cli/hooks/opencode-plugin.mjs,
|
|
42
|
+
* shipped in the npm package beside the Claude hooks). The local launch
|
|
43
|
+
* resolver (oc-08) merges `buildOpenCodePluginConfig()` into the agent's
|
|
44
|
+
* OPENCODE_CONFIG_CONTENT; OpenCode loads plugins from `plugin: [file URL]`
|
|
45
|
+
* (the path the oc-04 spike verified). Hosted uses the copy under
|
|
46
|
+
* /opt/hiveop/opencode/ via the same key.
|
|
47
|
+
*/
|
|
48
|
+
function resolveOpenCodePluginPath(hooksDir = path.join(__dirname, '..', '..', 'hooks')) {
|
|
49
|
+
return path.join(hooksDir, 'opencode-plugin.mjs');
|
|
50
|
+
}
|
|
51
|
+
/** Config fragment to merge into OpenCode's config. pathToFileURL encodes spaces. */
|
|
52
|
+
function buildOpenCodePluginConfig(pluginPath = resolveOpenCodePluginPath()) {
|
|
53
|
+
return { plugin: [(0, url_1.pathToFileURL)(pluginPath).href] };
|
|
54
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type SqliteRow = Record<string, unknown>;
|
|
2
|
+
/** Run one read-only SELECT. Throws 'sqlite-unavailable' when no driver exists. */
|
|
3
|
+
export type SqliteQuery = (dbPath: string, sql: string) => SqliteRow[];
|
|
4
|
+
export type OpencodeTranscriptResult = {
|
|
5
|
+
ok: true;
|
|
6
|
+
content: string;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
reason: 'not-found' | 'too-large' | 'transcript-unavailable';
|
|
10
|
+
};
|
|
11
|
+
export interface OpencodeTranscriptDeps {
|
|
12
|
+
/** Root holding one dir per agent (default $WENRWA_OPENCODE_DATA_ROOT or /data/.opencode). */
|
|
13
|
+
dataRoot?: string;
|
|
14
|
+
existsSync?: (p: string) => boolean;
|
|
15
|
+
query?: SqliteQuery;
|
|
16
|
+
}
|
|
17
|
+
/** Where the hosted launch put this agent's OpenCode database. */
|
|
18
|
+
export declare function opencodeDbPath(agentId: string, deps?: OpencodeTranscriptDeps): string;
|
|
19
|
+
/** Default driver: node:sqlite (Node >= 22.5, the image runs 24), else the sqlite3 CLI. */
|
|
20
|
+
export declare const defaultSqliteQuery: SqliteQuery;
|
|
21
|
+
/** Pure: OpenCode message/part rows -> Claude-shaped jsonl lines. */
|
|
22
|
+
export declare function opencodeRowsToJsonl(sessionId: string, messages: SqliteRow[], parts: SqliteRow[]): string;
|
|
23
|
+
/** Read ONE agent's OpenCode transcript. `sessionId` pins a session; absent -> the newest root session. */
|
|
24
|
+
export declare function readOpencodeTranscript(agentId: string, sessionId: string | undefined, deps?: OpencodeTranscriptDeps): OpencodeTranscriptResult;
|