agentlas 1.0.65 → 1.0.67
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 +94 -0
- package/README.md +12 -4
- package/bin/agentlas.cjs +354 -16
- package/engine/acp/server.cjs +34 -3
- package/engine/agentlas-api-agent.cjs +151 -120
- package/engine/agentlas-banner.cjs +6 -2
- package/engine/agentlas-capabilities.cjs +17 -10
- package/engine/agentlas-cloud-runtime.cjs +4 -5
- package/engine/agentlas-config.cjs +19 -54
- package/engine/agentlas-core-harness.cjs +59 -5
- package/engine/agentlas-desktop-loadout.cjs +17 -3
- package/engine/agentlas-evolution.cjs +75 -18
- package/engine/agentlas-experience-exchange.cjs +145 -14
- package/engine/agentlas-experience-intake.cjs +2 -2
- package/engine/agentlas-i18n.cjs +0 -2
- package/engine/agentlas-input.cjs +273 -20
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-mcp-env.cjs +84 -23
- package/engine/agentlas-mcp-wrapper.cjs +36 -4
- package/engine/agentlas-memory-governance.cjs +2 -1
- package/engine/agentlas-memory-import.cjs +49 -12
- package/engine/agentlas-native-host.cjs +279 -44
- package/engine/agentlas-onboard.cjs +23 -1
- package/engine/agentlas-permissions.cjs +5 -1
- package/engine/agentlas-sqlite-policy.cjs +40 -2
- package/engine/agentlas-tasks.cjs +7 -0
- package/engine/agentlas-tools.cjs +37 -10
- package/engine/agentlas-ui.cjs +26 -4
- package/engine/agentlas-workforce.cjs +30 -5
- package/engine/agentlas-workload-routing.cjs +83 -10
- package/engine/agentlas.cjs +21 -4
- package/engine/agents/builder.cjs +35 -10
- package/engine/agents/files.cjs +81 -10
- package/engine/agents/import-local.cjs +10 -2
- package/engine/agents/registry.cjs +10 -3
- package/engine/agents/router.cjs +7 -7
- package/engine/agents/routes.cjs +31 -9
- package/engine/automation/daemon.cjs +154 -40
- package/engine/automation/launchd.cjs +395 -16
- package/engine/automation/schedule.cjs +90 -3
- package/engine/automation/store.cjs +163 -59
- package/engine/browser/cdp.cjs +190 -17
- package/engine/browser/vault.cjs +1 -0
- package/engine/cli-output.cjs +96 -30
- package/engine/cloud/auth.cjs +131 -13
- package/engine/cloud/hub-client.cjs +41 -16
- package/engine/cloud-assets/cas.cjs +18 -9
- package/engine/cloud-assets/commands.cjs +108 -30
- package/engine/cloud-assets/package.cjs +66 -3
- package/engine/cloud-assets/restore.cjs +12 -9
- package/engine/cloud-assets/state.cjs +609 -116
- package/engine/commands/acp.cjs +13 -2
- package/engine/commands/automation.cjs +75 -24
- package/engine/commands/billing.cjs +6 -1
- package/engine/commands/browser.cjs +61 -29
- package/engine/commands/build.cjs +72 -14
- package/engine/commands/call.cjs +1 -1
- package/engine/commands/cd.cjs +1 -1
- package/engine/commands/cloud.cjs +2 -1
- package/engine/commands/connect.cjs +38 -13
- package/engine/commands/creds.cjs +74 -29
- package/engine/commands/doctor.cjs +17 -1
- package/engine/commands/document.cjs +38 -10
- package/engine/commands/env.cjs +6 -1
- package/engine/commands/firm.cjs +42 -12
- package/engine/commands/graph.cjs +36 -32
- package/engine/commands/help.cjs +11 -1
- package/engine/commands/hep.cjs +1 -1
- package/engine/commands/import.cjs +5 -4
- package/engine/commands/index.cjs +1 -1
- package/engine/commands/install.cjs +6 -4
- package/engine/commands/list.cjs +56 -16
- package/engine/commands/login.cjs +5 -0
- package/engine/commands/logout.cjs +6 -1
- package/engine/commands/mcp.cjs +150 -37
- package/engine/commands/memory.cjs +3 -2
- package/engine/commands/multimodal.cjs +12 -0
- package/engine/commands/native.cjs +5 -1
- package/engine/commands/one.cjs +78 -33
- package/engine/commands/plugin.cjs +13 -2
- package/engine/commands/project.cjs +15 -12
- package/engine/commands/research.cjs +18 -7
- package/engine/commands/roles.cjs +104 -28
- package/engine/commands/route.cjs +2 -2
- package/engine/commands/run.cjs +42 -9
- package/engine/commands/search.cjs +23 -2
- package/engine/commands/setup.cjs +6 -1
- package/engine/commands/storm.cjs +44 -6
- package/engine/commands/swarm.cjs +47 -9
- package/engine/commands/telegram.cjs +6 -1
- package/engine/commands/uninstall.cjs +12 -6
- package/engine/commands/update.cjs +5 -0
- package/engine/commands/upload.cjs +2 -1
- package/engine/commands/usage.cjs +6 -1
- package/engine/commands/version.cjs +6 -1
- package/engine/commands/whoami.cjs +6 -1
- package/engine/commands/workforce.cjs +60 -16
- package/engine/core/capability-grants.cjs +7 -2
- package/engine/core/daemon-client.cjs +36 -7
- package/engine/core/db.cjs +10 -2
- package/engine/core/desktop-core-fetch.cjs +477 -77
- package/engine/core/schema-ensure.cjs +23 -11
- package/engine/experience/build.cjs +5 -1
- package/engine/experience/intents.cjs +35 -82
- package/engine/experience/runtime.cjs +7 -1
- package/engine/experience/variant.cjs +58 -4
- package/engine/firms/orchestrate.cjs +9 -6
- package/engine/graph/ask-model.cjs +22 -11
- package/engine/graph/interview.cjs +74 -23
- package/engine/graph/package.cjs +63 -8
- package/engine/hephaestus/local-core.cjs +18 -5
- package/engine/hephaestus/runtime.cjs +48 -43
- package/engine/hub/install.cjs +484 -63
- package/engine/hub/plugins.cjs +391 -29
- package/engine/mcp/consent.cjs +25 -17
- package/engine/mcp/contract.cjs +229 -24
- package/engine/mcp/inventory.cjs +46 -10
- package/engine/mcp/plan.cjs +10 -1
- package/engine/mcp/probe.cjs +24 -9
- package/engine/memory-cli/curate.cjs +74 -16
- package/engine/oberon/common.cjs +37 -12
- package/engine/oberon/manifest.cjs +25 -2
- package/engine/oberon/outputs.cjs +49 -19
- package/engine/oberon/render.cjs +665 -40
- package/engine/project/career-graph.cjs +15 -2
- package/engine/project/controller.cjs +133 -15
- package/engine/project/credentials.cjs +382 -34
- package/engine/project/env-file.cjs +93 -8
- package/engine/project/memory-context.cjs +11 -57
- package/engine/project/ontology.cjs +66 -25
- package/engine/project/paths.cjs +42 -8
- package/engine/project/seed.cjs +88 -51
- package/engine/project/state.cjs +41 -16
- package/engine/project/team.cjs +26 -1
- package/engine/runtime-refusal.cjs +4 -1
- package/engine/runtimes/acp-driver.cjs +56 -9
- package/engine/runtimes/auth-evidence.cjs +21 -2
- package/engine/runtimes/detect.cjs +28 -8
- package/engine/runtimes/kinds.cjs +12 -4
- package/engine/runtimes/overrides.cjs +4 -3
- package/engine/runtimes/resolve.cjs +8 -7
- package/engine/runtimes/roles.cjs +7 -5
- package/engine/sessions/apply-fences.cjs +396 -56
- package/engine/sessions/fences.cjs +111 -27
- package/engine/sessions/memory-turn.cjs +62 -3
- package/engine/sessions/orchestrator.cjs +11 -1
- package/engine/sessions/prompt.cjs +66 -9
- package/engine/sessions/session.cjs +70 -10
- package/engine/sessions/sink.cjs +78 -12
- package/engine/sessions/store.cjs +29 -7
- package/engine/storm/storm.cjs +26 -3
- package/engine/storm/swarm.cjs +31 -3
- package/engine/telegram/connect.cjs +185 -22
- package/engine/ui/commands-catalog.cjs +1 -1
- package/engine/ui/palette.cjs +12 -2
- package/engine/ui/repl.cjs +57 -9
- package/engine/ui/screens.cjs +2 -4
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/engine/workforce/capture.cjs +9 -27
- package/engine/workforce/deps.cjs +18 -6
- package/engine/workforce/local-core-transport.cjs +68 -5
- package/package.json +1 -1
- package/engine/commands/career-graph.cjs +0 -51
|
@@ -305,7 +305,7 @@ function readPrivateFeed(file, now) {
|
|
|
305
305
|
if (error && error.code === "ENOENT") return { mode: "skip", reason: "desktop-loadout-feed-absent" };
|
|
306
306
|
return { mode: "skip", reason: "desktop-loadout-feed-unreadable" };
|
|
307
307
|
}
|
|
308
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
308
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
|
|
309
309
|
return { mode: "skip", reason: "desktop-loadout-feed-not-private-regular-file" };
|
|
310
310
|
}
|
|
311
311
|
if (process.platform !== "win32") {
|
|
@@ -340,6 +340,7 @@ function readPrivateFeed(file, now) {
|
|
|
340
340
|
const opened = fs.fstatSync(descriptor);
|
|
341
341
|
if (
|
|
342
342
|
!opened.isFile() ||
|
|
343
|
+
opened.nlink !== 1 ||
|
|
343
344
|
opened.dev !== stat.dev ||
|
|
344
345
|
opened.ino !== stat.ino ||
|
|
345
346
|
opened.size !== stat.size ||
|
|
@@ -348,7 +349,19 @@ function readPrivateFeed(file, now) {
|
|
|
348
349
|
return { mode: "skip", reason: "desktop-loadout-feed-changed-during-read" };
|
|
349
350
|
}
|
|
350
351
|
raw = fs.readFileSync(descriptor, "utf8");
|
|
351
|
-
|
|
352
|
+
const after = fs.fstatSync(descriptor);
|
|
353
|
+
if (
|
|
354
|
+
after.nlink !== 1 ||
|
|
355
|
+
after.dev !== opened.dev ||
|
|
356
|
+
after.ino !== opened.ino ||
|
|
357
|
+
after.size !== opened.size ||
|
|
358
|
+
after.mtimeMs !== opened.mtimeMs ||
|
|
359
|
+
after.ctimeMs !== opened.ctimeMs
|
|
360
|
+
) {
|
|
361
|
+
return { mode: "skip", reason: "desktop-loadout-feed-changed-during-read" };
|
|
362
|
+
}
|
|
363
|
+
const rawBytes = Buffer.byteLength(raw, "utf8");
|
|
364
|
+
if (rawBytes !== after.size || rawBytes > MAX_FILE_BYTES) {
|
|
352
365
|
return { mode: "skip", reason: "desktop-loadout-feed-size-invalid" };
|
|
353
366
|
}
|
|
354
367
|
} finally { fs.closeSync(descriptor); }
|
|
@@ -407,7 +420,8 @@ function exactLocalAuthority(db) {
|
|
|
407
420
|
function sameExactList(value, expected) {
|
|
408
421
|
if (value === undefined || value === null || (Array.isArray(value) && value.length === 0)) return true;
|
|
409
422
|
if (!Array.isArray(value)) return false;
|
|
410
|
-
const normalized =
|
|
423
|
+
const normalized = value.map(String);
|
|
424
|
+
if (normalized.some((item) => !item) || new Set(normalized).size !== normalized.length) return false;
|
|
411
425
|
return normalized.length === expected.length && normalized.every((item, index) => item === expected[index]);
|
|
412
426
|
}
|
|
413
427
|
|
|
@@ -22,6 +22,8 @@ function sha256(content) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const ABSENT_TARGET_HASH = sha256("agentlas:absent-agent-asset:v1");
|
|
25
|
+
const MAX_EVOLUTION_CONTENT_BYTES = 512 * 1024;
|
|
26
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
25
27
|
const RULE_TARGETS = new Set([
|
|
26
28
|
"system-prompt.md",
|
|
27
29
|
"soul.md",
|
|
@@ -42,6 +44,15 @@ function parseSource(json) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
function terminalSafe(value, maxLength = 300) {
|
|
48
|
+
return String(value ?? "").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim().slice(0, maxLength);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizedListLimit(value) {
|
|
52
|
+
const parsed = Number(value);
|
|
53
|
+
return Number.isInteger(parsed) ? Math.max(1, Math.min(parsed, 200)) : 50;
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
/** 대기 중(사람 결정 필요) 고위험 성장 제안 개수 — 터미널 홈 배너용. */
|
|
46
57
|
function countPendingGrowthProposals(db) {
|
|
47
58
|
try {
|
|
@@ -68,19 +79,23 @@ function listGrowthProposals(db, limit = 50) {
|
|
|
68
79
|
ORDER BY datetime(updated_at) DESC, datetime(created_at) DESC
|
|
69
80
|
LIMIT ?`,
|
|
70
81
|
)
|
|
71
|
-
.all(limit);
|
|
82
|
+
.all(normalizedListLimit(limit));
|
|
72
83
|
} catch {
|
|
73
84
|
rows = [];
|
|
74
85
|
}
|
|
75
86
|
const pending = [];
|
|
87
|
+
const applied = [];
|
|
76
88
|
const autoApplied = [];
|
|
77
89
|
for (const row of rows) {
|
|
78
90
|
const source = parseSource(row.source_json);
|
|
79
91
|
const entry = { row, source };
|
|
80
92
|
if (row.status === "candidate") pending.push(entry);
|
|
81
|
-
else
|
|
93
|
+
else {
|
|
94
|
+
applied.push(entry);
|
|
95
|
+
if (source._autoApplied === true) autoApplied.push(entry);
|
|
96
|
+
}
|
|
82
97
|
}
|
|
83
|
-
return { pending, autoApplied };
|
|
98
|
+
return { pending, applied, autoApplied };
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
function agentById(db, id) {
|
|
@@ -145,27 +160,28 @@ function printCard(out, entry, index) {
|
|
|
145
160
|
const { row, source } = entry;
|
|
146
161
|
const card = source.humanCard && typeof source.humanCard === "object" ? source.humanCard : null;
|
|
147
162
|
const tier = source.riskTier === "high" ? "high" : "low";
|
|
148
|
-
|
|
163
|
+
const application = row.status === "candidate" ? "승인 전" : source._autoApplied === true ? "자동 반영" : "승인 후 반영";
|
|
164
|
+
out(` [${index}] ${terminalSafe(row.id)} (${tier} · ${terminalSafe(row.status)} · ${application} · ${terminalSafe(row.agent_id)})`);
|
|
149
165
|
if (card) {
|
|
150
|
-
out(` 배운 것 : ${card.learned}`);
|
|
151
|
-
out(`
|
|
152
|
-
out(`
|
|
166
|
+
out(` 배운 것 : ${terminalSafe(card.learned)}`);
|
|
167
|
+
out(` 바뀐 것 : ${terminalSafe(card.change)}`);
|
|
168
|
+
out(` 안전장치: ${terminalSafe(card.reversible)}`);
|
|
153
169
|
} else {
|
|
154
|
-
out(` ${row.summary}`);
|
|
170
|
+
out(` ${terminalSafe(row.summary)}`);
|
|
155
171
|
}
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
function cmdList(db, out) {
|
|
159
|
-
const { pending,
|
|
175
|
+
const { pending, applied } = listGrowthProposals(db);
|
|
160
176
|
out("== agent growth proposals ==");
|
|
161
177
|
out("");
|
|
162
178
|
out(`대기(승인 필요) ${pending.length}건:`);
|
|
163
179
|
if (!pending.length) out(" (없음)");
|
|
164
180
|
pending.forEach((entry, i) => printCard(out, entry, i + 1));
|
|
165
181
|
out("");
|
|
166
|
-
out(
|
|
167
|
-
if (!
|
|
168
|
-
|
|
182
|
+
out(`성장 반영 완료 ${applied.length}건 (필요할 때 되돌릴 수 있는 안전장치):`);
|
|
183
|
+
if (!applied.length) out(" (없음)");
|
|
184
|
+
applied.forEach((entry, i) => printCard(out, entry, i + 1));
|
|
169
185
|
out("");
|
|
170
186
|
out("적용: agentlas evolve apply <id> · 되돌리기: agentlas evolve revert <id>");
|
|
171
187
|
}
|
|
@@ -182,6 +198,36 @@ function loadProposal(db, id, fail) {
|
|
|
182
198
|
return row;
|
|
183
199
|
}
|
|
184
200
|
|
|
201
|
+
function validateProposalPayload(row, fail) {
|
|
202
|
+
if (!SHA256_RE.test(String(row.before_hash)) || !SHA256_RE.test(String(row.after_hash))) {
|
|
203
|
+
fail("Evolution proposal contains an invalid content hash");
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (typeof row.before_content !== "string" || typeof row.after_content !== "string") {
|
|
207
|
+
fail("Evolution proposal content is invalid");
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
Buffer.byteLength(row.before_content, "utf8") > MAX_EVOLUTION_CONTENT_BYTES ||
|
|
212
|
+
Buffer.byteLength(row.after_content, "utf8") > MAX_EVOLUTION_CONTENT_BYTES
|
|
213
|
+
) {
|
|
214
|
+
fail("Evolution proposal exceeds the portable 512 KiB content limit");
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (sha256(row.after_content) !== row.after_hash) {
|
|
218
|
+
fail("Evolution proposal after-content does not match its approved hash");
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
(row.before_hash === ABSENT_TARGET_HASH && row.before_content !== "") ||
|
|
223
|
+
(row.before_hash !== ABSENT_TARGET_HASH && sha256(row.before_content) !== row.before_hash)
|
|
224
|
+
) {
|
|
225
|
+
fail("Evolution proposal before-content does not match its approved baseline hash");
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
185
231
|
// 터미널이 부트스트랩한 DB는 v45라 진화 영수증 테이블이 없을 수 있다(데스크탑 v51+ 마이그레이션이
|
|
186
232
|
// 추가). 앱과 동일한 스키마로 idempotent 생성 — 앱이 이미 만들었으면 no-op.
|
|
187
233
|
function ensureReceiptTable(db) {
|
|
@@ -206,7 +252,7 @@ function ensureReceiptTable(db) {
|
|
|
206
252
|
|
|
207
253
|
function insertReceipt(db, row, action, hashBefore, hashAfter, now) {
|
|
208
254
|
ensureReceiptTable(db);
|
|
209
|
-
db.prepare(
|
|
255
|
+
const inserted = db.prepare(
|
|
210
256
|
`INSERT INTO agent_evolution_receipts (
|
|
211
257
|
id, proposal_id, agent_id, action, target_path,
|
|
212
258
|
version_before, version_after, target_hash_before, target_hash_after,
|
|
@@ -225,11 +271,13 @@ function insertReceipt(db, row, action, hashBefore, hashAfter, now) {
|
|
|
225
271
|
hashAfter,
|
|
226
272
|
now,
|
|
227
273
|
);
|
|
274
|
+
if (inserted.changes !== 1) throw new Error(`Evolution ${action} receipt already exists; proposal state is inconsistent`);
|
|
228
275
|
}
|
|
229
276
|
|
|
230
277
|
function cmdApply(db, id, out, fail, agentFolder) {
|
|
231
278
|
const row = loadProposal(db, id, fail);
|
|
232
279
|
if (!row) return;
|
|
280
|
+
if (!validateProposalPayload(row, fail)) return;
|
|
233
281
|
if (row.status !== "candidate") {
|
|
234
282
|
return fail(`Only a pending candidate can be applied; ${id} is '${row.status}'.`);
|
|
235
283
|
}
|
|
@@ -273,6 +321,7 @@ function cmdApply(db, id, out, fail, agentFolder) {
|
|
|
273
321
|
function cmdRevert(db, id, out, fail, agentFolder) {
|
|
274
322
|
const row = loadProposal(db, id, fail);
|
|
275
323
|
if (!row) return;
|
|
324
|
+
if (!validateProposalPayload(row, fail)) return;
|
|
276
325
|
if (row.status !== "applied" && row.status !== "measured") {
|
|
277
326
|
return fail(`Only an applied proposal can be reverted; ${id} is '${row.status}'.`);
|
|
278
327
|
}
|
|
@@ -294,7 +343,11 @@ function cmdRevert(db, id, out, fail, agentFolder) {
|
|
|
294
343
|
const locked = db.prepare("SELECT status FROM agent_evolution_proposals WHERE id=?").get(row.id);
|
|
295
344
|
if (!locked || !["applied", "measured"].includes(locked.status)) throw new Error("Evolution proposal changed before revert; reload it first");
|
|
296
345
|
if (currentTargetHash(file).hash !== row.after_hash) throw new Error("Agent prompt changed before revert; reload it first");
|
|
297
|
-
|
|
346
|
+
// A proposal may have created the prompt asset from an absent baseline.
|
|
347
|
+
// Restoring that baseline means removing the created file, not writing an
|
|
348
|
+
// empty file (whose sha256 can never equal ABSENT_TARGET_HASH).
|
|
349
|
+
if (row.before_hash === ABSENT_TARGET_HASH) fs.rmSync(file, { force: true });
|
|
350
|
+
else writeTargetAtomic(file, row.before_content);
|
|
298
351
|
wroteTarget = true;
|
|
299
352
|
if (currentTargetHash(file).hash !== row.before_hash) throw new Error("Reverted content did not match the original hash");
|
|
300
353
|
db.prepare("UPDATE installed_agents SET system_prompt = ? WHERE id = ?").run(row.before_content, row.agent_id);
|
|
@@ -309,7 +362,7 @@ function cmdRevert(db, id, out, fail, agentFolder) {
|
|
|
309
362
|
});
|
|
310
363
|
} catch (error) {
|
|
311
364
|
if (wroteTarget) {
|
|
312
|
-
try { restoreTargetAfterFailure(file, before,
|
|
365
|
+
try { restoreTargetAfterFailure(file, before, row.before_hash); }
|
|
313
366
|
catch (rollbackError) { return fail(`${error.message}. ${rollbackError.message}`); }
|
|
314
367
|
}
|
|
315
368
|
return fail(`${error.message}; the applied target was restored.`);
|
|
@@ -326,19 +379,23 @@ function cmdEvolve(ctx) {
|
|
|
326
379
|
const args = Array.isArray(ctx.args) ? ctx.args : [];
|
|
327
380
|
const sub = args[0] || "list";
|
|
328
381
|
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
382
|
+
if (args.length !== 1) return fail("usage: agentlas evolve [list | apply <id> | revert <id>]");
|
|
329
383
|
out("usage: agentlas evolve [list | apply <id> | revert <id>]");
|
|
330
384
|
out(" list — review pending (approval-needed) and auto-applied agent growth proposals");
|
|
331
385
|
out(" apply <id> — approve and apply a pending proposal to the agent prompt");
|
|
332
386
|
out(" revert <id> — roll back an applied proposal");
|
|
333
387
|
return;
|
|
334
388
|
}
|
|
335
|
-
if (sub === "list" || sub === "ls")
|
|
389
|
+
if (sub === "list" || sub === "ls") {
|
|
390
|
+
if (args.length > 1) return fail("usage: agentlas evolve list");
|
|
391
|
+
return cmdList(db, out);
|
|
392
|
+
}
|
|
336
393
|
if (sub === "apply") {
|
|
337
|
-
if (!args[1]) return fail("usage: agentlas evolve apply <id>");
|
|
394
|
+
if (args.length !== 2 || !args[1]) return fail("usage: agentlas evolve apply <id>");
|
|
338
395
|
return cmdApply(db, args[1], out, fail, agentFolder);
|
|
339
396
|
}
|
|
340
397
|
if (sub === "revert" || sub === "rollback") {
|
|
341
|
-
if (!args[1]) return fail("usage: agentlas evolve revert <id>");
|
|
398
|
+
if (args.length !== 2 || !args[1]) return fail("usage: agentlas evolve revert <id>");
|
|
342
399
|
return cmdRevert(db, args[1], out, fail, agentFolder);
|
|
343
400
|
}
|
|
344
401
|
return fail(`Unknown evolve subcommand: ${sub} (list|apply|revert)`);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const crypto = require("node:crypto");
|
|
13
13
|
const fs = require("node:fs");
|
|
14
14
|
const path = require("node:path");
|
|
15
|
+
const { TextDecoder } = require("node:util");
|
|
15
16
|
|
|
16
17
|
const BUNDLE_SCHEMA = "agentlas.experience-bundle.v1";
|
|
17
18
|
const RECEIPT_SCHEMA = "agentlas.experience-upload-receipt.v1";
|
|
@@ -604,13 +605,94 @@ function validateExperienceBundle(payload) {
|
|
|
604
605
|
return { bundle: value, canonicalJson: canonicalJson(value), canonicalBytes, expectedPackHash, expectedBundleHash, expectedBundleId: experienceBundleId(value) };
|
|
605
606
|
}
|
|
606
607
|
|
|
608
|
+
function isBoundedSingleLinkRegular(stat, maxBytes, minBytes = 0) {
|
|
609
|
+
return Boolean(
|
|
610
|
+
stat && stat.isFile() && !stat.isSymbolicLink() && stat.nlink === 1 &&
|
|
611
|
+
Number.isSafeInteger(stat.size) && stat.size >= minBytes && stat.size <= maxBytes,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function sameBoundedFileSnapshot(left, right) {
|
|
616
|
+
return Boolean(
|
|
617
|
+
left && right && left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink &&
|
|
618
|
+
left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function readBoundedTextFile(filePath, maxBytes, options = {}) {
|
|
623
|
+
const {
|
|
624
|
+
label = "private file",
|
|
625
|
+
minBytes = 0,
|
|
626
|
+
allowMissing = false,
|
|
627
|
+
unsafeMessage = `${label} is unsafe`,
|
|
628
|
+
sizeMessage = `${label} exceeds ${maxBytes} bytes`,
|
|
629
|
+
symlinkMessage = unsafeMessage,
|
|
630
|
+
} = options;
|
|
631
|
+
let before;
|
|
632
|
+
try {
|
|
633
|
+
before = fs.lstatSync(filePath);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (allowMissing && error?.code === "ENOENT") return null;
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
if (before.isSymbolicLink()) throw new Error(symlinkMessage);
|
|
639
|
+
if (!before.isFile() || before.nlink !== 1) throw new Error(unsafeMessage);
|
|
640
|
+
if (!Number.isSafeInteger(before.size) || before.size < minBytes || before.size > maxBytes) throw new Error(sizeMessage);
|
|
641
|
+
|
|
642
|
+
const noFollow = fs.constants.O_NOFOLLOW;
|
|
643
|
+
if (!Number.isInteger(noFollow)) throw new Error(`${label} cannot enforce symlink-safe open`);
|
|
644
|
+
const nonBlock = Number.isInteger(fs.constants.O_NONBLOCK) ? fs.constants.O_NONBLOCK : 0;
|
|
645
|
+
const descriptor = fs.openSync(filePath, fs.constants.O_RDONLY | noFollow | nonBlock);
|
|
646
|
+
try {
|
|
647
|
+
const opened = fs.fstatSync(descriptor);
|
|
648
|
+
if (!isBoundedSingleLinkRegular(opened, maxBytes, minBytes) || !sameBoundedFileSnapshot(before, opened)) {
|
|
649
|
+
throw new Error(`${label} changed while opening`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const chunks = [];
|
|
653
|
+
let total = 0;
|
|
654
|
+
while (total <= maxBytes) {
|
|
655
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1 - total));
|
|
656
|
+
const count = fs.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
657
|
+
if (count === 0) break;
|
|
658
|
+
chunks.push(buffer.subarray(0, count));
|
|
659
|
+
total += count;
|
|
660
|
+
}
|
|
661
|
+
if (total > maxBytes) throw new Error(sizeMessage);
|
|
662
|
+
|
|
663
|
+
const after = fs.fstatSync(descriptor);
|
|
664
|
+
const pathAfter = fs.lstatSync(filePath);
|
|
665
|
+
if (
|
|
666
|
+
!isBoundedSingleLinkRegular(after, maxBytes, minBytes) ||
|
|
667
|
+
!isBoundedSingleLinkRegular(pathAfter, maxBytes, minBytes) ||
|
|
668
|
+
!sameBoundedFileSnapshot(opened, after) ||
|
|
669
|
+
!sameBoundedFileSnapshot(before, pathAfter) ||
|
|
670
|
+
!sameBoundedFileSnapshot(opened, pathAfter) ||
|
|
671
|
+
total !== after.size
|
|
672
|
+
) {
|
|
673
|
+
throw new Error(`${label} changed while reading`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total));
|
|
678
|
+
} catch (error) {
|
|
679
|
+
throw new Error(`${label} is invalid UTF-8: ${error.message}`);
|
|
680
|
+
}
|
|
681
|
+
} finally {
|
|
682
|
+
fs.closeSync(descriptor);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
607
686
|
function readBundleFile(filePath, cwd = process.cwd()) {
|
|
608
687
|
const absolute = path.resolve(cwd, filePath);
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
688
|
+
const text = readBoundedTextFile(absolute, MAX_BUNDLE_FILE_BYTES, {
|
|
689
|
+
label: "Experience bundle",
|
|
690
|
+
minBytes: 1,
|
|
691
|
+
unsafeMessage: "Experience bundle must be a regular file; symlinks are forbidden",
|
|
692
|
+
sizeMessage: `Experience bundle file must be 1..${MAX_BUNDLE_FILE_BYTES} bytes`,
|
|
693
|
+
});
|
|
612
694
|
let payload;
|
|
613
|
-
try { payload = JSON.parse(
|
|
695
|
+
try { payload = JSON.parse(text); } catch (error) { throw new Error(`Experience bundle is invalid JSON: ${error.message}`); }
|
|
614
696
|
return { absolute, ...validateExperienceBundle(payload) };
|
|
615
697
|
}
|
|
616
698
|
|
|
@@ -776,10 +858,14 @@ function withExchangeStateLock(userDataDir, action) {
|
|
|
776
858
|
function loadExchangeState(userDataDir) {
|
|
777
859
|
const file = exchangeStatePath(userDataDir);
|
|
778
860
|
recoverPrivateAtomicTarget(file);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
861
|
+
const text = readBoundedTextFile(file, MAX_STATE_BYTES, {
|
|
862
|
+
label: "Terminal Experience exchange state",
|
|
863
|
+
allowMissing: true,
|
|
864
|
+
unsafeMessage: "Terminal Experience exchange state is unsafe or too large",
|
|
865
|
+
sizeMessage: "Terminal Experience exchange state is unsafe or too large",
|
|
866
|
+
});
|
|
867
|
+
if (text === null) return emptyState();
|
|
868
|
+
const state = JSON.parse(text);
|
|
783
869
|
const allowed = new Set(["schemaVersion", "updatedAt", "bundles"]);
|
|
784
870
|
const issues = [];
|
|
785
871
|
strictObject(state, allowed, allowed, "exchange state", issues);
|
|
@@ -896,20 +982,51 @@ function resolveBundleInput(userDataDir, sourceOrRef, cwd) {
|
|
|
896
982
|
return readBundleFile(sourceOrRef, cwd);
|
|
897
983
|
}
|
|
898
984
|
|
|
899
|
-
function parseFlags(args) {
|
|
985
|
+
function parseFlags(args, schema = {}) {
|
|
900
986
|
const flags = { _: [] };
|
|
987
|
+
const values = new Set(schema.values || []);
|
|
988
|
+
const booleans = new Set(schema.booleans || []);
|
|
989
|
+
let passthrough = false;
|
|
901
990
|
for (let index = 0; index < args.length; index += 1) {
|
|
902
991
|
const token = String(args[index]);
|
|
992
|
+
if (passthrough) { flags._.push(token); continue; }
|
|
993
|
+
if (token === "--") { passthrough = true; continue; }
|
|
903
994
|
if (!token.startsWith("--")) { flags._.push(token); continue; }
|
|
904
995
|
const equal = token.indexOf("=");
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (
|
|
908
|
-
|
|
996
|
+
const key = token.slice(2, equal > 2 ? equal : undefined);
|
|
997
|
+
if (!values.has(key) && !booleans.has(key)) throw new Error(`unknown option: --${key}`);
|
|
998
|
+
if (Object.prototype.hasOwnProperty.call(flags, key)) throw new Error(`duplicate option: --${key}`);
|
|
999
|
+
if (booleans.has(key)) {
|
|
1000
|
+
if (equal > 2) throw new Error(`--${key} does not take a value`);
|
|
1001
|
+
flags[key] = true;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
const value = equal > 2 ? token.slice(equal + 1) : args[++index];
|
|
1005
|
+
if (value === undefined || value === "" || (equal <= 2 && String(value).startsWith("--"))) {
|
|
1006
|
+
throw new Error(`--${key} requires a value`);
|
|
1007
|
+
}
|
|
1008
|
+
flags[key] = String(value);
|
|
909
1009
|
}
|
|
910
1010
|
return flags;
|
|
911
1011
|
}
|
|
912
1012
|
|
|
1013
|
+
const EXPERIENCE_COMMAND_FLAG_SCHEMA = Object.freeze({
|
|
1014
|
+
list: { booleans: ["json"] },
|
|
1015
|
+
inspect: { booleans: ["json"] },
|
|
1016
|
+
validate: { booleans: ["json"] },
|
|
1017
|
+
save: {
|
|
1018
|
+
booleans: ["json", "dry-run", "local-only"],
|
|
1019
|
+
values: ["idempotency-key", "base-slug", "base-cloud-id", "base-package-hash", "base-package-hash-version"],
|
|
1020
|
+
},
|
|
1021
|
+
publish: {
|
|
1022
|
+
booleans: ["json", "dry-run"],
|
|
1023
|
+
values: ["visibility", "idempotency-key", "base-slug", "base-cloud-id", "base-package-hash", "base-package-hash-version"],
|
|
1024
|
+
},
|
|
1025
|
+
status: { booleans: ["json"] },
|
|
1026
|
+
export: { booleans: ["json", "overwrite"], values: ["out"] },
|
|
1027
|
+
unpublish: { booleans: ["json", "dry-run"] },
|
|
1028
|
+
});
|
|
1029
|
+
|
|
913
1030
|
function idempotencyKeyForBundle(bundle, explicit, operation = "save") {
|
|
914
1031
|
const defaultDigest = crypto.createHash("sha256")
|
|
915
1032
|
.update(canonicalJson({ bundleHash: bundle.bundleHash, operation, requestedVisibility: bundle.requestedVisibility }), "utf8")
|
|
@@ -1935,7 +2052,6 @@ function baseDescriptorFromFlags(flags) {
|
|
|
1935
2052
|
async function cmdExperienceExchange(options = {}) {
|
|
1936
2053
|
const args = options.args || [];
|
|
1937
2054
|
const sub = args[0] || "list";
|
|
1938
|
-
const flags = parseFlags(args.slice(1));
|
|
1939
2055
|
const emit = options.out || console.log;
|
|
1940
2056
|
if (!options.userDataDir) throw new Error("Terminal userData path is required");
|
|
1941
2057
|
|
|
@@ -1963,6 +2079,15 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1963
2079
|
const mapped = sub.slice("legacy-".length);
|
|
1964
2080
|
return options.legacyCommand({ ...options, args: [mapped, ...args.slice(1)] });
|
|
1965
2081
|
}
|
|
2082
|
+
const canonicalSub = sub === "ls" ? "list"
|
|
2083
|
+
: sub === "show" ? "inspect"
|
|
2084
|
+
: sub === "withdraw" ? "unpublish"
|
|
2085
|
+
: sub;
|
|
2086
|
+
const schema = EXPERIENCE_COMMAND_FLAG_SCHEMA[canonicalSub];
|
|
2087
|
+
if (!schema) {
|
|
2088
|
+
throw new Error("unknown experience subcommand (list|inspect|validate|save|publish|status|export|unpublish|withdraw; legacy: legacy-list|legacy-inspect|legacy-publish|legacy-unpublish)");
|
|
2089
|
+
}
|
|
2090
|
+
const flags = parseFlags(args.slice(1), schema);
|
|
1966
2091
|
if (sub === "list" || sub === "ls") {
|
|
1967
2092
|
if (flags._.length) throw new Error("usage: agentlas experience list [--json]");
|
|
1968
2093
|
const bundles = listStoredExperienceBundles(options.userDataDir, options.cwd);
|
|
@@ -1998,12 +2123,14 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1998
2123
|
return result;
|
|
1999
2124
|
}
|
|
2000
2125
|
if (sub === "validate") {
|
|
2126
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience validate <bundle.agentlas-experience.json> [--json]");
|
|
2001
2127
|
const validation = readBundleFile(flags._[0], options.cwd);
|
|
2002
2128
|
const result = { valid: true, bundleId: validation.bundle.bundleId, bundleHash: validation.bundle.bundleHash, packContentHash: validation.bundle.pack.contentHash, items: validation.bundle.items.length, canonicalBytes: validation.canonicalBytes, networkUsed: false, authority: "local-validation" };
|
|
2003
2129
|
emit(flags.json ? JSON.stringify(result, null, 2) : renderValidation(validation));
|
|
2004
2130
|
return result;
|
|
2005
2131
|
}
|
|
2006
2132
|
if (sub === "save") {
|
|
2133
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience save <bundle> [options]");
|
|
2007
2134
|
const validation = readBundleFile(flags._[0], options.cwd);
|
|
2008
2135
|
if (flags["local-only"] === true) {
|
|
2009
2136
|
if (flags["dry-run"] === true) {
|
|
@@ -2027,6 +2154,7 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
2027
2154
|
return result;
|
|
2028
2155
|
}
|
|
2029
2156
|
if (sub === "publish") {
|
|
2157
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience publish <bundle> [options]");
|
|
2030
2158
|
const source = flags._[0];
|
|
2031
2159
|
const validation = resolveBundleInput(options.userDataDir, source, options.cwd);
|
|
2032
2160
|
const result = await publishBundle(validation, {
|
|
@@ -2041,11 +2169,13 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
2041
2169
|
return result;
|
|
2042
2170
|
}
|
|
2043
2171
|
if (sub === "status") {
|
|
2172
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience status <bundle-id|upload-id> [--json]");
|
|
2044
2173
|
const result = await fetchUploadStatus(flags._[0], options);
|
|
2045
2174
|
emit(flags.json ? JSON.stringify(publicCommandExchangeResult(result), null, 2) : `Server-authoritative status: ${result.receipt.status} · ${result.receipt.uploadId}\nrequested visibility: ${result.receipt.requestedVisibility} · Terminal did not assert public activation/evaluator reputation`);
|
|
2046
2175
|
return result;
|
|
2047
2176
|
}
|
|
2048
2177
|
if (sub === "export") {
|
|
2178
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience export <bundle-id|upload-id> [--out file] [--overwrite] [--json]");
|
|
2049
2179
|
const result = await fetchUploadExport(flags._[0], {
|
|
2050
2180
|
...options,
|
|
2051
2181
|
outputPath: typeof flags.out === "string" ? flags.out : null,
|
|
@@ -2111,6 +2241,7 @@ module.exports = {
|
|
|
2111
2241
|
previewWithdrawUpload,
|
|
2112
2242
|
publicUploadReceipt,
|
|
2113
2243
|
publicCommandExchangeResult,
|
|
2244
|
+
parseFlags,
|
|
2114
2245
|
projectScopeHash,
|
|
2115
2246
|
idempotencyKeyForBundle,
|
|
2116
2247
|
idempotencyKeyHash,
|
|
@@ -75,9 +75,9 @@ function createRunReceipt(input) {
|
|
|
75
75
|
if (!input.exactBase || !ID_RE.test(String(input.exactBase.agentReleaseId || ""))) {
|
|
76
76
|
throw new Error("RunReceipt requires an exact agent definition release.");
|
|
77
77
|
}
|
|
78
|
-
const runId = cleanId(input.runId, opaqueId("run", input.exactBase.agentReleaseId, taskSignature, input.createdAt));
|
|
79
|
-
const idempotencyKey = cleanId(input.idempotencyKey, opaqueId("run-key", runId));
|
|
80
78
|
const createdAt = input.createdAt || new Date().toISOString();
|
|
79
|
+
const runId = cleanId(input.runId, opaqueId("run", input.exactBase.agentReleaseId, taskSignature, createdAt));
|
|
80
|
+
const idempotencyKey = cleanId(input.idempotencyKey, opaqueId("run-key", runId));
|
|
81
81
|
const provider = cleanId(input.model?.provider, "terminal-runtime");
|
|
82
82
|
const modelId = cleanId(input.model?.modelId, provider);
|
|
83
83
|
const outcome = ["succeeded", "partial", "failed", "cancelled"].includes(input.outcome?.status)
|
package/engine/agentlas-i18n.cjs
CHANGED
|
@@ -174,7 +174,6 @@ const STRINGS = {
|
|
|
174
174
|
"help.setup": "run language, runtime, and permission setup again",
|
|
175
175
|
"help.cwd": "show or change the working folder",
|
|
176
176
|
"help.memory": "show the memory being injected",
|
|
177
|
-
"help.careerGraph": "show/add source refs for the Career Graph routing index",
|
|
178
177
|
"help.ontology": "turn on/list/add project ontology sources with short natural commands",
|
|
179
178
|
"help.side": "ask a side question without saving it to project task context",
|
|
180
179
|
"help.status": "show model/runtime, agent, permission, and directory",
|
|
@@ -422,7 +421,6 @@ const STRINGS = {
|
|
|
422
421
|
"help.setup": "언어·런타임·권한 설정 다시 실행",
|
|
423
422
|
"help.cwd": "작업 폴더 보기/변경",
|
|
424
423
|
"help.memory": "주입되는 메모리 보기",
|
|
425
|
-
"help.careerGraph": "경력 그래프 라우팅 색인의 원본 경로 보기/등록",
|
|
426
424
|
"help.ontology": "짧은 자연어로 프로젝트 온톨로지 켜기/목록/자료 등록",
|
|
427
425
|
"help.side": "프로젝트 작업 맥락에 저장하지 않는 사이드 질문",
|
|
428
426
|
"help.status": "모델/런타임, 에이전트, 권한, 작업 폴더 보기",
|