march-cli 0.1.16 → 0.1.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "March CLI — terminal-native coding agent with context reconstruction",
5
5
  "type": "module",
6
6
  "main": "./src/main.mjs",
@@ -0,0 +1,65 @@
1
+ import { getOpenAICodexWebSocketDebugStats } from "@earendil-works/pi-ai/openai-codex-responses";
2
+
3
+ export function getCodexTransportDebugSnapshot(session) {
4
+ if (!isCodexTransportDebugEnabled()) return null;
5
+ const sessionId = session?.sessionId;
6
+ return sessionId ? (getOpenAICodexWebSocketDebugStats(sessionId) ?? null) : null;
7
+ }
8
+
9
+ export function dumpCodexTransportDebug({ before, session, ui, logger }) {
10
+ if (!isCodexTransportDebugEnabled()) return;
11
+ const sessionId = session?.sessionId;
12
+ const after = sessionId ? (getOpenAICodexWebSocketDebugStats(sessionId) ?? null) : null;
13
+ const fields = formatCodexTransportDebugFields(sessionId, before, after);
14
+ logger?.event("codex.transport", fields);
15
+ writeCodexTransportDebug(ui, formatCodexTransportDebugLines(fields));
16
+ }
17
+
18
+ function isCodexTransportDebugEnabled() {
19
+ const value = process.env.MARCH_CODEX_TRANSPORT_DEBUG;
20
+ return value === "1" || value === "true" || value === "yes";
21
+ }
22
+
23
+ function formatCodexTransportDebugFields(sessionId, before, after) {
24
+ const delta = (key) => (after?.[key] ?? 0) - (before?.[key] ?? 0);
25
+ return {
26
+ sessionId: sessionId ?? "unknown",
27
+ requests: delta("requests"),
28
+ totalRequests: after?.requests ?? 0,
29
+ connectionsCreated: delta("connectionsCreated"),
30
+ connectionsReused: delta("connectionsReused"),
31
+ cachedContextRequests: delta("cachedContextRequests"),
32
+ storeTrueRequests: delta("storeTrueRequests"),
33
+ fullContextRequests: delta("fullContextRequests"),
34
+ deltaRequests: delta("deltaRequests"),
35
+ websocketFailures: delta("websocketFailures"),
36
+ sseFallbacks: delta("sseFallbacks"),
37
+ websocketFallbackActive: Boolean(after?.websocketFallbackActive),
38
+ lastInputItems: after?.lastInputItems ?? 0,
39
+ lastDeltaInputItems: after?.lastDeltaInputItems ?? 0,
40
+ lastWebSocketError: after?.lastWebSocketError ?? null,
41
+ hasStats: Boolean(after),
42
+ };
43
+ }
44
+
45
+ function formatCodexTransportDebugLines(fields) {
46
+ if (!fields.hasStats) return [`[codex-transport] sessionId=${fields.sessionId} no Codex WebSocket stats`];
47
+ return [
48
+ `[codex-transport] sessionId=${fields.sessionId}`,
49
+ ` requests=${fields.requests} totalRequests=${fields.totalRequests}`,
50
+ ` wsConnections created=${fields.connectionsCreated} reused=${fields.connectionsReused}`,
51
+ ` modes full=${fields.fullContextRequests} delta=${fields.deltaRequests} cached=${fields.cachedContextRequests} storeTrue=${fields.storeTrueRequests}`,
52
+ ` fallback websocketFailures=${fields.websocketFailures} sseFallbacks=${fields.sseFallbacks} active=${fields.websocketFallbackActive}`,
53
+ ` lastInputItems=${fields.lastInputItems} lastDeltaInputItems=${fields.lastDeltaInputItems}`,
54
+ ...(fields.lastWebSocketError ? [` lastWebSocketError=${fields.lastWebSocketError}`] : []),
55
+ ];
56
+ }
57
+
58
+ function writeCodexTransportDebug(ui, lines) {
59
+ if (ui?.debugLines) {
60
+ ui.debugLines(lines);
61
+ return;
62
+ }
63
+ if (!ui?.writeln) return;
64
+ for (const line of lines) ui.writeln(line);
65
+ }
@@ -5,14 +5,14 @@ import { createMarchLifecycleAdapter } from "../extensions/lifecycle-adapter.mjs
5
5
  import { syncPiSessionSidecar } from "../session/sidecar-sync.mjs";
6
6
  import { LspService } from "../lsp/service.mjs";
7
7
  import { formatLspServiceEvent } from "../lsp/status-message.mjs";
