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.
Files changed (163) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +12 -4
  3. package/bin/agentlas.cjs +354 -16
  4. package/engine/acp/server.cjs +34 -3
  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 +30 -5
  30. package/engine/agentlas-workload-routing.cjs +83 -10
  31. package/engine/agentlas.cjs +21 -4
  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 +96 -30
  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 +609 -116
  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 +72 -14
  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 +150 -37
  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 +35 -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 +484 -63
  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 +46 -10
  118. package/engine/mcp/plan.cjs +10 -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 +382 -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
@@ -38,24 +38,62 @@ function resolvePermission(ctx) {
38
38
  function splitRuntimeOverride(args) {
39
39
  const rest = [];
40
40
  let runtimeOverride = null;
41
+ let seen = false;
42
+ let passthrough = false;
41
43
  for (let i = 0; i < args.length; i += 1) {
42
- if (args[i] === "--runtime" && args[i + 1]) {
43
- runtimeOverride = String(args[++i]);
44
+ const token = String(args[i]);
45
+ if (passthrough) { rest.push(token); continue; }
46
+ if (token === "--") { passthrough = true; rest.push(token); continue; }
47
+ if (token === "--runtime" || token.startsWith("--runtime=")) {
48
+ if (seen) throw new Error("duplicate option: --runtime");
49
+ seen = true;
50
+ const inline = token.startsWith("--runtime=");
51
+ const value = inline ? token.slice(10) : args[++i];
52
+ if (value === undefined || value === "" || (!inline && String(value).startsWith("--"))) {
53
+ throw new Error("--runtime requires a value");
54
+ }
55
+ runtimeOverride = String(value);
44
56
  continue;
45
57
  }
46
- rest.push(args[i]);
58
+ rest.push(token);
47
59
  }
48
60
  return { rest, runtimeOverride };
49
61
  }
50
62
 
63
+ function validateSwarmArgs(args) {
64
+ let seenParallel = false;
65
+ let passthrough = false;
66
+ let hasGoal = false;
67
+ for (let i = 0; i < args.length; i += 1) {
68
+ const token = String(args[i]);
69
+ if (passthrough) { hasGoal = true; continue; }
70
+ if (token === "--") { passthrough = true; continue; }
71
+ if (token === "--parallel" || token === "-n" || token.startsWith("--parallel=")) {
72
+ if (seenParallel) return { error: "duplicate option: --parallel", hasGoal };
73
+ seenParallel = true;
74
+ const inline = token.startsWith("--parallel=");
75
+ const raw = inline ? token.slice(11) : args[++i];
76
+ const value = Number(raw);
77
+ if (raw === undefined || raw === "" || (!inline && String(raw).startsWith("--")) || !Number.isInteger(value) || value < 1) {
78
+ return { error: "--parallel requires a positive integer (maximum 8)", hasGoal };
79
+ }
80
+ } else if (token.startsWith("-")) {
81
+ return { error: `unknown option: ${token} (use -- before a goal token that starts with '-')`, hasGoal };
82
+ } else hasGoal = true;
83
+ }
84
+ return { error: null, hasGoal };
85
+ }
86
+
51
87
  async function run(ctx, args) {
52
- const { rest, runtimeOverride } = splitRuntimeOverride(args);
88
+ let parsed;
89
+ try { parsed = splitRuntimeOverride(args); }
90
+ catch (error) { ctx.err(String((error && error.message) || error)); return 1; }
91
+ const { rest, runtimeOverride } = parsed;
92
+ const validation = validateSwarmArgs(rest);
93
+ if (validation.error) { ctx.err(validation.error); return 1; }
53
94
  // usage 경로(목표 텍스트 없음)는 SQLite를 열 이유가 없다 — cmdSwarm 의 플래그
54
95
  // (--parallel/-n <N>)만 있는 호출은 빈 목표다.
55
- const hasGoal = rest.some((token, i) =>
56
- !["--parallel", "-n"].includes(String(token))
57
- && !(i > 0 && ["--parallel", "-n"].includes(String(rest[i - 1]))));
58
- if (!hasGoal) {
96
+ if (!validation.hasGoal) {
59
97
  ctx.err("usage: agentlas swarm <goal> [--parallel N] [--runtime <kind>]");
60
98
  return 1;
61
99
  }
@@ -72,4 +110,4 @@ async function run(ctx, args) {
72
110
  return r && r.ok ? 0 : 1;
73
111
  }
74
112
 
75
- module.exports = { run, splitRuntimeOverride };
113
+ module.exports = { run, splitRuntimeOverride, validateSwarmArgs };
@@ -32,7 +32,12 @@ function renderTelegram(ctx) {
32
32
  return 0;
33
33
  }
34
34
 
35
- function run(ctx) {
35
+ function run(ctx, args = []) {
36
+ if (args.length) {
37
+ const error = new Error("usage: agentlas telegram");
38
+ error.code = "INVALID_ARGUMENT";
39
+ throw error;
40
+ }
36
41
  return renderTelegram(ctx);
37
42
  }
38
43
 
@@ -31,12 +31,18 @@ function chatDeletionCascadesFromAgent(db) {
31
31
 
32
32
  function run(ctx, args) {
33
33
  const ko = ctx.lang === "ko";
34
- // 동의 플래그를 슬러그보다 앞에 써도 되도록 첫 번째 비플래그 인자를 대상으로 본다.
35
- const token = args.find((a) => !String(a).startsWith("-"));
36
- if (!token) {
37
- ctx.err(ko ? "사용법: agentlas uninstall <agent>" : "Usage: agentlas uninstall <agent>");
38
- return 1;
34
+ const consentFlags = new Set(["--yes", "-y", "--force"]);
35
+ const unknownOptions = args.filter((arg) => String(arg).startsWith("-") && !consentFlags.has(arg));
36
+ const tokens = args.filter((arg) => !String(arg).startsWith("-"));
37
+ const confirmations = args.filter((arg) => consentFlags.has(arg));
38
+ if (unknownOptions.length || tokens.length !== 1 || confirmations.length > 1) {
39
+ const error = new Error(ko
40
+ ? "사용법: agentlas uninstall <agent> [--yes]"
41
+ : "Usage: agentlas uninstall <agent> [--yes]");
42
+ error.code = "INVALID_ARGUMENT";
43
+ throw error;
39
44
  }
45
+ const token = tokens[0];
40
46
  const db = ctx.db();
41
47
  const agent = findAgent(db, token);
42
48
  if (!agent) {
@@ -83,7 +89,7 @@ function run(ctx, args) {
83
89
  * 쓰고 그 파일의 스키마는 기기마다 다르다 — 아직 옛 사다리에 있는 기기에서는 파괴가
84
90
  * 여전히 사실이다. 판단은 열려 있는 DB 의 외래키에서 읽는다.
85
91
  */
86
- const consented = args.some((a) => a === "--yes" || a === "-y" || a === "--force");
92
+ const consented = confirmations.length === 1;
87
93
  const cascades = ctx.tableExists(db, "chats") && chatDeletionCascadesFromAgent(db);
88
94
  let chatCount = 0;
89
95
  let messageCount = 0;
@@ -49,6 +49,11 @@ async function checkNpmLatest({ fetch: fetchImpl, timeoutMs = 8_000 } = {}) {
49
49
  }
50
50
 
51
51
  async function run(ctx, args, deps = {}) {
52
+ if (args.some((arg) => arg !== "--json") || args.filter((arg) => arg === "--json").length > 1) {
53
+ const error = new Error("usage: agentlas update [--json]");
54
+ error.code = "INVALID_ARGUMENT";
55
+ throw error;
56
+ }
52
57
  const json = ctx.output?.format === "json" || args.includes("--json");
53
58
  const status = await checkNpmLatest(deps);
54
59
  if (json) {
@@ -13,7 +13,8 @@ async function run(ctx, args) {
13
13
  : args;
14
14
  return await runUpload(ctx, commandArgs);
15
15
  } catch (e) {
16
- ctx.err(String((e && e.message) || e));
16
+ if (typeof ctx.fail === "function") ctx.fail(e);
17
+ else ctx.err(String((e && e.message) || e));
17
18
  return 1;
18
19
  }
19
20
  }
@@ -2,7 +2,12 @@
2
2
  /* usage — 로컬 사용 현황 요약 (공유 DB 읽기 전용). 공급자 쿼터 대시보드는 데스크탑 소관. */
3
3
  const { listAgents } = require("../agents/registry.cjs");
4
4
 
5
- function run(ctx) {
5
+ function run(ctx, args = []) {
6
+ if (args.length) {
7
+ const error = new Error("usage: agentlas usage");
8
+ error.code = "INVALID_ARGUMENT";
9
+ throw error;
10
+ }
6
11
  const ko = ctx.lang === "ko";
7
12
  const db = ctx.db();
8
13
  const day = new Date(Date.now() - 86400000).toISOString();
@@ -29,7 +29,12 @@ function coreLine() {
29
29
  }
30
30
  }
31
31
 
32
- function run(ctx) {
32
+ function run(ctx, args = []) {
33
+ if (args.length) {
34
+ const error = new Error("usage: agentlas version");
35
+ error.code = "INVALID_ARGUMENT";
36
+ throw error;
37
+ }
33
38
  ctx.out(`agentlas ${readVersion()}`);
34
39
  ctx.out(coreLine());
35
40
  return 0;
@@ -5,8 +5,13 @@
5
5
  */
6
6
  const auth = require("../cloud/auth.cjs");
7
7
 
8
- async function run(ctx) {
8
+ async function run(ctx, args = []) {
9
9
  const ko = ctx.lang === "ko";
10
+ if (args.length) {
11
+ const error = new Error("usage: agentlas whoami");
12
+ error.code = "INVALID_ARGUMENT";
13
+ throw error;
14
+ }
10
15
  const cookie = auth.cloudSessionCookie();
11
16
  if (!cookie) {
12
17
  ctx.out(ko
@@ -27,9 +27,9 @@ function resolvePermission(ctx) {
27
27
  // persistent()가 fail-closed 로 강등한다.
28
28
  try {
29
29
  const policy = require("../agentlas-permissions.cjs");
30
- return policy.persistent(ctx && ctx.prefs && ctx.prefs.permission, "write");
30
+ return policy.persistent(ctx && ctx.prefs && ctx.prefs.permission, "read");
31
31
  } catch {
32
- return "write";
32
+ return "read";
33
33
  }
34
34
  }
35
35
 
@@ -37,16 +37,56 @@ function resolvePermission(ctx) {
37
37
  function splitRuntimeOverride(args) {
38
38
  const rest = [];
39
39
  let runtimeOverride = null;
40
+ let seen = false;
41
+ let passthrough = false;
40
42
  for (let i = 0; i < args.length; i += 1) {
41
- if (args[i] === "--runtime" && args[i + 1]) {
42
- runtimeOverride = String(args[++i]);
43
+ const token = String(args[i]);
44
+ if (passthrough) { rest.push(token); continue; }
45
+ if (token === "--") { passthrough = true; rest.push(token); continue; }
46
+ if (token === "--runtime" || token.startsWith("--runtime=")) {
47
+ if (seen) throw new Error("duplicate option: --runtime");
48
+ seen = true;
49
+ const inline = token.startsWith("--runtime=");
50
+ const value = inline ? token.slice(10) : args[++i];
51
+ if (value === undefined || value === "" || (!inline && String(value).startsWith("--"))) {
52
+ throw new Error("--runtime requires a value");
53
+ }
54
+ runtimeOverride = String(value);
43
55
  continue;
44
56
  }
45
- rest.push(args[i]);
57
+ rest.push(token);
46
58
  }
47
59
  return { rest, runtimeOverride };
48
60
  }
49
61
 
62
+ function validateWorkforceArgs(args) {
63
+ const seen = new Set();
64
+ let passthrough = false;
65
+ let hasTask = false;
66
+ for (let i = 0; i < args.length; i += 1) {
67
+ const token = String(args[i]);
68
+ if (passthrough) { hasTask = true; continue; }
69
+ if (token === "--") { passthrough = true; continue; }
70
+ if (token === "--benchmark" || token === "--json") {
71
+ const key = token.slice(2);
72
+ if (seen.has(key)) return { error: `duplicate option: ${token}`, hasTask };
73
+ seen.add(key);
74
+ } else if (token === "--parallel" || token === "-n" || token.startsWith("--parallel=")) {
75
+ if (seen.has("parallel")) return { error: "duplicate option: --parallel", hasTask };
76
+ seen.add("parallel");
77
+ const inline = token.startsWith("--parallel=");
78
+ const raw = inline ? token.slice(11) : args[++i];
79
+ const value = Number(raw);
80
+ if (raw === undefined || raw === "" || (!inline && String(raw).startsWith("--")) || !Number.isInteger(value) || value < 1 || value > 8) {
81
+ return { error: "--parallel requires a positive integer (maximum 8)", hasTask };
82
+ }
83
+ } else if (token.startsWith("-")) {
84
+ return { error: `unknown option: ${token} (use -- before a task token that starts with '-')`, hasTask };
85
+ } else hasTask = true;
86
+ }
87
+ return { error: null, hasTask };
88
+ }
89
+
50
90
  /**
51
91
  * command 별 디스패치. commands/index.cjs 는 명령 이름을 별도 인자로 넘기지 않으므로
52
92
  * run()이 기본 "workforce" 로 진입하고, 별칭 배선 시 이 dispatch 를 직접 쓴다.
@@ -56,13 +96,15 @@ async function dispatch(ctx, command, args) {
56
96
  case "workforce":
57
97
  case "network":
58
98
  case "taskforce": {
59
- const { rest, runtimeOverride } = splitRuntimeOverride(args);
99
+ let parsed;
100
+ try { parsed = splitRuntimeOverride(args); }
101
+ catch (error) { ctx.err(String((error && error.message) || error)); return 1; }
102
+ const { rest, runtimeOverride } = parsed;
103
+ const validation = validateWorkforceArgs(rest);
104
+ if (validation.error) { ctx.err(validation.error); return 1; }
60
105
  // usage 경로(작업 텍스트 없음)는 SQLite를 열 이유가 없다 — cmdWorkforce 의
61
106
  // parseArgs 와 동일한 판정: 플래그가 아닌 토큰이 하나도 없으면 빈 작업이다.
62
- const hasTask = rest.some((token, i) =>
63
- !["--benchmark", "--json", "--parallel", "-n"].includes(String(token))
64
- && !(i > 0 && ["--parallel", "-n"].includes(String(rest[i - 1]))));
65
- if (!hasTask) {
107
+ if (!validation.hasTask) {
66
108
  ctx.err("usage: agentlas workforce <task> [--parallel N] [--benchmark] [--json] [--runtime <kind>]");
67
109
  return 1;
68
110
  }
@@ -107,11 +149,13 @@ async function dispatch(ctx, command, args) {
107
149
  case "hep-hub": {
108
150
  const sourceScope = command.slice(4); // network|local|cloud|hub
109
151
  const ko = ctx.lang !== "en";
110
- const { rest, runtimeOverride } = splitRuntimeOverride(args);
111
- const hasTask = rest.some((token, i) =>
112
- !["--benchmark", "--json", "--parallel", "-n"].includes(String(token))
113
- && !(i > 0 && ["--parallel", "-n"].includes(String(rest[i - 1]))));
114
- if (!hasTask) {
152
+ let parsed;
153
+ try { parsed = splitRuntimeOverride(args); }
154
+ catch (error) { ctx.err(String((error && error.message) || error)); return 1; }
155
+ const { rest, runtimeOverride } = parsed;
156
+ const validation = validateWorkforceArgs(rest);
157
+ if (validation.error) { ctx.err(validation.error); return 1; }
158
+ if (!validation.hasTask) {
115
159
  ctx.err(`usage: agentlas ${command} "<task>" [--parallel N] [--json] [--runtime <kind>]`);
116
160
  return 1;
117
161
  }
@@ -162,4 +206,4 @@ function run(ctx, args) {
162
206
  return dispatch(ctx, "workforce", args);
163
207
  }
164
208
 
165
- module.exports = { run, dispatch };
209
+ module.exports = { run, dispatch, splitRuntimeOverride, validateWorkforceArgs };
@@ -55,7 +55,9 @@ function patternMatches(pattern, detail) {
55
55
  if (pattern === null || pattern === undefined || pattern === "") return true;
56
56
  if (!detail) return false;
57
57
  const text = String(pattern);
58
- if (text.endsWith("*")) return String(detail).startsWith(text.slice(0, -1).trimEnd());
58
+ // Keep the separator before `*`. Removing it turns `git push *` into the
59
+ // broader prefix `git push`, which also authorizes unrelated `git pushx`.
60
+ if (text.endsWith("*")) return String(detail).startsWith(text.slice(0, -1));
59
61
  return String(detail) === text;
60
62
  }
61
63
 
@@ -126,7 +128,10 @@ function recordCapabilityGrant(db, input) {
126
128
  if (!capabilityGrantsAvailable(db)) {
127
129
  return { ok: false, available: false, reason: UNAVAILABLE_REASON };
128
130
  }
129
- const decision = input && input.decision === "deny" ? "deny" : "allow";
131
+ const decision = input && input.decision;
132
+ if (decision !== "allow" && decision !== "deny") {
133
+ return { ok: false, available: true, reason: "capability_grants decision must be allow or deny" };
134
+ }
130
135
  try {
131
136
  db.prepare(
132
137
  `INSERT INTO ${TABLE} (capability, pattern, decision, scope, source, created_at)
@@ -17,9 +17,12 @@
17
17
  */
18
18
  const net = require("node:net");
19
19
  const crypto = require("node:crypto");
20
+ const fs = require("node:fs");
20
21
  const path = require("node:path");
21
22
  const { userDataDir } = require("./paths.cjs");
22
23
 
24
+ const DAEMON_MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
25
+
23
26
  /** 데몬 소켓 주소 — control-socket.ts 의 defaultControlSocketPath 와 같은 규칙. */
24
27
  function daemonSocketPath() {
25
28
  const dir = userDataDir();
@@ -41,11 +44,13 @@ function callDaemon(method, params, timeoutMs) {
41
44
  return new Promise((resolve, reject) => {
42
45
  const socket = net.connect(daemonSocketPath());
43
46
  const id = crypto.randomUUID();
44
- let buffer = "";
47
+ let buffer = Buffer.alloc(0);
48
+ let responseBytes = 0;
45
49
  let settled = false;
46
50
  const finish = (fn) => {
47
51
  if (settled) return;
48
52
  settled = true;
53
+ buffer = Buffer.alloc(0);
49
54
  clearTimeout(timer);
50
55
  socket.destroy();
51
56
  fn();
@@ -56,10 +61,19 @@ function callDaemon(method, params, timeoutMs) {
56
61
  );
57
62
  socket.on("connect", () => socket.write(`${JSON.stringify({ id, method, params })}\n`));
58
63
  socket.on("data", (chunk) => {
59
- buffer += chunk.toString("utf8");
64
+ if (settled) return;
65
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
66
+ responseBytes += bytes.length;
67
+ if (responseBytes > DAEMON_MAX_RESPONSE_BYTES) {
68
+ const error = new Error(`daemon response exceeded ${DAEMON_MAX_RESPONSE_BYTES} bytes`);
69
+ error.code = "DAEMON_RESPONSE_BODY_LIMIT";
70
+ finish(() => reject(error));
71
+ return;
72
+ }
73
+ buffer = buffer.length ? Buffer.concat([buffer, bytes]) : bytes;
60
74
  let nl;
61
- while ((nl = buffer.indexOf("\n")) >= 0) {
62
- const line = buffer.slice(0, nl).trim();
75
+ while (!settled && (nl = buffer.indexOf(0x0a)) >= 0) {
76
+ const line = buffer.subarray(0, nl).toString("utf8").trim();
63
77
  buffer = buffer.slice(nl + 1);
64
78
  if (!line) continue;
65
79
  let message;
@@ -109,8 +123,23 @@ function sharesOurStore(pong) {
109
123
  );
110
124
  return false;
111
125
  }
112
- const path = require("node:path");
113
- if (path.resolve(theirs) === path.resolve(ours)) return true;
126
+ let oursStat;
127
+ let theirsStat;
128
+ try {
129
+ oursStat = fs.statSync(path.resolve(ours));
130
+ theirsStat = fs.statSync(path.resolve(theirs));
131
+ } catch {
132
+ console.error(
133
+ `[daemon] could not verify the database identity (${theirs}) against this command (${ours}).\n`
134
+ + "[daemon] Running the graph locally instead, so the work cannot land in a different store.",
135
+ );
136
+ return false;
137
+ }
138
+ if (
139
+ oursStat.isFile() && theirsStat.isFile() &&
140
+ String(oursStat.dev) === String(theirsStat.dev) &&
141
+ String(oursStat.ino) === String(theirsStat.ino)
142
+ ) return true;
114
143
  console.error(
115
144
  `[daemon] the daemon opened a different database (${theirs}) than this command (${ours}).\n`
116
145
  + "[daemon] Running the graph locally instead, so the work cannot land in a different store.",
@@ -118,4 +147,4 @@ function sharesOurStore(pong) {
118
147
  return false;
119
148
  }
120
149
 
121
- module.exports = { daemonSocketPath, callDaemon, daemonAvailable, sharesOurStore };
150
+ module.exports = { daemonSocketPath, callDaemon, daemonAvailable, sharesOurStore, DAEMON_MAX_RESPONSE_BYTES };
@@ -14,6 +14,7 @@
14
14
  * 정직하게 거절한다(승급은 여전히 데스크탑의 일이다).
15
15
  */
16
16
  const fs = require("node:fs");
17
+ const { pathToFileURL } = require("node:url");
17
18
  const { configureSqliteConnection } = require("../agentlas-sqlite-policy.cjs");
18
19
  const { parseSemVer, compareSemVer } = require("../semver.cjs");
19
20
  const { dbPath } = require("./paths.cjs");
@@ -38,13 +39,16 @@ function loadNodeSqliteQuietly() {
38
39
  function openRaw(file) {
39
40
  try {
40
41
  const Database = require("better-sqlite3");
41
- const db = new Database(file);
42
+ const db = new Database(file, { fileMustExist: true });
42
43
  configureSqliteConnection(db);
43
44
  db.__driver = "better-sqlite3";
44
45
  return db;
45
46
  } catch { /* optional dep 미설치/ABI 불일치 → node:sqlite */ }
46
47
  const { DatabaseSync } = loadNodeSqliteQuietly();
47
- const db = new DatabaseSync(file);
48
+ // DatabaseSync does not expose better-sqlite3's `fileMustExist` option. A
49
+ // SQLite URI with mode=rw opens an existing file read/write but refuses to
50
+ // create it when an existsSync check races with deletion.
51
+ const db = new DatabaseSync(`${pathToFileURL(file).href}?mode=rw`);
48
52
  configureSqliteConnection(db);
49
53
  db.__driver = "node:sqlite";
50
54
  return db;
@@ -125,9 +129,13 @@ function columnExists(db, table, col) {
125
129
  }
126
130
 
127
131
  function runWriteTransaction(db, fn) {
132
+ if (!db || typeof fn !== "function") throw new TypeError("SQLite connection and transaction callback are required");
128
133
  db.exec("BEGIN IMMEDIATE");
129
134
  try {
130
135
  const result = fn();
136
+ if (result && typeof result.then === "function") {
137
+ throw new TypeError("SQLite write transactions must be synchronous");
138
+ }
131
139
  db.exec("COMMIT");
132
140
  return result;
133
141
  } catch (e) {