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.
Files changed (105) hide show
  1. package/README.md +70 -0
  2. package/conf/adapters/claude.json +33 -0
  3. package/conf/adapters/codex.json +33 -0
  4. package/conf/adapters/gemini.json +33 -0
  5. package/conf/adapters/glm.json +39 -0
  6. package/conf/adapters/hapi/claude.json +36 -0
  7. package/conf/adapters/hapi/codex.json +36 -0
  8. package/conf/adapters/hapi/gemini.json +36 -0
  9. package/conf/adapters/hapi/glm.json +40 -0
  10. package/conf/prompts/codereviewer.txt +8 -0
  11. package/conf/prompts/debug.txt +6 -0
  12. package/conf/prompts/default.txt +8 -0
  13. package/conf/prompts/json.txt +5 -0
  14. package/conf/prompts/planner.txt +8 -0
  15. package/dist/artifacts.d.ts +9 -0
  16. package/dist/artifacts.js +24 -0
  17. package/dist/artifacts.js.map +1 -0
  18. package/dist/concurrency.d.ts +15 -0
  19. package/dist/concurrency.js +39 -0
  20. package/dist/concurrency.js.map +1 -0
  21. package/dist/config.d.ts +103 -0
  22. package/dist/config.js +40 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/continuation.d.ts +15 -0
  25. package/dist/continuation.js +42 -0
  26. package/dist/continuation.js.map +1 -0
  27. package/dist/env.d.ts +10 -0
  28. package/dist/env.js +52 -0
  29. package/dist/env.js.map +1 -0
  30. package/dist/errors.d.ts +68 -0
  31. package/dist/errors.js +88 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/handler.d.ts +21 -0
  34. package/dist/handler.js +45 -0
  35. package/dist/handler.js.map +1 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +38 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/logger.d.ts +16 -0
  40. package/dist/logger.js +30 -0
  41. package/dist/logger.js.map +1 -0
  42. package/dist/parsers/claude-json.d.ts +2 -0
  43. package/dist/parsers/claude-json.js +58 -0
  44. package/dist/parsers/claude-json.js.map +1 -0
  45. package/dist/parsers/codex-jsonl.d.ts +2 -0
  46. package/dist/parsers/codex-jsonl.js +75 -0
  47. package/dist/parsers/codex-jsonl.js.map +1 -0
  48. package/dist/parsers/extract.d.ts +25 -0
  49. package/dist/parsers/extract.js +87 -0
  50. package/dist/parsers/extract.js.map +1 -0
  51. package/dist/parsers/gemini-json.d.ts +2 -0
  52. package/dist/parsers/gemini-json.js +72 -0
  53. package/dist/parsers/gemini-json.js.map +1 -0
  54. package/dist/parsers/json-extract.d.ts +2 -0
  55. package/dist/parsers/json-extract.js +19 -0
  56. package/dist/parsers/json-extract.js.map +1 -0
  57. package/dist/parsers/summary.d.ts +7 -0
  58. package/dist/parsers/summary.js +29 -0
  59. package/dist/parsers/summary.js.map +1 -0
  60. package/dist/parsers/text.d.ts +2 -0
  61. package/dist/parsers/text.js +14 -0
  62. package/dist/parsers/text.js.map +1 -0
  63. package/dist/parsers/types.d.ts +25 -0
  64. package/dist/parsers/types.js +2 -0
  65. package/dist/parsers/types.js.map +1 -0
  66. package/dist/parsers/utils.d.ts +11 -0
  67. package/dist/parsers/utils.js +116 -0
  68. package/dist/parsers/utils.js.map +1 -0
  69. package/dist/paths.d.ts +17 -0
  70. package/dist/paths.js +87 -0
  71. package/dist/paths.js.map +1 -0
  72. package/dist/pipeline.d.ts +37 -0
  73. package/dist/pipeline.js +232 -0
  74. package/dist/pipeline.js.map +1 -0
  75. package/dist/progress.d.ts +28 -0
  76. package/dist/progress.js +78 -0
  77. package/dist/progress.js.map +1 -0
  78. package/dist/prompt-mode.d.ts +15 -0
  79. package/dist/prompt-mode.js +23 -0
  80. package/dist/prompt-mode.js.map +1 -0
  81. package/dist/prompt.d.ts +25 -0
  82. package/dist/prompt.js +108 -0
  83. package/dist/prompt.js.map +1 -0
  84. package/dist/registry.d.ts +27 -0
  85. package/dist/registry.js +163 -0
  86. package/dist/registry.js.map +1 -0
  87. package/dist/result-contract.d.ts +13 -0
  88. package/dist/result-contract.js +80 -0
  89. package/dist/result-contract.js.map +1 -0
  90. package/dist/run-dir.d.ts +12 -0
  91. package/dist/run-dir.js +32 -0
  92. package/dist/run-dir.js.map +1 -0
  93. package/dist/runner.d.ts +39 -0
  94. package/dist/runner.js +220 -0
  95. package/dist/runner.js.map +1 -0
  96. package/dist/safety.d.ts +22 -0
  97. package/dist/safety.js +47 -0
  98. package/dist/safety.js.map +1 -0
  99. package/dist/schema.d.ts +69 -0
  100. package/dist/schema.js +91 -0
  101. package/dist/schema.js.map +1 -0
  102. package/dist/server.d.ts +11 -0
  103. package/dist/server.js +109 -0
  104. package/dist/server.js.map +1 -0
  105. package/package.json +34 -0
@@ -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":"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"}
@@ -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;
@@ -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"}
@@ -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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"}
@@ -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;