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.
Files changed (145) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +319 -341
  3. package/bin/agentlas.cjs +32 -9
  4. package/engine/agentlas-banner.cjs +24 -3
  5. package/engine/agentlas-composer.cjs +239 -31
  6. package/engine/agentlas-config.cjs +103 -7
  7. package/engine/agentlas-core-harness.cjs +48 -4
  8. package/engine/agentlas-evolution.cjs +3 -4
  9. package/engine/agentlas-i18n.cjs +88 -32
  10. package/engine/agentlas-input.cjs +234 -18
  11. package/engine/agentlas-memory-governance.cjs +3 -5
  12. package/engine/agentlas-memory-import.cjs +3 -3
  13. package/engine/agentlas-native-host.cjs +200 -9
  14. package/engine/agentlas-onboard.cjs +112 -20
  15. package/engine/agentlas-permissions.cjs +14 -7
  16. package/engine/agentlas-sqlite-policy.cjs +34 -0
  17. package/engine/agentlas-ui.cjs +93 -42
  18. package/engine/agentlas-workforce.cjs +472 -58
  19. package/engine/agentlas-workload-routing.cjs +8 -3
  20. package/engine/agentlas.cjs +140 -12367
  21. package/engine/agents/files.cjs +61 -0
  22. package/engine/agents/import-local.cjs +237 -0
  23. package/engine/agents/registry.cjs +158 -0
  24. package/engine/agents/router.cjs +565 -0
  25. package/engine/agents/routes.cjs +43 -0
  26. package/engine/architecture.data.json +2 -2
  27. package/engine/automation/daemon.cjs +335 -0
  28. package/engine/automation/schedule.cjs +181 -0
  29. package/engine/automation/store.cjs +209 -0
  30. package/engine/cloud/auth.cjs +279 -0
  31. package/engine/cloud/hub-client.cjs +239 -0
  32. package/engine/cloud-assets/cargo.cjs +49 -0
  33. package/engine/cloud-assets/cas.cjs +235 -0
  34. package/engine/cloud-assets/commands.cjs +273 -0
  35. package/engine/cloud-assets/package.cjs +936 -0
  36. package/engine/cloud-assets/restore.cjs +172 -0
  37. package/engine/cloud-assets/state.cjs +268 -0
  38. package/engine/commands/automation.cjs +195 -0
  39. package/engine/commands/billing.cjs +96 -0
  40. package/engine/commands/browser.cjs +20 -0
  41. package/engine/commands/build.cjs +33 -0
  42. package/engine/commands/call.cjs +24 -0
  43. package/engine/commands/career-graph.cjs +51 -0
  44. package/engine/commands/cd.cjs +22 -0
  45. package/engine/commands/chat.cjs +12 -0
  46. package/engine/commands/chats.cjs +28 -0
  47. package/engine/commands/cloud.cjs +17 -0
  48. package/engine/commands/connect.cjs +20 -0
  49. package/engine/commands/context.cjs +66 -0
  50. package/engine/commands/creds.cjs +203 -0
  51. package/engine/commands/doctor.cjs +68 -0
  52. package/engine/commands/env.cjs +33 -0
  53. package/engine/commands/evolve.cjs +23 -0
  54. package/engine/commands/experience.cjs +34 -0
  55. package/engine/commands/film.cjs +8 -0
  56. package/engine/commands/firm.cjs +115 -0
  57. package/engine/commands/help.cjs +65 -0
  58. package/engine/commands/hep.cjs +23 -0
  59. package/engine/commands/import.cjs +35 -0
  60. package/engine/commands/index.cjs +136 -0
  61. package/engine/commands/install.cjs +27 -0
  62. package/engine/commands/journal.cjs +31 -0
  63. package/engine/commands/legacy-network.cjs +29 -0
  64. package/engine/commands/list.cjs +49 -0
  65. package/engine/commands/login.cjs +68 -0
  66. package/engine/commands/logout.cjs +28 -0
  67. package/engine/commands/mcp.cjs +91 -0
  68. package/engine/commands/memory.cjs +21 -0
  69. package/engine/commands/multimodal.cjs +91 -0
  70. package/engine/commands/native.cjs +34 -0
  71. package/engine/commands/netadmin.cjs +31 -0
  72. package/engine/commands/oberon.cjs +70 -0
  73. package/engine/commands/ontology.cjs +24 -0
  74. package/engine/commands/open.cjs +48 -0
  75. package/engine/commands/plugin.cjs +101 -0
  76. package/engine/commands/project.cjs +47 -0
  77. package/engine/commands/research.cjs +36 -0
  78. package/engine/commands/route.cjs +37 -0
  79. package/engine/commands/run.cjs +149 -0
  80. package/engine/commands/search.cjs +55 -0
  81. package/engine/commands/setup.cjs +45 -0
  82. package/engine/commands/storm.cjs +75 -0
  83. package/engine/commands/swarm.cjs +75 -0
  84. package/engine/commands/telegram.cjs +32 -0
  85. package/engine/commands/uninstall.cjs +68 -0
  86. package/engine/commands/update.cjs +54 -0
  87. package/engine/commands/upload.cjs +18 -0
  88. package/engine/commands/usage.cjs +35 -0
  89. package/engine/commands/variant.cjs +25 -0
  90. package/engine/commands/version.cjs +9 -0
  91. package/engine/commands/whoami.cjs +38 -0
  92. package/engine/commands/workforce.cjs +103 -0
  93. package/engine/core/db.cjs +160 -0
  94. package/engine/core/paths.cjs +35 -0
  95. package/engine/experience/build.cjs +181 -0
  96. package/engine/experience/intents.cjs +492 -0
  97. package/engine/experience/runtime.cjs +242 -0
  98. package/engine/experience/variant.cjs +196 -0
  99. package/engine/firms/orchestrate.cjs +333 -0
  100. package/engine/hephaestus/runtime.cjs +697 -0
  101. package/engine/hub/install.cjs +872 -0
  102. package/engine/hub/plugins.cjs +213 -0
  103. package/engine/mcp/consent.cjs +289 -0
  104. package/engine/mcp/contract.cjs +202 -0
  105. package/engine/mcp/index.cjs +43 -0
  106. package/engine/mcp/inventory.cjs +322 -0
  107. package/engine/mcp/plan.cjs +286 -0
  108. package/engine/mcp/probe.cjs +151 -0
  109. package/engine/memory-cli/curate.cjs +163 -0
  110. package/engine/oberon/common.cjs +69 -0
  111. package/engine/oberon/manifest.cjs +164 -0
  112. package/engine/oberon/outputs.cjs +70 -0
  113. package/engine/oberon/render.cjs +164 -0
  114. package/engine/project/career-graph.cjs +249 -0
  115. package/engine/project/credentials.cjs +262 -0
  116. package/engine/project/env-file.cjs +46 -0
  117. package/engine/project/index.cjs +27 -0
  118. package/engine/project/memory-context.cjs +453 -0
  119. package/engine/project/ontology.cjs +467 -0
  120. package/engine/project/paths.cjs +39 -0
  121. package/engine/project/seed.cjs +200 -0
  122. package/engine/project/state.cjs +403 -0
  123. package/engine/project/super-ontology-seed.json +3288 -0
  124. package/engine/runtimes/detect.cjs +54 -0
  125. package/engine/runtimes/overrides.cjs +139 -0
  126. package/engine/runtimes/resolve.cjs +64 -0
  127. package/engine/sessions/apply-fences.cjs +188 -0
  128. package/engine/sessions/fences.cjs +362 -0
  129. package/engine/sessions/orchestrator.cjs +170 -0
  130. package/engine/sessions/prompt.cjs +212 -0
  131. package/engine/sessions/session.cjs +245 -0
  132. package/engine/sessions/sink.cjs +54 -0
  133. package/engine/sessions/store.cjs +79 -0
  134. package/engine/storm/deps.cjs +88 -0
  135. package/engine/storm/storm.cjs +218 -0
  136. package/engine/storm/swarm.cjs +422 -0
  137. package/engine/ui/palette.cjs +105 -0
  138. package/engine/ui/renderer.cjs +85 -0
  139. package/engine/ui/repl.cjs +444 -0
  140. package/engine/workforce/capture.cjs +701 -0
  141. package/engine/workforce/deps.cjs +472 -0
  142. package/package.json +2 -6
  143. package/engine/agentlas-experience-mcp.cjs +0 -1709
  144. package/engine/agentlas-parity.cjs +0 -1499
  145. package/engine/agentlas-repl.cjs +0 -1780
