@selesai/code 0.13.25 → 0.13.27

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 (124) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/cli/args.js +1 -0
  3. package/dist/cli/session-picker.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/core/agent-session-auto-handoff.test.js +14 -4
  6. package/dist/core/agent-session-length-continuation.test.js +2 -1
  7. package/dist/core/agent-session.d.ts +58 -7
  8. package/dist/core/agent-session.js +339 -169
  9. package/dist/core/bug-report-upload.d.ts +11 -0
  10. package/dist/core/bug-report-upload.js +21 -0
  11. package/dist/core/bug-report.d.ts +175 -0
  12. package/dist/core/bug-report.js +291 -0
  13. package/dist/core/cache-stats.js +12 -1
  14. package/dist/core/cache-warmer.d.ts +101 -0
  15. package/dist/core/cache-warmer.js +341 -0
  16. package/dist/core/compaction/branch-summarization.js +2 -2
  17. package/dist/core/compaction/compaction.d.ts +6 -5
  18. package/dist/core/compaction/compaction.js +49 -42
  19. package/dist/core/crash-log.d.ts +21 -0
  20. package/dist/core/crash-log.js +70 -0
  21. package/dist/core/experimental.d.ts +0 -4
  22. package/dist/core/experimental.js +0 -4
  23. package/dist/core/extensions/index.d.ts +1 -1
  24. package/dist/core/extensions/jiti-loader.d.ts +1 -0
  25. package/dist/core/extensions/jiti-loader.js +3 -0
  26. package/dist/core/extensions/jiti-static-loader.d.ts +1 -0
  27. package/dist/core/extensions/jiti-static-loader.js +3 -0
  28. package/dist/core/extensions/loader.js +39 -49
  29. package/dist/core/extensions/runner.d.ts +10 -7
  30. package/dist/core/extensions/runner.js +85 -67
  31. package/dist/core/extensions/types.d.ts +66 -52
  32. package/dist/core/extensions/virtual-modules.d.ts +2 -0
  33. package/dist/core/extensions/virtual-modules.js +39 -0
  34. package/dist/core/extensions/wrapper.js +1 -20
  35. package/dist/core/handoff.d.ts +1 -0
  36. package/dist/core/handoff.js +1 -0
  37. package/dist/core/index.d.ts +2 -1
  38. package/dist/core/keybindings.d.ts +1 -1
  39. package/dist/core/keybindings.js +1 -1
  40. package/dist/core/messages.js +1 -0
  41. package/dist/core/model-config.d.ts +101 -20
  42. package/dist/core/model-config.js +25 -18
  43. package/dist/core/model-resolver.js +2 -1
  44. package/dist/core/model-runtime.js +5 -3
  45. package/dist/core/provider-composer.d.ts +7 -2
  46. package/dist/core/provider-composer.js +10 -5
  47. package/dist/core/radius.d.ts +3 -0
  48. package/dist/core/radius.js +6 -0
  49. package/dist/core/sdk.js +60 -38
  50. package/dist/core/session-export.d.ts +6 -2
  51. package/dist/core/session-export.js +14 -13
  52. package/dist/core/session-format.test.d.ts +1 -0
  53. package/dist/core/session-format.test.js +96 -0
  54. package/dist/core/session-manager.d.ts +29 -6
  55. package/dist/core/session-manager.js +159 -96
  56. package/dist/core/settings-manager.d.ts +22 -5
  57. package/dist/core/settings-manager.js +56 -23
  58. package/dist/core/slash-commands.js +1 -0
  59. package/dist/core/system-prompt.d.ts +49 -4
  60. package/dist/core/system-prompt.js +155 -102
  61. package/dist/core/system-prompt.test.js +61 -23
  62. package/dist/core/tools/bash.d.ts +2 -1
  63. package/dist/core/tools/bash.js +10 -4
  64. package/dist/core/tools/edit.js +1 -2
  65. package/dist/core/tools/read.js +1 -2
  66. package/dist/core/tools/renderers/bash.js +9 -1
  67. package/dist/core/tools/write.js +1 -2
  68. package/dist/core/usage-totals.d.ts +1 -1
  69. package/dist/core/usage-totals.js +5 -1
  70. package/dist/defaults/models.json +2 -72
  71. package/dist/extensions/handoff-new.test.ts +13 -13
  72. package/dist/extensions/handoff-new.ts +17 -6
  73. package/dist/extensions/pi-web-agent/src/backends/factory.ts +14 -6
  74. package/dist/extensions/pi-web-agent/src/extension.ts +3 -1
  75. package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +7 -3
  76. package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +3 -0
  77. package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +2 -0
  78. package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +4 -2
  79. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +4 -1
  80. package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +2 -1
  81. package/dist/extensions/pi-web-agent/src/types.ts +4 -0
  82. package/dist/index.d.ts +3 -2
  83. package/dist/main.js +13 -9
  84. package/dist/migrations.js +2 -2
  85. package/dist/modes/interactive/bug-report.d.ts +15 -0
  86. package/dist/modes/interactive/bug-report.js +205 -0
  87. package/dist/modes/interactive/chat-viewport.js +1 -1
  88. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  89. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  90. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  91. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  92. package/dist/modes/interactive/components/extension-editor.js +7 -2
  93. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  94. package/dist/modes/interactive/components/extension-input.js +6 -0
  95. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  96. package/dist/modes/interactive/components/extension-selector.js +4 -0
  97. package/dist/modes/interactive/components/footer.js +4 -1
  98. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  99. package/dist/modes/interactive/components/session-selector.js +74 -48
  100. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  101. package/dist/modes/interactive/components/settings-selector.js +11 -0
  102. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  103. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  104. package/dist/modes/interactive/components/status-indicator.js +7 -7
  105. package/dist/modes/interactive/components/tool-execution.js +18 -9
  106. package/dist/modes/interactive/components/tree-selector.js +2 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  108. package/dist/modes/interactive/interactive-mode.js +257 -81
  109. package/dist/modes/interactive/session-share.d.ts +2 -0
  110. package/dist/modes/interactive/session-share.js +8 -4
  111. package/dist/modes/interactive/tui-renderer.js +2 -2
  112. package/dist/modes/rpc/rpc-mode.js +7 -2
  113. package/dist/utils/clipboard-command.d.ts +6 -0
  114. package/dist/utils/clipboard-command.js +44 -0
  115. package/dist/utils/clipboard-image.js +55 -89
  116. package/dist/utils/clipboard.d.ts +1 -1
  117. package/dist/utils/clipboard.js +118 -108
  118. package/dist/utils/wsl.d.ts +2 -0
  119. package/dist/utils/wsl.js +14 -0
  120. package/dist/utils/zip.d.ts +6 -0
  121. package/dist/utils/zip.js +59 -0
  122. package/package.json +9 -9
  123. package/dist/utils/clipboard-native.d.ts +0 -10
  124. package/dist/utils/clipboard-native.js +0 -19
