@ridit/lens 0.4.6 → 0.4.7

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/index.mjs CHANGED
@@ -78205,16 +78205,42 @@ function getLastDeniedAction(messages) {
78205
78205
  async function runHeadless(opts) {
78206
78206
  const repoPath = opts.path;
78207
78207
  let session = opts.sessionId ? loadSession(opts.sessionId) ?? createSessionWithId(opts.sessionId, repoPath) : opts.single ? getLatestSession(repoPath) ?? createSession(repoPath) : createSession(repoPath);
78208
- let prompt = opts.prompt;
78209
- if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
78210
- const pending = getLastDeniedAction(getMessages(session));
78211
- if (pending) {
78212
- prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
78208
+ if (opts.resume) {
78209
+ const msgs = getMessages(session);
78210
+ let trimAt = -1;
78211
+ for (let i = msgs.length - 1;i >= 0; i--) {
78212
+ const msg = msgs[i];
78213
+ if (!msg || msg.role !== "tool")
78214
+ continue;
78215
+ const content = Array.isArray(msg.content) ? msg.content : [];
78216
+ const isDenied = content.some((p) => typeof p === "object" && p !== null && ("type" in p) && p.type === "tool-result" && ("result" in p) && typeof p.result === "string" && p.result.includes("Permission denied"));
78217
+ if (isDenied) {
78218
+ for (let j = i - 1;j >= 0; j--) {
78219
+ if (msgs[j]?.role === "assistant") {
78220
+ trimAt = j;
78221
+ break;
78222
+ }
78223
+ }
78224
+ break;
78225
+ }
78226
+ }
78227
+ if (trimAt >= 0) {
78228
+ session = { ...session, messages: msgs.slice(0, trimAt) };
78229
+ }
78230
+ if (!opts.single || opts.sessionId)
78231
+ saveSession(session);
78232
+ } else {
78233
+ let prompt = opts.prompt;
78234
+ if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
78235
+ const pending = getLastDeniedAction(getMessages(session));
78236
+ if (pending) {
78237
+ prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
78238
+ }
78213
78239
  }
78240
+ session = addMessage(session, "user", prompt);
78241
+ if (!opts.single || opts.sessionId)
78242
+ saveSession(session);
78214
78243
  }
78215
- session = addMessage(session, "user", prompt);
78216
- if (!opts.single || opts.sessionId)
78217
- saveSession(session);
78218
78244
  const toolLog = [];
78219
78245
  const denied = [];
78220
78246
  const runtimeToolNames = new Set;
@@ -78264,10 +78290,10 @@ async function runHeadless(opts) {
78264
78290
  });
78265
78291
  }
78266
78292
  var program = new Command().enablePositionalOptions();
78267
- program.command("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").option("-d, --dev", "Output structured JSON (no UI)").option("--single", "Single-shot: run one message then exit").option("--session <id>", "Resume session by ID, or create one with that ID").option("--id <id>", "Alias for --session").option("--force-all", "Auto-approve all tools").option("--prompt <text>", "Run a prompt non-interactively").option("--runtime-tools <path>", "path to runtime tools JSON file").action((opts) => {
78293
+ program.command("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").option("-d, --dev", "Output structured JSON (no UI)").option("--single", "Single-shot: run one message then exit").option("--session <id>", "Resume session by ID, or create one with that ID").option("--id <id>", "Alias for --session").option("--force-all", "Auto-approve all tools").option("--prompt <text>", "Run a prompt non-interactively").option("--resume", "Resume from last permission-denied tool call (no new prompt needed)").option("--runtime-tools <path>", "path to runtime tools JSON file").action((opts) => {
78268
78294
  const sessionId = opts.session ?? opts.id;
78269
- if (opts.prompt && (opts.dev || opts.single)) {
78270
- runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll, runtimeTools: opts.runtimeTools });
78295
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
78296
+ runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll ?? opts.resume, runtimeTools: opts.runtimeTools, resume: opts.resume });
78271
78297
  return;
78272
78298
  }
