agentlas 1.0.5 → 1.0.6

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.6 — 2026-07-27
4
+
5
+ - **The startup banner is back.** The v2 REPL called `renderBanner` with the
6
+ v1 contract — no `ui`, and using the return value as a string — so it threw
7
+ a TypeError on every launch, and an argument-less `catch` disguised the
8
+ crash as a one-line `agentlas <version>` fallback. Nobody could see it,
9
+ including the tests. A banner contract test now guards it.
10
+ - **Per-stage model assignment.** One workforce run has stages with very
11
+ different demands: the leader must author a large exact schema (measured:
12
+ Haiku failed it twice in a row), while a worker writes one packet of prose
13
+ (measured: Haiku workers produced real code patches in the SWE run). The
14
+ engine used a single model for all of them. Stages now resolve their model
15
+ independently:
16
+ `AGENTLAS_WORKFORCE_MODEL_LEADER` (leader/selection/planner/refinement),
17
+ `AGENTLAS_WORKFORCE_MODEL_WORKER`, `AGENTLAS_WORKFORCE_MODEL_SYNTHESIS`,
18
+ `AGENTLAS_WORKFORCE_MODEL_VERIFIER`. Unset stages inherit the leader
19
+ setting, and with nothing set the behaviour is byte-identical to before.
20
+
3
21
  ## 1.0.5 — 2026-07-27
4
22
 
5
23
  - A verifier that reports "no issues" as `[""]` instead of `[]` no longer
@@ -1754,8 +1754,45 @@ function create(deps = {}) {
1754
1754
  return new Ui({ lang: lang || (typeof D.prefsLang === "function" ? D.prefsLang() : "en") });
1755
1755
  }
1756
1756
 
1757
+ /*
1758
+ * 스테이지별 모델 배정 (토큰 이코노미).
1759
+ *
1760
+ * 한 실행 안의 단계는 요구 난이도가 다르다: 리더/플래너는 거대한 스키마를 정확히
1761
+ * 작성해야 하고(실측 2026-07-27: Haiku는 워크오더 JSON에서 2회 연속 실패), 워커는
1762
+ * 자기 패킷 하나를 글로 쓰면 된다(같은 날 SWE 벤치에서 Haiku 워커가 실제 패치 생성).
1763
+ * 그런데 엔진은 전 단계에 모델 하나를 썼다 — 제일 어려운 단계에 맞추면 워커까지
1764
+ * 비싸고, 워커에 맞추면 리더가 죽는다.
1765
+ *
1766
+ * 설정은 명시적이며 기본값은 무변경이다(미설정 시 기존 동작 그대로):
1767
+ * AGENTLAS_WORKFORCE_MODEL_LEADER 리더/선발/플래너/워크오더 정제
1768
+ * AGENTLAS_WORKFORCE_MODEL_WORKER 워커(중첩 팀 워커 포함)
1769
+ * AGENTLAS_WORKFORCE_MODEL_SYNTHESIS 합성
1770
+ * AGENTLAS_WORKFORCE_MODEL_VERIFIER 검증
1771
+ * 미지정 스테이지는 리더 설정 → 명시 modelPin → 런타임 기본 순으로 내려간다.
1772
+ */
1773
+ const STAGE_MODEL_ENV = Object.freeze({
1774
+ leader: "AGENTLAS_WORKFORCE_MODEL_LEADER",
1775
+ worker: "AGENTLAS_WORKFORCE_MODEL_WORKER",
1776
+ synthesis: "AGENTLAS_WORKFORCE_MODEL_SYNTHESIS",
1777
+ verifier: "AGENTLAS_WORKFORCE_MODEL_VERIFIER",
1778
+ });
1779
+
1780
+ function stageModelPin(stage, env = process.env) {
1781
+ const key = STAGE_MODEL_ENV[stage];
1782
+ const exact = key ? String(env[key] || "").trim() : "";
1783
+ if (exact) return exact;
1784
+ // 워커/합성/검증에 별도 지정이 없으면 리더 설정을 상속한다 — 리더만 올려도
1785
+ // 전 단계가 일관되게 동작하고, 아무것도 없으면 기존 경로와 완전히 동일하다.
1786
+ const leader = String(env[STAGE_MODEL_ENV.leader] || "").trim();
1787
+ return leader || null;
1788
+ }
1789
+
1757
1790
  async function runModel(runtime, system, prompt, context) {
1758
- const localContextSlice = typeof D.projectContextSlice === "function"
1791
+ // Core context slice는 리더 단계(작업 분석/선택/플래너/goal)의 프로젝트 접지다.
1792
+ // 핀 워커·합성·검증 호출의 계약 입력은 패킷/핸드오프뿐이므로(EXECUTION AUTHORITY
1793
+ // 고지와 동일 원칙) projectGrounding=false로 붙이지 않는다 — 2026-07-27 실측:
1794
+ // 무도구 콘텐츠 브리프에 프로젝트 파일 지도가 붙자 산출물이 디렉터리 나열로 샜다.
1795
+ const localContextSlice = context.projectGrounding !== false && typeof D.projectContextSlice === "function"
1759
1796
  ? D.projectContextSlice(context.cwd, context.task || "")
1760
1797
  : "";
1761
1798
  const effectiveSystem = localContextSlice
@@ -1780,12 +1817,12 @@ function create(deps = {}) {
1780
1817
  cwd: context.cwd,
1781
1818
  env: context.env,
1782
1819
  permission: context.permission,
1783
- model: context.modelPin || runtime.model || null,
1820
+ model: stageModelPin(context.stage) || context.modelPin || runtime.model || null,
1784
1821
  effort: context.effortPin == null ? null : context.effortPin,
1785
1822
  authorityMode,
1786
1823
  }));
