@shanepadgett/tau-agent 0.33.1 → 0.34.0

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 (190) hide show
  1. package/docs/context.md +29 -20
  2. package/docs/extending-tau-agent.md +63 -24
  3. package/extensions/appshot/index.ts +112 -111
  4. package/extensions/aside/README.md +7 -0
  5. package/extensions/aside/index.ts +168 -0
  6. package/extensions/aside/panel.ts +83 -0
  7. package/extensions/branch/index.ts +38 -21
  8. package/extensions/cache-diagnostics/index.ts +70 -58
  9. package/extensions/checkpoint/README.md +9 -0
  10. package/extensions/checkpoint/checkpoint-budget.ts +79 -0
  11. package/extensions/checkpoint/checkpoint.ts +277 -0
  12. package/extensions/checkpoint/index.ts +102 -0
  13. package/extensions/checkpoint/messages.ts +169 -0
  14. package/extensions/checkpoint/prompt.ts +24 -0
  15. package/extensions/checkpoint/settings.ts +27 -0
  16. package/extensions/commit/commit-effort.ts +3 -3
  17. package/extensions/context/README.md +12 -8
  18. package/extensions/context/definitions.ts +178 -55
  19. package/extensions/context/index.ts +192 -160
  20. package/extensions/context/panel.ts +57 -37
  21. package/extensions/context/sync.ts +139 -89
  22. package/extensions/context/validation.ts +37 -25
  23. package/extensions/effort/README.md +1 -1
  24. package/extensions/effort/index.ts +3 -3
  25. package/extensions/effort/state.ts +3 -18
  26. package/extensions/explore/guidance.ts +6 -47
  27. package/extensions/explore/index.ts +38 -53
  28. package/extensions/explore/{ast/read → read}/hook.ts +12 -7
  29. package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
  30. package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
  31. package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
  32. package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
  33. package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
  34. package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
  35. package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
  36. package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
  37. package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
  38. package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
  39. package/extensions/explore/tools/target-params.ts +9 -0
  40. package/extensions/footer/index.ts +75 -56
  41. package/extensions/handoff/index.ts +8 -30
  42. package/extensions/image-gen/index.ts +46 -40
  43. package/extensions/patch/executor.ts +190 -135
  44. package/extensions/patch/matcher.ts +82 -48
  45. package/extensions/patch/parser.ts +149 -105
  46. package/extensions/patch/render.ts +32 -39
  47. package/extensions/patch/summary.ts +30 -26
  48. package/extensions/ready/README.md +7 -0
  49. package/extensions/ready/index.ts +58 -0
  50. package/extensions/ready/model.ts +128 -0
  51. package/extensions/ready/packs.ts +272 -0
  52. package/extensions/ready/render.ts +445 -0
  53. package/extensions/ready/scan.ts +760 -0
  54. package/extensions/reference/panel.ts +28 -16
  55. package/extensions/review/index.ts +63 -51
  56. package/extensions/review/panel.ts +5 -34
  57. package/extensions/runtime-context/context.ts +30 -26
  58. package/extensions/script-runner/README.md +2 -2
  59. package/extensions/script-runner/index.ts +122 -82
  60. package/extensions/silent-command-runner/index.ts +38 -23
  61. package/extensions/subagent/agents/context-sync.md +185 -33
  62. package/extensions/subagent/agents/scout.md +2 -2
  63. package/extensions/subagent/agents.ts +137 -84
  64. package/extensions/subagent/cmux-dashboard.ts +39 -30
  65. package/extensions/subagent/index.ts +79 -59
  66. package/extensions/subagent/resume.ts +1 -1
  67. package/extensions/subagent/run.ts +304 -193
  68. package/extensions/subagent/runtime.ts +781 -343
  69. package/extensions/tau/index.ts +47 -38
  70. package/extensions/tau-help/help.md +14 -6
  71. package/extensions/tool-loader/README.md +4 -2
  72. package/extensions/tool-loader/index.ts +77 -52
  73. package/extensions/web/index.ts +6 -3
  74. package/extensions/web/webfetch.ts +114 -86
  75. package/package.json +3 -3
  76. package/schemas/tau.schema.json +20 -33
  77. package/shared/bounded-text-result.ts +1 -0
  78. package/shared/checkpoint-visibility.ts +9 -0
  79. package/shared/events.ts +28 -10
  80. package/shared/isolated-session.ts +71 -29
  81. package/shared/model-effort.ts +9 -19
  82. package/shared/model-fallback/index.ts +66 -41
  83. package/shared/settings/load.ts +45 -38
  84. package/shared/temporary-output-store.ts +16 -10
  85. package/{extensions/explore → src}/ast/adapter.ts +11 -2
  86. package/{extensions/explore → src}/ast/engine.ts +54 -38
  87. package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
  88. package/{extensions/explore → src}/ast/format/context.ts +3 -6
  89. package/src/ast/format/deps.ts +94 -0
  90. package/{extensions/explore → src}/ast/format/discover.ts +2 -2
  91. package/{extensions/explore → src}/ast/format/impact.ts +9 -11
  92. package/{extensions/explore → src}/ast/format/outline.ts +2 -2
  93. package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
  94. package/{extensions/explore → src}/ast/format/show.ts +1 -1
  95. package/src/ast/format/targeted-result.ts +20 -0
  96. package/src/ast/grammars/c_sharp.wasm +0 -0
  97. package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
  98. package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
  99. package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
  100. package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
  101. package/{extensions/explore → src}/ast/ir.ts +6 -0
  102. package/src/ast/languages/csharp-file-deps.ts +90 -0
  103. package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
  104. package/src/ast/languages/dotted-index.ts +131 -0
  105. package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
  106. package/src/ast/languages/fixtures/sample.cs +53 -0
  107. package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
  108. package/src/ast/languages/fixtures/sample.go +37 -0
  109. package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
  110. package/src/ast/languages/fixtures/sample.java +40 -0
  111. package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
  112. package/src/ast/languages/fixtures/sample.kt +31 -0
  113. package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
  114. package/src/ast/languages/fixtures/sample.md +23 -0
  115. package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
  116. package/src/ast/languages/fixtures/sample.rs +54 -0
  117. package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
  118. package/src/ast/languages/fixtures/sample.swift +45 -0
  119. package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
  120. package/src/ast/languages/fixtures/sample.ts +45 -0
  121. package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
  122. package/src/ast/languages/fixtures/sample.tsx +36 -0
  123. package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
  124. package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
  125. package/{extensions/explore → src}/ast/languages/go.ts +21 -3
  126. package/{extensions/explore → src}/ast/languages/java.ts +5 -38
  127. package/src/ast/languages/jvm-file-deps.ts +135 -0
  128. package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
  129. package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
  130. package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
  131. package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
  132. package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
  133. package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
  134. package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
  135. package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
  136. package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
  137. package/{extensions/explore → src}/ast/markdown.ts +72 -47
  138. package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
  139. package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
  140. package/{extensions/explore → src}/ast/queries/context.ts +116 -80
  141. package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
  142. package/src/ast/queries/impact.ts +291 -0
  143. package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
  144. package/{extensions/explore → src}/ast/queries/show.ts +46 -42
  145. package/{extensions/explore → src}/ast/query.ts +1 -1
  146. package/{extensions/explore → src}/ast/registry.ts +1 -2
  147. package/{extensions/explore → src}/ast/scan.ts +1 -1
  148. package/src/ast/session.ts +50 -0
  149. package/{extensions/explore → src}/ast/slice.ts +8 -0
  150. package/{extensions/explore → src/ast}/traverse.ts +32 -20
  151. package/src/file-injection/index.ts +547 -0
  152. package/src/image-generation/client.ts +50 -31
  153. package/src/image-generation/index.ts +177 -134
  154. package/src/index.ts +2 -0
  155. package/src/tool-loading/index.ts +58 -0
  156. package/extensions/context/evidence.ts +0 -517
  157. package/extensions/context/projection.ts +0 -141
  158. package/extensions/context/state.ts +0 -30
  159. package/extensions/context/write-scope.ts +0 -109
  160. package/extensions/explore/ast/format/deps.ts +0 -86
  161. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  162. package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
  163. package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
  164. package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
  165. package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
  166. package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
  167. package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
  168. package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
  169. package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
  170. package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
  171. package/extensions/explore/ast/languages/odin.ts +0 -407
  172. package/extensions/explore/ast/queries/impact.ts +0 -250
  173. package/extensions/explore/outline-injection.ts +0 -151
  174. package/extensions/explore/read/autoread.ts +0 -118
  175. package/extensions/working-memory/README.md +0 -17
  176. package/extensions/working-memory/checkpoint.ts +0 -242
  177. package/extensions/working-memory/index.ts +0 -341
  178. package/extensions/working-memory/memory.ts +0 -235
  179. package/extensions/working-memory/render.ts +0 -185
  180. package/extensions/working-memory/settings.ts +0 -38
  181. package/extensions/working-memory/state.ts +0 -158
  182. package/shared/autoread.ts +0 -234
  183. package/shared/context-messages.ts +0 -19
  184. package/shared/outline-injection.ts +0 -56
  185. /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
  186. /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
  187. /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
  188. /package/{extensions/explore → src}/ast/identity.ts +0 -0
  189. /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
  190. /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
