@matheuskrumenauer/tanya 0.2.0-beta.0 → 0.4.0-beta.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.
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync as existsSync15, readFileSync as readFileSync7 } from "fs";
5
- import { join as join20, resolve as resolve12 } from "path";
4
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
5
+ import { join as join22, resolve as resolve12 } from "path";
6
6
  import { Command, Option } from "commander";
7
7
 
8
8
  // src/config/env.ts
@@ -40,6 +40,170 @@ function numberEnvValue(local, key, fallback) {
40
40
  return Number.isFinite(parsed) ? parsed : fallback;
41
41
  }
42
42
 
43
+ // src/providers/adapters/deepseek.ts
44
+ var deepSeekAdapter = {
45
+ id: "deepseek",
46
+ matchBaseUrl: /api\.deepseek\.com/i,
47
+ defaultBaseUrl: "https://api.deepseek.com",
48
+ defaultModel: "deepseek-chat",
49
+ capabilities: {
50
+ toolChoiceRequired: false,
51
+ parallelToolCalls: false,
52
+ jsonMode: true,
53
+ vision: false,
54
+ reasoning: true,
55
+ flattenSchemas: false
56
+ }
57
+ };
58
+
59
+ // src/providers/adapters/types.ts
60
+ function withoutUnsupportedToolChoice(req) {
61
+ if (req.tool_choice === "required") {
62
+ const { tool_choice: _toolChoice, ...rest } = req;
63
+ return rest;
64
+ }
65
+ return req;
66
+ }
67
+
68
+ // src/providers/adapters/grok.ts
69
+ var grokAdapter = {
70
+ id: "grok",
71
+ matchBaseUrl: /(?:api\.)?x\.ai/i,
72
+ defaultBaseUrl: "https://api.x.ai/v1",
73
+ defaultModel: "grok-3-mini",
74
+ capabilities: {
75
+ toolChoiceRequired: false,
76
+ parallelToolCalls: false,
77
+ jsonMode: true,
78
+ vision: true,
79
+ reasoning: true,
80
+ flattenSchemas: false
81
+ },
82
+ preRequest: withoutUnsupportedToolChoice
83
+ };
84
+
85
+ // src/providers/adapters/groq.ts
86
+ var groqAdapter = {
87
+ id: "groq",
88
+ matchBaseUrl: /api\.groq\.com/i,
89
+ defaultBaseUrl: "https://api.groq.com/openai/v1",
90
+ defaultModel: "llama-3.3-70b-versatile",
91
+ capabilities: {
92
+ toolChoiceRequired: false,
93
+ parallelToolCalls: false,
94
+ jsonMode: true,
95
+ vision: false,
96
+ reasoning: false,
97
+ flattenSchemas: false
98
+ }
99
+ };
100
+
101
+ // src/providers/adapters/ollama.ts
102
+ var ollamaAdapter = {
103
+ id: "ollama",
104
+ matchBaseUrl: /(?:localhost:11434|127\.0\.0\.1:11434|ollama)/i,
105
+ defaultBaseUrl: "http://localhost:11434/v1",
106
+ defaultModel: "qwen2.5-coder:7b",
107
+ capabilities: {
108
+ toolChoiceRequired: false,
109
+ parallelToolCalls: false,
110
+ jsonMode: true,
111
+ vision: false,
112
+ reasoning: false,
113
+ flattenSchemas: false
114
+ },
115
+ preRequest: withoutUnsupportedToolChoice
116
+ };
117
+
118
+ // src/providers/adapters/openai.ts
119
+ var openAiAdapter = {
120
+ id: "openai",
121
+ matchBaseUrl: /(?:api\.)?openai\.com/i,
122
+ defaultBaseUrl: "https://api.openai.com/v1",
123
+ defaultModel: "gpt-4.1-mini",
124
+ capabilities: {
125
+ toolChoiceRequired: true,
126
+ parallelToolCalls: true,
127
+ jsonMode: true,
128
+ vision: true,
129
+ reasoning: true,
130
+ flattenSchemas: false
131
+ }
132
+ };
133
+
134
+ // src/providers/adapters/qwen.ts
135
+ var qwenAdapter = {
136
+ id: "qwen",
137
+ matchBaseUrl: /(?:dashscope|aliyuncs|qwen)/i,
138
+ defaultBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
139
+ defaultModel: "qwen3-coder-plus",
140
+ capabilities: {
141
+ toolChoiceRequired: false,
142
+ parallelToolCalls: false,
143
+ jsonMode: true,
144
+ vision: true,
145
+ reasoning: true,
146
+ flattenSchemas: true
147
+ },
148
+ preRequest: (req) => ({
149
+ ...withoutUnsupportedToolChoice(req),
150
+ parallel_tool_calls: false
151
+ })
152
+ };
153
+
154
+ // src/providers/adapters/together.ts
155
+ var togetherAdapter = {
156
+ id: "together",
157
+ matchBaseUrl: /api\.together\.xyz/i,
158
+ defaultBaseUrl: "https://api.together.xyz/v1",
159
+ defaultModel: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
160
+ capabilities: {
161
+ toolChoiceRequired: false,
162
+ parallelToolCalls: false,
163
+ jsonMode: true,
164
+ vision: false,
165
+ reasoning: false,
166
+ flattenSchemas: true
167
+ },
168
+ preRequest: withoutUnsupportedToolChoice
169
+ };
170
+
171
+ // src/providers/adapters/index.ts
172
+ var providerAdapters = [
173
+ deepSeekAdapter,
174
+ qwenAdapter,
175
+ grokAdapter,
176
+ groqAdapter,
177
+ togetherAdapter,
178
+ ollamaAdapter,
179
+ openAiAdapter
180
+ ];
181
+ var aliases = /* @__PURE__ */ new Map([
182
+ ["deepseek-reasoner", "deepseek"],
183
+ ["deepseek-chat", "deepseek"],
184
+ ["xai", "grok"],
185
+ ["openai-compatible", "openai"],
186
+ ["custom", "openai"]
187
+ ]);
188
+ function resolveProviderAdapter(input = {}) {
189
+ const provider = normalizeProviderId(input.provider);
190
+ if (provider) {
191
+ const explicit = providerAdapters.find((adapter) => adapter.id === provider);
192
+ if (explicit) return explicit;
193
+ }
194
+ const baseUrl = input.baseUrl?.trim();
195
+ if (baseUrl) {
196
+ const matched = providerAdapters.find((adapter) => adapter.matchBaseUrl?.test(baseUrl));
197
+ if (matched) return matched;
198
+ }
199
+ return openAiAdapter;
200
+ }
201
+ function normalizeProviderId(provider) {
202
+ const normalized = provider?.trim().toLowerCase();
203
+ if (!normalized) return null;
204
+ return aliases.get(normalized) ?? normalized;
205
+ }
206
+
43
207
  // src/config/env.ts