@@ -14,7 +14,7 @@
14
14
  "id": "celestial-pro",
15
15
  "name": "Celestial Pro",
16
16
  "reasoning": true,
17
- "input": ["text"],
17
+ "input": ["text", "image"],
18
18
  "contextWindow": 393216,
19
19
  "maxTokens": 64000,
20
20
  "thinkingLevelMap": {
@@ -67,51 +67,13 @@
67
67
  "requiresReasoningContentOnAssistantMessages": true
68
68
  }
69
69
  },
70
- {
71
- "id": "deepseek-v4-flash",
72
- "name": "DeepSeek V4 Flash",
73
- "reasoning": true,
74
- "input": ["text"],
75
- "contextWindow": 512000,
76
- "maxTokens": 64000,
77
- "thinkingLevelMap": {
78
- "minimal": null,
79
- "low": null,
80
- "medium": null,
81
- "high": "high",
82
- "xhigh": null,
83
- "max": "max"
84
- },
85
- "compat": {
86
- "requiresReasoningContentOnAssistantMessages": true
87
- }
88
- },
89
- {
90
- "id": "deepseek-v4-flash-vision-exp",
91
- "name": "DeepSeek V4 Flash Vision Exp",
92
- "reasoning": true,
93
- "input": ["text", "image"],
94
- "contextWindow": 512000,
95
- "maxTokens": 128000,
96
- "thinkingLevelMap": {
97
- "minimal": null,
98
- "low": null,
99
- "medium": null,
100
- "high": "high",
101
- "xhigh": null,
102
- "max": "max"
103
- },
104
- "compat": {
105
- "requiresReasoningContentOnAssistantMessages": true
106
- }
107
- },
108
70
  {
109
71
  "id": "deepseek-v4.1-flash",
110
72
  "name": "DeepSeek V4.1 Flash",
111
73
  "reasoning": true,
112
74
  "input": ["text", "image"],
113
75
  "contextWindow": 512000,
114
- "maxTokens": 128000,
76
+ "maxTokens": 64000,
115
77
  "thinkingLevelMap": {
116
78
  "minimal": null,
117
79
  "low": null,
@@ -207,38 +169,6 @@
207
169
  "max": "max"
208
170
  }
209
171
  },
