@mduan/paseo-cli 0.11.0-fork.34 → 0.11.0-fork.35

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 (51) hide show
  1. package/node_modules/@getpaseo/client/package.json +3 -3
  2. package/node_modules/@getpaseo/highlight/package.json +1 -1
  3. package/node_modules/@getpaseo/plugin/package.json +3 -3
  4. package/node_modules/@getpaseo/protocol/dist/generated/validation/ws-outbound.aot.js +190 -0
  5. package/node_modules/@getpaseo/protocol/dist/messages.d.ts +381 -0
  6. package/node_modules/@getpaseo/protocol/dist/messages.js +9 -0
  7. package/node_modules/@getpaseo/protocol/dist/validation/ws-outbound-schema-metadata.d.ts +90 -0
  8. package/node_modules/@getpaseo/protocol/package.json +1 -1
  9. package/node_modules/@getpaseo/relay/package.json +1 -1
  10. package/node_modules/@getpaseo/server/dist/server/builtin-plugins/package.json +1 -1
  11. package/node_modules/@getpaseo/server/dist/server/server/bootstrap.js +7 -0
  12. package/node_modules/@getpaseo/server/dist/server/server/session.d.ts +7 -0
  13. package/node_modules/@getpaseo/server/dist/server/server/session.js +37 -0
  14. package/node_modules/@getpaseo/server/dist/server/server/websocket-server.js +2 -0
  15. package/node_modules/@getpaseo/server/dist/server/server/workspace-git-service.d.ts +8 -2
  16. package/node_modules/@getpaseo/server/dist/server/server/workspace-git-service.js +55 -14
  17. package/node_modules/@getpaseo/server/dist/server/server/workspace-reconciliation-service.js +6 -2
  18. package/node_modules/@getpaseo/server/dist/server/server/workspace-registry.d.ts +2 -0
  19. package/node_modules/@getpaseo/server/dist/server/server/workspace-registry.js +4 -0
  20. package/node_modules/@getpaseo/server/dist/server/server/workspace-response-recorder.d.ts +13 -0
  21. package/node_modules/@getpaseo/server/dist/server/server/workspace-response-recorder.js +21 -0
  22. package/node_modules/@getpaseo/server/dist/server/utils/git-process-scheduler.d.ts +5 -3
  23. package/node_modules/@getpaseo/server/dist/server/utils/git-process-scheduler.js +26 -23
  24. package/node_modules/@getpaseo/server/dist/server/utils/run-git-command.d.ts +6 -1
  25. package/node_modules/@getpaseo/server/dist/server/utils/run-git-command.js +12 -9
  26. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-ffe0cd60080175f6fb2dec3200390aec.js → desktop-attachment-bridge-e52f9cb2de89e698c51c18241d12cb99.js} +1 -1
  27. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-ffe0cd60080175f6fb2dec3200390aec.js.br → desktop-attachment-bridge-e52f9cb2de89e698c51c18241d12cb99.js.br} +0 -0
  28. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e52f9cb2de89e698c51c18241d12cb99.js.gz +0 -0
  29. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-4d76abbc28cb391667944e7e96595e46.js → desktop-attachment-store-8cb998792f7a176d7612229dafb2c2d3.js} +1 -1
  30. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-4d76abbc28cb391667944e7e96595e46.js.br → desktop-attachment-store-8cb998792f7a176d7612229dafb2c2d3.js.br} +0 -0
  31. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-8cb998792f7a176d7612229dafb2c2d3.js.gz +0 -0
  32. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{index-52db3c28898b127e24f9b06f05f60336.js → index-c5f0ae884c9004e9301096d186baee98.js} +963 -961
  33. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-c5f0ae884c9004e9301096d186baee98.js.br +0 -0
  34. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{index-52db3c28898b127e24f9b06f05f60336.js.gz → index-c5f0ae884c9004e9301096d186baee98.js.gz} +0 -0
  35. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-9b53f73c1bb6949c2c9ef911ef57ab87.js → indexeddb-attachment-store-cf65163fc7d8cd4c646c36e83771e937.js} +1 -1
  36. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-cf65163fc7d8cd4c646c36e83771e937.js.br +0 -0
  37. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-9b53f73c1bb6949c2c9ef911ef57ab87.js.gz → indexeddb-attachment-store-cf65163fc7d8cd4c646c36e83771e937.js.gz} +0 -0
  38. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-ccf24f3ad53e8871f63d8e9c5417c2d7.js → native-file-attachment-store-da2a50627d6e788990a7f324838c24d2.js} +3 -3
  39. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-da2a50627d6e788990a7f324838c24d2.js.br +0 -0
  40. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-da2a50627d6e788990a7f324838c24d2.js.gz +0 -0
  41. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html +1 -1
  42. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.br +0 -0
  43. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.gz +0 -0
  44. package/node_modules/@getpaseo/server/package.json +6 -6
  45. package/package.json +7 -7
  46. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-ffe0cd60080175f6fb2dec3200390aec.js.gz +0 -0
  47. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-4d76abbc28cb391667944e7e96595e46.js.gz +0 -0
  48. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-52db3c28898b127e24f9b06f05f60336.js.br +0 -0
  49. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-9b53f73c1bb6949c2c9ef911ef57ab87.js.br +0 -0
  50. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-ccf24f3ad53e8871f63d8e9c5417c2d7.js.br +0 -0
  51. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-ccf24f3ad53e8871f63d8e9c5417c2d7.js.gz +0 -0
