@getpaseo/server 0.1.97 → 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 (69) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +11 -3
  2. package/dist/server/server/agent/agent-manager.js +94 -22
  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-sdk-types.d.ts +9 -3
  6. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  7. package/dist/server/server/agent/create-agent/create.js +8 -7
  8. package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
  9. package/dist/server/server/agent/lifecycle-command.js +9 -2
  10. package/dist/server/server/agent/mcp-server.js +254 -115
  11. package/dist/server/server/agent/provider-notices.d.ts +3 -0
  12. package/dist/server/server/agent/provider-notices.js +5 -0
  13. package/dist/server/server/agent/provider-registry.d.ts +2 -0
  14. package/dist/server/server/agent/provider-registry.js +10 -3
  15. package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
  16. package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
  17. package/dist/server/server/agent/providers/claude/agent.js +257 -143
  18. package/dist/server/server/agent/providers/claude/models.js +7 -3
  19. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
  20. package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
  21. package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
  22. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
  23. package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
  24. package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
  25. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
  26. package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
  27. package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
  28. package/dist/server/server/agent/providers/opencode-agent.js +14 -9
  29. package/dist/server/server/agent/providers/pi/agent.js +27 -14
  30. package/dist/server/server/bootstrap.d.ts +2 -0
  31. package/dist/server/server/bootstrap.js +32 -2
  32. package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
  33. package/dist/server/server/managed-processes/managed-processes.js +326 -0
  34. package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
  35. package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
  36. package/dist/server/server/session.d.ts +12 -1
  37. package/dist/server/server/session.js +230 -40
  38. package/dist/server/server/speech/providers/openai/runtime.js +3 -4
  39. package/dist/server/server/websocket-server.d.ts +1 -0
  40. package/dist/server/server/websocket-server.js +11 -0
  41. package/dist/server/server/workspace-archive-service.js +2 -3
  42. package/dist/server/server/workspace-directory.js +5 -5
  43. package/dist/server/server/workspace-reconciliation-service.js +2 -2
  44. package/dist/server/server/worktree-core.d.ts +1 -0
  45. package/dist/server/server/worktree-core.js +5 -1
  46. package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
  47. package/dist/server/services/quota-fetcher/manifest.js +47 -0
  48. package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
  49. package/dist/server/services/quota-fetcher/provider.js +2 -0
  50. package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
  51. package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
  52. package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
  53. package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
  54. package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
  55. package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
  56. package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
  57. package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
  58. package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
  59. package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
  60. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
  61. package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
  62. package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
  63. package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
  64. package/dist/server/services/quota-fetcher/service.d.ts +28 -0
  65. package/dist/server/services/quota-fetcher/service.js +58 -0
  66. package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
  67. package/dist/server/services/quota-fetcher/usage.js +49 -0
  68. package/dist/server/utils/directory-suggestions.js +98 -2
  69. package/package.json +5 -5
@@ -25,6 +25,7 @@ import { FileBackedChatService } from "./chat/chat-service.js";
25
25
  import { LoopService } from "./loop-service.js";
26
26
  import { ScheduleService } from "./schedule/service.js";
27
27
  import { type GitHubService } from "../services/github-service.js";
28
+ import type { ProviderUsageService } from "../services/quota-fetcher/service.js";
28
29
  import { generateBranchNameFromFirstAgentContext } from "./worktree-branch-name-generator.js";
