labgate 0.5.37 → 0.5.39

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.
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ /**
3
+ * Automation engine: monitors terminal output for "waiting for input" patterns
4
+ * and auto-responds via an external LLM (Anthropic Messages API).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.AutomationEngine = void 0;
8
+ exports.stripAnsi = stripAnsi;
9
+ // ── ANSI stripping ───────────────────────────────────────────
10
+ // Matches ANSI escape sequences (CSI, OSC, single-char escapes, etc.)
11
+ const ANSI_RE = /\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)?|[()][AB012]|[78DEHM=>])/g;
12
+ // Matches common control characters (except newline/tab)
13
+ const CTRL_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
14
+ function stripAnsi(text) {
15
+ return text.replace(ANSI_RE, '').replace(CTRL_RE, '');
16
+ }
17
+ // ── Engine ───────────────────────────────────────────────────
18
+ const MAX_BUFFER = 32768; // 32KB rolling buffer
19
+ const MAX_LOG_ENTRIES = 200;
20
+ class AutomationEngine {
21
+ config;
22
+ writeFn;
23
+ broadcastFn;
24
+ broadcastTurnFn;
25
+ enabled;
26
+ paused;
27
+ turnCount;
28
+ turnSequence;
29
+ activityLog;
30
+ turns;
31
+ currentTurn;
32
+ pendingTimer;
33
+ abortController;
34
+ compiledPatterns;
35
+ strippedBuffer;
36
+ lastTriggerTime;
37
+ bridgeId;
38
+ constructor(bridgeId, config, writeFn, broadcastFn, broadcastTurnFn) {
39
+ this.bridgeId = bridgeId;
40
+ this.config = { ...config };
41
+ this.writeFn = writeFn;
42
+ this.broadcastFn = broadcastFn ?? null;
43
+ this.broadcastTurnFn = broadcastTurnFn ?? null;
44
+ this.enabled = true;
45
+ this.paused = false;
46
+ this.turnCount = 0;
47
+ this.turnSequence = 0;
48
+ this.activityLog = [];
49
+ this.turns = [];
50
+ this.currentTurn = null;
51
+ this.pendingTimer = null;
52
+ this.abortController = null;
53
+ this.strippedBuffer = '';
54
+ this.lastTriggerTime = 0;
55
+ this.compiledPatterns = this.compilePatterns(config.trigger_patterns);
56
+ this.addLog({ timestamp: new Date().toISOString(), type: 'enabled' });
57
+ }
58
+ // ── Public API ───────────────────────────────────────────
59
+ /** Feed raw terminal output data into the engine. */
60
+ onTerminalOutput(rawData) {
61
+ if (!this.enabled || this.paused)
62
+ return;
63
+ const stripped = stripAnsi(rawData);
64
+ this.strippedBuffer += stripped;
65
+ if (this.strippedBuffer.length > MAX_BUFFER) {
66
+ this.strippedBuffer = this.strippedBuffer.slice(-MAX_BUFFER);
67
+ }
68
+ // Don't trigger if we already have a pending timer or in-flight request
69
+ if (this.pendingTimer || this.abortController)
70
+ return;
71
+ // Debounce: ignore triggers within 500ms of last trigger to let output settle
72
+ const now = Date.now();
73
+ if (now - this.lastTriggerTime < 500)
74
+ return;
75
+ const lastLines = this.getLastLines(20);
76
+ const match = this.matchTrigger(lastLines);
77
+ if (!match)
78
+ return;
79
+ this.lastTriggerTime = now;
80
+ // Start delay timer (human override window)
81
+ this.pendingTimer = setTimeout(() => {
82
+ this.pendingTimer = null;
83
+ this.fireTrigger(match.pattern, match.text);
84
+ }, this.config.delay_ms);
85
+ }
86
+ /** Cancel any pending timer and in-flight LLM request. Resets turn counter. */
87
+ cancelPending() {
88
+ if (this.pendingTimer) {
89
+ clearTimeout(this.pendingTimer);
90
+ this.pendingTimer = null;
91
+ }
92
+ if (this.abortController) {
93
+ this.abortController.abort();
94
+ this.abortController = null;
95
+ }
96
+ // Human typed — reset turn counter
97
+ this.turnCount = 0;
98
+ }
99
+ pause() {
100
+ this.paused = true;
101
+ this.cancelPending();
102
+ this.addLog({ timestamp: new Date().toISOString(), type: 'paused' });
103
+ }
104
+ resume() {
105
+ this.paused = false;
106
+ this.turnCount = 0;
107
+ this.addLog({ timestamp: new Date().toISOString(), type: 'resumed' });
108
+ }
109
+ stop() {
110
+ this.enabled = false;
111
+ this.cancelPending();
112
+ this.addLog({ timestamp: new Date().toISOString(), type: 'disabled' });
113
+ }
114
+ getLog() {
115
+ return [...this.activityLog];
116
+ }
117
+ getStatus() {
118
+ return {
119
+ enabled: this.enabled,
120
+ paused: this.paused,
121
+ turnCount: this.turnCount,
122
+ maxTurns: this.config.max_turns,
123
+ inflight: this.abortController !== null,
124
+ };
125
+ }
126
+ getConfig() {
127
+ return { ...this.config };
128
+ }
129
+ getTurns() {
130
+ return [...this.turns];
131
+ }
132
+ /** Infer terminal status from the buffer: 'awaiting_input', 'working', or 'idle'. */
133
+ getTerminalStatus() {
134
+ if (this.abortController)
135
+ return 'working'; // LLM call in-flight
136
+ const lastLines = this.getLastLines(20);
137
+ if (!lastLines.trim())
138
+ return 'idle';
139
+ const match = this.matchTrigger(lastLines);
140
+ if (match)
141
+ return 'awaiting_input';
142
+ return 'working';
143
+ }
144
+ updateConfig(partial) {
145
+ Object.assign(this.config, partial);
146
+ if (partial.trigger_patterns) {
147
+ this.compiledPatterns = this.compilePatterns(partial.trigger_patterns);
148
+ }
149
+ }
150
+ // ── Private ──────────────────────────────────────────────
151
+ compilePatterns(patterns) {
152
+ const compiled = [];
153
+ for (const pat of patterns) {
154
+ try {
155
+ compiled.push(new RegExp(pat, 'm'));
156
+ }
157
+ catch {
158
+ // Skip invalid patterns
159
+ }
160
+ }
161
+ return compiled;
162
+ }
163
+ getLastLines(n) {
164
+ const lines = this.strippedBuffer.split('\n');
165
+ return lines.slice(-n).join('\n');
166
+ }
167
+ getContextLines() {
168
+ const lines = this.strippedBuffer.split('\n');
169
+ return lines.slice(-this.config.context_lines).join('\n');
170
+ }
171
+ matchTrigger(text) {
172
+ for (let i = 0; i < this.compiledPatterns.length; i++) {
173
+ const re = this.compiledPatterns[i];
174
+ const m = re.exec(text);
175
+ if (m) {
176
+ return { pattern: this.config.trigger_patterns[i], text: m[0] };
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+ async fireTrigger(pattern, matchedText) {
182
+ if (!this.enabled || this.paused)
183
+ return;
184
+ // Check turn limit
185
+ if (this.turnCount >= this.config.max_turns) {
186
+ this.pause();
187
+ this.addLog({
188
+ timestamp: new Date().toISOString(),
189
+ type: 'paused',
190
+ error: `Auto-paused: max turns (${this.config.max_turns}) reached`,
191
+ });
192
+ return;
193
+ }
194
+ // Create turn
195
+ this.turnSequence += 1;
196
+ const turn = {
197
+ turnId: `${this.bridgeId}-turn-${this.turnSequence}`,
198
+ turnNumber: this.turnSequence,
199
+ status: 'pending',
200
+ triggeredAt: new Date().toISOString(),
201
+ triggerPattern: pattern,
202
+ matchedText: matchedText.slice(0, 500),
203
+ contextPreview: '',
204
+ contextLineCount: 0,
205
+ };
206
+ this.currentTurn = turn;
207
+ this.turns.push(turn);
208
+ if (this.turns.length > MAX_LOG_ENTRIES) {
209
+ this.turns = this.turns.slice(-MAX_LOG_ENTRIES);
210
+ }
211
+ this.broadcastTurn('started', turn);
212
+ this.addLog({
213
+ timestamp: new Date().toISOString(),
214
+ type: 'trigger',
215
+ trigger_pattern: pattern,
216
+ matched_text: matchedText.slice(0, 200),
217
+ });
218
+ const context = this.getContextLines();
219
+ turn.contextLineCount = context.split('\n').length;
220
+ turn.contextPreview = this.makeContextPreview(context);
221
+ const apiKey = this.config.api_key || process.env.ANTHROPIC_API_KEY;
222
+ if (!apiKey) {
223
+ turn.status = 'error';
224
+ turn.error = 'No API key configured (set automation.api_key in config or ANTHROPIC_API_KEY env var)';
225
+ turn.completedAt = new Date().toISOString();
226
+ this.broadcastTurn('completed', turn);
227
+ this.currentTurn = null;
228
+ this.addLog({
229
+ timestamp: new Date().toISOString(),
230
+ type: 'error',
231
+ error: turn.error,
232
+ });
233
+ return;
234
+ }
235
+ // Update to calling_llm
236
+ turn.status = 'calling_llm';
237
+ turn.llmStartedAt = new Date().toISOString();
238
+ turn.llmModel = this.config.model;
239
+ this.broadcastTurn('updated', turn);
240
+ try {
241
+ this.abortController = new AbortController();
242
+ const llmStart = Date.now();
243
+ const response = await this.callAnthropicApi(apiKey, context);
244
+ this.abortController = null;
245
+ turn.llmDurationMs = Date.now() - llmStart;
246
+ if (!this.enabled || this.paused) {
247
+ turn.status = 'cancelled';
248
+ turn.completedAt = new Date().toISOString();
249
+ this.broadcastTurn('completed', turn);
250
+ this.currentTurn = null;
251
+ return;
252
+ }
253
+ if (response) {
254
+ // Inject into terminal: write the response + Enter
255
+ this.writeFn(response + '\r');
256
+ this.turnCount++;
257
+ turn.status = 'done';
258
+ turn.fullResponse = response;
259
+ turn.responseSummary = response.slice(0, 100);
260
+ turn.completedAt = new Date().toISOString();
261
+ this.broadcastTurn('completed', turn);
262
+ this.addLog({
263
+ timestamp: new Date().toISOString(),
264
+ type: 'response',
265
+ response_preview: response.slice(0, 200),
266
+ });
267
+ }
268
+ else {
269
+ turn.status = 'done';
270
+ turn.responseSummary = '';
271
+ turn.completedAt = new Date().toISOString();
272
+ this.broadcastTurn('completed', turn);
273
+ this.addLog({
274
+ timestamp: new Date().toISOString(),
275
+ type: 'response',
276
+ response_preview: '(empty response)',
277
+ });
278
+ }
279
+ }
280
+ catch (err) {
281
+ this.abortController = null;
282
+ if (err?.name === 'AbortError') {
283
+ turn.status = 'cancelled';
284
+ turn.completedAt = new Date().toISOString();
285
+ this.broadcastTurn('completed', turn);
286
+ this.currentTurn = null;
287
+ return;
288
+ }
289
+ const errMsg = err?.message ?? String(err);
290
+ turn.status = 'error';
291
+ turn.error = errMsg;
292
+ turn.completedAt = new Date().toISOString();
293
+ this.broadcastTurn('completed', turn);
294
+ this.addLog({
295
+ timestamp: new Date().toISOString(),
296
+ type: 'error',
297
+ error: errMsg,
298
+ });
299
+ }
300
+ this.currentTurn = null;
301
+ }
302
+ async callAnthropicApi(apiKey, terminalContext) {
303
+ const userMessage = [
304
+ 'The terminal output below shows a coding agent session.',
305
+ 'The agent appears to be waiting for user input.',
306
+ 'Based on your instructions, provide the appropriate response.',
307
+ 'Reply with ONLY the text that should be typed into the terminal — no explanation, no markdown, no code fences.',
308
+ '',
309
+ '--- Terminal output (last lines) ---',
310
+ terminalContext,
311
+ ].join('\n');
312
+ const body = {
313
+ model: this.config.model,
314
+ max_tokens: this.config.max_tokens,
315
+ system: this.config.system_prompt,
316
+ messages: [{ role: 'user', content: userMessage }],
317
+ stream: true,
318
+ };
319
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
320
+ method: 'POST',
321
+ headers: {
322
+ 'x-api-key': apiKey,
323
+ 'anthropic-version': '2023-06-01',
324
+ 'content-type': 'application/json',
325
+ },
326
+ body: JSON.stringify(body),
327
+ signal: this.abortController?.signal,
328
+ });
329
+ if (!res.ok) {
330
+ const text = await res.text().catch(() => '');
331
+ throw new Error(`Anthropic API error ${res.status}: ${text.slice(0, 200)}`);
332
+ }
333
+ // Parse SSE stream
334
+ return this.parseSSEStream(res);
335
+ }
336
+ async parseSSEStream(res) {
337
+ const reader = res.body?.getReader();
338
+ if (!reader)
339
+ throw new Error('No response body');
340
+ const decoder = new TextDecoder();
341
+ let accumulated = '';
342
+ let sseBuffer = '';
343
+ try {
344
+ while (true) {
345
+ const { done, value } = await reader.read();
346
+ if (done)
347
+ break;
348
+ sseBuffer += decoder.decode(value, { stream: true });
349
+ const lines = sseBuffer.split('\n');
350
+ // Keep the last potentially incomplete line in the buffer
351
+ sseBuffer = lines.pop() ?? '';
352
+ for (const line of lines) {
353
+ if (!line.startsWith('data: '))
354
+ continue;
355
+ const jsonStr = line.slice(6).trim();
356
+ if (jsonStr === '[DONE]')
357
+ continue;
358
+ try {
359
+ const event = JSON.parse(jsonStr);
360
+ if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
361
+ accumulated += event.delta.text;
362
+ }
363
+ }
364
+ catch {
365
+ // Skip malformed JSON lines
366
+ }
367
+ }
368
+ }
369
+ }
370
+ finally {
371
+ reader.releaseLock();
372
+ }
373
+ return accumulated.trim();
374
+ }
375
+ makeContextPreview(context) {
376
+ const lines = context.split('\n');
377
+ if (lines.length <= 6)
378
+ return context;
379
+ return [
380
+ ...lines.slice(0, 3),
381
+ `... (${lines.length - 6} more lines) ...`,
382
+ ...lines.slice(-3),
383
+ ].join('\n');
384
+ }
385
+ broadcastTurn(action, turn) {
386
+ if (this.broadcastTurnFn) {
387
+ this.broadcastTurnFn(action, { ...turn });
388
+ }
389
+ }
390
+ addLog(entry) {
391
+ this.activityLog.push(entry);
392
+ if (this.activityLog.length > MAX_LOG_ENTRIES) {
393
+ this.activityLog = this.activityLog.slice(-MAX_LOG_ENTRIES);
394
+ }
395
+ if (this.broadcastFn) {
396
+ this.broadcastFn(entry);
397
+ }
398
+ }
399
+ }
400
+ exports.AutomationEngine = AutomationEngine;
401
+ //# sourceMappingURL=automation-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-engine.js","sourceRoot":"","sources":["../../src/lib/automation-engine.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,27 @@ export interface LabgateConfig {
66
66
  enabled: boolean;
