agentlas 0.9.10 → 1.0.2
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 +106 -0
- package/README.md +319 -341
- package/bin/agentlas.cjs +32 -9
- package/engine/agentlas-banner.cjs +24 -3
- package/engine/agentlas-composer.cjs +239 -31
- package/engine/agentlas-config.cjs +103 -7
- package/engine/agentlas-core-harness.cjs +48 -4
- package/engine/agentlas-evolution.cjs +3 -4
- package/engine/agentlas-i18n.cjs +88 -32
- package/engine/agentlas-input.cjs +234 -18
- package/engine/agentlas-memory-governance.cjs +3 -5
- package/engine/agentlas-memory-import.cjs +3 -3
- package/engine/agentlas-native-host.cjs +200 -9
- package/engine/agentlas-onboard.cjs +112 -20
- package/engine/agentlas-permissions.cjs +14 -7
- package/engine/agentlas-sqlite-policy.cjs +34 -0
- package/engine/agentlas-ui.cjs +93 -42
- package/engine/agentlas-workforce.cjs +472 -58
- package/engine/agentlas-workload-routing.cjs +8 -3
- package/engine/agentlas.cjs +140 -12367
- package/engine/agents/files.cjs +61 -0
- package/engine/agents/import-local.cjs +237 -0
- package/engine/agents/registry.cjs +158 -0
- package/engine/agents/router.cjs +565 -0
- package/engine/agents/routes.cjs +43 -0
- package/engine/architecture.data.json +2 -2
- package/engine/automation/daemon.cjs +335 -0
- package/engine/automation/schedule.cjs +181 -0
- package/engine/automation/store.cjs +209 -0
- package/engine/cloud/auth.cjs +279 -0
- package/engine/cloud/hub-client.cjs +239 -0
- package/engine/cloud-assets/cargo.cjs +49 -0
- package/engine/cloud-assets/cas.cjs +235 -0
- package/engine/cloud-assets/commands.cjs +273 -0
- package/engine/cloud-assets/package.cjs +936 -0
- package/engine/cloud-assets/restore.cjs +172 -0
- package/engine/cloud-assets/state.cjs +268 -0
- package/engine/commands/automation.cjs +195 -0
- package/engine/commands/billing.cjs +96 -0
- package/engine/commands/browser.cjs +20 -0
- package/engine/commands/build.cjs +33 -0
- package/engine/commands/call.cjs +24 -0
- package/engine/commands/career-graph.cjs +51 -0
- package/engine/commands/cd.cjs +22 -0
- package/engine/commands/chat.cjs +12 -0
- package/engine/commands/chats.cjs +28 -0
- package/engine/commands/cloud.cjs +17 -0
- package/engine/commands/connect.cjs +20 -0
- package/engine/commands/context.cjs +66 -0
- package/engine/commands/creds.cjs +203 -0
- package/engine/commands/doctor.cjs +68 -0
- package/engine/commands/env.cjs +33 -0
- package/engine/commands/evolve.cjs +23 -0
- package/engine/commands/experience.cjs +34 -0
- package/engine/commands/film.cjs +8 -0
- package/engine/commands/firm.cjs +115 -0
- package/engine/commands/help.cjs +65 -0
- package/engine/commands/hep.cjs +23 -0
- package/engine/commands/import.cjs +35 -0
- package/engine/commands/index.cjs +136 -0
- package/engine/commands/install.cjs +27 -0
- package/engine/commands/journal.cjs +31 -0
- package/engine/commands/legacy-network.cjs +29 -0
- package/engine/commands/list.cjs +49 -0
- package/engine/commands/login.cjs +68 -0
- package/engine/commands/logout.cjs +28 -0
- package/engine/commands/mcp.cjs +91 -0
- package/engine/commands/memory.cjs +21 -0
- package/engine/commands/multimodal.cjs +91 -0
- package/engine/commands/native.cjs +34 -0
- package/engine/commands/netadmin.cjs +31 -0
- package/engine/commands/oberon.cjs +70 -0
- package/engine/commands/ontology.cjs +24 -0
- package/engine/commands/open.cjs +48 -0
- package/engine/commands/plugin.cjs +101 -0
- package/engine/commands/project.cjs +47 -0
- package/engine/commands/research.cjs +36 -0
- package/engine/commands/route.cjs +37 -0
- package/engine/commands/run.cjs +149 -0
- package/engine/commands/search.cjs +55 -0
- package/engine/commands/setup.cjs +45 -0
- package/engine/commands/storm.cjs +75 -0
- package/engine/commands/swarm.cjs +75 -0
- package/engine/commands/telegram.cjs +32 -0
- package/engine/commands/uninstall.cjs +68 -0
- package/engine/commands/update.cjs +54 -0
- package/engine/commands/upload.cjs +18 -0
- package/engine/commands/usage.cjs +35 -0
- package/engine/commands/variant.cjs +25 -0
- package/engine/commands/version.cjs +9 -0
- package/engine/commands/whoami.cjs +38 -0
- package/engine/commands/workforce.cjs +103 -0
- package/engine/core/db.cjs +160 -0
- package/engine/core/paths.cjs +35 -0
- package/engine/experience/build.cjs +181 -0
- package/engine/experience/intents.cjs +492 -0
- package/engine/experience/runtime.cjs +242 -0
- package/engine/experience/variant.cjs +196 -0
- package/engine/firms/orchestrate.cjs +333 -0
- package/engine/hephaestus/runtime.cjs +697 -0
- package/engine/hub/install.cjs +872 -0
- package/engine/hub/plugins.cjs +213 -0
- package/engine/mcp/consent.cjs +289 -0
- package/engine/mcp/contract.cjs +202 -0
- package/engine/mcp/index.cjs +43 -0
- package/engine/mcp/inventory.cjs +322 -0
- package/engine/mcp/plan.cjs +286 -0
- package/engine/mcp/probe.cjs +151 -0
- package/engine/memory-cli/curate.cjs +163 -0
- package/engine/oberon/common.cjs +69 -0
- package/engine/oberon/manifest.cjs +164 -0
- package/engine/oberon/outputs.cjs +70 -0
- package/engine/oberon/render.cjs +164 -0
- package/engine/project/career-graph.cjs +249 -0
- package/engine/project/credentials.cjs +262 -0
- package/engine/project/env-file.cjs +46 -0
- package/engine/project/index.cjs +27 -0
- package/engine/project/memory-context.cjs +453 -0
- package/engine/project/ontology.cjs +467 -0
- package/engine/project/paths.cjs +39 -0
- package/engine/project/seed.cjs +200 -0
- package/engine/project/state.cjs +403 -0
- package/engine/project/super-ontology-seed.json +3288 -0
- package/engine/runtimes/detect.cjs +54 -0
- package/engine/runtimes/overrides.cjs +139 -0
- package/engine/runtimes/resolve.cjs +64 -0
- package/engine/sessions/apply-fences.cjs +188 -0
- package/engine/sessions/fences.cjs +362 -0
- package/engine/sessions/orchestrator.cjs +170 -0
- package/engine/sessions/prompt.cjs +212 -0
- package/engine/sessions/session.cjs +245 -0
- package/engine/sessions/sink.cjs +54 -0
- package/engine/sessions/store.cjs +79 -0
- package/engine/storm/deps.cjs +88 -0
- package/engine/storm/storm.cjs +218 -0
- package/engine/storm/swarm.cjs +422 -0
- package/engine/ui/palette.cjs +105 -0
- package/engine/ui/renderer.cjs +85 -0
- package/engine/ui/repl.cjs +444 -0
- package/engine/workforce/capture.cjs +701 -0
- package/engine/workforce/deps.cjs +472 -0
- package/package.json +2 -6
- package/engine/agentlas-experience-mcp.cjs +0 -1709
- package/engine/agentlas-parity.cjs +0 -1499
- package/engine/agentlas-repl.cjs +0 -1780
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* workforce/deps — engine/agentlas-workforce.cjs (Agent Workforce Ontology 런타임)의
|
|
4
|
+
* 의존성 주머니(D bag) 조립.
|
|
5
|
+
*
|
|
6
|
+
* v1에서는 모놀리스(engine/agentlas.cjs)의 lazy 팩토리 workforce()가 이 D를 만들었다
|
|
7
|
+
* (legacy-v1-engine-snapshot, 10727–10858행). v2에서는 각 멤버를 모듈 경계에 맞게
|
|
8
|
+
* 다시 배선한다:
|
|
9
|
+
*
|
|
10
|
+
* cloudSessionCookie/fetchHub → engine/cloud/hub-client.cjs
|
|
11
|
+
* userDataDir → engine/core/paths.cjs
|
|
12
|
+
* captureRuntime/runApi/buildChildEnv/projectCwd → engine/workforce/capture.cjs
|
|
13
|
+
* MCP tools/list → engine/mcp (동의된 시스템 서버만)
|
|
14
|
+
* goal 연속성/컨텍스트 슬라이스 → engine/agentlas-core-harness.cjs (Agentlas Core CLI)
|
|
15
|
+
*
|
|
16
|
+
* 불변식(오너 결정, 약화 금지):
|
|
17
|
+
* - fail-closed 툴 루프: 여기서는 어떤 어휘/키워드 폴백도 만들지 않는다. 런타임이
|
|
18
|
+
* 없으면 code="no_runtime"으로 정직하게 던진다.
|
|
19
|
+
* - 정확-릴리스 선택권은 호스트 LLM에 있다. D는 검색/검증/준비 전송로만 제공한다.
|
|
20
|
+
* - Hub의 거절 코드는 원문 그대로 전파된다(워크포스 모듈 내부 callHubTool 폴백이
|
|
21
|
+
* 담당). v1과 동일하게 callHubTool을 주입하지 않고 fetchHub+cookie만 준다 —
|
|
22
|
+
* v2 hub-client.callHubTool은 다른 envelope 계약이라 여기 끼우면 안 된다.
|
|
23
|
+
* - 영수증(JSONL)에 시크릿 금지: 여기 append 계열은 워크포스 모듈이 이미 다이제스트/
|
|
24
|
+
* 요약으로 재구성한 영수증 객체만 받아 기록한다. 원문 프롬프트/env/키를 절대
|
|
25
|
+
* 추가로 끼워 넣지 않는다. 파일 권한은 0700 디렉터리 / 0600 파일.
|
|
26
|
+
*/
|
|
27
|
+
const crypto = require("node:crypto");
|
|
28
|
+
const fs = require("node:fs");
|
|
29
|
+
const os = require("node:os");
|
|
30
|
+
const path = require("node:path");
|
|
31
|
+
|
|
32
|
+
const { userDataDir } = require("../core/paths.cjs");
|
|
33
|
+
const hubClient = require("../cloud/hub-client.cjs");
|
|
34
|
+
const detect = require("../runtimes/detect.cjs");
|
|
35
|
+
const capture = require("./capture.cjs");
|
|
36
|
+
|
|
37
|
+
// ── 런타임 해석 (v1 resolveRuntime의 워크포스 어댑터) ─────────────────────
|
|
38
|
+
// 워크포스 모듈이 기대하는 형태: {mode:"cli", kind, model?} | {mode:"api", backend, model}.
|
|
39
|
+
// v2 runtimes/resolve.cjs는 {kind, bin, source}만 주고 BYOK/Ollama를 모르므로,
|
|
40
|
+
// v1의 사다리(명시 override > prefs 저장값 > 공유 DB active_runtime(byok/ollama 포함)
|
|
41
|
+
// > PATH 탐지)를 여기서 복원한다. 아무것도 없으면 no_runtime 정직 정지 — 폴백 금지.
|
|
42
|
+
function resolveWorkforceRuntime(db, override) {
|
|
43
|
+
if (!override) {
|
|
44
|
+
try {
|
|
45
|
+
const saved = require("../agentlas-config.cjs").loadPrefs(userDataDir()).runtime;
|
|
46
|
+
if (saved && saved !== "auto" && capture.RUNTIME_BIN[saved] && capture.which(capture.RUNTIME_BIN[saved])) override = saved;
|
|
47
|
+
} catch { /* prefs 없음 — 사다리 계속 */ }
|
|
48
|
+
}
|
|
49
|
+
const ar = db ? detect.activeRuntimeRow(db) : null;
|
|
50
|
+
const activeCli = ar && capture.RUNTIME_BIN[ar.kind]
|
|
51
|
+
? {
|
|
52
|
+
mode: "cli",
|
|
53
|
+
kind: ar.kind,
|
|
54
|
+
model: ar.model || null,
|
|
55
|
+
capabilities: ["code", "tools", ...(ar.long_context ? ["long-context"] : [])],
|
|
56
|
+
efforts: [],
|
|
57
|
+
}
|
|
58
|
+
: null;
|
|
59
|
+
if (override) {
|
|
60
|
+
if (!capture.RUNTIME_BIN[override]) {
|
|
61
|
+
const error = new Error(`unknown workforce runtime: ${override} (capture drivers: ${Object.keys(capture.RUNTIME_BIN).join(", ")})`);
|
|
62
|
+
error.code = "no_runtime";
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return activeCli && activeCli.kind === override ? activeCli : { mode: "cli", kind: override };
|
|
66
|
+
}
|
|
67
|
+
if (activeCli) return activeCli;
|
|
68
|
+
if (ar && ar.kind === "byok" && ar.backend) return { mode: "api", backend: ar.backend, model: ar.model };
|
|
69
|
+
if (ar && ar.kind === "ollama") return { mode: "api", backend: "ollama", model: ar.model };
|
|
70
|
+
for (const kind of Object.keys(capture.RUNTIME_BIN)) {
|
|
71
|
+
if (capture.which(capture.RUNTIME_BIN[kind])) return { mode: "cli", kind };
|
|
72
|
+
}
|
|
73
|
+
const error = new Error("no_runtime: no agent CLI or connected API runtime found (claude / codex / gemini / BYOK / Ollama).");
|
|
74
|
+
error.code = "no_runtime";
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── 영수증 (v1 모놀리스의 JSONL 계약 포팅: userData 하위, 0700/0600) ──────
|
|
79
|
+
function receiptFile() {
|
|
80
|
+
return path.join(userDataDir(), "workforce-execution-receipts.jsonl");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function appendJsonl(file, record) {
|
|
84
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
85
|
+
fs.appendFileSync(file, `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function appendReceipt(receipt) {
|
|
89
|
+
appendJsonl(receiptFile(), receipt);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function appendAuditReceipt(audit) {
|
|
93
|
+
appendJsonl(path.join(userDataDir(), "workforce-orchestration-audits.jsonl"), audit);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function persistBenchmarkArtifact(artifact, executionIdHint) {
|
|
97
|
+
const directory = path.join(userDataDir(), "workforce-benchmarks");
|
|
98
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
99
|
+
// 실패 런에는 executionReceipt가 없다. 이미 만들어진 run id를 써서 반복 실패가
|
|
100
|
+
// workforce-run.json 하나를 조용히 덮지 않고 개별 포렌식 아티팩트로 남게 한다.
|
|
101
|
+
const executionId = String(artifact?.executionReceipt?.executionId || executionIdHint || `workforce-run:${crypto.randomUUID()}`)
|
|
102
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
103
|
+
.slice(0, 180);
|
|
104
|
+
const file = path.join(directory, `${executionId}.json`);
|
|
105
|
+
fs.writeFileSync(file, `${JSON.stringify(artifact, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
106
|
+
return file;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── 로컬 MCP tools/list 인벤토리 (v1 listWorkforceToolsCli 포팅) ──────────
|
|
110
|
+
// 동의된(consented) 시스템 MCP 서버만 프리플라이트한다. Terminal의 원샷 러너는
|
|
111
|
+
// 아직 "정확 per-tool 부착 경계"를 증명하지 못하므로, 실제 tools/list 관측은
|
|
112
|
+
// 보존하되 runtimeIds=[] / status="observed-not-executable"로 광고한다 —
|
|
113
|
+
// 필요 capability에 대해 워크포스 모듈이 플래너 전에 fail-closed 하게 된다.
|
|
114
|
+
// 권한을 제조하는 것보다 정직 정지가 계약이다.
|
|
115
|
+
async function listWorkforceTools({ db, roster, cwd, env, timeoutMs, signal }) {
|
|
116
|
+
const mcp = require("../mcp/index.cjs");
|
|
117
|
+
const servers = mcp.readConsentedSystemMcpServers(db, {
|
|
118
|
+
userDataDir: userDataDir(),
|
|
119
|
+
createRuntimeHome: false,
|
|
120
|
+
}).slice(0, 8);
|
|
121
|
+
if (!servers.length) return [];
|
|
122
|
+
const deadline = Date.now() + Math.max(50, Math.min(12_000, Number(timeoutMs) || 12_000));
|
|
123
|
+
const outcomes = new Array(servers.length);
|
|
124
|
+
let cursor = 0;
|
|
125
|
+
const worker = async () => {
|
|
126
|
+
while (true) {
|
|
127
|
+
const index = cursor++;
|
|
128
|
+
if (index >= servers.length) return;
|
|
129
|
+
const remaining = deadline - Date.now();
|
|
130
|
+
if (remaining <= 0 || signal?.aborted) return;
|
|
131
|
+
outcomes[index] = await mcp.probeSystemMcpServerConnection(servers[index], {
|
|
132
|
+
cwd,
|
|
133
|
+
env,
|
|
134
|
+
userDataDir: userDataDir(),
|
|
135
|
+
timeoutMs: Math.min(4_000, remaining),
|
|
136
|
+
signal,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
await Promise.all(Array.from({ length: Math.min(3, servers.length) }, () => worker()));
|
|
141
|
+
|
|
142
|
+
const safeId = /^[A-Za-z0-9][A-Za-z0-9_.$:/@+~-]{0,127}$/;
|
|
143
|
+
const rows = [];
|
|
144
|
+
for (let index = 0; index < servers.length; index += 1) {
|
|
145
|
+
const server = servers[index];
|
|
146
|
+
const listed = outcomes[index];
|
|
147
|
+
if (!listed?.connected || !Array.isArray(listed.tools)) continue;
|
|
148
|
+
for (const tool of listed.tools.slice(0, 256)) {
|
|
149
|
+
if (!tool || typeof tool !== "object" || Array.isArray(tool)) continue;
|
|
150
|
+
const declared = tool._meta?.agentlas || {};
|
|
151
|
+
const toolId = typeof declared.toolId === "string" ? declared.toolId : String(tool.name || "");
|
|
152
|
+
const capabilityIds = Array.isArray(declared.capabilityIds)
|
|
153
|
+
? [...new Set(declared.capabilityIds.filter((id) => /^[A-Za-z0-9][A-Za-z0-9._:/@-]{1,255}$/.test(String(id))))]
|
|
154
|
+
: [];
|
|
155
|
+
if (!safeId.test(toolId) || !capabilityIds.length) continue;
|
|
156
|
+
const schemaJson = JSON.stringify(tool.inputSchema || {}, Object.keys(tool.inputSchema || {}).sort());
|
|
157
|
+
for (const pinned of roster || []) {
|
|
158
|
+
if (pinned?.permissionPolicy?.mcp?.mode !== "allowlist" || !pinned.permissionPolicy.mcp.allowedTools.includes(toolId)) continue;
|
|
159
|
+
rows.push({
|
|
160
|
+
slotId: pinned.slotId,
|
|
161
|
+
agentReleaseId: pinned.agentReleaseId,
|
|
162
|
+
permissionPolicyDigest: pinned.permissionPolicyDigest,
|
|
163
|
+
provider: "mcp",
|
|
164
|
+
toolId,
|
|
165
|
+
serverId: server.id,
|
|
166
|
+
description: "Ready consented host MCP tool",
|
|
167
|
+
inputSchemaDigest: `sha256:${crypto.createHash("sha256").update(schemaJson).digest("hex")}`,
|
|
168
|
+
// Terminal의 원샷 native/API 러너는 아직 정확 per-tool 부착 경계를 증명하지
|
|
169
|
+
// 못한다. 실제 tools/list 관측은 보존하되 실행 가능한 런타임을 광고하지
|
|
170
|
+
// 않는다 — collectToolInventory가 이 행을 걸러 required capability에 대해
|
|
171
|
+
// 플래너 전에 fail-closed 한다.
|
|
172
|
+
runtimeIds: [],
|
|
173
|
+
selectiveEnforcement: "unavailable",
|
|
174
|
+
capabilityIds,
|
|
175
|
+
status: "observed-not-executable",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return rows;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Workforce goal 연속성 (v1 포팅 — Agentlas Core CLI 경유) ─────────────
|
|
184
|
+
// goal-bind / goal-runtime / goal-turn / goal-complete는 Core의 workforce
|
|
185
|
+
// 명령으로 실행된다. Core가 없으면 정직하게 실패한다(조용한 생략 금지 —
|
|
186
|
+
// 워크포스 모듈이 goal_binding_unavailable 계열로 fail-closed 처리).
|
|
187
|
+
function coreHarness() {
|
|
188
|
+
return require("../agentlas-core-harness.cjs");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function workforceAccountContext() {
|
|
192
|
+
const cookie = await hubClient.cloudSessionCookie();
|
|
193
|
+
if (!cookie) throw new Error("Agentlas sign-in is required for cross-session Workforce continuity.");
|
|
194
|
+
const webBase = (process.env.AGENTLAS_WEB_BASE_URL || "https://agentlas.cloud").replace(/\/$/, "");
|
|
195
|
+
const mcpBase = (process.env.AGENTLAS_MCP_BASE_URL || `${webBase}/api/mcp/v1`).replace(/\/$/, "");
|
|
196
|
+
const response = await hubClient.fetchHub(mcpBase, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: {
|
|
199
|
+
"content-type": "application/json",
|
|
200
|
+
accept: "application/json",
|
|
201
|
+
cookie,
|
|
202
|
+
origin: webBase,
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
jsonrpc: "2.0",
|
|
206
|
+
id: crypto.randomUUID(),
|
|
207
|
+
method: "tools/call",
|
|
208
|
+
params: { name: "agentlas.account_context", arguments: {} },
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) throw new Error(`Agentlas account context failed with HTTP ${response.status}.`);
|
|
212
|
+
const rpc = hubClient.parseHubJson(response, "Agentlas account context");
|
|
213
|
+
const text = rpc?.result?.content?.[0]?.text;
|
|
214
|
+
let payload;
|
|
215
|
+
try { payload = JSON.parse(String(text || "")); } catch { payload = null; }
|
|
216
|
+
// 연속성 영수증은 스키마·계정 다이제스트·과금 권한을 정확히 검증한다 — 위조/구버전
|
|
217
|
+
// 응답으로 goal 바인딩을 진행하면 안 된다.
|
|
218
|
+
if (
|
|
219
|
+
!payload ||
|
|
220
|
+
payload.schemaVersion !== "agentlas.account-context.v1" ||
|
|
221
|
+
!/^sha256:[0-9a-f]{64}$/.test(String(payload.accountSubject || "")) ||
|
|
222
|
+
payload.leaseWindowHours !== 24 ||
|
|
223
|
+
payload.billingAuthority !== "agentlas-web"
|
|
224
|
+
) {
|
|
225
|
+
throw new Error("Agentlas account context returned an invalid continuity receipt.");
|
|
226
|
+
}
|
|
227
|
+
return { ...payload, webBase };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function implicitWorkforceGoalId(workOrder) {
|
|
231
|
+
const seed = String(workOrder?.workOrderId || "").trim();
|
|
232
|
+
if (!seed) throw new Error("Workforce WorkOrder id is required for automatic continuity.");
|
|
233
|
+
return `goal:auto:${crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, 40)}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 준비된 실행 계획은 핀·다이제스트를 담으므로 0700/0600 전용 임시 경로로만 Core에
|
|
237
|
+
// 전달하고 반드시 지운다(argv/공유 tmp 노출 금지).
|
|
238
|
+
async function withPrivateWorkforcePlan(plan, callback) {
|
|
239
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-workforce-goal-"));
|
|
240
|
+
try {
|
|
241
|
+
fs.chmodSync(directory, 0o700);
|
|
242
|
+
const file = path.join(directory, "prepared.json");
|
|
243
|
+
fs.writeFileSync(file, `${JSON.stringify(plan)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
244
|
+
return await callback(file);
|
|
245
|
+
} finally {
|
|
246
|
+
try {
|
|
247
|
+
const file = path.join(directory, "prepared.json");
|
|
248
|
+
if (fs.existsSync(file) && !fs.lstatSync(file).isSymbolicLink()) fs.unlinkSync(file);
|
|
249
|
+
fs.rmdirSync(directory);
|
|
250
|
+
} catch {
|
|
251
|
+
// 임시 경로에는 이 호출의 prepared plan만 있다. 외부 스캐너가 unlink와 경합해도
|
|
252
|
+
// 이후 OS temp 정리로 안전하다.
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function bindWorkforceGoal({ workOrder, candidateSet, selection, validationReceipt, prepared, cwd, goalId }) {
|
|
258
|
+
const core = coreHarness();
|
|
259
|
+
const coreRoot = core.resolveCoreRuntimeRoot();
|
|
260
|
+
if (!coreRoot) throw new Error("Agentlas Core is unavailable for mandatory Workforce goal binding.");
|
|
261
|
+
const account = await workforceAccountContext();
|
|
262
|
+
const resolvedGoalId = String(goalId || "").trim() || implicitWorkforceGoalId(workOrder);
|
|
263
|
+
const continuation = {
|
|
264
|
+
schemaVersion: "agentlas.workforce-terminal-continuation.v1",
|
|
265
|
+
status: "prepared",
|
|
266
|
+
runtimeSourcePins: (prepared?.executionRoster || []).map((row) => ({
|
|
267
|
+
slotId: row.slotId,
|
|
268
|
+
agentReleaseId: row.agentReleaseId,
|
|
269
|
+
source: "hub",
|
|
270
|
+
})),
|
|
271
|
+
workOrder,
|
|
272
|
+
candidateSet,
|
|
273
|
+
selection,
|
|
274
|
+
validationReceipt,
|
|
275
|
+
executionPlan: prepared,
|
|
276
|
+
};
|
|
277
|
+
return withPrivateWorkforcePlan(continuation, (file) =>
|
|
278
|
+
core.captureCoreJson(
|
|
279
|
+
"agentlas_cloud",
|
|
280
|
+
[
|
|
281
|
+
"workforce", "goal-bind", resolvedGoalId, file,
|
|
282
|
+
"--project", path.resolve(cwd || process.cwd()),
|
|
283
|
+
"--account-subject", account.accountSubject,
|
|
284
|
+
"--hub-base-url", account.webBase,
|
|
285
|
+
"--label", "automatic Terminal Workforce continuity",
|
|
286
|
+
],
|
|
287
|
+
{ cwd: path.resolve(cwd || process.cwd()) },
|
|
288
|
+
coreRoot,
|
|
289
|
+
)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function loadWorkforceGoalRuntime(cwd, goalId = null) {
|
|
294
|
+
const core = coreHarness();
|
|
295
|
+
const coreRoot = core.resolveCoreRuntimeRoot();
|
|
296
|
+
if (!coreRoot) throw new Error("Agentlas Core is unavailable for Workforce continuation.");
|
|
297
|
+
const account = await workforceAccountContext();
|
|
298
|
+
const args = [
|
|
299
|
+
"workforce", "goal-runtime",
|
|
300
|
+
"--project", path.resolve(cwd || process.cwd()),
|
|
301
|
+
"--account-subject", account.accountSubject,
|
|
302
|
+
"--hub-base-url", account.webBase,
|
|
303
|
+
];
|
|
304
|
+
if (goalId) args.push("--goal-id", String(goalId));
|
|
305
|
+
return core.captureCoreJson(
|
|
306
|
+
"agentlas_cloud",
|
|
307
|
+
args,
|
|
308
|
+
{ cwd: path.resolve(cwd || process.cwd()) },
|
|
309
|
+
coreRoot,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function completeWorkforceGoal(cwd, goalId = null, status = "completed") {
|
|
314
|
+
const core = coreHarness();
|
|
315
|
+
const coreRoot = core.resolveCoreRuntimeRoot();
|
|
316
|
+
if (!coreRoot) throw new Error("Agentlas Core is unavailable for explicit Workforce completion.");
|
|
317
|
+
const account = await workforceAccountContext();
|
|
318
|
+
const project = path.resolve(cwd || process.cwd());
|
|
319
|
+
let resolvedGoalId = String(goalId || "").trim();
|
|
320
|
+
if (!resolvedGoalId) {
|
|
321
|
+
const context = await loadWorkforceGoalRuntime(project);
|
|
322
|
+
resolvedGoalId = String(context?.goals?.[0]?.goalId || "");
|
|
323
|
+
}
|
|
324
|
+
if (!resolvedGoalId) throw new Error("No active Workforce goal exists in this account/project.");
|
|
325
|
+
return core.captureCoreJson(
|
|
326
|
+
"agentlas_cloud",
|
|
327
|
+
[
|
|
328
|
+
"workforce", "goal-complete", resolvedGoalId,
|
|
329
|
+
"--project", project,
|
|
330
|
+
"--account-subject", account.accountSubject,
|
|
331
|
+
"--hub-base-url", account.webBase,
|
|
332
|
+
"--status", status === "cancelled" ? "cancelled" : "completed",
|
|
333
|
+
"--reason", "explicit-terminal-user-command",
|
|
334
|
+
"--explicit",
|
|
335
|
+
],
|
|
336
|
+
{ cwd: project },
|
|
337
|
+
coreRoot,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function recordWorkforceGoalTurn({
|
|
342
|
+
cwd,
|
|
343
|
+
goalId,
|
|
344
|
+
decision,
|
|
345
|
+
usedRosterKeys = [],
|
|
346
|
+
localSkillIds = [],
|
|
347
|
+
gapCodes = [],
|
|
348
|
+
hostRuntime = null,
|
|
349
|
+
turnId = null,
|
|
350
|
+
}) {
|
|
351
|
+
const core = coreHarness();
|
|
352
|
+
const coreRoot = core.resolveCoreRuntimeRoot();
|
|
353
|
+
if (!coreRoot) throw new Error("Agentlas Core is unavailable for Workforce turn receipts.");
|
|
354
|
+
const account = await workforceAccountContext();
|
|
355
|
+
const args = [
|
|
356
|
+
"workforce", "goal-turn",
|
|
357
|
+
String(goalId),
|
|
358
|
+
String(turnId || `turn:terminal:${crypto.randomUUID()}`),
|
|
359
|
+
String(decision),
|
|
360
|
+
"--project", path.resolve(cwd || process.cwd()),
|
|
361
|
+
"--account-subject", account.accountSubject,
|
|
362
|
+
"--hub-base-url", account.webBase,
|
|
363
|
+
];
|
|
364
|
+
if (hostRuntime) args.push("--host-runtime", String(hostRuntime));
|
|
365
|
+
for (const key of usedRosterKeys) args.push("--use-roster", String(key));
|
|
366
|
+
for (const skill of localSkillIds) args.push("--local-skill", String(skill));
|
|
367
|
+
for (const gap of gapCodes) args.push("--gap", String(gap));
|
|
368
|
+
return core.captureCoreJson(
|
|
369
|
+
"agentlas_cloud",
|
|
370
|
+
args,
|
|
371
|
+
{ cwd: path.resolve(cwd || process.cwd()) },
|
|
372
|
+
coreRoot,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── 프로젝트 컨텍스트 슬라이스 (v1 cliProjectContextSlice 포팅) ──────────
|
|
377
|
+
// Core context slice는 워크포스 계약상 "있으면 좋은" 보강이다: 워크포스 모듈은
|
|
378
|
+
// 빈 문자열을 '컨텍스트 없음'으로 취급한다(runModel 1678–1683행). 그래서 이 함수는
|
|
379
|
+
// Core 부재/타임아웃 시 조용히 ""를 반환한다 — 이것은 폴백이 아니라 계약된 부재다.
|
|
380
|
+
// 소스 경로/내용은 프로젝트 로컬에 머무르며 Network/Cloud 검색으로 절대 안 나간다.
|
|
381
|
+
function projectContextSlice(projectPath, task) {
|
|
382
|
+
if (!projectPath || !String(task || "").trim()) return "";
|
|
383
|
+
try {
|
|
384
|
+
const core = coreHarness();
|
|
385
|
+
const coreRoot = core.resolveCoreRuntimeRoot();
|
|
386
|
+
if (!coreRoot) return "";
|
|
387
|
+
const result = core.captureCoreJsonSync(
|
|
388
|
+
"agentlas_cloud",
|
|
389
|
+
[
|
|
390
|
+
"context", "slice",
|
|
391
|
+
"--project", projectPath,
|
|
392
|
+
"--task-stdin",
|
|
393
|
+
"--no-refresh",
|
|
394
|
+
"--render",
|
|
395
|
+
],
|
|
396
|
+
{
|
|
397
|
+
cwd: projectPath,
|
|
398
|
+
input: String(task || "").slice(0, 12_000),
|
|
399
|
+
timeout: 4_000,
|
|
400
|
+
},
|
|
401
|
+
coreRoot,
|
|
402
|
+
);
|
|
403
|
+
return result
|
|
404
|
+
&& result.schemaVersion === "agentlas.context-slice.v1"
|
|
405
|
+
&& typeof result.rendered === "string"
|
|
406
|
+
? result.rendered.trim()
|
|
407
|
+
: "";
|
|
408
|
+
} catch {
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* D bag 조립. ctx = { lang?, out? } (엔진 DI 객체의 부분집합).
|
|
415
|
+
* 모든 멤버는 v1 workforce() 팩토리와 같은 계약 형태를 유지한다.
|
|
416
|
+
*/
|
|
417
|
+
function buildWorkforceDeps(ctx = {}) {
|
|
418
|
+
return {
|
|
419
|
+
now: () => new Date(),
|
|
420
|
+
out: typeof ctx.out === "function" ? ctx.out : (s) => process.stdout.write(`${s}\n`),
|
|
421
|
+
prefsLang: () => ctx.lang || "en",
|
|
422
|
+
userDataDir,
|
|
423
|
+
projectCwd: capture.projectCwd,
|
|
424
|
+
cloudSessionCookie: hubClient.cloudSessionCookie,
|
|
425
|
+
// v1과 동일: callHubTool은 주입하지 않는다. 워크포스 모듈 내부의 jsonrpc 경로가
|
|
426
|
+
// 거절 코드 원문 전파·retryClass 계약을 소유하며, fetchHub는 버퍼드
|
|
427
|
+
// {ok,status,headers,text} 어댑터 형태를 만족한다(3중 타임아웃 + 16MB 상한).
|
|
428
|
+
fetchHub: (url, init) => hubClient.fetchHub(url, init),
|
|
429
|
+
resolveRuntime: resolveWorkforceRuntime,
|
|
430
|
+
captureRuntime: capture.captureRuntime,
|
|
431
|
+
runApi: capture.runApi,
|
|
432
|
+
buildChildEnv: capture.buildChildEnv,
|
|
433
|
+
receiptFile,
|
|
434
|
+
appendReceipt,
|
|
435
|
+
appendAuditReceipt,
|
|
436
|
+
persistBenchmarkArtifact,
|
|
437
|
+
listWorkforceTools,
|
|
438
|
+
// Terminal 원샷 러너는 네이티브 per-tool 권한 부여 경계를 아직 증명하지 못했다.
|
|
439
|
+
// 증명 없이 true를 돌려주면 권한 제조가 된다 — v1과 동일하게 항상 false.
|
|
440
|
+
supportsWorkforceToolAuthority: async () => false,
|
|
441
|
+
bindWorkforceGoal,
|
|
442
|
+
loadWorkforceGoalRuntime,
|
|
443
|
+
recordWorkforceGoalTurn,
|
|
444
|
+
projectContextSlice,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let _workforce = null;
|
|
449
|
+
/** 워크포스 런타임 lazy 싱글턴 — v1 workforce() 팩토리와 동일한 수명 규칙. */
|
|
450
|
+
function workforceRuntime(ctx = {}) {
|
|
451
|
+
if (!_workforce) {
|
|
452
|
+
_workforce = require("../agentlas-workforce.cjs").create(buildWorkforceDeps(ctx));
|
|
453
|
+
}
|
|
454
|
+
return _workforce;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = {
|
|
458
|
+
buildWorkforceDeps,
|
|
459
|
+
workforceRuntime,
|
|
460
|
+
resolveWorkforceRuntime,
|
|
461
|
+
receiptFile,
|
|
462
|
+
appendReceipt,
|
|
463
|
+
appendAuditReceipt,
|
|
464
|
+
persistBenchmarkArtifact,
|
|
465
|
+
listWorkforceTools,
|
|
466
|
+
bindWorkforceGoal,
|
|
467
|
+
loadWorkforceGoalRuntime,
|
|
468
|
+
completeWorkforceGoal,
|
|
469
|
+
recordWorkforceGoalTurn,
|
|
470
|
+
projectContextSlice,
|
|
471
|
+
workforceAccountContext,
|
|
472
|
+
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.2",
|
|
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"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"smoke": "sh test/smoke.sh",
|
|
10
|
-
"test:
|
|
11
|
-
"test:workforce": "node test/workforce-runtime-contract.cjs",
|
|
12
|
-
"test:project-bootstrap": "node test/project-bootstrap-contract.cjs",
|
|
13
|
-
"test:project-bootstrap:release": "node test/project-bootstrap-contract.cjs --strict",
|
|
14
|
-
"test:release-contracts": "npm run test:stormbreaker-core && npm run test:workforce && npm run test:project-bootstrap:release"
|
|
10
|
+
"test:release-contracts": "npm run smoke"
|
|
15
11
|
},
|
|
16
12
|
"engines": {
|
|
17
13
|
"node": ">=20"
|