@@ -10,7 +10,7 @@ import { createForgeService } from "../services/forge-registry.js";
10
10
  import { createForgeResolver, } from "../services/forge-resolver.js";
11
11
  import { parseGitRevParsePath } from "../utils/git-rev-parse-path.js";
12
12
  import { createRealpathAwarePathMatcher, getRealpathAwareRelativePath, isPathInsideRoot, isRealpathInsideRoot, looksLikeDefiniteWindowsPath, } from "../utils/path.js";
13
- import { createRunGitCommand, runGitCommand, } from "../utils/run-git-command.js";
13
+ import { createRunGitCommand, runGitCommand, runWithGitCommandPriority, } from "../utils/run-git-command.js";
14
14
  import { branchNameFromRef } from "../utils/worktree-metadata.js";
15
15
  import { listPaseoWorktrees } from "../utils/worktree.js";
16
16
  import { READ_ONLY_GIT_ENV } from "./checkout-git-utils.js";
@@ -86,6 +86,16 @@ export function getWorkspaceGitObservationReensurePhaseMs(cwd) {
86
86
  }
87
87
  // Kept for local diagnostic fixtures that only use the stable phase calculation.
88
88
  export const getWorkspaceGitSelfHealPhaseMs = getWorkspaceGitObservationReensurePhaseMs;