67
67
  log_dir: string;
68
68
  };
69
+ /** Plugin enable/disable state (plugin ID → boolean). Core is always enabled. */
70
+ plugins: Record<string, boolean>;
71
+ /** Automation plugin: external LLM monitors terminal and auto-responds */
72
+ automation?: {
73
+ /** Anthropic API key (falls back to ANTHROPIC_API_KEY env var) */
74
+ api_key?: string;
75
+ /** Model to use for automation responses */
76
+ model: string;
77
+ /** System prompt sent to the orchestrating LLM */
78
+ system_prompt: string;
79
+ /** Regex patterns that indicate the terminal is waiting for input */
80
+ trigger_patterns: string[];
81
+ /** Number of recent terminal lines to send as context */
82
+ context_lines: number;
83
+ /** Delay in ms before auto-responding (human override window) */
84
+ delay_ms: number;
85
+ /** Maximum consecutive auto-responses before pausing */
86
+ max_turns: number;
87
+ /** Max tokens for LLM response */
88
+ max_tokens: number;
89
+ };
69
90
  /** Headless mode defaults for non-interactive agent runs */
70
91
  headless?: {
71
92
  /**
@@ -73,9 +94,21 @@ export interface LabgateConfig {
73
94
  * per-tool approval prompts are not interactive in this mode.
74
95
  */
75
96
  claude_run_with_allowed_permissions?: boolean;
97
+ /**
98
+ * Show the "Continue in terminal via labgate continue ..." footer hint.
99
+ * Disable this to reduce cross-terminal continuation discoverability.
100
+ */
101
+ continuation_in_other_terminals?: boolean;
102
+ /**
103
+ * Enable Git-related UI integration (sidebar DAG and footer branch controls).
104
+ * Disabled by default while this feature remains experimental.
105
+ */
106
+ git_integration?: boolean;
76
107
  };
