Merge branch 'main' into merge/main-into-dev
# Conflicts: # packages/app/src/components/combined-model-selector.tsx # packages/app/src/components/message-input.tsx # packages/app/src/components/sidebar-workspace-list.tsx # packages/app/src/screens/agent/draft-agent-screen.tsx # packages/app/src/screens/settings-screen.tsx # packages/desktop/src/main.ts # packages/server/src/server/agent/provider-launch-config.ts # packages/server/src/server/session.ts # packages/server/src/server/session.workspaces.test.ts # packages/server/src/shared/messages.ts # packages/server/src/utils/checkout-git.ts
This commit is contained in:
@@ -1047,6 +1047,8 @@ export function readEventIdentifiers(message: SDKMessage): EventIdentifiers {
|
||||
};
|
||||
}
|
||||
|
||||
const claudeDebug = process.env.PASEO_CLAUDE_DEBUG === "1";
|
||||
|
||||
export class ClaudeAgentClient implements AgentClient {
|
||||
readonly provider: "claude" = "claude";
|
||||
readonly capabilities = CLAUDE_CAPABILITIES;
|
||||
@@ -2453,15 +2455,17 @@ class ClaudeAgentSession implements AgentSession {
|
||||
while (!this.closed && this.query === activeQuery) {
|
||||
try {
|
||||
for await (const message of activeQuery) {
|
||||
this.logger.trace(
|
||||
{
|
||||
claudeSessionId: this.claudeSessionId,
|
||||
messageType: message.type,
|
||||
messageSubtype: "subtype" in message ? message.subtype : undefined,
|
||||
messageUuid: "uuid" in message ? message.uuid : undefined,
|
||||
},
|
||||
"Claude query pump: raw SDK message",
|
||||
);
|
||||
if (claudeDebug) {
|
||||
this.logger.trace(
|
||||
{
|
||||
claudeSessionId: this.claudeSessionId,
|
||||
messageType: message.type,
|
||||
messageSubtype: "subtype" in message ? message.subtype : undefined,
|
||||
messageUuid: "uuid" in message ? message.uuid : undefined,
|
||||
},
|
||||
"Claude query pump: raw SDK message",
|
||||
);
|
||||
}
|
||||
consecutiveInterruptAbortRecoveries = 0;
|
||||
if (await this.handleMissingResumedConversation(message, activeQuery)) {
|
||||
return;
|
||||
@@ -2537,14 +2541,16 @@ class ClaudeAgentSession implements AgentSession {
|
||||
const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? null;
|
||||
const identifiers = readEventIdentifiers(message);
|
||||
|
||||
this.logger.trace(
|
||||
{
|
||||
claudeSessionId: this.claudeSessionId,
|
||||
messageType: message.type,
|
||||
turnId,
|
||||
},
|
||||
"Claude query pump: SDK message",
|
||||
);
|
||||
if (claudeDebug) {
|
||||
this.logger.trace(
|
||||
{
|
||||
claudeSessionId: this.claudeSessionId,
|
||||
messageType: message.type,
|
||||
turnId,
|
||||
},
|
||||
"Claude query pump: SDK message",
|
||||
);
|
||||
}
|
||||
|
||||
const messageEvents = this.translateMessageToEvents(message, {
|
||||
suppressAssistantText: true,
|
||||
|
||||
@@ -14,6 +14,14 @@ import {
|
||||
} from "./test-utils/index.js";
|
||||
import { getFullAccessConfig, getAskModeConfig } from "./daemon-e2e/agent-configs.js";
|
||||
import { chunkPcm16, parsePcm16MonoWav, wordSimilarity } from "./test-utils/dictation-e2e.js";
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentPersistenceHandle,
|
||||
AgentRunResult,
|
||||
AgentSession,
|
||||
AgentSessionConfig,
|
||||
AgentStreamEvent,
|
||||
} from "./agent/agent-sdk-types.js";
|
||||
|
||||
const openaiApiKey = process.env.OPENAI_API_KEY ?? null;
|
||||
|
||||
@@ -94,6 +102,116 @@ function waitForSignal<T>(
|
||||
});
|
||||
}
|
||||
|
||||
class NonPersistentReloadSession implements AgentSession {
|
||||
readonly provider = "claude" as const;
|
||||
readonly id = null;
|
||||
readonly capabilities = {
|
||||
supportsStreaming: false,
|
||||
supportsSessionPersistence: true,
|
||||
supportsDynamicModes: false,
|
||||
supportsMcpServers: false,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: false,
|
||||
} as const;
|
||||
|
||||
constructor(private readonly onClose: () => void) {}
|
||||
|
||||
async run(): Promise<AgentRunResult> {
|
||||
return {
|
||||
sessionId: "non-persistent",
|
||||
finalText: "",
|
||||
timeline: [],
|
||||
};
|
||||
}
|
||||
|
||||
async startTurn(): Promise<{ turnId: string }> {
|
||||
return { turnId: "non-persistent-turn" };
|
||||
}
|
||||
|
||||
subscribe(_callback: (event: AgentStreamEvent) => void): () => void {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
async *streamHistory(): AsyncGenerator<AgentStreamEvent> {
|
||||
return;
|
||||
}
|
||||
|
||||
async getRuntimeInfo() {
|
||||
return {
|
||||
provider: "claude" as const,
|
||||
sessionId: null,
|
||||
model: null,
|
||||
modeId: null,
|
||||
};
|
||||
}
|
||||
|
||||
async getAvailableModes(): Promise<[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getCurrentMode(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async setMode(_modeId: string): Promise<void> {}
|
||||
|
||||
getPendingPermissions() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async respondToPermission(): Promise<void> {}
|
||||
|
||||
describePersistence(): AgentPersistenceHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async interrupt(): Promise<void> {}
|
||||
|
||||
async close(): Promise<void> {
|
||||
this.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
class NonPersistentReloadClient implements AgentClient {
|
||||
readonly provider = "claude" as const;
|
||||
readonly capabilities = {
|
||||
supportsStreaming: false,
|
||||
supportsSessionPersistence: true,
|
||||
supportsDynamicModes: false,
|
||||
supportsMcpServers: false,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: false,
|
||||
} as const;
|
||||
createSessionCalls = 0;
|
||||
resumeSessionCalls = 0;
|
||||
closeCalls = 0;
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async createSession(_config: AgentSessionConfig): Promise<AgentSession> {
|
||||
this.createSessionCalls += 1;
|
||||
return new NonPersistentReloadSession(() => {
|
||||
this.closeCalls += 1;
|
||||
});
|
||||
}
|
||||
|
||||
async resumeSession(
|
||||
_handle: AgentPersistenceHandle,
|
||||
_overrides?: Partial<AgentSessionConfig>,
|
||||
): Promise<AgentSession> {
|
||||
this.resumeSessionCalls += 1;
|
||||
return new NonPersistentReloadSession(() => {
|
||||
this.closeCalls += 1;
|
||||
});
|
||||
}
|
||||
|
||||
async listModels() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
describe("daemon client E2E", () => {
|
||||
let ctx: DaemonTestContext;
|
||||
|
||||
@@ -281,6 +399,38 @@ describe("daemon client E2E", () => {
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
test("refresh_agent rebuilds a live agent even when it has no persistence handle", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const client = new NonPersistentReloadClient();
|
||||
const localCtx = await createDaemonTestContext({
|
||||
agentClients: {
|
||||
claude: client,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const created = await localCtx.client.createAgent({
|
||||
config: {
|
||||
provider: "claude",
|
||||
cwd,
|
||||
},
|
||||
});
|
||||
|
||||
expect(client.createSessionCalls).toBe(1);
|
||||
expect(client.resumeSessionCalls).toBe(0);
|
||||
expect(client.closeCalls).toBe(0);
|
||||
|
||||
await localCtx.client.refreshAgent(created.id);
|
||||
|
||||
expect(client.createSessionCalls).toBe(2);
|
||||
expect(client.resumeSessionCalls).toBe(0);
|
||||
expect(client.closeCalls).toBe(1);
|
||||
} finally {
|
||||
await localCtx.cleanup();
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("resume_agent auto-unarchives archived agents", async () => {
|
||||
const cwd = tmpCwd();
|
||||
try {
|
||||
|
||||
@@ -50,7 +50,7 @@ const LOG_LEVEL_PRIORITIES: Record<LogLevel, number> = {
|
||||
|
||||
const DEFAULT_CONSOLE_LEVEL: LogLevel = "info";
|
||||
const DEFAULT_CONSOLE_FORMAT: LogFormat = "pretty";
|
||||
const DEFAULT_FILE_LEVEL: LogLevel = "trace";
|
||||
const DEFAULT_FILE_LEVEL: LogLevel = "debug";
|
||||
const DEFAULT_FILE_ROTATE_SIZE = "10m";
|
||||
const DEFAULT_FILE_ROTATE_MAX_FILES = 2;
|
||||
const DEFAULT_DAEMON_LOG_FILENAME = "daemon.log";
|
||||
|
||||
@@ -217,6 +217,7 @@ function clientSupportsAllProviders(appVersion: string | null): boolean {
|
||||
function clientSupportsFlexibleEditorIds(appVersion: string | null): boolean {
|
||||
return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
|
||||
}
|
||||
|
||||
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
|
||||
const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
|
||||
const TERMINAL_STREAM_HIGH_WATER_BYTES = 256 * 1024;
|
||||
|
||||
@@ -1130,6 +1130,7 @@ export const WorkspaceSetupStatusRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
|
||||
// TODO(2026-07): Remove once most clients are on >=0.1.50 and support arbitrary editor ids.
|
||||
export const LEGACY_EDITOR_TARGET_IDS = [
|
||||
"cursor",
|
||||
|
||||
@@ -1946,6 +1946,7 @@ const ReviewDecisionSchema = z
|
||||
type CheckRunNode = z.infer<typeof CheckRunNodeSchema>;
|
||||
type StatusContextNode = z.infer<typeof StatusContextNodeSchema>;
|
||||
|
||||
|
||||
async function resolveGhPath(): Promise<string> {
|
||||
if (cachedGhPath === undefined) {
|
||||
cachedGhPath = await findExecutable("gh");
|
||||
|
||||
Reference in New Issue
Block a user