agent-trajectories 0.5.3 → 0.5.5
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-YI27BR5O.js → chunk-2LMOXJE7.js} +175 -27
- package/dist/chunk-2LMOXJE7.js.map +1 -0
- package/dist/cli/index.js +224 -44
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-Bw0Jk4zO.d.ts → index-DiSIfGXl.d.ts} +270 -200
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/package.json +6 -3
- package/dist/chunk-YI27BR5O.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -61,8 +61,10 @@ var TrajectoryEventTypeSchema = z.union([
|
|
|
61
61
|
z.literal("reflection"),
|
|
62
62
|
z.literal("note"),
|
|
63
63
|
z.literal("error"),
|
|
64
|
+
z.literal("completion-evidence"),
|
|
65
|
+
z.literal("completion-marker"),
|
|
64
66
|
z.string()
|
|
65
|
-
// Allow event types emitted by other tools
|
|
67
|
+
// Allow event types emitted by other tools. Downstream code filters to known types.
|
|
66
68
|
]);
|
|
67
69
|
var EventSignificanceSchema = z.enum([
|
|
68
70
|
"low",
|
|
@@ -92,7 +94,7 @@ var DecisionSchema = z.object({
|
|
|
92
94
|
});
|
|
93
95
|
var AgentParticipationSchema = z.object({
|
|
94
96
|
name: z.string().min(1, "Agent name is required"),
|
|
95
|
-
role: z.
|
|
97
|
+
role: z.string().min(1, "Agent role is required"),
|
|
96
98
|
joinedAt: z.string().datetime(),
|
|
97
99
|
leftAt: z.string().datetime().optional()
|
|
98
100
|
});
|
|
@@ -152,7 +154,7 @@ var TrajectoryTraceRefSchema = z.object({
|
|
|
152
154
|
traceId: z.string().optional()
|
|
153
155
|
});
|
|
154
156
|
var TrajectorySchema = z.object({
|
|
155
|
-
id: z.string().regex(/^traj_[a-z0-
|
|
157
|
+
id: z.string().regex(/^traj_[a-z0-9_]+$/, "Invalid trajectory ID format"),
|
|
156
158
|
version: z.literal(1),
|
|
157
159
|
task: TaskReferenceSchema,
|
|
158
160
|
status: TrajectoryStatusSchema,
|
|
@@ -161,11 +163,11 @@ var TrajectorySchema = z.object({
|
|
|
161
163
|
agents: z.array(AgentParticipationSchema),
|
|
162
164
|
chapters: z.array(ChapterSchema),
|
|
163
165
|
retrospective: RetrospectiveSchema.optional(),
|
|
164
|
-
commits: z.array(z.string()),
|
|
165
|
-
filesChanged: z.array(z.string()),
|
|
166
|
-
projectId: z.string(),
|
|
166
|
+
commits: z.array(z.string()).default([]),
|
|
167
|
+
filesChanged: z.array(z.string()).default([]),
|
|
168
|
+
projectId: z.string().optional(),
|
|
167
169
|
workflowId: z.string().optional(),
|
|
168
|
-
tags: z.array(z.string()),
|
|
170
|
+
tags: z.array(z.string()).default([]),
|
|
169
171
|
_trace: TrajectoryTraceRefSchema.optional()
|
|
170
172
|
});
|
|
171
173
|
var CreateTrajectoryInputSchema = z.object({
|
|
@@ -567,11 +569,11 @@ function extractDecisions(trajectory) {
|
|
|
567
569
|
}
|
|
568
570
|
|
|
569
571
|
// src/storage/file.ts
|
|
570
|
-
function expandPath(
|
|
571
|
-
if (
|
|
572
|
-
return join(process.env.HOME ?? "",
|
|
572
|
+
function expandPath(path2) {
|
|
573
|
+
if (path2.startsWith("~")) {
|
|
574
|
+
return join(process.env.HOME ?? "", path2.slice(1));
|
|
573
575
|
}
|
|
574
|
-
return
|
|
576
|
+
return path2;
|
|
575
577
|
}
|
|
576
578
|
function getSearchPaths() {
|
|
577
579
|
const searchPathsEnv = process.env.TRAJECTORIES_SEARCH_PATHS;
|
|
@@ -616,11 +618,129 @@ var FileStorage = class {
|
|
|
616
618
|
trajectories: {}
|
|
617
619
|
});
|
|
618
620
|
}
|
|
621
|
+
await this.reconcileIndex();
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Scan active/ and completed/ recursively and add any trajectory files
|
|
625
|
+
* missing from the index. Existing entries are preserved — reconcile
|
|
626
|
+
* only adds, never removes.
|
|
627
|
+
*
|
|
628
|
+
* Handles three on-disk layouts in completed/:
|
|
629
|
+
* - flat: completed/{id}.json (legacy workforce data)
|
|
630
|
+
* - monthly: completed/YYYY-MM/{id}.json (current save() writes)
|
|
631
|
+
* - nested: completed/.../{id}.json (defensive — any depth)
|
|
632
|
+
*
|
|
633
|
+
* Returns a ReconcileSummary so tests and CLI wrappers can observe
|
|
634
|
+
* outcomes without parsing logs. Only writes the index if anything was
|
|
635
|
+
* added.
|
|
636
|
+
*/
|
|
637
|
+
async reconcileIndex() {
|
|
638
|
+
const summary = {
|
|
639
|
+
scanned: 0,
|
|
640
|
+
added: 0,
|
|
641
|
+
alreadyIndexed: 0,
|
|
642
|
+
skippedMalformedJson: 0,
|
|
643
|
+
skippedSchemaViolation: 0,
|
|
644
|
+
skippedIoError: 0
|
|
645
|
+
};
|
|
646
|
+
const index = await this.loadIndex();
|
|
647
|
+
const before = Object.keys(index.trajectories).length;
|
|
648
|
+
const discovered = [];
|
|
649
|
+
try {
|
|
650
|
+
const activeFiles = await readdir(this.activeDir);
|
|
651
|
+
for (const file of activeFiles) {
|
|
652
|
+
if (!file.endsWith(".json")) continue;
|
|
653
|
+
discovered.push(join(this.activeDir, file));
|
|
654
|
+
}
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (error.code !== "ENOENT") throw error;
|
|
657
|
+
}
|
|
658
|
+
await this.walkJsonFilesInto(this.completedDir, discovered);
|
|
659
|
+
for (const filePath of discovered) {
|
|
660
|
+
summary.scanned += 1;
|
|
661
|
+
const result = await this.readTrajectoryFile(filePath);
|
|
662
|
+
if (!result.ok) {
|
|
663
|
+
if (result.reason === "malformed_json") {
|
|
664
|
+
summary.skippedMalformedJson += 1;
|
|
665
|
+
} else if (result.reason === "schema_violation") {
|
|
666
|
+
summary.skippedSchemaViolation += 1;
|
|
667
|
+
} else {
|
|
668
|
+
summary.skippedIoError += 1;
|
|
669
|
+
}
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const trajectory = result.trajectory;
|
|
673
|
+
if (index.trajectories[trajectory.id]) {
|
|
674
|
+
summary.alreadyIndexed += 1;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
index.trajectories[trajectory.id] = {
|
|
678
|
+
title: trajectory.task.title,
|
|
679
|
+
status: trajectory.status,
|
|
680
|
+
startedAt: trajectory.startedAt,
|
|
681
|
+
completedAt: trajectory.completedAt,
|
|
682
|
+
path: filePath
|
|
683
|
+
};
|
|
684
|
+
summary.added += 1;
|
|
685
|
+
}
|
|
686
|
+
if (Object.keys(index.trajectories).length !== before) {
|
|
687
|
+
await this.saveIndex(index);
|
|
688
|
+
}
|
|
689
|
+
const hadSkips = summary.skippedMalformedJson + summary.skippedSchemaViolation + summary.skippedIoError > 0;
|
|
690
|
+
if (summary.added > 0 || hadSkips) {
|
|
691
|
+
const parts = [`reconciled ${summary.added}/${summary.scanned}`];
|
|
692
|
+
if (summary.skippedMalformedJson > 0) {
|
|
693
|
+
parts.push(`malformed: ${summary.skippedMalformedJson}`);
|
|
694
|
+
}
|
|
695
|
+
if (summary.skippedSchemaViolation > 0) {
|
|
696
|
+
parts.push(`invalid: ${summary.skippedSchemaViolation}`);
|
|
697
|
+
}
|
|
698
|
+
if (summary.skippedIoError > 0) {
|
|
699
|
+
parts.push(`io: ${summary.skippedIoError}`);
|
|
700
|
+
}
|
|
701
|
+
console.warn(`[trajectories] ${parts.join(", ")}`);
|
|
702
|
+
}
|
|
703
|
+
return summary;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Recursively collect all .json file paths under `dir` into `out`.
|
|
707
|
+
* Silently treats a missing directory as empty.
|
|
708
|
+
*/
|
|
709
|
+
async walkJsonFilesInto(dir, out) {
|
|
710
|
+
let entries;
|
|
711
|
+
try {
|
|
712
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (error.code === "ENOENT") return;
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
for (const entry of entries) {
|
|
718
|
+
const entryPath = join(dir, entry.name);
|
|
719
|
+
if (entry.isDirectory()) {
|
|
720
|
+
await this.walkJsonFilesInto(entryPath, out);
|
|
721
|
+
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
722
|
+
out.push(entryPath);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
619
725
|
}
|
|
620
726
|
/**
|
|
621
|
-
* Save a trajectory
|
|
727
|
+
* Save a trajectory.
|
|
728
|
+
*
|
|
729
|
+
* Validates the input against the trajectory schema before touching
|
|
730
|
+
* disk. Closes the historical read/write asymmetry where save() would
|
|
731
|
+
* happily write data that the reader then rejected, producing files
|
|
732
|
+
* that could never be loaded back.
|
|
622
733
|
*/
|
|
623
|
-
async save(
|
|
734
|
+
async save(input) {
|
|
735
|
+
const validation = validateTrajectory(input);
|
|
736
|
+
if (!validation.success) {
|
|
737
|
+
const issues = validation.errors?.issues.map((issue) => {
|
|
738
|
+
const path2 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
739
|
+
return `${path2}: ${issue.message}`;
|
|
740
|
+
}).join("; ") ?? "unknown validation error";
|
|
741
|
+
throw new Error(`Cannot save invalid trajectory: ${issues}`);
|
|
742
|
+
}
|
|
743
|
+
const trajectory = validation.data;
|
|
624
744
|
const isCompleted = trajectory.status === "completed" || trajectory.status === "abandoned";
|
|
625
745
|
let filePath;
|
|
626
746
|
if (isCompleted) {
|
|
@@ -650,19 +770,23 @@ var FileStorage = class {
|
|
|
650
770
|
async get(id) {
|
|
651
771
|
const activePath = join(this.activeDir, `${id}.json`);
|
|
652
772
|
if (existsSync(activePath)) {
|
|
653
|
-
return this.
|
|
773
|
+
return this.readTrajectoryOrNull(activePath);
|
|
654
774
|
}
|
|
655
775
|
const index = await this.loadIndex();
|
|
656
776
|
const entry = index.trajectories[id];
|
|
657
777
|
if (entry?.path && existsSync(entry.path)) {
|
|
658
|
-
return this.
|
|
778
|
+
return this.readTrajectoryOrNull(entry.path);
|
|
659
779
|
}
|
|
660
780
|
try {
|
|
781
|
+
const flatPath = join(this.completedDir, `${id}.json`);
|
|
782
|
+
if (existsSync(flatPath)) {
|
|
783
|
+
return this.readTrajectoryOrNull(flatPath);
|
|
784
|
+
}
|
|
661
785
|
const months = await readdir(this.completedDir);
|
|
662
786
|
for (const month of months) {
|
|
663
787
|
const filePath = join(this.completedDir, month, `${id}.json`);
|
|
664
788
|
if (existsSync(filePath)) {
|
|
665
|
-
return this.
|
|
789
|
+
return this.readTrajectoryOrNull(filePath);
|
|
666
790
|
}
|
|
667
791
|
}
|
|
668
792
|
} catch (error) {
|
|
@@ -685,7 +809,7 @@ var FileStorage = class {
|
|
|
685
809
|
let mostRecent = null;
|
|
686
810
|
let mostRecentTime = 0;
|
|
687
811
|
for (const file of jsonFiles) {
|
|
688
|
-
const trajectory = await this.
|
|
812
|
+
const trajectory = await this.readTrajectoryOrNull(
|
|
689
813
|
join(this.activeDir, file)
|
|
690
814
|
);
|
|
691
815
|
if (trajectory) {
|
|
@@ -735,7 +859,7 @@ var FileStorage = class {
|
|
|
735
859
|
return sortOrder === "asc" ? cmp : -cmp;
|
|
736
860
|
});
|
|
737
861
|
const offset = query.offset ?? 0;
|
|
738
|
-
const limit = query.limit ??
|
|
862
|
+
const limit = query.limit ?? 500;
|
|
739
863
|
entries = entries.slice(offset, offset + limit);
|
|
740
864
|
return Promise.all(
|
|
741
865
|
entries.map(async ([id, entry]) => {
|
|
@@ -813,20 +937,44 @@ var FileStorage = class {
|
|
|
813
937
|
async close() {
|
|
814
938
|
}
|
|
815
939
|
// Private helpers
|
|
816
|
-
|
|
940
|
+
/**
|
|
941
|
+
* Read a trajectory file and return a tagged result so callers can
|
|
942
|
+
* distinguish missing files, malformed JSON, and schema violations.
|
|
943
|
+
*
|
|
944
|
+
* Does NOT log. Callers choose whether to warn, swallow, or throw.
|
|
945
|
+
*/
|
|
946
|
+
async readTrajectoryFile(path2) {
|
|
947
|
+
let content;
|
|
817
948
|
try {
|
|
818
|
-
|
|
819
|
-
const data = JSON.parse(content);
|
|
820
|
-
const validation = validateTrajectory(data);
|
|
821
|
-
if (validation.success) {
|
|
822
|
-
return validation.data;
|
|
823
|
-
}
|
|
824
|
-
console.error(`Invalid trajectory at ${path}:`, validation.errors);
|
|
825
|
-
return null;
|
|
949
|
+
content = await readFile(path2, "utf-8");
|
|
826
950
|
} catch (error) {
|
|
827
|
-
|
|
828
|
-
return null;
|
|
951
|
+
return { ok: false, reason: "io_error", path: path2, error };
|
|
829
952
|
}
|
|
953
|
+
let data;
|
|
954
|
+
try {
|
|
955
|
+
data = JSON.parse(content);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
return { ok: false, reason: "malformed_json", path: path2, error };
|
|
958
|
+
}
|
|
959
|
+
const validation = validateTrajectory(data);
|
|
960
|
+
if (validation.success) {
|
|
961
|
+
return { ok: true, trajectory: validation.data };
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
ok: false,
|
|
965
|
+
reason: "schema_violation",
|
|
966
|
+
path: path2,
|
|
967
|
+
error: validation.errors
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Convenience wrapper for callers that only care whether they got a
|
|
972
|
+
* trajectory. Returns null for any failure and writes nothing to the
|
|
973
|
+
* console — so nothing leaks into test output or the CLI spinner.
|
|
974
|
+
*/
|
|
975
|
+
async readTrajectoryOrNull(path2) {
|
|
976
|
+
const result = await this.readTrajectoryFile(path2);
|
|
977
|
+
return result.ok ? result.trajectory : null;
|
|
830
978
|
}
|
|
831
979
|
async loadIndex() {
|
|
832
980
|
try {
|
|
@@ -1662,9 +1810,9 @@ async function resolveCLIProvider() {
|
|
|
1662
1810
|
return SUPPORTED_CLIS;
|
|
1663
1811
|
})();
|
|
1664
1812
|
for (const cli of clisToTry) {
|
|
1665
|
-
const
|
|
1666
|
-
if (
|
|
1667
|
-
return new CLIProvider(cli,
|
|
1813
|
+
const path2 = await findBinary(cli);
|
|
1814
|
+
if (path2) {
|
|
1815
|
+
return new CLIProvider(cli, path2);
|
|
1668
1816
|
}
|
|
1669
1817
|
}
|
|
1670
1818
|
return null;
|
|
@@ -1672,8 +1820,8 @@ async function resolveCLIProvider() {
|
|
|
1672
1820
|
async function findBinary(name) {
|
|
1673
1821
|
try {
|
|
1674
1822
|
const { stdout } = await execFileAsync("which", [name]);
|
|
1675
|
-
const
|
|
1676
|
-
if (
|
|
1823
|
+
const path2 = stdout.trim();
|
|
1824
|
+
if (path2) return path2;
|
|
1677
1825
|
} catch {
|
|
1678
1826
|
}
|
|
1679
1827
|
const home = homedir();
|
|
@@ -2775,8 +2923,8 @@ function generateTrace(trajectory, startRef) {
|
|
|
2775
2923
|
return null;
|
|
2776
2924
|
}
|
|
2777
2925
|
const model = detectModel();
|
|
2778
|
-
const traceFiles = changedFiles.map(({ path, ranges }) => ({
|
|
2779
|
-
path,
|
|
2926
|
+
const traceFiles = changedFiles.map(({ path: path2, ranges }) => ({
|
|
2927
|
+
path: path2,
|
|
2780
2928
|
conversations: [
|
|
2781
2929
|
{
|
|
2782
2930
|
contributor: {
|
|
@@ -2923,8 +3071,11 @@ if [ -z "$ACTIVE_FILE" ]; then
|
|
|
2923
3071
|
exit 0
|
|
2924
3072
|
fi
|
|
2925
3073
|
|
|
2926
|
-
# Extract trajectory ID (grep for the "id" field)
|
|
2927
|
-
|
|
3074
|
+
# Extract trajectory ID (grep for the "id" field). Character class must
|
|
3075
|
+
# include underscore to match legacy traj_<timestamp>_<hex> ids -- without
|
|
3076
|
+
# it, grep -o silently truncates at the first internal underscore and
|
|
3077
|
+
# emits a wrong (shorter) id into the commit trailer.
|
|
3078
|
+
TRAJ_ID=$(grep -o '"id"[[:space:]]*:[[:space:]]*"traj_[a-z0-9_]*"' "$ACTIVE_FILE" | head -1 | grep -o 'traj_[a-z0-9_]*')
|
|
2928
3079
|
if [ -z "$TRAJ_ID" ]; then
|
|
2929
3080
|
exit 0
|
|
2930
3081
|
fi
|
|
@@ -3882,19 +4033,19 @@ function registerExportCommand(program2) {
|
|
|
3882
4033
|
}
|
|
3883
4034
|
});
|
|
3884
4035
|
}
|
|
3885
|
-
function openInBrowser(
|
|
4036
|
+
function openInBrowser(path2) {
|
|
3886
4037
|
const platform = process.platform;
|
|
3887
4038
|
let command;
|
|
3888
4039
|
if (platform === "darwin") {
|
|
3889
|
-
command = `open "${
|
|
4040
|
+
command = `open "${path2}"`;
|
|
3890
4041
|
} else if (platform === "win32") {
|
|
3891
|
-
command = `start "" "${
|
|
4042
|
+
command = `start "" "${path2}"`;
|
|
3892
4043
|
} else {
|
|
3893
|
-
command = `xdg-open "${
|
|
4044
|
+
command = `xdg-open "${path2}"`;
|
|
3894
4045
|
}
|
|
3895
4046
|
exec(command, (error) => {
|
|
3896
4047
|
if (error) {
|
|
3897
|
-
console.log(`Open manually: file://${
|
|
4048
|
+
console.log(`Open manually: file://${path2}`);
|
|
3898
4049
|
}
|
|
3899
4050
|
});
|
|
3900
4051
|
}
|
|
@@ -4399,8 +4550,37 @@ function registerCommands(program2) {
|
|
|
4399
4550
|
registerCompactCommand(program2);
|
|
4400
4551
|
}
|
|
4401
4552
|
|
|
4553
|
+
// src/cli/version.ts
|
|
4554
|
+
import fs from "fs";
|
|
4555
|
+
import path from "path";
|
|
4556
|
+
import { fileURLToPath } from "url";
|
|
4557
|
+
function findPackageJson(startDir) {
|
|
4558
|
+
let dir = startDir;
|
|
4559
|
+
while (dir !== path.dirname(dir)) {
|
|
4560
|
+
const candidate = path.join(dir, "package.json");
|
|
4561
|
+
if (fs.existsSync(candidate)) {
|
|
4562
|
+
return candidate;
|
|
4563
|
+
}
|
|
4564
|
+
dir = path.dirname(dir);
|
|
4565
|
+
}
|
|
4566
|
+
throw new Error("Could not find package.json");
|
|
4567
|
+
}
|
|
4568
|
+
function resolveCliVersion() {
|
|
4569
|
+
try {
|
|
4570
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
4571
|
+
const packageJsonPath = findPackageJson(here);
|
|
4572
|
+
const packageJson = JSON.parse(
|
|
4573
|
+
fs.readFileSync(packageJsonPath, "utf-8")
|
|
4574
|
+
);
|
|
4575
|
+
return packageJson.version ?? "unknown";
|
|
4576
|
+
} catch {
|
|
4577
|
+
return "unknown";
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
var VERSION = resolveCliVersion();
|
|
4581
|
+
|
|
4402
4582
|
// src/cli/index.ts
|
|
4403
|
-
program.name("trail").description("Leave a trail of your work for others to follow").version(
|
|
4583
|
+
program.name("trail").description("Leave a trail of your work for others to follow").version(VERSION).option(
|
|
4404
4584
|
"--data-dir <path>",
|
|
4405
4585
|
"Override trajectory storage directory (or set TRAJECTORIES_DATA_DIR)"
|
|
4406
4586
|
).hook("preAction", (thisCommand) => {
|