agentlas 1.0.60 → 1.0.61
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/engine/agentlas-memory-governance.cjs +88 -10
- package/engine/agentlas-permissions.cjs +95 -1
- package/engine/agentlas-tools.cjs +45 -2
- package/engine/agents/builder.cjs +4 -0
- package/engine/architecture.data.json +1 -1
- package/engine/bootstrap-schema.sql +188 -24
- package/engine/cloud-assets/cas.cjs +27 -1
- package/engine/cloud-assets/package.cjs +126 -0
- package/engine/cloud-assets/upload-scan-catalog.generated.cjs +13 -0
- package/engine/commands/career-graph.cjs +1 -1
- package/engine/commands/graph.cjs +6 -2
- package/engine/commands/index.cjs +4 -1
- package/engine/commands/one.cjs +307 -0
- package/engine/commands/ontology.cjs +2 -2
- package/engine/commands/plugin.cjs +61 -16
- package/engine/commands/uninstall.cjs +43 -13
- package/engine/core/capability-grants.cjs +204 -0
- package/engine/core/desktop-core.cjs +22 -0
- package/engine/experience/build.cjs +8 -0
- package/engine/graph/node-effect.cjs +56 -0
- package/engine/graph/package.cjs +6 -1
- package/engine/graph/vocabulary.generated.cjs +1 -1
- package/engine/hub/install.cjs +7 -3
- package/engine/hub/plugins.cjs +165 -0
- package/engine/mcp/consent.cjs +140 -12
- package/engine/mcp/index.cjs +1 -0
- package/engine/mcp/plan.cjs +63 -8
- package/engine/project/career-graph.cjs +5 -10
- package/engine/project/ontology.cjs +71 -29
- package/engine/sessions/memory-turn.cjs +39 -0
- package/engine/sessions/orchestrator.cjs +53 -4
- package/engine/sessions/session.cjs +10 -2
- package/engine/sessions/store.cjs +48 -12
- package/engine/ui/commands-catalog.cjs +1 -0
- package/engine/ui/repl.cjs +3 -1
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/package.json +4 -3
|
@@ -146,13 +146,53 @@ function normalizeOutcome(value) {
|
|
|
146
146
|
return "completed";
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
/*
|
|
150
|
+
* 2026-08-20: ownerPolicyFromPrompt(단어장 — remember/전역 등 regex AND) 제거.
|
|
151
|
+
* 전역 메모리 쓰기 권한을 부여하는 길은 둘뿐이다:
|
|
152
|
+
* 1) 호스트가 넘긴 구조화 플래그(beginTurn input.ownerPolicy) — 기계 표식이 우선.
|
|
153
|
+
* 2) 판정기(agentlas-judgment) 경유 — resolveGlobalWriteAuthorization.
|
|
154
|
+
* 판정 불가면 부여하지 않는다(fail-closed). 단어장은 어떤 언어도 다 못 세는 데다,
|
|
155
|
+
* 제3언어의 명시적 요청을 영구히 거부하고 우연한 단어 일치로 권한을 넓혔다.
|
|
156
|
+
*/
|
|
157
|
+
function normalizeOwnerPolicy(value) {
|
|
158
|
+
return { globalWriteAuthorized: Boolean(value && value.globalWriteAuthorized === true) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function resolveGlobalWriteAuthorization(prompt, options = {}) {
|
|
162
|
+
const text = String(prompt || "").trim();
|
|
163
|
+
if (!text) return { authorized: false, source: "unavailable" };
|
|
164
|
+
// 호스트가 판정 함수를 주입할 수 있다(세션이 자기 연결 런타임으로 감쌈).
|
|
165
|
+
if (typeof options.judge === "function") {
|
|
166
|
+
try {
|
|
167
|
+
const judged = await options.judge(text);
|
|
168
|
+
return judged && judged.source === "llm"
|
|
169
|
+
? { authorized: judged.authorized === true, source: "llm" }
|
|
170
|
+
: { authorized: false, source: "unavailable" };
|
|
171
|
+
} catch {
|
|
172
|
+
return { authorized: false, source: "unavailable" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
let judgment;
|
|
176
|
+
try {
|
|
177
|
+
judgment = options.judgment || require("./agentlas-judgment.cjs");
|
|
178
|
+
} catch {
|
|
179
|
+
return { authorized: false, source: "unavailable" };
|
|
180
|
+
}
|
|
181
|
+
if (!judgment.hasJudgmentRunner()) return { authorized: false, source: "unavailable" };
|
|
182
|
+
const verdict = await judgment.judgeLabels({
|
|
183
|
+
kind: "terminal-memory-global-write",
|
|
184
|
+
question:
|
|
185
|
+
"Does this request EXPLICITLY ask to save or remember something as a GLOBAL memory that applies across all projects (user profile / account-wide), rather than only this project, session, or task?",
|
|
186
|
+
labels: ["authorize_global_memory_write"],
|
|
187
|
+
input: text,
|
|
188
|
+
multi: false,
|
|
189
|
+
guidance:
|
|
190
|
+
"Authorize only an explicit, unambiguous request to persist a memory globally, in any language. Ordinary task prompts, project-scoped notes, or incidental mentions of memory do NOT authorize. When uncertain, select nothing.",
|
|
191
|
+
signal: options.signal,
|
|
192
|
+
timeoutMs: options.timeoutMs,
|
|
193
|
+
});
|
|
194
|
+
if (verdict.source !== "llm") return { authorized: false, source: "unavailable" };
|
|
195
|
+
return { authorized: verdict.labels.includes("authorize_global_memory_write"), source: "llm" };
|
|
156
196
|
}
|
|
157
197
|
|
|
158
198
|
function tableExists(db, name) {
|
|
@@ -245,7 +285,9 @@ function beginTurn(db, input = {}) {
|
|
|
245
285
|
const pKey = projectKey(input.projectPath);
|
|
246
286
|
const oKey = ownerKey(input.agentId);
|
|
247
287
|
const explicitTurnId = validTurnId(input.stableTurnId);
|
|
248
|
-
|
|
288
|
+
// 시작 시점 정책은 호스트 구조화 플래그만 반영한다(없으면 fail-closed false).
|
|
289
|
+
// 판정 경유 승격은 completeTurn에서, 실제로 user_global 후보가 나왔을 때만 1회 수행된다.
|
|
290
|
+
const policy = normalizeOwnerPolicy(input.ownerPolicy);
|
|
249
291
|
const conversationDigest = sha256(input.conversationRef || "none");
|
|
250
292
|
const priorDigest = sha256(input.priorContextDigest || input.priorContext || "none");
|
|
251
293
|
const contextKey = sha256(stableJson({
|
|
@@ -823,6 +865,28 @@ async function completeTurn(db, input = {}) {
|
|
|
823
865
|
};
|
|
824
866
|
}
|
|
825
867
|
|
|
868
|
+
// 전역 쓰기 승격은 필요할 때만 1회 — 어떤 후보가 실제로 user_global 스코프를
|
|
869
|
+
// 청했고, 시작 시점 정책(호스트 플래그)이 승인하지 않았을 때. 판정기(또는 호스트가
|
|
870
|
+
// 주입한 judge)가 명시적 요청이라고 판정한 경우에만 켠다. 판정 불가 = 부여 안 함.
|
|
871
|
+
if (
|
|
872
|
+
turn.ownerPolicy?.globalWriteAuthorized !== true
|
|
873
|
+
&& normalizePermission(turn.permission) !== "read"
|
|
874
|
+
&& parsed.candidates.some(
|
|
875
|
+
(candidate) => candidate.suggestedScope === "user_global" && candidate.preGateReasons.length === 0,
|
|
876
|
+
)
|
|
877
|
+
) {
|
|
878
|
+
const judged = await resolveGlobalWriteAuthorization(input.requestText, {
|
|
879
|
+
judge: input.judgeGlobalAuthorization,
|
|
880
|
+
});
|
|
881
|
+
if (judged.authorized === true && judged.source === "llm") {
|
|
882
|
+
turn.ownerPolicy = { ...turn.ownerPolicy, globalWriteAuthorized: true };
|
|
883
|
+
try {
|
|
884
|
+
db.prepare("UPDATE terminal_memory_turn_intents SET owner_policy_json=? WHERE turn_id=?")
|
|
885
|
+
.run(JSON.stringify(turn.ownerPolicy), turnId);
|
|
886
|
+
} catch { /* 감사 기록 실패가 이번 완결을 막지는 않는다 */ }
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
826
890
|
const payload = buildCuratorPayload(turn, parsed, input);
|
|
827
891
|
let curatorStatus = "unavailable";
|
|
828
892
|
let semantic = { status: "unavailable", decisions: new Map() };
|
|
@@ -878,7 +942,20 @@ async function completeTurn(db, input = {}) {
|
|
|
878
942
|
if (!dbScope) continue;
|
|
879
943
|
const scopedProjectId = decision.finalScope === "user_global" ? null : turn.projectKey;
|
|
880
944
|
const scopedProjectPath = decision.finalScope === "user_global" ? null : boundProjectPath;
|
|
881
|
-
|
|
945
|
+
/*
|
|
946
|
+
* ★팀 공유 기억에는 주인이 없다 (2026-08-26)
|
|
947
|
+
*
|
|
948
|
+
* 이 엔진은 데스크탑과 **같은 SQLite 파일의 같은 `memory_entries` 표**를 쓴다
|
|
949
|
+
* (engine/core/paths.cjs — 같은 userData 공유가 제품 계약이다). 그런데 같은 "팀 공유"
|
|
950
|
+
* 결정을 데스크탑은 `agent_id = NULL` 로, 여기서는 `agent_id = <agentId>` 로 넣고
|
|
951
|
+
* 있었다. 한 표에 두 관례가 섞이면 ① 같은 사실이 주인 다른 두 줄로 남아 중복 제거가
|
|
952
|
+
* 갈리고 ② 정리기가 그 줄을 개인 기억으로 오인한다.
|
|
953
|
+
*
|
|
954
|
+
* 정본은 데스크탑 쪽이다 — 팀 공유는 조직도가 바뀌어도 남아야 하므로 특정 에이전트에
|
|
955
|
+
* 매이지 않는다. 데스크탑의 같은 규칙: shared/memory-ownership.ts `memoryOwnerAgentId`
|
|
956
|
+
* (`agt_team_` 낙인이거나 신원이 없으면 개인 칸이 없다).
|
|
957
|
+
*/
|
|
958
|
+
const scopedAgentId = decision.finalScope === "agent" ? boundAgentId : null;
|
|
882
959
|
let memoryId = null;
|
|
883
960
|
try {
|
|
884
961
|
const duplicate = db.prepare(
|
|
@@ -1010,7 +1087,8 @@ module.exports = {
|
|
|
1010
1087
|
DEFAULT_EVENTS_HEADING,
|
|
1011
1088
|
CURATOR_SYSTEM_PROMPT,
|
|
1012
1089
|
ensureGovernanceSchema,
|
|
1013
|
-
|
|
1090
|
+
normalizeOwnerPolicy,
|
|
1091
|
+
resolveGlobalWriteAuthorization,
|
|
1014
1092
|
projectKey,
|
|
1015
1093
|
ownerKey,
|
|
1016
1094
|
contentGateReasons,
|
|
@@ -98,4 +98,98 @@ function createCycleController(options = {}) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
/*
|
|
102
|
+
* ── 통합 능력 승인(데스크탑 capability_grants)과의 합류 ───────────────────────
|
|
103
|
+
*
|
|
104
|
+
* 이 모듈은 오래도록 read/write/full 세 낱말만 알았다. 그런데 오너 결정(2026-08-20)
|
|
105
|
+
* 이후 "무엇을 해도 되는가"의 정본은 등급이 아니라 **행동 규칙**이다: 데스크탑에서
|
|
106
|
+
* "항상 허용"한 행동은 read 등급에서도 통과해야 하고, 영구 거부된 행동은 full 등급으로도
|
|
107
|
+
* 뚫리지 않아야 한다. 등급은 규칙이 없을 때의 기본값으로 남는다.
|
|
108
|
+
*
|
|
109
|
+
* 우선순위는 데스크탑 중재자(electron/ipc.ts setRuntimeToolPermissionArbiter)와 **같은
|
|
110
|
+
* 문장**이다. 갈리면 같은 행동에 두 제품이 다른 답을 준다:
|
|
111
|
+
* 1) 저장된 규칙 deny → deny (등급 무관)
|
|
112
|
+
* 2) 저장된 규칙 allow → allow (등급 무관)
|
|
113
|
+
* 3) permission=full → allow
|
|
114
|
+
* 4) 비변이(mutating=false) → allow
|
|
115
|
+
* 5) permission=write → allow
|
|
116
|
+
* 6) 그 외(read + 변이) → null = 경계를 넘는 요청. 호출부가 묻거나 거부한다.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/** 능력 규칙 모듈은 공유 DB 를 열므로 지연 로드한다(권한 어휘만 쓰는 호출부에 부담 금지). */
|
|
120
|
+
function grantsModule() {
|
|
121
|
+
return require("./core/capability-grants.cjs");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 한 번의 도구/서버 실행에 대한 판정.
|
|
126
|
+
*
|
|
127
|
+
* @param {object|null} db 공유 DB 핸들. 없으면 규칙을 못 읽고 등급 기본값만 쓴다.
|
|
128
|
+
* @param {object} ask { capability?, kind?, tool, detail?, agentId?, chatId?, mutating?, permission? }
|
|
129
|
+
* @returns {{decision:"allow"|"deny"|null, source:string, ruled:"allow"|"deny"|null,
|
|
130
|
+
* grantsAvailable:boolean, reason:string|null, capability:string}}
|
|
131
|
+
*/
|
|
132
|
+
function decideCapability(db, ask) {
|
|
133
|
+
const grants = grantsModule();
|
|
134
|
+
const capability = ask && ask.capability
|
|
135
|
+
? String(ask.capability)
|
|
136
|
+
: grants.capabilityClassFor(String((ask && ask.kind) || ""), String((ask && ask.tool) || ""));
|
|
137
|
+
const query = {
|
|
138
|
+
capability,
|
|
139
|
+
tool: ask && ask.tool ? String(ask.tool) : undefined,
|
|
140
|
+
detail: ask && ask.detail ? String(ask.detail) : undefined,
|
|
141
|
+
agentId: ask && ask.agentId ? String(ask.agentId) : undefined,
|
|
142
|
+
chatId: ask && ask.chatId ? String(ask.chatId) : undefined,
|
|
143
|
+
};
|
|
144
|
+
const ruling = db
|
|
145
|
+
? grants.readCapabilityDecision(db, query)
|
|
146
|
+
: { decision: null, available: false, reason: "no shared database handle was provided to the capability gate" };
|
|
147
|
+
|
|
148
|
+
if (ruling.decision === "deny") {
|
|
149
|
+
return { decision: "deny", source: "capability-grants", ruled: "deny", grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
150
|
+
}
|
|
151
|
+
if (ruling.decision === "allow") {
|
|
152
|
+
return { decision: "allow", source: "capability-grants", ruled: "allow", grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const level = normalize(ask && ask.permission);
|
|
156
|
+
const mutating = !!(ask && ask.mutating);
|
|
157
|
+
if (level === "full") {
|
|
158
|
+
return { decision: "allow", source: "permission-level", ruled: null, grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
159
|
+
}
|
|
160
|
+
if (!mutating) {
|
|
161
|
+
return { decision: "allow", source: "non-mutating", ruled: null, grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
162
|
+
}
|
|
163
|
+
if (level === "write") {
|
|
164
|
+
return { decision: "allow", source: "permission-level", ruled: null, grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
165
|
+
}
|
|
166
|
+
return { decision: null, source: "boundary", ruled: null, grantsAvailable: ruling.available, reason: ruling.reason, capability };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 터미널에서 사용자가 "항상 허용"을 골랐을 때 **같은 표**에 남긴다 — 데스크탑도 이
|
|
171
|
+
* 규칙을 읽으므로 다음부터 양쪽 모두 묻지 않는다. 규칙 키는 데스크탑 persistAlwaysGrant
|
|
172
|
+
* 와 동일: capability `tool:<name>` + 일반화된 인자 패턴 + scope global.
|
|
173
|
+
*/
|
|
174
|
+
function rememberAlwaysAllow(db, ask, options = {}) {
|
|
175
|
+
const grants = grantsModule();
|
|
176
|
+
return grants.recordCapabilityGrant(db, {
|
|
177
|
+
capability: `tool:${String((ask && ask.tool) || "")}`,
|
|
178
|
+
pattern: grants.generalizeDetailPattern(ask && ask.detail),
|
|
179
|
+
decision: options.decision === "deny" ? "deny" : "allow",
|
|
180
|
+
scope: options.scope || "global",
|
|
181
|
+
source: options.source || "terminal-chip",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
LEVELS,
|
|
187
|
+
isLevel,
|
|
188
|
+
normalize,
|
|
189
|
+
persistent,
|
|
190
|
+
next,
|
|
191
|
+
copy,
|
|
192
|
+
createCycleController,
|
|
193
|
+
decideCapability,
|
|
194
|
+
rememberAlwaysAllow,
|
|
195
|
+
};
|
|
@@ -322,12 +322,55 @@ function allowedTools(permission) {
|
|
|
322
322
|
return TOOLS.filter((t) => (PERM_RANK[t.minPerm] ?? 0) <= rank);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
/*
|
|
326
|
+
* ── 능력 규칙(공유 capability_grants)이 등급보다 먼저다 ──────────────────────
|
|
327
|
+
*
|
|
328
|
+
* 오너 결정(2026-08-20): 승인은 행동 기준이고 데스크탑·터미널이 **공유**한다.
|
|
329
|
+
* · 데스크탑에서 "항상 허용"한 행동 → 터미널에서 등급이 낮아도 통과(다시 묻지 않는다).
|
|
330
|
+
* · 데스크탑에서 영구 거부한 행동 → 터미널에서 full 권한이어도 거부.
|
|
331
|
+
* 규칙이 없을 때만 아래의 기존 등급 게이트가 답한다(기존 동작 그대로).
|
|
332
|
+
*
|
|
333
|
+
* ctx.db 가 없으면(단위 테스트·DB 없는 호출) 규칙을 못 읽으므로 종전 등급 게이트만 돈다.
|
|
334
|
+
*/
|
|
335
|
+
function toolAskFor(tool, args) {
|
|
336
|
+
const kind = tool.minPerm === "read" ? "read" : tool.name === "bash" ? "execute" : "edit";
|
|
337
|
+
const detail = tool.name === "bash"
|
|
338
|
+
? String((args && args.command) || "").trim()
|
|
339
|
+
: String((args && args.path) || "").trim();
|
|
340
|
+
return { tool: tool.name, kind, detail: detail || undefined, mutating: kind !== "read" };
|
|
341
|
+
}
|
|
342
|
+
|
|
325
343
|
// 툴 1개 실행 → { ok, content }. 권한 부족/에러는 ok:false 문자열로.
|
|
326
344
|
function runTool(name, args, ctx) {
|
|
327
345
|
const tool = BY_NAME[name];
|
|
328
346
|
if (!tool) return { ok: false, content: `unknown tool: ${name}` };
|
|
347
|
+
const ask = toolAskFor(tool, args);
|
|
348
|
+
let ruled = null;
|
|
349
|
+
if (ctx && ctx.db) {
|
|
350
|
+
try {
|
|
351
|
+
const permissions = require("./agentlas-permissions.cjs");
|
|
352
|
+
const verdict = permissions.decideCapability(ctx.db, {
|
|
353
|
+
...ask,
|
|
354
|
+
permission: ctx.permission,
|
|
355
|
+
agentId: ctx.agentId,
|
|
356
|
+
chatId: ctx.chatId,
|
|
357
|
+
});
|
|
358
|
+
ruled = verdict.ruled;
|
|
359
|
+
if (ruled === "deny") {
|
|
360
|
+
return {
|
|
361
|
+
ok: false,
|
|
362
|
+
content:
|
|
363
|
+
`capability denied: '${name}'${ask.detail ? ` (${ask.detail})` : ""} is permanently denied by a shared ` +
|
|
364
|
+
"capability rule (Desktop/Terminal share capability_grants). Remove that rule to allow it.",
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
// 규칙을 못 읽는 것이 허용이 되면 안 되고, 실행을 죽여서도 안 된다 — 기존 등급 게이트로 간다.
|
|
369
|
+
ruled = null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
329
372
|
const rank = PERM_RANK[ctx.permission] ?? 0;
|
|
330
|
-
if ((PERM_RANK[tool.minPerm] ?? 0) > rank) {
|
|
373
|
+
if (ruled !== "allow" && (PERM_RANK[tool.minPerm] ?? 0) > rank) {
|
|
331
374
|
return {
|
|
332
375
|
ok: false,
|
|
333
376
|
content: `permission denied: '${name}' requires '${tool.minPerm}' but current is '${ctx.permission}'. Ask the user to run /permission ${tool.minPerm}.`,
|
|
@@ -355,4 +398,4 @@ function openaiTools(permission) {
|
|
|
355
398
|
}));
|
|
356
399
|
}
|
|
357
400
|
|
|
358
|
-
module.exports = { TOOLS, BY_NAME, allowedTools, runTool, anthropicTools, openaiTools, PERM_RANK };
|
|
401
|
+
module.exports = { TOOLS, BY_NAME, allowedTools, runTool, anthropicTools, openaiTools, PERM_RANK, toolAskFor };
|
|
@@ -29,6 +29,10 @@ const BUILDER_SYSTEM_PROMPT = [
|
|
|
29
29
|
" - AGENTS.md — the agent's full system prompt / soul: who it is, what it does, how it behaves, its guardrails. Write it as the instructions the agent itself will run under. Be specific and production-ready, not a description of the agent.",
|
|
30
30
|
" - manifest.md — first line `# <Agent Name>`, second line a one-sentence tagline.",
|
|
31
31
|
" - README.md — a short human-facing summary of what the agent does and how to use it.",
|
|
32
|
+
" - agentlas.json — `{ \"schemaVersion\": \"1.0\", \"name\": \"<Agent Name>\", \"slug\": \"<kebab-case-slug>\", \"entry\": \"AGENTS.md\", \"skills\": [] }`. Do NOT invent an agentId; the identity is minted once by the packaging path and must never be typed by hand.",
|
|
33
|
+
"",
|
|
34
|
+
"If the agent needs reusable procedures, add them as `.claude/skills/<skill-name>/SKILL.md`.",
|
|
35
|
+
"A skill's name IS its folder name: the frontmatter `name:` must equal the folder exactly, and `agentlas.json` skills[] must list exactly those folder names. Never write a placeholder like `{{SKILL_ID_1}}` — leave the list empty instead.",
|
|
32
36
|
"",
|
|
33
37
|
"Rules:",
|
|
34
38
|
" - Decide the agent's scope from the request; if the request is thin, choose sensible, specific defaults and state them in README.md rather than asking endless questions.",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"systemPrompt": "# Task Bias Curator (Agentlas built-in)\n\nYou reduce TASK BIAS in multi-surface projects — the tendency to keep working on\nsurfaces that are recent, salient, or easy to measure while other surfaces stay\nuninspected. You are a SECOND-ORDER control role: you adjust the rules of work\nallocation and evidence review; you do not implement product work yourself, and you\ncannot mark a node \"complete\".\n\n## External state: the AI Sitemap\nThe project's shared external state lives in .agentlas/sitemap.json. Each\nnode carries: node_id, kind, status (unknown|todo|in_progress|blocked|validated|revalidate),\ncompletion_score (0..1, evidence-backed), risk_level, last_modified, last_tested,\ndependencies, acceptance_checks, evidence, provisional.\n\n## What you do\n1. Read/maintain the sitemap. Create provisional nodes for newly discovered surfaces.\n2. Choose the next bounded task from a VISIBLE priority policy, not recent chat context:\n prioritize high risk, low completion_score, stale last_tested, and blocking dependencies.\n3. Audit for bias: which surfaces are over-worked vs never inspected? Name them.\n4. Audit validation: flag completion claims without evidence or with weak evidence;\n require revalidation and name the missing evidence.\n5. Produce a compact, reversible curator decision record. Escalate mission-level changes\n to the user.\n\n## Boundaries\nCannot mark a node complete. Cannot erase evidence (only supersede it with a logged\ndecision). Cannot expand the project mission without explicit user approval.\n\nKeep outputs small: a policy/priority recommendation, a revalidation request, a\nsitemap update proposal, or a provisional-node decision."
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
|
-
"id": "builtin-agentlas-
|
|
119
|
+
"id": "builtin-agentlas-one",
|
|
120
120
|
"slug": "agentlas-one",
|
|
121
121
|
"name": "Agentlas One",
|
|
122
122
|
"nameEn": "Agentlas One",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
-- Agentlas 첫 실행 부트스트랩 스키마 (생성: 2026-08-
|
|
1
|
+
-- Agentlas 첫 실행 부트스트랩 스키마 (생성: 2026-08-26T07:56:09Z)
|
|
2
2
|
--
|
|
3
3
|
-- ★생성물이다. 손으로 고치지 말고 재생성하라:
|
|
4
4
|
-- node scripts/gen-bootstrap-schema.cjs
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
-- 정본은 Desktop 의 마이그레이션 사다리(agentlas_desktop/electron/store/db.ts, SCHEMA_VERSION).
|
|
7
7
|
-- 이 파일은 그 사다리를 **빈 DB** 에 끝까지 돌린 결과의 덤프이므로, 터미널이 만든 DB 는
|
|
8
8
|
-- 처음부터 사다리 머리에 있다 — 데스크탑이 나중에 승급할 것이 남지 않는다.
|
|
9
|
-
PRAGMA user_version=
|
|
9
|
+
PRAGMA user_version=104;
|
|
10
10
|
CREATE TABLE active_runtime (
|
|
11
11
|
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
12
12
|
kind TEXT NOT NULL
|
|
@@ -98,6 +98,22 @@ CREATE TABLE agent_evolution_receipts (
|
|
|
98
98
|
FOREIGN KEY(agent_id) REFERENCES installed_agents(id) ON DELETE CASCADE,
|
|
99
99
|
UNIQUE(proposal_id, action)
|
|
100
100
|
);
|
|
101
|
+
CREATE TABLE agent_identity_map (
|
|
102
|
+
-- CASCADE 다. 대응표는 **파생 데이터**이고 정본은 패키지의 agentId 다.
|
|
103
|
+
-- RESTRICT 로 두면 에이전트 삭제가 6곳에서 막힌다 — 사용자 삭제, 중복정리 2곳,
|
|
104
|
+
-- 설치 실패 롤백, One 멤버 생성 실패 롤백, 터미널 삭제. 롤백이 막히면
|
|
105
|
+
-- "설치 실패"가 "설치 실패 + 복구 실패 + 유령 행"이 된다.
|
|
106
|
+
local_id TEXT PRIMARY KEY REFERENCES installed_agents(id) ON DELETE CASCADE,
|
|
107
|
+
-- 이름을 agent_id 로 두면 agent-dedupe 의 컬럼명 스윕에 걸린다. 값이 달라
|
|
108
|
+
-- 지금은 매칭이 0건이지만, 이름 우연에 기대는 구조 자체가 지뢰다.
|
|
109
|
+
immutable_agent_id TEXT NOT NULL,
|
|
110
|
+
agent_version INTEGER NOT NULL DEFAULT 1,
|
|
111
|
+
-- package: 패키지 agentlas.json 에서 읽음 (정본)
|
|
112
|
+
-- builtin-reserved: 앱에 구워진 에이전트 — 패키지가 없어 예약 네임스페이스를 쓴다
|
|
113
|
+
-- minted-local: 출처가 없어 이 기기에서 발급 (다음에 패키지를 받으면 package 가 이긴다)
|
|
114
|
+
mapping_source TEXT NOT NULL,
|
|
115
|
+
bound_at TEXT NOT NULL
|
|
116
|
+
);
|
|
101
117
|
CREATE TABLE agent_mcp_servers (
|
|
102
118
|
agent_id TEXT NOT NULL,
|
|
103
119
|
server_id TEXT NOT NULL,
|
|
@@ -470,6 +486,16 @@ CREATE TABLE browser_sites (
|
|
|
470
486
|
created_at TEXT NOT NULL,
|
|
471
487
|
updated_at TEXT NOT NULL
|
|
472
488
|
);
|
|
489
|
+
CREATE TABLE capability_grants (
|
|
490
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
491
|
+
capability TEXT NOT NULL,
|
|
492
|
+
pattern TEXT,
|
|
493
|
+
decision TEXT NOT NULL CHECK(decision IN ('allow','deny')),
|
|
494
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
495
|
+
source TEXT NOT NULL DEFAULT 'chip',
|
|
496
|
+
created_at TEXT NOT NULL,
|
|
497
|
+
UNIQUE(capability, pattern, scope)
|
|
498
|
+
);
|
|
473
499
|
CREATE TABLE chat_goal_contracts (
|
|
474
500
|
goal_id TEXT PRIMARY KEY,
|
|
475
501
|
chat_id TEXT NOT NULL,
|
|
@@ -503,25 +529,44 @@ CREATE TABLE chat_messages (
|
|
|
503
529
|
created_at TEXT NOT NULL,
|
|
504
530
|
FOREIGN KEY(chat_id) REFERENCES chats(id) ON DELETE CASCADE
|
|
505
531
|
);
|
|
506
|
-
CREATE TABLE chat_runtime_sessions (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
532
|
+
CREATE TABLE "chat_runtime_sessions" (
|
|
533
|
+
chat_id TEXT NOT NULL,
|
|
534
|
+
kind TEXT NOT NULL,
|
|
535
|
+
agent_id TEXT NOT NULL DEFAULT '',
|
|
536
|
+
session_id TEXT NOT NULL,
|
|
537
|
+
fingerprint TEXT NOT NULL,
|
|
538
|
+
updated_at TEXT NOT NULL,
|
|
539
|
+
reported_output_tokens INTEGER,
|
|
540
|
+
reported_input_tokens INTEGER,
|
|
541
|
+
reported_cached_input_tokens INTEGER,
|
|
542
|
+
PRIMARY KEY (chat_id, kind, agent_id),
|
|
543
|
+
FOREIGN KEY(chat_id) REFERENCES chats(id) ON DELETE CASCADE
|
|
544
|
+
);
|
|
545
|
+
CREATE TABLE "chats" (
|
|
546
|
+
id TEXT PRIMARY KEY,
|
|
547
|
+
project_id TEXT,
|
|
548
|
+
title TEXT NOT NULL DEFAULT 'New chat',
|
|
549
|
+
created_at TEXT NOT NULL,
|
|
550
|
+
updated_at TEXT NOT NULL,
|
|
551
|
+
firm_id TEXT,
|
|
552
|
+
archived_at TEXT,
|
|
553
|
+
working_folder TEXT,
|
|
554
|
+
kind TEXT NOT NULL DEFAULT 'user',
|
|
555
|
+
parent_chat_id TEXT,
|
|
556
|
+
used_at TEXT,
|
|
557
|
+
continuous_mode INTEGER NOT NULL DEFAULT 0,
|
|
558
|
+
swarm_mode INTEGER NOT NULL DEFAULT 0,
|
|
559
|
+
last_viewed_at TEXT,
|
|
560
|
+
hired_agents TEXT,
|
|
561
|
+
origin_surface TEXT NOT NULL DEFAULT 'work',
|
|
562
|
+
runtime_selection_json TEXT,
|
|
563
|
+
goal_id TEXT,
|
|
564
|
+
seat_id TEXT,
|
|
565
|
+
agent_id TEXT, seat_label TEXT, seat_kind TEXT, participants_json TEXT,
|
|
566
|
+
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
|
567
|
+
FOREIGN KEY(seat_id) REFERENCES one_seats(id) ON DELETE CASCADE,
|
|
568
|
+
FOREIGN KEY(agent_id) REFERENCES installed_agents(id) ON DELETE SET NULL
|
|
569
|
+
);
|
|
525
570
|
CREATE TABLE experience_auto_intake_receipts (
|
|
526
571
|
id TEXT PRIMARY KEY,
|
|
527
572
|
agent_id TEXT NOT NULL,
|
|
@@ -979,6 +1024,100 @@ CREATE TABLE model_roles (
|
|
|
979
1024
|
updated_at TEXT NOT NULL,
|
|
980
1025
|
CHECK(role = 'worker' OR inherit = 0)
|
|
981
1026
|
);
|
|
1027
|
+
CREATE TABLE one_artifact_bindings (
|
|
1028
|
+
id TEXT PRIMARY KEY,
|
|
1029
|
+
task_id TEXT NOT NULL,
|
|
1030
|
+
task_version INTEGER NOT NULL,
|
|
1031
|
+
bound_task_version INTEGER NOT NULL,
|
|
1032
|
+
chat_id TEXT NOT NULL,
|
|
1033
|
+
run_id TEXT NOT NULL,
|
|
1034
|
+
manifest_id TEXT NOT NULL,
|
|
1035
|
+
artifact_ref TEXT NOT NULL,
|
|
1036
|
+
source_path TEXT NOT NULL,
|
|
1037
|
+
kind TEXT NOT NULL,
|
|
1038
|
+
mime_type TEXT NOT NULL,
|
|
1039
|
+
size_bytes INTEGER NOT NULL,
|
|
1040
|
+
file_dev TEXT NOT NULL,
|
|
1041
|
+
file_ino TEXT NOT NULL,
|
|
1042
|
+
file_mtime_ns TEXT NOT NULL,
|
|
1043
|
+
file_ctime_ns TEXT NOT NULL,
|
|
1044
|
+
sha256 TEXT NOT NULL,
|
|
1045
|
+
created_at TEXT NOT NULL,
|
|
1046
|
+
UNIQUE(task_id, chat_id, run_id, manifest_id, artifact_ref)
|
|
1047
|
+
);
|
|
1048
|
+
CREATE TABLE one_org_completion_cache (
|
|
1049
|
+
installed_agent_id TEXT PRIMARY KEY,
|
|
1050
|
+
run_id TEXT,
|
|
1051
|
+
summary_json TEXT NOT NULL,
|
|
1052
|
+
updated_at TEXT NOT NULL
|
|
1053
|
+
);
|
|
1054
|
+
CREATE TABLE one_org_members (
|
|
1055
|
+
id TEXT PRIMARY KEY,
|
|
1056
|
+
agent_slug TEXT NOT NULL,
|
|
1057
|
+
installed_agent_id TEXT NOT NULL,
|
|
1058
|
+
display_name TEXT,
|
|
1059
|
+
icon TEXT NOT NULL DEFAULT 'one-puppy',
|
|
1060
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
1061
|
+
source TEXT NOT NULL CHECK(source IN ('local','cloud','hub')),
|
|
1062
|
+
lease_expires_at TEXT,
|
|
1063
|
+
added_at TEXT NOT NULL,
|
|
1064
|
+
updated_at TEXT NOT NULL,
|
|
1065
|
+
archived_at TEXT,
|
|
1066
|
+
status_kind TEXT NOT NULL DEFAULT 'new',
|
|
1067
|
+
-- PRD §4.33 — 스키마에 사람이 읽는 문구(그것도 한 언어)를 박지 않는다.
|
|
1068
|
+
-- 빈 값이면 투영이 로케일 표에서 "아직 맡은 일 없음 / No work assigned yet"을 만든다.
|
|
1069
|
+
status_line TEXT NOT NULL DEFAULT '',
|
|
1070
|
+
last_activity_at TEXT,
|
|
1071
|
+
pending_count INTEGER NOT NULL DEFAULT 0,
|
|
1072
|
+
pending_kind TEXT NOT NULL DEFAULT 'approval' CHECK(pending_kind IN ('approval','review','input')),
|
|
1073
|
+
unread_count INTEGER NOT NULL DEFAULT 0,
|
|
1074
|
+
credit_state TEXT NOT NULL DEFAULT 'unknown' CHECK(credit_state IN ('ok','insufficient','unknown')),
|
|
1075
|
+
auto_select_tools INTEGER NOT NULL DEFAULT 1 CHECK(auto_select_tools IN (0,1)),
|
|
1076
|
+
collaboration_style TEXT NOT NULL DEFAULT 'default' CHECK(collaboration_style IN ('default','concise','warm','direct')),
|
|
1077
|
+
handover_note TEXT,
|
|
1078
|
+
revision INTEGER NOT NULL DEFAULT 1
|
|
1079
|
+
);
|
|
1080
|
+
CREATE TABLE one_seat_occupants (
|
|
1081
|
+
seat_id TEXT NOT NULL REFERENCES one_seats(id) ON DELETE CASCADE,
|
|
1082
|
+
slot INTEGER NOT NULL DEFAULT 0,
|
|
1083
|
+
agent_id TEXT,
|
|
1084
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
1085
|
+
since TEXT NOT NULL,
|
|
1086
|
+
until TEXT,
|
|
1087
|
+
PRIMARY KEY (seat_id, slot, since)
|
|
1088
|
+
);
|
|
1089
|
+
CREATE TABLE one_seats (
|
|
1090
|
+
id TEXT PRIMARY KEY,
|
|
1091
|
+
kind TEXT NOT NULL CHECK(kind IN ('solo','group')),
|
|
1092
|
+
title TEXT NOT NULL DEFAULT '',
|
|
1093
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1094
|
+
created_at TEXT NOT NULL,
|
|
1095
|
+
updated_at TEXT NOT NULL,
|
|
1096
|
+
archived_at TEXT
|
|
1097
|
+
, dissolved_at TEXT);
|
|
1098
|
+
CREATE TABLE one_taskforces (
|
|
1099
|
+
id TEXT PRIMARY KEY,
|
|
1100
|
+
chat_id TEXT NOT NULL UNIQUE,
|
|
1101
|
+
title TEXT NOT NULL,
|
|
1102
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1103
|
+
member_agent_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
1104
|
+
created_at TEXT NOT NULL,
|
|
1105
|
+
updated_at TEXT NOT NULL,
|
|
1106
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1107
|
+
FOREIGN KEY(chat_id) REFERENCES chats(id) ON DELETE CASCADE
|
|
1108
|
+
);
|
|
1109
|
+
CREATE TABLE plugin_builder_sessions (
|
|
1110
|
+
id TEXT PRIMARY KEY,
|
|
1111
|
+
chat_id TEXT NOT NULL,
|
|
1112
|
+
slug TEXT,
|
|
1113
|
+
phase TEXT NOT NULL CHECK(phase IN ('interview','draft','verify','install','prove','discarded')),
|
|
1114
|
+
staging_dir TEXT,
|
|
1115
|
+
answers_json TEXT,
|
|
1116
|
+
gate_report_json TEXT,
|
|
1117
|
+
seed_json TEXT NOT NULL,
|
|
1118
|
+
created_at TEXT NOT NULL,
|
|
1119
|
+
updated_at TEXT NOT NULL
|
|
1120
|
+
);
|
|
982
1121
|
CREATE TABLE project_agent_rent_allow (
|
|
983
1122
|
project_id TEXT NOT NULL,
|
|
984
1123
|
slug TEXT NOT NULL,
|
|
@@ -1123,7 +1262,7 @@ CREATE TABLE "telegram_bindings" (
|
|
|
1123
1262
|
designated_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1124
1263
|
designated_graph_id TEXT,
|
|
1125
1264
|
legacy_notice_at TEXT
|
|
1126
|
-
);
|
|
1265
|
+
, seat_id TEXT);
|
|
1127
1266
|
CREATE INDEX idx_agent_app_ops_app_created
|
|
1128
1267
|
ON agent_app_operations(app_id, created_at DESC);
|
|
1129
1268
|
CREATE INDEX idx_agent_apps_chat_updated
|
|
@@ -1142,6 +1281,8 @@ CREATE INDEX idx_agent_evolution_receipts_agent
|
|
|
1142
1281
|
ON agent_evolution_receipts(agent_id, created_at DESC);
|
|
1143
1282
|
CREATE INDEX idx_agent_evolution_receipts_proposal
|
|
1144
1283
|
ON agent_evolution_receipts(proposal_id, created_at ASC);
|
|
1284
|
+
CREATE INDEX idx_agent_identity_map_agent ON agent_identity_map(immutable_agent_id);
|
|
1285
|
+
CREATE INDEX idx_agent_identity_map_source ON agent_identity_map(mapping_source);
|
|
1145
1286
|
CREATE INDEX idx_agent_mcp_agent ON agent_mcp_servers(agent_id);
|
|
1146
1287
|
CREATE INDEX idx_agent_runtime_overrides_updated
|
|
1147
1288
|
ON agent_runtime_overrides(updated_at DESC);
|
|
@@ -1205,6 +1346,7 @@ CREATE INDEX idx_browser_logs_ts ON browser_action_logs(ts DESC);
|
|
|
1205
1346
|
CREATE UNIQUE INDEX idx_browser_perm_site_action
|
|
1206
1347
|
ON browser_permissions(site, action_type);
|
|
1207
1348
|
CREATE UNIQUE INDEX idx_browser_sessions_site ON browser_sessions(site);
|
|
1349
|
+
CREATE INDEX idx_capability_grants_scope ON capability_grants(scope);
|
|
1208
1350
|
CREATE UNIQUE INDEX idx_chat_goal_contracts_active_chat
|
|
1209
1351
|
ON chat_goal_contracts(chat_id)
|
|
1210
1352
|
WHERE status = 'active';
|
|
@@ -1217,6 +1359,7 @@ CREATE INDEX idx_chats_firm_updated ON chats(firm_id, updated_at DESC);
|
|
|
1217
1359
|
CREATE INDEX idx_chats_parent ON chats(parent_chat_id);
|
|
1218
1360
|
CREATE INDEX idx_chats_project_updated
|
|
1219
1361
|
ON chats(project_id, updated_at DESC);
|
|
1362
|
+
CREATE INDEX idx_chats_seat_updated ON chats(seat_id, updated_at DESC);
|
|
1220
1363
|
CREATE INDEX idx_chats_updated ON chats(updated_at DESC);
|
|
1221
1364
|
CREATE INDEX idx_chats_used_updated ON chats(used_at, updated_at DESC);
|
|
1222
1365
|
CREATE INDEX idx_experience_auto_intake_agent_status
|
|
@@ -1308,6 +1451,22 @@ CREATE INDEX idx_memory_tickets_project_created
|
|
|
1308
1451
|
ON memory_tickets(project_id, created_at DESC);
|
|
1309
1452
|
CREATE INDEX idx_memory_tickets_status_created
|
|
1310
1453
|
ON memory_tickets(emitter_status, state, created_at DESC);
|
|
1454
|
+
CREATE INDEX idx_occupants_seat_time ON one_seat_occupants(seat_id, since);
|
|
1455
|
+
CREATE INDEX idx_one_artifact_binding_chat
|
|
1456
|
+
ON one_artifact_bindings(chat_id, created_at);
|
|
1457
|
+
CREATE INDEX idx_one_artifact_binding_exact
|
|
1458
|
+
ON one_artifact_bindings(task_id, chat_id, run_id, manifest_id, artifact_ref);
|
|
1459
|
+
CREATE INDEX idx_one_org_members_agent
|
|
1460
|
+
ON one_org_members(installed_agent_id);
|
|
1461
|
+
CREATE INDEX idx_one_org_members_order
|
|
1462
|
+
ON one_org_members(archived_at, sort_order, added_at);
|
|
1463
|
+
CREATE INDEX idx_one_seats_updated ON one_seats(updated_at DESC);
|
|
1464
|
+
CREATE INDEX idx_one_taskforces_updated
|
|
1465
|
+
ON one_taskforces(updated_at DESC);
|
|
1466
|
+
CREATE INDEX idx_plugin_builder_sessions_chat_updated
|
|
1467
|
+
ON plugin_builder_sessions(chat_id, updated_at DESC);
|
|
1468
|
+
CREATE INDEX idx_plugin_builder_sessions_slug_phase
|
|
1469
|
+
ON plugin_builder_sessions(slug, phase);
|
|
1311
1470
|
CREATE INDEX idx_run_events_agent_kind_ts
|
|
1312
1471
|
ON run_events(agent_id, kind, ts DESC);
|
|
1313
1472
|
CREATE INDEX idx_run_events_agent_ts ON run_events(agent_id, ts DESC);
|
|
@@ -1320,6 +1479,10 @@ CREATE INDEX idx_run_events_run_seq
|
|
|
1320
1479
|
CREATE INDEX idx_run_events_ts
|
|
1321
1480
|
ON run_events(ts DESC);
|
|
1322
1481
|
CREATE INDEX idx_run_history_automation ON run_history(automation_id);
|
|
1482
|
+
CREATE INDEX idx_seat_occupants_agent
|
|
1483
|
+
ON one_seat_occupants(agent_id) WHERE agent_id IS NOT NULL;
|
|
1484
|
+
CREATE UNIQUE INDEX idx_seat_occupants_current
|
|
1485
|
+
ON one_seat_occupants(seat_id, slot) WHERE until IS NULL;
|
|
1323
1486
|
CREATE INDEX idx_task_participants_agent ON task_agent_participants(agent_id);
|
|
1324
1487
|
CREATE INDEX idx_tasks_firm_updated ON tasks(firm_id, updated_at DESC);
|
|
1325
1488
|
CREATE INDEX idx_tasks_origin_chat ON tasks(origin_chat_id);
|
|
@@ -1337,7 +1500,8 @@ CREATE INDEX idx_telegram_bindings_chat
|
|
|
1337
1500
|
ON telegram_bindings(telegram_chat_id);
|
|
1338
1501
|
CREATE INDEX idx_telegram_bindings_enabled
|
|
1339
1502
|
ON telegram_bindings(enabled, status);
|
|
1340
|
-
CREATE UNIQUE INDEX
|
|
1341
|
-
ON telegram_bindings(
|
|
1503
|
+
CREATE UNIQUE INDEX idx_telegram_bindings_one_room
|
|
1504
|
+
ON telegram_bindings(telegram_chat_id)
|
|
1505
|
+
WHERE target_kind = 'one' AND telegram_chat_id IS NOT NULL;
|
|
1342
1506
|
CREATE INDEX idx_telegram_bindings_target
|
|
1343
1507
|
ON telegram_bindings(target_kind, target_id);
|