From c59ab6f9e97339cd99d258f9527d6b44e2580c46 Mon Sep 17 00:00:00 2001 From: oratis Date: Sun, 14 Jun 2026 21:21:13 +0800 Subject: [PATCH] feat(dispatch): `lisa agents` CLI snapshot + R6 comment tidy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOUNDATIONS §4 (one `lisa ` command per pillar) — Dispatch was the only pillar without one (Reve has `lisa autonomy`, Soul `lisa soul summary`, Sense `lisa sense`). - src/cli/agents.ts + cli.ts — `lisa agents` spins the orchestrator hub briefly, lists every agent session grouped by agent (state, project, age, branch/last tool/pending-permission), then exits. Works without a running `serve`. Smoke-tested live: correctly snapshots claude-code sessions incl. this one. - completions: add `agents` to lisa.bash / _lisa / lisa.fish. - R6 tidy: the last two internal comments saying "idle/dreams" → "idle reflection (Reve)" (autonomy/runs.ts, tools/registry.ts). No user-facing "Dreams" brand remained; the README rename note + CSS tokens + the `dreaming` state name are intentional/internal (R6 permits internal history). 690 tests pass; typecheck + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- completions/_lisa | 1 + completions/lisa.bash | 2 +- completions/lisa.fish | 3 ++- src/autonomy/runs.ts | 2 +- src/cli.ts | 12 +++++++-- src/cli/agents.ts | 58 +++++++++++++++++++++++++++++++++++++++++++ src/tools/registry.ts | 2 +- 7 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/cli/agents.ts diff --git a/completions/_lisa b/completions/_lisa index c552ef6..766513f 100644 --- a/completions/_lisa +++ b/completions/_lisa @@ -71,6 +71,7 @@ _lisa_first() { 'model:local model lifecycle (list/install/use/health)' 'consent:consent for sensitive ambient signals' 'sense:recent ambient sense events' + 'agents:snapshot of agent sessions across observers' ) _describe 'subcommand' subcommands } diff --git a/completions/lisa.bash b/completions/lisa.bash index 681ac59..dd1c7a4 100644 --- a/completions/lisa.bash +++ b/completions/lisa.bash @@ -12,7 +12,7 @@ _lisa_completion() { local cur prev words cword _init_completion -n : 2>/dev/null || _get_comp_words_by_ref -n : cur prev words cword - local subcommands="resume sessions serve heartbeat autostart search birth soul channels skills wishlist status doctor monitor autonomy model consent sense" + local subcommands="resume sessions serve heartbeat autostart search birth soul channels skills wishlist status doctor monitor autonomy model consent sense agents" local global_flags="--model --provider --think --no-reflect --compact --approval --no-mcp --no-plugins --voice --idle --no-idle --help -h --version -v" local serve_flags="--web --imessage --channels --port" local skills_actions="list approve disable enable audit" diff --git a/completions/lisa.fish b/completions/lisa.fish index 30df435..98088e9 100644 --- a/completions/lisa.fish +++ b/completions/lisa.fish @@ -17,7 +17,7 @@ end # ── helper: did the user already type a subcommand? ───────────────── function __lisa_no_subcommand set cmd (commandline -opc) - set sub resume sessions serve heartbeat autostart search birth soul channels skills wishlist status doctor monitor autonomy model consent sense + set sub resume sessions serve heartbeat autostart search birth soul channels skills wishlist status doctor monitor autonomy model consent sense agents for word in $cmd[2..-1] if contains -- $word $sub return 1 @@ -55,6 +55,7 @@ complete -c lisa -n __lisa_no_subcommand -f -a autonomy -d "summarize autonomo complete -c lisa -n __lisa_no_subcommand -f -a model -d "local model lifecycle" complete -c lisa -n __lisa_no_subcommand -f -a consent -d "consent for ambient signals" complete -c lisa -n __lisa_no_subcommand -f -a sense -d "recent ambient sense events" +complete -c lisa -n __lisa_no_subcommand -f -a agents -d "agent sessions snapshot" # ── model / consent / sense sub-actions ───────────────────────────── complete -c lisa -n "__lisa_using_subcommand model" -f -a "list install use health" -d "model action" diff --git a/src/autonomy/runs.ts b/src/autonomy/runs.ts index 306e2cf..2a8f438 100644 --- a/src/autonomy/runs.ts +++ b/src/autonomy/runs.ts @@ -1,7 +1,7 @@ /** * Autonomy run ledger (PLAN_REVE_v1.0 R2). * - * Every self-driven run — idle/dreams, heartbeat tasks, desire pursuits, the + * Every self-driven run — idle reflection (Reve), heartbeat tasks, desire pursuits, the * weekly examen, end-of-session reflection — appends one structured record * here. Before this, autonomy was a black box: idle/heartbeat logged their * final text and a `silent` boolean, so you couldn't tell "did real work" from diff --git a/src/cli.ts b/src/cli.ts index 60131ce..73e58dd 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -63,6 +63,7 @@ INSPECTION all off): list, grant , revoke , revoke-all. lisa sense [list] Recent ambient sense events + granted signals. + lisa agents Snapshot of agent sessions across all observers. LIFECYCLE lisa birth Run the birth ritual (auto-runs on first launch). @@ -168,7 +169,8 @@ interface ParsedArgs { | "autonomy" | "model" | "consent" - | "sense"; + | "sense" + | "agents"; subargs: string[]; serveWeb: boolean; serveImessage: boolean; @@ -289,7 +291,8 @@ function parseArgs(argv: string[]): ParsedArgs { first === "autonomy" || first === "model" || first === "consent" || - first === "sense" + first === "sense" || + first === "agents" ) { out.subcommand = first; out.subargs = positional.slice(1); @@ -480,6 +483,11 @@ async function main(): Promise { process.exit(await runSenseCommand(args.subargs)); } + if (args.subcommand === "agents") { + const { runAgentsCommand } = await import("./cli/agents.js"); + process.exit(await runAgentsCommand(args.subargs)); + } + if (args.subcommand === "sessions") { const sessions = await listSessionsOnDisk(); for (const s of sessions) { diff --git a/src/cli/agents.ts b/src/cli/agents.ts new file mode 100644 index 0000000..28604eb --- /dev/null +++ b/src/cli/agents.ts @@ -0,0 +1,58 @@ +/** + * `lisa agents` — one-shot snapshot of every agent session the orchestrator hub + * sees right now (Dispatch's `lisa ` command, FOUNDATIONS §4). Spins the + * hub briefly, lists, exits — works without a running `serve`. Structural only. + */ +import os from "node:os"; +import path from "node:path"; +import { OrchestratorHub, loadOrchestratorConfig } from "../integrations/hub.js"; +import { registerBuiltinIntegrations } from "../integrations/registry.js"; +import type { AgentSession } from "../integrations/types.js"; + +const LISA_HOME = process.env.LISA_HOME ?? path.join(os.homedir(), ".lisa"); + +function rel(ms: number): string { + if (ms < 60_000) return `${Math.round(ms / 1000)}s`; + if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`; + return `${Math.round(ms / 3_600_000)}h`; +} + +export async function runAgentsCommand(_subargs: string[]): Promise { + await registerBuiltinIntegrations(); + const cfg = await loadOrchestratorConfig(path.join(LISA_HOME, "agents.json")); + const hub = new OrchestratorHub(cfg, { log: () => {} }); + await hub.start(); + await new Promise((r) => setTimeout(r, 600)); // let file-watch / poll settle + const sessions = hub.list(); + await hub.stop(); + + if (sessions.length === 0) { + console.log("No active agent sessions in the last window."); + console.log("(claude-code is observed by default; enable others in ~/.lisa/agents.json)"); + return 0; + } + + const byAgent = new Map(); + for (const s of sessions) { + const arr = byAgent.get(s.agent) ?? []; + arr.push(s); + byAgent.set(s.agent, arr); + } + const now = Date.now(); + for (const [agent, list] of byAgent) { + console.log(`\n${agent} (${list.length})`); + for (const s of list.slice(0, 10)) { + const a = s.activity; + const bits: string[] = []; + if (a?.gitBranch) bits.push(a.gitBranch); + if (a?.lastTools?.length) bits.push(a.lastTools[a.lastTools.length - 1]!); + if (a?.pendingPermission) bits.push(`⚠${a.pendingPermission}`); + const reason = s.stateReason ? `:${s.stateReason}` : ""; + console.log( + ` ${(s.state + reason).padEnd(16)} ${s.project.padEnd(20)} ${rel(now - s.lastMtime).padStart(4)} ago` + + (bits.length ? ` · ${bits.join(" ")}` : ""), + ); + } + } + return 0; +} diff --git a/src/tools/registry.ts b/src/tools/registry.ts index bddfecb..5f76d23 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -133,7 +133,7 @@ export function readOnlySubset(tools: ToolDefinition[]): ToolDefinition[] { /** * Tools that must NOT be available to self-driven autonomous runs (desire - * heartbeats, idle/dreams). These runs execute unattended on a timer with + * heartbeats, idle reflection / Reve). These runs execute unattended on a timer with * prompts that Lisa wrote herself — if an indirect prompt injection (e.g. via * web_fetch content) plants a malicious actionable desire, these are the tools * that would turn it into persistent code execution. Soul / memory / journal /