@tuned-tensor/cli 0.18.0 → 0.19.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/{chunk-U5HNS367.js → chunk-4Q54NHKR.js} +117 -1
- package/dist/chunk-4Q54NHKR.js.map +1 -0
- package/dist/index.js +75 -221
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +1 -1
- package/docs/local-runtime/foundation-long-runs.md +2 -2
- package/docs/local-runtime/serving.md +1 -1
- package/docs/spec-architecture.html +1 -1
- package/docs/spec-workflow.md +6 -2
- package/package.json +1 -1
- package/dist/chunk-U5HNS367.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -39,10 +39,11 @@ import {
|
|
|
39
39
|
runReportSchema,
|
|
40
40
|
specHash,
|
|
41
41
|
validateBehaviorSpec,
|
|
42
|
+
validateEvalCases,
|
|
42
43
|
validatePipeline,
|
|
43
44
|
warningsFromSnapshot,
|
|
44
45
|
withFoundationPythonEnvironment
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-4Q54NHKR.js";
|
|
46
47
|
|
|
47
48
|
// src/cli.ts
|
|
48
49
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -51,183 +52,22 @@ import { Command } from "commander";
|
|
|
51
52
|
import chalk9 from "chalk";
|
|
52
53
|
|
|
53
54
|
// src/local-agent-client.ts
|
|
54
|
-
import { randomUUID as
|
|
55
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
55
56
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
56
57
|
|
|
57
58
|
// src/spec-workspace.ts
|
|
58
|
-
import { randomUUID } from "crypto";
|
|
59
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
59
60
|
import { constants as constants2 } from "fs";
|
|
60
61
|
import { lstat as lstat2, mkdir as mkdir2, open as open2, readdir, rename, unlink } from "fs/promises";
|
|
61
62
|
import { isAbsolute, join as join2, relative, sep } from "path";
|
|
62
63
|
|
|
63
64
|
// src/local-spec-workspace.ts
|
|
64
|
-
import { createHash } from "crypto";
|
|
65
|
+
import { createHash, randomUUID } from "crypto";
|
|
65
66
|
import { constants } from "fs";
|
|
66
67
|
import { lstat, mkdir, open, realpath, rmdir, writeFile } from "fs/promises";
|
|
67
68
|
import { join } from "path";
|
|
68
69
|
import { Type } from "typebox";
|
|
69
70
|
import { Value } from "typebox/value";
|
|
70
|
-
|
|
71
|
-
// src/eval/rules.ts
|
|
72
|
-
function validateSpec(spec) {
|
|
73
|
-
const checks = [];
|
|
74
|
-
checks.push({
|
|
75
|
-
name: "Has name",
|
|
76
|
-
passed: Boolean(spec.name?.trim()),
|
|
77
|
-
message: spec.name?.trim() ? void 0 : "Spec is missing a name"
|
|
78
|
-
});
|
|
79
|
-
checks.push({
|
|
80
|
-
name: "Has system prompt",
|
|
81
|
-
passed: Boolean(spec.system_prompt?.trim()),
|
|
82
|
-
message: spec.system_prompt?.trim() ? void 0 : "Spec is missing a system_prompt"
|
|
83
|
-
});
|
|
84
|
-
const isFoundation = spec.engine === "foundation";
|
|
85
|
-
checks.push({
|
|
86
|
-
name: isFoundation ? "Has foundation engine" : "Has base model",
|
|
87
|
-
passed: isFoundation ? spec.foundation != null && typeof spec.foundation === "object" : Boolean(spec.base_model?.trim()),
|
|
88
|
-
message: isFoundation ? spec.foundation != null ? void 0 : "Foundation spec is missing a foundation block" : spec.base_model?.trim() ? void 0 : "Spec is missing a base_model"
|
|
89
|
-
});
|
|
90
|
-
const exCount = spec.examples?.length ?? 0;
|
|
91
|
-
const minExamples = isFoundation ? 2 : 1;
|
|
92
|
-
checks.push({
|
|
93
|
-
name: "Has examples",
|
|
94
|
-
passed: exCount >= minExamples,
|
|
95
|
-
message: exCount >= minExamples ? `${exCount} example(s)` : isFoundation ? "Add at least two chat examples" : "Add at least one example"
|
|
96
|
-
});
|
|
97
|
-
checks.push({
|
|
98
|
-
name: "Has guidelines",
|
|
99
|
-
passed: (spec.guidelines?.length ?? 0) > 0,
|
|
100
|
-
message: (spec.guidelines?.length ?? 0) > 0 ? void 0 : "Consider adding guidelines for better eval coverage"
|
|
101
|
-
});
|
|
102
|
-
if (spec.constraints?.length && spec.examples?.length) {
|
|
103
|
-
const violations = checkExamplesAgainstConstraints(spec);
|
|
104
|
-
checks.push({
|
|
105
|
-
name: "Examples satisfy constraints",
|
|
106
|
-
passed: violations.length === 0,
|
|
107
|
-
message: violations.length === 0 ? void 0 : violations.join("; ")
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
const evalCaseErrors = validateEvalCases(spec);
|
|
111
|
-
checks.push({
|
|
112
|
-
name: "Eval cases valid",
|
|
113
|
-
passed: evalCaseErrors.length === 0,
|
|
114
|
-
message: evalCaseErrors.length === 0 ? spec.eval_cases?.length ? `${spec.eval_cases.length} executable eval case(s)` : void 0 : evalCaseErrors.join("; ")
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
valid: checks.every((c) => c.passed),
|
|
118
|
-
checks
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function validateEvalCases(spec) {
|
|
122
|
-
const errors = [];
|
|
123
|
-
const cases = spec.eval_cases ?? [];
|
|
124
|
-
for (const [caseIndex, evalCase] of cases.entries()) {
|
|
125
|
-
const label = `eval_cases[${caseIndex}]`;
|
|
126
|
-
if (!evalCase || typeof evalCase !== "object") {
|
|
127
|
-
errors.push(`${label} must be an object`);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (typeof evalCase.input !== "string" || evalCase.input.trim().length === 0) {
|
|
131
|
-
errors.push(`${label}.input must be a non-empty string`);
|
|
132
|
-
}
|
|
133
|
-
if (evalCase.runtime !== "python") {
|
|
134
|
-
errors.push(`${label}.runtime must be "python"`);
|
|
135
|
-
}
|
|
136
|
-
if (!Array.isArray(evalCase.tests) || evalCase.tests.length === 0) {
|
|
137
|
-
errors.push(`${label}.tests must contain at least one test`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
for (const [testIndex, test] of evalCase.tests.entries()) {
|
|
141
|
-
const testLabel = `${label}.tests[${testIndex}]`;
|
|
142
|
-
if (!test || typeof test !== "object") {
|
|
143
|
-
errors.push(`${testLabel} must be an object`);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (test.name !== void 0 && (typeof test.name !== "string" || test.name.trim().length === 0)) {
|
|
147
|
-
errors.push(`${testLabel}.name must be a non-empty string when provided`);
|
|
148
|
-
}
|
|
149
|
-
if (test.args !== void 0 && (!Array.isArray(test.args) || !test.args.every((arg) => typeof arg === "string"))) {
|
|
150
|
-
errors.push(`${testLabel}.args must be an array of strings`);
|
|
151
|
-
}
|
|
152
|
-
if (test.stdin !== void 0 && typeof test.stdin !== "string") {
|
|
153
|
-
errors.push(`${testLabel}.stdin must be a string`);
|
|
154
|
-
}
|
|
155
|
-
if (test.expected_exit_code !== void 0 && (typeof test.expected_exit_code !== "number" || !Number.isInteger(test.expected_exit_code) || test.expected_exit_code < 0 || test.expected_exit_code > 255)) {
|
|
156
|
-
errors.push(`${testLabel}.expected_exit_code must be an integer between 0 and 255`);
|
|
157
|
-
}
|
|
158
|
-
if (test.expected_stdout !== void 0 && typeof test.expected_stdout !== "string") {
|
|
159
|
-
errors.push(`${testLabel}.expected_stdout must be a string`);
|
|
160
|
-
}
|
|
161
|
-
validateFiles(test.files, `${testLabel}.files`, errors);
|
|
162
|
-
validateFiles(test.expected_files, `${testLabel}.expected_files`, errors);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return errors;
|
|
166
|
-
}
|
|
167
|
-
function validateFiles(files, label, errors) {
|
|
168
|
-
if (files === void 0) return;
|
|
169
|
-
if (!Array.isArray(files)) {
|
|
170
|
-
errors.push(`${label} must be an array`);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
for (const [index, file] of files.entries()) {
|
|
174
|
-
const fileLabel = `${label}[${index}]`;
|
|
175
|
-
if (!file || typeof file !== "object") {
|
|
176
|
-
errors.push(`${fileLabel} must be an object`);
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
const row = file;
|
|
180
|
-
if (typeof row.path !== "string" || !isSafeRelativePath(row.path)) {
|
|
181
|
-
errors.push(`${fileLabel}.path must be a safe relative path`);
|
|
182
|
-
}
|
|
183
|
-
if (row.content !== void 0 && typeof row.content !== "string") {
|
|
184
|
-
errors.push(`${fileLabel}.content must be a string`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function isSafeRelativePath(value) {
|
|
189
|
-
if (value.length === 0 || value.length > 240) return false;
|
|
190
|
-
if (value.includes("\0") || value.startsWith("/")) return false;
|
|
191
|
-
const normalized = value.replace(/\\/g, "/");
|
|
192
|
-
return !normalized.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
193
|
-
}
|
|
194
|
-
function checkExamplesAgainstConstraints(spec) {
|
|
195
|
-
const violations = [];
|
|
196
|
-
for (const [i, ex] of spec.examples.entries()) {
|
|
197
|
-
for (const constraint of spec.constraints) {
|
|
198
|
-
const result2 = evaluateConstraint(ex.output, constraint);
|
|
199
|
-
if (!result2.passed) {
|
|
200
|
-
violations.push(`Example ${i + 1} violates "${constraint}": ${result2.message}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return violations;
|
|
205
|
-
}
|
|
206
|
-
function evaluateConstraint(text2, constraint) {
|
|
207
|
-
const lower = constraint.toLowerCase();
|
|
208
|
-
const neverMatch = lower.match(/^never (?:share|mention|include|reveal|use|say|output|return|give|provide|show|disclose|expose)\s+(.+)/);
|
|
209
|
-
if (neverMatch) {
|
|
210
|
-
const forbidden = neverMatch[1].replace(/['"]/g, "").trim();
|
|
211
|
-
if (text2.toLowerCase().includes(forbidden.toLowerCase())) {
|
|
212
|
-
return { passed: false, message: `Output contains "${forbidden}"` };
|
|
213
|
-
}
|
|
214
|
-
return { passed: true };
|
|
215
|
-
}
|
|
216
|
-
const alwaysMatch = lower.match(/^always (?:include|use|start with|end with|contain|respond in|reply in)\s+(.+)/);
|
|
217
|
-
if (alwaysMatch) {
|
|
218
|
-
const required = alwaysMatch[1].replace(/['"]/g, "").trim();
|
|
219
|
-
if (!text2.toLowerCase().includes(required.toLowerCase())) {
|
|
220
|
-
return { passed: false, message: `Output missing "${required}"` };
|
|
221
|
-
}
|
|
222
|
-
return { passed: true };
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
passed: true,
|
|
226
|
-
message: `Constraint not enforceable by rules: "${constraint}" (will be checked by LLM judge if a provider is configured)`
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// src/local-spec-workspace.ts
|
|
231
71
|
var FolderName = Type.String({
|
|
232
72
|
minLength: 1,
|
|
233
73
|
maxLength: 120,
|
|
@@ -286,17 +126,6 @@ function validateInputs(directory, spec) {
|
|
|
286
126
|
throw new Error("Local spec content does not match the canonical tunedtensor.json schema.");
|
|
287
127
|
}
|
|
288
128
|
pipelineForRunInput(parseLocalRunInput(spec, join(directory, "tunedtensor.json")));
|
|
289
|
-
if (spec.engine === "foundation") return;
|
|
290
|
-
const validation = validateSpec({
|
|
291
|
-
...spec,
|
|
292
|
-
description: spec.description ?? "",
|
|
293
|
-
constraints: spec.constraints ?? []
|
|
294
|
-
});
|
|
295
|
-
if (!validation.valid) {
|
|
296
|
-
const messages = validation.checks.filter((check) => !check.passed).map((check) => check.message ?? check.name);
|
|
297
|
-
throw new Error(`Local spec validation failed:
|
|
298
|
-
- ${messages.join("\n- ")}`);
|
|
299
|
-
}
|
|
300
129
|
}
|
|
301
130
|
async function canonicalWorkspace(workspaceRoot) {
|
|
302
131
|
const rootInfo = await lstat(workspaceRoot);
|
|
@@ -425,7 +254,7 @@ async function createLocalSpecProject(workspaceRoot, directory, spec, expectedWo
|
|
|
425
254
|
}
|
|
426
255
|
const stableSpecPath = join(`/proc/self/fd/${directoryHandle.fd}`, "tunedtensor.json");
|
|
427
256
|
writeStarted = true;
|
|
428
|
-
await operations.writeFile(stableSpecPath, `${JSON.stringify(spec, null, 2)}
|
|
257
|
+
await operations.writeFile(stableSpecPath, `${JSON.stringify({ id: randomUUID(), ...spec }, null, 2)}
|
|
429
258
|
`, {
|
|
430
259
|
encoding: "utf8",
|
|
431
260
|
flag: "wx",
|
|
@@ -509,7 +338,8 @@ var EDIT_KEYS = /* @__PURE__ */ new Set([
|
|
|
509
338
|
"dataset_prebuilt",
|
|
510
339
|
"runtime",
|
|
511
340
|
"evaluation",
|
|
512
|
-
"pipeline"
|
|
341
|
+
"pipeline",
|
|
342
|
+
"add_examples"
|
|
513
343
|
]);
|
|
514
344
|
function isObject(value) {
|
|
515
345
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -599,6 +429,26 @@ async function inspectLocalSpec(workspaceRoot, specPath = "tunedtensor.json") {
|
|
|
599
429
|
}
|
|
600
430
|
return { ...location, source, sha256: specHash(source), document, validation };
|
|
601
431
|
}
|
|
432
|
+
function arrayDiff(before, after) {
|
|
433
|
+
const added = after.map((item) => JSON.stringify(item));
|
|
434
|
+
const removed = [];
|
|
435
|
+
for (const item of before.map((value) => JSON.stringify(value))) {
|
|
436
|
+
const index = added.indexOf(item);
|
|
437
|
+
if (index === -1) removed.push(`- ${item}`);
|
|
438
|
+
else added.splice(index, 1);
|
|
439
|
+
}
|
|
440
|
+
const lines = [...removed, ...added.map((item) => `+ ${item}`)];
|
|
441
|
+
return lines.length ? lines : [" (order changed)"];
|
|
442
|
+
}
|
|
443
|
+
function derivedSpecId(document, path) {
|
|
444
|
+
if (document.id !== void 0) return void 0;
|
|
445
|
+
try {
|
|
446
|
+
const input = parseLocalRunInput(document, path);
|
|
447
|
+
return input.kind === "spec" ? input.request.behavior_spec_id : void 0;
|
|
448
|
+
} catch {
|
|
449
|
+
return void 0;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
602
452
|
function specDiff(before, after) {
|
|
603
453
|
const left = isObject(before) ? before : {};
|
|
604
454
|
const right = isObject(after) ? after : {};
|
|
@@ -606,6 +456,10 @@ function specDiff(before, after) {
|
|
|
606
456
|
const lines = [];
|
|
607
457
|
for (const key of keys) {
|
|
608
458
|
if (JSON.stringify(left[key]) === JSON.stringify(right[key])) continue;
|
|
459
|
+
if (Array.isArray(left[key]) && Array.isArray(right[key])) {
|
|
460
|
+
lines.push(`@@ ${key} @@`, ...arrayDiff(left[key], right[key]));
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
609
463
|
lines.push(
|
|
610
464
|
`@@ ${key} @@`,
|
|
611
465
|
...JSON.stringify(left[key] ?? null, null, 2).split("\n").map((line) => `- ${line}`),
|
|
@@ -621,7 +475,14 @@ async function prepareSpecUpdate(workspaceRoot, specPath, expectedSha256, change
|
|
|
621
475
|
if (!isObject(changes) || !Object.keys(changes).length || Object.keys(changes).some((key) => !EDIT_KEYS.has(key))) {
|
|
622
476
|
throw new Error("Provide supported spec field changes; identity and engine cannot be changed by an edit.");
|
|
623
477
|
}
|
|
624
|
-
const
|
|
478
|
+
const { add_examples: added, ...fields } = changes;
|
|
479
|
+
if (added !== void 0) {
|
|
480
|
+
if (!Array.isArray(added) || "examples" in fields) throw new Error("Use add_examples to append examples, or examples to replace them, not both.");
|
|
481
|
+
const existing = Array.isArray(current.document.examples) ? current.document.examples : [];
|
|
482
|
+
fields.examples = [...existing, ...added];
|
|
483
|
+
}
|
|
484
|
+
const id = derivedSpecId(current.document, current.path);
|
|
485
|
+
const next = { ...id ? { id } : {}, ...current.document, ...fields };
|
|
625
486
|
function mergeSettings(before, patch2) {
|
|
626
487
|
const result2 = { ...before };
|
|
627
488
|
for (const [key, value] of Object.entries(patch2)) {
|
|
@@ -631,13 +492,13 @@ async function prepareSpecUpdate(workspaceRoot, specPath, expectedSha256, change
|
|
|
631
492
|
return result2;
|
|
632
493
|
}
|
|
633
494
|
for (const key of ["runtime", "evaluation", "pipeline"]) {
|
|
634
|
-
if (
|
|
635
|
-
else if (key !== "pipeline" && isObject(
|
|
495
|
+
if (fields[key] === null) delete next[key];
|
|
496
|
+
else if (key !== "pipeline" && isObject(fields[key])) next[key] = mergeSettings(isObject(current.document[key]) ? current.document[key] : {}, fields[key]);
|
|
636
497
|
}
|
|
637
498
|
for (const key of ["hyperparameters", "foundation"]) {
|
|
638
|
-
if (isObject(
|
|
499
|
+
if (isObject(fields[key])) {
|
|
639
500
|
const previous = isObject(current.document[key]) ? current.document[key] : {};
|
|
640
|
-
next[key] = { ...previous, ...
|
|
501
|
+
next[key] = { ...previous, ...fields[key] };
|
|
641
502
|
}
|
|
642
503
|
}
|
|
643
504
|
const validation = validateDocument(next, current.path);
|
|
@@ -686,7 +547,7 @@ async function privateChild(parent, name) {
|
|
|
686
547
|
}
|
|
687
548
|
async function atomicText(parent, name, source) {
|
|
688
549
|
const base = `/proc/self/fd/${parent.fd}`;
|
|
689
|
-
const temporary = join2(base, `.tt-${
|
|
550
|
+
const temporary = join2(base, `.tt-${randomUUID2()}.tmp`);
|
|
690
551
|
try {
|
|
691
552
|
const file = await open2(temporary, "wx", 384);
|
|
692
553
|
try {
|
|
@@ -726,7 +587,7 @@ async function applySpecUpdate(workspaceRoot, update) {
|
|
|
726
587
|
handles.push(state);
|
|
727
588
|
const history = await privateChild(state, "spec-history");
|
|
728
589
|
handles.push(history);
|
|
729
|
-
const id = `${Date.now()}-${
|
|
590
|
+
const id = `${Date.now()}-${randomUUID2()}`;
|
|
730
591
|
const revision = {
|
|
731
592
|
id,
|
|
732
593
|
status: "prepared",
|
|
@@ -778,7 +639,7 @@ async function readSpecHistory(workspaceRoot, specPath = "tunedtensor.json") {
|
|
|
778
639
|
import { createHash as createHash3 } from "crypto";
|
|
779
640
|
|
|
780
641
|
// src/local-pipeline-action.ts
|
|
781
|
-
import { createHash as createHash2, randomUUID as
|
|
642
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
782
643
|
import { lstat as lstat3, mkdtemp, readFile, realpath as realpath2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
783
644
|
import { tmpdir } from "os";
|
|
784
645
|
import { dirname, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve, sep as sep2 } from "path";
|
|
@@ -959,7 +820,7 @@ async function validatePreparedLocalPipelineAction(args2) {
|
|
|
959
820
|
async function executeLocalPipelineAction(args2) {
|
|
960
821
|
const prepared = await validatePreparedLocalPipelineAction(args2);
|
|
961
822
|
const temporaryDirectory = await mkdtemp(join3(tmpdir(), "tt-agent-pipeline-"));
|
|
962
|
-
const temporaryId =
|
|
823
|
+
const temporaryId = randomUUID3();
|
|
963
824
|
const specPath = join3(temporaryDirectory, `${temporaryId}.spec.json`);
|
|
964
825
|
const config = prepared.resolvedConfig;
|
|
965
826
|
const { evaluation, dryRun: _dryRun, ...runtime } = config;
|
|
@@ -1583,7 +1444,7 @@ function ensureOpenRouterAppHeaders(modelsPath) {
|
|
|
1583
1444
|
}
|
|
1584
1445
|
|
|
1585
1446
|
// src/agent-tools.ts
|
|
1586
|
-
import { createHash as createHash4, randomUUID as
|
|
1447
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
1587
1448
|
import { lstatSync, readFileSync as readFileSync3 } from "fs";
|
|
1588
1449
|
import { fileURLToPath } from "url";
|
|
1589
1450
|
import { Type as Type2 } from "typebox";
|
|
@@ -1962,7 +1823,7 @@ function define(name, label, description, parameters, execute) {
|
|
|
1962
1823
|
}
|
|
1963
1824
|
function proposal(operation, title, summary, args2, preview) {
|
|
1964
1825
|
const action = {
|
|
1965
|
-
id:
|
|
1826
|
+
id: randomUUID4(),
|
|
1966
1827
|
operation,
|
|
1967
1828
|
title,
|
|
1968
1829
|
summary,
|
|
@@ -2236,7 +2097,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2236
2097
|
define(
|
|
2237
2098
|
"prepare_update_local_spec",
|
|
2238
2099
|
"Prepare behavior spec edit",
|
|
2239
|
-
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec.
|
|
2100
|
+
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
|
|
2240
2101
|
Type2.Object({
|
|
2241
2102
|
spec_path: Type2.Optional(Type2.String({ maxLength: 1e3 })),
|
|
2242
2103
|
expected_sha256: Type2.String({ pattern: "^[a-f0-9]{64}$" }),
|
|
@@ -2251,6 +2112,10 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2251
2112
|
input: Type2.String(),
|
|
2252
2113
|
output: Type2.String()
|
|
2253
2114
|
}, { additionalProperties: false }))),
|
|
2115
|
+
add_examples: Type2.Optional(Type2.Array(Type2.Object({
|
|
2116
|
+
input: Type2.String(),
|
|
2117
|
+
output: Type2.String()
|
|
2118
|
+
}, { additionalProperties: false }), { minItems: 1 })),
|
|
2254
2119
|
hyperparameters: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2255
2120
|
foundation: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2256
2121
|
runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
@@ -2381,7 +2246,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2381
2246
|
// src/local-agent-client.ts
|
|
2382
2247
|
var MAX_TOOL_CALLS_PER_TURN = 12;
|
|
2383
2248
|
var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
|
|
2384
|
-
The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new project. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file. Use /spec, /spec diff, /spec validate and /spec history for direct review. General educational or inspection questions do not require a spec mutation.
|
|
2249
|
+
The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes; use add_examples to append examples. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new project. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file. Use /spec, /spec diff, /spec validate and /spec history for direct review. General educational or inspection questions do not require a spec mutation.
|
|
2385
2250
|
Local workflow commands need no TT access token. For local inspection commands such as runs, models, or doctor, tell the user to run the matching TT command in this shell (for example \`runs list\` or \`doctor\`); those commands execute outside the agent. User-owned AWS GPU training uses \`tt pipeline run\` with runtime.gpu configuration in tunedtensor.json, without a TT token. \`tt cloud\` is for TT account records, not GPU execution; hosted training start/estimate are retired. AWS credentials authorize instance lookup; separate SSH access is required. The instance must already be running, and the user manages quota, capacity, costs and shutdown. Keep the laptop running until outputs return. Model-provider choice is independent of GPU placement.
|
|
2386
2251
|
When the user requests commands to inspect a run or serve a result, provide the direct CLI handoff: \`tt runs list\` to find a run, \`tt runs get <run-id>\` for its details, \`tt runs report <run-id>\` for its evaluation report, \`tt models list\` to find the resulting model ID, and \`tt serve <model-id>\` to serve that model. Replace placeholders with actual IDs from those commands; never invent results or IDs. Until the user supplies command output, keep literal <run-id> and <model-id> placeholders; do not fabricate example IDs or prefixes. Local run IDs are UUIDs and adapter model IDs are local-<run-id>. Do not refresh hardware or propose spec edits merely to supply those commands; perform those actions only when requested.
|
|
2387
2252
|
For accurate adapter and foundation workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
|
|
@@ -2453,7 +2318,7 @@ function createLocalAgentClient(options) {
|
|
|
2453
2318
|
const timestamp = now();
|
|
2454
2319
|
const state = {
|
|
2455
2320
|
thread: {
|
|
2456
|
-
id:
|
|
2321
|
+
id: randomUUID5(),
|
|
2457
2322
|
title: "New conversation",
|
|
2458
2323
|
status: "active",
|
|
2459
2324
|
last_message_at: null,
|
|
@@ -2476,7 +2341,7 @@ function createLocalAgentClient(options) {
|
|
|
2476
2341
|
async runTurn(threadId, prompt, onEvent, signal, context) {
|
|
2477
2342
|
const state = await options.store.load(threadId);
|
|
2478
2343
|
const actions = [];
|
|
2479
|
-
const turnId =
|
|
2344
|
+
const turnId = randomUUID5();
|
|
2480
2345
|
let response = "";
|
|
2481
2346
|
let failure;
|
|
2482
2347
|
const effectiveToolApi = {
|
|
@@ -2630,7 +2495,7 @@ import {
|
|
|
2630
2495
|
unlink as unlink2,
|
|
2631
2496
|
writeFile as writeFile3
|
|
2632
2497
|
} from "fs/promises";
|
|
2633
|
-
import { randomUUID as
|
|
2498
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2634
2499
|
import { join as join6 } from "path";
|
|
2635
2500
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2636
2501
|
var DEFAULT_MAX_THREAD_BYTES = 1e6;
|
|
@@ -2763,7 +2628,7 @@ var LocalAgentStore = class {
|
|
|
2763
2628
|
const destination = this.path(state.thread.id);
|
|
2764
2629
|
const temporary = join6(
|
|
2765
2630
|
this.threadsDir,
|
|
2766
|
-
`.${state.thread.id}.${
|
|
2631
|
+
`.${state.thread.id}.${randomUUID6()}.tmp`
|
|
2767
2632
|
);
|
|
2768
2633
|
let serialized;
|
|
2769
2634
|
try {
|
|
@@ -3549,10 +3414,7 @@ var FEATURED_AGENT_PROVIDERS = [
|
|
|
3549
3414
|
var RECOMMENDED_AGENT_MODELS = [
|
|
3550
3415
|
{ provider: "tunedtensor", ids: ["managed"] },
|
|
3551
3416
|
{ provider: "openai", ids: ["gpt-5.6-sol"] },
|
|
3552
|
-
{
|
|
3553
|
-
provider: "openrouter",
|
|
3554
|
-
ids: ["deepseek/deepseek-v4-flash-0731", "deepseek/deepseek-v4-flash"]
|
|
3555
|
-
}
|
|
3417
|
+
{ provider: "openrouter", ids: ["anthropic/claude-sonnet-5"] }
|
|
3556
3418
|
];
|
|
3557
3419
|
var FEATURED_PROVIDER_INDEX = new Map(
|
|
3558
3420
|
FEATURED_AGENT_PROVIDERS.map((id, index) => [id, index])
|
|
@@ -4781,26 +4643,18 @@ ${chalk4.yellow.bold("Approval required")} ${chalk4.dim(action.id.slice(0, 8))}
|
|
|
4781
4643
|
if (event.type === "text_delta") {
|
|
4782
4644
|
const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
|
|
4783
4645
|
if (!delta) return;
|
|
4784
|
-
|
|
4785
|
-
this.endOpenLine();
|
|
4786
|
-
this.options.io.write("\n");
|
|
4787
|
-
}
|
|
4646
|
+
this.reasoningActive = false;
|
|
4788
4647
|
this.writeRenderedResponse(this.responseMarkdown.push(delta));
|
|
4789
4648
|
return;
|
|
4790
4649
|
}
|
|
4791
4650
|
if (event.type === "reasoning_delta") {
|
|
4792
4651
|
const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
|
|
4793
4652
|
if (!delta) return;
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
this.options.io.write("\n");
|
|
4800
|
-
this.reasoningActive = true;
|
|
4801
|
-
}
|
|
4802
|
-
this.options.io.write(chalk4.dim.italic(safeDelta));
|
|
4803
|
-
this.lineOpen = !safeDelta.endsWith("\n");
|
|
4653
|
+
if (this.reasoningActive) return;
|
|
4654
|
+
this.flushResponse();
|
|
4655
|
+
this.endOpenLine();
|
|
4656
|
+
this.options.io.write(chalk4.dim(" \u25CB Thinking\u2026\n"));
|
|
4657
|
+
this.reasoningActive = true;
|
|
4804
4658
|
return;
|
|
4805
4659
|
}
|
|
4806
4660
|
if (event.type === "tool_call") {
|
|
@@ -5062,7 +4916,7 @@ ${chalk4.yellow.bold("Approval required")} ${chalk4.dim(action.id.slice(0, 8))}
|
|
|
5062
4916
|
|
|
5063
4917
|
// src/local-runner.ts
|
|
5064
4918
|
import { constants as osConstants } from "os";
|
|
5065
|
-
import { randomUUID as
|
|
4919
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
5066
4920
|
import { spawn } from "child_process";
|
|
5067
4921
|
import { existsSync as existsSync3 } from "fs";
|
|
5068
4922
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -5817,7 +5671,7 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
5817
5671
|
}
|
|
5818
5672
|
const temporaryPath = join8(
|
|
5819
5673
|
dirname4(sourcePath),
|
|
5820
|
-
`.${basename3(sourcePath)}.tt-local-${process.pid}-${
|
|
5674
|
+
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
|
|
5821
5675
|
);
|
|
5822
5676
|
await writeFile4(
|
|
5823
5677
|
temporaryPath,
|
|
@@ -6258,7 +6112,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
6258
6112
|
}
|
|
6259
6113
|
|
|
6260
6114
|
// src/local-runtime/foundation-runner.ts
|
|
6261
|
-
import { createHash as createHash5, randomUUID as
|
|
6115
|
+
import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
|
|
6262
6116
|
import { createReadStream } from "fs";
|
|
6263
6117
|
import { chmod as chmod2, lstat as lstat6, mkdir as mkdir4, open as open3, readdir as readdir4, readFile as readFile6, rename as rename4, rm as rm3 } from "fs/promises";
|
|
6264
6118
|
import { basename as basename4, dirname as dirname6, join as join10, resolve as resolve5 } from "path";
|
|
@@ -6342,7 +6196,7 @@ async function readRequiredJsonObject(path, description) {
|
|
|
6342
6196
|
return parsed;
|
|
6343
6197
|
}
|
|
6344
6198
|
async function writePrivateTextAtomic(path, content) {
|
|
6345
|
-
const temporary = join10(dirname6(path), `.${basename4(path)}.${
|
|
6199
|
+
const temporary = join10(dirname6(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
|
|
6346
6200
|
const file = await open3(temporary, "wx", 384);
|
|
6347
6201
|
let closed = false;
|
|
6348
6202
|
try {
|
|
@@ -6458,7 +6312,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6458
6312
|
assertFoundationPlanSupported(args2.spec, args2.plan);
|
|
6459
6313
|
assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
|
|
6460
6314
|
const outputDir = resolve5(
|
|
6461
|
-
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join10(resolve5(dirname6(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs",
|
|
6315
|
+
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join10(resolve5(dirname6(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join10(dirname6(resolve5(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
|
|
6462
6316
|
);
|
|
6463
6317
|
if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
|
|
6464
6318
|
throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
|
|
@@ -8769,7 +8623,7 @@ Run \`tt init\` to create one.`
|
|
|
8769
8623
|
|
|
8770
8624
|
// src/serve-manager.ts
|
|
8771
8625
|
import { spawn as spawn2 } from "child_process";
|
|
8772
|
-
import { randomUUID as
|
|
8626
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
8773
8627
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
8774
8628
|
import { createServer } from "http";
|
|
8775
8629
|
import { createServer as createNetServer } from "net";
|
|
@@ -8993,7 +8847,7 @@ var ManagedModelServer = class {
|
|
|
8993
8847
|
});
|
|
8994
8848
|
}
|
|
8995
8849
|
async processGeneration(route, body, res, receivedAt) {
|
|
8996
|
-
const requestId = `ttreq-${
|
|
8850
|
+
const requestId = `ttreq-${randomUUID9()}`;
|
|
8997
8851
|
const startedAt = Date.now();
|
|
8998
8852
|
const queuedMs = startedAt - receivedAt;
|
|
8999
8853
|
this.activeRequests += 1;
|
|
@@ -11142,7 +10996,7 @@ async function runCli(version, runtime = {}) {
|
|
|
11142
10996
|
}
|
|
11143
10997
|
|
|
11144
10998
|
// src/index.ts
|
|
11145
|
-
runCli("0.
|
|
10999
|
+
runCli("0.19.0").catch((err) => {
|
|
11146
11000
|
reportError(err);
|
|
11147
11001
|
process.exit(1);
|
|
11148
11002
|
});
|