agent-relay 1.3.1 → 1.3.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.
Files changed (202) hide show
  1. package/.trajectories/active/traj_3yx9dy148mge.json +42 -0
  2. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
  3. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
  4. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
  5. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
  6. package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
  7. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
  8. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
  9. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
  10. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
  11. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
  12. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
  13. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
  14. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
  15. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
  16. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
  17. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
  18. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
  19. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
  20. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
  21. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
  22. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
  23. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
  24. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
  25. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
  26. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
  27. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
  28. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
  29. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
  30. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
  31. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
  32. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
  33. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
  34. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
  35. package/.trajectories/index.json +140 -1
  36. package/README.md +23 -9
  37. package/TRAIL_GIT_AUTH_FIX.md +113 -0
  38. package/deploy/workspace/codex.config.toml +1 -1
  39. package/deploy/workspace/entrypoint.sh +20 -79
  40. package/deploy/workspace/gh-relay +156 -0
  41. package/deploy/workspace/git-credential-relay +5 -1
  42. package/dist/bridge/multi-project-client.js +13 -10
  43. package/dist/bridge/spawner.d.ts +2 -0
  44. package/dist/bridge/spawner.js +58 -76
  45. package/dist/bridge/types.d.ts +2 -0
  46. package/dist/cli/index.d.ts +8 -6
  47. package/dist/cli/index.js +297 -30
  48. package/dist/cloud/api/admin.js +16 -3
  49. package/dist/cloud/api/codex-auth-helper.js +28 -8
  50. package/dist/cloud/api/consensus.d.ts +13 -0
  51. package/dist/cloud/api/consensus.js +259 -0
  52. package/dist/cloud/api/daemons.js +205 -1
  53. package/dist/cloud/api/git.js +37 -7
  54. package/dist/cloud/api/onboarding.js +4 -1
  55. package/dist/cloud/api/provider-env.d.ts +5 -0
  56. package/dist/cloud/api/provider-env.js +27 -0
  57. package/dist/cloud/api/providers.js +2 -0
  58. package/dist/cloud/api/test-helpers.js +130 -0
  59. package/dist/cloud/api/workspaces.js +38 -3
  60. package/dist/cloud/db/bulk-ingest.d.ts +88 -0
  61. package/dist/cloud/db/bulk-ingest.js +268 -0
  62. package/dist/cloud/db/drizzle.d.ts +33 -0
  63. package/dist/cloud/db/drizzle.js +174 -2
  64. package/dist/cloud/db/index.d.ts +24 -5
  65. package/dist/cloud/db/index.js +19 -4
  66. package/dist/cloud/db/schema.d.ts +397 -3
  67. package/dist/cloud/db/schema.js +75 -1
  68. package/dist/cloud/provisioner/index.d.ts +8 -0
  69. package/dist/cloud/provisioner/index.js +256 -50
  70. package/dist/cloud/server.js +47 -3
  71. package/dist/cloud/services/index.d.ts +1 -0
  72. package/dist/cloud/services/index.js +2 -0
  73. package/dist/cloud/services/nango.d.ts +3 -4
  74. package/dist/cloud/services/nango.js +11 -33
  75. package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
  76. package/dist/cloud/services/workspace-keepalive.js +234 -0
  77. package/dist/config/relay-config.d.ts +23 -0
  78. package/dist/config/relay-config.js +23 -0
  79. package/dist/daemon/agent-manager.d.ts +20 -1
  80. package/dist/daemon/agent-manager.js +51 -0
  81. package/dist/daemon/agent-registry.js +4 -4
  82. package/dist/daemon/agent-signing.d.ts +158 -0
  83. package/dist/daemon/agent-signing.js +523 -0
  84. package/dist/daemon/api.js +18 -1
  85. package/dist/daemon/cli-auth.d.ts +4 -1
  86. package/dist/daemon/cli-auth.js +55 -11
  87. package/dist/daemon/cloud-sync.d.ts +47 -1
  88. package/dist/daemon/cloud-sync.js +152 -3
  89. package/dist/daemon/connection.d.ts +28 -0
  90. package/dist/daemon/connection.js +113 -22
  91. package/dist/daemon/consensus-integration.d.ts +167 -0
  92. package/dist/daemon/consensus-integration.js +371 -0
  93. package/dist/daemon/consensus.d.ts +271 -0
  94. package/dist/daemon/consensus.js +632 -0
  95. package/dist/daemon/delivery-tracker.d.ts +34 -0
  96. package/dist/daemon/delivery-tracker.js +104 -0
  97. package/dist/daemon/enhanced-features.d.ts +118 -0
  98. package/dist/daemon/enhanced-features.js +178 -0
  99. package/dist/daemon/index.d.ts +4 -0
  100. package/dist/daemon/index.js +5 -0
  101. package/dist/daemon/rate-limiter.d.ts +68 -0
  102. package/dist/daemon/rate-limiter.js +130 -0
  103. package/dist/daemon/router.d.ts +18 -11
  104. package/dist/daemon/router.js +57 -113
  105. package/dist/daemon/server.d.ts +13 -1
  106. package/dist/daemon/server.js +71 -9
  107. package/dist/daemon/sync-queue.d.ts +116 -0
  108. package/dist/daemon/sync-queue.js +361 -0
  109. package/dist/dashboard/out/404.html +1 -1
  110. package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
  111. package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  112. package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
  113. package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
  114. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
  115. package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
  116. package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
  117. package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
  118. package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
  119. package/dist/dashboard/out/app/onboarding.html +1 -1
  120. package/dist/dashboard/out/app/onboarding.txt +1 -1
  121. package/dist/dashboard/out/app.html +1 -1
  122. package/dist/dashboard/out/app.txt +2 -2
  123. package/dist/dashboard/out/cloud/link.html +1 -0
  124. package/dist/dashboard/out/cloud/link.txt +7 -0
  125. package/dist/dashboard/out/connect-repos.html +1 -1
  126. package/dist/dashboard/out/connect-repos.txt +1 -1
  127. package/dist/dashboard/out/history.html +1 -1
  128. package/dist/dashboard/out/history.txt +2 -2
  129. package/dist/dashboard/out/index.html +1 -1
  130. package/dist/dashboard/out/index.txt +2 -2
  131. package/dist/dashboard/out/login.html +2 -3
  132. package/dist/dashboard/out/login.txt +2 -2
  133. package/dist/dashboard/out/metrics.html +1 -1
  134. package/dist/dashboard/out/metrics.txt +2 -2
  135. package/dist/dashboard/out/pricing.html +2 -2
  136. package/dist/dashboard/out/pricing.txt +1 -1
  137. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  138. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  139. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  140. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  141. package/dist/dashboard/out/providers.html +1 -1
  142. package/dist/dashboard/out/providers.txt +1 -1
  143. package/dist/dashboard/out/signup.html +2 -2
  144. package/dist/dashboard/out/signup.txt +1 -1
  145. package/dist/dashboard-server/server.js +244 -28
  146. package/dist/health-worker-manager.d.ts +62 -0
  147. package/dist/health-worker-manager.js +144 -0
  148. package/dist/health-worker.d.ts +9 -0
  149. package/dist/health-worker.js +79 -0
  150. package/dist/index.d.ts +2 -1
  151. package/dist/index.js +5 -1
  152. package/dist/memory/context-compaction.d.ts +156 -0
  153. package/dist/memory/context-compaction.js +453 -0
  154. package/dist/memory/index.d.ts +1 -0
  155. package/dist/memory/index.js +1 -0
  156. package/dist/protocol/channels.js +4 -4
  157. package/dist/protocol/framing.d.ts +72 -10
  158. package/dist/protocol/framing.js +194 -25
  159. package/dist/storage/adapter.d.ts +8 -1
  160. package/dist/storage/adapter.js +11 -0
  161. package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
  162. package/dist/storage/batched-sqlite-adapter.js +183 -0
  163. package/dist/storage/dead-letter-queue.d.ts +196 -0
  164. package/dist/storage/dead-letter-queue.js +427 -0
  165. package/dist/storage/dlq-adapter.d.ts +195 -0
  166. package/dist/storage/dlq-adapter.js +664 -0
  167. package/dist/trajectory/config.d.ts +32 -14
  168. package/dist/trajectory/config.js +38 -16
  169. package/dist/trajectory/integration.js +217 -64
  170. package/dist/utils/git-remote.d.ts +47 -0
  171. package/dist/utils/git-remote.js +125 -0
  172. package/dist/utils/id-generator.d.ts +35 -0
  173. package/dist/utils/id-generator.js +60 -0
  174. package/dist/utils/index.d.ts +1 -0
  175. package/dist/utils/index.js +1 -0
  176. package/dist/utils/precompiled-patterns.d.ts +110 -0
  177. package/dist/utils/precompiled-patterns.js +322 -0
  178. package/dist/wrapper/auth-detection.js +1 -1
  179. package/dist/wrapper/base-wrapper.d.ts +40 -0
  180. package/dist/wrapper/base-wrapper.js +60 -6
  181. package/dist/wrapper/client.d.ts +14 -4
  182. package/dist/wrapper/client.js +89 -31
  183. package/dist/wrapper/idle-detector.d.ts +102 -0
  184. package/dist/wrapper/idle-detector.js +279 -0
  185. package/dist/wrapper/parser.d.ts +4 -0
  186. package/dist/wrapper/parser.js +19 -1
  187. package/dist/wrapper/pty-wrapper.d.ts +14 -2
  188. package/dist/wrapper/pty-wrapper.js +132 -32
  189. package/dist/wrapper/shared.d.ts +1 -1
  190. package/dist/wrapper/shared.js +1 -1
  191. package/dist/wrapper/tmux-wrapper.d.ts +20 -2
  192. package/dist/wrapper/tmux-wrapper.js +163 -40
  193. package/package.json +3 -1
  194. package/scripts/run-migrations.js +43 -0
  195. package/scripts/verify-schema.js +134 -0
  196. package/tests/benchmarks/protocol.bench.ts +310 -0
  197. package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
  198. package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
  199. package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
  200. package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
  201. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
  202. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