8
- import { formatRecallHints } from "../memory/markdown-store.mjs";
9
- import { appendProviderUserMessage, estimateProviderPayloadTokens, installModelPayloadDumper, replaceProviderContextMessages } from "./model-payload-dumper.mjs";
8
+ import { estimateProviderPayloadTokens, installModelPayloadDumper, replaceProviderContextMessages } from "./model-payload-dumper.mjs";
10
9
  import { resolveInitialModel, resolveRunnerSessionManager } from "./runner/runner-init.mjs";
11
10
  import { runRunnerCleanup } from "./runner/runner-cleanup.mjs";
12
11
  import { createRunnerRuntimeHost } from "./runtime/runner-runtime-host.mjs";
13
12
  import { createRuntimeUiBridge } from "./runtime/ui-event-bridge.mjs";
14
13
  import { getRunnerSessionStats, syncEngineSessionState } from "./runner/runner-session-state.mjs";
15
14
  import { notifyTurnEndBestEffort, notifyTurnEndDetached, providerContextToPayload } from "./runner/runner-utils.mjs";
15
+ import { dumpCodexTransportDebug, getCodexTransportDebugSnapshot } from "./runner/codex-transport-debug.mjs";
16
16
  import { resolveRunnerSessionOptions } from "./session/session-options.mjs";
17
17
  import { createSessionBinding } from "./session/session-binding.mjs";
18
18
  import { maybeAutoNameSession } from "./session/session-auto-name.mjs";
@@ -55,7 +55,6 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
55
55
  let currentModelCallKind = "model", currentTurnId = null, currentPromptForContext = "";
56
56
  let currentTurnContextMode = "rebuild";
57
57
  let nextTurnContextMode = "rebuild";
58
- let pendingMidTurnRecallHints = [];
59
58
  let lastNotificationResult = null, runtimeHost = null, lifecycleAdapter = null;
60
59
  let _currentFastEntry = null;