@@ -0,0 +1,453 @@
1
+ "use strict";
2
+ /*
3
+ * project/memory-context — 프로젝트 메모리 주입/큐레이션 (v1 monolith 8285–8330,
4
+ * 8691–8925, 9044–9054, 9446–9495 포팅).
5
+ *
6
+ * 계약:
7
+ * - read 권한 실행은 어떤 durable 메모리도 쓰지 않는다: Memory Events 블록은
8
+ * 사용자 출력에서 제거만 하고 버린다 (curateCliReply), emitter 프롬프트도
9
+ * durable write를 요청하지 않는다 (augmentSystem withEmitter의 receipt-only).
10
+ * - 비밀(SECRET_RE 매치)은 메모리/로그에 절대 저장하지 않는다.
11
+ * - 컨텍스트 슬라이스는 Core 런타임이 있을 때만 붙는다 — 없으면 조용히 생략
12
+ * (시스템 프롬프트 보강은 best-effort). 명시적 `agentlas context` 명령의
13
+ * 정직 정지는 commands/context.cjs 가 소유한다.
14
+ */
15
+ const crypto = require("node:crypto");
16
+ const fs = require("node:fs");
17
+ const path = require("node:path");
18
+ const { userDataDir } = require("../core/paths.cjs");
19
+ const { loadArch, tableExists, columnExists } = require("../core/db.cjs");
20
+ const { captureCoreJsonSync, resolveCoreRuntimeRoot } = require("../agentlas-core-harness.cjs");
21
+ const terminalMemoryGovernance = require("../agentlas-memory-governance.cjs");
22
+ const terminalExperienceIntake = require("../agentlas-experience-intake.cjs");
23
+ const terminalExperienceExchange = require("../agentlas-experience-exchange.cjs");
24
+ const { routesMap } = require("../agents/routes.cjs");
25
+ const { agentFolder } = require("../agents/files.cjs");
26
+ const { ensureProjectMemoryCli } = require("./seed.cjs");
27
+ const { projectCwd } = require("./paths.cjs");
28
+
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];
30
+
31
+ function sha(value) {
32
+ return crypto.createHash("sha256").update(value).digest("hex");
33
+ }
34
+
35
+ function ensureMemoryContextColumn(db) {
36
+ try {
37
+ if (tableExists(db, "memory_entries") && !columnExists(db, "memory_entries", "context_json")) {
38
+ db.exec("ALTER TABLE memory_entries ADD COLUMN context_json TEXT NOT NULL DEFAULT '{}'");
39
+ }
40
+ } catch { /* ignore */ }
41
+ }
42
+
43
+ function prefsLangCli() {
44
+ try {
45
+ const prefs = require("../agentlas-config.cjs").loadPrefs(userDataDir());
46
+ return prefs.lang || prefs.language || "en";
47
+ } catch {
48
+ return "en";
49
+ }
50
+ }
51
+
52
+ function langDirective(lang) {
53
+ return require("../agentlas-style.cjs").responseDirective(lang);
54
+ }
55
+
56
+ function logCli(projectPath, rec) {
57
+ if (!projectPath) return;
58
+ try {
59
+ // 0.9.10 경계: 초기화되지 않은 프로젝트에 시드를 만들지 않는다. 로그는
60
+ // 이미 존재하는 .agentlas/ 에만 덧붙인다 (seed는 project init 전용).
61
+ const { initializedAgentlasProjectPathCli } = require("./state.cjs");
62
+ if (!initializedAgentlasProjectPathCli(projectPath)) return;
63
+ const dir = ensureProjectMemoryCli(projectPath);
64
+ if (!dir) return;
65
+ fs.appendFileSync(path.join(dir, loadArch().logFile || "memory-log.jsonl"), JSON.stringify(rec) + "\n", "utf8");
66
+ } catch { /* ignore */ }
67
+ }
68
+
69
+ function coerceText(v, max) {
70
+ if (typeof v !== "string") return undefined;
71
+ const s = v.trim();
72
+ return s ? s.slice(0, max) : undefined;
73
+ }
74
+ function coerceNullableText(v, max) {
75
+ if (v === null) return null;
76
+ return coerceText(v, max);
77
+ }
78
+ function normalizeRequestContext(ev, ctx, projectPath) {
79
+ const raw = ev && ev.request_context && typeof ev.request_context === "object" ? ev.request_context : {};
80
+ const triggerTerms = Array.isArray(raw.trigger_terms)
81
+ ? [...new Set(raw.trigger_terms.filter((x) => typeof x === "string").map((x) => x.trim()).filter(Boolean))]
82
+ .slice(0, 12)
83
+ .map((x) => x.slice(0, 40))
84
+ : undefined;
85
+ const cwd = coerceNullableText(raw.cwd_at_request, 500) ?? ctx.cwdAtRequest ?? ctx.cwd ?? ctx.projectPath ?? null;
86
+ const targetProject = coerceNullableText(raw.target_project, 120) ?? ctx.projectId ?? null;
87
+ const targetPath = coerceNullableText(raw.target_path, 500) ?? projectPath ?? null;
88
+ const out = {};
89
+ const userIntent = coerceText(raw.user_intent, 240);
90
+ const outcome = coerceNullableText(raw.outcome, 240);
91
+ if (userIntent) out.user_intent = userIntent;
92
+ if (triggerTerms && triggerTerms.length) out.trigger_terms = triggerTerms;
93
+ if (cwd !== undefined) out.cwd_at_request = cwd;
94
+ if (targetProject !== undefined) out.target_project = targetProject;
95
+ if (targetPath !== undefined) out.target_path = targetPath;
96
+ out.cross_context = typeof raw.cross_context === "boolean" ? raw.cross_context : !!(cwd && targetPath && cwd !== targetPath);
97
+ if (outcome !== undefined) out.outcome = outcome;
98
+ if (SECRET_RE.some((re) => re.test(JSON.stringify(out)))) return {};
99
+ return Object.keys(out).length ? out : {};
100
+ }
101
+ function contextLine(json) {
102
+ try {
103
+ const ctx = JSON.parse(json || "{}");
104
+ const parts = [
105
+ ctx.user_intent || ctx.userIntent,
106
+ (ctx.target_project || ctx.targetProject) ? `target:${ctx.target_project || ctx.targetProject}` : null,
107
+ Array.isArray(ctx.trigger_terms || ctx.triggerTerms) && (ctx.trigger_terms || ctx.triggerTerms).length
108
+ ? `terms:${(ctx.trigger_terms || ctx.triggerTerms).join(",")}`
109
+ : null,
110
+ ].filter(Boolean);
111
+ return parts.length ? ` (context: ${parts.join("; ").slice(0, 180)})` : "";
112
+ } catch {
113
+ return "";
114
+ }
115
+ }
116
+
117
+ function cliProjectContextSlice(projectPath, task) {
118
+ if (!projectPath || !String(task || "").trim()) return "";
119
+ try {
120
+ const coreRoot = resolveCoreRuntimeRoot();
121
+ if (!coreRoot) return "";
122
+ const result = captureCoreJsonSync(
123
+ "agentlas_cloud",
124
+ [
125
+ "context", "slice",
126
+ "--project", projectPath,
127
+ "--task-stdin",
128
+ "--no-refresh",
129
+ "--render",
130
+ ],
131
+ {
132
+ cwd: projectPath,
133
+ input: String(task || "").slice(0, 12_000),
134
+ timeout: 4_000,
135
+ },
136
+ coreRoot,
137
+ );
138
+ return result
139
+ && result.schemaVersion === "agentlas.context-slice.v1"
140
+ && typeof result.rendered === "string"
141
+ ? result.rendered.trim()
142
+ : "";
143
+ } catch {
144
+ return "";
145
+ }
146
+ }
147
+
148
+ function cliMemoryContext(db, projectPath, agentId = null, task = "") {
149
+ const sections = [];
150
+ const arch = loadArch();
151
+ ensureMemoryContextColumn(db);
152
+ if (projectPath) {
153
+ try {
154
+ const soulPath = path.join(projectPath, arch.memoryDir || ".agentlas", arch.soulFile || "project-soul-memory.md");
155
+ if (fs.existsSync(soulPath)) {
156
+ let s = fs.readFileSync(soulPath, "utf8");
157
+ if (s.length > 1800) s = s.slice(0, 1800) + "\n…(truncated)";
158
+ if (s.trim()) sections.push(`### Project memory (${projectPath})\n${s.trim()}`);
159
+ }
160
+ } catch { /* ignore */ }
161
+ const contextSlice = cliProjectContextSlice(projectPath, task);
162
+ if (contextSlice) sections.push(contextSlice);
163
+ }
164
+ if (tableExists(db, "memory_entries")) {
165
+ try {
166
+ // New writes are read through a scoped global<->project timeline. The
167
+ // project key is a digest, and team/agent lanes additionally require the
168
+ // current owner id, so project B cannot recall project A's local memory.
169
+ const governed = terminalMemoryGovernance.listScopedTimeline(db, {
170
+ projectPath,
171
+ agentId,
172
+ limit: 16,
173
+ });
174
+ const seen = new Set(governed.map((row) => row.id));
175
+ // Legacy rows predate the timeline. Keep only intentional user-global
176
+ // rows, this exact project, and this exact agent/team owner. In
177
+ // particular, do not revive the old global team-memory leakage query.
178
+ const legacy = projectPath
179
+ ? db.prepare(`
180
+ SELECT id,kind,content,context_json,created_at
181
+ FROM memory_entries
182
+ WHERE superseded_at IS NULL AND (
183
+ (scope='user_identity' AND project_path IS NULL)
184
+ OR (scope='project' AND project_path=?)
185
+ OR (scope IN ('team_memory','agent_team','agent_repo') AND agent_id=? AND (project_path IS NULL OR project_path=?))
186
+ )
187
+ ORDER BY created_at DESC LIMIT 16
188
+ `).all(projectPath, agentId, projectPath)
189
+ : db.prepare(`
190
+ SELECT id,kind,content,context_json,created_at
191
+ FROM memory_entries
192
+ WHERE superseded_at IS NULL AND (
193
+ (scope='user_identity' AND project_path IS NULL)
194
+ OR (scope IN ('team_memory','agent_team','agent_repo') AND agent_id=? AND project_path IS NULL)
195
+ )
196
+ ORDER BY created_at DESC LIMIT 16
197
+ `).all(agentId);
198
+ const rows = [...governed, ...legacy.filter((row) => !seen.has(row.id))].slice(0, 16);
199
+ if (rows.length) {
200
+ sections.push(
201
+ (projectPath ? "### Scoped global + current-project memory timeline\n" : "### Curated user-global memory\n") +
202
+ rows.map((r) => `- [${r.kind}] ${r.content}${contextLine(r.context_json)}`).join("\n"),
203
+ );
204
+ }
205
+ } catch { /* ignore */ }
206
+ }
207
+ if (!sections.length) return "";
208
+ return "## Agentlas memory (read before answering; governed scope recall)\n\n" + sections.join("\n\n");
209
+ }
210
+
211
+ function parseMemoryEventsCli(text) {
212
+ const heading = loadArch().eventsHeading || "## Memory Events";
213
+ const idx = text.lastIndexOf(heading);
214
+ if (idx < 0) return { events: [], cleaned: text.trim() };
215
+ const after = text.slice(idx + heading.length);
216
+ const fence = after.match(/```(?:json)?\s*([\s\S]*?)```/);
217
+ let events = [];
218
+ if (fence) { try { const d = JSON.parse(fence[1].trim()); if (Array.isArray(d)) events = d; } catch { /* ignore */ } }
219
+ let cut = text.length;
220
+ if (fence && fence.index != null) cut = idx + heading.length + fence.index + fence[0].length;
221
+ const before = text.slice(0, idx).replace(/<!--\s*$/u, "");
222
+ const remainder = text.slice(cut).replace(/^\s*-->/u, "");
223
+ return { events, cleaned: (before + remainder).trim() };
224
+ }
225
+
226
+ function curateCliReply(db, text, ctx) {
227
+ const { events, cleaned } = parseMemoryEventsCli(text);
228
+ const style = require("../agentlas-style.cjs");
229
+ // read 권한: 어떤 durable write도 없이 숨김 메타데이터만 제거해 돌려준다.
230
+ if (ctx && ctx.permission === "read") return style.sanitizeAssistantText(cleaned);
231
+ if (!events.length || !tableExists(db, "memory_entries")) return style.sanitizeAssistantText(cleaned);
232
+ ensureMemoryContextColumn(db);
233
+ const arch = loadArch();
234
+ const { randomUUID } = require("node:crypto");
235
+ const now = new Date().toISOString();
236
+ const rememberCurated = (memory) => {
237
+ if (!ctx || !Array.isArray(ctx.curatedMemories) || !memory) return;
238
+ if (!ctx.curatedMemories.some((item) => item.id === memory.id)) ctx.curatedMemories.push(memory);
239
+ };
240
+ const kinds = Array.isArray(arch.kinds) ? arch.kinds : [];
241
+ const scopes = Array.isArray(arch.scopes) ? arch.scopes : [];
242
+ for (const ev of events) {
243
+ const content = ev && typeof ev.content === "string" ? ev.content.trim() : "";
244
+ if (!content) continue;
245
+ if (ev.sensitivity === "secret" || SECRET_RE.some((re) => re.test(content))) continue;
246
+ const kind = kinds.includes(ev.memory_kind) ? ev.memory_kind : "fact";
247
+ let scope = ev.suggested_scope === "agent_team"
248
+ ? "team_memory"
249
+ : scopes.includes(ev.suggested_scope) ? ev.suggested_scope : "session";
250
+ const kindAllowsUserIdentity = ["fact", "decision", "preference", "procedure"].includes(kind);
251
+ if (scope === "user_identity" && (ev.confidence !== "high" || !kindAllowsUserIdentity)) scope = "session";
252
+ if (scope === "discard" || scope === "session") { logCli(ctx.projectPath, { action: scope, kind, content, at: now }); continue; }
253
+ if (scope === "project" && !ctx.projectPath) scope = "team_memory";
254
+ const ppath = scope === "project" ? ctx.projectPath : null;
255
+ const requestContext = normalizeRequestContext(ev, ctx, ppath);
256
+ try {
257
+ 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=?) LIMIT 1").get(scope, kind, content.toLowerCase(), ppath, ppath);
258
+ if (dup) {
259
+ rememberCurated({ ...dup, requestContext });
260
+ continue;
261
+ }
262
+ const memoryId = randomUUID();
263
+ const confidence = ev.confidence || "medium";
264
+ const sensitivity = ev.sensitivity || "internal";
265
+ 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, ctx.agentId || null, null, confidence, sensitivity, JSON.stringify(Array.isArray(ev.evidence_refs) ? ev.evidence_refs : []), JSON.stringify(requestContext), now);
266
+ rememberCurated({ id: memoryId, scope, kind, content, confidence, sensitivity, requestContext });
267
+ logCli(ctx.projectPath, { action: "written", scope, kind, content, request_context: requestContext, at: now });
268
+ } catch { /* ignore */ }
269
+ }
270
+ return style.sanitizeAssistantText(cleaned);
271
+ }
272
+
273
+ const TERMINAL_MEMORY_CORE_MAX_TOKENS = 150;
274
+ const TERMINAL_MEMORY_CORE = [
275
+ "## Memory governance",
276
+ "End every completed reply with hidden `## Memory Events` plus fenced JSON:",
277
+ '{"turn_id":"<stable-id>","observation":{"outcome":"completed","summary":"safe short outcome"},"candidates":[]}',
278
+ "Candidates 0..N: memory_kind,content,suggested_scope,confidence.",
279
+ "Scopes: user_global|team|agent|project|session|discard.",
280
+ "No raw prompts/transcripts, secrets, logs, or absolute paths. Curator suggests; deterministic gates decide writes.",
281
+ ].join("\n");
282
+ const MEMORY_DETAIL_RE = /\b(?:remember|memory|save this|record this|memory event)\b|기억|메모리|저장해|기록해|남겨/i;
283
+ const CREDENTIAL_INDEX_RE = /\b(?:deploy|release|billing|auth|oauth|credential|api key|secret key|cloud)\b|배포|릴리스|출시|결제|인증|자격 증명|API\s*키|시크릿|클라우드/i;
284
+
285
+ function approximatePromptTokens(text) {
286
+ return Math.ceil(Buffer.byteLength(String(text || ""), "utf8") / 3);
287
+ }
288
+ if (approximatePromptTokens(TERMINAL_MEMORY_CORE) > TERMINAL_MEMORY_CORE_MAX_TOKENS) {
289
+ throw new Error("Terminal always-on memory core exceeds 150 tokens");
290
+ }
291
+
292
+ function memoryEmitterPromptFor(request, arch = loadArch(), turnId = null, permission = "write") {
293
+ const stableId = String(turnId || "").replace(/[^A-Za-z0-9:._-]/g, "").slice(0, 160);
294
+ let prompt = TERMINAL_MEMORY_CORE;
295
+ if (stableId) prompt += `\nUse turn_id=${stableId}. permission=${permission === "read" ? "receipt-only" : "curated-write"}.`;
296
+ if (!MEMORY_DETAIL_RE.test(String(request || ""))) return prompt;
297
+ const kinds = Array.isArray(arch?.kinds) && arch.kinds.length ? arch.kinds.join("|") : "fact|decision|preference|risk|procedure";
298
+ prompt += [
299
+ "",
300
+ `Allowed memory_kind: ${kinds}.`,
301
+ "Global requires explicit owner authorization; suggest only, never promote.",
302
+ "Do not emit request_context; put only a safe, short outcome in observation.",
303
+ ].join("\n");
304
+ return prompt;
305
+ }
306
+
307
+ function credentialIndexReminderFor(request) {
308
+ if (!CREDENTIAL_INDEX_RE.test(String(request || ""))) return "";
309
+ return [
310
+ "## Local credential lookup (triggered)",
311
+ "Before saying a deploy, release, billing, auth, API, or cloud credential is missing, read `.agentlas/local-credentials.map.json` and the Local Credential Index in `.agentlas/project-soul-memory.md`.",
312
+ "Use only env names and local relative references; never copy credential values into memory or output.",
313
+ ].join("\n");
314
+ }
315
+
316
+ function loadGlobalConnectionSkill() {
317
+ try {
318
+ return require("../dist/electron/runtime/global-skill.js").GLOBAL_CONNECTION_SKILL || "";
319
+ } catch {
320
+ return [
321
+ "## Connecting accounts for the user (always-on background skill)",
322
+ "When the task needs Slack, Gmail, Google Cloud, Firebase, a database, store release access, or another third-party service, open the provider website with the browser tool when available, guide the user one tiny step at a time, and save the issued credential with `agentlas creds save --provider <name> --key <ENV_NAME> --value \"<value>\" --project \"<cwd>\"` or `agentlas creds file --source <path> --provider <name> --env <ENV_NAME> --project \"<cwd>\"`.",
323
+ "Do not lecture the user about security in this local single-user setup; make the connection effortless.",
324
+ ].join("\n\n");
325
+ }
326
+ }
327
+
328
+ function augmentSystem(db, baseSystem, ctx, withEmitter, request = "") {
329
+ const arch = loadArch();
330
+ let sys = baseSystem || "";
331
+ // 언어/말투 지시를 맨 앞에 둔다. imported/cloud/company agents도 같은 전역 계약을 따른다.
332
+ const lang = (ctx && ctx.lang) || prefsLangCli();
333
+ sys = langDirective(lang) + (sys ? "\n\n" + sys : "");
334
+ const connectionSkill = loadGlobalConnectionSkill();
335
+ if (connectionSkill) sys += "\n\n" + connectionSkill;
336
+ const mem = cliMemoryContext(db, ctx && ctx.projectPath, ctx && ctx.agentId, request);
337
+ if (mem) sys += "\n\n" + mem;
338
+ if (withEmitter) {
339
+ sys += "\n\n" + memoryEmitterPromptFor(request, arch, ctx && ctx.turnId, ctx && ctx.permission);
340
+ const credentialReminder = credentialIndexReminderFor(request);
341
+ if (credentialReminder) sys += "\n\n" + credentialReminder;
342
+ }
343
+ return sys;
344
+ }
345
+
346
+ function exactAgentBaseForExecution(db, agent, runtimeExperience = null) {
347
+ if (!agent || agent.builtin) return null;
348
+ const portableId = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{2,255}$/;
349
+ let binding = null;
350
+ try {
351
+ if (tableExists(db, "installed_agent_hub_bindings")) {
352
+ binding = db.prepare(
353
+ "SELECT agent_definition_id,agent_release_id FROM installed_agent_hub_bindings WHERE installed_agent_id=?",
354
+ ).get(agent.id) || null;
355
+ }
356
+ } catch { binding = null; }
357
+ const route = routesMap()[agent.id] || {};
358
+ const markerResult = terminalExperienceExchange.readExactLocalBaseMarker(agentFolder(agent), agent.slug);
359
+ const marker = markerResult.marker;
360
+ const rawHash = String(marker?.packageHash || route.packageHash || route.definitionHash || "").replace(/^sha256:/i, "").toLowerCase();
361
+ const packageHash = /^[a-f0-9]{64}$/.test(rawHash) ? `sha256:${rawHash}` : null;
362
+ const explicitDefinition = String(runtimeExperience?.agentDefinitionId || "");
363
+ const explicitRelease = String(runtimeExperience?.baseAgentReleaseId || "");
364
+ if (portableId.test(explicitDefinition) && portableId.test(explicitRelease)) {
365
+ return { agentDefinitionId: explicitDefinition, agentReleaseId: explicitRelease, packageHash, authority: "explicit-runtime-binding" };
366
+ }
367
+ if (binding && portableId.test(String(binding.agent_definition_id)) && portableId.test(String(binding.agent_release_id))) {
368
+ return { agentDefinitionId: binding.agent_definition_id, agentReleaseId: binding.agent_release_id, packageHash, authority: "installed-hub-binding" };
369
+ }
370
+ if (!packageHash) return null;
371
+ const definitionDigest = sha(`terminal-local-definition\0${agent.id}\0${agent.slug}`);
372
+ const releaseDigest = sha(`terminal-local-release\0${definitionDigest}\0${packageHash}`);
373
+ return {
374
+ agentDefinitionId: `local-agent-definition:${definitionDigest.slice(0, 32)}`,
375
+ agentReleaseId: `local-agent-release:${releaseDigest.slice(0, 32)}`,
376
+ packageHash,
377
+ authority: "exact-local-package-hash",
378
+ };
379
+ }
380
+
381
+ function finalizeExperienceExecutionCli(db, input) {
382
+ if (input.permission === "read") return null;
383
+ if (!input.agentId) return null;
384
+ let agent;
385
+ try { agent = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(input.agentId); }
386
+ catch { return null; }
387
+ if (!agent) return null;
388
+ const exactBase = exactAgentBaseForExecution(db, agent, input.runtimeExperience);
389
+ if (!exactBase) return null;
390
+ const runtime = input.runtime || {};
391
+ const provider = runtime.mode === "cli" ? runtime.kind : runtime.backend;
392
+ const modelId = input.model || runtime.model || provider;
393
+ const usage = input.usage || {};
394
+ try {
395
+ return terminalExperienceIntake.finalizeAgentExecution({
396
+ db,
397
+ userDataDir: userDataDir(),
398
+ cwd: input.cwd || input.projectPath || projectCwd(),
399
+ agent,
400
+ exactBase,
401
+ environment: { runtime: provider || "terminal", os: process.platform, arch: process.arch },
402
+ model: { provider: provider || "terminal-runtime", modelId: modelId || "terminal-runtime" },
403
+ mcp: (input.mcpServers || []).flatMap((server) => {
404
+ const catalogId = server.catalog_id || server.catalogId;
405
+ // A reviewed runtime allowlist proves approval, not that this turn's
406
+ // child completed an MCP initialize/tool call. Do not inflate it into
407
+ // connected evidence without an exact runtime signal.
408
+ return catalogId ? [{ catalogId, status: "approved" }] : [];
409
+ }),
410
+ outcome: input.outcome,
411
+ metrics: {
412
+ promptTokens: usage.input_tokens || usage.prompt_tokens || 0,
413
+ completionTokens: usage.output_tokens || usage.completion_tokens || 0,
414
+ totalTokens: usage.total_tokens || 0,
415
+ durationMs: input.durationMs || usage.duration_ms || 0,
416
+ retryCount: 0,
417
+ },
418
+ curatedMemories: input.curatedMemories || [],
419
+ taskHint: input.taskHint,
420
+ taskSignatures: input.runtimeExperience?.taskSignatures || [],
421
+ experiencePackReleaseId: input.runtimeExperience?.experiencePackReleaseIds?.[0] || null,
422
+ locale: input.lang || prefsLangCli(),
423
+ runId: input.runId,
424
+ createdAt: input.createdAt,
425
+ });
426
+ } catch (error) {
427
+ process.stderr.write(`▸ local Experience intake skipped · ${String((error && error.message) || error).slice(0, 180)}\n`);
428
+ return null;
429
+ }
430
+ }
431
+
432
+ module.exports = {
433
+ SECRET_RE,
434
+ TERMINAL_MEMORY_CORE,
435
+ TERMINAL_MEMORY_CORE_MAX_TOKENS,
436
+ ensureMemoryContextColumn,
437
+ prefsLangCli,
438
+ langDirective,
439
+ logCli,
440
+ normalizeRequestContext,
441
+ contextLine,
442
+ cliProjectContextSlice,
443
+ cliMemoryContext,
444
+ parseMemoryEventsCli,
445
+ curateCliReply,
446
+ approximatePromptTokens,
447
+ memoryEmitterPromptFor,
448
+ credentialIndexReminderFor,
449
+ loadGlobalConnectionSkill,
450
+ augmentSystem,
451
+ exactAgentBaseForExecution,
452
+ finalizeExperienceExecutionCli,
453
+ };