1787
1824
  }
1788
- return normalizeModelText(await D.runApi(runtime.backend, context.modelPin || runtime.model, effectiveSystem, prompt));
1825
+ return normalizeModelText(await D.runApi(runtime.backend, stageModelPin(context.stage) || context.modelPin || runtime.model, effectiveSystem, prompt));
1789
1826
  }
1790
1827
 
1791
1828
  async function callHubTool(name, args) {
@@ -2002,6 +2039,11 @@ function create(deps = {}) {
2002
2039
  const runtime = ctx.runtime || D.resolveRuntime(db, ctx.runtimeOverride);
2003
2040
  const identity = runtimeIdentity(runtime, ctx.modelPin || null);
2004
2041
  const cwd = ctx.cwd || (typeof D.projectCwd === "function" ? D.projectCwd() : process.cwd());
2042
+ // 무도구(no-authority) 자식 CLI를 프로젝트 작업트리에서 실행하면 자식 CLI가
2043
+ // 프로젝트 설정·프로젝트 지시문·디렉터리 문맥을 스스로 삼킨다(2026-07-27 실측:
2044
+ // 프로젝트 설정 경고와 함께 워커 exit 1, 콘텐츠 브리프가 워크스페이스 코딩
2045
+ // 과제처럼 수행됨). 파일 권한이 없는 호출은 전용 중립 폴더에서 실행한다.
2046
+ const neutralCwd = typeof D.runCwd === "function" ? D.runCwd() : cwd;
2005
2047
  const permission = ctx.permission || "write";
2006
2048
  const env = typeof D.buildChildEnv === "function" ? await D.buildChildEnv(db, {
2007
2049
  projectPath: ctx.projectPath || null, permission, cwd, lang: ui.lang,
@@ -2134,7 +2176,7 @@ function create(deps = {}) {
2134
2176
  : system;
2135
2177
  let raw;
2136
2178
  try {
2137
- raw = await runModel(runtime, attemptSystem, attemptPrompt, modelContext);
2179
+ raw = await runModel(runtime, attemptSystem, attemptPrompt, { ...modelContext, stage: "leader" });
2138
2180
  } catch (error) {
2139
2181
  receipt.structuredModelAttempts.push({
2140
2182
  schemaVersion: "agentlas.workforce-structured-model-attempt.v1",
@@ -2976,6 +3018,10 @@ function create(deps = {}) {
2976
3018
  : "EXECUTION AUTHORITY: zero tools are granted to this invocation — no file system, no shell, no web, no MCP, no subagents. Never emit tool-call syntax or XML-like invocation markup, and never explore or wait for a workspace. Author the complete deliverable directly in this reply as plain text or markdown, using only the packet inputs provided.";
2977
3019
  const text = assertString(await runModel(runtime, [system, authorityDirective].join("\n\n"), prompt, {
2978
3020
  ...modelContext,
3021
+ // 무도구 호출은 패킷 입력만이 계약이다: 중립 cwd + 프로젝트 접지 차단.
3022
+ cwd: grantedToolIds.length ? modelContext.cwd : neutralCwd,
3023
+ projectGrounding: false,
3024
+ stage: "worker",
2979
3025
  authorityMode: grantedToolIds.length ? "policy-filtered" : "no-authority",
2980
3026
  grantedToolIds,
2981
3027
  permissionPolicy: pinned.permissionPolicy,
@@ -3225,6 +3271,8 @@ function create(deps = {}) {
3225
3271
  let verifierInvocationId = null;
3226
3272
  let priorAttempt = null;
3227
3273
  receipt.correctiveHistory = [];
3274
+ // 합성·검증도 무도구 핸드오프 파이프라인이다 — 워커와 동일한 격리 계약.
3275
+ const handoffModelContext = { ...modelContext, cwd: neutralCwd, projectGrounding: false };
3228
3276
  if (!ctx.silent) ui.info(ui.lang === "ko" ? "합성 → 검증 단계" : "synthesis → verification");
3229
3277
  for (let verifyAttempt = 1; verifyAttempt <= 2; verifyAttempt += 1) {
3230
3278
  const synthesisStarted = nowIso(D.now);
@@ -3235,7 +3283,7 @@ function create(deps = {}) {
3235
3283
  verifyAttempt > 1 ? "CORRECTIVE SYNTHESIS MODE: a pinned verifier rejected the prior synthesis. Repair the deliverable so every criterion is satisfied using only the existing worker handoffs. Never invent work that did not run." : "",
3236
3284
  ].filter(Boolean).join("\n\n"), stableJson(verifyAttempt > 1
3237
3285
  ? { workOrder, synthesis: delegationPlan.synthesis, handoffs: outputs, priorSynthesis: priorAttempt.text, verifierRejection: priorAttempt.verification }
3238
- : { workOrder, synthesis: delegationPlan.synthesis, handoffs: outputs }), modelContext), "synthesis output", 1_000_000);
3286
+ : { workOrder, synthesis: delegationPlan.synthesis, handoffs: outputs }), handoffModelContext), "synthesis output", 1_000_000);
3239
3287
  receipt.synthesis = {
3240
3288
  schemaVersion: "agentlas.workforce-synthesis-receipt.v1",
3241
3289
  receiptId: synthesisInvocationId,
@@ -3254,12 +3302,35 @@ function create(deps = {}) {
3254
3302
 
3255
3303
  const verifierStarted = nowIso(D.now);
3256
3304
  verifierInvocationId = `workforce-invocation:${crypto.randomUUID()}`;
3257
- const verifierRaw = await runModel(runtime, [
3258
- "You are the top-level host LLM verifier for this Agentlas workforce run.",
3259
- 'Evaluate the synthesis against every criterion and worker handoff. Return exactly one JSON object: {"schemaVersion":"agentlas.workforce-verification.v1","status":"passed|failed","checks":[{"checkId":"check:<id>","status":"passed|failed","evidence":"..."}],"issues":[]}.',
3305
+ // 검증자 JSON도 다른 구조화 단계처럼 1회 유계 스키마 교정을 받는다. 2026-07-27
3306
+ // 실측: 정직한 불합격 판정이 2000자 초과 issues 문자열 하나 때문에
3307
+ // invalid_contract 크래시가 되어 판정·교정 재합성이 통째로 증발했다.
3308
+ // 교정 후에도 스키마가 깨지면 조용한 절단 없이 정직하게 던진다.
3309
+ const verifierSchemaRequirements = [
3310
+ 'Return exactly one JSON object: {"schemaVersion":"agentlas.workforce-verification.v1","status":"passed|failed","checks":[{"checkId":"check:<id>","status":"passed|failed","evidence":"..."}],"issues":[]}.',
3260
3311
  "Use double-quoted valid JSON. Passing requires evidence for every criterion; do not rubber-stamp.",
3261
- ].join("\n\n"), stableJson({ workOrder, criteria: delegationPlan.verifier.criteria, handoffs: outputs, synthesis: finalText }), modelContext);
3262
- verification = validateVerifierResult(parseModelObject(verifierRaw, "workforce verifier"));
3312
+ "Every issues entry and every evidence value must be a plain string of at most 1900 characters; cite handoffs by slot id instead of quoting them at length.",
3313
+ ].join("\n");
3314
+ let verifierPrompt = stableJson({ workOrder, criteria: delegationPlan.verifier.criteria, handoffs: outputs, synthesis: finalText });
3315
+ let verifierParseAttempts = 0;
3316
+ verification = null;
3317
+ while (verification === null) {
3318
+ verifierParseAttempts += 1;
3319
+ const verifierRaw = await runModel(runtime, [
3320
+ "You are the top-level host LLM verifier for this Agentlas workforce run.",
3321
+ "Evaluate the synthesis against every criterion and worker handoff.",
3322
+ verifierSchemaRequirements,
3323
+ verifierParseAttempts > 1 ? "STRUCTURED OUTPUT REPAIR MODE: repair the schema and field bounds only; keep your verdict and findings." : "",
3324
+ ].filter(Boolean).join("\n\n"), verifierPrompt, handoffModelContext);
3325
+ try {
3326
+ verification = validateVerifierResult(parseModelObject(verifierRaw, "workforce verifier"));
3327
+ } catch (error) {
3328
+ if (!(error instanceof WorkforceContractError) || verifierParseAttempts >= MAX_STRUCTURED_MODEL_ATTEMPTS) throw error;
3329
+ const repair = buildSchemaRepairPrompt(error, verifierSchemaRequirements, verifierRaw);
3330
+ if (!repair.prompt) throw error;
3331
+ verifierPrompt = repair.prompt;
3332
+ }
3333
+ }
3263
3334
  receipt.verifier = {
3264
3335
  schemaVersion: "agentlas.workforce-verifier-receipt.v1",
3265
3336
  receiptId: verifierInvocationId,
@@ -3276,6 +3347,7 @@ function create(deps = {}) {
3276
3347
  result: verification,
3277
3348
  verdict: verification.status === "passed" ? "pass" : "fail",
3278
3349
  attempt: verifyAttempt,
3350
+ structuredAttemptCount: verifierParseAttempts,
3279
3351
  };
3280
3352
  if (verification.status === "passed") break;
3281
3353
  if (verifyAttempt === 1) {
@@ -38,13 +38,9 @@ async function startRepl(ctx, opts = {}) {
38
38
  const ui = ctx.uiInstance;
39
39
  const db = ctx.db();
40
40
 
41
- try {
42
- process.stdout.write(renderBanner({ version: readVersion(), lang: ctx.lang }) + "\n");
43
- } catch {
44
- ctx.out(`agentlas ${readVersion()}`);
45
- }
46
-
47
41
  // 첫 실행 온보딩 (언어 → 런타임 → 권한). setup 명령으로 언제든 재실행 가능.
42
+ // 스플래시는 이 뒤에 그린다 — 배너가 광고하는 런타임·권한은 이번 세션에 실제로
43
+ // 적용될 값이어야 한다. 첫 실행 사용자는 마법사가 먼저 마스코트를 띄운다.
48
44
  if (!ctx.prefs.onboarded && process.stdin.isTTY) {
49
45
  const wizardRl = readline.createInterface({ input: process.stdin, output: process.stdout });
50
46
  try {
@@ -88,9 +84,30 @@ async function startRepl(ctx, opts = {}) {
88
84
  return session;
89
85
  };
90
86
 
87
+ /*
88
+ * renderBanner는 ui.line으로 직접 그리고 아무것도 반환하지 않는다(ctx는 {ui,...} 형태).
89
+ * v2 REPL이 이걸 "문자열을 반환하는 v1 배너"로 호출해 매 실행 TypeError로 죽었고,
90
+ * 인자 없는 catch가 그 크래시를 `agentlas <version>` 한 줄로 위장해 왔다 —
91
+ * 스플래시 전체가 사라진 걸 사람도 게이트도 못 봤다. 실패 사유는 이제 남긴다.
92
+ */
93
+ try {
94
+ let runtimeLabel = "—";
95
+ try { runtimeLabel = resolveRt().kind; } catch { /* no_runtime: 첫 턴에서 정직 정지 */ }
96
+ let subjectLabel;
97
+ try {
98
+ const subject = opts.agent ? findAgent(db, opts.agent) : pickDefaultAgent(db);
99
+ if (subject) subjectLabel = subject.slug;
100
+ } catch { /* 표시용 — 못 정해도 배너는 그린다 */ }
101
+ renderBanner({ ui, version: readVersion(), runtimeLabel, subjectLabel, permission, cwd: process.cwd() });
102
+ } catch (e) {
103
+ ctx.out(`agentlas ${readVersion()}`);
104
+ ctx.err(ui.c.dim(`banner failed: ${(e && e.message) || e}`));
105
+ }
106
+
107
+ // 배너가 런타임·권한·작업 폴더를 이미 보여준다 — 여기서는 배너에 없는 것만.
91
108
  ctx.out(ui.c.dim(en
92
- ? `v2 engine · runtime auto · permission ${permission} · parallel ≤${maxParallel()} — /help, /sessions, /quit`
93
- : `v2 엔진 · 런타임 auto · 권한 ${permission} · 동시 ≤${maxParallel()} — /help, /sessions, /quit`));
109
+ ? `v2 engine · parallel ≤${maxParallel()} — /help, /sessions, /quit`
110
+ : `v2 엔진 · 동시 ≤${maxParallel()} — /help, /sessions, /quit`));
94
111
 
95
112
  if (opts.agent) {
96
113
  try {
@@ -421,6 +421,9 @@ function buildWorkforceDeps(ctx = {}) {
421
421
  prefsLang: () => ctx.lang || "en",
422
422
  userDataDir,
423
423
  projectCwd: capture.projectCwd,
424
+ // 무도구 핀 호출 전용 중립 작업 폴더 — 프로젝트 작업트리의 설정/지시문/디렉터리
425
+ // 문맥이 자식 CLI로 새는 것을 끊는다(agentlas-workforce.cjs neutralCwd 계약).
426
+ runCwd: capture.runCwd,
424
427
  cloudSessionCookie: hubClient.cloudSessionCookie,
425
428
  // v1과 동일: callHubTool은 주입하지 않는다. 워크포스 모듈 내부의 jsonrpc 경로가
426
429
  // 거절 코드 원문 전파·retryClass 계약을 소유하며, fetchHub는 버퍼드
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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"