agentlas 1.0.65 → 1.0.66

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 (163) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +6 -4
  3. package/bin/agentlas.cjs +341 -16
  4. package/engine/acp/server.cjs +27 -1
  5. package/engine/agentlas-api-agent.cjs +151 -120
  6. package/engine/agentlas-banner.cjs +6 -2
  7. package/engine/agentlas-capabilities.cjs +17 -10
  8. package/engine/agentlas-cloud-runtime.cjs +4 -5
  9. package/engine/agentlas-config.cjs +19 -54
  10. package/engine/agentlas-core-harness.cjs +59 -5
  11. package/engine/agentlas-desktop-loadout.cjs +17 -3
  12. package/engine/agentlas-evolution.cjs +75 -18
  13. package/engine/agentlas-experience-exchange.cjs +145 -14
  14. package/engine/agentlas-experience-intake.cjs +2 -2
  15. package/engine/agentlas-i18n.cjs +0 -2
  16. package/engine/agentlas-input.cjs +273 -20
  17. package/engine/agentlas-judgment.cjs +0 -0
  18. package/engine/agentlas-mcp-env.cjs +84 -23
  19. package/engine/agentlas-mcp-wrapper.cjs +36 -4
  20. package/engine/agentlas-memory-governance.cjs +2 -1
  21. package/engine/agentlas-memory-import.cjs +49 -12
  22. package/engine/agentlas-native-host.cjs +279 -44
  23. package/engine/agentlas-onboard.cjs +23 -1
  24. package/engine/agentlas-permissions.cjs +5 -1
  25. package/engine/agentlas-sqlite-policy.cjs +40 -2
  26. package/engine/agentlas-tasks.cjs +7 -0
  27. package/engine/agentlas-tools.cjs +37 -10
  28. package/engine/agentlas-ui.cjs +26 -4
  29. package/engine/agentlas-workforce.cjs +29 -5
  30. package/engine/agentlas-workload-routing.cjs +83 -10
  31. package/engine/agentlas.cjs +10 -3
  32. package/engine/agents/builder.cjs +35 -10
  33. package/engine/agents/files.cjs +81 -10
  34. package/engine/agents/import-local.cjs +10 -2
  35. package/engine/agents/registry.cjs +10 -3
  36. package/engine/agents/router.cjs +7 -7
  37. package/engine/agents/routes.cjs +31 -9
  38. package/engine/automation/daemon.cjs +154 -40
  39. package/engine/automation/launchd.cjs +395 -16
  40. package/engine/automation/schedule.cjs +90 -3
  41. package/engine/automation/store.cjs +163 -59
  42. package/engine/browser/cdp.cjs +190 -17
  43. package/engine/browser/vault.cjs +1 -0
  44. package/engine/cli-output.cjs +80 -28
  45. package/engine/cloud/auth.cjs +131 -13
  46. package/engine/cloud/hub-client.cjs +41 -16
  47. package/engine/cloud-assets/cas.cjs +18 -9
  48. package/engine/cloud-assets/commands.cjs +108 -30
  49. package/engine/cloud-assets/package.cjs +66 -3
  50. package/engine/cloud-assets/restore.cjs +12 -9
  51. package/engine/cloud-assets/state.cjs +308 -113
  52. package/engine/commands/acp.cjs +13 -2
  53. package/engine/commands/automation.cjs +75 -24
  54. package/engine/commands/billing.cjs +6 -1
  55. package/engine/commands/browser.cjs +61 -29
  56. package/engine/commands/build.cjs +42 -12
  57. package/engine/commands/call.cjs +1 -1
  58. package/engine/commands/cd.cjs +1 -1
  59. package/engine/commands/cloud.cjs +2 -1
  60. package/engine/commands/connect.cjs +38 -13
  61. package/engine/commands/creds.cjs +74 -29
  62. package/engine/commands/doctor.cjs +17 -1
  63. package/engine/commands/document.cjs +38 -10
  64. package/engine/commands/env.cjs +6 -1
  65. package/engine/commands/firm.cjs +42 -12
  66. package/engine/commands/graph.cjs +36 -32
  67. package/engine/commands/help.cjs +11 -1
  68. package/engine/commands/hep.cjs +1 -1
  69. package/engine/commands/import.cjs +5 -4
  70. package/engine/commands/index.cjs +1 -1
  71. package/engine/commands/install.cjs +6 -4
  72. package/engine/commands/list.cjs +56 -16
  73. package/engine/commands/login.cjs +5 -0
  74. package/engine/commands/logout.cjs +6 -1
  75. package/engine/commands/mcp.cjs +8 -2
  76. package/engine/commands/memory.cjs +3 -2
  77. package/engine/commands/multimodal.cjs +12 -0
  78. package/engine/commands/native.cjs +5 -1
  79. package/engine/commands/one.cjs +78 -33
  80. package/engine/commands/plugin.cjs +13 -2
  81. package/engine/commands/project.cjs +15 -12
  82. package/engine/commands/research.cjs +18 -7
  83. package/engine/commands/roles.cjs +104 -28
  84. package/engine/commands/route.cjs +2 -2
  85. package/engine/commands/run.cjs +42 -9
  86. package/engine/commands/search.cjs +23 -2
  87. package/engine/commands/setup.cjs +6 -1
  88. package/engine/commands/storm.cjs +44 -6
  89. package/engine/commands/swarm.cjs +47 -9
  90. package/engine/commands/telegram.cjs +6 -1
  91. package/engine/commands/uninstall.cjs +12 -6
  92. package/engine/commands/update.cjs +5 -0
  93. package/engine/commands/upload.cjs +2 -1
  94. package/engine/commands/usage.cjs +6 -1
  95. package/engine/commands/version.cjs +6 -1
  96. package/engine/commands/whoami.cjs +6 -1
  97. package/engine/commands/workforce.cjs +60 -16
  98. package/engine/core/capability-grants.cjs +7 -2
  99. package/engine/core/daemon-client.cjs +36 -7
  100. package/engine/core/db.cjs +10 -2
  101. package/engine/core/desktop-core-fetch.cjs +477 -77
  102. package/engine/core/schema-ensure.cjs +23 -11
  103. package/engine/experience/build.cjs +5 -1
  104. package/engine/experience/intents.cjs +29 -82
  105. package/engine/experience/runtime.cjs +7 -1
  106. package/engine/experience/variant.cjs +58 -4
  107. package/engine/firms/orchestrate.cjs +9 -6
  108. package/engine/graph/ask-model.cjs +22 -11
  109. package/engine/graph/interview.cjs +74 -23
  110. package/engine/graph/package.cjs +63 -8
  111. package/engine/hephaestus/local-core.cjs +18 -5
  112. package/engine/hephaestus/runtime.cjs +48 -43
  113. package/engine/hub/install.cjs +230 -61
  114. package/engine/hub/plugins.cjs +391 -29
  115. package/engine/mcp/consent.cjs +25 -17
  116. package/engine/mcp/contract.cjs +229 -24
  117. package/engine/mcp/inventory.cjs +15 -5
  118. package/engine/mcp/plan.cjs +5 -1
  119. package/engine/mcp/probe.cjs +24 -9
  120. package/engine/memory-cli/curate.cjs +74 -16
  121. package/engine/oberon/common.cjs +37 -12
  122. package/engine/oberon/manifest.cjs +25 -2
  123. package/engine/oberon/outputs.cjs +49 -19
  124. package/engine/oberon/render.cjs +665 -40
  125. package/engine/project/career-graph.cjs +15 -2
  126. package/engine/project/controller.cjs +133 -15
  127. package/engine/project/credentials.cjs +212 -34
  128. package/engine/project/env-file.cjs +93 -8
  129. package/engine/project/memory-context.cjs +11 -57
  130. package/engine/project/ontology.cjs +66 -25
  131. package/engine/project/paths.cjs +42 -8
  132. package/engine/project/seed.cjs +88 -51
  133. package/engine/project/state.cjs +41 -16
  134. package/engine/project/team.cjs +26 -1
  135. package/engine/runtime-refusal.cjs +4 -1
  136. package/engine/runtimes/acp-driver.cjs +56 -9
  137. package/engine/runtimes/auth-evidence.cjs +21 -2
  138. package/engine/runtimes/detect.cjs +28 -8
  139. package/engine/runtimes/kinds.cjs +12 -4
  140. package/engine/runtimes/overrides.cjs +4 -3
  141. package/engine/runtimes/resolve.cjs +8 -7
  142. package/engine/runtimes/roles.cjs +7 -5
  143. package/engine/sessions/apply-fences.cjs +396 -56
  144. package/engine/sessions/fences.cjs +111 -27
  145. package/engine/sessions/memory-turn.cjs +62 -3
  146. package/engine/sessions/orchestrator.cjs +11 -1
  147. package/engine/sessions/prompt.cjs +66 -9
  148. package/engine/sessions/session.cjs +70 -10
  149. package/engine/sessions/sink.cjs +78 -12
  150. package/engine/sessions/store.cjs +29 -7
  151. package/engine/storm/storm.cjs +26 -3
  152. package/engine/storm/swarm.cjs +31 -3
  153. package/engine/telegram/connect.cjs +185 -22
  154. package/engine/ui/commands-catalog.cjs +1 -1
  155. package/engine/ui/palette.cjs +12 -2
  156. package/engine/ui/repl.cjs +57 -9
  157. package/engine/ui/screens.cjs +2 -4
  158. package/engine/vendor/desktop-core.manifest.json +5 -5
  159. package/engine/workforce/capture.cjs +9 -27
  160. package/engine/workforce/deps.cjs +18 -6
  161. package/engine/workforce/local-core-transport.cjs +68 -5
  162. package/package.json +1 -1
  163. package/engine/commands/career-graph.cjs +0 -51
