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.
- package/dist/pi-tui-layout.js +3 -3
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/event-wait.js +98 -2
- package/node_modules/@maka/core/dist/llm-connections.js +19 -1
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +12 -37
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +6 -10
- package/node_modules/@maka/runtime/dist/conversation-copy.js +11 -10
- package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +29 -0
- package/node_modules/@maka/runtime/dist/history-compaction.js +29 -19
- package/node_modules/@maka/runtime/dist/memory-extraction.js +1 -2
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +14 -7
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +6 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +10 -8
- package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +0 -19
- package/node_modules/@maka/runtime-host/dist/server/event-wait-adapter.js +47 -0
- package/node_modules/@maka/runtime-host/dist/server/event-wait-manager.js +1186 -0
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +13 -4
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +4 -51
- package/node_modules/@maka/runtime-host/dist/server/goal-evaluation-context.js +354 -0
- package/node_modules/@maka/runtime-host/dist/server/goal-form-evidence.js +118 -0
- package/node_modules/@maka/runtime-host/dist/server/skill-catalog-transaction.js +19 -5
- package/node_modules/@maka/storage/dist/codex-session-adapter.js +81 -2
- package/node_modules/@maka/storage/dist/event-wait-authority.js +132 -2
- package/node_modules/@maka/storage/dist/execution-stores.js +19 -1
- package/node_modules/@maka/storage/dist/sqlite-event-wait-authority.js +116 -1
- package/node_modules/@maka/storage/dist/sqlite-workflow-schema.js +16 -1
- package/node_modules/@maka/storage/package.json +1 -0
- package/package.json +1 -1
package/dist/pi-tui-layout.js
CHANGED
|
@@ -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
|
|
49
|
-
*
|
|
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:
|
|
63
|
+
editorRows: Math.max(0, budget - pendingLines.length),
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
export class MakaTranscriptComponent {
|
|
Binary file
|
|
@@ -29,7 +29,12 @@ export const EVENT_WAIT_LIMITS = Object.freeze({
|
|
|
29
29
|
evidenceRefs: 8,
|
|
30
30
|
evidenceRefBytes: 1024,
|
|
31
31
|
reasonBytes: 1024,
|
|
32
|
-
|
|
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-
|
|
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
|
|
152
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
475
|
-
if (
|
|
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
|
-
|
|
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-
|
|
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
|
|
1048
|
-
if (
|
|
1049
|
-
throw new Error(`durable checkpoint projection mismatch: ${
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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 =
|
|
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 =
|
|
209
|
-
?
|
|
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-
|
|
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
|
|
63
|
-
if (
|
|
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
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
162
|
+
return settled ? 'rejected' : 'unsettled';
|
|
156
163
|
}
|
|
157
164
|
this.#publishResult(turnId, messageId, result, queue);
|
|
158
|
-
return
|
|
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);
|