77
108
  }
78
109
  export declare const DEFAULT_CONFIG: LabgateConfig;
110
+ export declare const KNOWN_PLUGIN_IDS: readonly string[];
111
+ export declare function isKnownPluginId(pluginId: string): boolean;
79
112
  export declare const LABGATE_DIR: string;
80
113
  export declare const CONFIG_FILE = "config.json";
81
114
  export declare const PRIVATE_DIR_MODE = 448;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PRIVATE_FILE_MODE = exports.PRIVATE_DIR_MODE = exports.CONFIG_FILE = exports.LABGATE_DIR = exports.DEFAULT_CONFIG = void 0;
3
+ exports.PRIVATE_FILE_MODE = exports.PRIVATE_DIR_MODE = exports.CONFIG_FILE = exports.LABGATE_DIR = exports.KNOWN_PLUGIN_IDS = exports.DEFAULT_CONFIG = void 0;
4
+ exports.isKnownPluginId = isKnownPluginId;
4
5
  exports.ensurePrivateDir = ensurePrivateDir;
5
6
  exports.ensurePrivateFile = ensurePrivateFile;
6
7
  exports.getConfigPath = getConfigPath;
@@ -88,10 +89,58 @@ exports.DEFAULT_CONFIG = {
88
89
  enabled: true,
89
90
  log_dir: '~/.labgate/logs',
90
91
  },
