wotann 0.5.39 → 0.5.41

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 (149) hide show
  1. package/dist/autopilot/completion-oracle.d.ts +71 -0
  2. package/dist/autopilot/completion-oracle.d.ts.map +1 -1
  3. package/dist/autopilot/completion-oracle.js +69 -2
  4. package/dist/autopilot/completion-oracle.js.map +1 -1
  5. package/dist/context/compaction-floor.d.ts +100 -0
  6. package/dist/context/compaction-floor.d.ts.map +1 -0
  7. package/dist/context/compaction-floor.js +94 -0
  8. package/dist/context/compaction-floor.js.map +1 -0
  9. package/dist/context/compaction.d.ts +27 -1
  10. package/dist/context/compaction.d.ts.map +1 -1
  11. package/dist/context/compaction.js +31 -0
  12. package/dist/context/compaction.js.map +1 -1
  13. package/dist/daemon/kairos-rpc.d.ts +1 -0
  14. package/dist/daemon/kairos-rpc.d.ts.map +1 -1
  15. package/dist/daemon/kairos-rpc.js +24 -0
  16. package/dist/daemon/kairos-rpc.js.map +1 -1
  17. package/dist/daemon/rpc-handlers/ports-rpc.d.ts +100 -0
  18. package/dist/daemon/rpc-handlers/ports-rpc.d.ts.map +1 -0
  19. package/dist/daemon/rpc-handlers/ports-rpc.js +278 -0
  20. package/dist/daemon/rpc-handlers/ports-rpc.js.map +1 -0
  21. package/dist/hooks/agentmemory-event-taxonomy.d.ts +144 -0
  22. package/dist/hooks/agentmemory-event-taxonomy.d.ts.map +1 -0
  23. package/dist/hooks/agentmemory-event-taxonomy.js +85 -0
  24. package/dist/hooks/agentmemory-event-taxonomy.js.map +1 -0
  25. package/dist/hooks/built-in.d.ts.map +1 -1
  26. package/dist/hooks/built-in.js +7 -3
  27. package/dist/hooks/built-in.js.map +1 -1
  28. package/dist/hooks/engine.d.ts +14 -0
  29. package/dist/hooks/engine.d.ts.map +1 -1
  30. package/dist/hooks/engine.js +62 -0
  31. package/dist/hooks/engine.js.map +1 -1
  32. package/dist/index.js +91 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.d.ts.map +1 -1
  36. package/dist/lib.js +1 -1
  37. package/dist/lib.js.map +1 -1
  38. package/dist/memory/injection-scanner.d.ts +78 -0
  39. package/dist/memory/injection-scanner.d.ts.map +1 -0
  40. package/dist/memory/injection-scanner.js +204 -0
  41. package/dist/memory/injection-scanner.js.map +1 -0
  42. package/dist/middleware/output-truncation.d.ts +18 -5
  43. package/dist/middleware/output-truncation.d.ts.map +1 -1
  44. package/dist/middleware/output-truncation.js +73 -1
  45. package/dist/middleware/output-truncation.js.map +1 -1
  46. package/dist/middleware/ttsr.d.ts +79 -3
  47. package/dist/middleware/ttsr.d.ts.map +1 -1
  48. package/dist/middleware/ttsr.js +136 -16
  49. package/dist/middleware/ttsr.js.map +1 -1
  50. package/dist/orchestration/autonomous.d.ts +182 -0
  51. package/dist/orchestration/autonomous.d.ts.map +1 -1
  52. package/dist/orchestration/autonomous.js +288 -1
  53. package/dist/orchestration/autonomous.js.map +1 -1
  54. package/dist/orchestration/ports-bridge.d.ts +325 -0
  55. package/dist/orchestration/ports-bridge.d.ts.map +1 -0
  56. package/dist/orchestration/ports-bridge.js +712 -0
  57. package/dist/orchestration/ports-bridge.js.map +1 -0
  58. package/dist/orchestration/textgrad-refinement.d.ts +123 -0
  59. package/dist/orchestration/textgrad-refinement.d.ts.map +1 -0
  60. package/dist/orchestration/textgrad-refinement.js +111 -0
  61. package/dist/orchestration/textgrad-refinement.js.map +1 -0
  62. package/dist/prompt/engine.d.ts +15 -0
  63. package/dist/prompt/engine.d.ts.map +1 -1
  64. package/dist/prompt/engine.js +20 -7
  65. package/dist/prompt/engine.js.map +1 -1
  66. package/dist/prompt/modules/capabilities.d.ts.map +1 -1
  67. package/dist/prompt/modules/capabilities.js +1 -0
  68. package/dist/prompt/modules/capabilities.js.map +1 -1
  69. package/dist/prompt/modules/caveman.d.ts.map +1 -1
  70. package/dist/prompt/modules/caveman.js +1 -0
  71. package/dist/prompt/modules/caveman.js.map +1 -1
  72. package/dist/prompt/modules/conventions.d.ts.map +1 -1
  73. package/dist/prompt/modules/conventions.js +1 -0
  74. package/dist/prompt/modules/conventions.js.map +1 -1
  75. package/dist/prompt/modules/identity.d.ts.map +1 -1
  76. package/dist/prompt/modules/identity.js +1 -0
  77. package/dist/prompt/modules/identity.js.map +1 -1
  78. package/dist/prompt/modules/index.d.ts +29 -0
  79. package/dist/prompt/modules/index.d.ts.map +1 -1
  80. package/dist/prompt/modules/index.js +51 -9
  81. package/dist/prompt/modules/index.js.map +1 -1
  82. package/dist/prompt/modules/llms-txt.d.ts.map +1 -1
  83. package/dist/prompt/modules/llms-txt.js +2 -4
  84. package/dist/prompt/modules/llms-txt.js.map +1 -1
  85. package/dist/prompt/modules/safety.d.ts.map +1 -1
  86. package/dist/prompt/modules/safety.js +1 -0
  87. package/dist/prompt/modules/safety.js.map +1 -1
  88. package/dist/prompt/modules/security.d.ts.map +1 -1
  89. package/dist/prompt/modules/security.js +1 -0
  90. package/dist/prompt/modules/security.js.map +1 -1
  91. package/dist/prompt/modules/skills.d.ts.map +1 -1
  92. package/dist/prompt/modules/skills.js +1 -0
  93. package/dist/prompt/modules/skills.js.map +1 -1
  94. package/dist/prompt/modules/tools.d.ts.map +1 -1
  95. package/dist/prompt/modules/tools.js +1 -0
  96. package/dist/prompt/modules/tools.js.map +1 -1
  97. package/dist/prompt/modules/user.d.ts.map +1 -1
  98. package/dist/prompt/modules/user.js +1 -0
  99. package/dist/prompt/modules/user.js.map +1 -1
  100. package/dist/providers/credential-pool.d.ts +45 -1
  101. package/dist/providers/credential-pool.d.ts.map +1 -1
  102. package/dist/providers/credential-pool.js +94 -1
  103. package/dist/providers/credential-pool.js.map +1 -1
  104. package/dist/providers/sticky-rotation-wire.d.ts +133 -0
  105. package/dist/providers/sticky-rotation-wire.d.ts.map +1 -0
  106. package/dist/providers/sticky-rotation-wire.js +185 -0
  107. package/dist/providers/sticky-rotation-wire.js.map +1 -0
  108. package/dist/runtime-hooks/ttsr-rule.d.ts +82 -0
  109. package/dist/runtime-hooks/ttsr-rule.d.ts.map +1 -0
  110. package/dist/runtime-hooks/ttsr-rule.js +207 -0
  111. package/dist/runtime-hooks/ttsr-rule.js.map +1 -0
  112. package/dist/runtime-hooks/ttsr-runner.d.ts +129 -0
  113. package/dist/runtime-hooks/ttsr-runner.d.ts.map +1 -0
  114. package/dist/runtime-hooks/ttsr-runner.js +193 -0
  115. package/dist/runtime-hooks/ttsr-runner.js.map +1 -0
  116. package/dist/runtime-hooks/ttsr-scope.d.ts +115 -0
  117. package/dist/runtime-hooks/ttsr-scope.d.ts.map +1 -0
  118. package/dist/runtime-hooks/ttsr-scope.js +378 -0
  119. package/dist/runtime-hooks/ttsr-scope.js.map +1 -0
  120. package/dist/sandbox/unified-exec.d.ts.map +1 -1
  121. package/dist/sandbox/unified-exec.js +6 -1
  122. package/dist/sandbox/unified-exec.js.map +1 -1
  123. package/dist/skills/cli-anything.d.ts.map +1 -1
  124. package/dist/skills/cli-anything.js +4 -1
  125. package/dist/skills/cli-anything.js.map +1 -1
  126. package/dist/skills/limits.d.ts +86 -0
  127. package/dist/skills/limits.d.ts.map +1 -0
  128. package/dist/skills/limits.js +140 -0
  129. package/dist/skills/limits.js.map +1 -0
  130. package/dist/skills/loader.d.ts +9 -0
  131. package/dist/skills/loader.d.ts.map +1 -1
  132. package/dist/skills/loader.js +29 -1
  133. package/dist/skills/loader.js.map +1 -1
  134. package/dist/storage/session-artifacts.d.ts +105 -0
  135. package/dist/storage/session-artifacts.d.ts.map +1 -0
  136. package/dist/storage/session-artifacts.js +198 -0
  137. package/dist/storage/session-artifacts.js.map +1 -0
  138. package/dist/tools/workspace-pack.d.ts +97 -0
  139. package/dist/tools/workspace-pack.d.ts.map +1 -0
  140. package/dist/tools/workspace-pack.js +228 -0
  141. package/dist/tools/workspace-pack.js.map +1 -0
  142. package/dist/tui/composer-history.d.ts +99 -0
  143. package/dist/tui/composer-history.d.ts.map +1 -0
  144. package/dist/tui/composer-history.js +169 -0
  145. package/dist/tui/composer-history.js.map +1 -0
  146. package/dist/ui/components/PromptInput.d.ts.map +1 -1
  147. package/dist/ui/components/PromptInput.js +80 -9
  148. package/dist/ui/components/PromptInput.js.map +1 -1
  149. package/package.json +1 -1
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Session artifacts — ported from Hmbown/DeepSeek-TUI's `artifacts.rs`
3
+ * (crates/tui/src/artifacts.rs).
4
+ *
5
+ * WHAT: Spill large tool outputs to disk so the in-context transcript can
6
+ * reference them by path/sha rather than inlining the full payload.
7
+ * `spill()` writes a tool output under
8
+ * `~/.wotann/artifacts/{sessionId}/{sha8}-{name}` and returns metadata
9
+ * the caller (transcript renderer) embeds inline. `read()` and `list()`
10
+ * are the round-trip / index APIs.
11
+ *
12
+ * WHY: Tool outputs over ~8 KB consume an outsized share of the context
13
+ * window and burn the prefix cache on every replay. Most large outputs
14
+ * are reference material — `ls -R`, `cat <big-file>`, `npm test` logs —
15
+ * that the model wants to fetch on demand, not re-read on every turn.
16
+ * Spilling them to disk lets the transcript carry a small reference
17
+ * block (`artifact_id`, path, sha, preview, size) instead of the body;
18
+ * the model can `read_artifact` to pull the full content back in only
19
+ * when it actually needs it.
20
+ *
21
+ * WHERE: Wire from the tool-result handler in the runtime. After a tool
22
+ * produces output:
23
+ * if (output.length >= threshold) {
24
+ * const { path, sizeBytes, sha } = artifacts.spill(sessionId, name, output);
25
+ * transcript.push(renderArtifactRef({ name, path, sizeBytes, sha, preview }));
26
+ * } else {
27
+ * transcript.push(output);
28
+ * }
29
+ *
30
+ * HOW: For each spill we compute a SHA-256 of the content, take the first
31
+ * 8 hex chars as the filename prefix, write `{sha8}-{name}` to the
32
+ * session directory, and write a `.meta.json` sidecar with the full
33
+ * metadata block. `read()` looks the artifact up by name and returns
34
+ * the body. `list()` enumerates the session directory and returns the
35
+ * metadata for each artifact.
36
+ *
37
+ * Safety:
38
+ * - Names are sanitised (only `[A-Za-z0-9._-]`; anything else becomes `_`).
39
+ * - Session ids are validated (must be non-empty alphanumeric / `-` / `_`).
40
+ * - We never follow symlinks out of the session directory.
41
+ * - All file operations are best-effort: if disk is full or the user
42
+ * deleted the session dir mid-flight, we surface the error rather
43
+ * than corrupting the in-context transcript.
44
+ */
45
+ import { createHash } from "node:crypto";
46
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
47
+ import { homedir } from "node:os";
48
+ import { join } from "node:path";
49
+ /** Default threshold (in bytes) above which we spill rather than inline. */
50
+ export const DEFAULT_SPILL_THRESHOLD_BYTES = 8 * 1024; // 8 KB
51
+ /** Default artifacts root: `~/.wotann/artifacts`. */
52
+ export function defaultArtifactsRoot() {
53
+ return join(homedir(), ".wotann", "artifacts");
54
+ }
55
+ /**
56
+ * `SessionArtifacts` is the spill / read / list API.
57
+ *
58
+ * Construct one per process. The class is stateless beyond its root path
59
+ * — there's no in-memory index, so two `SessionArtifacts` instances over
60
+ * the same root see the same filesystem state.
61
+ */
62
+ export class SessionArtifacts {
63
+ root;
64
+ threshold;
65
+ constructor(opts) {
66
+ this.root = opts?.root ?? defaultArtifactsRoot();
67
+ this.threshold = opts?.spillThresholdBytes ?? DEFAULT_SPILL_THRESHOLD_BYTES;
68
+ }
69
+ /**
70
+ * The spill threshold in bytes. Exposed so callers can decide whether to
71
+ * spill *before* paying the disk write — e.g. small outputs can stay
72
+ * inline without touching disk at all.
73
+ */
74
+ get spillThreshold() {
75
+ return this.threshold;
76
+ }
77
+ /**
78
+ * Write `content` to disk under the session directory and return its
79
+ * metadata. The metadata block is what the transcript should reference
80
+ * — `path`, `sizeBytes`, `sha` — instead of inlining the body.
81
+ *
82
+ * Throws if `sessionId` or `name` are invalid (would otherwise let a
83
+ * malicious tool result escape the session directory).
84
+ */
85
+ spill(sessionId, name, content) {
86
+ const sid = ensureValidSessionId(sessionId);
87
+ const safeName = sanitizeName(name);
88
+ const sha = sha256Hex(content);
89
+ const filename = `${sha.slice(0, 8)}-${safeName}`;
90
+ const sessionDir = join(this.root, sid);
91
+ mkdirSync(sessionDir, { recursive: true });
92
+ const absPath = join(sessionDir, filename);
93
+ writeFileSync(absPath, content, "utf8");
94
+ const sizeBytes = Buffer.byteLength(content, "utf8");
95
+ const meta = {
96
+ name: safeName,
97
+ path: absPath,
98
+ sizeBytes,
99
+ sha,
100
+ };
101
+ writeFileSync(`${absPath}.meta.json`, JSON.stringify(meta, null, 2), "utf8");
102
+ return {
103
+ name: safeName,
104
+ path: absPath,
105
+ sizeBytes,
106
+ sha,
107
+ };
108
+ }
109
+ /**
110
+ * Read back a previously-spilled artifact's content by session + name.
111
+ * Returns `null` if no artifact with that name exists in the session
112
+ * directory.
113
+ */
114
+ read(sessionId, name) {
115
+ const sid = ensureValidSessionId(sessionId);
116
+ const safeName = sanitizeName(name);
117
+ const sessionDir = join(this.root, sid);
118
+ if (!existsSync(sessionDir))
119
+ return null;
120
+ let entries;
121
+ try {
122
+ entries = readdirSync(sessionDir);
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ // Filenames look like `{sha8}-{name}`. We look for the matching trailing name.
128
+ const target = entries.find((e) => !e.endsWith(".meta.json") && e.endsWith(`-${safeName}`));
129
+ if (target === undefined)
130
+ return null;
131
+ try {
132
+ return readFileSync(join(sessionDir, target), "utf8");
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ /**
139
+ * List every artifact for `sessionId`. Returns an empty array if the
140
+ * session directory doesn't exist or is unreadable.
141
+ *
142
+ * The returned array is sorted by name for deterministic output.
143
+ */
144
+ list(sessionId) {
145
+ const sid = ensureValidSessionId(sessionId);
146
+ const sessionDir = join(this.root, sid);
147
+ if (!existsSync(sessionDir))
148
+ return [];
149
+ let entries;
150
+ try {
151
+ entries = readdirSync(sessionDir);
152
+ }
153
+ catch {
154
+ return [];
155
+ }
156
+ const records = [];
157
+ for (const entry of entries) {
158
+ if (!entry.endsWith(".meta.json"))
159
+ continue;
160
+ try {
161
+ const raw = readFileSync(join(sessionDir, entry), "utf8");
162
+ const parsed = JSON.parse(raw);
163
+ records.push({
164
+ name: parsed.name,
165
+ path: parsed.path,
166
+ sizeBytes: parsed.sizeBytes,
167
+ sha: parsed.sha,
168
+ });
169
+ }
170
+ catch {
171
+ // Skip unparseable sidecars — never abort the whole list.
172
+ }
173
+ }
174
+ return records.sort((a, b) => a.name.localeCompare(b.name));
175
+ }
176
+ }
177
+ /** Lower-level helper exposed for tests. */
178
+ export function sanitizeName(input) {
179
+ if (input.length === 0)
180
+ return "artifact";
181
+ return input
182
+ .split("")
183
+ .map((c) => (/[A-Za-z0-9._-]/.test(c) ? c : "_"))
184
+ .join("");
185
+ }
186
+ function ensureValidSessionId(input) {
187
+ if (input.length === 0) {
188
+ throw new Error("session id must be non-empty");
189
+ }
190
+ if (!/^[A-Za-z0-9_-]+$/.test(input)) {
191
+ throw new Error(`session id must contain only [A-Za-z0-9_-]; got "${input}"`);
192
+ }
193
+ return input;
194
+ }
195
+ function sha256Hex(content) {
196
+ return createHash("sha256").update(content, "utf8").digest("hex");
197
+ }
198
+ //# sourceMappingURL=session-artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-artifacts.js","sourceRoot":"","sources":["../../src/storage/session-artifacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Workspace pack — ported from UI-TARS-desktop's `workspace-pack.ts`
3
+ * (multimodal/tarko/context-engineer/src/node/workspace-pack.ts).
4
+ *
5
+ * WHAT: Recursive workspace packer for LLM consumption. Walks a directory
6
+ * tree, skips binary extensions and ignored directories, reads each
7
+ * eligible file in parallel, deduplicates, enforces per-file and total
8
+ * byte caps, and produces a single markdown-formatted string.
9
+ *
10
+ * WHY: When prompting an LLM with a workspace as context, naïve serialization
11
+ * (a recursive `cat` over the tree) explodes on `node_modules`, binaries,
12
+ * and oversize files.
13
+ * This packer applies the same filters the upstream pattern uses while
14
+ * keeping the output deterministic (sorted by relative path) so the
15
+ * prefix cache survives across repeated calls.
16
+ *
17
+ * WHERE: Use from any tool that needs to hand the model a workspace
18
+ * snapshot — context priming, code search corpora, "review this folder"
19
+ * flows. The result is immutable and safe to cache on disk.
20
+ *
21
+ * HOW: Pipeline is:
22
+ * 1. Walk the tree breadth-first with depth limit, collecting files
23
+ * that pass the binary-extension and ignored-dir filters.
24
+ * 2. Dedupe absolute paths (in case of overlapping inputs).
25
+ * 3. Read all files in parallel (`Promise.all`).
26
+ * 4. Enforce per-file size cap (emit `... (truncated)` marker on hit).
27
+ * 5. Enforce total-bytes cap (stop adding files once the total ceiling
28
+ * is reached; set `truncated: true`).
29
+ * 6. Render each file as `## <relpath>\n\n<content>\n\n`.
30
+ *
31
+ * This port simplifies the upstream by:
32
+ * - Dropping XML/`<directory>` framing in favour of plain markdown
33
+ * headings (more cache-friendly with chat models).
34
+ * - Adding `maxTotalBytes` enforcement (upstream had per-file only).
35
+ * - Skipping magic-byte sniffing — the extension filter covers the
36
+ * overwhelming majority of binaries we care about, and the cost of
37
+ * reading every file's prefix to sniff it is not worth the marginal
38
+ * accuracy on a workspace packer.
39
+ *
40
+ * Output format example:
41
+ *
42
+ * ## src/index.ts
43
+ *
44
+ * export function main() { ... }
45
+ *
46
+ * ## src/lib/helper.ts
47
+ *
48
+ * export const ANSWER = 42;
49
+ */
50
+ /**
51
+ * Options for `packWorkspace`.
52
+ *
53
+ * All fields are optional; sensible defaults are applied for missing fields.
54
+ * Use `readonly` arrays so callers can pass frozen defaults safely.
55
+ */
56
+ export interface WorkspacePackOptions {
57
+ /** Maximum recursion depth. Default: 8. */
58
+ readonly maxDepth?: number;
59
+ /** Per-file byte cap. Files larger than this are truncated. Default: 1 MB. */
60
+ readonly maxBytesPerFile?: number;
61
+ /** Total byte cap across the whole pack. Default: 8 MB. */
62
+ readonly maxTotalBytes?: number;
63
+ /** Lower-case extensions to skip (binary / asset files). */
64
+ readonly skipExtensions?: readonly string[];
65
+ /** Directory names to skip outright (build artefacts, vendor dirs). */
66
+ readonly skipDirs?: readonly string[];
67
+ }
68
+ /**
69
+ * Result of `packWorkspace`.
70
+ *
71
+ * `truncated` flips to `true` when either:
72
+ * - any individual file hit `maxBytesPerFile` (its body was replaced with
73
+ * a `... (truncated)` marker), or
74
+ * - the total pack reached `maxTotalBytes` (later files were skipped).
75
+ */
76
+ export interface WorkspacePackResult {
77
+ readonly content: string;
78
+ readonly fileCount: number;
79
+ readonly bytesRead: number;
80
+ readonly truncated: boolean;
81
+ }
82
+ /** Default binary / asset extensions to skip. Lower-case, dot-prefixed. */
83
+ export declare const DEFAULT_SKIP_EXTENSIONS: readonly string[];
84
+ /** Default directories to skip. */
85
+ export declare const DEFAULT_SKIP_DIRS: readonly string[];
86
+ /**
87
+ * Pack a workspace directory into a single LLM-friendly markdown string.
88
+ *
89
+ * The returned `content` lists each included file in sorted (relative-path)
90
+ * order with a `## <relpath>` heading followed by its contents. This is
91
+ * deterministic across runs so the prefix cache stays warm.
92
+ *
93
+ * @param rootPath Absolute path to the workspace root (or a single file).
94
+ * @param options Optional caps and skip lists. Sensible defaults applied.
95
+ */
96
+ export declare function packWorkspace(rootPath: string, options?: WorkspacePackOptions): Promise<WorkspacePackResult>;
97
+ //# sourceMappingURL=workspace-pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-pack.d.ts","sourceRoot":"","sources":["../../src/tools/workspace-pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAKH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,2DAA2D;IAC3D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAqBpD,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAW9C,CAAC;AAsHF;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAmC9B"}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Workspace pack — ported from UI-TARS-desktop's `workspace-pack.ts`
3
+ * (multimodal/tarko/context-engineer/src/node/workspace-pack.ts).
4
+ *
5
+ * WHAT: Recursive workspace packer for LLM consumption. Walks a directory
6
+ * tree, skips binary extensions and ignored directories, reads each
7
+ * eligible file in parallel, deduplicates, enforces per-file and total
8
+ * byte caps, and produces a single markdown-formatted string.
9
+ *
10
+ * WHY: When prompting an LLM with a workspace as context, naïve serialization
11
+ * (a recursive `cat` over the tree) explodes on `node_modules`, binaries,
12
+ * and oversize files.
13
+ * This packer applies the same filters the upstream pattern uses while
14
+ * keeping the output deterministic (sorted by relative path) so the
15
+ * prefix cache survives across repeated calls.
16
+ *
17
+ * WHERE: Use from any tool that needs to hand the model a workspace
18
+ * snapshot — context priming, code search corpora, "review this folder"
19
+ * flows. The result is immutable and safe to cache on disk.
20
+ *
21
+ * HOW: Pipeline is:
22
+ * 1. Walk the tree breadth-first with depth limit, collecting files
23
+ * that pass the binary-extension and ignored-dir filters.
24
+ * 2. Dedupe absolute paths (in case of overlapping inputs).
25
+ * 3. Read all files in parallel (`Promise.all`).
26
+ * 4. Enforce per-file size cap (emit `... (truncated)` marker on hit).
27
+ * 5. Enforce total-bytes cap (stop adding files once the total ceiling
28
+ * is reached; set `truncated: true`).
29
+ * 6. Render each file as `## <relpath>\n\n<content>\n\n`.
30
+ *
31
+ * This port simplifies the upstream by:
32
+ * - Dropping XML/`<directory>` framing in favour of plain markdown
33
+ * headings (more cache-friendly with chat models).
34
+ * - Adding `maxTotalBytes` enforcement (upstream had per-file only).
35
+ * - Skipping magic-byte sniffing — the extension filter covers the
36
+ * overwhelming majority of binaries we care about, and the cost of
37
+ * reading every file's prefix to sniff it is not worth the marginal
38
+ * accuracy on a workspace packer.
39
+ *
40
+ * Output format example:
41
+ *
42
+ * ## src/index.ts
43
+ *
44
+ * export function main() { ... }
45
+ *
46
+ * ## src/lib/helper.ts
47
+ *
48
+ * export const ANSWER = 42;
49
+ */
50
+ import { stat, readdir, readFile } from "node:fs/promises";
51
+ import { extname, join, relative } from "node:path";
52
+ /** Default binary / asset extensions to skip. Lower-case, dot-prefixed. */
53
+ export const DEFAULT_SKIP_EXTENSIONS = [
54
+ ".png",
55
+ ".jpg",
56
+ ".jpeg",
57
+ ".gif",
58
+ ".pdf",
59
+ ".zip",
60
+ ".tar",
61
+ ".gz",
62
+ ".bin",
63
+ ".exe",
64
+ ".so",
65
+ ".dylib",
66
+ ".dll",
67
+ ".node",
68
+ ".wasm",
69
+ ".woff",
70
+ ".woff2",
71
+ ".ttf",
72
+ ".eot",
73
+ ".ico",
74
+ ];
75
+ /** Default directories to skip. */
76
+ export const DEFAULT_SKIP_DIRS = [
77
+ "node_modules",
78
+ ".git",
79
+ "dist",
80
+ "dist-cjs",
81
+ ".next",
82
+ ".turbo",
83
+ "target",
84
+ "build",
85
+ ".venv",
86
+ "__pycache__",
87
+ ];
88
+ const DEFAULT_MAX_DEPTH = 8;
89
+ const DEFAULT_MAX_BYTES_PER_FILE = 1_048_576; // 1 MB
90
+ const DEFAULT_MAX_TOTAL_BYTES = 8_388_608; // 8 MB
91
+ function resolveOptions(options) {
92
+ const skipExtensions = new Set((options?.skipExtensions ?? DEFAULT_SKIP_EXTENSIONS).map((ext) => ext.toLowerCase()));
93
+ const skipDirs = new Set(options?.skipDirs ?? DEFAULT_SKIP_DIRS);
94
+ return {
95
+ maxDepth: options?.maxDepth ?? DEFAULT_MAX_DEPTH,
96
+ maxBytesPerFile: options?.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE,
97
+ maxTotalBytes: options?.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
98
+ skipExtensions,
99
+ skipDirs,
100
+ };
101
+ }
102
+ /**
103
+ * Walk `rootPath` recursively, returning absolute paths to every file that
104
+ * passes the binary-extension and ignored-dir filters.
105
+ *
106
+ * Depth-bounded by `opts.maxDepth`. Errors from `readdir` (permission denied,
107
+ * symlink loops with broken targets, etc.) are swallowed per-directory so a
108
+ * single broken subtree doesn't abort the whole pack.
109
+ */
110
+ async function collectFilePaths(rootPath, opts) {
111
+ const acc = [];
112
+ async function walk(dir, depth) {
113
+ if (depth > opts.maxDepth)
114
+ return;
115
+ let entries;
116
+ try {
117
+ entries = await readdir(dir, { withFileTypes: true });
118
+ }
119
+ catch {
120
+ return;
121
+ }
122
+ for (const entry of entries) {
123
+ const fullPath = join(dir, entry.name);
124
+ if (entry.isDirectory()) {
125
+ if (opts.skipDirs.has(entry.name))
126
+ continue;
127
+ await walk(fullPath, depth + 1);
128
+ }
129
+ else if (entry.isFile()) {
130
+ const ext = extname(entry.name).toLowerCase();
131
+ if (opts.skipExtensions.has(ext))
132
+ continue;
133
+ acc.push(fullPath);
134
+ }
135
+ }
136
+ }
137
+ // Handle the case where rootPath is itself a file rather than a directory.
138
+ try {
139
+ const rootStat = await stat(rootPath);
140
+ if (rootStat.isFile()) {
141
+ const ext = extname(rootPath).toLowerCase();
142
+ if (!opts.skipExtensions.has(ext))
143
+ acc.push(rootPath);
144
+ }
145
+ else if (rootStat.isDirectory()) {
146
+ await walk(rootPath, 0);
147
+ }
148
+ }
149
+ catch {
150
+ return acc;
151
+ }
152
+ return acc;
153
+ }
154
+ async function readOne(absPath, opts) {
155
+ try {
156
+ const st = await stat(absPath);
157
+ const size = st.size;
158
+ if (size > opts.maxBytesPerFile) {
159
+ const buf = await readFile(absPath);
160
+ const slice = buf.subarray(0, opts.maxBytesPerFile).toString("utf8");
161
+ return {
162
+ absPath,
163
+ body: `${slice}\n... (truncated)`,
164
+ bytesRead: opts.maxBytesPerFile,
165
+ fileTruncated: true,
166
+ };
167
+ }
168
+ const body = await readFile(absPath, "utf8");
169
+ return {
170
+ absPath,
171
+ body,
172
+ bytesRead: size,
173
+ fileTruncated: false,
174
+ };
175
+ }
176
+ catch (err) {
177
+ const msg = err instanceof Error ? err.message : String(err);
178
+ return {
179
+ absPath,
180
+ body: `... (read error: ${msg})`,
181
+ bytesRead: 0,
182
+ fileTruncated: true,
183
+ };
184
+ }
185
+ }
186
+ /**
187
+ * Pack a workspace directory into a single LLM-friendly markdown string.
188
+ *
189
+ * The returned `content` lists each included file in sorted (relative-path)
190
+ * order with a `## <relpath>` heading followed by its contents. This is
191
+ * deterministic across runs so the prefix cache stays warm.
192
+ *
193
+ * @param rootPath Absolute path to the workspace root (or a single file).
194
+ * @param options Optional caps and skip lists. Sensible defaults applied.
195
+ */
196
+ export async function packWorkspace(rootPath, options) {
197
+ const opts = resolveOptions(options);
198
+ const filePaths = await collectFilePaths(rootPath, opts);
199
+ // Dedup absolute paths (the walker shouldn't produce duplicates, but
200
+ // defending against future callers that pass overlapping inputs).
201
+ const uniquePaths = Array.from(new Set(filePaths)).sort();
202
+ // Read all eligible files in parallel. Upstream's `Promise.all` is fine
203
+ // for reasonable workspaces; for >10k files the caller should pre-filter.
204
+ const reads = await Promise.all(uniquePaths.map((p) => readOne(p, opts)));
205
+ const sections = [];
206
+ let bytesRead = 0;
207
+ let fileCount = 0;
208
+ let truncated = false;
209
+ for (const r of reads) {
210
+ if (bytesRead + r.bytesRead > opts.maxTotalBytes) {
211
+ truncated = true;
212
+ break;
213
+ }
214
+ const rel = relative(rootPath, r.absPath) || r.absPath;
215
+ sections.push(`## ${rel}\n\n${r.body}\n\n`);
216
+ bytesRead += r.bytesRead;
217
+ fileCount += 1;
218
+ if (r.fileTruncated)
219
+ truncated = true;
220
+ }
221
+ return {
222
+ content: sections.join(""),
223
+ fileCount,
224
+ bytesRead,
225
+ truncated,
226
+ };
227
+ }
228
+ //# sourceMappingURL=workspace-pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-pack.js","sourceRoot":"","sources":["../../src/tools/workspace-pack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Composer history — ported from Hmbown/DeepSeek-TUI's `composer_history.rs`
3
+ * (crates/tui/src/composer_history.rs).
4
+ *
5
+ * WHAT: Cross-session composer input history. Persists user-typed prompts to
6
+ * `~/.wotann/composer-history.txt` so pressing Up-arrow at the composer
7
+ * recalls submissions from previous sessions, not just the current one.
8
+ * One entry per line, oldest first.
9
+ *
10
+ * WHY: A composer that forgets every prompt the moment the session ends
11
+ * actively destroys the user's working memory. Persisting the history
12
+ * means recurring prompts ("run the build", "show me the diff", a specific
13
+ * debugging incantation) survive across restarts. The cap keeps the file
14
+ * small (≤200 KB at 1000 entries × ≤200 chars/entry).
15
+ *
16
+ * WHERE: Wire from the TUI composer's keyboard handler. On submit, call
17
+ * `history.add(entry)`. On Up-arrow, call `previous()` and render the
18
+ * returned string into the composer buffer. On Down-arrow, call `next()`.
19
+ *
20
+ * HOW: In-memory ring (capped at 1000 entries via FIFO eviction) backed by
21
+ * a plaintext file. Slash-commands (entries starting with `/`) and
22
+ * whitespace-only inputs are skipped — they pollute recall and the slash
23
+ * menu already covers them. Consecutive duplicates are also deduped so
24
+ * repeated submission of the same prompt doesn't bloat the file.
25
+ *
26
+ * Cursor model (matches readline / bash):
27
+ * - `previous()` walks one step toward the oldest entry. Returns the
28
+ * newest entry on first call, the next-older on the second, and so on.
29
+ * - `next()` walks one step toward the newest. Returns `undefined` once
30
+ * past the newest (so the composer can clear the buffer).
31
+ * - `add()` resets the cursor to "just past the newest" so the next
32
+ * `previous()` returns the entry that was just added.
33
+ * - `reset()` puts the cursor in the same "just past the newest" state
34
+ * without modifying entries.
35
+ *
36
+ * Persistence is best-effort — failures are swallowed because composer
37
+ * history is a UX nicety, not a correctness concern.
38
+ */
39
+ /** Hard cap on persisted history. Keeps the file under ~200 KB. */
40
+ export declare const MAX_HISTORY_ENTRIES = 1000;
41
+ /** Default history file path (~/.wotann/composer-history.txt). */
42
+ export declare function defaultComposerHistoryPath(): string;
43
+ /**
44
+ * `ComposerHistory` keeps in-memory entries in append-only order (oldest
45
+ * first) and persists them to a flat text file. It is NOT shared between
46
+ * processes safely; the read/write pattern is "load once on construction,
47
+ * rewrite-on-add". If a second process appends concurrently the last
48
+ * writer wins.
49
+ */
50
+ export declare class ComposerHistory {
51
+ private readonly path;
52
+ private entries;
53
+ /**
54
+ * Cursor for `previous` / `next`. Invariant:
55
+ * `cursor === entries.length` ⇒ "just past the newest entry"
56
+ * `cursor in [0, entries.length)` ⇒ pointing at that entry
57
+ */
58
+ private cursor;
59
+ constructor(path?: string);
60
+ /**
61
+ * Append an entry to history. Skips slash-commands and pure-whitespace
62
+ * inputs. Dedupes consecutive duplicates so repeated submission of the
63
+ * same prompt doesn't bloat the file. Resets the cursor to "just past
64
+ * the newest entry" so the next `previous()` returns the just-added one.
65
+ *
66
+ * Best-effort persistence — any I/O error is swallowed. The in-memory
67
+ * state is always consistent regardless of disk outcome.
68
+ */
69
+ add(entry: string): void;
70
+ /**
71
+ * Walk one step toward the oldest entry. Returns the entry the cursor
72
+ * lands on, or `undefined` if there's no older entry.
73
+ *
74
+ * First call (cursor at `entries.length`) returns the newest entry.
75
+ * Subsequent calls return progressively older entries. Once at the
76
+ * oldest, further calls clamp and return that oldest entry again.
77
+ */
78
+ previous(): string | undefined;
79
+ /**
80
+ * Walk one step toward the newest entry. Returns the entry the cursor
81
+ * lands on, or `undefined` if there's no newer entry (cursor moved past
82
+ * the newest — the composer should clear its buffer).
83
+ */
84
+ next(): string | undefined;
85
+ /** Reset the cursor to "just past the newest entry" without modifying entries. */
86
+ reset(): void;
87
+ /** Snapshot of all entries, oldest first. Returns a fresh array (immutable). */
88
+ getAll(): readonly string[];
89
+ private persist;
90
+ }
91
+ /**
92
+ * Read the persisted history into memory. Returns an empty array if the
93
+ * file doesn't exist or can't be parsed — this is best-effort.
94
+ *
95
+ * Exposed for tests and for callers that want to peek at the history
96
+ * without constructing a `ComposerHistory` instance.
97
+ */
98
+ export declare function loadEntries(path: string): string[];
99
+ //# sourceMappingURL=composer-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer-history.d.ts","sourceRoot":"","sources":["../../src/tui/composer-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAMH,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,kEAAkE;AAClE,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,GAAE,MAAqC;IAMvD;;;;;;;;OAQG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAsBxB;;;;;;;OAOG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAM9B;;;;OAIG;IACH,IAAI,IAAI,MAAM,GAAG,SAAS;IAW1B,kFAAkF;IAClF,KAAK,IAAI,IAAI;IAIb,gFAAgF;IAChF,MAAM,IAAI,SAAS,MAAM,EAAE;IAI3B,OAAO,CAAC,OAAO;CAShB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAWlD"}