agentlas 1.0.61 → 1.0.63
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 +24 -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.cjs +6 -3
- package/engine/automation/daemon.cjs +36 -3
- package/engine/automation/store.cjs +13 -11
- package/engine/bootstrap-schema.sql +30 -2
- package/engine/cloud/auth.cjs +34 -8
- package/engine/commands/connect.cjs +34 -6
- package/engine/commands/oberon.cjs +1 -1
- package/engine/commands/update.cjs +28 -7
- package/engine/core/desktop-core-fetch.cjs +94 -21
- package/engine/hephaestus/local-core.cjs +44 -9
- package/engine/memory-cli/curate.cjs +5 -2
- package/engine/oberon/outputs.cjs +10 -3
- package/engine/project/career-graph.cjs +4 -2
- package/engine/project/memory-context.cjs +9 -6
- package/engine/project/ontology.cjs +17 -7
- package/engine/runtimes/acp-driver.cjs +42 -3
- package/engine/sessions/session.cjs +18 -0
- package/engine/telegram/connect.cjs +31 -10
- package/engine/ui/screens.cjs +30 -6
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/package.json +3 -1
|
@@ -183,6 +183,13 @@ class Session extends EventEmitter {
|
|
|
183
183
|
this.lastError = null;
|
|
184
184
|
this._privateRecoveryEvidence.length = 0;
|
|
185
185
|
this._record({ type: "turn-start", at: Date.now(), prompt });
|
|
186
|
+
// ACP agents that cannot load a provider-side session need prior conversation
|
|
187
|
+
// reattached on a fresh session. Capture it before appending this turn so the
|
|
188
|
+
// current user prompt is not duplicated in both history and userPrompt.
|
|
189
|
+
const priorHistory = store.chatHistory(this.db, this.chatId).map((row) => ({
|
|
190
|
+
role: row.role,
|
|
191
|
+
text: row.text,
|
|
192
|
+
}));
|
|
186
193
|
store.appendMessage(this.db, this.chatId, "user", prompt);
|
|
187
194
|
let governedTurn = null;
|
|
188
195
|
try {
|
|
@@ -260,6 +267,9 @@ class Session extends EventEmitter {
|
|
|
260
267
|
this._apiAbort = null;
|
|
261
268
|
}
|
|
262
269
|
} else {
|
|
270
|
+
const isAcpRuntime = require("../runtimes/acp-driver.cjs").ACP_KINDS.has(this.runtime.kind);
|
|
271
|
+
const turnAbort = isAcpRuntime ? new AbortController() : null;
|
|
272
|
+
if (turnAbort) this._apiAbort = turnAbort;
|
|
263
273
|
const req = {
|
|
264
274
|
kind: this.runtime.kind,
|
|
265
275
|
bin: this.runtime.bin,
|
|
@@ -269,6 +279,11 @@ class Session extends EventEmitter {
|
|
|
269
279
|
systemPrompt,
|
|
270
280
|
permission: this.permission,
|
|
271
281
|
session: { ...this.runtimeSession },
|
|
282
|
+
history: priorHistory,
|
|
283
|
+
chatId: this.chatId,
|
|
284
|
+
agentId: this.agent.id,
|
|
285
|
+
locale: this.lang,
|
|
286
|
+
sessionFingerprintSeed: this.fingerprint,
|
|
272
287
|
model: this.runtime.model,
|
|
273
288
|
effort: this.runtime.effort,
|
|
274
289
|
// 사용자가 이미 동의한 MCP 서버를 턴에 싣는다.
|
|
@@ -286,6 +301,7 @@ class Session extends EventEmitter {
|
|
|
286
301
|
mcpServers: this._consentedMcpServers(),
|
|
287
302
|
onSpawn: (child) => { this._child = child; },
|
|
288
303
|
};
|
|
304
|
+
if (turnAbort) req.signal = turnAbort.signal;
|
|
289
305
|
if (this._spawnImpl) req.spawn = this._spawnImpl;
|
|
290
306
|
if (this._timeoutConfig) req.timeoutConfig = this._timeoutConfig;
|
|
291
307
|
try {
|
|
@@ -325,6 +341,8 @@ class Session extends EventEmitter {
|
|
|
325
341
|
}
|
|
326
342
|
} catch (e) {
|
|
327
343
|
res = { text: "", session: req.session, error: (e && e.message) || String(e) };
|
|
344
|
+
} finally {
|
|
345
|
+
if (turnAbort && this._apiAbort === turnAbort) this._apiAbort = null;
|
|
328
346
|
}
|
|
329
347
|
}
|
|
330
348
|
this._child = null;
|
|
@@ -55,10 +55,15 @@ async function verifyBotToken(token, opts) {
|
|
|
55
55
|
function tokenDir() {
|
|
56
56
|
const dir = path.join(userDataDir(), "telegram");
|
|
57
57
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
58
|
+
if (process.platform !== "win32") fs.chmodSync(dir, 0o700);
|
|
58
59
|
return dir;
|
|
59
60
|
}
|
|
60
61
|
function tokenFile(id) { return path.join(tokenDir(), `${id}.token`); }
|
|
61
|
-
function saveToken(id, token) {
|
|
62
|
+
function saveToken(id, token) {
|
|
63
|
+
const file = tokenFile(id);
|
|
64
|
+
fs.writeFileSync(file, token, { encoding: "utf8", mode: 0o600 });
|
|
65
|
+
if (process.platform !== "win32") fs.chmodSync(file, 0o600);
|
|
66
|
+
}
|
|
62
67
|
function readToken(id) {
|
|
63
68
|
try { return fs.readFileSync(tokenFile(id), "utf8").trim() || null; } catch { return null; }
|
|
64
69
|
}
|
|
@@ -81,19 +86,31 @@ async function startConnection(db, targetKind, targetId, token, opts) {
|
|
|
81
86
|
runWriteTransaction(db, () => {
|
|
82
87
|
db.prepare(
|
|
83
88
|
"INSERT INTO telegram_bindings (id, target_kind, target_id, bot_user_id, bot_username, bot_display_name, status, enabled, token_saved, token_fingerprint, created_at, updated_at) " +
|
|
84
|
-
"VALUES (?,?,?,?,?,?,'waiting_for_chat',1,
|
|
89
|
+
"VALUES (?,?,?,?,?,?,'waiting_for_chat',1,0,?,?,?)",
|
|
85
90
|
).run(id, targetKind, targetId, me.id, me.username || null, me.first_name || null, tokenFingerprint(token), now, now);
|
|
86
91
|
});
|
|
87
|
-
|
|
92
|
+
try {
|
|
93
|
+
saveToken(id, token);
|
|
94
|
+
runWriteTransaction(db, () => {
|
|
95
|
+
db.prepare("UPDATE telegram_bindings SET token_saved=1, updated_at=? WHERE id=?").run(new Date().toISOString(), id);
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
deleteToken(id);
|
|
99
|
+
try {
|
|
100
|
+
runWriteTransaction(db, () => db.prepare("DELETE FROM telegram_bindings WHERE id=?").run(id));
|
|
101
|
+
} catch { /* preserve the original storage failure */ }
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
88
104
|
// 남은 웹훅이 있으면 getUpdates가 막히므로 제거(있어도 무해).
|
|
89
105
|
await telegramApi(token, "deleteWebhook", { drop_pending_updates: false }, opts).catch(() => null);
|
|
90
106
|
return { id, botUsername: me.username || null };
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
/**
|
|
94
|
-
* getUpdates 폴링으로
|
|
95
|
-
* 보안:
|
|
96
|
-
*
|
|
110
|
+
* getUpdates 폴링으로 `/start <bindingId>`를 보낸 private chat을 이 바인딩에 귀속한다.
|
|
111
|
+
* 보안: 봇 이름을 발견한 제3자의 첫 메시지가 로컬 에이전트를 탈취하지 못하도록 정확한
|
|
112
|
+
* 페어링 토큰을 요구한다. 최종 UPDATE도 미페어링·enabled·waiting 상태를 조건으로 삼아
|
|
113
|
+
* 두 Terminal 프로세스가 같은 바인딩을 동시에 덮어쓰지 못하게 한다.
|
|
97
114
|
*/
|
|
98
115
|
async function pairByPolling(db, id, { timeoutMs = 120_000, opts, onWait } = {}) {
|
|
99
116
|
const token = readToken(id);
|
|
@@ -110,6 +127,8 @@ async function pairByPolling(db, id, { timeoutMs = 120_000, opts, onWait } = {})
|
|
|
110
127
|
if (typeof update.update_id === "number") offset = Math.max(offset, update.update_id);
|
|
111
128
|
const message = update.message;
|
|
112
129
|
if (!message || !message.chat || message.chat.type !== "private") continue;
|
|
130
|
+
const pairingToken = String(message.text || "").match(/^\/start(?:@\w+)?\s+(\S+)/i)?.[1] || "";
|
|
131
|
+
if (pairingToken !== id) continue;
|
|
113
132
|
// 신선 미페어링 바인딩인지 재확인(경합 방지) 후 귀속.
|
|
114
133
|
const row = getBinding(db, id);
|
|
115
134
|
if (!row || row.telegram_chat_id || row.status !== "waiting_for_chat") continue;
|
|
@@ -117,11 +136,13 @@ async function pairByPolling(db, id, { timeoutMs = 120_000, opts, onWait } = {})
|
|
|
117
136
|
if (!Number.isFinite(createdAt) || Date.now() - createdAt > 30 * 60 * 1000) throw new Error("pairing window expired (30 min) — reconnect");
|
|
118
137
|
const now = new Date().toISOString();
|
|
119
138
|
const title = message.chat.title || [message.chat.first_name, message.chat.last_name].filter(Boolean).join(" ") || message.chat.username || String(message.chat.id);
|
|
120
|
-
runWriteTransaction(db, () => {
|
|
121
|
-
db.prepare(
|
|
122
|
-
|
|
139
|
+
const claimed = runWriteTransaction(db, () => {
|
|
140
|
+
return db.prepare(
|
|
141
|
+
"UPDATE telegram_bindings SET telegram_chat_id=?, telegram_chat_title=?, status='chat_paired', last_update_id=?, updated_at=? " +
|
|
142
|
+
"WHERE id=? AND telegram_chat_id IS NULL AND enabled=1 AND status='waiting_for_chat'",
|
|
143
|
+
).run(String(message.chat.id), title, offset, now, id).changes === 1;
|
|
123
144
|
});
|
|
124
|
-
return getBinding(db, id);
|
|
145
|
+
if (claimed) return getBinding(db, id);
|
|
125
146
|
}
|
|
126
147
|
if (offset) {
|
|
127
148
|
runWriteTransaction(db, () => {
|
package/engine/ui/screens.cjs
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* - 모든 화면은 ctx.out 이 아니라 ui 를 직접 받아 pi 프레임 안에 그린다.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
const path = require("node:path");
|
|
15
|
+
|
|
14
16
|
function table(ui, rows, opts = {}) {
|
|
15
17
|
// rows: [[col, col, …]] — 첫 행이 헤더. 폭은 CJK 셀 폭으로 계산한다.
|
|
16
18
|
const { visWidth, truncateWidth } = require("./width.cjs");
|
|
@@ -52,6 +54,28 @@ function rows(db, sql, args = []) {
|
|
|
52
54
|
}
|
|
53
55
|
const shortTs = (v) => (v ? String(v).replace("T", " ").slice(0, 16) : "");
|
|
54
56
|
|
|
57
|
+
const ATTENTION_RUN_WHERE = `
|
|
58
|
+
r.id = (
|
|
59
|
+
SELECT r2.id FROM automation_runs r2
|
|
60
|
+
WHERE r2.automation_id IS r.automation_id
|
|
61
|
+
ORDER BY COALESCE(r2.started_at,'') DESC, r2.rowid DESC LIMIT 1
|
|
62
|
+
)
|
|
63
|
+
AND (
|
|
64
|
+
r.status IN ('error','partial','blocked','needs_input')
|
|
65
|
+
OR r.outcome IN ('needs_input','blocked','rejected')
|
|
66
|
+
OR (r.status = 'running' AND julianday(r.last_activity_at) < julianday('now','-15 minutes'))
|
|
67
|
+
)`;
|
|
68
|
+
|
|
69
|
+
function pathContains(root, candidate) {
|
|
70
|
+
if (!root) return false;
|
|
71
|
+
try {
|
|
72
|
+
const relative = path.relative(path.resolve(String(root)), path.resolve(String(candidate)));
|
|
73
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
/* ── /dashboard — 데스크탑 dashboard 의 관제 패널 집합 ── */
|
|
56
80
|
function dashboard(ui, db, en) {
|
|
57
81
|
const chip = (paint, s) => paint(` ${s} `);
|
|
@@ -66,11 +90,12 @@ function dashboard(ui, db, en) {
|
|
|
66
90
|
ui.line(` ${chip(ui.c.inverse, `${en ? "agents" : "에이전트"} ${local}`)} ${chip(ui.c.dim, `builtin ${builtin}`)} ${chip(ui.c.inverse, `${en ? "firms" : "회사"} ${firms}`)} ${chip(ui.c.dim, `${en ? "bookmarks" : "북마크"} ${marks}`)} ${chip(ui.c.dim, `${en ? "borrowed" : "대여"} ${borrowed}`)}`);
|
|
67
91
|
|
|
68
92
|
// ── 확인 필요 (D1 숨은 계약 2: 없으면 실행이 조용히 멈춘 채 정상처럼 보인다) ──
|
|
69
|
-
const pending = count(db,
|
|
93
|
+
const pending = count(db, `SELECT COUNT(*) n FROM automation_runs r WHERE ${ATTENTION_RUN_WHERE}`);
|
|
70
94
|
const stalled = rows(db,
|
|
71
95
|
`SELECT r.id, a.name, r.status, r.last_activity_at
|
|
72
96
|
FROM automation_runs r LEFT JOIN automations a ON a.id = r.automation_id
|
|
73
|
-
WHERE
|
|
97
|
+
WHERE ${ATTENTION_RUN_WHERE}
|
|
98
|
+
ORDER BY COALESCE(r.last_activity_at,'') DESC LIMIT 5`);
|
|
74
99
|
ui.line("");
|
|
75
100
|
ui.line(ui.c.bold(en ? "Needs attention" : "확인 필요"));
|
|
76
101
|
if (!pending && !stalled.length) {
|
|
@@ -242,10 +267,10 @@ function projects(ui, db, en) {
|
|
|
242
267
|
if (list.length) {
|
|
243
268
|
table(ui, [[en ? "project" : "프로젝트", en ? "source" : "소스", en ? "chats" : "채팅", en ? "tasks" : "작업", en ? "updated" : "수정"],
|
|
244
269
|
...list.map((p) => [
|
|
245
|
-
(p.folder_path
|
|
270
|
+
(pathContains(p.folder_path, cwd) ? "▸ " : " ") + (p.name || p.id),
|
|
246
271
|
p.source_type || "local", String(p.chats), String(p.tasks), shortTs(p.updated_at)])],
|
|
247
272
|
{ cap: [30, 10, 6, 6, 16] });
|
|
248
|
-
const here = list.find((p) => p.folder_path
|
|
273
|
+
const here = list.find((p) => pathContains(p.folder_path, cwd));
|
|
249
274
|
ui.line("");
|
|
250
275
|
ui.line(here
|
|
251
276
|
? ui.c.dim(en ? `▸ this folder is connected to "${here.name}"` : `▸ 이 폴더는 "${here.name}"에 연결돼 있습니다`)
|
|
@@ -350,5 +375,4 @@ function firms(ui, db, en, ctx, arg) {
|
|
|
350
375
|
void ctx;
|
|
351
376
|
}
|
|
352
377
|
|
|
353
|
-
module.exports = { dashboard, library, marketplace, settings, projects, automations, firms, table };
|
|
354
|
-
|
|
378
|
+
module.exports = { dashboard, library, marketplace, settings, projects, automations, firms, table, pathContains, ATTENTION_RUN_WHERE };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "
|
|
3
|
-
"url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-
|
|
4
|
-
"sha256": "
|
|
5
|
-
"sizeBytes":
|
|
6
|
-
"writtenAt": "2026-08-
|
|
2
|
+
"version": "15",
|
|
3
|
+
"url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-v15/desktop-core.tar.gz",
|
|
4
|
+
"sha256": "df39404b55ea4e6a3571e416167f64e75bcde92fdb86f19bd26e54746156f87b",
|
|
5
|
+
"sizeBytes": 18167726,
|
|
6
|
+
"writtenAt": "2026-08-29T08:38:53.468Z"
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.63",
|
|
4
4
|
"description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"files": [
|
|
28
28
|
"bin/",
|
|
29
29
|
"engine/",
|
|
30
|
+
"!engine/vendor/desktop-core/",
|
|
31
|
+
"!engine/vendor/desktop-core.tar.gz",
|
|
30
32
|
"install.sh",
|
|
31
33
|
"install.ps1",
|
|
32
34
|
"CHANGELOG.md",
|