agentlas 1.0.65 → 1.0.67
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/CHANGELOG.md +94 -0
- package/README.md +12 -4
- package/bin/agentlas.cjs +354 -16
- package/engine/acp/server.cjs +34 -3
- package/engine/agentlas-api-agent.cjs +151 -120
- package/engine/agentlas-banner.cjs +6 -2
- package/engine/agentlas-capabilities.cjs +17 -10
- package/engine/agentlas-cloud-runtime.cjs +4 -5
- package/engine/agentlas-config.cjs +19 -54
- package/engine/agentlas-core-harness.cjs +59 -5
- package/engine/agentlas-desktop-loadout.cjs +17 -3
- package/engine/agentlas-evolution.cjs +75 -18
- package/engine/agentlas-experience-exchange.cjs +145 -14
- package/engine/agentlas-experience-intake.cjs +2 -2
- package/engine/agentlas-i18n.cjs +0 -2
- package/engine/agentlas-input.cjs +273 -20
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-mcp-env.cjs +84 -23
- package/engine/agentlas-mcp-wrapper.cjs +36 -4
- package/engine/agentlas-memory-governance.cjs +2 -1
- package/engine/agentlas-memory-import.cjs +49 -12
- package/engine/agentlas-native-host.cjs +279 -44
- package/engine/agentlas-onboard.cjs +23 -1
- package/engine/agentlas-permissions.cjs +5 -1
- package/engine/agentlas-sqlite-policy.cjs +40 -2
- package/engine/agentlas-tasks.cjs +7 -0
- package/engine/agentlas-tools.cjs +37 -10
- package/engine/agentlas-ui.cjs +26 -4
- package/engine/agentlas-workforce.cjs +30 -5
- package/engine/agentlas-workload-routing.cjs +83 -10
- package/engine/agentlas.cjs +21 -4
- package/engine/agents/builder.cjs +35 -10
- package/engine/agents/files.cjs +81 -10
- package/engine/agents/import-local.cjs +10 -2
- package/engine/agents/registry.cjs +10 -3
- package/engine/agents/router.cjs +7 -7
- package/engine/agents/routes.cjs +31 -9
- package/engine/automation/daemon.cjs +154 -40
- package/engine/automation/launchd.cjs +395 -16
- package/engine/automation/schedule.cjs +90 -3
- package/engine/automation/store.cjs +163 -59
- package/engine/browser/cdp.cjs +190 -17
- package/engine/browser/vault.cjs +1 -0
- package/engine/cli-output.cjs +96 -30
- package/engine/cloud/auth.cjs +131 -13
- package/engine/cloud/hub-client.cjs +41 -16
- package/engine/cloud-assets/cas.cjs +18 -9
- package/engine/cloud-assets/commands.cjs +108 -30
- package/engine/cloud-assets/package.cjs +66 -3
- package/engine/cloud-assets/restore.cjs +12 -9
- package/engine/cloud-assets/state.cjs +609 -116
- package/engine/commands/acp.cjs +13 -2
- package/engine/commands/automation.cjs +75 -24
- package/engine/commands/billing.cjs +6 -1
- package/engine/commands/browser.cjs +61 -29
- package/engine/commands/build.cjs +72 -14
- package/engine/commands/call.cjs +1 -1
- package/engine/commands/cd.cjs +1 -1
- package/engine/commands/cloud.cjs +2 -1
- package/engine/commands/connect.cjs +38 -13
- package/engine/commands/creds.cjs +74 -29
- package/engine/commands/doctor.cjs +17 -1
- package/engine/commands/document.cjs +38 -10
- package/engine/commands/env.cjs +6 -1
- package/engine/commands/firm.cjs +42 -12
- package/engine/commands/graph.cjs +36 -32
- package/engine/commands/help.cjs +11 -1
- package/engine/commands/hep.cjs +1 -1
- package/engine/commands/import.cjs +5 -4
- package/engine/commands/index.cjs +1 -1
- package/engine/commands/install.cjs +6 -4
- package/engine/commands/list.cjs +56 -16
- package/engine/commands/login.cjs +5 -0
- package/engine/commands/logout.cjs +6 -1
- package/engine/commands/mcp.cjs +150 -37
- package/engine/commands/memory.cjs +3 -2
- package/engine/commands/multimodal.cjs +12 -0
- package/engine/commands/native.cjs +5 -1
- package/engine/commands/one.cjs +78 -33
- package/engine/commands/plugin.cjs +13 -2
- package/engine/commands/project.cjs +15 -12
- package/engine/commands/research.cjs +18 -7
- package/engine/commands/roles.cjs +104 -28
- package/engine/commands/route.cjs +2 -2
- package/engine/commands/run.cjs +42 -9
- package/engine/commands/search.cjs +23 -2
- package/engine/commands/setup.cjs +6 -1
- package/engine/commands/storm.cjs +44 -6
- package/engine/commands/swarm.cjs +47 -9
- package/engine/commands/telegram.cjs +6 -1
- package/engine/commands/uninstall.cjs +12 -6
- package/engine/commands/update.cjs +5 -0
- package/engine/commands/upload.cjs +2 -1
- package/engine/commands/usage.cjs +6 -1
- package/engine/commands/version.cjs +6 -1
- package/engine/commands/whoami.cjs +6 -1
- package/engine/commands/workforce.cjs +60 -16
- package/engine/core/capability-grants.cjs +7 -2
- package/engine/core/daemon-client.cjs +36 -7
- package/engine/core/db.cjs +10 -2
- package/engine/core/desktop-core-fetch.cjs +477 -77
- package/engine/core/schema-ensure.cjs +23 -11
- package/engine/experience/build.cjs +5 -1
- package/engine/experience/intents.cjs +35 -82
- package/engine/experience/runtime.cjs +7 -1
- package/engine/experience/variant.cjs +58 -4
- package/engine/firms/orchestrate.cjs +9 -6
- package/engine/graph/ask-model.cjs +22 -11
- package/engine/graph/interview.cjs +74 -23
- package/engine/graph/package.cjs +63 -8
- package/engine/hephaestus/local-core.cjs +18 -5
- package/engine/hephaestus/runtime.cjs +48 -43
- package/engine/hub/install.cjs +484 -63
- package/engine/hub/plugins.cjs +391 -29
- package/engine/mcp/consent.cjs +25 -17
- package/engine/mcp/contract.cjs +229 -24
- package/engine/mcp/inventory.cjs +46 -10
- package/engine/mcp/plan.cjs +10 -1
- package/engine/mcp/probe.cjs +24 -9
- package/engine/memory-cli/curate.cjs +74 -16
- package/engine/oberon/common.cjs +37 -12
- package/engine/oberon/manifest.cjs +25 -2
- package/engine/oberon/outputs.cjs +49 -19
- package/engine/oberon/render.cjs +665 -40
- package/engine/project/career-graph.cjs +15 -2
- package/engine/project/controller.cjs +133 -15
- package/engine/project/credentials.cjs +382 -34
- package/engine/project/env-file.cjs +93 -8
- package/engine/project/memory-context.cjs +11 -57
- package/engine/project/ontology.cjs +66 -25
- package/engine/project/paths.cjs +42 -8
- package/engine/project/seed.cjs +88 -51
- package/engine/project/state.cjs +41 -16
- package/engine/project/team.cjs +26 -1
- package/engine/runtime-refusal.cjs +4 -1
- package/engine/runtimes/acp-driver.cjs +56 -9
- package/engine/runtimes/auth-evidence.cjs +21 -2
- package/engine/runtimes/detect.cjs +28 -8
- package/engine/runtimes/kinds.cjs +12 -4
- package/engine/runtimes/overrides.cjs +4 -3
- package/engine/runtimes/resolve.cjs +8 -7
- package/engine/runtimes/roles.cjs +7 -5
- package/engine/sessions/apply-fences.cjs +396 -56
- package/engine/sessions/fences.cjs +111 -27
- package/engine/sessions/memory-turn.cjs +62 -3
- package/engine/sessions/orchestrator.cjs +11 -1
- package/engine/sessions/prompt.cjs +66 -9
- package/engine/sessions/session.cjs +70 -10
- package/engine/sessions/sink.cjs +78 -12
- package/engine/sessions/store.cjs +29 -7
- package/engine/storm/storm.cjs +26 -3
- package/engine/storm/swarm.cjs +31 -3
- package/engine/telegram/connect.cjs +185 -22
- package/engine/ui/commands-catalog.cjs +1 -1
- package/engine/ui/palette.cjs +12 -2
- package/engine/ui/repl.cjs +57 -9
- package/engine/ui/screens.cjs +2 -4
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/engine/workforce/capture.cjs +9 -27
- package/engine/workforce/deps.cjs +18 -6
- package/engine/workforce/local-core-transport.cjs +68 -5
- package/package.json +1 -1
- package/engine/commands/career-graph.cjs +0 -51
|
@@ -280,8 +280,14 @@ function triggerQuestion() {
|
|
|
280
280
|
*/
|
|
281
281
|
function autofillOutputChecks(bp) {
|
|
282
282
|
if (!bp || !Array.isArray(bp.steps)) return bp;
|
|
283
|
+
// 잘못된 모델 출력을 빈 목록으로 고쳐 쓰지 않는다. validateBlueprint가 retry 사유로
|
|
284
|
+
// 돌려줘야 모델이 자기 형식을 바로잡을 수 있다.
|
|
285
|
+
if (bp.checks != null && !Array.isArray(bp.checks)) return bp;
|
|
283
286
|
const checks = Array.isArray(bp.checks) ? [...bp.checks] : [];
|
|
284
|
-
const checked = new Set(checks
|
|
287
|
+
const checked = new Set(checks
|
|
288
|
+
.filter((check) => check && typeof check === "object")
|
|
289
|
+
.map((check) => String(check.subject || "").trim())
|
|
290
|
+
.filter(Boolean));
|
|
285
291
|
bp.steps.forEach((step, index) => {
|
|
286
292
|
if (step.effect !== "mutation") return;
|
|
287
293
|
for (const value of Array.isArray(step.consumes) ? step.consumes : []) {
|
|
@@ -310,10 +316,14 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
310
316
|
const problems = [];
|
|
311
317
|
const push = (reason, ask = null) => problems.push({ reason, ask });
|
|
312
318
|
if (!bp || typeof bp !== "object") { push("만들 내용을 읽지 못했습니다."); return problems; }
|
|
313
|
-
if (
|
|
319
|
+
if (typeof bp.name !== "string") {
|
|
320
|
+
push("이름(name)의 형식이 올바르지 않습니다.");
|
|
321
|
+
} else if (!bp.name.trim()) {
|
|
314
322
|
push("이름이 없습니다.", { id: "name", question: "이 자동화를 뭐라고 부를까요?", why: "목록에서 이 이름으로 찾게 됩니다." });
|
|
315
323
|
}
|
|
316
|
-
if (
|
|
324
|
+
if (typeof bp.goal !== "string") {
|
|
325
|
+
push("목표(goal)의 형식이 올바르지 않습니다.");
|
|
326
|
+
} else if (!bp.goal.trim()) {
|
|
317
327
|
push("무엇을 위한 자동화인지가 없습니다.", {
|
|
318
328
|
id: "goal", question: "이 자동화로 무엇을 얻고 싶으신가요? 한 문장이면 됩니다.",
|
|
319
329
|
why: "나중에 목록에서 보고 무엇이었는지 알아보려면 필요합니다.",
|
|
@@ -323,7 +333,9 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
323
333
|
if (!trigger || typeof trigger !== "object") {
|
|
324
334
|
push("언제 시작하는지가 없습니다.", triggerQuestion());
|
|
325
335
|
} else if (trigger.kind === "cron") {
|
|
326
|
-
if (
|
|
336
|
+
if (typeof trigger.schedule !== "string") {
|
|
337
|
+
push("실행 시각(schedule)의 형식이 올바르지 않습니다.");
|
|
338
|
+
} else if (!trigger.schedule.trim()) {
|
|
327
339
|
push("실행 시각이 없습니다.", {
|
|
328
340
|
id: "schedule", question: "몇 시에 돌릴까요?",
|
|
329
341
|
why: "시각을 대신 정하면, 보지 않는 시간에 조용히 돌게 됩니다.",
|
|
@@ -331,18 +343,22 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
331
343
|
});
|
|
332
344
|
}
|
|
333
345
|
} else if (trigger.kind === "input") {
|
|
334
|
-
if (
|
|
346
|
+
if (typeof trigger.label !== "string") {
|
|
347
|
+
push("입력 안내(label)의 형식이 올바르지 않습니다.");
|
|
348
|
+
} else if (!trigger.label.trim()) {
|
|
335
349
|
push("무엇을 입력받는지가 없습니다.", {
|
|
336
350
|
id: "input-label", question: "시작할 때 무엇을 입력받을까요? (예: 만들 프로젝트의 주제)",
|
|
337
351
|
why: "입력창에 이 문구가 그대로 보입니다.",
|
|
338
352
|
});
|
|
339
353
|
}
|
|
340
|
-
if (!VAR_RE.test(
|
|
354
|
+
if (typeof trigger.varName !== "string" || !VAR_RE.test(trigger.varName)) push("입력값의 이름이 올바르지 않습니다.");
|
|
341
355
|
} else {
|
|
342
356
|
push("언제 시작하는지를 알 수 없습니다.", triggerQuestion());
|
|
343
357
|
}
|
|
344
358
|
|
|
345
359
|
const steps = Array.isArray(bp.steps) ? bp.steps : [];
|
|
360
|
+
const checks = Array.isArray(bp.checks) ? bp.checks : [];
|
|
361
|
+
const branches = Array.isArray(bp.branches) ? bp.branches : [];
|
|
346
362
|
if (steps.length === 0) {
|
|
347
363
|
push("할 일이 하나도 없습니다.", {
|
|
348
364
|
id: "steps", question: "무슨 일을 해야 하나요? 순서대로 적어 주세요.",
|
|
@@ -350,14 +366,19 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
350
366
|
});
|
|
351
367
|
}
|
|
352
368
|
if (steps.length > MAX_STEPS) push(`단계가 ${steps.length}개입니다. 한 번에 만들 수 있는 것은 ${MAX_STEPS}개까지입니다.`);
|
|
369
|
+
if (bp.checks != null && !Array.isArray(bp.checks)) push("검증 목록(checks)의 형식이 올바르지 않습니다.");
|
|
370
|
+
if (bp.branches != null && !Array.isArray(bp.branches)) push("갈림길 목록(branches)의 형식이 올바르지 않습니다.");
|
|
353
371
|
|
|
354
372
|
const produced = new Set();
|
|
355
373
|
if (trigger && trigger.kind === "input" && trigger.varName) produced.add(trigger.varName);
|
|
356
374
|
steps.forEach((step, index) => {
|
|
357
375
|
const at = `${index + 1}번째 단계`;
|
|
358
376
|
if (!step || typeof step !== "object") { push(`${at}를 읽지 못했습니다.`); return; }
|
|
359
|
-
if (
|
|
360
|
-
if (!
|
|
377
|
+
if (typeof step.title !== "string") push(`${at}의 이름(title) 형식이 올바르지 않습니다.`);
|
|
378
|
+
else if (!step.title.trim()) push(`${at}에 이름이 없습니다.`);
|
|
379
|
+
if (typeof step.instruction !== "string") {
|
|
380
|
+
push(`${at}의 지시(instruction) 형식이 올바르지 않습니다.`);
|
|
381
|
+
} else if (!step.instruction.trim()) {
|
|
361
382
|
push(`${at}가 무엇을 할지 적혀 있지 않습니다.`, {
|
|
362
383
|
id: `step-${index}-instruction`,
|
|
363
384
|
question: `"${step.title || at}" 단계에서 정확히 무엇을 해야 하나요?`,
|
|
@@ -372,11 +393,14 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
372
393
|
choices: ["아니요, 만들기만 합니다", "네, 바깥으로 나갑니다"],
|
|
373
394
|
});
|
|
374
395
|
}
|
|
396
|
+
if (step.kind != null && !["agent", "code", "runGraph"].includes(step.kind)) {
|
|
397
|
+
push(`${at}의 실행 종류(kind) 형식이 올바르지 않습니다.`);
|
|
398
|
+
}
|
|
375
399
|
// ★부를 자동화는 실재해야 한다(데스크탑 shared/graph-blueprint.ts와 같은 규칙). 이름이
|
|
376
400
|
// 아니라 id로 가리키는 이유도 같다 — 이름은 바뀌고 지어낸 id는 실행에서 죽는다.
|
|
377
401
|
// 목록을 모르는 호출부에서는 형식만 본다(ctx.knownGraphs 없으면 건너뜀).
|
|
378
402
|
if (step.kind === "runGraph") {
|
|
379
|
-
const ref =
|
|
403
|
+
const ref = typeof step.graphRef === "string" ? step.graphRef.trim() : "";
|
|
380
404
|
if (!ref) {
|
|
381
405
|
push(`${at}가 어느 자동화를 부를지 정하지 않았습니다.`);
|
|
382
406
|
} else if (ctx.selfId && ref === ctx.selfId) {
|
|
@@ -388,7 +412,9 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
388
412
|
// ★코드 스텝은 스크립트가 있어야 한다. 없으면 "코드로 하겠다"고 해 놓고 빈 채로 저장돼
|
|
389
413
|
// 실행에서 CODE_NODE_EMPTY로 죽는다(데스크탑과 같은 규칙, 저작 시점에 막는다).
|
|
390
414
|
if (step.kind === "code") {
|
|
391
|
-
if (
|
|
415
|
+
if (typeof step.code !== "string") {
|
|
416
|
+
push(`${at}의 코드(code) 형식이 올바르지 않습니다.`);
|
|
417
|
+
} else if (!step.code.trim()) {
|
|
392
418
|
push(`${at}는 코드로 실행한다고 했는데 스크립트가 비어 있습니다.`, {
|
|
393
419
|
id: `step-${index}-code`,
|
|
394
420
|
question: `"${step.title || at}" 단계에서 무엇을 계산·가공하나요? (AI가 스크립트를 채웁니다)`,
|
|
@@ -398,8 +424,19 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
398
424
|
if (step.codeLang && step.codeLang !== "python" && step.codeLang !== "js") {
|
|
399
425
|
push(`${at}의 코드 언어 "${step.codeLang}"을(를) 이 제품이 모릅니다(python 또는 js).`);
|
|
400
426
|
}
|
|
427
|
+
if (step.packages != null && !Array.isArray(step.packages)) {
|
|
428
|
+
push(`${at}의 코드 패키지 목록(packages) 형식이 올바르지 않습니다.`);
|
|
429
|
+
} else if (Array.isArray(step.packages) && step.packages.some((value) => typeof value !== "string" || !value.trim())) {
|
|
430
|
+
push(`${at}의 코드 패키지 이름 하나가 올바르지 않습니다.`);
|
|
431
|
+
}
|
|
401
432
|
}
|
|
402
|
-
|
|
433
|
+
const consumes = Array.isArray(step.consumes) ? step.consumes : [];
|
|
434
|
+
if (step.consumes != null && !Array.isArray(step.consumes)) push(`${at}의 입력값 목록(consumes) 형식이 올바르지 않습니다.`);
|
|
435
|
+
for (const name of consumes) {
|
|
436
|
+
if (typeof name !== "string" || !VAR_RE.test(name)) {
|
|
437
|
+
push(`${at}의 입력값 이름 하나가 올바르지 않습니다.`);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
403
440
|
if (!produced.has(name)) {
|
|
404
441
|
push(`${at}가 쓰는 "${name}" 값을 아무도 만들지 않습니다.`, {
|
|
405
442
|
id: `step-${index}-consumes-${name}`,
|
|
@@ -409,7 +446,9 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
409
446
|
}
|
|
410
447
|
}
|
|
411
448
|
|
|
412
|
-
|
|
449
|
+
const uses = Array.isArray(step.uses) ? step.uses : [];
|
|
450
|
+
if (step.uses != null && !Array.isArray(step.uses)) push(`${at}의 도구 목록(uses) 형식이 올바르지 않습니다.`);
|
|
451
|
+
for (const use of uses) {
|
|
413
452
|
if (!use || typeof use !== "object") { push(`${at}의 도구 선언을 읽지 못했습니다.`); continue; }
|
|
414
453
|
if (!CAPABILITIES.includes(use.capability)) {
|
|
415
454
|
push(`${at}가 이 제품이 모르는 도구("${use.capability}")를 쓰려고 합니다.`, {
|
|
@@ -430,15 +469,20 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
430
469
|
}
|
|
431
470
|
}
|
|
432
471
|
if (step.produces) {
|
|
433
|
-
if (!VAR_RE.test(step.produces)) push(`${at}의 결과 이름 "${step.produces}"은(는) 쓸 수 없습니다.`);
|
|
472
|
+
if (typeof step.produces !== "string" || !VAR_RE.test(step.produces)) push(`${at}의 결과 이름 "${step.produces}"은(는) 쓸 수 없습니다.`);
|
|
434
473
|
else produced.add(step.produces);
|
|
474
|
+
} else if (step.produces != null && typeof step.produces !== "string") {
|
|
475
|
+
push(`${at}의 결과 이름은 문자열이어야 합니다.`);
|
|
435
476
|
}
|
|
477
|
+
if (step.role != null && typeof step.role !== "string") push(`${at}의 역할(role) 형식이 올바르지 않습니다.`);
|
|
478
|
+
if (step.roleEn != null && typeof step.roleEn !== "string") push(`${at}의 영어 역할(roleEn) 형식이 올바르지 않습니다.`);
|
|
436
479
|
});
|
|
437
480
|
|
|
438
481
|
|
|
439
482
|
// 검증 단계
|
|
440
483
|
const checkVerdicts = new Set();
|
|
441
|
-
for (const check of
|
|
484
|
+
for (const check of checks) {
|
|
485
|
+
if (!check || typeof check !== "object") { push("검증 항목 하나의 형식이 올바르지 않습니다."); continue; }
|
|
442
486
|
const at = `${(check.afterStep ?? 0) + 1}번째 단계 뒤의 검증`;
|
|
443
487
|
if (!steps[check.afterStep]) { push(`${at}가 없는 단계를 가리킵니다.`); continue; }
|
|
444
488
|
if (!check.subject || !produced.has(check.subject)) {
|
|
@@ -478,7 +522,10 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
478
522
|
*/
|
|
479
523
|
{
|
|
480
524
|
const checkedSubjects = new Set(
|
|
481
|
-
|
|
525
|
+
checks
|
|
526
|
+
.filter((check) => check && typeof check === "object")
|
|
527
|
+
.map((check) => String(check.subject || "").trim())
|
|
528
|
+
.filter(Boolean),
|
|
482
529
|
);
|
|
483
530
|
steps.forEach((step, index) => {
|
|
484
531
|
if (step.effect !== "mutation") return;
|
|
@@ -514,8 +561,9 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
514
561
|
*/
|
|
515
562
|
{
|
|
516
563
|
const resultChecks = new Map();
|
|
517
|
-
for (const check of
|
|
518
|
-
|
|
564
|
+
for (const check of checks) {
|
|
565
|
+
if (!check || typeof check !== "object") continue;
|
|
566
|
+
const subj = String(check.subject || "").trim();
|
|
519
567
|
if (subj) resultChecks.set(subj, check);
|
|
520
568
|
}
|
|
521
569
|
steps.forEach((step, index) => {
|
|
@@ -552,7 +600,8 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
552
600
|
}
|
|
553
601
|
|
|
554
602
|
// 반복이 있는데 검증이 없으면 "마음에 들 때까지"를 글자 찾기로 흉내 내게 된다(실측).
|
|
555
|
-
for (const branch of
|
|
603
|
+
for (const branch of branches) {
|
|
604
|
+
if (!branch || typeof branch !== "object") { push("갈림길 항목 하나의 형식이 올바르지 않습니다."); continue; }
|
|
556
605
|
if (branch.repeatStep === undefined) continue;
|
|
557
606
|
if (!checkVerdicts.has(branch.var)) {
|
|
558
607
|
push(`${(branch.afterStep ?? 0) + 1}번째 단계 뒤의 반복이 검증 결과가 아니라 "${branch.var}"의 내용을 보고 돌지 말지 정합니다.`, {
|
|
@@ -569,15 +618,16 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
569
618
|
* 정본은 데스크탑 shared/graph-blueprint.ts — 이 파일은 손복사본이고 패리티 게이트가 지킨다.
|
|
570
619
|
*/
|
|
571
620
|
const emptinessTestedVars = new Set(
|
|
572
|
-
|
|
573
|
-
.filter((branch) => branch.op === "truthy" || branch.op === "falsy")
|
|
621
|
+
branches
|
|
622
|
+
.filter((branch) => branch && typeof branch === "object" && (branch.op === "truthy" || branch.op === "falsy"))
|
|
574
623
|
.map((branch) => String(branch.var || "").trim())
|
|
575
624
|
.filter(Boolean),
|
|
576
625
|
);
|
|
577
|
-
for (const check of
|
|
626
|
+
for (const check of checks) {
|
|
627
|
+
if (!check || typeof check !== "object") continue;
|
|
578
628
|
const subject = String(check.subject || "").trim();
|
|
579
629
|
if (!subject || !emptinessTestedVars.has(subject)) continue;
|
|
580
|
-
const branch =
|
|
630
|
+
const branch = branches.find((b) => b && typeof b === "object" && String(b.var || "").trim() === subject);
|
|
581
631
|
if (!branch) continue;
|
|
582
632
|
// 위치를 본다 — 갈림길 뒤 값-있는 쪽의 검증은 비는 날 아예 안 돌므로 문제가 아니다.
|
|
583
633
|
const at = typeof check.afterStep === "number" ? check.afterStep : -1;
|
|
@@ -599,7 +649,8 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
599
649
|
);
|
|
600
650
|
}
|
|
601
651
|
|
|
602
|
-
for (const branch of
|
|
652
|
+
for (const branch of branches) {
|
|
653
|
+
if (!branch || typeof branch !== "object") continue;
|
|
603
654
|
const at = `${(branch.afterStep ?? 0) + 1}번째 단계 뒤의 갈림길`;
|
|
604
655
|
if (!steps[branch.afterStep]) { push(`${at}가 없는 단계를 가리킵니다.`); continue; }
|
|
605
656
|
if (!OPS.has(branch.op)) { push(`${at}의 판단 방법을 알 수 없습니다.`); continue; }
|
package/engine/graph/package.cjs
CHANGED
|
@@ -35,14 +35,55 @@ const PERSONAL_PATH_RE = /(\/Users\/[^/\s"']+|\/home\/[^/\s"']+|C:\\Users\\[^\\\
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
function vaultKeyFor(
|
|
39
|
-
return
|
|
38
|
+
function vaultKeyFor(fieldPath) {
|
|
39
|
+
return String(fieldPath).replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function looksSecretValue(value) {
|
|
43
43
|
return typeof value === "string" && SECRET_VALUE_PATTERNS.some((re) => re.test(value));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function scrubNestedConfigValue(nodeId, fieldPath, value, state) {
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.map((item, index) => scrubNestedConfigValue(nodeId, `${fieldPath}[${index}]`, item, state));
|
|
49
|
+
}
|
|
50
|
+
if (!value || typeof value !== "object") return value;
|
|
51
|
+
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [key, child] of Object.entries(value)) {
|
|
54
|
+
const childPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
55
|
+
if (SECRET_KEY_RE.test(key)) {
|
|
56
|
+
const vaultKey = vaultKeyFor(childPath);
|
|
57
|
+
out[key] = `\${vault.${vaultKey}}`;
|
|
58
|
+
state.vaultTemplate.push({ key: vaultKey, kind: "secret", requiredBy: [nodeId], sourceField: childPath });
|
|
59
|
+
state.findings.push({ rule: "secret-field", nodeId, field: childPath, action: `templated:${vaultKey}` });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (looksSecretValue(child)) {
|
|
63
|
+
state.blockers.push({
|
|
64
|
+
nodeId,
|
|
65
|
+
field: childPath,
|
|
66
|
+
reason: `"${childPath}" 값이 자격증명처럼 보입니다. 어떤 키인지 알 수 없어 자동으로 빈칸 처리할 수 없습니다.`,
|
|
67
|
+
nextAction: `이 값을 금고 변수로 바꾼 뒤(예: \${vault.MY_TOKEN}) 다시 내보내세요.`,
|
|
68
|
+
});
|
|
69
|
+
out[key] = child;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (typeof child === "string") {
|
|
73
|
+
PERSONAL_PATH_RE.lastIndex = 0;
|
|
74
|
+
if (PERSONAL_PATH_RE.test(child)) {
|
|
75
|
+
PERSONAL_PATH_RE.lastIndex = 0;
|
|
76
|
+
out[key] = child.replace(PERSONAL_PATH_RE, "<사용자 폴더>");
|
|
77
|
+
PERSONAL_PATH_RE.lastIndex = 0;
|
|
78
|
+
state.findings.push({ rule: "personal-path", nodeId, field: childPath, action: "removed" });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
out[key] = scrubNestedConfigValue(nodeId, childPath, child, state);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
46
87
|
/**
|
|
47
88
|
* 노드 설정을 훑어 비밀·모델핀·개인 경로를 처리한다.
|
|
48
89
|
* 반환: { config, findings, vaultTemplate, blockers }
|
|
@@ -55,7 +96,7 @@ function scrubNodeConfig(nodeId, config) {
|
|
|
55
96
|
for (const [key, value] of Object.entries(config || {})) {
|
|
56
97
|
// 1) 비밀로 선언된 칸 — 값 유무와 무관하게 금고 변수로 바꾼다.
|
|
57
98
|
if (SECRET_KEY_RE.test(key)) {
|
|
58
|
-
const vaultKey = vaultKeyFor(
|
|
99
|
+
const vaultKey = vaultKeyFor(key);
|
|
59
100
|
out[key] = `$\{vault.${vaultKey}}`;
|
|
60
101
|
vaultTemplate.push({ key: vaultKey, kind: "secret", requiredBy: [nodeId], sourceField: key });
|
|
61
102
|
findings.push({ rule: "secret-field", nodeId, field: key, action: `templated:${vaultKey}` });
|
|
@@ -86,7 +127,7 @@ function scrubNodeConfig(nodeId, config) {
|
|
|
86
127
|
PERSONAL_PATH_RE.lastIndex = 0;
|
|
87
128
|
continue;
|
|
88
129
|
}
|
|
89
|
-
out[key] = value;
|
|
130
|
+
out[key] = scrubNestedConfigValue(nodeId, key, value, { findings, vaultTemplate, blockers });
|
|
90
131
|
}
|
|
91
132
|
return { config: out, findings, vaultTemplate, blockers };
|
|
92
133
|
}
|
|
@@ -134,8 +175,13 @@ function buildPackage(input) {
|
|
|
134
175
|
}
|
|
135
176
|
}
|
|
136
177
|
const server = node.config?.mcpServer;
|
|
137
|
-
if (typeof server === "string" && server
|
|
138
|
-
dependencies.mcp.
|
|
178
|
+
if (typeof server === "string" && server) {
|
|
179
|
+
const existingMcp = dependencies.mcp.find((item) => item.serverSlug === server);
|
|
180
|
+
if (existingMcp) {
|
|
181
|
+
if (!existingMcp.requiredBy.includes(node.id)) existingMcp.requiredBy.push(node.id);
|
|
182
|
+
} else {
|
|
183
|
+
dependencies.mcp.push({ serverSlug: server, requiredBy: [node.id] });
|
|
184
|
+
}
|
|
139
185
|
}
|
|
140
186
|
}
|
|
141
187
|
|
|
@@ -210,11 +256,20 @@ function verifyPackage(pkg) {
|
|
|
210
256
|
if (!graph || !Array.isArray(graph.nodes) || graph.nodes.length === 0) {
|
|
211
257
|
problems.push("그래프에 단계가 없습니다.");
|
|
212
258
|
}
|
|
213
|
-
|
|
214
|
-
|
|
259
|
+
const integrity = manifest?.integrity;
|
|
260
|
+
if (integrity && typeof integrity.graphDigest === "string" && graph) {
|
|
261
|
+
if (digestOf(graph) !== integrity.graphDigest) {
|
|
215
262
|
problems.push("그래프 내용이 매니페스트 지문과 다릅니다(전송 중 변형되었을 수 있습니다).");
|
|
216
263
|
}
|
|
217
264
|
}
|
|
265
|
+
if (!integrity || typeof integrity.graphDigest !== "string" || typeof integrity.manifestDigest !== "string") {
|
|
266
|
+
problems.push("매니페스트 무결성 지문이 없습니다.");
|
|
267
|
+
} else if (digestOf({
|
|
268
|
+
...manifest,
|
|
269
|
+
integrity: { graphDigest: integrity.graphDigest, manifestDigest: null },
|
|
270
|
+
}) !== integrity.manifestDigest) {
|
|
271
|
+
problems.push("매니페스트 내용이 자체 지문과 다릅니다(전송 중 변형되었을 수 있습니다).");
|
|
272
|
+
}
|
|
218
273
|
return problems;
|
|
219
274
|
}
|
|
220
275
|
|
|
@@ -22,6 +22,8 @@ const os = require("node:os");
|
|
|
22
22
|
const path = require("node:path");
|
|
23
23
|
const { spawn } = require("node:child_process");
|
|
24
24
|
|
|
25
|
+
const MAX_CORE_STDOUT_LINE_BYTES = 16 * 1024 * 1024;
|
|
26
|
+
|
|
25
27
|
function localCoreBin() {
|
|
26
28
|
const candidates = [
|
|
27
29
|
process.env.HEPHAESTUS_BIN,
|
|
@@ -66,7 +68,7 @@ function createLocalCoreClient({ cwd, timeoutMs = 60_000 } = {}) {
|
|
|
66
68
|
// 세션에서 `hephaestus mcp serve` 17개 누적. exit 훅으로 반드시 걷는다.
|
|
67
69
|
const reap = () => { try { child.kill(); } catch { /* already gone */ } };
|
|
68
70
|
process.once("exit", reap);
|
|
69
|
-
let buffer =
|
|
71
|
+
let buffer = Buffer.alloc(0);
|
|
70
72
|
let nextId = 0;
|
|
71
73
|
let initialized = null;
|
|
72
74
|
const pending = new Map();
|
|
@@ -93,10 +95,21 @@ function createLocalCoreClient({ cwd, timeoutMs = 60_000 } = {}) {
|
|
|
93
95
|
failPending("local_core_transport_error", `the local core input stream failed: ${(error && error.message) || error}`);
|
|
94
96
|
});
|
|
95
97
|
child.stdout.on("data", (chunk) => {
|
|
96
|
-
|
|
98
|
+
if (exited) return;
|
|
99
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
100
|
+
if (buffer.length + bytes.length > MAX_CORE_STDOUT_LINE_BYTES) {
|
|
101
|
+
failPending(
|
|
102
|
+
"local_core_response_too_large",
|
|
103
|
+
`the local core emitted a response line larger than ${MAX_CORE_STDOUT_LINE_BYTES} bytes`,
|
|
104
|
+
);
|
|
105
|
+
buffer = Buffer.alloc(0);
|
|
106
|
+
try { child.kill(); } catch { /* already gone */ }
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
buffer = buffer.length ? Buffer.concat([buffer, bytes]) : bytes;
|
|
97
110
|
let index;
|
|
98
|
-
while ((index = buffer.indexOf(
|
|
99
|
-
const line = buffer.
|
|
111
|
+
while ((index = buffer.indexOf(0x0a)) >= 0) {
|
|
112
|
+
const line = buffer.subarray(0, index).toString("utf8");
|
|
100
113
|
buffer = buffer.slice(index + 1);
|
|
101
114
|
if (!line.trim()) continue;
|
|
102
115
|
let message;
|
|
@@ -191,4 +204,4 @@ function createLocalCoreClient({ cwd, timeoutMs = 60_000 } = {}) {
|
|
|
191
204
|
return { call, close };
|
|
192
205
|
}
|
|
193
206
|
|
|
194
|
-
module.exports = { createLocalCoreClient, localCoreBin };
|
|
207
|
+
module.exports = { createLocalCoreClient, localCoreBin, MAX_CORE_STDOUT_LINE_BYTES };
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* v2 이식 규칙:
|
|
12
12
|
* - D.out → ctx.out, D.prefsLang() → ctx.lang.
|
|
13
|
-
* - D.runCwd —
|
|
14
|
-
*
|
|
15
|
-
* homedir 폴백). 그 동작을 그대로 보존해 여기 로컬로 이식했다. (프로젝트
|
|
16
|
-
* 실행 위치가 필요한 곳은 v1과 동일하게 projectCwd()를 쓴다.)
|
|
13
|
+
* - D.runCwd — Terminal 정본(project/paths.cjs)의 private agent-cwd 경계를 쓴다.
|
|
14
|
+
* 생성 실패 시 홈 폴더로 권한을 넓히지 않는다.
|
|
17
15
|
* - 런타임 발견은 engine/agentlas-core-harness.cjs(복원본)를 소비만 한다.
|
|
18
16
|
* - 런타임 부재 = 정직 정지(exit 1) + v1 설치 안내 문구 그대로. 폴백 금지.
|
|
19
17
|
*
|
|
@@ -30,8 +28,10 @@ const { Ui } = require("../agentlas-ui.cjs");
|
|
|
30
28
|
const { truncateWidth, visWidth, wrapWidth } = require("../ui/width.cjs");
|
|
31
29
|
const coreHarness = require("../agentlas-core-harness.cjs");
|
|
32
30
|
const { userDataDir } = require("../core/paths.cjs");
|
|
31
|
+
const { runCwd, projectCwd } = require("../project/paths.cjs");
|
|
33
32
|
|
|
34
33
|
const { CONTEXT_MAP_MIN_CORE_VERSION, resolveContextMapCoreRoot } = coreHarness;
|
|
34
|
+
const CAPTURE_KILL_GRACE_MS = 250;
|
|
35
35
|
|
|
36
36
|
// ── 명령 usage 문자열 (v1 TOP_LEVEL_COMMAND_USAGE에서 hephaestus 클러스터만 발췌) ──
|
|
37
37
|
const USAGE = Object.freeze({
|
|
@@ -47,7 +47,7 @@ const USAGE = Object.freeze({
|
|
|
47
47
|
hephaestus: "usage: agentlas hephaestus <subcommand> [args]",
|
|
48
48
|
journal: "usage: agentlas journal <status|verify|repair|gate> --run-id <id> | --journal <path>",
|
|
49
49
|
netadmin: "usage: agentlas netadmin <init|status|reindex|bench|add-source> [args]",
|
|
50
|
-
research: "usage: agentlas research <
|
|
50
|
+
research: "usage: agentlas research <subcommand> [args] (full list: agentlas research --help)",
|
|
51
51
|
route: 'usage: agentlas route "<request>" [--json]',
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -66,6 +66,13 @@ function isHelpToken(value) {
|
|
|
66
66
|
return value === "help" || value === "--help" || value === "-h";
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function isHelpRequest(args) {
|
|
70
|
+
if (!Array.isArray(args) || !args.length) return false;
|
|
71
|
+
if (args.length === 1) return isHelpToken(args[0]);
|
|
72
|
+
if (args.length === 2 && args[1] === "help") return true;
|
|
73
|
+
return args.some((value) => value === "--help" || value === "-h");
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
// ── 라우팅/리서치 미리보기 모델 (v1 module-level 순수 함수, 그대로) ──
|
|
70
77
|
function routePreviewModel(value) {
|
|
71
78
|
const json = value && typeof value === "object" ? value : {};
|
|
@@ -204,31 +211,6 @@ function create(ctx, deps = {}) {
|
|
|
204
211
|
return new Ui({ lang: uiLang || lang() });
|
|
205
212
|
}
|
|
206
213
|
|
|
207
|
-
// v1 agentlas.cjs runCwd() 그대로: 에이전트 전용 안전 샌드박스 폴더.
|
|
208
|
-
// (검증: v1에 AGENTLAS_RUN_CWD env는 없었다 — process.cwd() 매핑이 아니라
|
|
209
|
-
// 이 샌드박스가 원래 동작이므로 그대로 보존한다.)
|
|
210
|
-
function runCwd() {
|
|
211
|
-
const dir = path.join(userDataDir(), "agent-cwd");
|
|
212
|
-
try {
|
|
213
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
214
|
-
return dir;
|
|
215
|
-
} catch {
|
|
216
|
-
return os.homedir();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// v1: 에이전트가 실제로 실행될 작업 폴더 = 사용자가 명령을 친 현재 디렉터리.
|
|
221
|
-
// 단, home/userData/agent-cwd 같은 "프로젝트 아님" 위치면 샌드박스로 폴백.
|
|
222
|
-
function projectCwd() {
|
|
223
|
-
try {
|
|
224
|
-
const cwd = process.cwd();
|
|
225
|
-
if (!cwd || cwd === os.homedir() || cwd === userDataDir() || cwd === runCwd()) return runCwd();
|
|
226
|
-
return cwd;
|
|
227
|
-
} catch {
|
|
228
|
-
return runCwd();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
214
|
// ── Hephaestus 런타임 해석 (설치 런처 우선, 앱 번들 폴백) ──
|
|
233
215
|
// v1 후보 순서 그대로: HEPHAESTUS_BIN → ~/.agentlas/runtime/current/bin/hephaestus
|
|
234
216
|
// → Core python 모듈(resolveCoreRuntimeRoot). win32는 bin 후보를 건너뛴다(v1 동일).
|
|
@@ -300,29 +282,44 @@ function create(ctx, deps = {}) {
|
|
|
300
282
|
let overflow = false;
|
|
301
283
|
let timedOut = false;
|
|
302
284
|
let settled = false;
|
|
285
|
+
let forceKillTimer = null;
|
|
286
|
+
const terminate = () => {
|
|
287
|
+
if (settled) return;
|
|
288
|
+
try { child.kill("SIGTERM"); } catch { /* already gone */ }
|
|
289
|
+
if (forceKillTimer) return;
|
|
290
|
+
forceKillTimer = setTimeout(() => {
|
|
291
|
+
forceKillTimer = null;
|
|
292
|
+
if (!settled) {
|
|
293
|
+
try { child.kill("SIGKILL"); } catch { /* already gone */ }
|
|
294
|
+
}
|
|
295
|
+
}, CAPTURE_KILL_GRACE_MS);
|
|
296
|
+
if (forceKillTimer.unref) forceKillTimer.unref();
|
|
297
|
+
};
|
|
303
298
|
const append = (target, chunk) => {
|
|
304
299
|
if (overflow) return;
|
|
305
300
|
bytes += chunk.length;
|
|
306
301
|
if (bytes > maxBytes) {
|
|
307
302
|
overflow = true;
|
|
308
|
-
|
|
303
|
+
terminate();
|
|
309
304
|
return;
|
|
310
305
|
}
|
|
311
306
|
target.push(chunk);
|
|
312
307
|
};
|
|
313
308
|
child.stdout.on("data", (chunk) => append(stdout, Buffer.from(chunk)));
|
|
314
309
|
child.stderr.on("data", (chunk) => append(stderr, Buffer.from(chunk)));
|
|
315
|
-
const onInterrupt = () =>
|
|
310
|
+
const onInterrupt = () => terminate();
|
|
316
311
|
process.once("SIGINT", onInterrupt);
|
|
317
312
|
const timer = setTimeout(() => {
|
|
318
313
|
timedOut = true;
|
|
319
|
-
|
|
314
|
+
terminate();
|
|
320
315
|
}, timeoutMs);
|
|
321
316
|
if (timer.unref) timer.unref();
|
|
322
317
|
const done = (code, error) => {
|
|
323
318
|
if (settled) return;
|
|
324
319
|
settled = true;
|
|
325
320
|
clearTimeout(timer);
|
|
321
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
322
|
+
forceKillTimer = null;
|
|
326
323
|
process.removeListener("SIGINT", onInterrupt);
|
|
327
324
|
resolve({
|
|
328
325
|
code: overflow || timedOut || error ? 1 : (code ?? 0),
|
|
@@ -426,14 +423,15 @@ function create(ctx, deps = {}) {
|
|
|
426
423
|
ui.line(" " + (index === 0 ? ui.c.paw("✗ ") : " ") + ui.c.text(line));
|
|
427
424
|
});
|
|
428
425
|
};
|
|
429
|
-
if (
|
|
426
|
+
if (result.overflow || result.timedOut) {
|
|
427
|
+
if (result.overflow) renderError(ui.lang === "ko" ? "라우팅 출력이 2MB 제한을 넘었습니다." : "Route output exceeded the 2 MB limit.");
|
|
428
|
+
if (result.timedOut) renderError(ui.lang === "ko" ? "라우팅이 30초 제한을 넘었습니다." : "Route timed out after 30 seconds.");
|
|
429
|
+
} else if (json) renderRoutePreview(json, ui);
|
|
430
430
|
else {
|
|
431
431
|
const detail = (result.stderr || result.stdout || "").trim();
|
|
432
432
|
const message = detail || (ui.lang === "ko" ? "라우팅 결과를 읽지 못했습니다." : "Could not read the route result.");
|
|
433
433
|
renderError(message);
|
|
434
434
|
}
|
|
435
|
-
if (result.overflow) renderError(ui.lang === "ko" ? "라우팅 출력이 2MB 제한을 넘었습니다." : "Route output exceeded the 2 MB limit.");
|
|
436
|
-
if (result.timedOut) renderError(ui.lang === "ko" ? "라우팅이 30초 제한을 넘었습니다." : "Route timed out after 30 seconds.");
|
|
437
435
|
return result.code;
|
|
438
436
|
}
|
|
439
437
|
|
|
@@ -551,7 +549,7 @@ function create(ctx, deps = {}) {
|
|
|
551
549
|
}
|
|
552
550
|
|
|
553
551
|
async function runHephaestusResearch(args, opts = {}) {
|
|
554
|
-
if (args
|
|
552
|
+
if (isHelpRequest(args)) {
|
|
555
553
|
return runHephaestusInteractive(args, { ...opts, human: false });
|
|
556
554
|
}
|
|
557
555
|
const raw = args.includes("--json");
|
|
@@ -573,8 +571,13 @@ function create(ctx, deps = {}) {
|
|
|
573
571
|
const preview = json ? researchPreviewModel(json) : null;
|
|
574
572
|
const code = result.code !== 0 || preview?.totalEvidenceFailure ? 1 : 0;
|
|
575
573
|
if (raw) {
|
|
576
|
-
if (result.
|
|
577
|
-
|
|
574
|
+
if (!result.overflow && !result.timedOut) {
|
|
575
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
576
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
577
|
+
} else {
|
|
578
|
+
if (result.overflow) process.stderr.write("Research output exceeded the 2 MB limit.\n");
|
|
579
|
+
if (result.timedOut) process.stderr.write("Research timed out after 60 seconds.\n");
|
|
580
|
+
}
|
|
578
581
|
return code;
|
|
579
582
|
}
|
|
580
583
|
const renderError = (message) => {
|
|
@@ -583,15 +586,16 @@ function create(ctx, deps = {}) {
|
|
|
583
586
|
ui.line(" " + (index === 0 ? ui.c.paw("✗ ") : " ") + ui.c.text(line));
|
|
584
587
|
});
|
|
585
588
|
};
|
|
586
|
-
if (
|
|
589
|
+
if (result.overflow || result.timedOut) {
|
|
590
|
+
if (result.overflow) renderError(ui.lang === "ko" ? "리서치 출력이 2MB 제한을 넘었습니다." : "Research output exceeded the 2 MB limit.");
|
|
591
|
+
if (result.timedOut) renderError(ui.lang === "ko" ? "리서치가 60초 제한을 넘었습니다." : "Research timed out after 60 seconds.");
|
|
592
|
+
} else if (json) {
|
|
587
593
|
renderResearchPreview(json, ui);
|
|
588
594
|
} else {
|
|
589
595
|
const message = (result.stderr || result.stdout || "").trim()
|
|
590
596
|
|| (ui.lang === "ko" ? "리서치 결과를 읽지 못했습니다." : "Could not read the research result.");
|
|
591
597
|
renderError(message);
|
|
592
598
|
}
|
|
593
|
-
if (result.overflow) renderError(ui.lang === "ko" ? "리서치 출력이 2MB 제한을 넘었습니다." : "Research output exceeded the 2 MB limit.");
|
|
594
|
-
if (result.timedOut) renderError(ui.lang === "ko" ? "리서치가 60초 제한을 넘었습니다." : "Research timed out after 60 seconds.");
|
|
595
599
|
return code;
|
|
596
600
|
}
|
|
597
601
|
|
|
@@ -638,7 +642,7 @@ function create(ctx, deps = {}) {
|
|
|
638
642
|
}
|
|
639
643
|
return Promise.resolve(1);
|
|
640
644
|
}
|
|
641
|
-
const helpOnly = args
|
|
645
|
+
const helpOnly = isHelpRequest(args);
|
|
642
646
|
const cwd = opts.cwd || (helpOnly ? process.cwd() : runCwd());
|
|
643
647
|
const moduleName = found.kind === "python" && isCareerGraph ? "career_graph" : "agentlas_cloud";
|
|
644
648
|
const moduleArgs = moduleName === "career_graph" ? args.slice(1) : args;
|
|
@@ -744,5 +748,6 @@ module.exports = {
|
|
|
744
748
|
USAGE,
|
|
745
749
|
usageFor,
|
|
746
750
|
isHelpToken,
|
|
751
|
+
isHelpRequest,
|
|
747
752
|
CONTEXT_MAP_MIN_CORE_VERSION,
|
|
748
753
|
};
|