maka-agent 0.2.1-dev.88.20261016 → 0.2.1-dev.90.20261017

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.
Files changed (28) hide show
  1. package/dist/pi-tui-layout.js +3 -3
  2. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  3. package/node_modules/@maka/core/dist/event-wait.js +98 -2
  4. package/node_modules/@maka/core/dist/llm-connections.js +19 -1
  5. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +12 -37
  6. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +6 -10
  7. package/node_modules/@maka/runtime/dist/conversation-copy.js +11 -10
  8. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +29 -0
  9. package/node_modules/@maka/runtime/dist/history-compaction.js +29 -19
  10. package/node_modules/@maka/runtime/dist/memory-extraction.js +1 -2
  11. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +14 -7
  12. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +6 -0
  13. package/node_modules/@maka/runtime/dist/session-manager.js +10 -8
  14. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +0 -19
  15. package/node_modules/@maka/runtime-host/dist/server/event-wait-adapter.js +47 -0
  16. package/node_modules/@maka/runtime-host/dist/server/event-wait-manager.js +1186 -0
  17. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +13 -4
  18. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +4 -51
  19. package/node_modules/@maka/runtime-host/dist/server/goal-evaluation-context.js +354 -0
  20. package/node_modules/@maka/runtime-host/dist/server/goal-form-evidence.js +118 -0
  21. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-transaction.js +19 -5
  22. package/node_modules/@maka/storage/dist/codex-session-adapter.js +81 -2
  23. package/node_modules/@maka/storage/dist/event-wait-authority.js +132 -2
  24. package/node_modules/@maka/storage/dist/execution-stores.js +19 -1
  25. package/node_modules/@maka/storage/dist/sqlite-event-wait-authority.js +116 -1
  26. package/node_modules/@maka/storage/dist/sqlite-workflow-schema.js +16 -1
  27. package/node_modules/@maka/storage/package.json +1 -0
  28. package/package.json +1 -1
@@ -45,8 +45,8 @@ export function fitPendingQueueLines(lines, maxRows) {
45
45
  * The fullscreen chrome's editor/pending-queue row account, isolated in one
46
46
  * function so the budget and its consumers cannot drift apart: from the rows
47
47
  * available below the transcript, reserve the editor's minimum viewport (the
48
- * queue trims first), then give the editor whatever remains, never less than
49
- * that minimum while any budget is left to reserve it from. `rowsAvailable` is
48
+ * queue trims first), then report the actual remainder so the caller can
49
+ * detect a deficit and yield optional chrome. `rowsAvailable` is
50
50
  * everything already carved out of the terminal rows — the chrome subtracts the
51
51
  * status line, activity strip, unread indicator, and the reserved transcript row
52
52
  * before calling. The main-screen layout keeps its own (upstream-owned)
@@ -60,7 +60,7 @@ function budgetEditorAndPendingRows(rowsAvailable, allPendingLines, editor) {
60
60
  const pendingLines = fitPendingQueueLines(allPendingLines, pendingRowsAvailable);
61
61
  return {
62
62
  pendingLines,
63
- editorRows: budget === 0 ? 0 : Math.max(minimumEditorRows, budget - pendingLines.length),
63
+ editorRows: Math.max(0, budget - pendingLines.length),
64
64
  };
65
65
  }
66
66
  export class MakaTranscriptComponent {
@@ -29,7 +29,12 @@ export const EVENT_WAIT_LIMITS = Object.freeze({
29
29
  evidenceRefs: 8,
30
30
  evidenceRefBytes: 1024,
31
31
  reasonBytes: 1024,
32
- recordBytes: 32768,
32
+ cursorBytes: 2048,
33
+ observationAttempts: 1_000_000,
34
+ // Every combination of bounded fields, including observation and a cancelled
35
+ // priorResolution, fits below 42 KiB even with JSON escaping. Leave room for
36
+ // the full lifecycle so an accepted result can always retain its facts on cancel.
37
+ recordBytes: 48 * 1024,
33
38
  });
34
39
  const encoder = new TextEncoder();
35
40
  const invalidIdCharacterPattern = /[^A-Za-z0-9_-]/u;
@@ -187,7 +192,7 @@ export function decodeEventWaitRecord(value) {
187
192
  'deadlineAt',
188
193
  'status',
189
194
  ...lifecycleKeys,
190
- ], root.status === 'cancelled' ? ['priorResolution'] : []);
195
+ ], root.status === 'cancelled' ? ['priorResolution', 'observation'] : ['observation']);
191
196
  if (r.schemaVersion !== EVENT_WAIT_SCHEMA_VERSION)
192
197
  invalid('schemaVersion');
193
198
  const lease = shape(r.goalControlLease, ['goalId', 'generation']);
@@ -230,6 +235,62 @@ export function decodeEventWaitRecord(value) {
230
235
  invalid('lifecycle time');
231
236
  return result;
232
237
  };
