maka-agent 0.2.1-dev.89.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.
@@ -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');
@@ -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);
@@ -27,25 +27,6 @@ const ANTIGRAVITY_ENTRY_ID = 'antigravity-acp';
27
27
  const STAGING_DIRECTORY = 'builtin-plugin-staging-v1';
28
28
  const RUNTIME_ENTRY = 'plugin.mjs';
29
29
  const COMPOSITION_PATCH = 'maka.composition.json';
30
- // Built-in setup entries remain discoverable before their Plugin is installed.
31
- // Registered Plugins replace these placeholders with their live catalogs.
32
- const BUILTIN_EXTERNAL_AGENT_CATALOG = Object.freeze([
33
- Object.freeze({
34
- id: ANTIGRAVITY_ENTRY_ID,
35
- displayName: 'Antigravity',
36
- readiness: 'unavailable',
37
- models: Object.freeze([]),
38
- supportsAttachments: false,
39
- supportsModelChange: false,
40
- }),
41
- ]);
42
- export function withBuiltinExternalAgentCatalog(catalog) {
43
- const registered = new Set(catalog.map((entry) => entry.id));
44
- return [
45
- ...catalog,
46
- ...BUILTIN_EXTERNAL_AGENT_CATALOG.filter((entry) => !registered.has(entry.id)),
47
- ];
48
- }
49
30
  /**
50
31
  * Projects durable product settings into reserved Plugin packages and Entries.
51
32
  *
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ function key(provider, resource, condition, version) {
20
+ return JSON.stringify([provider, resource, condition, version]);
21
+ }
22
+ export class EventWaitAdapterRegistry {
23
+ #adapters = new Map();
24
+ constructor(adapters) {
25
+ for (const adapter of adapters) {
26
+ if (!['snapshot', 'history'].includes(adapter.reconciliation) ||
27
+ !Number.isSafeInteger(adapter.conditionVersion) ||
28
+ adapter.conditionVersion < 1 ||
29
+ [adapter.providerId, adapter.resourceType, adapter.conditionType].some((v) => typeof v !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(v)))
30
+ throw new TypeError('Invalid event wait adapter');
31
+ const id = key(adapter.providerId, adapter.resourceType, adapter.conditionType, adapter.conditionVersion);
32
+ if (this.#adapters.has(id))
33
+ throw new TypeError('Duplicate event wait adapter');
34
+ this.#adapters.set(id, adapter);
35
+ }
36
+ }
37
+ get(wait) {
38
+ return this.#adapters.get(key(wait.resource.providerId, wait.resource.resourceType, wait.condition.typeId, wait.condition.version));
39
+ }
40
+ validate(wait) {
41
+ const adapter = this.get(wait);
42
+ if (!adapter)
43
+ throw new TypeError('Unsupported event wait condition');
44
+ adapter.validate(wait);
45
+ return adapter;
46
+ }
47
+ }