agentlas 1.0.2 → 1.0.4
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 +54 -0
- package/engine/agentlas-workforce.cjs +117 -48
- package/engine/agentlas.cjs +3 -1
- package/engine/commands/index.cjs +26 -3
- package/engine/workforce/capture.cjs +7 -1
- package/engine/workforce/deps.cjs +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.4 — 2026-07-27
|
|
4
|
+
|
|
5
|
+
**The hub boundary stops guessing.** Owner decision after live runs: rules
|
|
6
|
+
that infer private data from shape were removed rather than tuned, because
|
|
7
|
+
each one refused work orders whose flagged phrase *was* the task — a slash
|
|
8
|
+
between Korean words read as a file path, "멱등키 설계" read as a credential
|
|
9
|
+
assignment, "고객 ID를 조회하는 API" read as a labeled identifier. No repair
|
|
10
|
+
was possible, so the request simply died.
|
|
11
|
+
|
|
12
|
+
- Gone: path inference from a slash, labeled-identifier inference, UUID/IP/
|
|
13
|
+
phone shape matching, keyword-adjacency credential matching.
|
|
14
|
+
- Kept: forms that can only be one thing — issuer-prefixed provider tokens,
|
|
15
|
+
PEM headers, JWTs, credentials embedded in a URL, email addresses.
|
|
16
|
+
- Those are now **redacted from the outgoing text instead of refusing the
|
|
17
|
+
run**, and the redaction is printed, so a pasted secret never leaves the
|
|
18
|
+
machine and the task still proceeds. `AGENTLAS_HUB_BOUNDARY=off` sends
|
|
19
|
+
text verbatim.
|
|
20
|
+
- Upload and packaging are unchanged: they already block by file identity
|
|
21
|
+
(`.env*`, `*.pem/key/p12`, `credentials*`, `id_rsa`), which is fact rather
|
|
22
|
+
than inference.
|
|
23
|
+
|
|
24
|
+
Shared fixtures (`privacy-guard-fixtures/`) and `scripts/sync-privacy-guard.sh`
|
|
25
|
+
now pin this contract across the terminal engine and the server.
|
|
26
|
+
|
|
27
|
+
## 1.0.3 — 2026-07-27
|
|
28
|
+
|
|
29
|
+
Live `workforce` runs surfaced four defects that no unit gate could reach.
|
|
30
|
+
|
|
31
|
+
- **A slash after Korean/Japanese/Chinese text no longer reads as a file
|
|
32
|
+
path.** The hub-boundary guard's absolute-path lookbehind excluded only
|
|
33
|
+
ASCII, so ordinary phrases ("진단/멱등키", "한국어/영어") were rejected as
|
|
34
|
+
private paths — and the phrase being the task itself meant no repair was
|
|
35
|
+
possible. Shared fixtures now pin this in both this engine and the server
|
|
36
|
+
(`scripts/sync-privacy-guard.sh`).
|
|
37
|
+
- **Bundle preparation is no longer killed by the connect timeout.** The
|
|
38
|
+
15s "connect" budget actually measured time-to-response-headers, and the
|
|
39
|
+
server computes a multi-slot roster before its first byte, so preparation
|
|
40
|
+
died as a transport error. Workforce calls now use their own budget.
|
|
41
|
+
- **Selection cycle rules match the Hub exactly.** Local validation only
|
|
42
|
+
checked handsOffTo/reportsTo, so a `reviews` cycle passed locally and came
|
|
43
|
+
back as a Hub rejection. All relations and self-edges now count.
|
|
44
|
+
- **A worker that exits non-zero reports its stdout tail too**, so a failure
|
|
45
|
+
whose stderr holds only unrelated warnings is no longer a dead end.
|
|
46
|
+
|
|
47
|
+
Also in this release:
|
|
48
|
+
|
|
49
|
+
- **Live narration**: a `workforce` run now prints the slot count and hub
|
|
50
|
+
menu size, the picked agent per slot by name, hub acceptance, each worker
|
|
51
|
+
as it starts, and the synthesis→verification transition.
|
|
52
|
+
- **One name per feature across platforms**: `hep-network`, `hep-cloud`,
|
|
53
|
+
`hep-build`, `hep-call`, `hep-search`, `hep-upload`, `hep-storm`,
|
|
54
|
+
`hep-browser`, `hep-connect` now work as terminal commands, matching the
|
|
55
|
+
skill names used from Claude Code and Codex. The typo guard suggests them.
|
|
56
|
+
|
|
3
57
|
## 1.0.2 — 2026-07-27
|
|
4
58
|
|
|
5
59
|
Workforce execution-contract fixes. Every worker in a `workforce` run now
|
|
@@ -100,7 +100,11 @@ const HUB_PATH_PATTERNS = [
|
|
|
100
100
|
/(?:^|[\s"'`()\[\]{}=:,;])~[/\\](?=\S)/,
|
|
101
101
|
/(?<![A-Za-z0-9])[A-Za-z]:[/\\](?=\S)/,
|
|
102
102
|
/(?:^|[\s"'`()\[\]{}=:,;])\\\\[^\\/\s]+[\\/][^\\/\s]+/,
|
|
103
|
-
|
|
103
|
+
// lookbehind가 ASCII만 제외하면 한글 단어 뒤 슬래시("진단/멱등키", "한국어/영어")가
|
|
104
|
+
// 절대경로로 오탐된다(2026-07-27 실측 — 한국어 워크오더 전멸 원인). 문자·숫자
|
|
105
|
+
// 전반(\p{L}\p{N})을 제외해 "A/B" 표기는 통과시키고, 공백·행머리 뒤 실제 경로는
|
|
106
|
+
// 그대로 잡는다.
|
|
107
|
+
/(?<![\p{L}\p{N}$])\/(?!\/|\s)(?:[^/\s"'`<>]+\/)*[^/\s"'`<>]+/u,
|
|
104
108
|
];
|
|
105
109
|
const HUB_SECRET_PATTERNS = [
|
|
106
110
|
["provider_token", /\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b/],
|
|
@@ -802,49 +806,79 @@ function decodedHubText(value) {
|
|
|
802
806
|
return text;
|
|
803
807
|
}
|
|
804
808
|
|
|
809
|
+
/*
|
|
810
|
+
* Hub-bound text hygiene — SELF-PROVING FORMS ONLY, and it masks instead of killing.
|
|
811
|
+
*
|
|
812
|
+
* Owner decision 2026-07-27, after live runs: every GUESSING rule (a slash after
|
|
813
|
+
* a word = a path, "key"/"secret" near a value = a credential, digit runs =
|
|
814
|
+
* a phone, hex groups = a UUID/IP) produced false positives that killed real
|
|
815
|
+
* requests with no repair available — the flagged phrase WAS the task
|
|
816
|
+
* ("진단/멱등키", "멱등키 설계"). Guessing is removed, not tuned.
|
|
817
|
+
*
|
|
818
|
+
* What remains is a narrow certainty: strings whose form can only be one thing
|
|
819
|
+
* (provider token with its issuer prefix, PEM header, JWT, credentials in a
|
|
820
|
+
* URL, an email address). Those are redacted from the outgoing text rather than
|
|
821
|
+
* refused, so a paste accident never leaves the machine and the run continues.
|
|
822
|
+
* Set AGENTLAS_HUB_BOUNDARY=off to send text verbatim.
|
|
823
|
+
*/
|
|
824
|
+
const HUB_REDACTION_PATTERNS = [
|
|
825
|
+
["secret_provider_token", /\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g],
|
|
826
|
+
["secret_private_key", /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/gi],
|
|
827
|
+
["secret_bearer_token", /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi],
|
|
828
|
+
["secret_jwt", /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g],
|
|
829
|
+
["secret_credential_url", /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@[^\s/]+/gi],
|
|
830
|
+
["email", /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi],
|
|
831
|
+
];
|
|
832
|
+
|
|
833
|
+
function hubBoundaryEnabled(env = process.env) {
|
|
834
|
+
return String(env.AGENTLAS_HUB_BOUNDARY || "").trim().toLowerCase() !== "off";
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Redact self-proving secrets/identifiers. Returns { text, redacted: [kinds] }. */
|
|
838
|
+
function redactHubText(value) {
|
|
839
|
+
if (!hubBoundaryEnabled()) return { text: value, redacted: [] };
|
|
840
|
+
let text = String(value);
|
|
841
|
+
const redacted = [];
|
|
842
|
+
for (const [kind, pattern] of HUB_REDACTION_PATTERNS) {
|
|
843
|
+
pattern.lastIndex = 0;
|
|
844
|
+
if (pattern.test(text)) {
|
|
845
|
+
pattern.lastIndex = 0;
|
|
846
|
+
text = text.replace(pattern, "<redacted>");
|
|
847
|
+
redacted.push(kind);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return { text, redacted };
|
|
851
|
+
}
|
|
852
|
+
|
|
805
853
|
function hubTextFindingKinds(value) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function assertHubWorkOrderBoundary(order) {
|
|
831
|
-
const issues = [];
|
|
832
|
-
const fields = [["taskBrief", order.taskBrief]];
|
|
854
|
+
// 인코딩 우회로 숨긴 자격증명도 같은 기준으로 본다(판정만, 치환은 원문 기준).
|
|
855
|
+
return redactHubText(decodedHubText(value)).redacted;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Redact self-proving secrets from the Hub-bound WorkOrder in place.
|
|
860
|
+
*
|
|
861
|
+
* This replaced a reject-and-ask-the-model-to-rewrite gate. Refusing was the
|
|
862
|
+
* wrong shape twice over: the model cannot remove a phrase that IS the task, and
|
|
863
|
+
* a real pasted credential should never depend on a model choosing to drop it.
|
|
864
|
+
* Redaction is deterministic, keeps the run alive, and the caller reports what
|
|
865
|
+
* was masked so nothing is hidden from the user.
|
|
866
|
+
*/
|
|
867
|
+
function redactHubWorkOrder(order) {
|
|
868
|
+
const redactions = [];
|
|
869
|
+
const applyField = (fieldPath, value, assign) => {
|
|
870
|
+
if (typeof value !== "string" || !value) return;
|
|
871
|
+
const { text, redacted } = redactHubText(value);
|
|
872
|
+
if (!redacted.length) return;
|
|
873
|
+
assign(text);
|
|
874
|
+
for (const kind of redacted) redactions.push({ path: fieldPath, kind });
|
|
875
|
+
};
|
|
876
|
+
applyField("taskBrief", order.taskBrief, (next) => { order.taskBrief = next; });
|
|
833
877
|
order.roleSlots.forEach((slot, index) => {
|
|
834
|
-
|
|
878
|
+
applyField(`roleSlots[${index}].title`, slot.title, (next) => { slot.title = next; });
|
|
879
|
+
applyField(`roleSlots[${index}].task`, slot.task, (next) => { slot.task = next; });
|
|
835
880
|
});
|
|
836
|
-
|
|
837
|
-
for (const kind of hubTextFindingKinds(value)) {
|
|
838
|
-
issues.push({ path: fieldPath, code: kind.startsWith("secret_") ? `hub_${kind}` : `hub_private_${kind}` });
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
if (issues.length) {
|
|
842
|
-
fail(
|
|
843
|
-
"work_order_hub_boundary_rejected",
|
|
844
|
-
"Hub-bound WorkOrder free text failed the deterministic privacy boundary",
|
|
845
|
-
{ issues },
|
|
846
|
-
);
|
|
847
|
-
}
|
|
881
|
+
return redactions;
|
|
848
882
|
}
|
|
849
883
|
|
|
850
884
|
function validateWorkOrder(value) {
|
|
@@ -919,7 +953,12 @@ function validateWorkOrder(value) {
|
|
|
919
953
|
if (!Number.isInteger(policy.minimumCandidatesPerSlot) || policy.minimumCandidatesPerSlot < 2 || policy.minimumCandidatesPerSlot > 30) fail("work_order_invalid", "selectionPolicy.minimumCandidatesPerSlot is invalid");
|
|
920
954
|
if (!Number.isInteger(policy.maximumCandidatesPerSlot) || policy.maximumCandidatesPerSlot < 2 || policy.maximumCandidatesPerSlot > 100) fail("work_order_invalid", "selectionPolicy.maximumCandidatesPerSlot is invalid");
|
|
921
955
|
if (policy.minimumCandidatesPerSlot > policy.maximumCandidatesPerSlot) fail("work_order_invalid", "candidate window minimum exceeds maximum");
|
|
922
|
-
|
|
956
|
+
// 자기증명 자격증명만 결정적으로 마스킹한다(거절 아님) — 마스킹 내역은
|
|
957
|
+
// 비파괴 필드로 달아 호출자가 화면에 정직히 표시한다.
|
|
958
|
+
const hubRedactions = redactHubWorkOrder(order);
|
|
959
|
+
if (hubRedactions.length) {
|
|
960
|
+
Object.defineProperty(order, "__hubRedactions", { value: hubRedactions, enumerable: false });
|
|
961
|
+
}
|
|
923
962
|
return order;
|
|
924
963
|
}
|
|
925
964
|
|
|
@@ -1133,12 +1172,16 @@ function validateSelection(value, candidateSet, workOrder, identity, options = {
|
|
|
1133
1172
|
if (!["reportsTo", "handsOffTo", "reviews", "coordinatesWith"].includes(edge.relation)) fail("selection_invalid", "selection edge relation is invalid");
|
|
1134
1173
|
assertIds(edge.artifactKinds, "selection edge artifactKinds");
|
|
1135
1174
|
}
|
|
1136
|
-
//
|
|
1137
|
-
//
|
|
1175
|
+
// 엣지 사이클은 Hub validate가 task_force_cycle로 거절한다. 서버 규칙과 동일하게:
|
|
1176
|
+
// 관계 종류 불문 모든 엣지 + 자기참조가 사이클이다(reviews 맞교환도 거절 —
|
|
1177
|
+
// 2026-07-27 실측: handsOffTo만 검사하던 로컬 검증이 reviews 사이클을 통과시켜
|
|
1178
|
+
// 서버 거절로 되돌아왔다). 로컬에서 먼저 걸어야 재시도 루프가 왕복 없이 교정한다.
|
|
1138
1179
|
{
|
|
1139
|
-
const directed = selection.edges.filter((edge) => edge.relation === "handsOffTo" || edge.relation === "reportsTo");
|
|
1140
1180
|
const adjacency = new Map();
|
|
1141
|
-
for (const edge of
|
|
1181
|
+
for (const edge of selection.edges) {
|
|
1182
|
+
if (edge.fromSlot === edge.toSlot) {
|
|
1183
|
+
fail("selection_invalid", `edges form a circular task force: ${edge.fromSlot} points at itself`);
|
|
1184
|
+
}
|
|
1142
1185
|
if (!adjacency.has(edge.fromSlot)) adjacency.set(edge.fromSlot, []);
|
|
1143
1186
|
adjacency.get(edge.fromSlot).push(edge.toSlot);
|
|
1144
1187
|
}
|
|
@@ -1147,7 +1190,7 @@ function validateSelection(value, candidateSet, workOrder, identity, options = {
|
|
|
1147
1190
|
const state = states.get(slot);
|
|
1148
1191
|
if (state === "done") return;
|
|
1149
1192
|
if (state === "visiting") {
|
|
1150
|
-
fail("selection_invalid", `
|
|
1193
|
+
fail("selection_invalid", `edges form a circular task force: ${[...trail, slot].join(" -> ")}`);
|
|
1151
1194
|
}
|
|
1152
1195
|
states.set(slot, "visiting");
|
|
1153
1196
|
for (const next of adjacency.get(slot) || []) walk(next, [...trail, slot]);
|
|
@@ -1628,7 +1671,7 @@ function buildPrompts(task, identity) {
|
|
|
1628
1671
|
`Exact direct Selection example: ${stableJson(selectionShape)}`,
|
|
1629
1672
|
"decisionAuthor must contain exactly kind, modelId, and runtimeId. Every required slot must have exactly its cardinality in assignments. Every assignment must contain exactly slotId, an exact candidate agentReleaseId, and a non-empty reasonCodes array.",
|
|
1630
1673
|
"edges, alternativesConsidered, and requestExpansionForSlots must be explicitly authored arrays. Every edge must contain exactly fromSlot, toSlot, relation (one of reportsTo|handsOffTo|reviews|coordinatesWith), and artifactKinds. The host will not add or normalize fields.",
|
|
1631
|
-
"
|
|
1674
|
+
"edges must form an acyclic directed graph regardless of relation — reviews and coordinatesWith count too, and a slot may never point at itself. Never author a circular chain (for example A reviews B while B reviews A); the Hub rejects circular task forces.",
|
|
1632
1675
|
].join("\n");
|
|
1633
1676
|
const plannerSchemaRequirements = [
|
|
1634
1677
|
`Return exactly one object: ${stableJson(plannerShape)}`,
|
|
@@ -2701,10 +2744,34 @@ function create(deps = {}) {
|
|
|
2701
2744
|
workOrderInvocationId,
|
|
2702
2745
|
};
|
|
2703
2746
|
|
|
2747
|
+
// 실황 내레이션: 사용자는 "누가 소집됐고 지금 뭘 하는지"를 보면서 신뢰를
|
|
2748
|
+
// 형성한다(2026-07-27 오너 요구). 결과에 영향 없는 표시 전용 — silent 존중.
|
|
2749
|
+
if (!ctx.silent) {
|
|
2750
|
+
const redactions = workOrder.__hubRedactions || [];
|
|
2751
|
+
if (redactions.length) {
|
|
2752
|
+
ui.info(ui.lang === "ko"
|
|
2753
|
+
? `허브 전송 전 ${redactions.length}건 마스킹: ${[...new Set(redactions.map((row) => row.kind))].join(", ")}`
|
|
2754
|
+
: `redacted before leaving this machine (${redactions.length}): ${[...new Set(redactions.map((row) => row.kind))].join(", ")}`);
|
|
2755
|
+
}
|
|
2756
|
+
const nameByRelease = new Map();
|
|
2757
|
+
for (const slotRow of candidateSet.slots) {
|
|
2758
|
+
for (const cand of slotRow.candidates) nameByRelease.set(cand.agentReleaseId, cand.name || cand.agentReleaseId);
|
|
2759
|
+
}
|
|
2760
|
+
const menuCount = candidateSet.slots.reduce((sum, slotRow) => sum + slotRow.candidates.length, 0);
|
|
2761
|
+
ui.info(ui.lang === "ko"
|
|
2762
|
+
? `워크오더 ${workOrder.roleSlots.length}슬롯 · 허브 후보 ${menuCount}명 메뉴 수신`
|
|
2763
|
+
: `work order: ${workOrder.roleSlots.length} slot(s) · hub menu of ${menuCount} candidates`);
|
|
2764
|
+
for (const row of selection.assignments) {
|
|
2765
|
+
ui.info(ui.lang === "ko"
|
|
2766
|
+
? ` 선발 ${row.slotId} ← ${nameByRelease.get(row.agentReleaseId) || row.agentReleaseId}`
|
|
2767
|
+
: ` picked ${row.slotId} ← ${nameByRelease.get(row.agentReleaseId) || row.agentReleaseId}`);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2704
2770
|
const validationRaw = await hubStage("workforce.validate_selection", { workOrder, candidateSet, selection });
|
|
2705
2771
|
validationReceipt = validateSelectionReceipt(validationRaw, selection, candidateSet, workOrder);
|
|
2706
2772
|
benchmarkState.selectionValidation = validationReceipt;
|
|
2707
2773
|
receipt.selectionReceiptId = validationReceipt.selectionReceiptId;
|
|
2774
|
+
if (!ctx.silent) ui.info(ui.lang === "ko" ? "허브 검증 수락 — 번들 준비 중" : "hub validation accepted — preparing bundles");
|
|
2708
2775
|
|
|
2709
2776
|
const preparedRaw = await hubStage("workforce.prepare_execution", { workOrder, candidateSet, selection, validationReceipt });
|
|
2710
2777
|
({ prepared, rosterByPair } = validatePreparedExecution(preparedRaw, workOrder, selection, candidateSet, validationReceipt));
|
|
@@ -2996,6 +3063,7 @@ function create(deps = {}) {
|
|
|
2996
3063
|
const packet = delegationPlan.packets[index];
|
|
2997
3064
|
const pair = `${packet.slotId}\0${packet.agentReleaseId}`;
|
|
2998
3065
|
const pinned = rosterByPair.get(pair);
|
|
3066
|
+
if (!ctx.silent) ui.info(ui.lang === "ko" ? ` 워커 실행 중: ${packet.slotId}` : ` worker running: ${packet.slotId}`);
|
|
2999
3067
|
const capabilityBindings = bindingsByPair.get(pair) || [];
|
|
3000
3068
|
const grantedToolIds = [...new Set(capabilityBindings.map((row) => row.toolId))].sort();
|
|
3001
3069
|
const startedAt = nowIso(D.now);
|
|
@@ -3152,6 +3220,7 @@ function create(deps = {}) {
|
|
|
3152
3220
|
let verifierInvocationId = null;
|
|
3153
3221
|
let priorAttempt = null;
|
|
3154
3222
|
receipt.correctiveHistory = [];
|
|
3223
|
+
if (!ctx.silent) ui.info(ui.lang === "ko" ? "합성 → 검증 단계" : "synthesis → verification");
|
|
3155
3224
|
for (let verifyAttempt = 1; verifyAttempt <= 2; verifyAttempt += 1) {
|
|
3156
3225
|
const synthesisStarted = nowIso(D.now);
|
|
3157
3226
|
synthesisInvocationId = `workforce-invocation:${crypto.randomUUID()}`;
|
package/engine/agentlas.cjs
CHANGED
|
@@ -127,7 +127,9 @@ function main() {
|
|
|
127
127
|
*/
|
|
128
128
|
if (!agent && normalized.length === 1 && /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(normalized[0])) {
|
|
129
129
|
const token = normalized[0];
|
|
130
|
-
const names = Object.keys(commands.COMMANDS)
|
|
130
|
+
const names = Object.keys(commands.COMMANDS)
|
|
131
|
+
.concat(Object.keys(commands.COMMAND_ALIASES || {}))
|
|
132
|
+
.concat(commands.NOT_YET_PORTED || []);
|
|
131
133
|
const near = nearestCommands(token, names);
|
|
132
134
|
const ko = ctx.lang === "ko";
|
|
133
135
|
ctx.err(ko
|
|
@@ -103,9 +103,32 @@ const DESKTOP_ONLY_SURFACES = {
|
|
|
103
103
|
// 무인자 호출이 프롬프트로 오라우팅되면 안 되는 명령 (smoke 가드 대상)
|
|
104
104
|
const GUARDED_NO_ARG = new Set(["search", "install", "upload"]);
|
|
105
105
|
|
|
106
|
+
// 플랫폼 간 이름 통일(오너 결정 2026-07-27): 클로드코드/코덱스에서 부르는 hep-*
|
|
107
|
+
// 스킬명과 터미널 명령이 서로 다르면 사용자가 어느 표면에 있는지에 따라 이름을
|
|
108
|
+
// 바꿔 써야 한다. 같은 기능은 어디서든 같은 이름으로 부른다.
|
|
109
|
+
const COMMAND_ALIASES = {
|
|
110
|
+
"hep-network": "workforce",
|
|
111
|
+
network: "workforce",
|
|
112
|
+
"hep-cloud": "cloud",
|
|
113
|
+
"hep-build": "build",
|
|
114
|
+
"hep-call": "call",
|
|
115
|
+
"hep-search": "search",
|
|
116
|
+
"hep-upload": "upload",
|
|
117
|
+
"hep-storm": "storm",
|
|
118
|
+
"hep-browser": "browser",
|
|
119
|
+
"hep-connect": "connect",
|
|
120
|
+
"hep-local": "workforce",
|
|
121
|
+
"hep-hub": "search",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function resolveCommandName(cmd) {
|
|
125
|
+
return COMMAND_ALIASES[cmd] || cmd;
|
|
126
|
+
}
|
|
127
|
+
|
|
106
128
|
function dispatch(ctx, argv) {
|
|
107
|
-
const [
|
|
108
|
-
if (!
|
|
129
|
+
const [rawCmd, ...rest] = argv;
|
|
130
|
+
if (!rawCmd) return null; // 엔진이 REPL로 진입
|
|
131
|
+
const cmd = resolveCommandName(rawCmd);
|
|
109
132
|
|
|
110
133
|
if (COMMANDS[cmd]) {
|
|
111
134
|
return COMMANDS[cmd]().run(ctx, rest);
|
|
@@ -133,4 +156,4 @@ function dispatch(ctx, argv) {
|
|
|
133
156
|
return undefined; // 알 수 없는 토큰 — 엔진이 에이전트 이름/프롬프트로 해석 시도
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
module.exports = { dispatch, COMMANDS, NOT_YET_PORTED, GUARDED_NO_ARG, DESKTOP_ONLY_SURFACES };
|
|
159
|
+
module.exports = { dispatch, COMMANDS, COMMAND_ALIASES, resolveCommandName, NOT_YET_PORTED, GUARDED_NO_ARG, DESKTOP_ONLY_SURFACES };
|
|
@@ -359,7 +359,13 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
|
|
|
359
359
|
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
360
360
|
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
361
361
|
if (code && code !== 0) {
|
|
362
|
-
|
|
362
|
+
// stderr가 경고문뿐이면 진짜 원인이 stdout(JSON 오류 응답 등)에 있을 수 있다 —
|
|
363
|
+
// 2026-07-27 실측: 워커 exit 1이 설정 경고 2줄만 남기고 원인 불명이 됐다.
|
|
364
|
+
// 두 스트림의 꼬리를 모두 싣는다.
|
|
365
|
+
const stdoutTail = stdout.trim().slice(-400);
|
|
366
|
+
finishReject(new Error(
|
|
367
|
+
`${kind} exited ${code}: ${stderr.slice(-500)}${stdoutTail ? `\n--- stdout tail ---\n${stdoutTail}` : ""}`,
|
|
368
|
+
));
|
|
363
369
|
return;
|
|
364
370
|
}
|
|
365
371
|
const raw = stdout.trim() || stderr.trim();
|
|
@@ -425,7 +425,13 @@ function buildWorkforceDeps(ctx = {}) {
|
|
|
425
425
|
// v1과 동일: callHubTool은 주입하지 않는다. 워크포스 모듈 내부의 jsonrpc 경로가
|
|
426
426
|
// 거절 코드 원문 전파·retryClass 계약을 소유하며, fetchHub는 버퍼드
|
|
427
427
|
// {ok,status,headers,text} 어댑터 형태를 만족한다(3중 타임아웃 + 16MB 상한).
|
|
428
|
-
|
|
428
|
+
// 워크포스 전용 타임아웃: prepare_execution은 서버가 로스터 번들을 조립하는 동안
|
|
429
|
+
// 첫 바이트 없이 계산한다(1슬롯 실측 7.2s, 다슬롯은 그 배수). 기본 connect 15s는
|
|
430
|
+
// 실제로는 "응답 헤더까지"를 재므로 다슬롯 준비를 처형한다(2026-07-27 전송오류
|
|
431
|
+
// 2연속의 진범). 준비 상한을 여유 있게 준다 — idle/total 계약은 유지.
|
|
432
|
+
fetchHub: (url, init) => hubClient.fetchHub(url, init, {
|
|
433
|
+
timeoutConfig: { connectMs: 120_000, idleMs: 60_000, totalMs: 300_000 },
|
|
434
|
+
}),
|
|
429
435
|
resolveRuntime: resolveWorkforceRuntime,
|
|
430
436
|
captureRuntime: capture.captureRuntime,
|
|
431
437
|
runApi: capture.runApi,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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"
|