@intentic/sandbox-contract 1.328.0 → 1.330.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 (126) hide show
  1. package/dist/chores/probes.d.ts.map +1 -1
  2. package/dist/chores/probes.js +1 -2
  3. package/dist/chores/probes.js.map +1 -1
  4. package/dist/contracts/agents.contract.d.ts +69 -0
  5. package/dist/contracts/agents.contract.d.ts.map +1 -1
  6. package/dist/contracts/capabilities.contract.d.ts +8 -0
  7. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  8. package/dist/contracts/device.contract.d.ts +42 -0
  9. package/dist/contracts/device.contract.d.ts.map +1 -1
  10. package/dist/contracts/device.contract.js +3 -0
  11. package/dist/contracts/device.contract.js.map +1 -1
  12. package/dist/contracts/runs.contract.d.ts +35 -0
  13. package/dist/contracts/runs.contract.d.ts.map +1 -0
  14. package/dist/contracts/runs.contract.js +22 -0
  15. package/dist/contracts/runs.contract.js.map +1 -0
  16. package/dist/contracts/settings.contract.d.ts +8 -0
  17. package/dist/contracts/settings.contract.d.ts.map +1 -1
  18. package/dist/contracts/system.contract.d.ts +42 -30
  19. package/dist/contracts/system.contract.d.ts.map +1 -1
  20. package/dist/contracts/system.contract.js +10 -1
  21. package/dist/contracts/system.contract.js.map +1 -1
  22. package/dist/contracts/workspace.contract.d.ts +37 -0
  23. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  24. package/dist/contracts/workspace.contract.js +11 -1
  25. package/dist/contracts/workspace.contract.js.map +1 -1
  26. package/dist/events/system-events.d.ts +8 -0
  27. package/dist/events/system-events.d.ts.map +1 -1
  28. package/dist/index.d.ts +204 -31
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/protocol/doors/listener-protocol.d.ts +0 -1
  33. package/dist/protocol/doors/listener-protocol.d.ts.map +1 -1
  34. package/dist/protocol/doors/listener-protocol.js +3 -2
  35. package/dist/protocol/doors/listener-protocol.js.map +1 -1
  36. package/dist/protocol/raw/raw-routes.d.ts +29 -0
  37. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  38. package/dist/protocol/raw/raw-routes.js +8 -0
  39. package/dist/protocol/raw/raw-routes.js.map +1 -1
  40. package/dist/schemas/agents-list.d.ts +4 -0
  41. package/dist/schemas/agents-list.d.ts.map +1 -1
  42. package/dist/schemas/agents.d.ts +13 -0
  43. package/dist/schemas/agents.d.ts.map +1 -1
  44. package/dist/schemas/agents.js +4 -3
  45. package/dist/schemas/agents.js.map +1 -1
  46. package/dist/schemas/capabilities.d.ts +12 -0
  47. package/dist/schemas/capabilities.d.ts.map +1 -1
  48. package/dist/schemas/capabilities.js +1 -0
  49. package/dist/schemas/capabilities.js.map +1 -1
  50. package/dist/schemas/device-artifacts.d.ts +67 -0
  51. package/dist/schemas/device-artifacts.d.ts.map +1 -0
  52. package/dist/schemas/device-artifacts.js +61 -0
  53. package/dist/schemas/device-artifacts.js.map +1 -0
  54. package/dist/schemas/hosts.d.ts +2 -0
  55. package/dist/schemas/hosts.d.ts.map +1 -1
  56. package/dist/schemas/hosts.js +2 -1
  57. package/dist/schemas/hosts.js.map +1 -1
  58. package/dist/schemas/run-targets.d.ts +82 -0
  59. package/dist/schemas/run-targets.d.ts.map +1 -0
  60. package/dist/schemas/run-targets.js +49 -0
  61. package/dist/schemas/run-targets.js.map +1 -0
  62. package/dist/schemas/settings.d.ts +8 -0
  63. package/dist/schemas/settings.d.ts.map +1 -1
  64. package/dist/schemas/settings.js +2 -1
  65. package/dist/schemas/settings.js.map +1 -1
  66. package/dist/schemas/speech.d.ts +75 -0
  67. package/dist/schemas/speech.d.ts.map +1 -0
  68. package/dist/schemas/speech.js +66 -0
  69. package/dist/schemas/speech.js.map +1 -0
  70. package/dist/schemas/terminal.d.ts +4 -0
  71. package/dist/schemas/terminal.d.ts.map +1 -1
  72. package/dist/schemas/terminal.js +4 -0
  73. package/dist/schemas/terminal.js.map +1 -1
  74. package/dist/schemas/turn-break.d.ts +7 -1
  75. package/dist/schemas/turn-break.d.ts.map +1 -1
  76. package/dist/schemas/turn-break.js +4 -2
  77. package/dist/schemas/turn-break.js.map +1 -1
  78. package/dist/schemas/workspace/workspace-graph.d.ts +133 -0
  79. package/dist/schemas/workspace/workspace-graph.d.ts.map +1 -0
  80. package/dist/schemas/workspace/workspace-graph.js +83 -0
  81. package/dist/schemas/workspace/workspace-graph.js.map +1 -0
  82. package/dist/schemas/workspace/workspace-repos.d.ts +0 -39
  83. package/dist/schemas/workspace/workspace-repos.d.ts.map +1 -1
  84. package/dist/schemas/workspace/workspace-repos.js +0 -15
  85. package/dist/schemas/workspace/workspace-repos.js.map +1 -1
  86. package/dist/state/definition.d.ts +12 -0
  87. package/dist/state/definition.d.ts.map +1 -1
  88. package/dist/state/workspace-state.d.ts +2 -1
  89. package/dist/state/workspace-state.d.ts.map +1 -1
  90. package/dist/state/workspace-state.js +5 -1
  91. package/dist/state/workspace-state.js.map +1 -1
  92. package/dist/text/transcript-page.d.ts +5 -0
  93. package/dist/text/transcript-page.d.ts.map +1 -0
  94. package/dist/text/transcript-page.js +17 -0
  95. package/dist/text/transcript-page.js.map +1 -0
  96. package/package.json +5 -5
  97. package/src/chores/probes.ts +1 -2
  98. package/src/contracts/device.contract.ts +10 -0
  99. package/src/contracts/runs.contract.ts +26 -0
  100. package/src/contracts/system.contract.ts +13 -0
  101. package/src/contracts/workspace.contract.ts +12 -1
  102. package/src/index.ts +8 -1
  103. package/src/models/agent-runtimes.ts +2 -2
  104. package/src/protocol/doors/listener-protocol.ts +5 -4
  105. package/src/protocol/raw/raw-routes.ts +15 -0
  106. package/src/schemas/agents.ts +6 -3
  107. package/src/schemas/capabilities.ts +4 -0
  108. package/src/schemas/device-artifacts.ts +99 -0
  109. package/src/schemas/hosts.ts +4 -1
  110. package/src/schemas/run-targets.ts +93 -0
  111. package/src/schemas/settings.ts +6 -1
  112. package/src/schemas/speech.ts +96 -0
  113. package/src/schemas/terminal.ts +4 -0
  114. package/src/schemas/turn-break.ts +18 -9
  115. package/src/schemas/workspace/workspace-graph.ts +116 -0
  116. package/src/schemas/workspace/workspace-repos.ts +0 -22
  117. package/src/state/workspace-state.test.ts +42 -1
  118. package/src/state/workspace-state.ts +12 -1
  119. package/src/text/transcript-page.test.ts +39 -0
  120. package/src/text/transcript-page.ts +37 -0
  121. package/dist/text/whisper.d.ts +0 -3
  122. package/dist/text/whisper.d.ts.map +0 -1
  123. package/dist/text/whisper.js +0 -11
  124. package/dist/text/whisper.js.map +0 -1
  125. package/src/text/whisper.test.ts +0 -18
  126. package/src/text/whisper.ts +0 -17
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+
3
+ // A monorepo's architecture as its files state it: the packages pnpm-workspace.yaml names, which siblings each declares
4
+ // and what its own files do with them, and, inside one package, how its modules import each other. Facts only: what to
5
+ // draw, and what counts as a finding, is the reader's.
6
+
7
+ // A component of the repository's map, docs/architecture/repo.json, which the documentation extension writes.
8
+ export const WorkspaceComponentSchema = z.object({
9
+ id: z.string().describe("The component's id."),
10
+ name: z.string().describe("What the map calls it."),
11
+ accent: z.string().optional().describe('The map\'s colour slot for it: "1" to "5", or "neutral".'),
12
+ });
13
+ export type WorkspaceComponent = z.infer<typeof WorkspaceComponentSchema>;
14
+
15
+ // A pnpm-workspace package, discovered from pnpm-workspace.yaml's globs. group is the top-level dir segment; component
16
+ // is the repo.json component listing its dir, when the repository has a map.
17
+ export const WorkspacePackageSchema = z.object({
18
+ name: z.string().describe("The name the package declares."),
19
+ dir: z.string().describe("Where it lives, relative to the repository."),
20
+ group: z.string().describe("The top-level folder it sits under, which is what a diagram colours by."),
21
+ component: z.string().optional().describe("The component of docs/architecture/repo.json that lists it, by id. Absent without a map."),
22
+ });
23
+ export type WorkspacePackage = z.infer<typeof WorkspacePackageSchema>;
24
+ export const WorkspaceDepTypeSchema = z.enum(["prod", "dev", "peer"]);
25
+ export type WorkspaceDepType = z.infer<typeof WorkspaceDepTypeSchema>;
26
+
27
+ // What the dependent's own files do with a sibling it declares, read from them: the strongest use found wins.
28
+ export const WorkspaceDepUsageSchema = z
29
+ .enum(["code", "types", "tooling", "reference", "none"])
30
+ .describe(
31
+ "What the dependent's own files do with it: `code`, a shipped file imports a value from it; `types`, shipped files import only its types; `tooling`, only its tests, scripts and config files use it; `reference`, a shipped file names it without importing it (a Dockerfile copying its build, a manifest listing its files); `none`, nothing in the dependent names it.",
32
+ );
33
+ export type WorkspaceDepUsage = z.infer<typeof WorkspaceDepUsageSchema>;
34
+
35
+ // A workspace-internal dependency edge: from depends on to, typed by which dependency block declared it.
36
+ export const WorkspaceDepEdgeSchema = z.object({
37
+ from: z.string().describe("The package that depends."),
38
+ to: z.string().describe("The package it depends on."),
39
+ type: WorkspaceDepTypeSchema.describe("Which kind of dependency declared it."),
40
+ usage: WorkspaceDepUsageSchema.optional(),
41
+ });
42
+ export type WorkspaceDepEdge = z.infer<typeof WorkspaceDepEdgeSchema>;
43
+ export const WorkspaceGraphSchema = z.object({
44
+ packages: z.array(WorkspacePackageSchema).describe("Every package in the repository."),
45
+ edges: z.array(WorkspaceDepEdgeSchema).describe("Which of them use which. Pure data: how to lay it out is yours to decide."),
46
+ components: z
47
+ .array(WorkspaceComponentSchema)
48
+ .optional()
49
+ .describe("The components of docs/architecture/repo.json, in the map's order. Absent when the repository has no map."),
50
+ });
51
+ export type WorkspaceGraph = z.infer<typeof WorkspaceGraphSchema>;
52
+
53
+ // layers.json beside a package's package.json: the layers its modules sit in, lowest first. A module may import its own
54
+ // layer and any below it; an import reaching higher is a finding, and so is a cycle between units of one layer.
55
+ export const PackageLayerSchema = z.object({
56
+ name: z.string().min(1).describe("The layer's name."),
57
+ about: z.string().optional().describe("What sits in it, in a sentence."),
58
+ units: z
59
+ .array(z.string().min(1))
60
+ .describe(
61
+ "Path prefixes under the root it holds: a directory, a subdirectory, or a module named without its extension. A file belongs to the layer whose unit matches it longest.",
62
+ ),
63
+ });
64
+ export type PackageLayer = z.infer<typeof PackageLayerSchema>;
65
+ export const PackageLayersFileSchema = z.object({
66
+ root: z.string().optional().describe('The directory the units are relative to, from the package. Default "src".'),
67
+ shelves: z.array(z.string()).optional().describe("Top-level directories whose every subdirectory is a unit of its own, such as one per feature."),
68
+ surface: z
69
+ .array(z.string())
70
+ .optional()
71
+ .describe('File-name patterns, `*` a wildcard, of modules that sit above every layer, such as "*.routes.ts". Nothing below may import one.'),
72
+ layers: z.array(PackageLayerSchema).min(1).describe("The layers, lowest first."),
73
+ });
74
+ export type PackageLayersFile = z.infer<typeof PackageLayersFileSchema>;
75
+
76
+ export const PackageModulesParamSchema = z.object({
77
+ repo: z.string().describe("Which repository."),
78
+ package: z.string().min(1).describe("Which of its workspace packages, by the name it declares."),
79
+ });
80
+ export const PackageUnitSchema = z.object({
81
+ id: z
82
+ .string()
83
+ .describe(
84
+ 'The unit: a directory under the root, a shelf\'s subdirectory ("features/chat"), a unit layers.json names, "(surface)" for every surface module, or "(root)" for the modules directly in the root, which no rule judges.',
85
+ ),
86
+ modules: z.number().int().nonnegative().describe("How many source modules it holds."),
87
+ layer: z
88
+ .number()
89
+ .int()
90
+ .nonnegative()
91
+ .optional()
92
+ .describe("The declared layer it sits in, by index, lowest 0. Absent when the package declares no layers, or none places it."),
93
+ surface: z.literal(true).optional().describe("The surface modules' unit, above every layer."),
94
+ });
95
+ export type PackageUnit = z.infer<typeof PackageUnitSchema>;
96
+ export const PackageUnitEdgeSchema = z.object({
97
+ from: z.string().describe("The importing unit."),
98
+ to: z.string().describe("The unit it imports."),
99
+ imports: z.number().int().positive().describe("How many value imports make the edge."),
100
+ sites: z.array(z.string()).describe("Up to five of them, as `path:line` relative to the root."),
101
+ });
102
+ export type PackageUnitEdge = z.infer<typeof PackageUnitEdgeSchema>;
103
+ export const PackageModulesSchema = z.object({
104
+ package: z.string().describe("The package."),
105
+ root: z.string().describe("The directory read, relative to the repository."),
106
+ layers: z
107
+ .array(PackageLayerSchema.pick({ name: true, about: true }))
108
+ .optional()
109
+ .describe("Its declared layers, lowest first. Absent when it has no layers.json."),
110
+ invalid: z.string().optional().describe("Why its layers.json could not be read, when it has one that does not parse."),
111
+ units: z.array(PackageUnitSchema).describe("Every unit with a module in it."),
112
+ edges: z
113
+ .array(PackageUnitEdgeSchema)
114
+ .describe("Value imports between units: type-only imports erase and tests import their subject, so neither counts."),
115
+ });
116
+ export type PackageModules = z.infer<typeof PackageModulesSchema>;
@@ -91,28 +91,6 @@ export const RepoAppSchema = z.object({
91
91
  export type RepoApp = z.infer<typeof RepoAppSchema>;
92
92
  export const AppsListSchema = z.object({ apps: z.array(RepoAppSchema).describe("The apps in this repository.") });
93
93
  export type AppsList = z.infer<typeof AppsListSchema>;
94
- // A pnpm-workspace package, discovered from pnpm-workspace.yaml's globs. group is the top-level dir segment, the
95
- // dependencies view's coloring axis.
96
- export const WorkspacePackageSchema = z.object({
97
- name: z.string().describe("The name the package declares."),
98
- dir: z.string().describe("Where it lives, relative to the repository."),
99
- group: z.string().describe("The top-level folder it sits under, which is what a diagram colours by."),
100
- });
101
- export type WorkspacePackage = z.infer<typeof WorkspacePackageSchema>;
102
- export const WorkspaceDepTypeSchema = z.enum(["prod", "dev", "peer"]);
103
- export type WorkspaceDepType = z.infer<typeof WorkspaceDepTypeSchema>;
104
- // A workspace-internal dependency edge: from depends on to, typed by which dependency block declared it.
105
- export const WorkspaceDepEdgeSchema = z.object({
106
- from: z.string().describe("The package that depends."),
107
- to: z.string().describe("The package it depends on."),
108
- type: WorkspaceDepTypeSchema.describe("Which kind of dependency declared it."),
109
- });
110
- export type WorkspaceDepEdge = z.infer<typeof WorkspaceDepEdgeSchema>;
111
- export const WorkspaceGraphSchema = z.object({
112
- packages: z.array(WorkspacePackageSchema).describe("Every package in the repository."),
113
- edges: z.array(WorkspaceDepEdgeSchema).describe("Which of them use which. Pure data: how to lay it out is yours to decide."),
114
- });
115
- export type WorkspaceGraph = z.infer<typeof WorkspaceGraphSchema>;
116
94
  // Path params for the per-repo apps routes: repo names the monorepo, validated like PanelRepoParam.
117
95
  export const RepoAppsParamSchema = z.object({ repo: z.string().describe("Which repository.") });
118
96
  export const AppParamSchema = z.object({
@@ -5,6 +5,7 @@ import {
5
5
  isLockedWorkspacePath,
6
6
  isReportedManifest,
7
7
  isReviewableLockedPath,
8
+ isUnversionedStatePath,
8
9
  LOCKED_STATE_ENTRIES,
9
10
  lockedWorkspaceEntry,
10
11
  PLAN_DOCUMENTS_DIR,
@@ -29,7 +30,13 @@ const AUTOMATIONS: readonly FileContribution[] = [
29
30
 
30
31
  describe(`staleQueryKeys`, () => {
31
32
  it(`maps a manifest write to the queries it makes stale`, () => {
32
- expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([`capabilities`, `extensions`, `environment`, `panels`, `manifests`]);
33
+ expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([
34
+ `capabilities`,
35
+ `extensions`,
36
+ `environment`,
37
+ `panels`,
38
+ `manifests`,
39
+ ]);
33
40
  });
34
41
 
35
42
  it(`refreshes the unreadable-manifest notice for the four files a person hand-edits`, () => {
@@ -299,6 +306,40 @@ describe(`isReviewableLockedPath`, () => {
299
306
  });
300
307
  });
301
308
 
309
+ // What lets a git-reading reaction skip a batch: the daemon's own churn under `.intentic` must not rescan every repo,
310
+ // while a tracked config file still has to.
311
+ describe(`isUnversionedStatePath`, () => {
312
+ it(`holds the machine state the daemon rewrites constantly`, () => {
313
+ expect(isUnversionedStatePath(`.intentic/local/privacy-log.json`)).toBe(true);
314
+ expect(isUnversionedStatePath(`.intentic/local/.privacy-log.json.55.3275.tmp`)).toBe(true);
315
+ expect(isUnversionedStatePath(`.intentic/records/extension-usage.json`)).toBe(true);
316
+ expect(isUnversionedStatePath(`.intentic/records/artifacts/browser/page.png`)).toBe(true);
317
+ // An unversioned file beside versioned ones in config is still invisible to git.
318
+ expect(isUnversionedStatePath(`.intentic/config/docs/index.json`)).toBe(true);
319
+ });
320
+
321
+ it(`lets every versioned entry through, files and directories alike`, () => {
322
+ for (const path of VERSIONED_STATE_PATHS) {
323
+ const inside = path.endsWith(`/`) ? `${path}nested/file.json` : path;
324
+ expect([inside, isUnversionedStatePath(inside)]).toEqual([inside, false]);
325
+ }
326
+ });
327
+
328
+ it(`never claims a path outside the workspace's own state dir`, () => {
329
+ expect(isUnversionedStatePath(`intentic/_sandbox/sandbox/src/main.ts`)).toBe(false);
330
+ expect(isUnversionedStatePath(`README.md`)).toBe(false);
331
+ // A repo's nested state dir is its project's content, which git does track.
332
+ expect(isUnversionedStatePath(`myrepo/.intentic/local/privacy-log.json`)).toBe(false);
333
+ expect(isUnversionedStatePath(`.intentic`)).toBe(false);
334
+ });
335
+
336
+ it(`reads a platform path and a dot-relative one the same as a posix one`, () => {
337
+ expect(isUnversionedStatePath(`.intentic\\local\\privacy-log.json`)).toBe(true);
338
+ expect(isUnversionedStatePath(`./.intentic/local/privacy-log.json`)).toBe(true);
339
+ expect(isUnversionedStatePath(`./.intentic/config/capabilities.json`)).toBe(false);
340
+ });
341
+ });
342
+
302
343
  describe(`VERSIONED_STATE_PATHS`, () => {
303
344
  // versioned exempts an entry from the `.intentic` exclusion; a mismarked credential can't be unmarked once
304
345
  // committed.
@@ -397,7 +397,7 @@ const STATE_FILES = [
397
397
  {
398
398
  path: ".intentic/local/cache/",
399
399
  invalidates: [],
400
- why: "Rebuildable indexes and caches, the iq index and its vector sidecar, the whisper model, fileq's derived/ markdown shadows of binary files; ignored by the watcher and recreated from carried workspace content.",
400
+ why: "Rebuildable indexes and caches, the iq index and its vector sidecar, the speech models an image does not bake, fileq's derived/ markdown shadows of binary files; ignored by the watcher and recreated from carried workspace content.",
401
401
  portability: "derived",
402
402
  },
403
403
  // Extension runtime scratch, one directory per extension via `extensionStateDir` below (the only way an extension
@@ -769,6 +769,17 @@ export const isReviewableLockedPath = (relPath: string): boolean => {
769
769
  return isLockedWorkspacePath(rel) && VERSIONED_STATE_PATHS.some((path) => (path.endsWith("/") ? rel.startsWith(path) : rel === path));
770
770
  };
771
771
 
772
+ // Machine state the root repo never tracks: everything under the workspace's own `.intentic/` but the `versioned`
773
+ // entries history.ts carves back out of the wholesale exclusion. A write here moves no git status, no landing and no
774
+ // review, so a reaction that exists to re-read git can pass over a batch made only of these. The daemon rewrites
775
+ // some of them every few seconds under load (the privacy ledger, extension usage, browser screenshots), and each
776
+ // one used to rescan every repo. Root-level only, like the exclusion it mirrors: a repo's own nested `.intentic` is
777
+ // its project's content. Accepts either slash.
778
+ export const isUnversionedStatePath = (relPath: string): boolean => {
779
+ const rel = relPath.replaceAll("\\", "/").replace(/^\.\//, "");
780
+ return rel.startsWith(`${STATE_DIR}/`) && !VERSIONED_STATE_PATHS.some((path) => (path.endsWith("/") ? rel.startsWith(path) : rel === path));
781
+ };
782
+
772
783
  // Every path this table declares, as a type; the daemon joins paths only through `statePath`
773
784
  // (workspace/state-paths.ts), which accepts one of these and nothing else, so renaming a store's file is a compile
774
785
  // error everywhere it's named rather than a view that silently stops refreshing.
@@ -0,0 +1,39 @@
1
+ import type { TranscriptRow, TranscriptTool } from "../events/transcript.js";
2
+ import { fitNestedTool, fitPageRow, PAGE_TEXT_CAP } from "./transcript-page.js";
3
+
4
+ // What a page carries of a row, which the daemon serves and the editor mirrors: the two have to agree to the byte.
5
+
6
+ const long = `x`.repeat(PAGE_TEXT_CAP + 500);
7
+ const call = (id: string, extra: Partial<TranscriptTool> = {}): TranscriptTool => ({
8
+ id,
9
+ name: `Bash`,
10
+ category: `execute`,
11
+ status: `completed`,
12
+ ...extra,
13
+ });
14
+
15
+ describe(`fitPageRow`, () => {
16
+ it(`hands back a row with no calls as it is`, () => {
17
+ const row: TranscriptRow = { role: `assistant`, text: `done` };
18
+ expect(fitPageRow(row)).toBe(row);
19
+ });
20
+
21
+ it(`keeps each output's start and counts a delegation's calls instead of carrying them`, () => {
22
+ const row: TranscriptRow = {
23
+ role: `assistant`,
24
+ text: ``,
25
+ tools: [call(`a`, { content: [{ type: `text`, text: long }] }), call(`b`, { name: `Agent`, children: [call(`c`), call(`d`)] })],
26
+ };
27
+ const fitted = fitPageRow(row);
28
+ expect(fitted.tools?.[0]?.content).toEqual([{ type: `text`, text: long.slice(0, PAGE_TEXT_CAP) }]);
29
+ expect(fitted.tools?.[1]?.children).toBeUndefined();
30
+ expect(fitted.tools?.[1]?.nested).toBe(2);
31
+ });
32
+ });
33
+
34
+ describe(`fitNestedTool`, () => {
35
+ it(`keeps every call under it, each output fitted at every depth`, () => {
36
+ const fitted = fitNestedTool(call(`a`, { children: [call(`b`, { content: [{ type: `text`, text: long }] })] }));
37
+ expect(fitted.children?.[0]?.content).toEqual([{ type: `text`, text: long.slice(0, PAGE_TEXT_CAP) }]);
38
+ });
39
+ });
@@ -0,0 +1,37 @@
1
+ import type { ToolCallContent } from "../events/requests.js";
2
+ import type { TranscriptRow, TranscriptTool } from "../events/transcript.js";
3
+
4
+ // WHAT A TRANSCRIPT PAGE CARRIES OF A ROW, written once for both ends: the daemon fits every row of a page it serves to
5
+ // this (sessions/agent-transcript.ts), and the editor fits the rows it mirrors to disk the same way, so a reopened chat
6
+ // paints from its mirror exactly the rows the daemon's page then hands back, and keeps them rather than drawing them
7
+ // again (transcriptState.ts rebuildKeeping matches rows by their contents).
8
+
9
+ // What a page carries of one tool output: the pane truncates text at 4000 characters of its own accord
10
+ // (toolPresentation.ts TEXT_CAP), so twice that leaves room to raise that cap without a second round trip.
11
+ export const PAGE_TEXT_CAP = 8_000;
12
+
13
+ const fitContent = (entry: ToolCallContent): ToolCallContent =>
14
+ entry.type === "text" && entry.text.length > PAGE_TEXT_CAP ? { ...entry, text: entry.text.slice(0, PAGE_TEXT_CAP) } : entry;
15
+
16
+ // A delegation's own calls are left behind, counted rather than carried: the card draws collapsed until it is opened,
17
+ // and the daemon answers that press with the calls themselves. On the records this was measured on they were 86% of the
18
+ // bytes of the longest conversation.
19
+ const fitPageTool = (tool: TranscriptTool): TranscriptTool => {
20
+ const { children, content, ...carried } = tool;
21
+ return {
22
+ ...carried,
23
+ ...(content !== undefined ? { content: content.map(fitContent) } : {}),
24
+ ...(children !== undefined && children.length > 0 ? { nested: children.length } : {}),
25
+ };
26
+ };
27
+
28
+ /** A row as a page carries it; a row with no calls is handed back as it is. */
29
+ export const fitPageRow = <Row extends TranscriptRow>(row: Row): Row =>
30
+ row.tools === undefined ? row : { ...row, tools: row.tools.map(fitPageTool) };
31
+
32
+ /** A call with everything under it, each output at every depth fitted the same way: what an opened delegation draws. */
33
+ export const fitNestedTool = (tool: TranscriptTool): TranscriptTool => ({
34
+ ...tool,
35
+ ...(tool.content !== undefined ? { content: tool.content.map(fitContent) } : {}),
36
+ ...(tool.children !== undefined ? { children: tool.children.map(fitNestedTool) } : {}),
37
+ });
@@ -1,3 +0,0 @@
1
- export declare const WHISPER_MODEL_REPO = "ggerganov/whisper.cpp";
2
- export declare const cleanTranscription: (stdout: string) => string | undefined;
3
- //# sourceMappingURL=whisper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"whisper.d.ts","sourceRoot":"","sources":["../../src/text/whisper.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAI1D,eAAO,MAAM,kBAAkB,WAAY,MAAM,KAAG,MAAM,GAAG,SAQ5D,CAAC"}
@@ -1,11 +0,0 @@
1
- export const WHISPER_MODEL_REPO = "ggerganov/whisper.cpp";
2
- export const cleanTranscription = (stdout) => {
3
- const text = stdout
4
- .split("\n")
5
- .map((line) => line.trim())
6
- .filter((line) => line !== "" && !/^[[(].*[\])]$/.test(line))
7
- .join(" ")
8
- .trim();
9
- return text === "" ? undefined : text;
10
- };
11
- //# sourceMappingURL=whisper.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"whisper.js","sourceRoot":"","sources":["../../src/text/whisper.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAI1D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAc,EAAsB,EAAE;IACrE,MAAM,IAAI,GAAG,MAAM;SACd,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SAC5D,IAAI,CAAC,GAAG,CAAC;SACT,IAAI,EAAE,CAAC;IACZ,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC,CAAC"}
@@ -1,18 +0,0 @@
1
- import { cleanTranscription } from "./whisper.js";
2
-
3
- test("flattens whisper output onto one line", () => {
4
- expect(cleanTranscription(" Hello there.\n General Kenobi.\n")).toBe("Hello there. General Kenobi.");
5
- });
6
-
7
- // The whole reason this filter exists: whisper narrates silence, and a narration dispatched as speech is a
8
- // turn nobody asked for.
9
- test("drops noise-only annotations, in both bracket styles", () => {
10
- expect(cleanTranscription(" [BLANK_AUDIO]\n")).toBeUndefined();
11
- expect(cleanTranscription("(wind blowing)")).toBeUndefined();
12
- expect(cleanTranscription("")).toBeUndefined();
13
- });
14
-
15
- // An annotation next to real speech drops the annotation and keeps the speech: the utterance did happen.
16
- test("keeps speech that arrived alongside an annotation", () => {
17
- expect(cleanTranscription("[music] \nreal words")).toBe("real words");
18
- });
@@ -1,17 +0,0 @@
1
- // Shared reading of whisper.cpp's stdout, used by the daemon's speech route and Discord's voice session, so both filter
2
- // non-speech annotations the same way.
3
-
4
- // Hugging Face repo the ggml-*.bin weights pull from on first use; each integration downloads its own size.
5
- export const WHISPER_MODEL_REPO = "ggerganov/whisper.cpp";
6
-
7
- // Cleans whisper's stdout to one line of text, or undefined for silence/noise-only output — a line that is entirely a
8
- // bracketed annotation like `[BLANK_AUDIO]`.
9
- export const cleanTranscription = (stdout: string): string | undefined => {
10
- const text = stdout
11
- .split("\n")
12
- .map((line) => line.trim())
13
- .filter((line) => line !== "" && !/^[[(].*[\])]$/.test(line))
14
- .join(" ")
15
- .trim();
16
- return text === "" ? undefined : text;
17
- };