pi-harness-runtime 1.1.250 → 1.1.254
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/index.ts +96 -48
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/dist/src/index.d.ts +1 -0
- package/packages/codex-watcher/dist/src/index.d.ts.map +1 -1
- package/packages/codex-watcher/dist/src/index.js +41 -17
- package/packages/codex-watcher/dist/src/index.js.map +1 -1
- package/packages/codex-watcher/dist/src/session.js +23 -29
- package/packages/codex-watcher/dist/src/session.js.map +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/codex-watcher/src/index.ts +45 -19
- package/packages/codex-watcher/src/session.ts +20 -33
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts +10 -0
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +18 -1
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/index.d.ts +1 -1
- package/packages/notification/dist/index.d.ts.map +1 -1
- package/packages/notification/dist/index.js +1 -1
- package/packages/notification/dist/index.js.map +1 -1
- package/packages/notification/dist/notification-center.d.ts +4 -0
- package/packages/notification/dist/notification-center.d.ts.map +1 -1
- package/packages/notification/dist/notification-center.js +10 -0
- package/packages/notification/dist/notification-center.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +29 -2
- package/packages/notification/src/index.ts +1 -1
- package/packages/notification/src/notification-center.ts +11 -0
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/index.ts
CHANGED
|
@@ -127,6 +127,14 @@ const startupStatus = new HarnessStartupStatus();
|
|
|
127
127
|
const harnessMessages = startupStatus.messages;
|
|
128
128
|
const addHarnessMessage = startupStatus.update.bind(startupStatus);
|
|
129
129
|
|
|
130
|
+
// Shared state for callback handlers (accessible across init functions)
|
|
131
|
+
const sharedState = {
|
|
132
|
+
waitingForUserSince: null as Date | null,
|
|
133
|
+
todoContinuationController: null as unknown,
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
updateAutoContinueStatus: null as any,
|
|
136
|
+
};
|
|
137
|
+
|
|
130
138
|
async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
131
139
|
addHarnessMessage("Jev", "initializing", "wait");
|
|
132
140
|
try {
|
|
@@ -190,6 +198,10 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
190
198
|
ERROR: "Auto-continue unavailable",
|
|
191
199
|
} as const;
|
|
192
200
|
|
|
201
|
+
// Wire shared state for callback handlers
|
|
202
|
+
sharedState.waitingForUserSince = waitingForUserSince;
|
|
203
|
+
sharedState.updateAutoContinueStatus = updateAutoContinueStatus;
|
|
204
|
+
|
|
193
205
|
// Update auto-continue status in TUI footer
|
|
194
206
|
function updateAutoContinueStatus(state: keyof typeof AC_STATE_MESSAGES, requestId?: string): void {
|
|
195
207
|
if (!_setAutoContinueStatus) return;
|
|
@@ -282,15 +294,26 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
282
294
|
return null;
|
|
283
295
|
}
|
|
284
296
|
|
|
285
|
-
// Detect routine continuation questions ("Should I continue?")
|
|
286
|
-
// These should NOT block immediate continuation - let Jev/todo controller decide
|
|
287
297
|
// Detect routine continuation questions ("Should I continue?")
|
|
288
298
|
// These should NOT block immediate continuation - let Jev/todo controller decide
|
|
289
299
|
function isRoutineContinuation(question: string): boolean {
|
|
290
300
|
const lower = question.toLowerCase();
|
|
291
|
-
// Match
|
|
292
|
-
|
|
293
|
-
|
|
301
|
+
// Match ALL routine continuation patterns
|
|
302
|
+
const routinePatterns = [
|
|
303
|
+
/\b(should i|should we|should the|can i continue|can we continue)\b/,
|
|
304
|
+
/\b(would you like me to continue|shall i proceed)\b/,
|
|
305
|
+
/\b(can i proceed|may i continue)\b/,
|
|
306
|
+
/\b(continue|proceed)\s*(with|to|on|with the next|to the next)\b/,
|
|
307
|
+
/\bshall\s+(i|we)\s+(proceed|continue)\b/,
|
|
308
|
+
/\bshould\s+(i|we)\s+(continue|proceed|move on)\b/,
|
|
309
|
+
/\b(continue|proceed)\s+\?/,
|
|
310
|
+
];
|
|
311
|
+
for (const pattern of routinePatterns) {
|
|
312
|
+
if (pattern.test(lower)) {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
294
317
|
}
|
|
295
318
|
|
|
296
319
|
// Detect when agent asks for continuation
|
|
@@ -329,19 +352,24 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
329
352
|
// Build rich context message
|
|
330
353
|
const waitMinutes = Math.round((Date.now() - waitingForUserSince!.getTime()) / 60000);
|
|
331
354
|
const taskInfo = taskContext.total > 0
|
|
332
|
-
? `
|
|
333
|
-
: "";
|
|
334
|
-
const contextInfo = `Waiting: ${waitMinutes}m${taskInfo}`;
|
|
355
|
+
? `Tasks: ${taskContext.completed}/${taskContext.total} done`
|
|
356
|
+
: "No active tasks";
|
|
357
|
+
const contextInfo = `Waiting: ${waitMinutes}m | ${taskInfo}`;
|
|
335
358
|
|
|
336
359
|
// Emit WaitingForUserInput to Telegram with Yes/No buttons
|
|
337
360
|
_nc?.notifyWithApproval("WaitingForUserInput", {
|
|
338
361
|
jobId: `waiting-user-${questionHash}`,
|
|
339
362
|
requirement: detectedQuestion,
|
|
340
|
-
error: contextInfo,
|
|
341
363
|
}, {
|
|
342
364
|
approveLabel: "Continue",
|
|
343
365
|
rejectLabel: "Not yet",
|
|
344
366
|
});
|
|
367
|
+
|
|
368
|
+
// Also send context info as a follow-up message (not in error field)
|
|
369
|
+
_nc?.notify("Info", {
|
|
370
|
+
jobId: `info-${questionHash}`,
|
|
371
|
+
message: contextInfo,
|
|
372
|
+
});
|
|
345
373
|
}
|
|
346
374
|
});
|
|
347
375
|
|
|
@@ -665,7 +693,6 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
665
693
|
const result = await todoContinuationController.decide();
|
|
666
694
|
|
|
667
695
|
if (result.action === "continue" && result.nextItemId) {
|
|
668
|
-
_steerSentThisTurn = true;
|
|
669
696
|
updateAutoContinueStatus("CONTINUING");
|
|
670
697
|
await todoContinuationController.steerToContinue(result.nextItemId);
|
|
671
698
|
// Queue remains set while agent processes, cleared on agent_end
|
|
@@ -863,7 +890,7 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
863
890
|
|
|
864
891
|
// Register callback handler for inline keyboard button clicks (approve/reject)
|
|
865
892
|
const { parseCallbackData, CallbackActions } = await import(
|
|
866
|
-
|
|
893
|
+
"./packages/notification/dist/telegram-webhook-handler.js"
|
|
867
894
|
);
|
|
868
895
|
center.setCallbackHandler(async (data: string, _query: { id: string }) => {
|
|
869
896
|
const parsed = parseCallbackData(data);
|
|
@@ -871,18 +898,31 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
871
898
|
|
|
872
899
|
switch (parsed.action) {
|
|
873
900
|
case CallbackActions.APPROVE:
|
|
874
|
-
//
|
|
875
|
-
logToFile("[TelegramCallback] Approved - user clicked Continue");
|
|
901
|
+
// User clicked Continue - clear waiting state and resume continuation
|
|
902
|
+
logToFile("[TelegramCallback] Approved - user clicked Continue, resuming work");
|
|
903
|
+
sharedState.waitingForUserSince = null;
|
|
904
|
+
sharedState.updateAutoContinueStatus?.("IDLE");
|
|
905
|
+
// Also resume continuation controller
|
|
906
|
+
(sharedState.todoContinuationController as any)?.resume?.();
|
|
876
907
|
break;
|
|
877
908
|
case CallbackActions.REJECT:
|
|
878
|
-
//
|
|
879
|
-
logToFile("[TelegramCallback] Rejected - user clicked Not yet");
|
|
909
|
+
// User clicked Not yet - just dismiss, don't resume
|
|
910
|
+
logToFile("[TelegramCallback] Rejected - user clicked Not yet, pausing");
|
|
911
|
+
// Explicitly pause the continuation
|
|
912
|
+
(sharedState.todoContinuationController as any)?.explicitPause?.();
|
|
880
913
|
break;
|
|
881
914
|
default:
|
|
882
915
|
logToFile("[TelegramCallback]", `Unknown action: ${parsed.action}`);
|
|
883
916
|
}
|
|
884
917
|
});
|
|
885
918
|
|
|
919
|
+
// Register message handler to forward Telegram messages to Pi
|
|
920
|
+
center.setMessageHandler(async (text: string, _chatId: string) => {
|
|
921
|
+
logToFile("[TelegramInbound]", `Forwarding message to Pi: "${text.slice(0, 50)}..."`);
|
|
922
|
+
// Forward the message to Pi as a user message
|
|
923
|
+
await pi.sendUserMessage(text, { deliverAs: "steer" });
|
|
924
|
+
});
|
|
925
|
+
|
|
886
926
|
// Wire to singleton nc() helper so all event emitters can reach Telegram
|
|
887
927
|
_nc = {
|
|
888
928
|
center,
|
|
@@ -1937,22 +1977,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
1937
1977
|
// Collect promise so we can wait for initialization before showing startup notification
|
|
1938
1978
|
asyncInitPromises.push(initTelegram(pi));
|
|
1939
1979
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1980
|
+
// --- codex-watcher: Inject Codex plans into pi.dev + Telegram --------------
|
|
1981
|
+
// Lightweight: polls ~/.codex/sessions/ every 5s, no LangChain daemon.
|
|
1982
|
+
// On plan detected: sends Telegram alert + steer message to pi.dev.
|
|
1983
|
+
try {
|
|
1984
|
+
// Track accepted and delivered ordinals per session
|
|
1985
|
+
// Accepted: we've seen this ordinal and are trying to deliver
|
|
1986
|
+
// Delivered: successfully sent to destination
|
|
1987
|
+
const sessionAcceptedOrdinals = new Map<string, number>();
|
|
1988
|
+
const sessionDeliveredOrdinals = new Map<string, number>();
|
|
1989
|
+
let currentSessionId: string | null = null;
|
|
1990
|
+
|
|
1991
|
+
const codexHandle = startWatcher({
|
|
1992
|
+
pollIntervalMs: 5000,
|
|
1993
|
+
maxIdlePolls: Infinity,
|
|
1994
|
+
onSessionChange: (session) => {
|
|
1950
1995
|
if (session) {
|
|
1951
1996
|
logToFile("[codex]", `Session started: ${session.threadName}`);
|
|
1997
|
+
currentSessionId = session.id;
|
|
1998
|
+
// Initialize ordinal tracking for new session if not tracked yet
|
|
1999
|
+
if (!sessionAcceptedOrdinals.has(session.id)) {
|
|
2000
|
+
sessionAcceptedOrdinals.set(session.id, 0);
|
|
2001
|
+
sessionDeliveredOrdinals.set(session.id, 0);
|
|
2002
|
+
}
|
|
1952
2003
|
nc("CodexSessionStarted", {
|
|
1953
2004
|
requirement: session.threadName || "Codex session",
|
|
1954
2005
|
taskTitle: session.id,
|
|
1955
2006
|
});
|
|
2007
|
+
} else {
|
|
2008
|
+
currentSessionId = null;
|
|
1956
2009
|
}
|
|
1957
2010
|
},
|
|
1958
2011
|
onMessages: (msgs) => {
|
|
@@ -1962,27 +2015,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
1962
2015
|
|
|
1963
2016
|
const classification = classifyMessage(msg.text);
|
|
1964
2017
|
if (classification !== "plan" && classification !== "mixed") continue;
|
|
1965
|
-
if (msg.ordinal <= latestPlanOrdinal) continue;
|
|
1966
2018
|
|
|
1967
|
-
|
|
2019
|
+
// Get session-specific ordinal tracking (use currentSessionId from watcher)
|
|
2020
|
+
const sessionId = currentSessionId;
|
|
2021
|
+
if (!sessionId) continue;
|
|
2022
|
+
|
|
2023
|
+
// Check if we've already delivered this ordinal to this session
|
|
2024
|
+
const deliveredOrdinal = sessionDeliveredOrdinals.get(sessionId) ?? 0;
|
|
2025
|
+
if (msg.ordinal <= deliveredOrdinal) continue;
|
|
1968
2026
|
|
|
1969
|
-
// Emit Telegram alert
|
|
2027
|
+
// Emit Telegram alert with preview (include more of the plan)
|
|
2028
|
+
const planPreview = msg.text.slice(0, 800) + (msg.text.length > 800 ? "\n... (full plan forwarded to Pi)" : "");
|
|
1970
2029
|
nc("CodexPlanDetected", {
|
|
1971
|
-
requirement:
|
|
2030
|
+
requirement: planPreview,
|
|
1972
2031
|
taskTitle: `Ordinal ${msg.ordinal}`,
|
|
1973
2032
|
});
|
|
1974
2033
|
|
|
1975
|
-
// Inject plan into pi.dev as a steer message
|
|
1976
|
-
const
|
|
2034
|
+
// Inject full plan into pi.dev as a steer message
|
|
2035
|
+
const planContent = `[Codex plan detected — ordinal ${msg.ordinal}]\n\n${msg.text}`;
|
|
1977
2036
|
try {
|
|
1978
|
-
pi.sendUserMessage(
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
${
|
|
1982
|
-
{ deliverAs: "steer" },
|
|
1983
|
-
);
|
|
2037
|
+
pi.sendUserMessage(planContent, { deliverAs: "steer" });
|
|
2038
|
+
// Mark as delivered only if Pi send succeeds
|
|
2039
|
+
sessionDeliveredOrdinals.set(sessionId, msg.ordinal);
|
|
2040
|
+
logToFile("[codex]", `Delivered ordinal ${msg.ordinal} to Pi`);
|
|
1984
2041
|
} catch {
|
|
1985
|
-
|
|
2042
|
+
// pi.dev may not be ready - log failure, will retry on next poll
|
|
2043
|
+
logToFile("[codex]", `Delivery failed for ordinal ${msg.ordinal}, will retry on next poll`);
|
|
1986
2044
|
}
|
|
1987
2045
|
}
|
|
1988
2046
|
},
|
|
@@ -2486,9 +2544,6 @@ Run \`bd ready\` to see current bd issues.
|
|
|
2486
2544
|
let outputLimitResumeAttempts = 0;
|
|
2487
2545
|
let pendingOutputLimitResumeAfterCompact = false;
|
|
2488
2546
|
let pendingOutputLimitResumeAfterSettled = false;
|
|
2489
|
-
|
|
2490
|
-
// Track if we sent a steer message - cleared when agent completes
|
|
2491
|
-
let _steerSentThisTurn = false;
|
|
2492
2547
|
let providerOverloadResumeAttempts = 0;
|
|
2493
2548
|
let providerOverloadResumeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2494
2549
|
|
|
@@ -3552,13 +3607,6 @@ Run \`bd ready\` to see current bd issues.
|
|
|
3552
3607
|
|
|
3553
3608
|
pi.on("agent_end", () => {
|
|
3554
3609
|
setTimeout(() => {
|
|
3555
|
-
// Clear steer queue when agent completes processing our steer message
|
|
3556
|
-
if (_steerSentThisTurn) {
|
|
3557
|
-
_steerSentThisTurn = false;
|
|
3558
|
-
todoContinuationController?.clearQueue();
|
|
3559
|
-
logToFile("[auto-continue]", "Agent turn complete, queue cleared");
|
|
3560
|
-
}
|
|
3561
|
-
|
|
3562
3610
|
if (!pendingOutputLimitResumeAfterSettled) {
|
|
3563
3611
|
return;
|
|
3564
3612
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.254",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,6 +37,7 @@ export declare function getVisibleMessages(rolloutPath: string, options?: {
|
|
|
37
37
|
* 1. Polls session_index.jsonl to detect active session changes
|
|
38
38
|
* 2. Tracks file position so it only reads new lines (no re-read of full file)
|
|
39
39
|
* 3. Calls onMessages with incremental messages each poll cycle
|
|
40
|
+
* 4. Preserves incomplete JSON lines until a complete newline-delimited record arrives
|
|
40
41
|
*/
|
|
41
42
|
export declare function startWatcher(options?: WatcherOptions): WatcherHandle;
|
|
42
43
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAE,eAAe,EAAoB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AAM7D,oFAAoF;AACpF,wBAAsB,kBAAkB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAMD
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAE,eAAe,EAAoB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AAM7D,oFAAoF;AACpF,wBAAsB,kBAAkB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,cAAmB,GAAG,aAAa,CAsLxE"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* });
|
|
18
18
|
* // later: handle.stop();
|
|
19
19
|
*/
|
|
20
|
-
import { existsSync, openSync, readFileSync,
|
|
20
|
+
import { existsSync, openSync, readFileSync, readSync, fstatSync, closeSync } from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { classifyMessage, parseNewMessages } from "./parser.js";
|
|
@@ -57,6 +57,7 @@ export async function getVisibleMessages(rolloutPath, options = {}) {
|
|
|
57
57
|
* 1. Polls session_index.jsonl to detect active session changes
|
|
58
58
|
* 2. Tracks file position so it only reads new lines (no re-read of full file)
|
|
59
59
|
* 3. Calls onMessages with incremental messages each poll cycle
|
|
60
|
+
* 4. Preserves incomplete JSON lines until a complete newline-delimited record arrives
|
|
60
61
|
*/
|
|
61
62
|
export function startWatcher(options = {}) {
|
|
62
63
|
const { pollIntervalMs = 2000, codexDir, onMessages, onSessionChange, } = options;
|
|
@@ -65,6 +66,8 @@ export function startWatcher(options = {}) {
|
|
|
65
66
|
let currentFd = null;
|
|
66
67
|
let currentPath = null;
|
|
67
68
|
let currentOrdinal = 0;
|
|
69
|
+
let currentBytePos = 0; // Track byte position we're at in the file
|
|
70
|
+
let pendingText = ""; // Preserve incomplete lines across reads
|
|
68
71
|
let pollCount = 0;
|
|
69
72
|
const maxIdlePolls = options.maxIdlePolls ?? Infinity;
|
|
70
73
|
// Track last known session to detect changes
|
|
@@ -73,12 +76,12 @@ export function startWatcher(options = {}) {
|
|
|
73
76
|
stopped = true;
|
|
74
77
|
if (currentFd !== null) {
|
|
75
78
|
try {
|
|
76
|
-
const { closeSync } = require("node:fs");
|
|
77
79
|
closeSync(currentFd);
|
|
78
80
|
}
|
|
79
81
|
catch {
|
|
80
82
|
// ignore
|
|
81
83
|
}
|
|
84
|
+
currentFd = null;
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
function isRunning() {
|
|
@@ -93,32 +96,52 @@ export function startWatcher(options = {}) {
|
|
|
93
96
|
function openRollout(path) {
|
|
94
97
|
if (currentFd !== null && currentPath !== path) {
|
|
95
98
|
try {
|
|
96
|
-
const { closeSync } = require("node:fs");
|
|
97
99
|
closeSync(currentFd);
|
|
98
100
|
}
|
|
99
101
|
catch {
|
|
100
102
|
// ignore
|
|
101
103
|
}
|
|
104
|
+
currentFd = null;
|
|
102
105
|
}
|
|
103
|
-
//
|
|
104
|
-
const stat = statSync(path);
|
|
106
|
+
// Open the file for reading
|
|
105
107
|
currentFd = openSync(path, "r");
|
|
106
108
|
currentPath = path;
|
|
107
109
|
currentOrdinal = 0;
|
|
108
|
-
|
|
110
|
+
currentBytePos = 0;
|
|
111
|
+
pendingText = ""; // Reset pending text for new file
|
|
112
|
+
return currentFd;
|
|
109
113
|
}
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Read new lines from the file, preserving incomplete records.
|
|
116
|
+
* Returns complete lines only; incomplete trailing data is kept for next read.
|
|
117
|
+
*/
|
|
118
|
+
function readNewLines(fd) {
|
|
112
119
|
const stat = fstatSync(fd);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
const fileSize = stat.size;
|
|
121
|
+
// Read from our tracked position to current end of file
|
|
122
|
+
const bytesToRead = fileSize - currentBytePos;
|
|
123
|
+
if (bytesToRead <= 0) {
|
|
124
|
+
return { lines: [], newBytePos: currentBytePos };
|
|
116
125
|
}
|
|
117
|
-
const buf = Buffer.alloc(
|
|
118
|
-
const bytesRead = readSync(fd, buf, 0, buf.length,
|
|
126
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
127
|
+
const bytesRead = readSync(fd, buf, 0, buf.length, currentBytePos);
|
|
119
128
|
const text = buf.subarray(0, bytesRead).toString("utf8");
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
// Prepend any pending text from previous incomplete read
|
|
130
|
+
const fullText = pendingText + text;
|
|
131
|
+
// Split by newline, keeping track of whether we ended with a complete line
|
|
132
|
+
const lines = fullText.split("\n");
|
|
133
|
+
// If text doesn't end with newline, last element is incomplete - save it
|
|
134
|
+
if (!text.endsWith("\n")) {
|
|
135
|
+
pendingText = lines.pop() ?? "";
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
pendingText = "";
|
|
139
|
+
}
|
|
140
|
+
// Filter out empty lines
|
|
141
|
+
const completeLines = lines.filter((l) => l.trim());
|
|
142
|
+
// Update our tracked position by bytes actually read
|
|
143
|
+
const newBytePos = currentBytePos + bytesRead;
|
|
144
|
+
return { lines: completeLines, newBytePos };
|
|
122
145
|
}
|
|
123
146
|
async function poll() {
|
|
124
147
|
if (stopped)
|
|
@@ -155,8 +178,9 @@ export function startWatcher(options = {}) {
|
|
|
155
178
|
}
|
|
156
179
|
return;
|
|
157
180
|
}
|
|
158
|
-
|
|
159
|
-
const { lines } = readNewLines(currentFd
|
|
181
|
+
// Read new lines from our tracked byte position
|
|
182
|
+
const { lines, newBytePos } = readNewLines(currentFd);
|
|
183
|
+
currentBytePos = newBytePos; // Update tracked position
|
|
160
184
|
if (lines.length > 0) {
|
|
161
185
|
pollCount = 0;
|
|
162
186
|
const messages = parseNewMessages(lines, currentOrdinal);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session discovery — reads ~/.codex/session_index.jsonl to find active sessions.
|
|
3
3
|
*/
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
/** Find the most recently active Codex session. */
|
|
@@ -60,46 +60,40 @@ function findRolloutPath(sessionId, codexDir) {
|
|
|
60
60
|
// The rollout path follows: sessions/YYYY/MM/DD/rollout-YYYY-MM-DDTHH-MM-SS-<sessionId>.jsonl
|
|
61
61
|
// We need to find the file whose name ends with <sessionId>.jsonl
|
|
62
62
|
// Since there can be multiple sessions per day, iterate all files.
|
|
63
|
-
try {
|
|
64
|
-
const { readdirSync } = require("node:fs");
|
|
65
|
-
for (const dirPath of walkDirs(sessionsDir)) {
|
|
66
|
-
for (const file of readdirSync(dirPath)) {
|
|
67
|
-
if (file.endsWith(`${sessionId}.jsonl`)) {
|
|
68
|
-
return join(dirPath, file);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// walkSync may not be available on all Node versions; fall back to find
|
|
75
|
-
}
|
|
76
|
-
return findRolloutPathFallback(sessionId, sessionsDir);
|
|
77
|
-
}
|
|
78
|
-
function findRolloutPathFallback(sessionId, sessionsDir) {
|
|
79
63
|
const found = walkForSession(sessionsDir, sessionId);
|
|
80
|
-
|
|
64
|
+
if (found)
|
|
65
|
+
return found;
|
|
66
|
+
return join(sessionsDir, "unknown", `${sessionId}.jsonl`);
|
|
81
67
|
}
|
|
82
68
|
/** Recursively walk all subdirectories, yielding each directory path. */
|
|
83
69
|
function* walkDirs(dir) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
70
|
+
try {
|
|
71
|
+
for (const entry of readdirSync(dir)) {
|
|
72
|
+
const full = join(dir, entry);
|
|
73
|
+
const stat = statSync(full);
|
|
74
|
+
if (stat.isDirectory()) {
|
|
75
|
+
yield full;
|
|
76
|
+
yield* walkDirs(full);
|
|
77
|
+
}
|
|
91
78
|
}
|
|
92
79
|
}
|
|
80
|
+
catch {
|
|
81
|
+
// Directory may not exist or be readable
|
|
82
|
+
}
|
|
93
83
|
}
|
|
94
84
|
/** Find the rollout file for a given session ID by walking directories. */
|
|
95
85
|
function walkForSession(sessionsDir, sessionId) {
|
|
96
|
-
const { readdirSync } = require("node:fs");
|
|
97
86
|
for (const dirPath of walkDirs(sessionsDir)) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
try {
|
|
88
|
+
for (const file of readdirSync(dirPath)) {
|
|
89
|
+
if (file.startsWith("rollout-") && file.endsWith(`${sessionId}.jsonl`)) {
|
|
90
|
+
return join(dirPath, file);
|
|
91
|
+
}
|
|
101
92
|
}
|
|
102
93
|
}
|
|
94
|
+
catch {
|
|
95
|
+
// Skip unreadable directories
|
|
96
|
+
}
|
|
103
97
|
}
|
|
104
98
|
return null;
|
|
105
99
|
}
|