210
- {
211
- "id": "kimi-k2.7-code",
212
- "name": "Kimi K2.7 Code",
213
- "reasoning": true,
214
- "input": ["text", "image"],
215
- "contextWindow": 262128,
216
- "maxTokens": 64000,
217
- "thinkingLevelMap": {
218
- "minimal": null,
219
- "low": "low",
220
- "medium": null,
221
- "high": "high",
222
- "xhigh": null,
223
- "max": "max"
224
- }
225
- },
226
- {
227
- "id": "glm-5.2",
228
- "name": "GLM-5.2",
229
- "reasoning": true,
230
- "input": ["text"],
231
- "contextWindow": 1000000,
232
- "maxTokens": 64000,
233
- "thinkingLevelMap": {
234
- "minimal": null,
235
- "low": "low",
236
- "medium": null,
237
- "high": "high",
238
- "xhigh": null,
239
- "max": null
240
- }
241
- },
242
172
  {
243
173
  "id": "glm-5.3-flash",
244
174
  "name": "GLM-5.3 Flash",
@@ -226,11 +226,11 @@ describe("handoff-new extension", () => {
226
226
  expect(calls.newSession).not.toBeNull();
227
227
  });
228
228
 
229
- it("non-tui mode generation error notifies and opens no session", async () => {
229
+ it("non-tui mode generation error notifies, rejects, and opens no session", async () => {
230
230
  (complete as any).mockRejectedValueOnce(new Error("api down"));
231
231
  const { commands } = createPiHarness();
232
232
  const { ctx, calls } = createCtx({ mode: "rpc" });
233
- await commands.get("handoff-new")!.handler("goal", ctx);
233
+ await expect(commands.get("handoff-new")!.handler("goal", ctx)).rejects.toThrow("api down");
234
234
  expect(calls.newSession).toBeNull();
235
235
  expect(calls.notify.some((n) => /api down/.test(n.msg))).toBe(true);
236
236
  });
@@ -259,10 +259,10 @@ describe("handoff-new extension", () => {
259
259
  expect(calls.notify.some((n) => /Cancelled/.test(n.msg))).toBe(true);
260
260
  });
261
261
 
262
- it("empty generated handoff does not open a blank session", async () => {
262
+ it("empty generated handoff notifies, rejects, and opens no blank session", async () => {
263
263
  const { commands } = createPiHarness();
264
264
  const { ctx, calls } = createCtx({ customResult: " " });
265
- await commands.get("handoff-new")!.handler("goal", ctx);
265
+ await expect(commands.get("handoff-new")!.handler("goal", ctx)).rejects.toThrow("returned no text");
266
266
  expect(calls.newSession).toBeNull();
267
267
  expect(calls.notify.some((n) => /returned no text/.test(n.msg))).toBe(true);
268
268
  });
@@ -358,29 +358,29 @@ describe("handoff-new extension", () => {
358
358
  expect(calls.notify.some((n) => /Cancelled/.test(n.msg))).toBe(true);
359
359
  });
360
360
 
361
- it("factory: missing API key throws and notifies Cancelled", async () => {
361
+ it("factory: missing API key notifies and rejects", async () => {
362
362
  const { commands } = createPiHarness();
363
363
  const { ctx, calls } = createFactoryCtx({ auth: { ok: true, error: "no key" } });
364
- await commands.get("handoff-new")!.handler("goal", ctx);
364
+ await expect(commands.get("handoff-new")!.handler("goal", ctx)).rejects.toThrow("No API key");
365
365
  expect(calls.newSession).toBeNull();
366
- expect(calls.notify.some((n) => /Cancelled/.test(n.msg))).toBe(true);
366
+ expect(calls.notify.some((n) => /No API key/.test(n.msg))).toBe(true);
367
367
  });
368
368
 
369
- it("factory: auth error throws and notifies Cancelled", async () => {
369
+ it("factory: auth error notifies and rejects", async () => {
370
370
  const { commands } = createPiHarness();
371
371
  const { ctx, calls } = createFactoryCtx({ auth: { ok: false, error: "auth failed" } });
372
- await commands.get("handoff-new")!.handler("goal", ctx);
372
+ await expect(commands.get("handoff-new")!.handler("goal", ctx)).rejects.toThrow("auth failed");
373
373
  expect(calls.newSession).toBeNull();
374
- expect(calls.notify.some((n) => /Cancelled/.test(n.msg))).toBe(true);
374
+ expect(calls.notify.some((n) => /auth failed/.test(n.msg))).toBe(true);
375
375
  });
376
376
 
377
- it("factory: complete rejection is caught and notifies Cancelled", async () => {
377
+ it("factory: complete rejection notifies and rejects", async () => {
378
378
  (complete as any).mockRejectedValueOnce(new Error("network down"));
379
379
  const { commands } = createPiHarness();
380
380
  const { ctx, calls } = createFactoryCtx({});
381
- await commands.get("handoff-new")!.handler("goal", ctx);
381
+ await expect(commands.get("handoff-new")!.handler("goal", ctx)).rejects.toThrow("network down");
382
382
  expect(calls.newSession).toBeNull();
383
- expect(calls.notify.some((n) => /Cancelled/.test(n.msg))).toBe(true);
383
+ expect(calls.notify.some((n) => /network down/.test(n.msg))).toBe(true);
384
384
  });
385
385
 
386
386
  it("factory: loader onAbort resolves null", async () => {
@@ -74,15 +74,20 @@ async function handoffNew(args: string, ctx: ExtensionCommandContext) {
74
74
  const aiContext = buildAiContext(conversationText, goal);
75
75
 
76
76
  let result: string | null;
77
+ let generationError: unknown;
77
78
  if (ctx.mode === "tui") {
79
+ let cancelled = false;
78
80
  result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
79
81
  const loader = new BorderedLoader(tui, theme, `Generating handoff prompt...`);
80
- loader.onAbort = () => done(null);
82
+ loader.onAbort = () => {
83
+ cancelled = true;
84
+ done(null);
85
+ };
81
86
 
82
87
  generateHandoffText(ctx, aiContext, loader.signal)
83
88
  .then(done)
84
89
  .catch((err) => {
85
- console.error("handoff-new generation failed:", err);
90
+ if (!cancelled) generationError = err;
86
91
  done(null);
87
92
  });
88
93
 
@@ -94,18 +99,24 @@ async function handoffNew(args: string, ctx: ExtensionCommandContext) {
94
99
  try {
95
100
  result = await generateHandoffText(ctx, aiContext, ctx.signal);
96
101
  } catch (err) {
97
- ctx.ui.notify(err instanceof Error ? err.message : String(err), "error");
98
- return;
102
+ generationError = err;
103
+ result = null;
99
104
  }
100
105
  }
101
106
 
107
+ if (generationError !== undefined) {
108
+ const error = generationError instanceof Error ? generationError : new Error(String(generationError));
109
+ ctx.ui.notify(error.message, "error");
110
+ throw error;
111
+ }
102
112
  if (result === null) {
103
113
  ctx.ui.notify("Cancelled", "info");
104
114
  return;
105
115
  }
106
116
  if (!result.trim()) {
107
- ctx.ui.notify("Handoff generation returned no text", "error");
108
- return;
117
+ const error = new Error("Handoff generation returned no text");
118
+ ctx.ui.notify(error.message, "error");
119
+ throw error;
109
120
  }
110
121
 
111
122
  // Read the name before newSession: the original ctx is invalidated once
@@ -213,13 +213,21 @@ export function createBackendSet(
213
213
  });
214
214
  }
215
215
 
216
- // Keep the keyless Tavily safety net for the no-key DuckDuckGo default, even under fanout —
217
- // it wraps whatever search ended up being (plain DDG or the fanout set) so a total failure
218
- // still has somewhere to go. Opt out with PI_WEB_AGENT_DISABLE_KEYLESS_FALLBACK=1.
216
+ // The keyless Tavily safety net, kept even under fanout — it wraps whatever search ended up
217
+ // being so a total failure still has somewhere to go. It used to arm only for the bare
218
+ // no-account DuckDuckGo default, which skipped every hosted provider configured with
219
+ // `fallback: duckduckgo`: when that fallback then hit a DNS-blocked or bot-walled DuckDuckGo
220
+ // the whole search died even though keyless Tavily was sitting right there. Arm it whenever
221
+ // DuckDuckGo is reachable in the chain. Opt out with PI_WEB_AGENT_DISABLE_KEYLESS_FALLBACK=1.
219
222
  const keylessFallbackDisabled = process.env.PI_WEB_AGENT_DISABLE_KEYLESS_FALLBACK === '1';
220
- const usingDuckDuckGoDefault =
221
- config.search.provider === 'duckduckgo' || !config.search.provider;
222
- if (usingDuckDuckGoDefault && !keylessFallbackDisabled) {
223
+ // Fanout with no explicit provider list means "all usable", which always includes DuckDuckGo.
224
+ const fanoutActive = config.search.fanout?.mode !== undefined && config.search.fanout.mode !== 'off';
225
+ const duckDuckGoInChain =
226
+ !config.search.provider ||
227
+ config.search.provider === 'duckduckgo' ||
228
+ config.search.fallback === 'duckduckgo' ||
229
+ (fanoutActive && (config.search.fanout?.providers?.includes('duckduckgo') ?? true));
230
+ if (duckDuckGoInChain && !keylessFallbackDisabled) {
223
231
  search = withSearchFallback(search, createTavilySearch({ keyless: true }), 'duckduckgo');
224
232
  }
225
233
 
@@ -23,7 +23,9 @@ function serializeForModel(result: WebExploreResponse): string {
23
23
  return `Research failed: ${result.error?.message ?? 'Unknown research failure.'}`;
24
24
  }
25
25
  if (result.findings.length === 0 && result.sources.length === 0) {
26
- return 'No usable evidence found.';
26
+ return result.metadata?.failureReason
27
+ ? `No usable evidence found: ${result.metadata.failureReason}`
28
+ : 'No usable evidence found.';
27
29
  }
28
30
  return [
29
31
  result.findings.join('\n\n'),
@@ -28,23 +28,27 @@ function joinReasons(reasons: string[]) {
28
28
  return `${reasons.slice(0, -1).join(', ')}, and ${reasons.at(-1)}`;
29
29
  }
30
30
 
31
- function caveatText(partial: boolean, caveatReasons: EvidenceCaveatReason[] = []) {
31
+ function caveatText(partial: boolean, caveatReasons: EvidenceCaveatReason[] = [], failureReason?: string) {
32
32
  if (!partial) return undefined;
33
33
  const specificReasons = caveatReasons.map(sentenceForReason);
34
34
  if (specificReasons.length > 0) {
35
35
  return `Evidence is partial: ${joinReasons(specificReasons)}.`;
36
36
  }
37
+ // With nothing to show, the budget line is a cover-up: say what actually failed.
38
+ if (failureReason) return `No usable evidence: ${failureReason}`;
37
39
  return 'Evidence is partial, so this answer is based on the strongest source found within the bounded research budget.';
38
40
  }
39
41
 
40
42
  export function synthesizeAnswer({
41
43
  evidence,
42
44
  partial,
43
- caveatReasons = []
45
+ caveatReasons = [],
46
+ failureReason
44
47
  }: {
45
48
  evidence: ResearchEvidence[];
46
49
  partial: boolean;
47
50
  caveatReasons?: EvidenceCaveatReason[];
51
+ failureReason?: string;
48
52
  }) {
49
53
  const findings = evidence.slice(0, 5).map((item) => {
50
54
  const summary = normalizeSummary(item.summary);
@@ -55,6 +59,6 @@ export function synthesizeAnswer({
55
59
 
56
60
  return {
57
61
  findings,
58
- caveat: caveatText(partial, caveatReasons)
62
+ caveat: caveatText(partial, caveatReasons, failureReason)
59
63
  };
60
64
  }
@@ -136,6 +136,9 @@ function buildMetadata({
136
136
  headlessAttempts,
137
137
  exhaustedBudget,
138
138
  caveatReasons,
139
+ // The first gap is why the run came up dry; it was collected and then thrown away, which
140
+ // is how a DNS-blocked DuckDuckGo surfaced as a contentless "No usable evidence found".
141
+ failureReason: allEvidence.length === 0 ? allGaps[0]?.message : undefined,
139
142
  fanoutProviders,
140
143
  fanoutSkipped
141
144
  };
@@ -48,6 +48,8 @@ export type ResearchRunMetadata = {
48
48
  fetchedPages: number;
49
49
  headlessAttempts: number;
50
50
  exhaustedBudget: boolean;
51
+ caveatReasons?: string[];
52
+ failureReason?: string;
51
53
  fanoutProviders?: SearchProviderName[];
52
54
  fanoutSkipped?: SearchProviderName[];
53
55
  };
@@ -34,7 +34,7 @@ export function buildExplorePresentation(result: WebExploreResponse): Presentati
34
34
  const hasEvidence = result.findings.length > 0 || result.sources.length > 0;
35
35
  const evidenceLines = hasEvidence
36
36
  ? result.findings.map((finding, index) => `- [${internalReaderLabel(result.sources[index]?.method)}] ${finding}`)
37
- : ['No usable evidence found.'];
37
+ : [result.metadata?.failureReason ? `No usable evidence: ${result.metadata.failureReason}` : 'No usable evidence found.'];
38
38
  const preview = [
39
39
  ...evidenceLines,
40
40
  internalSummary ? `\n${internalSummary}` : undefined
@@ -55,7 +55,9 @@ export function buildExplorePresentation(result: WebExploreResponse): Presentati
55
55
 
56
56
  const compact = hasEvidence
57
57
  ? `Reviewed ${result.sources.length} sources · synthesized answer with ${result.findings.length} findings`
58
- : 'No usable evidence found';
58
+ : result.metadata?.failureReason
59
+ ? `No usable evidence: ${result.metadata.failureReason}`
60
+ : 'No usable evidence found';
59
61
 
60
62
  return {
61
63
  mode: 'compact',
@@ -6,7 +6,10 @@ import type { SearchResult, WebSearchResponse } from '../types.js';
6
6
 
7
7
  export const TOKENIN_SEARCH_TOOL_NAME = 'firecrawl';
8
8
  export const TOKENIN_DEFAULT_BASE_URL = 'https://lite.andlet.me/v1';
9
- const TOKENIN_SEARCH_TIMEOUT_MS = 10_000;
9
+ // ponytail: measured 20-36s per query through the LiteLLM proxy, so the old 10s ceiling
10
+ // aborted every search and quietly punted to the fallback chain. Ceiling: a dead proxy now
11
+ // costs 45s before falling back. Lower it if your proxy is local; raise it if still timing out.
12
+ const TOKENIN_SEARCH_TIMEOUT_MS = 45_000;
10
13
 
11
14
  type TokenInAccount = { id: string; label: string; apiKey: string; baseUrl?: string };
12
15
  type TokenInAuth = { accounts: TokenInAccount[]; activeId: string | null };
@@ -52,7 +52,8 @@ export function createWebExploreTool({
52
52
  const synthesized = synthesizeAnswer({
53
53
  evidence: result.evidence,
54
54
  partial: result.decision.action !== 'answer',
55
- caveatReasons: result.metadata?.caveatReasons as EvidenceCaveatReason[] | undefined
55
+ caveatReasons: result.metadata?.caveatReasons as EvidenceCaveatReason[] | undefined,
56
+ failureReason: result.metadata?.failureReason
56
57
  });
57
58
 
58
59
  const shaped: WebExploreResponse = {
@@ -95,6 +95,10 @@ export type WebExploreResponse = {
95
95
  headlessAttempts: number;
96
96
  exhaustedBudget: boolean;
97
97
  caveatReasons?: string[];
98
+ // Why nothing came back, when nothing came back. Carried from the research pass's first
99
+ // gap so the empty result reads as "DuckDuckGo search request failed: fetch failed"
100
+ // instead of a bare "No usable evidence found".
101
+ failureReason?: string;
98
102
  fanoutProviders?: SearchProviderName[];
99
103
  fanoutSkipped?: SearchProviderName[];
100
104
  };
package/dist/index.d.ts CHANGED
@@ -2,10 +2,11 @@ export { type Args, parseArgs } from "./cli/args.ts";
2
2
  export { CONFIG_DIR_NAME, getAgentDir, getDocsPath, getModelsPath, getSettingsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION, } from "./config.ts";
3
3
  export { AgentSession, type AgentSessionConfig, type AgentSessionEvent, type AgentSessionEventListener, type ModelCycleResult, type ParsedSkillBlock, type ParsedSkillBlocks, type PromptOptions, parseSkillBlock, parseSkillBlocks, type SessionStats, } from "./core/agent-session.ts";
4
4
  export { readStoredCredential } from "./core/auth-storage.ts";
5
+ export type { CacheWarmingDecision, CacheWarmingStatus } from "./core/cache-warmer.ts";
5
6
  export { ensureTool, getToolPath, type ManagedTool } from "./utils/tools-manager.ts";
6
7
  export { type BranchPreparation, type BranchSummaryResult, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
7
8
  export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
8
- export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ResolvedSkillInfo, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
9
+ export type { AfterProviderResponseEvent, AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CacheWarmingDecisionEvent, CacheWarmingDecisionEventResult, CompactOptions, ContextEvent, ContextEventResult, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageEndEventResult, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ModelSelectEvent, ModelSelectSource, NormalizedBuildSystemPromptOptions, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ResolvedSkillInfo, ResourcesDiscoverEvent, ResourcesDiscoverResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, SessionBeforeForkResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionCompactEvent, SessionCompactFailedEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ThinkingLevelSelectEvent, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TurnEndEvent, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
9
10
  export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
10
11
  export type { ReadonlyFooterDataProvider } from "./core/footer-data-provider.ts";
11
12
  export { convertToLlm } from "./core/messages.ts";
@@ -18,7 +19,7 @@ export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./co
18
19
  export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.ts";
19
20
  export { AgentSessionRuntime, type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionOptions, type CreateAgentSessionResult, type CreateAgentSessionRuntimeFactory, type CreateAgentSessionRuntimeResult, type CreateAgentSessionServicesOptions, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, type PromptTemplate, } from "./core/sdk.ts";
20
21
  export { type BranchSummaryEntry, buildContextEntries, buildSessionContext, type CompactionEntry, CURRENT_SESSION_VERSION, type CustomEntry, type CustomMessageEntry, type FileEntry, getLatestCompactionEntry, type ModelChangeEntry, migrateSessionEntries, type NewSessionOptions, parseSessionEntries, type SessionContext, type SessionEntry, type SessionEntryBase, type SessionHeader, type SessionInfo, type SessionInfoEntry, SessionManager, type SessionMessageEntry, type SessionTreeNode, sessionEntryToContextMessages, type ThinkingLevelChangeEntry, } from "./core/session-manager.ts";
21
- export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type FullscreenExitOutput, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
22
+ export { type AutoHandoffSettings, type CacheWarmingMode, type CompactionModelOverride, type CompactionSettings, type DefaultProjectTrust, type FullscreenExitOutput, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
22
23
  export { formatSkillsForPrompt, type LoadSkillsFromDirOptions, type LoadSkillsResult, loadSkills, loadSkillsFromDir, type Skill, type SkillFrontmatter, } from "./core/skills.ts";
23
24
  export { createSyntheticSourceInfo } from "./core/source-info.ts";
24
25
  export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
package/dist/main.js CHANGED
@@ -151,19 +151,23 @@ async function prepareInitialMessage(parsed, autoResizeImages, stdinContent) {
151
151
  * Resolve a session argument to a file path.
152
152
  * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
153
153
  */
154
- async function findLocalSessionByExactId(sessionId, cwd, sessionDir) {
155
- const localSessions = await SessionManager.list(cwd, sessionDir);
156
- const localMatch = localSessions.find((s) => s.id === sessionId);
157
- return localMatch ? { type: "local", path: localMatch.path } : undefined;
154
+ function findLocalSessionByExactId(sessionId, cwd, sessionDir) {
155
+ const path = SessionManager.findById(cwd, sessionId, sessionDir);
156
+ return path ? { type: "local", path } : undefined;
158
157
  }
159
158
  async function resolveSessionPath(sessionArg, cwd, sessionDir) {
160
159
  // If it looks like a file path, resolve it before handing it to the session manager.
161
160
  if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
162
161
  return { type: "path", path: resolvePath(sessionArg, cwd) };
163
162
  }
164
- // Try to match as session ID in current project first
163
+ // Exact IDs only require reading session headers. Fall back to the full
164
+ // metadata listing for prefix matches.
165
+ const exactLocalMatch = findLocalSessionByExactId(sessionArg, cwd, sessionDir);
166
+ if (exactLocalMatch) {
167
+ return exactLocalMatch;
168
+ }
165
169
  const localSessions = await SessionManager.list(cwd, sessionDir);
166
- const localMatch = localSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));
170
+ const localMatch = localSessions.find((s) => s.id.startsWith(sessionArg));
167
171
  if (localMatch) {
168
172
  return { type: "local", path: localMatch.path };
169
173
  }
@@ -250,7 +254,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
250
254
  }
251
255
  if (parsed.fork) {
252
256
  if (parsed.sessionId) {
253
- const existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
257
+ const existingTarget = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
254
258
  if (existingTarget) {
255
259
  console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
256
260
  process.exit(1);
@@ -289,7 +293,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
289
293
  }
290
294
  if (parsed.resume) {
291
295
  try {
292
- const selectedPath = await selectSession((onProgress) => SessionManager.list(cwd, sessionDir, onProgress), (onProgress) => SessionManager.listAll(sessionDir, onProgress), settingsManager);
296
+ const selectedPath = await selectSession((onProgress, signal) => SessionManager.list(cwd, sessionDir, onProgress, signal), (onProgress, signal) => SessionManager.listAll(sessionDir, onProgress, signal), settingsManager);
293
297
  if (!selectedPath) {
294
298
  console.log(chalk.dim("No session selected"));
295
299
  process.exit(0);
@@ -304,7 +308,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
304
308
  return SessionManager.continueRecent(cwd, sessionDir);
305
309
  }
306
310
  if (parsed.sessionId) {
307
- const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
311
+ const existingSession = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
308
312
  if (existingSession) {
309
313
  return SessionManager.open(existingSession.path, sessionDir);
310
314
  }
@@ -6,8 +6,8 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, w
6
6
  import { dirname, join } from "path";
7
7
  import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.js";
8
8
  import { migrateKeybindingsConfig } from "./core/keybindings.js";
9
- const MIGRATION_GUIDE_URL = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
10
- const EXTENSIONS_DOC_URL = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/docs/extensions.md";
9
+ const MIGRATION_GUIDE_URL = "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
10
+ const EXTENSIONS_DOC_URL = "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md";
11
11
  /**
12
12
  * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
13
13
  *
@@ -0,0 +1,15 @@
1
+ import type { Container, EditorComponent, TUI } from "@earendil-works/pi-tui";
2
+ import type { AgentSession } from "../../core/agent-session.ts";
3
+ import type { KeybindingsManager } from "../../core/keybindings.ts";
4
+ interface BugReportContext {
5
+ session: AgentSession;
6
+ ui: TUI;
7
+ editorContainer: Container;
8
+ editor: EditorComponent;
9
+ keybindings: KeybindingsManager;
10
+ showStatus: (message: string) => void;
11
+ showError: (message: string) => void;
12
+ }
13
+ /** Run the `/bug` flow: consent, optional summary, then upload or export. */
14
+ export declare function reportBug(context: BugReportContext, initialHint?: string): Promise<void>;
15
+ export {};