agentlas 1.0.10 → 1.0.12
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 +48 -0
- package/engine/agentlas-input.cjs +121 -20
- package/engine/agentlas-workforce.cjs +859 -101
- package/engine/agentlas-workload-routing.cjs +29 -3
- package/engine/automation/daemon.cjs +9 -0
- package/engine/automation/store.cjs +22 -0
- package/engine/bootstrap-schema.sql +24 -0
- package/engine/commands/doctor.cjs +23 -1
- package/engine/commands/firm.cjs +59 -4
- package/engine/commands/help.cjs +8 -5
- package/engine/commands/list.cjs +25 -1
- package/engine/commands/run.cjs +65 -11
- package/engine/firms/orchestrate.cjs +10 -3
- package/engine/runtimes/overrides.cjs +91 -22
- package/engine/runtimes/resolve.cjs +38 -8
- package/engine/runtimes/roles.cjs +162 -0
- package/engine/sessions/orchestrator.cjs +2 -1
- package/engine/sessions/session.cjs +56 -20
- package/engine/storm/swarm.cjs +28 -12
- package/engine/ui/palette.cjs +40 -0
- package/engine/ui/repl.cjs +83 -17
- package/engine/workforce/capture.cjs +199 -14
- package/engine/workforce/deps.cjs +145 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.12 — 2026-07-28
|
|
4
|
+
|
|
5
|
+
- **Orchestrator/worker model roles now resolve from ordered candidate
|
|
6
|
+
pools.** The shared `model_role_members` table (Desktop schema v80) holds
|
|
7
|
+
n candidates per role in priority order; the terminal picks the first
|
|
8
|
+
member that is actually executable here, records every skipped member
|
|
9
|
+
with its reason, and never silently substitutes a lower-priority runtime
|
|
10
|
+
when all members are unavailable — the head member is used and the skip
|
|
11
|
+
list is preserved. An empty worker pool inherits the orchestrator pool,
|
|
12
|
+
matching the single-row inherit contract, and databases without pools
|
|
13
|
+
keep resolving through the v79 single-row and legacy `active_runtime`
|
|
14
|
+
ladders unchanged.
|
|
15
|
+
- **Workforce escalation is bounded and receipted end to end.** A worker
|
|
16
|
+
that violates the handoff output contract twice — or is named in two
|
|
17
|
+
consecutive verifier failures — gets exactly one orchestrator-role
|
|
18
|
+
retry, stamped with `escalated-after-failure`, the failure count, and
|
|
19
|
+
the attempt number; a failing escalation stops honestly instead of
|
|
20
|
+
looping or downgrading.
|
|
21
|
+
- **BYOK Anthropic calls mark the system prefix as a prompt-cache
|
|
22
|
+
breakpoint.** Real Anthropic endpoints receive the system prompt as one
|
|
23
|
+
`cache_control: ephemeral` block (~90% cheaper cached input on hits;
|
|
24
|
+
a silent no-op below the per-model minimum). Anthropic-compatible
|
|
25
|
+
endpoints (GLM/Kimi/DeepSeek) keep the plain string form they expect.
|
|
26
|
+
- Model-allocation receipts split "no allocation was provided"
|
|
27
|
+
(`allocation_not_provided`) from "the allocation was malformed"
|
|
28
|
+
(`invalid_ai_allocation`), and real invocations now retain the
|
|
29
|
+
provider-reported token usage per role instead of discarding it.
|
|
30
|
+
|
|
31
|
+
## 1.0.11 — 2026-07-27
|
|
32
|
+
|
|
33
|
+
- **The slash palette repaints in place instead of stacking copies of
|
|
34
|
+
itself.** Every keystroke left the previous frame on screen, so a few
|
|
35
|
+
arrow presses filled the terminal with duplicate palettes and pushed the
|
|
36
|
+
prompt out of view. Two causes, both measured on an emulated terminal:
|
|
37
|
+
the frame was drawn with the cursor saved and restored by absolute
|
|
38
|
+
position, and the frame was 18 lines tall with no regard for the window.
|
|
39
|
+
In a REPL the prompt sits at the bottom of the screen, so drawing below
|
|
40
|
+
it always scrolls — and after a scroll the saved absolute row points at
|
|
41
|
+
different content, so the next repaint erased the wrong region and left
|
|
42
|
+
the old frame behind. The palette now returns to the prompt with a
|
|
43
|
+
relative cursor move, which survives scrolling, and never draws a frame
|
|
44
|
+
taller than the window: the list shrinks around the highlighted entry,
|
|
45
|
+
and the selection detail folds away before the list does, so the
|
|
46
|
+
highlighted row and the controls hint survive at any height. Verified at
|
|
47
|
+
14, 18, 24 and 50 rows — one palette on screen, prompt intact.
|
|
48
|
+
- Quitting no longer announces a wait for commands that finish in
|
|
49
|
+
milliseconds; the notice now appears only after 400ms.
|
|
50
|
+
|
|
3
51
|
## 1.0.10 — 2026-07-27
|
|
4
52
|
|
|
5
53
|
Four defects in the REPL's slash surface, all found by sweeping for the shape
|
|
@@ -509,31 +509,56 @@ function renderSlashPalette(rows, selectedIndex, opts = {}) {
|
|
|
509
509
|
);
|
|
510
510
|
const descWidth = Math.max(0, lineWidth - commandWidth - 1);
|
|
511
511
|
const selected = rows[Math.max(0, Math.min(selectedIndex, rows.length - 1))] || rows[0];
|
|
512
|
-
const
|
|
512
|
+
const head = [
|
|
513
513
|
c.faint(truncateVisible(`${i18n.t(lang, "palette.title")} ${i18n.t(lang, "palette.search")}`, lineWidth)),
|
|
514
514
|
c.faint("─".repeat(lineWidth)),
|
|
515
515
|
];
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
out
|
|
516
|
+
/*
|
|
517
|
+
* 꼬리(구분선·선택 상세·조작 안내)를 먼저 만든다 — 목록만 예산에 맞춰 줄이고
|
|
518
|
+
* 머리와 꼬리는 어떤 높이에서도 지키기 위해서다.
|
|
519
|
+
*/
|
|
520
|
+
const tail = [c.faint("─".repeat(lineWidth))];
|
|
521
|
+
const tailStart = tail.length; // 이 뒤는 자리가 모자라면 접는다 (상세 → 예시 순으로 버림)
|
|
522
|
+
const out = head;
|
|
523
523
|
if (selected) {
|
|
524
524
|
const usage = truncateVisible(selected.usage || selected.command, lineWidth - 2);
|
|
525
525
|
const detail = truncateVisible(selected.detail || selected.description || "", lineWidth - 2);
|
|
526
526
|
const category = selected.category ? i18n.t(lang, "palette.category", selected.category) : "";
|
|
527
527
|
const categoryRoom = Math.max(0, lineWidth - visibleWidthLite(usage) - 3);
|
|
528
528
|
const categoryText = categoryRoom > 0 ? truncateVisible(category, categoryRoom) : "";
|
|
529
|
-
|
|
530
|
-
if (detail)
|
|
529
|
+
tail.push(" " + c.text(usage) + (categoryText ? c.dim(" " + categoryText) : ""));
|
|
530
|
+
if (detail) tail.push(" " + c.dim(detail));
|
|
531
531
|
if (selected.examples && selected.examples.length) {
|
|
532
|
-
|
|
532
|
+
tail.push(c.dim(truncateVisible(" " + i18n.t(lang, "palette.examples", selected.examples.slice(0, 2).join(" | ")), lineWidth)));
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
|
-
|
|
536
|
-
|
|
535
|
+
/*
|
|
536
|
+
* 화면 높이 예산. 터미널보다 긴 프레임을 쏟으면 그리는 도중 스크롤이 나고, 그 순간
|
|
537
|
+
* 오버레이가 제자리를 잃어 이전 프레임이 화면에 남는다(실측: 24행에서 18행짜리
|
|
538
|
+
* 프레임 → 블록이 겹겹이 쌓이고 프롬프트가 화면 밖으로 밀려남).
|
|
539
|
+
* 선택 항목이 잘려 나가지 않도록 강조 위치를 중심으로 창을 잡는다.
|
|
540
|
+
*/
|
|
541
|
+
const controls = c.dim(truncateVisible(" " + i18n.t(lang, "palette.controls"), lineWidth));
|
|
542
|
+
const budget = Math.max(1, Math.floor(Number(opts.maxRows) || (rows.length + head.length + tail.length + 1)));
|
|
543
|
+
/*
|
|
544
|
+
* 자리가 모자라면 선택 상세부터 접는다. 목록 한 줄과 조작 안내는 마지막까지 지킨다 —
|
|
545
|
+
* 아무것도 못 고르는 팔레트나 나가는 법을 모르는 팔레트는 없느니만 못하다.
|
|
546
|
+
* 최소 프레임은 5줄(제목·구분선·목록 1줄·구분선·조작 안내)이며, 그보다 좁은 예산도 5줄이다.
|
|
547
|
+
*/
|
|
548
|
+
while (tail.length > tailStart && budget - head.length - tail.length - 1 < 1) tail.pop();
|
|
549
|
+
const listBudget = Math.max(1, budget - head.length - tail.length - 1);
|
|
550
|
+
const start = Math.min(
|
|
551
|
+
Math.max(0, selectedIndex - listBudget + 1),
|
|
552
|
+
Math.max(0, rows.length - listBudget),
|
|
553
|
+
);
|
|
554
|
+
rows.slice(start, start + listBudget).forEach((row, offset) => {
|
|
555
|
+
const index = start + offset;
|
|
556
|
+
const command = padVisible(truncateVisible(row.command, commandWidth), commandWidth);
|
|
557
|
+
const desc = truncateVisible(row.description, descWidth);
|
|
558
|
+
const body = " " + c.blue(command) + c.text(desc);
|
|
559
|
+
out.push(index === selectedIndex ? c.inverse(padVisible(body, lineWidth)) : body);
|
|
560
|
+
});
|
|
561
|
+
return out.concat(tail, [controls]).join("\n");
|
|
537
562
|
}
|
|
538
563
|
|
|
539
564
|
function attachSlashPalette(rl, opts = {}) {
|
|
@@ -557,6 +582,7 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
557
582
|
selected: 0,
|
|
558
583
|
selectedCommand: null,
|
|
559
584
|
visible: false,
|
|
585
|
+
navigated: false,
|
|
560
586
|
dismissedForLine: null,
|
|
561
587
|
};
|
|
562
588
|
|
|
@@ -571,12 +597,43 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
571
597
|
return rows().length > 0 && state.dismissedForLine !== (rl.line || "");
|
|
572
598
|
}
|
|
573
599
|
function replaceLine(value) {
|
|
600
|
+
const next = String(value || "");
|
|
601
|
+
/*
|
|
602
|
+
* keypress 리스너 안에서 rl.write(Ctrl-U) → rl.write(text)를 재진입시키면
|
|
603
|
+
* Node readline의 원래 Tab/Enter 핸들러가 아직 같은 키를 처리하는 중이라
|
|
604
|
+
* 기존 `/s` 뒤에 선택값을 붙였다(`/s/team`, `/s/skills` 실측). line/cursor는
|
|
605
|
+
* readline의 공개 관측 상태이고 `_refreshLine`은 그 상태를 그리는 유일한
|
|
606
|
+
* 부수효과라, 한 번에 교체해 재진입을 피한다. 구형 Node만 기존 키 시퀀스로
|
|
607
|
+
* 폴백한다.
|
|
608
|
+
*/
|
|
609
|
+
if (typeof rl._refreshLine === "function") {
|
|
610
|
+
rl.line = next;
|
|
611
|
+
rl.cursor = next.length;
|
|
612
|
+
rl._refreshLine();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
574
615
|
rl.write(null, { ctrl: true, name: "u" });
|
|
575
|
-
rl.write(
|
|
616
|
+
rl.write(next);
|
|
617
|
+
}
|
|
618
|
+
/*
|
|
619
|
+
* 커서 복원은 상대 이동으로만 한다.
|
|
620
|
+
*
|
|
621
|
+
* 예전 구현은 DECSC/DECRC(`\x1b7`/`\x1b8`)로 절대 위치를 저장·복원했다. 그런데 REPL은
|
|
622
|
+
* 프롬프트가 화면 맨 아래에 있는 게 보통이라, 그 아래로 프레임을 그리면 반드시 스크롤이
|
|
623
|
+
* 난다. 스크롤 뒤 저장된 절대 행은 다른 내용을 가리키므로 복원이 어긋나고, 다음 렌더의
|
|
624
|
+
* "커서 아래 전부 지우기"가 이전 프레임을 못 지운다 — 화면에 팔레트가 겹겹이 쌓였다.
|
|
625
|
+
* (pyte 에뮬레이션 실측: 24행에서 ↓ 3회 → 잔상 블록 + 프롬프트 유실.)
|
|
626
|
+
* `\x1b[nA` 같은 상대 이동은 내용과 함께 밀리므로 스크롤이 나도 어긋나지 않는다.
|
|
627
|
+
*/
|
|
628
|
+
function promptColumn() {
|
|
629
|
+
const prompt = typeof rl.getPrompt === "function" ? rl.getPrompt() : "";
|
|
630
|
+
const typed = String(rl.line || "").slice(0, rl.cursor);
|
|
631
|
+
return visibleWidthLite(String(prompt)) + visibleWidthLite(typed) + 1;
|
|
576
632
|
}
|
|
577
633
|
function clear() {
|
|
578
634
|
if (!state.visible) return;
|
|
579
|
-
|
|
635
|
+
// 그린 뒤에는 프롬프트 아래에 자리가 있으므로 커서 아래로 이동은 스크롤을 만들지 않는다.
|
|
636
|
+
stream.write(`\x1b[1B\r\x1b[0J\x1b[1A\x1b[${promptColumn()}G`);
|
|
580
637
|
state.visible = false;
|
|
581
638
|
}
|
|
582
639
|
function render() {
|
|
@@ -596,10 +653,27 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
596
653
|
if (state.selected < 0 || state.selected >= list.length) state.selected = 0;
|
|
597
654
|
const body = renderSlashPalette(list, state.selected, {
|
|
598
655
|
columns: stream.columns || process.stdout.columns || 88,
|
|
656
|
+
// 프롬프트 줄과 여유 한 줄을 남긴다 — 프레임이 화면을 다 먹으면 제자리 갱신이 불가능하다.
|
|
657
|
+
maxRows: Math.max(5, (stream.rows || process.stdout.rows || 24) - 2),
|
|
599
658
|
colors,
|
|
600
659
|
lang: opts.lang || (opts.ui && opts.ui.lang) || "en",
|
|
601
660
|
});
|
|
602
|
-
|
|
661
|
+
if (!body) { clear(); return; }
|
|
662
|
+
const lines = body.split("\n");
|
|
663
|
+
/*
|
|
664
|
+
* 첫 줄바꿈은 프롬프트 아래로 내려가며, 자리가 없으면 여기서 화면이 한 번 밀린다.
|
|
665
|
+
* 그린 만큼 그대로 되올라오므로 이후 갱신은 제자리에서 일어난다.
|
|
666
|
+
*
|
|
667
|
+
* 복귀 후에는 커서 열만 맞춘다. readline 의 prompt(true) 로 프롬프트 줄을 다시
|
|
668
|
+
* 그리면 refreshLine 이 커서 아래를 지워 방금 그린 프레임까지 함께 날아간다
|
|
669
|
+
* (실측: 리사이즈 없이도 팔레트가 통째로 사라짐). 프롬프트 줄 자체는 지운 적이
|
|
670
|
+
* 없으므로 평상시에는 화면에 그대로 남아 있다.
|
|
671
|
+
*
|
|
672
|
+
* 남은 한계: 창 크기를 줄이면 터미널이 프롬프트 줄을 리플로우하며 지울 수 있고,
|
|
673
|
+
* 그때는 다음 입력 전까지 프롬프트가 보이지 않는다(40행→18행 축소에서 실측).
|
|
674
|
+
* 팔레트는 리사이즈를 구독하지 않는다.
|
|
675
|
+
*/
|
|
676
|
+
stream.write(`\r\n\x1b[0J${lines.join("\r\n")}\x1b[${lines.length}A\x1b[${promptColumn()}G`);
|
|
603
677
|
state.visible = true;
|
|
604
678
|
}
|
|
605
679
|
/*
|
|
@@ -620,6 +694,7 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
620
694
|
if (!list.length) return false;
|
|
621
695
|
state.selected = (state.selected + delta + list.length) % list.length;
|
|
622
696
|
state.selectedCommand = list[state.selected].command;
|
|
697
|
+
state.navigated = true;
|
|
623
698
|
const query = rl.line || "";
|
|
624
699
|
setImmediate(() => {
|
|
625
700
|
if ((rl.line || "") !== query) replaceLine(query);
|
|
@@ -648,12 +723,38 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
648
723
|
move(name === "down" ? 1 : -1);
|
|
649
724
|
return;
|
|
650
725
|
}
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
if (active() &&
|
|
654
|
-
select()
|
|
726
|
+
// Tab 은 완성이다 — 강조된 항목으로 줄을 채운다.
|
|
727
|
+
// Shift-Tab 은 팔레트 확정 키가 아니다(호출자 REPL 의 권한 순환 단축키다).
|
|
728
|
+
if (active() && name === "tab" && !key.shift) {
|
|
729
|
+
if (select()) {
|
|
730
|
+
/*
|
|
731
|
+
* prependListener는 readline 자체 Tab 처리를 중단시키지 못한다. 선택을 먼저
|
|
732
|
+
* 반영한 뒤, 기본 완성기가 같은 키로 줄을 다시 바꿔도 다음 tick에 exact
|
|
733
|
+
* 선택을 한 번 재적용한다.
|
|
734
|
+
*/
|
|
735
|
+
const selected = state.selectedCommand;
|
|
736
|
+
setImmediate(() => {
|
|
737
|
+
if (selected) replaceLine(selected);
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
/*
|
|
743
|
+
* Enter 는 사용자가 실제로 목록을 훑었을 때만 강조 항목을 확정한다.
|
|
744
|
+
*
|
|
745
|
+
* 예전에는 팔레트가 떠 있기만 하면 Enter 가 무조건 select() 를 불렀고,
|
|
746
|
+
* 훑은 적이 없으면 state.selected 는 0이라 "목록 첫 줄"이 대신 실행됐다.
|
|
747
|
+
* `/s`(활성 세션 전환)를 치고 Enter 하면 `/sessions` 가 돌아간다 — 친 것과
|
|
748
|
+
* 다른 명령이 실행되는 것이다(pty 실측). 게다가 피해 대상은 팔레트 정렬의
|
|
749
|
+
* 함수라, 명령 목록을 손볼 때마다 어떤 명령이 가로채이는지가 조용히 바뀐다.
|
|
750
|
+
*/
|
|
751
|
+
if (active() && name === "return") {
|
|
752
|
+
if (state.navigated) select();
|
|
753
|
+
else clear();
|
|
655
754
|
return;
|
|
656
755
|
}
|
|
756
|
+
// 타이핑이 이어지면 "훑었다"는 사실은 무효가 된다 — 질의가 달라졌기 때문.
|
|
757
|
+
state.navigated = false;
|
|
657
758
|
state.dismissedForLine = null;
|
|
658
759
|
setImmediate(render);
|
|
659
760
|
}
|