agentlas 1.0.38 → 1.0.40

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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.40 — 2026-08-11
4
+
5
+ Desktop surfaces reach the pi shell: dashboard and graph view.
6
+
7
+ - `/dashboard` (pi shell): agents/firms/telegram badges + automations panel
8
+ (enabled state, last run) from the local store — first of the thirteen
9
+ Desktop-only honest stops to be lifted.
10
+ - `/graph show <name>` (pi shell): the automation graph renders as Unicode
11
+ box art (grok-mermaid) — condition branches, loop-back edges, and Korean
12
+ labels align correctly. No canvas emulation; editing stays declarative.
13
+ - First-run onboarding now runs before the pi shell starts (sequential
14
+ readline → pi-tui, no stdin contention), so `AGENTLAS_TUI=pi` works for
15
+ brand-new installs too.
16
+
17
+ ## 1.0.39 — 2026-08-11
18
+
19
+ pi-tui shell increment 2 + architecture mirror sync.
20
+
21
+ - Experimental pi shell (`AGENTLAS_TUI=pi`): persisted history (cli-history.json
22
+ v2 contract), Shift-Tab permission cycling (same two-step FULL arming state
23
+ machine as the classic REPL), `!` shell passthrough (full-permission gate,
24
+ secret masking), and `/s` `/switch` `/kill` `/rm` `/sessions` `/tree` session
25
+ observation wired to the same orchestrator/renderer pair.
26
+ - Streaming display masks the Memory Events envelope (runtime contract, not
27
+ user-facing text); the harvest pipeline is unchanged.
28
+ - ARCHITECTURE_VERSION mirror synced 1.7.0 → 1.7.1 (vendor regenerated from
29
+ the desktop dist, value-level parity gate green).
30
+
3
31
  ## 1.0.38 — 2026-08-11
4
32
 
5
33
  Silence was the worst failure mode — this release makes failures speak.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.0",
2
+ "version": "1.7.1",
3
3
  "emitterBlock": "## Memory (Agentlas curated memory)\n\nAt the end of EVERY completed normal reply, emit exactly one hidden Memory Events\nenvelope. The runtime removes it before display. This envelope is the per-turn receipt:\nalways include a compact safe turn_summary, and use an empty candidates array when\nnothing durable was learned. Do not skip the envelope.\n\nRules:\n- Never include secrets, credentials, API keys, raw logs, or full transcripts.\n- Real credential values may live only in local project .env/.env.local,\n ignored signing/ or credentials/ files, or a local keychain/vault. Memory\n Events may mention env names and local relative paths only.\n- For deploy, release, store, billing, auth, API, or cloud work, first read the\n project's .agentlas/local-credentials.map.json and the top\n \"Local Credential Index\" section of .agentlas/project-soul-memory.md\n before saying a credential is missing.\n- One candidate per durable item. Keep \"content\" to one or two sentences.\n- \"memory_kind\": fact | decision | preference | risk | procedure | hypothesis | evidence | deprecation | conflict\n- \"suggested_scope\": user_identity | team_memory | project (this folder) | agent_repo | session (temporary) | discard\n- Use user_identity for a stable operator preference or personal fact (their name, role, language, tone,\n how they want you to behave) — these must outlive any one project. The curator only files user_identity\n when you label it so with \"confidence\": \"high\"; it never promotes into that scope, so a preference emitted\n at lower confidence is demoted to a throwaway session note.\n- \"agent_team\" is accepted only as a legacy alias for team_memory.\n- Add \"request_context\" when it improves future recall: user_intent, trigger_terms,\n cwd_at_request, target_project, target_path, cross_context, outcome.\n- Never put the raw user prompt or transcript in request_context.\n- Suggest a scope; the separate Memory Curator decides the final destination.\n- turn_summary is one value-free sentence about the completed outcome. It is not the\n user prompt, a transcript, raw log, secret, or absolute local path.\n\nFormat (always emit, including an empty candidates array):\n\n## Memory Events\n```json\n{\n \"schema_version\": \"agentlas.memory-ticket.v1\",\n \"turn_summary\": \"Completed outcome in one safe sentence.\",\n \"candidates\": [\n {\n \"memory_kind\": \"decision\",\n \"content\": \"...\",\n \"suggested_scope\": \"project\",\n \"confidence\": \"high\",\n \"sensitivity\": \"internal\",\n \"evidence_refs\": [],\n \"request_context\": {\n \"user_intent\": \"...\",\n \"trigger_terms\": [\"...\"],\n \"cwd_at_request\": null,\n \"target_project\": null,\n \"target_path\": null,\n \"cross_context\": false,\n \"outcome\": \"...\"\n }\n }\n ]\n}\n```",