@@ -1,21 +1,26 @@
1
1
  /**
2
2
  * Trajectory Configuration
3
3
  *
4
- * Handles repo-level opt-in/opt-out for trajectory storage.
5
- * When trajectories are opt-out (not in source control), they're stored
6
- * in the user's home directory instead of the repo.
4
+ * Manages centralized relay configuration for trajectory storage settings.
5
+ * Config is stored in a central location (not per-repo) and applies to all projects.
7
6
  *
8
- * DECISIONS:
9
- * 1. Default behavior: trajectories are OPT-OUT (stored outside repo)
10
- * - Reasoning: Most repos won't want trajectory files in source control
11
- * - Users must explicitly opt-in to store in repo
7
+ * ARCHITECTURE:
8
+ * 1. Config location: Centralized, not repo-specific
9
+ * - Primary: AGENT_RELAY_CONFIG_DIR/relay.json (if env var set)
10
+ * - Default: ~/.config/agent-relay/relay.json
11
+ * - Reasoning: Single config applies to all projects; survives repo deletion
12
12
  *
13
- * 2. Setting location: .relay/config.json in repo root
14
- * - Reasoning: Keeps relay config separate from .claude/ which may have other uses
15
- * - Alternative considered: .claude/settings.json - rejected to avoid conflicts
13
+ * 2. Default behavior: trajectories are OPT-OUT (stored in user home)
14
+ * - Location: ~/.config/agent-relay/trajectories/<project-hash>/
15
+ * - Users can opt-in via central config: storeInRepo: true
16
16
  *
17
- * 3. User-level storage: ~/.config/agent-relay/trajectories/<project-hash>/
18
- * - Reasoning: XDG-compliant, project-isolated, survives repo deletion
17
+ * 3. Opt-in storage: .trajectories/ directory in repo root
18
+ * - Applied when: User sets storeInRepo: true in central relay.json
19
+ * - Users should add .trajectories/ to .gitignore if private
20
+ *
21
+ * 4. Initialization: When users initialize relay in a repo, prompt to opt-in
22
+ * - Creates entry in ~/.config/agent-relay/relay.json
23
+ * - Does NOT create repo-specific config
19
24
  */