44
208
  function loadDotEnv(cwd) {
45
209
  const envPath = join(cwd, ".env");
@@ -48,11 +212,18 @@ function loadDotEnv(cwd) {
48
212
  }
49
213
  function loadConfig(cwd = process.cwd()) {
50
214
  const local = loadDotEnv(cwd);
51
- const provider = envValue(local, "TANYA_PROVIDER") === "custom" ? "custom" : "deepseek";
52
215
  const profile = envValue(local, "TANYA_PROFILE") === "reasoner" ? "reasoner" : "chat";
216
+ const requestedProvider = envValue(local, "TANYA_PROVIDER").trim();
217
+ const requestedBaseUrl = envValue(local, "TANYA_BASE_URL").trim();
218
+ const providerSeed = requestedProvider || (requestedBaseUrl ? "" : "deepseek");
219
+ const adapter = resolveProviderAdapter({
220
+ provider: providerSeed,
221
+ baseUrl: requestedBaseUrl
222
+ });
223
+ const provider = providerSeed || adapter.id;
53
224
  const apiKey = provider === "deepseek" ? envValue(local, "DEEPSEEK_API_KEY") || envValue(local, "TANYA_API_KEY") : envValue(local, "TANYA_API_KEY");
54
- const baseUrl = provider === "deepseek" ? envValue(local, "DEEPSEEK_BASE_URL") || envValue(local, "TANYA_BASE_URL") || "https://api.deepseek.com" : envValue(local, "TANYA_BASE_URL");
55
- const profileModelDefault = profile === "reasoner" ? "deepseek-reasoner" : "deepseek-chat";
225
+ const baseUrl = provider === "deepseek" ? envValue(local, "DEEPSEEK_BASE_URL") || envValue(local, "TANYA_BASE_URL") || adapter.defaultBaseUrl || "https://api.deepseek.com" : envValue(local, "TANYA_BASE_URL") || adapter.defaultBaseUrl || "";
226
+ const profileModelDefault = profile === "reasoner" && adapter.id === "deepseek" ? "deepseek-reasoner" : adapter.defaultModel ?? "deepseek-chat";
56
227
  const model = envValue(local, "TANYA_MODEL") || profileModelDefault;
57
228
  const profileTimeoutDefault = profile === "reasoner" ? 18e4 : 9e4;
58
229
  const timeoutMs = numberEnvValue(local, "TANYA_TIMEOUT_MS", profileTimeoutDefault);
@@ -144,12 +315,12 @@ function buildAutoBriefBlock(metadata) {
144
315
  }
145
316
  return lines;
146
317
  }
147
- function normalizeRunContext(input2) {
148
- if (!isRecord(input2)) throw new Error("Context file must contain a JSON object.");
149
- const taskInput = isRecord(input2.task) ? input2.task : void 0;
150
- const artifactsInput = Array.isArray(input2.artifacts) ? input2.artifacts : void 0;
151
- const verificationInput = isRecord(input2.verification) ? input2.verification : void 0;
152
- const contextFilesInput = Array.isArray(input2.contextFiles) ? input2.contextFiles : void 0;
318
+ function normalizeRunContext(input) {
319
+ if (!isRecord(input)) throw new Error("Context file must contain a JSON object.");
320
+ const taskInput = isRecord(input.task) ? input.task : void 0;
321
+ const artifactsInput = Array.isArray(input.artifacts) ? input.artifacts : void 0;
322
+ const verificationInput = isRecord(input.verification) ? input.verification : void 0;
323
+ const contextFilesInput = Array.isArray(input.contextFiles) ? input.contextFiles : void 0;
153
324
  const artifacts = artifactsInput?.filter(isRecord).map((artifact) => ({
154
325
  path: asString(artifact.path) ?? "",
155
326
  ...asString(artifact.sourcePath) ? { sourcePath: asString(artifact.sourcePath) } : {},
@@ -170,10 +341,10 @@ function normalizeRunContext(input2) {
170
341
  ...asString(taskInput.summary) ? { summary: asString(taskInput.summary) } : {}
171
342
  } : void 0;
172
343
  const verificationCommands = verificationInput ? stringArray(verificationInput.commands) : void 0;
173
- const instructions = stringArray(input2.instructions);
174
- const languages = stringArray(input2.languages);
175
- const frameworks = stringArray(input2.frameworks);
176
- const stack = asString(input2.stack);
344
+ const instructions = stringArray(input.instructions);
345
+ const languages = stringArray(input.languages);
346
+ const frameworks = stringArray(input.frameworks);
347
+ const stack = asString(input.stack);
177
348
  return {
178
349
  ...task && Object.keys(task).length > 0 ? { task } : {},
179
350
  ...artifacts?.length ? { artifacts } : {},
@@ -183,8 +354,8 @@ function normalizeRunContext(input2) {
183
354
  ...languages ? { languages } : {},
184
355
  ...frameworks ? { frameworks } : {},
185
356
  ...stack ? { stack } : {},
186
- ...isRecord(input2.expected_report) ? { expected_report: input2.expected_report } : {},
187
- ...isRecord(input2.metadata) ? { metadata: input2.metadata } : {}
357
+ ...isRecord(input.expected_report) ? { expected_report: input.expected_report } : {},
358
+ ...isRecord(input.metadata) ? { metadata: input.metadata } : {}
188
359
  };
189
360
  }
190
361
  function loadRunContextFile(path) {
@@ -281,12 +452,12 @@ function relativeMaterializedPath(root, artifact, source) {
281
452
  }
282
453
  return basename(source);
283
454
  }
284
- function materializeCliArtifacts(input2) {
285
- const artifactInputs = unique(input2.artifacts);
286
- const contextInputs = unique(input2.contextPaths ?? []);
287
- if (artifactInputs.length === 0 && contextInputs.length === 0 && !input2.artifactOutputRoot) return input2.baseContext;
288
- const cwd = resolve2(input2.cwd);
289
- const root = input2.root ? resolve2(input2.root) : void 0;
455
+ function materializeCliArtifacts(input) {
456
+ const artifactInputs = unique(input.artifacts);
457
+ const contextInputs = unique(input.contextPaths ?? []);
458
+ if (artifactInputs.length === 0 && contextInputs.length === 0 && !input.artifactOutputRoot) return input.baseContext;
459
+ const cwd = resolve2(input.cwd);
460
+ const root = input.root ? resolve2(input.root) : void 0;
290
461
  const targetRoot = resolve2(cwd, ".tania", "artifacts");
291
462
  const contextTargetRoot = resolve2(cwd, ".tania", "context");
292
463
  const materialized = [];
@@ -366,13 +537,13 @@ function materializeCliArtifacts(input2) {
366
537
  "utf8"
367
538
  );
368
539
  }
369
- const artifactOutputRoot = input2.artifactOutputRoot ? resolve2(input2.artifactOutputRoot) : void 0;
540
+ const artifactOutputRoot = input.artifactOutputRoot ? resolve2(input.artifactOutputRoot) : void 0;
370
541
  return {
371
- ...input2.baseContext ?? {},
372
- artifacts: [...input2.baseContext?.artifacts ?? [], ...materialized],
373
- contextFiles: [...input2.baseContext?.contextFiles ?? [], ...contextFiles],
542
+ ...input.baseContext ?? {},
543
+ artifacts: [...input.baseContext?.artifacts ?? [], ...materialized],
544
+ contextFiles: [...input.baseContext?.contextFiles ?? [], ...contextFiles],
374
545
  instructions: [
375
- ...input2.baseContext?.instructions ?? [],
546
+ ...input.baseContext?.instructions ?? [],
376
547
  ...materialized.length > 0 ? [
377
548
  "Caller artifacts were provided through Tanya's artifact input contract and materialized under .tania/artifacts.",
378
549
  "Read relevant materialized artifact paths before implementing related code."
@@ -387,14 +558,14 @@ function materializeCliArtifacts(input2) {
387
558
  "When reporting artifact reuse, use the artifact sourcePath label when available."
388
559
  ],
389
560
  expected_report: {
390
- ...input2.baseContext?.expected_report ?? {},
561
+ ...input.baseContext?.expected_report ?? {},
391
562
  artifact_reuse: true
392
563
  },
393
564
  metadata: {
394
- ...input2.baseContext?.metadata ?? {},
565
+ ...input.baseContext?.metadata ?? {},
395
566
  ...artifactOutputRoot ? { artifactOutputRoot } : {},
396
567
  tanyaMaterializedContext: true,
397
- keepMaterializedContext: input2.keepContext === true
568
+ keepMaterializedContext: input.keepContext === true
398
569
  }
399
570
  };
400
571
  }
@@ -471,6 +642,50 @@ function createCosmoSink(stream = process.stdout) {
471
642
  partialOutput: event.partialOutput
472
643
  });
473
644
  break;
645
+ case "command_invoked":
646
+ ensureEventLine();
647
+ writeEvent(stream, {
648
+ t: "status",
649
+ message: `Command invoked: /${event.name}`,
650
+ key: `tanya:command:${event.name}`,
651
+ command: event.name,
652
+ args: event.args,
653
+ runId: event.runId
654
+ });
655
+ break;
656
+ case "tool_call_parse_warning":
657
+ ensureEventLine();
658
+ writeEvent(stream, {
659
+ t: "status",
660
+ message: `Tool-call parse warning: ${event.reason}`,
661
+ key: `tanya:tool-call-parse-warning:${event.toolCallId ?? event.turn ?? "unknown"}`,
662
+ provider: event.provider,
663
+ tool: event.tool,
664
+ attempt: event.attempt
665
+ });
666
+ break;
667
+ case "schema_flatten_warning":
668
+ ensureEventLine();
669
+ writeEvent(stream, {
670
+ t: "status",
671
+ message: `Schema flatten warning: ${event.reason}`,
672
+ key: `tanya:schema-flatten-warning:${event.tool ?? event.path}`,
673
+ provider: event.provider,
674
+ tool: event.tool,
675
+ path: event.path
676
+ });
677
+ break;
678
+ case "provider_throttle":
679
+ ensureEventLine();
680
+ writeEvent(stream, {
681
+ t: "status",
682
+ message: `Provider throttle: waiting ${Math.ceil(event.waitMs / 1e3)}s before retry ${event.attempt}.`,
683
+ key: `tanya:provider-throttle:${event.provider}:${event.attempt}`,
684
+ provider: event.provider,
685
+ attempt: event.attempt,
686
+ waitMs: event.waitMs
687
+ });
688
+ break;
474
689
  case "final":
475
690
  ensureEventLine();
476
691
  if (event.message.trim()) {
@@ -520,6 +735,10 @@ var knownEventTypes = /* @__PURE__ */ new Set([
520
735
  "tool_cancel_requested",
521
736
  "tool_cancelled",
522
737
  "tool_result",
738
+ "tool_call_parse_warning",
739
+ "schema_flatten_warning",
740
+ "provider_throttle",
741
+ "command_invoked",
523
742
  "final",
524
743
  "error"
525
744
  ]);
@@ -536,27 +755,237 @@ function createJsonlSink(stream = process.stdout) {
536
755
  };
537
756
  }
538
757
 
539
- // src/providers/openAiCompatible.ts
540
- var RETRYABLE_HTTP_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503]);
541
- var MAX_FETCH_ATTEMPTS = 3;
542
- function sleep(ms) {
543
- return new Promise((resolve13) => setTimeout(resolve13, ms));
758
+ // src/providers/schemaFlatten.ts
759
+ function flattenJsonSchema(schema) {
760
+ const warnings = [];
761
+ const root = clone(schema);
762
+ const flattened = flattenValue(root, root, "#", warnings, /* @__PURE__ */ new Set());
763
+ return { schema: flattened, warnings };
544
764
  }
545
- function retryAfterMs(response) {
765
+ function flattenToolDefinition(tool) {
766
+ const result = flattenJsonSchema(tool.function.parameters);
767
+ return {
768
+ schema: {
769
+ ...tool,
770
+ function: {
771
+ ...tool.function,
772
+ parameters: result.schema
773
+ }
774
+ },
775
+ warnings: result.warnings.map((warning) => ({ ...warning, tool: tool.function.name }))
776
+ };
777
+ }
778
+ function flattenToolDefinitions(tools) {
779
+ const flattened = [];
780
+ const warnings = [];
781
+ for (const tool of tools) {
782
+ const result = flattenToolDefinition(tool);
783
+ flattened.push(result.schema);
784
+ warnings.push(...result.warnings);
785
+ }
786
+ return { schema: flattened, warnings };
787
+ }
788
+ function flattenValue(value, root, path, warnings, seenRefs) {
789
+ if (Array.isArray(value)) {
790
+ return value.map((item, index) => flattenValue(item, root, `${path}/${index}`, warnings, seenRefs));
791
+ }
792
+ if (!isObject(value)) return value;
793
+ const ref = typeof value.$ref === "string" ? value.$ref : null;
794
+ if (ref) {
795
+ const resolved = resolveLocalRef(root, ref);
796
+ if (resolved === void 0) {
797
+ warnings.push({ path, reason: `unresolved $ref ${ref}; leaving reference in place` });
798
+ return value;
799
+ }
800
+ if (seenRefs.has(ref)) {
801
+ warnings.push({ path, reason: `circular $ref ${ref}; leaving reference in place` });
802
+ return value;
803
+ }
804
+ warnings.push({ path, reason: `inlined $ref ${ref}` });
805
+ seenRefs.add(ref);
806
+ const flattened = flattenValue(resolved, root, path, warnings, seenRefs);
807
+ seenRefs.delete(ref);
808
+ const { $ref: _ref, ...overrides } = value;
809
+ return isObject(flattened) ? flattenObject({ ...flattened, ...overrides }, root, path, warnings, seenRefs) : flattened;
810
+ }
811
+ return flattenObject(value, root, path, warnings, seenRefs);
812
+ }
813
+ function flattenObject(object, root, path, warnings, seenRefs) {
814
+ const withoutDefs = stripDefinitions(object);
815
+ if (Array.isArray(withoutDefs.oneOf)) {
816
+ return flattenOneOf(withoutDefs, root, path, warnings, seenRefs);
817
+ }
818
+ const output = {};
819
+ for (const [key, raw] of Object.entries(withoutDefs)) {
820
+ output[key] = flattenValue(raw, root, `${path}/${escapePointer(key)}`, warnings, seenRefs);
821
+ }
822
+ return output;
823
+ }
824
+ function flattenOneOf(object, root, path, warnings, seenRefs) {
825
+ const variants = object.oneOf;
826
+ const flattenedVariants = variants.map((variant, index) => flattenValue(variant, root, `${path}/oneOf/${index}`, warnings, seenRefs)).filter(isObject);
827
+ if (flattenedVariants.length === 0) {
828
+ warnings.push({ path, reason: "oneOf had no object variants; dropped oneOf" });
829
+ const { oneOf: _oneOf2, ...rest2 } = object;
830
+ return flattenObject(rest2, root, path, warnings, seenRefs);
831
+ }
832
+ const commonType = commonScalar(flattenedVariants.map((variant) => variant.type));
833
+ const commonProperties = commonObjectProperties(flattenedVariants);
834
+ const commonRequired = commonRequiredFields(flattenedVariants);
835
+ const { oneOf: _oneOf, ...rest } = object;
836
+ warnings.push({ path, reason: `collapsed oneOf (${variants.length} variants) to common object shape` });
837
+ return flattenObject({
838
+ ...rest,
839
+ ...commonType ? { type: commonType } : {},
840
+ ...Object.keys(commonProperties).length ? { properties: commonProperties } : {},
841
+ ...commonRequired.length ? { required: commonRequired } : {}
842
+ }, root, path, warnings, seenRefs);
843
+ }
844
+ function commonObjectProperties(variants) {
845
+ const propertyMaps = variants.map((variant) => isObject(variant.properties) ? variant.properties : {});
846
+ if (propertyMaps.length === 0) return {};
847
+ const commonKeys = Object.keys(propertyMaps[0] ?? {}).filter((key) => propertyMaps.every((properties) => key in properties));
848
+ const output = {};
849
+ for (const key of commonKeys) output[key] = propertyMaps[0]?.[key];
850
+ return output;
851
+ }
852
+ function commonRequiredFields(variants) {
853
+ const requiredLists = variants.map((variant) => Array.isArray(variant.required) ? variant.required.filter((item) => typeof item === "string") : []);
854
+ if (requiredLists.length === 0) return [];
855
+ return requiredLists[0]?.filter((key) => requiredLists.every((list) => list.includes(key))) ?? [];
856
+ }
857
+ function commonScalar(values) {
858
+ const [first] = values;
859
+ return typeof first === "string" && values.every((value) => value === first) ? first : null;
860
+ }
861
+ function resolveLocalRef(root, ref) {
862
+ if (!ref.startsWith("#/")) return void 0;
863
+ const parts = ref.slice(2).split("/").map(unescapePointer);
864
+ let current = root;
865
+ for (const part of parts) {
866
+ if (!isObject(current) && !Array.isArray(current)) return void 0;
867
+ current = current[part];
868
+ }
869
+ return current;
870
+ }
871
+ function stripDefinitions(object) {
872
+ const { $defs: _defs, definitions: _definitions, ...rest } = object;
873
+ return rest;
874
+ }
875
+ function clone(value) {
876
+ return JSON.parse(JSON.stringify(value));
877
+ }
878
+ function isObject(value) {
879
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
880
+ }
881
+ function escapePointer(value) {
882
+ return value.replace(/~/g, "~0").replace(/\//g, "~1");
883
+ }
884
+ function unescapePointer(value) {
885
+ return value.replace(/~1/g, "/").replace(/~0/g, "~");
886
+ }
887
+
888
+ // src/providers/retry.ts
889
+ var DEFAULT_MAX_RETRIES = 3;
890
+ var DEFAULT_MAX_WAIT_MS = 3e4;
891
+ var DEFAULT_INITIAL_WAIT_MS = 500;
892
+ var DEFAULT_PROVIDER_CONCURRENCY = 4;
893
+ var semaphores = /* @__PURE__ */ new Map();
894
+ function retryAfterMs(response, now = Date.now()) {
546
895
  const raw = response.headers.get("retry-after");
547
896
  if (!raw) return null;
548
897
  const seconds = Number(raw);
549
- if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, 6e4);
898
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.max(0, seconds * 1e3);
550
899
  const dateMs = Date.parse(raw);
551
- if (!Number.isNaN(dateMs)) return Math.min(Math.max(dateMs - Date.now(), 0), 6e4);
900
+ if (!Number.isNaN(dateMs)) return Math.max(dateMs - now, 0);
901
+ return null;
902
+ }
903
+ function backoffMs(attempt, options = {}) {
904
+ const initial = options.initialWaitMs ?? DEFAULT_INITIAL_WAIT_MS;
905
+ const max = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
906
+ return Math.min(initial * 2 ** Math.max(0, attempt - 1), max);
907
+ }
908
+ function retryWaitMs(response, attempt, options = {}) {
909
+ if (response.status === 429) {
910
+ const retryAfter = retryAfterMs(response);
911
+ if (retryAfter !== null) return Math.min(retryAfter, options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
912
+ }
913
+ if (response.status >= 500 && response.status <= 599 || response.status === 429) {
914
+ return backoffMs(attempt, options);
915
+ }
552
916
  return null;
553
917
  }
554
- function backoffMs(attempt, response) {
555
- const retryAfter = response?.status === 429 ? retryAfterMs(response) : null;
556
- if (retryAfter !== null) return retryAfter;
557
- const base = 100 * 2 ** attempt;
558
- const jitter = base * 0.1 * (Math.random() * 2 - 1);
559
- return Math.max(0, Math.round(base + jitter));
918
+ async function fetchWithProviderRetry(options) {
919
+ const semaphore = getProviderSemaphore(options.provider, options.concurrency ?? DEFAULT_PROVIDER_CONCURRENCY);
920
+ return semaphore.run(() => fetchWithRetry(options));
921
+ }
922
+ async function fetchWithRetry(options) {
923
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
924
+ const sleep2 = options.sleep ?? defaultSleep;
925
+ let response;
926
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
927
+ response = await options.fetch();
928
+ const retryAttempt = attempt + 1;
929
+ const waitOptions = {
930
+ ...options.initialWaitMs !== void 0 ? { initialWaitMs: options.initialWaitMs } : {},
931
+ ...options.maxWaitMs !== void 0 ? { maxWaitMs: options.maxWaitMs } : {}
932
+ };
933
+ const waitMs = attempt < maxRetries ? retryWaitMs(response, retryAttempt, waitOptions) : null;
934
+ if (waitMs === null) return response;
935
+ await options.onThrottle?.({ provider: options.provider, attempt: retryAttempt, waitMs });
936
+ await sleep2(waitMs);
937
+ }
938
+ return response;
939
+ }
940
+ function getProviderSemaphore(provider, concurrency) {
941
+ const normalizedProvider = provider || "unknown";
942
+ const existing = semaphores.get(normalizedProvider);
943
+ if (existing && existing.limit === concurrency) return existing;
944
+ const created = new ProviderSemaphore(Math.max(1, Math.floor(concurrency)));
945
+ semaphores.set(normalizedProvider, created);
946
+ return created;
947
+ }
948
+ function defaultSleep(ms) {
949
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
950
+ }
951
+ var ProviderSemaphore = class {
952
+ limit;
953
+ active = 0;
954
+ queue = [];
955
+ constructor(limit) {
956
+ this.limit = limit;
957
+ }
958
+ async run(fn) {
959
+ await this.acquire();
960
+ try {
961
+ return await fn();
962
+ } finally {
963
+ this.release();
964
+ }
965
+ }
966
+ acquire() {
967
+ if (this.active < this.limit) {
968
+ this.active += 1;
969
+ return Promise.resolve();
970
+ }
971
+ return new Promise((resolve13) => {
972
+ this.queue.push(() => {
973
+ this.active += 1;
974
+ resolve13();
975
+ });
976
+ });
977
+ }
978
+ release() {
979
+ this.active -= 1;
980
+ const next = this.queue.shift();
981
+ if (next) next();
982
+ }
983
+ };
984
+
985
+ // src/providers/openAiCompatible.ts
986
+ function providerConcurrency() {
987
+ const raw = Number(envValue({}, "TANYA_PROVIDER_CONCURRENCY"));
988
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 4;
560
989
  }
561
990
  var OpenAiCompatibleProvider = class {
562
991
  id;
@@ -566,18 +995,20 @@ var OpenAiCompatibleProvider = class {
566
995
  timeoutMs;
567
996
  temperature;
568
997
  topP;
998
+ adapter;
569
999
  constructor(options) {
1000
+ this.adapter = resolveProviderAdapter({ provider: options.id, baseUrl: options.baseUrl });
570
1001
  this.id = options.id;
571
1002
  this.apiKey = options.apiKey;
572
- this.baseUrl = options.baseUrl.replace(/\/$/, "");
573
- this.model = options.model;
1003
+ this.baseUrl = (options.baseUrl || this.adapter.defaultBaseUrl || "").replace(/\/$/, "");
1004
+ this.model = options.model || this.adapter.defaultModel || "";
574
1005
  const envTimeout = parseInt(envValue({}, "TANYA_TIMEOUT_MS"), 10);
575
1006
  const envTimeoutMs = Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : null;
576
1007
  this.timeoutMs = envTimeoutMs ?? options.timeoutMs ?? 9e4;
577
1008
  this.temperature = options.temperature ?? 0;
578
1009
  this.topP = options.topP ?? 0.2;
579
1010
  }
580
- async *streamChat(input2) {
1011
+ async *streamChat(input) {
581
1012
  if (!this.apiKey) {
582
1013
  throw new Error(`Missing API key for provider "${this.id}".`);
583
1014
  }
@@ -587,54 +1018,62 @@ var OpenAiCompatibleProvider = class {
587
1018
  if (timeout) clearTimeout(timeout);
588
1019
  timeout = setTimeout(() => controller.abort(), this.timeoutMs);
589
1020
  };
590
- const requestBody = JSON.stringify({
1021
+ const request = {
591
1022
  model: this.model,
592
- messages: input2.messages,
593
- tools: input2.tools?.length ? input2.tools : void 0,
594
- tool_choice: input2.tools?.length ? "auto" : void 0,
595
- temperature: input2.temperature ?? this.temperature,
596
- top_p: input2.topP ?? this.topP,
597
- max_tokens: input2.maxTokens ?? 8192,
1023
+ messages: input.messages,
1024
+ temperature: input.temperature ?? this.temperature,
1025
+ top_p: input.topP ?? this.topP,
1026
+ max_tokens: input.maxTokens ?? 8192,
598
1027
  stream: true
599
- });
600
- let response = null;
601
- let finalHttpErrorDetail = "";
602
- for (let attempt = 0; attempt < MAX_FETCH_ATTEMPTS; attempt += 1) {
603
- resetTimeout();
604
- response = await fetch(`${this.baseUrl}/chat/completions`, {
605
- method: "POST",
606
- signal: controller.signal,
607
- headers: {
608
- Authorization: `Bearer ${this.apiKey}`,
609
- "Content-Type": "application/json"
610
- },
611
- body: requestBody
612
- }).catch((error) => {
613
- if (controller.signal.aborted) {
614
- throw new Error(`Provider ${this.id} timed out before streaming a response.`);
615
- }
616
- throw error;
617
- });
618
- if (response.ok && response.body) break;
619
- if (!RETRYABLE_HTTP_STATUSES.has(response.status) || attempt === MAX_FETCH_ATTEMPTS - 1) {
620
- break;
1028
+ };
1029
+ const schemaWarnings = [];
1030
+ if (input.tools?.length) {
1031
+ if (this.adapter.capabilities.flattenSchemas) {
1032
+ const flattened = flattenToolDefinitions(input.tools);
1033
+ request.tools = flattened.schema;
1034
+ schemaWarnings.push(...flattened.warnings);
1035
+ } else {
1036
+ request.tools = input.tools;
621
1037
  }
622
- finalHttpErrorDetail = await response.text().catch(() => "");
623
- await sleep(backoffMs(attempt, response));
624
- }
625
- if (!response) {
626
- if (timeout) clearTimeout(timeout);
627
- throw new Error(`Provider ${this.id} failed to open a streaming response.`);
628
- }
1038
+ request.tool_choice = "auto";
1039
+ }
1040
+ const requestBody = JSON.stringify(this.adapter.preRequest ? this.adapter.preRequest(request) : request);
1041
+ resetTimeout();
1042
+ const retryOptions = {
1043
+ provider: this.adapter.id,
1044
+ concurrency: providerConcurrency(),
1045
+ fetch: () => {
1046
+ resetTimeout();
1047
+ return fetch(`${this.baseUrl}/chat/completions`, {
1048
+ method: "POST",
1049
+ signal: controller.signal,
1050
+ headers: {
1051
+ Authorization: `Bearer ${this.apiKey}`,
1052
+ "Content-Type": "application/json"
1053
+ },
1054
+ body: requestBody
1055
+ }).catch((error) => {
1056
+ if (controller.signal.aborted) {
1057
+ throw new Error(`Provider ${this.id} timed out before streaming a response.`);
1058
+ }
1059
+ throw error;
1060
+ });
1061
+ },
1062
+ ...input.onProviderThrottle ? { onThrottle: input.onProviderThrottle } : {}
1063
+ };
1064
+ const response = await fetchWithProviderRetry(retryOptions);
629
1065
  if (!response.ok || !response.body) {
630
1066
  if (timeout) clearTimeout(timeout);
631
- const detail = finalHttpErrorDetail || await response.text().catch(() => "");
1067
+ const detail = await response.text().catch(() => "");
632
1068
  throw new Error(`Provider ${this.id} returned HTTP ${response.status}: ${detail.slice(0, 500)}`);
633
1069
  }
634
1070
  const decoder = new TextDecoder();
635
1071
  let buffer = "";
636
1072
  const toolCallParts = /* @__PURE__ */ new Map();
637
1073
  try {
1074
+ if (schemaWarnings.length > 0) {
1075
+ yield { schemaWarnings };
1076
+ }
638
1077
  for await (const chunk of response.body) {
639
1078
  resetTimeout();
640
1079
  buffer += decoder.decode(chunk, { stream: true });
@@ -647,7 +1086,8 @@ var OpenAiCompatibleProvider = class {
647
1086
  if (data === "[DONE]") return;
648
1087
  let parsed;
649
1088
  try {
650
- parsed = JSON.parse(data);
1089
+ const rawParsed = JSON.parse(data);
1090
+ parsed = this.adapter.postResponse ? this.adapter.postResponse(rawParsed) : rawParsed;
651
1091
  } catch (error) {
652
1092
  if (envValue({}, "TANYA_DEBUG")) {
653
1093
  const message = error instanceof Error ? error.message : String(error);
@@ -700,7 +1140,7 @@ var OpenAiCompatibleProvider = class {
700
1140
  // src/providers/factory.ts
701
1141
  function createProvider(config) {
702
1142
  return new OpenAiCompatibleProvider({
703
- id: config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
1143
+ id: config.provider === "deepseek" && config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
704
1144
  apiKey: config.apiKey,
705
1145
  baseUrl: config.baseUrl,
706
1146
  model: config.model,
@@ -710,6 +1150,161 @@ function createProvider(config) {
710
1150
  });
711
1151
  }
712
1152
 
1153
+ // src/providers/parser.ts
1154
+ var TOOL_CALL_CORRECTION_LIMIT = 3;
1155
+ function malformedToolCallCorrectionMessage(reason) {
1156
+ return `[your last tool call was malformed: ${reason}. Try again with valid JSON.]`;
1157
+ }
1158
+ function parseProviderToolCalls(rawToolCalls, options = {}) {
1159
+ const warnings = [];
1160
+ const failures = [];
1161
+ const toolCalls = [];
1162
+ rawToolCalls.forEach((raw, index) => {
1163
+ const parsed = parseOneToolCall(raw, index, options.turn ?? 0);
1164
+ warnings.push(...parsed.warnings);
1165
+ if (parsed.failure) {
1166
+ failures.push(parsed.failure);
1167
+ return;
1168
+ }
1169
+ toolCalls.push(parsed.toolCall);
1170
+ });
1171
+ return { toolCalls, warnings, failures };
1172
+ }
1173
+ function parseToolArguments(rawArguments) {
1174
+ if (rawArguments === void 0 || rawArguments === null || rawArguments === "") {
1175
+ return { ok: true, input: {} };
1176
+ }
1177
+ if (typeof rawArguments === "object") {
1178
+ return { ok: true, input: rawArguments };
1179
+ }
1180
+ if (typeof rawArguments !== "string") {
1181
+ return {
1182
+ ok: false,
1183
+ reason: `tool arguments must be JSON object or string, got ${typeof rawArguments}`,
1184
+ rawArguments: String(rawArguments)
1185
+ };
1186
+ }
1187
+ if (!rawArguments.trim()) return { ok: true, input: {} };
1188
+ try {
1189
+ return { ok: true, input: JSON.parse(rawArguments) };
1190
+ } catch {
1191
+ const preview = previewRawToolArguments(rawArguments);
1192
+ return {
1193
+ ok: false,
1194
+ reason: `malformed JSON arguments: ${preview}`,
1195
+ rawArguments: preview
1196
+ };
1197
+ }
1198
+ }
1199
+ function parseOneToolCall(raw, index, turn) {
1200
+ const warnings = [];
1201
+ if (!isRecord2(raw)) {
1202
+ const toolCall3 = fallbackToolCall(turn, index, "__malformed_tool_call__", "");
1203
+ return {
1204
+ toolCall: toolCall3,
1205
+ warnings,
1206
+ failure: { reason: `tool call must be an object, got ${typeof raw}`, toolCall: toolCall3, raw }
1207
+ };
1208
+ }
1209
+ const idValue = typeof raw.id === "string" && raw.id.trim() ? raw.id : void 0;
1210
+ const id = idValue ?? `call_${turn}_${index}`;
1211
+ if (!idValue) {
1212
+ warnings.push({
1213
+ reason: `missing tool call id; synthesized ${id}`,
1214
+ toolCallId: id,
1215
+ raw
1216
+ });
1217
+ }
1218
+ const functionRecord = isRecord2(raw.function) ? raw.function : raw;
1219
+ if (!isRecord2(raw.function)) {
1220
+ warnings.push({
1221
+ reason: "missing function wrapper; accepted top-level name/arguments",
1222
+ toolCallId: id,
1223
+ raw
1224
+ });
1225
+ }
1226
+ const name = typeof functionRecord.name === "string" ? functionRecord.name.trim() : "";
1227
+ if (!name) {
1228
+ const toolCall3 = fallbackToolCall(turn, index, "__malformed_tool_call__", stringifyArguments(functionRecord.arguments));
1229
+ return {
1230
+ toolCall: toolCall3,
1231
+ warnings,
1232
+ failure: { reason: "missing function name", toolCall: toolCall3, raw }
1233
+ };
1234
+ }
1235
+ const normalizedArguments = normalizeArguments(functionRecord.arguments, warnings, id, name, raw);
1236
+ const toolCall2 = {
1237
+ id,
1238
+ type: "function",
1239
+ function: {
1240
+ name,
1241
+ arguments: normalizedArguments
1242
+ }
1243
+ };
1244
+ const parsedArguments = parseToolArguments(normalizedArguments);
1245
+ if (!parsedArguments.ok) {
1246
+ return {
1247
+ toolCall: toolCall2,
1248
+ warnings,
1249
+ failure: { reason: parsedArguments.reason, toolCall: toolCall2, raw }
1250
+ };
1251
+ }
1252
+ return { toolCall: toolCall2, warnings };
1253
+ }
1254
+ function normalizeArguments(value, warnings, toolCallId, tool, raw) {
1255
+ if (value === void 0 || value === null) {
1256
+ warnings.push({
1257
+ reason: "missing arguments; using empty object",
1258
+ toolCallId,
1259
+ tool,
1260
+ raw
1261
+ });
1262
+ return "{}";
1263
+ }
1264
+ if (typeof value === "string") return value;
1265
+ if (typeof value === "object") {
1266
+ warnings.push({
1267
+ reason: "arguments arrived as object; stringified for OpenAI-compatible history",
1268
+ toolCallId,
1269
+ tool,
1270
+ raw
1271
+ });
1272
+ return JSON.stringify(value);
1273
+ }
1274
+ warnings.push({
1275
+ reason: `arguments arrived as ${typeof value}; stringified for correction`,
1276
+ toolCallId,
1277
+ tool,
1278
+ raw
1279
+ });
1280
+ return String(value);
1281
+ }
1282
+ function stringifyArguments(value) {
1283
+ if (value === void 0 || value === null) return "";
1284
+ if (typeof value === "string") return value;
1285
+ try {
1286
+ return JSON.stringify(value);
1287
+ } catch {
1288
+ return String(value);
1289
+ }
1290
+ }
1291
+ function fallbackToolCall(turn, index, name, args) {
1292
+ return {
1293
+ id: `call_${turn}_${index}`,
1294
+ type: "function",
1295
+ function: {
1296
+ name,
1297
+ arguments: args
1298
+ }
1299
+ };
1300
+ }
1301
+ function previewRawToolArguments(raw) {
1302
+ return raw.length > 500 ? `${raw.slice(0, 500)}...[truncated ${raw.length - 500} chars]` : raw;
1303
+ }
1304
+ function isRecord2(value) {
1305
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1306
+ }
1307
+
713
1308
  // src/safety/workspace.ts
714
1309
  import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
715
1310
  import { dirname as dirname2, resolve as resolve3, relative as relative2 } from "path";
@@ -757,28 +1352,28 @@ import sharp2 from "sharp";
757
1352
  import { mkdir, readFile, writeFile } from "fs/promises";
758
1353
  import { dirname as dirname3 } from "path";
759
1354
  import sharp from "sharp";
760
- function asRecord(input2) {
761
- return input2 && typeof input2 === "object" ? input2 : {};
1355
+ function asRecord(input) {
1356
+ return input && typeof input === "object" ? input : {};
762
1357
  }
763
- function asString2(input2, key) {
764
- const value = asRecord(input2)[key];
1358
+ function asString2(input, key) {
1359
+ const value = asRecord(input)[key];
765
1360
  if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
766
1361
  return value;
767
1362
  }
768
- function asOptionalString(input2, key) {
769
- const value = asRecord(input2)[key];
1363
+ function asOptionalString(input, key) {
1364
+ const value = asRecord(input)[key];
770
1365
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
771
1366
  }
772
- function asOptionalNumber(input2, key, fallback) {
773
- const value = asRecord(input2)[key];
1367
+ function asOptionalNumber(input, key, fallback) {
1368
+ const value = asRecord(input)[key];
774
1369
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
775
1370
  }
776
1371
  function ensureRelativePath(path) {
777
1372
  if (path.startsWith("/")) throw new Error(`Path must be relative to the workspace: ${path}`);
778
1373
  return path;
779
1374
  }
780
- function optionalStringArray(input2, key, fallback) {
781
- const value = asRecord(input2)[key];
1375
+ function optionalStringArray(input, key, fallback) {
1376
+ const value = asRecord(input)[key];
782
1377
  if (!Array.isArray(value)) return fallback;
783
1378
  return value.filter((item) => typeof item === "string" && item.trim().length > 0);
784
1379
  }
@@ -855,12 +1450,12 @@ var resizeImageTool = {
855
1450
  }
856
1451
  }
857
1452
  },
858
- async run(input2, context) {
859
- const source = ensureRelativePath(asString2(input2, "source"));
860
- const destination = ensureRelativePath(asString2(input2, "destination"));
861
- const width = Math.round(asOptionalNumber(input2, "width", 0));
862
- const height = Math.round(asOptionalNumber(input2, "height", width));
863
- const background = asOptionalString(input2, "background") ?? "#ffffff";
1453
+ async run(input, context) {
1454
+ const source = ensureRelativePath(asString2(input, "source"));
1455
+ const destination = ensureRelativePath(asString2(input, "destination"));
1456
+ const width = Math.round(asOptionalNumber(input, "width", 0));
1457
+ const height = Math.round(asOptionalNumber(input, "height", width));
1458
+ const background = asOptionalString(input, "background") ?? "#ffffff";
864
1459
  if (width <= 0 || height <= 0) return { ok: false, summary: "Invalid image dimensions.", error: "width and height must be positive." };
865
1460
  const sourceAbs = resolveInsideWorkspace(context.workspace, source);
866
1461
  const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
@@ -895,12 +1490,12 @@ var renderSvgToPngTool = {
895
1490
  }
896
1491
  }
897
1492
  },
898
- async run(input2, context) {
899
- const source = ensureRelativePath(asString2(input2, "source"));
900
- const destination = ensureRelativePath(asString2(input2, "destination"));
901
- const width = Math.round(asOptionalNumber(input2, "width", 1024));
902
- const height = Math.round(asOptionalNumber(input2, "height", width));
903
- const background = asOptionalString(input2, "background") ?? "#ffffff";
1493
+ async run(input, context) {
1494
+ const source = ensureRelativePath(asString2(input, "source"));
1495
+ const destination = ensureRelativePath(asString2(input, "destination"));
1496
+ const width = Math.round(asOptionalNumber(input, "width", 1024));
1497
+ const height = Math.round(asOptionalNumber(input, "height", width));
1498
+ const background = asOptionalString(input, "background") ?? "#ffffff";
904
1499
  if (width <= 0 || height <= 0) return { ok: false, summary: "Invalid image dimensions.", error: "width and height must be positive." };
905
1500
  const sourceAbs = resolveInsideWorkspace(context.workspace, source);
906
1501
  const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
@@ -940,12 +1535,12 @@ var createAppleAppIconSetTool = {
940
1535
  }
941
1536
  }
942
1537
  },
943
- async run(input2, context) {
944
- const source = ensureRelativePath(asString2(input2, "source"));
945
- const outputDir = ensureRelativePath(asString2(input2, "outputDir"));
946
- const platforms = optionalStringArray(input2, "platforms", ["ios"]);
1538
+ async run(input, context) {
1539
+ const source = ensureRelativePath(asString2(input, "source"));
1540
+ const outputDir = ensureRelativePath(asString2(input, "outputDir"));
1541
+ const platforms = optionalStringArray(input, "platforms", ["ios"]);
947
1542
  const slots = slotsForPlatforms(platforms);
948
- const background = asOptionalString(input2, "background") ?? "#ffffff";
1543
+ const background = asOptionalString(input, "background") ?? "#ffffff";
949
1544
  if (slots.length === 0) {
950
1545
  return { ok: false, summary: "No Apple icon platforms selected.", error: "Use platforms ['ios'], ['macos'], or both." };
951
1546
  }
@@ -1016,12 +1611,12 @@ var createAndroidLauncherIconSetTool = {
1016
1611
  }
1017
1612
  }
1018
1613
  },
1019
- async run(input2, context) {
1020
- const source = ensureRelativePath(asString2(input2, "source"));
1021
- const resDir = ensureRelativePath(asString2(input2, "resDir"));
1022
- const background = asOptionalString(input2, "background") ?? "#ffffff";
1023
- const iconName = sanitizeName(asOptionalString(input2, "iconName") ?? "ic_launcher");
1024
- const roundIconName = sanitizeName(asOptionalString(input2, "roundIconName") ?? "ic_launcher_round");
1614
+ async run(input, context) {
1615
+ const source = ensureRelativePath(asString2(input, "source"));
1616
+ const resDir = ensureRelativePath(asString2(input, "resDir"));
1617
+ const background = asOptionalString(input, "background") ?? "#ffffff";
1618
+ const iconName = sanitizeName(asOptionalString(input, "iconName") ?? "ic_launcher");
1619
+ const roundIconName = sanitizeName(asOptionalString(input, "roundIconName") ?? "ic_launcher_round");
1025
1620
  const sourceAbs = resolveInsideWorkspace(context.workspace, source);
1026
1621
  const generatedFiles = [];
1027
1622
  for (const density of androidLauncherDensities) {
@@ -1120,10 +1715,10 @@ var validateAppleAppIconSetTool = {
1120
1715
  }
1121
1716
  }
1122
1717
  },
1123
- async run(input2, context) {
1124
- const appIconSetDir = ensureRelativePath(asString2(input2, "appIconSetDir"));
1125
- const platforms = optionalStringArray(input2, "platforms", ["ios"]);
1126
- const requireNoAlpha = asRecord(input2).requireNoAlpha !== false;
1718
+ async run(input, context) {
1719
+ const appIconSetDir = ensureRelativePath(asString2(input, "appIconSetDir"));
1720
+ const platforms = optionalStringArray(input, "platforms", ["ios"]);
1721
+ const requireNoAlpha = asRecord(input).requireNoAlpha !== false;
1127
1722
  const expectedSlots = slotsForPlatforms(platforms);
1128
1723
  const contentsPath = `${appIconSetDir}/Contents.json`;
1129
1724
  const contentsText = await readFile(resolveInsideWorkspace(context.workspace, contentsPath), "utf8");
@@ -1173,10 +1768,10 @@ var validateAndroidLauncherIconSetTool = {
1173
1768
  }
1174
1769
  }
1175
1770
  },
1176
- async run(input2, context) {
1177
- const resDir = ensureRelativePath(asString2(input2, "resDir"));
1178
- const iconName = sanitizeName(asOptionalString(input2, "iconName") ?? "ic_launcher");
1179
- const roundIconName = sanitizeName(asOptionalString(input2, "roundIconName") ?? "ic_launcher_round");
1771
+ async run(input, context) {
1772
+ const resDir = ensureRelativePath(asString2(input, "resDir"));
1773
+ const iconName = sanitizeName(asOptionalString(input, "iconName") ?? "ic_launcher");
1774
+ const roundIconName = sanitizeName(asOptionalString(input, "roundIconName") ?? "ic_launcher_round");
1180
1775
  const problems = [];
1181
1776
  for (const density of androidLauncherDensities) {
1182
1777
  for (const [fileName, expectedPx] of [
@@ -1227,19 +1822,19 @@ import { mkdir as mkdir2, writeFile as writeFile2, copyFile } from "fs/promises"
1227
1822
  import { dirname as dirname4, join as join2 } from "path";
1228
1823
  import { spawn, spawnSync } from "child_process";
1229
1824
  import WebSocket from "ws";
1230
- function asRecord2(input2) {
1231
- return input2 && typeof input2 === "object" ? input2 : {};
1825
+ function asRecord2(input) {
1826
+ return input && typeof input === "object" ? input : {};
1232
1827
  }
1233
- function asOptionalString2(input2, key) {
1234
- const value = asRecord2(input2)[key];
1828
+ function asOptionalString2(input, key) {
1829
+ const value = asRecord2(input)[key];
1235
1830
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
1236
1831
  }
1237
- function asOptionalNumber2(input2, key, fallback) {
1238
- const value = asRecord2(input2)[key];
1832
+ function asOptionalNumber2(input, key, fallback) {
1833
+ const value = asRecord2(input)[key];
1239
1834
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1240
1835
  }
1241
- function optionalStringArray2(input2, key, fallback) {
1242
- const value = asRecord2(input2)[key];
1836
+ function optionalStringArray2(input, key, fallback) {
1837
+ const value = asRecord2(input)[key];
1243
1838
  if (!Array.isArray(value)) return fallback;
1244
1839
  return value.filter((item) => typeof item === "string" && item.trim().length > 0);
1245
1840
  }
@@ -1406,7 +2001,7 @@ function terminalSvg(config, frame) {
1406
2001
  ${body}
1407
2002
  </svg>`;
1408
2003
  }
1409
- function sleep2(ms) {
2004
+ function sleep(ms) {
1410
2005
  return new Promise((resolve13) => setTimeout(resolve13, ms));
1411
2006
  }
1412
2007
  async function waitForJson(url, timeout = 1e4) {
@@ -1417,7 +2012,7 @@ async function waitForJson(url, timeout = 1e4) {
1417
2012
  if (res.ok) return await res.json();
1418
2013
  } catch {
1419
2014
  }
1420
- await sleep2(120);
2015
+ await sleep(120);
1421
2016
  }
1422
2017
  throw new Error(`Timed out waiting for ${url}`);
1423
2018
  }
@@ -1483,7 +2078,7 @@ async function captureFrames(config, frameDir, svgDir, chromeProfile) {
1483
2078
  await page.send("Emulation.setDefaultBackgroundColorOverride", { color: { r: 0, g: 0, b: 0, a: 0 } });
1484
2079
  const html = `<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;width:${config.width}px;height:${config.height}px;overflow:hidden;background:transparent;}svg{display:block;width:${config.width}px;height:${config.height}px;}</style></head><body></body></html>`;
1485
2080
  await page.send("Page.navigate", { url: `data:text/html;charset=utf-8,${encodeURIComponent(html)}` });
1486
- await sleep2(250);
2081
+ await sleep(250);
1487
2082
  const totalFrames = Math.round(config.fps * config.duration);
1488
2083
  for (let i = 0; i < totalFrames; i += 1) {
1489
2084
  const n = String(i + 1).padStart(4, "0");
@@ -1647,25 +2242,25 @@ var generateVideoAssetTool = {
1647
2242
  }
1648
2243
  }
1649
2244
  },
1650
- async run(input2, context) {
1651
- const preset = asOptionalString2(input2, "preset");
1652
- const outputDir = asOptionalString2(input2, "outputDir");
1653
- const basename4 = asOptionalString2(input2, "basename");
2245
+ async run(input, context) {
2246
+ const preset = asOptionalString2(input, "preset");
2247
+ const outputDir = asOptionalString2(input, "outputDir");
2248
+ const basename5 = asOptionalString2(input, "basename");
1654
2249
  const options = {
1655
- width: asOptionalNumber2(input2, "width", 980),
1656
- height: asOptionalNumber2(input2, "height", 1012),
1657
- fps: asOptionalNumber2(input2, "fps", 30),
1658
- duration: asOptionalNumber2(input2, "duration", 3),
1659
- formats: optionalStringArray2(input2, "formats", ["webm", "mov", "poster"]),
1660
- lines: optionalStringArray2(input2, "lines", [])
2250
+ width: asOptionalNumber2(input, "width", 980),
2251
+ height: asOptionalNumber2(input, "height", 1012),
2252
+ fps: asOptionalNumber2(input, "fps", 30),
2253
+ duration: asOptionalNumber2(input, "duration", 3),
2254
+ formats: optionalStringArray2(input, "formats", ["webm", "mov", "poster"]),
2255
+ lines: optionalStringArray2(input, "lines", [])
1661
2256
  };
1662
2257
  if (preset) options.preset = preset;
1663
2258
  if (outputDir) options.outputDir = outputDir;
1664
- if (basename4) options.basename = basename4;
1665
- const title = asOptionalString2(input2, "title");
1666
- const tab = asOptionalString2(input2, "tab");
1667
- const secondaryTab = asOptionalString2(input2, "secondaryTab");
1668
- const badge = asOptionalString2(input2, "badge");
2259
+ if (basename5) options.basename = basename5;
2260
+ const title = asOptionalString2(input, "title");
2261
+ const tab = asOptionalString2(input, "tab");
2262
+ const secondaryTab = asOptionalString2(input, "secondaryTab");
2263
+ const badge = asOptionalString2(input, "badge");
1669
2264
  if (title) options.title = title;
1670
2265
  if (tab) options.tab = tab;
1671
2266
  if (secondaryTab) options.secondaryTab = secondaryTab;
@@ -1773,24 +2368,24 @@ var artifactFileExtensions = /* @__PURE__ */ new Set([
1773
2368
  ".yaml",
1774
2369
  ".css"
1775
2370
  ]);
1776
- function asRecord3(input2) {
1777
- return input2 && typeof input2 === "object" ? input2 : {};
2371
+ function asRecord3(input) {
2372
+ return input && typeof input === "object" ? input : {};
1778
2373
  }
1779
- function asString3(input2, key) {
1780
- const value = asRecord3(input2)[key];
2374
+ function asString3(input, key) {
2375
+ const value = asRecord3(input)[key];
1781
2376
  if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
1782
2377
  return value.trim();
1783
2378
  }
1784
- function asOptionalString3(input2, key) {
1785
- const value = asRecord3(input2)[key];
2379
+ function asOptionalString3(input, key) {
2380
+ const value = asRecord3(input)[key];
1786
2381
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
1787
2382
  }
1788
- function asOptionalNumber3(input2, key, fallback) {
1789
- const value = asRecord3(input2)[key];
2383
+ function asOptionalNumber3(input, key, fallback) {
2384
+ const value = asRecord3(input)[key];
1790
2385
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1791
2386
  }
1792
- function asOptionalBoolean(input2, key, fallback) {
1793
- const value = asRecord3(input2)[key];
2387
+ function asOptionalBoolean(input, key, fallback) {
2388
+ const value = asRecord3(input)[key];
1794
2389
  if (typeof value === "boolean") return value;
1795
2390
  if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
1796
2391
  return fallback;
@@ -2075,38 +2670,38 @@ function inferTaskSignals(task, workspace) {
2075
2670
  return { platforms: [...platforms].sort(), domains: [...domains].sort() };
2076
2671
  }
2077
2672
  function recommendedVerificationCommands(files, packageScripts, signals) {
2078
- const commands = /* @__PURE__ */ new Set();
2673
+ const commands2 = /* @__PURE__ */ new Set();
2079
2674
  const hasExplicitPlatformSignal = signals.platforms.length > 0;
2080
2675
  const hasScript = (name) => Object.prototype.hasOwnProperty.call(packageScripts, name);
2081
- if (hasScript("prisma:generate")) commands.add("npm run prisma:generate");
2082
- if (hasScript("typecheck")) commands.add("npm run typecheck");
2083
- if (hasScript("test")) commands.add("npm test");
2084
- if (hasScript("lint")) commands.add("npm run lint");
2085
- if (hasScript("build")) commands.add("npm run build");
2676
+ if (hasScript("prisma:generate")) commands2.add("npm run prisma:generate");
2677
+ if (hasScript("typecheck")) commands2.add("npm run typecheck");
2678
+ if (hasScript("test")) commands2.add("npm test");
2679
+ if (hasScript("lint")) commands2.add("npm run lint");
2680
+ if (hasScript("build")) commands2.add("npm run build");
2086
2681
  const text2 = files.join("\n").toLowerCase();
2087
2682
  if (signals.platforms.includes("android") || !hasExplicitPlatformSignal && /gradlew|settings\.gradle|androidmanifest\.xml/.test(text2)) {
2088
- commands.add("./gradlew test --no-daemon");
2089
- commands.add("./gradlew assembleDebug --no-daemon");
2090
- if (/ktlint/.test(text2)) commands.add("./gradlew ktlintCheck --no-daemon");
2683
+ commands2.add("./gradlew test --no-daemon");
2684
+ commands2.add("./gradlew assembleDebug --no-daemon");
2685
+ if (/ktlint/.test(text2)) commands2.add("./gradlew ktlintCheck --no-daemon");
2091
2686
  }
2092
2687
  if (signals.platforms.includes("ios") || signals.platforms.includes("macos") || !hasExplicitPlatformSignal && /\.xcodeproj|\.xcworkspace/.test(text2)) {
2093
- commands.add("xcodebuild -list");
2688
+ commands2.add("xcodebuild -list");
2094
2689
  }
2095
2690
  if (signals.platforms.includes("script")) {
2096
2691
  if (/cargo\.toml/.test(text2)) {
2097
- commands.add("cargo build --release");
2098
- commands.add("cargo test");
2692
+ commands2.add("cargo build --release");
2693
+ commands2.add("cargo test");
2099
2694
  }
2100
2695
  if (/pyproject\.toml|setup\.py/.test(text2)) {
2101
- commands.add("python -m pytest");
2102
- commands.add("python -m build");
2696
+ commands2.add("python -m pytest");
2697
+ commands2.add("python -m build");
2103
2698
  }
2104
2699
  if (/go\.mod/.test(text2)) {
2105
- commands.add("go build ./...");
2106
- commands.add("go test ./...");
2700
+ commands2.add("go build ./...");
2701
+ commands2.add("go test ./...");
2107
2702
  }
2108
2703
  }
2109
- return [...commands];
2704
+ return [...commands2];
2110
2705
  }
2111
2706
  function capabilityPacksForSignals(signals) {
2112
2707
  const packs = [];
@@ -2215,16 +2810,16 @@ async function findArtifactsForSignals(params) {
2215
2810
  }
2216
2811
  return merged;
2217
2812
  }
2218
- async function buildTaskBrief(input2) {
2219
- const maxArtifacts = Math.min(input2.maxArtifacts ?? 12, 40);
2220
- const maxContextFiles = Math.min(input2.maxContextFiles ?? 16, 50);
2221
- const files = await collectFiles(input2.workspace, 1e3);
2222
- const packageScripts = await readPackageScripts(input2.workspace);
2223
- const signals = inferTaskSignals(input2.task, input2.workspace);
2813
+ async function buildTaskBrief(input) {
2814
+ const maxArtifacts = Math.min(input.maxArtifacts ?? 12, 40);
2815
+ const maxContextFiles = Math.min(input.maxContextFiles ?? 16, 50);
2816
+ const files = await collectFiles(input.workspace, 1e3);
2817
+ const packageScripts = await readPackageScripts(input.workspace);
2818
+ const signals = inferTaskSignals(input.task, input.workspace);
2224
2819
  const contextFiles = files.filter((file) => !file.endsWith("/")).map((file) => ({ path: file, role: classifyContextFile(file) })).filter((file) => Boolean(file.role)).slice(0, maxContextFiles);
2225
- const artifactQuery = [input2.task, signals.platforms.join(" "), signals.domains.join(" ")].filter(Boolean).join(" ");
2820
+ const artifactQuery = [input.task, signals.platforms.join(" "), signals.domains.join(" ")].filter(Boolean).join(" ");
2226
2821
  const artifacts = await findArtifactsForSignals({
2227
- workspace: input2.workspace,
2822
+ workspace: input.workspace,
2228
2823
  query: artifactQuery,
2229
2824
  signals,
2230
2825
  maxResults: maxArtifacts
@@ -2243,7 +2838,7 @@ async function buildTaskBrief(input2) {
2243
2838
  "scan_secrets"
2244
2839
  ].filter((value) => typeof value === "string");
2245
2840
  return {
2246
- task: input2.task,
2841
+ task: input.task,
2247
2842
  signals,
2248
2843
  contextFiles,
2249
2844
  artifacts,
@@ -2277,13 +2872,13 @@ var inspectProjectContextTool = {
2277
2872
  }
2278
2873
  }
2279
2874
  },
2280
- async run(input2, context) {
2281
- const record = asRecord3(input2);
2875
+ async run(input, context) {
2876
+ const record = asRecord3(input);
2282
2877
  const root = resolveOptionalWorkspacePath(context, asOptionalString3(record, "path"));
2283
2878
  const rootRel = normalizePath2(relative3(context.workspace, root)) || ".";
2284
- const includeExcerpts = asOptionalBoolean(input2, "includeExcerpts", true);
2285
- const maxFiles = Math.min(asOptionalNumber3(input2, "maxFiles", 500), 2e3);
2286
- const maxExcerptChars = Math.min(asOptionalNumber3(input2, "maxExcerptChars", 900), 4e3);
2879
+ const includeExcerpts = asOptionalBoolean(input, "includeExcerpts", true);
2880
+ const maxFiles = Math.min(asOptionalNumber3(input, "maxFiles", 500), 2e3);
2881
+ const maxExcerptChars = Math.min(asOptionalNumber3(input, "maxExcerptChars", 900), 4e3);
2287
2882
  const files = await collectFiles(root, maxFiles);
2288
2883
  const platforms = detectPlatforms(files);
2289
2884
  const packageScripts = await readPackageScripts(root);
@@ -2351,11 +2946,11 @@ var findReusableArtifactsTool = {
2351
2946
  }
2352
2947
  }
2353
2948
  },
2354
- async run(input2, context) {
2355
- const query = asOptionalString3(input2, "query") ?? "";
2356
- const platform = asOptionalString3(input2, "platform");
2357
- const maxResults = Math.min(asOptionalNumber3(input2, "maxResults", 12), 50);
2358
- const artifactRoot = asOptionalString3(input2, "artifactRoot");
2949
+ async run(input, context) {
2950
+ const query = asOptionalString3(input, "query") ?? "";
2951
+ const platform = asOptionalString3(input, "platform");
2952
+ const maxResults = Math.min(asOptionalNumber3(input, "maxResults", 12), 50);
2953
+ const artifactRoot = asOptionalString3(input, "artifactRoot");
2359
2954
  const artifacts = await findArtifacts({
2360
2955
  workspace: context.workspace,
2361
2956
  query,
@@ -2395,13 +2990,13 @@ var buildTaskBriefTool = {
2395
2990
  }
2396
2991
  }
2397
2992
  },
2398
- async run(input2, context) {
2399
- const task = asString3(input2, "task");
2993
+ async run(input, context) {
2994
+ const task = asString3(input, "task");
2400
2995
  const brief = await buildTaskBrief({
2401
2996
  workspace: context.workspace,
2402
2997
  task,
2403
- maxArtifacts: Math.min(asOptionalNumber3(input2, "maxArtifacts", 12), 40),
2404
- maxContextFiles: Math.min(asOptionalNumber3(input2, "maxContextFiles", 16), 50)
2998
+ maxArtifacts: Math.min(asOptionalNumber3(input, "maxArtifacts", 12), 40),
2999
+ maxContextFiles: Math.min(asOptionalNumber3(input, "maxContextFiles", 16), 50)
2405
3000
  });
2406
3001
  return {
2407
3002
  ok: true,
@@ -2513,13 +3108,13 @@ function scoreNote(relPath, title, markdown, queryTerms) {
2513
3108
  function safeMaterializedPath(relPath) {
2514
3109
  return normalizePath3(relPath).replace(/^\.+\/?/, "").replace(/[^A-Za-z0-9._/-]/g, "_").replace(/\/+/g, "/");
2515
3110
  }
2516
- async function searchObsidianNotes(input2) {
2517
- const vault = resolve5(input2.vaultPath);
3111
+ async function searchObsidianNotes(input) {
3112
+ const vault = resolve5(input.vaultPath);
2518
3113
  if (!existsSync6(vault)) return [];
2519
- const queryTerms = terms(input2.query);
2520
- const maxResults = Math.min(input2.maxResults ?? 5, 20);
2521
- const maxFiles = Math.min(input2.maxFiles ?? 1e3, 5e3);
2522
- const maxExcerptChars = Math.min(input2.maxExcerptChars ?? 1800, 8e3);
3114
+ const queryTerms = terms(input.query);
3115
+ const maxResults = Math.min(input.maxResults ?? 5, 20);
3116
+ const maxFiles = Math.min(input.maxFiles ?? 1e3, 5e3);
3117
+ const maxExcerptChars = Math.min(input.maxExcerptChars ?? 1800, 8e3);
2523
3118
  const files = await collectMarkdownFiles(vault, maxFiles);
2524
3119
  const results = [];
2525
3120
  for (const file of files) {
@@ -2545,13 +3140,13 @@ async function searchObsidianNotes(input2) {
2545
3140
  }
2546
3141
  return results.sort((a, b) => b.score - a.score || String(b.modifiedAt ?? "").localeCompare(String(a.modifiedAt ?? "")) || a.path.localeCompare(b.path)).slice(0, maxResults);
2547
3142
  }
2548
- async function materializeObsidianContext(input2) {
3143
+ async function materializeObsidianContext(input) {
2549
3144
  const notes = await searchObsidianNotes({
2550
- vaultPath: input2.vaultPath,
2551
- query: input2.query,
2552
- maxResults: input2.maxResults ?? 5
3145
+ vaultPath: input.vaultPath,
3146
+ query: input.query,
3147
+ maxResults: input.maxResults ?? 5
2553
3148
  });
2554
- const contextRoot = resolve5(input2.workspace, ".tania", "context", "obsidian");
3149
+ const contextRoot = resolve5(input.workspace, ".tania", "context", "obsidian");
2555
3150
  const contextFiles = [];
2556
3151
  for (const note of notes) {
2557
3152
  const targetRel = safeMaterializedPath(note.path);
@@ -2584,7 +3179,7 @@ async function materializeObsidianContext(input2) {
2584
3179
  await mkdir3(contextRoot, { recursive: true });
2585
3180
  await writeFile3(
2586
3181
  resolve5(contextRoot, "manifest.json"),
2587
- JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), query: input2.query, notes }, null, 2),
3182
+ JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), query: input.query, notes }, null, 2),
2588
3183
  "utf8"
2589
3184
  );
2590
3185
  }
@@ -2592,19 +3187,19 @@ async function materializeObsidianContext(input2) {
2592
3187
  }
2593
3188
 
2594
3189
  // src/tools/obsidianTools.ts
2595
- function asRecord4(input2) {
2596
- return input2 && typeof input2 === "object" ? input2 : {};
3190
+ function asRecord4(input) {
3191
+ return input && typeof input === "object" ? input : {};
2597
3192
  }
2598
- function asOptionalString4(input2, key) {
2599
- const value = asRecord4(input2)[key];
3193
+ function asOptionalString4(input, key) {
3194
+ const value = asRecord4(input)[key];
2600
3195
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
2601
3196
  }
2602
- function asOptionalNumber4(input2, key, fallback) {
2603
- const value = asRecord4(input2)[key];
3197
+ function asOptionalNumber4(input, key, fallback) {
3198
+ const value = asRecord4(input)[key];
2604
3199
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
2605
3200
  }
2606
- function asOptionalBoolean2(input2, key, fallback) {
2607
- const value = asRecord4(input2)[key];
3201
+ function asOptionalBoolean2(input, key, fallback) {
3202
+ const value = asRecord4(input)[key];
2608
3203
  if (typeof value === "boolean") return value;
2609
3204
  if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
2610
3205
  return fallback;
@@ -2629,8 +3224,8 @@ var searchObsidianNotesTool = {
2629
3224
  }
2630
3225
  }
2631
3226
  },
2632
- async run(input2, context) {
2633
- const query = asOptionalString4(input2, "query");
3227
+ async run(input, context) {
3228
+ const query = asOptionalString4(input, "query");
2634
3229
  if (!query) throw new Error("Missing string field: query");
2635
3230
  const vaultPath = envValue({}, "TANYA_OBSIDIAN_VAULT").trim();
2636
3231
  if (!vaultPath) {
@@ -2640,8 +3235,8 @@ var searchObsidianNotesTool = {
2640
3235
  output: { notes: [], guidance: "Set TANYA_OBSIDIAN_VAULT to enable generic note retrieval." }
2641
3236
  };
2642
3237
  }
2643
- const maxResults = Math.min(asOptionalNumber4(input2, "maxResults", 5), 20);
2644
- if (asOptionalBoolean2(input2, "materialize", false)) {
3238
+ const maxResults = Math.min(asOptionalNumber4(input, "maxResults", 5), 20);
3239
+ if (asOptionalBoolean2(input, "materialize", false)) {
2645
3240
  const materialized = await materializeObsidianContext({
2646
3241
  workspace: context.workspace,
2647
3242
  vaultPath,
@@ -2679,24 +3274,24 @@ function localPropertiesWriteError() {
2679
3274
  error: "local.properties is machine-local Android SDK configuration. Do not create or modify it; use ANDROID_HOME or ANDROID_SDK_ROOT for verification instead."
2680
3275
  };
2681
3276
  }
2682
- function asRecord5(input2) {
2683
- return input2 && typeof input2 === "object" ? input2 : {};
3277
+ function asRecord5(input) {
3278
+ return input && typeof input === "object" ? input : {};
2684
3279
  }
2685
- function asString4(input2, key) {
2686
- const value = asRecord5(input2)[key];
3280
+ function asString4(input, key) {
3281
+ const value = asRecord5(input)[key];
2687
3282
  if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
2688
3283
  return value;
2689
3284
  }
2690
- function asOptionalNumber5(input2, key, fallback) {
2691
- const value = asRecord5(input2)[key];
3285
+ function asOptionalNumber5(input, key, fallback) {
3286
+ const value = asRecord5(input)[key];
2692
3287
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
2693
3288
  }
2694
- function asOptionalString5(input2, key) {
2695
- const value = asRecord5(input2)[key];
3289
+ function asOptionalString5(input, key) {
3290
+ const value = asRecord5(input)[key];
2696
3291
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
2697
3292
  }
2698
- function asOptionalBoolean3(input2, key, fallback) {
2699
- const value = asRecord5(input2)[key];
3293
+ function asOptionalBoolean3(input, key, fallback) {
3294
+ const value = asRecord5(input)[key];
2700
3295
  if (typeof value === "boolean") return value;
2701
3296
  if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
2702
3297
  return fallback;
@@ -2738,15 +3333,15 @@ function runProcess(command, args, context, timeoutMs, cwd = context.workspace)
2738
3333
  });
2739
3334
  child.on("close", (code) => {
2740
3335
  clearTimeout(timer);
2741
- const output2 = `${stdout}${stderr ? `
3336
+ const output = `${stdout}${stderr ? `
2742
3337
  ${stderr}` : ""}`.trim();
2743
- const displayOutput = summarizeProcessOutput(output2, 12e3);
3338
+ const displayOutput = summarizeProcessOutput(output, 12e3);
2744
3339
  const baseResult = {
2745
3340
  ok: code === 0,
2746
- summary: buildProcessSummary("Command", code, output2, displayOutput.truncated),
3341
+ summary: buildProcessSummary("Command", code, output, displayOutput.truncated),
2747
3342
  output: displayOutput.text
2748
3343
  };
2749
- resolve13(code === 0 ? baseResult : { ...baseResult, error: output2.slice(0, 2e3) });
3344
+ resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
2750
3345
  });
2751
3346
  child.on("error", (err) => {
2752
3347
  clearTimeout(timer);
@@ -2851,9 +3446,9 @@ ${stderr}` : ""}`.trim();
2851
3446
  });
2852
3447
  child.on("close", (code) => {
2853
3448
  cleanupShell();
2854
- const output2 = outputSoFar();
3449
+ const output = outputSoFar();
2855
3450
  if (cancelled || context.signal?.aborted) {
2856
- const partialOutput = summarizeProcessOutput(output2, 16e3).text;
3451
+ const partialOutput = summarizeProcessOutput(output, 16e3).text;
2857
3452
  resolve13({
2858
3453
  ok: false,
2859
3454
  summary: "Shell cancelled by user. Partial output captured.",
@@ -2864,13 +3459,13 @@ ${stderr}` : ""}`.trim();
2864
3459
  });
2865
3460
  return;
2866
3461
  }
2867
- const displayOutput = summarizeProcessOutput(output2, 16e3);
3462
+ const displayOutput = summarizeProcessOutput(output, 16e3);
2868
3463
  const baseResult = {
2869
3464
  ok: code === 0,
2870
- summary: buildProcessSummary("Shell", code, output2, displayOutput.truncated),
3465
+ summary: buildProcessSummary("Shell", code, output, displayOutput.truncated),
2871
3466
  output: displayOutput.text
2872
3467
  };
2873
- resolve13(code === 0 ? baseResult : { ...baseResult, error: output2.slice(0, 2e3) });
3468
+ resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
2874
3469
  });
2875
3470
  child.on("error", (err) => {
2876
3471
  cleanupShell();
@@ -2900,28 +3495,28 @@ function unsafeHostPackageMutation(script) {
2900
3495
  }
2901
3496
  return null;
2902
3497
  }
2903
- function summarizeProcessOutput(output2, maxChars) {
2904
- if (output2.length <= maxChars) return { text: output2, truncated: false };
3498
+ function summarizeProcessOutput(output, maxChars) {
3499
+ if (output.length <= maxChars) return { text: output, truncated: false };
2905
3500
  const headSize = Math.floor(maxChars * 0.35);
2906
3501
  const tailSize = maxChars - headSize - 120;
2907
3502
  return {
2908
- text: `${output2.slice(0, headSize)}
3503
+ text: `${output.slice(0, headSize)}
2909
3504
 
2910
3505
  [output truncated: showing head and tail; exit code remains authoritative]
2911
3506
 
2912
- ${output2.slice(-tailSize)}`,
3507
+ ${output.slice(-tailSize)}`,
2913
3508
  truncated: true
2914
3509
  };
2915
3510
  }
2916
- function buildProcessSummary(kind, code, output2, truncated) {
3511
+ function buildProcessSummary(kind, code, output, truncated) {
2917
3512
  const exit = code ?? "unknown";
2918
- const successHint = code === 0 && /BUILD SUCCEEDED|Test Suite '.+' passed|0 failures|Process completed successfully/i.test(output2) ? " Success marker found." : "";
3513
+ const successHint = code === 0 && /BUILD SUCCEEDED|Test Suite '.+' passed|0 failures|Process completed successfully/i.test(output) ? " Success marker found." : "";
2919
3514
  const truncatedHint = truncated ? " Output was truncated for display, but the exit code is authoritative." : "";
2920
3515
  return `${kind} exited ${exit}.${successHint}${truncatedHint}`;
2921
3516
  }
2922
- function filterTypeScriptErrorOutput(output2) {
2923
- if (typeof output2 !== "string" || !output2.trim()) return null;
2924
- const lines = output2.split(/\r?\n/);
3517
+ function filterTypeScriptErrorOutput(output) {
3518
+ if (typeof output !== "string" || !output.trim()) return null;
3519
+ const lines = output.split(/\r?\n/);
2925
3520
  const kept = [];
2926
3521
  const errorPattern = /^[^:]+\.tsx?:\d+:\d+\s+-\s+error\s+TS\d+:/;
2927
3522
  for (let i = 0; i < lines.length; i += 1) {
@@ -2961,7 +3556,7 @@ function resolveToolCwd(context, cwdInput) {
2961
3556
  if (!cwdInput) return context.workspace;
2962
3557
  return isAbsolute2(cwdInput) ? resolveInsideWorkspace(context.workspace, cwdInput) : resolveInsideWorkspace(context.workspace, ensureRelativePath3(cwdInput));
2963
3558
  }
2964
- function runProcessWithInput(command, args, input2, context, timeoutMs) {
3559
+ function runProcessWithInput(command, args, input, context, timeoutMs) {
2965
3560
  return new Promise((resolve13) => {
2966
3561
  const child = spawn2(command, args, {
2967
3562
  cwd: context.workspace,
@@ -2981,20 +3576,20 @@ function runProcessWithInput(command, args, input2, context, timeoutMs) {
2981
3576
  });
2982
3577
  child.on("close", (code) => {
2983
3578
  clearTimeout(timer);
2984
- const output2 = `${stdout}${stderr ? `
3579
+ const output = `${stdout}${stderr ? `
2985
3580
  ${stderr}` : ""}`.trim();
2986
3581
  const baseResult = {
2987
3582
  ok: code === 0,
2988
3583
  summary: `Command exited ${code ?? "unknown"}.`,
2989
- output: output2.slice(0, 12e3)
3584
+ output: output.slice(0, 12e3)
2990
3585
  };
2991
- resolve13(code === 0 ? baseResult : { ...baseResult, error: output2.slice(0, 2e3) });
3586
+ resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
2992
3587
  });
2993
3588
  child.on("error", (err) => {
2994
3589
  clearTimeout(timer);
2995
3590
  resolve13({ ok: false, summary: "Command failed to start.", error: err.message });
2996
3591
  });
2997
- child.stdin.end(input2);
3592
+ child.stdin.end(input);
2998
3593
  });
2999
3594
  }
3000
3595
  function stripPatchPath(path, stripLevel) {
@@ -3051,9 +3646,9 @@ var listFilesTool = {
3051
3646
  }
3052
3647
  }
3053
3648
  },
3054
- async run(input2, context) {
3055
- const maxFiles = Math.min(asOptionalNumber5(input2, "maxFiles", 120), 500);
3056
- const path = asOptionalString5(input2, "path");
3649
+ async run(input, context) {
3650
+ const maxFiles = Math.min(asOptionalNumber5(input, "maxFiles", 120), 500);
3651
+ const path = asOptionalString5(input, "path");
3057
3652
  const root = path ? resolveInsideWorkspace(context.workspace, ensureRelativePath3(path)) : context.workspace;
3058
3653
  const files = collectFiles2(root, maxFiles).map((file) => path ? `${path.replace(/\/+$/, "")}/${file}` : file);
3059
3654
  return { ok: true, summary: `Listed ${files.length} file${files.length === 1 ? "" : "s"}.`, output: files };
@@ -3078,9 +3673,9 @@ var readFileTool = {
3078
3673
  }
3079
3674
  }
3080
3675
  },
3081
- async run(input2, context) {
3082
- const path = asString4(input2, "path");
3083
- const maxChars = Math.min(asOptionalNumber5(input2, "maxChars", 12e3), 4e4);
3676
+ async run(input, context) {
3677
+ const path = asString4(input, "path");
3678
+ const maxChars = Math.min(asOptionalNumber5(input, "maxChars", 12e3), 4e4);
3084
3679
  const abs = resolveInsideWorkspace(context.workspace, path);
3085
3680
  const content = await readFile4(abs, "utf8");
3086
3681
  return {
@@ -3110,9 +3705,9 @@ var writeFileTool = {
3110
3705
  }
3111
3706
  }
3112
3707
  },
3113
- async run(input2, context) {
3114
- const path = asString4(input2, "path");
3115
- const content = asString4(input2, "content");
3708
+ async run(input, context) {
3709
+ const path = asString4(input, "path");
3710
+ const content = asString4(input, "content");
3116
3711
  if (isProtectedLocalConfigPath(path)) return localPropertiesWriteError();
3117
3712
  const abs = resolveInsideWorkspace(context.workspace, path);
3118
3713
  await mkdir4(dirname6(abs), { recursive: true });
@@ -3147,9 +3742,9 @@ var searchTool = {
3147
3742
  }
3148
3743
  }
3149
3744
  },
3150
- async run(input2, context) {
3151
- const query = asString4(input2, "query");
3152
- const maxResults = Math.min(asOptionalNumber5(input2, "maxResults", 80), 300);
3745
+ async run(input, context) {
3746
+ const query = asString4(input, "query");
3747
+ const maxResults = Math.min(asOptionalNumber5(input, "maxResults", 80), 300);
3153
3748
  const result = await runProcess("rg", ["-n", "--hidden", "-g", "!node_modules", "-g", "!.git", query], context, 2e4);
3154
3749
  if (!result.ok && typeof result.output === "string" && !result.output) {
3155
3750
  return { ok: true, summary: "No matches.", output: [] };
@@ -3179,13 +3774,13 @@ var runCommandTool = {
3179
3774
  }
3180
3775
  }
3181
3776
  },
3182
- async run(input2, context) {
3183
- const command = asString4(input2, "command");
3184
- const rawArgs = asRecord5(input2).args;
3777
+ async run(input, context) {
3778
+ const command = asString4(input, "command");
3779
+ const rawArgs = asRecord5(input).args;
3185
3780
  const args = Array.isArray(rawArgs) ? rawArgs.filter((arg) => typeof arg === "string") : [];
3186
- const cwdInput = asOptionalString5(input2, "cwd");
3781
+ const cwdInput = asOptionalString5(input, "cwd");
3187
3782
  const cwd = resolveToolCwd(context, cwdInput);
3188
- const timeoutMs = Math.min(asOptionalNumber5(input2, "timeoutMs", 12e4), 3e5);
3783
+ const timeoutMs = Math.min(asOptionalNumber5(input, "timeoutMs", 12e4), 3e5);
3189
3784
  const result = await runProcess(command, args, context, timeoutMs, cwd);
3190
3785
  return maybeFilterTypeScriptErrorResult(result, commandRunsTypeScript(command, args));
3191
3786
  }
@@ -3211,8 +3806,8 @@ var runShellTool = {
3211
3806
  }
3212
3807
  }
3213
3808
  },
3214
- async run(input2, context) {
3215
- const script = asOptionalString5(input2, "script") ?? asString4(input2, "command");
3809
+ async run(input, context) {
3810
+ const script = asOptionalString5(input, "script") ?? asString4(input, "command");
3216
3811
  if (/\b(rm\s+-rf|sudo|ssh|scp|curl\s+[^|>]*\|\s*(?:sh|bash|zsh)|while\s+true|tail\s+-f)\b/.test(script)) {
3217
3812
  return { ok: false, summary: "Shell script rejected by safety checks.", error: "Use bounded, non-destructive, non-interactive commands only." };
3218
3813
  }
@@ -3230,9 +3825,9 @@ var runShellTool = {
3230
3825
  if (/(?:>\s*["']?[^&|;\n]*local\.properties\b|tee\s+[^|;\n]*local\.properties\b)/.test(script)) {
3231
3826
  return localPropertiesWriteError();
3232
3827
  }
3233
- const cwdInput = asOptionalString5(input2, "cwd");
3828
+ const cwdInput = asOptionalString5(input, "cwd");
3234
3829
  const cwd = resolveToolCwd(context, cwdInput);
3235
- const timeoutMs = Math.min(asOptionalNumber5(input2, "timeoutMs", 12e4), 3e5);
3830
+ const timeoutMs = Math.min(asOptionalNumber5(input, "timeoutMs", 12e4), 3e5);
3236
3831
  const result = await runShell(script, context, timeoutMs, cwd);
3237
3832
  return maybeFilterTypeScriptErrorResult(result, shellRunsTypeScript(script));
3238
3833
  }
@@ -3256,9 +3851,9 @@ var applyPatchTool = {
3256
3851
  }
3257
3852
  }
3258
3853
  },
3259
- async run(input2, context) {
3260
- const patch = asString4(input2, "patch");
3261
- const explicitStrip = asOptionalNumber5(input2, "stripLevel", Number.NaN);
3854
+ async run(input, context) {
3855
+ const patch = asString4(input, "patch");
3856
+ const explicitStrip = asOptionalNumber5(input, "stripLevel", Number.NaN);
3262
3857
  const stripLevel = Number.isFinite(explicitStrip) ? explicitStrip : inferPatchStripLevel(patch);
3263
3858
  const files = extractPatchFiles(patch, stripLevel);
3264
3859
  if (files.length === 0) {
@@ -3280,7 +3875,7 @@ var applyPatchTool = {
3280
3875
  context,
3281
3876
  6e4
3282
3877
  );
3283
- const output2 = typeof result.output === "string" ? result.output : "";
3878
+ const output = typeof result.output === "string" ? result.output : "";
3284
3879
  if (!result.ok) {
3285
3880
  return {
3286
3881
  ...result,
@@ -3293,7 +3888,7 @@ var applyPatchTool = {
3293
3888
  return {
3294
3889
  ok: true,
3295
3890
  summary: `Applied patch to ${files.length} file${files.length === 1 ? "" : "s"}.${backupNote}`,
3296
- output: output2,
3891
+ output,
3297
3892
  files
3298
3893
  };
3299
3894
  }
@@ -3319,12 +3914,12 @@ var searchReplaceTool = {
3319
3914
  }
3320
3915
  }
3321
3916
  },
3322
- async run(input2, context) {
3323
- const path = asString4(input2, "path");
3324
- const oldString = asString4(input2, "old_string");
3325
- const newString = asRecord5(input2).new_string;
3917
+ async run(input, context) {
3918
+ const path = asString4(input, "path");
3919
+ const oldString = asString4(input, "old_string");
3920
+ const newString = asRecord5(input).new_string;
3326
3921
  if (typeof newString !== "string") throw new Error("Missing string field: new_string");
3327
- const expectedCount = asOptionalNumber5(input2, "expected_count", 1);
3922
+ const expectedCount = asOptionalNumber5(input, "expected_count", 1);
3328
3923
  if (isProtectedLocalConfigPath(path)) return localPropertiesWriteError();
3329
3924
  let abs;
3330
3925
  try {
@@ -3383,10 +3978,10 @@ var copyFileTool = {
3383
3978
  }
3384
3979
  }
3385
3980
  },
3386
- async run(input2, context) {
3387
- const source = ensureRelativePath3(asString4(input2, "source"));
3388
- const destination = ensureRelativePath3(asString4(input2, "destination"));
3389
- const overwrite = asRecord5(input2).overwrite !== false;
3981
+ async run(input, context) {
3982
+ const source = ensureRelativePath3(asString4(input, "source"));
3983
+ const destination = ensureRelativePath3(asString4(input, "destination"));
3984
+ const overwrite = asRecord5(input).overwrite !== false;
3390
3985
  const sourceAbs = resolveInsideWorkspace(context.workspace, source);
3391
3986
  const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
3392
3987
  await mkdir4(dirname6(destinationAbs), { recursive: true });
@@ -3414,10 +4009,10 @@ var copyDirTool = {
3414
4009
  }
3415
4010
  }
3416
4011
  },
3417
- async run(input2, context) {
3418
- const source = ensureRelativePath3(asString4(input2, "source"));
3419
- const destination = ensureRelativePath3(asString4(input2, "destination"));
3420
- const overwrite = asRecord5(input2).overwrite !== false;
4012
+ async run(input, context) {
4013
+ const source = ensureRelativePath3(asString4(input, "source"));
4014
+ const destination = ensureRelativePath3(asString4(input, "destination"));
4015
+ const overwrite = asRecord5(input).overwrite !== false;
3421
4016
  const sourceAbs = resolveInsideWorkspace(context.workspace, source);
3422
4017
  const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
3423
4018
  await cp(sourceAbs, destinationAbs, { recursive: true, force: overwrite, errorOnExist: !overwrite });
@@ -3448,9 +4043,9 @@ var validateApiContractRoutesTool = {
3448
4043
  }
3449
4044
  }
3450
4045
  },
3451
- async run(input2, context) {
3452
- const source = ensureRelativePath3(asString4(input2, "source"));
3453
- const target = ensureRelativePath3(asString4(input2, "target"));
4046
+ async run(input, context) {
4047
+ const source = ensureRelativePath3(asString4(input, "source"));
4048
+ const target = ensureRelativePath3(asString4(input, "target"));
3454
4049
  const sourceText = await readFile4(resolveInsideWorkspace(context.workspace, source), "utf8");
3455
4050
  const targetText = await readFile4(resolveInsideWorkspace(context.workspace, target), "utf8");
3456
4051
  const sourceRoutes = parseMarkdownApiRoutes(sourceText);
@@ -3494,14 +4089,14 @@ var validateAndroidProjectConfigTool = {
3494
4089
  }
3495
4090
  }
3496
4091
  },
3497
- async run(input2, context) {
3498
- const manifestPath = ensureRelativePath3(asString4(input2, "manifestPath"));
3499
- const gradlePath = ensureRelativePath3(asString4(input2, "gradlePath"));
3500
- const minCompileSdk = asOptionalNumber5(input2, "minCompileSdk", 35);
3501
- const minTargetSdk = asOptionalNumber5(input2, "minTargetSdk", 35);
3502
- const minSdk = asOptionalNumber5(input2, "minSdk", 26);
3503
- const expectedIcon = asOptionalString5(input2, "expectedIcon") ?? "@mipmap/ic_launcher";
3504
- const expectedRoundIcon = asOptionalString5(input2, "expectedRoundIcon") ?? "@mipmap/ic_launcher_round";
4092
+ async run(input, context) {
4093
+ const manifestPath = ensureRelativePath3(asString4(input, "manifestPath"));
4094
+ const gradlePath = ensureRelativePath3(asString4(input, "gradlePath"));
4095
+ const minCompileSdk = asOptionalNumber5(input, "minCompileSdk", 35);
4096
+ const minTargetSdk = asOptionalNumber5(input, "minTargetSdk", 35);
4097
+ const minSdk = asOptionalNumber5(input, "minSdk", 26);
4098
+ const expectedIcon = asOptionalString5(input, "expectedIcon") ?? "@mipmap/ic_launcher";
4099
+ const expectedRoundIcon = asOptionalString5(input, "expectedRoundIcon") ?? "@mipmap/ic_launcher_round";
3505
4100
  const manifest = await readFile4(resolveInsideWorkspace(context.workspace, manifestPath), "utf8");
3506
4101
  const gradle = await readFile4(resolveInsideWorkspace(context.workspace, gradlePath), "utf8");
3507
4102
  const problems = [];
@@ -3544,9 +4139,9 @@ var validateAppleProjectFilesTool = {
3544
4139
  }
3545
4140
  }
3546
4141
  },
3547
- async run(input2, context) {
3548
- const record = asRecord5(input2);
3549
- const xcodeprojPath = asOptionalString5(input2, "xcodeprojPath");
4142
+ async run(input, context) {
4143
+ const record = asRecord5(input);
4144
+ const xcodeprojPath = asOptionalString5(input, "xcodeprojPath");
3550
4145
  const requiredPaths = Array.isArray(record.requiredPaths) ? record.requiredPaths.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
3551
4146
  const requireProjectReferences = record.requireProjectReferences === true;
3552
4147
  const problems = [];
@@ -3566,8 +4161,8 @@ var validateAppleProjectFilesTool = {
3566
4161
  problems.push(`Missing ${relPath}.`);
3567
4162
  }
3568
4163
  if (requireProjectReferences && pbxprojText) {
3569
- const basename4 = relPath.split("/").filter(Boolean).pop() ?? relPath;
3570
- if (!pbxprojText.includes(basename4)) problems.push(`project.pbxproj does not reference ${basename4}.`);
4164
+ const basename5 = relPath.split("/").filter(Boolean).pop() ?? relPath;
4165
+ if (!pbxprojText.includes(basename5)) problems.push(`project.pbxproj does not reference ${basename5}.`);
3571
4166
  }
3572
4167
  }
3573
4168
  return {
@@ -3599,9 +4194,9 @@ var validateFastlaneConfigTool = {
3599
4194
  }
3600
4195
  }
3601
4196
  },
3602
- async run(input2, context) {
3603
- const record = asRecord5(input2);
3604
- const fastfilePath = asOptionalString5(input2, "fastfilePath") ?? "fastlane/Fastfile";
4197
+ async run(input, context) {
4198
+ const record = asRecord5(input);
4199
+ const fastfilePath = asOptionalString5(input, "fastfilePath") ?? "fastlane/Fastfile";
3605
4200
  const requiredLanes = Array.isArray(record.requiredLanes) ? record.requiredLanes.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
3606
4201
  const requiredFiles = Array.isArray(record.requiredFiles) ? record.requiredFiles.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
3607
4202
  const forbiddenFiles = Array.isArray(record.forbiddenFiles) ? record.forbiddenFiles.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
@@ -3681,9 +4276,9 @@ var validatePrismaSchemaTool = {
3681
4276
  }
3682
4277
  }
3683
4278
  },
3684
- async run(input2, context) {
3685
- const record = asRecord5(input2);
3686
- const schemaPath = ensureRelativePath3(asOptionalString5(input2, "schemaPath") ?? "prisma/schema.prisma");
4279
+ async run(input, context) {
4280
+ const record = asRecord5(input);
4281
+ const schemaPath = ensureRelativePath3(asOptionalString5(input, "schemaPath") ?? "prisma/schema.prisma");
3687
4282
  const requiredModels = Array.isArray(record.requiredModels) ? record.requiredModels.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
3688
4283
  const forbiddenModels = Array.isArray(record.forbiddenModels) ? record.forbiddenModels.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
3689
4284
  const schema = await readFile4(resolveInsideWorkspace(context.workspace, schemaPath), "utf8");
@@ -3722,11 +4317,11 @@ var applyArtifactTool = {
3722
4317
  }
3723
4318
  }
3724
4319
  },
3725
- async run(input2, context) {
3726
- const artifactPath = ensureRelativePath3(asString4(input2, "artifactPath"));
3727
- const targetPath = ensureRelativePath3(asString4(input2, "targetPath"));
4320
+ async run(input, context) {
4321
+ const artifactPath = ensureRelativePath3(asString4(input, "artifactPath"));
4322
+ const targetPath = ensureRelativePath3(asString4(input, "targetPath"));
3728
4323
  if (isProtectedLocalConfigPath(targetPath)) return localPropertiesWriteError();
3729
- const overwrite = asRecord5(input2).overwrite !== false;
4324
+ const overwrite = asRecord5(input).overwrite !== false;
3730
4325
  const sourceAbs = resolveInsideWorkspace(context.workspace, artifactPath);
3731
4326
  const targetAbs = resolveInsideWorkspace(context.workspace, targetPath);
3732
4327
  await mkdir4(dirname6(targetAbs), { recursive: true });
@@ -3802,13 +4397,13 @@ var createIosSplashTool = {
3802
4397
  }
3803
4398
  }
3804
4399
  },
3805
- async run(input2, context) {
3806
- const viewPath = ensureRelativePath3(asString4(input2, "viewPath"));
3807
- const assetSetDir = (asOptionalString5(input2, "assetSetDir") ?? inferIosSplashAssetSetDir(viewPath)).replace(/\/+$/, "");
3808
- const sourceIcon = asOptionalString5(input2, "sourceIcon");
3809
- const appName = asOptionalString5(input2, "appName") ?? "App";
3810
- const brandHex = asOptionalString5(input2, "brandHex") ?? "#000000";
3811
- const durationMs = Math.max(0, Math.round(asOptionalNumber5(input2, "durationMs", 1200)));
4400
+ async run(input, context) {
4401
+ const viewPath = ensureRelativePath3(asString4(input, "viewPath"));
4402
+ const assetSetDir = (asOptionalString5(input, "assetSetDir") ?? inferIosSplashAssetSetDir(viewPath)).replace(/\/+$/, "");
4403
+ const sourceIcon = asOptionalString5(input, "sourceIcon");
4404
+ const appName = asOptionalString5(input, "appName") ?? "App";
4405
+ const brandHex = asOptionalString5(input, "brandHex") ?? "#000000";
4406
+ const durationMs = Math.max(0, Math.round(asOptionalNumber5(input, "durationMs", 1200)));
3812
4407
  const durationNs = durationMs * 1e6;
3813
4408
  const rgb = brandHex.replace("#", "").match(/.{1,2}/g)?.slice(0, 3).map((part) => Number.parseInt(part, 16)) ?? [0, 0, 0];
3814
4409
  const view = [
@@ -3909,12 +4504,12 @@ var createAndroidSplashTool = {
3909
4504
  }
3910
4505
  }
3911
4506
  },
3912
- async run(input2, context) {
3913
- const resDir = ensureRelativePath3(asString4(input2, "resDir")).replace(/\/+$/, "");
3914
- const sourceIcon = asOptionalString5(input2, "sourceIcon");
3915
- const brandHex = asOptionalString5(input2, "brandHex") ?? "#000000";
3916
- const themeName = asOptionalString5(input2, "themeName") ?? "Theme.App.Starting";
3917
- const iconName = (asOptionalString5(input2, "iconName") ?? "ic_splash_logo").replace(/[^a-z0-9_]/gi, "_").toLowerCase();
4507
+ async run(input, context) {
4508
+ const resDir = ensureRelativePath3(asString4(input, "resDir")).replace(/\/+$/, "");
4509
+ const sourceIcon = asOptionalString5(input, "sourceIcon");
4510
+ const brandHex = asOptionalString5(input, "brandHex") ?? "#000000";
4511
+ const themeName = asOptionalString5(input, "themeName") ?? "Theme.App.Starting";
4512
+ const iconName = (asOptionalString5(input, "iconName") ?? "ic_splash_logo").replace(/[^a-z0-9_]/gi, "_").toLowerCase();
3918
4513
  const valuesPath = `${resDir}/values/splash_theme.xml`;
3919
4514
  const drawablePath = `${resDir}/drawable/${iconName}.png`;
3920
4515
  const xml = [
@@ -3964,12 +4559,12 @@ var generateAppIconsTool = {
3964
4559
  }
3965
4560
  }
3966
4561
  },
3967
- async run(input2, context) {
3968
- const source = ensureRelativePath3(asString4(input2, "source"));
3969
- const appleOutputDir = asOptionalString5(input2, "appleOutputDir");
3970
- const androidResDir = asOptionalString5(input2, "androidResDir");
3971
- const background = asOptionalString5(input2, "background") ?? "#ffffff";
3972
- const rawApplePlatforms = asRecord5(input2).applePlatforms;
4562
+ async run(input, context) {
4563
+ const source = ensureRelativePath3(asString4(input, "source"));
4564
+ const appleOutputDir = asOptionalString5(input, "appleOutputDir");
4565
+ const androidResDir = asOptionalString5(input, "androidResDir");
4566
+ const background = asOptionalString5(input, "background") ?? "#ffffff";
4567
+ const rawApplePlatforms = asRecord5(input).applePlatforms;
3973
4568
  const applePlatforms = Array.isArray(rawApplePlatforms) ? rawApplePlatforms.filter((item) => typeof item === "string" && item.trim().length > 0) : ["ios", "macos"];
3974
4569
  const files = [];
3975
4570
  const outputs = {};
@@ -3994,8 +4589,8 @@ var generateAppIconsTool = {
3994
4589
  function packageToDir(packageName) {
3995
4590
  return packageName.split(".").map((part) => part.replace(/[^A-Za-z0-9_]/g, "")).filter(Boolean).join("/");
3996
4591
  }
3997
- function kotlinIdentifier(input2, fallback) {
3998
- const cleaned = input2.replace(/[^A-Za-z0-9_]/g, "").replace(/^[0-9]+/, "");
4592
+ function kotlinIdentifier(input, fallback) {
4593
+ const cleaned = input.replace(/[^A-Za-z0-9_]/g, "").replace(/^[0-9]+/, "");
3999
4594
  return cleaned || fallback;
4000
4595
  }
4001
4596
  function addLineBeforeClosingPluginsBlock(gradle, line) {
@@ -4068,17 +4663,17 @@ var createAndroidFoundationTool = {
4068
4663
  }
4069
4664
  }
4070
4665
  },
4071
- async run(input2, context) {
4072
- const packageName = asString4(input2, "packageName").trim();
4073
- const appName = asOptionalString5(input2, "appName") ?? "App";
4074
- const sourceRoot = ensureRelativePath3(asOptionalString5(input2, "sourceRoot") ?? "app/src/main/java").replace(/\/+$/, "");
4075
- const rootGradlePath = ensureRelativePath3(asOptionalString5(input2, "rootGradlePath") ?? "build.gradle.kts");
4076
- const moduleGradlePath = ensureRelativePath3(asOptionalString5(input2, "moduleGradlePath") ?? "app/build.gradle.kts");
4077
- const brandPrimaryHex = (asOptionalString5(input2, "brandPrimaryHex") ?? "#A52A2A").replace(/^#?/, "0xFF");
4078
- const brandSecondaryHex = (asOptionalString5(input2, "brandSecondaryHex") ?? "#2D3748").replace(/^#?/, "0xFF");
4079
- const updateGradle = asOptionalBoolean3(input2, "updateGradle", true);
4080
- const overwriteExisting = asOptionalBoolean3(input2, "overwriteExisting", false);
4081
- const preserveExisting = overwriteExisting ? false : asOptionalBoolean3(input2, "preserveExisting", true);
4666
+ async run(input, context) {
4667
+ const packageName = asString4(input, "packageName").trim();
4668
+ const appName = asOptionalString5(input, "appName") ?? "App";
4669
+ const sourceRoot = ensureRelativePath3(asOptionalString5(input, "sourceRoot") ?? "app/src/main/java").replace(/\/+$/, "");
4670
+ const rootGradlePath = ensureRelativePath3(asOptionalString5(input, "rootGradlePath") ?? "build.gradle.kts");
4671
+ const moduleGradlePath = ensureRelativePath3(asOptionalString5(input, "moduleGradlePath") ?? "app/build.gradle.kts");
4672
+ const brandPrimaryHex = (asOptionalString5(input, "brandPrimaryHex") ?? "#A52A2A").replace(/^#?/, "0xFF");
4673
+ const brandSecondaryHex = (asOptionalString5(input, "brandSecondaryHex") ?? "#2D3748").replace(/^#?/, "0xFF");
4674
+ const updateGradle = asOptionalBoolean3(input, "updateGradle", true);
4675
+ const overwriteExisting = asOptionalBoolean3(input, "overwriteExisting", false);
4676
+ const preserveExisting = overwriteExisting ? false : asOptionalBoolean3(input, "preserveExisting", true);
4082
4677
  const packageDir = packageToDir(packageName);
4083
4678
  if (!packageDir) return { ok: false, summary: "Invalid package name.", error: "packageName must contain at least one valid package segment." };
4084
4679
  const classPrefix = kotlinIdentifier(appName, "App");
@@ -4389,10 +4984,10 @@ var commitPlatformChangesTool = {
4389
4984
  }
4390
4985
  }
4391
4986
  },
4392
- async run(input2, context) {
4393
- const message = asString4(input2, "message");
4394
- const amend = asOptionalBoolean3(input2, "amend", false);
4395
- const record = asRecord5(input2);
4987
+ async run(input, context) {
4988
+ const message = asString4(input, "message");
4989
+ const amend = asOptionalBoolean3(input, "amend", false);
4990
+ const record = asRecord5(input);
4396
4991
  const rawPaths = Array.isArray(record.files) ? record.files : record.paths;
4397
4992
  const paths = Array.isArray(rawPaths) ? rawPaths.filter((path) => typeof path === "string" && path.trim().length > 0).map(ensureRelativePath3) : [];
4398
4993
  if (paths.length === 0) return { ok: false, summary: "No paths provided for commit.", error: "Provide at least one path to stage." };
@@ -4457,9 +5052,9 @@ var scanSecretsTool = {
4457
5052
  }
4458
5053
  }
4459
5054
  },
4460
- async run(input2, context) {
4461
- const scanPath = asOptionalString5(input2, "path");
4462
- const maxFiles = Math.min(asOptionalNumber5(input2, "maxFiles", 500), 2e3);
5055
+ async run(input, context) {
5056
+ const scanPath = asOptionalString5(input, "path");
5057
+ const maxFiles = Math.min(asOptionalNumber5(input, "maxFiles", 500), 2e3);
4463
5058
  const root = scanPath ? resolveInsideWorkspace(context.workspace, ensureRelativePath3(scanPath)) : context.workspace;
4464
5059
  const files = collectFiles2(root, maxFiles);
4465
5060
  const findings = [];
@@ -4537,11 +5132,11 @@ var ToolRegistry = class {
4537
5132
  get(name) {
4538
5133
  return this.tools.get(name);
4539
5134
  }
4540
- run(tool, input2, context, options = {}) {
5135
+ run(tool, input, context, options = {}) {
4541
5136
  const runContext = { ...context };
4542
5137
  if (options.onProgress) runContext.onProgress = options.onProgress;
4543
5138
  if (options.signal) runContext.signal = options.signal;
4544
- return tool.run(input2, runContext);
5139
+ return tool.run(input, runContext);
4545
5140
  }
4546
5141
  };
4547
5142
 
@@ -5165,15 +5760,15 @@ function metadataBoolean(runContext, key) {
5165
5760
  const value = runContext?.metadata?.[key];
5166
5761
  return value === true || value === "true" || value === "yes";
5167
5762
  }
5168
- function isRecord2(value) {
5763
+ function isRecord3(value) {
5169
5764
  return value !== null && typeof value === "object" && !Array.isArray(value);
5170
5765
  }
5171
5766
  function metadataRecord(runContext, key) {
5172
5767
  const value = runContext?.metadata?.[key];
5173
- return isRecord2(value) ? value : void 0;
5768
+ return isRecord3(value) ? value : void 0;
5174
5769
  }
5175
5770
  function recordArray2(value) {
5176
- return Array.isArray(value) ? value.filter(isRecord2) : [];
5771
+ return Array.isArray(value) ? value.filter(isRecord3) : [];
5177
5772
  }
5178
5773
  function autoBriefRecords(runContext, key) {
5179
5774
  return recordArray2(metadataRecord(runContext, "autoBrief")?.[key]);
@@ -7200,14 +7795,14 @@ function normalizeVerificationCommand(line) {
7200
7795
  return line.replace(/^Verification:\s*/i, "").replace(/\s*->\s*(passed|failed|BUILD SUCCESSFUL|BUILD FAILED|blocked|.+)$/i, "").replace(/\s+2>&1\b/g, "").replace(/\s+/g, " ").trim();
7201
7796
  }
7202
7797
  function successfulVerificationCommands(text2) {
7203
- const commands = /* @__PURE__ */ new Set();
7798
+ const commands2 = /* @__PURE__ */ new Set();
7204
7799
  for (const line of text2.split(/\r?\n/)) {
7205
7800
  if (!/^Verification:\s*/i.test(line)) continue;
7206
7801
  if (!/->\s*(passed|BUILD SUCCESSFUL)\b/i.test(line)) continue;
7207
7802
  const command = normalizeVerificationCommand(line);
7208
- if (command) commands.add(command);
7803
+ if (command) commands2.add(command);
7209
7804
  }
7210
- return commands;
7805
+ return commands2;
7211
7806
  }
7212
7807
  function hasSuccessfulVerification2(verificationLines, pattern) {
7213
7808
  return verificationLines.some((line) => /->\s*passed\b/i.test(line) && pattern.test(line));
@@ -7719,9 +8314,9 @@ ${content}`);
7719
8314
  for (const { path, content } of indexReads) {
7720
8315
  lines.push("", `### ${path}`, content.trim());
7721
8316
  }
7722
- const output2 = lines.join("\n");
7723
- return output2.length > 18e3 ? `${output2.slice(0, 17980)}
7724
- [... truncated]` : output2;
8317
+ const output = lines.join("\n");
8318
+ return output.length > 18e3 ? `${output.slice(0, 17980)}
8319
+ [... truncated]` : output;
7725
8320
  }
7726
8321
  function runGit(workspace, args) {
7727
8322
  const result = spawnSync2("git", args, {
@@ -7846,8 +8441,8 @@ function buildExportMap(workspace) {
7846
8441
  if (totalExportingFiles > entries.length) {
7847
8442
  lines.push(`[... ${totalExportingFiles - entries.length} more files]`);
7848
8443
  }
7849
- let output2 = lines.join("\n");
7850
- if (output2.length <= 3e3) return output2;
8444
+ let output = lines.join("\n");
8445
+ if (output.length <= 3e3) return output;
7851
8446
  const truncatedLines = ["## Workspace export map"];
7852
8447
  let included = 0;
7853
8448
  for (const line of lines.slice(1)) {
@@ -7859,9 +8454,9 @@ function buildExportMap(workspace) {
7859
8454
  }
7860
8455
  const omitted = entries.length - included + Math.max(0, totalExportingFiles - entries.length);
7861
8456
  if (omitted > 0) truncatedLines.push(`[... ${omitted} more files]`);
7862
- output2 = truncatedLines.join("\n");
7863
- return output2.length > 3e3 ? `${output2.slice(0, 2980)}
7864
- [... truncated]` : output2;
8457
+ output = truncatedLines.join("\n");
8458
+ return output.length > 3e3 ? `${output.slice(0, 2980)}
8459
+ [... truncated]` : output;
7865
8460
  }
7866
8461
  function detectVerificationSuggestions(workspace, projectTypes, packageScripts) {
7867
8462
  const suggestions = /* @__PURE__ */ new Set();
@@ -8469,6 +9064,7 @@ function loadSkillPacksFromRoot(ctx, skillsRoot) {
8469
9064
  return enforceBudget(matched).map((pack) => ({
8470
9065
  slug: pack.frontmatter.slug,
8471
9066
  title: pack.title,
9067
+ sourcePath: join13(skillsRoot, `${pack.relativePath}.md`),
8472
9068
  content: pack.body,
8473
9069
  tokens: pack.tokens,
8474
9070
  reason: pack.reason
@@ -8478,6 +9074,19 @@ function loadSkillPacks(ctx) {
8478
9074
  return loadSkillPacksFromRoot(ctx, resolveDefaultSkillsRoot());
8479
9075
  }
8480
9076
 
9077
+ // src/skills/summary.ts
9078
+ function formatSkillPackSummary(packs) {
9079
+ const totalTokens = packs.reduce((sum, pack) => sum + pack.tokens, 0);
9080
+ const lines = [
9081
+ `Skill packs loaded: ${packs.length}`,
9082
+ "| slug | source | reason | tokens |",
9083
+ "|------|--------|--------|--------|",
9084
+ ...packs.map((pack) => `| ${pack.slug} | ${pack.sourcePath} | ${pack.reason} | ${pack.tokens} |`),
9085
+ `Total pack tokens: ${totalTokens} / ${SKILL_PACK_TOKEN_BUDGET}`
9086
+ ];
9087
+ return lines.join("\n");
9088
+ }
9089
+
8481
9090
  // src/agent/systemPrompt.ts
8482
9091
  function readProjectInstructions(workspace) {
8483
9092
  const path = join14(workspace, ".tania", "INSTRUCTIONS.md");
@@ -8584,25 +9193,6 @@ import { cp as cp2, mkdir as mkdir10, rm as rm2, stat as stat6 } from "fs/promis
8584
9193
  import { dirname as dirname12, isAbsolute as isAbsolute3, join as join15, relative as relative9, resolve as resolve9 } from "path";
8585
9194
  var CONTEXT_TOKEN_LIMIT = 48e3;
8586
9195
  var CONTEXT_SUMMARY_KEEP_RECENT = 6;
8587
- function isMalformedToolArguments(value) {
8588
- return Boolean(value && typeof value === "object" && value.ok === false && typeof value.rawArguments === "string");
8589
- }
8590
- function previewRawToolArguments(raw) {
8591
- return raw.length > 500 ? `${raw.slice(0, 500)}...[truncated ${raw.length - 500} chars]` : raw;
8592
- }
8593
- function parseToolArguments(raw) {
8594
- if (!raw.trim()) return {};
8595
- try {
8596
- return JSON.parse(raw);
8597
- } catch {
8598
- const rawPreview = previewRawToolArguments(raw);
8599
- return {
8600
- ok: false,
8601
- error: `malformed tool arguments: ${rawPreview}`,
8602
- rawArguments: rawPreview
8603
- };
8604
- }
8605
- }
8606
9196
  function findSafeCompressionBoundary(messages, desiredKeepCount) {
8607
9197
  if (messages.length <= desiredKeepCount + 1) return Math.max(1, messages.length - desiredKeepCount);
8608
9198
  let startIndex = messages.length - desiredKeepCount;
@@ -8624,10 +9214,10 @@ function fieldMatchesType(value, expectedType) {
8624
9214
  if (expectedType === "array") return Array.isArray(value);
8625
9215
  return typeof value === expectedType;
8626
9216
  }
8627
- function validateToolInput(input2, definition) {
9217
+ function validateToolInput(input, definition) {
8628
9218
  const params = definition.function.parameters;
8629
9219
  if (!params) return null;
8630
- const record = input2 && typeof input2 === "object" ? input2 : {};
9220
+ const record = input && typeof input === "object" ? input : {};
8631
9221
  for (const key of params.required ?? []) {
8632
9222
  if (!(key in record) || record[key] === void 0 || record[key] === null) {
8633
9223
  return `Missing required field: "${key}"`;
@@ -8706,6 +9296,7 @@ function logRunSummarySilently(params) {
8706
9296
  {
8707
9297
  ts,
8708
9298
  prompt: params.prompt.slice(0, 200),
9299
+ provider: params.provider,
8709
9300
  model: params.model,
8710
9301
  durationMs: params.metrics.durationMs,
8711
9302
  promptTokens: params.metrics.promptTokens,
@@ -8853,8 +9444,8 @@ function repairAttemptSnapshot(attempt, manifest) {
8853
9444
  changedFileCount: manifest.changedFiles.length
8854
9445
  };
8855
9446
  }
8856
- function commandLabel(toolName, input2) {
8857
- const record = input2 && typeof input2 === "object" ? input2 : {};
9447
+ function commandLabel(toolName, input) {
9448
+ const record = input && typeof input === "object" ? input : {};
8858
9449
  if (toolName === "run_shell") {
8859
9450
  const script = typeof record.script === "string" ? record.script.trim() : typeof record.command === "string" ? record.command.trim() : "";
8860
9451
  return script || null;
@@ -8902,9 +9493,9 @@ function requiredHighLevelTool(runContext, prompt = "") {
8902
9493
  }
8903
9494
  return null;
8904
9495
  }
8905
- function toolCallMayMutate(toolName, input2) {
9496
+ function toolCallMayMutate(toolName, input) {
8906
9497
  if (mutatingToolNames.has(toolName)) return true;
8907
- const record = input2 && typeof input2 === "object" ? input2 : {};
9498
+ const record = input && typeof input === "object" ? input : {};
8908
9499
  if (toolName === "run_shell") {
8909
9500
  const script = typeof record.script === "string" ? record.script : "";
8910
9501
  return /\b(?:cat|printf|echo)\b[\s\S]{0,200}>\s*[^&|;\n]|\btee\s+[^|;\n]+|\b(?:mkdir|touch|rm|mv|cp)\s+|\bsed\s+-i\b|\bperl\s+-pi\b|\bktlintFormat\b/.test(script);
@@ -8931,27 +9522,27 @@ function verificationKey(label) {
8931
9522
  if (/\bgit\s+rev-parse\s+--short\s+HEAD\b/i.test(label)) return "git head";
8932
9523
  return label.replace(/\s+/g, " ").trim();
8933
9524
  }
8934
- function artifactPathFromRead(toolName, input2) {
9525
+ function artifactPathFromRead(toolName, input) {
8935
9526
  if (toolName !== "read_file") return null;
8936
- const record = input2 && typeof input2 === "object" ? input2 : {};
9527
+ const record = input && typeof input === "object" ? input : {};
8937
9528
  const path = typeof record.path === "string" ? record.path.trim() : "";
8938
9529
  if (!path) return null;
8939
9530
  if (path.startsWith(".tania/artifacts/")) return path;
8940
9531
  if (path.startsWith("artifacts/")) return path;
8941
9532
  return null;
8942
9533
  }
8943
- function contextPathFromRead(toolName, input2, runContext) {
9534
+ function contextPathFromRead(toolName, input, runContext) {
8944
9535
  if (toolName !== "read_file") return null;
8945
- const record = input2 && typeof input2 === "object" ? input2 : {};
9536
+ const record = input && typeof input === "object" ? input : {};
8946
9537
  const path = typeof record.path === "string" ? record.path.trim() : "";
8947
9538
  if (!path) return null;
8948
9539
  if (path.startsWith(".tania/context/")) return path;
8949
9540
  if ((runContext?.contextFiles ?? []).some((contextFile) => contextFile.path === path)) return path;
8950
9541
  return null;
8951
9542
  }
8952
- function outsideWorkspaceReadMessage(workspace, toolName, input2) {
9543
+ function outsideWorkspaceReadMessage(workspace, toolName, input) {
8953
9544
  if (toolName !== "read_file") return null;
8954
- const record = input2 && typeof input2 === "object" ? input2 : {};
9545
+ const record = input && typeof input === "object" ? input : {};
8955
9546
  const path = typeof record.path === "string" ? record.path.trim() : "";
8956
9547
  if (!path || !isAbsolute3(path)) return null;
8957
9548
  const target = resolve9(path);
@@ -9000,6 +9591,7 @@ async function runAgent(options) {
9000
9591
  let createdArtifactPaths = [];
9001
9592
  const requiredTool = requiredHighLevelTool(options.runContext, options.prompt);
9002
9593
  let requiredToolUsed = requiredTool ? false : true;
9594
+ let toolCallCorrectionAttempts = 0;
9003
9595
  async function syncArtifactOutput() {
9004
9596
  const outputRootValue = options.runContext?.metadata?.artifactOutputRoot;
9005
9597
  if (typeof outputRootValue !== "string" || !outputRootValue.trim()) return [];
@@ -9045,6 +9637,7 @@ async function runAgent(options) {
9045
9637
  logRunSummarySilently({
9046
9638
  workspace,
9047
9639
  prompt: options.prompt,
9640
+ provider: options.provider.id,
9048
9641
  model: options.provider.model,
9049
9642
  metrics,
9050
9643
  manifest: manifest2
@@ -9067,7 +9660,7 @@ async function runAgent(options) {
9067
9660
  }
9068
9661
  await options.sink({ type: "message_start" });
9069
9662
  let assistantText = "";
9070
- let toolCalls = [];
9663
+ let rawToolCalls = [];
9071
9664
  const codingProviderOptions = isCodingTask(options.runContext) ? { temperature: 0, topP: 0.2 } : {};
9072
9665
  let providerAttempt = 0;
9073
9666
  const PROVIDER_TRANSIENT_RETRIES = 1;
@@ -9076,24 +9669,44 @@ async function runAgent(options) {
9076
9669
  for await (const delta of options.provider.streamChat({
9077
9670
  messages,
9078
9671
  tools: registry.list().map((tool) => tool.definition),
9672
+ onProviderThrottle: (event) => {
9673
+ void Promise.resolve(options.sink({
9674
+ type: "provider_throttle",
9675
+ provider: event.provider,
9676
+ attempt: event.attempt,
9677
+ waitMs: event.waitMs
9678
+ })).catch(() => {
9679
+ });
9680
+ },
9079
9681
  ...codingProviderOptions
9080
9682
  })) {
9081
9683
  if (delta.usage) {
9082
9684
  totalPromptTokens += delta.usage.promptTokens;
9083
9685
  totalCompletionTokens += delta.usage.completionTokens;
9084
9686
  }
9687
+ if (delta.schemaWarnings) {
9688
+ for (const warning of delta.schemaWarnings) {
9689
+ await options.sink({
9690
+ type: "schema_flatten_warning",
9691
+ reason: warning.reason,
9692
+ path: warning.path,
9693
+ provider: options.provider.id,
9694
+ ...warning.tool ? { tool: warning.tool } : {}
9695
+ });
9696
+ }
9697
+ }
9085
9698
  if (delta.content) {
9086
9699
  assistantText += delta.content;
9087
9700
  finalText += delta.content;
9088
9701
  await options.sink({ type: "message_delta", text: delta.content });
9089
9702
  }
9090
- if (delta.toolCalls?.length) toolCalls = delta.toolCalls;
9703
+ if (delta.toolCalls?.length) rawToolCalls = delta.toolCalls;
9091
9704
  }
9092
9705
  break;
9093
9706
  } catch (err) {
9094
9707
  const message2 = err instanceof Error ? err.message : String(err);
9095
9708
  const isTransient = /timed out|fetch failed|ECONNRESET|EAI_AGAIN|ENOTFOUND|socket hang up/i.test(message2);
9096
- const noProgressYet = assistantText.length === 0 && toolCalls.length === 0;
9709
+ const noProgressYet = assistantText.length === 0 && rawToolCalls.length === 0;
9097
9710
  if (isTransient && noProgressYet && providerAttempt < PROVIDER_TRANSIENT_RETRIES) {
9098
9711
  providerAttempt += 1;
9099
9712
  await options.sink({ type: "status", message: `Provider transient error (${message2.slice(0, 120)}); retrying same turn (${providerAttempt}/${PROVIDER_TRANSIENT_RETRIES}).` });
@@ -9103,6 +9716,62 @@ async function runAgent(options) {
9103
9716
  }
9104
9717
  }
9105
9718
  await options.sink({ type: "message_end" });
9719
+ const parsedToolCalls = rawToolCalls.length > 0 ? parseProviderToolCalls(rawToolCalls, { turn }) : { toolCalls: [], warnings: [], failures: [] };
9720
+ for (const warning of parsedToolCalls.warnings) {
9721
+ await options.sink({
9722
+ type: "tool_call_parse_warning",
9723
+ reason: warning.reason,
9724
+ provider: options.provider.id,
9725
+ turn,
9726
+ attempt: toolCallCorrectionAttempts,
9727
+ ...warning.toolCallId ? { toolCallId: warning.toolCallId } : {},
9728
+ ...warning.tool ? { tool: warning.tool } : {}
9729
+ });
9730
+ }
9731
+ for (const failure of parsedToolCalls.failures) {
9732
+ await options.sink({
9733
+ type: "tool_call_parse_warning",
9734
+ reason: failure.reason,
9735
+ provider: options.provider.id,
9736
+ turn,
9737
+ attempt: toolCallCorrectionAttempts + 1,
9738
+ toolCallId: failure.toolCall.id,
9739
+ tool: failure.toolCall.function.name
9740
+ });
9741
+ }
9742
+ if (parsedToolCalls.failures.length > 0 && toolCallCorrectionAttempts < TOOL_CALL_CORRECTION_LIMIT && turn < maxTurns - 1) {
9743
+ toolCallCorrectionAttempts += 1;
9744
+ messages.push({ role: "assistant", content: assistantText || null });
9745
+ messages.push({
9746
+ role: "user",
9747
+ content: malformedToolCallCorrectionMessage(parsedToolCalls.failures.map((failure) => failure.reason).join("; "))
9748
+ });
9749
+ continue;
9750
+ }
9751
+ if (parsedToolCalls.failures.length > 0) {
9752
+ const failedToolCalls = parsedToolCalls.failures.map((failure) => failure.toolCall);
9753
+ toolErrorCount += failedToolCalls.length;
9754
+ messages.push({
9755
+ role: "assistant",
9756
+ content: assistantText || null,
9757
+ tool_calls: failedToolCalls
9758
+ });
9759
+ for (const failure of parsedToolCalls.failures) {
9760
+ const error = `malformed tool call after ${TOOL_CALL_CORRECTION_LIMIT} correction attempts: ${failure.reason}`;
9761
+ messages.push({ role: "tool", tool_call_id: failure.toolCall.id, content: JSON.stringify({ ok: false, error }) });
9762
+ await options.sink({
9763
+ type: "tool_result",
9764
+ id: failure.toolCall.id,
9765
+ tool: failure.toolCall.function.name,
9766
+ ok: false,
9767
+ summary: "Malformed tool call after correction attempts.",
9768
+ error
9769
+ });
9770
+ }
9771
+ continue;
9772
+ }
9773
+ const toolCalls = parsedToolCalls.toolCalls;
9774
+ if (toolCalls.length > 0) toolCallCorrectionAttempts = 0;
9106
9775
  const assistantMessage = {
9107
9776
  role: "assistant",
9108
9777
  content: assistantText || null
@@ -9169,16 +9838,16 @@ async function runAgent(options) {
9169
9838
  for (const toolCall2 of toolCalls) {
9170
9839
  const toolName = toolCall2.function.name;
9171
9840
  const tool = registry.get(toolName);
9172
- const callInput = parseToolArguments(toolCall2.function.arguments);
9173
- if (isMalformedToolArguments(callInput)) {
9841
+ const parsedInput = parseToolArguments(toolCall2.function.arguments);
9842
+ if (!parsedInput.ok) {
9174
9843
  toolErrorCount += 1;
9175
9844
  messages.push({
9176
9845
  role: "tool",
9177
9846
  tool_call_id: toolCall2.id,
9178
9847
  content: JSON.stringify({
9179
9848
  ok: false,
9180
- error: callInput.error,
9181
- rawArguments: callInput.rawArguments
9849
+ error: parsedInput.reason,
9850
+ rawArguments: parsedInput.rawArguments
9182
9851
  })
9183
9852
  });
9184
9853
  await options.sink({
@@ -9187,11 +9856,12 @@ async function runAgent(options) {
9187
9856
  tool: toolName,
9188
9857
  ok: false,
9189
9858
  summary: "Invalid tool arguments (malformed JSON).",
9190
- output: `raw arguments (preview): ${callInput.rawArguments}`,
9191
- error: callInput.error
9859
+ output: `raw arguments (preview): ${parsedInput.rawArguments}`,
9860
+ error: parsedInput.reason
9192
9861
  });
9193
9862
  continue;
9194
9863
  }
9864
+ const callInput = parsedInput.input;
9195
9865
  toolCallCount += 1;
9196
9866
  await options.sink({ type: "tool_call", id: toolCall2.id, tool: toolName, input: callInput });
9197
9867
  if (!tool) {
@@ -9431,12 +10101,12 @@ function runCommand(cmd, args, cwd, timeoutMs) {
9431
10101
  timeout: timeoutMs,
9432
10102
  shell: false
9433
10103
  });
9434
- const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
10104
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
9435
10105
  const exitCode = result.status ?? (result.error ? 1 : 0);
9436
- return { exitCode, output: output2 };
10106
+ return { exitCode, output };
9437
10107
  }
9438
- function parseTypeScriptErrors(output2) {
9439
- return output2.split(/\r?\n/).filter((line) => /\.tsx?:\d+:\d+\s*-?\s*error\s+TS\d+:/i.test(line) || /error TS\d+:/i.test(line)).slice(0, 5);
10108
+ function parseTypeScriptErrors(output) {
10109
+ return output.split(/\r?\n/).filter((line) => /\.tsx?:\d+:\d+\s*-?\s*error\s+TS\d+:/i.test(line) || /error TS\d+:/i.test(line)).slice(0, 5);
9440
10110
  }
9441
10111
  async function detectPostRunBlockers(cwd, manifest) {
9442
10112
  const blockers = [];
@@ -9445,9 +10115,9 @@ async function detectPostRunBlockers(cwd, manifest) {
9445
10115
  const packageManager = readPackageManager(cwd);
9446
10116
  if (tsconfigExists && !hasPassingVerification(manifest, /tsc|typecheck|type-check/i)) {
9447
10117
  const typecheckCommand = scripts.typecheck ? packageScriptCommand(packageManager, "typecheck") : { cmd: "npx", args: ["tsc", "--noEmit", "--pretty", "false"] };
9448
- const { exitCode, output: output2 } = runCommand(typecheckCommand.cmd, typecheckCommand.args, cwd, POST_CHECK_TIMEOUT_MS);
10118
+ const { exitCode, output } = runCommand(typecheckCommand.cmd, typecheckCommand.args, cwd, POST_CHECK_TIMEOUT_MS);
9449
10119
  if (exitCode !== 0) {
9450
- const errorLines = parseTypeScriptErrors(output2);
10120
+ const errorLines = parseTypeScriptErrors(output);
9451
10121
  const summary = errorLines.length > 0 ? `TypeScript errors after run:
9452
10122
  ${errorLines.map((line) => ` ${line}`).join("\n")}` : "TypeScript compilation failed (post-run check)";
9453
10123
  blockers.push(summary);
@@ -9540,11 +10210,424 @@ ${diff.slice(0, 8e3)}
9540
10210
 
9541
10211
  // src/agent/chat.ts
9542
10212
  import { createInterface } from "readline/promises";
9543
- import { stdin as input, stdout as output } from "process";
10213
+ import { stdin as processInput, stdout as processOutput } from "process";
10214
+
10215
+ // src/commands/registry.ts
10216
+ var commands = /* @__PURE__ */ new Map();
10217
+ function registerCommand(command) {
10218
+ const name = normalizeCommandName(command.name);
10219
+ if (!name) throw new Error("Command name cannot be empty.");
10220
+ commands.set(name, { ...command, name });
10221
+ }
10222
+ function getCommand(name) {
10223
+ return commands.get(normalizeCommandName(name));
10224
+ }
10225
+ function listCommands() {
10226
+ return [...commands.values()].sort((a, b) => {
10227
+ const category = (a.category ?? "built-in").localeCompare(b.category ?? "built-in");
10228
+ return category || a.name.localeCompare(b.name);
10229
+ });
10230
+ }
10231
+ function removeCommandsByCategory(category) {
10232
+ for (const [name, command] of commands) {
10233
+ if (command.category === category) commands.delete(name);
10234
+ }
10235
+ }
10236
+ async function commandIsAvailable(command, ctx) {
10237
+ return command.availability ? await command.availability(ctx) : true;
10238
+ }
10239
+ function normalizeCommandName(name) {
10240
+ return name.trim().replace(/^\//, "").toLowerCase();
10241
+ }
10242
+
10243
+ // src/commands/builtin/clear.ts
10244
+ var clearCommand = {
10245
+ name: "clear",
10246
+ description: "Clear the active conversation history.",
10247
+ category: "built-in",
10248
+ handler(_args, ctx) {
10249
+ if (ctx.clearHistory) {
10250
+ ctx.clearHistory();
10251
+ } else if (ctx.history) {
10252
+ ctx.history.length = 0;
10253
+ }
10254
+ ctx.output.write("Conversation history cleared.\n");
10255
+ }
10256
+ };
10257
+ registerCommand(clearCommand);
10258
+
10259
+ // src/memory/runLogs.ts
10260
+ import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
10261
+ import { join as join17 } from "path";
10262
+ var deepSeekPricingByModel = {
10263
+ "deepseek-chat": { inputPerMillion: 0.27, outputPerMillion: 1.1 },
10264
+ "deepseek-reasoner": { inputPerMillion: 0.55, outputPerMillion: 2.19 }
10265
+ };
10266
+ function readRunLogs(workspace, limit) {
10267
+ const runsDir = join17(workspace, ".tania", "runs");
10268
+ if (!existsSync15(runsDir)) return [];
10269
+ const files = readdirSync5(runsDir).filter((file) => file.endsWith(".json")).sort().reverse();
10270
+ const selected = limit === void 0 ? files : files.slice(0, limit);
10271
+ return selected.flatMap((file) => {
10272
+ try {
10273
+ const parsed = JSON.parse(readFileSync7(join17(runsDir, file), "utf8"));
10274
+ if (typeof parsed.ts !== "string" || typeof parsed.model !== "string") return [];
10275
+ return [{
10276
+ ts: parsed.ts,
10277
+ prompt: typeof parsed.prompt === "string" ? parsed.prompt : "",
10278
+ ...typeof parsed.provider === "string" ? { provider: parsed.provider } : {},
10279
+ model: parsed.model,
10280
+ durationMs: typeof parsed.durationMs === "number" ? parsed.durationMs : 0,
10281
+ promptTokens: typeof parsed.promptTokens === "number" ? parsed.promptTokens : 0,
10282
+ completionTokens: typeof parsed.completionTokens === "number" ? parsed.completionTokens : 0,
10283
+ changedFiles: Array.isArray(parsed.changedFiles) ? parsed.changedFiles.filter((file2) => typeof file2 === "string") : [],
10284
+ blockers: Array.isArray(parsed.blockers) ? parsed.blockers.filter((blocker) => typeof blocker === "string") : []
10285
+ }];
10286
+ } catch {
10287
+ return [];
10288
+ }
10289
+ });
10290
+ }
10291
+ function estimateRunCost(log) {
10292
+ const provider = normalizeProvider(log.provider, log.model);
10293
+ const pricing = provider === "deepseek" ? deepSeekPricingByModel[log.model] : void 0;
10294
+ if (!pricing) {
10295
+ return { provider, usd: null, display: "pricing unknown" };
10296
+ }
10297
+ const usd = log.promptTokens / 1e6 * pricing.inputPerMillion + log.completionTokens / 1e6 * pricing.outputPerMillion;
10298
+ return { provider, usd, display: formatUsd(usd) };
10299
+ }
10300
+ function formatRunLogLine(log) {
10301
+ const cost = estimateRunCost(log);
10302
+ const status = log.blockers.length > 0 ? "BLOCKED" : "OK";
10303
+ const duration = `${Math.round(log.durationMs / 1e3)}s`;
10304
+ const fileCount = log.changedFiles.length;
10305
+ return `${log.ts.slice(0, 16)} ${status.padEnd(7)} ${duration.padStart(5)} ${cost.display.padStart(15)} ${fileCount} file(s) ${log.prompt.slice(0, 60)}`;
10306
+ }
10307
+ function formatUsd(usd) {
10308
+ if (usd < 1e-3) return "<$0.001";
10309
+ return `$${usd.toFixed(3)}`;
10310
+ }
10311
+ function normalizeProvider(provider, model) {
10312
+ if (provider?.trim()) return provider.trim();
10313
+ if (model.startsWith("deepseek-")) return "deepseek";
10314
+ return "unknown";
10315
+ }
10316
+
10317
+ // src/commands/builtin/cost.ts
10318
+ var costCommand = {
10319
+ name: "cost",
10320
+ description: "Show token usage and estimated run costs.",
10321
+ category: "built-in",
10322
+ handler(_args, ctx) {
10323
+ const logs = readRunLogs(ctx.cwd);
10324
+ if (logs.length === 0) {
10325
+ ctx.output.write("No run logs found. Run tanya run first.\n");
10326
+ return;
10327
+ }
10328
+ let knownTotal = 0;
10329
+ let unknownCount = 0;
10330
+ ctx.output.write("Recent run costs:\n");
10331
+ for (const log of logs) {
10332
+ const estimate = estimateRunCost(log);
10333
+ if (estimate.usd === null) {
10334
+ unknownCount += 1;
10335
+ } else {
10336
+ knownTotal += estimate.usd;
10337
+ }
10338
+ ctx.output.write(
10339
+ `${log.ts.slice(0, 16)} ${estimate.provider}:${log.model} ${log.promptTokens.toLocaleString()} in / ${log.completionTokens.toLocaleString()} out ${estimate.display}
10340
+ `
10341
+ );
10342
+ }
10343
+ ctx.output.write(`Session total: ${formatUsd(knownTotal)}${unknownCount > 0 ? ` (${unknownCount} run${unknownCount === 1 ? "" : "s"} pricing unknown)` : ""}
10344
+ `);
10345
+ }
10346
+ };
10347
+ registerCommand(costCommand);
10348
+
10349
+ // src/commands/builtin/help.ts
10350
+ var helpCommand = {
10351
+ name: "help",
10352
+ description: "List available slash commands.",
10353
+ category: "built-in",
10354
+ async handler(_args, ctx) {
10355
+ const commands2 = [];
10356
+ for (const command of listCommands()) {
10357
+ if (await commandIsAvailable(command, ctx)) commands2.push(command);
10358
+ }
10359
+ ctx.output.write("Built-in commands:\n");
10360
+ for (const command of commands2.filter((candidate) => (candidate.category ?? "built-in") === "built-in")) {
10361
+ ctx.output.write(`/${command.name} \u2014 ${command.description}
10362
+ `);
10363
+ }
10364
+ const projectCommands = commands2.filter((candidate) => candidate.category === "project");
10365
+ if (projectCommands.length > 0) {
10366
+ ctx.output.write("Project commands:\n");
10367
+ for (const command of projectCommands) {
10368
+ ctx.output.write(`/${command.name} \u2014 ${command.description}
10369
+ `);
10370
+ }
10371
+ }
10372
+ }
10373
+ };
10374
+ registerCommand(helpCommand);
10375
+
10376
+ // src/commands/builtin/memory.ts
10377
+ var DEFAULT_LIMIT = 10;
10378
+ var memoryCommand = {
10379
+ name: "memory",
10380
+ description: "Show recent golden-task memory.",
10381
+ category: "built-in",
10382
+ async handler(args, ctx) {
10383
+ const records = [...await readGoldenTaskMemory(ctx.cwd)].sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
10384
+ const fullId = flagValue(args, "--full");
10385
+ if (fullId) {
10386
+ const record = records.find((candidate) => candidate.signature === fullId);
10387
+ ctx.output.write(record ? `${JSON.stringify(record, null, 2)}
10388
+ ` : `Golden task not found: ${fullId}
10389
+ `);
10390
+ return;
10391
+ }
10392
+ const limit = parseLimit(flagValue(args, "--limit"));
10393
+ if (records.length === 0) {
10394
+ ctx.output.write("No golden task memory found.\n");
10395
+ return;
10396
+ }
10397
+ ctx.output.write("Recent golden tasks:\n");
10398
+ for (const record of records.slice(0, limit)) {
10399
+ const title = record.task?.title ?? "(untitled)";
10400
+ ctx.output.write(`${record.recordedAt.slice(0, 16)} ${record.outcome.padEnd(6)} ${record.signature} ${title}
10401
+ `);
10402
+ }
10403
+ }
10404
+ };
10405
+ function flagValue(args, flag) {
10406
+ const index = args.indexOf(flag);
10407
+ const value = index >= 0 ? args[index + 1] : void 0;
10408
+ return value && !value.startsWith("--") ? value : void 0;
10409
+ }
10410
+ function parseLimit(raw) {
10411
+ const parsed = raw ? Number(raw) : DEFAULT_LIMIT;
10412
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LIMIT;
10413
+ }
10414
+ registerCommand(memoryCommand);
10415
+
10416
+ // src/commands/builtin/skills.ts
10417
+ var skillsCommand = {
10418
+ name: "skills",
10419
+ description: "Show skill packs matched for this workspace.",
10420
+ category: "built-in",
10421
+ handler(args, ctx) {
10422
+ const taskHint = args.join(" ");
10423
+ const packs = loadPromptSkillPacks(ctx.cwd, void 0, taskHint);
10424
+ ctx.output.write(`${formatSkillPackSummary(packs)}
10425
+ `);
10426
+ }
10427
+ };
10428
+ registerCommand(skillsCommand);
10429
+
10430
+ // src/commands/builtin/verify.ts
10431
+ var verifyCommand = {
10432
+ name: "verify",
10433
+ description: "Run Tanya's deterministic final-state verifier for the current workspace.",
10434
+ category: "built-in",
10435
+ async handler(_args, ctx) {
10436
+ const beforeGitSnapshot = await captureGitSnapshot(ctx.cwd);
10437
+ const manifest = await buildFinalManifest({
10438
+ workspace: ctx.cwd,
10439
+ beforeGitSnapshot,
10440
+ changed: [],
10441
+ verificationLines: [],
10442
+ toolErrorCount: 0,
10443
+ readArtifactPaths: [],
10444
+ readContextPaths: [],
10445
+ createdArtifactPaths: [],
10446
+ blockers: [],
10447
+ prompt: "Ad-hoc /verify command"
10448
+ });
10449
+ const report = ensureCodingReport("", manifest);
10450
+ ctx.output.write(`${report.trim() || "No verifier evidence available for this workspace."}
10451
+ `);
10452
+ }
10453
+ };
10454
+ registerCommand(verifyCommand);
10455
+
10456
+ // src/commands/project.ts
10457
+ import { existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
10458
+ import { basename as basename4, extname as extname2, join as join18, relative as relative10 } from "path";
10459
+ import { pathToFileURL } from "url";
10460
+ import { tsImport } from "tsx/esm/api";
10461
+ var supportedExtensions = /* @__PURE__ */ new Set([".js", ".ts", ".sh"]);
10462
+ var loadedWorkspace = null;
10463
+ async function loadProjectCommands(workspace) {
10464
+ if (loadedWorkspace === workspace) return;
10465
+ loadedWorkspace = workspace;
10466
+ removeCommandsByCategory("project");
10467
+ const commandsDir = join18(workspace, ".tania", "commands");
10468
+ if (!existsSync16(commandsDir)) return;
10469
+ let files = [];
10470
+ try {
10471
+ files = readdirSync6(commandsDir).filter((file) => supportedExtensions.has(extname2(file))).sort();
10472
+ } catch (error) {
10473
+ warnProjectCommand(commandsDir, error);
10474
+ return;
10475
+ }
10476
+ for (const file of files) {
10477
+ const path = join18(commandsDir, file);
10478
+ try {
10479
+ const extension = extname2(file);
10480
+ if (extension === ".sh") {
10481
+ registerShellCommand(workspace, path);
10482
+ } else {
10483
+ await registerModuleCommand(path);
10484
+ }
10485
+ } catch (error) {
10486
+ warnProjectCommand(path, error);
10487
+ }
10488
+ }
10489
+ }
10490
+ function registerShellCommand(workspace, path) {
10491
+ const slug = basename4(path, extname2(path));
10492
+ const relativePath = relative10(workspace, path).replace(/\\/g, "/");
10493
+ registerCommand({
10494
+ name: `project:${slug}`,
10495
+ description: `Run ${relativePath}.`,
10496
+ category: "project",
10497
+ async handler(args, ctx) {
10498
+ const script = ["zsh", quoteShell(relativePath), ...args.map(quoteShell)].join(" ");
10499
+ const result = await runShellTool.run({ script }, { workspace: ctx.cwd });
10500
+ const output = typeof result.output === "string" ? result.output : "";
10501
+ if (output) ctx.output.write(`${output}${output.endsWith("\n") ? "" : "\n"}`);
10502
+ if (!result.ok) ctx.output.write(`${result.error ?? result.summary}
10503
+ `);
10504
+ }
10505
+ });
10506
+ }
10507
+ async function registerModuleCommand(path) {
10508
+ const href = pathToFileURL(path).href;
10509
+ const imported = extname2(path) === ".ts" ? await tsImport(href, import.meta.url) : await import(href);
10510
+ const command = unwrapDefaultCommand(imported);
10511
+ if (!isCommandDefinition(command)) {
10512
+ throw new Error("default export is not a CommandDefinition");
10513
+ }
10514
+ registerCommand({
10515
+ ...command,
10516
+ name: command.name.startsWith("project:") ? command.name : `project:${command.name}`,
10517
+ category: "project"
10518
+ });
10519
+ }
10520
+ function unwrapDefaultCommand(imported) {
10521
+ if (isCommandDefinition(imported.default)) return imported.default;
10522
+ const nested = imported.default;
10523
+ if (nested && typeof nested === "object" && "default" in nested) {
10524
+ return nested.default;
10525
+ }
10526
+ return imported.default;
10527
+ }
10528
+ function isCommandDefinition(value) {
10529
+ return Boolean(
10530
+ value && typeof value === "object" && typeof value.name === "string" && typeof value.description === "string" && typeof value.handler === "function"
10531
+ );
10532
+ }
10533
+ function quoteShell(value) {
10534
+ return `'${value.replace(/'/g, "'\\''")}'`;
10535
+ }
10536
+ function warnProjectCommand(path, error) {
10537
+ const message = error instanceof Error ? error.message : String(error);
10538
+ console.warn(`[tanya] Skipping project command ${path}: ${message}`);
10539
+ }
10540
+
10541
+ // src/commands/index.ts
10542
+ function parseCommandLine(line) {
10543
+ const trimmed = line.trim();
10544
+ if (!trimmed.startsWith("/")) return null;
10545
+ const body = trimmed.slice(1).trim();
10546
+ if (!body) return null;
10547
+ const firstWhitespace = body.search(/\s/);
10548
+ const rawName = firstWhitespace === -1 ? body : body.slice(0, firstWhitespace);
10549
+ const rawArgs = firstWhitespace === -1 ? "" : body.slice(firstWhitespace).trim();
10550
+ const name = normalizeCommandName(rawName);
10551
+ if (!/^[a-z0-9:_-]+$/.test(name)) return null;
10552
+ return { name, args: splitCommandArgs(rawArgs) };
10553
+ }
10554
+ function commandNameFromLine(line) {
10555
+ return parseCommandLine(line)?.name ?? null;
10556
+ }
10557
+ async function runCommand2(line, ctx) {
10558
+ const parsed = parseCommandLine(line);
10559
+ if (!parsed) return false;
10560
+ await loadProjectCommands(ctx.cwd);
10561
+ const command = getCommand(parsed.name);
10562
+ if (!command) return false;
10563
+ if (!await commandIsAvailable(command, ctx)) return false;
10564
+ await ctx.sink({
10565
+ type: "command_invoked",
10566
+ name: command.name,
10567
+ args: parsed.args,
10568
+ ...ctx.runId ? { runId: ctx.runId } : {}
10569
+ });
10570
+ await command.handler(parsed.args, ctx);
10571
+ return true;
10572
+ }
10573
+ function splitCommandArgs(input) {
10574
+ const args = [];
10575
+ let current = "";
10576
+ let quote = null;
10577
+ let escaping = false;
10578
+ for (const char of input) {
10579
+ if (escaping) {
10580
+ current += char;
10581
+ escaping = false;
10582
+ continue;
10583
+ }
10584
+ if (char === "\\") {
10585
+ escaping = true;
10586
+ continue;
10587
+ }
10588
+ if (quote) {
10589
+ if (char === quote) {
10590
+ quote = null;
10591
+ } else {
10592
+ current += char;
10593
+ }
10594
+ continue;
10595
+ }
10596
+ if (char === '"' || char === "'") {
10597
+ quote = char;
10598
+ continue;
10599
+ }
10600
+ if (/\s/.test(char)) {
10601
+ if (current) {
10602
+ args.push(current);
10603
+ current = "";
10604
+ }
10605
+ continue;
10606
+ }
10607
+ current += char;
10608
+ }
10609
+ if (escaping) current += "\\";
10610
+ if (current) args.push(current);
10611
+ return args;
10612
+ }
10613
+
10614
+ // src/agent/chat.ts
10615
+ async function dispatchInteractiveCommand(prompt, ctx) {
10616
+ if (!prompt.startsWith("/")) return false;
10617
+ const handled = await runCommand2(prompt, ctx);
10618
+ if (handled) return true;
10619
+ const commandName = commandNameFromLine(prompt);
10620
+ ctx.output.write(`unknown command: /${commandName ?? ""}; try /help
10621
+ `);
10622
+ return true;
10623
+ }
9544
10624
  async function startInteractiveChat(inputOptions) {
10625
+ const input = inputOptions.input ?? processInput;
10626
+ const output = inputOptions.output ?? processOutput;
9545
10627
  const rl = createInterface({ input, output });
9546
10628
  const history = [];
9547
10629
  let activeAbortController = null;
10630
+ await loadProjectCommands(inputOptions.cwd);
9548
10631
  output.write(`Tanya live chat (${inputOptions.provider.id}:${inputOptions.provider.model}). Type /exit to quit.
9549
10632
  `);
9550
10633
  const handleSigint = () => {
@@ -9562,17 +10645,34 @@ async function startInteractiveChat(inputOptions) {
9562
10645
  const prompt = (await rl.question("\nYou: ")).trim();
9563
10646
  if (!prompt) continue;
9564
10647
  if (prompt === "/exit" || prompt === "/quit") break;
9565
- const abortController = new AbortController();
9566
- activeAbortController = abortController;
9567
- const { message } = await runAgent({
10648
+ if (await dispatchInteractiveCommand(prompt, {
9568
10649
  provider: inputOptions.provider,
9569
- prompt,
9570
10650
  cwd: inputOptions.cwd,
9571
10651
  sink: inputOptions.sink,
10652
+ output,
9572
10653
  history,
9573
- signal: abortController.signal
9574
- });
9575
- activeAbortController = null;
10654
+ clearHistory: () => {
10655
+ history.length = 0;
10656
+ }
10657
+ })) {
10658
+ continue;
10659
+ }
10660
+ const abortController = new AbortController();
10661
+ activeAbortController = abortController;
10662
+ let message;
10663
+ try {
10664
+ const result = await runAgent({
10665
+ provider: inputOptions.provider,
10666
+ prompt,
10667
+ cwd: inputOptions.cwd,
10668
+ sink: inputOptions.sink,
10669
+ history,
10670
+ signal: abortController.signal
10671
+ });
10672
+ message = result.message;
10673
+ } finally {
10674
+ activeAbortController = null;
10675
+ }
9576
10676
  history.push({ role: "user", content: prompt });
9577
10677
  history.push({ role: "assistant", content: message });
9578
10678
  }
@@ -9628,6 +10728,21 @@ ${toolGlyph} ${event.tool}
9628
10728
  break;
9629
10729
  case "tool_cancelled":
9630
10730
  stream.write(` cancelled: ${event.tool ?? event.toolCallId}
10731
+ `);
10732
+ break;
10733
+ case "command_invoked":
10734
+ break;
10735
+ case "tool_call_parse_warning":
10736
+ stream.write(` warning: malformed tool call (${event.reason})
10737
+ `);
10738
+ break;
10739
+ case "schema_flatten_warning":
10740
+ stream.write(` warning: flattened schema${event.tool ? ` for ${event.tool}` : ""} (${event.reason})
10741
+ `);
10742
+ break;
10743
+ case "provider_throttle":
10744
+ stream.write(`
10745
+ Provider ${event.provider} throttled; waiting ${Math.ceil(event.waitMs / 1e3)}s before retry ${event.attempt}.
9631
10746
  `);
9632
10747
  break;
9633
10748
  case "final":
@@ -9651,7 +10766,7 @@ ${event.detail}` : ""}
9651
10766
 
9652
10767
  // src/tools/adRenderTools.ts
9653
10768
  import { mkdir as mkdir11, readFile as readFile10, readdir as readdir6, rm as rm3, writeFile as writeFile9 } from "fs/promises";
9654
- import { dirname as dirname13, isAbsolute as isAbsolute4, join as join17 } from "path";
10769
+ import { dirname as dirname13, isAbsolute as isAbsolute4, join as join19 } from "path";
9655
10770
  import { spawnSync as spawnSync4 } from "child_process";
9656
10771
  import sharp3 from "sharp";
9657
10772
  function findExecutable2(name, explicit) {
@@ -9745,17 +10860,17 @@ async function loadStaticFrame(src, workspace) {
9745
10860
  }
9746
10861
  async function extractVideoFrames(params) {
9747
10862
  await mkdir11(params.outDir, { recursive: true });
9748
- const input2 = resolveAssetPath(params.src, params.workspace);
10863
+ const input = resolveAssetPath(params.src, params.workspace);
9749
10864
  run2(params.ffmpegPath, [
9750
10865
  "-y",
9751
10866
  "-i",
9752
- input2,
10867
+ input,
9753
10868
  "-vf",
9754
10869
  `fps=${params.fps}`,
9755
- join17(params.outDir, "frame-%05d.png")
10870
+ join19(params.outDir, "frame-%05d.png")
9756
10871
  ], params.workspace);
9757
10872
  const files = (await readdir6(params.outDir)).filter((file) => file.endsWith(".png")).sort();
9758
- return Promise.all(files.map((file) => readFile10(join17(params.outDir, file))));
10873
+ return Promise.all(files.map((file) => readFile10(join19(params.outDir, file))));
9759
10874
  }
9760
10875
  async function buildMediaFrames(spec, workspace, tmpDir, ffmpegPath, warnings) {
9761
10876
  const media = /* @__PURE__ */ new Map();
@@ -9765,7 +10880,7 @@ async function buildMediaFrames(spec, workspace, tmpDir, ffmpegPath, warnings) {
9765
10880
  const frames = await extractVideoFrames({
9766
10881
  src: asset.src,
9767
10882
  workspace,
9768
- outDir: join17(tmpDir, "media", asset.id),
10883
+ outDir: join19(tmpDir, "media", asset.id),
9769
10884
  fps: spec.canvas.fps,
9770
10885
  ffmpegPath
9771
10886
  });
@@ -9861,13 +10976,13 @@ async function shadowLayer(width, height, radius) {
9861
10976
  }
9862
10977
  }).composite([{ input: mask, blend: "dest-in" }]).blur(18).extend({ top: 24, bottom: 24, left: 24, right: 24, background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer();
9863
10978
  }
9864
- async function scaleOverlay(input2, scale) {
9865
- if (Math.abs(scale - 1) < 1e-3) return { input: input2, dx: 0, dy: 0 };
9866
- const meta = await sharp3(input2).metadata();
10979
+ async function scaleOverlay(input, scale) {
10980
+ if (Math.abs(scale - 1) < 1e-3) return { input, dx: 0, dy: 0 };
10981
+ const meta = await sharp3(input).metadata();
9867
10982
  const width = Math.max(1, Math.round((meta.width ?? 1) * scale));
9868
10983
  const height = Math.max(1, Math.round((meta.height ?? 1) * scale));
9869
10984
  return {
9870
- input: await sharp3(input2).resize(width, height).png().toBuffer(),
10985
+ input: await sharp3(input).resize(width, height).png().toBuffer(),
9871
10986
  dx: Math.round(((meta.width ?? width) - width) / 2),
9872
10987
  dy: Math.round(((meta.height ?? height) - height) / 2)
9873
10988
  };
@@ -9963,12 +11078,12 @@ async function renderFullAd(options, workspace) {
9963
11078
  const inputPath = isAbsolute4(options.input) ? options.input : resolveInsideWorkspace(workspace, options.input);
9964
11079
  const spec = parseSpec(JSON.parse(await readFile10(inputPath, "utf8")));
9965
11080
  const outputDir = resolveInsideWorkspace(workspace, options.outputDir ?? "tanya-video-ads");
9966
- const basename4 = (options.basename ?? `full-ad-${Date.now()}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
11081
+ const basename5 = (options.basename ?? `full-ad-${Date.now()}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
9967
11082
  const formats = options.formats?.length ? options.formats : ["mp4", "poster"];
9968
11083
  const ffmpegPath = findExecutable2("ffmpeg", options.ffmpegPath ?? envValue({}, "TANYA_FFMPEG_PATH"));
9969
11084
  const tmpRoot = resolveInsideWorkspace(workspace, ".tania/tmp");
9970
- const tmpDir = await fsMkdtemp(join17(tmpRoot, "tanya-render-ad-"));
9971
- const frameDir = join17(tmpDir, "frames");
11085
+ const tmpDir = await fsMkdtemp(join19(tmpRoot, "tanya-render-ad-"));
11086
+ const frameDir = join19(tmpDir, "frames");
9972
11087
  const warnings = [];
9973
11088
  await mkdir11(frameDir, { recursive: true });
9974
11089
  await mkdir11(outputDir, { recursive: true });
@@ -9980,7 +11095,7 @@ async function renderFullAd(options, workspace) {
9980
11095
  for (let i = 0; i < frameCount; i += 1) {
9981
11096
  const frame = await renderFrame(spec, scene, mediaFrames, i);
9982
11097
  await writeFile9(
9983
- join17(frameDir, `frame_${String(frameIndex).padStart(6, "0")}.jpg`),
11098
+ join19(frameDir, `frame_${String(frameIndex).padStart(6, "0")}.jpg`),
9984
11099
  await sharp3(frame).jpeg({ quality: 84, mozjpeg: true }).toBuffer()
9985
11100
  );
9986
11101
  frameIndex += 1;
@@ -9995,18 +11110,18 @@ async function renderFullAd(options, workspace) {
9995
11110
  renderSpec: spec
9996
11111
  };
9997
11112
  if (formats.includes("poster")) {
9998
- const posterPath = join17(outputDir, `${basename4}-poster.png`);
9999
- await sharp3(await readFile10(join17(frameDir, "frame_000000.jpg"))).png().toFile(posterPath);
11113
+ const posterPath = join19(outputDir, `${basename5}-poster.png`);
11114
+ await sharp3(await readFile10(join19(frameDir, "frame_000000.jpg"))).png().toFile(posterPath);
10000
11115
  result.posterPath = posterPath;
10001
11116
  }
10002
11117
  if (formats.includes("mp4")) {
10003
- const mp4Path = join17(outputDir, `${basename4}.mp4`);
11118
+ const mp4Path = join19(outputDir, `${basename5}.mp4`);
10004
11119
  run2(ffmpegPath, [
10005
11120
  "-y",
10006
11121
  "-framerate",
10007
11122
  String(spec.canvas.fps),
10008
11123
  "-i",
10009
- join17(frameDir, "frame_%06d.jpg"),
11124
+ join19(frameDir, "frame_%06d.jpg"),
10010
11125
  "-c:v",
10011
11126
  "libx264",
10012
11127
  "-pix_fmt",
@@ -10245,7 +11360,7 @@ var BUILT_IN_GOLDEN_TASK_PROFILES = [
10245
11360
  import { execFile as execFile3 } from "child_process";
10246
11361
  import { mkdtemp, mkdir as mkdir12, writeFile as writeFile10 } from "fs/promises";
10247
11362
  import { tmpdir } from "os";
10248
- import { join as join18 } from "path";
11363
+ import { join as join20 } from "path";
10249
11364
  import { promisify as promisify3 } from "util";
10250
11365
  function toolCall(id, name, args) {
10251
11366
  return {
@@ -10263,15 +11378,15 @@ function scriptedProvider(id, turns) {
10263
11378
  id: `golden:${id}`,
10264
11379
  model: "scripted",
10265
11380
  requests,
10266
- async *streamChat(input2) {
10267
- requests.push({ ...input2, messages: [...input2.messages] });
11381
+ async *streamChat(input) {
11382
+ requests.push({ ...input, messages: [...input.messages] });
10268
11383
  yield turns[Math.min(requests.length - 1, turns.length - 1)] ?? { content: "" };
10269
11384
  }
10270
11385
  };
10271
11386
  }
10272
11387
  var execFileAsync3 = promisify3(execFile3);
10273
11388
  async function createBaseWorkspace(profileId) {
10274
- return mkdtemp(join18(tmpdir(), `tanya-golden-${profileId.replace(/[^a-z0-9]+/gi, "-")}-`));
11389
+ return mkdtemp(join20(tmpdir(), `tanya-golden-${profileId.replace(/[^a-z0-9]+/gi, "-")}-`));
10275
11390
  }
10276
11391
  var GENERIC_BENCHMARK_SPECS = {
10277
11392
  "tanya.low.search-replace": {
@@ -10423,8 +11538,8 @@ async function genericBenchmarkFixture(profile) {
10423
11538
  if (!spec) throw new Error(`No generic benchmark spec for ${profile.id}`);
10424
11539
  const workspace = await createBaseWorkspace(profile.id);
10425
11540
  const marker = `${profile.id.split(".").pop() ?? "benchmark"}-ready`;
10426
- await mkdir12(join18(workspace, "scripts"), { recursive: true });
10427
- await writeFile10(join18(workspace, "scripts/check.js"), [
11541
+ await mkdir12(join20(workspace, "scripts"), { recursive: true });
11542
+ await writeFile10(join20(workspace, "scripts/check.js"), [
10428
11543
  "const fs = require('fs');",
10429
11544
  `const targets = ${JSON.stringify(spec.targetFiles.map((file) => ({ path: file.path, marker })))};`,
10430
11545
  "for (const target of targets) {",
@@ -10440,37 +11555,37 @@ async function genericBenchmarkFixture(profile) {
10440
11555
  if (spec.useSearchReplace) {
10441
11556
  for (const file of spec.targetFiles) {
10442
11557
  const dir = file.path.split("/").slice(0, -1).join("/");
10443
- if (dir) await mkdir12(join18(workspace, dir), { recursive: true });
10444
- await writeFile10(join18(workspace, file.path), file.content.replace(marker, "PENDING_MARKER"));
11558
+ if (dir) await mkdir12(join20(workspace, dir), { recursive: true });
11559
+ await writeFile10(join20(workspace, file.path), file.content.replace(marker, "PENDING_MARKER"));
10445
11560
  }
10446
11561
  }
10447
11562
  for (const file of spec.preFiles ?? []) {
10448
11563
  const dir = file.path.split("/").slice(0, -1).join("/");
10449
- if (dir) await mkdir12(join18(workspace, dir), { recursive: true });
10450
- await writeFile10(join18(workspace, file.path), file.content);
11564
+ if (dir) await mkdir12(join20(workspace, dir), { recursive: true });
11565
+ await writeFile10(join20(workspace, file.path), file.content);
10451
11566
  }
10452
11567
  if (spec.useArtifact) {
10453
- await mkdir12(join18(workspace, ".tania/artifacts/generic"), { recursive: true });
10454
- await writeFile10(join18(workspace, ".tania/artifacts/generic/Pattern.md"), `# Pattern
11568
+ await mkdir12(join20(workspace, ".tania/artifacts/generic"), { recursive: true });
11569
+ await writeFile10(join20(workspace, ".tania/artifacts/generic/Pattern.md"), `# Pattern
10455
11570
 
10456
11571
  Use ${marker} in the adapted output.
10457
11572
  `);
10458
11573
  }
10459
11574
  if (spec.useContext) {
10460
- await mkdir12(join18(workspace, ".tania/context"), { recursive: true });
10461
- await writeFile10(join18(workspace, ".tania/context/task.md"), `# Task Context
11575
+ await mkdir12(join20(workspace, ".tania/context"), { recursive: true });
11576
+ await writeFile10(join20(workspace, ".tania/context/task.md"), `# Task Context
10462
11577
 
10463
11578
  Use ${marker}.
10464
11579
  `);
10465
11580
  }
10466
11581
  if (spec.dirtyWorktree) {
10467
- await writeFile10(join18(workspace, "unrelated.txt"), "baseline\n");
11582
+ await writeFile10(join20(workspace, "unrelated.txt"), "baseline\n");
10468
11583
  await execFileAsync3("git", ["init"], { cwd: workspace });
10469
11584
  await execFileAsync3("git", ["config", "user.email", "tanya@example.test"], { cwd: workspace });
10470
11585
  await execFileAsync3("git", ["config", "user.name", "Tanya Benchmark"], { cwd: workspace });
10471
11586
  await execFileAsync3("git", ["add", "unrelated.txt"], { cwd: workspace });
10472
11587
  await execFileAsync3("git", ["commit", "-m", "baseline"], { cwd: workspace });
10473
- await writeFile10(join18(workspace, "unrelated.txt"), "pre-existing dirty change\n");
11588
+ await writeFile10(join20(workspace, "unrelated.txt"), "pre-existing dirty change\n");
10474
11589
  }
10475
11590
  const toolCalls = [];
10476
11591
  if (spec.useArtifact) toolCalls.push(toolCall("read-artifact", "read_file", { path: ".tania/artifacts/generic/Pattern.md" }));
@@ -10516,8 +11631,8 @@ Use ${marker}.
10516
11631
  async function streamingLongToolFixture(profile) {
10517
11632
  const workspace = await createBaseWorkspace(profile.id);
10518
11633
  const script = 'for i in 1 2 3 4 5 6; do printf "stream-$i\\n"; sleep 2; done; node scripts/check.js';
10519
- await mkdir12(join18(workspace, "scripts"), { recursive: true });
10520
- await writeFile10(join18(workspace, "scripts/check.js"), [
11634
+ await mkdir12(join20(workspace, "scripts"), { recursive: true });
11635
+ await writeFile10(join20(workspace, "scripts/check.js"), [
10521
11636
  "const fs = require('fs');",
10522
11637
  "const text = fs.readFileSync('src/streamingLongTool.ts', 'utf8');",
10523
11638
  "if (!text.includes('streaming-long-tool-ready')) process.exit(1);",
@@ -10558,13 +11673,13 @@ async function streamingLongToolFixture(profile) {
10558
11673
  }
10559
11674
  async function androidSplashFixture(profile) {
10560
11675
  const workspace = await createBaseWorkspace(profile.id);
10561
- await mkdir12(join18(workspace, ".tania/artifacts/android"), { recursive: true });
10562
- await mkdir12(join18(workspace, "app/src/main/java/com/example/app/ui/splash"), { recursive: true });
10563
- await mkdir12(join18(workspace, "app/src/main/res/values"), { recursive: true });
10564
- await mkdir12(join18(workspace, "app/src/main/res/drawable"), { recursive: true });
10565
- await writeFile10(join18(workspace, ".tania/artifacts/android/SplashScreenPattern.kt"), "fun SplashPattern() {}\n");
10566
- await writeFile10(join18(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
10567
- await writeFile10(join18(workspace, "app/src/main/res/drawable/ic_splash_logo.png"), "png\n");
11676
+ await mkdir12(join20(workspace, ".tania/artifacts/android"), { recursive: true });
11677
+ await mkdir12(join20(workspace, "app/src/main/java/com/example/app/ui/splash"), { recursive: true });
11678
+ await mkdir12(join20(workspace, "app/src/main/res/values"), { recursive: true });
11679
+ await mkdir12(join20(workspace, "app/src/main/res/drawable"), { recursive: true });
11680
+ await writeFile10(join20(workspace, ".tania/artifacts/android/SplashScreenPattern.kt"), "fun SplashPattern() {}\n");
11681
+ await writeFile10(join20(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
11682
+ await writeFile10(join20(workspace, "app/src/main/res/drawable/ic_splash_logo.png"), "png\n");
10568
11683
  const provider = scriptedProvider(profile.id, [
10569
11684
  {
10570
11685
  content: "Create Android splash resources from the artifact.",
@@ -10621,10 +11736,10 @@ async function androidSplashFixture(profile) {
10621
11736
  }
10622
11737
  async function iosFoundationFixture(profile) {
10623
11738
  const workspace = await createBaseWorkspace(profile.id);
10624
- await mkdir12(join18(workspace, ".tania/artifacts/ios"), { recursive: true });
10625
- await writeFile10(join18(workspace, ".tania/artifacts/ios/ThemeSystem.swift"), "import SwiftUI\n");
10626
- await writeFile10(join18(workspace, ".tania/artifacts/ios/SwiftDataSetup.swift"), "import SwiftData\n");
10627
- await writeFile10(join18(workspace, ".tania/artifacts/ios/NavigationSetup.swift"), "import SwiftUI\n");
11739
+ await mkdir12(join20(workspace, ".tania/artifacts/ios"), { recursive: true });
11740
+ await writeFile10(join20(workspace, ".tania/artifacts/ios/ThemeSystem.swift"), "import SwiftUI\n");
11741
+ await writeFile10(join20(workspace, ".tania/artifacts/ios/SwiftDataSetup.swift"), "import SwiftData\n");
11742
+ await writeFile10(join20(workspace, ".tania/artifacts/ios/NavigationSetup.swift"), "import SwiftUI\n");
10628
11743
  const provider = scriptedProvider(profile.id, [
10629
11744
  {
10630
11745
  content: "Create iOS foundation from theme, SwiftData, and navigation artifacts.",
@@ -10695,7 +11810,7 @@ async function iosFoundationFixture(profile) {
10695
11810
  }
10696
11811
  async function appleAppIconFixture(profile) {
10697
11812
  const workspace = await createBaseWorkspace(profile.id);
10698
- await mkdir12(join18(workspace, "CosaNostra/Assets.xcassets"), { recursive: true });
11813
+ await mkdir12(join20(workspace, "CosaNostra/Assets.xcassets"), { recursive: true });
10699
11814
  const provider = scriptedProvider(profile.id, [
10700
11815
  {
10701
11816
  content: "Generate Apple app icon set with iOS and macOS slots.",
@@ -10738,14 +11853,14 @@ async function appleAppIconFixture(profile) {
10738
11853
  }
10739
11854
  async function androidFoundationFixture(profile) {
10740
11855
  const workspace = await createBaseWorkspace(profile.id);
10741
- await mkdir12(join18(workspace, ".tania/artifacts/android"), { recursive: true });
10742
- await mkdir12(join18(workspace, "app"), { recursive: true });
10743
- await writeFile10(join18(workspace, ".tania/artifacts/android/ThemeSystem.kt"), "package artifact\n");
10744
- await writeFile10(join18(workspace, ".tania/artifacts/android/NavigationSetup.kt"), "package artifact\n");
10745
- await writeFile10(join18(workspace, ".tania/artifacts/android/RoomSetup.kt"), "package artifact\n");
10746
- await writeFile10(join18(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
10747
- await writeFile10(join18(workspace, "build.gradle.kts"), 'plugins {\n id("com.android.application") version "8.7.2" apply false\n}\n');
10748
- await writeFile10(join18(workspace, "app/build.gradle.kts"), [
11856
+ await mkdir12(join20(workspace, ".tania/artifacts/android"), { recursive: true });
11857
+ await mkdir12(join20(workspace, "app"), { recursive: true });
11858
+ await writeFile10(join20(workspace, ".tania/artifacts/android/ThemeSystem.kt"), "package artifact\n");
11859
+ await writeFile10(join20(workspace, ".tania/artifacts/android/NavigationSetup.kt"), "package artifact\n");
11860
+ await writeFile10(join20(workspace, ".tania/artifacts/android/RoomSetup.kt"), "package artifact\n");
11861
+ await writeFile10(join20(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
11862
+ await writeFile10(join20(workspace, "build.gradle.kts"), 'plugins {\n id("com.android.application") version "8.7.2" apply false\n}\n');
11863
+ await writeFile10(join20(workspace, "app/build.gradle.kts"), [
10749
11864
  "plugins {",
10750
11865
  ' id("com.android.application")',
10751
11866
  ' id("org.jetbrains.kotlin.android")',
@@ -10806,12 +11921,12 @@ async function androidFoundationFixture(profile) {
10806
11921
  }
10807
11922
  async function backendApiFoundationFixture(profile) {
10808
11923
  const workspace = await createBaseWorkspace(profile.id);
10809
- await mkdir12(join18(workspace, ".tania/artifacts/backend"), { recursive: true });
10810
- await mkdir12(join18(workspace, "app/api/health"), { recursive: true });
10811
- await mkdir12(join18(workspace, "lib"), { recursive: true });
10812
- await mkdir12(join18(workspace, "prisma"), { recursive: true });
10813
- await writeFile10(join18(workspace, ".tania/artifacts/backend/JwtAuthRoutes.ts"), "export function authRoutePattern() {}\n");
10814
- await writeFile10(join18(workspace, "package.json"), JSON.stringify({ scripts: { typecheck: "tsc --noEmit", test: "node --test" }, dependencies: {} }, null, 2));
11924
+ await mkdir12(join20(workspace, ".tania/artifacts/backend"), { recursive: true });
11925
+ await mkdir12(join20(workspace, "app/api/health"), { recursive: true });
11926
+ await mkdir12(join20(workspace, "lib"), { recursive: true });
11927
+ await mkdir12(join20(workspace, "prisma"), { recursive: true });
11928
+ await writeFile10(join20(workspace, ".tania/artifacts/backend/JwtAuthRoutes.ts"), "export function authRoutePattern() {}\n");
11929
+ await writeFile10(join20(workspace, "package.json"), JSON.stringify({ scripts: { typecheck: "tsc --noEmit", test: "node --test" }, dependencies: {} }, null, 2));
10815
11930
  const provider = scriptedProvider(profile.id, [
10816
11931
  {
10817
11932
  content: "Create backend health/API foundation from the route artifact.",
@@ -11005,7 +12120,7 @@ async function runGoldenSuiteCommand(workspace, action, json = false, options =
11005
12120
 
11006
12121
  // src/init/projectInit.ts
11007
12122
  import { mkdir as mkdir13, readFile as readFile11, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "fs/promises";
11008
- import { dirname as dirname14, join as join19, resolve as resolve11 } from "path";
12123
+ import { dirname as dirname14, join as join21, resolve as resolve11 } from "path";
11009
12124
  async function fileExists(path) {
11010
12125
  try {
11011
12126
  const entry = await stat7(path);
@@ -11015,7 +12130,7 @@ async function fileExists(path) {
11015
12130
  }
11016
12131
  }
11017
12132
  async function readPackageJson(workspace) {
11018
- const packagePath = join19(workspace, "package.json");
12133
+ const packagePath = join21(workspace, "package.json");
11019
12134
  try {
11020
12135
  return JSON.parse(await readFile11(packagePath, "utf8"));
11021
12136
  } catch {
@@ -11043,10 +12158,10 @@ async function detectStack(workspace) {
11043
12158
  const pkg = await readPackageJson(workspace);
11044
12159
  const entries = await rootEntries(workspace);
11045
12160
  const hasPackageJson = pkg !== null;
11046
- const hasPrisma = await fileExists(join19(workspace, "prisma", "schema.prisma"));
12161
+ const hasPrisma = await fileExists(join21(workspace, "prisma", "schema.prisma"));
11047
12162
  const hasNextConfig = entries.some((entry) => /^next\.config\.(?:js|mjs|cjs|ts)$/.test(entry));
11048
- const hasTsconfig = await fileExists(join19(workspace, "tsconfig.json"));
11049
- const hasGradlew = await fileExists(join19(workspace, "gradlew"));
12163
+ const hasTsconfig = await fileExists(join21(workspace, "tsconfig.json"));
12164
+ const hasGradlew = await fileExists(join21(workspace, "gradlew"));
11050
12165
  const xcodeProjects = entries.filter((entry) => entry.endsWith(".xcodeproj"));
11051
12166
  const projectTypes = [];
11052
12167
  if (hasNextConfig || hasDependency(pkg, "next")) projectTypes.push("Next.js");
@@ -11097,7 +12212,7 @@ function buildInstructions(detection) {
11097
12212
  }
11098
12213
  async function initTanyaProject(cwd) {
11099
12214
  const workspace = resolve11(cwd);
11100
- const instructionsPath = join19(workspace, ".tania", "INSTRUCTIONS.md");
12215
+ const instructionsPath = join21(workspace, ".tania", "INSTRUCTIONS.md");
11101
12216
  const detection = await detectStack(workspace);
11102
12217
  await mkdir13(dirname14(instructionsPath), { recursive: true });
11103
12218
  try {
@@ -11236,6 +12351,7 @@ var cliOptionDefinitions = [
11236
12351
  { flags: "--output-dir <path>", key: "output-dir", kind: "string", property: "outputDir", aliases: ["outputDir"] },
11237
12352
  { flags: "--plan", key: "plan", kind: "boolean", property: "plan" },
11238
12353
  { flags: "--profile <id>", key: "profile", kind: "string", property: "profile" },
12354
+ { flags: "--provider <name>", key: "provider", kind: "string", property: "provider" },
11239
12355
  { flags: "--prompt-file <path>", key: "prompt-file", kind: "string", property: "promptFile" },
11240
12356
  { flags: "--repair-attempts <count>", key: "repair-attempts", kind: "string", property: "repairAttempts" },
11241
12357
  { flags: "--require-verification <command>", key: "require-verification", kind: "array", property: "requireVerification" },
@@ -11382,6 +12498,11 @@ function applyCliProfileFlag(args) {
11382
12498
  if (!profile) return;
11383
12499
  process.env.TANYA_PROFILE = profile;
11384
12500
  }
12501
+ function applyCliProviderFlag(args) {
12502
+ const provider = flagString(args, "provider");
12503
+ if (!provider) return;
12504
+ process.env.TANYA_PROVIDER = provider;
12505
+ }
11385
12506
  function buildRetryContext(manifest, attempt, extraBlockers = []) {
11386
12507
  const lines = [
11387
12508
  `RETRY CONTEXT (attempt ${attempt}): the previous run did not complete cleanly.`,
@@ -11403,14 +12524,6 @@ function buildRetryContext(manifest, attempt, extraBlockers = []) {
11403
12524
  lines.push("", "Fix the blockers above and complete the task.");
11404
12525
  return lines.join("\n");
11405
12526
  }
11406
- function estimateRunCost(model, promptTokens, completionTokens) {
11407
- const isReasoner = model.includes("reasoner");
11408
- const inputRate = isReasoner ? 0.55 : 0.27;
11409
- const outputRate = isReasoner ? 2.19 : 1.1;
11410
- const cost = promptTokens / 1e6 * inputRate + completionTokens / 1e6 * outputRate;
11411
- if (cost < 1e-3) return "<$0.001";
11412
- return `~$${cost.toFixed(3)}`;
11413
- }
11414
12527
  function usage() {
11415
12528
  return `Tanya CLI
11416
12529
 
@@ -11455,7 +12568,7 @@ Usage:
11455
12568
  tanya benchmark profiles List benchmark profiles
11456
12569
  tanya benchmark run --all Run executable regression benchmarks
11457
12570
  tanya benchmark validate Validate recent benchmark signatures
11458
- tanya providers test Test provider configuration
12571
+ tanya providers test --provider deepseek Test provider configuration (live only with TANYA_RUN_LIVE_PROVIDER_TESTS=1)
11459
12572
  tanya doctor Check local setup
11460
12573
  tanya patterns Show forbidden-pattern fire metrics for this workspace
11461
12574
 
@@ -11466,7 +12579,7 @@ Install locally during development:
11466
12579
  }
11467
12580
  function readPrompt(args) {
11468
12581
  const promptFile = flagString(args, "prompt-file");
11469
- if (promptFile) return readFileSync7(resolve12(promptFile), "utf8");
12582
+ if (promptFile) return readFileSync8(resolve12(promptFile), "utf8");
11470
12583
  return args.positional.join(" ").trim();
11471
12584
  }
11472
12585
  async function buildRunContextForCli(args, cwd, prompt, obsidianVault) {
@@ -11474,7 +12587,7 @@ async function buildRunContextForCli(args, cwd, prompt, obsidianVault) {
11474
12587
  const baseRunContext = contextFile ? loadRunContextFile(contextFile) : void 0;
11475
12588
  const explicitArtifactsRoot = flagString(args, "artifacts-root");
11476
12589
  const localArtifactsRoot = resolve12(cwd, "artifacts");
11477
- const artifactsRoot = explicitArtifactsRoot ?? (existsSync15(localArtifactsRoot) ? localArtifactsRoot : void 0);
12590
+ const artifactsRoot = explicitArtifactsRoot ?? (existsSync17(localArtifactsRoot) ? localArtifactsRoot : void 0);
11478
12591
  let runContext = materializeCliArtifacts({
11479
12592
  cwd,
11480
12593
  root: artifactsRoot,
@@ -11574,17 +12687,6 @@ ${heading}
11574
12687
  }
11575
12688
  return matched.join("\n\n---\n\n");
11576
12689
  }
11577
- function formatSkillPackSummary(packs) {
11578
- const totalTokens = packs.reduce((sum, pack) => sum + pack.tokens, 0);
11579
- const lines = [
11580
- `Skill packs loaded: ${packs.length}`,
11581
- "| slug | reason | tokens |",
11582
- "|------|--------|--------|",
11583
- ...packs.map((pack) => `| ${pack.slug} | ${pack.reason} | ${pack.tokens} |`),
11584
- `Total pack tokens: ${totalTokens} / ${SKILL_PACK_TOKEN_BUDGET}`
11585
- ];
11586
- return lines.join("\n");
11587
- }
11588
12690
  async function askOnce(provider, prompt) {
11589
12691
  let text2 = "";
11590
12692
  for await (const delta of provider.streamChat({ messages: [{ role: "user", content: prompt }], tools: [] })) {
@@ -11596,20 +12698,31 @@ async function askOnce(provider, prompt) {
11596
12698
  process.stdout.write("\n");
11597
12699
  return text2;
11598
12700
  }
11599
- async function testProvider() {
12701
+ async function testProvider(args) {
12702
+ const requestedProvider = flagString(args, "provider") ?? "configured";
12703
+ if (envValue({}, "TANYA_RUN_LIVE_PROVIDER_TESTS") !== "1") {
12704
+ console.log(`skipped live provider test for ${requestedProvider}; set TANYA_RUN_LIVE_PROVIDER_TESTS=1 to run against the real endpoint.`);
12705
+ return;
12706
+ }
11600
12707
  const config = loadConfig();
11601
12708
  const provider = createProvider(config);
11602
12709
  const startedAt = Date.now();
11603
12710
  let text2 = "";
11604
12711
  for await (const delta of provider.streamChat({
11605
- messages: [{ role: "user", content: "Reply with exactly: pong" }],
12712
+ messages: [
12713
+ { role: "system", content: "You are a provider conformance probe. Keep answers short." },
12714
+ { role: "user", content: "Reply with exactly: pong" },
12715
+ { role: "user", content: "No tools are needed." }
12716
+ ],
11606
12717
  tools: [],
11607
12718
  maxTokens: 12,
11608
12719
  temperature: 0
11609
12720
  })) {
11610
12721
  if (delta.content) text2 += delta.content;
11611
12722
  }
11612
- console.log(`ok ${provider.id}:${provider.model} ${Date.now() - startedAt}ms ${text2.trim()}`);
12723
+ console.log(`PASS adapter: ${provider.id}:${provider.model}`);
12724
+ console.log(`PASS streaming-chat: ${Date.now() - startedAt}ms ${text2.trim()}`);
12725
+ console.log("PASS parser-surface: mock conformance covers malformed tool-call quirks in CI");
11613
12726
  }
11614
12727
  async function doctor(args) {
11615
12728
  const cwd = resolve12(args ? flagString(args, "cwd") ?? process.cwd() : process.cwd());
@@ -11627,16 +12740,16 @@ async function doctor(args) {
11627
12740
  else fail("provider.baseUrl", "missing \u2014 set TANYA_BASE_URL");
11628
12741
  ok("provider.model", `${config.provider}:${config.model} (profile=${config.profile})`);
11629
12742
  ok("provider.timeoutMs", `${config.timeoutMs}ms`);
11630
- const cwdHasGit = existsSync15(join20(cwd, ".git"));
12743
+ const cwdHasGit = existsSync17(join22(cwd, ".git"));
11631
12744
  if (cwdHasGit) ok("workspace.git", `${cwd}`);
11632
12745
  else warn("workspace.git", `${cwd} is not a git repository \u2014 stash/retry recovery will be disabled`);
11633
- const cwdHasArtifacts = existsSync15(join20(cwd, "artifacts"));
11634
- if (cwdHasArtifacts) ok("workspace.artifacts", `${join20(cwd, "artifacts")} (auto-detected)`);
12746
+ const cwdHasArtifacts = existsSync17(join22(cwd, "artifacts"));
12747
+ if (cwdHasArtifacts) ok("workspace.artifacts", `${join22(cwd, "artifacts")} (auto-detected)`);
11635
12748
  else warn("workspace.artifacts", "no ./artifacts dir \u2014 pass --artifacts-root or run from a project that has one");
11636
- const fpPath = join20(cwd, ".tania", "forbidden-patterns.json");
11637
- if (existsSync15(fpPath)) {
12749
+ const fpPath = join22(cwd, ".tania", "forbidden-patterns.json");
12750
+ if (existsSync17(fpPath)) {
11638
12751
  try {
11639
- const raw = readFileSync7(fpPath, "utf8");
12752
+ const raw = readFileSync8(fpPath, "utf8");
11640
12753
  const parsed = JSON.parse(raw);
11641
12754
  const count = Array.isArray(parsed?.patterns) ? parsed.patterns.length : 0;
11642
12755
  ok("workspace.forbiddenPatterns", `${count} project pattern(s) loaded from ${fpPath}`);
@@ -11646,10 +12759,10 @@ async function doctor(args) {
11646
12759
  } else {
11647
12760
  ok("workspace.forbiddenPatterns", "no project overrides (using defaults)");
11648
12761
  }
11649
- const fpMetricsPath = join20(cwd, ".tania", "memory", "forbidden-patterns-metrics.json");
11650
- if (existsSync15(fpMetricsPath)) {
12762
+ const fpMetricsPath = join22(cwd, ".tania", "memory", "forbidden-patterns-metrics.json");
12763
+ if (existsSync17(fpMetricsPath)) {
11651
12764
  try {
11652
- const raw = readFileSync7(fpMetricsPath, "utf8");
12765
+ const raw = readFileSync8(fpMetricsPath, "utf8");
11653
12766
  const parsed = JSON.parse(raw);
11654
12767
  const totals = parsed.totals ?? {};
11655
12768
  const top = Object.entries(totals).sort((a, b) => b[1] - a[1]).slice(0, 5);
@@ -11666,7 +12779,7 @@ async function doctor(args) {
11666
12779
  ok("workspace.forbiddenPatterns.metrics", "no metrics yet (no scans recorded)");
11667
12780
  }
11668
12781
  if (config.obsidianVault) {
11669
- if (existsSync15(config.obsidianVault)) ok("obsidian.vault", config.obsidianVault);
12782
+ if (existsSync17(config.obsidianVault)) ok("obsidian.vault", config.obsidianVault);
11670
12783
  else warn("obsidian.vault", `${config.obsidianVault} configured but path does not exist`);
11671
12784
  } else {
11672
12785
  ok("obsidian.vault", "not configured (optional)");
@@ -11695,29 +12808,29 @@ async function runVideoCommand(args) {
11695
12808
  if (preset === "presets" || preset === "list") {
11696
12809
  console.log("Video presets:");
11697
12810
  for (const item of videoPresets) {
11698
- const aliases = item.aliases.length ? ` aliases: ${item.aliases.join(", ")}` : "";
11699
- console.log(`- ${item.name} (${item.width}x${item.height}, ${item.fps}fps, ${item.duration}s)${aliases}`);
12811
+ const aliases2 = item.aliases.length ? ` aliases: ${item.aliases.join(", ")}` : "";
12812
+ console.log(`- ${item.name} (${item.width}x${item.height}, ${item.fps}fps, ${item.duration}s)${aliases2}`);
11700
12813
  console.log(` ${item.description}`);
11701
12814
  }
11702
12815
  return;
11703
12816
  }
11704
12817
  if (preset === "render-ad") {
11705
12818
  const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
11706
- const input2 = flagString(args, "input");
11707
- if (!input2) {
12819
+ const input = flagString(args, "input");
12820
+ if (!input) {
11708
12821
  console.log("Usage: tanya video render-ad --input spec.json [--output-dir dir] [--basename name] [--format mp4] [--format poster]");
11709
12822
  return;
11710
12823
  }
11711
12824
  const formats2 = flagStrings(args, "format");
11712
12825
  const renderOptions = {
11713
- input: input2,
12826
+ input,
11714
12827
  formats: formats2.length ? formats2 : ["mp4", "poster"]
11715
12828
  };
11716
12829
  const outputDir2 = flagString(args, "output-dir") ?? flagString(args, "outputDir");
11717
- const basename5 = flagString(args, "basename");
12830
+ const basename6 = flagString(args, "basename");
11718
12831
  const ffmpegPath = flagString(args, "ffmpeg-path") ?? flagString(args, "ffmpegPath");
11719
12832
  if (outputDir2) renderOptions.outputDir = outputDir2;
11720
- if (basename5) renderOptions.basename = basename5;
12833
+ if (basename6) renderOptions.basename = basename6;
11721
12834
  if (ffmpegPath) renderOptions.ffmpegPath = ffmpegPath;
11722
12835
  const result2 = await renderFullAd(renderOptions, cwd2);
11723
12836
  console.log(JSON.stringify({
@@ -11739,7 +12852,7 @@ async function runVideoCommand(args) {
11739
12852
  const formats = flagStrings(args, "format");
11740
12853
  const options = { preset };
11741
12854
  const outputDir = flagString(args, "output-dir") ?? flagString(args, "outputDir");
11742
- const basename4 = flagString(args, "basename");
12855
+ const basename5 = flagString(args, "basename");
11743
12856
  const width = flagNumber(args, "width");
11744
12857
  const height = flagNumber(args, "height");
11745
12858
  const fps = flagNumber(args, "fps");
@@ -11750,7 +12863,7 @@ async function runVideoCommand(args) {
11750
12863
  const badge = flagString(args, "badge");
11751
12864
  const lines = flagStrings(args, "line");
11752
12865
  if (outputDir) options.outputDir = outputDir;
11753
- if (basename4) options.basename = basename4;
12866
+ if (basename5) options.basename = basename5;
11754
12867
  if (width !== void 0) options.width = width;
11755
12868
  if (height !== void 0) options.height = height;
11756
12869
  if (fps !== void 0) options.fps = fps;
@@ -11767,6 +12880,7 @@ async function runVideoCommand(args) {
11767
12880
  }
11768
12881
  async function main() {
11769
12882
  const args = parseArgs(process.argv.slice(2));
12883
+ applyCliProviderFlag(args);
11770
12884
  if (args.command === "help" || args.command === "--help" || args.command === "-h") {
11771
12885
  console.log(usage());
11772
12886
  return;
@@ -11777,13 +12891,13 @@ async function main() {
11777
12891
  }
11778
12892
  if (args.command === "patterns") {
11779
12893
  const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
11780
- const metricsPath = join20(cwd2, ".tania", "memory", "forbidden-patterns-metrics.json");
11781
- if (!existsSync15(metricsPath)) {
12894
+ const metricsPath = join22(cwd2, ".tania", "memory", "forbidden-patterns-metrics.json");
12895
+ if (!existsSync17(metricsPath)) {
11782
12896
  console.log(`No metrics file at ${metricsPath}. Run a tanya task in this workspace first.`);
11783
12897
  return;
11784
12898
  }
11785
12899
  try {
11786
- const parsed = JSON.parse(readFileSync7(metricsPath, "utf8"));
12900
+ const parsed = JSON.parse(readFileSync8(metricsPath, "utf8"));
11787
12901
  const totals = parsed.totals ?? {};
11788
12902
  const lastFiredAt = parsed.lastFiredAt ?? {};
11789
12903
  const entries = Object.entries(totals).sort((a, b) => b[1] - a[1]);
@@ -11807,10 +12921,10 @@ async function main() {
11807
12921
  }
11808
12922
  if (args.command === "providers") {
11809
12923
  if (args.positional[0] !== "test") {
11810
- console.log("Usage: tanya providers test");
12924
+ console.log("Usage: tanya providers test --provider <name>");
11811
12925
  return;
11812
12926
  }
11813
- await testProvider();
12927
+ await testProvider(args);
11814
12928
  return;
11815
12929
  }
11816
12930
  if (args.command === "init") {
@@ -11840,31 +12954,13 @@ async function main() {
11840
12954
  }
11841
12955
  if (args.command === "runs") {
11842
12956
  const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
11843
- const runsDir = join20(cwd2, ".tania", "runs");
11844
- if (!existsSync15(runsDir)) {
12957
+ const logs = readRunLogs(cwd2, 10);
12958
+ if (logs.length === 0) {
11845
12959
  process.stdout.write("No run logs found. Run tanya run first.\n");
11846
12960
  return;
11847
12961
  }
11848
- const { readdirSync: readdirSync5, readFileSync: readFileSync8 } = await import("fs");
11849
- const files = readdirSync5(runsDir).filter((file) => file.endsWith(".json")).sort().reverse().slice(0, 10);
11850
- if (files.length === 0) {
11851
- process.stdout.write("No run logs found.\n");
11852
- return;
11853
- }
11854
- for (const file of files) {
11855
- try {
11856
- const log = JSON.parse(readFileSync8(join20(runsDir, file), "utf8"));
11857
- const cost = estimateRunCost(log.model, log.promptTokens ?? 0, log.completionTokens ?? 0);
11858
- const status = log.blockers?.length > 0 ? "BLOCKED" : "OK";
11859
- const duration = `${Math.round((log.durationMs ?? 0) / 1e3)}s`;
11860
- const fileCount = log.changedFiles?.length ?? 0;
11861
- process.stdout.write(
11862
- `${log.ts?.slice(0, 16)} ${status.padEnd(7)} ${duration.padStart(5)} ${cost.padStart(8)} ${fileCount} file(s) ${log.prompt?.slice(0, 60)}
11863
- `
11864
- );
11865
- } catch {
11866
- }
11867
- }
12962
+ for (const log of logs) process.stdout.write(`${formatRunLogLine(log)}
12963
+ `);
11868
12964
  return;
11869
12965
  }
11870
12966
  applyCliProfileFlag(args);
@@ -11878,9 +12974,9 @@ async function main() {
11878
12974
  const systemPrompt = buildSystemPrompt(cwd2, runContext, historyBlock, task);
11879
12975
  const skillPacks = loadPromptSkillPacks(cwd2, runContext, task);
11880
12976
  const sections = flagStrings(args, "section");
11881
- const output2 = selectPromptSections(systemPrompt, sections);
12977
+ const output = selectPromptSections(systemPrompt, sections);
11882
12978
  process.stdout.write("=== SYSTEM PROMPT ===\n\n");
11883
- process.stdout.write(output2);
12979
+ process.stdout.write(output);
11884
12980
  process.stdout.write("\n\n=== END SYSTEM PROMPT ===\n");
11885
12981
  if (sections.length === 0) {
11886
12982
  process.stdout.write(`
@@ -12053,7 +13149,12 @@ ${postBlockers.map((blocker) => ` - ${blocker}`).join("\n")}
12053
13149
  }
12054
13150
  }
12055
13151
  if (runPromptTokens > 0 || runCompletionTokens > 0) {
12056
- const costStr = estimateRunCost(config.model, runPromptTokens, runCompletionTokens);
13152
+ const costStr = estimateRunCost({
13153
+ provider: config.provider,
13154
+ model: config.model,
13155
+ promptTokens: runPromptTokens,
13156
+ completionTokens: runCompletionTokens
13157
+ }).display;
12057
13158
  process.stderr.write(
12058
13159
  `[tanya] Tokens: ${runPromptTokens.toLocaleString()} in / ${runCompletionTokens.toLocaleString()} out ${costStr}
12059
13160
  `