agentlas 1.0.65 → 1.0.66
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 +78 -0
- package/README.md +6 -4
- package/bin/agentlas.cjs +341 -16
- package/engine/acp/server.cjs +27 -1
- 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 +29 -5
- package/engine/agentlas-workload-routing.cjs +83 -10
- package/engine/agentlas.cjs +10 -3
- 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 +80 -28
- 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 +308 -113
- 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 +42 -12
- 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 +8 -2
- 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 +29 -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 +230 -61
- 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 +15 -5
- package/engine/mcp/plan.cjs +5 -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 +212 -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
|
@@ -8,13 +8,85 @@
|
|
|
8
8
|
*/
|
|
9
9
|
const fs = require("node:fs");
|
|
10
10
|
const path = require("node:path");
|
|
11
|
+
const crypto = require("node:crypto");
|
|
12
|
+
|
|
13
|
+
const MAX_DOT_ENV_BYTES = 512 * 1024;
|
|
14
|
+
|
|
15
|
+
function readEnvSnapshot(file) {
|
|
16
|
+
let before;
|
|
17
|
+
try {
|
|
18
|
+
before = fs.lstatSync(file);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error && error.code === "ENOENT") return { exists: false, body: "" };
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > MAX_DOT_ENV_BYTES) {
|
|
24
|
+
throw new Error("credential env target must be a bounded regular non-symbolic-link file");
|
|
25
|
+
}
|
|
26
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
27
|
+
try {
|
|
28
|
+
const opened = fs.fstatSync(fd);
|
|
29
|
+
if (
|
|
30
|
+
!opened.isFile() || opened.size > MAX_DOT_ENV_BYTES ||
|
|
31
|
+
opened.dev !== before.dev || opened.ino !== before.ino
|
|
32
|
+
) throw new Error("credential env target changed while opening");
|
|
33
|
+
return {
|
|
34
|
+
exists: true,
|
|
35
|
+
body: fs.readFileSync(fd, "utf8"),
|
|
36
|
+
dev: opened.dev,
|
|
37
|
+
ino: opened.ino,
|
|
38
|
+
size: opened.size,
|
|
39
|
+
mtimeMs: opened.mtimeMs,
|
|
40
|
+
ctimeMs: opened.ctimeMs,
|
|
41
|
+
};
|
|
42
|
+
} finally {
|
|
43
|
+
fs.closeSync(fd);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertEnvSnapshotUnchanged(file, snapshot) {
|
|
48
|
+
let current;
|
|
49
|
+
try { current = fs.lstatSync(file); }
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error && error.code === "ENOENT" && !snapshot.exists) return;
|
|
52
|
+
throw new Error("credential env target changed before replacement");
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
!snapshot.exists || !current.isFile() || current.isSymbolicLink() ||
|
|
56
|
+
current.dev !== snapshot.dev || current.ino !== snapshot.ino ||
|
|
57
|
+
current.size !== snapshot.size || current.mtimeMs !== snapshot.mtimeMs || current.ctimeMs !== snapshot.ctimeMs
|
|
58
|
+
) throw new Error("credential env target changed before replacement");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function replaceEnvFileAtomic(temp, file, snapshot) {
|
|
62
|
+
assertEnvSnapshotUnchanged(file, snapshot);
|
|
63
|
+
try {
|
|
64
|
+
fs.renameSync(temp, file);
|
|
65
|
+
return;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (
|
|
68
|
+
process.platform !== "win32" || !snapshot.exists ||
|
|
69
|
+
!["EEXIST", "EPERM", "EACCES"].includes(error && error.code)
|
|
70
|
+
) throw error;
|
|
71
|
+
}
|
|
72
|
+
assertEnvSnapshotUnchanged(file, snapshot);
|
|
73
|
+
const backup = `${file}.agentlas-${process.pid}-${crypto.randomUUID()}.bak`;
|
|
74
|
+
fs.renameSync(file, backup);
|
|
75
|
+
try {
|
|
76
|
+
fs.renameSync(temp, file);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
try { if (!fs.existsSync(file)) fs.renameSync(backup, file); } catch { /* leave the backup recoverable */ }
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
try { fs.rmSync(backup, { force: true }); } catch { /* committed target is authoritative */ }
|
|
82
|
+
}
|
|
11
83
|
|
|
12
84
|
/** .env 파싱 — 값 보존 없이 키만 필요할 때도 같은 파서를 쓴다 (KEY=VALUE, # 주석). */
|
|
13
85
|
function readDotEnvFile(file) {
|
|
14
86
|
const result = {};
|
|
15
87
|
let raw;
|
|
16
88
|
try {
|
|
17
|
-
raw =
|
|
89
|
+
raw = readEnvSnapshot(file).body;
|
|
18
90
|
} catch {
|
|
19
91
|
return result;
|
|
20
92
|
}
|
|
@@ -30,17 +102,30 @@ function readDotEnvFile(file) {
|
|
|
30
102
|
}
|
|
31
103
|
|
|
32
104
|
function upsertEnvLine(file, key, value) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
105
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(String(key || ""))) throw new Error("credential env key must look like ENV_NAME");
|
|
106
|
+
const text = String(value == null ? "" : value);
|
|
107
|
+
if (/[\u0000\r\n]/.test(text)) throw new Error("credential env value must be a single line; use `agentlas creds file` for multiline credentials");
|
|
108
|
+
const snapshot = readEnvSnapshot(file);
|
|
109
|
+
let body = snapshot.body;
|
|
110
|
+
const line = `${key}=${text}`;
|
|
36
111
|
const re = new RegExp("^" + key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "=.*$", "m");
|
|
37
|
-
|
|
112
|
+
// Replacement strings interpret `$&`, `$1`, and similar tokens. Credentials
|
|
113
|
+
// are opaque bytes, so use a function replacement and preserve them verbatim.
|
|
114
|
+
if (re.test(body)) body = body.replace(re, () => line);
|
|
38
115
|
else body = body ? body.replace(/\n?$/, "\n") + line + "\n" : line + "\n";
|
|
39
|
-
|
|
116
|
+
if (Buffer.byteLength(body, "utf8") > MAX_DOT_ENV_BYTES) throw new Error("credential env file exceeds the 512 KiB safety limit");
|
|
117
|
+
const directory = path.dirname(file);
|
|
118
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
119
|
+
const temp = path.join(directory, `.${path.basename(file)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
120
|
+
try {
|
|
121
|
+
fs.writeFileSync(temp, body, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
122
|
+
replaceEnvFileAtomic(temp, file, snapshot);
|
|
123
|
+
} finally {
|
|
124
|
+
try { fs.rmSync(temp, { force: true }); } catch { /* best-effort cleanup */ }
|
|
125
|
+
}
|
|
40
126
|
// 이 헬퍼의 모든 호출자는 credential 값/경로를 기록한다. 새 파일뿐 아니라 기존 0644
|
|
41
127
|
// 파일도 매번 0600으로 수렴시켜 같은 머신의 다른 계정이 읽지 못하게 한다.
|
|
42
|
-
fs.writeFileSync(file, body, { encoding: "utf8", mode: 0o600 });
|
|
43
128
|
try { fs.chmodSync(file, 0o600); } catch { /* Windows/읽기전용 FS best-effort */ }
|
|
44
129
|
}
|
|
45
130
|
|
|
46
|
-
module.exports = { readDotEnvFile, upsertEnvLine };
|
|
131
|
+
module.exports = { MAX_DOT_ENV_BYTES, readDotEnvFile, upsertEnvLine };
|
|
@@ -21,9 +21,9 @@ const { captureCoreJsonSync, resolveContextMapCoreRoot } = require("../agentlas-
|
|
|
21
21
|
const terminalMemoryGovernance = require("../agentlas-memory-governance.cjs");
|
|
22
22
|
const terminalExperienceIntake = require("../agentlas-experience-intake.cjs");
|
|
23
23
|
const terminalExperienceExchange = require("../agentlas-experience-exchange.cjs");
|
|
24
|
+
const permissions = require("../agentlas-permissions.cjs");
|
|
24
25
|
const { routesMap } = require("../agents/routes.cjs");
|
|
25
26
|
const { agentFolder } = require("../agents/files.cjs");
|
|
26
|
-
const { ensureProjectMemoryCli } = require("./seed.cjs");
|
|
27
27
|
const { projectCwd } = require("./paths.cjs");
|
|
28
28
|
|
|
29
29
|
const SECRET_RE = [/\b(?:sk|pk|rk)-[A-Za-z0-9]{16,}/, /AKIA[0-9A-Z]{16}/, /ghp_[A-Za-z0-9]{20,}/, /xox[baprs]-[A-Za-z0-9-]{10,}/, /-----BEGIN [A-Z ]*PRIVATE KEY-----/, /\b(?:password|passwd|secret|api[_-]?key|access[_-]?token|bearer)\b\s*[:=]\s*\S+/i];
|
|
@@ -50,16 +50,7 @@ function langDirective(lang) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function logCli(projectPath, rec) {
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
// 0.9.10 경계: 초기화되지 않은 프로젝트에 시드를 만들지 않는다. 로그는
|
|
56
|
-
// 이미 존재하는 .agentlas/ 에만 덧붙인다 (seed는 project init 전용).
|
|
57
|
-
const { initializedAgentlasProjectPathCli } = require("./state.cjs");
|
|
58
|
-
if (!initializedAgentlasProjectPathCli(projectPath)) return;
|
|
59
|
-
const dir = ensureProjectMemoryCli(projectPath);
|
|
60
|
-
if (!dir) return;
|
|
61
|
-
fs.appendFileSync(path.join(dir, loadArch().logFile || "memory-log.jsonl"), JSON.stringify(rec) + "\n", "utf8");
|
|
62
|
-
} catch { /* ignore */ }
|
|
53
|
+
return require("../memory-cli/curate.cjs").logCli(projectPath, rec);
|
|
63
54
|
}
|
|
64
55
|
|
|
65
56
|
function coerceText(v, max) {
|
|
@@ -242,51 +233,7 @@ function parseMemoryEventsCli(text) {
|
|
|
242
233
|
}
|
|
243
234
|
|
|
244
235
|
function curateCliReply(db, text, ctx) {
|
|
245
|
-
|
|
246
|
-
const style = require("../agentlas-style.cjs");
|
|
247
|
-
// read 권한: 어떤 durable write도 없이 숨김 메타데이터만 제거해 돌려준다.
|
|
248
|
-
if (ctx && ctx.permission === "read") return style.sanitizeAssistantText(cleaned);
|
|
249
|
-
if (!events.length || !tableExists(db, "memory_entries")) return style.sanitizeAssistantText(cleaned);
|
|
250
|
-
ensureMemoryContextColumn(db);
|
|
251
|
-
const arch = loadArch();
|
|
252
|
-
const { randomUUID } = require("node:crypto");
|
|
253
|
-
const now = new Date().toISOString();
|
|
254
|
-
const rememberCurated = (memory) => {
|
|
255
|
-
if (!ctx || !Array.isArray(ctx.curatedMemories) || !memory) return;
|
|
256
|
-
if (!ctx.curatedMemories.some((item) => item.id === memory.id)) ctx.curatedMemories.push(memory);
|
|
257
|
-
};
|
|
258
|
-
const kinds = Array.isArray(arch.kinds) ? arch.kinds : [];
|
|
259
|
-
const scopes = Array.isArray(arch.scopes) ? arch.scopes : [];
|
|
260
|
-
for (const ev of events) {
|
|
261
|
-
const content = ev && typeof ev.content === "string" ? ev.content.trim() : "";
|
|
262
|
-
if (!content) continue;
|
|
263
|
-
if (ev.sensitivity === "secret" || SECRET_RE.some((re) => re.test(content))) continue;
|
|
264
|
-
const kind = kinds.includes(ev.memory_kind) ? ev.memory_kind : "fact";
|
|
265
|
-
let scope = ev.suggested_scope === "agent_team"
|
|
266
|
-
? "team_memory"
|
|
267
|
-
: scopes.includes(ev.suggested_scope) ? ev.suggested_scope : "session";
|
|
268
|
-
const kindAllowsUserIdentity = ["fact", "decision", "preference", "procedure"].includes(kind);
|
|
269
|
-
if (scope === "user_identity" && (ev.confidence !== "high" || !kindAllowsUserIdentity)) scope = "session";
|
|
270
|
-
if (scope === "discard" || scope === "session") { logCli(ctx.projectPath, { action: scope, kind, content, at: now }); continue; }
|
|
271
|
-
if (scope === "project" && !ctx.projectPath) scope = "team_memory";
|
|
272
|
-
const ppath = scope === "project" ? ctx.projectPath : null;
|
|
273
|
-
const scopedAgentId = scope === "agent_repo" ? (ctx.agentId || null) : null;
|
|
274
|
-
const requestContext = normalizeRequestContext(ev, ctx, ppath);
|
|
275
|
-
try {
|
|
276
|
-
const dup = db.prepare("SELECT id,scope,kind,content,confidence,sensitivity,context_json FROM memory_entries WHERE scope=? AND kind=? AND lower(trim(content))=? AND superseded_at IS NULL AND (project_path IS ? OR project_path=?) AND (agent_id IS ? OR agent_id=?) LIMIT 1").get(scope, kind, content.toLowerCase(), ppath, ppath, scopedAgentId, scopedAgentId);
|
|
277
|
-
if (dup) {
|
|
278
|
-
rememberCurated({ ...dup, requestContext });
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const memoryId = randomUUID();
|
|
282
|
-
const confidence = ev.confidence || "medium";
|
|
283
|
-
const sensitivity = ev.sensitivity || "internal";
|
|
284
|
-
db.prepare("INSERT INTO memory_entries (id,scope,kind,content,project_id,project_path,agent_id,chat_id,confidence,sensitivity,evidence_json,context_json,superseded_at,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,NULL,?)").run(memoryId, scope, kind, content, ctx.projectId || null, ppath, scopedAgentId, null, confidence, sensitivity, JSON.stringify(Array.isArray(ev.evidence_refs) ? ev.evidence_refs : []), JSON.stringify(requestContext), now);
|
|
285
|
-
rememberCurated({ id: memoryId, scope, kind, content, confidence, sensitivity, requestContext });
|
|
286
|
-
logCli(ctx.projectPath, { action: "written", scope, kind, content, request_context: requestContext, at: now });
|
|
287
|
-
} catch { /* ignore */ }
|
|
288
|
-
}
|
|
289
|
-
return style.sanitizeAssistantText(cleaned);
|
|
236
|
+
return require("../memory-cli/curate.cjs").curateCliReply(db, text, ctx);
|
|
290
237
|
}
|
|
291
238
|
|
|
292
239
|
const TERMINAL_MEMORY_CORE_MAX_TOKENS = 150;
|
|
@@ -382,9 +329,16 @@ function exactAgentBaseForExecution(db, agent, runtimeExperience = null) {
|
|
|
382
329
|
const packageHash = /^[a-f0-9]{64}$/.test(rawHash) ? `sha256:${rawHash}` : null;
|
|
383
330
|
const explicitDefinition = String(runtimeExperience?.agentDefinitionId || "");
|
|
384
331
|
const explicitRelease = String(runtimeExperience?.baseAgentReleaseId || "");
|
|
332
|
+
const hasExplicitBinding = !!runtimeExperience && (
|
|
333
|
+
Object.prototype.hasOwnProperty.call(runtimeExperience, "agentDefinitionId") ||
|
|
334
|
+
Object.prototype.hasOwnProperty.call(runtimeExperience, "baseAgentReleaseId")
|
|
335
|
+
);
|
|
385
336
|
if (portableId.test(explicitDefinition) && portableId.test(explicitRelease)) {
|
|
386
337
|
return { agentDefinitionId: explicitDefinition, agentReleaseId: explicitRelease, packageHash, authority: "explicit-runtime-binding" };
|
|
387
338
|
}
|
|
339
|
+
// A partially supplied runtime binding is an attempted exact authority, not
|
|
340
|
+
// permission to fall through to an unrelated installed/local identity.
|
|
341
|
+
if (hasExplicitBinding) return null;
|
|
388
342
|
if (binding && portableId.test(String(binding.agent_definition_id)) && portableId.test(String(binding.agent_release_id))) {
|
|
389
343
|
return { agentDefinitionId: binding.agent_definition_id, agentReleaseId: binding.agent_release_id, packageHash, authority: "installed-hub-binding" };
|
|
390
344
|
}
|
|
@@ -400,7 +354,7 @@ function exactAgentBaseForExecution(db, agent, runtimeExperience = null) {
|
|
|
400
354
|
}
|
|
401
355
|
|
|
402
356
|
function finalizeExperienceExecutionCli(db, input) {
|
|
403
|
-
if (input.permission === "read") return null;
|
|
357
|
+
if (permissions.normalize(input.permission) === "read") return null;
|
|
404
358
|
if (!input.agentId) return null;
|
|
405
359
|
let agent;
|
|
406
360
|
try { agent = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(input.agentId); }
|
|
@@ -20,23 +20,31 @@ const { initializedAgentlasProjectPathCli } = require("./state.cjs");
|
|
|
20
20
|
|
|
21
21
|
const ONTOLOGY_SUPPORTED_EXTS = new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv"]);
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
function parseFlagsCli(args) {
|
|
23
|
+
/** Ontology source registration options. Unknown/duplicate flags fail closed. */
|
|
24
|
+
function parseFlagsCli(args, schema = {}) {
|
|
25
25
|
const flags = { _: [] };
|
|
26
|
+
const values = new Set(schema.values || ["kind", "scope"]);
|
|
27
|
+
const booleans = new Set(schema.booleans || []);
|
|
28
|
+
let passthrough = false;
|
|
26
29
|
for (let i = 0; i < args.length; i++) {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
const token = String(args[i]);
|
|
31
|
+
if (passthrough) { flags._.push(token); continue; }
|
|
32
|
+
if (token === "--") { passthrough = true; continue; }
|
|
33
|
+
if (!token.startsWith("--")) { flags._.push(token); continue; }
|
|
34
|
+
const at = token.indexOf("=");
|
|
35
|
+
const key = token.slice(2, at > 2 ? at : undefined);
|
|
36
|
+
if (!values.has(key) && !booleans.has(key)) throw new Error(`unknown option: --${key}`);
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(flags, key)) throw new Error(`duplicate option: --${key}`);
|
|
38
|
+
if (booleans.has(key)) {
|
|
39
|
+
if (at > 2) throw new Error(`--${key} does not take a value`);
|
|
40
|
+
flags[key] = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const value = at > 2 ? token.slice(at + 1) : args[++i];
|
|
44
|
+
if (value === undefined || value === "" || (at <= 2 && String(value).startsWith("--"))) {
|
|
45
|
+
throw new Error(`--${key} requires a value`);
|
|
39
46
|
}
|
|
47
|
+
flags[key] = String(value);
|
|
40
48
|
}
|
|
41
49
|
return flags;
|
|
42
50
|
}
|
|
@@ -233,18 +241,24 @@ function resolveOntologyPathCli(value, cwd) {
|
|
|
233
241
|
function inferOntologyKindCli(value, text) {
|
|
234
242
|
const v = String(value || "").toLowerCase();
|
|
235
243
|
const hay = String(text || "").toLowerCase();
|
|
236
|
-
if (["company", "work", "business", "corp", "team", "회사", "업무", "팀", "조직"].includes(v)
|
|
237
|
-
if (["personal", "private-life", "life", "me", "개인", "내자료", "일상"].includes(v)
|
|
238
|
-
if (["project", "repo", "프로젝트", "레포"].includes(v)
|
|
244
|
+
if (["company", "work", "business", "corp", "team", "회사", "업무", "팀", "조직"].includes(v)) return "company";
|
|
245
|
+
if (["personal", "private-life", "life", "me", "개인", "내자료", "일상"].includes(v)) return "personal";
|
|
246
|
+
if (["project", "repo", "프로젝트", "레포"].includes(v)) return "project";
|
|
247
|
+
if (/(company|work|business|corp|team|회사|업무|조직)/i.test(hay)) return "company";
|
|
248
|
+
if (/(personal|private-life|\bme\b|개인|내\s*자료|일상)/i.test(hay)) return "personal";
|
|
249
|
+
if (/(project|repo|프로젝트|레포)/i.test(hay)) return "project";
|
|
239
250
|
return "project";
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
function inferOntologyScopeCli(value, text, kind) {
|
|
243
254
|
const v = String(value || "").toLowerCase();
|
|
244
255
|
const hay = String(text || "").toLowerCase();
|
|
245
|
-
if (["public", "open", "공개"].includes(v)
|
|
246
|
-
if (["internal", "team", "내부", "팀"].includes(v)
|
|
247
|
-
if (["private", "secret", "local", "비공개", "개인"].includes(v)
|
|
256
|
+
if (["public", "open", "공개"].includes(v)) return "public";
|
|
257
|
+
if (["internal", "team", "내부", "팀"].includes(v)) return "internal";
|
|
258
|
+
if (["private", "secret", "local", "비공개", "개인"].includes(v)) return "private";
|
|
259
|
+
if (/(public|open|공개)/i.test(hay)) return "public";
|
|
260
|
+
if (/(internal|team-only|company-wide|내부|팀\s*공유|회사\s*공유)/i.test(hay)) return "internal";
|
|
261
|
+
if (/(private|secret|local-only|비공개|개인만|나만)/i.test(hay)) return "private";
|
|
248
262
|
return kind === "company" || kind === "personal" ? "private" : "private";
|
|
249
263
|
}
|
|
250
264
|
|
|
@@ -366,7 +380,7 @@ function formatOntologyStatusCli(paths, lang) {
|
|
|
366
380
|
lines.push("", `${ko ? "소스" : "Sources"} (${sources.length}):`);
|
|
367
381
|
for (const source of sources) {
|
|
368
382
|
const sourcePath = path.resolve(String(source.path || ""));
|
|
369
|
-
lines.push(` ${fs.existsSync(sourcePath) ? "✓" : "!"} ${sourcePath} ${source.kind || "project"} / ${source.scope || "
|
|
383
|
+
lines.push(` ${fs.existsSync(sourcePath) ? "✓" : "!"} ${sourcePath} ${source.kind || "project"} / ${source.scope || "private"}`);
|
|
370
384
|
}
|
|
371
385
|
if (!sources.length) lines.push(ko ? " (없음)" : " (none)");
|
|
372
386
|
lines.push(
|
|
@@ -438,6 +452,7 @@ async function runOntologyCli(args, opts) {
|
|
|
438
452
|
const sub = normalizedArgs[0] || "status";
|
|
439
453
|
const passivePaths = ontologyPathsForCli(projectPath);
|
|
440
454
|
if (sub === "status" || sub === "list") {
|
|
455
|
+
if (normalizedArgs.length > 1) throw new Error(`usage: agentlas ontology ${sub}`);
|
|
441
456
|
if (!initializedAgentlasProjectPathCli(projectPath)) {
|
|
442
457
|
return [
|
|
443
458
|
ko ? "온톨로지: 초기화되지 않음" : "Ontology: not initialized",
|
|
@@ -448,7 +463,10 @@ async function runOntologyCli(args, opts) {
|
|
|
448
463
|
}
|
|
449
464
|
return formatOntologyStatusCli(passivePaths, opts.lang);
|
|
450
465
|
}
|
|
451
|
-
if (sub === "help" || sub === "--help" || sub === "-h")
|
|
466
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
467
|
+
if (normalizedArgs.length > 1) throw new Error("usage: agentlas ontology help");
|
|
468
|
+
return ontologyUsageLinesCli(opts.lang);
|
|
469
|
+
}
|
|
452
470
|
const directOntologyCommand = ["open", "add", "company", "personal", "project"].includes(String(sub).toLowerCase())
|
|
453
471
|
|| isOntologyPathishCli(sub, cwd, true);
|
|
454
472
|
if (!directOntologyCommand) {
|
|
@@ -456,6 +474,22 @@ async function runOntologyCli(args, opts) {
|
|
|
456
474
|
const parsed = await parseOntologyNaturalArgsCli(normalizedArgs.join(" "), cwd);
|
|
457
475
|
return runOntologyCli(parsed, opts);
|
|
458
476
|
}
|
|
477
|
+
let directFlags = null;
|
|
478
|
+
if (sub === "open") {
|
|
479
|
+
if (normalizedArgs.length !== 1) throw new Error("usage: agentlas ontology open");
|
|
480
|
+
} else if (sub === "add") {
|
|
481
|
+
directFlags = parseFlagsCli(normalizedArgs.slice(1));
|
|
482
|
+
if (directFlags._.length < 1 || directFlags._.length > 3) throw new Error("usage: agentlas ontology add <path> [kind] [scope] [--kind <kind>] [--scope <scope>]");
|
|
483
|
+
if (directFlags.kind !== undefined && directFlags._[1] !== undefined) throw new Error("kind was provided twice; use either the positional value or --kind");
|
|
484
|
+
if (directFlags.scope !== undefined && directFlags._[2] !== undefined) throw new Error("scope was provided twice; use either the positional value or --scope");
|
|
485
|
+
} else if (["company", "personal", "project"].includes(String(sub).toLowerCase())) {
|
|
486
|
+
directFlags = parseFlagsCli(normalizedArgs.slice(1), { values: ["scope"] });
|
|
487
|
+
if (directFlags._.length < 1 || directFlags._.length > 2) throw new Error(`usage: agentlas ontology ${sub} <path> [scope] [--scope <scope>]`);
|
|
488
|
+
if (directFlags.scope !== undefined && directFlags._[1] !== undefined) throw new Error("scope was provided twice; use either the positional value or --scope");
|
|
489
|
+
} else if (isOntologyPathishCli(sub, cwd, true)) {
|
|
490
|
+
directFlags = parseFlagsCli(normalizedArgs.slice(1));
|
|
491
|
+
if (directFlags._.length) throw new Error("unexpected positional argument after ontology source path");
|
|
492
|
+
}
|
|
459
493
|
const paths = ensureOntologyCli(projectPath, opts.lang);
|
|
460
494
|
if (sub === "open") {
|
|
461
495
|
const opened = opts.noOpen ? true : openLocalPathCli(paths.inboxPath, opts.notify);
|
|
@@ -464,20 +498,27 @@ async function runOntologyCli(args, opts) {
|
|
|
464
498
|
: (ko ? "온톨로지 수신함을 자동으로 열지 못했습니다. 직접 여세요" : "Could not open ontology inbox automatically; open it manually")}: ${paths.inboxPath}`];
|
|
465
499
|
}
|
|
466
500
|
if (sub === "add") {
|
|
467
|
-
const flags =
|
|
501
|
+
const flags = directFlags;
|
|
468
502
|
const source = flags._[0];
|
|
469
503
|
const kind = inferOntologyKindCli(flags.kind || flags._[1], normalizedArgs.join(" "));
|
|
470
504
|
const scope = inferOntologyScopeCli(flags.scope || flags._[2], normalizedArgs.join(" "), kind);
|
|
471
505
|
return registerOntologySourceCli(paths, source, kind, scope, cwd, opts.lang);
|
|
472
506
|
}
|
|
473
507
|
if (["company", "personal", "project"].includes(String(sub).toLowerCase())) {
|
|
474
|
-
const flags =
|
|
508
|
+
const flags = directFlags;
|
|
475
509
|
const kind = inferOntologyKindCli(sub, normalizedArgs.join(" "));
|
|
476
510
|
const scope = inferOntologyScopeCli(flags.scope || flags._[1], normalizedArgs.join(" "), kind);
|
|
477
511
|
return registerOntologySourceCli(paths, flags._[0], kind, scope, cwd, opts.lang);
|
|
478
512
|
}
|
|
479
513
|
if (isOntologyPathishCli(sub, cwd, true)) {
|
|
480
|
-
return registerOntologySourceCli(
|
|
514
|
+
return registerOntologySourceCli(
|
|
515
|
+
paths,
|
|
516
|
+
sub,
|
|
517
|
+
inferOntologyKindCli(directFlags.kind, normalizedArgs.join(" ")),
|
|
518
|
+
inferOntologyScopeCli(directFlags.scope, normalizedArgs.join(" "), directFlags.kind || "project"),
|
|
519
|
+
cwd,
|
|
520
|
+
opts.lang,
|
|
521
|
+
);
|
|
481
522
|
}
|
|
482
523
|
throw new Error(ko
|
|
483
524
|
? "사용법: /ontology status|list|open|add <경로>"
|
package/engine/project/paths.cjs
CHANGED
|
@@ -2,23 +2,50 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* project/paths — 프로젝트 경계 판정용 경로 헬퍼 (v1 runCwd/projectCwd 포팅).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 바꿔야 한다 (v1 monolith 9961–9981 라인이 원본 계약).
|
|
5
|
+
* 이 파일이 Terminal 전체의 단일 정본이다. Workforce와 Hephaestus가 각자 복제하면
|
|
6
|
+
* 폴백 보안 경계가 갈리므로 둘 다 여기의 함수를 그대로 export한다.
|
|
8
7
|
*/
|
|
9
8
|
const fs = require("node:fs");
|
|
10
9
|
const os = require("node:os");
|
|
11
10
|
const path = require("node:path");
|
|
12
11
|
const { userDataDir } = require("../core/paths.cjs");
|
|
13
12
|
|
|
13
|
+
let temporaryRunCwd = null;
|
|
14
|
+
|
|
15
|
+
function ensurePrivateDirectory(directory) {
|
|
16
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
17
|
+
const stat = fs.lstatSync(directory);
|
|
18
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
19
|
+
throw new Error("agent run folder is not a private directory");
|
|
20
|
+
}
|
|
21
|
+
if (process.platform !== "win32") {
|
|
22
|
+
fs.chmodSync(directory, 0o700);
|
|
23
|
+
if ((fs.statSync(directory).mode & 0o777) !== 0o700) {
|
|
24
|
+
throw new Error("agent run folder permissions could not be restricted");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return fs.realpathSync.native(directory);
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/** 에이전트 격리 실행용 전용 폴더 — "프로젝트 아님" 위치의 안전한 폴백. */
|
|
15
31
|
function runCwd() {
|
|
16
32
|
const dir = path.join(userDataDir(), "agent-cwd");
|
|
17
33
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
return ensurePrivateDirectory(dir);
|
|
35
|
+
} catch (primaryError) {
|
|
36
|
+
try {
|
|
37
|
+
if (!temporaryRunCwd) {
|
|
38
|
+
temporaryRunCwd = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-agent-cwd-"));
|
|
39
|
+
}
|
|
40
|
+
return ensurePrivateDirectory(temporaryRunCwd);
|
|
41
|
+
} catch (fallbackError) {
|
|
42
|
+
const error = new Error(
|
|
43
|
+
"Agentlas could not create a private agent working directory; refusing to use the home folder.",
|
|
44
|
+
);
|
|
45
|
+
error.code = "AGENTLAS_RUN_CWD_UNAVAILABLE";
|
|
46
|
+
error.cause = fallbackError || primaryError;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
22
49
|
}
|
|
23
50
|
}
|
|
24
51
|
|
|
@@ -29,7 +56,14 @@ function runCwd() {
|
|
|
29
56
|
function projectCwd() {
|
|
30
57
|
try {
|
|
31
58
|
const cwd = process.cwd();
|
|
32
|
-
|
|
59
|
+
const neutral = runCwd();
|
|
60
|
+
const canonical = cwd ? fs.realpathSync.native(cwd) : "";
|
|
61
|
+
const home = fs.realpathSync.native(os.homedir());
|
|
62
|
+
let data = path.resolve(userDataDir());
|
|
63
|
+
try { data = fs.realpathSync.native(userDataDir()); } catch { /* compare the unresolved configured path */ }
|
|
64
|
+
if (!canonical || canonical === home || canonical === data || canonical === neutral || path.parse(canonical).root === canonical) {
|
|
65
|
+
return neutral;
|
|
66
|
+
}
|
|
33
67
|
return cwd;
|
|
34
68
|
} catch {
|
|
35
69
|
return runCwd();
|