tau-all-agent 0.1.5 → 0.1.6

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.
@@ -95,14 +95,19 @@ interface ModelRequestAuth {
95
95
  model: Model<Api>;
96
96
  apiKey?: string;
97
97
  headers?: Record<string, string>;
98
+ env?: Record<string, string>;
98
99
  }
99
100
 
100
101
  type ModelAuthLookup = {
101
102
  find: (provider: string, modelId: string) => Model<Api> | undefined;
102
- getApiKeyAndHeaders: (
103
- model: Model<Api>,
104
- ) => Promise<
105
- { ok: true; apiKey?: string; headers?: Record<string, string> } | { ok: false; error: string }
103
+ getApiKeyAndHeaders: (model: Model<Api>) => Promise<
104
+ | {
105
+ ok: true;
106
+ apiKey?: string;
107
+ headers?: Record<string, string>;
108
+ env?: Record<string, string>;
109
+ }
110
+ | { ok: false; error: string }
106
111
  >;
107
112
  };
108
113
 
@@ -149,7 +154,7 @@ async function resolveModelRequestAuth(
149
154
  model: Model<Api>,
150
155
  ): Promise<ModelRequestAuth | null> {
151
156
  const auth = await modelRegistry.getApiKeyAndHeaders(model);
152
- return auth.ok ? { model, apiKey: auth.apiKey, headers: auth.headers } : null;
157
+ return auth.ok ? { model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env } : null;
153
158
  }
154
159
 
