@visorcraft/idlehands 2.2.7 → 2.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent.js CHANGED
@@ -41,6 +41,7 @@ import { normalizeApprovalMode } from './shared/config-utils.js';
41
41
  import { collectSnapshot } from './sys/context.js';
42
42
  import { ToolError, ValidationError } from './tools/tool-error.js';
43
43
  import * as tools from './tools.js';
44
+ import { EditTransaction } from './tools/transaction.js';
44
45
  import { stateDir, timestampedId } from './utils.js';
45
46
  import { VaultStore } from './vault.js';
46
47
  export { parseToolCallsFromContent };
@@ -489,6 +490,7 @@ export async function createSession(opts) {
489
490
  let inFlight = null;
490
491
  let initialConnectionProbeDone = false;
491
492
  let lastEditedPath;
493
+ let lastTurnTransaction;
492
494
  // Plan mode state (Phase 8)
493
495
  let planSteps = [];
494
496
  // Sub-agent queue state (Phase 18): enforce sequential execution on single-GPU setups.
@@ -3002,6 +3004,8 @@ export async function createSession(opts) {
3002
3004
  const absPath = args.path.startsWith('/')
3003
3005
  ? args.path
3004
3006
  : path.resolve(projectDir, args.path);
3007
+ // Track in turn transaction for potential atomic rollback.
3008
+ turnTransaction.track(absPath);
3005
3009
  // ── Pre-dispatch: block edits to files in a mutation spiral ──
3006
3010
  if (fileMutationBlocked.has(absPath)) {
3007
3011
  const basename = path.basename(absPath);
@@ -3493,6 +3497,7 @@ export async function createSession(opts) {
3493
3497
  return { id: callId, content: truncated.content };
3494
3498
  };
3495
3499
  const results = [];
3500
+ const turnTransaction = new EditTransaction();
3496
3501
  let invalidArgsThisTurn = false;
3497
3502
  // Helper: catch tool errors but re-throw AgentLoopBreak (those must break the outer loop)
3498
3503
  const catchToolError = async (e, tc) => {
@@ -3647,6 +3652,11 @@ export async function createSession(opts) {
3647
3652
  });
3648
3653
  }
3649
3654
  }
3655
+ // Store the turn transaction for potential post-turn rollback.
3656
+ if (turnTransaction.hasChanges) {
3657
+ turnTransaction.commit();
3658
+ lastTurnTransaction = turnTransaction;
3659
+ }
3650
3660
  // Bail immediately if cancelled during tool execution
3651
3661
  if (ac.signal.aborted)
3652
3662
  break;
@@ -3918,6 +3928,16 @@ export async function createSession(opts) {
3918
3928
  return currentContextTokens > 0 ? currentContextTokens : estimateTokensFromMessages(messages);
3919
3929
  },
3920
3930
  ask,
3931
+ rollbackLastTurnEdits: async () => {
3932
+ if (!lastTurnTransaction || !lastTurnTransaction.hasChanges) {
3933
+ return { ok: false, error: 'No file edits to roll back.' };
3934
+ }
3935
+ const tx = lastTurnTransaction;
3936
+ lastTurnTransaction = undefined;
3937
+ const callCtx = { cwd: projectDir, noConfirm: true, dryRun: false };
3938
+ const results = await tx.rollback(callCtx);
3939
+ return { ok: true, results };
3940
+ },
3921
3941
  rollback: () => {
3922
3942
  const cp = conversationBranch.rollback();
3923
3943
  if (!cp)