Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions completions/_lisa
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion completions/lisa.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion completions/lisa.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/autonomy/runs.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 10 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ INSPECTION
all off): list, grant <signal>, revoke <signal>,
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).
Expand Down Expand Up @@ -168,7 +169,8 @@ interface ParsedArgs {
| "autonomy"
| "model"
| "consent"
| "sense";
| "sense"
| "agents";
subargs: string[];
serveWeb: boolean;
serveImessage: boolean;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -480,6 +483,11 @@ async function main(): Promise<void> {
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) {
Expand Down
58 changes: 58 additions & 0 deletions src/cli/agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* `lisa agents` — one-shot snapshot of every agent session the orchestrator hub
* sees right now (Dispatch's `lisa <domain>` 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<number> {
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<string, AgentSession[]>();
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;
}
2 changes: 1 addition & 1 deletion src/tools/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 /
Expand Down
Loading