macro-agent 0.1.3 → 0.1.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.
@@ -2,8 +2,10 @@
2
2
  * Trajectory Reporter — builds and reports trajectory checkpoints to the MAP hub.
3
3
  *
4
4
  * Sends checkpoints via the `trajectory/checkpoint` JSON-RPC extension.
5
- * Also handles inbound `trajectory/content.request` notifications by serving
6
- * session transcript data.
5
+ * Handles inbound `trajectory/content.request` notifications by serving
6
+ * session transcripts via sessionlog's SessionStore and CheckpointStore.
7
+ * Supports all agent types (Claude Code, Codex, Gemini, etc.) through
8
+ * sessionlog's adapter system.
7
9
  *
8
10
  * @module map/trajectory-reporter
9
11
  */
@@ -34,37 +36,173 @@ export interface TrajectoryConnection {
34
36
  get isConnected(): boolean;
35
37
  }
36
38
 
39
+ /**
40
+ * Resolve transcript content for a checkpoint ID using sessionlog.
41
+ *
42
+ * Two-source strategy (matching cc-swarm):
43
+ * 1. Live session — use sessionlog's SessionStore to find a session
44
+ * matching the checkpoint ID, then read its transcript from disk.
45
+ * 2. Committed checkpoint — use sessionlog's CheckpointStore to read
46
+ * content from the git history.
47
+ *
48
+ * Returns null if no content is found or sessionlog is unavailable.
49
+ */
50
+ async function resolveContent(
51
+ checkpointId: string,
52
+ sessionDirs: string[],
53
+ ): Promise<{
54
+ transcript: string;
55
+ metadata: Record<string, unknown>;
56
+ prompts: string;
57
+ context: string;
58
+ } | null> {
59
+ let sessionlog: typeof import("sessionlog") | null = null;
60
+ try {
61
+ sessionlog = await import("sessionlog");
62
+ } catch {
63
+ return null; // sessionlog not available
64
+ }
65
+
66
+ const { readFileSync, existsSync } = await import("node:fs");
67
+
68
+ // Derive the session ID from checkpoint ID (e.g., "sess-abc-step3" → "sess-abc")
69
+ const sessionId = checkpointId.replace(/-step\d+$/, "");
70
+
71
+ // ── 1. Live session lookup via SessionStore ────────────────────────────
72
+ for (const sessionsDir of sessionDirs) {
73
+ if (!existsSync(sessionsDir)) continue;
74
+
75
+ try {
76
+ const store = sessionlog.createSessionStore(undefined, sessionsDir);
77
+ // Try loading by derived session ID first, then by raw checkpoint ID
78
+ let state = await store.load(sessionId);
79
+ if (!state) state = await store.load(checkpointId);
80
+
81
+ // If not found by ID, scan all sessions for checkpoint match
82
+ if (!state) {
83
+ const allSessions = await store.list();
84
+ state = allSessions.find((s) =>
85
+ s.lastCheckpointID === checkpointId ||
86
+ (s.turnCheckpointIDs || []).includes(checkpointId),
87
+ ) ?? null;
88
+ }
89
+
90
+ if (!state?.transcriptPath || !existsSync(state.transcriptPath)) continue;
91
+
92
+ const transcript = readFileSync(state.transcriptPath, "utf-8");
93
+
94
+ // Use sessionlog's prompt extraction if the agent has a TranscriptAnalyzer,
95
+ // otherwise fall back to the prompts stored in state
96
+ let prompts = "";
97
+ if (state.firstPrompt) {
98
+ // Collect from promptAttributions if available, otherwise use firstPrompt
99
+ const attrs = state.promptAttributions;
100
+ if (attrs && attrs.length > 0) {
101
+ prompts = attrs.map((a) => a.prompt).join("\n---\n");
102
+ } else {
103
+ prompts = state.firstPrompt;
104
+ }
105
+ }
106
+
107
+ return {
108
+ transcript,
109
+ prompts,
110
+ metadata: {
111
+ sessionID: state.sessionID,
112
+ phase: state.phase,
113
+ agentType: state.agentType,
114
+ stepCount: state.stepCount || 0,
115
+ filesTouched: state.filesTouched || [],
116
+ tokenUsage: state.tokenUsage || {},
117
+ startedAt: state.startedAt,
118
+ endedAt: state.endedAt,
119
+ source: "live",
120
+ },
121
+ context: `Session ${state.sessionID} (${state.phase})`,
122
+ };
123
+ } catch {
124
+ continue;
125
+ }
126
+ }
127
+
128
+ // ── 2. Committed checkpoint via CheckpointStore ────────────────────────
129
+ try {
130
+ if (sessionlog.createCheckpointStore) {
131
+ const store = sessionlog.createCheckpointStore();
132
+ const content = await store.readSessionContent(checkpointId, 0);
133
+ if (content) {
134
+ return {
135
+ transcript: content.transcript,
136
+ prompts: content.prompts,
137
+ metadata: { ...content.metadata, source: "committed" },
138
+ context: content.context,
139
+ };
140
+ }
141
+ }
142
+ } catch {
143
+ // Checkpoint not found or store unavailable
144
+ }
145
+
146
+ return null;
147
+ }
148
+
37
149
  /**
38
150
  * Create a trajectory reporter that sends checkpoints to the MAP hub
39
- * and serves content on demand.
151
+ * and serves session transcript content on demand via sessionlog.
40
152
  */
