maka-agent 0.2.0-dev.76.20261009 → 0.2.0-dev.78.20261010
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/README.md +1 -1
- package/README.zh-CN.md +3 -3
- package/dist/pi-tui-pickers.js +5 -2
- package/dist/pi-tui-runner.js +211 -33
- package/dist/runtime-host-session-driver.js +91 -13
- package/dist/session-catalog-limits.js +22 -0
- package/dist/session-driver.js +10 -0
- package/dist/tui-copy-catalog.js +15 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/bot-chat-settings.js +3 -0
- package/node_modules/@maka/core/dist/bot-events.js +1 -8
- package/node_modules/@maka/core/dist/local-memory.js +38 -1
- package/node_modules/@maka/core/dist/scheduled-task.js +14 -15
- package/node_modules/@maka/core/dist/session.js +3 -0
- package/node_modules/@maka/core/dist/text-sanitize.js +8 -3
- package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
- package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +11 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +13 -5
- package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +29 -1
- package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
- package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
- package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
- package/node_modules/@maka/runtime/dist/file-stable-write.js +15 -3
- package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
- package/node_modules/@maka/runtime/dist/model-adapter.js +20 -1
- package/node_modules/@maka/runtime/dist/network/proxy-test.js +28 -1
- package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +13 -2
- package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
- package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
- package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +216 -8
- package/node_modules/@maka/runtime/dist/sandbox-boundary-tool.js +12 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +156 -17
- package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
- package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
- package/node_modules/@maka/runtime/dist/tool-availability.js +55 -18
- package/node_modules/@maka/runtime/dist/tool-call-snapshot.js +142 -0
- package/node_modules/@maka/runtime/dist/tool-runtime.js +27 -84
- package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +12 -1
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +30 -6
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
- package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
- package/node_modules/@maka/runtime-host/dist/protocol/message.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +4 -7
- package/node_modules/@maka/runtime-host/dist/server/archive-retention-coordinator.js +81 -105
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +87 -67
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
- package/node_modules/@maka/runtime-host/dist/server/host-maintenance-guards.js +95 -0
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +18 -8
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +32 -3
- package/node_modules/@maka/runtime-host/dist/server/project-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +109 -8
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
- package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
- package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
- package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
- package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +19 -24
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +74 -19
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +6 -99
- package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +14 -126
- package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
- package/node_modules/@maka/storage/dist/git-entry.js +142 -5
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
- package/package.json +2 -2
|
@@ -22,6 +22,15 @@ import { projectAgentGraphSchedule, } from './stream-graph-supervisor-tools.js';
|
|
|
22
22
|
import { stableHash } from './request-shape.js';
|
|
23
23
|
const MAX_RECONCILIATION_ATTEMPTS = 8;
|
|
24
24
|
const SCHEDULE_INTENT_SCHEMA_VERSION = 1;
|
|
25
|
+
// A failed control pass can park the wave it is stopping, and nothing else may
|
|
26
|
+
// wake it. Retry with a capped backoff while that wave is still running.
|
|
27
|
+
const CONTROL_RETRY_INITIAL_DELAY_MS = 100;
|
|
28
|
+
const CONTROL_RETRY_MAX_DELAY_MS = 5_000;
|
|
29
|
+
// The eighth consecutive failure of a stop or a control read lands about 11 s
|
|
30
|
+
// after the first, once the backoff has reached its cap: a fault that clears
|
|
31
|
+
// during the ramp is never reported as stuck, while a persistent one is
|
|
32
|
+
// surfaced instead of leaving the graph parked behind a single failure record.
|
|
33
|
+
const CONTROL_STUCK_FAILURES = 8;
|
|
25
34
|
/**
|
|
26
35
|
* Applies durable supervisor schedule intent to existing and newly
|
|
27
36
|
* materialized graph operators.
|
|
@@ -35,6 +44,39 @@ export async function reconcileAgentGraphSchedule(input) {
|
|
|
35
44
|
if (!Number.isSafeInteger(input.maxNewActivations) || input.maxNewActivations < 0) {
|
|
36
45
|
throw new Error('Agent graph maxNewActivations must be a non-negative safe integer');
|
|
37
46
|
}
|
|
47
|
+
const wake = createReconciliationWake();
|
|
48
|
+
// Install before reading the durable schedule: a commit during any await
|
|
49
|
+
// remains pending until this reconciliation consumes it.
|
|
50
|
+
const unsubscribe = input.subscribeToScheduleChanges?.(wake.notify);
|
|
51
|
+
try {
|
|
52
|
+
return await reconcileSchedule(input, wake);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
unsubscribe?.();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function createReconciliationWake() {
|
|
59
|
+
let pending = false;
|
|
60
|
+
let resolve;
|
|
61
|
+
let next = new Promise((done) => {
|
|
62
|
+
resolve = done;
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
notify() {
|
|
66
|
+
pending = true;
|
|
67
|
+
resolve();
|
|
68
|
+
},
|
|
69
|
+
async wait() {
|
|
70
|
+
if (!pending)
|
|
71
|
+
await next;
|
|
72
|
+
pending = false;
|
|
73
|
+
next = new Promise((done) => {
|
|
74
|
+
resolve = done;
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function reconcileSchedule(input, wake) {
|
|
38
80
|
const processedIntentIds = new Set();
|
|
39
81
|
const dispatches = [];
|
|
40
82
|
const stops = [];
|
|
@@ -43,6 +85,11 @@ export async function reconcileAgentGraphSchedule(input) {
|
|
|
43
85
|
// reconciliation; cache them per source graph so repeated snapshot reads do
|
|
44
86
|
// not replay the full committed projection of a closed epoch.
|
|
45
87
|
const selectedResultCache = new Map();
|
|
88
|
+
// Exact stops that already succeeded in this reconciliation; later passes
|
|
89
|
+
// and wakes do not stop the same activation again.
|
|
90
|
+
const resolvedStops = new Set();
|
|
91
|
+
// Consecutive stop failures per target, reset when the target resolves.
|
|
92
|
+
const stopFailureStreaks = new Map();
|
|
46
93
|
let newActivationCount = 0;
|
|
47
94
|
let observedExistingActivationCount = 0;
|
|
48
95
|
let snapshot = await readScheduleSnapshot(input, selectedResultCache);
|
|
@@ -50,10 +97,8 @@ export async function reconcileAgentGraphSchedule(input) {
|
|
|
50
97
|
if (input.abortSignal?.aborted) {
|
|
51
98
|
return reconciliationResult('cancelled', newActivationCount, observedExistingActivationCount, dispatches, stops, [], failures, snapshot);
|
|
52
99
|
}
|
|
53
|
-
const stopWave = await applyScheduleStops(input, snapshot);
|
|
54
|
-
stops
|
|
55
|
-
for (const failure of stopWave.failures)
|
|
56
|
-
recordReconciliationFailure(input, failures, failure);
|
|
100
|
+
const stopWave = await applyScheduleStops(input, snapshot, resolvedStops);
|
|
101
|
+
recordScheduleStops(input, stops, failures, stopFailureStreaks, stopWave);
|
|
57
102
|
if (failures.length > 0) {
|
|
58
103
|
snapshot = await readScheduleSnapshot(input, selectedResultCache);
|
|
59
104
|
return reconciliationResult(input.abortSignal?.aborted ? 'cancelled' : 'failed', newActivationCount, observedExistingActivationCount, dispatches, stops, [], failures, snapshot);
|
|
@@ -196,13 +241,81 @@ export async function reconcileAgentGraphSchedule(input) {
|
|
|
196
241
|
snapshot = await readScheduleSnapshot(input, selectedResultCache);
|
|
197
242
|
return reconciliationResult('failed', newActivationCount, observedExistingActivationCount, dispatches, stops, deferredWork, failures, snapshot);
|
|
198
243
|
}
|
|
199
|
-
|
|
244
|
+
let waveSettled = false;
|
|
245
|
+
const wave = Promise.all(prepared.map(async (work) => {
|
|
200
246
|
const outcome = await dispatchScheduledWork(input, work, snapshot.schedule.revision);
|
|
201
247
|
if (outcome.status === 'rejected') {
|
|
202
248
|
notifySupervisor(input.supervisor?.onReconciliationFailure, outcome.failure);
|
|
203
249
|
}
|
|
204
250
|
return outcome;
|
|
205
|
-
}))
|
|
251
|
+
})).finally(() => {
|
|
252
|
+
waveSettled = true;
|
|
253
|
+
wake.notify();
|
|
254
|
+
});
|
|
255
|
+
let controlReadFailure;
|
|
256
|
+
let controlReadFailureStreak = 0;
|
|
257
|
+
let retryTimer;
|
|
258
|
+
let retryDelay = CONTROL_RETRY_INITIAL_DELAY_MS;
|
|
259
|
+
try {
|
|
260
|
+
// A driver abort does not end this loop: the wave still owns its
|
|
261
|
+
// dispatches, and a child parked behind a failed stop settles only
|
|
262
|
+
// after a retry succeeds.
|
|
263
|
+
while (!waveSettled) {
|
|
264
|
+
await wake.wait();
|
|
265
|
+
clearTimeout(retryTimer);
|
|
266
|
+
retryTimer = undefined;
|
|
267
|
+
if (waveSettled)
|
|
268
|
+
break;
|
|
269
|
+
let retry;
|
|
270
|
+
try {
|
|
271
|
+
const latest = await readScheduleSnapshot(input, selectedResultCache);
|
|
272
|
+
const controls = await applyScheduleStops(input, latest, resolvedStops);
|
|
273
|
+
recordScheduleStops(input, stops, failures, stopFailureStreaks, controls);
|
|
274
|
+
if (controlReadFailure) {
|
|
275
|
+
failures.splice(failures.indexOf(controlReadFailure), 1);
|
|
276
|
+
controlReadFailure = undefined;
|
|
277
|
+
}
|
|
278
|
+
controlReadFailureStreak = 0;
|
|
279
|
+
retry = controls.failures.length > 0;
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (controlReadFailure)
|
|
283
|
+
failures.splice(failures.indexOf(controlReadFailure), 1);
|
|
284
|
+
controlReadFailureStreak += 1;
|
|
285
|
+
// Like a stop, a read failure notifies on its first occurrence and,
|
|
286
|
+
// once, when it has persisted long enough to be stuck.
|
|
287
|
+
controlReadFailure =
|
|
288
|
+
controlReadFailureStreak < CONTROL_STUCK_FAILURES
|
|
289
|
+
? { phase: 'schedule', error }
|
|
290
|
+
: {
|
|
291
|
+
phase: 'schedule',
|
|
292
|
+
error: new Error(`Reading graph schedule controls is stuck after ${controlReadFailureStreak} consecutive failures and is still retrying`, { cause: error }),
|
|
293
|
+
};
|
|
294
|
+
failures.push(controlReadFailure);
|
|
295
|
+
if (controlReadFailureStreak === 1 ||
|
|
296
|
+
controlReadFailureStreak === CONTROL_STUCK_FAILURES) {
|
|
297
|
+
notifySupervisor(input.supervisor?.onReconciliationFailure, controlReadFailure);
|
|
298
|
+
}
|
|
299
|
+
retry = true;
|
|
300
|
+
}
|
|
301
|
+
// A failed stop can keep its child, and so this wave, parked until a
|
|
302
|
+
// retry succeeds. Back off instead of waiting for another commit.
|
|
303
|
+
if (retry) {
|
|
304
|
+
retryTimer = setTimeout(wake.notify, retryDelay);
|
|
305
|
+
retryDelay = Math.min(retryDelay * 2, CONTROL_RETRY_MAX_DELAY_MS);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
retryDelay = CONTROL_RETRY_INITIAL_DELAY_MS;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
clearTimeout(retryTimer);
|
|
314
|
+
// This wave remains the sole owner of all dispatched executions, even
|
|
315
|
+
// if a control-store read fails while processing a schedule wake.
|
|
316
|
+
await wave;
|
|
317
|
+
}
|
|
318
|
+
const outcomes = await wave;
|
|
206
319
|
let stale = false;
|
|
207
320
|
for (const outcome of outcomes) {
|
|
208
321
|
if (outcome.status === 'stale') {
|
|
@@ -259,6 +372,7 @@ async function readScheduleSnapshot(input, selectedResultCache) {
|
|
|
259
372
|
assertGraphObservation(input.topology.graphId, observation);
|
|
260
373
|
notifySupervisor(input.supervisor?.onObservation, observation);
|
|
261
374
|
const schedule = projectAgentGraphSchedule(input.topology.graphId, updates);
|
|
375
|
+
notifySupervisor(input.onScheduleObserved, schedule);
|
|
262
376
|
const selectedInputs = schedule.work
|
|
263
377
|
.filter((work) => work.status === 'requested')
|
|
264
378
|
.flatMap((work) => work.selectedResultInputs ?? []);
|
|
@@ -359,7 +473,43 @@ async function resolveSelectedResultRecords(input, selectedInputs, cache) {
|
|
|
359
473
|
function selectedResultKey(selected) {
|
|
360
474
|
return `${selected.sourceGraphId}\u0000${selected.resultId}`;
|
|
361
475
|
}
|
|
362
|
-
|
|
476
|
+
function recordScheduleStops(input, stops, failures, streaks, controls) {
|
|
477
|
+
stops.push(...controls.stops);
|
|
478
|
+
const resolvedTargets = new Set(controls.stops.map((stop) => stop.targetId));
|
|
479
|
+
for (const targetId of resolvedTargets)
|
|
480
|
+
streaks.delete(targetId);
|
|
481
|
+
for (let index = failures.length - 1; index >= 0; index -= 1) {
|
|
482
|
+
const failure = failures[index];
|
|
483
|
+
if (failure.phase === 'stop' && failure.targetId && resolvedTargets.has(failure.targetId)) {
|
|
484
|
+
failures.splice(index, 1);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const failure of controls.failures) {
|
|
488
|
+
const targetId = failure.targetId ?? '';
|
|
489
|
+
const streak = (streaks.get(targetId) ?? 0) + 1;
|
|
490
|
+
streaks.set(targetId, streak);
|
|
491
|
+
// The stuck state travels in the message: supervisor notifications are
|
|
492
|
+
// structured clones and the durable client failure keeps only the reason.
|
|
493
|
+
const recorded = streak < CONTROL_STUCK_FAILURES
|
|
494
|
+
? failure
|
|
495
|
+
: {
|
|
496
|
+
...failure,
|
|
497
|
+
error: new Error(`Stop for graph target ${targetId} is stuck after ${streak} consecutive failures and is still retrying`, { cause: failure.error }),
|
|
498
|
+
};
|
|
499
|
+
// A retried stop that fails again replaces its earlier failure. The
|
|
500
|
+
// supervisor hears about the first failure and, once, about a stuck stop,
|
|
501
|
+
// not about every backoff attempt.
|
|
502
|
+
const previous = failures.findIndex((existing) => existing.phase === 'stop' && existing.targetId === failure.targetId);
|
|
503
|
+
if (previous >= 0)
|
|
504
|
+
failures[previous] = recorded;
|
|
505
|
+
else
|
|
506
|
+
failures.push(recorded);
|
|
507
|
+
if (streak === 1 || streak === CONTROL_STUCK_FAILURES) {
|
|
508
|
+
notifySupervisor(input.supervisor?.onReconciliationFailure, recorded);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function applyScheduleStops(input, snapshot, resolvedStops) {
|
|
363
513
|
const requests = new Map();
|
|
364
514
|
for (const stopped of snapshot.schedule.stoppedTargets) {
|
|
365
515
|
requests.set(stopped.targetId, stopped.reason);
|
|
@@ -375,7 +525,7 @@ async function applyScheduleStops(input, snapshot) {
|
|
|
375
525
|
const claimsByIntent = new Map(snapshot.claims.map((claim) => [claim.intentId, claim]));
|
|
376
526
|
const activationTargets = graphActivationTargets(snapshot.observation);
|
|
377
527
|
const immediate = [];
|
|
378
|
-
const
|
|
528
|
+
const targets = [];
|
|
379
529
|
const failures = [];
|
|
380
530
|
for (const [targetId, reason] of requests) {
|
|
381
531
|
const work = workById.get(targetId);
|
|
@@ -386,13 +536,26 @@ async function applyScheduleStops(input, snapshot) {
|
|
|
386
536
|
? activationTargets.get(claim.targetRunId)
|
|
387
537
|
: activationTargets.get(targetId);
|
|
388
538
|
if (activation && isTerminalActivationStatus(activation.status)) {
|
|
389
|
-
|
|
390
|
-
targetId,
|
|
391
|
-
reason,
|
|
392
|
-
status: 'already_terminal',
|
|
539
|
+
const identity = {
|
|
393
540
|
sessionId: activation.sessionId,
|
|
394
|
-
|
|
395
|
-
|
|
541
|
+
runId: activation.activationId,
|
|
542
|
+
turnId: activation.turnId,
|
|
543
|
+
};
|
|
544
|
+
// A durable terminal does not prove that backend cleanup succeeded.
|
|
545
|
+
// Retry the exact stop only while a failed stop retains cleanup for this
|
|
546
|
+
// Run; a settled historical target resolves without a runtime call.
|
|
547
|
+
if (resolvedStops.has(stopTargetKey(targetId, identity)) ||
|
|
548
|
+
!input.stopController.hasPendingAgentGraphActivationStop?.(identity)) {
|
|
549
|
+
immediate.push({
|
|
550
|
+
targetId,
|
|
551
|
+
reason,
|
|
552
|
+
status: 'already_terminal',
|
|
553
|
+
sessionId: activation.sessionId,
|
|
554
|
+
activationId: activation.activationId,
|
|
555
|
+
});
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
targets.push({ targetId, reason, status: 'already_terminal', identity });
|
|
396
559
|
continue;
|
|
397
560
|
}
|
|
398
561
|
if (work && claim) {
|
|
@@ -424,36 +587,37 @@ async function applyScheduleStops(input, snapshot) {
|
|
|
424
587
|
}
|
|
425
588
|
const sessionId = activation?.sessionId ?? claim.targetSessionId;
|
|
426
589
|
const activationId = activation?.activationId ?? claim.targetRunId;
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
reason,
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
590
|
+
const turnId = activation?.turnId ?? claim.targetTurnId;
|
|
591
|
+
const identity = { sessionId, runId: activationId, turnId };
|
|
592
|
+
if (resolvedStops.has(stopTargetKey(targetId, identity))) {
|
|
593
|
+
immediate.push({ targetId, reason, status: 'stopped', sessionId, activationId });
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
targets.push({ targetId, reason, status: 'stopped', identity });
|
|
434
597
|
}
|
|
435
|
-
const settled = await Promise.allSettled(
|
|
436
|
-
await input.stopController.
|
|
437
|
-
|
|
598
|
+
const settled = await Promise.allSettled(targets.map(async (target) => {
|
|
599
|
+
await input.stopController.stopAgentGraphActivation(target.identity, {
|
|
600
|
+
source: 'graph_supervisor',
|
|
601
|
+
});
|
|
602
|
+
return {
|
|
438
603
|
targetId: target.targetId,
|
|
439
604
|
reason: target.reason,
|
|
440
|
-
status:
|
|
441
|
-
sessionId,
|
|
442
|
-
activationId: target.
|
|
443
|
-
}
|
|
605
|
+
status: target.status,
|
|
606
|
+
sessionId: target.identity.sessionId,
|
|
607
|
+
activationId: target.identity.runId,
|
|
608
|
+
};
|
|
444
609
|
}));
|
|
445
610
|
settled.forEach((result, index) => {
|
|
611
|
+
const target = targets[index];
|
|
446
612
|
if (result.status === 'fulfilled') {
|
|
447
|
-
|
|
613
|
+
resolvedStops.add(stopTargetKey(target.targetId, target.identity));
|
|
614
|
+
immediate.push(result.value);
|
|
448
615
|
return;
|
|
449
616
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
targetId: target.targetId,
|
|
455
|
-
error: new Error(`Failed to stop graph supervisor target ${target.targetId} in ${sessionId}`, { cause: result.reason }),
|
|
456
|
-
});
|
|
617
|
+
failures.push({
|
|
618
|
+
phase: 'stop',
|
|
619
|
+
targetId: target.targetId,
|
|
620
|
+
error: new Error(`Failed to stop graph supervisor target ${target.targetId} in ${target.identity.sessionId}`, { cause: result.reason }),
|
|
457
621
|
});
|
|
458
622
|
});
|
|
459
623
|
return {
|
|
@@ -461,6 +625,9 @@ async function applyScheduleStops(input, snapshot) {
|
|
|
461
625
|
failures,
|
|
462
626
|
};
|
|
463
627
|
}
|
|
628
|
+
function stopTargetKey(targetId, identity) {
|
|
629
|
+
return [targetId, identity.sessionId, identity.runId, identity.turnId].join('\u0000');
|
|
630
|
+
}
|
|
464
631
|
async function dispatchScheduledWork(input, prepared, expectedRevision) {
|
|
465
632
|
let admission;
|
|
466
633
|
try {
|
|
@@ -593,7 +760,7 @@ function scheduledWorkIntent(topology, observation, work, provision) {
|
|
|
593
760
|
],
|
|
594
761
|
};
|
|
595
762
|
}
|
|
596
|
-
function scheduledWorkIntentId(graphId, workId) {
|
|
763
|
+
export function scheduledWorkIntentId(graphId, workId) {
|
|
597
764
|
const hash = stableHash({
|
|
598
765
|
schemaVersion: SCHEDULE_INTENT_SCHEMA_VERSION,
|
|
599
766
|
graphId,
|
|
@@ -712,15 +879,20 @@ function orderedRequestedWork(schedule) {
|
|
|
712
879
|
}
|
|
713
880
|
function graphActivationTargets(observation) {
|
|
714
881
|
const targets = new Map();
|
|
882
|
+
const sourceByActivation = new Map(observation.projection.records.map((record) => [record.activationId, record.source]));
|
|
715
883
|
for (const binding of observation.projection.operators) {
|
|
716
884
|
const state = observation.projection.state.operators[binding.operatorId];
|
|
717
885
|
for (const activation of Object.values(state?.activations ?? {})) {
|
|
718
886
|
if (targets.has(activation.activationId)) {
|
|
719
887
|
throw new Error(`Graph activation ${activation.activationId} belongs to multiple operators`);
|
|
720
888
|
}
|
|
889
|
+
const source = sourceByActivation.get(activation.activationId);
|
|
890
|
+
if (!source)
|
|
891
|
+
throw new Error(`Graph activation ${activation.activationId} has no source`);
|
|
721
892
|
targets.set(activation.activationId, {
|
|
722
893
|
sessionId: binding.sessionId,
|
|
723
894
|
activationId: activation.activationId,
|
|
895
|
+
turnId: source.turnId,
|
|
724
896
|
status: activation.status,
|
|
725
897
|
});
|
|
726
898
|
}
|
|
@@ -106,9 +106,9 @@ export function toolAvailabilityConnectorNames(tools, config) {
|
|
|
106
106
|
/**
|
|
107
107
|
* Immutable, backend-scoped bound-tool inventory and MiniSearch index.
|
|
108
108
|
*
|
|
109
|
-
* Mutable activation
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
* Mutable activation is passed into prepare(). One AiSdkBackend constructs one
|
|
110
|
+
* index; Turns on that backend share the Session activation map so later
|
|
111
|
+
* Turns keep previously activated schemas.
|
|
112
112
|
*/
|
|
113
113
|
export class ToolAvailabilityRuntime {
|
|
114
114
|
invalidTool;
|
|
@@ -249,7 +249,11 @@ export class ToolAvailabilityRuntime {
|
|
|
249
249
|
diagnostics: () => undefined,
|
|
250
250
|
};
|
|
251
251
|
}
|
|
252
|
-
const
|
|
252
|
+
const requiredNames = [...requiredToolNames].filter((name) => this.toolsByName.has(name));
|
|
253
|
+
const requiredNameSet = new Set(requiredNames);
|
|
254
|
+
// Required names are already on the provider list. Omit them from the
|
|
255
|
+
// search inventory so an exact-name search does not "activate" them again.
|
|
256
|
+
const connector = this.buildSearchConnector(activeTools, requiredNameSet);
|
|
253
257
|
const allTools = [...this.tools, connector];
|
|
254
258
|
const canonical = canonicalizeToolSet(allTools, this.invalidTool);
|
|
255
259
|
const knownNames = new Set(canonical.providerTools.map((tool) => tool.name));
|
|
@@ -260,7 +264,6 @@ export class ToolAvailabilityRuntime {
|
|
|
260
264
|
if (this.activationKeysByName.get(name) !== activatedKey)
|
|
261
265
|
activeTools.delete(name);
|
|
262
266
|
}
|
|
263
|
-
const requiredNames = [...requiredToolNames].filter((name) => knownNames.has(name));
|
|
264
267
|
const step = { active: new Set() };
|
|
265
268
|
const computeActive = () => {
|
|
266
269
|
const names = new Set([...this.directNames, TOOL_SEARCH_NAME]);
|
|
@@ -284,12 +287,16 @@ export class ToolAvailabilityRuntime {
|
|
|
284
287
|
diagnostics: (active, chars) => this.buildDiagnostic(allTools, active, chars),
|
|
285
288
|
};
|
|
286
289
|
}
|
|
287
|
-
buildSearchConnector(activeTools) {
|
|
290
|
+
buildSearchConnector(activeTools, requiredToolNames) {
|
|
288
291
|
return {
|
|
289
292
|
name: TOOL_SEARCH_NAME,
|
|
290
|
-
description: renderInventory(this.groups),
|
|
293
|
+
description: renderInventory(this.groups, requiredToolNames),
|
|
291
294
|
parameters: z.object({
|
|
292
|
-
query: z
|
|
295
|
+
query: z
|
|
296
|
+
.string()
|
|
297
|
+
.trim()
|
|
298
|
+
.min(1)
|
|
299
|
+
.describe('Search query describing the needed capability. An exact catalog tool name activates only that tool.'),
|
|
293
300
|
limit: z
|
|
294
301
|
.number()
|
|
295
302
|
.int()
|
|
@@ -300,11 +307,7 @@ export class ToolAvailabilityRuntime {
|
|
|
300
307
|
}),
|
|
301
308
|
impl: ({ query, limit = TOOL_SEARCH_DEFAULT_LIMIT }, context) => {
|
|
302
309
|
const normalizedQuery = query.trim();
|
|
303
|
-
const ranked = this.
|
|
304
|
-
.map((result) => String(result.id))
|
|
305
|
-
.filter((name) => !activeTools.has(name))
|
|
306
|
-
.slice(0, TOOL_SEARCH_MAX_LIMIT)
|
|
307
|
-
.filter((name) => this.searchableNames.has(name));
|
|
310
|
+
const ranked = this.rankDeferredSearchHits(normalizedQuery, activeTools, requiredToolNames);
|
|
308
311
|
const activated = [];
|
|
309
312
|
let blocked;
|
|
310
313
|
let schemaChars = 0;
|
|
@@ -355,6 +358,38 @@ export class ToolAvailabilityRuntime {
|
|
|
355
358
|
},
|
|
356
359
|
};
|
|
357
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Rank deferred tools for one search. An exact catalog name (case-sensitive,
|
|
363
|
+
* then unique case-insensitive) never expands into MiniSearch neighbors, so
|
|
364
|
+
* naming one tool cannot invalidate the prompt-cache prefix with up to
|
|
365
|
+
* `TOOL_SEARCH_DEFAULT_LIMIT` extra schemas.
|
|
366
|
+
*/
|
|
367
|
+
rankDeferredSearchHits(query, activeTools, requiredToolNames) {
|
|
368
|
+
const withheld = (name) => activeTools.has(name) || requiredToolNames.has(name);
|
|
369
|
+
const exactName = this.resolveExactCatalogName(query);
|
|
370
|
+
if (exactName !== undefined) {
|
|
371
|
+
return this.searchableNames.has(exactName) && !withheld(exactName) ? [exactName] : [];
|
|
372
|
+
}
|
|
373
|
+
return this.searchIndex.search(query)
|
|
374
|
+
.map((result) => String(result.id))
|
|
375
|
+
.filter((name) => !withheld(name))
|
|
376
|
+
.slice(0, TOOL_SEARCH_MAX_LIMIT)
|
|
377
|
+
.filter((name) => this.searchableNames.has(name));
|
|
378
|
+
}
|
|
379
|
+
resolveExactCatalogName(query) {
|
|
380
|
+
if (this.toolsByName.has(query))
|
|
381
|
+
return query;
|
|
382
|
+
const lowered = query.toLowerCase();
|
|
383
|
+
let match;
|
|
384
|
+
for (const name of this.toolsByName.keys()) {
|
|
385
|
+
if (name.toLowerCase() !== lowered)
|
|
386
|
+
continue;
|
|
387
|
+
if (match !== undefined)
|
|
388
|
+
return undefined;
|
|
389
|
+
match = name;
|
|
390
|
+
}
|
|
391
|
+
return match;
|
|
392
|
+
}
|
|
358
393
|
buildDiagnostic(allTools, active, visibleToolSchemaChars) {
|
|
359
394
|
const activeSet = new Set(active);
|
|
360
395
|
const enabledSourceIds = this.groups
|
|
@@ -384,11 +419,13 @@ export class ToolAvailabilityRuntime {
|
|
|
384
419
|
};
|
|
385
420
|
}
|
|
386
421
|
}
|
|
387
|
-
function renderInventory(groups) {
|
|
388
|
-
const lines = groups.flatMap((group) =>
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
422
|
+
function renderInventory(groups, omitNames = new Set()) {
|
|
423
|
+
const lines = groups.flatMap((group) => {
|
|
424
|
+
const names = group.toolNames.filter((name) => !omitNames.has(name));
|
|
425
|
+
if (names.length === 0)
|
|
426
|
+
return [];
|
|
427
|
+
return [`${group.id}:`, ...names.map((name) => `- ${name}`)];
|
|
428
|
+
});
|
|
392
429
|
return [
|
|
393
430
|
'Search the deferred tools bound to this run. A successful search activates the',
|
|
394
431
|
'bounded top matches; their complete callable definitions become visible on the',
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
/**
|
|
20
|
+
* The call-data boundary for one tool execution: the argument views a call
|
|
21
|
+
* produces.
|
|
22
|
+
*
|
|
23
|
+
* Extracted from `executeTool()` so the ownership of each rule is named
|
|
24
|
+
* instead of living inline in a thousand-line method. This module owns
|
|
25
|
+
* *construction only* — admission, dispatch identity, publication timing and
|
|
26
|
+
* persistence stay in `executeTool()`.
|
|
27
|
+
*/
|
|
28
|
+
import { computerUseModelCallArgs } from '@maka/core/computer-use';
|
|
29
|
+
/** The recursively frozen, cycle-rejecting argument snapshot. */
|
|
30
|
+
export function snapshotToolArgs(value) {
|
|
31
|
+
return snapshotJsonValue(value, new WeakSet());
|
|
32
|
+
}
|
|
33
|
+
function snapshotJsonValue(value, seen) {
|
|
34
|
+
if (value === null || typeof value !== 'object')
|
|
35
|
+
return value;
|
|
36
|
+
if (seen.has(value))
|
|
37
|
+
throw new Error('Tool arguments must not contain cycles');
|
|
38
|
+
seen.add(value);
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return Object.freeze(value.map((entry) => snapshotJsonValue(entry, seen)));
|
|
41
|
+
}
|
|
42
|
+
const output = {};
|
|
43
|
+
for (const key of Object.keys(value)) {
|
|
44
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
45
|
+
if (!descriptor || !('value' in descriptor)) {
|
|
46
|
+
throw new Error(`Tool argument ${key} must be a plain data property`);
|
|
47
|
+
}
|
|
48
|
+
output[key] = snapshotJsonValue(descriptor.value, seen);
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze(output);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validates call arguments against the tool's declared schema, accepting the
|
|
54
|
+
* schema shapes the runtime already supports (zod `safeParseAsync`/`safeParse`,
|
|
55
|
+
* a `validate` callable, or a standard-schema `~standard.validate`). A tool
|
|
56
|
+
* without a usable schema declares nothing and validates nothing.
|
|
57
|
+
*/
|
|
58
|
+
export async function validateDeclaredToolArgs(parameters, args) {
|
|
59
|
+
if (!parameters || (typeof parameters !== 'object' && typeof parameters !== 'function')) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const schema = parameters;
|
|
63
|
+
if (typeof schema.safeParseAsync === 'function') {
|
|
64
|
+
const parsed = await schema.safeParseAsync(args);
|
|
65
|
+
if (parsed.success)
|
|
66
|
+
return;
|
|
67
|
+
throw parsed.error;
|
|
68
|
+
}
|
|
69
|
+
if (typeof schema.safeParse === 'function') {
|
|
70
|
+
const parsed = schema.safeParse(args);
|
|
71
|
+
if (parsed.success)
|
|
72
|
+
return;
|
|
73
|
+
throw parsed.error;
|
|
74
|
+
}
|
|
75
|
+
if (typeof schema.validate === 'function') {
|
|
76
|
+
const parsed = await schema.validate(args);
|
|
77
|
+
if (parsed.success)
|
|
78
|
+
return;
|
|
79
|
+
throw parsed.error;
|
|
80
|
+
}
|
|
81
|
+
if (typeof schema['~standard']?.validate === 'function') {
|
|
82
|
+
const parsed = await schema['~standard'].validate(args);
|
|
83
|
+
if ('value' in parsed)
|
|
84
|
+
return;
|
|
85
|
+
throw new Error('Tool arguments failed declared schema validation', { cause: parsed.issues });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The named argument-view construction operation: validation, permission
|
|
90
|
+
* projection, and the persisted view, in the order and with the guards the
|
|
91
|
+
* inline region in `executeTool()` established.
|
|
92
|
+
*
|
|
93
|
+
* The args written into the `tool_start` event and the durable ledger are the
|
|
94
|
+
* record of the call the model reads back on its next turn
|
|
95
|
+
* (`model-history.ts` replays `event.content.args`).
|
|
96
|
+
*
|
|
97
|
+
* Computer Use used the host's approval summary there. That projection exists
|
|
98
|
+
* to decide and display a permission: it renames `window_id` to `windowId`,
|
|
99
|
+
* adds `approvalClass` and `rememberForTurnAllowed`, and drops every argument
|
|
100
|
+
* it does not need. On the real ToolRuntime a model that sent
|
|
101
|
+
* {action:'press_key', app, window_id, observation_id, element_id,
|
|
102
|
+
* text:'cmd+s'} read back {action, approvalClass, rememberForTurnAllowed,
|
|
103
|
+
* app, windowId, observationId} — a key the tool rejects, two fields it never
|
|
104
|
+
* sent, no element, and a press_key with no key. It then went on calling it
|
|
105
|
+
* that way.
|
|
106
|
+
*
|
|
107
|
+
* The permission prompt still reads the permission view, and the approval
|
|
108
|
+
* scope key is still computed from the raw call, so the projection only
|
|
109
|
+
* changes what is written down. `computerUseModelCallArgs` keeps the same
|
|
110
|
+
* privacy rule — screen-derived and user-typed values are reduced to a shape
|
|
111
|
+
* — and speaks the tool's own argument names.
|
|
112
|
+
*/
|
|
113
|
+
export async function buildToolCallArgs(input) {
|
|
114
|
+
let permissionArgs = input.executionArgs;
|
|
115
|
+
let permissionArgsError;
|
|
116
|
+
if (!input.directOnlyRejected) {
|
|
117
|
+
try {
|
|
118
|
+
if (!input.validationDeferred) {
|
|
119
|
+
await validateDeclaredToolArgs(input.parameters, input.executionArgs);
|
|
120
|
+
}
|
|
121
|
+
permissionArgs = input.permissionArgs
|
|
122
|
+
? snapshotToolArgs(input.permissionArgs(structuredClone(input.executionArgs), {
|
|
123
|
+
sessionId: input.sessionId,
|
|
124
|
+
turnId: input.turnId,
|
|
125
|
+
toolCallId: input.toolCallId,
|
|
126
|
+
}))
|
|
127
|
+
: input.executionArgs;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
permissionArgsError = error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const persistedArgs = input.categoryHint === 'computer_use'
|
|
134
|
+
? snapshotToolArgs(computerUseModelCallArgs(permissionArgs))
|
|
135
|
+
: permissionArgs;
|
|
136
|
+
return {
|
|
137
|
+
executionArgs: input.executionArgs,
|
|
138
|
+
permissionArgs,
|
|
139
|
+
permissionArgsError,
|
|
140
|
+
persistedArgs,
|
|
141
|
+
};
|
|
142
|
+
}
|