clinkx 0.1.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/README.md +70 -0
- package/conf/adapters/claude.json +33 -0
- package/conf/adapters/codex.json +33 -0
- package/conf/adapters/gemini.json +33 -0
- package/conf/adapters/glm.json +39 -0
- package/conf/adapters/hapi/claude.json +36 -0
- package/conf/adapters/hapi/codex.json +36 -0
- package/conf/adapters/hapi/gemini.json +36 -0
- package/conf/adapters/hapi/glm.json +40 -0
- package/conf/prompts/codereviewer.txt +8 -0
- package/conf/prompts/debug.txt +6 -0
- package/conf/prompts/default.txt +8 -0
- package/conf/prompts/json.txt +5 -0
- package/conf/prompts/planner.txt +8 -0
- package/dist/artifacts.d.ts +9 -0
- package/dist/artifacts.js +24 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/concurrency.d.ts +15 -0
- package/dist/concurrency.js +39 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config.d.ts +103 -0
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -0
- package/dist/continuation.d.ts +15 -0
- package/dist/continuation.js +42 -0
- package/dist/continuation.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +52 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.js +88 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +21 -0
- package/dist/handler.js +45 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +30 -0
- package/dist/logger.js.map +1 -0
- package/dist/parsers/claude-json.d.ts +2 -0
- package/dist/parsers/claude-json.js +58 -0
- package/dist/parsers/claude-json.js.map +1 -0
- package/dist/parsers/codex-jsonl.d.ts +2 -0
- package/dist/parsers/codex-jsonl.js +75 -0
- package/dist/parsers/codex-jsonl.js.map +1 -0
- package/dist/parsers/extract.d.ts +25 -0
- package/dist/parsers/extract.js +87 -0
- package/dist/parsers/extract.js.map +1 -0
- package/dist/parsers/gemini-json.d.ts +2 -0
- package/dist/parsers/gemini-json.js +72 -0
- package/dist/parsers/gemini-json.js.map +1 -0
- package/dist/parsers/json-extract.d.ts +2 -0
- package/dist/parsers/json-extract.js +19 -0
- package/dist/parsers/json-extract.js.map +1 -0
- package/dist/parsers/summary.d.ts +7 -0
- package/dist/parsers/summary.js +29 -0
- package/dist/parsers/summary.js.map +1 -0
- package/dist/parsers/text.d.ts +2 -0
- package/dist/parsers/text.js +14 -0
- package/dist/parsers/text.js.map +1 -0
- package/dist/parsers/types.d.ts +25 -0
- package/dist/parsers/types.js +2 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/parsers/utils.d.ts +11 -0
- package/dist/parsers/utils.js +116 -0
- package/dist/parsers/utils.js.map +1 -0
- package/dist/paths.d.ts +17 -0
- package/dist/paths.js +87 -0
- package/dist/paths.js.map +1 -0
- package/dist/pipeline.d.ts +37 -0
- package/dist/pipeline.js +232 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/progress.d.ts +28 -0
- package/dist/progress.js +78 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-mode.d.ts +15 -0
- package/dist/prompt-mode.js +23 -0
- package/dist/prompt-mode.js.map +1 -0
- package/dist/prompt.d.ts +25 -0
- package/dist/prompt.js +108 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +27 -0
- package/dist/registry.js +163 -0
- package/dist/registry.js.map +1 -0
- package/dist/result-contract.d.ts +13 -0
- package/dist/result-contract.js +80 -0
- package/dist/result-contract.js.map +1 -0
- package/dist/run-dir.d.ts +12 -0
- package/dist/run-dir.js +32 -0
- package/dist/run-dir.js.map +1 -0
- package/dist/runner.d.ts +39 -0
- package/dist/runner.js +220 -0
- package/dist/runner.js.map +1 -0
- package/dist/safety.d.ts +22 -0
- package/dist/safety.js +47 -0
- package/dist/safety.js.map +1 -0
- package/dist/schema.d.ts +69 -0
- package/dist/schema.js +91 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +109 -0
- package/dist/server.js.map +1 -0
- package/package.json +34 -0
package/dist/registry.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync, existsSync, } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { CliAdapterConfigSchema } from "./config.js";
|
|
6
|
+
import { InvalidParamsError } from "./errors.js";
|
|
7
|
+
import { logger } from "./logger.js";
|
|
8
|
+
function discoverConfigPaths() {
|
|
9
|
+
const envPath = process.env["CLINKX_CONFIG_PATH"];
|
|
10
|
+
if (envPath != null && envPath !== "") {
|
|
11
|
+
return resolveConfigPath(envPath);
|
|
12
|
+
}
|
|
13
|
+
const paths = [];
|
|
14
|
+
// XDG-aligned: ~/.config/clinkx/adapters/*.json
|
|
15
|
+
const xdgHome = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
|
|
16
|
+
const xdgDir = join(xdgHome, "clinkx", "adapters");
|
|
17
|
+
paths.push(...listJsonFiles(xdgDir));
|
|
18
|
+
// Built-in: conf/adapters/*.json relative to package install location
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const builtinDir = join(__dirname, "..", "conf", "adapters");
|
|
21
|
+
paths.push(...listJsonFiles(builtinDir));
|
|
22
|
+
return paths;
|
|
23
|
+
}
|
|
24
|
+
function resolveConfigPath(configPath) {
|
|
25
|
+
try {
|
|
26
|
+
const stats = statSync(configPath);
|
|
27
|
+
if (stats.isDirectory()) {
|
|
28
|
+
return listJsonFiles(configPath);
|
|
29
|
+
}
|
|
30
|
+
return [configPath];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
logger.warn({ path: configPath }, "CLINKX_CONFIG_PATH not found");
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function listJsonFiles(dirPath) {
|
|
38
|
+
if (!existsSync(dirPath))
|
|
39
|
+
return [];
|
|
40
|
+
try {
|
|
41
|
+
return readdirSync(dirPath)
|
|
42
|
+
.filter((f) => f.endsWith(".json"))
|
|
43
|
+
.sort()
|
|
44
|
+
.map((f) => join(dirPath, f));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class AdapterRegistry {
|
|
51
|
+
adapters = new Map();
|
|
52
|
+
configSources = new Map();
|
|
53
|
+
builtinAdaptersDir;
|
|
54
|
+
constructor() {
|
|
55
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
56
|
+
this.builtinAdaptersDir = join(__dirname, "..", "conf", "adapters");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load adapters from config files discovered via the standard precedence:
|
|
60
|
+
* CLINKX_CONFIG_PATH → XDG config → conf/adapters/*.json
|
|
61
|
+
*/
|
|
62
|
+
static load() {
|
|
63
|
+
const registry = new AdapterRegistry();
|
|
64
|
+
const paths = discoverConfigPaths();
|
|
65
|
+
for (const filePath of paths) {
|
|
66
|
+
registry.loadConfigFile(filePath);
|
|
67
|
+
}
|
|
68
|
+
logger.info({ adapters: registry.getAdapterNames() }, "adapter registry loaded");
|
|
69
|
+
return registry;
|
|
70
|
+
}
|
|
71
|
+
/** Create a registry from explicit config objects (for testing). */
|
|
72
|
+
static fromConfigs(configs) {
|
|
73
|
+
const registry = new AdapterRegistry();
|
|
74
|
+
for (const config of configs) {
|
|
75
|
+
if (registry.adapters.has(config.name)) {
|
|
76
|
+
throw new Error(`Duplicate adapter name "${config.name}"`);
|
|
77
|
+
}
|
|
78
|
+
registry.adapters.set(config.name, config);
|
|
79
|
+
}
|
|
80
|
+
return registry;
|
|
81
|
+
}
|
|
82
|
+
loadConfigFile(filePath) {
|
|
83
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
84
|
+
const json = JSON.parse(raw);
|
|
85
|
+
const result = CliAdapterConfigSchema.safeParse(json);
|
|
86
|
+
if (!result.success) {
|
|
87
|
+
throw new Error(`Invalid adapter config in ${filePath}: ${result.error.message}`);
|
|
88
|
+
}
|
|
89
|
+
const config = result.data;
|
|
90
|
+
// Resolve prompt_file references into inline_prompt (inline_prompt wins if both set).
|
|
91
|
+
// Try relative to the config file first; fall back to the built-in adapters dir
|
|
92
|
+
// so user-override configs can reference shipped prompt files without copying them.
|
|
93
|
+
const configDir = dirname(filePath);
|
|
94
|
+
for (const [roleName, role] of Object.entries(config.roles)) {
|
|
95
|
+
if (role.inline_prompt == null && role.prompt_file != null) {
|
|
96
|
+
const resolved = join(configDir, role.prompt_file);
|
|
97
|
+
let promptPath;
|
|
98
|
+
if (existsSync(resolved)) {
|
|
99
|
+
promptPath = resolved;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const builtinFallback = join(this.builtinAdaptersDir, role.prompt_file);
|
|
103
|
+
if (existsSync(builtinFallback)) {
|
|
104
|
+
promptPath = builtinFallback;
|
|
105
|
+
logger.debug({ role: roleName, resolved, fallback: builtinFallback }, "prompt_file not found next to config, falling back to built-in");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (promptPath == null) {
|
|
109
|
+
throw new Error(`Prompt file not found for role "${roleName}" in ${filePath}: ${resolved}`);
|
|
110
|
+
}
|
|
111
|
+
role.inline_prompt = readFileSync(promptPath, "utf-8").trimEnd();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const existing = this.configSources.get(config.name);
|
|
115
|
+
if (existing != null) {
|
|
116
|
+
// First-wins: higher-precedence source (user XDG) already loaded — skip built-in.
|
|
117
|
+
logger.info({ name: config.name, kept: existing, skipped: filePath }, "adapter \"%s\" already loaded from %s — skipping %s", config.name, existing, filePath);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.adapters.set(config.name, config);
|
|
121
|
+
this.configSources.set(config.name, filePath);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a cli_name to its adapter config.
|
|
125
|
+
*
|
|
126
|
+
* Default CLI policy: 1 adapter = implicit; >1 = require explicit cli_name.
|
|
127
|
+
* Throws InvalidParamsError (→ -32602) on unknown name.
|
|
128
|
+
*/
|
|
129
|
+
resolveAdapter(cliName) {
|
|
130
|
+
if (cliName == null) {
|
|
131
|
+
if (this.adapters.size === 0) {
|
|
132
|
+
throw new InvalidParamsError("No CLI adapters configured");
|
|
133
|
+
}
|
|
134
|
+
if (this.adapters.size === 1) {
|
|
135
|
+
return [...this.adapters.values()][0];
|
|
136
|
+
}
|
|
137
|
+
throw new InvalidParamsError(`cli_name is required when multiple adapters are configured. Available: ${this.getAdapterNames().join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
const adapter = this.adapters.get(cliName);
|
|
140
|
+
if (adapter == null) {
|
|
141
|
+
throw new InvalidParamsError(`Unknown cli_name "${cliName}". Available: ${this.getAdapterNames().join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
return adapter;
|
|
144
|
+
}
|
|
145
|
+
/** Sorted list of configured adapter names (for schema enum / error messages). */
|
|
146
|
+
getAdapterNames() {
|
|
147
|
+
return [...this.adapters.keys()].sort();
|
|
148
|
+
}
|
|
149
|
+
/** Deduplicated, sorted list of all role names across every adapter. */
|
|
150
|
+
getAllRoleNames() {
|
|
151
|
+
const roles = new Set();
|
|
152
|
+
for (const adapter of this.adapters.values()) {
|
|
153
|
+
for (const name of Object.keys(adapter.roles)) {
|
|
154
|
+
roles.add(name);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return [...roles].sort();
|
|
158
|
+
}
|
|
159
|
+
get size() {
|
|
160
|
+
return this.adapters.size;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAyB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,SAAS,mBAAmB;IAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClD,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACtC,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,gDAAgD;IAChD,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IAErC,sEAAsE;IACtE,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;IAEzC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAkB;IAC3C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,aAAa,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,8BAA8B,CAAC,CAAC;QAClE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,OAAO,CAAC;aACxB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;aAClC,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,OAAO,eAAe;IACT,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC/C,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,kBAAkB,CAAS;IAE5C;QACE,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI;QACT,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAC;QACpC,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC7B,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,CAAC,IAAI,CACT,EAAE,QAAQ,EAAE,QAAQ,CAAC,eAAe,EAAE,EAAE,EACxC,yBAAyB,CAC1B,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,oEAAoE;IACpE,MAAM,CAAC,WAAW,CAAC,OAA2B;QAC5C,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;YAC7D,CAAC;YACD,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,cAAc,CAAC,QAAgB;QACrC,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,6BAA6B,QAAQ,KAAK,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CACjE,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC;QAE3B,sFAAsF;QACtF,gFAAgF;QAChF,oFAAoF;QACpF,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpC,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5D,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,EAAE,CAAC;gBAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;gBACnD,IAAI,UAA8B,CAAC;gBACnC,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACzB,UAAU,GAAG,QAAQ,CAAC;gBACxB,CAAC;qBAAM,CAAC;oBACN,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;oBACxE,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;wBAChC,UAAU,GAAG,eAAe,CAAC;wBAC7B,MAAM,CAAC,KAAK,CACV,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,EACvD,gEAAgE,CACjE,CAAC;oBACJ,CAAC;gBACH,CAAC;gBACD,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;oBACvB,MAAM,IAAI,KAAK,CACb,mCAAmC,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,EAAE,CAC3E,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;YACnE,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,kFAAkF;YAClF,MAAM,CAAC,IAAI,CACT,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EACxD,qDAAqD,EACrD,MAAM,CAAC,IAAI,EACX,QAAQ,EACR,QAAQ,CACT,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,OAA2B;QACxC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,kBAAkB,CAAC,4BAA4B,CAAC,CAAC;YAC7D,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC7B,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC;YACzC,CAAC;YACD,MAAM,IAAI,kBAAkB,CAC1B,0EAA0E,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9G,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,kBAAkB,CAC1B,qBAAqB,OAAO,iBAAiB,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,kFAAkF;IAClF,eAAe;QACb,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,wEAAwE;IACxE,eAAe;QACb,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely read a result contract file from a run working directory.
|
|
3
|
+
*
|
|
4
|
+
* Safety rules:
|
|
5
|
+
* 1. Only allowlisted filenames (RESULT.md, RESULT.json)
|
|
6
|
+
* 2. lstat must show a regular file (not a symlink)
|
|
7
|
+
* 3. realpath must resolve within the run directory
|
|
8
|
+
*
|
|
9
|
+
* Returns file contents as a string, or null if:
|
|
10
|
+
* - The file doesn't exist
|
|
11
|
+
* - The file fails safety checks
|
|
12
|
+
*/
|
|
13
|
+
export declare function readResultFile(runDir: string, filename: string): string | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
/** Filenames we're willing to read from a run directory. */
|
|
5
|
+
const ALLOWED_RESULT_FILES = new Set(["RESULT.md", "RESULT.json"]);
|
|
6
|
+
/**
|
|
7
|
+
* Safely read a result contract file from a run working directory.
|
|
8
|
+
*
|
|
9
|
+
* Safety rules:
|
|
10
|
+
* 1. Only allowlisted filenames (RESULT.md, RESULT.json)
|
|
11
|
+
* 2. lstat must show a regular file (not a symlink)
|
|
12
|
+
* 3. realpath must resolve within the run directory
|
|
13
|
+
*
|
|
14
|
+
* Returns file contents as a string, or null if:
|
|
15
|
+
* - The file doesn't exist
|
|
16
|
+
* - The file fails safety checks
|
|
17
|
+
*/
|
|
18
|
+
export function readResultFile(runDir, filename) {
|
|
19
|
+
if (!ALLOWED_RESULT_FILES.has(filename)) {
|
|
20
|
+
logger.warn({ filename }, "result contract: filename not in allowlist");
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const filePath = path.join(runDir, filename);
|
|
24
|
+
let resolvedRunDir;
|
|
25
|
+
try {
|
|
26
|
+
resolvedRunDir = fs.realpathSync(path.resolve(runDir));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
resolvedRunDir = path.resolve(runDir);
|
|
30
|
+
}
|
|
31
|
+
// Check existence via lstat (does not follow symlinks)
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = fs.lstatSync(filePath);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// File doesn't exist — normal case
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Reject symlinks
|
|
41
|
+
if (stat.isSymbolicLink()) {
|
|
42
|
+
logger.warn({ filePath }, "result contract: rejecting symlink");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// Must be a regular file
|
|
46
|
+
if (!stat.isFile()) {
|
|
47
|
+
logger.warn({ filePath }, "result contract: not a regular file");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Enforce size limit to prevent memory pressure from oversized result files
|
|
51
|
+
const MAX_RESULT_SIZE = 1_048_576; // 1 MB
|
|
52
|
+
if (stat.size > MAX_RESULT_SIZE) {
|
|
53
|
+
logger.warn({ filePath, size: stat.size, max: MAX_RESULT_SIZE }, "result contract: file exceeds size limit");
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// Realpath must resolve within run dir
|
|
57
|
+
let realFilePath;
|
|
58
|
+
try {
|
|
59
|
+
realFilePath = fs.realpathSync(filePath);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
logger.warn({ filePath }, "result contract: failed to resolve realpath");
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const normalizedRunDir = resolvedRunDir.endsWith(path.sep)
|
|
66
|
+
? resolvedRunDir
|
|
67
|
+
: resolvedRunDir + path.sep;
|
|
68
|
+
if (!realFilePath.startsWith(normalizedRunDir) && realFilePath !== resolvedRunDir) {
|
|
69
|
+
logger.warn({ realFilePath, runDir: resolvedRunDir }, "result contract: realpath escapes run directory");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
logger.warn({ filePath, err }, "result contract: failed to read file");
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=result-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result-contract.js","sourceRoot":"","sources":["../src/result-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RunDir {
|
|
2
|
+
path: string;
|
|
3
|
+
runId: string;
|
|
4
|
+
cleanup(): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Create a per-run temp directory under ${TMPDIR}/clinkx/runs/<run_id>.
|
|
8
|
+
*
|
|
9
|
+
* This directory is NOT used as subprocess cwd — it's a separate workspace
|
|
10
|
+
* passed by absolute path in the prompt and/or env var.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createRunDir(): RunDir;
|
package/dist/run-dir.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a per-run temp directory under ${TMPDIR}/clinkx/runs/<run_id>.
|
|
8
|
+
*
|
|
9
|
+
* This directory is NOT used as subprocess cwd — it's a separate workspace
|
|
10
|
+
* passed by absolute path in the prompt and/or env var.
|
|
11
|
+
*/
|
|
12
|
+
export function createRunDir() {
|
|
13
|
+
const runId = randomUUID();
|
|
14
|
+
const base = join(tmpdir(), "clinkx", "runs");
|
|
15
|
+
mkdirSync(base, { recursive: true });
|
|
16
|
+
const path = mkdtempSync(join(base, `${runId}-`));
|
|
17
|
+
logger.debug({ runId, path }, "created run directory");
|
|
18
|
+
return {
|
|
19
|
+
path,
|
|
20
|
+
runId,
|
|
21
|
+
cleanup() {
|
|
22
|
+
try {
|
|
23
|
+
rmSync(path, { recursive: true, force: true });
|
|
24
|
+
logger.debug({ runId }, "cleaned up run directory");
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
logger.warn({ runId, err }, "failed to clean up run directory");
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=run-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-dir.js","sourceRoot":"","sources":["../src/run-dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9C,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC;IAElD,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAEvD,OAAO;QACL,IAAI;QACJ,KAAK;QACL,OAAO;YACL,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC/C,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,0BAA0B,CAAC,CAAC;YACtD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,kCAAkC,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import type { CliAdapterConfig } from "./config.js";
|
|
3
|
+
/** Active subprocess registry for graceful shutdown. */
|
|
4
|
+
export declare const activeProcesses: Set<ChildProcess>;
|
|
5
|
+
/** Kill all tracked subprocesses (SIGTERM). */
|
|
6
|
+
export declare function killAllActive(): void;
|
|
7
|
+
export interface RunOptions {
|
|
8
|
+
adapter: CliAdapterConfig;
|
|
9
|
+
prompt: string;
|
|
10
|
+
runDirPath: string;
|
|
11
|
+
timeoutSeconds?: number | undefined;
|
|
12
|
+
unsafe?: boolean | undefined;
|
|
13
|
+
signal?: AbortSignal | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface RunResult {
|
|
16
|
+
exitCode: number | null;
|
|
17
|
+
signal: string | null;
|
|
18
|
+
stdoutHead: string;
|
|
19
|
+
stdoutTail: string;
|
|
20
|
+
stderrHead: string;
|
|
21
|
+
stderrTail: string;
|
|
22
|
+
stdoutTruncated: boolean;
|
|
23
|
+
stderrTruncated: boolean;
|
|
24
|
+
stdoutBytes: number;
|
|
25
|
+
stderrBytes: number;
|
|
26
|
+
timedOut: boolean;
|
|
27
|
+
killed: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Spawn and manage a subprocess for a CLI adapter.
|
|
31
|
+
*
|
|
32
|
+
* - shell:false for safety
|
|
33
|
+
* - Prompt delivered via configured prompt_mode
|
|
34
|
+
* - Head+tail output capture per stream
|
|
35
|
+
* - SIGTERM → grace → SIGKILL timeout escalation
|
|
36
|
+
* - Process-group kill on Unix
|
|
37
|
+
* - AbortSignal for MCP cancellation
|
|
38
|
+
*/
|
|
39
|
+
export declare function runSubprocess(options: RunOptions): Promise<RunResult>;
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { ExecutionError } from "./errors.js";
|
|
4
|
+
import { buildEnv, redactSecrets } from "./env.js";
|
|
5
|
+
import { preparePromptDelivery } from "./prompt-mode.js";
|
|
6
|
+
import { assertNoTtyRequired, resolveArgs } from "./safety.js";
|
|
7
|
+
/** Active subprocess registry for graceful shutdown. */
|
|
8
|
+
export const activeProcesses = new Set();
|
|
9
|
+
/** Kill all tracked subprocesses (SIGTERM). */
|
|
10
|
+
export function killAllActive() {
|
|
11
|
+
for (const child of activeProcesses) {
|
|
12
|
+
killProcessGroup(child, "SIGTERM");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_MAX_PROMPT_BYTES = 1_048_576; // 1 MB
|
|
16
|
+
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
17
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 10_485_760; // 10 MB
|
|
18
|
+
const GRACE_PERIOD_MS = 5_000;
|
|
19
|
+
const HEAD_TAIL_BYTES = 50_000; // 50 KB per head/tail segment
|
|
20
|
+
function safeParseInt(val, fallback) {
|
|
21
|
+
if (val == null || val === "")
|
|
22
|
+
return fallback;
|
|
23
|
+
const n = Number(val);
|
|
24
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
25
|
+
}
|
|
26
|
+
function createStreamCapture(maxBytes) {
|
|
27
|
+
const headSize = Math.min(HEAD_TAIL_BYTES, Math.floor(maxBytes / 2));
|
|
28
|
+
const tailSize = Math.min(HEAD_TAIL_BYTES, Math.floor(maxBytes / 2));
|
|
29
|
+
const capture = {
|
|
30
|
+
head: Buffer.alloc(0),
|
|
31
|
+
tail: Buffer.alloc(0),
|
|
32
|
+
totalBytes: 0,
|
|
33
|
+
truncated: false,
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
capture,
|
|
37
|
+
onData(chunk) {
|
|
38
|
+
capture.totalBytes += chunk.length;
|
|
39
|
+
let tailPortion = null;
|
|
40
|
+
// Fill head buffer first
|
|
41
|
+
if (capture.head.length < headSize) {
|
|
42
|
+
const remaining = headSize - capture.head.length;
|
|
43
|
+
const toHead = chunk.subarray(0, remaining);
|
|
44
|
+
capture.head = Buffer.concat([capture.head, toHead]);
|
|
45
|
+
// Only the overflow portion goes to tail
|
|
46
|
+
if (chunk.length > remaining) {
|
|
47
|
+
tailPortion = chunk.subarray(remaining);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
tailPortion = chunk;
|
|
52
|
+
}
|
|
53
|
+
// Update tail (ring-buffer style)
|
|
54
|
+
if (tailPortion != null) {
|
|
55
|
+
const combined = Buffer.concat([capture.tail, tailPortion]);
|
|
56
|
+
if (combined.length > tailSize) {
|
|
57
|
+
capture.tail = combined.subarray(combined.length - tailSize);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
capture.tail = combined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Only mark truncated when total exceeds head + tail capacity
|
|
64
|
+
if (capture.totalBytes > headSize + tailSize) {
|
|
65
|
+
capture.truncated = true;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function killProcessGroup(child, sig) {
|
|
71
|
+
const pid = child.pid;
|
|
72
|
+
if (pid == null)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
// Kill entire process group on Unix (detached = new group)
|
|
76
|
+
process.kill(-pid, sig);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
try {
|
|
80
|
+
child.kill(sig);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Process already exited
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Spawn and manage a subprocess for a CLI adapter.
|
|
89
|
+
*
|
|
90
|
+
* - shell:false for safety
|
|
91
|
+
* - Prompt delivered via configured prompt_mode
|
|
92
|
+
* - Head+tail output capture per stream
|
|
93
|
+
* - SIGTERM → grace → SIGKILL timeout escalation
|
|
94
|
+
* - Process-group kill on Unix
|
|
95
|
+
* - AbortSignal for MCP cancellation
|
|
96
|
+
*/
|
|
97
|
+
export function runSubprocess(options) {
|
|
98
|
+
const { adapter, prompt, runDirPath, signal } = options;
|
|
99
|
+
const timeoutSeconds = options.timeoutSeconds ??
|
|
100
|
+
adapter.timeout_seconds ??
|
|
101
|
+
safeParseInt(process.env["CLINKX_TIMEOUT_SECONDS"], DEFAULT_TIMEOUT_SECONDS);
|
|
102
|
+
const maxPromptBytes = safeParseInt(process.env["CLINKX_MAX_PROMPT_BYTES"], DEFAULT_MAX_PROMPT_BYTES);
|
|
103
|
+
const maxOutputBytes = safeParseInt(process.env["CLINKX_MAX_OUTPUT_BYTES"], DEFAULT_MAX_OUTPUT_BYTES);
|
|
104
|
+
// Bound input size
|
|
105
|
+
const promptBuf = Buffer.from(prompt, "utf-8");
|
|
106
|
+
if (promptBuf.length > maxPromptBytes) {
|
|
107
|
+
return Promise.reject(new ExecutionError(`Prompt exceeds maximum size (${promptBuf.length} > ${maxPromptBytes} bytes)`));
|
|
108
|
+
}
|
|
109
|
+
// Requires-TTY check
|
|
110
|
+
try {
|
|
111
|
+
assertNoTtyRequired(adapter.name, adapter.requires_tty);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return Promise.reject(err);
|
|
115
|
+
}
|
|
116
|
+
// Build args
|
|
117
|
+
const args = resolveArgs(adapter.args, adapter.unsafe_args, options.unsafe === true);
|
|
118
|
+
// Prepare prompt delivery
|
|
119
|
+
const delivery = preparePromptDelivery(adapter.prompt_mode, prompt, runDirPath);
|
|
120
|
+
args.push(...delivery.extraArgs);
|
|
121
|
+
// Build sanitized env
|
|
122
|
+
const env = buildEnv(adapter, { CLINKX_RUN_DIR: runDirPath });
|
|
123
|
+
logger.debug({
|
|
124
|
+
command: adapter.command,
|
|
125
|
+
args,
|
|
126
|
+
env: redactSecrets(env),
|
|
127
|
+
timeoutSeconds,
|
|
128
|
+
promptMode: adapter.prompt_mode,
|
|
129
|
+
}, "spawning subprocess");
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
let timedOut = false;
|
|
132
|
+
let killed = false;
|
|
133
|
+
let timeoutHandle;
|
|
134
|
+
let graceHandle;
|
|
135
|
+
let child;
|
|
136
|
+
try {
|
|
137
|
+
child = spawn(adapter.command, args, {
|
|
138
|
+
shell: false,
|
|
139
|
+
cwd: process.cwd(),
|
|
140
|
+
env,
|
|
141
|
+
stdio: [delivery.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
142
|
+
detached: true, // New process group for clean kill
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
147
|
+
reject(new ExecutionError(`Failed to spawn "${adapter.command}": ${msg}`));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
activeProcesses.add(child);
|
|
151
|
+
const stdoutCapture = createStreamCapture(maxOutputBytes);
|
|
152
|
+
const stderrCapture = createStreamCapture(maxOutputBytes);
|
|
153
|
+
child.stdout?.on("data", (chunk) => stdoutCapture.onData(chunk));
|
|
154
|
+
child.stderr?.on("data", (chunk) => stderrCapture.onData(chunk));
|
|
155
|
+
// Write prompt to stdin
|
|
156
|
+
if (delivery.useStdin && child.stdin) {
|
|
157
|
+
child.stdin.on("error", (err) => {
|
|
158
|
+
// EPIPE is expected when child exits before we finish writing — non-fatal
|
|
159
|
+
logger.debug({ err: err.message }, "stdin write error (non-fatal)");
|
|
160
|
+
});
|
|
161
|
+
child.stdin.write(promptBuf);
|
|
162
|
+
child.stdin.end();
|
|
163
|
+
}
|
|
164
|
+
// Timeout: SIGTERM → grace → SIGKILL
|
|
165
|
+
timeoutHandle = setTimeout(() => {
|
|
166
|
+
timedOut = true;
|
|
167
|
+
logger.warn({ pid: child.pid, timeoutSeconds }, "subprocess timed out, sending SIGTERM");
|
|
168
|
+
killProcessGroup(child, "SIGTERM");
|
|
169
|
+
graceHandle = setTimeout(() => {
|
|
170
|
+
logger.warn({ pid: child.pid }, "grace period expired, sending SIGKILL");
|
|
171
|
+
killProcessGroup(child, "SIGKILL");
|
|
172
|
+
}, GRACE_PERIOD_MS);
|
|
173
|
+
}, timeoutSeconds * 1_000);
|
|
174
|
+
// Cancellation via AbortSignal
|
|
175
|
+
const onAbort = () => {
|
|
176
|
+
killed = true;
|
|
177
|
+
logger.info({ pid: child.pid }, "cancellation received, killing subprocess");
|
|
178
|
+
killProcessGroup(child, "SIGTERM");
|
|
179
|
+
graceHandle = setTimeout(() => {
|
|
180
|
+
killProcessGroup(child, "SIGKILL");
|
|
181
|
+
}, GRACE_PERIOD_MS);
|
|
182
|
+
};
|
|
183
|
+
if (signal) {
|
|
184
|
+
if (signal.aborted) {
|
|
185
|
+
onAbort();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
child.on("error", (err) => {
|
|
192
|
+
clearTimeout(timeoutHandle);
|
|
193
|
+
clearTimeout(graceHandle);
|
|
194
|
+
signal?.removeEventListener("abort", onAbort);
|
|
195
|
+
activeProcesses.delete(child);
|
|
196
|
+
reject(new ExecutionError(`Failed to spawn "${adapter.command}": ${err.message}`));
|
|
197
|
+
});
|
|
198
|
+
child.on("close", (code, exitSignal) => {
|
|
199
|
+
clearTimeout(timeoutHandle);
|
|
200
|
+
clearTimeout(graceHandle);
|
|
201
|
+
signal?.removeEventListener("abort", onAbort);
|
|
202
|
+
activeProcesses.delete(child);
|
|
203
|
+
resolve({
|
|
204
|
+
exitCode: code,
|
|
205
|
+
signal: exitSignal ?? null,
|
|
206
|
+
stdoutHead: stdoutCapture.capture.head.toString("utf-8"),
|
|
207
|
+
stdoutTail: stdoutCapture.capture.tail.toString("utf-8"),
|
|
208
|
+
stderrHead: stderrCapture.capture.head.toString("utf-8"),
|
|
209
|
+
stderrTail: stderrCapture.capture.tail.toString("utf-8"),
|
|
210
|
+
stdoutTruncated: stdoutCapture.capture.truncated,
|
|
211
|
+
stderrTruncated: stderrCapture.capture.truncated,
|
|
212
|
+
stdoutBytes: stdoutCapture.capture.totalBytes,
|
|
213
|
+
stderrBytes: stderrCapture.capture.totalBytes,
|
|
214
|
+
timedOut,
|
|
215
|
+
killed,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/safety.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the final CLI args array, optionally merging unsafe_args.
|
|
3
|
+
*
|
|
4
|
+
* Policy (v1.1): the env gate CLINKX_ENABLE_UNSAFE=1 is the sole security
|
|
5
|
+
* boundary. When it is set and the adapter declares non-empty unsafeArgs,
|
|
6
|
+
* they are always included — the per-request `unsafe` flag is no longer
|
|
7
|
+
* required (retained for backward compat but ignored when the env gate
|
|
8
|
+
* is open).
|
|
9
|
+
*
|
|
10
|
+
* Returns the safe args array, optionally merged with unsafe_args.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveArgs(args: readonly string[], unsafeArgs: readonly string[], requestUnsafe: boolean): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Build debug metadata object. Returns metadata only when debug=true.
|
|
15
|
+
* Never appends to model output — caller must place in a separate content block.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildDebugMetadata(debug: boolean, data: Record<string, unknown>): Record<string, unknown> | null;
|
|
18
|
+
/**
|
|
19
|
+
* Guard against adapters that require a TTY.
|
|
20
|
+
* PTY emulation is not supported in v1 — throw ExecutionError with actionable message.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assertNoTtyRequired(adapterName: string, requiresTty: boolean): void;
|