dsh-cursor-dev 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/tool.js CHANGED
@@ -22,6 +22,7 @@ import { delegationDepthOf, resolveChildDepth } from './mirror.js';
22
22
  import { sameModel } from './models.js';
23
23
  import { buildPrompt } from './prompt.js';
24
24
  import { storeKeyOf } from './session-pool.js';
25
+ import { cursorQuotaFailure, quotaBlockedMessage } from './quota-guard.js';
25
26
 
26
27
  /** Default wall-clock budget for one Cursor turn. */
27
28
  const DEFAULT_TIMEOUT_SECONDS = 1800;
@@ -89,7 +90,7 @@ function clean(value, seen) {
89
90
  * @param deps - plugin services and shared state.
90
91
  */
91
92
  export function cursorCodeTool(deps) {
92
- const { pool, journal, index, logger, unavailable, mirrors } = deps;
93
+ const { pool, journal, index, logger, unavailable, mirrors, quotaGuard } = deps;
93
94
  // The recursion budget this tool enforces: the child it starts is a real
94
95
  // DSH child session, so a subagent calling this tool would create a
95
96
  // GRANDCHILD. `1` is the platform's own default (only a top-level agent
@@ -291,6 +292,15 @@ export function cursorCodeTool(deps) {
291
292
  }
292
293
  }
293
294
 
295
+ const quotaStatus = await quotaGuard?.check?.();
296
+ if (quotaStatus?.blocked === true) {
297
+ return toLosslessJson({
298
+ ok: false, sessionKey: 'quota-exhausted', conversationKey,
299
+ stopReason: 'error', durationMs: 0,
300
+ error: quotaBlockedMessage(quotaStatus), steps: [], changes: [],
301
+ });
302
+ }
303
+
294
304
  if (input.resetSession) {
295
305
  pool.dropConversation(conversationKey);
296
306
  journal.clear(conversationKey);
@@ -377,8 +387,8 @@ export function cursorCodeTool(deps) {
377
387
  let reused = false;
378
388
  let reloaded = false;
379
389
  const lifecycle = exec.cursorLifecycle;
380
- // External native runs live in the subagents service as a row without a
381
- // local transcript; only a lifecycle that asks for the mirror creates one.
390
+ // The service's external lifecycle id is separate from this child:
391
+ // the panel follows the mirror, whose transcript is written below.
382
392
  const mirrorEnabled = lifecycle?.mirror !== false;
383
393
  const openChild = async () => {
384
394
  const entry = await mirrors?.open({
@@ -436,6 +446,13 @@ export function cursorCodeTool(deps) {
436
446
  if (pickedMode !== undefined && session.modes?.currentModeId !== pickedMode)
437
447
  await session.setMode(pickedMode);
438
448
  turn = await session.prompt(input.prompt, abort.signal);
449
+ if (cursorQuotaFailure(turn.text)) {
450
+ const status = quotaGuard?.observeFailure?.(turn.text);
451
+ if (status?.blocked === true) {
452
+ const partialTurn = { ...turn, stopReason: 'error' };
453
+ throw Object.assign(new Error(quotaBlockedMessage(status)), { partialTurn });
454
+ }
455
+ }
439
456
  pool.recordTurn(sessionKey, turn);
440
457
  const after = await fingerprint(workspaceRoot, deps.maxScanFiles);
441
458
  const changes = mergeChanges(turn.diffs, compareFingerprints(before, after), workspaceRoot);
@@ -484,7 +501,10 @@ export function cursorCodeTool(deps) {
484
501
  });
485
502
  }
486
503
  catch (error) {
487
- const message = error instanceof Error ? error.message : String(error);
504
+ const quotaStatus = cursorQuotaFailure(error) ? quotaGuard?.observeFailure?.(error) : undefined;
505
+ const message = quotaStatus?.blocked === true
506
+ ? quotaBlockedMessage(quotaStatus)
507
+ : error instanceof Error ? error.message : String(error);
488
508
  logger?.warn?.(`cursor_code failed: ${message}`);
489
509
  return toLosslessJson({
490
510
  ok: false, sessionKey, conversationKey, generation: picked.generation,