agentlas 1.0.18 → 1.0.19

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.
@@ -1,6 +1,6 @@
1
- -- Agentlas 실행 부트스트랩 스키마 (생성: 2026-07-28T08:31:20Z)
2
- -- 소스 DB user_version=81 앱이 나중에 설치되면 여기서부터 마이그레이션한다.
3
- PRAGMA user_version=81;
1
+ -- Agentlas first-run bootstrap schema (project-first Work contract)
2
+ -- Source DB user_version=85. Desktop remains the migration authority.
3
+ PRAGMA user_version=85;
4
4
  CREATE TABLE active_runtime (
5
5
  id INTEGER PRIMARY KEY CHECK(id = 1),
6
6
  kind TEXT NOT NULL
@@ -21,11 +21,13 @@ CREATE TABLE projects (
21
21
  id TEXT PRIMARY KEY,
22
22
  name TEXT NOT NULL,
23
23
  description TEXT,
24
- default_agent_id TEXT,
25
- context_note TEXT,
24
+ system_prompt TEXT,
25
+ agent_pool_json TEXT NOT NULL DEFAULT '[]',
26
+ source_type TEXT NOT NULL DEFAULT 'local' CHECK(source_type IN ('local','github','sample')),
27
+ source_ref TEXT,
26
28
  created_at TEXT NOT NULL,
27
- updated_at TEXT NOT NULL, folder_path TEXT,
28
- FOREIGN KEY(default_agent_id) REFERENCES installed_agents(id) ON DELETE SET NULL
29
+ updated_at TEXT NOT NULL,
30
+ folder_path TEXT
29
31
  );
30
32
  CREATE TABLE chats (
31
33
  id TEXT PRIMARY KEY,
@@ -33,7 +35,7 @@ CREATE TABLE chats (
33
35
  agent_id TEXT NOT NULL,
34
36
  title TEXT NOT NULL DEFAULT '새 채팅',
35
37
  created_at TEXT NOT NULL,
36
- updated_at TEXT NOT NULL, firm_id TEXT REFERENCES firms(id) ON DELETE SET NULL, archived_at TEXT, working_folder TEXT, kind TEXT NOT NULL DEFAULT 'user', parent_chat_id TEXT, used_at TEXT, agent_group_id TEXT REFERENCES agent_groups(id) ON DELETE SET NULL, continuous_mode INTEGER NOT NULL DEFAULT 0, swarm_mode INTEGER NOT NULL DEFAULT 0, last_viewed_at TEXT, hired_agents TEXT, origin_surface TEXT NOT NULL DEFAULT 'work', runtime_selection_json TEXT,
38
+ updated_at TEXT NOT NULL, firm_id TEXT REFERENCES firms(id) ON DELETE SET NULL, archived_at TEXT, working_folder TEXT, kind TEXT NOT NULL DEFAULT 'user', parent_chat_id TEXT, used_at TEXT, continuous_mode INTEGER NOT NULL DEFAULT 0, swarm_mode INTEGER NOT NULL DEFAULT 0, last_viewed_at TEXT, hired_agents TEXT, origin_surface TEXT NOT NULL DEFAULT 'work', runtime_selection_json TEXT,
37
39
  FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
38
40
  FOREIGN KEY(agent_id) REFERENCES installed_agents(id) ON DELETE CASCADE
39
41
  );
@@ -116,8 +118,20 @@ CREATE TABLE automations (
116
118
  last_run_at TEXT,
117
119
  next_run_at TEXT,
118
120
  created_at TEXT NOT NULL
119
- , graph_json TEXT, schedule_json TEXT, timezone TEXT, end_at TEXT, max_runs INTEGER, run_count INTEGER NOT NULL DEFAULT 0, trigger_type TEXT NOT NULL DEFAULT 'schedule', trigger_json TEXT, claimed_at TEXT, lease_owner TEXT, tool_mode TEXT NOT NULL DEFAULT 'auto', hub_mode TEXT NOT NULL DEFAULT 'hub-allowed', execution_permission TEXT NOT NULL DEFAULT 'write' CHECK(execution_permission IN ('read','write')), target_version TEXT, runtime_selection_json TEXT);
121
+ , graph_json TEXT, schedule_json TEXT, timezone TEXT, end_at TEXT, max_runs INTEGER, run_count INTEGER NOT NULL DEFAULT 0, trigger_type TEXT NOT NULL DEFAULT 'schedule', trigger_json TEXT, claimed_at TEXT, lease_owner TEXT, tool_mode TEXT NOT NULL DEFAULT 'auto', hub_mode TEXT NOT NULL DEFAULT 'hub-allowed', execution_permission TEXT NOT NULL DEFAULT 'write' CHECK(execution_permission IN ('read','write')), target_version TEXT, runtime_selection_json TEXT, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL);
120
122
  CREATE INDEX idx_automations_due ON automations(enabled, next_run_at);
123
+ CREATE TABLE automation_sessions (
124
+ id TEXT PRIMARY KEY,
125
+ automation_id TEXT NOT NULL,
126
+ target_kind TEXT NOT NULL CHECK(target_kind IN ('host','agent','firm','hub')),
127
+ target_id TEXT NOT NULL,
128
+ ledger_chat_id TEXT NOT NULL UNIQUE REFERENCES chats(id) ON DELETE CASCADE,
129
+ created_at TEXT NOT NULL,
130
+ updated_at TEXT NOT NULL,
131
+ UNIQUE(automation_id, target_kind, target_id)
132
+ );
133
+ CREATE INDEX idx_automation_sessions_owner
134
+ ON automation_sessions(automation_id, updated_at DESC);
121
135
  CREATE TABLE agent_apps (
122
136
  id TEXT PRIMARY KEY,
123
137
  chat_id TEXT NOT NULL,
@@ -379,18 +393,6 @@ CREATE INDEX idx_persona_loop_runs_updated
379
393
  CREATE INDEX idx_persona_loop_runs_automation
380
394
  ON persona_loop_runs(automation_id);
381
395
  CREATE INDEX idx_chats_used_updated ON chats(used_at, updated_at DESC);
382
- CREATE TABLE agent_groups (
383
- id TEXT PRIMARY KEY,
384
- name TEXT NOT NULL,
385
- description TEXT NOT NULL DEFAULT '',
386
- orchestrator_name TEXT NOT NULL,
387
- members_json TEXT NOT NULL DEFAULT '[]',
388
- created_at TEXT NOT NULL,
389
- updated_at TEXT NOT NULL
390
- );
391
- CREATE INDEX idx_agent_groups_updated
392
- ON agent_groups(updated_at DESC);
393
- CREATE INDEX idx_chats_agent_group_updated ON chats(agent_group_id, updated_at DESC);
394
396
  CREATE TABLE run_history (
395
397
  id TEXT PRIMARY KEY,
396
398
  automation_id TEXT,
@@ -9,14 +9,13 @@
9
9
  * --permission <level> read | write | full
10
10
  * 프롬프트가 없고 stdin이 TTY가 아니면 stdin을 읽는다.
11
11
  *
12
- * 자동 라우팅(agents/router.cjs): 첫 토큰이 에이전트로 해석되지 않으면 호스트 LLM
13
- * 판정(resolveAutoRoute)이 최적 에이전트를 고른다 어휘 점수는 후보 모집 전용이다.
14
- * 판정 런타임이 없으면 기본 에이전트로 정직 폴백하되 note를 stderr에 반드시 출력한다
15
- * (조용한 오라우팅/폴백 금지 — 오너 결정).
12
+ * 첫 토큰이 정확한 에이전트이면 고급 직접 호출이다. 그 외 일반 실행은 현재 폴더에
13
+ * 연결된 Desktop Work 프로젝트의 에이전트를 컨트롤러로 사용한다. 프로젝트나
14
+ * 컨트롤러가 불명확하면 다른 에이전트로 대체하지 않고 실행을 중단한다.
16
15
  * 런타임 사다리: 명시 핀 > 에이전트별 오버라이드(agent_runtime_overrides) >
17
16
  * model_roles[orchestrator] > active_runtime > detected (runtimes/overrides.cjs).
18
17
  */
19
- const { findAgent, listAgents } = require("../agents/registry.cjs");
18
+ const { findAgent } = require("../agents/registry.cjs");
20
19
  const {
21
20
  resolveRuntimeForAgent,
22
21
  unavailableOverrideNote,
@@ -28,6 +27,7 @@ const permissions = require("../agentlas-permissions.cjs");
28
27
  const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
29
28
  const { projectCwd } = require("../project/paths.cjs");
30
29
  const { ensureTerminalProjectForExecutionCli } = require("../project/state.cjs");
30
+ const { resolveProjectController, withProjectControllerContext } = require("../project/controller.cjs");
31
31
 
32
32
  function readStdin() {
33
33
  return new Promise((resolve) => {
@@ -99,7 +99,23 @@ async function runOnce(ctx, args) {
99
99
  return 1;
100
100
  }
101
101
 
102
- // 기본 런타임을 먼저 확정한다 — 판정 러너(자동 라우팅)도 이 런타임을 쓴다.
102
+ const cwd = projectCwd();
103
+ if (!agent) {
104
+ try {
105
+ const resolved = resolveProjectController(db, cwd);
106
+ agent = withProjectControllerContext(resolved.controller, resolved.project);
107
+ ctx.err(ctx.uiInstance.c.dim(
108
+ ctx.lang === "ko"
109
+ ? `프로젝트: ${resolved.project.name} · 컨트롤러: ${agent.name}`
110
+ : `Project: ${resolved.project.name} · Controller: ${agent.nameEn || agent.name}`,
111
+ ));
112
+ } catch (e) {
113
+ ctx.err(String((e && e.message) || e));
114
+ return 1;
115
+ }
116
+ }
117
+
118
+ // 프로젝트 컨트롤러 또는 명시 에이전트의 런타임을 확정한다.
103
119
  let runtime;
104
120
  try {
105
121
  runtime = resolveRuntimeForAgent({
@@ -109,6 +125,7 @@ async function runOnce(ctx, args) {
109
125
  model: parsed.model,
110
126
  effort: parsed.effort,
111
127
  role: "orchestrator",
128
+ agentId: agent.id,
112
129
  });
113
130
  if (parsed.tier) runtime.modelTier = parsed.tier;
114
131
  } catch (e) {
@@ -116,33 +133,6 @@ async function runOnce(ctx, args) {
116
133
  return 1;
117
134
  }
118
135
 
119
- if (!agent) {
120
- // 자동 라우팅 — 최종 픽은 호스트 LLM 판정. 판정 불가 시 기본 에이전트 정직 폴백 + note.
121
- const router = require("../agents/router.cjs");
122
- let choice = null;
123
- try {
124
- choice = await router.resolveAutoRoute(db, prompt, { lang: ctx.lang, runtime });
125
- } catch (e) {
126
- ctx.err(ctx.lang === "ko"
127
- ? `자동 라우팅 실패(${(e && e.message) || e}) — 기본 에이전트로 실행합니다`
128
- : `auto-routing failed (${(e && e.message) || e}) — running with the default agent`);
129
- }
130
- if (choice && choice.agent) {
131
- agent = choice.agent;
132
- } else {
133
- const visible = listAgents(db);
134
- agent = visible[0] || findAgent(db, "agentlas-orchestrator");
135
- if (!agent) {
136
- ctx.err(ctx.lang === "en" ? "no installed agent (agentlas search/install first)" : "설치된 에이전트가 없습니다 (agentlas search/install 먼저)");
137
- return 1;
138
- }
139
- // 직답 판정 — 페르소나 오염 없이 기본 에이전트 챗으로, 플레인 프롬프트로 답한다.
140
- if (choice && choice.direct) agent = { ...agent, systemPrompt: router.directSystemPrompt(ctx.lang) };
141
- }
142
- // 조용한 라우팅 금지 — 누가 왜 선택됐는지(또는 왜 판정을 못 했는지) 반드시 출력.
143
- if (choice && choice.note) ctx.err(ctx.uiInstance.c.dim(choice.note));
144
- }
145
-
146
136
  // 에이전트가 확정된 뒤 에이전트별 오버라이드를 포함한 전체 사다리를 다시 해석한다.
147
137
  // 명시 runtime/model/effort 핀은 항상 최상단이라 에이전트 기본값보다 우선한다.
148
138
  if (agent && agent.id) {
@@ -167,7 +157,6 @@ async function runOnce(ctx, args) {
167
157
  }
168
158
 
169
159
  const permission = permissions.normalize(parsed.permission || (ctx.prefs && ctx.prefs.permission) || "write");
170
- const cwd = projectCwd();
171
160
  try {
172
161
  ensureTerminalProjectForExecutionCli(db, cwd, permission, "terminal-run");
173
162
  } catch (e) {
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { listRoutableAgents } = require("../agents/registry.cjs");
6
+
7
+ function canonicalPath(value) {
8
+ const resolved = path.resolve(String(value || ""));
9
+ try {
10
+ return fs.realpathSync.native(resolved);
11
+ } catch {
12
+ return resolved;
13
+ }
14
+ }
15
+
16
+ function pathContains(parent, child) {
17
+ const relative = path.relative(parent, child);
18
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
19
+ }
20
+
21
+ function projectColumns(db) {
22
+ try {
23
+ return new Set(db.prepare("PRAGMA table_info(projects)").all().map((row) => row.name));
24
+ } catch {
25
+ return new Set();
26
+ }
27
+ }
28
+
29
+ function parseAgentPool(raw) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(raw || "[]");
33
+ } catch {
34
+ throw new Error("This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again.");
35
+ }
36
+ if (!Array.isArray(parsed)) {
37
+ throw new Error("This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again.");
38
+ }
39
+ return parsed.filter((member) => member && typeof member === "object"
40
+ && typeof member.agentId === "string" && member.agentId.trim()
41
+ && ["local", "cloud", "hub"].includes(member.source)
42
+ && typeof member.nameSnapshot === "string");
43
+ }
44
+
45
+ function resolveProjectForCwd(db, cwd) {
46
+ const columns = projectColumns(db);
47
+ if (!columns.has("folder_path") || !columns.has("agent_pool_json")) {
48
+ throw new Error("This Agentlas data store does not support project teams yet. Open the latest Agentlas Desktop once, then retry.");
49
+ }
50
+ const rows = db.prepare(
51
+ `SELECT id, name, system_prompt, agent_pool_json, source_type, source_ref, folder_path
52
+ FROM projects
53
+ WHERE folder_path IS NOT NULL AND trim(folder_path) <> ''`,
54
+ ).all();
55
+ const target = canonicalPath(cwd);
56
+ const matches = rows.map((row) => ({ row, root: canonicalPath(row.folder_path) }))
57
+ .filter(({ root }) => pathContains(root, target))
58
+ .sort((a, b) => b.root.length - a.root.length);
59
+ if (!matches.length) {
60
+ throw new Error("This folder is not connected to an Agentlas project. Connect it in Desktop Work, or pass an exact agent for an advanced direct invocation.");
61
+ }
62
+ const bestLength = matches[0].root.length;
63
+ const best = matches.filter((entry) => entry.root.length === bestLength);
64
+ if (best.length !== 1) {
65
+ throw new Error("More than one Agentlas project is connected to this folder. Keep one source connection, then retry.");
66
+ }
67
+ return { ...best[0].row, rootPath: best[0].root, agentPool: parseAgentPool(best[0].row.agent_pool_json) };
68
+ }
69
+
70
+ function resolveProjectController(db, cwd) {
71
+ const project = resolveProjectForCwd(db, cwd);
72
+ if (!project.agentPool.length) {
73
+ throw new Error("This project has no agent team. Drag agents into the project in Desktop Work, then retry.");
74
+ }
75
+ const controllerRef = project.agentPool[0];
76
+ if (controllerRef.source !== "local") {
77
+ throw new Error(`The project controller “${controllerRef.nameSnapshot}” is not installed locally for Terminal execution. Install that exact release locally or reorder the project team.`);
78
+ }
79
+ const controller = listRoutableAgents(db).find((agent) => agent.id === controllerRef.agentId) || null;
80
+ if (!controller) {
81
+ throw new Error(`The project controller “${controllerRef.nameSnapshot}” is unavailable. Restore that agent or explicitly choose a new first agent in Desktop Work.`);
82
+ }
83
+ return { project, controller };
84
+ }
85
+
86
+ function withProjectControllerContext(controller, project) {
87
+ const team = project.agentPool.map((member, index) =>
88
+ `${index + 1}. ${member.nameSnapshot} (${member.source}${member.releaseId ? `, exact release ${member.releaseId}` : ""})`,
89
+ ).join("\n");
90
+ const projectInstruction = String(project.system_prompt || "").trim();
91
+ const ownership = [
92
+ `You are the controller for the Agentlas Work project “${project.name}”.`,
93
+ "You own this task. The remaining ordered project members are eligible sub-agents for task-scoped WorkOrders only.",
94
+ "Do not transfer task ownership, silently substitute an unavailable member, or claim a sub-agent ran without an assignment and execution receipt.",
95
+ "Project team:",
96
+ team,
97
+ ].join("\n");
98
+ return {
99
+ ...controller,
100
+ systemPrompt: [controller.systemPrompt, projectInstruction && `Project system prompt:\n${projectInstruction}`, ownership]
101
+ .filter(Boolean)
102
+ .join("\n\n"),
103
+ };
104
+ }
105
+
106
+ module.exports = {
107
+ canonicalPath,
108
+ pathContains,
109
+ parseAgentPool,
110
+ resolveProjectForCwd,
111
+ resolveProjectController,
112
+ withProjectControllerContext,
113
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"