20
25
  /**
21
26
  * Relay config structure
@@ -26,16 +31,29 @@ export interface RelayConfig {
26
31
  /**
27
32
  * Whether to store trajectories in the repo (.trajectories/)
28
33
  * Default: false (stored in ~/.config/agent-relay/trajectories/)
34
+ *
35
+ * NOTE: When true, this project will track trajectories in git.
36
+ * Users should add .trajectories/ to .gitignore if they prefer
37
+ * not to track trajectories for privacy reasons.
29
38
  */
30
39
  storeInRepo?: boolean;
31
40
  };
32
41
  }
33
42
  /**
34
43
  * Get the relay config file path
44
+ *
45
+ * Returns the central relay configuration file, not repo-specific.
46
+ * This config is shared across all projects and stored in:
47
+ * - AGENT_RELAY_CONFIG_DIR/relay.json (if AGENT_RELAY_CONFIG_DIR is set)
48
+ * - ~/.config/agent-relay/relay.json (default)
49
+ *
50
+ * NOTE: projectRoot parameter is kept for backwards compatibility but ignored.
51
+ * Config is always loaded from the central location.
35
52
  */
36
- export declare function getRelayConfigPath(projectRoot?: string): string;
53
+ export declare function getRelayConfigPath(_projectRoot?: string): string;
37
54
  /**
38
- * Read the relay config from the repo
55
+ * Read the relay config from the central location
56
+ * Config is shared across all projects
39
57
  */
