agent-trajectories 0.5.9 → 0.6.1
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 +16 -8
- package/dist/{chunk-JMH3Z5BB.js → chunk-BVTUGGYW.js} +228 -54
- package/dist/chunk-BVTUGGYW.js.map +1 -0
- package/dist/cli/index.js +188 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-B4yIThRL.d.ts → index-RDYnMyn2.d.ts} +10 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/package.json +3 -1
- package/dist/chunk-JMH3Z5BB.js.map +0 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ A **trajectory** is the complete story of agent work on a task:
|
|
|
21
21
|
Works with any task system: Beads, Linear, Jira, GitHub Issues, or standalone. Trajectories are a universal format—like Markdown for documentation.
|
|
22
22
|
|
|
23
23
|
### Multiple Storage Backends
|
|
24
|
-
- **File system** (default) - `.trajectories/` directory, git-friendly
|
|
24
|
+
- **File system** (default) - `.agentworkforce/trajectories/` directory, git-friendly
|
|
25
25
|
- **SQLite** - Local indexing and search
|
|
26
26
|
- **PostgreSQL/S3** - For teams and archival
|
|
27
27
|
|
|
@@ -95,17 +95,25 @@ Over time, trajectories become a searchable knowledge base:
|
|
|
95
95
|
### CLI
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
#
|
|
98
|
+
# Run without installing globally
|
|
99
|
+
npx --yes agent-trajectories start "Implement auth module"
|
|
100
|
+
|
|
101
|
+
# Or install globally if you prefer the short trail command
|
|
99
102
|
npm install -g agent-trajectories
|
|
103
|
+
trail start "Implement auth module"
|
|
100
104
|
|
|
101
|
-
# Or install locally
|
|
105
|
+
# Or install locally in a project
|
|
102
106
|
npm install agent-trajectories
|
|
107
|
+
npx --no-install trail start "Implement auth module"
|
|
108
|
+
# or
|
|
109
|
+
npm exec -- trail start "Implement auth module"
|
|
103
110
|
```
|
|
104
111
|
|
|
105
112
|
```bash
|
|
106
113
|
# Start tracking a task
|
|
107
114
|
trail start "Implement auth module"
|
|
108
|
-
# (
|
|
115
|
+
# (for non-global installs, replace `trail` with
|
|
116
|
+
# `npx --yes agent-trajectories`, `npx --no-install trail`, or `npm exec -- trail`)
|
|
109
117
|
|
|
110
118
|
# View current status
|
|
111
119
|
trail status
|
|
@@ -145,7 +153,7 @@ Use `--discard-sources` when the compacted summary should replace the raw source
|
|
|
145
153
|
- name: Compact trajectories
|
|
146
154
|
run: |
|
|
147
155
|
PR_COMMITS=$(git log ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} --format=%H | paste -sd, -)
|
|
148
|
-
OUTPUT=".trajectories/compacted/pr-${{ github.event.pull_request.number }}.json"
|
|
156
|
+
OUTPUT=".agentworkforce/trajectories/compacted/pr-${{ github.event.pull_request.number }}.json"
|
|
149
157
|
if [ -n "$PR_COMMITS" ]; then
|
|
150
158
|
npx agent-trajectories compact --commits "$PR_COMMITS" --output "$OUTPUT" --discard-sources
|
|
151
159
|
else
|
|
@@ -153,7 +161,7 @@ Use `--discard-sources` when the compacted summary should replace the raw source
|
|
|
153
161
|
fi
|
|
154
162
|
- name: Commit compacted trajectories
|
|
155
163
|
run: |
|
|
156
|
-
git add .trajectories/ || true
|
|
164
|
+
git add .agentworkforce/trajectories/ || true
|
|
157
165
|
git diff --cached --quiet || \
|
|
158
166
|
(git commit -m "chore: compact trajectories for PR #${{ github.event.pull_request.number }}" && git push)
|
|
159
167
|
```
|
|
@@ -463,7 +471,7 @@ The client manages trajectories with persistent storage.
|
|
|
463
471
|
```typescript
|
|
464
472
|
const client = new TrajectoryClient({
|
|
465
473
|
defaultAgent: 'my-agent', // Default agent name
|
|
466
|
-
dataDir: '.
|
|
474
|
+
dataDir: '.', // Base directory; stores under .agentworkforce/trajectories
|
|
467
475
|
autoSave: true, // Auto-save after operations
|
|
468
476
|
});
|
|
469
477
|
|
|
@@ -542,7 +550,7 @@ const t = TrajectoryBuilder.create('Task')
|
|
|
542
550
|
This project is in early development. See [PROPOSAL-trajectories.md](./PROPOSAL-trajectories.md) for the full design document.
|
|
543
551
|
|
|
544
552
|
**v1.0 (current)**
|
|
545
|
-
- [x] File-based storage (`.trajectories/`)
|
|
553
|
+
- [x] File-based storage (`.agentworkforce/trajectories/`)
|
|
546
554
|
- [x] Core CLI commands (`start`, `decision`, `complete`, `list`, `show`, `export`)
|
|
547
555
|
- [x] Agent Trace spec compliance (`.trace.json` generation)
|
|
548
556
|
- [x] Multi-agent participation tracking
|
|
@@ -1,8 +1,157 @@
|
|
|
1
1
|
// src/sdk/client.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
|
-
import { existsSync as
|
|
3
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname3, resolve as resolvePath } from "path";
|
|
6
|
+
|
|
7
|
+
// src/core/project-id.ts
|
|
8
|
+
import { execFileSync } from "child_process";
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
import { dirname, join, resolve } from "path";
|
|
11
|
+
function resolveProjectId(explicitProjectId, options = {}) {
|
|
12
|
+
return readString(explicitProjectId) ?? readString((options.env ?? process.env).TRAJECTORIES_PROJECT) ?? resolveDefaultProjectId(options.cwd);
|
|
13
|
+
}
|
|
14
|
+
function resolveDefaultProjectId(cwd = process.cwd()) {
|
|
15
|
+
const packageJson = readNearestPackageJson(cwd);
|
|
16
|
+
return resolvePackageRepositoryId(packageJson) ?? resolveGitRemoteProjectId(cwd) ?? resolvePackageName(packageJson);
|
|
17
|
+
}
|
|
18
|
+
function normalizeRepositoryId(value) {
|
|
19
|
+
const raw = readString(value);
|
|
20
|
+
if (!raw) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
const withoutGitPrefix = raw.replace(/^git\+/, "");
|
|
24
|
+
const shorthand = withoutGitPrefix.match(
|
|
25
|
+
/^(?:github|gitlab|bitbucket):([^/]+\/[^/]+(?:\/[^/]+)*)$/
|
|
26
|
+
);
|
|
27
|
+
if (shorthand) {
|
|
28
|
+
return cleanRepositoryPath(shorthand[1]);
|
|
29
|
+
}
|
|
30
|
+
const ownerRepo = withoutGitPrefix.match(
|
|
31
|
+
/^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/
|
|
32
|
+
);
|
|
33
|
+
if (ownerRepo) {
|
|
34
|
+
return cleanRepositoryPath(ownerRepo[1]);
|
|
35
|
+
}
|
|
36
|
+
const sshUrlScpLike = withoutGitPrefix.match(
|
|
37
|
+
/^ssh:\/\/[^@/\s]+@[^:/\s]+:(.+)$/i
|
|
38
|
+
);
|
|
39
|
+
if (sshUrlScpLike) {
|
|
40
|
+
return cleanRepositoryPath(sshUrlScpLike[1]);
|
|
41
|
+
}
|
|
42
|
+
const scpLike = withoutGitPrefix.match(/^[^@/\s]+@[^:/\s]+:(.+)$/);
|
|
43
|
+
if (scpLike) {
|
|
44
|
+
return cleanRepositoryPath(scpLike[1]);
|
|
45
|
+
}
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = new URL(withoutGitPrefix);
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(parsed.protocol)) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
return cleanRepositoryPath(parsed.pathname);
|
|
56
|
+
}
|
|
57
|
+
function resolvePackageRepositoryId(packageJson) {
|
|
58
|
+
if (!packageJson) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
const repository = packageJson.repository;
|
|
62
|
+
if (typeof repository === "string") {
|
|
63
|
+
return normalizeRepositoryId(repository);
|
|
64
|
+
}
|
|
65
|
+
if (isRepositoryObject(repository) && typeof repository.url === "string") {
|
|
66
|
+
const projectId = normalizeRepositoryId(repository.url);
|
|
67
|
+
const directory = cleanRepositoryDirectory(repository.directory);
|
|
68
|
+
return directory && projectId ? `${projectId}//${directory}` : projectId;
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
function resolvePackageName(packageJson) {
|
|
73
|
+
return readString(packageJson?.name);
|
|
74
|
+
}
|
|
75
|
+
function resolveGitRemoteProjectId(cwd) {
|
|
76
|
+
for (const remote of ["upstream", "origin"]) {
|
|
77
|
+
const remoteUrl = getGitRemoteUrl(cwd, remote);
|
|
78
|
+
if (!remoteUrl) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const projectId = normalizeRepositoryId(remoteUrl);
|
|
82
|
+
if (projectId) {
|
|
83
|
+
return projectId;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
function getGitRemoteUrl(cwd, remote) {
|
|
89
|
+
try {
|
|
90
|
+
return readString(
|
|
91
|
+
execFileSync("git", ["config", "--get", `remote.${remote}.url`], {
|
|
92
|
+
cwd,
|
|
93
|
+
encoding: "utf-8",
|
|
94
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
} catch {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function readNearestPackageJson(cwd) {
|
|
102
|
+
let current = resolve(cwd);
|
|
103
|
+
while (true) {
|
|
104
|
+
const candidate = join(current, "package.json");
|
|
105
|
+
if (existsSync(candidate)) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
108
|
+
if (isPackageJson(parsed)) {
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const parent = dirname(current);
|
|
115
|
+
if (parent === current) {
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
current = parent;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function cleanRepositoryPath(path) {
|
|
122
|
+
const withoutQuery = path.split(/[?#]/, 1)[0] ?? "";
|
|
123
|
+
const withoutSlashes = withoutQuery.replace(/^\/+|\/+$/g, "");
|
|
124
|
+
const withoutGitSuffix = withoutSlashes.replace(/\.git$/i, "");
|
|
125
|
+
const parts = withoutGitSuffix.split("/").map((part) => part.trim()).filter(Boolean);
|
|
126
|
+
if (parts.length < 2) {
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
return parts.join("/");
|
|
130
|
+
}
|
|
131
|
+
function cleanRepositoryDirectory(directory) {
|
|
132
|
+
const raw = readString(directory);
|
|
133
|
+
if (!raw || raw.startsWith("/") || raw.startsWith("\\") || /^[A-Za-z]:[\\/]/.test(raw)) {
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
const parts = raw.split(/[\\/]/).map((part) => part.trim()).filter(Boolean);
|
|
137
|
+
if (parts.length === 0 || parts.some((part) => part === "." || part === "..")) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
return parts.join("/");
|
|
141
|
+
}
|
|
142
|
+
function readString(value) {
|
|
143
|
+
if (typeof value !== "string") {
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
const trimmed = value.trim();
|
|
147
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
148
|
+
}
|
|
149
|
+
function isPackageJson(value) {
|
|
150
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
151
|
+
}
|
|
152
|
+
function isRepositoryObject(value) {
|
|
153
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
154
|
+
}
|
|
6
155
|
|
|
7
156
|
// src/core/id.ts
|
|
8
157
|
import { webcrypto } from "crypto";
|
|
@@ -269,7 +418,7 @@ function createTrajectory(input) {
|
|
|
269
418
|
chapters: [],
|
|
270
419
|
commits: [],
|
|
271
420
|
filesChanged: [],
|
|
272
|
-
projectId: input.projectId
|
|
421
|
+
projectId: input.projectId,
|
|
273
422
|
tags: input.tags ?? []
|
|
274
423
|
};
|
|
275
424
|
}
|
|
@@ -666,7 +815,7 @@ function formatTime(isoString) {
|
|
|
666
815
|
}
|
|
667
816
|
|
|
668
817
|
// src/storage/file.ts
|
|
669
|
-
import { existsSync } from "fs";
|
|
818
|
+
import { existsSync as existsSync2 } from "fs";
|
|
670
819
|
import {
|
|
671
820
|
mkdir,
|
|
672
821
|
readFile,
|
|
@@ -677,19 +826,27 @@ import {
|
|
|
677
826
|
} from "fs/promises";
|
|
678
827
|
import {
|
|
679
828
|
basename,
|
|
680
|
-
dirname,
|
|
829
|
+
dirname as dirname2,
|
|
681
830
|
isAbsolute,
|
|
682
|
-
join,
|
|
831
|
+
join as join2,
|
|
683
832
|
relative,
|
|
684
|
-
resolve
|
|
833
|
+
resolve as resolve2
|
|
685
834
|
} from "path";
|
|
686
835
|
var TRAJECTORY_FILE = "trajectory.json";
|
|
687
836
|
var SUMMARY_FILE = "summary.md";
|
|
688
837
|
var COMPACTION_FILE = "compaction.json";
|
|
689
838
|
var LEGACY_COMPACTION_SUFFIX = ".compaction.json";
|
|
839
|
+
var DEFAULT_TRAJECTORY_DATA_DIR = join2(
|
|
840
|
+
".agentworkforce",
|
|
841
|
+
"trajectories"
|
|
842
|
+
);
|
|
843
|
+
var LEGACY_TRAJECTORY_DATA_DIR = ".trajectories";
|
|
844
|
+
function getDefaultTrajectoryDataDir(baseDir = process.cwd()) {
|
|
845
|
+
return join2(baseDir, DEFAULT_TRAJECTORY_DATA_DIR);
|
|
846
|
+
}
|
|
690
847
|
function expandPath(path) {
|
|
691
848
|
if (path.startsWith("~")) {
|
|
692
|
-
return
|
|
849
|
+
return join2(process.env.HOME ?? "", path.slice(1));
|
|
693
850
|
}
|
|
694
851
|
return path;
|
|
695
852
|
}
|
|
@@ -713,28 +870,42 @@ var FileStorage = class {
|
|
|
713
870
|
activeDir;
|
|
714
871
|
completedDir;
|
|
715
872
|
lastReconcileSummary;
|
|
873
|
+
shouldMigrateLegacyDefault = false;
|
|
716
874
|
constructor(baseDir) {
|
|
717
875
|
this.baseDir = baseDir ?? process.cwd();
|
|
718
876
|
const dataDir = process.env.TRAJECTORIES_DATA_DIR;
|
|
719
877
|
if (dataDir) {
|
|
720
878
|
this.trajectoriesDir = expandPath(dataDir);
|
|
721
879
|
} else {
|
|
722
|
-
this.trajectoriesDir =
|
|
880
|
+
this.trajectoriesDir = getDefaultTrajectoryDataDir(this.baseDir);
|
|
881
|
+
this.shouldMigrateLegacyDefault = true;
|
|
723
882
|
}
|
|
724
|
-
this.activeDir =
|
|
725
|
-
this.completedDir =
|
|
883
|
+
this.activeDir = join2(this.trajectoriesDir, "active");
|
|
884
|
+
this.completedDir = join2(this.trajectoriesDir, "completed");
|
|
726
885
|
}
|
|
727
886
|
/**
|
|
728
887
|
* Initialize storage directories
|
|
729
888
|
*/
|
|
730
889
|
async initialize() {
|
|
890
|
+
await this.migrateLegacyDefaultDir();
|
|
731
891
|
await mkdir(this.trajectoriesDir, { recursive: true });
|
|
732
892
|
await mkdir(this.activeDir, { recursive: true });
|
|
733
893
|
await mkdir(this.completedDir, { recursive: true });
|
|
734
894
|
await this.migrateLegacyIndexCompactionMarkers();
|
|
735
|
-
await rm(
|
|
895
|
+
await rm(join2(this.trajectoriesDir, "index.json"), { force: true });
|
|
736
896
|
await this.reconcileIndex();
|
|
737
897
|
}
|
|
898
|
+
async migrateLegacyDefaultDir() {
|
|
899
|
+
if (!this.shouldMigrateLegacyDefault) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const legacyDir = join2(this.baseDir, LEGACY_TRAJECTORY_DATA_DIR);
|
|
903
|
+
if (!existsSync2(legacyDir) || existsSync2(this.trajectoriesDir)) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
await mkdir(dirname2(this.trajectoriesDir), { recursive: true });
|
|
907
|
+
await rename(legacyDir, this.trajectoriesDir);
|
|
908
|
+
}
|
|
738
909
|
/**
|
|
739
910
|
* Scan active/ and completed/ recursively and report trajectory files
|
|
740
911
|
* that can be loaded plus files that should be surfaced by doctor.
|
|
@@ -805,7 +976,7 @@ var FileStorage = class {
|
|
|
805
976
|
return this.lastReconcileSummary;
|
|
806
977
|
}
|
|
807
978
|
/**
|
|
808
|
-
* Move trajectory files that fail to load into `.trajectories/invalid/`
|
|
979
|
+
* Move trajectory files that fail to load into `.agentworkforce/trajectories/invalid/`
|
|
809
980
|
* so reconcile no longer scans them. Only quarantines parse and schema
|
|
810
981
|
* failures — transient io_error failures are left in place because the
|
|
811
982
|
* file may load fine on the next attempt.
|
|
@@ -815,7 +986,7 @@ var FileStorage = class {
|
|
|
815
986
|
*/
|
|
816
987
|
async quarantineInvalid() {
|
|
817
988
|
const summary = await this.reconcileIndex();
|
|
818
|
-
const targetDir =
|
|
989
|
+
const targetDir = join2(this.trajectoriesDir, "invalid");
|
|
819
990
|
const candidates = summary.failures.filter((f) => f.reason !== "io_error");
|
|
820
991
|
if (candidates.length === 0) {
|
|
821
992
|
return { moved: [], targetDir };
|
|
@@ -825,7 +996,7 @@ var FileStorage = class {
|
|
|
825
996
|
for (const failure of candidates) {
|
|
826
997
|
const dest = await this.resolveQuarantineDest(failure.path, targetDir);
|
|
827
998
|
try {
|
|
828
|
-
await mkdir(
|
|
999
|
+
await mkdir(dirname2(dest), { recursive: true });
|
|
829
1000
|
await rename(failure.path, dest);
|
|
830
1001
|
moved.push(failure);
|
|
831
1002
|
} catch (error) {
|
|
@@ -851,13 +1022,13 @@ var FileStorage = class {
|
|
|
851
1022
|
async resolveQuarantineDest(sourcePath, targetDir) {
|
|
852
1023
|
const rel = relative(this.trajectoriesDir, sourcePath);
|
|
853
1024
|
const safeRel = rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : basename(sourcePath);
|
|
854
|
-
let dest =
|
|
855
|
-
if (!
|
|
1025
|
+
let dest = join2(targetDir, safeRel);
|
|
1026
|
+
if (!existsSync2(dest)) return dest;
|
|
856
1027
|
const ext = safeRel.endsWith(".json") ? ".json" : "";
|
|
857
1028
|
const stem = ext ? safeRel.slice(0, -ext.length) : safeRel;
|
|
858
1029
|
for (let i = 1; i < 1e3; i += 1) {
|
|
859
|
-
dest =
|
|
860
|
-
if (!
|
|
1030
|
+
dest = join2(targetDir, `${stem}.${i}${ext}`);
|
|
1031
|
+
if (!existsSync2(dest)) return dest;
|
|
861
1032
|
}
|
|
862
1033
|
return dest;
|
|
863
1034
|
}
|
|
@@ -874,7 +1045,7 @@ var FileStorage = class {
|
|
|
874
1045
|
throw error;
|
|
875
1046
|
}
|
|
876
1047
|
for (const entry of entries) {
|
|
877
|
-
const entryPath =
|
|
1048
|
+
const entryPath = join2(dir, entry.name);
|
|
878
1049
|
if (entry.isDirectory()) {
|
|
879
1050
|
await this.walkJsonFilesInto(entryPath, out);
|
|
880
1051
|
} else if (entry.isFile() && isTrajectoryJsonFile(entry.name)) {
|
|
@@ -905,20 +1076,20 @@ var FileStorage = class {
|
|
|
905
1076
|
let trajectoryDir;
|
|
906
1077
|
if (isCompleted) {
|
|
907
1078
|
const date = new Date(trajectory2.completedAt ?? trajectory2.startedAt);
|
|
908
|
-
const monthDir =
|
|
1079
|
+
const monthDir = join2(
|
|
909
1080
|
this.completedDir,
|
|
910
1081
|
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`
|
|
911
1082
|
);
|
|
912
|
-
trajectoryDir =
|
|
1083
|
+
trajectoryDir = join2(monthDir, trajectory2.id);
|
|
913
1084
|
} else {
|
|
914
|
-
trajectoryDir =
|
|
1085
|
+
trajectoryDir = join2(this.activeDir, trajectory2.id);
|
|
915
1086
|
}
|
|
916
|
-
const filePath =
|
|
1087
|
+
const filePath = join2(trajectoryDir, TRAJECTORY_FILE);
|
|
917
1088
|
await this.removeTrajectoryFiles(existingPaths, filePath);
|
|
918
1089
|
await mkdir(trajectoryDir, { recursive: true });
|
|
919
1090
|
if (isCompleted) {
|
|
920
1091
|
const markdown = exportToMarkdown(trajectory2);
|
|
921
|
-
await writeFile(
|
|
1092
|
+
await writeFile(join2(trajectoryDir, SUMMARY_FILE), markdown, "utf-8");
|
|
922
1093
|
}
|
|
923
1094
|
await writeFile(filePath, JSON.stringify(trajectory2, null, 2), "utf-8");
|
|
924
1095
|
}
|
|
@@ -927,7 +1098,7 @@ var FileStorage = class {
|
|
|
927
1098
|
*/
|
|
928
1099
|
async get(id) {
|
|
929
1100
|
for (const filePath of this.getActiveCandidatePaths(id)) {
|
|
930
|
-
if (!
|
|
1101
|
+
if (!existsSync2(filePath)) continue;
|
|
931
1102
|
const trajectory2 = await this.readTrajectoryOrNull(filePath);
|
|
932
1103
|
if (trajectory2?.id === id) {
|
|
933
1104
|
return trajectory2;
|
|
@@ -1132,9 +1303,9 @@ var FileStorage = class {
|
|
|
1132
1303
|
return [];
|
|
1133
1304
|
}
|
|
1134
1305
|
return [
|
|
1135
|
-
|
|
1306
|
+
join2(this.activeDir, id, TRAJECTORY_FILE),
|
|
1136
1307
|
// Legacy layout from v0.5.x and earlier.
|
|
1137
|
-
|
|
1308
|
+
join2(this.activeDir, `${id}.json`)
|
|
1138
1309
|
];
|
|
1139
1310
|
}
|
|
1140
1311
|
async loadAllTrajectories() {
|
|
@@ -1188,7 +1359,7 @@ var FileStorage = class {
|
|
|
1188
1359
|
}
|
|
1189
1360
|
getTrajectoryIdFromPath(filePath) {
|
|
1190
1361
|
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1191
|
-
const id = basename(
|
|
1362
|
+
const id = basename(dirname2(filePath));
|
|
1192
1363
|
return isSafeTrajectoryId(id) ? id : void 0;
|
|
1193
1364
|
}
|
|
1194
1365
|
const name = basename(filePath);
|
|
@@ -1209,10 +1380,10 @@ var FileStorage = class {
|
|
|
1209
1380
|
}
|
|
1210
1381
|
async removeTrajectoryFile(filePath, summary) {
|
|
1211
1382
|
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1212
|
-
const trajectoryDir =
|
|
1383
|
+
const trajectoryDir = dirname2(filePath);
|
|
1213
1384
|
await this.countDirectoryTrajectoryFiles(trajectoryDir, summary);
|
|
1214
1385
|
await this.removeFileIfExists(
|
|
1215
|
-
|
|
1386
|
+
join2(dirname2(trajectoryDir), `${basename(trajectoryDir)}.trace.json`),
|
|
1216
1387
|
"trace",
|
|
1217
1388
|
summary
|
|
1218
1389
|
);
|
|
@@ -1238,40 +1409,40 @@ var FileStorage = class {
|
|
|
1238
1409
|
}
|
|
1239
1410
|
async countDirectoryTrajectoryFiles(trajectoryDir, summary) {
|
|
1240
1411
|
await this.countFileIfExists(
|
|
1241
|
-
|
|
1412
|
+
join2(trajectoryDir, TRAJECTORY_FILE),
|
|
1242
1413
|
"json",
|
|
1243
1414
|
summary
|
|
1244
1415
|
);
|
|
1245
1416
|
await this.countFileIfExists(
|
|
1246
|
-
|
|
1417
|
+
join2(trajectoryDir, SUMMARY_FILE),
|
|
1247
1418
|
"markdown",
|
|
1248
1419
|
summary
|
|
1249
1420
|
);
|
|
1250
1421
|
await this.countFileIfExists(
|
|
1251
|
-
|
|
1422
|
+
join2(trajectoryDir, `${basename(trajectoryDir)}.trace.json`),
|
|
1252
1423
|
"trace",
|
|
1253
1424
|
summary
|
|
1254
1425
|
);
|
|
1255
1426
|
await this.countFileIfExists(
|
|
1256
|
-
|
|
1427
|
+
join2(trajectoryDir, "trace.json"),
|
|
1257
1428
|
"trace",
|
|
1258
1429
|
summary
|
|
1259
1430
|
);
|
|
1260
1431
|
await this.countFileIfExists(
|
|
1261
|
-
|
|
1432
|
+
join2(trajectoryDir, COMPACTION_FILE),
|
|
1262
1433
|
"compaction",
|
|
1263
1434
|
summary
|
|
1264
1435
|
);
|
|
1265
1436
|
}
|
|
1266
1437
|
async removeFileIfExists(path, kind, summary) {
|
|
1267
|
-
if (!
|
|
1438
|
+
if (!existsSync2(path)) {
|
|
1268
1439
|
return;
|
|
1269
1440
|
}
|
|
1270
1441
|
await rm(path, { force: true });
|
|
1271
1442
|
this.incrementDeleteSummary(kind, summary);
|
|
1272
1443
|
}
|
|
1273
1444
|
async countFileIfExists(path, kind, summary) {
|
|
1274
|
-
if (
|
|
1445
|
+
if (existsSync2(path)) {
|
|
1275
1446
|
this.incrementDeleteSummary(kind, summary);
|
|
1276
1447
|
}
|
|
1277
1448
|
}
|
|
@@ -1312,21 +1483,21 @@ var FileStorage = class {
|
|
|
1312
1483
|
}
|
|
1313
1484
|
getCompactionMarkerPath(filePath, id) {
|
|
1314
1485
|
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1315
|
-
return
|
|
1486
|
+
return join2(dirname2(filePath), COMPACTION_FILE);
|
|
1316
1487
|
}
|
|
1317
|
-
return
|
|
1488
|
+
return join2(dirname2(filePath), `${id}${LEGACY_COMPACTION_SUFFIX}`);
|
|
1318
1489
|
}
|
|
1319
1490
|
getTrajectoryIdFromCompactionMarkerPath(markerPath) {
|
|
1320
1491
|
if (basename(markerPath) === COMPACTION_FILE) {
|
|
1321
|
-
const id = basename(
|
|
1492
|
+
const id = basename(dirname2(markerPath));
|
|
1322
1493
|
return id.startsWith("traj_") ? id : void 0;
|
|
1323
1494
|
}
|
|
1324
1495
|
const markerName = basename(markerPath);
|
|
1325
1496
|
return markerName.endsWith(LEGACY_COMPACTION_SUFFIX) ? markerName.slice(0, -LEGACY_COMPACTION_SUFFIX.length) : void 0;
|
|
1326
1497
|
}
|
|
1327
1498
|
async migrateLegacyIndexCompactionMarkers() {
|
|
1328
|
-
const indexPath =
|
|
1329
|
-
if (!
|
|
1499
|
+
const indexPath = join2(this.trajectoriesDir, "index.json");
|
|
1500
|
+
if (!existsSync2(indexPath)) {
|
|
1330
1501
|
return;
|
|
1331
1502
|
}
|
|
1332
1503
|
let parsed;
|
|
@@ -1352,7 +1523,7 @@ var FileStorage = class {
|
|
|
1352
1523
|
if (typeof compactedInto !== "string") {
|
|
1353
1524
|
return;
|
|
1354
1525
|
}
|
|
1355
|
-
const paths = typeof path === "string" &&
|
|
1526
|
+
const paths = typeof path === "string" && existsSync2(path) && this.isPathInsideTrajectoriesDir(path) ? [path] : await this.findTrajectoryFilePaths(id);
|
|
1356
1527
|
if (paths.length === 0) return;
|
|
1357
1528
|
const marker = {
|
|
1358
1529
|
trajectoryId: id,
|
|
@@ -1372,7 +1543,7 @@ var FileStorage = class {
|
|
|
1372
1543
|
);
|
|
1373
1544
|
}
|
|
1374
1545
|
isPathInsideTrajectoriesDir(path) {
|
|
1375
|
-
const rel = relative(
|
|
1546
|
+
const rel = relative(resolve2(this.trajectoriesDir), resolve2(path));
|
|
1376
1547
|
return Boolean(rel && !rel.startsWith("..") && !isAbsolute(rel));
|
|
1377
1548
|
}
|
|
1378
1549
|
async walkFilesInto(dir, out, predicate) {
|
|
@@ -1384,7 +1555,7 @@ var FileStorage = class {
|
|
|
1384
1555
|
throw error;
|
|
1385
1556
|
}
|
|
1386
1557
|
for (const entry of entries) {
|
|
1387
|
-
const entryPath =
|
|
1558
|
+
const entryPath = join2(dir, entry.name);
|
|
1388
1559
|
if (entry.isDirectory()) {
|
|
1389
1560
|
await this.walkFilesInto(entryPath, out, predicate);
|
|
1390
1561
|
} else if (entry.isFile() && predicate(entry.name)) {
|
|
@@ -1517,12 +1688,12 @@ function resolveTrajectoryCliInvocation() {
|
|
|
1517
1688
|
try {
|
|
1518
1689
|
const packageJsonPath = require2.resolve("agent-trajectories/package.json");
|
|
1519
1690
|
const pkg = JSON.parse(
|
|
1520
|
-
|
|
1691
|
+
readFileSync2(packageJsonPath, "utf-8")
|
|
1521
1692
|
);
|
|
1522
1693
|
const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.trail ?? (pkg.name ? pkg.bin?.[pkg.name] : void 0);
|
|
1523
1694
|
if (binEntry) {
|
|
1524
|
-
const cliPath = resolvePath(
|
|
1525
|
-
if (
|
|
1695
|
+
const cliPath = resolvePath(dirname3(packageJsonPath), binEntry);
|
|
1696
|
+
if (existsSync3(cliPath)) {
|
|
1526
1697
|
return { command: process.execPath, args: [cliPath] };
|
|
1527
1698
|
}
|
|
1528
1699
|
}
|
|
@@ -1567,7 +1738,7 @@ async function compactWorkflow(workflowId, options) {
|
|
|
1567
1738
|
if (options?.discardSources) {
|
|
1568
1739
|
args.push("--discard-sources");
|
|
1569
1740
|
}
|
|
1570
|
-
return new Promise((
|
|
1741
|
+
return new Promise((resolve3, reject) => {
|
|
1571
1742
|
const child = spawn(cli.command, args, {
|
|
1572
1743
|
cwd: options?.cwd,
|
|
1573
1744
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1595,7 +1766,7 @@ async function compactWorkflow(workflowId, options) {
|
|
|
1595
1766
|
}
|
|
1596
1767
|
try {
|
|
1597
1768
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
1598
|
-
|
|
1769
|
+
resolve3(parseCompactWorkflowOutput(stdout));
|
|
1599
1770
|
} catch (error) {
|
|
1600
1771
|
reject(
|
|
1601
1772
|
error instanceof Error ? error : new Error("compactWorkflow failed: unable to parse CLI output")
|
|
@@ -1822,7 +1993,7 @@ var TrajectoryClient = class {
|
|
|
1822
1993
|
constructor(options = {}) {
|
|
1823
1994
|
this.storage = options.storage ?? new FileStorage(options.dataDir);
|
|
1824
1995
|
this.defaultAgent = options.defaultAgent ?? process.env.TRAJECTORIES_AGENT;
|
|
1825
|
-
this.projectId = options.projectId
|
|
1996
|
+
this.projectId = resolveProjectId(options.projectId);
|
|
1826
1997
|
this.autoSave = options.autoSave ?? true;
|
|
1827
1998
|
this.autoCompact = normalizeAutoCompactOptions(options.autoCompact);
|
|
1828
1999
|
this.autoCompactCwd = options.storage ? void 0 : options.dataDir;
|
|
@@ -2264,7 +2435,7 @@ function trajectory(title) {
|
|
|
2264
2435
|
|
|
2265
2436
|
// src/core/trailers.ts
|
|
2266
2437
|
import { execSync as execSync2 } from "child_process";
|
|
2267
|
-
import { readFileSync as
|
|
2438
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2268
2439
|
|
|
2269
2440
|
// src/core/trace.ts
|
|
2270
2441
|
import { execSync } from "child_process";
|
|
@@ -2407,6 +2578,9 @@ export {
|
|
|
2407
2578
|
exportToMarkdown,
|
|
2408
2579
|
exportToPRSummary,
|
|
2409
2580
|
exportToTimeline,
|
|
2581
|
+
DEFAULT_TRAJECTORY_DATA_DIR,
|
|
2582
|
+
LEGACY_TRAJECTORY_DATA_DIR,
|
|
2583
|
+
getDefaultTrajectoryDataDir,
|
|
2410
2584
|
FileStorage,
|
|
2411
2585
|
compactWorkflow,
|
|
2412
2586
|
TrajectorySession,
|
|
@@ -2420,4 +2594,4 @@ export {
|
|
|
2420
2594
|
getCommitsBetween,
|
|
2421
2595
|
getFilesChangedBetween
|
|
2422
2596
|
};
|
|
2423
|
-
//# sourceMappingURL=chunk-
|
|
2597
|
+
//# sourceMappingURL=chunk-BVTUGGYW.js.map
|