@@ -2,19 +2,19 @@ import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
3
  import { basename } from "node:path";
4
4
  import { Type, type Static } from "typebox";
5
- import { truncateBoundedHead } from "../../../../shared/bounded-text-result.ts";
6
- import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
7
- import type { ExploreEngine } from "../engine.ts";
8
- import { formatShowBatch } from "../format/show.ts";
9
- import { showTargets, type ShowTargetInput } from "../queries/show.ts";
10
- import { stripLeadingAt } from "../../traverse.ts";
5
+ import { truncateBoundedHead } from "../../../shared/bounded-text-result.ts";
6
+ import type { ToolRowStateStore } from "../../../shared/tool-row-state.ts";
7
+ import type { ExploreEngine } from "../../../src/ast/engine.ts";
8
+ import { formatShowBatch } from "../../../src/ast/format/show.ts";
9
+ import { showTargets, type ShowTargetInput } from "../../../src/ast/queries/show.ts";
10
+ import { stripLeadingAt } from "../../../src/ast/traverse.ts";
11
11
  import { ExploreCallComponent, renderExploreResult, shrinkingListVariants, type ExploreToolDetails } from "./render.ts";
12
12
 
13
13
  const showTargetSchema = Type.Object(
14
14
  {
15
- path: Type.String({ description: "File containing the declaration" }),
16
- name: Type.String({ minLength: 1, description: "Declaration name; may be dotted (Type.method)" }),
17
- line: Type.Optional(Type.Integer({ minimum: 1, description: "1-indexed line covered by the declaration range" })),
15
+ path: Type.String({ description: "Defining file" }),
16
+ name: Type.String({ minLength: 1, description: "Decl name; dotted Type.method ok" }),
17
+ line: Type.Optional(Type.Integer({ minimum: 1, description: "1-based line inside decl range" })),
18
18
  },
19
19
  { additionalProperties: false },
20
20
  );
