@myagentroam/node 0.9.173 → 0.9.175

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.
@@ -231,6 +231,7 @@ export class CodexAppServerClient {
231
231
  startThread(cwd, configuration = {}) {
232
232
  return this.request('thread/start', {
233
233
  cwd,
234
+ experimentalRawEvents: true,
234
235
  ...codexThreadConfiguration(cwd, configuration)
235
236
  });
236
237
  }
@@ -8,6 +8,7 @@ import { parseSecretEnvironment } from '../../util/secret-environment.js';
8
8
  import { safeErrorCode } from '../../util/safe-error.js';
9
9
  import { runnerErrorProjection } from '../../util/runner-error.js';
10
10
  import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
11
+ import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
11
12
  export class ClaudeManagedRunController {
12
13
  runner;
13
14
  host;
@@ -164,12 +165,27 @@ export class ClaudeManagedRunController {
164
165
  try {
165
166
  const gate = new ClaudeChannelGate(sessionId);
166
167
  const responseLatency = new ResponseLatencyTracker();
168
+ const generationTiming = new GenerationTimingTracker();
169
+ const finalOutputTokens = new Map();
170
+ const pendingOutputTokens = new Map();
171
+ const completedMessages = new Set();
167
172
  let responseMessageId;
168
173
  const observeResponse = (id, incremental = false) => {
169
174
  const latencyMs = responseLatency.response(id, incremental);
170
175
  if (latencyMs !== undefined)
171
176
  this.host.emit(runId, 'run.performance', { latencyMs });
172
177
  };
178
+ const settleGeneration = (id, observedAt) => {
179
+ if (id === undefined)
180
+ return;
181
+ generationTiming.end(id, observedAt);
182
+ const outputTokens = finalOutputTokens.get(id);
183
+ if (outputTokens === undefined)
184
+ return;
185
+ const result = generationTiming.settle(id, outputTokens, observedAt);
186
+ if (result !== undefined)
187
+ this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
188
+ };
173
189
  handle = this.runner.startChannel({
174
190
  prompt: payload.input,
175
191
  cwd,
@@ -200,9 +216,14 @@ export class ClaudeManagedRunController {
200
216
  }))
201
217
  }),
202
218
  onPermission: (toolName, toolInput, options) => {
203
- const latencyMs = responseLatency.toolStarted(options.toolUseID);
204
- if (latencyMs !== undefined)
205
- this.host.emit(runId, 'run.performance', { latencyMs });
219
+ if (options.agentID === undefined) {
220
+ const receiptAt = generationTiming.timestamp();
221
+ if (responseMessageId !== undefined)
222
+ generationTiming.pause(responseMessageId, receiptAt);
223
+ const latencyMs = responseLatency.toolStarted(options.toolUseID);
224
+ if (latencyMs !== undefined)
225
+ this.host.emit(runId, 'run.performance', { latencyMs });
226
+ }
206
227
  return toolName === 'AskUserQuestion'
207
228
  ? (toolKinds.set(options.toolUseID, 'user_input_request'),
208
229
  this.host.requestUserInput(runId, toolInput, options.toolUseID))
@@ -212,6 +233,7 @@ export class ClaudeManagedRunController {
212
233
  onMessage: (message) => {
213
234
  if (terminal)
214
235
  return;
236
+ const receiptAt = generationTiming.timestamp();
215
237
  const record = message;
216
238
  const nativeSessionId = typeof record.session_id === 'string' ? record.session_id : undefined;
217
239
  const current = this.runner.execution.runs.get(runId);
@@ -236,7 +258,8 @@ export class ClaudeManagedRunController {
236
258
  });
237
259
  }
238
260
  }
239
- if (record.type === 'stream_event') {
261
+ const mainTask = record.parent_tool_use_id === undefined || record.parent_tool_use_id === null;
262
+ if (record.type === 'stream_event' && mainTask) {
240
263
  // Observe semantic timing without forwarding raw thinking or input fragments.
241
264
  const event = record.event;
242
265
  const block = event?.content_block;
@@ -245,14 +268,49 @@ export class ClaudeManagedRunController {
245
268
  const message = event.message;
246
269
  responseMessageId = typeof message?.id === 'string' ? message.id : undefined;
247
270
  }
271
+ if (event?.type === 'message_delta') {
272
+ const usage = event.usage;
273
+ if (responseMessageId !== undefined) {
274
+ if (typeof usage?.output_tokens === 'number')
275
+ pendingOutputTokens.set(responseMessageId, usage.output_tokens);
276
+ if (typeof delta?.stop_reason === 'string' && delta.stop_reason.length > 0) {
277
+ const tokens = pendingOutputTokens.get(responseMessageId);
278
+ if (tokens !== undefined)
279
+ finalOutputTokens.set(responseMessageId, tokens);
280
+ completedMessages.add(responseMessageId);
281
+ settleGeneration(responseMessageId, receiptAt);
282
+ }
283
+ }
284
+ }
285
+ if (event?.type === 'message_stop') {
286
+ if (responseMessageId !== undefined) {
287
+ completedMessages.add(responseMessageId);
288
+ if (!finalOutputTokens.has(responseMessageId)) {
289
+ const tokens = pendingOutputTokens.get(responseMessageId);
290
+ if (tokens !== undefined)
291
+ finalOutputTokens.set(responseMessageId, tokens);
292
+ }
293
+ }
294
+ settleGeneration(responseMessageId, receiptAt);
295
+ }
248
296
  if (event?.type === 'content_block_start' &&
249
- block?.type === 'tool_use' &&
250
- typeof block.id === 'string') {
251
- const latencyMs = responseLatency.toolStarted(block.id);
252
- if (latencyMs !== undefined)
253
- this.host.emit(runId, 'run.performance', { latencyMs });
254
- // A later full snapshot of this streamed message is not a new response.
255
- responseLatency.response(responseMessageId);
297
+ (block?.type === 'tool_use' ||
298
+ block?.type === 'thinking' ||
299
+ block?.type === 'redacted_thinking' ||
300
+ block?.type === 'encrypted_thinking' ||
301
+ block?.type === 'text')) {
302
+ if (responseMessageId !== undefined)
303
+ generationTiming.start(responseMessageId, receiptAt);
304
+ if (block?.type !== 'text' ||
305
+ (typeof block.text === 'string' && block.text.length > 0)) {
306
+ const latencyMs = block?.type === 'tool_use' && typeof block.id === 'string'
307
+ ? responseLatency.toolStarted(block.id)
308
+ : responseLatency.response(responseMessageId);
309
+ if (latencyMs !== undefined)
310
+ this.host.emit(runId, 'run.performance', { latencyMs });
311
+ if (block?.type === 'tool_use')
312
+ responseLatency.response(responseMessageId);
313
+ }
256
314
  }
257
315
  if ((event?.type === 'content_block_start' && block?.type === 'thinking') ||
258
316
  (event?.type === 'content_block_delta' &&
@@ -264,12 +322,27 @@ export class ClaudeManagedRunController {
264
322
  delta.thinking.length > 0) ||
265
323
  (delta?.type === 'input_json_delta' &&
266
324
  typeof delta.partial_json === 'string' &&
267
- delta.partial_json.length > 0))))
325
+ delta.partial_json.length > 0)))) {
326
+ if (responseMessageId !== undefined)
327
+ generationTiming.start(responseMessageId, receiptAt);
268
328
  observeResponse(responseMessageId, event?.type === 'content_block_delta');
329
+ }
269
330
  }