238
+ if (Object.hasOwn(r, 'observation')) {
239
+ const x = shape(r.observation, [
240
+ 'schemaVersion',
241
+ 'attempts',
242
+ 'lastAttemptAt',
243
+ 'lastObservedAt',
244
+ 'nextObserveAt',
245
+ 'nextPollAt',
246
+ 'consecutiveErrors',
247
+ 'outcome',
248
+ 'cursor',
249
+ 'eventKey',
250
+ 'reason',
251
+ ]);
252
+ const attempts = integer(x.attempts, 'observation attempts', 1);
253
+ const consecutiveErrors = integer(x.consecutiveErrors, 'consecutive errors');
254
+ const lastAttemptAt = time(x.lastAttemptAt);
255
+ const nextObserveAt = integer(x.nextObserveAt, 'next observation');
256
+ const nextPollAt = integer(x.nextPollAt, 'next poll');
257
+ const lastObservedAt = x.lastObservedAt === null ? null : time(x.lastObservedAt);
258
+ if (x.schemaVersion !== 1 ||
259
+ attempts > EVENT_WAIT_LIMITS.observationAttempts ||
260
+ consecutiveErrors > attempts ||
261
+ nextObserveAt < lastAttemptAt ||
262
+ nextPollAt < nextObserveAt ||
263
+ (x.outcome !== 'checking' && (lastObservedAt === null || lastObservedAt < lastAttemptAt)) ||
264
+ typeof x.outcome !== 'string' ||
265
+ ![
266
+ 'checking',
267
+ 'pending',
268
+ 'unknown',
269
+ 'satisfied',
270
+ 'invalidated',
271
+ 'source_unavailable',
272
+ ].includes(x.outcome))
273
+ invalid('observation');
274
+ Object.assign(base, {
275
+ observation: {
276
+ schemaVersion: 1,
277
+ attempts,
278
+ lastAttemptAt,
279
+ lastObservedAt,
280
+ nextObserveAt,
281
+ nextPollAt,
282
+ consecutiveErrors,
283
+ outcome: x.outcome,
284
+ cursor: x.cursor === null ? null : text(x.cursor, 'cursor', EVENT_WAIT_LIMITS.cursorBytes),
285
+ eventKey: x.eventKey === null
286
+ ? null
287
+ : text(x.eventKey, 'event key', EVENT_WAIT_LIMITS.receiptKeyBytes),
288
+ reason: x.reason === null
289
+ ? null
290
+ : text(x.reason, 'observation reason', EVENT_WAIT_LIMITS.reasonBytes),
291
+ },
292
+ });
293
+ }
233
294
  const resolution = (v, resolvedAt) => {
234
295
  const x = shape(v, ['outcome', 'receiptKey', 'observedAt', 'evidenceRefs']);
235
296
  if (x.outcome !== 'satisfied' &&
@@ -306,7 +367,41 @@ export function assertEventWaitTransition(previous, next) {
306
367
  }
307
368
  if (b.updatedAt < a.updatedAt)
308
369
  invalid('time regression');
370
+ if (a.observation) {
371
+ if (!b.observation ||
372
+ b.observation.attempts < a.observation.attempts ||
373
+ b.observation.attempts > a.observation.attempts + 1 ||
374
+ b.observation.lastAttemptAt < a.observation.lastAttemptAt ||
375
+ (a.observation.lastObservedAt !== null &&
376
+ (b.observation.lastObservedAt === null ||
377
+ b.observation.lastObservedAt < a.observation.lastObservedAt)))
378
+ invalid('observation regression');
379
+ if (b.observation.attempts === a.observation.attempts &&
380
+ b.observation.lastAttemptAt !== a.observation.lastAttemptAt)
381
+ invalid('attempt time without reservation');
382
+ if (b.observation.attempts > a.observation.attempts &&
383
+ (b.status !== 'waiting' ||
384
+ b.observation.outcome !== 'checking' ||
385
+ b.observation.lastObservedAt !== a.observation.lastObservedAt ||
386
+ b.observation.cursor !== a.observation.cursor ||
387
+ b.observation.eventKey !== a.observation.eventKey ||
388
+ b.observation.consecutiveErrors !== a.observation.consecutiveErrors))
389
+ invalid('attempt reservation');
390
+ }
391
+ else if (b.observation && b.observation.attempts !== 1)
392
+ invalid('initial observation budget');
309
393
  if (a.status === 'waiting') {
394
+ if (b.status === 'waiting' && b.observation && !same(a.observation, b.observation)) {
395
+ if (b.observation.attempts > (a.observation?.attempts ?? 0) &&
396
+ b.observation.outcome === 'checking')
397
+ return;
398
+ if (a.observation?.outcome === 'checking' &&
399
+ (b.observation.outcome === 'pending' || b.observation.outcome === 'unknown'))
400
+ return;
401
+ invalid('observation update without reservation');
402
+ }
403
+ if (b.status === 'resolved' && b.resolvedAt >= b.deadlineAt)
404
+ invalid('resolution after deadline');
310
405
  if (b.status === 'resolved' ||
311
406
  b.status === 'expired' ||
312
407
  (b.status === 'cancelled' && !b.priorResolution))
@@ -314,6 +409,7 @@ export function assertEventWaitTransition(previous, next) {
314
409
  }
315
410
  else if (a.status === 'resolved' &&
316
411
  b.status === 'cancelled' &&
412
+ same(a.observation, b.observation) &&
317
413
  same(b.priorResolution, { resolvedAt: a.resolvedAt, resolution: a.resolution }))
318
414
  return;
319
415
  invalid('state transition');
@@ -359,8 +359,14 @@ export function deriveInteractiveOAuthConnectionSlug(providerType, existingSlugs
359
359
  * - Any other scheme (`file:`, `javascript:`, `data:`, `vbscript:`,
360
360
  * `chrome-extension:`, `app:`, `maka:`, custom).
361
361
  * - Malformed URL strings the `URL` constructor throws on.
362
+ * - Embedded credentials (`https://user:pw@host`), query strings and
363
+ * fragments — the Runtime Host codec refuses these, so accepting
364
+ * them here only turns the eventual rejection into the generic
365
+ * "service unavailable" copy.
362
366
  * - Pathological lengths (> 2048 chars — defense against
363
- * adversarial inputs; real-world baseUrls are < 100 chars).
367
+ * adversarial inputs; real-world baseUrls are < 100 chars) and
368
+ * canonical forms longer than 2048 bytes, which is the unit the
369
+ * Host measures.
364
370
  *
365
371
  * Returns `null` on accept, an error string on reject. Mirrors
366
372
  * `validateSlug`'s shape.
@@ -389,6 +395,18 @@ export function validateConnectionBaseUrl(baseUrl) {
389
395
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
390
396
  return `baseUrl scheme '${parsed.protocol}' is not allowed (use http: or https:)`;
391
397
  }
398
+ // The Runtime Host rejects these too (`normalizeCatalogConnectionBaseUrl`),
399
+ // so gating here is what turns a downstream, unclassified failure into a
400
+ // message that names the endpoint field.
401
+ if (parsed.username || parsed.password) {
402
+ return 'baseUrl must not contain credentials';
403
+ }
404
+ if (trimmed.includes('?') || trimmed.includes('#')) {
405
+ return 'baseUrl must not contain a query or fragment';
406
+ }
407
+ if (new TextEncoder().encode(parsed.toString()).byteLength > 2048) {
408
+ return 'baseUrl must be 2048 bytes or fewer';
409
+ }
392
410
  return null;
393
411
  }
394
412
  /**
@@ -18,8 +18,7 @@
18
18
  */
19
19
  import { compactionDecisionDiagnosticPatch } from './compaction-boundary.js';
20
20
  import { buildContextBudgetDiagnosticShell, mergeContextBudgetDiagnostic, mergeContextBudgetDiagnosticPatches, } from './context-budget.js';
21
- import { isHistoryCompactContentEvent } from './history-compaction.js';
22
- import { canContinueHistoryCompactCheckpointForModel, canReplayHistoryCompactCheckpointForModel, historyCompactSourceDigest, matchHistoryCompactCheckpointPrefix, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
21
+ import { canContinueHistoryCompactCheckpointForModel, canReplayHistoryCompactCheckpointForModel, checkHistoryCompactCheckpointCurrency, isHistoryCompactContentEvent, matchHistoryCompactCheckpointPrefix, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
23
22
  import { HistoryCompactSummarizerError, isMalformedHistoryCompactSummaryReason, } from './history-compact-error.js';
24
23
  import { archiveToolResultAsTransition, collectToolResultArchiveCandidates, serializedToolResultProjection, } from './tool-result-archive-transition.js';
25
24
  import { estimateTokens, sha256 } from './context-budget-helpers.js';
@@ -140,6 +139,9 @@ export class AiSdkCompaction {
140
139
  // The current durable RuntimeEvents remain sufficient for a fresh checkpoint.
141
140
  }
142
141
  if (previousCheckpoint) {
142
+ // Load the transition view only when the raw prefix can replay at all:
143
+ // a checkpoint that cannot match never reaches for the ledger, the
144
+ // same as before (#5930 keeps the load point, not the check).
143
145
  const match = matchHistoryCompactCheckpointPrefix(previousCheckpoint, runtimeContext);
144
146
  if (!match.reason && match.successorRuntimeEvents.length === 0) {
145
147
  // Raw identity is not enough: a projection transition committed
@@ -148,9 +150,9 @@ export class AiSdkCompaction {
148
150
  // digest to still match — the same gate the pre-send path applies
149
151
  // (#5929). On drift, fall through to the planner, whose roll-forward
150
152
  // currency check discards the stale checkpoint and re-summarizes.
151
- const effectiveCovered = await this.foldEffectiveModelHistory(match.coveredRuntimeEvents);
152
- if (this.checkpointEffectiveCoverageMatches(previousCheckpoint, effectiveCovered)) {
153
- const projectedEvents = projectHistoryCompactCheckpointReplay(previousCheckpoint, match.coveredRuntimeEvents, []);
153
+ const currency = checkHistoryCompactCheckpointCurrency(previousCheckpoint, runtimeContext, await this.loadModelProjectionTransitions());
154
+ if (currency.status === 'current') {
155
+ const projectedEvents = projectHistoryCompactCheckpointReplay(previousCheckpoint, currency.match.coveredRuntimeEvents, []);
154
156
  return {
155
157
  outcome: { kind: 'unchanged', reason: 'already_compacted' },
156
158
  contextBudget: mergeContextBudgetDiagnostic(buildContextBudgetDiagnosticShell(runtimeContext, projectedEvents, policy), {
@@ -198,11 +200,11 @@ export class AiSdkCompaction {
198
200
  : {}),
199
201
  ...(automaticMemoryBoundary ? { memoryExtractionBoundary: automaticMemoryBoundary } : {}),
200
202
  ...(previousCheckpoint ? { previousCheckpoint } : {}),
201
- // The planner projects the covered span to its effective view before
203
+ // The planner folds the covered span through this snapshot before
202
204
  // summarizing and pins its digest as coverage.effectiveSourceDigest:
203
205
  // the summary can never quote a body a durable transition removed, and
204
206
  // a later transition invalidates the checkpoint at replay (#4845).
205
- projectEffectiveCoverage: (covered) => this.foldEffectiveModelHistory(covered),
207
+ loadProjectionSnapshot: () => this.loadModelProjectionTransitions(),
206
208
  summarize: async ({ coveredRuntimeEvents, newlyFoldedRuntimeEvents, previousCheckpoint, }) => {
207
209
  return await this.summarizeWithFailureCircuit(summarizer, {
208
210
  sessionId: this.sessionId,
@@ -363,27 +365,6 @@ export class AiSdkCompaction {
363
365
  return reduceEffectiveModelProjections(events, loaded.transitions, loaded.unreadableTargets)
364
366
  .events;
365
367
  }
366
- /**
367
- * Whether the checkpoint's pinned effective view still is the covered
368
- * prefix's effective view. The raw identity match happens later in the
369
- * replay authority; this gate is about content currency: a projection
370
- * transition committed after the fold changes what the model may see of the
371
- * covered span without touching the raw ledger, and the checkpoint's summary
372
- * or provider state must not survive that drift (#4845 review).
373
- */
374
- checkpointEffectiveCoverageMatches(checkpoint, effectiveEvents) {
375
- const pinned = checkpoint.coverage.effectiveSourceDigest;
376
- if (pinned === undefined)
377
- return false;
378
- const covered = effectiveEvents
379
- .filter(isHistoryCompactContentEvent)
380
- .slice(0, checkpoint.coverage.eventCount);
381
- if (covered.length !== checkpoint.coverage.eventCount)
382
- return false;
383
- if (covered.at(-1)?.id !== checkpoint.coverage.through.runtimeEventId)
384
- return false;
385
- return historyCompactSourceDigest(covered) === pinned;
386
- }
387
368
  /**
388
369
  * Apply the same bounded-result rule to current and prior events.
389
370
  *
@@ -471,14 +452,8 @@ export class AiSdkCompaction {
471
452
  // This gate is only for the case where the raw identity matches but a
472
453
  // projection transition committed after the fold changed the effective
473
454
  // view the summary (or provider state) was built from (#4845 review).
474
- const rawIdentityMatch = matchHistoryCompactCheckpointPrefix(loadedCheckpoint, runtimeContext.filter(isHistoryCompactContentEvent));
475
- if (rawIdentityMatch.reason) {
476
- nextPolicy = {
477
- ...nextPolicy,
478
- historyCompact: { ...nextPolicy.historyCompact, checkpoint: loadedCheckpoint },
479
- };
480
- }
481
- else if (this.checkpointEffectiveCoverageMatches(loadedCheckpoint, effective.events)) {
455
+ const currency = checkHistoryCompactCheckpointCurrency(loadedCheckpoint, runtimeContext.filter(isHistoryCompactContentEvent), prepared.projectionSnapshot);
456
+ if (currency.status !== 'effective_history_changed') {
482
457
  nextPolicy = {
483
458
  ...nextPolicy,
484
459
  historyCompact: { ...nextPolicy.historyCompact, checkpoint: loadedCheckpoint },
@@ -801,7 +776,7 @@ export class AiSdkCompaction {
801
776
  },
802
777
  }
803
778
  : {}),
804
- projectEffectiveCoverage: (covered) => this.foldEffectiveModelHistory(covered),
779
+ loadProjectionSnapshot: () => this.loadModelProjectionTransitions(),
805
780
  summarize: async ({ coveredRuntimeEvents, newlyFoldedRuntimeEvents, previousCheckpoint }) => {
806
781
  // Same contract as the standalone path: the planner hands the
807
782
  // effective (transition-folded) view to the summarizer and pins its
@@ -46,8 +46,7 @@ import { renderSwarmModePrompt } from './swarm-mode.js';
46
46
  import { renderGraphModePrompt } from './graph-mode.js';
47
47
  import { modelUsesNativeOpenAiResponses } from './model-runtime.js';
48
48
  import { applyRuntimeEventContextBudget, buildContextBudgetDiagnosticShell, mergeContextBudgetDiagnostic, addToolResultPruneStats, minimalContextBudgetDiagnostic, shouldAppendContextCompactedNote, shouldAppendContextCompactionFailedOpenNote, } from './context-budget.js';
49
- import { isHistoryCompactContentEvent } from './history-compaction.js';
50
- import { canContinueHistoryCompactCheckpointForModel, historyCompactSourceDigest, isProviderHistoryCompactCheckpoint, matchHistoryCompactCheckpointPrefix, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
49
+ import { canContinueHistoryCompactCheckpointForModel, checkHistoryCompactCheckpointCurrency, isHistoryCompactContentEvent, isProviderHistoryCompactCheckpoint, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
51
50
  import { resolveSelectedModelContextWindow } from './context-budget-policy.js';
52
51
  import { INVALID_TOOL_NAME, isProviderSandboxBoundaryAttempt, repairMakaToolCall, } from './ai-sdk-tool-repair.js';
53
52
  const CHILD_STEP_BUDGET_FINALIZATION_PROMPT = [
@@ -1044,23 +1043,20 @@ export class AiSdkTurn {
1044
1043
  let replayEvents = rawProjectionEvents;
1045
1044
  let effectiveProjectionCheckpoint = projectionCheckpoint;
1046
1045
  if (projectionCheckpoint) {
1047
- const checkpointMatch = matchHistoryCompactCheckpointPrefix(projectionCheckpoint, rawProjectionEvents);
1048
- if (checkpointMatch.reason) {
1049
- throw new Error(`durable checkpoint projection mismatch: ${checkpointMatch.reason}`);
1046
+ const currency = checkHistoryCompactCheckpointCurrency(projectionCheckpoint, rawProjectionEvents, pruned.projectionSnapshot);
1047
+ if (currency.status === 'raw_mismatch') {
1048
+ throw new Error(`durable checkpoint projection mismatch: ${currency.reason}`);
1050
1049
  }
1051
1050
  // Content-currency guard: the raw identity still matches, but a
1052
1051
  // transition committed after this fold (e.g. an active-turn prune
1053
1052
  // in this very send) changed the effective view the block was
1054
1053
  // built from. Replay without the stale block — the provider
1055
1054
  // decides fit and overflow recovery re-folds (#4845 review).
1056
- const pinnedEffectiveDigest = projectionCheckpoint.coverage.effectiveSourceDigest;
1057
- const coveredEffective = await this.deps.compaction.foldEffectiveModelHistory(checkpointMatch.coveredRuntimeEvents, pruned.projectionSnapshot);
1058
- if (pinnedEffectiveDigest === undefined ||
1059
- historyCompactSourceDigest(coveredEffective) !== pinnedEffectiveDigest) {
1055
+ if (currency.status === 'effective_history_changed') {
1060
1056
  effectiveProjectionCheckpoint = undefined;
1061
1057
  }
1062
1058
  else {
1063
- replayEvents = projectHistoryCompactCheckpointReplay(projectionCheckpoint, checkpointMatch.coveredRuntimeEvents, checkpointMatch.successorRuntimeEvents);
1059
+ replayEvents = projectHistoryCompactCheckpointReplay(projectionCheckpoint, currency.match.coveredRuntimeEvents, currency.match.successorRuntimeEvents);
1064
1060
  }
1065
1061
  // The checkpoint was capacity-validated before it was persisted.
1066
1062
  // Do not re-run that gate against a later, larger successor tail:
@@ -25,9 +25,8 @@ import { isEmittedAgentRunEventType } from '@maka/core/agent-run';
25
25
  import { decodeModelCallAttempt, MODEL_CALL_ATTEMPT_EVENT_TYPE, } from '@maka/core/model-call-attempt';
26
26
  import { TOOL_RECOVERY_DECISION_FACT_KIND } from '@maka/core/tool-recovery-fact';
27
27
  import { canonicalToolArgsHash } from '@maka/core/tool-args-identity';
28
- import { buildHistoryCompactCheckpoint, historyCompactSourceDigest, matchHistoryCompactCheckpointPrefix, validateHistoryCompactCheckpointShape, } from './history-compact-checkpoint.js';
28
+ import { buildHistoryCompactCheckpoint, checkHistoryCompactCheckpointCurrency, isHistoryCompactContentEvent, validateHistoryCompactCheckpointShape, } from './history-compact-checkpoint.js';
29
29
  import { findCheckpointSummaryDefect } from './history-compact-summary-validation.js';
30
- import { isHistoryCompactContentEvent } from './history-compaction.js';
31
30
  import { classifyTerminalRuntimeLedger, commitTerminalRunWithRuntimeFact, } from './terminal-run-commit.js';
32
31
  import { buildToolOperationId } from './runtime-commit-sink.js';
33
32
  import { isContinuationStartRuntimeEvent } from './runtime-event-read-model.js';
@@ -842,13 +841,15 @@ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clo
842
841
  return null;
843
842
  if (sourceCheckpoint.version === 3)
844
843
  return null;
845
- const match = matchHistoryCompactCheckpointPrefix(sourceCheckpoint, sourceCompactableEvents);
846
- if (match.reason)
847
- return null;
848
- // Rebinding a digest must not make an already stale source summary valid.
849
- const sourceEffective = reduceEffectiveModelProjections(match.coveredRuntimeEvents, [...clonedTransitions.keys()].map((record) => decodeModelProjectionTransition(record.data?.transition, record.sessionId))).events;
850
- if (sourceCheckpoint.coverage.effectiveSourceDigest !==
851
- historyCompactSourceDigest(sourceEffective))
844
+ // Rebinding a digest must not make an already stale source summary valid:
845
+ // the currency check covers the raw prefix match too, so a prefix that no
846
+ // longer matches is dropped here as well.
847
+ const currency = checkHistoryCompactCheckpointCurrency(sourceCheckpoint, sourceCompactableEvents, {
848
+ transitions: [...clonedTransitions.keys()].map((record) => decodeModelProjectionTransition(record.data?.transition, record.sessionId)),
849
+ unreadableTargets: new Set(),
850
+ unscopedUnreadable: 0,
851
+ });
852
+ if (currency.status !== 'current')
852
853
  return null;
853
854
  // Copy is an admission seam for the sectioned summary contract: a marked
854
855
  // checkpoint whose summary no longer satisfies the COMPLETE predicate —
@@ -858,7 +859,7 @@ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clo
858
859
  if (findCheckpointSummaryDefect(sourceCheckpoint.summary) !== undefined) {
859
860
  throw new Error(`Cannot copy invalid history compact checkpoint ${event.id}`);
860
861
  }
861
- const coveredRuntimeEvents = match.coveredRuntimeEvents.map((sourceEvent) => {
862
+ const coveredRuntimeEvents = currency.match.coveredRuntimeEvents.map((sourceEvent) => {
862
863
  const cloned = clonedRuntimeEvents.get(sourceEvent.id);
863
864
  if (!cloned) {
864
865
  throw new Error(`History compact checkpoint ${event.id} crosses the conversation copy boundary`);
@@ -19,6 +19,8 @@
19
19
  import { createHash } from 'node:crypto';
20
20
  import { Buffer } from 'node:buffer';
21
21
  import { nonEmpty, sha256 } from './context-budget-helpers.js';
22
+ import { estimateRuntimeEventChars } from './model-history.js';
23
+ import { reduceEffectiveModelProjections, } from './model-projection-transition-ledger.js';
22
24
  import { stableStringify } from './request-shape.js';
23
25
  import { findCheckpointSummaryDefect, SECTIONED_SUMMARY_FORMAT, } from './history-compact-summary-validation.js';
24
26
  // v3: coverage identity and the source digest stay pinned on the RAW ledger
@@ -399,6 +401,29 @@ export function matchHistoryCompactCheckpointPrefix(checkpoint, events) {
399
401
  successorRuntimeEvents,
400
402
  };
401
403
  }
404
+ /**
405
+ * Judges a checkpoint against `events` under `projectionSnapshot` — the SAME
406
+ * loaded transition view the caller used to build the request being judged.
407
+ * The function never loads transitions: a checkpoint is only current for the
408
+ * snapshot it is checked with, and a record committed after that snapshot
409
+ * belongs to the next request's decision.
410
+ */
411
+ export function checkHistoryCompactCheckpointCurrency(checkpoint, events, projectionSnapshot) {
412
+ const match = matchHistoryCompactCheckpointPrefix(checkpoint, events);
413
+ if (match.reason !== undefined) {
414
+ return { status: 'raw_mismatch', reason: match.reason };
415
+ }
416
+ const effectiveCovered = reduceEffectiveModelProjections(match.coveredRuntimeEvents, projectionSnapshot.transitions, projectionSnapshot.unreadableTargets).events.filter(isHistoryCompactContentEvent);
417
+ const pinned = checkpoint.coverage.effectiveSourceDigest;
418
+ if (pinned === undefined ||
419
+ effectiveCovered.length < checkpoint.coverage.eventCount ||
420
+ effectiveCovered[checkpoint.coverage.eventCount - 1]?.id !==
421
+ checkpoint.coverage.through.runtimeEventId ||
422
+ historyCompactSourceDigest(effectiveCovered.slice(0, checkpoint.coverage.eventCount)) !== pinned) {
423
+ return { status: 'effective_history_changed' };
424
+ }
425
+ return { status: 'current', match };
426
+ }
402
427
  /**
403
428
  * Deterministic RuntimeEvent projection for a checkpoint. V2 prepends its
404
429
  * text block; V3 stays out of the event list and is materialized directly at
@@ -516,6 +541,10 @@ function effectiveDigestEvent(event) {
516
541
  }
517
542
  return { ...event, content: { ...identity, result } };
518
543
  }
544
+ /** True when the event carries model-visible content the compact projection counts. */
545
+ export function isHistoryCompactContentEvent(event) {
546
+ return event.modelVisibility !== 'hidden' && estimateRuntimeEventChars(event) > 0;
547
+ }
519
548
  function validHistoryCompactProviderState(value) {
520
549
  if (!value || typeof value !== 'object')
521
550
  return false;
@@ -16,11 +16,12 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { estimateRuntimeEventChars, estimateRuntimeEventsTokens } from './model-history.js';
19
+ import { estimateRuntimeEventsTokens } from './model-history.js';
20
20
  import { compactionDecisionDiagnosticPatch } from './compaction-boundary.js';
21
21
  import { HistoryCompactSummarizerError, } from './history-compact-error.js';
22
22
  import { findCheckpointSummaryDefect } from './history-compact-summary-validation.js';
23
- import { buildHistoryCompactCheckpoint, historyCompactCheckpointToRuntimeEvent, historyCompactSourceDigest, matchHistoryCompactCheckpointPrefix, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
23
+ import { buildHistoryCompactCheckpoint, checkHistoryCompactCheckpointCurrency, historyCompactCheckpointToRuntimeEvent, isHistoryCompactContentEvent, matchHistoryCompactCheckpointPrefix, projectHistoryCompactCheckpointReplay, } from './history-compact-checkpoint.js';
24
+ import { reduceEffectiveModelProjections, } from './model-projection-transition-ledger.js';
24
25
  /**
25
26
  * Select the largest contiguous covered prefix that is safe to fold:
26
27
  *
@@ -180,12 +181,14 @@ export async function planHistoryCompaction(input) {
180
181
  return noSafeSpan();
181
182
  const coveredRuntimeEvents = input.orderedEvents.slice(0, boundary.coveredCount);
182
183
  const tailRuntimeEvents = input.orderedEvents.slice(boundary.coveredCount);
184
+ // One transition view per pass: the same loaded snapshot judges the
185
+ // previous checkpoint's currency and produces the effective view the
186
+ // summary is written from.
187
+ const projectionSnapshot = input.loadProjectionSnapshot
188
+ ? await input.loadProjectionSnapshot()
189
+ : undefined;
183
190
  // Roll forward from a previous checkpoint when it is an exact prefix of the
184
191
  // covered events, so the summary only re-reads the newly folded span.
185
- const checkpointMatch = input.previousCheckpoint
186
- ? matchHistoryCompactCheckpointPrefix(input.previousCheckpoint, coveredRuntimeEvents)
187
- : undefined;
188
- let previousCheckpoint = checkpointMatch && !checkpointMatch.reason ? input.previousCheckpoint : undefined;
189
192
  // Content-currency gate for roll-forward: the inherited summary or
190
193
  // provider state describes the EFFECTIVE view of the previous coverage at
191
194
  // its own creation. A projection transition committed since rewrites that
@@ -193,20 +196,31 @@ export async function planHistoryCompaction(input) {
193
196
  // would launder it into the new checkpoint under the current effective
194
197
  // digest — later replay guards would then pass it (#4845 review). On
195
198
  // drift, discard the checkpoint and re-summarize the whole effective span.
196
- if (previousCheckpoint && checkpointMatch && input.projectEffectiveCoverage) {
197
- const pinned = previousCheckpoint.coverage.effectiveSourceDigest;
198
- const previousEffectiveCovered = await input.projectEffectiveCoverage(checkpointMatch.coveredRuntimeEvents);
199
- if (pinned === undefined || historyCompactSourceDigest(previousEffectiveCovered) !== pinned) {
199
+ let previousCheckpoint = input.previousCheckpoint;
200
+ let successorRuntimeEvents;
201
+ if (previousCheckpoint && projectionSnapshot) {
202
+ const currency = checkHistoryCompactCheckpointCurrency(previousCheckpoint, coveredRuntimeEvents, projectionSnapshot);
203
+ if (currency.status === 'current') {
204
+ successorRuntimeEvents = currency.match.successorRuntimeEvents;
205
+ }
206
+ else {
207
+ previousCheckpoint = undefined;
208
+ }
209
+ }
210
+ else if (previousCheckpoint) {
211
+ const match = matchHistoryCompactCheckpointPrefix(previousCheckpoint, coveredRuntimeEvents);
212
+ if (match.reason === undefined) {
213
+ successorRuntimeEvents = match.successorRuntimeEvents;
214
+ }
215
+ else {
200
216
  previousCheckpoint = undefined;
201
217
  }
202
218
  }
203
- const newlyFoldedRuntimeEvents = previousCheckpoint
204
- ? checkpointMatch.successorRuntimeEvents
205
- : coveredRuntimeEvents;
219
+ const newlyFoldedRuntimeEvents = successorRuntimeEvents ?? coveredRuntimeEvents;
206
220
  // The model-visible summary reads the effective (transition-folded) view
207
221
  // of the covered span; the raw events keep the coverage identity.
208
- const effectiveCoveredRuntimeEvents = input.projectEffectiveCoverage
209
- ? [...(await input.projectEffectiveCoverage(coveredRuntimeEvents))]
222
+ const effectiveCoveredRuntimeEvents = projectionSnapshot
223
+ ? reduceEffectiveModelProjections(coveredRuntimeEvents, projectionSnapshot.transitions, projectionSnapshot.unreadableTargets).events
210
224
  : undefined;
211
225
  const effectiveNewlyFoldedRuntimeEvents = effectiveCoveredRuntimeEvents
212
226
  ? newlyFoldedRuntimeEvents.length === coveredRuntimeEvents.length
@@ -350,7 +364,3 @@ export function applyRuntimeEventHistoryCompact(events, policy, charsPerToken =
350
364
  }),
351
365
  };
352
366
  }
353
- /** True when the event carries model-visible content the compact projection counts. */
354
- export function isHistoryCompactContentEvent(event) {
355
- return event.modelVisibility !== 'hidden' && estimateRuntimeEventChars(event) > 0;
356
- }
@@ -21,8 +21,7 @@ import { redactSecrets } from '@maka/core/redaction';
21
21
  import { z } from 'zod';
22
22
  import { fitMemoryExtractionEvidence, bindProviderVisibleEvidence, isMemoryToolName, planMemoryCoverage, projectMemoryExtractionEvidence, renderMemoryLocalizationContext, searchSameSessionMemoryHistory, } from './memory-extraction-evidence.js';
23
23
  import { admitMemoryProposalItemDetailed, buildFirstMemoryProposalPrompt, buildLocalizedMemoryProposalPrompt, buildMemoryCanonicalizationPrompt, deterministicMemoryPolicyRejection, minuteTimestamp, parseMemoryCanonicalization, parseLocalizedMemoryProposal, parseMemoryProposal, } from './memory-extraction-proposal.js';
24
- import { isHistoryCompactContentEvent } from './history-compaction.js';
25
- import { isTextHistoryCompactCheckpoint, matchHistoryCompactCheckpointPrefix, renderHistoryCompactCheckpoint, } from './history-compact-checkpoint.js';
24
+ import { isHistoryCompactContentEvent, isTextHistoryCompactCheckpoint, matchHistoryCompactCheckpointPrefix, renderHistoryCompactCheckpoint, } from './history-compact-checkpoint.js';
26
25
  import { applyRuntimeEventProviderHistoryBoundary } from './model-history.js';
27
26
  export const MEMORY_REMEMBER_TOOL_NAME = 'memory_remember';
28
27
  export const MEMORY_EXTRACT_TOOL_NAME = 'memory_extract';
@@ -59,8 +59,8 @@ export class PluginExecutorBackend {
59
59
  yield event;
60
60
  queue.ackConsumed();
61
61
  }
62
- const returnedResult = await producer;
63
- if (returnedResult) {
62
+ const settlement = await producer;
63
+ if (settlement === 'published') {
64
64
  // The Runtime Kernel requests the next item only after onSessionEvent
65
65
  // resolves. Reaching this point means its terminal event was accepted.
66
66
  // The Plugin decides whether its external execution actually settled;
@@ -78,8 +78,11 @@ export class PluginExecutorBackend {
78
78
  finally {
79
79
  queue.noteConsumerDetached();
80
80
  abort.abort(new Error('Plugin executor event consumer detached'));
81
- const returnedResult = await producer.catch(() => false);
82
- if (returnedResult &&
81
+ // A settled result that was not acknowledged is abandoned, including one
82
+ // the Runtime rejected before publishing it. A provider failure before
83
+ // settlement is neither acknowledged nor abandoned.
84
+ const settlement = await producer.catch(() => 'unsettled');
85
+ if (settlement !== 'unsettled' &&
83
86
  this.#binding.acknowledgeExecution &&
84
87
  !acknowledged &&
85
88
  this.#binding.abandonExecution)
@@ -113,6 +116,7 @@ export class PluginExecutorBackend {
113
116
  let result;
114
117
  let failure;
115
118
  let failed = false;
119
+ let settled = false;
116
120
  try {
117
121
  result = await this.#binding.execute({
118
122
  sessionId: this.sessionId,
@@ -130,6 +134,9 @@ export class PluginExecutorBackend {
130
134
  ...(input.quotes ? { quotes: input.quotes } : {}),
131
135
  }, {
132
136
  signal,
137
+ onSettled: () => {
138
+ settled = true;
139
+ },
133
140
  onEvent: (event) => {
134
141
  if (event.type === 'thinking_delta')
135
142
  thinkingText += event.text;
@@ -148,14 +155,14 @@ export class PluginExecutorBackend {
148
155
  this.#publishCancellation(turnId, { status: 'cancelled', source: 'caller' }, queue);
149
156
  else
150
157
  this.#publishFailure(turnId, failure instanceof Error ? failure.message : 'External executor failed', undefined, false, queue);
151
- return false;
158
+ return settled ? 'rejected' : 'unsettled';
152
159
  }
153
160
  if (result === undefined) {
154
161
  this.#publishFailure(turnId, 'External executor returned no terminal result', undefined, false, queue);
155
- return false;
162
+ return settled ? 'rejected' : 'unsettled';
156
163
  }
157
164
  this.#publishResult(turnId, messageId, result, queue);
158
- return true;
165
+ return 'published';
159
166
  }
160
167
  async #requestPermission(input, request, signal, toolUseIds, queue) {
161
168
  const hosted = input.hostedInteraction;
@@ -280,6 +280,12 @@ export class PluginExecutorService extends Service {
280
280
  return normalizePermissionResult(result, normalized);
281
281
  },
282
282
  });
283
+ try {
284
+ options.onSettled?.();
285
+ }
286
+ catch {
287
+ // A settlement observer must not change external execution.
288
+ }
283
289
  if (signal.aborted) {
284
290
  const cancelled = cancelledResult(signal.reason);
285
291
  const normalized = normalizeResult(result);