78273
78299
  render(/* @__PURE__ */ jsxDEV21(ChatCommand, {
@@ -78365,13 +78391,13 @@ program.command("run <cmd>").description("Run your dev server. Lens watches and
78365
78391
  });
78366
78392
  var firstArg = process.argv[2];
78367
78393
  if (!firstArg || firstArg.startsWith("-")) {
78368
- const defaultFlags = new Command().option("-p, --path <path>", "Path to the repo", ".").option("--session <id>", "Resume session by ID").option("--single", "Single-shot mode").option("--prompt <text>", "Run a prompt").option("-d, --dev", "Output JSON (no UI)").option("--force-all", "Auto-approve all tools").allowUnknownOption().exitOverride();
78394
+ const defaultFlags = new Command().option("-p, --path <path>", "Path to the repo", ".").option("--session <id>", "Resume session by ID").option("--single", "Single-shot mode").option("--prompt <text>", "Run a prompt").option("-d, --dev", "Output JSON (no UI)").option("--force-all", "Auto-approve all tools").option("--resume", "Resume from last permission-denied tool call").allowUnknownOption().exitOverride();
78369
78395
  try {
78370
78396
  defaultFlags.parse(process.argv);
78371
78397
  } catch {}
78372
78398
  const opts = defaultFlags.opts();
78373
- if (opts.prompt && (opts.dev || opts.single)) {
78374
- runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll });
78399
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
78400
+ runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll ?? opts.resume, resume: opts.resume });
78375
78401
  } else {
78376
78402
  render(/* @__PURE__ */ jsxDEV21(ChatCommand, {
78377
78403
  path: opts.path ?? ".",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Understand your codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
package/src/index.tsx CHANGED
@@ -72,11 +72,12 @@ function getLastDeniedAction(messages: ReturnType<typeof getMessages>): { tool:
72
72
 
73
73
  async function runHeadless(opts: {
74
74
  path: string;
75
- prompt: string;
75
+ prompt?: string;
76
76
  sessionId?: string;
77
77
  single?: boolean;
78
78
  forceAll?: boolean;
79
79
  runtimeTools?: string;
80
+ resume?: boolean;
80
81
  }) {
81
82
  const repoPath = opts.path;
82
83
 
@@ -86,18 +87,54 @@ async function runHeadless(opts: {
86
87
  ? (getLatestSession(repoPath) ?? createSession(repoPath))
87
88
  : createSession(repoPath);
88
89
 
89
- // if user is approving a prior denial, make the intent unambiguous
90
- let prompt = opts.prompt;
91
- if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
92
- const pending = getLastDeniedAction(getMessages(session));
93
- if (pending) {
94
- prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
90
+ if (opts.resume) {
91
+ // Rewind the session: strip the last denied tool-call (assistant message),
92
+ // its "Permission denied" tool result, and the assistant's text response after it.
93
+ // This leaves the session ending at the last successful state so the agent
94
+ // can re-attempt the denied tool with forceAll: true.
95
+ const msgs = getMessages(session);
96
+ let trimAt = -1;
97
+ for (let i = msgs.length - 1; i >= 0; i--) {
98
+ const msg = msgs[i];
99
+ if (!msg || msg.role !== "tool") continue;
100
+ const content = Array.isArray(msg.content) ? msg.content : [];
101
+ const isDenied = content.some(
102
+ (p: unknown) =>
103
+ typeof p === "object" && p !== null &&
104
+ "type" in p && (p as { type: string }).type === "tool-result" &&
105
+ "result" in p && typeof (p as { result: unknown }).result === "string" &&
106
+ ((p as { result: string }).result).includes("Permission denied"),
107
+ );
108
+ if (isDenied) {
109
+ // Find the assistant message immediately before this tool result (the tool-call message)
110
+ for (let j = i - 1; j >= 0; j--) {
111
+ if (msgs[j]?.role === "assistant") {
112
+ trimAt = j;
113
+ break;
114
+ }
115
+ }
116
+ break;
117
+ }
118
+ }
119
+ if (trimAt >= 0) {
120
+ session = { ...session, messages: msgs.slice(0, trimAt) };
121
+ }
122
+ // Save trimmed session so context is clean for this run
123
+ if (!opts.single || opts.sessionId) saveSession(session);
124
+ } else {
125
+ // if user is approving a prior denial, make the intent unambiguous
126
+ let prompt = opts.prompt!;
127
+ if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
128
+ const pending = getLastDeniedAction(getMessages(session));
129
+ if (pending) {
130
+ prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
131
+ }
95
132
  }
96
- }
97
133
 
98
- session = addMessage(session, "user", prompt);
99
- // save now so context is available on follow-up messages even if we exit early
100
- if (!opts.single || opts.sessionId) saveSession(session);
134
+ session = addMessage(session, "user", prompt);
135
+ // save now so context is available on follow-up messages even if we exit early
136
+ if (!opts.single || opts.sessionId) saveSession(session);
137
+ }
101
138
 
102
139
  const toolLog: { tool: string; args: unknown; result: unknown }[] = [];
103
140
  const denied: { tool: string; description: string }[] = [];
@@ -170,6 +207,7 @@ program
170
207
  .option("--id <id>", "Alias for --session")
171
208
  .option("--force-all", "Auto-approve all tools")
172
209
  .option("--prompt <text>", "Run a prompt non-interactively")
210
+ .option("--resume", "Resume from last permission-denied tool call (no new prompt needed)")
173
211
  .option("--runtime-tools <path>", "path to runtime tools JSON file")
174
212
  .action(
175
213
  (opts: {
@@ -180,12 +218,13 @@ program
180
218
  id?: string;
181
219
  forceAll?: boolean;
182
220
  prompt?: string;
221
+ resume?: boolean;
183
222
  runtimeTools?: string;
184
223
  }) => {
185
224
  const sessionId = opts.session ?? opts.id;
186
- // headless: dev+prompt or single+prompt → no UI, output JSON and exit
187
- if (opts.prompt && (opts.dev || opts.single)) {
188
- runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll, runtimeTools: opts.runtimeTools });
225
+ // headless: dev+prompt, single+prompt, or --resume → no UI, output JSON and exit
226
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
227
+ runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll ?? opts.resume, runtimeTools: opts.runtimeTools, resume: opts.resume });
189
228
  return;
190
229
  }
191
230
  render(
@@ -367,6 +406,7 @@ if (!firstArg || firstArg.startsWith("-")) {
367
406
  .option("--prompt <text>", "Run a prompt")
368
407
  .option("-d, --dev", "Output JSON (no UI)")
369
408
  .option("--force-all", "Auto-approve all tools")
409
+ .option("--resume", "Resume from last permission-denied tool call")
370
410
  .allowUnknownOption()
371
411
  .exitOverride();
372
412
 
@@ -379,10 +419,11 @@ if (!firstArg || firstArg.startsWith("-")) {
379
419
  prompt?: string;
380
420
  dev?: boolean;
381
421
  forceAll?: boolean;
422
+ resume?: boolean;
382
423
  }>();
383
424
 
384
- if (opts.prompt && (opts.dev || opts.single)) {
385
- runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll });
425
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
426
+ runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll ?? opts.resume, resume: opts.resume });
386
427
  } else {
387
428
  render(
388
429
  <ChatCommand