@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
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
/**
|
|
37
|
+
* oc-17 -- OpenCode transcript reads. The fixture DB uses the table shapes captured from
|
|
38
|
+
* opencode-ai@1.18.35 (session / message / part, JSON in `data`) and is built with the
|
|
39
|
+
* sqlite3 CLI; the suite skips (loudly) where that CLI is missing.
|
|
40
|
+
*/
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const claudeTranscriptReader_1 = require("../bridge/claudeTranscriptReader");
|
|
46
|
+
const opencodeTranscriptReader_1 = require("../bridge/opencodeTranscriptReader");
|
|
47
|
+
const HAVE_SQLITE = (0, child_process_1.spawnSync)('sqlite3', ['-version']).status === 0;
|
|
48
|
+
const AGENT = '22222222-2222-2222-2222-222222222222';
|
|
49
|
+
const OLD = 'ses_old111';
|
|
50
|
+
const NEW = 'ses_new222';
|
|
51
|
+
const SUB = 'ses_sub333';
|
|
52
|
+
const j = (o) => JSON.stringify(o).replace(/'/g, "''");
|
|
53
|
+
function makeRoot() {
|
|
54
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'oc17-db-'));
|
|
55
|
+
const db = (0, opencodeTranscriptReader_1.opencodeDbPath)(AGENT, { dataRoot: root });
|
|
56
|
+
fs.mkdirSync(path.dirname(db), { recursive: true });
|
|
57
|
+
const sql = `
|
|
58
|
+
CREATE TABLE session (id text PRIMARY KEY, parent_id text, title text NOT NULL, time_created integer NOT NULL, time_updated integer NOT NULL);
|
|
59
|
+
CREATE TABLE message (id text PRIMARY KEY, session_id text NOT NULL, time_created integer NOT NULL, time_updated integer NOT NULL, data text NOT NULL);
|
|
60
|
+
CREATE TABLE part (id text PRIMARY KEY, message_id text NOT NULL, session_id text NOT NULL, time_created integer NOT NULL, time_updated integer NOT NULL, data text NOT NULL);
|
|
61
|
+
INSERT INTO session VALUES ('${OLD}', NULL, 'old', 1, 10), ('${NEW}', NULL, 'new', 2, 50), ('${SUB}', '${NEW}', 'sub', 3, 90);
|
|
62
|
+
INSERT INTO message VALUES ('msg_a', '${OLD}', 5, 5, '${j({ role: 'user' })}');
|
|
63
|
+
INSERT INTO part VALUES ('prt_a', 'msg_a', '${OLD}', 5, 5, '${j({ type: 'text', text: 'old question' })}');
|
|
64
|
+
INSERT INTO message VALUES ('msg_1', '${NEW}', 1791671762759, 1, '${j({ role: 'user' })}');
|
|
65
|
+
INSERT INTO part VALUES ('prt_1', 'msg_1', '${NEW}', 1, 1, '${j({ type: 'text', text: "it's RUNTOOL please" })}');
|
|
66
|
+
INSERT INTO message VALUES ('msg_2', '${NEW}', 1791671762800, 1, '${j({ role: 'assistant' })}');
|
|
67
|
+
INSERT INTO part VALUES ('prt_2a', 'msg_2', '${NEW}', 1, 1, '${j({ type: 'step-start' })}');
|
|
68
|
+
INSERT INTO part VALUES ('prt_2b', 'msg_2', '${NEW}', 1, 1, '${j({ type: 'reasoning', text: 'hidden thoughts' })}');
|
|
69
|
+
INSERT INTO part VALUES ('prt_2c', 'msg_2', '${NEW}', 1, 1, '${j({ type: 'tool', tool: 'bash', callID: 'call_1', state: { status: 'completed', input: { command: 'echo hi' }, output: 'hi\n' } })}');
|
|
70
|
+
INSERT INTO part VALUES ('prt_2d', 'msg_2', '${NEW}', 1, 1, '${j({ type: 'tool', tool: 'read', callID: 'call_2', state: { status: 'error', input: { filePath: '/x' }, error: 'ENOENT' } })}');
|
|
71
|
+
INSERT INTO message VALUES ('msg_3', '${NEW}', 1791671762900, 1, '${j({ role: 'assistant' })}');
|
|
72
|
+
INSERT INTO part VALUES ('prt_3', 'msg_3', '${NEW}', 1, 1, '${j({ type: 'text', text: 'Done.' })}');
|
|
73
|
+
INSERT INTO message VALUES ('msg_s', '${SUB}', 9, 9, '${j({ role: 'user' })}');
|
|
74
|
+
INSERT INTO part VALUES ('prt_s', 'msg_s', '${SUB}', 9, 9, '${j({ type: 'text', text: 'subagent chatter' })}');`;
|
|
75
|
+
(0, child_process_1.execFileSync)('sqlite3', [db, sql]);
|
|
76
|
+
return root;
|
|
77
|
+
}
|
|
78
|
+
const lines = (s) => s.trim().split('\n').map((l) => JSON.parse(l));
|
|
79
|
+
const d = HAVE_SQLITE ? describe : describe.skip;
|
|
80
|
+
d('readOpencodeTranscript (oc-17)', () => {
|
|
81
|
+
let root;
|
|
82
|
+
beforeAll(() => { root = makeRoot(); });
|
|
83
|
+
it('reads the newest root session as Claude-shaped jsonl (text, tool_use, tool_result; no reasoning, no sub-agents)', () => {
|
|
84
|
+
const r = (0, opencodeTranscriptReader_1.readOpencodeTranscript)(AGENT, undefined, { dataRoot: root });
|
|
85
|
+
expect(r.ok).toBe(true);
|
|
86
|
+
const out = lines(r.content);
|
|
87
|
+
expect(out.map((l) => l.type)).toEqual(['user', 'assistant', 'user', 'assistant']);
|
|
88
|
+
expect(out[0].message).toEqual({ role: 'user', content: [{ type: 'text', text: "it's RUNTOOL please" }] });
|
|
89
|
+
expect(out[0].sessionId).toBe(NEW);
|
|
90
|
+
expect(out[0].timestamp).toBe(new Date(1791671762759).toISOString());
|
|
91
|
+
expect(out[1].message.content).toEqual([
|
|
92
|
+
{ type: 'tool_use', id: 'call_1', name: 'bash', input: { command: 'echo hi' } },
|
|
93
|
+
{ type: 'tool_use', id: 'call_2', name: 'read', input: { filePath: '/x' } },
|
|
94
|
+
]);
|
|
95
|
+
expect(out[2].message.content).toEqual([
|
|
96
|
+
{ type: 'tool_result', tool_use_id: 'call_1', content: 'hi\n' },
|
|
97
|
+
{ type: 'tool_result', tool_use_id: 'call_2', is_error: true, content: 'ENOENT' },
|
|
98
|
+
]);
|
|
99
|
+
expect(out[3].message.content).toEqual([{ type: 'text', text: 'Done.' }]);
|
|
100
|
+
expect(r.content).not.toMatch(/hidden thoughts|subagent chatter|old question/);
|
|
101
|
+
});
|
|
102
|
+
it('reads a pinned session id', () => {
|
|
103
|
+
const r = (0, opencodeTranscriptReader_1.readOpencodeTranscript)(AGENT, OLD, { dataRoot: root });
|
|
104
|
+
expect(lines(r.content)[0].message.content[0].text).toBe('old question');
|
|
105
|
+
});
|
|
106
|
+
it('refuses a session id that is not ses_<alnum> (no SQL injection) and an unknown one', () => {
|
|
107
|
+
expect((0, opencodeTranscriptReader_1.readOpencodeTranscript)(AGENT, "x' OR '1'='1", { dataRoot: root })).toEqual({ ok: false, reason: 'not-found' });
|
|
108
|
+
expect((0, opencodeTranscriptReader_1.readOpencodeTranscript)(AGENT, 'ses_nope', { dataRoot: root })).toEqual({ ok: false, reason: 'not-found' });
|
|
109
|
+
});
|
|
110
|
+
it('is not-found when the agent has no database', () => {
|
|
111
|
+
expect((0, opencodeTranscriptReader_1.readOpencodeTranscript)('33333333-3333-3333-3333-333333333333', undefined, { dataRoot: root }))
|
|
112
|
+
.toEqual({ ok: false, reason: 'not-found' });
|
|
113
|
+
});
|
|
114
|
+
it('never writes: the database file is byte-identical after a read', () => {
|
|
115
|
+
const db = (0, opencodeTranscriptReader_1.opencodeDbPath)(AGENT, { dataRoot: root });
|
|
116
|
+
const before = fs.readFileSync(db);
|
|
117
|
+
(0, opencodeTranscriptReader_1.readOpencodeTranscript)(AGENT, undefined, { dataRoot: root });
|
|
118
|
+
expect(fs.readFileSync(db).equals(before)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
it('the default driver answers a plain SELECT', () => {
|
|
121
|
+
const db = (0, opencodeTranscriptReader_1.opencodeDbPath)(AGENT, { dataRoot: root });
|
|
122
|
+
expect((0, opencodeTranscriptReader_1.defaultSqliteQuery)(db, 'SELECT count(*) AS n FROM session')).toEqual([{ n: 3 }]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('readAgentTranscript engine dispatch (oc-17)', () => {
|
|
126
|
+
const noClaude = { existsSync: (p) => p.endsWith('opencode.db'), env: { CLAUDE_CONFIG_DIR: undefined }, homedir: () => '/root' };
|
|
127
|
+
it('falls through to the OpenCode reader when there is no Claude jsonl but an OpenCode db', () => {
|
|
128
|
+
const query = jest.fn()
|
|
129
|
+
.mockReturnValueOnce([{ id: 'ses_abc' }])
|
|
130
|
+
.mockReturnValueOnce([{ id: 'm1', time_created: 1, data: '{"role":"user"}' }])
|
|
131
|
+
.mockReturnValueOnce([{ message_id: 'm1', data: '{"type":"text","text":"hi"}' }]);
|
|
132
|
+
const r = (0, claudeTranscriptReader_1.readAgentTranscript)('/data/repos', 'ws-1', AGENT, { ...noClaude, opencode: { dataRoot: '/d', query } });
|
|
133
|
+
expect(r.ok && JSON.parse(r.content.trim()).message.content[0].text).toBe('hi');
|
|
134
|
+
});
|
|
135
|
+
it('engine: "opencode" skips the Claude file even when one exists', () => {
|
|
136
|
+
const query = jest.fn().mockReturnValue([]);
|
|
137
|
+
const r = (0, claudeTranscriptReader_1.readAgentTranscript)('/data/repos', 'ws-1', AGENT, {
|
|
138
|
+
existsSync: () => true, engine: 'opencode', opencode: { dataRoot: '/d', query },
|
|
139
|
+
});
|
|
140
|
+
expect(r).toEqual({ ok: false, reason: 'not-found' });
|
|
141
|
+
expect(query).toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
it('engine: "claude_code" never probes the OpenCode db', () => {
|
|
144
|
+
const query = jest.fn();
|
|
145
|
+
const r = (0, claudeTranscriptReader_1.readAgentTranscript)('/data/repos', 'ws-1', AGENT, {
|
|
146
|
+
...noClaude, engine: 'claude_code', opencode: { dataRoot: '/d', query },
|
|
147
|
+
});
|
|
148
|
+
expect(r).toEqual({ ok: false, reason: 'not-found' });
|
|
149
|
+
expect(query).not.toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
it('answers transcript-unavailable when no sqlite driver exists', () => {
|
|
152
|
+
const query = () => { throw new Error('sqlite-unavailable'); };
|
|
153
|
+
const r = (0, claudeTranscriptReader_1.readAgentTranscript)('/data/repos', 'ws-1', AGENT, { ...noClaude, opencode: { dataRoot: '/d', query } });
|
|
154
|
+
expect(r).toEqual({ ok: false, reason: 'transcript-unavailable' });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -11,6 +11,13 @@ export interface ActivityRecord {
|
|
|
11
11
|
* a record written before this field existed, or any malformed value.
|
|
12
12
|
*/
|
|
13
13
|
tool?: string;
|
|
14
|
+
/**
|
|
15
|
+
* oc-22 — ONLY ever 'needs_you' (the OpenCode plugin's permission/question
|
|
16
|
+
* prompt). Every other kind the hooks write ('tool', 'stop') is deliberately
|
|
17
|
+
* left off: the outward frame for Claude Code stays byte-identical, and the
|
|
18
|
+
* relay still tells a stop from a tool use by shape.
|
|
19
|
+
*/
|
|
20
|
+
kind?: 'needs_you';
|
|
14
21
|
}
|
|
15
22
|
export interface ActivityTailOpts {
|
|
16
23
|
/** Path to the JSONL file the local hooks append to. */
|
|
@@ -105,6 +105,7 @@ function startActivityTail(opts) {
|
|
|
105
105
|
...(Array.isArray(rec.filePaths) && rec.filePaths.length ? { filePaths: rec.filePaths } : {}),
|
|
106
106
|
...(typeof rec.thinking === 'string' ? { thinking: rec.thinking } : {}),
|
|
107
107
|
...(typeof rec.tool === 'string' ? { tool: rec.tool } : {}),
|
|
108
|
+
...(rec.kind === 'needs_you' ? { kind: 'needs_you' } : {}),
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
catch {
|
|
@@ -78,7 +78,35 @@ export interface SessionPin {
|
|
|
78
78
|
* (claudeSessionHistory.ts's hasAgentSessionHistory). */
|
|
79
79
|
exists: boolean;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* oc-08 — what the SERVER delivers with a session start for a local OpenCode
|
|
83
|
+
* agent (never the OpenRouter key; the token is the per-agent gateway token,
|
|
84
|
+
* ADR-0013). Absent / engine !== 'opencode' → the Claude path, byte-identical.
|
|
85
|
+
*/
|
|
86
|
+
export interface OpencodeLaunchInput {
|
|
87
|
+
engine?: string;
|
|
88
|
+
model?: string | null;
|
|
89
|
+
/** Full OpenAI-compatible base URL of the metering gateway. */
|
|
90
|
+
gatewayUrl?: string | null;
|
|
91
|
+
gatewayToken?: string | null;
|
|
92
|
+
/** OpenCode's own captured session id (`ses_...`); absent on first launch. */
|
|
93
|
+
sessionId?: string | null;
|
|
94
|
+
}
|
|
95
|
+
/** The message shown when a launch command is not usable on this machine. */
|
|
96
|
+
export declare function missingCommandMessage(command: string): string;
|
|
81
97
|
export declare function resolveAgentLaunch(opts: {
|
|
98
|
+
/** oc-08 — engine-aware launch; see OpencodeLaunchInput. */
|
|
99
|
+
opencode?: OpencodeLaunchInput;
|
|
100
|
+
/** Connect token (WENRWA_CONNECT_TOKEN) — adds the MCP server for opencode. */
|
|
101
|
+
connectToken?: string;
|
|
102
|
+
/** oc-07 — the installed MCP server (mcpInstall.ts), the same absolute
|
|
103
|
+
* command mcpRegister gives Claude. Absent → npx fallback. */
|
|
104
|
+
mcpServer?: {
|
|
105
|
+
command: string;
|
|
106
|
+
args: string[];
|
|
107
|
+
};
|
|
108
|
+
/** oc-07 — MARKETPLACE_API_URL for the MCP server (as mcpRegister passes it). */
|
|
109
|
+
apiUrl?: string;
|
|
82
110
|
autonomous?: boolean;
|
|
83
111
|
activity?: AgentActivityWiring;
|
|
84
112
|
sessionPin?: SessionPin;
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HIVEOP_CONTEXT_ENV_VALUE = exports.HIVEOP_CONTEXT_ENV_VAR = void 0;
|
|
4
|
+
exports.missingCommandMessage = missingCommandMessage;
|
|
4
5
|
exports.resolveAgentLaunch = resolveAgentLaunch;
|
|
5
6
|
const localContextWriter_1 = require("./localContextWriter");
|
|
7
|
+
const opencodeConfig_1 = require("./opencodeConfig");
|
|
6
8
|
/** lws-08 — env var name a spawned `claude` (or any tooling inspecting its
|
|
7
9
|
* own environment) can read to find the pushed context manifest. */
|
|
8
10
|
exports.HIVEOP_CONTEXT_ENV_VAR = 'HIVEOP_CONTEXT_FILE';
|
|
9
11
|
/** lws-08 — re-exported from localContextWriter.ts (the single source of
|
|
10
12
|
* truth for where the manifest actually lands) so this can never drift. */
|
|
11
13
|
exports.HIVEOP_CONTEXT_ENV_VALUE = localContextWriter_1.LOCAL_CONTEXT_RELATIVE_PATH;
|
|
14
|
+
/** The message shown when a launch command is not usable on this machine. */
|
|
15
|
+
function missingCommandMessage(command) {
|
|
16
|
+
return command === 'opencode'
|
|
17
|
+
? `opencode not found on PATH (or older than the minimum) — ${opencodeConfig_1.OPENCODE_INSTALL_HINT}`
|
|
18
|
+
: `${command} not found on PATH — install Claude Code CLI on this machine`;
|
|
19
|
+
}
|
|
12
20
|
function resolveAgentLaunch(opts) {
|
|
21
|
+
const oc = opts.opencode;
|
|
22
|
+
if (oc && oc.engine === 'opencode' && oc.model && oc.gatewayUrl && oc.gatewayToken) {
|
|
23
|
+
return resolveOpencodeLaunch(opts, { model: oc.model, gatewayUrl: oc.gatewayUrl, gatewayToken: oc.gatewayToken, sessionId: oc.sessionId });
|
|
24
|
+
}
|
|
13
25
|
const args = opts.autonomous ? ['--dangerously-skip-permissions'] : [];
|
|
14
26
|
if (opts.activity)
|
|
15
27
|
args.push('--settings', opts.activity.settingsPath);
|
|
@@ -27,3 +39,40 @@ function resolveAgentLaunch(opts) {
|
|
|
27
39
|
env.WENRWA_WORKSPACE_ID = opts.workspaceId;
|
|
28
40
|
return { command: 'claude', args, env };
|
|
29
41
|
}
|
|
42
|
+
/** oc-08 — the OpenCode branch. Resume is `-s <captured id>` only; the id is
|
|
43
|
+
* captured from the first run, never chosen (oc-04 s3). No Claude flags, no
|
|
44
|
+
* --settings hook file: activity for OpenCode rides its plugin (oc-06). */
|
|
45
|
+
function resolveOpencodeLaunch(opts, oc) {
|
|
46
|
+
const args = [];
|
|
47
|
+
if ((0, opencodeConfig_1.isCapturedOpencodeSessionId)(oc.sessionId))
|
|
48
|
+
args.push('-s', oc.sessionId);
|
|
49
|
+
const config = (0, opencodeConfig_1.buildOpencodeConfig)({
|
|
50
|
+
model: oc.model,
|
|
51
|
+
gatewayBaseUrl: oc.gatewayUrl,
|
|
52
|
+
autonomous: !!opts.autonomous,
|
|
53
|
+
withMcp: !!opts.connectToken,
|
|
54
|
+
// oc-23: Claude loads .hiveop/context.md via its SessionStart hook; OpenCode has none, so it reads it as instructions.
|
|
55
|
+
instructions: [exports.HIVEOP_CONTEXT_ENV_VALUE],
|
|
56
|
+
...(opts.mcpServer ? { mcpCommand: [opts.mcpServer.command, ...opts.mcpServer.args] } : {}),
|
|
57
|
+
...(opts.apiUrl ? { mcpEnvPassthrough: ['MARKETPLACE_API_URL'] } : {}),
|
|
58
|
+
});
|
|
59
|
+
const env = {
|
|
60
|
+
[exports.HIVEOP_CONTEXT_ENV_VAR]: exports.HIVEOP_CONTEXT_ENV_VALUE,
|
|
61
|
+
...opencodeConfig_1.OPENCODE_QUIET_ENV,
|
|
62
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
|
|
63
|
+
[opencodeConfig_1.OPENCODE_GATEWAY_TOKEN_ENV]: oc.gatewayToken,
|
|
64
|
+
};
|
|
65
|
+
if (opts.connectToken)
|
|
66
|
+
env.WENRWA_CONNECT_TOKEN = opts.connectToken;
|
|
67
|
+
if (opts.connectToken && opts.apiUrl)
|
|
68
|
+
env.MARKETPLACE_API_URL = opts.apiUrl;
|
|
69
|
+
if (opts.activity) {
|
|
70
|
+
env.WENRWA_SESSION_ID = opts.activity.sessionId;
|
|
71
|
+
env.HIVEOP_ACTIVITY_FILE = opts.activity.activityFile;
|
|
72
|
+
}
|
|
73
|
+
if (opts.sessionPin)
|
|
74
|
+
env.WENRWA_AGENT_ID = opts.sessionPin.agentId;
|
|
75
|
+
if (opts.workspaceId)
|
|
76
|
+
env.WENRWA_WORKSPACE_ID = opts.workspaceId;
|
|
77
|
+
return { command: 'opencode', args, env };
|
|
78
|
+
}
|
|
@@ -39,6 +39,20 @@ export declare function attachmentRootFor(sessions: {
|
|
|
39
39
|
list(): string[];
|
|
40
40
|
cwdOf(id: string): string | undefined;
|
|
41
41
|
}, sessionId: unknown): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* oc-16 — what to type into the PTY after the file is written, per engine.
|
|
44
|
+
* Claude Code: nothing (the pane types the path itself; bytes unchanged).
|
|
45
|
+
* OpenCode: the path as a BRACKETED PASTE with no Enter — its TUI detects an
|
|
46
|
+
* image file path on the paste event (a bare typed path is not guaranteed to
|
|
47
|
+
* attach) and the user keeps typing. Spaces are backslash-escaped.
|
|
48
|
+
*/
|
|
49
|
+
export declare function pastePayloadFor(engine: string | undefined, filePath: string): string | null;
|
|
50
|
+
/** Paste the path into an OpenCode session's prompt; true when it was written
|
|
51
|
+
* (`pasted` in the reply tells the pane not to type the path itself). */
|
|
52
|
+
export declare function pasteIfOpencode(sessions: {
|
|
53
|
+
engineOf?(id: string): string | undefined;
|
|
54
|
+
writeTo?(id: string, data: string): boolean;
|
|
55
|
+
}, sessionId: string, filePath: string): boolean;
|
|
42
56
|
/**
|
|
43
57
|
* The whole `attach-file` frame → `file-attached` reply, shared verbatim by
|
|
44
58
|
* the local server and the relay client so the two lanes cannot drift.
|
|
@@ -51,4 +65,6 @@ export declare function handleAttachFrame(frame: {
|
|
|
51
65
|
}, sessions: {
|
|
52
66
|
list(): string[];
|
|
53
67
|
cwdOf(id: string): string | undefined;
|
|
68
|
+
engineOf?(id: string): string | undefined;
|
|
69
|
+
writeTo?(id: string, data: string): boolean;
|
|
54
70
|
}, deps?: AttachmentDeps): Record<string, unknown>;
|
|
@@ -38,6 +38,8 @@ exports.sniffImageExt = sniffImageExt;
|
|
|
38
38
|
exports.safeAttachmentStem = safeAttachmentStem;
|
|
39
39
|
exports.writeAttachment = writeAttachment;
|
|
40
40
|
exports.attachmentRootFor = attachmentRootFor;
|
|
41
|
+
exports.pastePayloadFor = pastePayloadFor;
|
|
42
|
+
exports.pasteIfOpencode = pasteIfOpencode;
|
|
41
43
|
exports.handleAttachFrame = handleAttachFrame;
|
|
42
44
|
/**
|
|
43
45
|
* Terminal attach — save a screenshot NEXT TO the agent that will read it.
|
|
@@ -154,6 +156,24 @@ function attachmentRootFor(sessions, sessionId) {
|
|
|
154
156
|
return null;
|
|
155
157
|
return sessions.cwdOf(sessionId) ?? os.homedir();
|
|
156
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* oc-16 — what to type into the PTY after the file is written, per engine.
|
|
161
|
+
* Claude Code: nothing (the pane types the path itself; bytes unchanged).
|
|
162
|
+
* OpenCode: the path as a BRACKETED PASTE with no Enter — its TUI detects an
|
|
163
|
+
* image file path on the paste event (a bare typed path is not guaranteed to
|
|
164
|
+
* attach) and the user keeps typing. Spaces are backslash-escaped.
|
|
165
|
+
*/
|
|
166
|
+
function pastePayloadFor(engine, filePath) {
|
|
167
|
+
if (engine !== 'opencode')
|
|
168
|
+
return null;
|
|
169
|
+
return `\x1b[200~${filePath.replace(/ /g, '\\ ')}\x1b[201~`;
|
|
170
|
+
}
|
|
171
|
+
/** Paste the path into an OpenCode session's prompt; true when it was written
|
|
172
|
+
* (`pasted` in the reply tells the pane not to type the path itself). */
|
|
173
|
+
function pasteIfOpencode(sessions, sessionId, filePath) {
|
|
174
|
+
const paste = pastePayloadFor(sessions.engineOf?.(sessionId), filePath);
|
|
175
|
+
return paste !== null && sessions.writeTo?.(sessionId, paste) === true;
|
|
176
|
+
}
|
|
157
177
|
/**
|
|
158
178
|
* The whole `attach-file` frame → `file-attached` reply, shared verbatim by
|
|
159
179
|
* the local server and the relay client so the two lanes cannot drift.
|
|
@@ -168,5 +188,8 @@ function handleAttachFrame(frame, sessions, deps = exports.realAttachmentDeps) {
|
|
|
168
188
|
if (!root)
|
|
169
189
|
return { ...head, ok: false, reason: 'no-session' };
|
|
170
190
|
const result = writeAttachment({ root, name: typeof frame.name === 'string' ? frame.name : 'image', dataBase64: typeof frame.data === 'string' ? frame.data : '' }, deps);
|
|
171
|
-
|
|
191
|
+
if (!result.ok)
|
|
192
|
+
return { ...head, ok: false, reason: result.reason };
|
|
193
|
+
const pasted = pasteIfOpencode(sessions, frame.sessionId, result.path);
|
|
194
|
+
return { ...head, ok: true, path: result.path, bytes: result.bytes, ...(pasted ? { pasted: true } : {}) };
|
|
172
195
|
}
|
|
@@ -27,6 +27,8 @@ export interface BundleInstallerDeps {
|
|
|
27
27
|
readFile?: (filePath: string) => string;
|
|
28
28
|
remove?: (filePath: string) => void;
|
|
29
29
|
}
|
|
30
|
+
/** oc-18 — which engine's file layout this bundle is installed as. */
|
|
31
|
+
export type BundleEngine = 'claude' | 'opencode';
|
|
30
32
|
export type SkipReason = 'unsafe' | 'unmanaged';
|
|
31
33
|
export interface InstallBundleResult {
|
|
32
34
|
/** The absolute workspace checkout root, or null if the workspaceId itself was unsafe. */
|
|
@@ -53,4 +55,5 @@ export declare function isSafeWorkspaceId(workspaceId: string): boolean;
|
|
|
53
55
|
export declare function installBundle(bundle: {
|
|
54
56
|
workspaceId: string;
|
|
55
57
|
files: FileMap;
|
|
58
|
+
engine?: BundleEngine | string;
|
|
56
59
|
}, deps: BundleInstallerDeps): InstallBundleResult;
|
|
@@ -65,6 +65,7 @@ exports.installBundle = installBundle;
|
|
|
65
65
|
* the crew files.
|
|
66
66
|
*/
|
|
67
67
|
const path = __importStar(require("path"));
|
|
68
|
+
const crewTranslator_1 = require("./crewTranslator");
|
|
68
69
|
/**
|
|
69
70
|
* The marker every forged crew member carries. Its presence is what
|
|
70
71
|
* distinguishes a file this installer put there from one the pilot wrote by
|
|
@@ -89,11 +90,24 @@ function isSafeWorkspaceId(workspaceId) {
|
|
|
89
90
|
workspaceId !== '.' &&
|
|
90
91
|
workspaceId !== '..');
|
|
91
92
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Only the crew-managed surface is writable: `.claude/**` + `CLAUDE.md`, plus
|
|
95
|
+
* (oc-18, engine=opencode only) `.opencode/agent/**` + `AGENTS.md`. The widening
|
|
96
|
+
* is per-call: a Claude install can never write OpenCode paths and vice versa.
|
|
97
|
+
*/
|
|
98
|
+
function isManaged(normalizedRel, engine) {
|
|
99
|
+
if (normalizedRel === 'CLAUDE.md' || normalizedRel.startsWith('.claude/'))
|
|
100
|
+
return true;
|
|
101
|
+
return engine === 'opencode' && (normalizedRel === 'AGENTS.md' || normalizedRel.startsWith(`${crewTranslator_1.OPENCODE_AGENT_DIR}/`));
|
|
95
102
|
}
|
|
96
103
|
function installBundle(bundle, deps) {
|
|
104
|
+
// oc-18: anything but an explicit 'opencode' is the Claude path, unchanged.
|
|
105
|
+
const engine = bundle.engine === 'opencode' ? 'opencode' : 'claude';
|
|
106
|
+
const agentsDirRel = engine === 'opencode' ? crewTranslator_1.OPENCODE_AGENT_DIR : AGENTS_DIR_REL;
|
|
107
|
+
// Translate BEFORE the path checks so the translated paths are the ones vetted.
|
|
108
|
+
const sourceFiles = engine === 'opencode'
|
|
109
|
+
? (0, crewTranslator_1.translateCrewBundleForOpencode)(bundle.files ?? {})
|
|
110
|
+
: (bundle.files ?? {});
|
|
97
111
|
const written = [];
|
|
98
112
|
const skipped = [];
|
|
99
113
|
const pruned = [];
|
|
@@ -106,7 +120,7 @@ function installBundle(bundle, deps) {
|
|
|
106
120
|
const workspaceRoot = deps.workspaceRoot
|
|
107
121
|
? path.posix.resolve(deps.workspaceRoot)
|
|
108
122
|
: path.posix.resolve(deps.reposBaseDir, bundle.workspaceId);
|
|
109
|
-
for (const [rawRel, content] of Object.entries(
|
|
123
|
+
for (const [rawRel, content] of Object.entries(sourceFiles)) {
|
|
110
124
|
// Reject obvious escapes up front: empty, null byte, absolute (posix or
|
|
111
125
|
// Windows-drive). Absolute paths would make path.posix.resolve ignore the
|
|
112
126
|
// workspace root entirely.
|
|
@@ -130,7 +144,7 @@ function installBundle(bundle, deps) {
|
|
|
130
144
|
continue;
|
|
131
145
|
}
|
|
132
146
|
// Managed surface only.
|
|
133
|
-
if (!isManaged(normalized)) {
|
|
147
|
+
if (!isManaged(normalized, engine)) {
|
|
134
148
|
skipped.push({ path: rawRel, reason: 'unmanaged' });
|
|
135
149
|
continue;
|
|
136
150
|
}
|
|
@@ -157,14 +171,14 @@ function installBundle(bundle, deps) {
|
|
|
157
171
|
- and any error reading the directory abandons pruning entirely — an
|
|
158
172
|
install that wrote its files must not fail because cleanup could not.
|
|
159
173
|
*/
|
|
160
|
-
const installedAgents = new Set(written.filter((rel) => rel.startsWith(`${
|
|
174
|
+
const installedAgents = new Set(written.filter((rel) => rel.startsWith(`${agentsDirRel}/`)));
|
|
161
175
|
if (installedAgents.size > 0 && deps.listDir && deps.readFile && deps.remove) {
|
|
162
|
-
const agentsDir = path.posix.resolve(workspaceRoot,
|
|
176
|
+
const agentsDir = path.posix.resolve(workspaceRoot, agentsDirRel);
|
|
163
177
|
try {
|
|
164
178
|
for (const name of deps.listDir(agentsDir)) {
|
|
165
179
|
if (!name.endsWith('.md'))
|
|
166
180
|
continue;
|
|
167
|
-
const rel = `${
|
|
181
|
+
const rel = `${agentsDirRel}/${name}`;
|
|
168
182
|
if (installedAgents.has(rel))
|
|
169
183
|
continue;
|
|
170
184
|
const full = path.posix.resolve(agentsDir, name);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ClaudeSessionHistoryDeps } from './claudeSessionHistory';
|
|
2
|
+
import { type OpencodeTranscriptDeps } from './opencodeTranscriptReader';
|
|
2
3
|
/** 20MB — generous for a jsonl transcript (a real multi-hour session
|
|
3
4
|
* comfortably fits) while still refusing a runaway file rather than
|
|
4
5
|
* holding it all in memory and shipping it down one WS frame. */
|
|
@@ -8,12 +9,17 @@ export type ReadAgentTranscriptResult = {
|
|
|
8
9
|
content: string;
|
|
9
10
|
} | {
|
|
10
11
|
ok: false;
|
|
11
|
-
reason: 'not-found' | 'invalid-agent-id' | 'too-large';
|
|
12
|
+
reason: 'not-found' | 'invalid-agent-id' | 'too-large' | 'transcript-unavailable';
|
|
12
13
|
};
|
|
13
14
|
export interface ClaudeTranscriptReaderDeps extends ClaudeSessionHistoryDeps {
|
|
14
15
|
existsSync?: (p: string) => boolean;
|
|
15
16
|
statSize?: (p: string) => number;
|
|
16
17
|
readFileUtf8?: (p: string) => string;
|
|
18
|
+
/** oc-17: which reader. Absent -> Claude's jsonl when present, else the agent's OpenCode DB. */
|
|
19
|
+
engine?: 'claude_code' | 'opencode';
|
|
20
|
+
/** oc-17: OpenCode session id to read (absent -> the newest root session). */
|
|
21
|
+
opencodeSessionId?: string;
|
|
22
|
+
opencode?: OpencodeTranscriptDeps;
|
|
17
23
|
}
|
|
18
24
|
/** Read ONE agent's transcript out of `<reposBaseDir>/<workspaceId>`'s
|
|
19
25
|
* Claude Code project directory. Never writes, never caches — a single
|
|
@@ -70,6 +70,7 @@ const fs = __importStar(require("fs"));
|
|
|
70
70
|
const path = __importStar(require("path"));
|
|
71
71
|
const claudeSessionHistory_1 = require("./claudeSessionHistory");
|
|
72
72
|
const bundleInstaller_1 = require("./bundleInstaller");
|
|
73
|
+
const opencodeTranscriptReader_1 = require("./opencodeTranscriptReader");
|
|
73
74
|
/** 20MB — generous for a jsonl transcript (a real multi-hour session
|
|
74
75
|
* comfortably fits) while still refusing a runaway file rather than
|
|
75
76
|
* holding it all in memory and shipping it down one WS frame. */
|
|
@@ -101,8 +102,17 @@ function readAgentTranscript(reposBaseDir, workspaceId, agentId, deps = {}) {
|
|
|
101
102
|
const workspaceRoot = path.posix.resolve(reposBaseDir, workspaceId);
|
|
102
103
|
const projectDir = path.join((0, claudeSessionHistory_1.claudeConfigDir)(deps), 'projects', (0, claudeSessionHistory_1.claudeProjectSlug)(workspaceRoot));
|
|
103
104
|
const transcriptPath = path.join(projectDir, `${agentId}.jsonl`);
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
const readOpencode = () => (0, opencodeTranscriptReader_1.readOpencodeTranscript)(agentId, deps.opencodeSessionId, { existsSync, ...deps.opencode });
|
|
106
|
+
if (deps.engine === 'opencode')
|
|
107
|
+
return readOpencode();
|
|
108
|
+
if (!existsSync(transcriptPath)) {
|
|
109
|
+
if (deps.engine === 'claude_code')
|
|
110
|
+
return { ok: false, reason: 'not-found' };
|
|
111
|
+
// oc-17: no Claude jsonl -- an OpenCode agent's history lives in its own DB.
|
|
112
|
+
return existsSync((0, opencodeTranscriptReader_1.opencodeDbPath)(agentId, { existsSync, ...deps.opencode }))
|
|
113
|
+
? readOpencode()
|
|
114
|
+
: { ok: false, reason: 'not-found' };
|
|
115
|
+
}
|
|
106
116
|
let size;
|
|
107
117
|
try {
|
|
108
118
|
size = statSize(transcriptPath);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oc-23 — which project-instructions file an engine reads. ONE mapping, shared by
|
|
3
|
+
* every writer that puts HiveOp context into a checkout.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code reads CLAUDE.md. OpenCode reads AGENTS.md, and falls back to
|
|
6
|
+
* CLAUDE.md only while AGENTS.md is absent (oc-18 notes), so a region written
|
|
7
|
+
* to CLAUDE.md alone is invisible to an OpenCode agent the moment a crew
|
|
8
|
+
* bundle or anything else creates AGENTS.md.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CLAUDE_CONTEXT_FILE = "CLAUDE.md";
|
|
11
|
+
export declare const OPENCODE_CONTEXT_FILE = "AGENTS.md";
|
|
12
|
+
export type ContextFile = typeof CLAUDE_CONTEXT_FILE | typeof OPENCODE_CONTEXT_FILE;
|
|
13
|
+
/** The signal the launch already carries: `WENRWA_AGENT_ENGINE` / the relay `opencode` block. Unknown = Claude. */
|
|
14
|
+
export declare function contextFileFor(engine: string | null | undefined): ContextFile;
|
|
15
|
+
/**
|
|
16
|
+
* Files to write for one engine's context: its own, plus the sibling when that
|
|
17
|
+
* already exists. One checkout can hold a Claude agent and an OpenCode agent;
|
|
18
|
+
* a file nobody has asked for is never created, but one that exists stays current.
|
|
19
|
+
*/
|
|
20
|
+
export declare function contextTargets(engine: string | null | undefined, exists: (file: ContextFile) => boolean): ContextFile[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OPENCODE_CONTEXT_FILE = exports.CLAUDE_CONTEXT_FILE = void 0;
|
|
4
|
+
exports.contextFileFor = contextFileFor;
|
|
5
|
+
exports.contextTargets = contextTargets;
|
|
6
|
+
/**
|
|
7
|
+
* oc-23 — which project-instructions file an engine reads. ONE mapping, shared by
|
|
8
|
+
* every writer that puts HiveOp context into a checkout.
|
|
9
|
+
*
|
|
10
|
+
* Claude Code reads CLAUDE.md. OpenCode reads AGENTS.md, and falls back to
|
|
11
|
+
* CLAUDE.md only while AGENTS.md is absent (oc-18 notes), so a region written
|
|
12
|
+
* to CLAUDE.md alone is invisible to an OpenCode agent the moment a crew
|
|
13
|
+
* bundle or anything else creates AGENTS.md.
|
|
14
|
+
*/
|
|
15
|
+
exports.CLAUDE_CONTEXT_FILE = 'CLAUDE.md';
|
|
16
|
+
exports.OPENCODE_CONTEXT_FILE = 'AGENTS.md';
|
|
17
|
+
/** The signal the launch already carries: `WENRWA_AGENT_ENGINE` / the relay `opencode` block. Unknown = Claude. */
|
|
18
|
+
function contextFileFor(engine) {
|
|
19
|
+
return engine === 'opencode' ? exports.OPENCODE_CONTEXT_FILE : exports.CLAUDE_CONTEXT_FILE;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Files to write for one engine's context: its own, plus the sibling when that
|
|
23
|
+
* already exists. One checkout can hold a Claude agent and an OpenCode agent;
|
|
24
|
+
* a file nobody has asked for is never created, but one that exists stays current.
|
|
25
|
+
*/
|
|
26
|
+
function contextTargets(engine, exists) {
|
|
27
|
+
const own = contextFileFor(engine);
|
|
28
|
+
const other = own === exports.CLAUDE_CONTEXT_FILE ? exports.OPENCODE_CONTEXT_FILE : exports.CLAUDE_CONTEXT_FILE;
|
|
29
|
+
return exists(other) ? [own, other] : [own];
|
|
30
|
+
}
|
|
@@ -18,8 +18,11 @@ export type ContextWriteAction = 'created' | 'replaced' | 'appended' | 'removed'
|
|
|
18
18
|
export interface ContextWriteResult {
|
|
19
19
|
/** The workspace checkout root, or null if the workspaceId was unsafe. */
|
|
20
20
|
workspaceRoot: string | null;
|
|
21
|
-
/** The CLAUDE.md
|
|
21
|
+
/** The engine's own context file (CLAUDE.md, or AGENTS.md for opencode), or null if nothing was written.
|
|
22
|
+
* Name kept for the wire contract; see `contextPaths` for every file touched. */
|
|
22
23
|
claudeMdPath: string | null;
|
|
24
|
+
/** oc-23 — every context file this write touched (the engine's own first). */
|
|
25
|
+
contextPaths?: string[];
|
|
23
26
|
/** What happened. `skipped` = unsafe workspaceId (nothing written). */
|
|
24
27
|
action: ContextWriteAction;
|
|
25
28
|
}
|
|
@@ -54,4 +57,10 @@ export declare function writeContext(input: {
|
|
|
54
57
|
* selected files or on a browser being connected at all.
|
|
55
58
|
*/
|
|
56
59
|
pointersOnly?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* oc-23 — the engine of the agent this write is for (`WENRWA_AGENT_ENGINE`).
|
|
62
|
+
* `opencode` targets AGENTS.md; anything else CLAUDE.md. A sibling file that
|
|
63
|
+
* already exists is kept in sync (see contextFile.contextTargets).
|
|
64
|
+
*/
|
|
65
|
+
engine?: string;
|
|
57
66
|
}, deps: ContextWriterDeps): ContextWriteResult;
|