waypoint-codex 0.18.0 → 0.18.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 CHANGED
@@ -189,6 +189,15 @@ The continuity story matters:
189
189
  - `.waypoint/context/RECENT_THREAD.md` helps the agent retain the important
190
190
  parts of the previous conversation
191
191
 
192
+ Waypoint defaults to Codex transcript discovery.
193
+ If you use Pi instead, set this in `.waypoint/config.toml`:
194
+
195
+ ```toml
196
+ coding_agent = "pi"
197
+ ```
198
+
199
+ Supported values are `"codex"` and `"pi"`.
200
+
192
201
  ## Best fit
193
202
 
194
203
  Waypoint is most useful when you want:
package/dist/src/core.js CHANGED
@@ -143,6 +143,7 @@ function buildWaypointConfig(projectRoot, existingConfig, options) {
143
143
  return {
144
144
  version: existingConfig?.version ?? defaults.version,
145
145
  profile: options.profile,
146
+ coding_agent: existingConfig?.coding_agent ?? defaults.coding_agent,
146
147
  workspace_file: existingConfig?.workspace_file ?? defaults.workspace_file,
147
148
  docs_dirs: configuredRootDirs(projectRoot, existingConfig?.docs_dirs, existingConfig?.docs_dir, DEFAULT_DOCS_DIR).map((dir) => path.relative(projectRoot, dir).split(path.sep).join("/")),
148
149
  plans_dirs: configuredRootDirs(projectRoot, existingConfig?.plans_dirs, existingConfig?.plans_dir, DEFAULT_PLANS_DIR).map((dir) => path.relative(projectRoot, dir).split(path.sep).join("/")),
@@ -30,6 +30,7 @@ export function defaultWaypointConfig(options) {
30
30
  return {
31
31
  version: 1,
32
32
  profile: options.profile,
33
+ coding_agent: "codex",
33
34
  workspace_file: ".waypoint/WORKSPACE.md",
34
35
  docs_dirs: [".waypoint/docs"],
35
36
  plans_dirs: [".waypoint/plans"],
@@ -44,6 +45,7 @@ export function renderWaypointConfig(config) {
44
45
  const renderedConfig = {
45
46
  version: config.version,
46
47
  profile: config.profile,
48
+ coding_agent: config.coding_agent,
47
49
  workspace_file: config.workspace_file,
48
50
  docs_dirs: config.docs_dirs,
49
51
  plans_dirs: config.plans_dirs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waypoint-codex",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Make Codex better by default with stronger planning, code quality, reviews, tracking, and repo guidance.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -116,7 +116,7 @@ function renderPullRequestBlock(result, emptyMessage) {
116
116
  return result.stdout || emptyMessage;
117
117
  }
118
118
 
119
- const SESSION_DIR_NAMES = ["sessions", "archived_sessions"];
119
+ const CODEX_SESSION_DIR_NAMES = ["sessions", "archived_sessions"];
120
120
  const SECRET_PATTERNS = [
121
121
  /npm_[A-Za-z0-9]+/g,
122
122
  /github_pat_[A-Za-z0-9_]+/g,
@@ -132,6 +132,29 @@ function codexHome() {
132
132
  return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
133
133
  }
134
134
 
135
+ function piAgentHome() {
136
+ return process.env.PI_AGENT_HOME || path.join(os.homedir(), ".pi", "agent");
137
+ }
138
+
139
+ function loadCodingAgent(projectRoot) {
140
+ const configPath = path.join(projectRoot, ".waypoint", "config.toml");
141
+ if (!existsSync(configPath)) {
142
+ return "codex";
143
+ }
144
+
145
+ const configText = readFileSync(configPath, "utf8");
146
+ const match = configText.match(/^\s*coding_agent\s*=\s*"(codex|pi)"\s*$/m);
147
+ return match?.[1] || "codex";
148
+ }
149
+
150
+ function codingAgentLabel(codingAgent) {
151
+ return codingAgent === "pi" ? "Pi" : "Codex";
152
+ }
153
+
154
+ function codingAgentHome(codingAgent) {
155
+ return codingAgent === "pi" ? piAgentHome() : codexHome();
156
+ }
157
+
135
158
  function redactSecrets(text) {
136
159
  return SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, "[REDACTED]"), text);
137
160
  }
@@ -181,7 +204,7 @@ function collectSessionFiles(rootDir) {
181
204
  return files;
182
205
  }
183
206
 
184
- function extractMessageText(content) {
207
+ function extractCodexMessageText(content) {
185
208
  if (!Array.isArray(content)) {
186
209
  return "";
187
210
  }
@@ -192,6 +215,17 @@ function extractMessageText(content) {
192
215
  .trim();
193
216
  }
194
217
 
218
+ function extractPiMessageText(content) {
219
+ if (!Array.isArray(content)) {
220
+ return "";
221
+ }
222
+ return content
223
+ .filter((block) => block?.type === "text")
224
+ .map((block) => (typeof block?.text === "string" ? block.text : ""))
225
+ .join("")
226
+ .trim();
227
+ }
228
+
195
229
  function isBootstrapNoise(role, text) {
196
230
  return role === "user" && text.startsWith("# AGENTS.md instructions for ");
197
231
  }
@@ -213,11 +247,33 @@ function mergeConsecutiveTurns(turns) {
213
247
  return merged;
214
248
  }
215
249
 
216
- function parseSession(sessionFile, projectRoot) {
250
+ function finalizeParsedSession(sessionFile, projectRoot, sessionId, sessionCwd, sessionStartedAt, rawTurns, compactionBoundaries) {
251
+ if (!sessionCwd || !isWithinPath(sessionCwd, projectRoot)) {
252
+ return null;
253
+ }
254
+
255
+ const selectedFromPreCompaction = compactionBoundaries.length > 0;
256
+ const relevantTurns = selectedFromPreCompaction ? rawTurns.slice(0, compactionBoundaries.at(-1)) : rawTurns;
257
+ const turns = mergeConsecutiveTurns(relevantTurns);
258
+ if (turns.length === 0) {
259
+ return null;
260
+ }
261
+
262
+ return {
263
+ path: sessionFile,
264
+ sessionId,
265
+ sessionCwd,
266
+ turns,
267
+ compactionCount: compactionBoundaries.length,
268
+ selectedFromPreCompaction,
269
+ sessionStartedAt,
270
+ };
271
+ }
272
+
273
+ function parseCodexSession(sessionFile, projectRoot) {
217
274
  let sessionId = null;
218
275
  let sessionCwd = null;
219
276
  let sessionStartedAt = null;
220
- let compactionCount = 0;
221
277
  const rawTurns = [];
222
278
  const compactionBoundaries = [];
223
279
 
@@ -250,7 +306,6 @@ function parseSession(sessionFile, projectRoot) {
250
306
  }
251
307
 
252
308
  if (parsed.type === "compacted") {
253
- compactionCount += 1;
254
309
  compactionBoundaries.push(rawTurns.length);
255
310
  continue;
256
311
  }
@@ -264,7 +319,7 @@ function parseSession(sessionFile, projectRoot) {
264
319
  continue;
265
320
  }
266
321
 
267
- const text = redactSecrets(extractMessageText(parsed.payload?.content));
322
+ const text = redactSecrets(extractCodexMessageText(parsed.payload?.content));
268
323
  if (!text || isBootstrapNoise(role, text)) {
269
324
  continue;
270
325
  }
@@ -277,37 +332,90 @@ function parseSession(sessionFile, projectRoot) {
277
332
  });
278
333
  }
279
334
 
280
- if (!sessionCwd || !isWithinPath(sessionCwd, projectRoot)) {
281
- return null;
282
- }
335
+ return finalizeParsedSession(sessionFile, projectRoot, sessionId, sessionCwd, sessionStartedAt, rawTurns, compactionBoundaries);
336
+ }
283
337
 
284
- const selectedFromPreCompaction = compactionBoundaries.length > 0;
285
- const relevantTurns = selectedFromPreCompaction ? rawTurns.slice(0, compactionBoundaries.at(-1)) : rawTurns;
286
- const turns = mergeConsecutiveTurns(relevantTurns);
287
- if (turns.length === 0) {
288
- return null;
338
+ function parsePiSession(sessionFile, projectRoot) {
339
+ let sessionId = null;
340
+ let sessionCwd = null;
341
+ let sessionStartedAt = null;
342
+ const rawTurns = [];
343
+ const compactionBoundaries = [];
344
+
345
+ for (const line of readFileSync(sessionFile, "utf8").split("\n")) {
346
+ if (!line.trim()) {
347
+ continue;
348
+ }
349
+
350
+ let parsed;
351
+ try {
352
+ parsed = JSON.parse(line);
353
+ } catch {
354
+ continue;
355
+ }
356
+
357
+ if (parsed.type === "session") {
358
+ if (typeof parsed.id === "string") {
359
+ sessionId = parsed.id;
360
+ }
361
+ if (typeof parsed.cwd === "string") {
362
+ sessionCwd = parsed.cwd;
363
+ }
364
+ if (typeof parsed.timestamp === "string") {
365
+ sessionStartedAt = parsed.timestamp;
366
+ }
367
+ continue;
368
+ }
369
+
370
+ if (parsed.type === "compaction") {
371
+ compactionBoundaries.push(rawTurns.length);
372
+ continue;
373
+ }
374
+
375
+ if (parsed.type !== "message") {
376
+ continue;
377
+ }
378
+
379
+ const role = parsed.message?.role;
380
+ if (role !== "user" && role !== "assistant") {
381
+ continue;
382
+ }
383
+
384
+ const text = redactSecrets(extractPiMessageText(parsed.message?.content));
385
+ if (!text || isBootstrapNoise(role, text)) {
386
+ continue;
387
+ }
388
+
389
+ rawTurns.push({
390
+ role,
391
+ text,
392
+ timestamp: parsed.timestamp || parsed.message?.timestamp || null,
393
+ messageCount: 1,
394
+ });
289
395
  }
290
396
 
291
- return {
292
- path: sessionFile,
293
- sessionId,
294
- sessionCwd,
295
- turns,
296
- compactionCount,
297
- selectedFromPreCompaction,
298
- sessionStartedAt,
299
- };
397
+ return finalizeParsedSession(sessionFile, projectRoot, sessionId, sessionCwd, sessionStartedAt, rawTurns, compactionBoundaries);
300
398
  }
301
399
 
302
- function latestMatchingSession(projectRoot, threadIdOverride = null) {
400
+ function latestMatchingSession(projectRoot, codingAgent, threadIdOverride = null) {
303
401
  const matches = [];
304
- for (const dirName of SESSION_DIR_NAMES) {
305
- for (const sessionFile of collectSessionFiles(path.join(codexHome(), dirName))) {
306
- const parsed = parseSession(sessionFile, projectRoot);
402
+
403
+ if (codingAgent === "pi") {
404
+ for (const sessionFile of collectSessionFiles(path.join(piAgentHome(), "sessions"))) {
405
+ const parsed = parsePiSession(sessionFile, projectRoot);
307
406
  if (parsed) {
308
407
  matches.push(parsed);
309
408
  }
310
409
  }
410
+ } else {
411
+ for (const dirName of CODEX_SESSION_DIR_NAMES) {
412
+ for (const sessionFile of collectSessionFiles(path.join(codexHome(), dirName))) {
413
+ const parsed = parseCodexSession(sessionFile, projectRoot);
414
+ if (parsed) {
415
+ matches.push(parsed);
416
+ }
417
+ }
418
+ }
311
419
  }
312
420
 
313
421
  const requestedThreadId = threadIdOverride || process.env.CODEX_THREAD_ID || null;
@@ -331,7 +439,10 @@ function latestMatchingSession(projectRoot, threadIdOverride = null) {
331
439
 
332
440
  function writeRecentThread(contextDir, projectRoot, threadIdOverride = null) {
333
441
  const filePath = path.join(contextDir, "RECENT_THREAD.md");
334
- const snapshot = latestMatchingSession(projectRoot, threadIdOverride);
442
+ const codingAgent = loadCodingAgent(projectRoot);
443
+ const agentLabel = codingAgentLabel(codingAgent);
444
+ const agentHome = codingAgentHome(codingAgent);
445
+ const snapshot = latestMatchingSession(projectRoot, codingAgent, threadIdOverride);
335
446
  const generatedAt = new Date().toString();
336
447
 
337
448
  if (!snapshot) {
@@ -342,7 +453,7 @@ function writeRecentThread(contextDir, projectRoot, threadIdOverride = null) {
342
453
  "",
343
454
  `Generated by \`${path.relative(projectRoot, fileURLToPath(import.meta.url))}\` on ${generatedAt}.`,
344
455
  "",
345
- "No matching local Codex session was found for this repo yet.",
456
+ `No matching local ${agentLabel} session was found for this repo yet.`,
346
457
  "",
347
458
  ].join("\n"),
348
459
  "utf8"
@@ -358,10 +469,10 @@ function writeRecentThread(contextDir, projectRoot, threadIdOverride = null) {
358
469
  "",
359
470
  `Generated by \`${path.relative(projectRoot, fileURLToPath(import.meta.url))}\` on ${generatedAt}.`,
360
471
  "",
361
- `- Source session: \`${path.relative(codexHome(), snapshot.path)}\``,
472
+ `- Source session: \`${path.relative(agentHome, snapshot.path)}\``,
362
473
  `- Session cwd: \`${snapshot.sessionCwd}\``,
363
474
  `- Compactions in source session: ${snapshot.compactionCount}`,
364
- "- No compaction was found in the latest matching local Codex session, so there is nothing to restore into startup context yet.",
475
+ `- No compaction was found in the latest matching local ${agentLabel} session, so there is nothing to restore into startup context yet.`,
365
476
  "",
366
477
  ].join("\n"),
367
478
  "utf8"
@@ -370,7 +481,7 @@ function writeRecentThread(contextDir, projectRoot, threadIdOverride = null) {
370
481
  }
371
482
 
372
483
  const selectedTurns = snapshot.turns.slice(-MAX_RECENT_TURNS);
373
- const relSessionPath = path.relative(codexHome(), snapshot.path);
484
+ const relSessionPath = path.relative(agentHome, snapshot.path);
374
485
  const lines = [
375
486
  "# Recent Thread",
376
487
  "",
@@ -435,6 +546,8 @@ function main() {
435
546
 
436
547
  const docsIndexPath = writeDocsIndex(projectRoot);
437
548
  const { outputPath: tracksIndexPath, activeTracks } = writeTracksIndex(projectRoot);
549
+ const codingAgent = loadCodingAgent(projectRoot);
550
+ const codingAgentLabelText = codingAgentLabel(codingAgent);
438
551
 
439
552
  const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
440
553
  const currentLocalDatetime = new Date().toString();
@@ -577,7 +690,7 @@ function main() {
577
690
  `- \`${path.relative(projectRoot, uncommittedChangesPath)}\` — uncommitted change summary`,
578
691
  `- \`${path.relative(projectRoot, recentCommitsPath)}\` — recent commits`,
579
692
  `- \`${path.relative(projectRoot, prsPath)}\` — open and recently merged pull requests`,
580
- `- \`${path.relative(projectRoot, recentThreadPath)}\` — latest meaningful turns from the local Codex session for this repo`,
693
+ `- \`${path.relative(projectRoot, recentThreadPath)}\` — latest meaningful turns from the local ${codingAgentLabelText} session for this repo`,
581
694
  `- \`${path.relative(projectRoot, docsIndexPath)}\` — current docs index`,
582
695
  `- \`${path.relative(projectRoot, tracksIndexPath)}\` — current tracker index`,
583
696
  `- \`${path.relative(projectRoot, activeTrackersPath)}\` — active tracker summary`,