4
4
  "eventsHeading": "## Memory Events",
5
5
  "memoryDir": ".agentlas",
@@ -24,6 +24,7 @@ const SLASH_COMMANDS = [
24
24
  { command: "/graph", args: "[run <이름>]", ko: "저장된 자동화 그래프", en: "Saved automation graphs" },
25
25
  { command: "/mcp", args: "", ko: "MCP 서버 목록", en: "MCP servers" },
26
26
  { command: "/doctor", args: "", ko: "런타임·데이터 점검", en: "Health check" },
27
+ { command: "/dashboard", args: "", ko: "관제 대시보드 (pi 셸)", en: "Dashboard (pi shell)" },
27
28
  { command: "/runtime", args: "<kind>", ko: "새 세션 런타임 지정", en: "Set runtime for new sessions" },
28
29
  { command: "/model", args: "<id|default>", ko: "새 세션 모델 지정", en: "Set model for new sessions" },
29
30
  { command: "/effort", args: "<level|none>", ko: "새 세션 추론 강도 지정", en: "Set effort for new sessions" },
@@ -202,6 +202,26 @@ async function startPiShell(ctx, opts = {}) {
202
202
  const editor = new pi.Editor(tui, editorTheme, { autocompleteMaxVisible: 8 });
203
203
  editor.setAutocompleteProvider(new pi.CombinedAutocompleteProvider(toSlashCommands(ctx.lang), process.cwd()));
204
204
 
205
+ // ── 히스토리 디스크 영속 (증분 2) — cli-history.json v2 계약을 그대로 재사용 ──
206
+ const input = require("../agentlas-input.cjs");
207
+ const historyLedger = (() => { try { return input.loadHistory(process.cwd()); } catch { return []; } })();
208
+ // readline 히스토리는 최신-우선 배열 — Editor에는 과거→최신 순으로 먹인다.
209
+ for (const entry of [...historyLedger].reverse()) editor.addToHistory(entry);
210
+ const recordHistory = (line) => {
211
+ historyLedger.unshift(line);
212
+ if (historyLedger.length > input.HISTORY_MAX) historyLedger.length = input.HISTORY_MAX;
213
+ try { input.saveHistory(historyLedger, process.cwd()); } catch { /* 히스토리는 최선노력 */ }
214
+ };
215
+
216
+ // ── Shift-Tab 권한 순환 (증분 2) — repl의 순수 상태기계를 그대로 재사용 ──
217
+ const { createPermissionShortcut } = require("./repl.cjs");
218
+ const permShortcut = createPermissionShortcut({
219
+ lang: ctx.lang,
220
+ getPermission: () => permission,
221
+ setPermission: (level) => { permission = level; },
222
+ onMessage: (msg) => { ui.ensureNl(); ui.line(ui.c.dim(msg.text)); },
223
+ });
224
+
205
225
  const commands = require("../commands/index.cjs");
206
226
  const handleSlash = async (cmdline) => {
207
227
  const raw = cmdline.split(/\s+/)[0] || "";
@@ -212,6 +232,82 @@ async function startPiShell(ctx, opts = {}) {
212
232
  ui.line(palette.renderPalette(ctx.lang));
213
233
  return;
214
234
  }
235
+ // 그래프 보기 (Phase 4) — 캔버스를 흉내내지 않는다: mermaid → 유니코드 박스 아트.
236
+ if (cmd === "graph" && rest[0] === "show" && rest[1]) {
237
+ const name = rest.slice(1).join(" ");
238
+ const row = db.prepare(
239
+ "SELECT name, graph_json FROM automations WHERE name = ? AND graph_json IS NOT NULL").get(name);
240
+ if (row) {
241
+ try {
242
+ const g = JSON.parse(row.graph_json);
243
+ const esc = (s) => String(s || "").replace(/["[\]{}|<>]/g, " ").trim().slice(0, 28) || "·";
244
+ const lines = ["flowchart TD"];
245
+ for (const n of g.nodes || []) {
246
+ const label = esc(n.label || n.id);
247
+ lines.push(n.type === "condition" ? ` ${n.id}{${label}}` : ` ${n.id}[${label}]`);
248
+ }
249
+ for (const e of g.edges || []) {
250
+ const lbl = e.sourceHandle === "true" ? "|참|" : e.sourceHandle === "false" ? "|거짓|" : "";
251
+ lines.push(` ${e.source} -->${lbl} ${e.target}`);
252
+ }
253
+ const { render, toAnsi } = require("grok-mermaid");
254
+ const art = toAnsi(render(lines.join("\n")));
255
+ ui.ensureNl();
256
+ ui.line(ui.c.bold(row.name));
257
+ for (const rowLine of (Array.isArray(art) ? art : String(art).split("\n"))) ui.line(rowLine);
258
+ ui.line("");
259
+ return;
260
+ } catch { /* 렌더 실패 → 아래 클래식 폴스루가 텍스트로 보여준다 */ }
261
+ }
262
+ }
263
+ // 대시보드 (Phase 3-2) — 데스크탑 정직 정지 13종 중 첫 해제. 로컬 DB 직조회.
264
+ if (cmd === "dashboard") {
265
+ const chip = (paint, s) => paint(` ${s} `);
266
+ const agents = db.prepare(
267
+ "SELECT COALESCE(NULLIF(entity_kind,''),'agent') k, builtin, COUNT(*) n FROM installed_agents WHERE COALESCE(visibility,'')!='background' GROUP BY k, builtin").all();
268
+ const firms = db.prepare("SELECT COUNT(*) n FROM firms").get().n;
269
+ const autos = db.prepare(
270
+ "SELECT name, enabled, last_run_at FROM automations ORDER BY COALESCE(last_run_at,'') DESC LIMIT 5").all();
271
+ const autoTotal = db.prepare("SELECT COUNT(*) n, SUM(enabled) e FROM automations").get();
272
+ const tg = db.prepare("SELECT COUNT(*) n FROM telegram_bindings").get().n;
273
+ ui.ensureNl();
274
+ ui.line(ui.c.bold(en ? "Dashboard" : "대시보드"));
275
+ const local = agents.filter(a => !a.builtin).reduce((s, a) => s + a.n, 0);
276
+ const builtin = agents.filter(a => a.builtin).reduce((s, a) => s + a.n, 0);
277
+ ui.line(` ${chip(ui.c.inverse, en ? `agents ${local}` : `에이전트 ${local}`)} ${chip(ui.c.dim, `builtin ${builtin}`)} ${chip(ui.c.inverse, en ? `firms ${firms}` : `회사 ${firms}`)} ${chip(ui.c.dim, `telegram ${tg}`)}`);
278
+ ui.line("");
279
+ ui.line(ui.c.bold(en ? `Automations ${autoTotal.e || 0}/${autoTotal.n} on` : `자동화 ${autoTotal.e || 0}/${autoTotal.n} 켜짐`));
280
+ for (const a of autos) {
281
+ const st = a.enabled ? ui.c.green("●") : ui.c.faint("○");
282
+ ui.line(` ${st} ${a.name}${a.last_run_at ? ui.c.dim(` · ${String(a.last_run_at).slice(0, 16)}`) : ""}`);
283
+ }
284
+ if (!autos.length) ui.line(ui.c.dim(en ? " (none — /automation add)" : " (없음 — /automation add)"));
285
+ ui.line("");
286
+ ui.line(ui.c.dim(en
287
+ ? "details: /agents · /automation list · /usage · /sessions"
288
+ : "자세히: /agents · /automation list · /usage · /sessions"));
289
+ return;
290
+ }
291
+ // 세션 관찰/전환 (증분 2b) — 기본 REPL과 같은 orch/renderer 배선
292
+ if (cmd === "sessions" || cmd === "tree") {
293
+ require("./repl.cjs").printSessions(shellCtx, orch);
294
+ return;
295
+ }
296
+ if (cmd === "s" || cmd === "switch" || cmd === "kill" || cmd === "rm") {
297
+ const token = rest[0];
298
+ if (!token) { ui.line(ui.c.dim(`Usage: /${cmd} <n>`)); return; }
299
+ const key = String(token).startsWith("s") ? token : `s${token}`;
300
+ if (cmd === "kill") { orch.kill(key); return; }
301
+ if (cmd === "rm") {
302
+ orch.remove(key);
303
+ const act = orch.active();
304
+ if (act) renderer.attach(act, { replay: false });
305
+ return;
306
+ }
307
+ const session = orch.setActive(key);
308
+ renderer.attach(session, { replay: true });
309
+ return;
310
+ }
215
311
  const EXCLUDED = new Set(["firm", "setup", "run"]);
216
312
  if (!EXCLUDED.has(cmd) && commands.COMMANDS[cmd]) {
217
313
  await commands.COMMANDS[cmd]().run(shellCtx, rest);
@@ -229,10 +325,16 @@ async function startPiShell(ctx, opts = {}) {
229
325
  const input = String(text || "").trim();
230
326
  if (!input) return;
231
327
  editor.addToHistory(input);
328
+ recordHistory(input);
232
329
  editor.setText("");
233
330
  ui.ensureNl();
234
331
  ui.line(ui.c.emerald("› ") + ui.c.text(input));
235
332
  (async () => {
333
+ if (input.startsWith("!")) {
334
+ await require("./repl.cjs").runShell(shellCtx, input.slice(1).trim(), permission)
335
+ .catch((e) => { if (e && (e.code || e.honestStop)) ui.error(e); else ui.error(); });
336
+ return;
337
+ }
236
338
  if (input.startsWith("/")) {
237
339
  const verdict = await handleSlash(input.slice(1)).catch((e) => {
238
340
  if (e && (e.code || e.honestStop)) ui.error(e);
@@ -265,6 +367,13 @@ async function startPiShell(ctx, opts = {}) {
265
367
  };
266
368
 
267
369
  tui.addInputListener((data) => {
370
+ // Shift-Tab 권한 순환 — pi-tui 가 raw mode 를 단독 소유하므로 readline 의
371
+ // swallowCompletion 우회 없이 여기서 직접 소비한다 (D2 위험 2의 해소 형태).
372
+ if (pi.matchesKey(data, "shift+tab")) {
373
+ permShortcut.handleKey("", { name: "tab", shift: true });
374
+ return { handled: true };
375
+ }
376
+ if (permShortcut.armed()) permShortcut.handleKey("", { name: "other" }); // 다른 키 = 무장 해제
268
377
  if (pi.matchesKey(data, "ctrl+c")) {
269
378
  const active = orch.active();
270
379
  if (active && active.isBusy()) {
@@ -86,14 +86,6 @@ function pickProjectController(db, cwd = process.cwd()) {
86
86
  }
87
87
 
88
88
  async function startRepl(ctx, opts = {}) {
89
- /*
90
- * 실험 셸 opt-in (D3 Phase 2 증분 1): AGENTLAS_TUI=pi + TTY 일 때만.
91
- * 기본 REPL 이 정본이며, 온보딩 전(prefs.onboarded=false)에는 켜지 않는다 —
92
- * 마법사는 아직 readline 계약이다(Phase 2-2 에서 이전).
93
- */
94
- if (process.env.AGENTLAS_TUI === "pi" && process.stdin.isTTY && ctx.prefs && ctx.prefs.onboarded) {
95
- return require("./pitui-shell.cjs").startPiShell(ctx, opts);
96
- }
97
89
  // 마법사가 언어를 바꾸면 이 뒤의 문구도 따라가야 한다 — 아래 온보딩 블록에서 갱신한다.
98
90
  let en = ctx.lang === "en";
99
91
  const ui = ctx.uiInstance;
@@ -132,6 +124,14 @@ async function startRepl(ctx, opts = {}) {
132
124
  }
133
125
  }
134
126
 
127
+ /*
128
+ * 실험 셸 opt-in (D3 Phase 2): AGENTLAS_TUI=pi + TTY.
129
+ * 온보딩 마법사(위 readline 블록)가 먼저 끝난 뒤 진입한다 — 순차 실행이라
130
+ * stdin 경합이 없고, 첫 실행 사용자도 pi 셸을 쓸 수 있다(증분 2c).
131
+ */
132
+ if (process.env.AGENTLAS_TUI === "pi" && process.stdin.isTTY) {
133
+ return require("./pitui-shell.cjs").startPiShell(ctx, opts);
134
+ }
135
135
  const orch = new Orchestrator({ db, lang: ctx.lang });
136
136
  const renderer = new Renderer(ui);
137
137
  let permission = permissions.normalize(opts.permission || (ctx.prefs && ctx.prefs.permission) || "write");
@@ -786,4 +786,4 @@ function handleSlash(ctx, cmdline, api) {
786
786
  }
787
787
  }
788
788
 
789
- module.exports = { startRepl, printSessions, createPermissionShortcut };
789
+ module.exports = { startRepl, printSessions, createPermissionShortcut, runShell };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
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"
@@ -50,6 +50,7 @@
50
50
  "author": "Agentlas (https://agentlas.cloud)",
51
51
  "license": "Apache-2.0",
52
52
  "dependencies": {
53
- "@earendil-works/pi-tui": "0.84.1"
53
+ "@earendil-works/pi-tui": "0.84.1",
54
+ "grok-mermaid": "0.2.2"
54
55
  }
55
56
  }