40
58
  export declare function readRelayConfig(projectRoot?: string): RelayConfig;
41
59
  /**
@@ -1,40 +1,62 @@
1
1
  /**
2
2
  * Trajectory Configuration
3
3
  *
4
- * Handles repo-level opt-in/opt-out for trajectory storage.
5
- * When trajectories are opt-out (not in source control), they're stored
6
- * in the user's home directory instead of the repo.
4
+ * Manages centralized relay configuration for trajectory storage settings.
5
+ * Config is stored in a central location (not per-repo) and applies to all projects.
7
6
  *
8
- * DECISIONS:
9
- * 1. Default behavior: trajectories are OPT-OUT (stored outside repo)
10
- * - Reasoning: Most repos won't want trajectory files in source control
11
- * - Users must explicitly opt-in to store in repo
7
+ * ARCHITECTURE:
8
+ * 1. Config location: Centralized, not repo-specific
9
+ * - Primary: AGENT_RELAY_CONFIG_DIR/relay.json (if env var set)
10
+ * - Default: ~/.config/agent-relay/relay.json
11
+ * - Reasoning: Single config applies to all projects; survives repo deletion
12
12
  *
13
- * 2. Setting location: .relay/config.json in repo root
14
- * - Reasoning: Keeps relay config separate from .claude/ which may have other uses
15
- * - Alternative considered: .claude/settings.json - rejected to avoid conflicts
13
+ * 2. Default behavior: trajectories are OPT-OUT (stored in user home)
14
+ * - Location: ~/.config/agent-relay/trajectories/<project-hash>/
15
+ * - Users can opt-in via central config: storeInRepo: true
16
16
  *
17
- * 3. User-level storage: ~/.config/agent-relay/trajectories/<project-hash>/
18
- * - Reasoning: XDG-compliant, project-isolated, survives repo deletion
17
+ * 3. Opt-in storage: .trajectories/ directory in repo root
18
+ * - Applied when: User sets storeInRepo: true in central relay.json
19
+ * - Users should add .trajectories/ to .gitignore if private
20
+ *
21
+ * 4. Initialization: When users initialize relay in a repo, prompt to opt-in
22
+ * - Creates entry in ~/.config/agent-relay/relay.json
23
+ * - Does NOT create repo-specific config
19
24
  */
20
25
  import { existsSync, readFileSync, mkdirSync, statSync } from 'node:fs';
21
26
  import { join } from 'node:path';
22
27
  import { homedir } from 'node:os';
23
28
  import { createHash } from 'node:crypto';
24
29
  import { getProjectPaths } from '../utils/project-namespace.js';
30
+ /**
31
+ * Get the central agent-relay config directory
32
+ * Uses AGENT_RELAY_CONFIG_DIR environment variable if set,
33
+ * otherwise defaults to ~/.config/agent-relay
34
+ */
35
+ function getAgentRelayConfigDir() {
36
+ return process.env.AGENT_RELAY_CONFIG_DIR ??
37
+ join(homedir(), '.config', 'agent-relay');
38
+ }
25
39
  /**
26
40
  * Cache for config to avoid repeated file reads
27
41
  */
28
42
  let configCache = null;
29
43
  /**
30
44
  * Get the relay config file path
45
+ *
46
+ * Returns the central relay configuration file, not repo-specific.
47
+ * This config is shared across all projects and stored in:
48
+ * - AGENT_RELAY_CONFIG_DIR/relay.json (if AGENT_RELAY_CONFIG_DIR is set)
49
+ * - ~/.config/agent-relay/relay.json (default)
50
+ *
51
+ * NOTE: projectRoot parameter is kept for backwards compatibility but ignored.
52
+ * Config is always loaded from the central location.
31
53
  */
