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.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +3 -3
  3. package/dist/pi-tui-pickers.js +5 -2
  4. package/dist/pi-tui-runner.js +211 -33
  5. package/dist/runtime-host-session-driver.js +91 -13
  6. package/dist/session-catalog-limits.js +22 -0
  7. package/dist/session-driver.js +10 -0
  8. package/dist/tui-copy-catalog.js +15 -0
  9. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  10. package/node_modules/@maka/core/dist/bot-chat-settings.js +3 -0
  11. package/node_modules/@maka/core/dist/bot-events.js +1 -8
  12. package/node_modules/@maka/core/dist/local-memory.js +38 -1
  13. package/node_modules/@maka/core/dist/scheduled-task.js +14 -15
  14. package/node_modules/@maka/core/dist/session.js +3 -0
  15. package/node_modules/@maka/core/dist/text-sanitize.js +8 -3
  16. package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
  17. package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
  18. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +11 -0
  19. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +13 -5
  20. package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +29 -1
  21. package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
  22. package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
  23. package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
  24. package/node_modules/@maka/runtime/dist/file-stable-write.js +15 -3
  25. package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
  26. package/node_modules/@maka/runtime/dist/model-adapter.js +20 -1
  27. package/node_modules/@maka/runtime/dist/network/proxy-test.js +28 -1
  28. package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +13 -2
  29. package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
  30. package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
  31. package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
  32. package/node_modules/@maka/runtime/dist/runtime-kernel.js +216 -8
  33. package/node_modules/@maka/runtime/dist/sandbox-boundary-tool.js +12 -0
  34. package/node_modules/@maka/runtime/dist/session-manager.js +156 -17
  35. package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
  36. package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
  37. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
  38. package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
  39. package/node_modules/@maka/runtime/dist/tool-availability.js +55 -18
  40. package/node_modules/@maka/runtime/dist/tool-call-snapshot.js +142 -0
  41. package/node_modules/@maka/runtime/dist/tool-runtime.js +27 -84
  42. package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +12 -1
  43. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +30 -6
  44. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  45. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
  46. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
  47. package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
  48. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  49. package/node_modules/@maka/runtime-host/dist/protocol/message.js +1 -13
  50. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
  51. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -13
  52. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +1 -13
  53. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +4 -7
  54. package/node_modules/@maka/runtime-host/dist/server/archive-retention-coordinator.js +81 -105
  55. package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
  56. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +87 -67
  57. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
  58. package/node_modules/@maka/runtime-host/dist/server/host-maintenance-guards.js +95 -0
  59. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +18 -8
  60. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +32 -3
  61. package/node_modules/@maka/runtime-host/dist/server/project-catalog-coordinator.js +2 -0
  62. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +109 -8
  63. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
  64. package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
  65. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
  66. package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
  67. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
  68. package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
  69. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +2 -0
  70. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +19 -24
  71. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +74 -19
  72. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +1 -1
  73. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +6 -99
  74. package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +14 -126
  75. package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
  76. package/node_modules/@maka/storage/dist/git-entry.js +142 -5
  77. package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
  78. package/package.json +2 -2
@@ -21,6 +21,7 @@ import { randomUUID } from 'node:crypto';
21
21
  import { decodeRuntimeBoundaryCursor, } from '@maka/core/runtime-boundary';
22
22
  import { isTerminalRuntimeEvent, } from '@maka/core/runtime-event';
23
23
  import { isDeepStrictEqual } from 'node:util';
24
+ import { createGenesisExecutionBoundary } from '@maka/core/sandbox-boundary';
24
25
  import { resolveEffectiveOrchestration, } from '@maka/core/orchestration';
25
26
  import { AgentRun, ContinuationStartCommitError, } from './agent-run.js';
26
27
  import { createSessionEventMapMemory, isLiveBackendSessionEvent, mapSessionEventToRuntimeEvent, } from './session-event-runtime-mapper.js';
