@cotal-ai/connector-opencode 0.68.0 → 0.70.0
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/agui-map.d.ts +1 -14
- package/dist/agui-map.d.ts.map +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/index.js +16 -8
- package/dist/launch.d.ts +14 -0
- package/dist/launch.d.ts.map +1 -0
- package/dist/launch.js +195 -0
- package/dist/opencode-line.d.ts +1 -1
- package/dist/plugin/index.js +3506 -3458
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin2.d.ts.map +1 -1
- package/dist/serve.js +20 -10
- package/package.json +4 -4
package/dist/agui-map.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* holds no per-part state to drop: a part arrives once, complete, and is mapped in one shot. So the
|
|
21
21
|
* divergence log is the source's, and there is nothing here for a revert to invalidate.
|
|
22
22
|
*/
|
|
23
|
-
import { type
|
|
23
|
+
import { type RecordMapper } from "@cotal-ai/connector-core";
|
|
24
24
|
import type { OpenCodeRecord } from "./agui-source.js";
|
|
25
25
|
export interface OpenCodeMapperOptions {
|
|
26
26
|
/** The native session id. It is the `threadId` on every event and nothing else may claim it. */
|
|
@@ -34,25 +34,12 @@ export interface OpenCodeMapperOptions {
|
|
|
34
34
|
}
|
|
35
35
|
export interface OpenCodeMapper {
|
|
36
36
|
map: RecordMapper<OpenCodeRecord>;
|
|
37
|
-
/**
|
|
38
|
-
* Close the open run at a boundary the record stream cannot see: `session.idle`, which §3.2 makes
|
|
39
|
-
* the flush boundary. Returns `null` when nothing is open, so calling it twice cannot manufacture
|
|
40
|
-
* a second `RUN_FINISHED` for the bracket machine to refuse.
|
|
41
|
-
*/
|
|
42
|
-
closeOpenRun: (timestamp: number, stopReason?: string) => {
|
|
43
|
-
runId: string;
|
|
44
|
-
events: AguiEvent[];
|
|
45
|
-
} | null;
|
|
46
|
-
/** The run currently open, or `null`. */
|
|
47
|
-
openRun: () => string | null;
|
|
48
37
|
/**
|
|
49
38
|
* Forget a run the emitter closed out of band. KEYED ON THE ID: the report can arrive after this
|
|
50
39
|
* mapper has already opened a newer run, and clearing unconditionally would orphan that one, whose
|
|
51
40
|
* events would then emit under no run at all and halt a session that had done nothing wrong.
|
|
52
41
|
*/
|
|
53
42
|
forgetOpenRun: (runId: string) => void;
|
|
54
|
-
/** Why this session opened no runs, or `null` once one has. A silent refusal is the defect. */
|
|
55
|
-
diagnose: () => string | null;
|
|
56
43
|
}
|
|
57
44
|
export declare function createOpenCodeMapper(opts: OpenCodeMapperOptions): OpenCodeMapper;
|
|
58
45
|
//# sourceMappingURL=agui-map.d.ts.map
|
package/dist/agui-map.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-map.d.ts","sourceRoot":"","sources":["../src/agui-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"agui-map.d.ts","sourceRoot":"","sources":["../src/agui-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAEL,KAAK,YAAY,EAalB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kGAAkG;IAClG,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;IAClC;;;;OAIG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAkDD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc,CAuGhF"}
|
package/dist/extension.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Connector } from "@cotal-ai/core";
|
|
2
2
|
/**
|
|
3
3
|
* The OpenCode connector: launches a watchable `opencode` TUI bound to the agent's session, using
|
|
4
|
-
* OpenCode's client/server split (see
|
|
4
|
+
* OpenCode's client/server split (see launch.ts). The Cotal mesh bridge runs as an in-process plugin
|
|
5
5
|
* inside a headless `opencode serve`: it holds the {@link MeshAgent}, registers the cotal_* tools
|
|
6
6
|
* natively (from the shared specs, at parity with Claude Code), reports presence off the event bus,
|
|
7
7
|
* and owns ONE session it drives — injecting each incoming peer batch through the authenticated
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,13 @@ import { resolve } from "node:path";
|
|
|
11
11
|
import { registry as registry3 } from "@cotal-ai/core";
|
|
12
12
|
|
|
13
13
|
// ../connector-core/dist/config.js
|
|
14
|
-
import { DEFAULT_SERVER, DEV_OWNER, LAUNCH_MATERIAL_ENV, discardLaunchMaterial, assertLifecycleToken, assertValidChannel, channelInAllow, credsClaims, eventChannel, idFromCreds, isConcreteChannel, loadAgentFile, parseJoinLink, readLaunchMaterial } from "@cotal-ai/core";
|
|
14
|
+
import { DEFAULT_SERVER, DEV_OWNER, LAUNCH_MATERIAL_ENV, discardLaunchMaterial, assertLifecycleToken, assertValidChannel, channelInAllow, credsClaims, eventChannel, idFromCreds, isConcreteChannel, loadAgentFile, parseJoinLink, readLaunchMaterial, resolveReadAcl } from "@cotal-ai/core";
|
|
15
15
|
|
|
16
16
|
// ../connector-core/dist/agent.js
|
|
17
|
-
import { normalizeMentions, RUN_LAUNCH_DEADLINE_MS, subjectMatches, isConcreteChannel as isConcreteChannel2, assertValidChannel as assertValidChannel2, channelInAllow as channelInAllow2, resolvePeer as resolvePeerInRoster, CotalEndpoint, BASELINE_LIFECYCLE_ENDPOINT as BASELINE_LIFECYCLE_ENDPOINT2, BIND_SPLIT_REISSUES, assertLifecycleToken as assertLifecycleToken3, EpEnvelopeError, isPublishPermissionDenied, unansweredRequest, renderLifecycleBlocked, bearerCommandFailure, partsToText, routeToken } from "@cotal-ai/core";
|
|
17
|
+
import { normalizeMentions, RUN_LAUNCH_DEADLINE_MS, subjectMatches, isConcreteChannel as isConcreteChannel2, assertValidChannel as assertValidChannel2, channelInAllow as channelInAllow2, resolvePeer as resolvePeerInRoster, peerLabel as peerLabel2, CotalEndpoint, BASELINE_LIFECYCLE_ENDPOINT as BASELINE_LIFECYCLE_ENDPOINT2, BIND_SPLIT_REISSUES, assertLifecycleToken as assertLifecycleToken3, EpEnvelopeError, isPublishPermissionDenied, unansweredRequest, renderLifecycleBlocked, controlReplyFrom, bearerCommandFailure, partsToText, routeToken } from "@cotal-ai/core";
|
|
18
|
+
|
|
19
|
+
// ../connector-core/dist/framing.js
|
|
20
|
+
import { peerLabel } from "@cotal-ai/core";
|
|
18
21
|
|
|
19
22
|
// ../connector-core/dist/manager-call.js
|
|
20
23
|
import { BASELINE_LIFECYCLE_ENDPOINT, assertLifecycleToken as assertLifecycleToken2, dialerFor, invokeCommand, isPermissionDenied, issuedUserCaller, replyRefusedBeforeEffect, resolveService, standaloneConnectOpts } from "@cotal-ai/core";
|
|
@@ -178,14 +181,11 @@ function materialEnv(opts) {
|
|
|
178
181
|
// ../connector-core/dist/agui.js
|
|
179
182
|
import { AGUI_FRAME_KIND, AGUI_EVENT_TYPE, isAguiFramePart } from "@cotal-ai/core";
|
|
180
183
|
import { AGUI_FRAME_KIND as AGUI_FRAME_KIND2, AGUI_EVENT_TYPE as AGUI_EVENT_TYPE2, isAguiFramePart as isAguiFramePart2 } from "@cotal-ai/core";
|
|
181
|
-
import { isCasLoss, principalKey as principalKey2 } from "@cotal-ai/core";
|
|
182
184
|
var TRUNCATABLE_FIELDS = [
|
|
183
185
|
{ type: AGUI_EVENT_TYPE2.TOOL_CALL_ARGS, field: "delta" },
|
|
184
186
|
{ type: AGUI_EVENT_TYPE2.TOOL_CALL_RESULT, field: "content" },
|
|
185
187
|
{ type: AGUI_EVENT_TYPE2.TEXT_MESSAGE_CONTENT, field: "delta" }
|
|
186
188
|
];
|
|
187
|
-
var SIZING_ID = "S".repeat(64);
|
|
188
|
-
var SIZING_EXPECTATION = Number.MAX_SAFE_INTEGER;
|
|
189
189
|
var EGRESS_FORBIDDEN_TYPES = /* @__PURE__ */ new Set([
|
|
190
190
|
AGUI_EVENT_TYPE2.TOOL_CALL_ARGS,
|
|
191
191
|
AGUI_EVENT_TYPE2.TOOL_CALL_RESULT
|
|
@@ -208,6 +208,11 @@ var KNOWN_EVENT_KEYS_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
208
208
|
[AGUI_EVENT_TYPE2.CUSTOM, /* @__PURE__ */ new Set(["type", "timestamp", "rawEvent", "name", "value", "cotal"])]
|
|
209
209
|
]);
|
|
210
210
|
|
|
211
|
+
// ../connector-core/dist/agui-emitter.js
|
|
212
|
+
import { isAguiFramePart as isAguiFramePart3, isCasLoss, principalKey as principalKey2 } from "@cotal-ai/core";
|
|
213
|
+
var SIZING_ID = "S".repeat(64);
|
|
214
|
+
var SIZING_EXPECTATION = Number.MAX_SAFE_INTEGER;
|
|
215
|
+
|
|
211
216
|
// ../connector-core/dist/event-wal.js
|
|
212
217
|
import { assertIdToken } from "@cotal-ai/core";
|
|
213
218
|
|
|
@@ -215,7 +220,7 @@ import { assertIdToken } from "@cotal-ai/core";
|
|
|
215
220
|
import { ensureDirNoSymlink } from "@cotal-ai/core";
|
|
216
221
|
|
|
217
222
|
// ../connector-core/dist/agui-render.js
|
|
218
|
-
import { AGUI_EVENT_TYPE as AGUI_EVENT_TYPE3, AGUI_FRAME_KIND as AGUI_FRAME_KIND3, isAguiFramePart as
|
|
223
|
+
import { AGUI_EVENT_TYPE as AGUI_EVENT_TYPE3, AGUI_FRAME_KIND as AGUI_FRAME_KIND3, isAguiFramePart as isAguiFramePart4, registry } from "@cotal-ai/core";
|
|
219
224
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
220
225
|
var CONT = " \xB7 ";
|
|
221
226
|
var TEXT_PREFIX = "\xBB ";
|
|
@@ -322,7 +327,7 @@ var aguiFramePartRenderer = Object.freeze({
|
|
|
322
327
|
kind: "part-renderer",
|
|
323
328
|
name: AGUI_FRAME_KIND3,
|
|
324
329
|
render(part) {
|
|
325
|
-
if (!
|
|
330
|
+
if (!isAguiFramePart4(part))
|
|
326
331
|
return `[not an AG-UI frame]`;
|
|
327
332
|
const events = part.events;
|
|
328
333
|
if (!Array.isArray(events) || events.length === 0)
|
|
@@ -14853,7 +14858,10 @@ function date4(params) {
|
|
|
14853
14858
|
config(en_default());
|
|
14854
14859
|
|
|
14855
14860
|
// ../connector-core/dist/tool-specs.js
|
|
14856
|
-
import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAllow3, AmbiguousPeerError, assertLifecycleToken as assertLifecycleToken4, isPermissionDenied as isPermissionDenied2, renderLifecycleBlocked as renderLifecycleBlocked2, LANG_PROBLEM_DETAIL_KIND } from "@cotal-ai/core";
|
|
14861
|
+
import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAllow3, AmbiguousPeerError, peerLabel as peerLabel4, assertLifecycleToken as assertLifecycleToken4, isPermissionDenied as isPermissionDenied2, renderLifecycleBlocked as renderLifecycleBlocked2, LANG_PROBLEM_DETAIL_KIND, formatAge, presenceAges } from "@cotal-ai/core";
|
|
14862
|
+
|
|
14863
|
+
// ../connector-core/dist/orientation.js
|
|
14864
|
+
import { peerLabel as peerLabel3 } from "@cotal-ai/core";
|
|
14857
14865
|
|
|
14858
14866
|
// ../connector-core/dist/docs.js
|
|
14859
14867
|
var STOP = new Set("a an and are as at be by do for from has how in is it of on or that the this to use using was what when where which who with you your".split(" "));
|
package/dist/launch.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const BIN: string;
|
|
2
|
+
/** The foreground TUI's argv, built from the server it attaches to. Its env carries the server's
|
|
3
|
+
* basic-auth credentials, so the argv never needs the secret. */
|
|
4
|
+
export type TuiArgv = (server: {
|
|
5
|
+
url: string;
|
|
6
|
+
session: string;
|
|
7
|
+
line: 1 | 2;
|
|
8
|
+
}) => [command: string, ...args: string[]];
|
|
9
|
+
/** Extra env for the server, built from inputs the caller writes into the agent's home. It runs only
|
|
10
|
+
* once no seat of this name is running, so a refused launch never rewrites files a running seat's
|
|
11
|
+
* server still reads. */
|
|
12
|
+
export type ServeEnv = (agentHome: string) => NodeJS.ProcessEnv;
|
|
13
|
+
export declare function launch(tuiArgv: TuiArgv, serveEnv?: ServeEnv): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../src/launch.ts"],"names":[],"mappings":"AAgDA,eAAO,MAAM,GAAG,QAAuB,CAAC;AAqExC;kEACkE;AAClE,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtH;;0BAE0B;AAC1B,MAAM,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,UAAU,CAAC;AAEhE,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+IjF"}
|
package/dist/launch.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/launch.ts
|
|
2
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { delimiter, join } from "node:path";
|
|
8
|
+
|
|
9
|
+
// src/opencode-line.ts
|
|
10
|
+
function opencodeLine(raw, bin) {
|
|
11
|
+
const m = raw.match(/(?:^|v)(\d+)\.\d+/);
|
|
12
|
+
const major = m ? Number(m[1]) : NaN;
|
|
13
|
+
if (major === 1 || major === 2) return major;
|
|
14
|
+
throw new Error(
|
|
15
|
+
`opencode connector: unsupported OpenCode version "${raw}" from ${bin}; this connector supports the 1.x (opencode-ai) and 2.x (@opencode/cli) lines`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/launch.ts
|
|
20
|
+
function resolveOpencodeBin() {
|
|
21
|
+
const override = process.env.COTAL_OPENCODE_BIN?.trim() || process.env.COTAL_OPENCODE_RESOLVED_BIN?.trim();
|
|
22
|
+
if (override) return override;
|
|
23
|
+
if (process.platform !== "win32") return "opencode";
|
|
24
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
25
|
+
if (!dir) continue;
|
|
26
|
+
for (const exe of [join(dir, "opencode.exe"), join(dir, "node_modules", "opencode-ai", "bin", "opencode.exe")]) {
|
|
27
|
+
if (existsSync(exe)) return exe;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return "opencode";
|
|
31
|
+
}
|
|
32
|
+
var BIN = resolveOpencodeBin();
|
|
33
|
+
var USERNAME = "opencode";
|
|
34
|
+
var SECRET = process.env.OPENCODE_SERVER_PASSWORD || randomBytes(24).toString("hex");
|
|
35
|
+
async function freePort() {
|
|
36
|
+
const srv = createServer();
|
|
37
|
+
srv.listen(0, "127.0.0.1");
|
|
38
|
+
await once(srv, "listening");
|
|
39
|
+
const port = srv.address().port;
|
|
40
|
+
await new Promise((r) => srv.close(() => r()));
|
|
41
|
+
return port;
|
|
42
|
+
}
|
|
43
|
+
async function terminate(child) {
|
|
44
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
45
|
+
child.kill("SIGTERM");
|
|
46
|
+
const dead = await Promise.race([
|
|
47
|
+
once(child, "exit").then(() => true),
|
|
48
|
+
new Promise((r) => setTimeout(() => r(false), 3e3))
|
|
49
|
+
]);
|
|
50
|
+
if (!dead) {
|
|
51
|
+
child.kill("SIGKILL");
|
|
52
|
+
await once(child, "exit");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function processCommand(pid) {
|
|
56
|
+
try {
|
|
57
|
+
if (process.platform === "win32") {
|
|
58
|
+
return execFileSync("powershell.exe", [
|
|
59
|
+
"-NoProfile",
|
|
60
|
+
"-NonInteractive",
|
|
61
|
+
"-Command",
|
|
62
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CommandLine`
|
|
63
|
+
], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
64
|
+
}
|
|
65
|
+
return execFileSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
68
|
+
}).trim();
|
|
69
|
+
} catch {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function isLiveOpencodeServe(pid) {
|
|
74
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
75
|
+
try {
|
|
76
|
+
process.kill(pid, 0);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const cmd = processCommand(pid);
|
|
81
|
+
if (!cmd) return true;
|
|
82
|
+
return /\bserve\b/.test(cmd) && (/(?:^|[\\/\s])opencode(?:\.exe)?(?:\s|$)/i.test(cmd) || /--hostname\s+127\.0\.0\.1\b/.test(cmd) && /--port\s+\d+\b/.test(cmd));
|
|
83
|
+
}
|
|
84
|
+
async function launch(tuiArgv, serveEnv) {
|
|
85
|
+
const port = process.env.COTAL_OPENCODE_PORT?.trim() || String(await freePort());
|
|
86
|
+
const url = `http://127.0.0.1:${port}`;
|
|
87
|
+
const name = process.env.COTAL_NAME?.trim() || "agent";
|
|
88
|
+
const dataRoot = process.env.COTAL_OPENCODE_HOME?.trim();
|
|
89
|
+
if (!dataRoot) throw new Error("COTAL_OPENCODE_HOME is not set \u2014 the connector must pin the agent's data root");
|
|
90
|
+
const agentHome = join(dataRoot, ".cotal", "opencode", name);
|
|
91
|
+
const dbPath = join(agentHome, "opencode.db");
|
|
92
|
+
const pidFile = join(agentHome, "serve.pid");
|
|
93
|
+
let recorded;
|
|
94
|
+
try {
|
|
95
|
+
recorded = readFileSync(pidFile, "utf8");
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e.code !== "ENOENT") throw e;
|
|
98
|
+
}
|
|
99
|
+
if (recorded !== void 0) {
|
|
100
|
+
const pid = Number(recorded);
|
|
101
|
+
if (isLiveOpencodeServe(pid))
|
|
102
|
+
throw new Error(`agent "${name}" is already running (opencode serve pid ${pid}) \u2014 kill it first`);
|
|
103
|
+
rmSync(pidFile, { force: true });
|
|
104
|
+
}
|
|
105
|
+
const versionRaw = execFileSync(BIN, ["--version"], { encoding: "utf8" }).trim();
|
|
106
|
+
const line = opencodeLine(versionRaw, BIN);
|
|
107
|
+
mkdirSync(agentHome, { recursive: true });
|
|
108
|
+
const serve = spawn(BIN, ["serve", "--hostname", "127.0.0.1", "--port", port], {
|
|
109
|
+
env: {
|
|
110
|
+
...process.env,
|
|
111
|
+
...serveEnv?.(agentHome),
|
|
112
|
+
COTAL_OPENCODE_SERVER_URL: url,
|
|
113
|
+
COTAL_OPENCODE_LINE: String(line),
|
|
114
|
+
OPENCODE_SERVER_USERNAME: USERNAME,
|
|
115
|
+
OPENCODE_SERVER_PASSWORD: SECRET,
|
|
116
|
+
OPENCODE_DB: dbPath
|
|
117
|
+
},
|
|
118
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
119
|
+
});
|
|
120
|
+
writeFileSync(pidFile, String(serve.pid));
|
|
121
|
+
serve.on("exit", () => rmSync(pidFile, { force: true }));
|
|
122
|
+
let sessionId;
|
|
123
|
+
let attached = false;
|
|
124
|
+
let onSession;
|
|
125
|
+
const scan = (d) => {
|
|
126
|
+
if (!attached) process.stderr.write(d);
|
|
127
|
+
if (!sessionId) {
|
|
128
|
+
const m = d.toString().match(/\[cotal-session\] (\S+)/);
|
|
129
|
+
if (m) {
|
|
130
|
+
sessionId = m[1];
|
|
131
|
+
onSession?.(sessionId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
serve.stdout?.on("data", scan);
|
|
136
|
+
serve.stderr?.on("data", scan);
|
|
137
|
+
serve.on("exit", (code, signal) => {
|
|
138
|
+
if (!attached) process.exit(code ?? (signal ? 1 : 0));
|
|
139
|
+
});
|
|
140
|
+
const auth = `Basic ${Buffer.from(`${USERNAME}:${SECRET}`).toString("base64")}`;
|
|
141
|
+
const pokePath = line === 2 ? "/api/config" : "/session";
|
|
142
|
+
void (async () => {
|
|
143
|
+
for (let i = 0; i < 300 && !sessionId; i++) {
|
|
144
|
+
try {
|
|
145
|
+
await fetch(`${url}${pokePath}`, { headers: { authorization: auth }, signal: AbortSignal.timeout(1500) });
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
const id = await new Promise((resolve) => {
|
|
152
|
+
if (sessionId) return resolve(sessionId);
|
|
153
|
+
onSession = resolve;
|
|
154
|
+
setTimeout(() => resolve(sessionId), 6e4);
|
|
155
|
+
});
|
|
156
|
+
if (!id) {
|
|
157
|
+
process.stderr.write(
|
|
158
|
+
`[cotal-connector] serve: agent session never came up (~60s) \u2014 aborting. Check the boot log above for plugin/mesh errors (OPENCODE_DB=${dbPath})
|
|
159
|
+
`
|
|
160
|
+
);
|
|
161
|
+
await terminate(serve);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
if (process.env.COTAL_SERVE_HEADLESS?.trim() === "1") {
|
|
165
|
+
process.stdout.write(`[cotal-serve] ${JSON.stringify({ port: Number(port), session: id })}
|
|
166
|
+
`);
|
|
167
|
+
for (const sig of ["SIGINT", "SIGTERM"])
|
|
168
|
+
process.on(sig, () => void terminate(serve).then(() => process.exit(0)));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const tuiEnv = {
|
|
172
|
+
...process.env,
|
|
173
|
+
OPENCODE_SERVER_USERNAME: USERNAME,
|
|
174
|
+
OPENCODE_SERVER_PASSWORD: SECRET,
|
|
175
|
+
OPENCODE_DB: dbPath
|
|
176
|
+
};
|
|
177
|
+
delete tuiEnv.OPENCODE_CONFIG_CONTENT;
|
|
178
|
+
for (const k of Object.keys(tuiEnv)) if (k.startsWith("COTAL_")) delete tuiEnv[k];
|
|
179
|
+
attached = true;
|
|
180
|
+
const [command, ...args] = tuiArgv({ url, session: id, line });
|
|
181
|
+
const tui = spawn(command, args, { env: tuiEnv, stdio: "inherit" });
|
|
182
|
+
for (const sig of ["SIGINT", "SIGTERM"])
|
|
183
|
+
process.on(sig, () => {
|
|
184
|
+
tui.kill(sig);
|
|
185
|
+
serve.kill(sig);
|
|
186
|
+
});
|
|
187
|
+
serve.on("exit", () => void terminate(tui));
|
|
188
|
+
tui.on("exit", (code, signal) => {
|
|
189
|
+
void terminate(serve).then(() => process.exit(code ?? (signal ? 1 : 0)));
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
BIN,
|
|
194
|
+
launch
|
|
195
|
+
};
|
package/dist/opencode-line.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Parses `opencode --version` output into the major protocol line: 1 (opencode-ai) or 2
|
|
2
|
-
* (@opencode/cli). Pure so both
|
|
2
|
+
* (@opencode/cli). Pure so both launch.ts (the launcher) and extension.ts (the model catalog) can
|
|
3
3
|
* detect the line from the same rule with no fallback for anything else. */
|
|
4
4
|
export declare function opencodeLine(raw: string, bin: string): 1 | 2;
|
|
5
5
|
//# sourceMappingURL=opencode-line.d.ts.map
|