agentlas 1.0.60 → 1.0.62
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 +18 -1
- package/engine/acp/server.cjs +47 -17
- package/engine/agentlas-evolution.cjs +85 -36
- package/engine/agentlas-experience-intake.cjs +21 -8
- package/engine/agentlas-memory-governance.cjs +88 -10
- package/engine/agentlas-permissions.cjs +95 -1
- package/engine/agentlas-tools.cjs +45 -2
- package/engine/agentlas.cjs +6 -3
- package/engine/agents/builder.cjs +4 -0
- package/engine/architecture.data.json +1 -1
- package/engine/automation/daemon.cjs +36 -3
- package/engine/automation/store.cjs +13 -11
- package/engine/bootstrap-schema.sql +216 -24
- package/engine/cloud/auth.cjs +34 -8
- 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/connect.cjs +34 -6
- package/engine/commands/graph.cjs +6 -2
- package/engine/commands/index.cjs +4 -1
- package/engine/commands/oberon.cjs +1 -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/commands/update.cjs +25 -4
- package/engine/core/capability-grants.cjs +204 -0
- package/engine/core/desktop-core-fetch.cjs +94 -21
- 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/hephaestus/local-core.cjs +44 -9
- 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/memory-cli/curate.cjs +5 -2
- package/engine/oberon/outputs.cjs +10 -3
- package/engine/project/career-graph.cjs +9 -12
- package/engine/project/memory-context.cjs +9 -6
- package/engine/project/ontology.cjs +88 -36
- package/engine/runtimes/acp-driver.cjs +42 -3
- package/engine/sessions/memory-turn.cjs +39 -0
- package/engine/sessions/orchestrator.cjs +53 -4
- package/engine/sessions/session.cjs +28 -2
- package/engine/sessions/store.cjs +48 -12
- package/engine/telegram/connect.cjs +31 -10
- package/engine/ui/commands-catalog.cjs +1 -0
- package/engine/ui/repl.cjs +3 -1
- package/engine/ui/screens.cjs +30 -6
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/package.json +6 -3
|
@@ -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",
|
|
@@ -185,9 +185,30 @@ async function runAutomationOnce(ctx, db, row, opts = {}) {
|
|
|
185
185
|
// 데스크탑 스케줄러는 60초마다 리스를 갱신한다. 여기는 한 번 잡고 끝이라
|
|
186
186
|
// TTL(15분)을 넘긴 실행은 프로세스가 살아 있어도 회수 대상이 됐다 — 같은
|
|
187
187
|
// 자동화가 두 실행기에서 겹쳐 도는 경로. 같은 주기로 심장박동을 보낸다.
|
|
188
|
+
let activeSession = null;
|
|
189
|
+
let leaseOwnershipLost = false;
|
|
190
|
+
let leaseRenewWarningEmitted = false;
|
|
188
191
|
const leaseHeartbeat = setInterval(() => {
|
|
189
|
-
try {
|
|
190
|
-
|
|
192
|
+
try {
|
|
193
|
+
const renewed = store.renewAutomationLease(db, row.id);
|
|
194
|
+
if (!renewed) {
|
|
195
|
+
leaseOwnershipLost = true;
|
|
196
|
+
if (activeSession && typeof activeSession.kill === "function") activeSession.kill();
|
|
197
|
+
} else {
|
|
198
|
+
leaseRenewWarningEmitted = false;
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// One SQLITE_BUSY/I/O miss is not ownership loss. Keep the run alive and
|
|
202
|
+
// retry on the next heartbeat, while surfacing the first deferred renewal.
|
|
203
|
+
if (!leaseRenewWarningEmitted) {
|
|
204
|
+
leaseRenewWarningEmitted = true;
|
|
205
|
+
const code = error && typeof error === "object" && "code" in error
|
|
206
|
+
? String(error.code || "transient")
|
|
207
|
+
: "transient";
|
|
208
|
+
ctx.err(`automation lease heartbeat deferred (${code.slice(0, 80)})`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}, Math.max(10, Number(opts.leaseHeartbeatMs) || 60_000));
|
|
191
212
|
if (typeof leaseHeartbeat.unref === "function") leaseHeartbeat.unref();
|
|
192
213
|
|
|
193
214
|
try {
|
|
@@ -244,9 +265,18 @@ async function runAutomationOnce(ctx, db, row, opts = {}) {
|
|
|
244
265
|
timeoutConfig: opts.timeoutConfig,
|
|
245
266
|
...(markerChatId ? { chatId: markerChatId } : {}),
|
|
246
267
|
});
|
|
268
|
+
activeSession = session;
|
|
269
|
+
if (leaseOwnershipLost) {
|
|
270
|
+
session.kill();
|
|
271
|
+
return { ok: false, skipped: true, reason: "lease-lost", error: "Automation execution lease ownership lost" };
|
|
272
|
+
}
|
|
247
273
|
if (typeof opts.onSession === "function") opts.onSession(session);
|
|
248
274
|
|
|
249
275
|
const res = await session.send(row.prompt_template);
|
|
276
|
+
if (leaseOwnershipLost) {
|
|
277
|
+
ctx.err("Automation execution lease ownership lost");
|
|
278
|
+
return { ok: false, skipped: true, reason: "lease-lost", error: "Automation execution lease ownership lost" };
|
|
279
|
+
}
|
|
250
280
|
const finalText = (res && (res.finalText || res.text)) || "";
|
|
251
281
|
const failed = session.status === "failed";
|
|
252
282
|
const errMsg = failed ? String(session.lastError || "runtime turn failed").slice(0, 500) : null;
|
|
@@ -265,12 +295,15 @@ async function runAutomationOnce(ctx, db, row, opts = {}) {
|
|
|
265
295
|
} catch (e) {
|
|
266
296
|
const msg = String((e && e.message) || e).slice(0, 500);
|
|
267
297
|
ctx.err(msg);
|
|
298
|
+
if (leaseOwnershipLost) {
|
|
299
|
+
return { ok: false, skipped: true, reason: "lease-lost", error: "Automation execution lease ownership lost" };
|
|
300
|
+
}
|
|
268
301
|
store.recordRun(db, row.id, "error", msg, opts.scheduledFor);
|
|
269
302
|
store.advanceAfterRun(db, row, { ok: false, advanceSchedule: !!opts.advanceSchedule });
|
|
270
303
|
return { ok: false, error: msg };
|
|
271
304
|
} finally {
|
|
272
305
|
clearInterval(leaseHeartbeat);
|
|
273
|
-
store.releaseAutomation(db, row.id);
|
|
306
|
+
store.releaseAutomation(db, row.id, store.LEASE_OWNER);
|
|
274
307
|
}
|
|
275
308
|
}
|
|
276
309
|
|
|
@@ -116,20 +116,22 @@ function claimAutomation(db, id, now = new Date(), owner = LEASE_OWNER) {
|
|
|
116
116
|
*/
|
|
117
117
|
function renewAutomationLease(db, id, now = new Date(), owner = LEASE_OWNER) {
|
|
118
118
|
if (!leaseSupported(db)) return false;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
119
|
+
// false means definitive ownership loss. SQLite busy/I/O errors must throw
|
|
120
|
+
// so the caller can retry instead of killing a valid run as if a peer had
|
|
121
|
+
// taken the lease (Desktop renewAutomationRunLease contract).
|
|
122
|
+
const result = db
|
|
123
|
+
.prepare("UPDATE automations SET claimed_at = ? WHERE id = ? AND lease_owner = ? AND claimed_at IS NOT NULL")
|
|
124
|
+
.run(now.toISOString(), id, owner);
|
|
125
|
+
return (result.changes ?? result.rowsAffected ?? 0) > 0;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
function releaseAutomation(db, id) {
|
|
128
|
+
function releaseAutomation(db, id, owner = LEASE_OWNER) {
|
|
130
129
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const result = db
|
|
131
|
+
.prepare("UPDATE automations SET claimed_at = NULL, lease_owner = NULL WHERE id = ? AND lease_owner = ?")
|
|
132
|
+
.run(id, owner);
|
|
133
|
+
return (result.changes ?? result.rowsAffected ?? 0) > 0;
|
|
134
|
+
} catch { return false; }
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
-- Agentlas 첫 실행 부트스트랩 스키마 (생성: 2026-08-
|
|
1
|
+
-- Agentlas 첫 실행 부트스트랩 스키마 (생성: 2026-08-29T07:55:23Z)
|
|
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=105;
|
|
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,
|
|
@@ -822,6 +867,20 @@ CREATE TABLE installed_agents (
|
|
|
822
867
|
installed_at TEXT NOT NULL,
|
|
823
868
|
tone TEXT NOT NULL
|
|
824
869
|
, env_requirements_json TEXT NOT NULL DEFAULT '[]', name_en TEXT NOT NULL DEFAULT '', tagline_en TEXT NOT NULL DEFAULT '', builtin INTEGER NOT NULL DEFAULT 0, role TEXT, visibility TEXT NOT NULL DEFAULT 'visible' CHECK(visibility IN ('visible','background','private')), entity_kind TEXT, local_display_name TEXT, bookmarked_at TEXT NULL, parent_team_id TEXT NULL);
|
|
870
|
+
CREATE TABLE invocation_steers (
|
|
871
|
+
id TEXT PRIMARY KEY,
|
|
872
|
+
chat_id TEXT NOT NULL,
|
|
873
|
+
original_run_id TEXT NOT NULL,
|
|
874
|
+
prompt_text TEXT NOT NULL,
|
|
875
|
+
prompt_hash TEXT NOT NULL,
|
|
876
|
+
request_json TEXT NOT NULL,
|
|
877
|
+
workspace_binding_json TEXT,
|
|
878
|
+
execution_context_json TEXT,
|
|
879
|
+
status TEXT NOT NULL CHECK(status IN ('queued','draining','started','cancelled','failed')),
|
|
880
|
+
drained_run_id TEXT,
|
|
881
|
+
queued_at TEXT NOT NULL,
|
|
882
|
+
updated_at TEXT NOT NULL
|
|
883
|
+
);
|
|
825
884
|
CREATE TABLE judgment_verdicts (
|
|
826
885
|
kind TEXT NOT NULL,
|
|
827
886
|
signature TEXT NOT NULL,
|
|
@@ -979,6 +1038,101 @@ CREATE TABLE model_roles (
|
|
|
979
1038
|
updated_at TEXT NOT NULL,
|
|
980
1039
|
CHECK(role = 'worker' OR inherit = 0)
|
|
981
1040
|
);
|
|
1041
|
+
CREATE TABLE one_artifact_bindings (
|
|
1042
|
+
id TEXT PRIMARY KEY,
|
|
1043
|
+
task_id TEXT NOT NULL,
|
|
1044
|
+
task_version INTEGER NOT NULL,
|
|
1045
|
+
bound_task_version INTEGER NOT NULL,
|
|
1046
|
+
chat_id TEXT NOT NULL,
|
|
1047
|
+
run_id TEXT NOT NULL,
|
|
1048
|
+
manifest_id TEXT NOT NULL,
|
|
1049
|
+
artifact_ref TEXT NOT NULL,
|
|
1050
|
+
source_path TEXT NOT NULL,
|
|
1051
|
+
kind TEXT NOT NULL,
|
|
1052
|
+
mime_type TEXT NOT NULL,
|
|
1053
|
+
size_bytes INTEGER NOT NULL,
|
|
1054
|
+
file_dev TEXT NOT NULL,
|
|
1055
|
+
file_ino TEXT NOT NULL,
|
|
1056
|
+
file_mtime_ns TEXT NOT NULL,
|
|
1057
|
+
file_ctime_ns TEXT NOT NULL,
|
|
1058
|
+
sha256 TEXT NOT NULL,
|
|
1059
|
+
created_at TEXT NOT NULL,
|
|
1060
|
+
UNIQUE(task_id, chat_id, run_id, manifest_id, artifact_ref)
|
|
1061
|
+
);
|
|
1062
|
+
CREATE TABLE one_org_completion_cache (
|
|
1063
|
+
installed_agent_id TEXT PRIMARY KEY,
|
|
1064
|
+
run_id TEXT,
|
|
1065
|
+
summary_json TEXT NOT NULL,
|
|
1066
|
+
updated_at TEXT NOT NULL
|
|
1067
|
+
);
|
|
1068
|
+
CREATE TABLE one_org_members (
|
|
1069
|
+
id TEXT PRIMARY KEY,
|
|
1070
|
+
agent_slug TEXT NOT NULL,
|
|
1071
|
+
installed_agent_id TEXT NOT NULL,
|
|
1072
|
+
display_name TEXT,
|
|
1073
|
+
icon TEXT NOT NULL DEFAULT 'one-puppy',
|
|
1074
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
1075
|
+
source TEXT NOT NULL CHECK(source IN ('local','cloud','hub')),
|
|
1076
|
+
lease_expires_at TEXT,
|
|
1077
|
+
added_at TEXT NOT NULL,
|
|
1078
|
+
updated_at TEXT NOT NULL,
|
|
1079
|
+
archived_at TEXT,
|
|
1080
|
+
status_kind TEXT NOT NULL DEFAULT 'new',
|
|
1081
|
+
-- PRD §4.33 — 스키마에 사람이 읽는 문구(그것도 한 언어)를 박지 않는다.
|
|
1082
|
+
-- 빈 값이면 투영이 로케일 표에서 "아직 맡은 일 없음 / No work assigned yet"을 만든다.
|
|
1083
|
+
status_line TEXT NOT NULL DEFAULT '',
|
|
1084
|
+
last_activity_at TEXT,
|
|
1085
|
+
pending_count INTEGER NOT NULL DEFAULT 0,
|
|
1086
|
+
pending_kind TEXT NOT NULL DEFAULT 'approval' CHECK(pending_kind IN ('approval','review','input')),
|
|
1087
|
+
unread_count INTEGER NOT NULL DEFAULT 0,
|
|
1088
|
+
unread_generation INTEGER NOT NULL DEFAULT 0,
|
|
1089
|
+
credit_state TEXT NOT NULL DEFAULT 'unknown' CHECK(credit_state IN ('ok','insufficient','unknown')),
|
|
1090
|
+
auto_select_tools INTEGER NOT NULL DEFAULT 1 CHECK(auto_select_tools IN (0,1)),
|
|
1091
|
+
collaboration_style TEXT NOT NULL DEFAULT 'default' CHECK(collaboration_style IN ('default','concise','warm','direct')),
|
|
1092
|
+
handover_note TEXT,
|
|
1093
|
+
revision INTEGER NOT NULL DEFAULT 1
|
|
1094
|
+
);
|
|
1095
|
+
CREATE TABLE one_seat_occupants (
|
|
1096
|
+
seat_id TEXT NOT NULL REFERENCES one_seats(id) ON DELETE CASCADE,
|
|
1097
|
+
slot INTEGER NOT NULL DEFAULT 0,
|
|
1098
|
+
agent_id TEXT,
|
|
1099
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
1100
|
+
since TEXT NOT NULL,
|
|
1101
|
+
until TEXT,
|
|
1102
|
+
PRIMARY KEY (seat_id, slot, since)
|
|
1103
|
+
);
|
|
1104
|
+
CREATE TABLE one_seats (
|
|
1105
|
+
id TEXT PRIMARY KEY,
|
|
1106
|
+
kind TEXT NOT NULL CHECK(kind IN ('solo','group')),
|
|
1107
|
+
title TEXT NOT NULL DEFAULT '',
|
|
1108
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1109
|
+
created_at TEXT NOT NULL,
|
|
1110
|
+
updated_at TEXT NOT NULL,
|
|
1111
|
+
archived_at TEXT
|
|
1112
|
+
, dissolved_at TEXT);
|
|
1113
|
+
CREATE TABLE one_taskforces (
|
|
1114
|
+
id TEXT PRIMARY KEY,
|
|
1115
|
+
chat_id TEXT NOT NULL UNIQUE,
|
|
1116
|
+
title TEXT NOT NULL,
|
|
1117
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1118
|
+
member_agent_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
1119
|
+
created_at TEXT NOT NULL,
|
|
1120
|
+
updated_at TEXT NOT NULL,
|
|
1121
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1122
|
+
FOREIGN KEY(chat_id) REFERENCES chats(id) ON DELETE CASCADE
|
|
1123
|
+
);
|
|
1124
|
+
CREATE TABLE plugin_builder_sessions (
|
|
1125
|
+
id TEXT PRIMARY KEY,
|
|
1126
|
+
chat_id TEXT NOT NULL,
|
|
1127
|
+
slug TEXT,
|
|
1128
|
+
phase TEXT NOT NULL CHECK(phase IN ('interview','draft','verify','install','prove','discarded')),
|
|
1129
|
+
staging_dir TEXT,
|
|
1130
|
+
answers_json TEXT,
|
|
1131
|
+
gate_report_json TEXT,
|
|
1132
|
+
seed_json TEXT NOT NULL,
|
|
1133
|
+
created_at TEXT NOT NULL,
|
|
1134
|
+
updated_at TEXT NOT NULL
|
|
1135
|
+
);
|
|
982
1136
|
CREATE TABLE project_agent_rent_allow (
|
|
983
1137
|
project_id TEXT NOT NULL,
|
|
984
1138
|
slug TEXT NOT NULL,
|
|
@@ -996,6 +1150,13 @@ CREATE TABLE projects (
|
|
|
996
1150
|
updated_at TEXT NOT NULL, folder_path TEXT, system_prompt TEXT, agent_pool_json TEXT NOT NULL DEFAULT '[]', source_type TEXT NOT NULL DEFAULT 'local', source_ref TEXT,
|
|
997
1151
|
FOREIGN KEY(default_agent_id) REFERENCES installed_agents(id) ON DELETE SET NULL
|
|
998
1152
|
);
|
|
1153
|
+
CREATE TABLE prompt_chat_start_intents (
|
|
1154
|
+
intent_id TEXT PRIMARY KEY,
|
|
1155
|
+
chat_id TEXT NOT NULL UNIQUE,
|
|
1156
|
+
prompt_digest TEXT NOT NULL,
|
|
1157
|
+
seed_only INTEGER NOT NULL CHECK(seed_only IN (0,1)),
|
|
1158
|
+
created_at TEXT NOT NULL
|
|
1159
|
+
);
|
|
999
1160
|
CREATE TABLE run_events (
|
|
1000
1161
|
id TEXT PRIMARY KEY,
|
|
1001
1162
|
run_id TEXT NOT NULL,
|
|
@@ -1123,7 +1284,7 @@ CREATE TABLE "telegram_bindings" (
|
|
|
1123
1284
|
designated_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1124
1285
|
designated_graph_id TEXT,
|
|
1125
1286
|
legacy_notice_at TEXT
|
|
1126
|
-
);
|
|
1287
|
+
, seat_id TEXT);
|
|
1127
1288
|
CREATE INDEX idx_agent_app_ops_app_created
|
|
1128
1289
|
ON agent_app_operations(app_id, created_at DESC);
|
|
1129
1290
|
CREATE INDEX idx_agent_apps_chat_updated
|
|
@@ -1142,6 +1303,8 @@ CREATE INDEX idx_agent_evolution_receipts_agent
|
|
|
1142
1303
|
ON agent_evolution_receipts(agent_id, created_at DESC);
|
|
1143
1304
|
CREATE INDEX idx_agent_evolution_receipts_proposal
|
|
1144
1305
|
ON agent_evolution_receipts(proposal_id, created_at ASC);
|
|
1306
|
+
CREATE INDEX idx_agent_identity_map_agent ON agent_identity_map(immutable_agent_id);
|
|
1307
|
+
CREATE INDEX idx_agent_identity_map_source ON agent_identity_map(mapping_source);
|
|
1145
1308
|
CREATE INDEX idx_agent_mcp_agent ON agent_mcp_servers(agent_id);
|
|
1146
1309
|
CREATE INDEX idx_agent_runtime_overrides_updated
|
|
1147
1310
|
ON agent_runtime_overrides(updated_at DESC);
|
|
@@ -1205,6 +1368,7 @@ CREATE INDEX idx_browser_logs_ts ON browser_action_logs(ts DESC);
|
|
|
1205
1368
|
CREATE UNIQUE INDEX idx_browser_perm_site_action
|
|
1206
1369
|
ON browser_permissions(site, action_type);
|
|
1207
1370
|
CREATE UNIQUE INDEX idx_browser_sessions_site ON browser_sessions(site);
|
|
1371
|
+
CREATE INDEX idx_capability_grants_scope ON capability_grants(scope);
|
|
1208
1372
|
CREATE UNIQUE INDEX idx_chat_goal_contracts_active_chat
|
|
1209
1373
|
ON chat_goal_contracts(chat_id)
|
|
1210
1374
|
WHERE status = 'active';
|
|
@@ -1217,6 +1381,7 @@ CREATE INDEX idx_chats_firm_updated ON chats(firm_id, updated_at DESC);
|
|
|
1217
1381
|
CREATE INDEX idx_chats_parent ON chats(parent_chat_id);
|
|
1218
1382
|
CREATE INDEX idx_chats_project_updated
|
|
1219
1383
|
ON chats(project_id, updated_at DESC);
|
|
1384
|
+
CREATE INDEX idx_chats_seat_updated ON chats(seat_id, updated_at DESC);
|
|
1220
1385
|
CREATE INDEX idx_chats_updated ON chats(updated_at DESC);
|
|
1221
1386
|
CREATE INDEX idx_chats_used_updated ON chats(used_at, updated_at DESC);
|
|
1222
1387
|
CREATE INDEX idx_experience_auto_intake_agent_status
|
|
@@ -1283,6 +1448,10 @@ CREATE INDEX idx_installed_agent_hub_binding_exact
|
|
|
1283
1448
|
ON installed_agent_hub_bindings(agent_definition_id, agent_release_id);
|
|
1284
1449
|
CREATE INDEX idx_installed_agents_parent_team ON installed_agents(parent_team_id) WHERE parent_team_id IS NOT NULL;
|
|
1285
1450
|
CREATE INDEX idx_installed_agents_visibility ON installed_agents(visibility, installed_at DESC);
|
|
1451
|
+
CREATE INDEX idx_invocation_steers_chat
|
|
1452
|
+
ON invocation_steers(chat_id, queued_at, id);
|
|
1453
|
+
CREATE INDEX idx_invocation_steers_queue
|
|
1454
|
+
ON invocation_steers(status, queued_at, id);
|
|
1286
1455
|
CREATE INDEX idx_judgment_verdicts_recency ON judgment_verdicts(last_hit_at);
|
|
1287
1456
|
CREATE INDEX idx_memory_agent ON memory_entries(agent_id, superseded_at);
|
|
1288
1457
|
CREATE INDEX idx_memory_chat ON memory_entries(chat_id);
|
|
@@ -1308,6 +1477,24 @@ CREATE INDEX idx_memory_tickets_project_created
|
|
|
1308
1477
|
ON memory_tickets(project_id, created_at DESC);
|
|
1309
1478
|
CREATE INDEX idx_memory_tickets_status_created
|
|
1310
1479
|
ON memory_tickets(emitter_status, state, created_at DESC);
|
|
1480
|
+
CREATE INDEX idx_occupants_seat_time ON one_seat_occupants(seat_id, since);
|
|
1481
|
+
CREATE INDEX idx_one_artifact_binding_chat
|
|
1482
|
+
ON one_artifact_bindings(chat_id, created_at);
|
|
1483
|
+
CREATE INDEX idx_one_artifact_binding_exact
|
|
1484
|
+
ON one_artifact_bindings(task_id, chat_id, run_id, manifest_id, artifact_ref);
|
|
1485
|
+
CREATE INDEX idx_one_org_members_agent
|
|
1486
|
+
ON one_org_members(installed_agent_id);
|
|
1487
|
+
CREATE INDEX idx_one_org_members_order
|
|
1488
|
+
ON one_org_members(archived_at, sort_order, added_at);
|
|
1489
|
+
CREATE INDEX idx_one_seats_updated ON one_seats(updated_at DESC);
|
|
1490
|
+
CREATE INDEX idx_one_taskforces_updated
|
|
1491
|
+
ON one_taskforces(updated_at DESC);
|
|
1492
|
+
CREATE INDEX idx_plugin_builder_sessions_chat_updated
|
|
1493
|
+
ON plugin_builder_sessions(chat_id, updated_at DESC);
|
|
1494
|
+
CREATE INDEX idx_plugin_builder_sessions_slug_phase
|
|
1495
|
+
ON plugin_builder_sessions(slug, phase);
|
|
1496
|
+
CREATE INDEX idx_prompt_chat_start_chat
|
|
1497
|
+
ON prompt_chat_start_intents(chat_id);
|
|
1311
1498
|
CREATE INDEX idx_run_events_agent_kind_ts
|
|
1312
1499
|
ON run_events(agent_id, kind, ts DESC);
|
|
1313
1500
|
CREATE INDEX idx_run_events_agent_ts ON run_events(agent_id, ts DESC);
|
|
@@ -1320,6 +1507,10 @@ CREATE INDEX idx_run_events_run_seq
|
|
|
1320
1507
|
CREATE INDEX idx_run_events_ts
|
|
1321
1508
|
ON run_events(ts DESC);
|
|
1322
1509
|
CREATE INDEX idx_run_history_automation ON run_history(automation_id);
|
|
1510
|
+
CREATE INDEX idx_seat_occupants_agent
|
|
1511
|
+
ON one_seat_occupants(agent_id) WHERE agent_id IS NOT NULL;
|
|
1512
|
+
CREATE UNIQUE INDEX idx_seat_occupants_current
|
|
1513
|
+
ON one_seat_occupants(seat_id, slot) WHERE until IS NULL;
|
|
1323
1514
|
CREATE INDEX idx_task_participants_agent ON task_agent_participants(agent_id);
|
|
1324
1515
|
CREATE INDEX idx_tasks_firm_updated ON tasks(firm_id, updated_at DESC);
|
|
1325
1516
|
CREATE INDEX idx_tasks_origin_chat ON tasks(origin_chat_id);
|
|
@@ -1337,7 +1528,8 @@ CREATE INDEX idx_telegram_bindings_chat
|
|
|
1337
1528
|
ON telegram_bindings(telegram_chat_id);
|
|
1338
1529
|
CREATE INDEX idx_telegram_bindings_enabled
|
|
1339
1530
|
ON telegram_bindings(enabled, status);
|
|
1340
|
-
CREATE UNIQUE INDEX
|
|
1341
|
-
ON telegram_bindings(
|
|
1531
|
+
CREATE UNIQUE INDEX idx_telegram_bindings_one_room
|
|
1532
|
+
ON telegram_bindings(telegram_chat_id)
|
|
1533
|
+
WHERE target_kind = 'one' AND telegram_chat_id IS NOT NULL;
|
|
1342
1534
|
CREATE INDEX idx_telegram_bindings_target
|
|
1343
1535
|
ON telegram_bindings(target_kind, target_id);
|
package/engine/cloud/auth.cjs
CHANGED
|
@@ -24,6 +24,12 @@ const LOGIN_CALLBACK_PATH = "/callback";
|
|
|
24
24
|
const LOGIN_TIMEOUT_MS = 180_000;
|
|
25
25
|
const MAX_LOGIN_SESSION_BYTES = 16 * 1024;
|
|
26
26
|
|
|
27
|
+
function validLoginSessionValue(value) {
|
|
28
|
+
return typeof value === "string" && value.length > 0 &&
|
|
29
|
+
Buffer.byteLength(value, "utf8") <= MAX_LOGIN_SESSION_BYTES &&
|
|
30
|
+
!/[\u0000-\u0020\u007f;,]/.test(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
function webBaseUrl() {
|
|
28
34
|
return (process.env.AGENTLAS_WEB_BASE_URL || "https://agentlas.cloud").replace(/\/$/, "");
|
|
29
35
|
}
|
|
@@ -97,13 +103,13 @@ function createLoginCallbackGuard(expectedState) {
|
|
|
97
103
|
message: "The callback did not include a session value.",
|
|
98
104
|
};
|
|
99
105
|
}
|
|
100
|
-
if (
|
|
106
|
+
if (!validLoginSessionValue(value)) {
|
|
101
107
|
return {
|
|
102
108
|
handled: true,
|
|
103
109
|
final: true,
|
|
104
110
|
ok: false,
|
|
105
111
|
statusCode: 400,
|
|
106
|
-
message: "The login session value is
|
|
112
|
+
message: "The login session value is invalid.",
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
115
|
return { handled: true, final: true, ok: true, statusCode: 200, value, message: "Agentlas login complete" };
|
|
@@ -120,6 +126,9 @@ function openInBrowser(url) {
|
|
|
120
126
|
: ["xdg-open", url];
|
|
121
127
|
try {
|
|
122
128
|
const child = spawn(argv[0], argv.slice(1), { stdio: "ignore", detached: true });
|
|
129
|
+
// spawn failures such as a missing xdg-open arrive asynchronously. Without
|
|
130
|
+
// a listener Node treats them as an uncaught process error.
|
|
131
|
+
child.once("error", () => {});
|
|
123
132
|
child.unref();
|
|
124
133
|
} catch { /* ignore */ }
|
|
125
134
|
}
|
|
@@ -220,18 +229,35 @@ function cliSessionPath() {
|
|
|
220
229
|
|
|
221
230
|
function readCliSessionValue() {
|
|
222
231
|
try {
|
|
223
|
-
const
|
|
224
|
-
|
|
232
|
+
const p = cliSessionPath();
|
|
233
|
+
const stat = fs.lstatSync(p);
|
|
234
|
+
if (stat.isSymbolicLink() || !stat.isFile() || stat.size > MAX_LOGIN_SESSION_BYTES * 2) return null;
|
|
235
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
236
|
+
return j && validLoginSessionValue(j.value) ? j.value : null;
|
|
225
237
|
} catch {
|
|
226
238
|
return null;
|
|
227
239
|
}
|
|
228
240
|
}
|
|
229
241
|
|
|
230
242
|
function saveCliSession(value) {
|
|
243
|
+
if (!validLoginSessionValue(value)) throw new Error("Refusing to save an invalid Agentlas login session value.");
|
|
231
244
|
const p = cliSessionPath();
|
|
232
245
|
// 디렉터리 0700 + 파일 0600 — 기본 umask(0644)로 세션이 world-readable 이 되는 것을 막는다.
|
|
233
|
-
|
|
234
|
-
fs.
|
|
246
|
+
const dir = path.dirname(p);
|
|
247
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
248
|
+
try { fs.chmodSync(dir, 0o700); } catch { /* win32 */ }
|
|
249
|
+
const temp = path.join(dir, `.cli-session.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
250
|
+
try {
|
|
251
|
+
fs.writeFileSync(temp, JSON.stringify({ version: 1, value, updatedAt: new Date().toISOString() }, null, 2) + "\n", {
|
|
252
|
+
encoding: "utf8",
|
|
253
|
+
mode: 0o600,
|
|
254
|
+
flag: "wx",
|
|
255
|
+
});
|
|
256
|
+
fs.renameSync(temp, p);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
try { fs.rmSync(temp, { force: true }); } catch { /* preserve original */ }
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
235
261
|
try { fs.chmodSync(p, 0o600); } catch { /* win32 */ }
|
|
236
262
|
return p;
|
|
237
263
|
}
|
|
@@ -246,7 +272,7 @@ function deleteCliSession() {
|
|
|
246
272
|
// 쿠키 해석 순서 계약: AGENTLAS_SESSION env → 세션 파일. (v1의 keytar 폴백은
|
|
247
273
|
// 데스크탑이 세션을 keytar에 두지 않아 항상 비어 있었다 — v2에서는 싣지 않는다.)
|
|
248
274
|
function cloudSessionCookie() {
|
|
249
|
-
if (process.env.AGENTLAS_SESSION) return `agentlas_session=${process.env.AGENTLAS_SESSION}`;
|
|
275
|
+
if (validLoginSessionValue(process.env.AGENTLAS_SESSION)) return `agentlas_session=${process.env.AGENTLAS_SESSION}`;
|
|
250
276
|
const fileValue = readCliSessionValue();
|
|
251
277
|
if (fileValue) return `agentlas_session=${fileValue}`;
|
|
252
278
|
return null;
|
|
@@ -275,5 +301,5 @@ module.exports = {
|
|
|
275
301
|
deleteCliSession,
|
|
276
302
|
cloudSessionCookie,
|
|
277
303
|
fetchSessionMeta,
|
|
278
|
-
_test: { createLoginState, createLoginCallbackGuard },
|
|
304
|
+
_test: { createLoginState, createLoginCallbackGuard, validLoginSessionValue },
|
|
279
305
|
};
|
|
@@ -68,6 +68,22 @@ function cloudCasResponseError(response, label) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** 등록(생성/갱신). 성공 시 검증된 리비전 디스크립터(+operation/url)를 반환한다. */
|
|
71
|
+
// 서버는 받은 것보다 적게 저장할 수 있고, 그건 증명 실패가 아니다.
|
|
72
|
+
//
|
|
73
|
+
// 등록은 제출 해시를 검증한 뒤, 자체 스캔이 자격증명으로 판정한 파일을 빼고
|
|
74
|
+
// 나머지를 새 해시로 저장한다(uploadReceipt.omissions가 뺀 경로를 전부 적는다).
|
|
75
|
+
// 저장 해시만 대조하면, 이미 허브에 게시가 끝난 뒤에 "잘못된 등록 영수증"으로
|
|
76
|
+
// 실패하게 된다 — 에이전트는 검색·호출되는데 올린 사람은 실패로 듣는다.
|
|
77
|
+
// 증명의 목적은 "서버가 바로 이 패키지를 봤다"이고 submittedPackageHash가 그
|
|
78
|
+
// 증거이므로, 둘 중 하나가 우리 해시와 같으면 통과시킨다. 둘 다 아니면 그대로 실패.
|
|
79
|
+
function registrationSawOurPackage(json, manifest) {
|
|
80
|
+
const ours = String(manifest.packageHash || "").toLowerCase();
|
|
81
|
+
if (String(json.packageHash || "").toLowerCase() === ours) return true;
|
|
82
|
+
const receipt = json && typeof json === "object" ? json.uploadReceipt : null;
|
|
83
|
+
if (!receipt || typeof receipt !== "object") return false;
|
|
84
|
+
return String(receipt.submittedPackageHash || "").toLowerCase() === ours;
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
async function registerCloudAgent(manifest, bundlePath, review, visibility, options = {}) {
|
|
72
88
|
const cookie = await cloudSessionCookie();
|
|
73
89
|
if (!cookie) throw new Error("Agent Cloud sign-in is required. Sign in through Desktop or set AGENTLAS_SESSION.");
|
|
@@ -119,7 +135,7 @@ async function registerCloudAgent(manifest, bundlePath, review, visibility, opti
|
|
|
119
135
|
json.dryRun !== false ||
|
|
120
136
|
typeof json.cloudId !== "string" || !json.cloudId.trim() ||
|
|
121
137
|
json.slug !== manifest.slug ||
|
|
122
|
-
json
|
|
138
|
+
!registrationSawOurPackage(json, manifest) ||
|
|
123
139
|
json.packageHashVersion !== manifest.packageHashVersion ||
|
|
124
140
|
typeof json.revision !== "string" || etag !== cloudRevisionEtag(json.revision) ||
|
|
125
141
|
typeof json.registeredAt !== "string" || !Number.isFinite(Date.parse(json.registeredAt)) ||
|
|
@@ -138,11 +154,21 @@ async function registerCloudAgent(manifest, bundlePath, review, visibility, opti
|
|
|
138
154
|
etag,
|
|
139
155
|
updatedAt: json.savedAt || json.registeredAt,
|
|
140
156
|
}, "registration receipt");
|
|
157
|
+
// 서버가 자기 스캔으로 뺀 파일은 사용자에게 보여야 한다. 영수증 안에만 있으면
|
|
158
|
+
// "내 파일이 안 올라갔다"는 사실을 아무도 읽지 않는다.
|
|
159
|
+
const serverWithheld = (() => {
|
|
160
|
+
const receipt = json && typeof json === "object" ? json.uploadReceipt : null;
|
|
161
|
+
const omissions = receipt && Array.isArray(receipt.omissions) ? receipt.omissions : [];
|
|
162
|
+
return omissions
|
|
163
|
+
.map((entry) => (entry && typeof entry.path === "string" ? entry.path : ""))
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
})();
|
|
141
166
|
return {
|
|
142
167
|
...descriptor,
|
|
143
168
|
operation: json.operation,
|
|
144
169
|
...(typeof json.url === "string" ? { url: json.url } : {}),
|
|
145
170
|
...(typeof json.marketplaceUrl === "string" ? { marketplaceUrl: json.marketplaceUrl } : {}),
|
|
171
|
+
...(serverWithheld.length ? { serverWithheld } : {}),
|
|
146
172
|
registeredAt: json.registeredAt,
|
|
147
173
|
dryRun: false,
|
|
148
174
|
};
|