155
160
  /**
@@ -542,6 +547,7 @@ export default function (pi: ExtensionAPI) {
542
547
  {
543
548
  apiKey: extractionSelection.apiKey,
544
549
  headers: extractionSelection.headers,
550
+ env: extractionSelection.env,
545
551
  signal: loader.signal,
546
552
  },
547
553
  );
package/extensions/btw.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
- import type { AssistantMessage, ThinkingLevel } from "@earendil-works/pi-ai";
2
+ import {
3
+ InMemoryCredentialStore,
4
+ type AssistantMessage,
5
+ type Provider,
6
+ type ThinkingLevel,
7
+ } from "@earendil-works/pi-ai";
3
8
  import {
4
9
  DefaultResourceLoader,
10
+ ModelRuntime,
5
11
  SessionManager,
6
12
  buildSessionContext,
7
13
  createAgentSession,
@@ -59,6 +65,15 @@ type ActiveBtwRequest = {
59
65
  abort?: () => Promise<void>;
60
66
  };
61
67
 
68
+ type BtwModelRegistry = ExtensionCommandContext["modelRegistry"];
69
+
70
+ type BtwModelRuntimeState = {
71
+ runtime: ModelRuntime;
72
+ providers: Map<string, Provider>;
73
+ };
74
+
75
+ const btwModelRuntimes = new WeakMap<BtwModelRegistry, Promise<BtwModelRuntimeState>>();
76
+
62
77
  export default function btwExtension(pi: ExtensionAPI): void {
63
78
  let activeRequest: ActiveBtwRequest | null = null;
64
79
  let statusTimer: ReturnType<typeof setInterval> | null = null;
@@ -227,11 +242,12 @@ async function runBtwRequest(
227
242
  });
228
243
  await resourceLoader.reload();
229
244
 
245
+ const modelRuntime = await createBtwModelRuntime(snapshot.modelRegistry, snapshot.model.provider);
230
246
  const { session } = await createAgentSession({
231
247
  cwd: snapshot.cwd,
232
248
  model: snapshot.model,
233
249
  thinkingLevel: snapshot.thinkingLevel,
234
- modelRegistry: snapshot.modelRegistry,
250
+ modelRuntime,
235
251
  resourceLoader,
236
252
  tools: BTW_TOOL_NAMES,
237
253
  sessionManager,
@@ -269,6 +285,61 @@ async function runBtwRequest(
269
285
  }
270
286
  }
271
287
 
288
+ async function createBtwModelRuntime(
289
+ modelRegistry: BtwModelRegistry,
290
+ providerId: string,
291
+ ): Promise<ModelRuntime> {
292
+ const provider = modelRegistry.getProvider(providerId);
293
+ if (!provider) {
294
+ throw new Error(`Model provider is unavailable: ${providerId}`);
295
+ }
296
+
297
+ const state = await getBtwModelRuntimeState(modelRegistry);
298
+ if (state.providers.get(providerId) !== provider) {
299
+ state.runtime.registerNativeProvider(withParentAuth(provider, modelRegistry));
300
+ state.providers.set(providerId, provider);
301
+ }
302
+ return state.runtime;
303
+ }
304
+
305
+ async function getBtwModelRuntimeState(
306
+ modelRegistry: BtwModelRegistry,
307
+ ): Promise<BtwModelRuntimeState> {
308
+ let state = btwModelRuntimes.get(modelRegistry);
309
+ if (!state) {
310
+ state = ModelRuntime.create({
311
+ credentials: new InMemoryCredentialStore(),
312
+ modelsPath: null,
313
+ }).then((runtime) => ({ runtime, providers: new Map() }));
314
+ btwModelRuntimes.set(modelRegistry, state);
315
+ }
316
+
317
+ try {
318
+ return await state;
319
+ } catch (error) {
320
+ if (btwModelRuntimes.get(modelRegistry) === state) {
321
+ btwModelRuntimes.delete(modelRegistry);
322
+ }
323
+ throw error;
324
+ }
325
+ }
326
+
327
+ function withParentAuth(provider: Provider, modelRegistry: BtwModelRegistry): Provider {
328
+ return {
329
+ ...provider,
330
+ refreshModels: undefined, // The parent runtime owns model catalog refreshes.
331
+ auth: {
332
+ ...provider.auth,
333
+ apiKey: {
334
+ name: `${provider.name} session authentication`,
335
+ async resolve() {
336
+ return modelRegistry.getProviderAuth(provider.id);
337
+ },
338
+ },
339
+ },
340
+ };
341
+ }
342
+
272
343
  function seedSessionManager(sessionManager: SessionManager, messages: AgentMessage[]): void {
273
344
  type SessionMessageInput = Parameters<SessionManager["appendMessage"]>[0];
274
345
 
@@ -27,6 +27,7 @@ import {
27
27
  } from "@earendil-works/pi-tui";
28
28
  import { createHash, randomBytes } from "node:crypto";
29
29
  import fs from "node:fs/promises";
30
+ import os from "node:os";
30
31
  import path from "node:path";
31
32
 
32
33
  // --- Constants ---
@@ -141,6 +142,7 @@ type ConfiguredModelSelection = {
141
142
  model: Model<Api>;
142
143
  apiKey?: string;
143
144
  headers?: Record<string, string>;
145
+ env?: Record<string, string>;
144
146
  };
145
147
 
146
148
  type ReadonlySessionManager = Pick<
@@ -562,9 +564,37 @@ async function runInsightsCommand(
562
564
  return;
563
565
  }
564
566
 
567
+ let reportPath: string | undefined;
568
+ try {
569
+ reportPath = await saveInsightsReport(result.reportMarkdown, result.generatedAt);
570
+ } catch (error) {
571
+ const message = error instanceof Error ? error.message : "Unknown error";
572
+ ctx.ui.notify(`Failed to save insights report: ${message}`, "warning");
573
+ }
574
+
565
575
  await ctx.ui.custom<void>((tui, theme, _kb, done) => {
566
576
  return new InsightsReportComponent(result, tui, theme, done);
567
577
  });
578
+
579
+ if (reportPath) {
580
+ ctx.ui.notify(`Insights report saved to ${reportPath}`, "info");
581
+ }
582
+ }
583
+
584
+ async function saveInsightsReport(reportMarkdown: string, generatedAt: string): Promise<string> {
585
+ const timestamp = generatedAt.replace(/[:.]/g, "-");
586
+ const reportPath = path.join(os.tmpdir(), `tau-insights-${timestamp}.md`);
587
+ const reportFile = await fs.open(reportPath, "wx", 0o600);
588
+
589
+ try {
590
+ await reportFile.writeFile(`${reportMarkdown.trimEnd()}\n`, "utf8");
591
+ await reportFile.close();
592
+ return reportPath;
593
+ } catch (error) {
594
+ await reportFile.close().catch(() => undefined);
595
+ await fs.rm(reportPath, { force: true }).catch(() => undefined);
596
+ throw error;
597
+ }
568
598
  }
569
599
 
570
600
  async function runInsightsPipeline(
@@ -749,7 +779,12 @@ async function getConfiguredModelSelection(
749
779
 
750
780
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
751
781
  if (!auth.ok) return null;
752
- return { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers };
782
+ return {
783
+ model: ctx.model,
784
+ apiKey: auth.apiKey,
785
+ headers: auth.headers,
786
+ env: auth.env,
787
+ };
753
788
  }
754
789
 
755
790
  async function listTargets(
@@ -1342,6 +1377,7 @@ async function extractFacet(
1342
1377
  {
1343
1378
  apiKey: selection.apiKey,
1344
1379
  headers: selection.headers,
1380
+ env: selection.env,
1345
1381
  signal,
1346
1382
  },
1347
1383
  );
@@ -1514,6 +1550,7 @@ async function synthesizeReport(
1514
1550
  {
1515
1551
  apiKey: selection.apiKey,
1516
1552
  headers: selection.headers,
1553
+ env: selection.env,
1517
1554
  signal,
1518
1555
  },
1519
1556
  );
@@ -54,6 +54,7 @@ type ModelRequestAuth = {
54
54
  model: Model<Api>;
55
55
  apiKey?: string;
56
56
  headers?: Record<string, string>;
57
+ env?: Record<string, string>;
57
58
  };
58
59
 
59
60
  async function withPromptSignal<T>(pi: ExtensionAPI, run: () => Promise<T>): Promise<T> {
@@ -137,13 +138,20 @@ async function selectSummaryModel(ctx: ExtensionContext): Promise<ModelRequestAu
137
138
 
138
139
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(candidate);
139
140
  if (auth.ok) {
140
- return { model: candidate, apiKey: auth.apiKey, headers: auth.headers };
141
+ return {
142
+ model: candidate,
143
+ apiKey: auth.apiKey,
144
+ headers: auth.headers,
145
+ env: auth.env,
146
+ };
141
147
  }
142
148
  }
143
149
  }
144
150
 
145
151
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
146
- return auth.ok ? { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers } : null;
152
+ return auth.ok
153
+ ? { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env }
154
+ : null;
147
155
  }
148
156
 
149
157
  async function summarizeBreakoutCondition(
@@ -165,7 +173,7 @@ async function summarizeBreakoutCondition(
165
173
  const response = await complete(
166
174
  selection.model,
167
175
  { systemPrompt: SUMMARY_SYSTEM_PROMPT, messages: [userMessage] },
168
- { apiKey: selection.apiKey, headers: selection.headers },
176
+ { apiKey: selection.apiKey, headers: selection.headers, env: selection.env },
169
177
  );
170
178
 
171
179
  if (response.stopReason === "aborted" || response.stopReason === "error") {
@@ -518,6 +526,9 @@ export default function loopExtension(pi: ExtensionAPI): void {
518
526
  auth.headers,
519
527
  instructionParts,
520
528
  event.signal,
529
+ ctx.thinkingLevel,
530
+ undefined, // Use Pi's default summarization stream.
531
+ auth.env,
521
532
  );
522
533
  return { compaction };
523
534
  } catch (error) {
@@ -1,5 +1,11 @@
1
1
  import { complete } from "@earendil-works/pi-ai/compat";
2
- import { StringEnum, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
2
+ import {
3
+ StringEnum,
4
+ type Api,
5
+ type Model,
6
+ type Usage,
7
+ type UserMessage,
8
+ } from "@earendil-works/pi-ai";
3
9
  import {
4
10
  defineTool,
5
11
  withFileMutationQueue,
@@ -171,6 +177,11 @@ type DreamResult = {
171
177
  summaryPath: string | null;
172
178
  };
173
179
 
180
+ type DreamExecution = {
181
+ result: DreamResult;
182
+ usage?: Usage;
183
+ };
184
+
174
185
  type AutoDreamStatus = {
175
186
  coreLines: number;
176
187
  coreChars: number;
@@ -189,6 +200,7 @@ type ModelSelection = {
189
200
  model: Model<Api>;
190
201
  apiKey?: string;
191
202
  headers?: Record<string, string>;
203
+ env?: Record<string, string>;
192
204
  };
193
205
 
194
206
  class MemoryReadmeMissingError extends Error {
@@ -204,12 +216,17 @@ async function toolDream(
204
216
  cwd: string,
205
217
  ctx: ExtensionContext,
206
218
  reason?: string,
207
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details: DreamResult }> {
219
+ ): Promise<{
220
+ content: Array<{ type: "text"; text: string }>;
221
+ details: DreamResult;
222
+ usage?: Usage;
223
+ }> {
208
224
  try {
209
- const result = await runMemoryDream(cwd, ctx, reason);
225
+ const execution = await runMemoryDream(cwd, ctx, reason);
210
226
  return {
211
- content: [{ type: "text", text: result.summary }],
212
- details: result,
227
+ content: [{ type: "text", text: execution.result.summary }],
228
+ details: execution.result,
229
+ usage: execution.usage,
213
230
  };
214
231
  } catch (error) {
215
232
  if (error instanceof MemoryReadmeMissingError) {
@@ -369,7 +386,7 @@ async function runMemoryDream(
369
386
  cwd: string,
370
387
  ctx: ExtensionContext,
371
388
  reason?: string,
372
- ): Promise<DreamResult> {
389
+ ): Promise<DreamExecution> {
373
390
  const paths = getMemoryPaths(cwd);
374
391
  const lockPaths = [
375
392
  paths.readmeFile,
@@ -394,13 +411,15 @@ async function runMemoryDream(
394
411
  const summary = "Memory dream: nothing to consolidate.";
395
412
  notify(ctx, summary, "info");
396
413
  return {
397
- summary,
398
- updatedBlocks: [],
399
- consumedLogs: 0,
400
- consumedCompactions: 0,
401
- lastDreamAt: snapshot.state?.lastDreamAt ?? nowIso(),
402
- lastDreamedLogAt: snapshot.state?.lastDreamedLogAt ?? null,
403
- summaryPath: null,
414
+ result: {
415
+ summary,
416
+ updatedBlocks: [],
417
+ consumedLogs: 0,
418
+ consumedCompactions: 0,
419
+ lastDreamAt: snapshot.state?.lastDreamAt ?? nowIso(),
420
+ lastDreamedLogAt: snapshot.state?.lastDreamedLogAt ?? null,
421
+ summaryPath: null,
422
+ },
404
423
  };
405
424
  }
406
425
 
@@ -414,6 +433,7 @@ async function runMemoryDream(
414
433
  {
415
434
  apiKey: selection.apiKey,
416
435
  headers: selection.headers,
436
+ env: selection.env,
417
437
  signal: ctx.signal,
418
438
  },
419
439
  );
@@ -464,7 +484,7 @@ async function runMemoryDream(
464
484
  );
465
485
  }
466
486
 
467
- return withMemoryMutationQueue(lockPaths, async () => {
487
+ const result = await withMemoryMutationQueue(lockPaths, async () => {
468
488
  ensureMemoryInitialized(await pathExists(paths.memoryRoot));
469
489
 
470
490
  const currentReplay = await collectDreamReplay(cwd);
@@ -512,6 +532,8 @@ async function runMemoryDream(
512
532
  summaryPath,
513
533
  };
514
534
  });
535
+
536
+ return { result, usage: response.usage };
515
537
  }
516
538
 
517
539
  async function loadMemoryPrompt(cwd: string): Promise<string | undefined> {
@@ -962,6 +984,7 @@ async function selectDreamModel(ctx: ExtensionContext): Promise<ModelSelection>
962
984
  model: ctx.model,
963
985
  apiKey: auth.apiKey,
964
986
  headers: auth.headers,
987
+ env: auth.env,
965
988
  };
966
989
  }
967
990
 
@@ -510,7 +510,7 @@ export async function resolveModels(
510
510
  requestedModels: string[],
511
511
  currentThinkingLevel: ReviewThinkingLevel,
512
512
  ): Promise<ResolvedReviewModel[]> {
513
- ctx.modelRegistry.refresh();
513
+ await ctx.modelRegistry.refresh();
514
514
  const currentProvider = typeof ctx.model?.provider === "string" ? ctx.model.provider : undefined;
515
515
  const currentModelId = ctx.model?.id;
516
516
  const allModels = ctx.modelRegistry.getAll();
@@ -63,7 +63,7 @@ Only flag issues with a concrete exploit path or trust-boundary failure introduc
63
63
  suffix: " specializing in reuse analysis",
64
64
  qualifier: " reuse",
65
65
  context: `Review the changes for potential reuse issues, such as:
66
- 1. Search for existing capabilities that could replace newly written code: standard library APIs, native platform features, already-installed dependencies, and existing utilities/helpers. Start with ripgrep-style searches (use the grep tool first), then inspect utility directories, shared modules, and adjacent files.
66
+ 1. Search for existing capabilities that could replace newly written code: standard library APIs, native platform features, already-installed dependencies, and existing utilities/helpers. Search for relevant names and behavior, then go beyond string matches by inspecting adjacent files, utility files and directories, and shared modules.
67
67
  2. Flag any new function that duplicates existing functionality. Suggest the existing function, API, or feature to use instead.
68
68
  3. Flag any inline logic that could use an existing capability — hand-rolled standard-library behavior, string manipulation, manual path handling, custom environment checks, ad-hoc type guards, native platform features, and similar patterns are common candidates.
69
69
  4. Flag new dependencies when the standard library, runtime/platform, or an already-installed dependency provides the same capability or behavior.
@@ -76,7 +76,7 @@ Only flag issues with a concrete exploit path or trust-boundary failure introduc
76
76
  1. Redundant state: state that duplicates existing state, cached values that could be derived, observers/effects that could be direct calls.
77
77
  2. Parameter sprawl: adding new parameters to a function instead of generalizing or restructuring existing ones.
78
78
  3. Copy-paste with slight variation: near-duplicate code blocks that should be unified with a shared abstraction.
79
- 4. Leaky abstractions: exposing internal details that should be encapsulated, or breaking existing abstraction boundaries.
79
+ 4. Layering and leaky abstractions: logic that lives in the wrong layer, exposes internal details that should be encapsulated, or breaks existing abstraction boundaries.
80
80
  5. Stringly-typed code: using raw strings where constants, enums (string unions), or branded types already exist in the codebase.
81
81
  6. Simplicity/YAGNI: prefer simple, direct solutions over wrappers, abstractions, configuration, options, extensibility, or scaffolding without clear reuse value or explicit need. Prefer deletion or direct code until the second use appears.
82
82
  7. Shrinkage: flag code that preserves behavior with fewer branches, lines, moving parts, or custom helpers. Do not shrink away input validation at trust boundaries, data-loss error handling, security measures, or accessibility basics.
@@ -141,10 +141,10 @@ export function buildProjectReviewGuidelinesSection(projectGuidelines: string |
141
141
  export const SUBMIT_TOOL_RETRY_PROMPT = `You did not call {SUBMIT_TOOL} as instructed. You must call that tool exactly once with the final payload. Do not output any text, only call the {SUBMIT_TOOL} when you're done.`;
142
142
 
143
143
  export const REVIEW_OUTPUT_CONTRACT_PROMPT = `Requirements:
144
- - Never output findings or notes as text or write them to files.
144
+ - Never output findings as text or write them to files.
145
145
  - Always call submit_review exactly once as your final action.
146
146
  - If no issues are found, pass an empty array of findings to submit_review.
147
- - If uncertain, pass a note to submit_review.`;
147
+ - Omit uncertain or speculative findings.`;
148
148
 
149
149
  export const REVIEW_FOCUS_PROMPT = `You are an expert code reviewer{FOCUS_SUFFIX}.
150
150
 
@@ -35,11 +35,6 @@ const SUBMIT_REVIEW_PARAMS = Type.Object(
35
35
  ),
36
36
  { description: "Final review findings" },
37
37
  ),
38
- note: Type.Optional(
39
- Type.String({
40
- description: "Optional note when submitting zero findings or explaining uncertainty.",
41
- }),
42
- ),
43
38
  },
44
39
  { additionalProperties: false },
45
40
  );
@@ -51,6 +46,7 @@ const submitReviewTool = defineTool({
51
46
  promptSnippet: "Submit the final review findings",
52
47
  promptGuidelines: ["Use submit_review exactly once as your final action."],
53
48
  parameters: SUBMIT_REVIEW_PARAMS,
49
+ constrainedSampling: { type: "json_schema", strict: "prefer" },
54
50
 
55
51
  async execute(_toolCallId, params) {
56
52
  return {
@@ -44,6 +44,7 @@ const submitTriageTool = defineTool({
44
44
  promptSnippet: "Submit the final PR feedback triage",
45
45
  promptGuidelines: ["Use submit_triage exactly once as your final action."],
46
46
  parameters: SUBMIT_TRIAGE_PARAMS,
47
+ constrainedSampling: { type: "json_schema", strict: "prefer" },
47
48
 
48
49
  async execute(_toolCallId, params) {
49
50
  return {