41
153
  export function createTrajectoryReporter(
42
154
  connection: TrajectoryConnection,
43
- config: Pick<MAPSidecarConfig, "trajectorySyncLevel">,
155
+ config: Pick<MAPSidecarConfig, "trajectorySyncLevel"> & {
156
+ /** Additional session directories to search for transcripts */
157
+ sessionDirs?: string[];
158
+ },
44
159
  ): TrajectoryReporter {
45
160
  // Cache the resource_id from the first checkpoint response
46
161
  // so subsequent calls reuse it (avoids creating duplicate session resources)
47
162
  let cachedResourceId: string | undefined;
48
163
 
164
+ // Build session directory search list
165
+ const defaultDirs: string[] = [];
166
+ try {
167
+ const cwd = process.cwd();
168
+ defaultDirs.push(
169
+ `${cwd}/.git/sessionlog-sessions`,
170
+ `${cwd}/.swarm/sessionlog/sessions`,
171
+ );
172
+ } catch {
173
+ // Can't resolve paths — will use config dirs only
174
+ }
175
+ const sessionDirs = [...(config.sessionDirs ?? []), ...defaultDirs];
176
+
49
177
  // Handler for inbound content requests
50
178
  const contentHandler = async (params: unknown): Promise<void> => {
51
179
  const req = params as TrajectoryContentRequest;
52
180
  if (!req?.request_id) return;
53
181
 
54
182
  try {
55
- // Respond with what we have — macro-agent doesn't store full transcripts
56
- // like sessionlog does, so we send a minimal response.
57
- // Future: integrate with ACP session history for richer content.
58
- await connection.sendNotification("trajectory/content.response", {
59
- request_id: req.request_id,
60
- transcript: null,
61
- metadata: {
62
- source: "macro-agent",
63
- note: "Full transcript serving not yet implemented",
64
- },
65
- });
183
+ const content = await resolveContent(
184
+ req.checkpoint_id,
185
+ sessionDirs,
186
+ );
187
+
188
+ if (content) {
189
+ await connection.sendNotification("trajectory/content.response", {
190
+ request_id: req.request_id,
191
+ transcript: content.transcript,
192
+ metadata: content.metadata,
193
+ prompts: content.prompts,
194
+ context: content.context,
195
+ });
196
+ } else {
197
+ await connection.sendNotification("trajectory/content.response", {
198
+ request_id: req.request_id,
199
+ transcript: "",
200
+ metadata: { source: "macro-agent" },
201
+ prompts: "",
202
+ context: "",
203
+ });
204
+ }
66
205
  } catch {
67
- // Best effort
68
206
  try {
69
207
  await connection.sendNotification("trajectory/content.response", {
70
208
  request_id: req.request_id,
package/src/map/types.ts CHANGED
@@ -145,46 +145,8 @@ export interface TrajectoryContentRequest {
145
145
  // Coordination Wire Format
146
146
  // =============================================================================
147
147
 
148
- /** Inbound task assignment from hub */
149
- export interface CoordinationTaskAssign {
150
- title: string;
151
- description?: string;
152
- assigned_to?: string;
153
- assigned_by: string;
154
- priority?: string;
155
- context?: Record<string, unknown>;
156
- deadline?: string;
157
- }
158
-
159
- /** Inbound task status update from hub */
160
- export interface CoordinationTaskStatus {
161
- task_id: string;
162
- status: string;
163
- progress?: number;
164
- result?: unknown;
165
- error?: string;
166
- }
167
-
168
- /** Inbound context share from hub */
169
- export interface CoordinationContextShare {
170
- hive_id?: string;
171
- source_swarm_id: string;
172
- context_type: string;
173
- data: unknown;
174
- target_swarm_ids?: string[];
175
- ttl_seconds?: number;
176
- }
177
-
178
- /** Inbound message from hub */
179
- export interface CoordinationMessage {
180
- hive_id?: string;
181
- from_swarm_id: string;
182
- to_swarm_id: string;
183
- content_type: string;
184
- content: unknown;
185
- reply_to?: string;
186
- metadata?: Record<string, unknown>;
187
- }
148
+ // Coordination uses generic MAP scope messages — see coordination-handler.ts.
149
+ // Wire format types are inlined there; no custom types needed here.
188
150
 
189
151
  // =============================================================================
190
152
  // Internal Bridge Types