@mclawnet/agent 0.6.34 → 0.6.36
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/cli.js +75 -7
- package/dist/__tests__/bootstrap-deps.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-deps.test.d.ts.map +1 -0
- package/dist/__tests__/collect-manifest.test.d.ts +2 -0
- package/dist/__tests__/collect-manifest.test.d.ts.map +1 -0
- package/dist/__tests__/hub-connection-on-activity.test.d.ts +2 -0
- package/dist/__tests__/hub-connection-on-activity.test.d.ts.map +1 -0
- package/dist/__tests__/hub-connection-wake-watch.test.d.ts +2 -0
- package/dist/__tests__/hub-connection-wake-watch.test.d.ts.map +1 -0
- package/dist/__tests__/ideas-rest-client.test.d.ts +2 -0
- package/dist/__tests__/ideas-rest-client.test.d.ts.map +1 -0
- package/dist/__tests__/legacy-claude-execute-compat.test.d.ts +2 -0
- package/dist/__tests__/legacy-claude-execute-compat.test.d.ts.map +1 -0
- package/dist/__tests__/no-adapter-cycle.test.d.ts +2 -0
- package/dist/__tests__/no-adapter-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts +2 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts +2 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-merge.test.d.ts +2 -0
- package/dist/__tests__/session-manager-merge.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-sticky.test.d.ts +2 -0
- package/dist/__tests__/session-manager-sticky.test.d.ts.map +1 -0
- package/dist/__tests__/session-protocol-dispatch.test.d.ts +2 -0
- package/dist/__tests__/session-protocol-dispatch.test.d.ts.map +1 -0
- package/dist/__tests__/worktree-bridge.test.d.ts +2 -0
- package/dist/__tests__/worktree-bridge.test.d.ts.map +1 -0
- package/dist/backend-adapter.d.ts +6 -232
- package/dist/backend-adapter.d.ts.map +1 -1
- package/dist/backend-factory-AFF6I7YF.js +11 -0
- package/dist/backend-factory.d.ts +23 -1
- package/dist/backend-factory.d.ts.map +1 -1
- package/dist/bootstrap-deps.d.ts +84 -0
- package/dist/bootstrap-deps.d.ts.map +1 -0
- package/dist/bootstrap-deps.js +173 -0
- package/dist/bootstrap-deps.js.map +1 -0
- package/dist/{chunk-PJ5M6Q36.js → chunk-376QZ7JB.js} +2 -2
- package/dist/chunk-376QZ7JB.js.map +1 -0
- package/dist/{chunk-2JDX6XFD.js → chunk-GOCWMRBB.js} +1817 -298
- package/dist/chunk-GOCWMRBB.js.map +1 -0
- package/dist/{chunk-M2CDVPQF.js → chunk-JH6RGJBQ.js} +2 -2
- package/dist/{chunk-MFXF77LG.js → chunk-VAEFJLPL.js} +25 -3
- package/dist/chunk-VAEFJLPL.js.map +1 -0
- package/dist/{dist-VLBO5CT3.js → dist-NWVHAP5R.js} +330 -23
- package/dist/dist-NWVHAP5R.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/hub-connection.d.ts +25 -1
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/ideas-rest-client.d.ts +25 -0
- package/dist/ideas-rest-client.d.ts.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{linux-IHA4O633.js → linux-MBU6ERXL.js} +3 -3
- package/dist/{macos-G4VK2253.js → macos-I2DUWFUH.js} +3 -3
- package/dist/projects-handler.d.ts +146 -1
- package/dist/projects-handler.d.ts.map +1 -1
- package/dist/runtime-env-defaults.d.ts +18 -0
- package/dist/runtime-env-defaults.d.ts.map +1 -0
- package/dist/service/index.js +5 -5
- package/dist/session-manager.d.ts +59 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +3 -2
- package/dist/{windows-P6U3JLUZ.js → windows-PEJ3KOLC.js} +3 -3
- package/dist/worktree-bridge.d.ts +51 -0
- package/dist/worktree-bridge.d.ts.map +1 -0
- package/package.json +10 -8
- package/dist/backend-factory-RUYUBJVF.js +0 -9
- package/dist/chunk-2JDX6XFD.js.map +0 -1
- package/dist/chunk-MFXF77LG.js.map +0 -1
- package/dist/chunk-PJ5M6Q36.js.map +0 -1
- package/dist/dist-VLBO5CT3.js.map +0 -1
- /package/dist/{backend-factory-RUYUBJVF.js.map → backend-factory-AFF6I7YF.js.map} +0 -0
- /package/dist/{chunk-M2CDVPQF.js.map → chunk-JH6RGJBQ.js.map} +0 -0
- /package/dist/{linux-IHA4O633.js.map → linux-MBU6ERXL.js.map} +0 -0
- /package/dist/{macos-G4VK2253.js.map → macos-I2DUWFUH.js.map} +0 -0
- /package/dist/{windows-P6U3JLUZ.js.map → windows-PEJ3KOLC.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadConfig,
|
|
3
3
|
saveConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-376QZ7JB.js";
|
|
5
5
|
|
|
6
6
|
// src/service/config.ts
|
|
7
7
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -85,4 +85,4 @@ export {
|
|
|
85
85
|
validateServiceConfig,
|
|
86
86
|
mergeServiceConfig
|
|
87
87
|
};
|
|
88
|
-
//# sourceMappingURL=chunk-
|
|
88
|
+
//# sourceMappingURL=chunk-JH6RGJBQ.js.map
|
|
@@ -21,7 +21,7 @@ async function createBackendAdapter(kind, ctx = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
} else if (resolved === "codex") {
|
|
23
23
|
try {
|
|
24
|
-
const mod = await import("./dist-
|
|
24
|
+
const mod = await import("./dist-NWVHAP5R.js");
|
|
25
25
|
adapter = new mod.CodexAdapter();
|
|
26
26
|
} catch (err) {
|
|
27
27
|
throw rethrowWithRole(
|
|
@@ -41,9 +41,31 @@ function rethrowWithRole(err, roleName, prefix) {
|
|
|
41
41
|
const tag = roleName ? ` [role=${roleName}]` : "";
|
|
42
42
|
return new Error(`${prefix}${tag}: ${base}`);
|
|
43
43
|
}
|
|
44
|
+
var ALL_KINDS_MAP = { claude: true, codex: true };
|
|
45
|
+
var ALL_KINDS = Object.keys(ALL_KINDS_MAP);
|
|
46
|
+
async function collectAgentManifest() {
|
|
47
|
+
const entries = await Promise.all(
|
|
48
|
+
ALL_KINDS.map(async (kind) => {
|
|
49
|
+
try {
|
|
50
|
+
const adapter = await createBackendAdapter(kind);
|
|
51
|
+
return await adapter.getManifest();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return {
|
|
54
|
+
kind,
|
|
55
|
+
installed: false,
|
|
56
|
+
unavailableReason: err instanceof Error ? err.message : String(err),
|
|
57
|
+
models: [],
|
|
58
|
+
modes: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
return { manifestVersion: 1, backends: entries };
|
|
64
|
+
}
|
|
44
65
|
|
|
45
66
|
export {
|
|
46
67
|
__resetBackendFactoryCache,
|
|
47
|
-
createBackendAdapter
|
|
68
|
+
createBackendAdapter,
|
|
69
|
+
collectAgentManifest
|
|
48
70
|
};
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-VAEFJLPL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backend-factory.ts"],"sourcesContent":["import type { BackendAdapter } from \"./backend-adapter.js\";\nimport type { AgentManifestPayload, BackendKind } from \"@mclawnet/shared\";\n\n// Re-export for back-compat with existing callers that import BackendKind from\n// this module rather than @mclawnet/shared.\nexport type { BackendKind };\n\nconst cache = new Map<BackendKind, BackendAdapter>();\n\nexport function __resetBackendFactoryCache(): void {\n cache.clear();\n}\n\nexport interface BackendFactoryContext {\n /** Role instance / template name — surfaces in error messages so users know\n * which role is misconfigured when its backend package fails to load. */\n roleName?: string;\n}\n\n/**\n * Resolve a BackendAdapter for the given backend kind.\n *\n * - \"claude\" / undefined → @mclawnet/claude-adapter (back-compat default).\n * - \"codex\" → @mclawnet/codex-adapter via dynamic import so the codex bundle\n * isn't pulled into claude-only deployments.\n *\n * Why dynamic import (and why codex-adapter is `optionalDependencies` in\n * agent's package.json, not `dependencies`):\n * - agent owns the runtime relationship \"I load adapters on demand\".\n * - Adapters only depend on @mclawnet/backend-types (the interface package),\n * so there is no build-time cycle.\n * - `optionalDependencies` keeps the \"user installs just @mclawnet/agent\n * and codex backend works out of the box\" UX: npm/pnpm will try to\n * install codex-adapter; if that fails (network, platform issue, user\n * opted out via --no-optional), the dynamic import below throws and\n * the agent surfaces a clear error instead of crashing at startup.\n * - Promoting codex-adapter to `dependencies` would force install, which\n * is fine for monorepo but unfriendly to claude-only deployments.\n *\n * Results are cached per kind; subsequent calls return the same instance.\n */\nexport async function createBackendAdapter(\n kind: BackendKind | undefined,\n ctx: BackendFactoryContext = {},\n): Promise<BackendAdapter> {\n const resolved: BackendKind = kind ?? \"claude\";\n const cached = cache.get(resolved);\n if (cached) return cached;\n\n let adapter: BackendAdapter;\n if (resolved === \"claude\") {\n try {\n const mod = (await import(\"@mclawnet/claude-adapter\" as string)) as {\n ClaudeCodeAdapter: new () => BackendAdapter;\n };\n adapter = new mod.ClaudeCodeAdapter();\n } catch (err) {\n throw rethrowWithRole(\n err,\n ctx.roleName,\n \"failed to load @mclawnet/claude-adapter\",\n );\n }\n } else if (resolved === \"codex\") {\n try {\n const mod = (await import(\"@mclawnet/codex-adapter\" as string)) as {\n CodexAdapter: new () => BackendAdapter;\n };\n adapter = new mod.CodexAdapter();\n } catch (err) {\n throw rethrowWithRole(\n err,\n ctx.roleName,\n \"failed to load @mclawnet/codex-adapter (is codex CLI installed?)\",\n );\n }\n } else {\n throw new Error(`unknown backend kind: ${resolved}`);\n }\n\n cache.set(resolved, adapter);\n return adapter;\n}\n\nfunction rethrowWithRole(err: unknown, roleName: string | undefined, prefix: string): Error {\n const base = err instanceof Error ? err.message : String(err);\n const tag = roleName ? ` [role=${roleName}]` : \"\";\n return new Error(`${prefix}${tag}: ${base}`);\n}\n\n/**\n * All backend kinds the agent should advertise in its manifest. Mirrors the\n * `BackendKind` union exposed by @mclawnet/shared. The `satisfies` clause\n * forces a tsc error if `BackendKind` grows without a matching map entry.\n */\nconst ALL_KINDS_MAP = { claude: true, codex: true } as const satisfies Record<BackendKind, true>;\nconst ALL_KINDS = Object.keys(ALL_KINDS_MAP) as BackendKind[];\n\n/**\n * Probe every known backend adapter and aggregate their `getManifest()` output\n * into a single payload the agent can ship to the hub. Failures (binary not on\n * PATH, adapter package broken, etc.) become `installed: false` entries rather\n * than rejecting the whole collection — the hub still wants to know \"we tried\n * but it isn't available\" for each kind.\n */\nexport async function collectAgentManifest(): Promise<AgentManifestPayload> {\n const entries = await Promise.all(\n ALL_KINDS.map(async (kind) => {\n try {\n const adapter = await createBackendAdapter(kind);\n return await adapter.getManifest();\n } catch (err) {\n return {\n kind,\n installed: false,\n unavailableReason: err instanceof Error ? err.message : String(err),\n models: [],\n modes: [],\n };\n }\n }),\n );\n return { manifestVersion: 1, backends: entries };\n}\n"],"mappings":";AAOA,IAAM,QAAQ,oBAAI,IAAiC;AAE5C,SAAS,6BAAmC;AACjD,QAAM,MAAM;AACd;AA8BA,eAAsB,qBACpB,MACA,MAA6B,CAAC,GACL;AACzB,QAAM,WAAwB,QAAQ;AACtC,QAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,MAAI,OAAQ,QAAO;AAEnB,MAAI;AACJ,MAAI,aAAa,UAAU;AACzB,QAAI;AACF,YAAM,MAAO,MAAM,OAAO,0BAAoC;AAG9D,gBAAU,IAAI,IAAI,kBAAkB;AAAA,IACtC,SAAS,KAAK;AACZ,YAAM;AAAA,QACJ;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,aAAa,SAAS;AAC/B,QAAI;AACF,YAAM,MAAO,MAAM,OAAO,oBAAmC;AAG7D,gBAAU,IAAI,IAAI,aAAa;AAAA,IACjC,SAAS,KAAK;AACZ,YAAM;AAAA,QACJ;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AACL,UAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,EACrD;AAEA,QAAM,IAAI,UAAU,OAAO;AAC3B,SAAO;AACT;AAEA,SAAS,gBAAgB,KAAc,UAA8B,QAAuB;AAC1F,QAAM,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC5D,QAAM,MAAM,WAAW,UAAU,QAAQ,MAAM;AAC/C,SAAO,IAAI,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE;AAC7C;AAOA,IAAM,gBAAgB,EAAE,QAAQ,MAAM,OAAO,KAAK;AAClD,IAAM,YAAY,OAAO,KAAK,aAAa;AAS3C,eAAsB,uBAAsD;AAC1E,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,UAAU,IAAI,OAAO,SAAS;AAC5B,UAAI;AACF,cAAM,UAAU,MAAM,qBAAqB,IAAI;AAC/C,eAAO,MAAM,QAAQ,YAAY;AAAA,MACnC,SAAS,KAAK;AACZ,eAAO;AAAA,UACL;AAAA,UACA,WAAW;AAAA,UACX,mBAAmB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UAClE,QAAQ,CAAC;AAAA,UACT,OAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,EAAE,iBAAiB,GAAG,UAAU,QAAQ;AACjD;","names":[]}
|
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
// ../codex-adapter/dist/codex-adapter.js
|
|
2
|
-
import { spawn } from "child_process";
|
|
2
|
+
import { execSync, spawn } from "child_process";
|
|
3
3
|
import { EventEmitter } from "events";
|
|
4
|
-
import { existsSync } from "fs";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
|
-
import { homedir } from "os";
|
|
7
|
-
import { join as join2 } from "path";
|
|
6
|
+
import { homedir, platform as platform2 } from "os";
|
|
7
|
+
import { dirname, join as join2 } from "path";
|
|
8
8
|
|
|
9
9
|
// ../codex-adapter/dist/codex-spawn-args.js
|
|
10
10
|
import { writeFileSync, unlinkSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { tmpdir } from "os";
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
14
|
-
import { DEFAULT_SANDBOX } from "@mclawnet/shared";
|
|
14
|
+
import { DEFAULT_SANDBOX, SANDBOX_LEVELS } from "@mclawnet/shared";
|
|
15
15
|
function escapeToml(value) {
|
|
16
16
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
17
17
|
}
|
|
18
|
+
var VALID_CODEX_SANDBOX = new Set(SANDBOX_LEVELS);
|
|
18
19
|
function buildCodexSpawnArgs(options) {
|
|
19
20
|
const args = ["app-server"];
|
|
20
21
|
let briefingFile;
|
|
21
22
|
void options.resumeId;
|
|
22
23
|
args.push("-c", 'approval_policy="never"');
|
|
23
|
-
|
|
24
|
+
let sandboxLevel;
|
|
25
|
+
let modeSource;
|
|
26
|
+
if (options.mode && VALID_CODEX_SANDBOX.has(options.mode)) {
|
|
27
|
+
sandboxLevel = options.mode;
|
|
28
|
+
modeSource = "options.mode";
|
|
29
|
+
} else if (options.sandbox) {
|
|
30
|
+
sandboxLevel = options.sandbox;
|
|
31
|
+
modeSource = "options.sandbox";
|
|
32
|
+
} else {
|
|
33
|
+
sandboxLevel = DEFAULT_SANDBOX;
|
|
34
|
+
modeSource = "default";
|
|
35
|
+
}
|
|
24
36
|
const codexSandboxMode = sandboxLevel === "read-only" ? "read-only" : sandboxLevel === "full-access" ? "danger-full-access" : "workspace-write";
|
|
25
37
|
args.push("-c", `sandbox_mode="${codexSandboxMode}"`);
|
|
38
|
+
if (options.model) {
|
|
39
|
+
args.push("-c", `model="${escapeToml(options.model)}"`);
|
|
40
|
+
}
|
|
26
41
|
if (options.mcpServer) {
|
|
27
42
|
const { command, args: serverArgs, env } = options.mcpServer;
|
|
28
43
|
args.push("-c", `mcp_servers.clawnet-mcp.command="${escapeToml(command)}"`);
|
|
@@ -38,11 +53,12 @@ function buildCodexSpawnArgs(options) {
|
|
|
38
53
|
args.push("-c", 'mcp_servers.clawnet-mcp.default_tools_approval_mode="approve"');
|
|
39
54
|
}
|
|
40
55
|
if (options.systemPrompt) {
|
|
41
|
-
|
|
56
|
+
const safeId = options.sessionId.replace(/[\\/:*?"<>|\x00-\x1F]/g, "_").replace(/[. ]+$/g, "_");
|
|
57
|
+
briefingFile = join(tmpdir(), `clawnet-briefing-${safeId}-${randomUUID().slice(0, 8)}.md`);
|
|
42
58
|
writeFileSync(briefingFile, options.systemPrompt, { encoding: "utf-8", mode: 384 });
|
|
43
59
|
args.push("-c", `model_instructions_file="${escapeToml(briefingFile)}"`);
|
|
44
60
|
}
|
|
45
|
-
return { args, briefingFile };
|
|
61
|
+
return { args, briefingFile, modeSource };
|
|
46
62
|
}
|
|
47
63
|
function cleanupBriefingFile(filePath) {
|
|
48
64
|
try {
|
|
@@ -69,17 +85,38 @@ var JsonRpcClient = class {
|
|
|
69
85
|
this.onNotification = opts.onNotification;
|
|
70
86
|
this.onMalformedLine = opts.onMalformedLine;
|
|
71
87
|
opts.stdout.on("data", (chunk) => this.feed(chunk.toString("utf-8")));
|
|
88
|
+
this.stdin.on("error", (err) => this.failAllPending(err));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reject every in-flight request with the given error. Called when stdin
|
|
92
|
+
* dies so callers get a clean rejection instead of hanging until the
|
|
93
|
+
* handshake death-poll catches up.
|
|
94
|
+
*/
|
|
95
|
+
failAllPending(err) {
|
|
96
|
+
for (const [, p] of this.pending) {
|
|
97
|
+
try {
|
|
98
|
+
p.reject(err);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this.pending.clear();
|
|
72
103
|
}
|
|
73
104
|
request(method, params) {
|
|
74
105
|
const id = this.nextId++;
|
|
75
106
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
76
107
|
return new Promise((resolve, reject) => {
|
|
77
108
|
this.pending.set(id, { resolve, reject });
|
|
78
|
-
this.stdin.write(JSON.stringify(frame) + "\n")
|
|
109
|
+
this.stdin.write(JSON.stringify(frame) + "\n", (err) => {
|
|
110
|
+
if (err) {
|
|
111
|
+
this.pending.delete(id);
|
|
112
|
+
reject(err);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
79
115
|
});
|
|
80
116
|
}
|
|
81
117
|
notify(method, params) {
|
|
82
|
-
this.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n")
|
|
118
|
+
this.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n", () => {
|
|
119
|
+
});
|
|
83
120
|
}
|
|
84
121
|
feed(s) {
|
|
85
122
|
this.buffer += s;
|
|
@@ -273,9 +310,6 @@ function mapCodexFrame(frame) {
|
|
|
273
310
|
}
|
|
274
311
|
case "item/completed": {
|
|
275
312
|
const item = params.item;
|
|
276
|
-
if (item?.type === "agentMessage" && typeof item.text === "string") {
|
|
277
|
-
return { kind: "assistant_text", text: item.text };
|
|
278
|
-
}
|
|
279
313
|
if (item?.type === "commandExecution") {
|
|
280
314
|
const failed = item.status === "failed" || item.status === "declined";
|
|
281
315
|
const badExit = typeof item.exitCode === "number" && item.exitCode !== 0;
|
|
@@ -354,13 +388,139 @@ function mapCodexFrame(frame) {
|
|
|
354
388
|
return { kind: "raw", backend: "codex", payload: frame };
|
|
355
389
|
}
|
|
356
390
|
|
|
391
|
+
// ../codex-adapter/dist/catalog.js
|
|
392
|
+
var CODEX_MODELS = [
|
|
393
|
+
{
|
|
394
|
+
code: "gpt-4o",
|
|
395
|
+
label: "GPT-4o",
|
|
396
|
+
tier: "premium",
|
|
397
|
+
contextWindow: 128e3,
|
|
398
|
+
supportsVision: true,
|
|
399
|
+
supportsToolUse: true,
|
|
400
|
+
default: true
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
code: "gpt-4o-mini",
|
|
404
|
+
label: "GPT-4o mini",
|
|
405
|
+
tier: "standard",
|
|
406
|
+
contextWindow: 128e3,
|
|
407
|
+
supportsVision: true,
|
|
408
|
+
supportsToolUse: true
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
var CODEX_MODES = [
|
|
412
|
+
{ code: "read-only", label: "Read Only", description: "No FS writes" },
|
|
413
|
+
{ code: "workspace-write", label: "Workspace Write", description: "Write inside cwd only", default: true },
|
|
414
|
+
{ code: "full-access", label: "Full Access", description: "No sandbox" }
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
// ../codex-adapter/dist/detect.js
|
|
418
|
+
import { execFile } from "child_process";
|
|
419
|
+
import { promisify } from "util";
|
|
420
|
+
import { platform } from "os";
|
|
421
|
+
var exec = promisify(execFile);
|
|
422
|
+
async function detectCodexInstall() {
|
|
423
|
+
try {
|
|
424
|
+
const isWin2 = platform() === "win32";
|
|
425
|
+
const lookup = isWin2 ? "where" : "which";
|
|
426
|
+
const { stdout: lookupOut } = await exec(lookup, ["codex"], { timeout: 5e3 });
|
|
427
|
+
const binaryPath = lookupOut.trim().split(/\r?\n/)[0]?.trim();
|
|
428
|
+
if (!binaryPath) {
|
|
429
|
+
return { installed: false, reason: `codex CLI not detected: empty ${lookup} output` };
|
|
430
|
+
}
|
|
431
|
+
const { stdout: verOut } = await exec(binaryPath, ["--version"], { timeout: 5e3 });
|
|
432
|
+
return { installed: true, binaryPath, version: verOut.trim() };
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
435
|
+
return { installed: false, reason: `codex CLI not detected: ${message}` };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
357
439
|
// ../codex-adapter/dist/codex-adapter.js
|
|
358
440
|
var log = createLogger({ module: "codex-adapter" });
|
|
441
|
+
var isWin = platform2() === "win32";
|
|
442
|
+
function resolveCodexBin(explicit) {
|
|
443
|
+
if (explicit && explicit !== "codex")
|
|
444
|
+
return explicit;
|
|
445
|
+
const names = isWin ? ["codex.exe", "codex.cmd", "codex.bat"] : ["codex"];
|
|
446
|
+
const whichCmd = isWin ? "where" : "which";
|
|
447
|
+
for (const name of names) {
|
|
448
|
+
try {
|
|
449
|
+
const found = execSync(`${whichCmd} ${name}`, {
|
|
450
|
+
encoding: "utf-8",
|
|
451
|
+
// 1.5s is plenty for `where`/`which` on a healthy machine; the old
|
|
452
|
+
// 5s × 3 worst-case (15s blocking event loop) showed up on slow
|
|
453
|
+
// domain-joined Windows hosts / network-PATH setups.
|
|
454
|
+
timeout: 1500,
|
|
455
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
456
|
+
}).trim().split(/\r?\n/)[0];
|
|
457
|
+
if (found && existsSync(found))
|
|
458
|
+
return found;
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const home = homedir();
|
|
463
|
+
const fallbacks = isWin ? [
|
|
464
|
+
join2(home, "AppData", "Roaming", "npm", "codex.cmd"),
|
|
465
|
+
join2(home, "AppData", "Roaming", "npm", "codex.exe"),
|
|
466
|
+
join2(home, ".cargo", "bin", "codex.exe"),
|
|
467
|
+
join2(home, ".local", "bin", "codex.exe")
|
|
468
|
+
] : [
|
|
469
|
+
join2(home, ".cargo", "bin", "codex"),
|
|
470
|
+
join2(home, ".local", "bin", "codex"),
|
|
471
|
+
"/opt/homebrew/bin/codex",
|
|
472
|
+
"/usr/local/bin/codex"
|
|
473
|
+
];
|
|
474
|
+
for (const p of fallbacks) {
|
|
475
|
+
if (existsSync(p))
|
|
476
|
+
return p;
|
|
477
|
+
}
|
|
478
|
+
return isWin ? "codex.cmd" : "codex";
|
|
479
|
+
}
|
|
480
|
+
var resolvedCodexBinCache = /* @__PURE__ */ new Map();
|
|
481
|
+
var resolveCodexBinCallCount = 0;
|
|
482
|
+
function getResolvedCodexBin(explicit) {
|
|
483
|
+
const key = explicit ?? "";
|
|
484
|
+
const hit = resolvedCodexBinCache.get(key);
|
|
485
|
+
if (hit)
|
|
486
|
+
return hit;
|
|
487
|
+
resolveCodexBinCallCount++;
|
|
488
|
+
const resolved = resolveCodexBin(explicit);
|
|
489
|
+
resolvedCodexBinCache.set(key, resolved);
|
|
490
|
+
return resolved;
|
|
491
|
+
}
|
|
492
|
+
function resolveSpawnTarget(codexBin) {
|
|
493
|
+
const isWindows = platform2() === "win32";
|
|
494
|
+
if (!isWindows || !codexBin.toLowerCase().endsWith(".cmd")) {
|
|
495
|
+
return { command: codexBin, prefixArgs: [] };
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const cmdContent = readFileSync(codexBin, "utf-8");
|
|
499
|
+
const exeMatch = cmdContent.match(/(?:%~?dp0%?)[\\/](.+?\.exe)/i);
|
|
500
|
+
if (exeMatch) {
|
|
501
|
+
const relParts = exeMatch[1].split(/[\\/]/);
|
|
502
|
+
const exePath = join2(dirname(codexBin), ...relParts);
|
|
503
|
+
if (existsSync(exePath))
|
|
504
|
+
return { command: exePath, prefixArgs: [] };
|
|
505
|
+
}
|
|
506
|
+
const jsMatch = cmdContent.match(/(?:%~?dp0%?)[\\/](.+?\.js)/i);
|
|
507
|
+
if (jsMatch) {
|
|
508
|
+
const relParts = jsMatch[1].split(/[\\/]/);
|
|
509
|
+
const cliJsPath = join2(dirname(codexBin), ...relParts);
|
|
510
|
+
if (existsSync(cliJsPath)) {
|
|
511
|
+
return { command: process.execPath, prefixArgs: [cliJsPath] };
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
return { command: codexBin, prefixArgs: [] };
|
|
517
|
+
}
|
|
359
518
|
var CLAWNET_CLIENT_INFO = {
|
|
360
519
|
name: "clawnet",
|
|
361
520
|
version: "0.1.0",
|
|
362
521
|
title: "ClawNet Agent"
|
|
363
522
|
};
|
|
523
|
+
var ASSISTANT_TEXT_SEEN_MAX = 64;
|
|
364
524
|
var CodexProcess = class extends EventEmitter {
|
|
365
525
|
id;
|
|
366
526
|
workDir;
|
|
@@ -388,6 +548,24 @@ var CodexProcess = class extends EventEmitter {
|
|
|
388
548
|
handshakeComplete = false;
|
|
389
549
|
/** Inputs queued by send() while the handshake is still in flight. */
|
|
390
550
|
pendingInputs = [];
|
|
551
|
+
/**
|
|
552
|
+
* Per-itemId accumulator of assistant text already emitted via
|
|
553
|
+
* `item/agentMessage/delta` chunks. Consulted on `item/completed` with
|
|
554
|
+
* `item.type === "agentMessage"` to decide what (if anything) of the
|
|
555
|
+
* completed payload still needs to be emitted. Without this, codex
|
|
556
|
+
* delivers the same assistant text twice (deltas + final) and the UI
|
|
557
|
+
* renders "Hello worldHello world".
|
|
558
|
+
*
|
|
559
|
+
* Entries are cleared per item when the matching `item/completed` fires.
|
|
560
|
+
* We do NOT clear the whole map on `turn/completed`: codex's wire
|
|
561
|
+
* doesn't strictly guarantee that every item/completed precedes its
|
|
562
|
+
* parent turn/completed, and a trailing item/completed against an empty
|
|
563
|
+
* accumulator would re-emit the full text (reactivating the bug). To
|
|
564
|
+
* cap unbounded growth from interrupted turns that never fire
|
|
565
|
+
* completed, the map is size-bounded with FIFO eviction; in practice a
|
|
566
|
+
* single turn produces 1-2 agentMessage items so the cap is generous.
|
|
567
|
+
*/
|
|
568
|
+
assistantTextSeen = /* @__PURE__ */ new Map();
|
|
391
569
|
/** Temp file for briefing injection; cleaned up on kill(). */
|
|
392
570
|
briefingFile;
|
|
393
571
|
/**
|
|
@@ -425,6 +603,7 @@ var CodexProcess = class extends EventEmitter {
|
|
|
425
603
|
this.approvalMethods.clear();
|
|
426
604
|
this.approvalResolvers.clear();
|
|
427
605
|
this.pendingInputs.length = 0;
|
|
606
|
+
this.assistantTextSeen.clear();
|
|
428
607
|
if (this.briefingFile) {
|
|
429
608
|
cleanupBriefingFile(this.briefingFile);
|
|
430
609
|
this.briefingFile = void 0;
|
|
@@ -448,28 +627,39 @@ var CodexAdapter = class {
|
|
|
448
627
|
type = "codex";
|
|
449
628
|
codexBin;
|
|
450
629
|
handshakeTimeoutMs;
|
|
630
|
+
detect;
|
|
451
631
|
constructor(options) {
|
|
452
|
-
this.codexBin = options?.codexBin
|
|
632
|
+
this.codexBin = getResolvedCodexBin(options?.codexBin);
|
|
453
633
|
const envTimeout = Number(process.env.CLAWNET_CODEX_HANDSHAKE_TIMEOUT_MS);
|
|
454
634
|
this.handshakeTimeoutMs = options?.handshakeTimeoutMs ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : 15e3);
|
|
635
|
+
this.detect = options?.detect ?? detectCodexInstall;
|
|
455
636
|
}
|
|
456
637
|
async spawn(options) {
|
|
457
638
|
let cwd = options.workDir || process.cwd();
|
|
458
639
|
if (!existsSync(cwd))
|
|
459
640
|
cwd = homedir();
|
|
460
641
|
const mcpServer = this.buildMcpServerConfig(options);
|
|
461
|
-
const { args, briefingFile } = buildCodexSpawnArgs({
|
|
642
|
+
const { args, briefingFile, modeSource } = buildCodexSpawnArgs({
|
|
462
643
|
sessionId: options.sessionId,
|
|
463
644
|
resumeId: options.resumeId,
|
|
464
645
|
systemPrompt: options.systemPrompt,
|
|
465
646
|
mcpServer,
|
|
466
|
-
sandbox: options.sandbox
|
|
647
|
+
sandbox: options.sandbox,
|
|
648
|
+
model: options.model,
|
|
649
|
+
mode: options.mode
|
|
467
650
|
});
|
|
651
|
+
if (options.mode && modeSource !== "options.mode") {
|
|
652
|
+
log.warn({ sessionId: options.sessionId, requestedMode: options.mode, modeSource }, "codex: invalid options.mode ignored, falling back");
|
|
653
|
+
}
|
|
468
654
|
log.info({ sessionId: options.sessionId, bin: this.codexBin, args, cwd, resumeId: options.resumeId }, "codex spawn: forking app-server");
|
|
469
|
-
const
|
|
655
|
+
const { command, prefixArgs } = resolveSpawnTarget(this.codexBin);
|
|
656
|
+
if (command !== this.codexBin) {
|
|
657
|
+
log.info({ sessionId: options.sessionId, bin: this.codexBin, spawnCmd: command, prefixArgs }, "codex spawn: using resolved shim target (windows .cmd bypass)");
|
|
658
|
+
}
|
|
659
|
+
const proc = spawn(command, [...prefixArgs, ...args], {
|
|
470
660
|
cwd,
|
|
471
661
|
stdio: ["pipe", "pipe", "pipe"],
|
|
472
|
-
env: process.env,
|
|
662
|
+
env: { ...process.env, ...options.env ?? {} },
|
|
473
663
|
windowsHide: true
|
|
474
664
|
});
|
|
475
665
|
const stderrChunks = [];
|
|
@@ -481,12 +671,32 @@ var CodexAdapter = class {
|
|
|
481
671
|
}
|
|
482
672
|
});
|
|
483
673
|
let exitInfo = null;
|
|
674
|
+
let createdCp;
|
|
675
|
+
const emitProcessExit = (cp2, code, signal) => {
|
|
676
|
+
try {
|
|
677
|
+
cp2.emit("exit", code);
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
const isClean = code === 0 || signal === "SIGTERM";
|
|
681
|
+
if (isClean)
|
|
682
|
+
return;
|
|
683
|
+
const stderr = stderrChunks.join("\n").trim().slice(0, 800);
|
|
684
|
+
const detail = stderr ? ` \u2014 stderr: ${stderr}` : "";
|
|
685
|
+
try {
|
|
686
|
+
cp2.emit("error", new Error(`codex process exited (code=${code ?? "null"}, signal=${signal ?? "null"})${detail}`));
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
};
|
|
484
690
|
proc.on("exit", (code, signal) => {
|
|
485
691
|
const isClean = code === 0 || signal === "SIGTERM";
|
|
486
692
|
const level = isClean ? "debug" : "warn";
|
|
487
693
|
log[level]({ sessionId: options.sessionId, code, signal }, "codex process exited");
|
|
488
694
|
if (!exitInfo)
|
|
489
695
|
exitInfo = { code, signal };
|
|
696
|
+
const cpRef = createdCp;
|
|
697
|
+
if (!cpRef)
|
|
698
|
+
return;
|
|
699
|
+
emitProcessExit(cpRef, code, signal);
|
|
490
700
|
});
|
|
491
701
|
await new Promise((resolve, reject) => {
|
|
492
702
|
const t = setTimeout(() => resolve(), 2e3);
|
|
@@ -505,15 +715,39 @@ var CodexAdapter = class {
|
|
|
505
715
|
cp.resumeId = options.resumeId;
|
|
506
716
|
cp.getExitInfo = () => exitInfo;
|
|
507
717
|
cp.getStderr = () => stderrChunks.join("\n").slice(0, 800);
|
|
718
|
+
createdCp = cp;
|
|
719
|
+
if (exitInfo) {
|
|
720
|
+
const { code, signal } = exitInfo;
|
|
721
|
+
setImmediate(() => emitProcessExit(cp, code, signal));
|
|
722
|
+
}
|
|
508
723
|
this.wireRpc(cp);
|
|
509
724
|
return cp;
|
|
510
725
|
}
|
|
511
726
|
buildMcpServerConfig(options) {
|
|
512
727
|
let serverPath;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
728
|
+
const require2 = createRequire(import.meta.url);
|
|
729
|
+
const tryResolve = (specifier) => {
|
|
730
|
+
try {
|
|
731
|
+
const p = require2.resolve(specifier);
|
|
732
|
+
return p && existsSync(p) ? p : void 0;
|
|
733
|
+
} catch {
|
|
734
|
+
return void 0;
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
serverPath = tryResolve("@mclawnet/mcp-server/server");
|
|
738
|
+
if (!serverPath) {
|
|
739
|
+
try {
|
|
740
|
+
const pkgPath = require2.resolve("@mclawnet/mcp-server/package.json");
|
|
741
|
+
const candidate = join2(dirname(pkgPath), "dist", "server.js");
|
|
742
|
+
if (existsSync(candidate))
|
|
743
|
+
serverPath = candidate;
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (!serverPath) {
|
|
748
|
+
serverPath = tryResolve("@mclawnet/mcp-server/dist/server.js");
|
|
749
|
+
}
|
|
750
|
+
if (!serverPath) {
|
|
517
751
|
const devPath = join2(import.meta.dirname ?? __dirname, "../../mcp-server/dist/server.js");
|
|
518
752
|
if (existsSync(devPath))
|
|
519
753
|
serverPath = devPath;
|
|
@@ -522,6 +756,7 @@ var CodexAdapter = class {
|
|
|
522
756
|
log.warn({ pkg: "@mclawnet/mcp-server" }, "codex: clawnet-mcp-server not resolvable, codex role will lack MCP tools");
|
|
523
757
|
return void 0;
|
|
524
758
|
}
|
|
759
|
+
log.debug({ serverPath }, "codex: resolved clawnet-mcp-server path");
|
|
525
760
|
const env = {};
|
|
526
761
|
if (options.workDir)
|
|
527
762
|
env.CLAWNET_WORK_DIR = options.workDir;
|
|
@@ -646,7 +881,24 @@ var CodexAdapter = class {
|
|
|
646
881
|
return;
|
|
647
882
|
}
|
|
648
883
|
if (method === "error") {
|
|
884
|
+
const p = params ?? {};
|
|
885
|
+
const message = p.error?.message ?? "codex emitted unspecified error";
|
|
649
886
|
log.warn({ sessionId: cp.id, params }, "codex error notification");
|
|
887
|
+
cp.emit("error", new Error(`codex: ${message}`));
|
|
888
|
+
if (p.willRetry === false) {
|
|
889
|
+
void cp.kill().catch((err) => {
|
|
890
|
+
log.warn({ err, sessionId: cp.id }, "kill after fatal codex error failed");
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (method === "thread/status/changed") {
|
|
896
|
+
const p = params ?? {};
|
|
897
|
+
if (p.status?.type === "systemError") {
|
|
898
|
+
log.warn({ sessionId: cp.id, params }, "codex thread systemError");
|
|
899
|
+
cp.emit("error", new Error("codex: thread entered systemError"));
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
650
902
|
}
|
|
651
903
|
if (method === "turn/completed" || method === "thread/turnComplete" || method === "turn/complete") {
|
|
652
904
|
log.info({ sessionId: cp.id, method }, "codex turn complete");
|
|
@@ -659,6 +911,49 @@ var CodexAdapter = class {
|
|
|
659
911
|
log.info({ sessionId: cp.id }, "codex turn started");
|
|
660
912
|
return;
|
|
661
913
|
}
|
|
914
|
+
if (method === "item/agentMessage/delta") {
|
|
915
|
+
const p = params ?? {};
|
|
916
|
+
if (typeof p.itemId === "string" && typeof p.delta === "string") {
|
|
917
|
+
const prev = cp.assistantTextSeen.get(p.itemId) ?? "";
|
|
918
|
+
if (!cp.assistantTextSeen.has(p.itemId) && cp.assistantTextSeen.size >= ASSISTANT_TEXT_SEEN_MAX) {
|
|
919
|
+
const oldest = cp.assistantTextSeen.keys().next().value;
|
|
920
|
+
if (oldest !== void 0)
|
|
921
|
+
cp.assistantTextSeen.delete(oldest);
|
|
922
|
+
}
|
|
923
|
+
cp.assistantTextSeen.set(p.itemId, prev + p.delta);
|
|
924
|
+
} else {
|
|
925
|
+
log.warn({ sessionId: cp.id, params: p }, "codex agentMessage delta: missing itemId/delta \u2014 dedupe will not apply to this item");
|
|
926
|
+
}
|
|
927
|
+
} else if (method === "item/completed") {
|
|
928
|
+
const p = params ?? {};
|
|
929
|
+
const item = p.item;
|
|
930
|
+
if (item?.type === "agentMessage") {
|
|
931
|
+
if (typeof item.id !== "string" || typeof item.text !== "string") {
|
|
932
|
+
log.warn({ sessionId: cp.id, item }, "codex agentMessage completed: malformed item.id/text \u2014 dropping to avoid duplicate emission");
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const seen = cp.assistantTextSeen.get(item.id) ?? "";
|
|
936
|
+
cp.assistantTextSeen.delete(item.id);
|
|
937
|
+
const full = item.text;
|
|
938
|
+
let suffix;
|
|
939
|
+
if (full === seen) {
|
|
940
|
+
return;
|
|
941
|
+
} else if (seen.length > 0 && full.startsWith(seen)) {
|
|
942
|
+
suffix = full.slice(seen.length);
|
|
943
|
+
} else {
|
|
944
|
+
suffix = full;
|
|
945
|
+
if (seen.length > 0) {
|
|
946
|
+
log.warn({ sessionId: cp.id, itemId: item.id, seenLen: seen.length, fullLen: full.length }, "codex agentMessage: completed text diverges from accumulated deltas \u2014 emitting full");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (suffix.length === 0)
|
|
950
|
+
return;
|
|
951
|
+
const out2 = { kind: "assistant_text", text: suffix };
|
|
952
|
+
this.logBackendOutput(cp.id, method, out2);
|
|
953
|
+
cp.emit("output", out2);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
662
957
|
const out = mapCodexFrame({ method, params });
|
|
663
958
|
this.logBackendOutput(cp.id, method, out);
|
|
664
959
|
cp.emit("output", out);
|
|
@@ -763,6 +1058,18 @@ var CodexAdapter = class {
|
|
|
763
1058
|
if (process2 instanceof CodexProcess)
|
|
764
1059
|
process2.on("exit", handler);
|
|
765
1060
|
}
|
|
1061
|
+
async getManifest() {
|
|
1062
|
+
const det = await this.detect();
|
|
1063
|
+
return {
|
|
1064
|
+
kind: "codex",
|
|
1065
|
+
installed: det.installed,
|
|
1066
|
+
binaryPath: det.binaryPath,
|
|
1067
|
+
version: det.version,
|
|
1068
|
+
unavailableReason: det.reason,
|
|
1069
|
+
models: det.installed ? CODEX_MODELS : [],
|
|
1070
|
+
modes: det.installed ? CODEX_MODES : []
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
766
1073
|
};
|
|
767
1074
|
export {
|
|
768
1075
|
CodexAdapter,
|
|
@@ -772,4 +1079,4 @@ export {
|
|
|
772
1079
|
mapCodexFrame,
|
|
773
1080
|
parseApprovalRequest
|
|
774
1081
|
};
|
|
775
|
-
//# sourceMappingURL=dist-
|
|
1082
|
+
//# sourceMappingURL=dist-NWVHAP5R.js.map
|