89
+ // Demand refreshes keep the caller's Git lane; watcher and poll refreshes run in background.
90
+ var WorkspaceGitRefreshOrigin;
91
+ (function (WorkspaceGitRefreshOrigin) {
92
+ WorkspaceGitRefreshOrigin["Demand"] = "demand";
93
+ WorkspaceGitRefreshOrigin["Observation"] = "observation";
94
+ })(WorkspaceGitRefreshOrigin || (WorkspaceGitRefreshOrigin = {}));
95
+ // A caller waiting on a merged refresh keeps it out of the background lane.
96
+ function mergeRefreshOrigins(pending, request) {
97
+ return pending === WorkspaceGitRefreshOrigin.Demand ? pending : request;
98
+ }
89
99
  class WorkspaceGitServiceDisposedError extends Error {
90
100
  constructor() {
91
101
  super("WorkspaceGitService is disposed");
@@ -255,14 +265,17 @@ export class WorkspaceGitServiceImpl {
255
265
  }
256
266
  return this.requestWorkspaceSnapshot(target, request);
257
267
  }
258
- async getCheckout(cwd) {
268
+ async getCheckout(cwd, options) {
259
269
  this.assertNotDisposed();
260
270
  const normalizedCwd = resolve(cwd);
261
- const status = await this.deps.getCheckoutStatus(normalizedCwd, {
271
+ const readStatus = () => this.deps.getCheckoutStatus(normalizedCwd, {
262
272
  paseoHome: this.paseoHome,
263
273
  worktreesRoot: this.worktreesRoot,
264
274
  logger: this.logger,
265
275
  });
276
+ const status = await (options?.priority
277
+ ? runWithGitCommandPriority(options.priority, readStatus)
278
+ : readStatus());
266
279
  if (!status.isGit) {
267
280
  return checkoutLiteFromGitSnapshot(normalizedCwd, {
268
281
  isGit: false,
@@ -407,6 +420,7 @@ export class WorkspaceGitServiceImpl {
407
420
  cwd = resolve(cwd);
408
421
  const target = this.ensureWorkspaceTarget(cwd);
409
422
  await this.refreshWorkspaceTarget(target, {
423
+ origin: WorkspaceGitRefreshOrigin.Demand,
410
424
  force: false,
411
425
  refreshStructure: true,
412
426
  refreshWorktree: true,
@@ -435,7 +449,10 @@ export class WorkspaceGitServiceImpl {
435
449
  cwd = resolve(cwd);
436
450
  const target = this.workspaceTargets.get(cwd);
437
451
  if (target) {
438
- this.scheduleWorkspaceRefresh(target, { queueIfBusy: false });
452
+ this.scheduleWorkspaceRefresh(target, {
453
+ origin: WorkspaceGitRefreshOrigin.Demand,
454
+ queueIfBusy: false,
455
+ });
439
456
  }
440
457
  }
441
458
  onWorkspaceStateMayHaveChanged(cwd) {
@@ -447,6 +464,7 @@ export class WorkspaceGitServiceImpl {
447
464
  }
448
465
  this.invalidateForge(normalizedCwd);
449
466
  this.scheduleWorkspaceRefresh(target, {
467
+ origin: WorkspaceGitRefreshOrigin.Demand,
450
468
  force: true,
451
469
  includeForge: true,
452
470
  reason: "external-state-change",
@@ -632,6 +650,7 @@ export class WorkspaceGitServiceImpl {
632
650
  return;
633
651
  }
634
652
  void this.refreshWorkspaceTarget(target, {
653
+ origin: WorkspaceGitRefreshOrigin.Demand,
635
654
  force: false,
636
655
  refreshStructure: true,
637
656
  refreshWorktree: true,
@@ -958,6 +977,7 @@ export class WorkspaceGitServiceImpl {
958
977
  return false;
959
978
  }
960
979
  const changed = await this.refreshDegradedPollTarget(workspaceTarget, {
980
+ origin: WorkspaceGitRefreshOrigin.Observation,
961
981
  force: false,
962
982
  refreshStructure: target.repoRoot === null,
963
983
  refreshWorktree: true,
@@ -1383,7 +1403,11 @@ export class WorkspaceGitServiceImpl {
1383
1403
  for (const workspaceKey of target.workspaceKeys) {
1384
1404
  const workspaceTarget = this.workspaceTargets.get(workspaceKey);
1385
1405
  if (workspaceTarget) {
1386
- this.scheduleWorkspaceRefresh(workspaceTarget, { scope: "worktree", reason });
1406
+ this.scheduleWorkspaceRefresh(workspaceTarget, {
1407
+ origin: WorkspaceGitRefreshOrigin.Observation,
1408
+ scope: "worktree",
1409
+ reason,
1410
+ });
1387
1411
  }
1388
1412
  }
1389
1413
  }
@@ -1818,6 +1842,7 @@ export class WorkspaceGitServiceImpl {
1818
1842
  }
1819
1843
  }
1820
1844
  this.scheduleWorkspaceRefresh(workspaceTarget, {
1845
+ origin: WorkspaceGitRefreshOrigin.Observation,
1821
1846
  scope,
1822
1847
  emitUnchanged: refresh.refreshBase,
1823
1848
  reason,
@@ -1856,6 +1881,7 @@ export class WorkspaceGitServiceImpl {
1856
1881
  workingTreeTargets.add(workingTreeTarget);
1857
1882
  }
1858
1883
  return await this.refreshDegradedPollTarget(workspaceTarget, {
1884
+ origin: WorkspaceGitRefreshOrigin.Observation,
1859
1885
  force: false,
1860
1886
  refreshStructure: true,
1861
1887
  refreshWorktree: true,
@@ -2141,6 +2167,8 @@ export class WorkspaceGitServiceImpl {
2141
2167
  if (request.queueIfBusy || addsWork) {
2142
2168
  target.refreshState.queued = this.mergeRefreshRequests(target.refreshState.queued, request);
2143
2169
  }
2170
+ // The caller now waits on this refresh — promote its remaining Git work.
2171
+ active.origin = mergeRefreshOrigins(active.origin, request.origin);
2144
2172
  return target.refreshState.promise;
2145
2173
  }
2146
2174
  if (!request.force && !request.queueIfBusy && this.shouldThrottleNonForcedRefresh(target)) {
@@ -2166,6 +2194,7 @@ export class WorkspaceGitServiceImpl {
2166
2194
  }
2167
2195
  const force = options?.force === true;
2168
2196
  return {
2197
+ origin: WorkspaceGitRefreshOrigin.Demand,
2169
2198
  force,
2170
2199
  refreshStructure: options?.refreshGit !== false,
2171
2200
  refreshWorktree: options?.refreshGit !== false,
@@ -2183,17 +2212,18 @@ export class WorkspaceGitServiceImpl {
2183
2212
  return this.deps.now().getTime() - target.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS;
2184
2213
  }
2185
2214
  buildScheduledRefreshRequest(options) {
2186
- const scope = options?.scope;
2215
+ const scope = options.scope;
2187
2216
  return {
2188
- force: options?.force === true,
2217
+ origin: options.origin,
2218
+ force: options.force === true,
2189
2219
  refreshStructure: scope !== "worktree" && scope !== "refs",
2190
2220
  refreshWorktree: scope !== "structure" && scope !== "refs",
2191
- includeForge: options?.includeForge ?? false,
2192
- emitUnchanged: options?.emitUnchanged,
2193
- reason: options?.reason ?? "watch",
2221
+ includeForge: options.includeForge ?? false,
2222
+ emitUnchanged: options.emitUnchanged,
2223
+ reason: options.reason ?? "watch",
2194
2224
  notify: true,
2195
- queueIfBusy: options?.queueIfBusy ?? true,
2196
- movedRemoteRefs: new Set(options?.movedRemoteRefs),
2225
+ queueIfBusy: options.queueIfBusy ?? true,
2226
+ movedRemoteRefs: new Set(options.movedRemoteRefs),
2197
2227
  };
2198
2228
  }
2199
2229
  mergeRefreshRequests(pending, request) {
@@ -2208,6 +2238,7 @@ export class WorkspaceGitServiceImpl {
2208
2238
  const upgradesEmit = request.emitUnchanged === true && pending.emitUnchanged !== true;
2209
2239
  const { merged: movedRemoteRefs, added: upgradesMovedRefs } = mergeSets(pending.movedRemoteRefs, request.movedRemoteRefs);
2210
2240
  return {
2241
+ origin: mergeRefreshOrigins(pending.origin, request.origin),
2211
2242
  force,
2212
2243
  refreshStructure: pending.refreshStructure || request.refreshStructure,
2213
2244
  refreshWorktree: pending.refreshWorktree || request.refreshWorktree,
@@ -2240,7 +2271,7 @@ export class WorkspaceGitServiceImpl {
2240
2271
  });
2241
2272
  }
2242
2273
  try {
2243
- const runRefreshGitCommand = createRunGitCommand(`workspace-refresh:${request.reason}`);
2274
+ const runRefreshGitCommand = this.createRefreshGitCommand(request);
2244
2275
  const admittedSnapshot = await this.workspaceRefreshLimit(() => {
2245
2276
  if (target.closed || this.workspaceTargets.get(target.cwd) !== target) {
2246
2277
  return null;
@@ -2276,6 +2307,16 @@ export class WorkspaceGitServiceImpl {
2276
2307
  }
2277
2308
  return snapshot;
2278
2309
  }
2310
+ // Origin is read per command so a joining demand caller promotes the rest of the refresh.
2311
+ // Demand uses the ambient lane so a "high" user action is not demoted.
2312
+ createRefreshGitCommand(request) {
2313
+ const provenance = `workspace-refresh:${request.reason}`;
2314
+ const runDemand = createRunGitCommand(provenance);
2315
+ const runObservation = createRunGitCommand(provenance, { priority: "background" });
2316
+ return (args, options) => request.origin === WorkspaceGitRefreshOrigin.Observation
2317
+ ? runObservation(args, options)
2318
+ : runDemand(args, options);
2319
+ }
2279
2320
  async refreshSnapshot(target, request, runRefreshGitCommand) {
2280
2321
  let facts = target.latestFacts;
2281
2322
  if (request.movedRemoteRefs.size > 0 && !request.refreshStructure && !request.refreshWorktree) {
@@ -2531,7 +2572,7 @@ export class WorkspaceGitServiceImpl {
2531
2572
  eventsBeforeFetchSnapshot.push(...events);
2532
2573
  }
2533
2574
  },
2534
- }, createRunGitCommand("background-fetch"));
2575
+ }, createRunGitCommand("background-fetch", { priority: "background" }));
2535
2576
  }
2536
2577
  catch (error) {
2537
2578
  this.logger.warn({ err: error, repoGitRoot: target.repoGitRoot, cwd: target.cwd }, "Background git fetch failed");
@@ -1,10 +1,13 @@
1
1
  import { statSync, watch as watchPath } from "node:fs";
2
+ import pLimit from "p-limit";
2
3
  import { areEquivalentPaths } from "../utils/path.js";
3
4
  import { deriveProjectKind, reconcileWorkspacePlacement, } from "./workspace-registry-model.js";
4
5
  import { workspaceIdsForProjects } from "./workspace-directory.js";
5
6
  import { deriveProjectKey } from "./project-key.js";
6
7
  const DEFAULT_RESCAN_INTERVAL_MS = 5 * 60000;
7
8
  const DEFAULT_DEBOUNCE_MS = 100;
9
+ // ~15 Git commands per read — unbounded sibling reads queued 1,000+ commands per rescan.
10
+ const CHECKOUT_READ_CONCURRENCY = 4;
8
11
  const systemClock = {
9
12
  setTimeout: (callback, delayMs) => setTimeout(() => void callback(), delayMs),
10
13
  clearTimeout: (timer) => clearTimeout(timer),
@@ -169,11 +172,12 @@ export class WorkspaceReconciliationService {
169
172
  }
170
173
  async reconcileGitMetadataForProjects(projectsToReconcile, workspacesByProject, changes) {
171
174
  const checkoutReads = [];
175
+ const checkoutReadLimit = pLimit(CHECKOUT_READ_CONCURRENCY);
172
176
  const readCheckout = (cwd) => {
173
177
  const existing = checkoutReads.find((read) => areEquivalentPaths(read.cwd, cwd));
174
178
  if (existing)
175
179
  return existing.checkout;
176
- const checkout = this.readCheckout(cwd);
180
+ const checkout = checkoutReadLimit(() => this.readCheckout(cwd));
177
181
  checkoutReads.push({ cwd, checkout });
178
182
  return checkout;
179
183
  };
@@ -367,7 +371,7 @@ export class WorkspaceReconciliationService {
367
371
  mainRepoRoot: null,
368
372
  };
369
373
  }
370
- return this.workspaceGitService.getCheckout(cwd);
374
+ return this.workspaceGitService.getCheckout(cwd, { priority: "background" });
371
375
  }
372
376
  inspectDirectory(targetPath) {
373
377
  try {
@@ -58,6 +58,8 @@ declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
58
58
  color: z.ZodString;
59
59
  }, z.core.$strip>>;
60
60
  }, z.core.$strip>>>;
61
+ lastHumanPromptAt: z.ZodOptional<z.ZodString>;
62
+ lastAgentResponseAt: z.ZodOptional<z.ZodString>;
61
63
  untrustedSource: z.ZodOptional<z.ZodObject<{
62
64
  kind: z.ZodLiteral<"change_request">;
63
65
  forge: z.ZodString;
@@ -98,6 +98,10 @@ const PersistedWorkspaceRecordSchema = z.object({
98
98
  .transform((value) => value ?? null),
99
99
  labels: z.array(z.string()).optional(),
100
100
  linearIssues: z.array(LinearIssueSchema).optional(),
101
+ // Last client-sent prompt; automated prompts never set it — see `recordHumanPrompt`.
102
+ lastHumanPromptAt: z.string().optional(),
103
+ // Last completed agent turn, whoever prompted it — see `attachWorkspaceResponseRecorder`.
104
+ lastAgentResponseAt: z.string().optional(),
101
105
  untrustedSource: UntrustedWorkspaceSourceSchema.optional(),
102
106
  });
103
107
  class FileBackedRegistry {
@@ -0,0 +1,13 @@
1
+ import type { Logger } from "pino";
2
+ import type { AgentManager } from "./agent/agent-manager.js";
3
+ import type { WorkspaceRegistry } from "./workspace-registry.js";
4
+ /**
5
+ * Stamps `lastAgentResponseAt` on the owning workspace each time a live agent turn completes,
6
+ * whoever prompted it. History replays never reach subscribers, so restarts don't move it.
7
+ */
8
+ export declare function attachWorkspaceResponseRecorder(input: {
9
+ logger: Logger;
10
+ agentManager: Pick<AgentManager, "subscribe" | "getAgent">;
11
+ workspaceRegistry: Pick<WorkspaceRegistry, "update">;
12
+ }): () => void;
13
+ //# sourceMappingURL=workspace-response-recorder.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Stamps `lastAgentResponseAt` on the owning workspace each time a live agent turn completes,
3
+ * whoever prompted it. History replays never reach subscribers, so restarts don't move it.
4
+ */
5
+ export function attachWorkspaceResponseRecorder(input) {
6
+ const { logger, agentManager, workspaceRegistry } = input;
7
+ return agentManager.subscribe((event) => {
8
+ if (event.type !== "agent_stream" || event.event.type !== "turn_completed")
9
+ return;
10
+ const agent = agentManager.getAgent(event.agentId);
11
+ if (!agent?.workspaceId || agent.internal)
12
+ return;
13
+ const lastAgentResponseAt = event.timestamp ?? new Date().toISOString();
14
+ void workspaceRegistry
15
+ .update(agent.workspaceId, (existing) => ({ ...existing, lastAgentResponseAt }))
16
+ .catch((error) => {
17
+ logger.warn({ err: error, agentId: event.agentId, workspaceId: agent.workspaceId }, "Failed to record agent response time");
18
+ });
19
+ }, { replayState: false });
20
+ }
21
+ //# sourceMappingURL=workspace-response-recorder.js.map
@@ -7,7 +7,7 @@ export interface ScheduledGitProcess<T> {
7
7
  result: Promise<T>;
8
8
  exited: Promise<void>;
9
9
  }
10
- export type GitProcessPriority = "normal" | "high";
10
+ export type GitProcessPriority = "high" | "normal" | "background";
11
11
  export declare function resolveGitProcessPolicy(input: {
12
12
  env: NodeJS.ProcessEnv;
13
13
  persisted?: Partial<GitProcessPolicy>;
@@ -15,9 +15,9 @@ export declare function resolveGitProcessPolicy(input: {
15
15
  export declare class GitProcessScheduler {
16
16
  readonly policy: GitProcessPolicy;
17
17
  private readonly startThrottled;
18
- private readonly highPriorityQueue;
19
- private readonly normalPriorityQueue;
18
+ private readonly queues;
20
19
  private admitted;
20
+ private reservedStarts;
21
21
  private running;
22
22
  private waiting;
23
23
  private drainScheduled;
@@ -27,8 +27,10 @@ export declare class GitProcessScheduler {
27
27
  run<T>(start: () => ScheduledGitProcess<T>, options?: {
28
28
  priority?: GitProcessPriority;
29
29
  }): Promise<T>;
30
+ private canReserveStart;
30
31
  private scheduleDrain;
31
32
  private drainOne;
33
+ private startNext;
32
34
  private execute;
33
35
  }
34
36
  //# sourceMappingURL=git-process-scheduler.d.ts.map
@@ -23,12 +23,18 @@ export function resolveGitProcessPolicy(input) {
23
23
  DEFAULT_GIT_PROCESS_POLICY.maxProcessConcurrency,
24
24
  };
25
25
  }
26
+ // Admission reserves a slot and a start-rate token; the job is chosen when the token fires, so
27
+ // work submitted while background reservations wait on the rate limit takes the next start.
26
28
  export class GitProcessScheduler {
27
29
  constructor(policy) {
28
30
  this.policy = policy;
29
- this.highPriorityQueue = [];
30
- this.normalPriorityQueue = [];
31
+ this.queues = {
32
+ high: [],
33
+ normal: [],
34
+ background: [],
35
+ };
31
36
  this.admitted = 0;
37
+ this.reservedStarts = 0;
32
38
  this.running = 0;
33
39
  this.waiting = 0;
34
40
  this.drainScheduled = false;
@@ -37,7 +43,7 @@ export class GitProcessScheduler {
37
43
  interval: 1000,
38
44
  strict: true,
39
45
  });
40
- this.startThrottled = throttle((start) => start());
46
+ this.startThrottled = throttle(() => this.startNext());
41
47
  }
42
48
  get activeCount() {
43
49
  return this.running;
@@ -48,16 +54,7 @@ export class GitProcessScheduler {
48
54
  run(start, options) {
49
55
  this.waiting += 1;
50
56
  return new Promise((resolve, reject) => {
51
- const admit = () => {
52
- void this.startThrottled(() => this.execute(start, resolve, reject)).catch((error) => {
53
- this.waiting = Math.max(0, this.waiting - 1);
54
- this.admitted = Math.max(0, this.admitted - 1);
55
- reject(error);
56
- this.scheduleDrain();
57
- });
58
- };
59
- const queue = options?.priority === "high" ? this.highPriorityQueue : this.normalPriorityQueue;
60
- queue.push(admit);
57
+ this.queues[options?.priority ?? "normal"].push(() => this.execute(start, resolve, reject));
61
58
  if (this.admitted === 0 && !this.drainScheduled) {
62
59
  this.drainOne();
63
60
  }
@@ -66,11 +63,12 @@ export class GitProcessScheduler {
66
63
  }
67
64
  });
68
65
  }
66
+ // Every reservation is backed by a queued job: jobs leave the queues only in startNext.
67
+ canReserveStart() {
68
+ return this.admitted < this.policy.maxProcessConcurrency && this.waiting > this.reservedStarts;
69
+ }
69
70
  scheduleDrain() {
70
- if (this.drainScheduled || this.admitted >= this.policy.maxProcessConcurrency) {
71
- return;
72
- }
73
- if (this.highPriorityQueue.length === 0 && this.normalPriorityQueue.length === 0) {
71
+ if (this.drainScheduled || !this.canReserveStart()) {
74
72
  return;
75
73
  }
76
74
  this.drainScheduled = true;
@@ -78,19 +76,24 @@ export class GitProcessScheduler {
78
76
  }
79
77
  drainOne() {
80
78
  this.drainScheduled = false;
81
- if (this.admitted >= this.policy.maxProcessConcurrency) {
82
- return;
83
- }
84
- const admit = this.highPriorityQueue.shift() ?? this.normalPriorityQueue.shift();
85
- if (!admit) {
79
+ if (!this.canReserveStart()) {
86
80
  return;
87
81
  }
88
82
  this.admitted += 1;
89
- admit();
83
+ this.reservedStarts += 1;
84
+ void this.startThrottled();
90
85
  // Spawning can be synchronous and expensive on a throttled host. Let timers,
91
86
  // sockets, and child exits run before admitting another Git process.
92
87
  this.scheduleDrain();
93
88
  }
89
+ startNext() {
90
+ this.reservedStarts -= 1;
91
+ const next = this.queues.high.shift() ?? this.queues.normal.shift() ?? this.queues.background.shift();
92
+ if (!next) {
93
+ throw new Error("Git process start fired without a queued process");
94
+ }
95
+ return next();
96
+ }
94
97
  async execute(start, resolve, reject) {
95
98
  this.waiting = Math.max(0, this.waiting - 1);
96
99
  this.running += 1;
@@ -22,7 +22,11 @@ export interface GitCommandResult<Output = string> {
22
22
  signal: NodeJS.Signals | null;
23
23
  }
24
24
  export type RunGitCommand = (args: string[], options: GitCommandOptions) => Promise<GitCommandResult>;
25
- export declare function createRunGitCommand(provenance: string): RunGitCommand;
25
+ export interface GitCommandRunnerOptions {
26
+ /** Overrides the ambient `runWithGitCommandPriority` lane for every command this runner starts. */
27
+ priority?: GitProcessPriority;
28
+ }
29
+ export declare function createRunGitCommand(provenance: string, runnerOptions?: GitCommandRunnerOptions): RunGitCommand;
26
30
  export interface GitCommandMetric {
27
31
  args: string[];
28
32
  cwd: string;
@@ -47,6 +51,7 @@ export interface GitCommandMetricsSnapshot {
47
51
  export interface GitCommandSubmissionMetric {
48
52
  args: string[];
49
53
  cwd: string;
54
+ priority: GitProcessPriority;
50
55
  }
51
56
  export declare function startGitCommandMetrics(): void;
52
57
  export declare function stopGitCommandMetrics(): GitCommandMetricsSnapshot;
@@ -23,8 +23,12 @@ export function configureGitProcessPolicy(policy) {
23
23
  gitProcessScheduler = new GitProcessScheduler(policy);
24
24
  gitRuntimeMetrics = createGitCommandRuntimeMetricsWindow(policy);
25
25
  }
26
- export function createRunGitCommand(provenance) {
27
- return (args, options) => runGitCommandWithProvenance(args, options, provenance);
26
+ export function createRunGitCommand(provenance, runnerOptions) {
27
+ const priority = runnerOptions?.priority;
28
+ if (!priority) {
29
+ return (args, options) => runGitCommandWithProvenance(args, options, provenance);
30
+ }
31
+ return (args, options) => runWithGitCommandPriority(priority, () => runGitCommandWithProvenance(args, options, provenance));
28
32
  }
29
33
  let gitCommandMetricsState = null;
30
34
  export function startGitCommandMetrics() {
@@ -92,8 +96,8 @@ function snapshotGitCommandMetricsState(state) {
92
96
  return {
93
97
  commands: [...state.commands],
94
98
  submissions: state.submissions.map((submission) => ({
99
+ ...submission,
95
100
  args: [...submission.args],
96
- cwd: submission.cwd,
97
101
  })),
98
102
  submitted: state.submitted,
99
103
  started: state.started,
@@ -111,12 +115,12 @@ export function snapshotGitCommandRuntimeMetrics() {
111
115
  pending: gitProcessScheduler.pendingCount,
112
116
  });
113
117
  }
114
- function submitGitCommandMetric(args, cwd) {
118
+ function submitGitCommandMetric(submission) {
115
119
  const state = gitCommandMetricsState;
116
120
  if (!state) {
117
121
  return null;
118
122
  }
119
- state.submissions.push({ args: [...args], cwd });
123
+ state.submissions.push({ ...submission, args: [...submission.args] });
120
124
  state.submitted += 1;
121
125
  state.lastSubmittedAtMs = Date.now();
122
126
  return state;
@@ -157,7 +161,8 @@ export function runGitCommandBytes(args, options) {
157
161
  return executeGitCommand(args, options, (output) => output);
158
162
  }
159
163
  function executeGitCommand(args, options, decode, provenance) {
160
- const metricsState = submitGitCommandMetric(args, options.cwd);
164
+ const priority = gitCommandPriority.getStore() ?? "normal";
165
+ const metricsState = submitGitCommandMetric({ args, cwd: options.cwd, priority });
161
166
  const commandTrace = submitGitCommandTrace(args, options.cwd, {
162
167
  active: gitProcessScheduler.activeCount,
163
168
  pending: gitProcessScheduler.pendingCount,
@@ -394,9 +399,7 @@ function executeGitCommand(args, options, decode, provenance) {
394
399
  });
395
400
  return { result: resultPromise, exited };
396
401
  };
397
- const promise = gitProcessScheduler.run(startCommand, {
398
- priority: gitCommandPriority.getStore() ?? "normal",
399
- });
402
+ const promise = gitProcessScheduler.run(startCommand, { priority });
400
403
  gitRuntimeMetrics.observeLimiter(gitProcessScheduler.activeCount, gitProcessScheduler.pendingCount);
401
404
  return promise;
402
405
  }
@@ -1 +1 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},5198,[4426,4427]);
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},5200,[4426,4427]);
@@ -1 +1 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},5197,[4301]);
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},5199,[4301]);