@getpaseo/cli 0.1.101 → 0.1.102

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.
@@ -4,7 +4,7 @@ export function addAttachOptions(cmd) {
4
4
  .argument("<id>", "Agent ID (or prefix)");
5
5
  }
6
6
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
7
- import { fetchProjectedTimelineItems } from "../../utils/timeline.js";
7
+ import { fetchProjectedTimelineItems, LIVE_HISTORY_FETCH_TIMEOUT_MS, } from "../../utils/timeline.js";
8
8
  /**
9
9
  * Format and print a timeline item to the terminal
10
10
  */
@@ -91,7 +91,7 @@ export async function runAttachCommand(id, options, _command) {
91
91
  process.exit(1);
92
92
  }
93
93
  try {
94
- const fetchResult = await client.fetchAgent(id);
94
+ const fetchResult = await client.fetchAgent({ agentId: id });
95
95
  if (!fetchResult) {
96
96
  console.error(`Error: No agent found matching: ${id}`);
97
97
  console.error("Use `paseo ls` to list available agents");
@@ -107,6 +107,7 @@ export async function runAttachCommand(id, options, _command) {
107
107
  const timelineItems = await fetchProjectedTimelineItems({
108
108
  client,
109
109
  agentId: resolvedId,
110
+ timeoutMs: LIVE_HISTORY_FETCH_TIMEOUT_MS,
110
111
  });
111
112
  for (const item of timelineItems) {
112
113
  printTimelineItem(item);
@@ -49,7 +49,7 @@ export async function runDeleteCommand(id, options, _command) {
49
49
  });
50
50
  }
51
51
  else if (id) {
52
- const fetchResult = await client.fetchAgent(id);
52
+ const fetchResult = await client.fetchAgent({ agentId: id });
53
53
  if (!fetchResult) {
54
54
  const error = {
55
55
  code: "AGENT_NOT_FOUND",
@@ -175,7 +175,7 @@ export async function runInspectCommand(agentIdArg, options, _command) {
175
175
  throw error;
176
176
  }
177
177
  try {
178
- const fetchResult = await client.fetchAgent(agentIdArg);
178
+ const fetchResult = await client.fetchAgent({ agentId: agentIdArg });
179
179
  if (!fetchResult) {
180
180
  const error = {
181
181
  code: "AGENT_NOT_FOUND",
@@ -11,7 +11,9 @@ export interface AgentLogsOptions extends CommandOptions {
11
11
  }
12
12
  export type AgentLogsResult = void;
13
13
  export declare const NO_ACTIVITY_MESSAGE = "No activity to display.";
14
- export declare function fetchAgentTimelineItems(client: DaemonClient, agentId: string): Promise<AgentTimelineItem[]>;
14
+ export declare function fetchAgentTimelineItems(client: DaemonClient, agentId: string, options?: {
15
+ timeoutMs?: number;
16
+ }): Promise<AgentTimelineItem[]>;
15
17
  export declare function formatAgentActivityTranscript(timelineItems: AgentTimelineItem[], tailCount?: number): string;
16
18
  export declare function runLogsCommand(id: string, options: AgentLogsOptions, _command: Command): Promise<AgentLogsResult>;
17
19
  //# sourceMappingURL=logs.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
- import { fetchProjectedTimelineItems } from "../../utils/timeline.js";
2
+ import { fetchProjectedTimelineItems, LIVE_HISTORY_FETCH_TIMEOUT_MS, } from "../../utils/timeline.js";
3
3
  import { curateAgentActivity } from "@getpaseo/server";
4
4
  export function addLogsOptions(cmd) {
5
5
  return cmd
@@ -11,8 +11,8 @@ export function addLogsOptions(cmd) {
11
11
  .option("--since <time>", "Show logs since timestamp");
12
12
  }
13
13
  export const NO_ACTIVITY_MESSAGE = "No activity to display.";
14
- export async function fetchAgentTimelineItems(client, agentId) {
15
- return fetchProjectedTimelineItems({ client, agentId });
14
+ export async function fetchAgentTimelineItems(client, agentId, options) {
15
+ return fetchProjectedTimelineItems({ client, agentId, timeoutMs: options?.timeoutMs });
16
16
  }
17
17
  export function formatAgentActivityTranscript(timelineItems, tailCount) {
18
18
  if (tailCount === 0) {
@@ -70,7 +70,7 @@ export async function runLogsCommand(id, options, _command) {
70
70
  process.exit(1);
71
71
  }
72
72
  try {
73
- const fetchResult = await client.fetchAgent(id);
73
+ const fetchResult = await client.fetchAgent({ agentId: id });
74
74
  if (!fetchResult) {
75
75
  console.error(`Error: No agent found matching: ${id}`);
76
76
  console.error("Use `paseo ls` to list available agents");
@@ -124,7 +124,15 @@ async function runFollowMode(client, agentId, options) {
124
124
  const DEFAULT_FOLLOW_TAIL = 10;
125
125
  const tailCount = parseTailCount(options.tail) ?? DEFAULT_FOLLOW_TAIL;
126
126
  // First, get existing timeline.
127
- let existingItems = await fetchAgentTimelineItems(client, agentId);
127
+ let existingItems = [];
128
+ try {
129
+ existingItems = await fetchAgentTimelineItems(client, agentId, {
130
+ timeoutMs: LIVE_HISTORY_FETCH_TIMEOUT_MS,
131
+ });
132
+ }
133
+ catch (error) {
134
+ console.warn("Warning: failed to fetch existing timeline", error);
135
+ }
128
136
  // Apply filter to existing items
129
137
  if (options.filter) {
130
138
  existingItems = existingItems.filter((item) => matchesFilter(item, options.filter));
@@ -31,7 +31,7 @@ export async function runModeCommand(id, mode, options, _command) {
31
31
  let client;
32
32
  try {
33
33
  client = await connectToDaemon({ host: options.host });
34
- const fetchResult = await client.fetchAgent(id);
34
+ const fetchResult = await client.fetchAgent({ agentId: id });
35
35
  if (!fetchResult) {
36
36
  const error = {
37
37
  code: "AGENT_NOT_FOUND",
@@ -55,7 +55,7 @@ export async function runStopCommand(id, options, _command) {
55
55
  }
56
56
  else if (id) {
57
57
  // Stop specific agent
58
- const fetchResult = await client.fetchAgent(id);
58
+ const fetchResult = await client.fetchAgent({ agentId: id });
59
59
  if (!fetchResult) {
60
60
  const error = {
61
61
  code: "AGENT_NOT_FOUND",
@@ -93,7 +93,7 @@ export async function runUpdateCommand(agentIdArg, options, _command) {
93
93
  throw error;
94
94
  }
95
95
  try {
96
- const fetchResult = await client.fetchAgent(agentIdArg);
96
+ const fetchResult = await client.fetchAgent({ agentId: agentIdArg });
97
97
  if (!fetchResult) {
98
98
  const error = {
99
99
  code: "AGENT_NOT_FOUND",
@@ -107,7 +107,7 @@ export async function runUpdateCommand(agentIdArg, options, _command) {
107
107
  ...(name ? { name } : {}),
108
108
  ...(Object.keys(labels).length > 0 ? { labels } : {}),
109
109
  });
110
- const updatedResult = await client.fetchAgent(agentId);
110
+ const updatedResult = await client.fetchAgent({ agentId });
111
111
  if (!updatedResult) {
112
112
  throw new Error(`Agent not found after update: ${agentId}`);
113
113
  }
@@ -11,6 +11,7 @@ export const agentWaitSchema = {
11
11
  ],
12
12
  };
13
13
  const WAIT_ACTIVITY_PREVIEW_COUNT = 5;
14
+ const WAIT_ACTIVITY_PREVIEW_TIMEOUT_MS = 2000;
14
15
  function appendRecentActivity(message, transcript) {
15
16
  if (!transcript || transcript.trim().length === 0) {
16
17
  return message;
@@ -19,7 +20,9 @@ function appendRecentActivity(message, transcript) {
19
20
  }
20
21
  async function getRecentActivityTranscript(client, agentId) {
21
22
  try {
22
- const timelineItems = await fetchAgentTimelineItems(client, agentId);
23
+ const timelineItems = await fetchAgentTimelineItems(client, agentId, {
24
+ timeoutMs: WAIT_ACTIVITY_PREVIEW_TIMEOUT_MS,
25
+ });
23
26
  return formatAgentActivityTranscript(timelineItems, WAIT_ACTIVITY_PREVIEW_COUNT);
24
27
  }
25
28
  catch {
@@ -8,7 +8,10 @@ export declare function connectChatClient(host?: string): Promise<{
8
8
  client: import("@getpaseo/client/internal/daemon-client").DaemonClient;
9
9
  daemonHost: string;
10
10
  }>;
11
- export declare function attachAgentNamesToMessages(client: Awaited<ReturnType<typeof connectToDaemon>>, messages: ChatMessageRow[]): Promise<ChatMessageRow[]>;
11
+ export declare function attachAgentNamesToMessages(client: Awaited<ReturnType<typeof connectToDaemon>>, messages: ChatMessageRow[], options?: {
12
+ timeout?: number;
13
+ bestEffort?: boolean;
14
+ }): Promise<ChatMessageRow[]>;
12
15
  export declare function toChatCommandError(code: string, action: string, err: unknown): CommandError;
13
16
  export declare function parseSinceValue(input?: string): string | undefined;
14
17
  export declare function parseTimeoutMs(input?: string): number | undefined;
@@ -16,7 +16,7 @@ export async function connectChatClient(host) {
16
16
  throw error;
17
17
  }
18
18
  }
19
- export async function attachAgentNamesToMessages(client, messages) {
19
+ export async function attachAgentNamesToMessages(client, messages, options = {}) {
20
20
  const agentIds = new Set();
21
21
  for (const message of messages) {
22
22
  agentIds.add(message.author);
@@ -27,9 +27,19 @@ export async function attachAgentNamesToMessages(client, messages) {
27
27
  if (agentIds.size === 0) {
28
28
  return messages;
29
29
  }
30
- const payload = await client.fetchAgents({
31
- filter: { includeArchived: true },
32
- });
30
+ let payload;
31
+ try {
32
+ payload = await client.fetchAgents({
33
+ filter: { includeArchived: true },
34
+ ...(typeof options.timeout === "number" ? { timeout: options.timeout } : {}),
35
+ });
36
+ }
37
+ catch (error) {
38
+ if (options.bestEffort) {
39
+ return messages;
40
+ }
41
+ throw error;
42
+ }
33
43
  const agentNames = new Map();
34
44
  for (const entry of payload.entries) {
35
45
  const title = entry.agent.title?.trim();
@@ -1,19 +1,34 @@
1
1
  import { attachAgentNamesToMessages, connectChatClient, parseTimeoutMs, toChatCommandError, } from "./shared.js";
2
2
  import { chatMessageSchema, toChatMessageRow } from "./schema.js";
3
+ const CHAT_WAIT_PREFLIGHT_TIMEOUT_MS = 2000;
3
4
  export async function runWaitCommand(room, options, _command) {
5
+ const timeoutMs = parseTimeoutMs(options.timeout);
4
6
  const { client } = await connectChatClient(options.host);
7
+ const deadline = typeof timeoutMs === "number" ? Date.now() + timeoutMs : null;
8
+ const hasExplicitTimeout = deadline !== null;
9
+ const remainingTimeoutMs = () => deadline === null ? undefined : Math.max(1, deadline - Date.now());
5
10
  try {
6
11
  const latest = await client.readChatMessages({
7
12
  room,
8
13
  limit: 1,
14
+ ...(hasExplicitTimeout
15
+ ? {
16
+ timeout: Math.min(remainingTimeoutMs() ?? 1, CHAT_WAIT_PREFLIGHT_TIMEOUT_MS),
17
+ }
18
+ : {}),
9
19
  });
10
20
  const afterMessageId = latest.messages[0]?.id;
11
21
  const payload = await client.waitForChatMessages({
12
22
  room,
13
23
  afterMessageId,
14
- timeoutMs: parseTimeoutMs(options.timeout),
24
+ timeoutMs: remainingTimeoutMs() ?? timeoutMs,
15
25
  });
16
- const messages = await attachAgentNamesToMessages(client, payload.messages.map(toChatMessageRow));
26
+ const messages = await attachAgentNamesToMessages(client, payload.messages.map(toChatMessageRow), hasExplicitTimeout
27
+ ? {
28
+ timeout: remainingTimeoutMs(),
29
+ bestEffort: true,
30
+ }
31
+ : {});
17
32
  return {
18
33
  type: "list",
19
34
  data: messages,
@@ -36,6 +36,8 @@ export function createDaemonCommand() {
36
36
  .option("--no-relay", "Disable relay on restarted daemon")
37
37
  .option("--no-mcp", "Disable Agent MCP on restarted daemon")
38
38
  .option("--no-inject-mcp", "Disable auto-injecting the Paseo MCP into created agents")
39
+ .option("--web-ui", "Enable the bundled daemon web UI on restarted daemon")
40
+ .option("--no-web-ui", "Disable the bundled daemon web UI on restarted daemon")
39
41
  .option("--hostnames <hosts>", 'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)')
40
42
  .addOption(new Option("--allowed-hosts <hosts>").hideHelp())
41
43
  .action(withOutput((...args) => {
@@ -9,6 +9,7 @@ export interface DaemonStartOptions {
9
9
  relayUseTls?: boolean;
10
10
  mcp?: boolean;
11
11
  injectMcp?: boolean;
12
+ webUi?: boolean;
12
13
  hostnames?: string;
13
14
  }
14
15
  export interface LocalDaemonPidInfo {
@@ -47,6 +48,8 @@ export interface StopLocalDaemonResult {
47
48
  home: string;
48
49
  pid: number | null;
49
50
  forced: boolean;
51
+ usedLifecycleRpc: boolean;
52
+ reason: "not_running" | "lifecycle_shutdown_rpc" | "owner_pid_signal" | "owner_pid_sigkill";
50
53
  message: string;
51
54
  }
52
55
  export interface DetachedDaemonProcess extends Pick<ChildProcess, "once" | "pid" | "unref"> {
@@ -43,6 +43,12 @@ function buildRunnerArgs(options) {
43
43
  if (options.injectMcp === false) {
44
44
  args.push("--no-inject-mcp");
45
45
  }
46
+ if (options.webUi === true) {
47
+ args.push("--web-ui");
48
+ }
49
+ if (options.webUi === false) {
50
+ args.push("--no-web-ui");
51
+ }
46
52
  return args;
47
53
  }
48
54
  function buildChildEnv(options) {
@@ -62,6 +68,12 @@ function buildChildEnv(options) {
62
68
  if (options.relayUseTls === true) {
63
69
  childEnv.PASEO_RELAY_USE_TLS = "true";
64
70
  }
71
+ if (options.webUi === true) {
72
+ childEnv.PASEO_WEB_UI_ENABLED = "true";
73
+ }
74
+ if (options.webUi === false) {
75
+ childEnv.PASEO_WEB_UI_ENABLED = "false";
76
+ }
65
77
  return childEnv;
66
78
  }
67
79
  function resolveServerRunnerFromDir(currentDir) {
@@ -115,6 +127,13 @@ function resolveStopMessage(forced, lifecycleRequested, fallbackMessage) {
115
127
  return "Daemon stopped gracefully";
116
128
  return fallbackMessage ?? "Daemon stopped via owner PID signal";
117
129
  }
130
+ function resolveStopReason(forced, lifecycleRequested) {
131
+ if (forced)
132
+ return "owner_pid_sigkill";
133
+ if (lifecycleRequested)
134
+ return "lifecycle_shutdown_rpc";
135
+ return "owner_pid_signal";
136
+ }
118
137
  function readPidFile(pidPath) {
119
138
  try {
120
139
  const parsed = JSON.parse(readFileSync(pidPath, "utf-8"));
@@ -285,6 +304,8 @@ function createNotRunningStopResult(state, pid, message) {
285
304
  home: state.home,
286
305
  pid,
287
306
  forced: false,
307
+ usedLifecycleRpc: false,
308
+ reason: "not_running",
288
309
  message,
289
310
  };
290
311
  }
@@ -455,7 +476,9 @@ async function requestLifecycleShutdown(state, timeoutMs) {
455
476
  reason: "daemon listen target is not TCP, falling back to owner PID signal",
456
477
  };
457
478
  }
458
- const client = await tryConnectToDaemon({ host, timeout: Math.min(timeoutMs, 5000) });
479
+ const deadline = Date.now() + timeoutMs;
480
+ const remainingTimeoutMs = () => Math.max(1, deadline - Date.now());
481
+ const client = await tryConnectToDaemon({ host, timeout: Math.min(remainingTimeoutMs(), 5000) });
459
482
  if (!client) {
460
483
  return {
461
484
  requested: false,
@@ -463,7 +486,7 @@ async function requestLifecycleShutdown(state, timeoutMs) {
463
486
  };
464
487
  }
465
488
  try {
466
- await client.shutdownServer();
489
+ await client.shutdownServer({ timeout: Math.min(remainingTimeoutMs(), 5000) });
467
490
  return { requested: true };
468
491
  }
469
492
  catch (error) {
@@ -480,7 +503,9 @@ export async function stopLocalDaemon(options = {}) {
480
503
  const timeoutMs = options.timeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
481
504
  const killTimeoutMs = options.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
482
505
  const state = resolveLocalDaemonState({ home: options.home });
483
- const shutdownAttempt = await requestLifecycleShutdown(state, timeoutMs);
506
+ const deadline = Date.now() + timeoutMs;
507
+ const remainingTimeoutMs = () => Math.max(1, deadline - Date.now());
508
+ const shutdownAttempt = await requestLifecycleShutdown(state, remainingTimeoutMs());
484
509
  const lifecycleRequested = shutdownAttempt.requested;
485
510
  if (!state.pidInfo || (!state.running && !lifecycleRequested)) {
486
511
  const staleSuffix = state.stalePidFile && state.pidInfo ? ` (stale PID file for ${state.pidInfo.pid})` : "";
@@ -496,7 +521,7 @@ export async function stopLocalDaemon(options = {}) {
496
521
  const { stopped, forced } = await waitForStopAfterRequest({
497
522
  state,
498
523
  pid,
499
- timeoutMs,
524
+ timeoutMs: remainingTimeoutMs(),
500
525
  killTimeoutMs,
501
526
  force: options.force,
502
527
  });
@@ -511,6 +536,8 @@ export async function stopLocalDaemon(options = {}) {
511
536
  home: state.home,
512
537
  pid,
513
538
  forced,
539
+ usedLifecycleRpc: lifecycleRequested,
540
+ reason: resolveStopReason(forced, lifecycleRequested),
514
541
  message: resolveStopMessage(forced, lifecycleRequested, fallbackMessage),
515
542
  };
516
543
  }
@@ -4,6 +4,7 @@ import { generateLocalPairingOffer, loadConfig, resolvePaseoHome } from "@getpas
4
4
  import { tryConnectToDaemon } from "../../utils/client.js";
5
5
  import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
6
6
  import { addJsonOption } from "../../utils/command-options.js";
7
+ const PAIRING_DAEMON_RPC_TIMEOUT_MS = 1500;
7
8
  export function pairCommand() {
8
9
  return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
9
10
  .option("--home <path>", "Paseo home directory (default: ~/.paseo)")
@@ -25,7 +26,9 @@ export async function runPairCommand(options) {
25
26
  const supportsDaemonStatusRpc = client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
26
27
  if (supportsDaemonStatusRpc) {
27
28
  try {
28
- const offer = await client.getDaemonPairingOffer();
29
+ const offer = await client.getDaemonPairingOffer({
30
+ timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
31
+ });
29
32
  await client.close().catch(() => { });
30
33
  outputPairingResult({ relayEnabled: offer.relayEnabled, url: offer.url, qr: offer.qr ?? null }, options);
31
34
  return;
@@ -35,6 +35,7 @@ function toStartOptions(options) {
35
35
  relay: typeof options.relay === "boolean" ? options.relay : undefined,
36
36
  mcp: typeof options.mcp === "boolean" ? options.mcp : undefined,
37
37
  injectMcp: typeof options.injectMcp === "boolean" ? options.injectMcp : undefined,
38
+ webUi: typeof options.webUi === "boolean" ? options.webUi : undefined,
38
39
  hostnames: typeof options.hostnames === "string" ? options.hostnames : undefined,
39
40
  };
40
41
  if (startOptions.listen && startOptions.port) {
@@ -13,6 +13,8 @@ export function startCommand() {
13
13
  .option("--relay-use-tls", "Use wss:// for the relay connection and pairing offers")
14
14
  .option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
15
15
  .option("--no-inject-mcp", "Disable auto-injecting the Paseo MCP into created agents")
16
+ .option("--web-ui", "Enable the bundled daemon web UI")
17
+ .option("--no-web-ui", "Disable the bundled daemon web UI")
16
18
  .option("--hostnames <hosts>", 'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)')
17
19
  .addOption(new Option("--allowed-hosts <hosts>").hideHelp())
18
20
  .action(async (options) => {
@@ -3,6 +3,7 @@ import { getOrCreateServerId, findExecutable, execCommand } from "@getpaseo/serv
3
3
  import { connectToDaemon } from "../../utils/client.js";
4
4
  import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
5
5
  import { resolveNodePathFromPid } from "./runtime-toolchain.js";
6
+ const DAEMON_STATUS_PROBE_TIMEOUT_MS = 1500;
6
7
  const require = createRequire(import.meta.url);
7
8
  function normalizeError(error) {
8
9
  if (error instanceof Error) {
@@ -91,18 +92,6 @@ function toStatusRows(status) {
91
92
  { key: "CLI", value: status.cliVersion },
92
93
  { key: "Daemon Version", value: status.daemonVersion ?? "-" },
93
94
  ];
94
- if (status.runningAgents !== null && status.idleAgents !== null) {
95
- rows.push({
96
- key: "Agents",
97
- value: `${status.runningAgents} running, ${status.idleAgents} idle`,
98
- });
99
- }
100
- else {
101
- rows.push({
102
- key: "Agents",
103
- value: `Unavailable (${status.agentsUnavailableReason ?? "daemon API not reachable"})`,
104
- });
105
- }
106
95
  if (status.note) {
107
96
  rows.push({ key: "Note", value: status.note });
108
97
  }
@@ -179,11 +168,6 @@ function describeDaemonAuthProbeFailure(host, failure) {
179
168
  }
180
169
  return `Daemon is reachable at ${host} but the supplied password was rejected. Check PASEO_PASSWORD and retry.`;
181
170
  }
182
- function describeAgentsUnavailableReason(failure) {
183
- if (failure === "auth_required")
184
- return "password required";
185
- return "incorrect password";
186
- }
187
171
  async function probeDaemonOverWebsocket(args) {
188
172
  const { host, state } = args;
189
173
  let client;
@@ -195,7 +179,6 @@ async function probeDaemonOverWebsocket(args) {
195
179
  if (authFailure) {
196
180
  return {
197
181
  connectedDaemon: authFailure,
198
- agentsUnavailableReason: describeAgentsUnavailableReason(authFailure),
199
182
  note: describeDaemonAuthProbeFailure(host, authFailure),
200
183
  };
201
184
  }
@@ -209,37 +192,22 @@ async function probeDaemonOverWebsocket(args) {
209
192
  return { connectedDaemon: "unreachable" };
210
193
  }
211
194
  const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
212
- const supportsDaemonStatusRpc = client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
213
195
  try {
214
- const agentsPayload = await client.fetchAgents({ filter: { includeArchived: true } });
215
- const agents = agentsPayload.entries.map((entry) => entry.agent);
216
- const runningAgents = agents.filter((a) => a.status === "running").length;
217
- const idleAgents = agents.filter((a) => a.status === "idle").length;
218
- let daemonProviders;
219
- if (supportsDaemonStatusRpc) {
220
- try {
221
- const statusPayload = await client.getDaemonStatus();
222
- const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
223
- daemonProviders = statusPayload.providers.map((p) => ({
224
- label: labelMap.get(p.provider) ?? p.provider,
225
- path: p.available ? "available" : null,
226
- version: p.available ? null : (p.error ?? null),
227
- source: "daemon",
228
- }));
229
- }
230
- catch {
231
- // COMPAT(daemon-rpc-rollout): fall back to CLI-side provider resolution while
232
- // old daemons lack daemonStatusRpc. Remove once the daemon floor is past
233
- // v0.1.76; status should come from daemon.get_status.
234
- }
235
- }
196
+ const statusPayload = await client.getDaemonStatus({
197
+ timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
198
+ });
199
+ const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
200
+ const daemonProviders = statusPayload.providers.map((p) => ({
201
+ label: labelMap.get(p.provider) ?? p.provider,
202
+ path: p.available ? "available" : null,
203
+ version: p.available ? null : (p.error ?? null),
204
+ source: "daemon",
205
+ }));
236
206
  if (!state.running) {
237
207
  return {
238
208
  connectedDaemon: "reachable",
239
- daemonVersion,
240
- runningAgents,
241
- idleAgents,
242
- daemonNodeOverride: "unknown (API reachable, PID unresolved)",
209
+ daemonVersion: statusPayload.version ?? daemonVersion,
210
+ daemonNodeOverride: statusPayload.nodePath,
243
211
  daemonProviders,
244
212
  note: state.pidInfo
245
213
  ? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
@@ -248,9 +216,8 @@ async function probeDaemonOverWebsocket(args) {
248
216
  }
249
217
  return {
250
218
  connectedDaemon: "reachable",
251
- daemonVersion,
252
- runningAgents,
253
- idleAgents,
219
+ daemonVersion: statusPayload.version ?? daemonVersion,
220
+ daemonNodeOverride: statusPayload.nodePath,
254
221
  daemonProviders,
255
222
  };
256
223
  }
@@ -258,10 +225,9 @@ async function probeDaemonOverWebsocket(args) {
258
225
  return {
259
226
  connectedDaemon: "reachable",
260
227
  daemonVersion,
261
- localDaemonOverride: state.running ? "unresponsive" : undefined,
262
228
  note: state.running
263
- ? `Local daemon PID is running but API requests to ${host} failed`
264
- : `Connected daemon websocket is reachable at ${host} but fetch_agents failed`,
229
+ ? `Local daemon PID is running but daemon detail request to ${host} failed`
230
+ : `Connected daemon websocket is reachable at ${host} but daemon status request failed`,
265
231
  };
266
232
  }
267
233
  finally {
@@ -275,10 +241,7 @@ function applyProbeToStatus(input) {
275
241
  localDaemon: probe.localDaemonOverride ?? input.localDaemon,
276
242
  daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
277
243
  daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
278
- runningAgents: probe.runningAgents !== undefined ? probe.runningAgents : input.runningAgents,
279
- idleAgents: probe.idleAgents !== undefined ? probe.idleAgents : input.idleAgents,
280
244
  daemonProviders: probe.daemonProviders ?? input.daemonProviders,
281
- agentsUnavailableReason: probe.agentsUnavailableReason ?? input.agentsUnavailableReason,
282
245
  note: probe.note ? appendNote(input.note, probe.note) : input.note,
283
246
  };
284
247
  }
@@ -316,11 +279,8 @@ export async function runStatusCommand(options, _command) {
316
279
  const cliNode = process.execPath;
317
280
  let localDaemon = state.running ? "running" : "stopped";
318
281
  let connectedDaemon = "not_probed";
319
- let runningAgents = null;
320
- let idleAgents = null;
321
282
  let daemonVersion = null;
322
283
  let daemonProviders;
323
- let agentsUnavailableReason;
324
284
  let note;
325
285
  if (!state.running && state.stalePidFile && state.pidInfo) {
326
286
  localDaemon = "stale_pid";
@@ -328,28 +288,16 @@ export async function runStatusCommand(options, _command) {
328
288
  }
329
289
  if (host) {
330
290
  const probe = await probeDaemonOverWebsocket({ host, state });
331
- ({
332
- connectedDaemon,
333
- localDaemon,
334
- daemonNode,
335
- daemonVersion,
336
- runningAgents,
337
- idleAgents,
338
- daemonProviders,
339
- agentsUnavailableReason,
340
- note,
341
- } = applyProbeToStatus({
342
- probe,
343
- connectedDaemon,
344
- localDaemon,
345
- daemonNode,
346
- daemonVersion,
347
- runningAgents,
348
- idleAgents,
349
- daemonProviders,
350
- agentsUnavailableReason,
351
- note,
352
- }));
291
+ ({ connectedDaemon, localDaemon, daemonNode, daemonVersion, daemonProviders, note } =
292
+ applyProbeToStatus({
293
+ probe,
294
+ connectedDaemon,
295
+ localDaemon,
296
+ daemonNode,
297
+ daemonVersion,
298
+ daemonProviders,
299
+ note,
300
+ }));
353
301
  }
354
302
  else {
355
303
  note = appendNote(note, "Daemon is configured for unix socket listen; API probe skipped");
@@ -373,15 +321,12 @@ export async function runStatusCommand(options, _command) {
373
321
  startedAt: state.pidInfo?.startedAt ?? null,
374
322
  owner,
375
323
  logPath: state.logPath,
376
- runningAgents,
377
- idleAgents,
378
324
  daemonNode,
379
325
  cliNode,
380
326
  cliVersion,
381
327
  daemonVersion,
382
328
  desktopManaged: state.pidInfo?.desktopManaged === true,
383
329
  providers,
384
- agentsUnavailableReason,
385
330
  note,
386
331
  };
387
332
  return {
@@ -5,6 +5,8 @@ interface StopResult {
5
5
  home: string;
6
6
  pid: string;
7
7
  forced: boolean;
8
+ usedLifecycleRpc: boolean;
9
+ reason: "not_running" | "lifecycle_shutdown_rpc" | "owner_pid_signal" | "owner_pid_sigkill";
8
10
  message: string;
9
11
  }
10
12
  export type StopCommandResult = SingleResult<StopResult>;
@@ -41,6 +41,8 @@ export async function runStopCommand(options, _command) {
41
41
  home: result.home,
42
42
  pid: result.pid === null ? "-" : String(result.pid),
43
43
  forced: result.forced,
44
+ usedLifecycleRpc: result.usedLifecycleRpc,
45
+ reason: result.reason,
44
46
  message: result.message,
45
47
  },
46
48
  schema: stopResultSchema,
@@ -6,6 +6,7 @@ import { generateLocalPairingOffer, loadConfig, loadPersistedConfig, } from "@ge
6
6
  import { resolveLocalPaseoHome, resolveLocalDaemonState, resolveTcpHostFromListen, startLocalDaemonDetached, tailDaemonLog, } from "./daemon/local-daemon.js";
7
7
  import { tryConnectToDaemon } from "../utils/client.js";
8
8
  const DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1000;
9
+ const READY_PROBE_TIMEOUT_MS = 1200;
9
10
  class OnboardCancelledError extends Error {
10
11
  }
11
12
  const plainNoteFormat = (line) => line;
@@ -129,14 +130,21 @@ function renderProgressLine(progress) {
129
130
  }
130
131
  return `Downloading speech model${modelSuffix}: ${progress.pct}%`;
131
132
  }
132
- async function probeDaemonReady(home) {
133
+ async function probeDaemonReady(home, timeoutMs) {
133
134
  const state = resolveLocalDaemonState({ home });
134
135
  const host = resolveTcpHostFromListen(state.listen);
136
+ const deadline = Date.now() + timeoutMs;
137
+ const remainingTimeoutMs = () => Math.max(1, deadline - Date.now());
135
138
  if (state.running && host) {
136
- const client = await tryConnectToDaemon({ host, timeout: 1200 });
139
+ const client = await tryConnectToDaemon({
140
+ host,
141
+ timeout: Math.min(remainingTimeoutMs(), READY_PROBE_TIMEOUT_MS),
142
+ });
137
143
  if (client) {
138
144
  try {
139
- await client.fetchAgents();
145
+ await client.fetchAgents({
146
+ timeout: Math.min(remainingTimeoutMs(), READY_PROBE_TIMEOUT_MS),
147
+ });
140
148
  return { kind: "ready", listen: state.listen, host };
141
149
  }
142
150
  catch {
@@ -168,20 +176,26 @@ function announceProgress(home, state, onStatus) {
168
176
  }
169
177
  async function waitForDaemonReady(args) {
170
178
  const deadline = Date.now() + args.timeoutMs;
179
+ const createTimeoutError = () => {
180
+ const recentLogs = tailDaemonLog(args.home, 60);
181
+ return new Error([
182
+ `Timed out after ${Math.ceil(args.timeoutMs / 1000)}s waiting for daemon readiness.`,
183
+ recentLogs ? `Recent daemon logs:\n${recentLogs}` : null,
184
+ ]
185
+ .filter(Boolean)
186
+ .join("\n\n"));
187
+ };
171
188
  async function poll(state) {
172
- const probe = await probeDaemonReady(args.home);
189
+ if (Date.now() >= deadline) {
190
+ throw createTimeoutError();
191
+ }
192
+ const probe = await probeDaemonReady(args.home, Math.max(1, deadline - Date.now()));
173
193
  if (probe.kind === "ready") {
174
194
  return { listen: probe.listen, host: probe.host };
175
195
  }
176
196
  const nextState = announceProgress(args.home, state, args.onStatus);
177
197
  if (Date.now() >= deadline) {
178
- const recentLogs = tailDaemonLog(args.home, 60);
179
- throw new Error([
180
- `Timed out after ${Math.ceil(args.timeoutMs / 1000)}s waiting for daemon readiness.`,
181
- recentLogs ? `Recent daemon logs:\n${recentLogs}` : null,
182
- ]
183
- .filter(Boolean)
184
- .join("\n\n"));
198
+ throw createTimeoutError();
185
199
  }
186
200
  await sleep(200);
187
201
  return poll(nextState);
@@ -52,7 +52,7 @@ export async function runAllowCommand(agentIdOrPrefix, reqId, options, _command)
52
52
  throw error;
53
53
  }
54
54
  try {
55
- const fetchResult = await client.fetchAgent(agentIdOrPrefix);
55
+ const fetchResult = await client.fetchAgent({ agentId: agentIdOrPrefix });
56
56
  if (!fetchResult) {
57
57
  await client.close();
58
58
  const error = {
@@ -25,7 +25,7 @@ export async function runDenyCommand(agentIdOrPrefix, reqId, options, _command)
25
25
  throw error;
26
26
  }
27
27
  try {
28
- const fetchResult = await client.fetchAgent(agentIdOrPrefix);
28
+ const fetchResult = await client.fetchAgent({ agentId: agentIdOrPrefix });
29
29
  if (!fetchResult) {
30
30
  await client.close();
31
31
  const error = {
@@ -1,8 +1,10 @@
1
1
  import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
2
2
  import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
3
+ export declare const LIVE_HISTORY_FETCH_TIMEOUT_MS = 2000;
3
4
  interface FetchProjectedTimelineItemsInput {
4
5
  client: DaemonClient;
5
6
  agentId: string;
7
+ timeoutMs?: number;
6
8
  }
7
9
  export declare function fetchProjectedTimelineItems(input: FetchProjectedTimelineItemsInput): Promise<AgentTimelineItem[]>;
8
10
  export {};
@@ -1,8 +1,10 @@
1
+ export const LIVE_HISTORY_FETCH_TIMEOUT_MS = 2000;
1
2
  export async function fetchProjectedTimelineItems(input) {
2
3
  const timeline = await input.client.fetchAgentTimeline(input.agentId, {
3
4
  direction: "tail",
4
5
  limit: 0,
5
6
  projection: "projected",
7
+ timeout: input.timeoutMs,
6
8
  });
7
9
  return timeline.entries.map((entry) => entry.item);
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/cli",
3
- "version": "0.1.101",
3
+ "version": "0.1.102",
4
4
  "description": "Paseo CLI - control your AI coding agents from the command line",
5
5
  "bin": {
6
6
  "paseo": "bin/paseo"
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@getpaseo/client": "0.1.101",
31
- "@getpaseo/protocol": "0.1.101",
32
- "@getpaseo/server": "0.1.101",
30
+ "@getpaseo/client": "0.1.102",
31
+ "@getpaseo/protocol": "0.1.102",
32
+ "@getpaseo/server": "0.1.102",
33
33
  "chalk": "^5.3.0",
34
34
  "commander": "^12.0.0",
35
35
  "mime-types": "^2.1.35",