270
331
  if (record.type === 'assistant') {
332
+ const snapshotMessage = record.message;
333
+ const snapshotId = typeof snapshotMessage?.id === 'string' ? snapshotMessage.id : undefined;
334
+ const snapshotUsage = snapshotMessage?.usage;
335
+ if (mainTask &&
336
+ snapshotId !== undefined &&
337
+ typeof snapshotUsage?.output_tokens === 'number' &&
338
+ (completedMessages.has(snapshotId) ||
339
+ (typeof snapshotMessage?.stop_reason === 'string' &&
340
+ snapshotMessage.stop_reason.length > 0))) {
341
+ finalOutputTokens.set(snapshotId, snapshotUsage.output_tokens);
342
+ settleGeneration(snapshotId, receiptAt);
343
+ }
271
344
  const text = extractClaudeText(record);
272
- if (text.length > 0 || extractClaudeThinkingSummaries(record).length > 0)
345
+ if (mainTask && (text.length > 0 || extractClaudeThinkingSummaries(record).length > 0))
273
346
  observeResponse(typeof record.message?.id === 'string'
274
347
  ? record.message.id
275
348
  : undefined);
@@ -292,7 +365,7 @@ export class ClaudeManagedRunController {
292
365
  });
293
366
  }
294
367
  for (const tool of extractClaudeToolUses(record)) {
295
- const latencyMs = responseLatency.toolStarted(tool.id);
368
+ const latencyMs = mainTask ? responseLatency.toolStarted(tool.id) : undefined;
296
369
  if (latencyMs !== undefined)
297
370
  this.host.emit(runId, 'run.performance', { latencyMs });
298
371
  if (tool.name === 'AskUserQuestion') {
@@ -358,7 +431,8 @@ export class ClaudeManagedRunController {
358
431
  }
359
432
  if (record.type === 'user') {
360
433
  for (const result of extractClaudeToolResults(record)) {
361
- responseLatency.toolCompleted(result.toolUseId);
434
+ if (mainTask)
435
+ responseLatency.toolCompleted(result.toolUseId);
362
436
  const kind = toolKinds.get(result.toolUseId) ?? 'tool_call';
363
437
  if (kind === 'user_input_request')
364
438
  continue;
@@ -4,6 +4,7 @@ import { parseSecretEnvironment } from '../../util/secret-environment.js';
4
4
  import { safeErrorCode } from '../../util/safe-error.js';
5
5
  import { runnerErrorProjection } from '../../util/runner-error.js';
6
6
  import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
7
+ import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
7
8
  import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
8
9
  export function resolveCodexNotificationRun(runs, input) {
9
10
  const entries = [...runs.entries()];
@@ -137,6 +138,7 @@ export class CodexManagedRunController {
137
138
  if (run === undefined)
138
139
  return;
139
140
  const [runId, activeRun] = run;
141
+ const receiptAt = activeRun.generationTiming?.timestamp();
140
142
  if (notification.method === 'turn/started' && turnId !== undefined) {
141
143
  const active = this.runner.execution.runs.get(runId);
142
144
  if (active !== undefined)
@@ -144,6 +146,16 @@ export class CodexManagedRunController {
144
146
  this.host.emit(runId, 'run.running', { threadId, turnId }, 'RUNNING');
145
147
  return;
146
148
  }
149
+ if (notification.method === 'rawResponseItem/completed')
150
+ return;
151
+ if (notification.method === 'rawResponse/completed') {
152
+ this.completeRawResponse(runId, params, activeRun, receiptAt);
153
+ return;
154
+ }
155
+ if (notification.method === 'thread/tokenUsage/updated') {
156
+ this.completeStandardUsage(runId, params, activeRun, receiptAt);
157
+ return;
158
+ }
147
159
  if (notification.method === 'thread/goal/updated' &&
148
160
  activeRun.goalMcp === true &&
149
161
  turnId !== undefined &&
@@ -168,6 +180,7 @@ export class CodexManagedRunController {
168
180
  if (notification.requestId !== undefined &&
169
181
  (notification.method === 'item/commandExecution/requestApproval' ||
170
182
  notification.method === 'item/fileChange/requestApproval')) {
183
+ this.pauseGeneration(runId, receiptAt);
171
184
  this.requestApproval(runId, {
172
185
  ...notification,
173
186
  requestId: notification.requestId
@@ -176,6 +189,7 @@ export class CodexManagedRunController {
176
189
  }
177
190
  if (notification.requestId !== undefined &&
178
191
  notification.method === 'item/tool/requestUserInput') {
192
+ this.pauseGeneration(runId, receiptAt);
179
193
  this.requestUserInput(runId, notification.requestId, params);
180
194
  return;
181
195
  }
@@ -200,15 +214,19 @@ export class CodexManagedRunController {
200
214
  text: params.delta,
201
215
  ...(itemId === undefined ? {} : { itemId })
202
216
  });
217
+ if (params.delta.length > 0)
218
+ this.beginGeneration(runId, receiptAt);
203
219
  if (params.delta.length > 0)
204
220
  this.observeResponse(runId, itemId, true);
205
221
  return;
206
222
  }
207
223
  if (notification.method === 'item/reasoning/textDelta' && typeof params.delta === 'string') {
208
- if (params.delta.length > 0)
224
+ if (params.delta.length > 0) {
225
+ this.beginGeneration(runId, receiptAt);
209
226
  this.observeResponse(runId, typeof params.itemId === 'string'
210
227
  ? boundedConversationItemId('codex', params.itemId)
211
228
  : undefined, true);
229
+ }
212
230
  return;
213
231
  }
214
232
  if (notification.method === 'item/reasoning/summaryTextDelta' &&
@@ -225,8 +243,10 @@ export class CodexManagedRunController {
225
243
  merge: true,
226
244
  append: 'sections'
227
245
  });
228
- if (params.delta.length > 0)
246
+ if (params.delta.length > 0) {
247
+ this.beginGeneration(runId, receiptAt);
229
248
  this.observeResponse(runId, boundedConversationItemId('codex', params.itemId), true);
249
+ }
230
250
  return;
231
251
  }
232
252
  if (notification.method === 'item/commandExecution/outputDelta' &&
@@ -260,9 +280,25 @@ export class CodexManagedRunController {
260
280
  const item = codexLiveConversationItem(params.item, notification.method === 'item/completed', attachments);
261
281
  if (item !== undefined) {
262
282
  this.host.emitItem(runId, item);
283
+ if (item.kind === 'context_compaction') {
284
+ activeRun.generationCompacting = notification.method === 'item/started';
285
+ if (activeRun.generationCompacting) {
286
+ for (const key of activeRun.generationKeys ?? [])
287
+ activeRun.generationTiming?.discard(key);
288
+ activeRun.generationKeys = [];
289
+ activeRun.currentGenerationKey = undefined;
290
+ }
291
+ return;
292
+ }
263
293
  const tracker = activeRun.responseLatency;
264
294
  const tool = ['tool_call', 'command_execution', 'file_change'].includes(item.kind);
265
295
  if (tool) {
296
+ if (notification.method === 'item/started') {
297
+ if (codexToolGeneratesArguments(params.item))
298
+ this.beginGeneration(runId, receiptAt);
299
+ else
300
+ this.pauseGeneration(runId, receiptAt);
301
+ }
266
302
  if (notification.method === 'item/completed')
267
303
  tracker?.toolCompleted(item.itemId);
268
304
  else {
@@ -273,12 +309,15 @@ export class CodexManagedRunController {
273
309
  }
274
310
  else if (['assistant_message', 'reasoning_summary', 'plan'].includes(item.kind)) {
275
311
  // A started reasoning item is itself a semantic response, even before its summary.
312
+ if (notification.method === 'item/started')
313
+ this.beginGeneration(runId, receiptAt);
276
314
  this.observeResponse(runId, item.itemId);
277
315
  }
278
316
  }
279
317
  return;
280
318
  }
281
319
  if (notification.method === 'turn/completed') {
320
+ this.endPendingGenerations(activeRun, receiptAt);
282
321
  const status = turn?.status;
283
322
  const terminal = status === 'completed' ? 'SUCCEEDED' : status === 'interrupted' ? 'INTERRUPTED' : 'FAILED';
284
323
  if (terminal === 'FAILED')
@@ -308,6 +347,87 @@ export class CodexManagedRunController {
308
347
  if (latencyMs !== undefined)
309
348
  this.host.emit(runId, 'run.performance', { latencyMs });
310
349
  }
350
+ beginGeneration(runId, observedAt) {
351
+ const active = this.runner.execution.runs.get(runId);
352
+ if (active?.generationTiming === undefined || active.generationCompacting === true)
353
+ return;
354
+ let key = active.currentGenerationKey;
355
+ if (key === undefined) {
356
+ const sequence = (active.generationRequestSequence ?? 0) + 1;
357
+ key = `${active.turnId ?? active.threadId}:generation:${sequence}`;
358
+ active.generationKeys ??= [];
359
+ active.generationKeys.push(key);
360
+ active.generationRequestSequence = sequence;
361
+ active.currentGenerationKey = key;
362
+ }
363
+ active.generationTiming.start(key, observedAt);
364
+ }
365
+ pauseGeneration(runId, observedAt) {
366
+ const active = this.runner.execution.runs.get(runId);
367
+ const key = active?.currentGenerationKey;
368
+ if (active?.generationTiming === undefined || key === undefined)
369
+ return;
370
+ active.generationTiming.pause(key, observedAt);
371
+ }
372
+ endPendingGenerations(active, observedAt) {
373
+ if (active.generationTiming === undefined)
374
+ return;
375
+ for (const key of active.generationKeys ?? [])
376
+ active.generationTiming.end(key, observedAt);
377
+ }
378
+ completeRawResponse(runId, params, active, observedAt) {
379
+ const responseId = typeof params.responseId === 'string' ? params.responseId : undefined;
380
+ if (responseId === undefined || active.completedGenerationResponseIds?.has(responseId))
381
+ return;
382
+ active.completedGenerationResponseIds ??= new Set();
383
+ active.completedGenerationResponseIds.add(responseId);
384
+ if (active.generationCompacting === true)
385
+ return;
386
+ const key = active.generationResponseIds?.get(responseId) ??
387
+ active.currentGenerationKey ??
388
+ active.generationKeys?.find((candidate) => active.generationTiming?.has(candidate));
389
+ if (key === undefined || active.generationTiming === undefined)
390
+ return;
391
+ active.generationResponseIds ??= new Map();
392
+ active.generationResponseIds.set(responseId, key);
393
+ active.generationTiming.end(key, observedAt);
394
+ if (active.currentGenerationKey === key)
395
+ active.currentGenerationKey = undefined;
396
+ const outputTokens = codexOutputTokens(params.usage);
397
+ if (outputTokens === undefined)
398
+ return;
399
+ this.emitGenerationTps(runId, active, key, outputTokens, observedAt);
400
+ }
401
+ completeStandardUsage(runId, params, active, observedAt) {
402
+ if (active.generationTiming === undefined)
403
+ return;
404
+ const usage = isPlainRecord(params.tokenUsage) ? params.tokenUsage : undefined;
405
+ const outputTokens = codexOutputTokens(usage?.last);
406
+ if (outputTokens === undefined)
407
+ return;
408
+ // TokenCount also repeats the previous request's snapshot (for example after compaction).
409
+ // Include total counters to distinguish equal last counts from distinct model requests.
410
+ const snapshot = JSON.stringify([
411
+ codexUsageCounters(usage?.last),
412
+ codexUsageCounters(usage?.total)
413
+ ]);
414
+ if (active.generationUsageSnapshot === snapshot)
415
+ return;
416
+ active.generationUsageSnapshot = snapshot;
417
+ if (active.generationCompacting === true)
418
+ return;
419
+ const key = active.generationKeys?.find((candidate) => active.generationTiming?.has(candidate));
420
+ if (key !== undefined)
421
+ this.emitGenerationTps(runId, active, key, outputTokens, observedAt);
422
+ }
423
+ emitGenerationTps(runId, active, key, outputTokens, observedAt) {
424
+ const result = active.generationTiming?.settle(key, outputTokens, observedAt);
425
+ if (result !== undefined)
426
+ this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
427
+ active.generationKeys = active.generationKeys?.filter((candidate) => candidate !== key);
428
+ if (active.currentGenerationKey === key)
429
+ active.currentGenerationKey = undefined;
430
+ }
311
431
  requestApproval(runId, notification) {
312
432
  const approvalId = `codex:${runId}:${notification.requestId}`;
313
433
  const expiresAt = Date.now() + 10 * 60_000;
@@ -494,7 +614,11 @@ export class CodexManagedRunController {
494
614
  cwd,
495
615
  goalMcp: hasGoalMcp(payload.mcpServers),
496
616
  scheduleMcp: hasScheduleMcp(payload.mcpServers),
497
- responseLatency: new ResponseLatencyTracker()
617
+ responseLatency: new ResponseLatencyTracker(),
618
+ generationTiming: new GenerationTimingTracker(),
619
+ generationKeys: [],
620
+ generationResponseIds: new Map(),
621
+ completedGenerationResponseIds: new Set()
498
622
  });
499
623
  turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments, runId);
500
624
  break;
@@ -560,3 +684,25 @@ function hasGoalMcp(value) {
560
684
  function hasScheduleMcp(value) {
561
685
  return isPlainRecord(value) && isPlainRecord(value.mar_schedule);
562
686
  }
687
+ function codexOutputTokens(value) {
688
+ if (!isPlainRecord(value) ||
689
+ typeof value.outputTokens !== 'number' ||
690
+ !Number.isSafeInteger(value.outputTokens) ||
691
+ value.outputTokens < 0)
692
+ return undefined;
693
+ return value.outputTokens;
694
+ }
695
+ function codexToolGeneratesArguments(value) {
696
+ return ['functionCall', 'function_call', 'customToolCall', 'custom_tool_call'].includes(typeof value.type === 'string' ? value.type : '');
697
+ }
698
+ function codexUsageCounters(value) {
699
+ if (!isPlainRecord(value))
700
+ return undefined;
701
+ return [
702
+ value.inputTokens,
703
+ value.cachedInputTokens,
704
+ value.outputTokens,
705
+ value.reasoningOutputTokens,
706
+ value.totalTokens
707
+ ];
708
+ }
@@ -1,4 +1,5 @@
1
1
  import { buildCompactEvidence, countModelTokens, generateModelText, MarAgentError } from '@myagentroam/agent';
2
+ import { highDensityCompactionPrompt } from './compact-prompt.js';
2
3
  import { marAgentRuntimeCompactConfigurationSchema } from '@myagentroam/protocol';
3
4
  /** Resolve optional dedicated credentials in the same factory batch without widening the SDK catalog. */
4
5
  export function compactRuntimeConfigurations(models, raw) {
@@ -59,7 +60,7 @@ export function marAgentCompactExtensionSource(state) {
59
60
  export async function generateMarAgentCompact(context, selected, signal) {
60
61
  signal.throwIfAborted();
61
62
  const startedAt = Date.now();
62
- const instruction = context.getPrompt();
63
+ const instruction = highDensityCompactionPrompt();
63
64
  const overhead = countModelTokens(system) + countModelTokens(instruction) + 128;
64
65
  const full = buildCompactEvidence(context.messages, {
65
66
  maxTokens: Number.MAX_SAFE_INTEGER,
@@ -0,0 +1,56 @@
1
+ export function highDensityCompactionPrompt() {
2
+ return `Compile the visible conversation into Textual Continuation IR that lets the next agent continue correctly after the transcript is discarded. Transfer state; do not recap, address the user, call tools, advance the task, or invent facts.
3
+
4
+ Optimize for continuation fidelity first and density second. Prefer sufficient continuation state over brevity; never omit information required to resume correctly.
5
+
6
+ ## Output contract
7
+ Use these English section names when applicable; omit empty sections:
8
+ 1. Goal contract
9
+ 2. Current state
10
+ 3. Settled matters
11
+ 4. Necessary evidence
12
+ 5. Open loops
13
+
14
+ IR grammar and vocabulary are English: headings, keys, enums, state atoms, and control terms. Natural-language values may follow the task's dominant language. Preserve exact user-designated text and established domain terms; do not translate identifiers or invent localized IR terminology.
15
+
16
+ Prefer records over prose:
17
+ <object> | <key>=<value> | <key>=<value>
18
+ <event/condition> -> <object>.<key>=<value> | <effect>
19
+
20
+ Use | for parallel facts, = for current state, and -> only for causality, invalidation, or transition. Keep enums lowercase and semantic: required, forbidden, pending; never YES/NO. Omit empty or irrelevant unknown fields. Keep pending, not run, unverified, and failed distinct. Use a sentence only when records cannot express a multi-object cause, conflict, authorization, or safety boundary unambiguously.
21
+
22
+ Examples:
23
+ src/discount.js | cause=verified | edit=pending | tests=not run
24
+ patch(src/discount.js) -> prior_read=stale | next=re-evidence
25
+ authorization | action=<exact> | scope=<exact> | lifetime=<exact> | status=active
26
+
27
+ ## Compiler
28
+ 1. Resolve intent. The latest user correction overrides older intent. Extract the primary objective, current deliverable, completion criteria, non-goals, and boundaries.
29
+ 2. Reduce time. Convert before -> event -> after into the latest evidenced state. Source mutation or base/branch change makes only affected evidence stale. History replacement invalidates Responses continuation, not factual evidence. Without an invalidating event, a verified claim remains current.
30
+ 3. Build objects. Emit one current record per file, symbol, feature, migration, test, worktree axis, selected Skill, Todo/plan item, external decision, or external side effect. Deduplicate equivalent facts.
31
+ 4. Separate status axes when needed:
32
+ - completion=pending|in_progress|completed|blocked
33
+ - epistemic=observed|verified|inferred|proposed|disproved
34
+ - freshness=current|stale|superseded
35
+ - scope=turn|worktree|base|branch|external
36
+ 5. Bind evidence to the claim it proves. Prefer a verified conclusion over raw snapshots or intermediate hypotheses.
37
+ 6. Select under budget. Protect, in order: objective, progress, boundaries and authorization, non-repeatable side effects, open loops, then necessary evidence. Remove provenance and recoverable detail only after those remain sufficient.
38
+
39
+ ## Retention rules
40
+ - Preserve active caller/user instructions outside filesystem-loaded AGENTS.md, exact authorization boundaries, confirmed decisions and exclusions, completed edits and side effects, verification actually run, reproducible failures, failed approaches that constrain the next step, unresolved risks, and the smallest established next action.
41
+ - Authorization is valid only for its exact action, scope, and lifetime in the active request. Completed or superseded scopes cannot revive it. Object state, completion, tool availability, and convenience never imply permission.
42
+ - Preserve decisive values, or an exact artifact path/command that deterministically recovers them, when a result controls later work. Counts or schemas alone are insufficient.
43
+ - Preserve paths, symbols, APIs, tools, models, providers, JSON keys, protocol fields, commands, arguments, error codes, commit hashes, call IDs, sequences, versions, numbers, and user-designated text verbatim.
44
+ - Preserve selected Skill names with only continuation-critical constraints. Preserve Todo/plan revision, item/status, and current minimum step.
45
+ - Keep observed HEAD, validated base, target commit, and current worktree as separate axes. Evidence updates only what it directly proves; cross-axis, cross-base, and cross-branch inference remains unverified.
46
+ - If evidence conflicts, use state=conflict | candidates=<short values> | next=re-evidence. Never choose a side without evidence.
47
+
48
+ ## Exclusions
49
+ Remove greetings, progress chatter, chronology that does not affect current state, duplicate facts, superseded hypotheses without remaining constraints, long logs, source bodies, verbose payloads, cheap-to-recover content, local-only integrity hashes, hidden reasoning, secrets, credentials, and unrelated content.
50
+
51
+ Remove user/project/directory AGENTS.md text and constraints derived only from those files; the next Execution reloads them. Remove retained process/Subagent inventory, IDs, status, commands, cwd, and reuse instructions; Runtime reloads them.
52
+
53
+ Before emitting, verify that objective, current progress, authorization/boundaries, completed versus unverified work, decisive evidence, open loops, and next action are explicit and non-contradictory.
54
+
55
+ Output only compact Markdown IR.`;
56
+ }
@@ -832,7 +832,7 @@ export class MarAgentManagedRunController {
832
832
  this.host.refreshContext?.(runId);
833
833
  if (event.type === 'model.performance')
834
834
  this.host.emit(runId, 'run.performance', {
835
- latencyMs: event.latencyMs,
835
+ ...(event.latencyMs === undefined ? {} : { latencyMs: event.latencyMs }),
836
836
  ...(event.tokensPerSecond === undefined ? {} : { tokensPerSecond: event.tokensPerSecond })
837
837
  });
838
838
  else if (event.type === 'message.delta')
@@ -64,7 +64,6 @@ export class MarAgentRuntimeModelFactory {
64
64
  defaultReasoningEffort: resolveMarAgentDefaultReasoningEffort(configuration.defaultReasoningEffort, configuration.reasoningEfforts),
65
65
  titleGeneration: configuration.titleGeneration,
66
66
  codeMode: configuration.codeMode,
67
- highDensityCompaction: configuration.highDensityCompaction,
68
67
  ...(configuration.serviceTier === undefined
69
68
  ? {}
70
69
  : { serviceTier: configuration.serviceTier }),
@@ -5,6 +5,7 @@ import { boundedConversationItemId, compactRunnerText } from '../codex/conversat
5
5
  import { openCodePatchToolPayload, openCodeQuestions } from './conversation-parser.js';
6
6
  import { normalizeOpenCodeTool } from './tool-normalizer.js';
7
7
  import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
8
+ import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
8
9
  export class OpenCodeManagedRunController {
9
10
  runner;
10
11
  host;
@@ -272,6 +273,7 @@ export class OpenCodeManagedRunController {
272
273
  execution.responsePartKinds = new Map();
273
274
  execution.responseStartedAt = Date.now();
274
275
  execution.responseMessages = new Set();
276
+ execution.generationTiming = new GenerationTimingTracker();
275
277
  }
276
278
  await client.prompt({
277
279
  directory: input.cwd,
@@ -295,6 +297,8 @@ export class OpenCodeManagedRunController {
295
297
  const event = record(value);
296
298
  const properties = record(event?.properties);
297
299
  const nested = record(properties?.part) ?? record(properties?.info);
300
+ const execution = this.runner.execution.get(runId);
301
+ const receiptAt = execution?.generationTiming?.timestamp();
298
302
  const sessionID = typeof properties?.sessionID === 'string'
299
303
  ? properties.sessionID
300
304
  : typeof nested?.sessionID === 'string'
@@ -325,8 +329,10 @@ export class OpenCodeManagedRunController {
325
329
  if (info.role === 'assistant' &&
326
330
  execution?.responseStartedAt !== undefined &&
327
331
  typeof created === 'number' &&
328
- created >= execution.responseStartedAt)
332
+ created >= execution.responseStartedAt) {
329
333
  execution.responseMessages?.add(info.id);
334
+ execution.currentResponseMessageId = info.id;
335
+ }
330
336
  }
331
337
  if (info?.role === 'assistant' &&
332
338
  typeof info.id === 'string' &&
@@ -358,8 +364,12 @@ export class OpenCodeManagedRunController {
358
364
  typeof properties.delta === 'string' &&
359
365
  properties.delta.length > 0 &&
360
366
  typeof properties.messageID === 'string' &&
361
- this.currentResponseMessage(runId, properties.messageID))
367
+ this.currentResponseMessage(runId, properties.messageID)) {
368
+ const key = this.generationKey(runId, properties.messageID, properties.partID);
369
+ if (key !== undefined)
370
+ execution?.generationTiming?.start(key, receiptAt);
362
371
  this.observeResponse(runId, boundedConversationItemId('opencode', properties.partID), true);
372
+ }
363
373
  return;
364
374
  }
365
375
  if (event?.type === 'message.part.updated') {
@@ -386,36 +396,91 @@ export class OpenCodeManagedRunController {
386
396
  return;
387
397
  if (typeof part.id === 'string' && typeof part.type === 'string')
388
398
  this.runner.execution.get(runId)?.responsePartKinds?.set(part.id, part.type);
399
+ const tracker = this.currentResponseMessage(runId, part.messageID)
400
+ ? execution?.generationTiming
401
+ : undefined;
402
+ if (part.type === 'step-start') {
403
+ if (tracker !== undefined && typeof part.id === 'string')
404
+ this.generationKey(runId, part.messageID, part.id, receiptAt, true);
405
+ return;
406
+ }
407
+ if (part.type === 'step-finish') {
408
+ const key = typeof part.id === 'string'
409
+ ? (execution?.generationPartKeys?.get(part.id) ??
410
+ execution?.generationRequestKeys?.get(part.messageID))
411
+ : undefined;
412
+ if (tracker !== undefined && key !== undefined && typeof part.id === 'string') {
413
+ execution?.generationPartKeys?.set(part.id, key);
414
+ tracker.end(key, receiptAt);
415
+ if (execution?.generationRequestKeys?.get(part.messageID) === key)
416
+ execution.generationRequestKeys.delete(part.messageID);
417
+ const outputTokens = openCodeOutputTokens(part.tokens);
418
+ if (outputTokens !== undefined) {
419
+ const result = tracker.settle(key, outputTokens, receiptAt);
420
+ if (result !== undefined)
421
+ this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
422
+ }
423
+ }
424
+ return;
425
+ }
389
426
  const item = openCodePart(part, this.runner.execution.get(runId)?.agent === 'plan');
390
427
  if (item !== undefined)
391
428
  this.host.emitItem(runId, item);
392
429
  if (typeof part.id === 'string') {
393
430
  const itemId = boundedConversationItemId('opencode', part.id);
394
- const tracker = this.currentResponseMessage(runId, part.messageID)
395
- ? this.runner.execution.get(runId)?.responseLatency
431
+ const responseLatency = this.currentResponseMessage(runId, part.messageID)
432
+ ? execution?.responseLatency
433
+ : undefined;
434
+ const generationTiming = this.currentResponseMessage(runId, part.messageID)
435
+ ? execution?.generationTiming
396
436
  : undefined;
437
+ const generationKey = generationTiming === undefined
438
+ ? undefined
439
+ : this.generationKey(runId, part.messageID, part.id);
397
440
  if (part.type === 'tool') {
398
441
  const state = record(part.state);
399
- if (state?.status === 'completed' || state?.status === 'error')
400
- tracker?.toolCompleted(itemId);
401
- else if (state?.status === 'pending' || state?.status === 'running') {
402
- const latencyMs = tracker?.toolStarted(itemId);
442
+ if (state?.status === 'pending') {
443
+ if (generationKey !== undefined)
444
+ generationTiming?.start(generationKey, receiptAt);
445
+ const latencyMs = responseLatency?.toolStarted(itemId);
446
+ if (latencyMs !== undefined)
447
+ this.host.emit(runId, 'run.performance', { latencyMs });
448
+ }
449
+ else if (state?.status === 'running') {
450
+ if (generationKey !== undefined)
451
+ generationTiming?.pause(generationKey, receiptAt);
452
+ const latencyMs = responseLatency?.toolStarted(itemId);
403
453
  if (latencyMs !== undefined)
404
454
  this.host.emit(runId, 'run.performance', { latencyMs });
405
455
  }
456
+ else if (state?.status === 'completed' || state?.status === 'error') {
457
+ if (generationKey !== undefined)
458
+ generationTiming?.pause(generationKey, receiptAt);
459
+ responseLatency?.toolCompleted(itemId);
460
+ }
406
461
  }
407
- else if (tracker !== undefined &&
462
+ else if (generationTiming !== undefined &&
463
+ generationKey !== undefined &&
408
464
  (part.type === 'text' || part.type === 'reasoning') &&
409
- typeof part.text === 'string' &&
410
- part.text.length > 0)
411
- this.observeResponse(runId, itemId);
465
+ typeof part.text === 'string') {
466
+ if (isOpenCodeGenerationStart(part.time))
467
+ generationTiming.start(generationKey, receiptAt);
468
+ if (isOpenCodeGenerationEnd(part.time))
469
+ generationTiming.pause(generationKey, receiptAt);
470
+ if (part.text.length > 0)
471
+ this.observeResponse(runId, itemId);
472
+ }
412
473
  }
413
474
  return;
414
475
  }
415
- if (event?.type === 'permission.asked')
476
+ if (event?.type === 'permission.asked') {
477
+ this.pauseCurrentGeneration(runId, receiptAt);
416
478
  this.requestPermission(runId, properties);
417
- if (event?.type === 'question.asked')
479
+ }
480
+ if (event?.type === 'question.asked') {
481
+ this.pauseCurrentGeneration(runId, receiptAt);
418
482
  this.requestQuestion(runId, properties);
483
+ }
419
484
  }
420
485
  currentResponseMessage(runId, messageId) {
421
486
  const parent = this.messageParents.get(runId)?.get(messageId);
@@ -430,6 +495,37 @@ export class OpenCodeManagedRunController {
430
495
  if (latencyMs !== undefined)
431
496
  this.host.emit(runId, 'run.performance', { latencyMs });
432
497
  }
498
+ pauseCurrentGeneration(runId, observedAt) {
499
+ const execution = this.runner.execution.get(runId);
500
+ const key = execution?.currentResponseMessageId === undefined
501
+ ? undefined
502
+ : execution.generationRequestKeys?.get(execution.currentResponseMessageId);
503
+ if (key !== undefined)
504
+ execution?.generationTiming?.pause(key, observedAt);
505
+ }
506
+ generationKey(runId, messageId, partId, observedAt, newRequest = false) {
507
+ const execution = this.runner.execution.get(runId);
508
+ if (execution?.generationTiming === undefined)
509
+ return undefined;
510
+ execution.generationPartKeys ??= new Map();
511
+ const existing = execution.generationPartKeys.get(partId);
512
+ if (existing !== undefined)
513
+ return existing;
514
+ execution.generationRequestKeys ??= new Map();
515
+ let key = execution.generationRequestKeys.get(messageId);
516
+ if (newRequest && key !== undefined) {
517
+ execution.generationTiming.end(key, observedAt);
518
+ key = undefined;
519
+ }
520
+ if (key === undefined) {
521
+ const sequence = (execution.generationRequestSequence ?? 0) + 1;
522
+ execution.generationRequestSequence = sequence;
523
+ key = `${messageId}:generation:${sequence}`;
524
+ execution.generationRequestKeys.set(messageId, key);
525
+ }
526
+ execution.generationPartKeys.set(partId, key);
527
+ return key;
528
+ }
433
529
  requestPermission(runId, properties) {
434
530
  const execution = this.runner.execution.get(runId);
435
531
  const requestId = typeof properties?.id === 'string' ? properties.id : undefined;
@@ -655,3 +751,21 @@ function record(value) {
655
751
  ? value
656
752
  : undefined;
657
753
  }
754
+ function isOpenCodeGenerationStart(value) {
755
+ return typeof record(value)?.start === 'number';
756
+ }
757
+ function isOpenCodeGenerationEnd(value) {
758
+ return typeof record(value)?.end === 'number';
759
+ }
760
+ function openCodeOutputTokens(value) {
761
+ const tokens = record(value);
762
+ const output = tokens?.output;
763
+ const reasoning = tokens?.reasoning;
764
+ if (!Number.isSafeInteger(output) ||
765
+ !Number.isSafeInteger(reasoning) ||
766
+ output < 0 ||
767
+ reasoning < 0 ||
768
+ output + reasoning > Number.MAX_SAFE_INTEGER)
769
+ return undefined;
770
+ return output + reasoning;
771
+ }
@@ -0,0 +1,81 @@
1
+ export class GenerationTimingTracker {
2
+ now;
3
+ requests = new Map();
4
+ constructor(now = () => performance.now()) {
5
+ this.now = now;
6
+ }
7
+ timestamp() {
8
+ return this.now();
9
+ }
10
+ start(key, observedAt) {
11
+ const at = observedAt ?? this.now();
12
+ const request = this.requests.get(key) ?? {
13
+ started: false,
14
+ durationMs: 0,
15
+ ended: false,
16
+ settled: false,
17
+ invalid: false
18
+ };
19
+ this.observe(request, at);
20
+ if (request.ended || request.settled || request.invalid || request.runningAt !== undefined)
21
+ return false;
22
+ request.started = true;
23
+ request.runningAt = at;
24
+ this.requests.set(key, request);
25
+ return true;
26
+ }
27
+ pause(key, observedAt) {
28
+ const request = this.requests.get(key);
29
+ if (request === undefined || request.settled)
30
+ return;
31
+ const at = observedAt ?? this.now();
32
+ this.observe(request, at);
33
+ if (request.runningAt === undefined)
34
+ return;
35
+ const duration = at - request.runningAt;
36
+ if (!(duration >= 0))
37
+ request.invalid = true;
38
+ else
39
+ request.durationMs += duration;
40
+ request.runningAt = undefined;
41
+ }
42
+ end(key, observedAt) {
43
+ const request = this.requests.get(key);
44
+ if (request === undefined || request.settled || request.ended)
45
+ return;
46
+ this.pause(key, observedAt);
47
+ request.ended = true;
48
+ }
49
+ settle(key, outputTokens, observedAt) {
50
+ const request = this.requests.get(key);
51
+ if (request === undefined ||
52
+ request.settled ||
53
+ !request.started ||
54
+ request.invalid ||
55
+ !Number.isSafeInteger(outputTokens) ||
56
+ outputTokens < 0)
57
+ return undefined;
58
+ this.end(key, observedAt);
59
+ request.settled = true;
60
+ if (request.invalid || request.durationMs < 1)
61
+ return undefined;
62
+ return {
63
+ outputTokens,
64
+ durationMs: request.durationMs,
65
+ tokensPerSecond: Math.round((outputTokens * 100_000) / request.durationMs) / 100
66
+ };
67
+ }
68
+ discard(key) {
69
+ this.requests.delete(key);
70
+ }
71
+ has(key) {
72
+ return this.requests.has(key);
73
+ }
74
+ observe(request, observedAt) {
75
+ if (!Number.isFinite(observedAt) ||
76
+ (request.lastAt !== undefined && observedAt < request.lastAt))
77
+ request.invalid = true;
78
+ if (Number.isFinite(observedAt))
79
+ request.lastAt = observedAt;
80
+ }
81
+ }
@@ -1950,7 +1950,6 @@ function modelSummary(model, index) {
1950
1950
  defaultReasoningEffort: resolveMarAgentDefaultReasoningEffort(model.defaultReasoningEffort, model.reasoningEfforts),
1951
1951
  titleGeneration: model.titleGeneration,
1952
1952
  codeMode: model.codeMode,
1953
- highDensityCompaction: model.highDensityCompaction,
1954
1953
  ...(model.fastMode === true ? { fastMode: true } : {}),
1955
1954
  hostedWebSearch: model.hostedWebSearch,
1956
1955
  ...(model.responsesTransport === undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.173",
3
+ "version": "0.9.175",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -36,10 +36,10 @@
36
36
  "playwright": "1.62.1",
37
37
  "ws": "^8.21.3",
38
38
  "zod": "4.4.3",
39
- "@myagentroam/agent": "0.9.173",
40
- "@myagentroam/code-navigation": "0.9.129",
39
+ "@myagentroam/agent": "0.9.175",
41
40
  "@myagentroam/marctl": "0.9.123",
42
- "@myagentroam/protocol": "0.9.173"
41
+ "@myagentroam/protocol": "0.9.175",
42
+ "@myagentroam/code-navigation": "0.9.129"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/ws": "^8.18.1"