@getpaseo/server 0.1.92 → 0.1.94
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/server/server/agent/agent-manager.d.ts +14 -0
- package/dist/server/server/agent/agent-manager.js +36 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.js +58 -3
- package/dist/server/server/agent/providers/claude/models.js +15 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/mock-load-test-agent.js +55 -28
- package/dist/server/server/agent/providers/pi/agent.js +3 -1
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +5 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +74 -12
- package/dist/server/server/agent/runtime-mcp-config.d.ts +6 -0
- package/dist/server/server/agent/runtime-mcp-config.js +3 -0
- package/dist/server/server/auth.d.ts +13 -0
- package/dist/server/server/auth.js +35 -0
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +28 -1
- package/dist/server/server/config.js +3 -1
- package/dist/server/server/daemon-config-store.js +3 -0
- package/dist/server/server/loop-service.d.ts +6 -6
- package/dist/server/server/persisted-config.d.ts +61 -0
- package/dist/server/server/persisted-config.js +2 -0
- package/dist/server/server/session.d.ts +1 -0
- package/dist/server/server/session.js +50 -3
- package/dist/server/server/websocket-server.js +2 -0
- package/dist/server/server/workspace-git-service.d.ts +4 -1
- package/dist/server/server/workspace-git-service.js +40 -22
- package/dist/server/services/github-service.d.ts +57 -0
- package/dist/server/services/github-service.js +327 -3
- package/dist/server/terminal/terminal-session-controller.d.ts +6 -0
- package/dist/server/terminal/terminal-session-controller.js +36 -2
- package/dist/src/server/persisted-config.js +2 -0
- package/package.json +5 -5
|
@@ -65,9 +65,13 @@ export interface AgentManagerOptions {
|
|
|
65
65
|
idFactory?: () => string;
|
|
66
66
|
registry?: AgentStorage;
|
|
67
67
|
onAgentAttention?: AgentAttentionCallback;
|
|
68
|
+
onWorkspaceStateMayHaveChanged?: (params: {
|
|
69
|
+
cwd: string;
|
|
70
|
+
}) => void;
|
|
68
71
|
durableTimelineStore?: AgentTimelineStore;
|
|
69
72
|
terminalManager?: TerminalManager | null;
|
|
70
73
|
mcpBaseUrl?: string;
|
|
74
|
+
mcpAuthToken?: string;
|
|
71
75
|
appendSystemPrompt?: string;
|
|
72
76
|
agentStreamCoalesceWindowMs?: number;
|
|
73
77
|
rescueTimeouts?: AgentManagerRescueTimeouts;
|
|
@@ -179,9 +183,11 @@ export declare class AgentManager {
|
|
|
179
183
|
private readonly backgroundTasks;
|
|
180
184
|
private readonly agentStreamCoalescer;
|
|
181
185
|
private mcpBaseUrl;
|
|
186
|
+
private readonly mcpAuthToken;
|
|
182
187
|
private appendSystemPrompt;
|
|
183
188
|
private onAgentAttention?;
|
|
184
189
|
private onAgentArchived?;
|
|
190
|
+
private onWorkspaceStateMayHaveChanged?;
|
|
185
191
|
private logger;
|
|
186
192
|
private readonly rescueTimeouts;
|
|
187
193
|
constructor(options: AgentManagerOptions);
|
|
@@ -194,6 +200,13 @@ export declare class AgentManager {
|
|
|
194
200
|
setAgentAttentionCallback(callback: AgentAttentionCallback): void;
|
|
195
201
|
setAgentArchivedCallback(callback: AgentArchivedCallback): void;
|
|
196
202
|
setMcpBaseUrl(url: string | null): void;
|
|
203
|
+
/**
|
|
204
|
+
* Capability token the daemon's own MCP clients must present to the Agent MCP
|
|
205
|
+
* endpoint when a daemon password is configured. Read by the per-client
|
|
206
|
+
* session to authenticate its own MCP connection. Stays in the daemon — never
|
|
207
|
+
* sent to remote clients.
|
|
208
|
+
*/
|
|
209
|
+
getMcpAuthToken(): string | null;
|
|
197
210
|
setAppendSystemPrompt(prompt: string | null | undefined): void;
|
|
198
211
|
getMetricsSnapshot(): AgentMetricsSnapshot;
|
|
199
212
|
private touchUpdatedAt;
|
|
@@ -355,4 +368,5 @@ export declare class AgentManager {
|
|
|
355
368
|
private requireAgent;
|
|
356
369
|
private requireSessionAgent;
|
|
357
370
|
}
|
|
371
|
+
export declare function commandMayHaveChangedExternalState(command: string): boolean;
|
|
358
372
|
//# sourceMappingURL=agent-manager.d.ts.map
|
|
@@ -177,7 +177,9 @@ export class AgentManager {
|
|
|
177
177
|
this.registry = options?.registry;
|
|
178
178
|
this.durableTimelineStore = options?.durableTimelineStore;
|
|
179
179
|
this.onAgentAttention = options?.onAgentAttention;
|
|
180
|
+
this.onWorkspaceStateMayHaveChanged = options?.onWorkspaceStateMayHaveChanged;
|
|
180
181
|
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
|
|
182
|
+
this.mcpAuthToken = options?.mcpAuthToken ?? null;
|
|
181
183
|
this.appendSystemPrompt = options.appendSystemPrompt ?? "";
|
|
182
184
|
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
|
183
185
|
this.rescueTimeouts = {
|
|
@@ -224,6 +226,15 @@ export class AgentManager {
|
|
|
224
226
|
setMcpBaseUrl(url) {
|
|
225
227
|
this.mcpBaseUrl = url;
|
|
226
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Capability token the daemon's own MCP clients must present to the Agent MCP
|
|
231
|
+
* endpoint when a daemon password is configured. Read by the per-client
|
|
232
|
+
* session to authenticate its own MCP connection. Stays in the daemon — never
|
|
233
|
+
* sent to remote clients.
|
|
234
|
+
*/
|
|
235
|
+
getMcpAuthToken() {
|
|
236
|
+
return this.mcpAuthToken;
|
|
237
|
+
}
|
|
227
238
|
setAppendSystemPrompt(prompt) {
|
|
228
239
|
this.appendSystemPrompt = prompt ?? "";
|
|
229
240
|
}
|
|
@@ -2277,6 +2288,15 @@ export class AgentManager {
|
|
|
2277
2288
|
epoch: this.timelineStore.getEpoch(agentId),
|
|
2278
2289
|
timestamp: row.timestamp,
|
|
2279
2290
|
});
|
|
2291
|
+
if (item.type === "tool_call" &&
|
|
2292
|
+
item.status === "completed" &&
|
|
2293
|
+
item.detail?.type === "shell" &&
|
|
2294
|
+
commandMayHaveChangedExternalState(item.detail.command)) {
|
|
2295
|
+
const agent = this.agents.get(agentId);
|
|
2296
|
+
if (agent) {
|
|
2297
|
+
this.onWorkspaceStateMayHaveChanged?.({ cwd: agent.cwd });
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2280
2300
|
return event;
|
|
2281
2301
|
}
|
|
2282
2302
|
async appendSystemErrorTimelineMessage(agent, provider, message, options) {
|
|
@@ -2536,6 +2556,7 @@ export class AgentManager {
|
|
|
2536
2556
|
config: storedConfig,
|
|
2537
2557
|
agentId,
|
|
2538
2558
|
mcpBaseUrl: this.mcpBaseUrl,
|
|
2559
|
+
mcpAuthToken: this.mcpAuthToken,
|
|
2539
2560
|
}));
|
|
2540
2561
|
return { storedConfig, launchConfig };
|
|
2541
2562
|
}
|
|
@@ -2626,4 +2647,19 @@ export class AgentManager {
|
|
|
2626
2647
|
return agent;
|
|
2627
2648
|
}
|
|
2628
2649
|
}
|
|
2650
|
+
export function commandMayHaveChangedExternalState(command) {
|
|
2651
|
+
const normalized = command.toLowerCase();
|
|
2652
|
+
// Commands that operate on remote state and do NOT trigger local file
|
|
2653
|
+
// watchers. Local git mutations (commit, checkout, merge, rebase, reset,
|
|
2654
|
+
// pull) are already caught by watchers on .git/HEAD and refs/heads/.
|
|
2655
|
+
return (
|
|
2656
|
+
// GitHub PR operations (merge, close, create, edit, comment, review)
|
|
2657
|
+
/\bgh\s+pr\s+(merge|close|create|edit|comment|review)\b/.test(normalized) ||
|
|
2658
|
+
// Pushes to remote — local refs unchanged, but remote state (PR checks,
|
|
2659
|
+
// mergeable status) may shift immediately after.
|
|
2660
|
+
/\bgit\s+push\b/.test(normalized) ||
|
|
2661
|
+
// Fetches update refs/remotes/ which our watchers do not watch, so
|
|
2662
|
+
// ahead/behind counts can drift stale until the next refresh.
|
|
2663
|
+
/\bgit\s+fetch\b/.test(normalized));
|
|
2664
|
+
}
|
|
2629
2665
|
//# sourceMappingURL=agent-manager.js.map
|
|
@@ -133,6 +133,7 @@ export interface AgentFeatureSelect {
|
|
|
133
133
|
}
|
|
134
134
|
export type AgentFeature = AgentFeatureToggle | AgentFeatureSelect;
|
|
135
135
|
export interface AgentCapabilityFlags {
|
|
136
|
+
[capability: string]: boolean | undefined;
|
|
136
137
|
supportsStreaming: boolean;
|
|
137
138
|
supportsSessionPersistence: boolean;
|
|
138
139
|
supportsSessionListing?: boolean;
|
|
@@ -3,6 +3,7 @@ import type { Logger } from "pino";
|
|
|
3
3
|
import { type ClaudeQueryFactory } from "./query.js";
|
|
4
4
|
import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOptions, type AgentFeature, type AgentLaunchContext, type AgentMetadata, type AgentModelDefinition, type AgentPersistenceHandle, type AgentSession, type AgentSessionConfig, type AgentTimelineItem, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ListModelsOptions, type McpServerConfig } from "../../agent-sdk-types.js";
|
|
5
5
|
import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
|
|
6
|
+
export declare function normalizeClaudeAskUserQuestionRequestInput(toolName: string, input: AgentMetadata): AgentMetadata;
|
|
6
7
|
export declare function normalizeClaudeAskUserQuestionUpdatedInput(updatedInput: AgentMetadata | undefined, fallbackInput: AgentMetadata | undefined): AgentMetadata;
|
|
7
8
|
interface EventIdentifiers {
|
|
8
9
|
taskId: string | null;
|
|
@@ -31,6 +31,42 @@ const CLAUDE_SETTING_SOURCES = [
|
|
|
31
31
|
function readNonEmptyString(value) {
|
|
32
32
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
33
33
|
}
|
|
34
|
+
export function normalizeClaudeAskUserQuestionRequestInput(toolName, input) {
|
|
35
|
+
if (toolName !== "AskUserQuestion" || !Array.isArray(input.questions)) {
|
|
36
|
+
return input;
|
|
37
|
+
}
|
|
38
|
+
// Claude Code's AskUserQuestion schema says "Other" is host-provided, not a
|
|
39
|
+
// model-supplied option. Paseo's shared question UI uses allowOther for that
|
|
40
|
+
// freeform answer path.
|
|
41
|
+
return {
|
|
42
|
+
...input,
|
|
43
|
+
questions: input.questions.map((item) => {
|
|
44
|
+
if (!isMetadata(item)) {
|
|
45
|
+
return item;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...item,
|
|
49
|
+
allowOther: true,
|
|
50
|
+
};
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function stripClaudeAskUserQuestionUiMetadata(input) {
|
|
55
|
+
if (!Array.isArray(input.questions)) {
|
|
56
|
+
return input;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
...input,
|
|
60
|
+
questions: input.questions.map((item) => {
|
|
61
|
+
if (!isMetadata(item) || !("allowOther" in item)) {
|
|
62
|
+
return item;
|
|
63
|
+
}
|
|
64
|
+
const itemForClaude = { ...item };
|
|
65
|
+
delete itemForClaude.allowOther;
|
|
66
|
+
return itemForClaude;
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
34
70
|
export function normalizeClaudeAskUserQuestionUpdatedInput(updatedInput, fallbackInput) {
|
|
35
71
|
const fallback = isMetadata(fallbackInput) ? fallbackInput : {};
|
|
36
72
|
const base = isMetadata(updatedInput) ? updatedInput : {};
|
|
@@ -38,7 +74,7 @@ export function normalizeClaudeAskUserQuestionUpdatedInput(updatedInput, fallbac
|
|
|
38
74
|
// AskUserQuestion tool expects answer keys to match the full question text. Merge
|
|
39
75
|
// the original request payload back in so provider callbacks that only return
|
|
40
76
|
// `{ answers }` still satisfy Claude's full tool input schema.
|
|
41
|
-
const merged = { ...fallback, ...base };
|
|
77
|
+
const merged = stripClaudeAskUserQuestionUiMetadata({ ...fallback, ...base });
|
|
42
78
|
const questions = (Array.isArray(base.questions) ? base.questions : null) ??
|
|
43
79
|
(Array.isArray(fallback.questions) ? fallback.questions : null);
|
|
44
80
|
const answers = isMetadata(base.answers) ? base.answers : null;
|
|
@@ -131,10 +167,28 @@ const REWIND_COMMAND = {
|
|
|
131
167
|
description: "Rewind tracked files to a previous user message",
|
|
132
168
|
argumentHint: "[user_message_uuid]",
|
|
133
169
|
};
|
|
170
|
+
const CLAUDE_ROOT_ONLY_COMMANDS = new Set([
|
|
171
|
+
"clear",
|
|
172
|
+
"compact",
|
|
173
|
+
"context",
|
|
174
|
+
"debug",
|
|
175
|
+
"extra-usage",
|
|
176
|
+
"heapdump",
|
|
177
|
+
"init",
|
|
178
|
+
"loop",
|
|
179
|
+
"schedule",
|
|
180
|
+
"usage",
|
|
181
|
+
]);
|
|
134
182
|
const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
|
|
135
183
|
const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/;
|
|
136
184
|
const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested.";
|
|
137
185
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
186
|
+
function classifyClaudeSlashCommand(commandName) {
|
|
187
|
+
// Claude exposes commands and skills as one flat SDK list, without structured source
|
|
188
|
+
// metadata. Keep obvious root-only/session controls out of inline autocomplete and
|
|
189
|
+
// treat the rest as skills; the worst failure mode is an inert inline suggestion.
|
|
190
|
+
return CLAUDE_ROOT_ONLY_COMMANDS.has(commandName) ? "command" : "skill";
|
|
191
|
+
}
|
|
138
192
|
function resolvePathEnvKey() {
|
|
139
193
|
if (process.env["Path"] !== undefined)
|
|
140
194
|
return "Path";
|
|
@@ -1204,6 +1258,7 @@ class ClaudeAgentSession {
|
|
|
1204
1258
|
this.handlePermissionRequest = async (toolName, input, options) => {
|
|
1205
1259
|
const requestId = `permission-${randomUUID()}`;
|
|
1206
1260
|
const kind = resolvePermissionKind(toolName, input);
|
|
1261
|
+
const requestInput = normalizeClaudeAskUserQuestionRequestInput(toolName, input);
|
|
1207
1262
|
const metadata = {};
|
|
1208
1263
|
if (options.toolUseID) {
|
|
1209
1264
|
metadata.toolUseId = options.toolUseID;
|
|
@@ -1224,7 +1279,7 @@ class ClaudeAgentSession {
|
|
|
1224
1279
|
provider: "claude",
|
|
1225
1280
|
name: toolName,
|
|
1226
1281
|
kind,
|
|
1227
|
-
input,
|
|
1282
|
+
input: requestInput,
|
|
1228
1283
|
detail: toolDetail,
|
|
1229
1284
|
suggestions: options.suggestions?.map((suggestion) => ({
|
|
1230
1285
|
...suggestion,
|
|
@@ -1665,7 +1720,7 @@ class ClaudeAgentSession {
|
|
|
1665
1720
|
name: cmd.name,
|
|
1666
1721
|
description: cmd.description,
|
|
1667
1722
|
argumentHint: cmd.argumentHint,
|
|
1668
|
-
kind:
|
|
1723
|
+
kind: classifyClaudeSlashCommand(cmd.name),
|
|
1669
1724
|
});
|
|
1670
1725
|
}
|
|
1671
1726
|
}
|
|
@@ -15,6 +15,13 @@ const CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS = [
|
|
|
15
15
|
{ id: "max", label: "Max" },
|
|
16
16
|
];
|
|
17
17
|
const CLAUDE_MODELS = [
|
|
18
|
+
{
|
|
19
|
+
provider: "claude",
|
|
20
|
+
id: "claude-fable-5",
|
|
21
|
+
label: "Fable 5",
|
|
22
|
+
description: "Fable 5 · Most powerful model",
|
|
23
|
+
thinkingOptions: [...CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS],
|
|
24
|
+
},
|
|
18
25
|
{
|
|
19
26
|
provider: "claude",
|
|
20
27
|
id: "claude-opus-4-8[1m]",
|
|
@@ -170,6 +177,14 @@ export function normalizeClaudeRuntimeModelId(value) {
|
|
|
170
177
|
if (CLAUDE_MODELS.some((model) => model.id === trimmed)) {
|
|
171
178
|
return trimmed;
|
|
172
179
|
}
|
|
180
|
+
// Fable uses a single-segment version (claude-fable-5), not the {major}-{minor}
|
|
181
|
+
// scheme of opus/sonnet/haiku, so match it separately. This maps dated runtime
|
|
182
|
+
// strings (e.g. claude-fable-5-20260301) back to the catalog ID. No [1m] variant:
|
|
183
|
+
// Fable 5 is natively 1M, so there is no 200K-default model to opt into 1M.
|
|
184
|
+
const fableMatch = trimmed.match(/(?:claude-)?fable[-_ ]+(\d+)/i);
|
|
185
|
+
if (fableMatch) {
|
|
186
|
+
return `claude-fable-${fableMatch[1]}`;
|
|
187
|
+
}
|
|
173
188
|
// Match: claude-{family}-{major}-{minor}[1m]? possibly followed by a date suffix
|
|
174
189
|
const runtimeMatch = trimmed.match(/(?:claude-)?(opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(\[1m\])?/i);
|
|
175
190
|
if (!runtimeMatch) {
|
|
@@ -10,15 +10,15 @@ declare const TaskNotificationEnvelopeSchema: z.ZodObject<{
|
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
status: string | null;
|
|
12
12
|
messageId: string | null;
|
|
13
|
-
taskId: string | null;
|
|
14
13
|
summary: string | null;
|
|
14
|
+
taskId: string | null;
|
|
15
15
|
outputFile: string | null;
|
|
16
16
|
rawText: string | null;
|
|
17
17
|
}, {
|
|
18
18
|
status: string | null;
|
|
19
19
|
messageId: string | null;
|
|
20
|
-
taskId: string | null;
|
|
21
20
|
summary: string | null;
|
|
21
|
+
taskId: string | null;
|
|
22
22
|
outputFile: string | null;
|
|
23
23
|
rawText: string | null;
|
|
24
24
|
}>;
|
|
@@ -64,8 +64,54 @@ const MODELS = [
|
|
|
64
64
|
function shouldEmitPlanApprovalPrompt(prompt) {
|
|
65
65
|
return /emit\s+(?:a\s+)?synthetic\s+plan\s+approval/i.test(promptToText(prompt));
|
|
66
66
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
67
|
+
function parseMockQuestionPrompt(prompt) {
|
|
68
|
+
const text = promptToText(prompt);
|
|
69
|
+
if (!/emit\s+(?:a\s+)?synthetic\s+questions?/i.test(text)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (/free[-\s]?write|freeform|text[-\s]?only/i.test(text)) {
|
|
73
|
+
return {
|
|
74
|
+
questions: [
|
|
75
|
+
{
|
|
76
|
+
question: "What is the GitHub private repo URL to push to?",
|
|
77
|
+
header: "repoUrl",
|
|
78
|
+
options: [],
|
|
79
|
+
multiSelect: false,
|
|
80
|
+
placeholder: "git@github.com:user/repo.git",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
question: "What should the first commit message be?",
|
|
84
|
+
header: "commitMessage",
|
|
85
|
+
options: [],
|
|
86
|
+
multiSelect: false,
|
|
87
|
+
placeholder: "Initial commit",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
questions: [
|
|
94
|
+
{
|
|
95
|
+
question: "Which surface should this apply to?",
|
|
96
|
+
header: "surface",
|
|
97
|
+
options: [{ label: "App" }, { label: "Desktop" }],
|
|
98
|
+
multiSelect: false,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
question: "Which rollout should we use?",
|
|
102
|
+
header: "rollout",
|
|
103
|
+
options: [{ label: "Immediately" }, { label: "Behind feature flag" }],
|
|
104
|
+
multiSelect: false,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
question: "What success criteria should we use?",
|
|
108
|
+
header: "success",
|
|
109
|
+
options: [],
|
|
110
|
+
multiSelect: false,
|
|
111
|
+
placeholder: "Describe success...",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
69
115
|
}
|
|
70
116
|
function resolveModelProfile(modelId) {
|
|
71
117
|
const model = MODELS.find((entry) => entry.id === modelId) ?? MODELS[0];
|
|
@@ -399,11 +445,12 @@ export class MockLoadTestAgentSession {
|
|
|
399
445
|
}, 0);
|
|
400
446
|
const largePayload = parseLargeAgentStreamPayloadPrompt(prompt);
|
|
401
447
|
const stress = parseAgentStreamStressPrompt(prompt);
|
|
448
|
+
const questionPrompt = parseMockQuestionPrompt(prompt);
|
|
402
449
|
if (shouldEmitPlanApprovalPrompt(prompt)) {
|
|
403
450
|
this.schedulePlanApprovalTurn(turn);
|
|
404
451
|
}
|
|
405
|
-
else if (
|
|
406
|
-
this.scheduleQuestionPromptTurn(turn);
|
|
452
|
+
else if (questionPrompt) {
|
|
453
|
+
this.scheduleQuestionPromptTurn(turn, questionPrompt);
|
|
407
454
|
}
|
|
408
455
|
else if (largePayload) {
|
|
409
456
|
this.scheduleLargePayloadTurn(turn, largePayload);
|
|
@@ -547,9 +594,9 @@ export class MockLoadTestAgentSession {
|
|
|
547
594
|
}, 0);
|
|
548
595
|
turn.timer.unref?.();
|
|
549
596
|
}
|
|
550
|
-
scheduleQuestionPromptTurn(turn) {
|
|
597
|
+
scheduleQuestionPromptTurn(turn, questionPrompt) {
|
|
551
598
|
turn.timer = setTimeout(() => {
|
|
552
|
-
this.emitQuestionPromptTurn(turn);
|
|
599
|
+
this.emitQuestionPromptTurn(turn, questionPrompt);
|
|
553
600
|
}, 0);
|
|
554
601
|
turn.timer.unref?.();
|
|
555
602
|
}
|
|
@@ -601,7 +648,7 @@ export class MockLoadTestAgentSession {
|
|
|
601
648
|
turnId: turn.turnId,
|
|
602
649
|
});
|
|
603
650
|
}
|
|
604
|
-
emitQuestionPromptTurn(turn) {
|
|
651
|
+
emitQuestionPromptTurn(turn, questionPrompt) {
|
|
605
652
|
if (this.activeTurn !== turn) {
|
|
606
653
|
return;
|
|
607
654
|
}
|
|
@@ -618,27 +665,7 @@ export class MockLoadTestAgentSession {
|
|
|
618
665
|
kind: "question",
|
|
619
666
|
title: "Questions",
|
|
620
667
|
input: {
|
|
621
|
-
questions:
|
|
622
|
-
{
|
|
623
|
-
question: "Which surface should this apply to?",
|
|
624
|
-
header: "surface",
|
|
625
|
-
options: [{ label: "App" }, { label: "Desktop" }],
|
|
626
|
-
multiSelect: false,
|
|
627
|
-
},
|
|
628
|
-
{
|
|
629
|
-
question: "Which rollout should we use?",
|
|
630
|
-
header: "rollout",
|
|
631
|
-
options: [{ label: "Immediately" }, { label: "Behind feature flag" }],
|
|
632
|
-
multiSelect: false,
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
question: "What success criteria should we use?",
|
|
636
|
-
header: "success",
|
|
637
|
-
options: [],
|
|
638
|
-
multiSelect: false,
|
|
639
|
-
placeholder: "Describe success...",
|
|
640
|
-
},
|
|
641
|
-
],
|
|
668
|
+
questions: questionPrompt.questions,
|
|
642
669
|
},
|
|
643
670
|
metadata: {
|
|
644
671
|
source: "mock_questions",
|
|
@@ -12,7 +12,7 @@ import { buildBinaryDiagnosticRows, formatDiagnosticStatus, formatProviderDiagno
|
|
|
12
12
|
import { getUserMessageText, streamPiHistory, } from "./history-mapper.js";
|
|
13
13
|
import { PiCliRuntime } from "./cli-runtime.js";
|
|
14
14
|
import { revertPiConversation } from "./rewind.js";
|
|
15
|
-
import { listPiImportableSessions } from "./session-descriptor.js";
|
|
15
|
+
import { listPiImportableSessions, readPiImportSessionConfig } from "./session-descriptor.js";
|
|
16
16
|
import { mapToolDetail, parseToolArgs, parseToolResult, resolveToolCallName, } from "./tool-call-mapper.js";
|
|
17
17
|
const PI_PROVIDER = "pi";
|
|
18
18
|
const DEFAULT_PI_THINKING_LEVEL = "medium";
|
|
@@ -1557,11 +1557,13 @@ export class PiRpcAgentClient {
|
|
|
1557
1557
|
});
|
|
1558
1558
|
}
|
|
1559
1559
|
async importSession(input, context) {
|
|
1560
|
+
const importConfig = await readPiImportSessionConfig(input.providerHandleId);
|
|
1560
1561
|
return importSessionFromPersistence({
|
|
1561
1562
|
provider: PI_PROVIDER,
|
|
1562
1563
|
request: input,
|
|
1563
1564
|
context,
|
|
1564
1565
|
resumeSession: this.resumeSession.bind(this),
|
|
1566
|
+
config: importConfig,
|
|
1565
1567
|
});
|
|
1566
1568
|
}
|
|
1567
1569
|
async isAvailable() {
|
|
@@ -6,6 +6,11 @@ interface PiSessionDescriptorOptions extends ListImportableSessionsOptions {
|
|
|
6
6
|
env?: NodeJS.ProcessEnv;
|
|
7
7
|
homeDir?: string;
|
|
8
8
|
}
|
|
9
|
+
export interface PiImportSessionConfig {
|
|
10
|
+
model?: string;
|
|
11
|
+
thinkingOptionId?: string;
|
|
12
|
+
}
|
|
9
13
|
export declare function listPiImportableSessions(options?: PiSessionDescriptorOptions): Promise<ImportableProviderSession[]>;
|
|
14
|
+
export declare function readPiImportSessionConfig(filePath: string): Promise<PiImportSessionConfig>;
|
|
10
15
|
export {};
|
|
11
16
|
//# sourceMappingURL=session-descriptor.d.ts.map
|
|
@@ -26,6 +26,12 @@ export async function listPiImportableSessions(options = {}) {
|
|
|
26
26
|
.sort((left, right) => right.lastActivityAt.getTime() - left.lastActivityAt.getTime())
|
|
27
27
|
.slice(0, limit);
|
|
28
28
|
}
|
|
29
|
+
export async function readPiImportSessionConfig(filePath) {
|
|
30
|
+
const descriptor = await readPiSessionDescriptor(filePath);
|
|
31
|
+
if (!descriptor)
|
|
32
|
+
return {};
|
|
33
|
+
return toPiImportSessionConfig(descriptor);
|
|
34
|
+
}
|
|
29
35
|
async function resolvePiSessionsDir(options) {
|
|
30
36
|
const env = options.env ?? process.env;
|
|
31
37
|
const homeDir = options.homeDir ?? homedir();
|
|
@@ -103,6 +109,19 @@ async function walkJsonlFiles(root) {
|
|
|
103
109
|
return files.flat();
|
|
104
110
|
}
|
|
105
111
|
async function readPiImportableSession(filePath) {
|
|
112
|
+
const descriptor = await readPiSessionDescriptor(filePath);
|
|
113
|
+
if (!descriptor)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
providerHandleId: filePath,
|
|
117
|
+
cwd: descriptor.cwd,
|
|
118
|
+
title: descriptor.title,
|
|
119
|
+
firstPromptPreview: normalizePromptPreview(descriptor.firstUserMessage),
|
|
120
|
+
lastPromptPreview: normalizePromptPreview(descriptor.lastUserMessage ?? descriptor.firstUserMessage),
|
|
121
|
+
lastActivityAt: descriptor.lastActivityAt,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function readPiSessionDescriptor(filePath) {
|
|
106
125
|
const firstLine = await readFirstLine(filePath);
|
|
107
126
|
if (!firstLine)
|
|
108
127
|
return null;
|
|
@@ -113,14 +132,23 @@ async function readPiImportableSession(filePath) {
|
|
|
113
132
|
const tailInfo = parseSessionTail(tail);
|
|
114
133
|
const headInfo = await scanSessionHead(filePath);
|
|
115
134
|
const title = tailInfo.title ?? headInfo.title ?? headInfo.firstUserMessage;
|
|
135
|
+
const model = tailInfo.model ?? headInfo.model;
|
|
136
|
+
const thinkingOptionId = tailInfo.thinkingOptionId ?? headInfo.thinkingOptionId;
|
|
116
137
|
const lastActivityAt = tailInfo.lastActivityAt ?? (await readFileMtime(filePath)) ?? header.createdAt ?? new Date(0);
|
|
117
138
|
return {
|
|
118
|
-
providerHandleId: filePath,
|
|
119
139
|
cwd: header.cwd,
|
|
120
140
|
title,
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
firstUserMessage: headInfo.firstUserMessage,
|
|
142
|
+
lastUserMessage: tailInfo.lastUserMessage,
|
|
123
143
|
lastActivityAt,
|
|
144
|
+
model,
|
|
145
|
+
thinkingOptionId,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function toPiImportSessionConfig(descriptor) {
|
|
149
|
+
return {
|
|
150
|
+
...(descriptor.model ? { model: descriptor.model } : {}),
|
|
151
|
+
...(descriptor.thinkingOptionId ? { thinkingOptionId: descriptor.thinkingOptionId } : {}),
|
|
124
152
|
};
|
|
125
153
|
}
|
|
126
154
|
async function readFirstLine(filePath) {
|
|
@@ -179,6 +207,8 @@ function parseSessionTail(tail) {
|
|
|
179
207
|
let lastActivityAt = null;
|
|
180
208
|
let fallbackTimestamp = null;
|
|
181
209
|
let lastUserMessage = null;
|
|
210
|
+
let model = null;
|
|
211
|
+
let thinkingOptionId = null;
|
|
182
212
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
183
213
|
const entry = parseJsonRecord(lines[index].trim());
|
|
184
214
|
if (!entry)
|
|
@@ -186,24 +216,32 @@ function parseSessionTail(tail) {
|
|
|
186
216
|
if (!title && entry.type === "session_info") {
|
|
187
217
|
title = readNonEmptyString(entry.name);
|
|
188
218
|
}
|
|
219
|
+
if (!model) {
|
|
220
|
+
model = extractModel(entry);
|
|
221
|
+
}
|
|
222
|
+
if (!thinkingOptionId) {
|
|
223
|
+
thinkingOptionId = extractThinkingOptionId(entry);
|
|
224
|
+
}
|
|
189
225
|
const entryTimestamp = parseDate(entry.timestamp);
|
|
190
226
|
if (!fallbackTimestamp && entryTimestamp) {
|
|
191
227
|
fallbackTimestamp = entryTimestamp;
|
|
192
228
|
}
|
|
193
|
-
if (entry.type !== "message")
|
|
229
|
+
if (entry.type !== "message")
|
|
194
230
|
continue;
|
|
195
|
-
}
|
|
196
231
|
if (!lastActivityAt && entryTimestamp) {
|
|
197
232
|
lastActivityAt = entryTimestamp;
|
|
198
233
|
}
|
|
199
234
|
if (!lastUserMessage && isRecord(entry.message) && entry.message.role === "user") {
|
|
200
235
|
lastUserMessage = extractMessageText(entry.message.content);
|
|
201
236
|
}
|
|
202
|
-
if (title && lastActivityAt && lastUserMessage) {
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
237
|
}
|
|
206
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
title,
|
|
240
|
+
lastActivityAt: lastActivityAt ?? fallbackTimestamp,
|
|
241
|
+
lastUserMessage,
|
|
242
|
+
model,
|
|
243
|
+
thinkingOptionId,
|
|
244
|
+
};
|
|
207
245
|
}
|
|
208
246
|
async function scanSessionHead(filePath) {
|
|
209
247
|
let content;
|
|
@@ -211,10 +249,12 @@ async function scanSessionHead(filePath) {
|
|
|
211
249
|
content = await readFile(filePath, "utf8");
|
|
212
250
|
}
|
|
213
251
|
catch {
|
|
214
|
-
return { title: null, firstUserMessage: null };
|
|
252
|
+
return { title: null, firstUserMessage: null, model: null, thinkingOptionId: null };
|
|
215
253
|
}
|
|
216
254
|
let title = null;
|
|
217
255
|
let firstUserMessage = null;
|
|
256
|
+
let model = null;
|
|
257
|
+
let thinkingOptionId = null;
|
|
218
258
|
let lineCount = 0;
|
|
219
259
|
for (const rawLine of content.split(/\r?\n/u)) {
|
|
220
260
|
lineCount += 1;
|
|
@@ -224,19 +264,41 @@ async function scanSessionHead(filePath) {
|
|
|
224
264
|
if (entry.type === "session_info") {
|
|
225
265
|
title = readNonEmptyString(entry.name) ?? title;
|
|
226
266
|
}
|
|
267
|
+
model = extractModel(entry) ?? model;
|
|
268
|
+
thinkingOptionId = extractThinkingOptionId(entry) ?? thinkingOptionId;
|
|
227
269
|
if (!firstUserMessage && entry.type === "message" && isRecord(entry.message)) {
|
|
228
270
|
if (entry.message.role === "user") {
|
|
229
271
|
firstUserMessage = extractMessageText(entry.message.content);
|
|
230
272
|
}
|
|
231
273
|
}
|
|
232
|
-
if (title && firstUserMessage) {
|
|
274
|
+
if (title && firstUserMessage && model && thinkingOptionId) {
|
|
233
275
|
break;
|
|
234
276
|
}
|
|
235
277
|
if (lineCount >= FULL_SCAN_LINE_LIMIT && firstUserMessage) {
|
|
236
278
|
break;
|
|
237
279
|
}
|
|
238
280
|
}
|
|
239
|
-
return { title, firstUserMessage };
|
|
281
|
+
return { title, firstUserMessage, model, thinkingOptionId };
|
|
282
|
+
}
|
|
283
|
+
function extractModel(entry) {
|
|
284
|
+
if (entry.type === "model_change") {
|
|
285
|
+
return buildModelId(entry.provider, entry.modelId);
|
|
286
|
+
}
|
|
287
|
+
if (entry.type === "message" && isRecord(entry.message)) {
|
|
288
|
+
return buildModelId(entry.message.provider, entry.message.model);
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
function extractThinkingOptionId(entry) {
|
|
293
|
+
return entry.type === "thinking_level_change" ? readNonEmptyString(entry.thinkingLevel) : null;
|
|
294
|
+
}
|
|
295
|
+
function buildModelId(provider, modelId) {
|
|
296
|
+
const providerName = readNonEmptyString(provider);
|
|
297
|
+
const modelName = readNonEmptyString(modelId);
|
|
298
|
+
if (!providerName || !modelName) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return `${providerName}/${modelName}`;
|
|
240
302
|
}
|
|
241
303
|
function parseJsonRecord(line) {
|
|
242
304
|
if (!line)
|
|
@@ -4,5 +4,11 @@ export declare function withRuntimePaseoMcpServer(params: {
|
|
|
4
4
|
config: AgentSessionConfig;
|
|
5
5
|
agentId: string;
|
|
6
6
|
mcpBaseUrl: string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Capability token authenticating the injected connection to the daemon's
|
|
9
|
+
* Agent MCP endpoint. The daemon password is gated off this route, so without
|
|
10
|
+
* this header the agent's MCP requests are rejected when a password is set.
|
|
11
|
+
*/
|
|
12
|
+
mcpAuthToken: string | null;
|
|
7
13
|
}): AgentSessionConfig;
|
|
8
14
|
//# sourceMappingURL=runtime-mcp-config.d.ts.map
|
|
@@ -31,6 +31,9 @@ export function withRuntimePaseoMcpServer(params) {
|
|
|
31
31
|
[PASEO_MCP_SERVER_NAME]: {
|
|
32
32
|
type: "http",
|
|
33
33
|
url: `${params.mcpBaseUrl}?callerAgentId=${params.agentId}`,
|
|
34
|
+
...(params.mcpAuthToken
|
|
35
|
+
? { headers: { Authorization: `Bearer ${params.mcpAuthToken}` } }
|
|
36
|
+
: {}),
|
|
34
37
|
},
|
|
35
38
|
...storedConfig.mcpServers,
|
|
36
39
|
},
|
|
@@ -21,5 +21,18 @@ export declare function extractWsBearerProtocol(value: string | undefined): stri
|
|
|
21
21
|
export declare function extractWsBearerToken(protocol: string | null): string | null;
|
|
22
22
|
export declare function createRequireBearerMiddleware(auth: DaemonAuthConfig | undefined, onReject?: (context: BearerAuthRejectContext) => void): RequestHandler;
|
|
23
23
|
export declare function shouldBypassBearerAuth(method: string, path: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Authorizes a request to the Agent MCP endpoint (/mcp/agents), which is exempt
|
|
26
|
+
* from the global daemon-password middleware. Accepts either the per-daemon-run
|
|
27
|
+
* capability token the daemon injects into its own agents' configs and MCP
|
|
28
|
+
* client, or a valid daemon-password bearer (so existing password-authenticated
|
|
29
|
+
* callers keep working). When no daemon password is configured the endpoint is
|
|
30
|
+
* open, matching the global middleware's behavior.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isAgentMcpRequestAuthorized(input: {
|
|
33
|
+
password: string | undefined;
|
|
34
|
+
capabilityToken: string | null;
|
|
35
|
+
authorizationHeader: string | undefined;
|
|
36
|
+
}): Promise<boolean>;
|
|
24
37
|
export {};
|
|
25
38
|
//# sourceMappingURL=auth.d.ts.map
|