92
+ plugins: {
93
+ files: true,
94
+ datasets: false,
95
+ results: false,
96
+ charting: true,
97
+ molecular: true,
98
+ genomics: true,
99
+ phylogenetics: true,
100
+ network: true,
101
+ slurm: true,
102
+ explorer: true,
103
+ automation: false,
104
+ },
105
+ automation: {
106
+ model: 'claude-sonnet-4-5-20250929',
107
+ system_prompt: 'You are an automation assistant helping guide a coding agent. When the agent asks a question or waits for input, provide a concise, helpful response based on the context.',
108
+ trigger_patterns: [
109
+ '\\?\\s*$',
110
+ '^>\\s*$',
111
+ 'Do you want to proceed',
112
+ 'Press Enter to continue',
113
+ 'Y/n\\]?\\s*$',
114
+ ],
115
+ context_lines: 100,
116
+ delay_ms: 3000,
117
+ max_turns: 20,
118
+ max_tokens: 1024,
119
+ },
91
120
  headless: {
92
121
  claude_run_with_allowed_permissions: true,
122
+ continuation_in_other_terminals: true,
123
+ git_integration: false,
93
124
  },
94
125
  };
126
+ exports.KNOWN_PLUGIN_IDS = Object.freeze(Object.keys(exports.DEFAULT_CONFIG.plugins));
127
+ const KNOWN_PLUGIN_ID_SET = new Set(exports.KNOWN_PLUGIN_IDS);
128
+ function isKnownPluginId(pluginId) {
129
+ return KNOWN_PLUGIN_ID_SET.has(pluginId);
130
+ }
131
+ function sanitizePluginMap(rawPlugins) {
132
+ const merged = { ...exports.DEFAULT_CONFIG.plugins };
133
+ if (!rawPlugins || typeof rawPlugins !== 'object' || Array.isArray(rawPlugins))
134
+ return merged;
135
+ for (const [pluginId, enabled] of Object.entries(rawPlugins)) {
136
+ if (!isKnownPluginId(pluginId))
137
+ continue;
138
+ if (typeof enabled !== 'boolean')
139
+ continue;
140
+ merged[pluginId] = enabled;
141
+ }
142
+ return merged;
143
+ }
95
144
  // ── Paths ─────────────────────────────────────────────────