@@ -74,12 +74,18 @@ async function streamAnthropic({ apiKey, model, system, messages, permission, ui
74
74
  if (toolDefs.length) body.tools = toolDefs;
75
75
 
76
76
  const idle = idleAbort(signal, IDLE_MS);
77
- const resp = await fetch("https://api.anthropic.com/v1/messages", {
78
- method: "POST",
79
- headers: { "content-type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
80
- signal: idle.signal,
81
- body: JSON.stringify(body),
82
- });
77
+ let resp;
78
+ try {
79
+ resp = await fetch("https://api.anthropic.com/v1/messages", {
80
+ method: "POST",
81
+ headers: { "content-type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
82
+ signal: idle.signal,
83
+ body: JSON.stringify(body),
84
+ });
85
+ } catch (error) {
86
+ idle.clear();
87
+ throw error;
88
+ }
83
89
  if (!resp.ok) {
84
90
  idle.clear();
85
91
  throw new Error(`Anthropic ${resp.status}: ${(await resp.text().catch(() => "")).slice(0, 300)}`);
@@ -88,45 +94,49 @@ async function streamAnthropic({ apiKey, model, system, messages, permission, ui
88
94
  const blocks = {}; // index → {type, text, name, id, inputJson}
89
95
  let stopReason = null;
90
96
  let usage = null;
91
- for await (const line of iterSse(resp, idle)) {
92
- const data = sseData(line);
93
- if (!data || data === "[DONE]") continue;
94
- let ev;
95
- try {
96
- ev = JSON.parse(data);
97
- } catch {
98
- continue;
99
- }
100
- if (ev.type === "content_block_start") {
101
- const cb = ev.content_block || {};
102
- blocks[ev.index] = { type: cb.type, text: "", name: cb.name, id: cb.id, inputJson: "" };
103
- if (cb.type === "tool_use") ui.tool(cb.name, "");
104
- else ui.streamStart();
105
- } else if (ev.type === "content_block_delta") {
106
- const b = blocks[ev.index] || (blocks[ev.index] = { type: "text", text: "", inputJson: "" });
107
- if (ev.delta.type === "text_delta") {
108
- b.text += ev.delta.text;
109
- ui.streamDelta(ev.delta.text);
110
- } else if (ev.delta.type === "input_json_delta") {
111
- b.inputJson += ev.delta.partial_json || "";
97
+ try {
98
+ for await (const line of iterSse(resp, idle)) {
99
+ const data = sseData(line);
100
+ if (!data || data === "[DONE]") continue;
101
+ let ev;
102
+ try {
103
+ ev = JSON.parse(data);
104
+ } catch {
105
+ continue;
112
106
  }
113
- } else if (ev.type === "content_block_stop") {
114
- const b = blocks[ev.index];
115
- if (b && b.type === "tool_use") {
116
- const arg = safeArg(b.inputJson);
117
- if (arg) ui.info(ui.c.dim(" " + arg));
107
+ if (ev.type === "content_block_start") {
108
+ const cb = ev.content_block || {};
109
+ blocks[ev.index] = { type: cb.type, text: "", name: cb.name, id: cb.id, inputJson: "" };
110
+ if (cb.type === "tool_use") ui.tool(cb.name, "");
111
+ else ui.streamStart();
112
+ } else if (ev.type === "content_block_delta") {
113
+ const b = blocks[ev.index] || (blocks[ev.index] = { type: "text", text: "", inputJson: "" });
114
+ if (ev.delta.type === "text_delta") {
115
+ b.text += ev.delta.text;
116
+ ui.streamDelta(ev.delta.text);
117
+ } else if (ev.delta.type === "input_json_delta") {
118
+ b.inputJson += ev.delta.partial_json || "";
119
+ }
120
+ } else if (ev.type === "content_block_stop") {
121
+ const b = blocks[ev.index];
122
+ if (b && b.type === "tool_use") {
123
+ const arg = safeArg(b.inputJson);
124
+ if (arg) ui.info(ui.c.dim(" " + arg));
125
+ }
126
+ // 텍스트 블록 stop에서는 streamEnd를 호출하지 않는다 — 메모리 가드 중간 flush 방지(턴 끝에서만 flush).
127
+ } else if (ev.type === "message_start") {
128
+ const u = ev.message && ev.message.usage;
129
+ if (u) usage = { input_tokens: u.input_tokens || 0, output_tokens: u.output_tokens || 0 };
130
+ } else if (ev.type === "message_delta") {
131
+ if (ev.delta && ev.delta.stop_reason) stopReason = ev.delta.stop_reason;
132
+ if (ev.usage) usage = { input_tokens: (usage && usage.input_tokens) || 0, output_tokens: ev.usage.output_tokens };
118
133
  }
119
- // 텍스트 블록 stop에서는 streamEnd를 호출하지 않는다 — 메모리 가드 중간 flush 방지(턴 끝에서만 flush).
120
- } else if (ev.type === "message_start") {
121
- const u = ev.message && ev.message.usage;
122
- if (u) usage = { input_tokens: u.input_tokens || 0, output_tokens: u.output_tokens || 0 };
123
- } else if (ev.type === "message_delta") {
124
- if (ev.delta && ev.delta.stop_reason) stopReason = ev.delta.stop_reason;
125
- if (ev.usage) usage = { input_tokens: (usage && usage.input_tokens) || 0, output_tokens: ev.usage.output_tokens };
126
134
  }
135
+ } finally {
136
+ // 본문 스트림이 중간에 끊겨도 Renderer의 열린 스트림 상태와 취소 리스너를 남기지 않는다.
137
+ ui.streamEnd();
138
+ idle.clear();
127
139
  }
128
- ui.streamEnd();
129
- idle.clear();
130
140
 
131
141
  // 어셈블
132
142
  const ordered = Object.keys(blocks)
@@ -192,12 +202,18 @@ async function streamOpenAI({ apiKey, model, messages, permission, ui, signal, b
192
202
  if (toolDefs.length) body.tools = toolDefs;
193
203
 
194
204
  const idle = idleAbort(signal, IDLE_MS);
195
- const resp = await fetch(`${baseUrl || "https://api.openai.com/v1"}/chat/completions`, {
196
- method: "POST",
197
- headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
198
- signal: idle.signal,
199
- body: JSON.stringify(body),
200
- });
205
+ let resp;
206
+ try {
207
+ resp = await fetch(`${baseUrl || "https://api.openai.com/v1"}/chat/completions`, {
208
+ method: "POST",
209
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
210
+ signal: idle.signal,
211
+ body: JSON.stringify(body),
212
+ });
213
+ } catch (error) {
214
+ idle.clear();
215
+ throw error;
216
+ }
201
217
  if (!resp.ok) {
202
218
  idle.clear();
203
219
  throw new Error(`OpenAI ${resp.status}: ${(await resp.text().catch(() => "")).slice(0, 300)}`);
@@ -208,43 +224,46 @@ async function streamOpenAI({ apiKey, model, messages, permission, ui, signal, b
208
224
  const toolCalls = {}; // index → {id, name, args}
209
225
  let finish = null;
210
226
  let usage = null;
211
- for await (const line of iterSse(resp, idle)) {
212
- const data = sseData(line);
213
- if (!data || data === "[DONE]") continue;
214
- let ev;
215
- try {
216
- ev = JSON.parse(data);
217
- } catch {
218
- continue;
219
- }
220
- if (ev.usage) usage = { input_tokens: ev.usage.prompt_tokens || 0, output_tokens: ev.usage.completion_tokens || 0 };
221
- const choice = ev.choices && ev.choices[0];
222
- if (!choice) continue;
223
- const delta = choice.delta || {};
224
- if (delta.content) {
225
- if (!started) {
226
- ui.streamStart();
227
- started = true;
227
+ try {
228
+ for await (const line of iterSse(resp, idle)) {
229
+ const data = sseData(line);
230
+ if (!data || data === "[DONE]") continue;
231
+ let ev;
232
+ try {
233
+ ev = JSON.parse(data);
234
+ } catch {
235
+ continue;
228
236
  }
229
- text += delta.content;
230
- ui.streamDelta(delta.content);
231
- }
232
- if (Array.isArray(delta.tool_calls)) {
233
- for (const tc of delta.tool_calls) {
234
- const idx = tc.index ?? 0;
235
- const cur = toolCalls[idx] || (toolCalls[idx] = { id: "", name: "", args: "" });
236
- if (tc.id) cur.id = tc.id;
237
- if (tc.function && tc.function.name) {
238
- if (!cur.name) ui.tool(tc.function.name, "");
239
- cur.name = tc.function.name;
237
+ if (ev.usage) usage = { input_tokens: ev.usage.prompt_tokens || 0, output_tokens: ev.usage.completion_tokens || 0 };
238
+ const choice = ev.choices && ev.choices[0];
239
+ if (!choice) continue;
240
+ const delta = choice.delta || {};
241
+ if (delta.content) {
242
+ if (!started) {
243
+ ui.streamStart();
244
+ started = true;
245
+ }
246
+ text += delta.content;
247
+ ui.streamDelta(delta.content);
248
+ }
249
+ if (Array.isArray(delta.tool_calls)) {
250
+ for (const tc of delta.tool_calls) {
251
+ const idx = tc.index ?? 0;
252
+ const cur = toolCalls[idx] || (toolCalls[idx] = { id: "", name: "", args: "" });
253
+ if (tc.id) cur.id = tc.id;
254
+ if (tc.function && tc.function.name) {
255
+ if (!cur.name) ui.tool(tc.function.name, "");
256
+ cur.name = tc.function.name;
257
+ }
258
+ if (tc.function && tc.function.arguments) cur.args += tc.function.arguments;
240
259
  }
241
- if (tc.function && tc.function.arguments) cur.args += tc.function.arguments;
242
260
  }
261
+ if (choice.finish_reason) finish = choice.finish_reason;
243
262
  }
244
- if (choice.finish_reason) finish = choice.finish_reason;
263
+ } finally {
264
+ if (started) ui.streamEnd();
265
+ idle.clear();
245
266
  }
246
- if (started) ui.streamEnd();
247
- idle.clear();
248
267
 
249
268
  const calls = Object.keys(toolCalls)
250
269
  .sort((a, b) => a - b)
@@ -327,26 +346,29 @@ async function runOllamaLoop(req) {
327
346
  let text = "";
328
347
  let started = false;
329
348
  let toolCalls = [];
330
- for await (const line of iterSse(resp, idle)) {
331
- let ev;
332
- try {
333
- ev = JSON.parse(line);
334
- } catch {
335
- continue;
336
- }
337
- const msg = ev.message || {};
338
- if (msg.content) {
339
- if (!started) {
340
- ui.streamStart();
341
- started = true;
349
+ try {
350
+ for await (const line of iterSse(resp, idle)) {
351
+ let ev;
352
+ try {
353
+ ev = JSON.parse(line);
354
+ } catch {
355
+ continue;
342
356
  }
343
- text += msg.content;
344
- ui.streamDelta(msg.content);
357
+ const msg = ev.message || {};
358
+ if (msg.content) {
359
+ if (!started) {
360
+ ui.streamStart();
361
+ started = true;
362
+ }
363
+ text += msg.content;
364
+ ui.streamDelta(msg.content);
365
+ }
366
+ if (Array.isArray(msg.tool_calls)) toolCalls = toolCalls.concat(msg.tool_calls);
345
367
  }
346
- if (Array.isArray(msg.tool_calls)) toolCalls = toolCalls.concat(msg.tool_calls);
368
+ } finally {
369
+ if (started) ui.streamEnd();
370
+ idle.clear();
347
371
  }
348
- if (started) ui.streamEnd();
349
- idle.clear();
350
372
  finalText = text || finalText;
351
373
  if (!toolCalls.length) break;
352
374
  messages.push({ role: "assistant", content: text, tool_calls: toolCalls });
@@ -373,39 +395,48 @@ async function runGoogleChat(req) {
373
395
  }
374
396
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${req.model}:streamGenerateContent?alt=sse&key=${encodeURIComponent(req.apiKey)}`;
375
397
  const idle = idleAbort(req.signal, IDLE_MS);
376
- const resp = await fetch(url, {
377
- method: "POST",
378
- headers: { "content-type": "application/json" },
379
- signal: idle.signal,
380
- body: JSON.stringify({ systemInstruction: { parts: [{ text: req.system }] }, contents }),
381
- });
398
+ let resp;
399
+ try {
400
+ resp = await fetch(url, {
401
+ method: "POST",
402
+ headers: { "content-type": "application/json" },
403
+ signal: idle.signal,
404
+ body: JSON.stringify({ systemInstruction: { parts: [{ text: req.system }] }, contents }),
405
+ });
406
+ } catch (error) {
407
+ idle.clear();
408
+ throw error;
409
+ }
382
410
  if (!resp.ok) {
383
411
  idle.clear();
384
412
  throw new Error(`Google ${resp.status}: ${(await resp.text().catch(() => "")).slice(0, 300)}`);
385
413
  }
386
414
  let text = "";
387
415
  let started = false;
388
- for await (const line of iterSse(resp, idle)) {
389
- const data = sseData(line);
390
- if (!data || data === "[DONE]") continue;
391
- let ev;
392
- try {
393
- ev = JSON.parse(data);
394
- } catch {
395
- continue;
396
- }
397
- const t = ev.candidates && ev.candidates[0] && ev.candidates[0].content && ev.candidates[0].content.parts && ev.candidates[0].content.parts[0] && ev.candidates[0].content.parts[0].text;
398
- if (t) {
399
- if (!started) {
400
- ui.streamStart();
401
- started = true;
416
+ try {
417
+ for await (const line of iterSse(resp, idle)) {
418
+ const data = sseData(line);
419
+ if (!data || data === "[DONE]") continue;
420
+ let ev;
421
+ try {
422
+ ev = JSON.parse(data);
423
+ } catch {
424
+ continue;
425
+ }
426
+ const t = ev.candidates && ev.candidates[0] && ev.candidates[0].content && ev.candidates[0].content.parts && ev.candidates[0].content.parts[0] && ev.candidates[0].content.parts[0].text;
427
+ if (t) {
428
+ if (!started) {
429
+ ui.streamStart();
430
+ started = true;
431
+ }
432
+ text += t;
433
+ ui.streamDelta(t);
402
434
  }
403
- text += t;
404
- ui.streamDelta(t);
405
435
  }
436
+ } finally {
437
+ if (started) ui.streamEnd();
438
+ idle.clear();
406
439
  }
407
- if (started) ui.streamEnd();
408
- idle.clear();
409
440
  return { text };
410
441
  }
411
442
 
@@ -163,8 +163,12 @@ function cardBottom(label, inner, c) {
163
163
  function renderCompactSplash(ui, value, room) {
164
164
  const c = ui.c;
165
165
  const version = value.version ? " v" + value.version : "";
166
+ const title = truncateWidth(
167
+ WORDMARK_COMPACT + version + " · " + ui.t("banner.product"),
168
+ Math.max(1, room),
169
+ );
166
170
  ui.line("");
167
- ui.line(CARD_MARGIN + c.bold(c.emerald(WORDMARK_COMPACT)) + c.faint(version) + c.dim(" · " + ui.t("banner.product")));
171
+ ui.line(CARD_MARGIN + c.bold(c.emerald(title)));
168
172
  ui.line(CARD_MARGIN + c.text(truncateWidth(ui.t("banner.session", value.runtime, value.subject, value.permission), room)));
169
173
  ui.line(CARD_MARGIN + c.faint(truncateWidth(ui.t("banner.location", value.cwd), room)));
170
174
  ui.line("");
@@ -186,7 +190,7 @@ function renderBanner(ctx) {
186
190
  const columns = ui.out.columns || 80;
187
191
  const inner = Math.min(CARD_MAX_INNER, columns - visWidth(CARD_MARGIN) * 2 - 2);
188
192
  if (inner < CARD_MIN_INNER) {
189
- renderCompactSplash(ui, value, Math.max(20, columns - 4));
193
+ renderCompactSplash(ui, value, Math.max(1, columns - visWidth(CARD_MARGIN)));
190
194
  return;
191
195
  }
192
196
 
@@ -24,22 +24,27 @@ const RUNTIME_CAPS = {
24
24
  upstage: { code: true, image: false, label: "solar" }, // Upstage Solar — Korean sovereign LLM (OpenAI-compatible)
25
25
  };
26
26
 
27
- // NOTE: grok은 CAPS(멀티모달 능력 인지)만 등록 터미널 스폰 러너(RUNTIME_BIN)가 아직 없어
28
- // CLI_KINDS에 넣으면 repl의 which(RUNTIME_BIN[k]) 탐지가 깨진다. 러너 추가 함께 확장할 것.
29
- // 목록의 정본은 runtimes/kinds.cjs — 여기서는 네이티브 스폰 러너 4종만 가져다 쓴다.
30
- const CLI_KINDS = require("./runtimes/kinds.cjs").NATIVE_CLI_KINDS;
27
+ // 목록·별칭의 정본은 runtimes/kinds.cjs다. ACP 공용 러너가 추가된 뒤에도 파일이
28
+ // native 4종만 보아 grok/kimi/cursor를 API backend로 잘못 만들지 않게 한다.
29
+ const KINDS = require("./runtimes/kinds.cjs");
30
+ const CLI_KINDS = KINDS.CLI_KINDS;
31
+ const API_SPECS = new Set(KINDS.API_BACKEND_SPECS);
31
32
 
32
33
  function capsFor(spec) {
33
- return RUNTIME_CAPS[spec] || { code: true, image: false, label: spec || "?" };
34
+ const canonical = KINDS.canonicalRuntimeKind(spec);
35
+ return RUNTIME_CAPS[canonical] || { code: true, image: false, label: canonical || "?" };
34
36
  }
35
37
 
36
38
  // runtime object ⇄ spec string
37
39
  function specOf(rt) {
38
40
  if (!rt) return "";
39
- return rt.mode === "cli" ? rt.kind : rt.backend;
41
+ return rt.mode === "cli" ? KINDS.canonicalRuntimeKind(rt.kind) : rt.backend;
40
42
  }
41
43
  function runtimeFromSpec(spec) {
42
- return CLI_KINDS.includes(spec) ? { mode: "cli", kind: spec } : { mode: "api", backend: spec, model: null };
44
+ const canonical = KINDS.canonicalRuntimeKind(spec);
45
+ if (CLI_KINDS.includes(canonical)) return { mode: "cli", kind: canonical };
46
+ if (API_SPECS.has(canonical)) return { mode: "api", backend: canonical, model: null };
47
+ return null;
43
48
  }
44
49
 
45
50
  // Image capability is judged only by the connected model from the complete
@@ -116,10 +121,12 @@ function clearImageJudgments() {
116
121
  // Image agents route to an installed image-capable runtime; otherwise keep the session default.
117
122
  function autoRuntimeFor(agent, { installedKinds, activeSpec }) {
118
123
  if (needsImage(agent)) {
119
- if (capsFor(activeSpec).image) return activeSpec;
120
- for (const k of ["agy", "gemini", "codex"]) if ((installedKinds || []).includes(k)) return k;
124
+ const canonicalActive = KINDS.canonicalRuntimeKind(activeSpec);
125
+ if (capsFor(canonicalActive).image) return canonicalActive;
126
+ const installed = new Set((installedKinds || []).map(KINDS.canonicalRuntimeKind));
127
+ for (const k of ["agy", "gemini", "codex"]) if (installed.has(k)) return k;
121
128
  }
122
- return activeSpec;
129
+ return KINDS.canonicalRuntimeKind(activeSpec);
123
130
  }
124
131
 
125
132
  // short capability badge for display
@@ -4,10 +4,9 @@ const fs = require("node:fs");
4
4
  const path = require("node:path");
5
5
  const os = require("node:os");
6
6
  const crypto = require("node:crypto");
7
+ const { looksSecret, redactSecrets } = require("./agentlas-secret-patterns.cjs");
7
8
 
8
9
  const TEXT_EXTS = new Set([".md", ".txt", ".json", ".jsonl", ".yaml", ".yml", ".toml", ".js", ".ts", ".tsx", ".cjs", ".mjs", ".sh"]);
9
- const SECRET_RE = /(sk-(?:ant-)?[A-Za-z0-9_-]{20,}|gh[opsu]_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----)/;
10
- const SECRET_ASSIGN_RE = /\b(?:api[_-]?key|secret|password|token)\s*[:=]\s*["']?([A-Za-z0-9+/=_-]{20,})["']?/i;
11
10
  const PROMPT_INJECTION_RE = /\b(ignore (?:all |previous |prior )?instructions|reveal (?:your )?system prompt|print hidden instructions)\b/i;
12
11
  const DESTRUCTIVE_RE = /\b(rm\s+-rf\s+(?:\/|~)|curl\b[^\n]{0,240}\|\s*(?:sudo\s+)?(?:sh|bash|zsh)|mkfs\.|dd\s+if=\/dev\/)\b/i;
13
12
 
@@ -133,7 +132,7 @@ function buildManifest(root, options = {}) {
133
132
  }
134
133
 
135
134
  function redact(text) {
136
- return text.replace(SECRET_RE, "[REDACTED_SECRET]").replace(SECRET_ASSIGN_RE, (match, secret) => match.replace(secret, "[REDACTED_SECRET]"));
135
+ return redactSecrets(text, "[REDACTED_SECRET]");
137
136
  }
138
137
 
139
138
  function scanFiles(files) {
@@ -146,13 +145,13 @@ function scanFiles(files) {
146
145
  // `/token|secret/i` test blocked ordinary vocabulary — measured 2026-07-29,
147
146
  // it gave the live `web-master` package a BLOCK verdict for its own design
148
147
  // system (`token-architecture.md`, `reference-token-db.json`). Dropping it
149
- // costs no coverage: the per-line SECRET_PATTERNS scan below reads every
148
+ // costs no coverage: the shared per-line credential scan below reads every
150
149
  // packaged file and is the check that actually looks at values.
151
150
  if (/^\.env(\.|$)/.test(file.path.split("/").pop() || "") || /(?:^|\/)(secrets|credentials|cookies)\//i.test(file.path)) {
152
151
  add("BLOCK", "credential-path", file, null, "Credential-like file path is excluded from Cloud package and public publish.");
153
152
  }
154
153
  file.content.split(/\r?\n/).forEach((line, index) => {
155
- if (SECRET_RE.test(line) || SECRET_ASSIGN_RE.test(line)) add("BLOCK", "secret-like-value", file, index + 1, "Secret-like value detected and redacted.");
154
+ if (looksSecret(line)) add("BLOCK", "secret-like-value", file, index + 1, "Secret-like value detected and redacted.");
156
155
  if (PROMPT_INJECTION_RE.test(line)) add("WARN", "prompt-injection", file, index + 1, "Prompt-injection style instruction needs review.");
157
156
  if (DESTRUCTIVE_RE.test(line)) add("WARN", "destructive-command", file, index + 1, "Destructive or remote shell command needs review before execution.");
158
157
  });
@@ -8,8 +8,11 @@
8
8
  */
9
9
  const fs = require("node:fs");
10
10
  const path = require("node:path");
11
- const LOCK_WAIT_MS = 2_000;
12
- const LOCK_STALE_MS = 30_000;
11
+ const {
12
+ readJsonFile,
13
+ writePrivateJsonAtomic,
14
+ withPrivateStateLock,
15
+ } = require("./mcp/contract.cjs");
13
16
 
14
17
  function prefsPath(userDataDir) {
15
18
  return path.join(userDataDir, "cli-prefs.json");
@@ -19,7 +22,7 @@ function backupPath(userDataDir) {
19
22
  }
20
23
  function readPrefsFile(file) {
21
24
  try {
22
- const value = JSON.parse(fs.readFileSync(file, "utf8"));
25
+ const { value } = readJsonFile(file, "CLI preferences");
23
26
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
24
27
  } catch {
25
28
  return null;
@@ -29,60 +32,15 @@ function loadPrefs(userDataDir) {
29
32
  return readPrefsFile(prefsPath(userDataDir)) || readPrefsFile(backupPath(userDataDir)) || {};
30
33
  }
31
34
 
32
- function sleepSync(ms) {
33
- try {
34
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
35
- } catch {
36
- const until = Date.now() + ms;
37
- while (Date.now() < until) { /* old Node fallback */ }
38
- }
39
- }
40
-
41
35
  function withPrefsLock(userDataDir, callback) {
42
- const lock = prefsPath(userDataDir) + ".lock";
43
- const deadline = Date.now() + LOCK_WAIT_MS;
44
- while (true) {
45
- try {
46
- fs.mkdirSync(lock, { mode: 0o700 });
47
- break;
48
- } catch (error) {
49
- if (!error || error.code !== "EEXIST") throw error;
50
- try {
51
- if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) {
52
- fs.rmdirSync(lock);
53
- continue;
54
- }
55
- } catch {
56
- continue;
57
- }
58
- if (Date.now() >= deadline) throw new Error("preferences lock timeout");
59
- sleepSync(10);
60
- }
61
- }
62
- try {
63
- return callback();
64
- } finally {
65
- try { fs.rmdirSync(lock); } catch { /* stale lock recovery handles interrupted writers */ }
66
- }
36
+ return withPrivateStateLock(prefsPath(userDataDir), {
37
+ unsafe: "preferences lock is unsafe",
38
+ busy: "preferences lock timeout",
39
+ }, callback);
67
40
  }
68
41
 
69
42
  function atomicWrite(file, value) {
70
- const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
71
- let fd;
72
- try {
73
- fd = fs.openSync(tmp, "wx", 0o600);
74
- fs.writeFileSync(fd, JSON.stringify(value, null, 2), "utf8");
75
- fs.fsyncSync(fd);
76
- fs.closeSync(fd);
77
- fd = null;
78
- fs.renameSync(tmp, file);
79
- try { fs.chmodSync(file, 0o600); } catch { /* win32 */ }
80
- } finally {
81
- if (fd != null) {
82
- try { fs.closeSync(fd); } catch { /* ignore */ }
83
- }
84
- try { fs.unlinkSync(tmp); } catch { /* already renamed or never created */ }
85
- }
43
+ writePrivateJsonAtomic(file, value);
86
44
  }
87
45
 
88
46
  function mergePrefs(base, patch) {
@@ -111,9 +69,16 @@ function updatePrefs(userDataDir, patch) {
111
69
  fs.mkdirSync(userDataDir, { recursive: true });
112
70
  return withPrefsLock(userDataDir, () => {
113
71
  const file = prefsPath(userDataDir);
72
+ let currentStat = null;
73
+ try { currentStat = fs.lstatSync(file); } catch (error) {
74
+ if (!error || error.code !== "ENOENT") throw error;
75
+ }
76
+ if (currentStat && (!currentStat.isFile() || currentStat.isSymbolicLink() || currentStat.nlink !== 1)) {
77
+ throw new Error("CLI preferences path is unsafe");
78
+ }
114
79
  const currentWasValid = Boolean(readPrefsFile(file));
115
80
  const current = loadPrefs(userDataDir);
116
- if (fs.existsSync(file) && !currentWasValid) {
81
+ if (currentStat && !currentWasValid) {
117
82
  try { fs.renameSync(file, `${file}.corrupt-${Date.now()}-${process.pid}`); } catch { /* recover from backup */ }
118
83
  }
119
84
  const next = mergePrefs(current, patch);
@@ -12,6 +12,8 @@ const HARNESS_ID = "agentlas-core/stormbreaker-goal-ultracode";
12
12
  const HARNESS_MODE = "stormbreaker-goal-ultracode";
13
13
  const CONTEXT_MAP_MIN_CORE_VERSION = "1.1.86";
14
14
  const CORE_MANIFEST_MAX_BYTES = 64 * 1024;
15
+ const CORE_CAPTURE_MAX_BYTES = 16 * 1024 * 1024;
16
+ const CORE_CAPTURE_TIMEOUT_MS = 120_000;
15
17
  const CORE_RUNTIME_MARKERS = [
16
18
  ["agentlas_cloud", "__main__.py"],
17
19
  ["schemas", "workforce-work-order.schema.json"],
@@ -222,13 +224,55 @@ async function captureCoreJson(moduleName, args, opts = {}, explicitRoot) {
222
224
  const child = spawnCoreModule(moduleName, args, { ...opts, stdio: ["ignore", "pipe", "pipe"] }, explicitRoot);
223
225
  if (!child) throw new Error("Agentlas Core runtime or Python 3.9+ is unavailable.");
224
226
  const result = await new Promise((resolve) => {
225
- let stdout = "";
227
+ const stdoutChunks = [];
228
+ let stdoutBytes = 0;
226
229
  let stderr = "";
227
- child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
230
+ let outputExceeded = false;
231
+ let timedOut = false;
232
+ let settled = false;
233
+ let forceKill = null;
234
+ const finish = (value) => {
235
+ if (settled) return;
236
+ settled = true;
237
+ clearTimeout(timeout);
238
+ if (forceKill) clearTimeout(forceKill);
239
+ resolve(value);
240
+ };
241
+ const terminate = () => {
242
+ try { child.kill("SIGTERM"); } catch { /* already gone */ }
243
+ if (!forceKill) {
244
+ forceKill = setTimeout(() => { try { child.kill("SIGKILL"); } catch { /* already gone */ } }, 250);
245
+ forceKill.unref?.();
246
+ }
247
+ };
248
+ const timeoutMs = Number.isFinite(Number(opts.timeout)) && Number(opts.timeout) > 0
249
+ ? Number(opts.timeout)
250
+ : CORE_CAPTURE_TIMEOUT_MS;
251
+ const timeout = setTimeout(() => { timedOut = true; terminate(); }, timeoutMs);
252
+ timeout.unref?.();
253
+ child.stdout.on("data", (chunk) => {
254
+ if (outputExceeded) return;
255
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
256
+ stdoutBytes += bytes.length;
257
+ if (stdoutBytes > CORE_CAPTURE_MAX_BYTES) {
258
+ outputExceeded = true;
259
+ terminate();
260
+ return;
261
+ }
262
+ stdoutChunks.push(bytes);
263
+ });
228
264
  child.stderr.on("data", (chunk) => { stderr = (stderr + chunk.toString()).slice(-4000); });
229
- child.on("error", (error) => resolve({ code: 1, stdout, stderr: String(error.message) }));
230
- child.on("close", (code) => resolve({ code: code ?? 0, stdout, stderr }));
265
+ child.on("error", (error) => finish({ code: 1, stdout: "", stderr: String(error.message), outputExceeded, timedOut }));
266
+ child.on("close", (code) => finish({
267
+ code: code ?? 0,
268
+ stdout: outputExceeded ? "" : Buffer.concat(stdoutChunks, stdoutBytes).toString("utf8"),
269
+ stderr,
270
+ outputExceeded,
271
+ timedOut,
272
+ }));
231
273
  });
274
+ if (result.outputExceeded) throw new Error(`Agentlas Core ${moduleName} response exceeded ${CORE_CAPTURE_MAX_BYTES} bytes.`);
275
+ if (result.timedOut) throw new Error(`Agentlas Core ${moduleName} timed out after ${Number(opts.timeout) > 0 ? Number(opts.timeout) : CORE_CAPTURE_TIMEOUT_MS}ms.`);
232
276
  const json = parseJsonOutput(result.stdout);
233
277
  if (result.code !== 0 || !json) {
234
278
  throw new Error(result.stderr.trim() || `Agentlas Core ${moduleName} did not return JSON.`);
@@ -247,7 +291,8 @@ function captureCoreJsonSync(moduleName, args, opts = {}, explicitRoot) {
247
291
  ...opts,
248
292
  encoding: "utf8",
249
293
  windowsHide: true,
250
- timeout: opts.timeout || 120_000,
294
+ maxBuffer: CORE_CAPTURE_MAX_BYTES,
295
+ timeout: opts.timeout || CORE_CAPTURE_TIMEOUT_MS,
251
296
  env: {
252
297
  ...(opts.env || process.env),
253
298
  HEPHAESTUS_RUNTIME_ROOT: root,
@@ -256,6 +301,13 @@ function captureCoreJsonSync(moduleName, args, opts = {}, explicitRoot) {
256
301
  },
257
302
  },
258
303
  );
304
+ if (result.error && result.error.code === "ENOBUFS") {
305
+ throw new Error(`Agentlas Core ${moduleName} response exceeded ${CORE_CAPTURE_MAX_BYTES} bytes.`);
306
+ }
307
+ if (result.error && result.error.code === "ETIMEDOUT") {
308
+ const timeoutMs = Number(opts.timeout) > 0 ? Number(opts.timeout) : CORE_CAPTURE_TIMEOUT_MS;
309
+ throw new Error(`Agentlas Core ${moduleName} timed out after ${timeoutMs}ms.`);
310
+ }
259
311
  const json = parseJsonOutput(result.stdout);
260
312
  if (result.status !== 0 || !json) {
261
313
  throw new Error(String(result.stderr || result.error?.message || `Agentlas Core ${moduleName} did not return JSON.`).trim());
@@ -279,6 +331,8 @@ module.exports = {
279
331
  HARNESS_MODE,
280
332
  CONTEXT_MAP_MIN_CORE_VERSION,
281
333
  CONTEXT_MAP_V3_RUNTIME_MARKERS,
334
+ CORE_CAPTURE_MAX_BYTES,
335
+ CORE_CAPTURE_TIMEOUT_MS,
282
336
  PY_BOOTSTRAP,
283
337
  readCoreRuntimeVersion,
284
338
  resolveCoreRuntimeRootFromCandidates,