agentlas 1.0.8 → 1.0.10

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 CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.10 — 2026-07-27
4
+
5
+ Four defects in the REPL's slash surface, all found by sweeping for the shape
6
+ that produced 1.0.8: a v2 caller using a v1-era contract.
7
+
8
+ - **Quoted arguments survive.** Slash arguments were split on whitespace, so
9
+ the quotes the palette itself advertises (`/search "<what you need>"`) were
10
+ passed through as part of the query — `/search "hello world"` searched for
11
+ `"hello`. The quote-aware tokenizer the top-level CLI uses was exported but
12
+ had no call sites; the REPL now uses it.
13
+ - **Aliases work inside the REPL.** `agentlas hep-network …` was accepted
14
+ while `/hep-network …` answered "unknown", because alias resolution lived
15
+ only in the top-level dispatcher. Both surfaces now resolve the same names,
16
+ and a test pins every alias to a command that actually exists.
17
+ - **A command issued just before `/quit` is no longer discarded.** Slash
18
+ commands are async and were fire-and-forget, so closing the prompt resolved
19
+ immediately and the process exited mid-flight: `/search …` followed by
20
+ `/quit` printed nothing at all, while the same pair typed 25 seconds apart
21
+ worked. In-flight commands are now awaited — bounded at 30s, so quitting
22
+ can never hang — and the wait is announced rather than silent.
23
+ - **The first-run wizard's language applies to the whole session.** Choosing a
24
+ language wrote it to preferences and to `ui.lang`, but not to `ctx.lang`, so
25
+ the banner switched while `/help`, the palette, orchestrator notices and the
26
+ shortcut hints stayed in the OS-locale language until the next launch.
27
+
28
+ ## 1.0.9 — 2026-07-27
29
+
30
+ Three repairs of one mistake, found by a live run that a 4-agent task force
31
+ (two of them managers over 8 and 10 sub-workers) completed in full before the
32
+ result was thrown away.
33
+
34
+ - **Field bounds are now stated in the prompt that must obey them.** A nested
35
+ manager wrote a synthesis brief over 2,000 characters and the whole run —
36
+ 20 model calls, 14 minutes, every worker's finished output — was discarded
37
+ on a contract error. The engine enforced that ceiling and had never told the
38
+ manager it existed, so the one repair attempt it does allow failed the same
39
+ way. Every enforced bound now appears in the stage's schema requirements
40
+ with headroom (1,900 against a 2,000 ceiling).
41
+ - **An empty worker deliverable reaches its retry.** The corrective re-run had
42
+ always existed but a contract assertion fired first, so it was dead code.
43
+ - **A failed nested team leaves a real ledger.** Nested executions were
44
+ recorded only on success, so a mid-flight failure left `nestedExecutions`
45
+ empty while 20 model calls had already been billed, and the failure receipt
46
+ minted an `invocationId` with `crypto.randomUUID()` that had never named a
47
+ real call. Nested runs are now written as `running` when they start and
48
+ updated per stage; stages that never ran stay `null` rather than invented,
49
+ and failures carry the real invocation id.
50
+
51
+ These four defects (with the verifier overflow in 1.0.7) share one shape: a
52
+ fail-closed assertion placed ahead of the repair path it makes unreachable,
53
+ enforcing a limit the other side was never told. Four regression tests pin it.
54
+
3
55
  ## 1.0.8 — 2026-07-27
4
56
 
5
57
  - **Arrow keys navigate the slash palette instead of collapsing it.** Moving
@@ -1737,6 +1737,9 @@ function buildPrompts(task, identity) {
1737
1737
  "Choose capabilityBindingPlan.inventory only from POLICY_FILTERED_LOCAL_TOOL_MENU_DATA. Cover every requiredToolCapabilities id exactly once for each slot/release pair. One selected tool row may cover multiple capabilities. If a required capability has no exact ready tool, do not invent a binding; return the best schema-valid plan and allow deterministic validation to reject it.",
1738
1738
  "Each bound inventory row must explicitly contain slotId, agentReleaseId, permissionPolicyDigest, provider, toolId, capabilityIds, status=bound. An empty inventory is required when every slot has no required tool capability.",
1739
1739
  "synthesis must explicitly author slotId, agentReleaseId, and brief. verifier must explicitly author slotId, agentReleaseId, brief, and a non-empty criteria array. The host will not add, remove, normalize, or substitute a release or field.",
1740
+ // 호스트가 강제하는 상한을 미리 알려준다 — 알려주지 않은 상한은 첫 시도를 반드시
1741
+ // 깨고 교정 1회로도 회복되지 않는다(2026-07-27 라이브 실측, 중첩 매니저 동일 계열).
1742
+ "Field bounds are hard: each packet objective at most 3800 characters, each expectedOutput at most 1900, at most 64 inputs of at most 1900 characters each, each synthesis/verifier brief at most 1900, and at most 32 verifier criteria of at most 450 characters each. Write briefs and criteria tightly.",
1740
1743
  ].join("\n");
