@kweaver-ai/kweaver-sdk 0.8.1 → 0.8.3
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/README.md +40 -52
- package/README.zh.md +41 -46
- package/dist/agent-providers/index.d.ts +7 -0
- package/dist/agent-providers/index.js +5 -0
- package/dist/agent-providers/prompt-template.d.ts +62 -0
- package/dist/agent-providers/prompt-template.js +105 -0
- package/dist/agent-providers/prompts/rubric-judge-v1.prompt.md +51 -0
- package/dist/agent-providers/prompts/within-trace-synthesizer-v1.prompt.md +60 -0
- package/dist/agent-providers/providers/claude-code-subprocess.d.ts +74 -0
- package/dist/agent-providers/providers/claude-code-subprocess.js +259 -0
- package/dist/agent-providers/providers/stub.d.ts +47 -0
- package/dist/agent-providers/providers/stub.js +77 -0
- package/dist/agent-providers/registry.d.ts +45 -0
- package/dist/agent-providers/registry.js +77 -0
- package/dist/agent-providers/types.d.ts +91 -0
- package/dist/agent-providers/types.js +25 -0
- package/dist/api/agent-chat.js +8 -6
- package/dist/api/context-loader.d.ts +1 -0
- package/dist/api/resources.d.ts +94 -0
- package/dist/api/resources.js +166 -0
- package/dist/api/semantic-search.d.ts +5 -0
- package/dist/api/semantic-search.js +5 -0
- package/dist/api/skills.d.ts +75 -2
- package/dist/api/skills.js +108 -12
- package/dist/api/trace.d.ts +5 -0
- package/dist/api/trace.js +4 -0
- package/dist/cli.js +109 -15
- package/dist/client.d.ts +3 -3
- package/dist/client.js +5 -5
- package/dist/commands/agent/mode.d.ts +6 -0
- package/dist/commands/agent/mode.js +75 -0
- package/dist/commands/agent-members.js +27 -11
- package/dist/commands/agent.js +469 -286
- package/dist/commands/auth.js +184 -71
- package/dist/commands/bkn-metric.js +37 -16
- package/dist/commands/bkn-ops.js +164 -86
- package/dist/commands/bkn-query.js +99 -31
- package/dist/commands/bkn-schema.d.ts +3 -3
- package/dist/commands/bkn-schema.js +127 -86
- package/dist/commands/bkn.js +153 -114
- package/dist/commands/call.js +23 -13
- package/dist/commands/config.js +22 -12
- package/dist/commands/context-loader.js +625 -49
- package/dist/commands/dataflow.js +14 -6
- package/dist/commands/ds.js +52 -30
- package/dist/commands/explore.js +18 -15
- package/dist/commands/model.js +53 -42
- package/dist/commands/resource.d.ts +1 -0
- package/dist/commands/{dataview.js → resource.js} +62 -84
- package/dist/commands/skill.d.ts +21 -1
- package/dist/commands/skill.js +567 -43
- package/dist/commands/token.js +11 -0
- package/dist/commands/tool.js +46 -29
- package/dist/commands/toolbox.js +31 -15
- package/dist/commands/trace.d.ts +26 -1
- package/dist/commands/trace.js +515 -15
- package/dist/commands/vega.js +466 -250
- package/dist/help/format.d.ts +65 -0
- package/dist/help/format.js +141 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/resources/bkn.d.ts +5 -0
- package/dist/resources/bkn.js +5 -0
- package/dist/resources/{dataviews.d.ts → resources.d.ts} +10 -11
- package/dist/resources/{dataviews.js → resources.js} +12 -13
- package/dist/resources/skills.d.ts +17 -1
- package/dist/resources/skills.js +32 -1
- package/dist/trace-ai/diagnose/agent-binding.d.ts +67 -0
- package/dist/trace-ai/diagnose/agent-binding.js +257 -0
- package/dist/trace-ai/diagnose/builtin-rules/tool-retry-intent-mismatch.yaml +68 -0
- package/dist/trace-ai/diagnose/index.d.ts +32 -0
- package/dist/trace-ai/diagnose/index.js +246 -0
- package/dist/trace-ai/diagnose/output-schema-converter.d.ts +24 -0
- package/dist/trace-ai/diagnose/output-schema-converter.js +81 -0
- package/dist/trace-ai/diagnose/query-extractor.d.ts +14 -0
- package/dist/trace-ai/diagnose/query-extractor.js +45 -0
- package/dist/trace-ai/diagnose/report-assembler.d.ts +31 -0
- package/dist/{trace-core → trace-ai}/diagnose/report-assembler.js +19 -9
- package/dist/trace-ai/diagnose/report-markdown.d.ts +18 -0
- package/dist/trace-ai/diagnose/report-markdown.js +192 -0
- package/dist/{trace-core → trace-ai}/diagnose/rule-loader.js +42 -8
- package/dist/{trace-core → trace-ai}/diagnose/schemas.d.ts +77 -2
- package/dist/trace-ai/diagnose/schemas.js +154 -0
- package/dist/trace-ai/diagnose/signal-probe.d.ts +17 -0
- package/dist/trace-ai/diagnose/signal-probe.js +39 -0
- package/dist/trace-ai/diagnose/synthesizer-agent.d.ts +40 -0
- package/dist/trace-ai/diagnose/synthesizer-agent.js +158 -0
- package/dist/{trace-core → trace-ai}/diagnose/trace-shaper.js +1 -0
- package/dist/{trace-core → trace-ai}/diagnose/types.d.ts +55 -6
- package/dist/trace-ai/eval-set/assertion-evaluator.d.ts +29 -0
- package/dist/trace-ai/eval-set/assertion-evaluator.js +100 -0
- package/dist/trace-ai/eval-set/builder.d.ts +36 -0
- package/dist/trace-ai/eval-set/builder.js +126 -0
- package/dist/trace-ai/eval-set/index.d.ts +15 -0
- package/dist/trace-ai/eval-set/index.js +10 -0
- package/dist/trace-ai/eval-set/output-writer.d.ts +27 -0
- package/dist/trace-ai/eval-set/output-writer.js +126 -0
- package/dist/trace-ai/eval-set/query-picker.d.ts +37 -0
- package/dist/trace-ai/eval-set/query-picker.js +147 -0
- package/dist/trace-ai/eval-set/redactor.d.ts +42 -0
- package/dist/trace-ai/eval-set/redactor.js +133 -0
- package/dist/trace-ai/eval-set/rubric-templates/answer-match-reference.prompt.md +19 -0
- package/dist/trace-ai/eval-set/schemas.d.ts +136 -0
- package/dist/trace-ai/eval-set/schemas.js +130 -0
- package/dist/trace-ai/eval-set/semantic-match-provider.d.ts +33 -0
- package/dist/trace-ai/eval-set/semantic-match-provider.js +51 -0
- package/dist/trace-ai/eval-set/test-runner.d.ts +34 -0
- package/dist/trace-ai/eval-set/test-runner.js +153 -0
- package/dist/trace-ai/eval-set/types.d.ts +46 -0
- package/dist/trace-ai/eval-set/types.js +8 -0
- package/dist/trace-ai/exp/bundle-writer.d.ts +10 -0
- package/dist/trace-ai/exp/bundle-writer.js +54 -0
- package/dist/trace-ai/exp/claude-binary.d.ts +5 -0
- package/dist/trace-ai/exp/claude-binary.js +30 -0
- package/dist/trace-ai/exp/coordinator.d.ts +45 -0
- package/dist/trace-ai/exp/coordinator.js +203 -0
- package/dist/trace-ai/exp/eval-runner.d.ts +14 -0
- package/dist/trace-ai/exp/eval-runner.js +47 -0
- package/dist/trace-ai/exp/exp-store/abort-signal.d.ts +3 -0
- package/dist/trace-ai/exp/exp-store/abort-signal.js +27 -0
- package/dist/trace-ai/exp/exp-store/candidate-lineage-yaml.d.ts +4 -0
- package/dist/trace-ai/exp/exp-store/candidate-lineage-yaml.js +37 -0
- package/dist/trace-ai/exp/exp-store/events-jsonl.d.ts +17 -0
- package/dist/trace-ai/exp/exp-store/events-jsonl.js +60 -0
- package/dist/trace-ai/exp/exp-store/exp-registry.d.ts +6 -0
- package/dist/trace-ai/exp/exp-store/exp-registry.js +41 -0
- package/dist/trace-ai/exp/exp-store/index.d.ts +46 -0
- package/dist/trace-ai/exp/exp-store/index.js +59 -0
- package/dist/trace-ai/exp/exp-store/lock.d.ts +3 -0
- package/dist/trace-ai/exp/exp-store/lock.js +73 -0
- package/dist/trace-ai/exp/exp-store/mission-md.d.ts +3 -0
- package/dist/trace-ai/exp/exp-store/mission-md.js +37 -0
- package/dist/trace-ai/exp/exp-store/readme-template.d.ts +5 -0
- package/dist/trace-ai/exp/exp-store/readme-template.js +25 -0
- package/dist/trace-ai/exp/exp-store/round-yaml.d.ts +3 -0
- package/dist/trace-ai/exp/exp-store/round-yaml.js +33 -0
- package/dist/trace-ai/exp/index.d.ts +8 -0
- package/dist/trace-ai/exp/index.js +238 -0
- package/dist/trace-ai/exp/info.d.ts +35 -0
- package/dist/trace-ai/exp/info.js +120 -0
- package/dist/trace-ai/exp/patch/agent-config.d.ts +1 -0
- package/dist/trace-ai/exp/patch/agent-config.js +26 -0
- package/dist/trace-ai/exp/patch/index.d.ts +2 -0
- package/dist/trace-ai/exp/patch/index.js +13 -0
- package/dist/trace-ai/exp/patch/skill.d.ts +1 -0
- package/dist/trace-ai/exp/patch/skill.js +24 -0
- package/dist/trace-ai/exp/providers/synthesizer-client.d.ts +14 -0
- package/dist/trace-ai/exp/providers/synthesizer-client.js +39 -0
- package/dist/trace-ai/exp/providers/triage-client.d.ts +19 -0
- package/dist/trace-ai/exp/providers/triage-client.js +51 -0
- package/dist/trace-ai/exp/schemas.d.ts +147 -0
- package/dist/trace-ai/exp/schemas.js +50 -0
- package/dist/trace-ai/exp/scoring.d.ts +2 -0
- package/dist/trace-ai/exp/scoring.js +46 -0
- package/dist/trace-ai/scan/aggregator.d.ts +20 -0
- package/dist/trace-ai/scan/aggregator.js +26 -0
- package/dist/trace-ai/scan/artifacts/paths.d.ts +12 -0
- package/dist/trace-ai/scan/artifacts/paths.js +18 -0
- package/dist/trace-ai/scan/artifacts/writer.d.ts +67 -0
- package/dist/trace-ai/scan/artifacts/writer.js +96 -0
- package/dist/trace-ai/scan/batched-rubric.d.ts +55 -0
- package/dist/trace-ai/scan/batched-rubric.js +159 -0
- package/dist/trace-ai/scan/cross-trace-synthesizer.d.ts +24 -0
- package/dist/trace-ai/scan/cross-trace-synthesizer.js +93 -0
- package/dist/trace-ai/scan/index.d.ts +31 -0
- package/dist/trace-ai/scan/index.js +390 -0
- package/dist/trace-ai/scan/prompts/builtin/cross-trace-synthesizer-v1.prompt.md +44 -0
- package/dist/trace-ai/scan/prompts/builtin/rubric-judge-batch-v1.prompt.md +44 -0
- package/dist/trace-ai/scan/runner.d.ts +25 -0
- package/dist/trace-ai/scan/runner.js +42 -0
- package/dist/trace-ai/scan/sampler.d.ts +18 -0
- package/dist/trace-ai/scan/sampler.js +81 -0
- package/dist/trace-ai/scan/scan-summary-markdown.d.ts +2 -0
- package/dist/trace-ai/scan/scan-summary-markdown.js +71 -0
- package/dist/trace-ai/scan/scan-summary-schema.d.ts +73 -0
- package/dist/trace-ai/scan/scan-summary-schema.js +61 -0
- package/dist/trace-ai/scan/single-agent-validator.d.ts +23 -0
- package/dist/trace-ai/scan/single-agent-validator.js +42 -0
- package/dist/trace-ai/scan/traces-list-parser.d.ts +15 -0
- package/dist/trace-ai/scan/traces-list-parser.js +46 -0
- package/package.json +2 -2
- package/dist/api/dataviews.d.ts +0 -117
- package/dist/api/dataviews.js +0 -265
- package/dist/commands/dataview.d.ts +0 -8
- package/dist/trace-core/diagnose/index.d.ts +0 -9
- package/dist/trace-core/diagnose/index.js +0 -104
- package/dist/trace-core/diagnose/report-assembler.d.ts +0 -12
- package/dist/trace-core/diagnose/schemas.js +0 -94
- package/dist/trace-core/diagnose/signal-probe.d.ts +0 -5
- package/dist/trace-core/diagnose/signal-probe.js +0 -21
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.yaml +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.yaml +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/register.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/register.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.yaml +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.yaml +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.yaml +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/predicate-registry.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/predicate-registry.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/rule-loader.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/synthesizer-template.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/synthesizer-template.js +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/trace-shaper.d.ts +0 -0
- /package/dist/{trace-core → trace-ai}/diagnose/types.js +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/trace-ai/exp/exp-store/candidate-lineage-yaml.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
function lineagePath(expDir) {
|
|
6
|
+
return path.join(expDir, ".trace-state", "candidate-lineage.yaml");
|
|
7
|
+
}
|
|
8
|
+
export async function appendLineage(expDir, entry) {
|
|
9
|
+
const p = lineagePath(expDir);
|
|
10
|
+
let entries = [];
|
|
11
|
+
try {
|
|
12
|
+
entries = yaml.load(await fs.readFile(p, "utf8")) ?? [];
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
entries.push({ ...entry, appended_at: new Date().toISOString() });
|
|
16
|
+
await fs.writeFile(p, yaml.dump(entries, { lineWidth: -1 }), "utf8");
|
|
17
|
+
}
|
|
18
|
+
export async function updateLineage(expDir, version, patch) {
|
|
19
|
+
const p = lineagePath(expDir);
|
|
20
|
+
let entries = [];
|
|
21
|
+
try {
|
|
22
|
+
entries = yaml.load(await fs.readFile(p, "utf8")) ?? [];
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
const idx = entries.findIndex(e => e.version === version);
|
|
26
|
+
if (idx >= 0)
|
|
27
|
+
Object.assign(entries[idx], patch);
|
|
28
|
+
await fs.writeFile(p, yaml.dump(entries, { lineWidth: -1 }), "utf8");
|
|
29
|
+
}
|
|
30
|
+
export async function readLineage(expDir) {
|
|
31
|
+
try {
|
|
32
|
+
return yaml.load(await fs.readFile(lineagePath(expDir), "utf8")) ?? [];
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExpEvent, ExpFsmState } from "../schemas.js";
|
|
2
|
+
export type EventInput = ExpEvent extends infer T ? T extends {
|
|
3
|
+
ts: string;
|
|
4
|
+
} ? Omit<T, "ts"> : never : never;
|
|
5
|
+
export declare function appendEvent(expDir: string, event: EventInput): Promise<void>;
|
|
6
|
+
export interface ReplayedState {
|
|
7
|
+
currentState: ExpFsmState;
|
|
8
|
+
currentRound: number;
|
|
9
|
+
lastEvent: ExpEvent | null;
|
|
10
|
+
lastFailure: {
|
|
11
|
+
state: ExpFsmState;
|
|
12
|
+
error: string;
|
|
13
|
+
retryable: boolean;
|
|
14
|
+
} | null;
|
|
15
|
+
isTerminal: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function replayState(expDir: string): Promise<ReplayedState>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function appendEvent(expDir, event) {
|
|
4
|
+
const filePath = path.join(expDir, ".trace-state", "events.jsonl");
|
|
5
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...event }) + "\n";
|
|
6
|
+
await fs.appendFile(filePath, line, "utf8");
|
|
7
|
+
}
|
|
8
|
+
const TERMINAL = new Set(["Published", "Aborted"]);
|
|
9
|
+
export async function replayState(expDir) {
|
|
10
|
+
const filePath = path.join(expDir, ".trace-state", "events.jsonl");
|
|
11
|
+
let raw;
|
|
12
|
+
try {
|
|
13
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { currentState: "Init", currentRound: 0, lastEvent: null, lastFailure: null, isTerminal: false };
|
|
17
|
+
}
|
|
18
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
19
|
+
if (lines.length === 0) {
|
|
20
|
+
return { currentState: "Init", currentRound: 0, lastEvent: null, lastFailure: null, isTerminal: false };
|
|
21
|
+
}
|
|
22
|
+
let currentState = "Init";
|
|
23
|
+
let currentRound = 0;
|
|
24
|
+
let lastEvent = null;
|
|
25
|
+
let lastFailure = null;
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
let ev;
|
|
28
|
+
try {
|
|
29
|
+
ev = JSON.parse(line);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Skip malformed lines (e.g. from a crash mid-write) rather than bricking replay.
|
|
33
|
+
process.stderr.write(`[warn] events.jsonl: skipping malformed line: ${line.slice(0, 120)}\n`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
lastEvent = ev;
|
|
37
|
+
if (ev.type === "state_transition") {
|
|
38
|
+
currentState = ev.to;
|
|
39
|
+
currentRound = ev.round;
|
|
40
|
+
lastFailure = null;
|
|
41
|
+
}
|
|
42
|
+
else if (ev.type === "step_failed") {
|
|
43
|
+
currentState = ev.state;
|
|
44
|
+
lastFailure = { state: ev.state, error: ev.error, retryable: ev.retryable };
|
|
45
|
+
}
|
|
46
|
+
else if (ev.type === "aborted") {
|
|
47
|
+
currentState = "Aborted";
|
|
48
|
+
}
|
|
49
|
+
else if (ev.type === "round_completed") {
|
|
50
|
+
currentRound = ev.round;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
currentState,
|
|
55
|
+
currentRound,
|
|
56
|
+
lastEvent,
|
|
57
|
+
lastFailure,
|
|
58
|
+
isTerminal: TERMINAL.has(currentState),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/trace-ai/exp/exp-store/exp-registry.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getConfigDir } from "../../../config/store.js";
|
|
5
|
+
function registryFilePath() {
|
|
6
|
+
return path.join(getConfigDir(), "exp-registry.json");
|
|
7
|
+
}
|
|
8
|
+
async function readRegistry() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await fs.readFile(registryFilePath(), "utf8");
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (!Array.isArray(parsed.entries))
|
|
13
|
+
return { schema_version: "exp-registry/v1", entries: [] };
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { schema_version: "exp-registry/v1", entries: [] };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function upsertRegistry(absPath, ts) {
|
|
21
|
+
try {
|
|
22
|
+
const reg = await readRegistry();
|
|
23
|
+
const idx = reg.entries.findIndex((e) => e.path === absPath);
|
|
24
|
+
if (idx >= 0) {
|
|
25
|
+
reg.entries[idx].last_active_ts = ts;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
reg.entries.push({ path: absPath, last_active_ts: ts });
|
|
29
|
+
}
|
|
30
|
+
const filePath = registryFilePath();
|
|
31
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
32
|
+
await fs.writeFile(filePath, JSON.stringify(reg, null, 2) + "\n", "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
process.stderr.write(`warn: exp-registry write failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function listRegistry() {
|
|
39
|
+
const reg = await readRegistry();
|
|
40
|
+
return [...reg.entries].sort((a, b) => b.last_active_ts.localeCompare(a.last_active_ts));
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LineageEntry, Mission, NextChange, RoundData } from "../schemas.js";
|
|
2
|
+
import { type ReplayedState, type EventInput } from "./events-jsonl.js";
|
|
3
|
+
export { type ReplayedState };
|
|
4
|
+
export declare class ExpStore {
|
|
5
|
+
readonly expDir: string;
|
|
6
|
+
constructor(expDir: string);
|
|
7
|
+
initDir(mission: Mission): Promise<string>;
|
|
8
|
+
archiveState(): Promise<void>;
|
|
9
|
+
readMission: () => Promise<{
|
|
10
|
+
schema_version: "trace-mission/v1";
|
|
11
|
+
goal: string;
|
|
12
|
+
eval_sets: {
|
|
13
|
+
path: string;
|
|
14
|
+
role: "seed" | "regression" | "holdout";
|
|
15
|
+
}[];
|
|
16
|
+
current_candidate: {
|
|
17
|
+
path: string;
|
|
18
|
+
};
|
|
19
|
+
max_rounds?: number | undefined;
|
|
20
|
+
provider?: string | undefined;
|
|
21
|
+
next_change?: {
|
|
22
|
+
target: string;
|
|
23
|
+
hypothesis: string;
|
|
24
|
+
patch: string;
|
|
25
|
+
} | undefined;
|
|
26
|
+
guardrails?: {
|
|
27
|
+
name: string;
|
|
28
|
+
kind: "hard" | "soft";
|
|
29
|
+
rule: string;
|
|
30
|
+
}[] | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
writeSuggestedChange: (c: NextChange) => Promise<void>;
|
|
33
|
+
appendEvent: (e: EventInput) => Promise<void>;
|
|
34
|
+
replayState: () => Promise<ReplayedState>;
|
|
35
|
+
acquireLock: () => Promise<void>;
|
|
36
|
+
releaseLock: () => Promise<void>;
|
|
37
|
+
updateHeartbeat: () => Promise<void>;
|
|
38
|
+
isAborted: () => Promise<boolean>;
|
|
39
|
+
writeAbortSignal: () => Promise<void>;
|
|
40
|
+
clearAbortSignal: () => Promise<void>;
|
|
41
|
+
writeRound: (n: number, data: Partial<RoundData>) => Promise<void>;
|
|
42
|
+
readAllRounds: () => Promise<RoundData[]>;
|
|
43
|
+
appendLineage: (e: Omit<LineageEntry, "appended_at">) => Promise<void>;
|
|
44
|
+
updateLineage: (v: number, p: Partial<LineageEntry>) => Promise<void>;
|
|
45
|
+
readLineage: () => Promise<LineageEntry[]>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/trace-ai/exp/exp-store/index.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { readMission, writeSuggestedChange } from "./mission-md.js";
|
|
6
|
+
import { appendEvent, replayState } from "./events-jsonl.js";
|
|
7
|
+
import { acquireLock, releaseLock, updateHeartbeat } from "./lock.js";
|
|
8
|
+
import { isAborted, writeAbortSignal, clearAbortSignal } from "./abort-signal.js";
|
|
9
|
+
import { writeRound, readAllRounds } from "./round-yaml.js";
|
|
10
|
+
import { appendLineage, updateLineage, readLineage } from "./candidate-lineage-yaml.js";
|
|
11
|
+
import { renderReadme } from "./readme-template.js";
|
|
12
|
+
export class ExpStore {
|
|
13
|
+
expDir;
|
|
14
|
+
constructor(expDir) {
|
|
15
|
+
this.expDir = expDir;
|
|
16
|
+
}
|
|
17
|
+
async initDir(mission) {
|
|
18
|
+
const experimentId = `exp_${crypto.randomBytes(4).toString("hex")}`;
|
|
19
|
+
await fs.mkdir(path.join(this.expDir, ".trace-state", "rounds"), { recursive: true });
|
|
20
|
+
await fs.mkdir(path.join(this.expDir, "candidates"), { recursive: true });
|
|
21
|
+
await fs.mkdir(path.join(this.expDir, "eval-sets"), { recursive: true });
|
|
22
|
+
await fs.mkdir(path.join(this.expDir, "outputs"), { recursive: true });
|
|
23
|
+
await fs.writeFile(path.join(this.expDir, ".trace-state", "events.jsonl"), "", { flag: "wx" }).catch(() => { }); // already exists ok
|
|
24
|
+
const readmePath = path.join(this.expDir, "README.md");
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(readmePath);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
await fs.writeFile(readmePath, renderReadme({
|
|
30
|
+
experimentId,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
goal: mission.goal,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
return experimentId;
|
|
36
|
+
}
|
|
37
|
+
async archiveState() {
|
|
38
|
+
const src = path.join(this.expDir, ".trace-state");
|
|
39
|
+
const dst = path.join(this.expDir, `.trace-state-archived-${Date.now()}`);
|
|
40
|
+
await fs.rename(src, dst);
|
|
41
|
+
await fs.mkdir(path.join(this.expDir, ".trace-state", "rounds"), { recursive: true });
|
|
42
|
+
await fs.writeFile(path.join(this.expDir, ".trace-state", "events.jsonl"), "");
|
|
43
|
+
}
|
|
44
|
+
readMission = () => readMission(this.expDir);
|
|
45
|
+
writeSuggestedChange = (c) => writeSuggestedChange(this.expDir, c);
|
|
46
|
+
appendEvent = (e) => appendEvent(this.expDir, e);
|
|
47
|
+
replayState = () => replayState(this.expDir);
|
|
48
|
+
acquireLock = () => acquireLock(this.expDir);
|
|
49
|
+
releaseLock = () => releaseLock(this.expDir);
|
|
50
|
+
updateHeartbeat = () => updateHeartbeat(this.expDir);
|
|
51
|
+
isAborted = () => isAborted(this.expDir);
|
|
52
|
+
writeAbortSignal = () => writeAbortSignal(this.expDir);
|
|
53
|
+
clearAbortSignal = () => clearAbortSignal(this.expDir);
|
|
54
|
+
writeRound = (n, data) => writeRound(this.expDir, n, data);
|
|
55
|
+
readAllRounds = () => readAllRounds(this.expDir);
|
|
56
|
+
appendLineage = (e) => appendLineage(this.expDir, e);
|
|
57
|
+
updateLineage = (v, p) => updateLineage(this.expDir, v, p);
|
|
58
|
+
readLineage = () => readLineage(this.expDir);
|
|
59
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const STALE_THRESHOLD_MS = 30_000;
|
|
5
|
+
function lockPath(expDir) {
|
|
6
|
+
return path.join(expDir, ".trace-state", "lock.json");
|
|
7
|
+
}
|
|
8
|
+
export async function acquireLock(expDir) {
|
|
9
|
+
const p = lockPath(expDir);
|
|
10
|
+
const lock = {
|
|
11
|
+
hostname: os.hostname(),
|
|
12
|
+
pid: process.pid,
|
|
13
|
+
started_at: new Date().toISOString(),
|
|
14
|
+
last_heartbeat_ts: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
const data = JSON.stringify(lock, null, 2);
|
|
17
|
+
// O_EXCL: atomic create — fails with EEXIST if a lock file already exists.
|
|
18
|
+
try {
|
|
19
|
+
await fs.writeFile(p, data, { encoding: "utf8", flag: "wx" });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err.code !== "EEXIST")
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
// Lock file exists — check freshness.
|
|
27
|
+
let existing;
|
|
28
|
+
try {
|
|
29
|
+
existing = JSON.parse(await fs.readFile(p, "utf8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Unreadable lock (e.g. partial write) — treat as stale.
|
|
33
|
+
existing = { hostname: "", pid: 0, started_at: "", last_heartbeat_ts: new Date(0).toISOString() };
|
|
34
|
+
}
|
|
35
|
+
const age = Date.now() - new Date(existing.last_heartbeat_ts).getTime();
|
|
36
|
+
if (age < STALE_THRESHOLD_MS) {
|
|
37
|
+
throw new Error(`Experiment is locked by pid ${existing.pid} on ${existing.hostname} (heartbeat ${Math.floor(age / 1000)}s ago). Use exp resume or wait.`);
|
|
38
|
+
}
|
|
39
|
+
// Stale — unlink then retry O_EXCL. If another process beats us here, we'll
|
|
40
|
+
// get EEXIST again and throw a clear error rather than silently overwriting.
|
|
41
|
+
await fs.unlink(p).catch(() => { });
|
|
42
|
+
try {
|
|
43
|
+
await fs.writeFile(p, data, { encoding: "utf8", flag: "wx" });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err.code === "EEXIST") {
|
|
47
|
+
throw new Error("Lock acquired by another process during stale recovery. Try again.");
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function releaseLock(expDir) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.unlink(lockPath(expDir));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err.code !== "ENOENT")
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function updateHeartbeat(expDir) {
|
|
62
|
+
const p = lockPath(expDir);
|
|
63
|
+
try {
|
|
64
|
+
const raw = await fs.readFile(p, "utf8");
|
|
65
|
+
const lock = JSON.parse(raw);
|
|
66
|
+
lock.last_heartbeat_ts = new Date().toISOString();
|
|
67
|
+
await fs.writeFile(p, JSON.stringify(lock, null, 2), "utf8");
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err.code !== "ENOENT")
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { MissionSchema } from "../schemas.js";
|
|
5
|
+
export async function readMission(expDir) {
|
|
6
|
+
const filePath = path.join(expDir, "mission.md");
|
|
7
|
+
let raw;
|
|
8
|
+
try {
|
|
9
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new Error(`mission.md not found in ${expDir}`);
|
|
13
|
+
}
|
|
14
|
+
// Extract YAML frontmatter between --- delimiters
|
|
15
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
16
|
+
if (!match)
|
|
17
|
+
throw new Error(`mission.md in ${expDir} has no YAML frontmatter`);
|
|
18
|
+
const parsed = yaml.load(match[1]);
|
|
19
|
+
const result = MissionSchema.safeParse(parsed);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
const issues = result.error.issues.map(i => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
22
|
+
throw new Error(`mission.md schema invalid: ${issues}`);
|
|
23
|
+
}
|
|
24
|
+
return result.data;
|
|
25
|
+
}
|
|
26
|
+
export async function writeSuggestedChange(expDir, change) {
|
|
27
|
+
const filePath = path.join(expDir, "mission.md");
|
|
28
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
29
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---(\n[\s\S]*)?$/);
|
|
30
|
+
if (!match)
|
|
31
|
+
throw new Error(`mission.md in ${expDir} has no YAML frontmatter`);
|
|
32
|
+
const frontmatter = yaml.load(match[1]);
|
|
33
|
+
frontmatter["next_change"] = change;
|
|
34
|
+
const body = match[2] ?? "";
|
|
35
|
+
const newContent = `---\n${yaml.dump(frontmatter, { lineWidth: -1 })}---${body}`;
|
|
36
|
+
await fs.writeFile(filePath, newContent, "utf8");
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/trace-ai/exp/exp-store/readme-template.ts
|
|
2
|
+
export function renderReadme(opts) {
|
|
3
|
+
return `# Experiment: ${opts.experimentId}
|
|
4
|
+
|
|
5
|
+
Created: ${opts.timestamp}
|
|
6
|
+
Goal: ${opts.goal}
|
|
7
|
+
|
|
8
|
+
## 目录说明
|
|
9
|
+
- mission.md — 实验意图(你来编辑)
|
|
10
|
+
- eval-sets/ — 评测集(来自 MVP-B 或手动预置)
|
|
11
|
+
- candidates/ — Agent 候选快照
|
|
12
|
+
- outputs/ — 最终产物(bundle / manifest / provenance)
|
|
13
|
+
- .trace-state/ — 运行态,勿手动编辑
|
|
14
|
+
|
|
15
|
+
## 常用命令
|
|
16
|
+
\`\`\`
|
|
17
|
+
kweaver trace exp run . — 启动 / 新开一轮
|
|
18
|
+
kweaver trace exp resume . — 从 Deciding 状态继续
|
|
19
|
+
kweaver trace exp show . — 查看当前状态和建议
|
|
20
|
+
kweaver trace exp status . — 一行摘要(适合脚本)
|
|
21
|
+
kweaver trace exp abort . — 优雅中止
|
|
22
|
+
kweaver trace exp doctor . — 环境自检
|
|
23
|
+
\`\`\`
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/trace-ai/exp/exp-store/round-yaml.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
function roundPath(expDir, n) {
|
|
6
|
+
return path.join(expDir, ".trace-state", "rounds", `round-${n}.yaml`);
|
|
7
|
+
}
|
|
8
|
+
export async function writeRound(expDir, n, data) {
|
|
9
|
+
const p = roundPath(expDir, n);
|
|
10
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
11
|
+
let existing = {};
|
|
12
|
+
try {
|
|
13
|
+
existing = yaml.load(await fs.readFile(p, "utf8"));
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
const merged = { ...existing, round: n, ...data };
|
|
17
|
+
await fs.writeFile(p, yaml.dump(merged, { lineWidth: -1 }), "utf8");
|
|
18
|
+
}
|
|
19
|
+
export async function readAllRounds(expDir) {
|
|
20
|
+
const roundsDir = path.join(expDir, ".trace-state", "rounds");
|
|
21
|
+
try {
|
|
22
|
+
const files = await fs.readdir(roundsDir);
|
|
23
|
+
const rounds = [];
|
|
24
|
+
for (const f of files.filter(f => f.endsWith(".yaml")).sort()) {
|
|
25
|
+
const raw = await fs.readFile(path.join(roundsDir, f), "utf8");
|
|
26
|
+
rounds.push(yaml.load(raw));
|
|
27
|
+
}
|
|
28
|
+
return rounds;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ParsedExpArgs {
|
|
2
|
+
subcommand: "run" | "resume" | "show" | "status" | "abort" | "doctor" | "list" | "info";
|
|
3
|
+
expDir: string;
|
|
4
|
+
newRun?: boolean;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseExpArgs(argv: string[]): ParsedExpArgs;
|
|
8
|
+
export declare function runExpCommand(argv: string[]): Promise<number>;
|