61
60
  if (useRuntimeHost) {
@@ -111,8 +110,8 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
111
110
  const contextMode = nextTurnContextMode;
112
111
  currentTurnContextMode = contextMode;
113
112
  nextTurnContextMode = "rebuild";
114
- pendingMidTurnRecallHints = [];
115
113
  const turnStartedAt = Date.now();
114
+ const codexTransportStatsBefore = getCodexTransportDebugSnapshot(sessionBinding.get());
116
115
  const turnLog = beginLoggedTurn({ logger, engine, modelId, provider, contextMode, userMessage, userRecallHints, startedAt: turnStartedAt }); currentTurnId = turnLog.turnId;
117
116
  try {
118
117
  const result = await runRunnerTurn({
@@ -121,7 +120,6 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
121
120
  setModelCallKind: (kind) => { currentModelCallKind = kind; },
122
121
  logger: turnLog.logger,
123
122
  setPhase: turnLog.setPhase,
124
- onMidTurnRecallHints: (hints) => { pendingMidTurnRecallHints.push(...hints); },
125
123
  syncCurrentPiSidecar,
126
124
  autoNameSession,
127
125
  contextMode,
@@ -144,6 +142,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
144
142
  turnLog.endError(err);
145
143
  throw err;
146
144
  } finally {
145
+ dumpCodexTransportDebug({ before: codexTransportStatsBefore, session: sessionBinding.get(), ui: runtimeUi, logger });
147
146
  currentTurnId = null;
148
147
  currentTurnContextMode = "rebuild";
149
148
  }
@@ -289,10 +288,6 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
289
288
  : replaceProviderContextMessages(payload, engine.buildProviderContext(currentPromptForContext));
290
289
  nextPayload = injectHostedTools(nextPayload, model, hostedTools);
291
290
  if (_currentFastEntry) nextPayload = { ...nextPayload, service_tier: "priority" };
292
- if (pendingMidTurnRecallHints.length > 0) {
293
- nextPayload = appendProviderUserMessage(nextPayload, formatRecallHints("assistant", pendingMidTurnRecallHints));
294
- pendingMidTurnRecallHints = [];
295
- }
296
291
  return nextPayload;
297
292
  }
298
293
  }
@@ -12,6 +12,7 @@ export function createRemoteRuntimeUiClient(peer) {
12
12
  retryStart: (event) => peer.notify("uiEvent", { type: "retry_start", ...event }),
13
13
  retryEnd: (event) => peer.notify("uiEvent", { type: "retry_end", ...event }),
14
14
  status: (text) => peer.notify("uiEvent", { type: "status", text }),
15
+ debugLines: (lines) => peer.notify("uiEvent", { type: "debug_lines", lines }),
15
16
  memoryHint: ({ source, hints }) => peer.notify("uiEvent", { type: "memory_hint", source, hints }),
16
17
  editDiff: (path, diffLines) => peer.notify("uiEvent", { type: "edit_diff", path, diffLines }),
17
18
  requestPermission: (request) => peer.call("uiRequest", { type: "permission_request", ...request }),
@@ -49,6 +49,7 @@ export function createRuntimeUiClient(eventBus) {
49
49
  retryStart: (event) => eventBus.emit({ type: "retry_start", ...event }),
50
50
  retryEnd: (event) => eventBus.emit({ type: "retry_end", ...event }),
51
51
  status: (text) => eventBus.emit({ type: "status", text }),
52
+ debugLines: (lines) => eventBus.emit({ type: "debug_lines", lines }),
52
53
  memoryHint: ({ source, hints }) => eventBus.emit({ type: "memory_hint", source, hints }),
53
54
  editDiff: (path, diffLines) => eventBus.emit({ type: "edit_diff", path, diffLines }),
54
55
  requestPermission: (request) => eventBus.request({ type: "permission_request", ...request }),
@@ -69,6 +70,7 @@ export function dispatchRuntimeUiEvent(ui, event) {
69
70
  case "retry_start": return ui.retryStart?.(pickRetryStart(event));
70
71
  case "retry_end": return ui.retryEnd?.(pickRetryEnd(event));
71
72
  case "status": return ui.status?.(event.text);
73
+ case "debug_lines": return writeDebugLines(ui, event.lines);
72
74
  case "memory_hint": return ui.memoryHint?.({ source: event.source, hints: event.hints });
73
75
  case "edit_diff": return ui.editDiff?.(event.path, event.diffLines);
74
76
  case "permission_request": return ui.requestPermission?.({ toolName: event.toolName, params: event.params, category: event.category });
@@ -76,6 +78,14 @@ export function dispatchRuntimeUiEvent(ui, event) {
76
78
  }
77
79
  }
78
80
 
81
+ function writeDebugLines(ui, lines) {
82
+ if (!Array.isArray(lines)) return undefined;
83
+ if (ui?.debugLines) return ui.debugLines(lines);
84
+ if (!ui?.writeln) return undefined;
85
+ for (const line of lines) ui.writeln(line);
86
+ return undefined;
87
+ }
88
+
79
89
  function pickRetryStart({ attempt, maxAttempts, delayMs, errorMessage }) {
80
90
  return { attempt, maxAttempts, delayMs, errorMessage };
81
91
  }
@@ -1,3 +1,4 @@
1
+ import { formatRecallHints } from "../../memory/markdown-store.mjs";
1
2
  import { resolveImageAttachmentReferences } from "../../session/attachment-references.mjs";
2
3
  import { closeAssistantReply, compactAssistantContext, createTurnEventState, handleRunnerSessionEvent } from "./turn-events.mjs";
3
4
 
@@ -13,7 +14,6 @@ export async function runRunnerTurn({
13
14
  setModelCallKind,
14
15
  logger = null,
15
16
  setPhase = null,
16
- onMidTurnRecallHints,
17
17
  syncCurrentPiSidecar,
18
18
  autoNameSession,
19
19
  contextMode = "rebuild",
@@ -41,7 +41,7 @@ export async function runRunnerTurn({
41
41
  const hints = flushAssistantRecall({ memoryStore, engine, turnState, currentProject });
42
42
  if (hints.length > 0) {
43
43
  midTurnRecallHints.push(...hints);
44
- onMidTurnRecallHints?.(hints);
44
+ queueMidTurnRecallHints(activeSession, hints, logger);
45
45
  ui.memoryHint?.({ source: "assistant", hints });
46
46
  }
47
47
  }
@@ -89,6 +89,20 @@ export async function runRunnerTurn({
89
89
  }
90
90
  }
91
91
 
92
+ function queueMidTurnRecallHints(session, hints, logger) {
93
+ const content = formatRecallHints("assistant", hints);
94
+ if (!content) return;
95
+ const injected = session.sendCustomMessage?.({
96
+ customType: "march.memory_hint",
97
+ content,
98
+ display: false,
99
+ details: { source: "assistant" },
100
+ }, { deliverAs: "steer" });
101
+ void injected?.catch?.((err) => {
102
+ logger?.debug("memory.mid_turn_recall.inject_failed", { errorMessage: err?.message ?? String(err) });
103
+ });
104
+ }
105
+
92
106
  function logSessionEvent(logger, event) {
93
107
  if (!logger) return;
94
108
  if (event.type === "message_update") {