1741
1744
  return {
1742
1745
  searchSystem: [
@@ -3149,6 +3152,9 @@ function create(deps = {}) {
3149
3152
  "Return exactly one agentlas.workforce-team-delegation-plan.v1 object with plannedWorkerIds, packets, and synthesisBrief.",
3150
3153
  `plannedWorkerIds and packet ids must be exactly this declared order: ${stableJson(exactWorkerIds)}.`,
3151
3154
  "Every packet contains exactly id, objective, inputs, expectedOutput. No worker may be omitted, added, reordered, or substituted.",
3155
+ // 상한을 말해주지 않으면 첫 시도가 반드시 상한을 넘고, 교정 1회로도 못 줄인다
3156
+ // (2026-07-27 라이브 실측: synthesisBrief > 2000자로 4워커 런이 통째로 폐기).
3157
+ "Field bounds are hard: synthesisBrief at most 1900 characters, each packet objective at most 3800, each expectedOutput at most 1900, and at most 64 inputs of at most 1900 characters each. Write briefs tightly; do not restate the packet contents.",
3152
3158
  ].join("\n");
3153
3159
  let attemptPrompt = stableJson({ sharedTask: workOrder.taskBrief, roleSlot: slotById.get(packet.slotId), packet, declaredWorkerIds: exactWorkerIds });
3154
3160
  let priorDigest = null;
@@ -23,6 +23,7 @@ const { findAgent, listAgents } = require("../agents/registry.cjs");
23
23
  const { resolveRuntime, NoRuntimeError } = require("../runtimes/resolve.cjs");
24
24
  const permissions = require("../agentlas-permissions.cjs");
25
25
  const i18n = require("../agentlas-i18n.cjs");
26
+ const { tokenizeCommandLine } = require("../agentlas-input.cjs");
26
27
 
27
28
  const DEFAULT_AGENT_SLUG = "agentlas-orchestrator";
28
29
 
@@ -90,7 +91,8 @@ function pickDefaultAgent(db) {
90
91
  }
91
92
 
92
93
  async function startRepl(ctx, opts = {}) {
93
- const en = ctx.lang === "en";
94
+ // 마법사가 언어를 바꾸면 뒤의 문구도 따라가야 한다 — 아래 온보딩 블록에서 갱신한다.
95
+ let en = ctx.lang === "en";
94
96
  const ui = ctx.uiInstance;
95
97
  const db = ctx.db();
96
98
 
@@ -103,7 +105,17 @@ async function startRepl(ctx, opts = {}) {
103
105
  const { runWizard } = require("../commands/setup.cjs");
104
106
  const result = await runWizard(ctx, wizardRl);
105
107
  if (result) {
106
- if (result.lang) { ctx.prefs.language = result.lang; }
108
+ /*
109
+ * 고른 언어를 이번 세션에도 즉시 반영한다. 예전에는 prefs 에만 적어서,
110
+ * runOnboard 가 손댄 ui.lang 덕에 배너만 새 언어로 나오고 /help·팔레트·
111
+ * 오케스트레이터·단축키 안내는 재시작 전까지 OS 로케일 언어로 남았다.
112
+ */
113
+ if (result.lang) {
114
+ ctx.prefs.language = result.lang;
115
+ ctx.lang = result.lang;
116
+ ui.lang = result.lang;
117
+ en = ctx.lang === "en";
118
+ }
107
119
  if (result.permission) ctx.prefs.permission = result.permission;
108
120
  if (result.runtime) ctx.prefs.runtime = result.runtime;
109
121
  ctx.prefs.onboarded = !!result.onboarded;
@@ -186,6 +198,13 @@ async function startRepl(ctx, opts = {}) {
186
198
  getSessionKeys: () => orch.list().map((r) => r.key),
187
199
  getCwd: () => process.cwd(),
188
200
  });
201
+ // 진행 중인 비동기 슬래시 명령 — 종료가 이걸 잘라먹지 않도록 close 핸들러가 기다린다.
202
+ const pendingCommands = new Set();
203
+ const trackCommand = (promise) => {
204
+ pendingCommands.add(promise);
205
+ promise.then(() => pendingCommands.delete(promise), () => pendingCommands.delete(promise));
206
+ return promise;
207
+ };
189
208
  // Shift-Tab 이 온 턴에는 완성 후보를 비운다 — 아래 권한 순환 주석 참고.
190
209
  let swallowCompletion = false;
191
210
  const rl = readline.createInterface({
@@ -299,7 +318,7 @@ async function startRepl(ctx, opts = {}) {
299
318
 
300
319
  if (input.startsWith("/")) {
301
320
  try {
302
- const quit = handleSlash(ctx, input.slice(1), { orch, renderer, ensureMainSession, resolveRt, setPermission: (p) => { permission = p; }, getPermission: () => permission, setRuntime: (r) => { runtimeOverride = r; } });
321
+ const quit = handleSlash(ctx, input.slice(1), { orch, renderer, ensureMainSession, resolveRt, track: trackCommand, setPermission: (p) => { permission = p; }, getPermission: () => permission, setRuntime: (r) => { runtimeOverride = r; } });
303
322
  if (quit === "quit") { rl.close(); return; }
304
323
  } catch (e) {
305
324
  ui.error(String((e && e.message) || e));
@@ -341,9 +360,24 @@ async function startRepl(ctx, opts = {}) {
341
360
  process.stdin.removeListener("keypress", onShortcutKey);
342
361
  slashPalette.detach();
343
362
  renderer.detach();
344
- orch.shutdown();
363
+ /*
364
+ * 슬래시 명령은 비동기다. 예전에는 close 가 곧바로 resolve 해서 프로세스가 끝나 버렸고,
365
+ * `/search …` 직후 `/quit` 을 치면 그 명령이 출력 한 줄 없이 사라졌다(실측: 같은 입력을
366
+ * 25초 벌려 치면 결과가 나온다). 진행 중인 명령을 먼저 기다린다.
367
+ *
368
+ * 다만 무한정 기다리지는 않는다 — 종료를 누른 사용자를 응답 없는 프로세스에 가둘 수 없다.
369
+ */
370
+ const finish = () => { orch.shutdown(); ui.ensureNl(); resolve(0); };
371
+ if (!pendingCommands.size) { finish(); return; }
345
372
  ui.ensureNl();
346
- resolve(0);
373
+ ui.line(ui.c.dim(en
374
+ ? `finishing ${pendingCommands.size} command(s)…`
375
+ : `실행 중인 명령 ${pendingCommands.size}개를 마무리하는 중…`));
376
+ let settled = false;
377
+ const once = () => { if (settled) return; settled = true; finish(); };
378
+ const timer = setTimeout(once, 30_000);
379
+ if (timer.unref) timer.unref();
380
+ Promise.allSettled([...pendingCommands]).then(() => { clearTimeout(timer); once(); }, once);
347
381
  });
348
382
 
349
383
  prompt();
@@ -448,8 +482,19 @@ function handleSlash(ctx, cmdline, api) {
448
482
  const en = ctx.lang === "en";
449
483
  const ui = ctx.uiInstance;
450
484
  const { orch, renderer, ensureMainSession } = api;
451
- const [cmd, ...rest] = cmdline.split(/\s+/);
452
- const restStr = cmdline.slice(cmd.length).trim();
485
+ const commands = require("../commands/index.cjs");
486
+ /*
487
+ * 인자는 따옴표를 인식해 쪼갠다. 공백 분해는 따옴표를 인자 안에 그대로 남겨,
488
+ * 팔레트가 안내하는 그대로 `/search "무엇이 필요한지"` 를 치면 따옴표째 검색어가 됐다.
489
+ * 최상위 CLI 와 같은 토크나이저를 쓴다. restStr 은 원문 꼬리를 그대로 넘기는 자리
490
+ * (/spawn·/steer·/broadcast)라 계속 원문에서 자른다.
491
+ */
492
+ const rawCmd = cmdline.split(/\s+/)[0] || "";
493
+ const rest = tokenizeCommandLine(cmdline).slice(1);
494
+ const restStr = cmdline.slice(rawCmd.length).trim();
495
+ // 별칭도 최상위 CLI 와 동일하게 해석한다 — `agentlas hep-network` 는 되는데
496
+ // `/hep-network` 는 "알 수 없는 명령" 이던 비대칭을 없앤다.
497
+ const cmd = commands.resolveCommandName(rawCmd);
453
498
 
454
499
  switch (cmd) {
455
500
  case "quit": case "exit": return "quit";
@@ -564,11 +609,11 @@ function handleSlash(ctx, cmdline, api) {
564
609
  */
565
610
  // help/agents/list/chats/mcp/doctor 등은 위 케이스에서 이미 처리된다.
566
611
  const REPL_EXCLUDED = new Set(["chat", "open", "firm", "setup", "run"]);
567
- const commands = require("../commands/index.cjs");
568
612
  if (!REPL_EXCLUDED.has(cmd) && commands.COMMANDS[cmd]) {
569
613
  const result = commands.COMMANDS[cmd]().run(ctx, rest);
570
- if (result && typeof result.catch === "function") {
571
- result.catch((e) => ctx.err(String((e && e.message) || e)));
614
+ if (result && typeof result.then === "function") {
615
+ // 진행 중임을 REPL 알아야 종료가 이걸 잘라먹지 않는다 (close 핸들러 참고).
616
+ api.track(result.catch((e) => ctx.err(String((e && e.message) || e))));
572
617
  }
573
618
  return;
574
619
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"