96
145
  exports.LABGATE_DIR = process.env.LABGATE_DIR ?? (0, path_1.join)((0, os_1.homedir)(), '.labgate');
97
146
  exports.CONFIG_FILE = 'config.json';
@@ -192,6 +241,7 @@ function validateConfig(config) {
192
241
  const blockedPatterns = config.filesystem?.blocked_patterns;
193
242
  const extraPaths = config.filesystem?.extra_paths;
194
243
  const blacklist = config.commands?.blacklist;
244
+ const plugins = config.plugins;
195
245
  const headless = config.headless;
196
246
  if (!VALID_RUNTIMES.includes(config.runtime)) {
197
247
  errors.push(`Invalid runtime: "${config.runtime}". Must be one of: ${VALID_RUNTIMES.join(', ')}`);
@@ -217,6 +267,21 @@ function validateConfig(config) {
217
267
  if (!Array.isArray(blacklist)) {
218
268
  errors.push('commands.blacklist must be an array');
219
269
  }
270
+ if (plugins !== undefined) {
271
+ if (!plugins || typeof plugins !== 'object' || Array.isArray(plugins)) {
272
+ errors.push('plugins must be an object');
273
+ }
274
+ else {
275
+ for (const [pluginId, enabled] of Object.entries(plugins)) {
276
+ if (!isKnownPluginId(pluginId)) {
277
+ errors.push(`Unknown plugin id "${pluginId}"`);
278
+ }
279
+ else if (typeof enabled !== 'boolean') {
280
+ errors.push(`plugins.${pluginId} must be a boolean`);
281
+ }
282
+ }
283
+ }
284
+ }
220
285
  if (headless !== undefined) {
221
286
  if (!headless || typeof headless !== 'object' || Array.isArray(headless)) {
222
287
  errors.push('headless must be an object');
@@ -225,6 +290,14 @@ function validateConfig(config) {
225
290
  typeof headless.claude_run_with_allowed_permissions !== 'boolean') {
226
291
  errors.push('headless.claude_run_with_allowed_permissions must be a boolean');
227
292
  }
293
+ else if (headless.continuation_in_other_terminals !== undefined &&
294
+ typeof headless.continuation_in_other_terminals !== 'boolean') {
295
+ errors.push('headless.continuation_in_other_terminals must be a boolean');
296
+ }
297
+ else if (headless.git_integration !== undefined &&
298
+ typeof headless.git_integration !== 'boolean') {
299
+ errors.push('headless.git_integration must be a boolean');
300
+ }
228
301
  }
229
302
  const mounts = Array.isArray(extraPaths) ? extraPaths : [];
230
303
  for (const mount of mounts) {
@@ -239,6 +312,52 @@ function validateConfig(config) {
239
312
  errors.push(`Invalid mount mode "${mount.mode}" for path "${mount.path}". Must be "rw" or "ro"`);
240
313
  }
241
314
  }
315
+ // ── Automation validation ────────────────────────────────
316
+ const automation = config.automation;
317
+ if (automation !== undefined) {
318
+ if (!automation || typeof automation !== 'object' || Array.isArray(automation)) {
319
+ errors.push('automation must be an object');
320
+ }
321
+ else {
322
+ if (typeof automation.model !== 'string' || !automation.model) {
323
+ errors.push('automation.model must be a non-empty string');
324
+ }
325
+ if (typeof automation.system_prompt !== 'string') {
326
+ errors.push('automation.system_prompt must be a string');
327
+ }
328
+ if (!Array.isArray(automation.trigger_patterns)) {
329
+ errors.push('automation.trigger_patterns must be an array');
330
+ }
331
+ else {
332
+ for (let i = 0; i < automation.trigger_patterns.length; i++) {
333
+ const pat = automation.trigger_patterns[i];
334
+ if (typeof pat !== 'string') {
335
+ errors.push(`automation.trigger_patterns[${i}] must be a string`);
336
+ }
337
+ else {
338
+ try {
339
+ new RegExp(pat);
340
+ }
341
+ catch {
342
+ errors.push(`automation.trigger_patterns[${i}] is not a valid regex: "${pat}"`);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ if (typeof automation.context_lines !== 'number' || automation.context_lines < 1) {
348
+ errors.push('automation.context_lines must be a number >= 1');
349
+ }
350
+ if (typeof automation.delay_ms !== 'number' || automation.delay_ms < 0) {
351
+ errors.push('automation.delay_ms must be a non-negative number');
352
+ }
353
+ if (typeof automation.max_turns !== 'number' || automation.max_turns < 1) {
354
+ errors.push('automation.max_turns must be a number >= 1');
355
+ }
356
+ if (typeof automation.max_tokens !== 'number' || automation.max_tokens < 1) {
357
+ errors.push('automation.max_tokens must be a number >= 1');
358
+ }
359
+ }
360
+ }
242
361
  // ── SLURM validation ─────────────────────────────────────
243
362
  if (config.slurm) {
244
363
  if (typeof config.slurm.poll_interval_seconds !== 'number' || config.slurm.poll_interval_seconds < 1) {
@@ -338,11 +457,28 @@ function loadConfig() {
338
457
  enabled: raw.audit?.enabled ?? exports.DEFAULT_CONFIG.audit.enabled,
339
458
  log_dir: raw.audit?.log_dir ?? exports.DEFAULT_CONFIG.audit.log_dir,
340
459
  },
460
+ automation: {
461
+ api_key: raw.automation?.api_key ?? exports.DEFAULT_CONFIG.automation?.api_key,
462
+ model: raw.automation?.model ?? exports.DEFAULT_CONFIG.automation.model,
463
+ system_prompt: raw.automation?.system_prompt ?? exports.DEFAULT_CONFIG.automation.system_prompt,
464
+ trigger_patterns: raw.automation?.trigger_patterns ?? [...exports.DEFAULT_CONFIG.automation.trigger_patterns],
465
+ context_lines: raw.automation?.context_lines ?? exports.DEFAULT_CONFIG.automation.context_lines,
466
+ delay_ms: raw.automation?.delay_ms ?? exports.DEFAULT_CONFIG.automation.delay_ms,
467
+ max_turns: raw.automation?.max_turns ?? exports.DEFAULT_CONFIG.automation.max_turns,
468
+ max_tokens: raw.automation?.max_tokens ?? exports.DEFAULT_CONFIG.automation.max_tokens,
469
+ },
341
470
  headless: {
342
471
  claude_run_with_allowed_permissions: raw.headless?.claude_run_with_allowed_permissions ??
343
472
  exports.DEFAULT_CONFIG.headless?.claude_run_with_allowed_permissions ??
344
473
  true,
474
+ continuation_in_other_terminals: raw.headless?.continuation_in_other_terminals ??
475
+ exports.DEFAULT_CONFIG.headless?.continuation_in_other_terminals ??
476
+ true,
477
+ git_integration: raw.headless?.git_integration ??
478
+ exports.DEFAULT_CONFIG.headless?.git_integration ??
479
+ false,
345
480
  },
481
+ plugins: sanitizePluginMap(raw.plugins),
346
482
  };
347
483
  const errors = validateConfig(config);
348
484
  if (errors.length > 0) {