@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.
Files changed (96) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +11 -3
  2. package/dist/server/server/agent/agent-manager.js +95 -23
  3. package/dist/server/server/agent/agent-prompt.d.ts +1 -1
  4. package/dist/server/server/agent/agent-prompt.js +3 -10
  5. package/dist/server/server/agent/agent-response-loop.js +9 -3
  6. package/dist/server/server/agent/agent-sdk-types.d.ts +9 -3
  7. package/dist/server/server/agent/agent-storage.d.ts +20 -240
  8. package/dist/server/server/agent/agent-storage.js +6 -6
  9. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  10. package/dist/server/server/agent/create-agent/create.js +8 -7
  11. package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
  12. package/dist/server/server/agent/lifecycle-command.js +9 -2
  13. package/dist/server/server/agent/mcp-server.js +263 -119
  14. package/dist/server/server/agent/mcp-shared.d.ts +35 -179
  15. package/dist/server/server/agent/provider-notices.d.ts +3 -0
  16. package/dist/server/server/agent/provider-notices.js +5 -0
  17. package/dist/server/server/agent/provider-registry.d.ts +2 -0
  18. package/dist/server/server/agent/provider-registry.js +10 -3
  19. package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
  20. package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
  21. package/dist/server/server/agent/providers/claude/agent.js +257 -143
  22. package/dist/server/server/agent/providers/claude/models.js +7 -3
  23. package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
  24. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
  25. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
  26. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
  27. package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
  28. package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
  29. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
  30. package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
  31. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
  32. package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
  33. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
  34. package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
  35. package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
  36. package/dist/server/server/agent/providers/opencode-agent.js +14 -9
  37. package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
  38. package/dist/server/server/agent/providers/pi/agent.js +27 -14
  39. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
  40. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
  41. package/dist/server/server/bootstrap.d.ts +2 -0
  42. package/dist/server/server/bootstrap.js +32 -2
  43. package/dist/server/server/loop-service.d.ts +60 -359
  44. package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
  45. package/dist/server/server/managed-processes/managed-processes.js +326 -0
  46. package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
  47. package/dist/server/server/package-version.d.ts +1 -7
  48. package/dist/server/server/paseo-worktree-service.js +15 -1
  49. package/dist/server/server/persisted-config.d.ts +138 -1009
  50. package/dist/server/server/persisted-config.js +1 -1
  51. package/dist/server/server/pid-lock.d.ts +1 -15
  52. package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
  53. package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
  54. package/dist/server/server/session.d.ts +18 -1
  55. package/dist/server/server/session.js +424 -64
  56. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
  57. package/dist/server/server/speech/providers/openai/runtime.js +3 -4
  58. package/dist/server/server/speech/speech-types.d.ts +9 -11
  59. package/dist/server/server/websocket-server.d.ts +1 -0
  60. package/dist/server/server/websocket-server.js +15 -0
  61. package/dist/server/server/workspace-archive-service.js +2 -3
  62. package/dist/server/server/workspace-directory.js +5 -5
  63. package/dist/server/server/workspace-reconciliation-service.js +2 -2
  64. package/dist/server/server/workspace-registry.d.ts +17 -48
  65. package/dist/server/server/workspace-registry.js +9 -0
  66. package/dist/server/server/worktree-core.d.ts +1 -0
  67. package/dist/server/server/worktree-core.js +5 -1
  68. package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
  69. package/dist/server/services/quota-fetcher/manifest.js +47 -0
  70. package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
  71. package/dist/server/services/quota-fetcher/provider.js +2 -0
  72. package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
  73. package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
  74. package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
  75. package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
  76. package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
  77. package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
  78. package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
  79. package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
  80. package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
  81. package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
  82. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
  83. package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
  84. package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
  85. package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
  86. package/dist/server/services/quota-fetcher/service.d.ts +28 -0
  87. package/dist/server/services/quota-fetcher/service.js +58 -0
  88. package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
  89. package/dist/server/services/quota-fetcher/usage.js +49 -0
  90. package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
  91. package/dist/server/terminal/terminal-session-controller.js +23 -3
  92. package/dist/server/utils/checkout-git.js +36 -76
  93. package/dist/server/utils/directory-suggestions.js +98 -2
  94. package/dist/server/utils/worktree-metadata.d.ts +7 -59
  95. package/dist/src/server/persisted-config.js +1 -1
  96. 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.ZodTypeDef, string>;
49
- export declare const LocalTtsModelIdSchema: z.ZodType<"kokoro-en-v0_19", z.ZodTypeDef, string>;
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.error({
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
- logger.warn("OpenAI speech providers are configured but credentials are missing");
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<["openai", "local"]>;
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<["openai", "local"]>;
8
+ provider: z.ZodEnum<{
9
+ local: "local";
10
+ openai: "openai";
11
+ }>;
6
12
  explicit: z.ZodBoolean;
7
13
  enabled: z.ZodOptional<z.ZodBoolean>;
8
- }, "strip", z.ZodTypeAny, {
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 as a first-class
216
- // empty project it persists until explicitly removed, so we never archive the
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. These persist as first-class
299
- // empty projects so the sidebar can render an empty project row with a
300
- // "+ New workspace" affordance.
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
- // Empty project parents ride only on the first page so the sidebar can render
363
- // them without them being duplicated across pagination.
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
- // A project with zero active workspaces is a first-class empty project — it
102
- // persists until explicitly removed and still reconciles its own metadata.
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<["git", "non_git"]>;
7
+ kind: z.ZodEnum<{
8
+ git: "git";
9
+ non_git: "non_git";
10
+ }>;
8
11
  displayName: z.ZodString;
9
- customName: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
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
- }, "strip", z.ZodTypeAny, {
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<["local_checkout", "worktree", "directory"]>;
21
+ kind: z.ZodEnum<{
22
+ local_checkout: "local_checkout";
23
+ worktree: "worktree";
24
+ directory: "directory";
25
+ }>;
37
26
  displayName: z.ZodString;
38
- title: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
39
- branch: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
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
- }, "strip", z.ZodTypeAny, {
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, z.ZodTypeDef, unknown>;
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,7 @@ import type { WorkspaceGitService } from "./workspace-git-service.js";
6
6
  export interface CreateWorktreeCoreInput {
7
7
  cwd: string;
8
8
  worktreeSlug?: string;
9
+ branchName?: string;
9
10
  refName?: string;
10
11
  action?: "branch-off" | "checkout";
11
12
  githubPrNumber?: number;
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provider.js.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