@@ -320,7 +321,17 @@ export class RuntimeKernel {
320
321
  const execution = this.takeExecutionClaim(sessionId, options.execution);
321
322
  try {
322
323
  await this.enterExecutionClaim(execution);
323
- const header = await this.readBackendHeader(execution);
324
+ execution.executionPolicy = options.executionPolicy;
325
+ const existing = this.active.get(sessionId);
326
+ if (existing && !isDeepStrictEqual(existing.executionPolicy, options.executionPolicy)) {
327
+ if (existing.activeRuns.size > 0)
328
+ throw new Error('Cannot change execution policy while a Turn is running');
329
+ await this.disposeBackend(sessionId);
330
+ }
331
+ const storedHeader = await this.readBackendHeader(execution);
332
+ const header = options.executionPolicy
333
+ ? { ...storedHeader, permissionMode: options.executionPolicy.permissionMode }
334
+ : storedHeader;
324
335
  let workspaceIdentity;
325
336
  if (this.deps.inspectContinuationSafety) {
326
337
  try {
@@ -355,7 +366,12 @@ export class RuntimeKernel {
355
366
  return active;
356
367
  },
357
368
  unregisterRun: (active, activeRun) => this.unregisterParentRun(active, activeRun),
358
- updateHeader: (targetSessionId, patch) => this.updateHeader(targetSessionId, patch),
369
+ updateHeader: async (targetSessionId, patch) => {
370
+ const updated = await this.updateHeader(targetSessionId, patch);
371
+ return options.executionPolicy
372
+ ? { ...updated, permissionMode: options.executionPolicy.permissionMode }
373
+ : updated;
374
+ },
359
375
  updateStatus: (targetSessionId, status, blockedReason, ts) => this.updateStatus(targetSessionId, status, blockedReason, ts),
360
376
  ...this.messageProjectionHook(),
361
377
  },
@@ -410,7 +426,14 @@ export class RuntimeKernel {
410
426
  (this.executionClaims.get(continuation.sessionId)?.size ?? 0) > 1) {
411
427
  throw new Error('Cannot continue while another run is active');
412
428
  }
413
- const header = await this.readBackendHeader(execution);
429
+ execution.executionPolicy = options.executionPolicy;
430
+ const existing = this.active.get(continuation.sessionId);
431
+ if (existing && !isDeepStrictEqual(existing.executionPolicy, options.executionPolicy))
432
+ await this.disposeBackend(continuation.sessionId);
433
+ const storedHeader = await this.readBackendHeader(execution);
434
+ const header = options.executionPolicy
435
+ ? { ...storedHeader, permissionMode: options.executionPolicy.permissionMode }
436
+ : storedHeader;
414
437
  const sessionRuns = await this.deps.runtimeEventStore.listSessionInvocations(continuation.sessionId);
415
438
  const sourceRun = sessionRuns.find((run) => run.runId === continuation.sourceRunId);
416
439
  if (!sourceRun) {
@@ -564,7 +587,12 @@ export class RuntimeKernel {
564
587
  return active;
565
588
  },
566
589
  unregisterRun: (active, activeRun) => this.unregisterParentRun(active, activeRun),
567
- updateHeader: (targetSessionId, patch) => this.updateHeader(targetSessionId, patch),
590
+ updateHeader: async (targetSessionId, patch) => {
591
+ const updated = await this.updateHeader(targetSessionId, patch);
592
+ return options.executionPolicy
593
+ ? { ...updated, permissionMode: options.executionPolicy.permissionMode }
594
+ : updated;
595
+ },
568
596
  updateStatus: (targetSessionId, status, blockedReason, ts) => this.updateStatus(targetSessionId, status, blockedReason, ts),
569
597
  ...this.messageProjectionHook(),
570
598
  },
@@ -1308,6 +1336,106 @@ export class RuntimeKernel {
1308
1336
  },
1309
1337
  ];
1310
1338
  }
1339
+ /** Stop one captured owner without cancelling other queued executions in its Session. */
1340
+ stopExecution(claim, input = {}) {
1341
+ normalizeStopSessionSource(input.source, input.workHubActionId);
1342
+ const execution = this.executionClaimStates.get(claim);
1343
+ if (!execution)
1344
+ throw new Error('Runtime execution claim belongs to another owner');
1345
+ if (execution.scopedStopAttempt)
1346
+ return execution.scopedStopAttempt;
1347
+ const run = execution.run;
1348
+ const active = run &&
1349
+ this.backendGenerationsFor(execution.sessionId).find((candidate) => candidate.activeRuns.get(run.runId) === run);
1350
+ if (!active && (execution.phase === 'released' || execution.phase === 'failed')) {
1351
+ return run
1352
+ ? this.retryStoppedRun({
1353
+ sessionId: execution.sessionId,
1354
+ runId: run.runId,
1355
+ turnId: run.turnId,
1356
+ }, input)
1357
+ : Promise.resolve();
1358
+ }
1359
+ if (active && run)
1360
+ this.assertScopedStopGeneration(active, run);
1361
+ // Validate first, then capture the capability synchronously. A pending
1362
+ // owner carries the stop through attach/reserve without a Session fence.
1363
+ execution.stopIntent ??= { input, claims: new Set([execution]) };
1364
+ if (active && run)
1365
+ this.captureScopedRunStop(active, run, input);
1366
+ else
1367
+ run?.stop(input.source, input.workHubActionId);
1368
+ execution.abortController.abort(execution.cancellation);
1369
+ const attempt = this.stopCapturedExecution(execution, input).finally(() => {
1370
+ if (execution.scopedStopAttempt === attempt)
1371
+ execution.scopedStopAttempt = undefined;
1372
+ });
1373
+ execution.scopedStopAttempt = attempt;
1374
+ return attempt;
1375
+ }
1376
+ /** A failed cleanup attempt keeps its captured owner parked until a successful retry. */
1377
+ waitForExecutionStop(claim) {
1378
+ const run = this.executionClaimStates.get(claim)?.run;
1379
+ const operation = run &&
1380
+ this.retainedRunStopOperation({
1381
+ sessionId: claim.sessionId,
1382
+ runId: run.runId,
1383
+ turnId: run.turnId,
1384
+ });
1385
+ return operation ? operation.completion : Promise.resolve();
1386
+ }
1387
+ /** Whether a retained stop operation still owns cleanup for this exact Run. */
1388
+ hasPendingRunStop(identity) {
1389
+ return this.retainedRunStopOperation(identity) !== undefined;
1390
+ }
1391
+ retainedRunStopOperation(identity) {
1392
+ const operation = this.stopOperations.get(identity.sessionId);
1393
+ return operation &&
1394
+ [...operation.targets.values()].some((target) => target.runs.get(identity.runId)?.turnId === identity.turnId)
1395
+ ? operation
1396
+ : undefined;
1397
+ }
1398
+ /** Exact identity lookup never redirects a completed Run's stop to its successor. */
1399
+ stopRun(identity, input = {}) {
1400
+ normalizeStopSessionSource(input.source, input.workHubActionId);
1401
+ const execution = [...(this.executionClaims.get(identity.sessionId) ?? [])].find((candidate) => candidate.run?.runId === identity.runId && candidate.run.turnId === identity.turnId);
1402
+ if (execution)
1403
+ return this.stopExecution(execution.handle, input);
1404
+ for (const active of this.backendGenerationsFor(identity.sessionId)) {
1405
+ const run = active.activeRuns.get(identity.runId);
1406
+ if (!run || run.turnId !== identity.turnId)
1407
+ continue;
1408
+ this.assertScopedStopGeneration(active, run);
1409
+ this.captureScopedRunStop(active, run, input);
1410
+ return this.retryStoppedRun(identity, input);
1411
+ }
1412
+ return this.retryStoppedRun(identity, input);
1413
+ }
1414
+ assertScopedStopGeneration(active, run) {
1415
+ if ([...active.activeRuns.values()].some((other) => other !== run && !other.isStopped())) {
1416
+ throw new Error('Cannot stop one Run while its backend generation owns another active Run');
1417
+ }
1418
+ }
1419
+ captureScopedRunStop(active, run, input) {
1420
+ const operation = this.claimRunForStop(active.sessionId, input, active, run);
1421
+ if (operation && active.phase === 'active')
1422
+ active.phase = 'stopping';
1423
+ }
1424
+ async stopCapturedExecution(execution, input) {
1425
+ await execution.settled;
1426
+ if (execution.run) {
1427
+ await this.retryStoppedRun({
1428
+ sessionId: execution.sessionId,
1429
+ runId: execution.run.runId,
1430
+ turnId: execution.run.turnId,
1431
+ }, input);
1432
+ }
1433
+ }
1434
+ async retryStoppedRun(identity, input) {
1435
+ const operation = this.retainedRunStopOperation(identity);
1436
+ if (operation)
1437
+ await this.enqueueStopOperation(identity.sessionId, operation, input, true);
1438
+ }
1311
1439
  stopSession(sessionId, input = {}) {
1312
1440
  normalizeStopSessionSource(input.source, input.workHubActionId);
1313
1441
  const existing = this.stopAttempts.get(sessionId);
@@ -1403,12 +1531,22 @@ export class RuntimeKernel {
1403
1531
  buildStopOperation(input) {
1404
1532
  const abortSource = normalizeStopSessionSource(input.source, input.workHubActionId);
1405
1533
  const ts = this.deps.now();
1534
+ let resolveCompletion;
1535
+ let rejectCompletion;
1536
+ const completion = new Promise((resolve, reject) => {
1537
+ resolveCompletion = resolve;
1538
+ rejectCompletion = reject;
1539
+ });
1540
+ void completion.catch(() => undefined);
1406
1541
  return {
1407
1542
  abortSource,
1408
1543
  ts,
1409
1544
  statusProjected: false,
1410
1545
  targets: new Map(),
1411
1546
  queue: Promise.resolve(),
1547
+ completion,
1548
+ resolveCompletion,
1549
+ rejectCompletion,
1412
1550
  };
1413
1551
  }
1414
1552
  enqueueStopOperation(sessionId, operation, input, deliverPending) {
@@ -1507,7 +1645,16 @@ export class RuntimeKernel {
1507
1645
  if (target.delivery.kind === 'failed')
1508
1646
  failures.add(target.delivery.error);
1509
1647
  }
1510
- failures.throwIfAny(`Stop cleanup failed for session ${sessionId}`);
1648
+ try {
1649
+ failures.throwIfAny(`Stop cleanup failed for session ${sessionId}`);
1650
+ if (completed)
1651
+ operation.resolveCompletion();
1652
+ }
1653
+ catch (error) {
1654
+ if (completed)
1655
+ operation.rejectCompletion(error);
1656
+ throw error;
1657
+ }
1511
1658
  }
1512
1659
  async respondToSandboxBoundary(sessionId, response) {
1513
1660
  const key = interactionOwnerKey(sessionId, response.requestId);
@@ -1570,6 +1717,32 @@ export class RuntimeKernel {
1570
1717
  hasActiveRun(sessionId, runId, turnId) {
1571
1718
  return this.activeRunsFor(sessionId).some((run) => run.runId === runId && (turnId === undefined || run.turnId === turnId));
1572
1719
  }
1720
+ readExecutionPolicy(sessionId, runId) {
1721
+ for (const generation of this.backendGenerationsFor(sessionId)) {
1722
+ if (generation.activeRuns.has(runId))
1723
+ return generation.executionPolicy;
1724
+ }
1725
+ return [...(this.executionClaims.get(sessionId) ?? [])].find((execution) => execution.run?.runId === runId)?.executionPolicy;
1726
+ }
1727
+ returnExecutionQuestions(sessionId, runId) {
1728
+ for (const generation of this.backendGenerationsFor(sessionId)) {
1729
+ const run = generation.activeRuns.get(runId);
1730
+ if (!run)
1731
+ continue;
1732
+ const policy = {
1733
+ permissionMode: run.headerSnapshot().permissionMode,
1734
+ questions: 'return',
1735
+ };
1736
+ generation.executionPolicy = policy;
1737
+ for (const execution of this.executionClaims.get(sessionId) ?? []) {
1738
+ if (execution.run?.runId === runId)
1739
+ execution.executionPolicy = policy;
1740
+ }
1741
+ return;
1742
+ }
1743
+ // A terminal backend can precede the Host's terminal admission. Its queued
1744
+ // steering is recovered into a successor with the durable delegated policy.
1745
+ }
1573
1746
  requestRunHandoff(sessionId, runId, pause, signal) {
1574
1747
  if (!this.deps.inspectContinuationSafety)
1575
1748
  return undefined;
@@ -1868,11 +2041,17 @@ export class RuntimeKernel {
1868
2041
  }));
1869
2042
  execution.run?.bindProviderStateIdentity(prepared.providerStateIdentity);
1870
2043
  const subagent = await this.resolveSubagentActivation(header);
2044
+ const kernel = this;
1871
2045
  const backend = await prepared.build({
1872
2046
  sessionId,
1873
2047
  workspaceRoot: header.workspaceRoot,
1874
2048
  header,
1875
- store: this.deps.store,
2049
+ store: execution.executionPolicy
2050
+ ? this.executionPolicyStore(sessionId, execution.executionPolicy)
2051
+ : this.deps.store,
2052
+ get executionPolicy() {
2053
+ return kernel.active.get(sessionId)?.executionPolicy ?? execution.executionPolicy;
2054
+ },
1876
2055
  abortSignal: execution.abortController.signal,
1877
2056
  ...(subagent
1878
2057
  ? {
@@ -1888,12 +2067,39 @@ export class RuntimeKernel {
1888
2067
  });
1889
2068
  await this.rejectCancelledBackendActivation(backend, header, execution);
1890
2069
  const generation = this.createBackendGeneration(sessionId, backend, header, prepared.providerStateIdentity);
2070
+ generation.executionPolicy = execution.executionPolicy;
1891
2071
  this.active.set(sessionId, generation);
1892
2072
  return generation;
1893
2073
  });
1894
2074
  entry.cachedHeader = header;
1895
2075
  return entry;
1896
2076
  }
2077
+ executionPolicyStore(targetSessionId, policy) {
2078
+ const store = this.deps.store;
2079
+ // Host stores are frozen. Proxy invariants forbid replacing their own
2080
+ // non-configurable methods, so intercept on an independent facade instead.
2081
+ return new Proxy(Object.create(store), {
2082
+ get(_facade, property) {
2083
+ if (property === 'readHeader')
2084
+ return async (sessionId) => {
2085
+ const header = await store.readHeader(sessionId);
2086
+ return sessionId === targetSessionId
2087
+ ? { ...header, permissionMode: policy.permissionMode }
2088
+ : header;
2089
+ };
2090
+ if (property === 'readExecutionBoundary')
2091
+ return async (sessionId) => {
2092
+ const boundary = await store.readExecutionBoundary(sessionId);
2093
+ if (sessionId === targetSessionId &&
2094
+ (policy.permissionMode === 'bypass' || boundary.kind === 'bypass'))
2095
+ return createGenesisExecutionBoundary(policy.permissionMode);
2096
+ return boundary;
2097
+ };
2098
+ const value = Reflect.get(store, property);
2099
+ return typeof value === 'function' ? value.bind(store) : value;
2100
+ },
2101
+ });
2102
+ }
1897
2103
  async shareBackendActivation(activationKey, activate) {
1898
2104
  let activation = this.backendActivationBuilds.get(activationKey);
1899
2105
  if (!activation) {
@@ -2038,7 +2244,9 @@ export class RuntimeKernel {
2038
2244
  releaseStoppedRunReferences(operation, run) {
2039
2245
  for (const target of operation.targets.values()) {
2040
2246
  const stoppedRun = target.runs.get(run.runId);
2041
- if (stoppedRun?.run === run)
2247
+ // A failed terminal append still needs this captured Run on retry, even
2248
+ // after its stream exits. Drop it only once stop settlement succeeded.
2249
+ if (stoppedRun?.run === run && stoppedRun.stopCompleted)
2042
2250
  stoppedRun.run = undefined;
2043
2251
  if (target.active &&
2044
2252
  target.delivery.kind !== 'pending' &&
@@ -2077,7 +2285,7 @@ export class RuntimeKernel {
2077
2285
  async clearBackendQuarantineForActivation(sessionId, execution) {
2078
2286
  const ownsCurrentStop = execution.phase === 'attached' &&
2079
2287
  execution.stopIntent !== undefined &&
2080
- this.stopIntents.get(sessionId) === execution.stopIntent;
2288
+ execution.stopIntent.claims.has(execution);
2081
2289
  if (this.stopOperations.has(sessionId) && !ownsCurrentStop) {
2082
2290
  throw new Error(`Session ${sessionId} is quarantined by a retained stop operation`);
2083
2291
  }
@@ -31,6 +31,18 @@ export const SANDBOX_BOUNDARY_UNAVAILABLE = 'request_sandbox_boundary is not ava
31
31
  'Retrying will fail the same way — redo the work inside the paths already allowed, or tell the ' +
32
32
  'user which path needs access.';
33
33
  export const REQUEST_SANDBOX_BOUNDARY_TOOL_NAME = 'request_sandbox_boundary';
34
+ /**
35
+ * Session-stable required names for the widening tool. Host only binds it when
36
+ * the Session is not Full access. Passing the name through `requiredToolNames`
37
+ * keeps the schema visible without a `tool_search` hop, and without depending
38
+ * on a live boundary prompt that would change the provider prefix per Turn
39
+ * (#5879). Returns empty when the tool is not bound.
40
+ */
41
+ export function requiredSandboxBoundaryToolNames(hostTools) {
42
+ return hostTools.some((tool) => tool.name === REQUEST_SANDBOX_BOUNDARY_TOOL_NAME)
43
+ ? new Set([REQUEST_SANDBOX_BOUNDARY_TOOL_NAME])
44
+ : new Set();
45
+ }
34
46
  export const SANDBOX_BOUNDARY_DENIED_FOR_TURN = 'The user denied a sandbox boundary expansion for this Turn. Do not request another expansion. ' +
35
47
  'Continue only with the authority already available, or explain the remaining blocker.';
36
48
  export const SANDBOX_BOUNDARY_FINALIZATION_PROMPT = [
@@ -27,7 +27,7 @@ import { PLAN_USER_ABANDON_REASON, PLAN_USER_CANCEL_REASON, PlanConflictError, a
27
27
  import { DEFAULT_SESSION_NAME } from '@maka/core/session-name';
28
28
  import { SUBAGENT_SESSION_RUNTIME_SCHEMA_VERSION, SUBAGENT_SESSION_SPAWN_SCHEMA_VERSION, childSessionsForParent, subagentSessionRuntimeSummary, } from '@maka/core/session';
29
29
  import { decodeAgentGraphIntentClaim } from '@maka/core/agent-graph-control';
30
- import { executionBoundaryContains } from '@maka/core/sandbox-boundary';
30
+ import { createGenesisExecutionBoundary, executionBoundaryContains, } from '@maka/core/sandbox-boundary';
31
31
  import { failureClassFromCompleteStopReason } from '@maka/core/events';
32
32
  import { isActiveShellRunStatus } from '@maka/core/shell-run';
33
33
  import { isTerminalRuntimeEvent } from '@maka/core/runtime-event';
@@ -1436,6 +1436,22 @@ export class SessionManager {
1436
1436
  async preflightContextCompaction(sessionId) {
1437
1437
  await this.runtimeKernel.preflightContextCompaction(sessionId);
1438
1438
  }
1439
+ returnExecutionQuestions(sessionId, runId) {
1440
+ if (!this.runtimeKernel.returnExecutionQuestions)
1441
+ throw new Error('Runtime does not support execution question policy');
1442
+ this.runtimeKernel.returnExecutionQuestions(sessionId, runId);
1443
+ }
1444
+ async readExecutionPolicy(sessionId, runId) {
1445
+ return (this.runtimeKernel.readExecutionPolicy?.(sessionId, runId) ??
1446
+ (await this.deps.resolveExecutionPolicy?.(sessionId, runId)));
1447
+ }
1448
+ async readExecutionBoundaryForRun(sessionId, runId) {
1449
+ const policy = await this.readExecutionPolicy(sessionId, runId);
1450
+ const boundary = await this.deps.store.readExecutionBoundary(sessionId);
1451
+ return policy && (policy.permissionMode === 'bypass' || boundary.kind === 'bypass')
1452
+ ? createGenesisExecutionBoundary(policy.permissionMode)
1453
+ : boundary;
1454
+ }
1439
1455
  /**
1440
1456
  * Create and run a durable linked child Session.
1441
1457
  *
@@ -1504,7 +1520,7 @@ export class SessionManager {
1504
1520
  const [parentHeader, sourceRun, parentBoundary] = await Promise.all([
1505
1521
  this.deps.store.readHeader(input.source.sessionId),
1506
1522
  this.readInvocation(input.source.sessionId, input.source.runId),
1507
- this.deps.store.readExecutionBoundary(input.source.sessionId),
1523
+ this.readExecutionBoundaryForRun(input.source.sessionId, input.source.runId),
1508
1524
  ]);
1509
1525
  if (sourceRun.sessionId !== input.source.sessionId ||
1510
1526
  sourceRun.runId !== input.source.runId ||
@@ -1530,7 +1546,10 @@ export class SessionManager {
1530
1546
  const resolvedToolNames = executorId
1531
1547
  ? []
1532
1548
  : await this.resolveChildToolNames(input.source.sessionId, parentHeader, definition);
1533
- const childPermissionMode = parentHeader.permissionMode === 'bypass' ? 'bypass' : definition.permissionMode;
1549
+ const childPermissionMode = ((await this.readExecutionPolicy(input.source.sessionId, input.source.runId))
1550
+ ?.permissionMode ?? parentHeader.permissionMode) === 'bypass'
1551
+ ? 'bypass'
1552
+ : definition.permissionMode;
1534
1553
  const initialTurnId = this.deps.newId();
1535
1554
  const initialRunId = this.deps.newId();
1536
1555
  const identityHash = stableHash({
@@ -1698,9 +1717,28 @@ export class SessionManager {
1698
1717
  return await inFlight.promise;
1699
1718
  }
1700
1719
  const runtimeExecution = this.runtimeKernel.claimExecution(claim.targetSessionId);
1701
- const promise = this.enqueueClaimedAgentGraphIntent(resolved, runtimeExecution).finally(() => runtimeExecution.release());
1720
+ const owner = {
1721
+ identity: {
1722
+ sessionId: claim.targetSessionId,
1723
+ runId: claim.targetRunId,
1724
+ turnId: claim.targetTurnId,
1725
+ },
1726
+ execution: runtimeExecution,
1727
+ hostAdmitted: false,
1728
+ };
1729
+ resolved.onHostAdmission = () => {
1730
+ owner.hostAdmitted = true;
1731
+ };
1732
+ const promise = this.enqueueClaimedAgentGraphIntent(resolved, runtimeExecution, async () => {
1733
+ runtimeExecution.release();
1734
+ // Keep the existing Session slot until scoped backend cleanup completes.
1735
+ // Other queued claims retain their own uncancelled runtime capabilities.
1736
+ await owner.stopTask?.catch(() => undefined);
1737
+ await this.runtimeKernel.waitForExecutionStop?.(runtimeExecution);
1738
+ });
1702
1739
  this.claimedAgentGraphIntentRuns.set(claim.claimId, {
1703
1740
  requestFingerprint,
1741
+ owner,
1704
1742
  promise,
1705
1743
  });
1706
1744
  try {
@@ -1712,7 +1750,7 @@ export class SessionManager {
1712
1750
  }
1713
1751
  }
1714
1752
  }
1715
- enqueueClaimedAgentGraphIntent(input, runtimeExecution) {
1753
+ enqueueClaimedAgentGraphIntent(input, runtimeExecution, release) {
1716
1754
  const sessionId = input.claim.targetSessionId;
1717
1755
  const previous = this.claimedAgentGraphSessionTails.get(sessionId) ?? Promise.resolve();
1718
1756
  let enteredQueue = false;
@@ -1729,7 +1767,8 @@ export class SessionManager {
1729
1767
  }
1730
1768
  enteredQueue = true;
1731
1769
  return this.runClaimedAgentGraphIntentOnce(input, runtimeExecution);
1732
- });
1770
+ })
1771
+ .finally(release);
1733
1772
  const tail = queuedExecution.then(() => { }, () => { });
1734
1773
  this.claimedAgentGraphSessionTails.set(sessionId, tail);
1735
1774
  void tail.then(() => {
@@ -1742,8 +1781,10 @@ export class SessionManager {
1742
1781
  rejectStopped = reject;
1743
1782
  });
1744
1783
  const onRuntimeStop = () => {
1745
- if (!enteredQueue)
1784
+ if (!enteredQueue) {
1785
+ runtimeExecution.release();
1746
1786
  rejectStopped(runtimeExecution.stopSignal.reason);
1787
+ }
1747
1788
  };
1748
1789
  runtimeExecution.stopSignal.addEventListener('abort', onRuntimeStop, { once: true });
1749
1790
  if (runtimeExecution.stopSignal.aborted)
@@ -1769,6 +1810,7 @@ export class SessionManager {
1769
1810
  throw new Error('Claimed graph execution target must be a linked child session');
1770
1811
  }
1771
1812
  await this.assertLinkedChildBoundaryMatchesParent(child.subagentParent.parentSessionId, child.id);
1813
+ const executionPolicy = await this.readExecutionPolicy(child.subagentParent.parentSessionId, child.subagentParent.spawnedBy.parentRunId);
1772
1814
  const rootExecution = {
1773
1815
  kind: 'claimed_agent_graph_intent',
1774
1816
  claim,
@@ -1804,6 +1846,9 @@ export class SessionManager {
1804
1846
  this.assertClaimedAgentGraphRun(child, snapshot, claim, run);
1805
1847
  if (input.hostedGraphExecution) {
1806
1848
  const admission = await this.requireClaimedGraphAdmissionIdentity(claim, input.hostedGraphExecution);
1849
+ if (input.hostedGraphExecution && runtimeExecution.stopSignal.aborted)
1850
+ throw runtimeExecution.stopSignal.reason;
1851
+ input.onHostAdmission?.();
1807
1852
  await this.consumeLinkedRootExecution({
1808
1853
  sessionId: child.id,
1809
1854
  turnId: claim.targetTurnId,
@@ -1839,13 +1884,22 @@ export class SessionManager {
1839
1884
  await this.finalizeChildWorkspacePatches(child.id);
1840
1885
  // An invocation is what makes a Turn exist on the ledger, so the run listing
1841
1886
  // is the whole occupancy check: a Turn with durable content has one.
1842
- const turnOwner = (await this.listInvocations(child.id)).find((candidate) => candidate.turnId === claim.targetTurnId);
1887
+ const invocations = await this.listInvocations(child.id);
1888
+ const turnOwner = invocations.find((candidate) => candidate.turnId === claim.targetTurnId);
1843
1889
  if (turnOwner) {
1844
1890
  throw new Error(`Claimed graph turn ${claim.targetTurnId} is already owned by run ${turnOwner.runId}`);
1845
1891
  }
1846
- if (child.isArchived || child.status === 'aborted') {
1892
+ if (child.isArchived) {
1847
1893
  throw new Error('Claimed graph execution target child session is terminated');
1848
1894
  }
1895
+ if (child.status === 'aborted') {
1896
+ const latest = [...invocations].sort((left, right) => right.openedAt - left.openedAt)[0];
1897
+ // A graph stop ends one activation, not the reusable operator. Only a
1898
+ // durable graph-supervisor stop permits a new claim on an aborted child.
1899
+ if (latest?.terminalEvent?.actions?.stateDelta?.abortSource !== 'graph.supervisor') {
1900
+ throw new Error('Claimed graph execution target child session is terminated');
1901
+ }
1902
+ }
1849
1903
  if (input.abortSignal?.aborted) {
1850
1904
  throw new Error('Claimed graph execution was cancelled before runtime admission');
1851
1905
  }
@@ -1860,11 +1914,28 @@ export class SessionManager {
1860
1914
  let aborted = false;
1861
1915
  let stopPromise;
1862
1916
  const userMessageId = await this.claimedGraphUserMessageId(claim, input.hostedGraphExecution);
1917
+ if (input.hostedGraphExecution && runtimeExecution.stopSignal.aborted)
1918
+ throw runtimeExecution.stopSignal.reason;
1919
+ // Host runs this gate in its Session admission lease immediately before
1920
+ // the durable admission. Until it passes, a graph stop only cancels the
1921
+ // local capability and the gate refuses the Turn; after it passes, the
1922
+ // stop must go through the Host fence.
1923
+ const hostedAdmitExecution = input.hostedGraphExecution
1924
+ ? async () => {
1925
+ if (runtimeExecution.stopSignal.aborted)
1926
+ return 'cancelled';
1927
+ const admission = admitExecution ? await admitExecution() : 'executing';
1928
+ if (admission === 'cancelled' || runtimeExecution.stopSignal.aborted)
1929
+ return 'cancelled';
1930
+ input.onHostAdmission?.();
1931
+ return 'executing';
1932
+ }
1933
+ : admitExecution;
1863
1934
  const execution = this.consumeLinkedRootExecution({
1864
1935
  ...identity,
1865
1936
  userMessageId,
1866
1937
  execution: rootExecution,
1867
- ...(admitExecution ? { admitExecution } : {}),
1938
+ ...(hostedAdmitExecution ? { admitExecution: hostedAdmitExecution } : {}),
1868
1939
  content: { text: input.prompt },
1869
1940
  start: ({ runId, userMessageId, onRunStarted }) => this.sendMessage(child.id, {
1870
1941
  turnId: claim.targetTurnId,
@@ -1882,6 +1953,7 @@ export class SessionManager {
1882
1953
  : {}),
1883
1954
  onRunStarted,
1884
1955
  execution: runtimeExecution,
1956
+ ...(executionPolicy ? { executionPolicy } : {}),
1885
1957
  }),
1886
1958
  onReady: notifyReady,
1887
1959
  onEvent: (event) => {
@@ -2002,8 +2074,9 @@ export class SessionManager {
2002
2074
  const [parentHeader, parentRun, parentBoundary] = await Promise.all([
2003
2075
  this.deps.store.readHeader(parentSessionId),
2004
2076
  this.readInvocation(parentSessionId, input.spawnedBy.parentRunId),
2005
- this.deps.store.readExecutionBoundary(parentSessionId),
2077
+ this.readExecutionBoundaryForRun(parentSessionId, input.spawnedBy.parentRunId),
2006
2078
  ]);
2079
+ const executionPolicy = await this.readExecutionPolicy(parentSessionId, input.spawnedBy.parentRunId);
2007
2080
  this.assertActiveParentRun(parentSessionId, parentRun, input.spawnedBy.parentTurnId);
2008
2081
  const definition = requireBuiltinAgentDefinitionByProfile(input.agentProfile);
2009
2082
  const executorId = input.executorId ?? (input.resolvedPreset ? undefined : parentHeader.executorId);
@@ -2163,6 +2236,7 @@ export class SessionManager {
2163
2236
  durability: 'required',
2164
2237
  onRunStarted,
2165
2238
  execution: runtimeOwner.execution,
2239
+ ...(executionPolicy ? { executionPolicy } : {}),
2166
2240
  }),
2167
2241
  onReady: notifyReady,
2168
2242
  onEvent: (event) => {
@@ -2312,8 +2386,11 @@ export class SessionManager {
2312
2386
  return target;
2313
2387
  }
2314
2388
  async assertLinkedChildBoundaryMatchesParent(parentSessionId, childSessionId) {
2389
+ const child = await this.deps.store.readHeader(childSessionId);
2390
+ if (!child.subagentParent)
2391
+ throw new Error('Linked child is missing parent provenance');
2315
2392
  const [parentBoundary, childBoundary] = await Promise.all([
2316
- this.deps.store.readExecutionBoundary(parentSessionId),
2393
+ this.readExecutionBoundaryForRun(parentSessionId, child.subagentParent.spawnedBy.parentRunId),
2317
2394
  this.deps.store.readExecutionBoundary(childSessionId),
2318
2395
  ]);
2319
2396
  if (!executionBoundaryContains(parentBoundary, childBoundary)) {
@@ -2478,6 +2555,61 @@ export class SessionManager {
2478
2555
  },
2479
2556
  };
2480
2557
  }
2558
+ /** Stop the captured graph activation, never a newer Run or a queued sibling. */
2559
+ async stopAgentGraphActivation(identity, input = { source: 'graph_supervisor' }) {
2560
+ const authority = isRuntimeHostedRootAuthority(this.deps.messageAuthority)
2561
+ ? this.deps.messageAuthority
2562
+ : undefined;
2563
+ // Until the Host admission gate passes, the capability is only local
2564
+ // ownership: cancel it synchronously, and both the pre-submission check
2565
+ // and that gate refuse the Turn. Once admitted, Host must commit its
2566
+ // durable fence before Runtime stop.
2567
+ if (authority) {
2568
+ const admission = await this.deps.hostedAgentGraphExecution?.readRootTurnAdmissionIdentity(identity.sessionId, identity.turnId);
2569
+ if (admission && admission.runId !== identity.runId) {
2570
+ throw new Error('Graph stop identity does not match its durable admission');
2571
+ }
2572
+ // A recovered local claim can be new while the Host already owns its Run.
2573
+ // Recheck the gate after the durable read closes that lookup race.
2574
+ if (admission || this.findGraphRuntimeActivation(identity)?.hostAdmitted) {
2575
+ await authority.stopRootRun(identity, input);
2576
+ return;
2577
+ }
2578
+ }
2579
+ await this.stopGraphRuntimeActivation(identity, input);
2580
+ }
2581
+ /** Whether a failed stop left cleanup retained for this exact graph activation. */
2582
+ hasPendingAgentGraphActivationStop(identity) {
2583
+ return this.runtimeKernel.hasPendingRunStop?.(identity) ?? false;
2584
+ }
2585
+ findGraphRuntimeActivation(identity) {
2586
+ return [...this.claimedAgentGraphIntentRuns.values()]
2587
+ .map((entry) => entry.owner)
2588
+ .find((candidate) => candidate.identity.sessionId === identity.sessionId &&
2589
+ candidate.identity.runId === identity.runId &&
2590
+ candidate.identity.turnId === identity.turnId);
2591
+ }
2592
+ stopGraphRuntimeActivation(identity, input) {
2593
+ const owner = this.findGraphRuntimeActivation(identity);
2594
+ if (owner) {
2595
+ if (owner.stopTask)
2596
+ return owner.stopTask;
2597
+ if (!this.runtimeKernel.stopExecution) {
2598
+ throw new Error('Runtime kernel does not support scoped graph activation stops');
2599
+ }
2600
+ const task = this.runtimeKernel.stopExecution(owner.execution, input);
2601
+ owner.stopTask = task;
2602
+ void task.catch(() => {
2603
+ if (owner.stopTask === task)
2604
+ owner.stopTask = undefined;
2605
+ });
2606
+ return task;
2607
+ }
2608
+ if (!this.runtimeKernel.stopRun) {
2609
+ throw new Error('Runtime kernel does not support exact Run stops');
2610
+ }
2611
+ return this.runtimeKernel.stopRun(identity, input);
2612
+ }
2481
2613
  async stopSession(sessionId, input = {}) {
2482
2614
  const hostedAuthority = isRuntimeHostedRootAuthority(this.deps.messageAuthority)
2483
2615
  ? this.deps.messageAuthority
@@ -2488,15 +2620,20 @@ export class SessionManager {
2488
2620
  ? hostedAuthority.stopSession(childSessionId, input)
2489
2621
  : this.runtimeKernel.stopSession(childSessionId, input));
2490
2622
  }
2491
- async deliverHostedRootStop(sessionId, input = {}) {
2623
+ async deliverHostedRootStop(sessionId, input = {}, identity) {
2624
+ if (identity && identity.sessionId !== sessionId) {
2625
+ throw new Error('Hosted stop identity belongs to another Session');
2626
+ }
2492
2627
  const authority = isRuntimeHostedRootAuthority(this.deps.messageAuthority)
2493
2628
  ? this.deps.messageAuthority
2494
2629
  : undefined;
2495
- await this.#stopSessionTree(sessionId, this.runtimeKernel.stopSession(sessionId, input), (childSessionId) => authority
2630
+ await this.#stopSessionTree(sessionId, identity
2631
+ ? this.stopGraphRuntimeActivation(identity, input)
2632
+ : this.runtimeKernel.stopSession(sessionId, input), (childSessionId) => authority
2496
2633
  ? authority.stopSession(childSessionId, input)
2497
- : this.runtimeKernel.stopSession(childSessionId, input));
2634
+ : this.runtimeKernel.stopSession(childSessionId, input), identity);
2498
2635
  }
2499
- async #stopSessionTree(sessionId, ownStop, stopChild) {
2636
+ async #stopSessionTree(sessionId, ownStop, stopChild, parentIdentity) {
2500
2637
  // Observe immediately while child lookup runs; await below still propagates the original error.
2501
2638
  void ownStop.catch(() => undefined);
2502
2639
  let childStops = [];
@@ -2504,7 +2641,9 @@ export class SessionManager {
2504
2641
  try {
2505
2642
  const children = await this.listChildSessions(sessionId);
2506
2643
  childStops = await Promise.allSettled(children
2507
- .filter((child) => child.subagentParent?.lifecycle === 'foreground')
2644
+ .filter((child) => child.subagentParent?.lifecycle === 'foreground' &&
2645
+ (!parentIdentity ||
2646
+ child.subagentParent.spawnedBy.parentTurnId === parentIdentity.turnId))
2508
2647
  .map((child) => stopChild(child.id)));
2509
2648
  }
2510
2649
  catch (error) {