29
30
  export declare function resolveWaitForFinishError(options: {
30
31
  status: "permission" | "error" | "idle";
@@ -74,6 +75,7 @@ export interface SessionOptions {
74
75
  tts: Resolvable<TextToSpeechProvider | null>;
75
76
  terminalManager: TerminalManager | null;
76
77
  providerSnapshotManager: ProviderSnapshotManager;
78
+ providerUsageService: ProviderUsageService;
77
79
  serviceProxy?: ServiceProxySubsystem;
78
80
  scriptRuntimeStore?: WorkspaceScriptRuntimeStore;
79
81
  workspaceSetupSnapshots?: Map<string, WorkspaceSetupSnapshot>;
@@ -180,6 +182,7 @@ export declare class Session {
180
182
  private clientActivity;
181
183
  private readonly terminalManager;
182
184
  private readonly providerSnapshotManager;
185
+ private readonly providerUsageService;
183
186
  private unsubscribeProviderSnapshotEvents;
184
187
  private readonly serviceProxy;
185
188
  private readonly scriptRuntimeStore;
@@ -273,6 +276,7 @@ export declare class Session {
273
276
  private matchesAgentFilter;
274
277
  private getAgentUpdateTargetId;
275
278
  private bufferOrEmitAgentUpdate;
279
+ private emitStoredAgentUpdate;
276
280
  private flushBootstrappedAgentUpdates;
277
281
  private findExactWorkspaceByDirectory;
278
282
  private resolveWorkspaceDirectory;
@@ -287,6 +291,7 @@ export declare class Session {
287
291
  private dispatchInboundMessage;
288
292
  private dispatchVoiceAndControlMessage;
289
293
  private dispatchAgentRewindMessage;
294
+ private dispatchAgentRelationshipMessage;
290
295
  private handleDictationStreamStart;
291
296
  private dispatchAgentLifecycleMessage;
292
297
  private dispatchAgentConfigMessage;
@@ -309,6 +314,7 @@ export declare class Session {
309
314
  private handleDeleteAgentRequest;
310
315
  private handleArchiveAgentRequest;
311
316
  private archiveAgentForClose;
317
+ private handleDetachAgentRequest;
312
318
  private handleCloseItemsRequest;
313
319
  private unarchiveAgentByHandle;
314
320
  private handleUpdateAgentRequest;
@@ -363,6 +369,7 @@ export declare class Session {
363
369
  private handleGetProvidersSnapshotRequest;
364
370
  private handleRefreshProvidersSnapshotRequest;
365
371
  private handleProviderDiagnosticRequest;
372
+ private handleProviderUsageListRequest;
366
373
  private assertSafeGitRef;
367
374
  private isPathWithinRoot;
368
375
  private generateCommitMessage;
@@ -462,6 +469,7 @@ export declare class Session {
462
469
  private listAgentPayloads;
463
470
  private resolveAgentIdentifier;
464
471
  private getAgentPayloadById;
472
+ private resolveDelegationRootWorkspaceId;
465
473
  private buildActiveProjectPlacementsByWorkspaceId;
466
474
  private collectFetchAgentsEntries;
467
475
  private listFetchAgentsEntries;
@@ -484,6 +492,8 @@ export declare class Session {
484
492
  private findOrCreateWorkspaceForDirectory;
485
493
  private resolveOrCreateWorkspaceIdForCreateAgent;
486
494
  private createWorkspaceForDirectory;
495
+ private findOrCreateProjectForDirectory;
496
+ private buildProjectDescriptor;
487
497
  private reclassifyOrUnarchiveWorkspaceForDirectory;
488
498
  private resolveProjectRecordForPlacement;
489
499
  private unarchiveOwningWorkspaceForAgent;
@@ -498,7 +508,7 @@ export declare class Session {
498
508
  private emitWorkspaceUpdatesForWorkspaceIds;
499
509
  private recordWorkspaceGitDescriptorState;
500
510
  private buildWorkspaceRemoveUpdatePayload;
501
- private resolveEmptyProjectForArchivedWorkspace;
511
+ private resolveProjectWithoutActiveWorkspacesForArchivedWorkspace;
502
512
  private emitWorkspaceUpdateForTerminalContribution;
503
513
  private emitWorkspaceUpdateForCwd;
504
514
  private handleFetchAgents;
@@ -513,6 +523,7 @@ export declare class Session {
513
523
  private handleWorkspaceCreateWorktree;
514
524
  private resolveWorktreeSourceCwd;
515
525
  private handleOpenProjectRequest;
526
+ private handleProjectAddRequest;
516
527
  private buildWorkspaceScriptPayloadSnapshot;
517
528
  private resolveWorkspaceScriptGitMetadata;
518
529
  private emitWorkspaceScriptStatusUpdate;
@@ -32,9 +32,10 @@ import { deriveProjectSlug } from "./workspace-git-metadata.js";
32
32
  import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
33
33
  import { getErrorMessage, getErrorMessageOr } from "@getpaseo/protocol/error-utils";
34
34
  import { getAgentStatusPriority } from "@getpaseo/protocol/agent-state-bucket";
35
+ import { getParentAgentIdFromLabels } from "@getpaseo/protocol/agent-labels";
35
36
  import { resolveSnapshotCwd } from "./agent/provider-snapshot-manager.js";
36
37
  import { createAgentCommand } from "./agent/create-agent/create.js";
37
- import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
38
+ import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, detachAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
38
39
  import { buildStoredAgentPayload, resolveEffectiveThinkingOptionId, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
39
40
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
40
41
  import { projectTimelineRows, selectProjectedTimelinePage, } from "./agent/timeline-projection.js";
@@ -342,7 +343,7 @@ export class Session {
342
343
  }
343
344
  },
344
345
  });
345
- const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, getTransportBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, filesystem, chatService, scheduleService, loopService, checkoutDiffManager, github, renameCurrentBranch, generateWorkspaceName, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
346
+ const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, getTransportBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, filesystem, chatService, scheduleService, loopService, checkoutDiffManager, github, renameCurrentBranch, generateWorkspaceName, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, providerUsageService, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
346
347
  this.clientId = clientId;
347
348
  this.appVersion = appVersion ?? null;
348
349
  this.clientCapabilities = parseClientCapabilities(clientCapabilities);
@@ -416,6 +417,7 @@ export class Session {
416
417
  logger: this.sessionLogger,
417
418
  });
418
419
  this.providerSnapshotManager = providerSnapshotManager;
420
+ this.providerUsageService = providerUsageService;
419
421
  this.serviceProxy = serviceProxy ?? null;
420
422
  this.scriptRuntimeStore = scriptRuntimeStore ?? null;
421
423
  this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map();
@@ -860,6 +862,39 @@ export class Session {
860
862
  payload,
861
863
  });
862
864
  }
865
+ async emitStoredAgentUpdate(record) {
866
+ const payload = this.buildStoredAgentPayload(record);
867
+ const subscription = this.agentUpdatesSubscription;
868
+ if (!subscription) {
869
+ return payload;
870
+ }
871
+ const project = payload.workspaceId
872
+ ? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
873
+ : null;
874
+ if (!project) {
875
+ this.bufferOrEmitAgentUpdate(subscription, {
876
+ kind: "remove",
877
+ agentId: payload.id,
878
+ });
879
+ return payload;
880
+ }
881
+ const matches = this.matchesAgentFilter({
882
+ agent: payload,
883
+ project,
884
+ filter: subscription.filter,
885
+ });
886
+ this.bufferOrEmitAgentUpdate(subscription, matches
887
+ ? {
888
+ kind: "upsert",
889
+ agent: payload,
890
+ project,
891
+ }
892
+ : {
893
+ kind: "remove",
894
+ agentId: payload.id,
895
+ });
896
+ return payload;
897
+ }
863
898
  flushBootstrappedAgentUpdates(options) {
864
899
  const subscription = this.agentUpdatesSubscription;
865
900
  if (!subscription || !subscription.isBootstrapping) {
@@ -1026,6 +1061,7 @@ export class Session {
1026
1061
  async dispatchInboundMessage(msg) {
1027
1062
  const promise = this.dispatchVoiceAndControlMessage(msg) ??
1028
1063
  this.dispatchAgentRewindMessage(msg) ??
1064
+ this.dispatchAgentRelationshipMessage(msg) ??
1029
1065
  this.dispatchAgentLifecycleMessage(msg) ??
1030
1066
  this.dispatchAgentConfigMessage(msg) ??
1031
1067
  this.dispatchCheckoutMessage(msg) ??
@@ -1094,6 +1130,14 @@ export class Session {
1094
1130
  return undefined;
1095
1131
  }
1096
1132
  }
1133
+ dispatchAgentRelationshipMessage(msg) {
1134
+ switch (msg.type) {
1135
+ case "agent.detach.request":
1136
+ return this.handleDetachAgentRequest(msg.agentId, msg.requestId);
1137
+ default:
1138
+ return undefined;
1139
+ }
1140
+ }
1097
1141
  async handleDictationStreamStart(msg) {
1098
1142
  const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
1099
1143
  if (unavailable) {
@@ -1350,6 +1394,8 @@ export class Session {
1350
1394
  return this.handleLegacyOpenInEditorRequest(msg);
1351
1395
  case "open_project_request":
1352
1396
  return this.handleOpenProjectRequest(msg);
1397
+ case "project.add.request":
1398
+ return this.handleProjectAddRequest(msg);
1353
1399
  case "archive_workspace_request":
1354
1400
  return this.handleArchiveWorkspaceRequest(msg);
1355
1401
  case "project.remove.request":
@@ -1389,6 +1435,8 @@ export class Session {
1389
1435
  return this.handleRefreshProvidersSnapshotRequest(msg);
1390
1436
  case "provider_diagnostic_request":
1391
1437
  return this.handleProviderDiagnosticRequest(msg);
1438
+ case "provider.usage.list.request":
1439
+ return this.handleProviderUsageListRequest(msg);
1392
1440
  default:
1393
1441
  return undefined;
1394
1442
  }
@@ -1580,39 +1628,60 @@ export class Session {
1580
1628
  logger: this.sessionLogger,
1581
1629
  }, agentId);
1582
1630
  if (this.agentUpdatesSubscription) {
1583
- const payload = this.buildStoredAgentPayload(archivedRecord);
1584
- const project = payload.workspaceId
1585
- ? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
1586
- : null;
1587
- if (project) {
1588
- const matches = this.matchesAgentFilter({
1589
- agent: payload,
1590
- project,
1591
- filter: this.agentUpdatesSubscription.filter,
1592
- });
1593
- this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, matches
1594
- ? {
1595
- kind: "upsert",
1596
- agent: payload,
1597
- project,
1598
- }
1599
- : {
1600
- kind: "remove",
1601
- agentId,
1602
- });
1603
- }
1604
- else {
1605
- this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
1606
- kind: "remove",
1607
- agentId,
1608
- });
1609
- }
1631
+ const payload = await this.emitStoredAgentUpdate(archivedRecord);
1610
1632
  if (payload.workspaceId) {
1611
1633
  await this.emitWorkspaceUpdateForWorkspaceId(payload.workspaceId);
1612
1634
  }
1613
1635
  }
1614
1636
  return { agentId, archivedAt };
1615
1637
  }
1638
+ async handleDetachAgentRequest(agentId, requestId) {
1639
+ this.sessionLogger.info({ agentId, requestId }, "Detaching agent from parent");
1640
+ try {
1641
+ const result = await detachAgentCommand({ agentManager: this.agentManager }, agentId);
1642
+ const affectedWorkspaceIds = new Set();
1643
+ if (!result.live) {
1644
+ const payload = await this.emitStoredAgentUpdate(result.record);
1645
+ if (payload.workspaceId) {
1646
+ affectedWorkspaceIds.add(payload.workspaceId);
1647
+ }
1648
+ }
1649
+ else if (result.record.workspaceId) {
1650
+ affectedWorkspaceIds.add(result.record.workspaceId);
1651
+ }
1652
+ if (result.previousParentAgentId) {
1653
+ const rootWorkspaceId = await this.resolveDelegationRootWorkspaceId(result.previousParentAgentId);
1654
+ if (rootWorkspaceId) {
1655
+ affectedWorkspaceIds.add(rootWorkspaceId);
1656
+ }
1657
+ }
1658
+ await this.emitWorkspaceUpdatesForWorkspaceIds(affectedWorkspaceIds, {
1659
+ skipReconcile: true,
1660
+ });
1661
+ this.emit({
1662
+ type: "agent.detach.response",
1663
+ payload: {
1664
+ requestId,
1665
+ agentId,
1666
+ accepted: true,
1667
+ error: null,
1668
+ },
1669
+ });
1670
+ }
1671
+ catch (error) {
1672
+ const message = getErrorMessageOr(error, "Failed to detach agent");
1673
+ this.sessionLogger.error({ err: error, agentId, requestId }, "Failed to detach agent");
1674
+ this.emit({
1675
+ type: "agent.detach.response",
1676
+ payload: {
1677
+ requestId,
1678
+ agentId,
1679
+ accepted: false,
1680
+ error: message,
1681
+ },
1682
+ });
1683
+ }
1684
+ }
1616
1685
  async handleCloseItemsRequest(msg) {
1617
1686
  const archiveResults = await Promise.allSettled(msg.agentIds.map((agentId) => this.archiveAgentForClose(agentId)));
1618
1687
  const agents = [];
@@ -3075,6 +3144,32 @@ export class Session {
3075
3144
  });
3076
3145
  }
3077
3146
  }
3147
+ async handleProviderUsageListRequest(msg) {
3148
+ try {
3149
+ const usage = await this.providerUsageService.listUsage();
3150
+ this.emit({
3151
+ type: "provider.usage.list.response",
3152
+ payload: {
3153
+ requestId: msg.requestId,
3154
+ fetchedAt: usage.fetchedAt,
3155
+ providers: usage.providers,
3156
+ },
3157
+ });
3158
+ }
3159
+ catch (error) {
3160
+ const err = error instanceof Error ? error : new Error(String(error));
3161
+ this.sessionLogger.error({ err }, "Failed to list provider usage");
3162
+ this.emit({
3163
+ type: "rpc_error",
3164
+ payload: {
3165
+ requestId: msg.requestId,
3166
+ requestType: msg.type,
3167
+ error: `Failed to list provider usage: ${err.message}`,
3168
+ code: "provider_usage_list_failed",
3169
+ },
3170
+ });
3171
+ }
3172
+ }
3078
3173
  assertSafeGitRef(ref, label) {
3079
3174
  if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
3080
3175
  throw new Error(`Invalid ${label}: ${ref}`);
@@ -3309,11 +3404,11 @@ export class Session {
3309
3404
  async handleSetAgentModeRequest(agentId, modeId, requestId) {
3310
3405
  this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request");
3311
3406
  try {
3312
- await setAgentModeCommand({ agentManager: this.agentManager }, { agentId, modeId });
3407
+ const result = await setAgentModeCommand({ agentManager: this.agentManager }, { agentId, modeId });
3313
3408
  this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request success");
3314
3409
  this.emit({
3315
3410
  type: "set_agent_mode_response",
3316
- payload: { requestId, agentId, accepted: true, error: null },
3411
+ payload: { requestId, agentId, accepted: true, error: null, notice: result.notice },
3317
3412
  });
3318
3413
  }
3319
3414
  catch (error) {
@@ -3405,11 +3500,11 @@ export class Session {
3405
3500
  async handleSetAgentThinkingRequest(agentId, thinkingOptionId, requestId) {
3406
3501
  this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request");
3407
3502
  try {
3408
- await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
3503
+ const notice = await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
3409
3504
  this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request success");
3410
3505
  this.emit({
3411
3506
  type: "set_agent_thinking_response",
3412
- payload: { requestId, agentId, accepted: true, error: null },
3507
+ payload: { requestId, agentId, accepted: true, error: null, notice },
3413
3508
  });
3414
3509
  }
3415
3510
  catch (error) {
@@ -4964,6 +5059,29 @@ export class Session {
4964
5059
  const payload = this.buildStoredAgentPayload(record);
4965
5060
  return this.isProviderVisibleToClient(payload.provider) ? payload : null;
4966
5061
  }
5062
+ async resolveDelegationRootWorkspaceId(agentId) {
5063
+ const seen = new Set();
5064
+ let currentAgentId = agentId;
5065
+ while (true) {
5066
+ if (seen.has(currentAgentId)) {
5067
+ return null;
5068
+ }
5069
+ seen.add(currentAgentId);
5070
+ const live = this.agentManager.getAgent(currentAgentId);
5071
+ const source = live ?? (await this.agentStorage.get(currentAgentId));
5072
+ if (!source) {
5073
+ return null;
5074
+ }
5075
+ if ("archivedAt" in source && source.archivedAt) {
5076
+ return null;
5077
+ }
5078
+ const parentAgentId = getParentAgentIdFromLabels(source.labels);
5079
+ if (!parentAgentId) {
5080
+ return source.workspaceId ?? null;
5081
+ }
5082
+ currentAgentId = parentAgentId;
5083
+ }
5084
+ }
4967
5085
  async buildActiveProjectPlacementsByWorkspaceId() {
4968
5086
  const [persistedWorkspaces, persistedProjects] = await Promise.all([
4969
5087
  this.workspaceRegistry.list(),
@@ -5371,6 +5489,26 @@ export class Session {
5371
5489
  await this.workspaceRegistry.upsert(workspaceRecord);
5372
5490
  return workspaceRecord;
5373
5491
  }
5492
+ async findOrCreateProjectForDirectory(cwd) {
5493
+ const normalizedCwd = resolve(cwd);
5494
+ const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
5495
+ const membership = classifyDirectoryForProjectMembership({ cwd: normalizedCwd, checkout });
5496
+ const projectRecord = await this.resolveProjectRecordForPlacement({
5497
+ membership,
5498
+ timestamp: new Date().toISOString(),
5499
+ });
5500
+ await this.projectRegistry.upsert(projectRecord);
5501
+ return projectRecord;
5502
+ }
5503
+ buildProjectDescriptor(project) {
5504
+ return {
5505
+ projectId: project.projectId,
5506
+ projectDisplayName: resolveProjectDisplayName(project),
5507
+ projectCustomName: project.customName ?? null,
5508
+ projectRootPath: project.rootPath,
5509
+ projectKind: project.kind,
5510
+ };
5511
+ }
5374
5512
  async reclassifyOrUnarchiveWorkspaceForDirectory(input) {
5375
5513
  const checkout = await this.workspaceGitService.getCheckout(input.cwd);
5376
5514
  const membership = classifyDirectoryForProjectMembership({ cwd: input.cwd, checkout });
@@ -5701,19 +5839,19 @@ export class Session {
5701
5839
  return {
5702
5840
  kind: "remove",
5703
5841
  id: workspaceId,
5704
- ...(await this.resolveEmptyProjectForArchivedWorkspace(workspaceId)),
5842
+ ...(await this.resolveProjectWithoutActiveWorkspacesForArchivedWorkspace(workspaceId)),
5705
5843
  };
5706
5844
  }
5707
- // When a workspace is archived its project may become empty. Resolve the
5708
- // now-empty project parent so the `remove` update can carry it, keeping the
5709
- // sidebar's empty project row in sync without a full re-hydration.
5710
- async resolveEmptyProjectForArchivedWorkspace(workspaceId) {
5845
+ // When a workspace is archived its project may have no active workspaces left.
5846
+ // Resolve that project parent so the `remove` update can carry it, keeping the
5847
+ // sidebar in sync without a full re-hydration.
5848
+ async resolveProjectWithoutActiveWorkspacesForArchivedWorkspace(workspaceId) {
5711
5849
  const archivedWorkspace = await this.workspaceRegistry.get(workspaceId);
5712
5850
  if (!archivedWorkspace) {
5713
5851
  return null;
5714
5852
  }
5715
- const emptyProject = (await this.workspaceDirectory.listEmptyProjects()).find((project) => project.projectId === archivedWorkspace.projectId);
5716
- return emptyProject ? { emptyProject } : null;
5853
+ const projectWithoutActiveWorkspaces = (await this.workspaceDirectory.listEmptyProjects()).find((project) => project.projectId === archivedWorkspace.projectId);
5854
+ return projectWithoutActiveWorkspaces ? { emptyProject: projectWithoutActiveWorkspaces } : null;
5717
5855
  }
5718
5856
  async emitWorkspaceUpdateForTerminalContribution(event) {
5719
5857
  // A terminal's activity contributes only to the workspace it carries. A
@@ -6159,6 +6297,58 @@ export class Session {
6159
6297
  });
6160
6298
  }
6161
6299
  }
6300
+ async handleProjectAddRequest(request) {
6301
+ const requestedCwd = request.cwd;
6302
+ const cwd = expandTilde(requestedCwd);
6303
+ const directoryExists = await this.filesystem.isDirectory(cwd).catch(() => false);
6304
+ if (!directoryExists) {
6305
+ this.sessionLogger.info({ requestedCwd, resolvedCwd: cwd, reason: "directory_not_found" }, "Add project rejected");
6306
+ this.emit({
6307
+ type: "project.add.response",
6308
+ payload: {
6309
+ requestId: request.requestId,
6310
+ project: null,
6311
+ error: `Directory not found: ${cwd}`,
6312
+ errorCode: "directory_not_found",
6313
+ },
6314
+ });
6315
+ return;
6316
+ }
6317
+ try {
6318
+ const projectsBefore = new Map();
6319
+ for (const project of await this.projectRegistry.list()) {
6320
+ projectsBefore.set(project.projectId, project);
6321
+ }
6322
+ const project = await this.findOrCreateProjectForDirectory(cwd);
6323
+ this.sessionLogger.info({
6324
+ requestedCwd,
6325
+ resolvedCwd: cwd,
6326
+ projectId: project.projectId,
6327
+ projectKind: project.kind,
6328
+ projectTransition: describeRegistryTransition(projectsBefore.get(project.projectId) ?? null),
6329
+ }, "Project added");
6330
+ this.emit({
6331
+ type: "project.add.response",
6332
+ payload: {
6333
+ requestId: request.requestId,
6334
+ project: this.buildProjectDescriptor(project),
6335
+ error: null,
6336
+ },
6337
+ });
6338
+ }
6339
+ catch (error) {
6340
+ const message = error instanceof Error ? error.message : "Failed to add project";
6341
+ this.sessionLogger.error({ err: error, cwd }, "Failed to add project");
6342
+ this.emit({
6343
+ type: "project.add.response",
6344
+ payload: {
6345
+ requestId: request.requestId,
6346
+ project: null,
6347
+ error: message,
6348
+ },
6349
+ });
6350
+ }
6351
+ }
6162
6352
  buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory) {
6163
6353
  if (!this.serviceProxy || !this.scriptRuntimeStore) {
6164
6354
  return [];
@@ -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,
@@ -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,
@@ -809,8 +814,14 @@ export class VoiceAssistantWebSocketServer {
809
814
  workspaceMultiplicity: true,
810
815
  // COMPAT(projectRemove): added in v0.1.97, drop the gate when floor >= v0.1.97.
811
816
  projectRemove: true,
817
+ // COMPAT(projectAdd): added in v0.1.97, drop the gate when floor >= v0.1.97.
818
+ projectAdd: true,
812
819
  // COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
813
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,
814
825
  },
815
826
  };
816
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
@@ -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