@getpaseo/cli 0.1.101 → 0.1.102-beta.2
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/dist/commands/agent/attach.js +3 -2
- package/dist/commands/agent/delete.js +1 -1
- package/dist/commands/agent/inspect.js +1 -1
- package/dist/commands/agent/logs.d.ts +3 -1
- package/dist/commands/agent/logs.js +13 -5
- package/dist/commands/agent/mode.js +1 -1
- package/dist/commands/agent/stop.js +1 -1
- package/dist/commands/agent/update.js +2 -2
- package/dist/commands/agent/wait.js +4 -1
- package/dist/commands/chat/shared.d.ts +4 -1
- package/dist/commands/chat/shared.js +14 -4
- package/dist/commands/chat/wait.js +17 -2
- package/dist/commands/daemon/index.js +2 -0
- package/dist/commands/daemon/local-daemon.d.ts +3 -0
- package/dist/commands/daemon/local-daemon.js +31 -4
- package/dist/commands/daemon/pair.js +4 -1
- package/dist/commands/daemon/restart.js +1 -0
- package/dist/commands/daemon/start.js +2 -0
- package/dist/commands/daemon/status.js +27 -82
- package/dist/commands/daemon/stop.d.ts +2 -0
- package/dist/commands/daemon/stop.js +2 -0
- package/dist/commands/onboard.js +25 -11
- package/dist/commands/permit/allow.js +1 -1
- package/dist/commands/permit/deny.js +1 -1
- package/dist/utils/timeline.d.ts +2 -0
- package/dist/utils/timeline.js +2 -0
- package/package.json +4 -4
|
@@ -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
|
|
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 =
|
|
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[]
|
|
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
|
-
|
|
31
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
264
|
-
: `Connected daemon websocket is reachable at ${host} but
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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,
|
package/dist/commands/onboard.js
CHANGED
|
@@ -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({
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
package/dist/utils/timeline.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/utils/timeline.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.1.102-beta.2",
|
|
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.
|
|
31
|
-
"@getpaseo/protocol": "0.1.
|
|
32
|
-
"@getpaseo/server": "0.1.
|
|
30
|
+
"@getpaseo/client": "0.1.102-beta.2",
|
|
31
|
+
"@getpaseo/protocol": "0.1.102-beta.2",
|
|
32
|
+
"@getpaseo/server": "0.1.102-beta.2",
|
|
33
33
|
"chalk": "^5.3.0",
|
|
34
34
|
"commander": "^12.0.0",
|
|
35
35
|
"mime-types": "^2.1.35",
|