@@ -23,16 +23,15 @@ const showParams = Type.Object(
23
23
  {
24
24
  targets: Type.Array(showTargetSchema, {
25
25
  minItems: 1,
26
- description: "One or more path+name targets (optional line to disambiguate)",
26
+ description: "path+name targets (optional line pin)",
27
27
  }),
28
28
  view: StringEnum(["signature", "signatureWithDocs", "declaration", "declarationWithImports"] as const, {
29
- description:
30
- "signature omits docs and bodies; signatureWithDocs adds attached docs; declaration is exact source; declarationWithImports adds import statements that mention identifiers in the declaration",
29
+ description: "signature | signatureWithDocs | declaration | declarationWithImports",
31
30
  }),
32
31
  contextLines: Type.Optional(
33
32
  Type.Integer({
34
33
  minimum: 0,
35
- description: "Lines of source context before and after each declaration (view=declaration only)",
34
+ description: "Extra lines around decl (declaration only)",
36
35
  }),
37
36
  ),
38
37
  },
@@ -69,13 +68,11 @@ export function createShowTool(rowState: ToolRowStateStore, engineFor: (cwd: str
69
68
  name: "show",
70
69
  label: "show",
71
70
  description:
72
- "Return signatures, documented signatures, exact declarations, or declarations with related imports for path+name targets. Ambiguous or missing targets fail the whole batch with candidates — no partial bodies.",
73
- promptSnippet: "Retrieve signatures or exact declarations for path+name targets",
71
+ "Exact signature or source for path+name targets. Whole batch fails on any missing/ambiguous target (candidate list, no partials). Views: signature → signatureWithDocs → declaration → declarationWithImports. Over budget throws — request fewer targets (bodies are not truncated).",
72
+ promptSnippet: "Signature or source for path+name targets",
74
73
  promptGuidelines: [
75
- "Prefer signature when the contract is enough; use signatureWithDocs only when attached docs matter.",
76
- "Use declaration for exact implementation source; declarationWithImports when edit context needs imports.",
77
- "Disambiguate with path and optional line. Do not guess among candidates.",
78
- "If the batch exceeds the model-output budget, request fewer targets — show never truncates bodies.",
74
+ "Cheapest view that answers; declarationWithImports only when edits need imports.",
75
+ "Pin with path and line when names collide — do not guess.",
79
76
  ],
80
77
  parameters: showParams,
81
78
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -0,0 +1,9 @@
1
+ import { Type } from "typebox";
2
+
3
+ /** Parameters every declaration-targeting Explore tool accepts. */
4
+ export const targetParams = {
5
+ path: Type.String({ description: "Directory scope" }),
6
+ targetPath: Type.Optional(Type.String({ description: "Defining file when known" })),
7
+ name: Type.String({ minLength: 1, description: "Decl name; dotted Type.method ok" }),
8
+ line: Type.Optional(Type.Integer({ minimum: 1, description: "1-based line pin" })),
9
+ };
@@ -260,12 +260,37 @@ async function saveEnabled(ctx: ExtensionContext, enabled: boolean): Promise<voi
260
260
  await updateTauExtensionSettings("global", ctx, footerSettings, (current) => ({ ...current, enabled }));
261
261
  }
262
262
 
263
+ function countGitLine(
264
+ line: string,
265
+ counts: { staged: number; modified: number; untracked: number; conflicts: number },
266
+ ): void {
267
+ const x = line[0];
268
+ const y = line[1];
269
+ if (x === "?" && y === "?") {
270
+ counts.untracked += 1;
271
+ return;
272
+ }
273
+ if (isConflict(x, y)) {
274
+ counts.conflicts += 1;
275
+ return;
276
+ }
277
+ if (x && x !== " ") counts.staged += 1;
278
+ if (y && y !== " ") counts.modified += 1;
279
+ }
280
+
281
+ function formatGitCounts(counts: { staged: number; modified: number; untracked: number; conflicts: number }): string {
282
+ const parts = [
283
+ counts.staged ? `+${counts.staged}` : "",
284
+ counts.modified ? `~${counts.modified}` : "",
285
+ counts.untracked ? `?${counts.untracked}` : "",
286
+ counts.conflicts ? `!${counts.conflicts}` : "",
287
+ ].filter(Boolean);
288
+ return parts.length ? parts.join(" ") : "clean";
289
+ }
290
+
263
291
  function parseGitStatus(stdout: string): GitSummary | undefined {
264
292
  let branch = "";
265
- let staged = 0;
266
- let modified = 0;
267
- let untracked = 0;
268
- let conflicts = 0;
293
+ const counts = { staged: 0, modified: 0, untracked: 0, conflicts: 0 };
269
294
 
270
295
  for (const line of stdout.split("\n")) {
271
296
  if (!line) continue;
@@ -273,29 +298,11 @@ function parseGitStatus(stdout: string): GitSummary | undefined {
273
298
  branch = line.slice(3).split("...")[0]?.trim() ?? "";
274
299
  continue;
275
300
  }
276
-
277
- const x = line[0];
278
- const y = line[1];
279
- if (x === "?" && y === "?") {
280
- untracked += 1;
281
- continue;
282
- }
283
- if (isConflict(x, y)) {
284
- conflicts += 1;
285
- continue;
286
- }
287
- if (x && x !== " ") staged += 1;
288
- if (y && y !== " ") modified += 1;
301
+ countGitLine(line, counts);
289
302
  }
290
303
 
291
304
  if (!branch) return undefined;
292
- const parts = [
293
- staged ? `+${staged}` : "",
294
- modified ? `~${modified}` : "",
295
- untracked ? `?${untracked}` : "",
296
- conflicts ? `!${conflicts}` : "",
297
- ].filter(Boolean);
298
- return { branch, text: parts.length ? parts.join(" ") : "clean" };
305
+ return { branch, text: formatGitCounts(counts) };
299
306
  }
300
307
 
301
308
  function gitText(git: GitSummary | undefined): string {
@@ -307,7 +314,7 @@ function isConflict(x: string | undefined, y: string | undefined): boolean {
307
314
  return x === "U" || y === "U" || (x === "A" && y === "A") || (x === "D" && y === "D");
308
315
  }
309
316
 
310
- export function sessionCost(ctx: Pick<ExtensionContext, "sessionManager">): UsageSummary {
317
+ function sessionCost(ctx: Pick<ExtensionContext, "sessionManager">): UsageSummary {
311
318
  let usage = zeroUsage();
312
319
  for (const entry of ctx.sessionManager.getEntries()) {
313
320
  if (entry.type === "message" && entry.message.role === "assistant") {
@@ -380,7 +387,44 @@ async function scanDailyCost(): Promise<number> {
380
387
  return total;
381
388
  }
382
389
 
383
- export function dailyCostFromJsonl(
390
+ function usageIdentityFromEntry(
391
+ entry: Record<string, unknown>,
392
+ ): { usageValue: unknown; timestamp: number; identity: unknown[] } | undefined {
393
+ if (entry.type === "message") {
394
+ const message = asRecord(entry.message);
395
+ if (!message) return undefined;
396
+ const timestamp = timestampMs(message.timestamp, entry.timestamp);
397
+ if (message.role === "assistant" && asRecord(message.usage)) {
398
+ return {
399
+ usageValue: message.usage,
400
+ timestamp,
401
+ identity: ["assistant", message.provider, message.model],
402
+ };
403
+ }
404
+ if (message.role === "toolResult" && asRecord(message.usage)) {
405
+ return {
406
+ usageValue: message.usage,
407
+ timestamp,
408
+ identity: ["toolResult", message.toolName, message.toolCallId],
409
+ };
410
+ }
411
+ return undefined;
412
+ }
413
+ if ((entry.type === "compaction" || entry.type === "branch_summary") && asRecord(entry.usage)) {
414
+ return {
415
+ usageValue: entry.usage,
416
+ timestamp: timestampMs(undefined, entry.timestamp),
417
+ identity: [entry.type, entry.id],
418
+ };
419
+ }
420
+ return undefined;
421
+ }
422
+
423
+ function usageDedupeKey(identity: readonly unknown[], timestamp: number, usage: UsageSummary): string {
424
+ return [...identity, timestamp, usage.input, usage.output, usage.cacheRead, usage.cacheWrite, usage.cost].join(":");
425
+ }
426
+
427
+ function dailyCostFromJsonl(
384
428
  raw: string,
385
429
  range: { startMs: number; endMs: number },
386
430
  seen: Set<string> = new Set<string>(),
@@ -389,36 +433,11 @@ export function dailyCostFromJsonl(
389
433
  for (const line of raw.split("\n")) {
390
434
  const entry = parseRecord(line);
391
435
  if (!entry) continue;
392
- let usageValue: unknown;
393
- let timestamp: number;
394
- let identity: unknown[];
395
- if (entry.type === "message") {
396
- const message = asRecord(entry.message);
397
- if (!message) continue;
398
- timestamp = timestampMs(message.timestamp, entry.timestamp);
399
- if (message.role === "assistant" && asRecord(message.usage)) {
400
- usageValue = message.usage;
401
- identity = ["assistant", message.provider, message.model];
402
- } else if (message.role === "toolResult" && asRecord(message.usage)) {
403
- usageValue = message.usage;
404
- identity = ["toolResult", message.toolName, message.toolCallId];
405
- } else continue;
406
- } else if ((entry.type === "compaction" || entry.type === "branch_summary") && asRecord(entry.usage)) {
407
- usageValue = entry.usage;
408
- timestamp = timestampMs(undefined, entry.timestamp);
409
- identity = [entry.type, entry.id];
410
- } else continue;
411
- if (timestamp < range.startMs || timestamp >= range.endMs) continue;
412
- const usage = normalizeUsage(usageValue);
413
- const key = [
414
- ...identity,
415
- timestamp,
416
- usage.input,
417
- usage.output,
418
- usage.cacheRead,
419
- usage.cacheWrite,
420
- usage.cost,
421
- ].join(":");
436
+ const parsed = usageIdentityFromEntry(entry);
437
+ if (!parsed) continue;
438
+ if (parsed.timestamp < range.startMs || parsed.timestamp >= range.endMs) continue;
439
+ const usage = normalizeUsage(parsed.usageValue);
440
+ const key = usageDedupeKey(parsed.identity, parsed.timestamp, usage);
422
441
  if (seen.has(key)) continue;
423
442
  seen.add(key);
424
443
  total += usage.cost;
@@ -6,9 +6,9 @@ import {
6
6
  type ExtensionAPI,
7
7
  serializeConversation,
8
8
  } from "@earendil-works/pi-coding-agent";
9
- import { prepareAutoreadMessage, type AutoreadDetails } from "../../shared/autoread.ts";
10
9
  import { generateToolValidated, resolveCandidates } from "../../shared/model-fallback/index.ts";
11
10
  import { errorText } from "../../shared/text.ts";
11
+ import { prepareFileInjection } from "@shanepadgett/tau-agent";
12
12
  import { buildHandoffRequest, type HandoffDraft, handoffDraftFromToolInput, HANDOFF_TOOL } from "./model.ts";
13
13
 
14
14
  export default function handoffExtension(pi: ExtensionAPI): void {
@@ -92,38 +92,16 @@ export default function handoffExtension(pi: ExtensionAPI): void {
92
92
  return;
93
93
  }
94
94
 
95
- const batchId = randomUUID();
96
- const cwd = ctx.cwd;
95
+ const fileMessages = await prepareFileInjection(pi, {
96
+ cwd: ctx.cwd,
97
+ source: "handoff",
98
+ batchId: randomUUID(),
99
+ files: draft.files.map((path) => ({ path, mode: "full" as const })),
100
+ });
97
101
  const result = await ctx.newSession({
98
102
  parentSession: currentSessionFile,
99
103
  setup: async (sessionManager) => {
100
- const messages = await Promise.all(
101
- draft.files.map(async (path, index) => {
102
- const details = {
103
- rowId: `${batchId}:${index}`,
104
- path,
105
- cwd,
106
- source: "handoff",
107
- batchId,
108
- };
109
- try {
110
- return await prepareAutoreadMessage({
111
- ...details,
112
- signal: undefined,
113
- isLifecycleCurrent: () => true,
114
- });
115
- } catch (error) {
116
- const message = errorText(error);
117
- return {
118
- customType: "tau.autoread" as const,
119
- content: `${path}\nAutoread failed: ${message}`,
120
- display: true as const,
121
- details: { ...details, status: "failed", error: message } satisfies AutoreadDetails,
122
- };
123
- }
124
- }),
125
- );
126
- for (const message of messages) {
104
+ for (const message of fileMessages) {
127
105
  sessionManager.appendCustomMessageEntry(
128
106
  message.customType,
129
107
  message.content,
@@ -1,6 +1,8 @@
1
1
  import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
3
  generateImage,
4
+ registerDeferredToolGroup,
5
+ type DeferredToolGroup,
4
6
  type GeneratedImageResult,
5
7
  type GenerateImageRequest,
6
8
  type ImageGenerationContext,
@@ -31,51 +33,55 @@ type ImageGenParams = Static<typeof imageGenSchema>;
31
33
  type ImageGenDetails = Omit<GeneratedImageResult, "bytes">;
32
34
 
33
35
  export default function imageGenExtension(pi: ExtensionAPI): void {
34
- pi.registerTool(
35
- defineTool<typeof imageGenSchema, ImageGenDetails | undefined>({
36
- name: "image_gen",
37
- label: "Image Generation",
38
- description:
39
- "Generate a requested raster image or AI-edit existing images with OpenAI GPT Image or xAI Grok Imagine. Omit provider to follow the parent model; set it to openai or xai to override. Omit referenced_image_paths to generate; pass one to three local paths to edit or compose. Omit path to use Tau's external image store; pass path only when the user explicitly requests a repository file or other destination. Returns the image for inspection.",
40
- parameters: imageGenSchema,
41
- async execute(_toolCallId, params: ImageGenParams, signal, onUpdate, ctx) {
42
- await onUpdate?.({
43
- content: [
44
- {
45
- type: "text",
46
- text: "Processing image request...",
47
- },
48
- ],
49
- details: undefined,
50
- });
51
- const request = {
52
- prompt: params.prompt,
53
- provider: params.provider,
54
- path: params.path,
55
- referencedImagePaths: params.referenced_image_paths,
56
- signal,
57
- } satisfies GenerateImageRequest;
58
- const { bytes, ...details } = await generateImage(ctx satisfies ImageGenerationContext, request);
59
- const verb = details.operation === "generate" ? "Generated" : "Edited";
60
- if (bytes.length > MAX_INLINE_BYTES) {
61
- return {
36
+ registerDeferredToolGroup(pi, {
37
+ id: "image",
38
+ description: "Raster image generation and editing",
39
+ tools: [
40
+ defineTool<typeof imageGenSchema, ImageGenDetails | undefined>({
41
+ name: "image_gen",
42
+ label: "Image Generation",
43
+ description:
44
+ "Generate a requested raster image or AI-edit existing images with OpenAI GPT Image or xAI Grok Imagine. Omit provider to follow the parent model; set it to openai or xai to override. Omit referenced_image_paths to generate; pass one to three local paths to edit or compose. Omit path to use Tau's external image store; pass path only when the user explicitly requests a repository file or other destination. Returns the image for inspection.",
45
+ parameters: imageGenSchema,
46
+ async execute(_toolCallId, params: ImageGenParams, signal, onUpdate, ctx) {
47
+ await onUpdate?.({
62
48
  content: [
63
49
  {
64
50
  type: "text",
65
- text: `${verb} image saved to ${details.path}. The image exceeds the 12 MiB attachment limit, so it was not added to model context.`,
51
+ text: "Processing image request...",
66
52
  },
67
53
  ],
54
+ details: undefined,
55
+ });
56
+ const request = {
57
+ prompt: params.prompt,
58
+ provider: params.provider,
59
+ path: params.path,
60
+ referencedImagePaths: params.referenced_image_paths,
61
+ signal,
62
+ } satisfies GenerateImageRequest;
63
+ const { bytes, ...details } = await generateImage(ctx satisfies ImageGenerationContext, request);
64
+ const verb = details.operation === "generate" ? "Generated" : "Edited";
65
+ if (bytes.length > MAX_INLINE_BYTES) {
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: `${verb} image saved to ${details.path}. The image exceeds the 12 MiB attachment limit, so it was not added to model context.`,
71
+ },
72
+ ],
73
+ details,
74
+ };
75
+ }
76
+ return {
77
+ content: [
78
+ { type: "text", text: `${verb} image saved to ${details.path}` },
79
+ { type: "image", data: bytes.toString("base64"), mimeType: details.mimeType },
80
+ ],
68
81
  details,
69
82
  };
70
- }
71
- return {
72
- content: [
73
- { type: "text", text: `${verb} image saved to ${details.path}` },
74
- { type: "image", data: bytes.toString("base64"), mimeType: details.mimeType },
75
- ],
76
- details,
77
- };
78
- },
79
- }),
80
- );
83
+ },
84
+ }),
85
+ ],
86
+ } satisfies DeferredToolGroup);
81
87
  }