@getpaseo/server 0.1.97-beta.3 → 0.1.98
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 +11 -3
- package/dist/server/server/agent/agent-manager.js +95 -23
- package/dist/server/server/agent/agent-prompt.d.ts +1 -1
- package/dist/server/server/agent/agent-prompt.js +3 -10
- package/dist/server/server/agent/agent-response-loop.js +9 -3
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -3
- package/dist/server/server/agent/agent-storage.d.ts +20 -240
- package/dist/server/server/agent/agent-storage.js +6 -6
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +8 -7
- package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
- package/dist/server/server/agent/lifecycle-command.js +9 -2
- package/dist/server/server/agent/mcp-server.js +263 -119
- package/dist/server/server/agent/mcp-shared.d.ts +35 -179
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +2 -0
- package/dist/server/server/agent/provider-registry.js +10 -3
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
- package/dist/server/server/agent/providers/claude/agent.js +257 -143
- package/dist/server/server/agent/providers/claude/models.js +7 -3
- package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
- package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
- package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
- package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode-agent.js +14 -9
- package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
- package/dist/server/server/agent/providers/pi/agent.js +27 -14
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +32 -2
- package/dist/server/server/loop-service.d.ts +60 -359
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
- package/dist/server/server/package-version.d.ts +1 -7
- package/dist/server/server/paseo-worktree-service.js +15 -1
- package/dist/server/server/persisted-config.d.ts +138 -1009
- package/dist/server/server/persisted-config.js +1 -1
- package/dist/server/server/pid-lock.d.ts +1 -15
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
- package/dist/server/server/session.d.ts +18 -1
- package/dist/server/server/session.js +424 -64
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
- package/dist/server/server/speech/providers/openai/runtime.js +3 -4
- package/dist/server/server/speech/speech-types.d.ts +9 -11
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.js +15 -0
- package/dist/server/server/workspace-archive-service.js +2 -3
- package/dist/server/server/workspace-directory.js +5 -5
- package/dist/server/server/workspace-reconciliation-service.js +2 -2
- package/dist/server/server/workspace-registry.d.ts +17 -48
- package/dist/server/server/workspace-registry.js +9 -0
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +5 -1
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +47 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
- package/dist/server/terminal/terminal-session-controller.js +23 -3
- package/dist/server/utils/checkout-git.js +36 -76
- package/dist/server/utils/directory-suggestions.js +98 -2
- package/dist/server/utils/worktree-metadata.d.ts +7 -59
- package/dist/src/server/persisted-config.js +1 -1
- package/package.json +9 -9
|
@@ -45,8 +45,8 @@ export declare const LOCAL_STT_MODEL_IDS: LocalSttModelId[];
|
|
|
45
45
|
export declare const LOCAL_TTS_MODEL_IDS: LocalTtsModelId[];
|
|
46
46
|
export declare const DEFAULT_LOCAL_STT_MODEL: LocalSttModelId;
|
|
47
47
|
export declare const DEFAULT_LOCAL_TTS_MODEL: "kokoro-en-v0_19";
|
|
48
|
-
export declare const LocalSttModelIdSchema: z.ZodType<LocalSttModelId, z.
|
|
49
|
-
export declare const LocalTtsModelIdSchema: z.ZodType<"kokoro-en-v0_19", z.
|
|
48
|
+
export declare const LocalSttModelIdSchema: z.ZodType<LocalSttModelId, string, z.core.$ZodTypeInternals<LocalSttModelId, string>>;
|
|
49
|
+
export declare const LocalTtsModelIdSchema: z.ZodType<"kokoro-en-v0_19", string, z.core.$ZodTypeInternals<"kokoro-en-v0_19", string>>;
|
|
50
50
|
export type SherpaOnnxModelSpec = SherpaOnnxCatalogEntry & {
|
|
51
51
|
id: SherpaOnnxModelId;
|
|
52
52
|
};
|
|
@@ -38,15 +38,14 @@ export function validateOpenAiCredentialRequirements(params) {
|
|
|
38
38
|
missingOpenAiCredentialsFor.push("dictation.stt");
|
|
39
39
|
}
|
|
40
40
|
if (missingOpenAiCredentialsFor.length > 0) {
|
|
41
|
-
logger.
|
|
41
|
+
logger.warn({
|
|
42
42
|
requestedProviders: {
|
|
43
43
|
dictationStt: providers.dictationStt.provider,
|
|
44
44
|
voiceStt: providers.voiceStt.provider,
|
|
45
45
|
voiceTts: providers.voiceTts.provider,
|
|
46
46
|
},
|
|
47
47
|
missingOpenAiCredentialsFor,
|
|
48
|
-
}, "Invalid speech configuration: OpenAI provider selected but credentials are missing");
|
|
49
|
-
throw new Error(`Missing OpenAI credentials for configured speech features: ${missingOpenAiCredentialsFor.join(", ")}`);
|
|
48
|
+
}, "Invalid speech configuration: OpenAI provider selected but credentials are missing — speech features will be unavailable");
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
function createOpenAiStt(apiKey, openaiConfig, logger) {
|
|
@@ -105,7 +104,7 @@ export function initializeOpenAiSpeechServices(params) {
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
else if (needsAnyOpenAi) {
|
|
108
|
-
|
|
107
|
+
// validateOpenAiCredentialRequirements already warned about missing credentials
|
|
109
108
|
}
|
|
110
109
|
return {
|
|
111
110
|
turnDetectionService,
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const SpeechProviderIdSchema: z.ZodEnum<
|
|
2
|
+
export declare const SpeechProviderIdSchema: z.ZodEnum<{
|
|
3
|
+
local: "local";
|
|
4
|
+
openai: "openai";
|
|
5
|
+
}>;
|
|
3
6
|
export type SpeechProviderId = z.infer<typeof SpeechProviderIdSchema>;
|
|
4
7
|
export declare const RequestedSpeechProviderSchema: z.ZodObject<{
|
|
5
|
-
provider: z.ZodEnum<
|
|
8
|
+
provider: z.ZodEnum<{
|
|
9
|
+
local: "local";
|
|
10
|
+
openai: "openai";
|
|
11
|
+
}>;
|
|
6
12
|
explicit: z.ZodBoolean;
|
|
7
13
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
-
},
|
|
9
|
-
provider: "local" | "openai";
|
|
10
|
-
explicit: boolean;
|
|
11
|
-
enabled?: boolean | undefined;
|
|
12
|
-
}, {
|
|
13
|
-
provider: "local" | "openai";
|
|
14
|
-
explicit: boolean;
|
|
15
|
-
enabled?: boolean | undefined;
|
|
16
|
-
}>;
|
|
14
|
+
}, z.core.$strip>;
|
|
17
15
|
export type RequestedSpeechProvider = z.infer<typeof RequestedSpeechProviderSchema>;
|
|
18
16
|
export interface RequestedSpeechProviders {
|
|
19
17
|
dictationStt: RequestedSpeechProvider;
|
|
@@ -93,6 +93,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
93
93
|
private eventLoopDelayMonitor;
|
|
94
94
|
private unsubscribeSpeechReadiness;
|
|
95
95
|
private unsubscribeDaemonConfigChange;
|
|
96
|
+
private readonly providerUsageService;
|
|
96
97
|
private unsubscribeTerminalActivity;
|
|
97
98
|
constructor(server: HTTPServer, logger: pino.Logger, serverId: string, agentManager: AgentManager, agentStorage: AgentStorage, downloadTokenStore: DownloadTokenStore, paseoHome: string, daemonConfigStore: DaemonConfigStore, mcpBaseUrl: string | null, wsConfig: WebSocketServerConfig, auth?: DaemonAuthConfig, speech?: SpeechService | null, terminalManager?: TerminalManager | null, dictation?: {
|
|
98
99
|
finalTimeoutMs?: number;
|
|
@@ -14,6 +14,7 @@ import { buildAgentAttentionNotificationPayload, findLatestPermissionRequest, }
|
|
|
14
14
|
import { createGitHubService } from "../services/github-service.js";
|
|
15
15
|
import { extractWsBearerProtocol, extractWsBearerToken, isBearerTokenValid, } from "./auth.js";
|
|
16
16
|
import { WebSocketRuntimeMetricsWindow, } from "./websocket/runtime-metrics.js";
|
|
17
|
+
import { ProviderUsageService } from "../services/quota-fetcher/service.js";
|
|
17
18
|
const WS_CLOSE_DAEMON_AUTH_FAILED = 4401;
|
|
18
19
|
function resolveTerminalAttentionReason(input) {
|
|
19
20
|
if (input.attentionReason === "finished")
|
|
@@ -298,6 +299,9 @@ export class VoiceAssistantWebSocketServer {
|
|
|
298
299
|
this.logger.warn({ err, agentId: params.agentId }, "Failed to broadcast agent attention");
|
|
299
300
|
});
|
|
300
301
|
});
|
|
302
|
+
this.providerUsageService = new ProviderUsageService({
|
|
303
|
+
logger: this.logger,
|
|
304
|
+
});
|
|
301
305
|
this.wss = this.createWebSocketServer(server, wsConfig, auth);
|
|
302
306
|
this.startRuntimeMetricsInterval();
|
|
303
307
|
this.logger.info("WebSocket server initialized on /ws");
|
|
@@ -649,6 +653,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
649
653
|
tts: () => this.speech?.resolveTts() ?? null,
|
|
650
654
|
terminalManager: this.terminalManager,
|
|
651
655
|
providerSnapshotManager: this.providerSnapshotManager,
|
|
656
|
+
providerUsageService: this.providerUsageService,
|
|
652
657
|
serviceProxy: this.serviceProxy ?? undefined,
|
|
653
658
|
scriptRuntimeStore: this.scriptRuntimeStore ?? undefined,
|
|
654
659
|
workspaceSetupSnapshots: this.workspaceSetupSnapshots,
|
|
@@ -807,6 +812,16 @@ export class VoiceAssistantWebSocketServer {
|
|
|
807
812
|
checkoutRefresh: true,
|
|
808
813
|
// COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
|
|
809
814
|
workspaceMultiplicity: true,
|
|
815
|
+
// COMPAT(projectRemove): added in v0.1.97, drop the gate when floor >= v0.1.97.
|
|
816
|
+
projectRemove: true,
|
|
817
|
+
// COMPAT(projectAdd): added in v0.1.97, drop the gate when floor >= v0.1.97.
|
|
818
|
+
projectAdd: true,
|
|
819
|
+
// COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
|
|
820
|
+
worktreeRestore: true,
|
|
821
|
+
// COMPAT(providerUsageList): added in v0.1.98, drop the gate when daemon floor >= v0.1.98.
|
|
822
|
+
providerUsageList: true,
|
|
823
|
+
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
|
|
824
|
+
agentDetach: true,
|
|
810
825
|
},
|
|
811
826
|
};
|
|
812
827
|
}
|
|
@@ -212,9 +212,8 @@ export async function killTerminalsForWorkspace(dependencies, workspaceId) {
|
|
|
212
212
|
}
|
|
213
213
|
}));
|
|
214
214
|
}
|
|
215
|
-
// Archiving the last workspace of a project leaves the project
|
|
216
|
-
//
|
|
217
|
-
// parent project here.
|
|
215
|
+
// Archiving the last workspace of a project leaves the project record active.
|
|
216
|
+
// The user removes the project explicitly, so we never archive the parent here.
|
|
218
217
|
export async function archivePersistedWorkspaceRecord(input) {
|
|
219
218
|
const existingWorkspace = await input.workspaceRegistry.get(input.workspaceId);
|
|
220
219
|
if (!existingWorkspace) {
|
|
@@ -295,9 +295,9 @@ export class WorkspaceDirectory {
|
|
|
295
295
|
const candidates = [...agentTimestamps, ...terminalTimestamps].sort();
|
|
296
296
|
return candidates.at(-1) ?? null;
|
|
297
297
|
}
|
|
298
|
-
// Project parents that have no active workspaces.
|
|
299
|
-
//
|
|
300
|
-
//
|
|
298
|
+
// Project parents that have no active workspaces. The wire field is the
|
|
299
|
+
// sidebar projection bucket for projects whose workspace list is currently
|
|
300
|
+
// empty; it is not a separate domain record.
|
|
301
301
|
async listEmptyProjects() {
|
|
302
302
|
const [persistedWorkspaces, persistedProjects] = await Promise.all([
|
|
303
303
|
this.deps.workspaceRegistry.list(),
|
|
@@ -359,8 +359,8 @@ export class WorkspaceDirectory {
|
|
|
359
359
|
const nextCursor = hasMore && pagedEntries.length > 0
|
|
360
360
|
? this.pager.encode(pagedEntries[pagedEntries.length - 1], sort)
|
|
361
361
|
: null;
|
|
362
|
-
//
|
|
363
|
-
// them without them
|
|
362
|
+
// Project parents with no active workspaces ride only on the first page so
|
|
363
|
+
// the sidebar can render them without duplicating them across pagination.
|
|
364
364
|
const projectIdFilter = filter?.projectId?.trim();
|
|
365
365
|
const emptyProjects = cursorToken
|
|
366
366
|
? []
|
|
@@ -98,8 +98,8 @@ export class WorkspaceReconciliationService {
|
|
|
98
98
|
// 2. Merge duplicate active project records that point at the same repo root.
|
|
99
99
|
await this.mergeDuplicateProjectsByRoot(activeProjects, workspacesByProject, changes);
|
|
100
100
|
// 3. Reconcile git metadata for active projects whose directories still exist.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
101
|
+
// Projects persist until explicitly removed, even when they currently have
|
|
102
|
+
// zero active workspaces, so they still reconcile their own metadata.
|
|
103
103
|
// Skip projects archived earlier in this pass (e.g. merged duplicates) so we
|
|
104
104
|
// don't resurrect them by upserting a stale, non-archived copy.
|
|
105
105
|
const archivedProjectIds = new Set(changes
|
|
@@ -4,65 +4,33 @@ import type { PersistedProjectKind, PersistedWorkspaceKind } from "./workspace-r
|
|
|
4
4
|
declare const PersistedProjectRecordSchema: z.ZodObject<{
|
|
5
5
|
projectId: z.ZodString;
|
|
6
6
|
rootPath: z.ZodString;
|
|
7
|
-
kind: z.ZodEnum<
|
|
7
|
+
kind: z.ZodEnum<{
|
|
8
|
+
git: "git";
|
|
9
|
+
non_git: "non_git";
|
|
10
|
+
}>;
|
|
8
11
|
displayName: z.ZodString;
|
|
9
|
-
customName: z.
|
|
12
|
+
customName: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
10
13
|
createdAt: z.ZodString;
|
|
11
14
|
updatedAt: z.ZodString;
|
|
12
15
|
archivedAt: z.ZodNullable<z.ZodString>;
|
|
13
|
-
},
|
|
14
|
-
kind: "git" | "non_git";
|
|
15
|
-
createdAt: string;
|
|
16
|
-
updatedAt: string;
|
|
17
|
-
archivedAt: string | null;
|
|
18
|
-
projectId: string;
|
|
19
|
-
displayName: string;
|
|
20
|
-
rootPath: string;
|
|
21
|
-
customName: string | null;
|
|
22
|
-
}, {
|
|
23
|
-
kind: "git" | "non_git";
|
|
24
|
-
createdAt: string;
|
|
25
|
-
updatedAt: string;
|
|
26
|
-
archivedAt: string | null;
|
|
27
|
-
projectId: string;
|
|
28
|
-
displayName: string;
|
|
29
|
-
rootPath: string;
|
|
30
|
-
customName?: string | null | undefined;
|
|
31
|
-
}>;
|
|
16
|
+
}, z.core.$strip>;
|
|
32
17
|
declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
|
|
33
18
|
workspaceId: z.ZodString;
|
|
34
19
|
projectId: z.ZodString;
|
|
35
20
|
cwd: z.ZodString;
|
|
36
|
-
kind: z.ZodEnum<
|
|
21
|
+
kind: z.ZodEnum<{
|
|
22
|
+
local_checkout: "local_checkout";
|
|
23
|
+
worktree: "worktree";
|
|
24
|
+
directory: "directory";
|
|
25
|
+
}>;
|
|
37
26
|
displayName: z.ZodString;
|
|
38
|
-
title: z.
|
|
39
|
-
branch: z.
|
|
27
|
+
title: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
28
|
+
branch: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
29
|
+
baseBranch: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
40
30
|
createdAt: z.ZodString;
|
|
41
31
|
updatedAt: z.ZodString;
|
|
42
32
|
archivedAt: z.ZodNullable<z.ZodString>;
|
|
43
|
-
},
|
|
44
|
-
kind: "local_checkout" | "worktree" | "directory";
|
|
45
|
-
workspaceId: string;
|
|
46
|
-
cwd: string;
|
|
47
|
-
title: string | null;
|
|
48
|
-
createdAt: string;
|
|
49
|
-
updatedAt: string;
|
|
50
|
-
archivedAt: string | null;
|
|
51
|
-
projectId: string;
|
|
52
|
-
displayName: string;
|
|
53
|
-
branch: string | null;
|
|
54
|
-
}, {
|
|
55
|
-
kind: "local_checkout" | "worktree" | "directory";
|
|
56
|
-
workspaceId: string;
|
|
57
|
-
cwd: string;
|
|
58
|
-
createdAt: string;
|
|
59
|
-
updatedAt: string;
|
|
60
|
-
archivedAt: string | null;
|
|
61
|
-
projectId: string;
|
|
62
|
-
displayName: string;
|
|
63
|
-
title?: string | null | undefined;
|
|
64
|
-
branch?: string | null | undefined;
|
|
65
|
-
}>;
|
|
33
|
+
}, z.core.$strip>;
|
|
66
34
|
export type PersistedProjectRecord = z.infer<typeof PersistedProjectRecordSchema>;
|
|
67
35
|
export type PersistedWorkspaceRecord = z.infer<typeof PersistedWorkspaceRecordSchema>;
|
|
68
36
|
export interface ProjectRegistry {
|
|
@@ -95,7 +63,7 @@ declare class FileBackedRegistry<TRecord extends RegistryRecord> {
|
|
|
95
63
|
constructor(options: {
|
|
96
64
|
filePath: string;
|
|
97
65
|
logger: Logger;
|
|
98
|
-
schema: z.ZodType<TRecord,
|
|
66
|
+
schema: z.ZodType<TRecord, unknown>;
|
|
99
67
|
getId: (record: TRecord) => string;
|
|
100
68
|
component: string;
|
|
101
69
|
});
|
|
@@ -135,6 +103,7 @@ export declare function createPersistedWorkspaceRecord(input: {
|
|
|
135
103
|
displayName: string;
|
|
136
104
|
title?: string | null;
|
|
137
105
|
branch?: string | null;
|
|
106
|
+
baseBranch?: string | null;
|
|
138
107
|
createdAt: string;
|
|
139
108
|
updatedAt: string;
|
|
140
109
|
archivedAt?: string | null;
|
|
@@ -39,6 +39,14 @@ const PersistedWorkspaceRecordSchema = z.object({
|
|
|
39
39
|
.nullable()
|
|
40
40
|
.optional()
|
|
41
41
|
.transform((value) => value ?? null),
|
|
42
|
+
// The base branch the worktree was created from (normalized like worktree.json's
|
|
43
|
+
// baseRefName). Only worktree workspaces carry a base branch; checkout-branch
|
|
44
|
+
// worktrees and directory/local_checkout workspaces leave it null.
|
|
45
|
+
baseBranch: z
|
|
46
|
+
.string()
|
|
47
|
+
.nullable()
|
|
48
|
+
.optional()
|
|
49
|
+
.transform((value) => value ?? null),
|
|
42
50
|
createdAt: z.string(),
|
|
43
51
|
updatedAt: z.string(),
|
|
44
52
|
archivedAt: z.string().nullable(),
|
|
@@ -170,6 +178,7 @@ export function createPersistedWorkspaceRecord(input) {
|
|
|
170
178
|
...input,
|
|
171
179
|
title: input.title ?? null,
|
|
172
180
|
branch: input.branch ?? null,
|
|
181
|
+
baseBranch: input.baseBranch ?? null,
|
|
173
182
|
archivedAt: input.archivedAt ?? null,
|
|
174
183
|
});
|
|
175
184
|
}
|
|
@@ -6,6 +6,9 @@ export async function createWorktreeCore(input, deps) {
|
|
|
6
6
|
const requestedWorktreeSlug = input.worktreeSlug
|
|
7
7
|
? normalizeWorktreeSlug(input.worktreeSlug)
|
|
8
8
|
: undefined;
|
|
9
|
+
const requestedBranchName = input.branchName
|
|
10
|
+
? validateWorktreeSlug(input.branchName.trim())
|
|
11
|
+
: undefined;
|
|
9
12
|
let intentInput;
|
|
10
13
|
if (input.action === "checkout") {
|
|
11
14
|
intentInput = {
|
|
@@ -27,6 +30,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
27
30
|
intentInput = {
|
|
28
31
|
action: "branch-off",
|
|
29
32
|
refName: input.refName,
|
|
33
|
+
branchName: requestedBranchName,
|
|
30
34
|
worktreeSlug,
|
|
31
35
|
};
|
|
32
36
|
}
|
|
@@ -37,7 +41,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
37
41
|
let normalizedSlug;
|
|
38
42
|
switch (intent.kind) {
|
|
39
43
|
case "branch-off": {
|
|
40
|
-
normalizedSlug = intent.branchName;
|
|
44
|
+
normalizedSlug = requestedWorktreeSlug ?? normalizeWorktreeSlug(intent.branchName);
|
|
41
45
|
break;
|
|
42
46
|
}
|
|
43
47
|
case "checkout-branch": {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderUsageFetcher, ProviderUsageFetcherFactoryOptions, ProviderUsageFetcherManifestEntry } from "./provider.js";
|
|
2
|
+
export declare const PROVIDER_USAGE_FETCHERS: readonly ProviderUsageFetcherManifestEntry[];
|
|
3
|
+
export declare function createProviderUsageFetchers(options: ProviderUsageFetcherFactoryOptions): ProviderUsageFetcher[];
|
|
4
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ClaudeQuotaProvider } from "./providers/claude.js";
|
|
2
|
+
import { CodexQuotaProvider } from "./providers/codex.js";
|
|
3
|
+
import { CopilotQuotaProvider } from "./providers/copilot.js";
|
|
4
|
+
import { CursorQuotaProvider } from "./providers/cursor.js";
|
|
5
|
+
import { GrokQuotaProvider } from "./providers/grok.js";
|
|
6
|
+
import { KimiQuotaProvider } from "./providers/kimi.js";
|
|
7
|
+
import { ZaiQuotaProvider } from "./providers/zai.js";
|
|
8
|
+
export const PROVIDER_USAGE_FETCHERS = [
|
|
9
|
+
{
|
|
10
|
+
providerId: "claude",
|
|
11
|
+
create: (options) => new ClaudeQuotaProvider({
|
|
12
|
+
logger: options.logger,
|
|
13
|
+
fetch: options.fetch,
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
providerId: "codex",
|
|
18
|
+
create: (options) => new CodexQuotaProvider({
|
|
19
|
+
logger: options.logger,
|
|
20
|
+
fetch: options.fetch,
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
providerId: "copilot",
|
|
25
|
+
create: (options) => new CopilotQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
providerId: "cursor",
|
|
29
|
+
create: (options) => new CursorQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
providerId: "zai",
|
|
33
|
+
create: (options) => new ZaiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
providerId: "grok",
|
|
37
|
+
create: (options) => new GrokQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
providerId: "kimi",
|
|
41
|
+
create: (options) => new KimiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
export function createProviderUsageFetchers(options) {
|
|
45
|
+
return PROVIDER_USAGE_FETCHERS.map((entry) => entry.create(options));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../server/messages.js";
|
|
3
|
+
export type ProviderApiFetch = typeof fetch;
|
|
4
|
+
export interface ProviderUsageFetcher {
|
|
5
|
+
readonly providerId: string;
|
|
6
|
+
readonly displayName: string;
|
|
7
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
8
|
+
}
|
|
9
|
+
export interface ProviderUsageFetcherFactoryOptions {
|
|
10
|
+
logger: Logger;
|
|
11
|
+
fetch?: ProviderApiFetch;
|
|
12
|
+
}
|
|
13
|
+
export interface ProviderUsageFetcherManifestEntry {
|
|
14
|
+
readonly providerId: string;
|
|
15
|
+
create(options: ProviderUsageFetcherFactoryOptions): ProviderUsageFetcher;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
|
|
4
|
+
interface ClaudeQuotaProviderOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
claudeHome?: string;
|
|
7
|
+
claudeKeychainReader?: () => Promise<unknown | null>;
|
|
8
|
+
platform?: typeof process.platform;
|
|
9
|
+
fetch?: ProviderApiFetch;
|
|
10
|
+
}
|
|
11
|
+
export declare class ClaudeQuotaProvider implements ProviderUsageFetcher {
|
|
12
|
+
readonly providerId = "claude";
|
|
13
|
+
readonly displayName = "Claude";
|
|
14
|
+
private readonly claudeHome;
|
|
15
|
+
private readonly readKeychainCredentials;
|
|
16
|
+
private readonly platform;
|
|
17
|
+
private readonly fetchApi;
|
|
18
|
+
constructor(options: ClaudeQuotaProviderOptions);
|
|
19
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
20
|
+
private readCredentials;
|
|
21
|
+
private callClaudeApi;
|
|
22
|
+
private refreshClaudeToken;
|
|
23
|
+
private saveClaudeCredentials;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { ApiNumberSchema, fetchProviderApi, unavailableUsage, windowFromUsedPct, } from "../usage.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const CLAUDE_KEYCHAIN_TIMEOUT_MS = 2000;
|
|
10
|
+
const CLAUDE_OAUTH_BETA = "oauth-2025-04-20";
|
|
11
|
+
const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
12
|
+
const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
13
|
+
const ClaudeCredentialsSchema = z.object({
|
|
14
|
+
claudeAiOauth: z
|
|
15
|
+
.object({
|
|
16
|
+
accessToken: z.string().optional(),
|
|
17
|
+
refreshToken: z.string().optional(),
|
|
18
|
+
subscriptionType: z.string().optional(),
|
|
19
|
+
rateLimitTier: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
});
|
|
23
|
+
const ClaudeUsageWindowSchema = z.object({
|
|
24
|
+
utilization: ApiNumberSchema,
|
|
25
|
+
resets_at: z.string().optional(),
|
|
26
|
+
});
|
|
27
|
+
const ClaudeUsageResponseSchema = z.object({
|
|
28
|
+
five_hour: ClaudeUsageWindowSchema.nullish(),
|
|
29
|
+
seven_day: ClaudeUsageWindowSchema.nullish(),
|
|
30
|
+
seven_day_opus: ClaudeUsageWindowSchema.nullish(),
|
|
31
|
+
seven_day_omelette: ClaudeUsageWindowSchema.nullish(),
|
|
32
|
+
extra_usage: z
|
|
33
|
+
.object({
|
|
34
|
+
is_enabled: z.boolean().optional(),
|
|
35
|
+
})
|
|
36
|
+
.nullish(),
|
|
37
|
+
});
|
|
38
|
+
const ClaudeTokenRefreshSchema = z.object({
|
|
39
|
+
access_token: z.string().optional(),
|
|
40
|
+
refresh_token: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
function buildClaudePlan(subscriptionType, rateLimitTier) {
|
|
43
|
+
if (!subscriptionType)
|
|
44
|
+
return null;
|
|
45
|
+
const label = subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1);
|
|
46
|
+
const tier = rateLimitTier?.split("_").pop();
|
|
47
|
+
return tier ? `${label} ${tier}` : label;
|
|
48
|
+
}
|
|
49
|
+
async function readClaudeKeychainCredentials() {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execFileAsync("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], { timeout: CLAUDE_KEYCHAIN_TIMEOUT_MS });
|
|
52
|
+
const raw = stdout.trim();
|
|
53
|
+
if (!raw)
|
|
54
|
+
return null;
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class ClaudeQuotaProvider {
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.providerId = "claude";
|
|
64
|
+
this.displayName = "Claude";
|
|
65
|
+
this.claudeHome =
|
|
66
|
+
options.claudeHome || process.env["CLAUDE_HOME"] || join(homedir(), ".claude");
|
|
67
|
+
this.readKeychainCredentials = options.claudeKeychainReader ?? readClaudeKeychainCredentials;
|
|
68
|
+
this.platform = options.platform ?? process.platform;
|
|
69
|
+
this.fetchApi = options.fetch ?? fetch;
|
|
70
|
+
}
|
|
71
|
+
async fetchUsage() {
|
|
72
|
+
const credentials = await this.readCredentials();
|
|
73
|
+
if (!credentials) {
|
|
74
|
+
return unavailableUsage(this);
|
|
75
|
+
}
|
|
76
|
+
const { oauth, filePath } = credentials;
|
|
77
|
+
const plan = buildClaudePlan(oauth.subscriptionType, oauth.rateLimitTier);
|
|
78
|
+
let resp = await this.callClaudeApi(oauth.accessToken);
|
|
79
|
+
if (resp === "NEEDS_AUTH") {
|
|
80
|
+
if (!filePath || !oauth.refreshToken) {
|
|
81
|
+
return unavailableUsage(this);
|
|
82
|
+
}
|
|
83
|
+
const refreshed = await this.refreshClaudeToken(oauth.refreshToken);
|
|
84
|
+
if (!refreshed?.access_token) {
|
|
85
|
+
return unavailableUsage(this);
|
|
86
|
+
}
|
|
87
|
+
await this.saveClaudeCredentials(filePath, {
|
|
88
|
+
...oauth,
|
|
89
|
+
accessToken: refreshed.access_token,
|
|
90
|
+
refreshToken: refreshed.refresh_token ?? oauth.refreshToken,
|
|
91
|
+
});
|
|
92
|
+
resp = await this.callClaudeApi(refreshed.access_token);
|
|
93
|
+
if (resp === "NEEDS_AUTH") {
|
|
94
|
+
return unavailableUsage(this);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const windows = [];
|
|
98
|
+
if (resp.five_hour) {
|
|
99
|
+
windows.push(windowFromUsedPct({
|
|
100
|
+
id: "five_hour",
|
|
101
|
+
label: "Session",
|
|
102
|
+
utilizationPct: resp.five_hour.utilization,
|
|
103
|
+
resetsAt: resp.five_hour.resets_at ?? null,
|
|
104
|
+
tone: "ok",
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
if (resp.seven_day) {
|
|
108
|
+
windows.push(windowFromUsedPct({
|
|
109
|
+
id: "weekly",
|
|
110
|
+
label: "Weekly",
|
|
111
|
+
utilizationPct: resp.seven_day.utilization,
|
|
112
|
+
resetsAt: resp.seven_day.resets_at ?? null,
|
|
113
|
+
tone: "ok",
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
if (resp.seven_day_opus) {
|
|
117
|
+
windows.push(windowFromUsedPct({
|
|
118
|
+
id: "weekly_opus",
|
|
119
|
+
label: "Weekly · Opus",
|
|
120
|
+
utilizationPct: resp.seven_day_opus.utilization,
|
|
121
|
+
resetsAt: resp.seven_day_opus.resets_at ?? null,
|
|
122
|
+
tone: "ok",
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
if (resp.seven_day_omelette) {
|
|
126
|
+
windows.push(windowFromUsedPct({
|
|
127
|
+
id: "weekly_omelette",
|
|
128
|
+
label: "Weekly · Omelette",
|
|
129
|
+
utilizationPct: resp.seven_day_omelette.utilization,
|
|
130
|
+
resetsAt: resp.seven_day_omelette.resets_at ?? null,
|
|
131
|
+
tone: "ok",
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
const details = [];
|
|
135
|
+
const extraUsageEnabled = resp.extra_usage?.is_enabled;
|
|
136
|
+
if (extraUsageEnabled !== undefined) {
|
|
137
|
+
details.push({
|
|
138
|
+
id: "extra_usage",
|
|
139
|
+
label: "Extra usage",
|
|
140
|
+
value: extraUsageEnabled ? "Enabled" : "Disabled",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
providerId: this.providerId,
|
|
145
|
+
displayName: this.displayName,
|
|
146
|
+
status: "available",
|
|
147
|
+
planLabel: plan,
|
|
148
|
+
windows,
|
|
149
|
+
balances: [],
|
|
150
|
+
details,
|
|
151
|
+
error: null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async readCredentials() {
|
|
155
|
+
const credPath = join(this.claudeHome, ".credentials.json");
|
|
156
|
+
if (existsSync(credPath)) {
|
|
157
|
+
try {
|
|
158
|
+
const creds = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
|
|
159
|
+
const oauth = creds.claudeAiOauth;
|
|
160
|
+
if (oauth?.accessToken) {
|
|
161
|
+
return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: credPath };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Fall through to the macOS Keychain below.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (this.platform === "darwin") {
|
|
169
|
+
const creds = ClaudeCredentialsSchema.safeParse(await this.readKeychainCredentials());
|
|
170
|
+
const oauth = creds.success ? creds.data.claudeAiOauth : undefined;
|
|
171
|
+
if (oauth?.accessToken) {
|
|
172
|
+
return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: null };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
async callClaudeApi(token) {
|
|
178
|
+
const res = await fetchProviderApi(this.fetchApi, "https://api.anthropic.com/api/oauth/usage", {
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${token}`,
|
|
181
|
+
Accept: "application/json",
|
|
182
|
+
"anthropic-beta": CLAUDE_OAUTH_BETA,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (res.status === 401 || res.status === 403)
|
|
186
|
+
return "NEEDS_AUTH";
|
|
187
|
+
if (!res.ok)
|
|
188
|
+
throw new Error(`Claude usage API returned ${res.status}`);
|
|
189
|
+
return ClaudeUsageResponseSchema.parse(await res.json());
|
|
190
|
+
}
|
|
191
|
+
async refreshClaudeToken(refreshToken) {
|
|
192
|
+
const res = await fetchProviderApi(this.fetchApi, "https://platform.claude.com/v1/oauth/token", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "Content-Type": "application/json" },
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
grant_type: "refresh_token",
|
|
197
|
+
refresh_token: refreshToken,
|
|
198
|
+
client_id: CLAUDE_CLIENT_ID,
|
|
199
|
+
scope: "user:profile user:inference user:sessions:claude_code user:mcp_servers",
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
return null;
|
|
204
|
+
return ClaudeTokenRefreshSchema.parse(await res.json());
|
|
205
|
+
}
|
|
206
|
+
async saveClaudeCredentials(credPath, oauth) {
|
|
207
|
+
try {
|
|
208
|
+
const existing = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
|
|
209
|
+
existing.claudeAiOauth = oauth;
|
|
210
|
+
await fs.writeFile(credPath, JSON.stringify(existing, null, 2), { mode: 0o600 });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Non-fatal; Claude Code can refresh again on its own next time.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=claude.js.map
|