32
- export function getRelayConfigPath(projectRoot) {
33
- const root = projectRoot ?? getProjectPaths().projectRoot;
34
- return join(root, '.relay', 'config.json');
54
+ export function getRelayConfigPath(_projectRoot) {
55
+ return join(getAgentRelayConfigDir(), 'relay.json');
35
56
  }
36
57
  /**
37
- * Read the relay config from the repo
58
+ * Read the relay config from the central location
59
+ * Config is shared across all projects
38
60
  */
39
61
  export function readRelayConfig(projectRoot) {
40
62
  const configPath = getRelayConfigPath(projectRoot);
@@ -14,13 +14,40 @@
14
14
  * - Provides hooks for key agent lifecycle events
15
15
  */
16
16
  import { spawn, execSync } from 'node:child_process';
17
- import { readFileSync, existsSync } from 'node:fs';
17
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
18
18
  import { join } from 'node:path';
19
19
  import { getProjectPaths } from '../utils/project-namespace.js';
20
20
  import { getPrimaryTrajectoriesDir, getAllTrajectoriesDirs, getTrajectoryEnvVars, } from './config.js';
21
21
  /**
22
22
  * Read a single trajectory index file from a directory
23
23
  */
24
+ function resolveIndexEntryPath(trajectoriesDir, entryPath) {
25
+ if (!entryPath) {
26
+ return entryPath;
27
+ }
28
+ if (existsSync(entryPath)) {
29
+ return entryPath;
30
+ }
31
+ const normalized = entryPath.replace(/\\/g, '/');
32
+ const marker = '/.trajectories/';
33
+ const markerIndex = normalized.indexOf(marker);
34
+ if (markerIndex >= 0) {
35
+ const relative = normalized.slice(markerIndex + marker.length);
36
+ const candidate = join(trajectoriesDir, relative);
37
+ if (existsSync(candidate)) {
38
+ return candidate;
39
+ }
40
+ }
41
+ const subdirMatch = normalized.match(/\/(active|completed)\/.+$/);
42
+ if (subdirMatch) {
43
+ const relative = subdirMatch[0].slice(1);
44
+ const candidate = join(trajectoriesDir, relative);
45
+ if (existsSync(candidate)) {
46
+ return candidate;
47
+ }
48
+ }
49
+ return entryPath;
50
+ }
24
51
  function readSingleTrajectoryIndex(trajectoriesDir) {
25
52
  try {
26
53
  const indexPath = join(trajectoriesDir, 'index.json');
@@ -28,7 +55,15 @@ function readSingleTrajectoryIndex(trajectoriesDir) {
28
55
  return null;
29
56
  }
30
57
  const content = readFileSync(indexPath, 'utf-8');
31
- return JSON.parse(content);
58
+ const index = JSON.parse(content);
59
+ const trajectories = index.trajectories ?? {};
60
+ for (const [id, entry] of Object.entries(trajectories)) {
61
+ trajectories[id] = {
62
+ ...entry,
63
+ path: resolveIndexEntryPath(trajectoriesDir, entry.path),
64
+ };
65
+ }
66
+ return { ...index, trajectories };
32
67
  }
33
68
  catch {
34
69
  return null;
@@ -91,6 +126,39 @@ function readTrajectoryFile(trajectoryPath) {
91
126
  return null;
92
127
  }
93
128
  }
129
+ function getCurrentPhaseFromTrajectory(trajectory) {
130
+ if (!trajectory?.chapters?.length) {
131
+ return undefined;
132
+ }
133
+ const lastChapter = trajectory.chapters[trajectory.chapters.length - 1];
134
+ for (const event of [...(lastChapter.events || [])].reverse()) {
135
+ if (event.type === 'phase_transition' || event.type === 'phase') {
136
+ const phaseMatch = event.content?.match(/phase[:\s]+(\w+)/i);
137
+ if (phaseMatch) {
138
+ return phaseMatch[1].toLowerCase();
139
+ }
140
+ }
141
+ }
142
+ return undefined;
143
+ }
144
+ function listActiveTrajectoryFiles(trajectoriesDir) {
145
+ const activeDir = join(trajectoriesDir, 'active');
146
+ if (!existsSync(activeDir)) {
147
+ return [];
148
+ }
149
+ return readdirSync(activeDir, { withFileTypes: true })
150
+ .filter(entry => entry.isFile() && entry.name.endsWith('.json'))
151
+ .map(entry => join(activeDir, entry.name));
152
+ }
153
+ function findActiveTrajectoryPathById(trajectoryDirs, trajectoryId) {
154
+ for (const dir of trajectoryDirs) {
155
+ const candidate = join(dir, 'active', `${trajectoryId}.json`);
156
+ if (existsSync(candidate)) {
157
+ return candidate;
158
+ }
159
+ }
160
+ return null;
161
+ }
94
162
  /**
95
163
  * Run a trail CLI command
96
164
  * Uses config-based environment to control trajectory storage location
@@ -163,33 +231,37 @@ export async function startTrajectory(options) {
163
231
  */
164
232
  export async function getTrajectoryStatus() {
165
233
  const index = readTrajectoryIndex();
166
- if (!index) {
234
+ const trajectoryDirs = getAllTrajectoriesDirs();
235
+ if (!index && trajectoryDirs.length === 0) {
167
236
  return { active: false };
168
237
  }
169
238
  // Find an active trajectory
170
- for (const [id, entry] of Object.entries(index.trajectories)) {
171
- if (entry.status === 'active') {
172
- // Read the full trajectory file to get phase info
173
- const trajectory = readTrajectoryFile(entry.path);
174
- let currentPhase;
175
- if (trajectory?.chapters?.length) {
176
- const lastChapter = trajectory.chapters[trajectory.chapters.length - 1];
177
- // Check events for phase transitions
178
- for (const event of [...(lastChapter.events || [])].reverse()) {
179
- if (event.type === 'phase_transition' || event.type === 'phase') {
180
- const phaseMatch = event.content?.match(/phase[:\s]+(\w+)/i);
181
- if (phaseMatch) {
182
- currentPhase = phaseMatch[1].toLowerCase();
183
- break;
184
- }
185
- }
186
- }
239
+ if (index) {
240
+ for (const [id, entry] of Object.entries(index.trajectories)) {
241
+ if (entry.status === 'active') {
242
+ // Read the full trajectory file to get phase info
243
+ const trajectory = readTrajectoryFile(entry.path);
244
+ const currentPhase = getCurrentPhaseFromTrajectory(trajectory);
245
+ return {
246
+ active: true,
247
+ trajectoryId: id,
248
+ phase: currentPhase,
249
+ task: entry.title,
250
+ };
251
+ }
252
+ }
253
+ }
254
+ for (const dir of trajectoryDirs) {
255
+ for (const activePath of listActiveTrajectoryFiles(dir)) {
256
+ const trajectory = readTrajectoryFile(activePath);
257
+ if (!trajectory) {
258
+ continue;
187
259
  }
188
260
  return {
189
261
  active: true,
190
- trajectoryId: id,
191
- phase: currentPhase,
192
- task: entry.title,
262
+ trajectoryId: trajectory.id,
263
+ phase: getCurrentPhaseFromTrajectory(trajectory),
264
+ task: trajectory.task?.title,
193
265
  };
194
266
  }
195
267
  }
@@ -284,51 +356,98 @@ export async function abandonTrajectory(reason) {
284
356
  * Reads directly from filesystem instead of using CLI
285
357
  */
286
358
  export async function listTrajectorySteps(trajectoryId) {
359
+ const trajectoryDirs = getAllTrajectoriesDirs();
287
360
  const index = readTrajectoryIndex();
288
- if (!index) {
361
+ if (!index && trajectoryDirs.length === 0) {
289
362
  return { success: true, steps: [] };
290
363
  }
291
- // Find the trajectory to load
292
- let trajectoryPath;
364
+ // Collect all trajectory paths to load
365
+ const trajectoryPaths = new Set();
293
366
  if (trajectoryId) {
294
- // Use specified trajectory
295
- const entry = index.trajectories[trajectoryId];
367
+ // Use specified trajectory - try multiple lookup strategies
368
+ let foundPath = null;
369
+ // Strategy 1: Look up by index key
370
+ const entry = index?.trajectories[trajectoryId];
296
371
  if (entry) {
297
- trajectoryPath = entry.path;
372
+ foundPath = entry.path;
373
+ console.log('[trajectory] Found by index key:', trajectoryId, '->', foundPath);
374
+ }
375
+ // Strategy 2: If not found by key, search index entries by file's internal ID
376
+ // This handles cases where the index key differs from the file's internal ID
377
+ if (!foundPath && index) {
378
+ for (const [_key, indexEntry] of Object.entries(index.trajectories)) {
379
+ const trajectory = readTrajectoryFile(indexEntry.path);
380
+ if (trajectory && trajectory.id === trajectoryId) {
381
+ foundPath = indexEntry.path;
382
+ console.log('[trajectory] Found by file ID scan:', trajectoryId, '->', foundPath);
383
+ break;
384
+ }
385
+ }
386
+ }
387
+ // Strategy 3: Fall back to active directory filename match
388
+ if (!foundPath) {
389
+ foundPath = findActiveTrajectoryPathById(trajectoryDirs, trajectoryId);
390
+ if (foundPath) {
391
+ console.log('[trajectory] Found by active dir fallback:', trajectoryId, '->', foundPath);
392
+ }
393
+ }
394
+ if (foundPath) {
395
+ trajectoryPaths.add(foundPath);
396
+ }
397
+ else {
398
+ console.log('[trajectory] Not found:', trajectoryId);
298
399
  }
299
400
  }
300
401
  else {
301
- // Find active trajectory
302
- for (const [_id, entry] of Object.entries(index.trajectories)) {
303
- if (entry.status === 'active') {
304
- trajectoryPath = entry.path;
305
- break;
402
+ // Collect ALL active trajectories (not just the first one)
403
+ if (index) {
404
+ for (const [_id, entry] of Object.entries(index.trajectories)) {
405
+ if (entry.status === 'active') {
406
+ trajectoryPaths.add(entry.path);
407
+ }
408
+ }
409
+ }
410
+ for (const dir of trajectoryDirs) {
411
+ for (const activePath of listActiveTrajectoryFiles(dir)) {
412
+ trajectoryPaths.add(activePath);
306
413
  }
307
414
  }
308
415
  }
309
- if (!trajectoryPath) {
310
- return { success: true, steps: [] };
311
- }
312
- const trajectory = readTrajectoryFile(trajectoryPath);
313
- if (!trajectory) {
416
+ if (trajectoryPaths.size === 0) {
314
417
  return { success: true, steps: [] };
315
418
  }
316
- // Extract events from all chapters
419
+ // Load all trajectories and merge their steps
317
420
  const steps = [];
318
421
  let stepIndex = 0;
319
- for (const chapter of trajectory.chapters || []) {
320
- for (const event of chapter.events || []) {
321
- steps.push({
322
- id: `step-${stepIndex++}`,
323
- timestamp: event.ts || Date.now(),
324
- type: mapEventType(event.type),
325
- title: event.content?.slice(0, 50) || event.type || 'Event',
326
- description: event.content,
327
- metadata: event.raw,
328
- status: mapEventStatus(trajectory.status),
329
- });
422
+ for (const trajectoryPath of trajectoryPaths) {
423
+ const trajectory = readTrajectoryFile(trajectoryPath);
424
+ if (!trajectory) {
425
+ continue;
426
+ }
427
+ // Extract events from all chapters
428
+ // Include trajectory ID in step IDs to ensure uniqueness across trajectories
429
+ // This prevents React key collisions when switching between trajectories
430
+ const trajId = trajectory.id || 'unknown';
431
+ for (const chapter of trajectory.chapters || []) {
432
+ for (const event of chapter.events || []) {
433
+ steps.push({
434
+ id: `${trajId}-step-${stepIndex++}`,
435
+ timestamp: event.ts || Date.now(),
436
+ type: mapEventType(event.type),
437
+ title: event.content?.slice(0, 50) || event.type || 'Event',
438
+ description: event.content,
439
+ metadata: event.raw,
440
+ status: mapEventStatus(trajectory.status),
441
+ });
442
+ }
330
443
  }
331
444
  }
445
+ // Sort steps by timestamp to maintain chronological order across all trajectories
446
+ steps.sort((a, b) => {
447
+ const timeA = typeof a.timestamp === 'number' ? a.timestamp : new Date(a.timestamp).getTime();
448
+ const timeB = typeof b.timestamp === 'number' ? b.timestamp : new Date(b.timestamp).getTime();
449
+ return timeA - timeB;
450
+ });
332
451
  return { success: true, steps };
333
452
  }
334
453
  /**
@@ -336,22 +455,33 @@ export async function listTrajectorySteps(trajectoryId) {
336
455
  * Reads directly from filesystem
337
456
  */
338
457
  export async function getTrajectoryHistory() {
458
+ const trajectoryDirs = getAllTrajectoriesDirs();
339
459
  const index = readTrajectoryIndex();
340
- if (!index) {
460
+ if (!index && trajectoryDirs.length === 0) {
341
461
  return { success: true, trajectories: [] };
342
462
  }
343
463
  const trajectories = [];
344
- for (const [id, entry] of Object.entries(index.trajectories)) {
345
- const historyEntry = {
346
- id,
347
- title: entry.title,
348
- status: entry.status,
349
- startedAt: entry.startedAt,
350
- completedAt: entry.completedAt,
351
- };
352
- // Try to read full trajectory for additional details
353
- if (entry.path) {
354
- const trajectory = readTrajectoryFile(entry.path);
464
+ const seenIds = new Set();
465
+ if (index) {
466
+ for (const [indexKey, entry] of Object.entries(index.trajectories)) {
467
+ // Read the trajectory file to get the actual internal ID
468
+ // This ensures consistency between history list IDs and file content
469
+ const trajectory = entry.path ? readTrajectoryFile(entry.path) : null;
470
+ // Use the file's internal ID if available, otherwise fall back to index key
471
+ // This fixes the mismatch where index key differs from file's internal ID
472
+ const actualId = trajectory?.id || indexKey;
473
+ // Skip if we've already seen this ID (from a previous directory's index)
474
+ if (seenIds.has(actualId)) {
475
+ continue;
476
+ }
477
+ const historyEntry = {
478
+ id: actualId,
479
+ title: trajectory?.task?.title || entry.title,
480
+ status: entry.status,
481
+ startedAt: trajectory?.startedAt || entry.startedAt,
482
+ completedAt: trajectory?.completedAt || entry.completedAt,
483
+ };
484
+ // Add additional details from trajectory file
355
485
  if (trajectory) {
356
486
  historyEntry.agents = trajectory.agents?.map(a => a.name);
357
487
  if (trajectory.retrospective) {
@@ -359,8 +489,31 @@ export async function getTrajectoryHistory() {
359
489
  historyEntry.confidence = trajectory.retrospective.confidence;
360
490
  }
361
491
  }
492
+ trajectories.push(historyEntry);
493
+ seenIds.add(actualId);
494
+ }
495
+ }
496
+ for (const dir of trajectoryDirs) {
497
+ for (const activePath of listActiveTrajectoryFiles(dir)) {
498
+ const trajectory = readTrajectoryFile(activePath);
499
+ if (!trajectory || seenIds.has(trajectory.id)) {
500
+ continue;
501
+ }
502
+ const historyEntry = {
503
+ id: trajectory.id,
504
+ title: trajectory.task?.title || 'Untitled trajectory',
505
+ status: trajectory.status ?? 'active',
506
+ startedAt: trajectory.startedAt,
507
+ completedAt: trajectory.completedAt,
508
+ agents: trajectory.agents?.map(a => a.name),
509
+ };
510
+ if (trajectory.retrospective) {
511
+ historyEntry.summary = trajectory.retrospective.summary;
512
+ historyEntry.confidence = trajectory.retrospective.confidence;
513
+ }
514
+ trajectories.push(historyEntry);
515
+ seenIds.add(trajectory.id);
362
516
  }
363
- trajectories.push(historyEntry);
364
517
  }
365
518
  // Sort by startedAt descending (most recent first)
366
519
  trajectories.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Git Remote Detection Utility
3
+ *
4
+ * Detects the git remote URL from a working directory and parses it
5
+ * to extract the repository full name (owner/repo).
6
+ */
7
+ /**
8
+ * Parse a git remote URL to extract owner/repo format.
9
+ *
10
+ * Supports:
11
+ * - git@github.com:owner/repo.git
12
+ * - https://github.com/owner/repo.git
13
+ * - https://github.com/owner/repo
14
+ * - git://github.com/owner/repo.git
15
+ */
16
+ export declare function parseGitRemoteUrl(url: string): string | null;
17
+ /**
18
+ * Get the git remote URL from a directory.
19
+ *
20
+ * @param workingDirectory The directory to check for git remote
21
+ * @param remoteName The remote name to use (default: 'origin')
22
+ * @returns The remote URL or null if not found
23
+ */
24
+ export declare function getGitRemoteUrl(workingDirectory: string, remoteName?: string): string | null;
25
+ /**
26
+ * Get the repository full name (owner/repo) from a working directory.
27
+ *
28
+ * @param workingDirectory The directory to check
29
+ * @returns The repo full name (e.g., "AgentWorkforce/relay") or null
30
+ */
31
+ export declare function getRepoFullName(workingDirectory: string): string | null;
32
+ /**
33
+ * Find the git root directory from a given path.
34
+ * Walks up the directory tree looking for .git folder.
35
+ *
36
+ * @param startPath The path to start searching from
37
+ * @returns The git root directory or null if not in a git repo
38
+ */
39
+ export declare function findGitRoot(startPath: string): string | null;
40
+ /**
41
+ * Get repository full name, walking up to find git root if needed.
42
+ *
43
+ * @param workingDirectory The directory to start from
44
+ * @returns The repo full name or null
45
+ */
46
+ export declare function getRepoFullNameFromPath(workingDirectory: string): string | null;
47
+ //# sourceMappingURL=git-remote.d.ts.map