experimental-a2 0.18.0 → 0.19.0

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 (152) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/{actor-Bjce9_5U.d.ts → actor-C7b-1arn.d.ts} +3 -3
  3. package/dist/{actor-Bjce9_5U.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-react.d.ts +1 -1
  6. package/dist/actor-react.js +1 -1
  7. package/dist/actor.d.ts +1 -1
  8. package/dist/actor.js +2 -2
  9. package/dist/{ai-DrUaMyRt.d.ts → ai-Cz3hqVhk.d.ts} +34 -17
  10. package/dist/ai-Cz3hqVhk.d.ts.map +1 -0
  11. package/dist/ai-client.d.ts +1 -1
  12. package/dist/ai-client.js +2 -2
  13. package/dist/ai-control-CvLbW10B.js +46 -0
  14. package/dist/ai-control-CvLbW10B.js.map +1 -0
  15. package/dist/ai-fx.d.ts +1 -1
  16. package/dist/ai-fx.d.ts.map +1 -1
  17. package/dist/ai-fx.js +122 -44
  18. package/dist/ai-fx.js.map +1 -1
  19. package/dist/{ai-harness-BuLh_EYi.js → ai-harness-BuuC-gJ3.js} +79 -15
  20. package/dist/ai-harness-BuuC-gJ3.js.map +1 -0
  21. package/dist/{ai-harness-DvsPeJeB.d.ts → ai-harness-CV6rXbB3.d.ts} +2 -2
  22. package/dist/ai-harness-CV6rXbB3.d.ts.map +1 -0
  23. package/dist/{ai-BpOGm-zB.js → ai-mMiWul1T.js} +117 -78
  24. package/dist/ai-mMiWul1T.js.map +1 -0
  25. package/dist/ai-pi.d.ts +1 -1
  26. package/dist/ai-pi.d.ts.map +1 -1
  27. package/dist/ai-pi.js +114 -23
  28. package/dist/ai-pi.js.map +1 -1
  29. package/dist/ai-react.d.ts +1 -1
  30. package/dist/ai-react.js +2 -2
  31. package/dist/ai-server.d.ts +3 -3
  32. package/dist/ai-server.d.ts.map +1 -1
  33. package/dist/ai-server.js +127 -55
  34. package/dist/ai-server.js.map +1 -1
  35. package/dist/ai-tool-input-DmSqMUQi.js +40 -0
  36. package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
  37. package/dist/ai.d.ts +2 -2
  38. package/dist/ai.js +1 -1
  39. package/dist/{client-BReKqwZC.js → client-BhHLoNKF.js} +3 -3
  40. package/dist/{client-BReKqwZC.js.map → client-BhHLoNKF.js.map} +1 -1
  41. package/dist/client.js +1 -1
  42. package/dist/index.d.ts +2 -2
  43. package/dist/{internal-B2T3U4Rq.js → internal-CscSY7K_.js} +4 -2
  44. package/dist/{internal-B2T3U4Rq.js.map → internal-CscSY7K_.js.map} +1 -1
  45. package/dist/react.js +1 -1
  46. package/dist/scheduler-qstash.d.ts +2 -2
  47. package/dist/scheduler-qstash.js +3 -3
  48. package/dist/scheduler-qstash.js.map +1 -1
  49. package/dist/scheduler-vercel.d.ts +7 -7
  50. package/dist/scheduler-vercel.d.ts.map +1 -1
  51. package/dist/scheduler-vercel.js +10 -14
  52. package/dist/scheduler-vercel.js.map +1 -1
  53. package/dist/{server-Dw_k_7kO.js → server-D-Y4pqwu.js} +37 -11
  54. package/dist/server-D-Y4pqwu.js.map +1 -0
  55. package/dist/{server-En7cbi3v.d.ts → server-DYAATFuT.d.ts} +11 -5
  56. package/dist/server-DYAATFuT.d.ts.map +1 -0
  57. package/dist/server.d.ts +2 -2
  58. package/dist/server.js +1 -1
  59. package/dist/{session-clear-Bo-XTFEy.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
  60. package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
  61. package/dist/{store-Djdv0bnZ.d.ts → store-CyLUy86v.d.ts} +23 -4
  62. package/dist/store-CyLUy86v.d.ts.map +1 -0
  63. package/dist/store-N8PXxDAS.js.map +1 -1
  64. package/dist/store-memory.d.ts +1 -1
  65. package/dist/store-memory.js +25 -6
  66. package/dist/store-memory.js.map +1 -1
  67. package/dist/{store-polling-B5FcwJfo.js → store-polling-7WJV0YWP.js} +2 -2
  68. package/dist/{store-polling-B5FcwJfo.js.map → store-polling-7WJV0YWP.js.map} +1 -1
  69. package/dist/store-postgres.d.ts +1 -1
  70. package/dist/store-postgres.d.ts.map +1 -1
  71. package/dist/store-postgres.js +55 -25
  72. package/dist/store-postgres.js.map +1 -1
  73. package/dist/{store-presence-polling-BAwS8RWn.js → store-presence-polling-B1ixeqIS.js} +3 -3
  74. package/dist/{store-presence-polling-BAwS8RWn.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
  75. package/dist/store-redis-http.d.ts +1 -1
  76. package/dist/store-redis-http.js +3 -3
  77. package/dist/{store-redis-notify-BRITti_o.js → store-redis-notify-CM0AfLY0.js} +88 -37
  78. package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
  79. package/dist/store-redis.d.ts +1 -1
  80. package/dist/store-redis.js +1 -1
  81. package/dist/store-sqlite.d.ts +1 -1
  82. package/dist/store-sqlite.js +35 -20
  83. package/dist/store-sqlite.js.map +1 -1
  84. package/dist/{wire-rSUglYo8.js → wire-BKxH1vbP.js} +2 -2
  85. package/dist/{wire-rSUglYo8.js.map → wire-BKxH1vbP.js.map} +1 -1
  86. package/docs/agents/03-conversations.mdx +27 -15
  87. package/docs/agents/04-harnesses.mdx +51 -5
  88. package/docs/contract/03-sessions.mdx +1 -1
  89. package/docs/index.mdx +3 -3
  90. package/docs/reference/02-server.mdx +12 -7
  91. package/docs/reference/04-ai.mdx +41 -23
  92. package/docs/reference/05-adapters.mdx +6 -6
  93. package/docs/server/01-handlers.mdx +1 -1
  94. package/docs/server/02-timers.mdx +1 -1
  95. package/docs/server/03-cancellation.mdx +3 -2
  96. package/docs/server/05-production.mdx +4 -3
  97. package/docs/server/06-guarantees.mdx +20 -11
  98. package/examples/playground/DEPLOY.md +11 -4
  99. package/examples/playground/app/a0/[appId]/page.tsx +2 -8
  100. package/examples/playground/app/a0/a0.css +244 -5
  101. package/examples/playground/app/a0/app-view.tsx +2 -1
  102. package/examples/playground/app/a0/attachments.ts +133 -0
  103. package/examples/playground/app/a0/chat.test.tsx +504 -0
  104. package/examples/playground/app/a0/chat.tsx +623 -63
  105. package/examples/playground/app/a0/context-menu.tsx +38 -11
  106. package/examples/playground/app/a0/devtools.tsx +2 -8
  107. package/examples/playground/app/a0/model.test.ts +28 -0
  108. package/examples/playground/app/a0/model.ts +30 -0
  109. package/examples/playground/app/a0/server.ts +13 -19
  110. package/examples/playground/app/a0/uploads/route.ts +29 -0
  111. package/examples/playground/app/agent/[agentId]/agent-client.tsx +2 -1
  112. package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +1 -0
  113. package/examples/playground/app/agent/model.test.ts +12 -3
  114. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +1 -5
  115. package/examples/playground/next.config.ts +7 -4
  116. package/examples/playground/package.json +3 -1
  117. package/package.json +1 -1
  118. package/src/ai-agent.ts +14 -3
  119. package/src/ai-client-state.ts +21 -9
  120. package/src/ai-context-schema.ts +28 -4
  121. package/src/ai-context.ts +83 -3
  122. package/src/ai-control-server.ts +70 -43
  123. package/src/ai-control-state.ts +3 -4
  124. package/src/ai-control.ts +59 -10
  125. package/src/ai-coordinator.ts +7 -6
  126. package/src/ai-fx.ts +184 -67
  127. package/src/ai-harness.ts +153 -29
  128. package/src/ai-message-shape.ts +41 -0
  129. package/src/ai-pi.ts +161 -46
  130. package/src/ai-stored-state.ts +1 -3
  131. package/src/ai-tool-input.ts +45 -0
  132. package/src/ai.ts +116 -77
  133. package/src/internal.ts +6 -0
  134. package/src/libfx.d.ts +8 -1
  135. package/src/scheduler-qstash.ts +5 -5
  136. package/src/scheduler-vercel.ts +15 -27
  137. package/src/server.ts +58 -12
  138. package/src/store-memory.ts +50 -9
  139. package/src/store-postgres.ts +79 -28
  140. package/src/store-redis-core.ts +112 -31
  141. package/src/store-sqlite.ts +61 -28
  142. package/src/store.ts +22 -3
  143. package/dist/ai-BpOGm-zB.js.map +0 -1
  144. package/dist/ai-DrUaMyRt.d.ts.map +0 -1
  145. package/dist/ai-harness-BuLh_EYi.js.map +0 -1
  146. package/dist/ai-harness-DvsPeJeB.d.ts.map +0 -1
  147. package/dist/server-Dw_k_7kO.js.map +0 -1
  148. package/dist/server-En7cbi3v.d.ts.map +0 -1
  149. package/dist/session-clear-Bo-XTFEy.d.ts.map +0 -1
  150. package/dist/store-Djdv0bnZ.d.ts.map +0 -1
  151. package/dist/store-redis-notify-BRITti_o.js.map +0 -1
  152. package/examples/playground/app/a0/no-photon.cjs +0 -4
package/dist/ai-server.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
2
  import { a as reducer, s as validateSync } from "./reducer-BAXc8JyF.js";
3
- import { h as installAmbientToolScopeStorage, m as failedPreviousAttempt, p as consumeSchedulerSendFailure } from "./internal-B2T3U4Rq.js";
3
+ import { h as installAmbientToolScopeStorage, m as failedPreviousAttempt, p as consumeSchedulerSendFailure } from "./internal-CscSY7K_.js";
4
4
  import { t as boundedCache } from "./bounded-cache-CPHWF-8h.js";
5
- import { a as CONSOLE_TELEMETRY, o as setServerFetchHooks, t as createServer } from "./server-Dw_k_7kO.js";
6
- import { A as interruptUIMessage, C as reduceToolActivity, D as flattenProgressBatches, E as upsertResponse, M as assertMessageBase, N as restoreMessageBase, O as progressBatches, S as projectedMessage, T as upsertMessage, _ as applyControlChanges, a as events, b as findMessage, d as appSlice, f as isAgentContract, g as initialControlState, h as aiEventId, j as pauseUIMessage, k as progressOwnedBy, m as AI_EVENT_ID_PREFIX, n as agentContract, p as withAIStorage, u as appReducerOf, v as controlCommit, w as retainMessageBase, y as advanceProjection } from "./ai-BpOGm-zB.js";
7
- import { a as harnessDriver, o as harnessLog, s as harnessStates, u as registerHarnessStates } from "./ai-harness-BuLh_EYi.js";
5
+ import { a as CONSOLE_TELEMETRY, o as setServerFetchHooks, t as createServer } from "./server-D-Y4pqwu.js";
6
+ import { a as unexplainedAdmission, i as steersOn, n as initialControlState, r as steeringEnd, t as STEERED_REASON } from "./ai-control-CvLbW10B.js";
7
+ import { A as interruptUIMessage, C as reduceToolActivity, D as flattenProgressBatches, E as upsertResponse, M as assertMessageBase, N as restoreMessageBase, O as progressBatches, S as projectedMessage, T as upsertMessage, _ as applyControlChanges, a as events, b as findMessage, d as appSlice, f as isAgentContract, g as isUIMessage, h as aiEventId, j as pauseUIMessage, k as progressOwnedBy, m as AI_EVENT_ID_PREFIX, n as agentContract, p as withAIStorage, u as appReducerOf, v as controlCommit, w as retainMessageBase, y as advanceProjection } from "./ai-mMiWul1T.js";
8
+ import { a as harnessDriver, c as harnessStates, d as registerHarnessStates, s as harnessLog } from "./ai-harness-BuuC-gJ3.js";
8
9
  import { AsyncLocalStorage } from "node:async_hooks";
9
10
  import { createHash } from "node:crypto";
10
11
  import { asSchema, convertToModelMessages, isToolUIPart, stepCountIs, streamText, toUIMessageStream } from "ai";
@@ -43,12 +44,11 @@ const foldCoordinator = (state, event) => {
43
44
  }
44
45
  case "ai.message.created": {
45
46
  const payload = event.payload;
46
- const message = payload.message;
47
- if (message.role !== "user") return state;
47
+ if (payload.role !== "user") return state;
48
48
  return {
49
49
  ...state,
50
- queued: [...state.queued.filter((item) => item.messageId !== message.id), {
51
- messageId: message.id,
50
+ queued: [...state.queued.filter((item) => item.messageId !== payload.messageId), {
51
+ messageId: payload.messageId,
52
52
  generate: payload.generate !== false
53
53
  }]
54
54
  };
@@ -244,7 +244,7 @@ const createControlRuntime = (options) => {
244
244
  const telemetry = options.telemetry ?? CONSOLE_TELEMETRY;
245
245
  const reducer$2 = reducer({
246
246
  contract: options.contract,
247
- name: "a2.ai.control.v5",
247
+ name: "a2.ai.control.v6",
248
248
  initialState: initialControlState()
249
249
  }).fold((state, event) => {
250
250
  if (event.type === "ai.control.requested") {
@@ -309,15 +309,41 @@ const createControlRuntime = (options) => {
309
309
  });
310
310
  state.active = null;
311
311
  };
312
- const insert = ({ message, generate = true, first = false, afterStepOf }) => {
312
+ const decide = async () => {
313
+ const receipt = {
314
+ commandId: ctx.event.id,
315
+ ...ctx.event.type === "ai.work.reported" ? { work: true } : {},
316
+ outcome: reason === void 0 ? "applied" : "rejected",
317
+ ...reason === void 0 ? {} : { reason }
318
+ };
319
+ return [
320
+ ...facts,
321
+ {
322
+ type: "ai.control.committed",
323
+ id: await aiEventId("a2.ai.control.committed", ctx.event.id),
324
+ payload: controlCommit({
325
+ before,
326
+ after: state,
327
+ moves
328
+ })
329
+ },
330
+ ...jobs,
331
+ {
332
+ type: "ai.control.decided",
333
+ id: await aiEventId("a2.ai.control.decided", ctx.event.id),
334
+ payload: receipt
335
+ }
336
+ ];
337
+ };
338
+ const insert = ({ message, generate = true, afterStepOf }) => {
313
339
  const candidate = {
314
340
  id: message.id,
315
- message,
341
+ role: message.role,
316
342
  generate: generate && message.role === "user",
317
343
  ...afterStepOf === void 0 ? {} : { afterStepOf }
318
344
  };
319
- if (first) state.inbox.items.unshift(candidate);
320
- else state.inbox.items.push(candidate);
345
+ if (afterStepOf === void 0) state.inbox.items.push(candidate);
346
+ else state.inbox.items.splice(steeringEnd(state.inbox.items, afterStepOf), 0, candidate);
321
347
  };
322
348
  const moveInput = ({ from, to }) => {
323
349
  if (from === to) return;
@@ -380,9 +406,8 @@ const createControlRuntime = (options) => {
380
406
  case "edit": {
381
407
  const item = state.inbox.items.find((candidate) => candidate.id === command.message.id);
382
408
  if (item === void 0) reason = "not-pending";
383
- else if (item.message.role !== command.message.role) reason = "role-mismatch";
384
- else item.message = command.message;
385
- break;
409
+ else if (item.role !== command.message.role) reason = "role-mismatch";
410
+ return decide();
386
411
  }
387
412
  case "remove": {
388
413
  const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
@@ -408,14 +433,15 @@ const createControlRuntime = (options) => {
408
433
  const selected = state.inbox.items[position];
409
434
  if (!selected) reason = "not-pending";
410
435
  else if ((state.active?.turnId ?? null) !== command.turnId) reason = "stale-turn";
411
- else if (selected.message.role !== "user") reason = "not-user-input";
436
+ else if (selected.role !== "user") reason = "not-user-input";
412
437
  else {
413
438
  selected.generate = true;
414
439
  if (state.active) selected.afterStepOf = state.active.turnId;
415
440
  else delete selected.afterStepOf;
441
+ const others = state.inbox.items.filter((item) => item !== selected);
416
442
  moveInput({
417
443
  from: position,
418
- to: 0
444
+ to: state.active ? steeringEnd(others, state.active.turnId) : 0
419
445
  });
420
446
  }
421
447
  break;
@@ -431,7 +457,6 @@ const createControlRuntime = (options) => {
431
457
  insert({
432
458
  message: command.message,
433
459
  generate: true,
434
- first: true,
435
460
  afterStepOf: command.turnId
436
461
  });
437
462
  break;
@@ -597,12 +622,12 @@ const createControlRuntime = (options) => {
597
622
  const modelDone = active.model === null || active.model.settled;
598
623
  const workSettled = modelDone && response.calls.every((call) => call.work === void 0 || call.work.settled);
599
624
  const externalPending = response.calls.some((call) => call.call.providerExecuted && call.call.supportsDeferredResults && !call.terminal && !approvalBlocksExecution(call));
600
- const handoffReady = handoff && workSettled && !externalPending && (active.suspended || response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
625
+ const handoffReady = handoff && workSettled && !externalPending && (active.suspended ? !state.inbox.paused : response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
601
626
  const readyForModel = !active.suspended && response.failure === void 0 && modelDone && callsDone && response.inputs.length === 0;
602
627
  const naturalCompletion = readyForModel && response.completion !== void 0 && response.completion.finishReason !== "tool-calls";
603
628
  if (handoffReady && !naturalCompletion) await end({
604
629
  lastSeenIndex: void 0,
605
- reason: "steered after step"
630
+ reason: STEERED_REASON
606
631
  });
607
632
  else if (readyForModel && (!handoff || handoffReady)) {
608
633
  const resume = active.model !== null && active.model.version !== active.version;
@@ -632,12 +657,14 @@ const createControlRuntime = (options) => {
632
657
  }
633
658
  if (state.active === null && !state.inbox.paused && !state.coordinator.closed) while (state.inbox.items.length > 0) {
634
659
  const item = state.inbox.items.shift();
660
+ if (steersOn(item, state.inbox.items)) item.generate = false;
635
661
  delete item.afterStepOf;
636
662
  await emit({
637
663
  type: "ai.message.created",
638
664
  payload: {
639
- message: item.message,
640
- generate: item.generate
665
+ messageId: item.id,
666
+ role: item.role,
667
+ ...item.generate ? {} : { generate: false }
641
668
  },
642
669
  id: await aiEventId("a2.ai.admitted", options.contract.name, ctx.event.sessionId, item.id)
643
670
  });
@@ -661,30 +688,7 @@ const createControlRuntime = (options) => {
661
688
  });
662
689
  break;
663
690
  }
664
- const receipt = {
665
- commandId: ctx.event.id,
666
- ...ctx.event.type === "ai.work.reported" ? { work: true } : {},
667
- outcome: reason === void 0 ? "applied" : "rejected",
668
- ...reason === void 0 ? {} : { reason }
669
- };
670
- return [
671
- ...facts,
672
- {
673
- type: "ai.control.committed",
674
- id: await aiEventId("a2.ai.control.committed", ctx.event.id),
675
- payload: controlCommit({
676
- before,
677
- after: state,
678
- moves
679
- })
680
- },
681
- ...jobs,
682
- {
683
- type: "ai.control.decided",
684
- id: await aiEventId("a2.ai.control.decided", ctx.event.id),
685
- payload: receipt
686
- }
687
- ];
691
+ return decide();
688
692
  };
689
693
  const report = async ({ ctx, events, settled, name }) => {
690
694
  const payload = ctx.event.payload;
@@ -784,8 +788,12 @@ const index = (value) => typeof value === "number" && Number.isSafeInteger(value
784
788
  const id = (value) => typeof value === "string" && value.length > 0;
785
789
  const nullableId = (value) => value === null || id(value);
786
790
  const matches = (schema, value) => validateSync(schema, value, "AI context checkpoint").issues === void 0;
787
- const isMessage = (value) => matches(events["ai.message.created"], { message: value });
788
791
  const indexes = (value, messages) => record(value) && Object.values(value).every(index) && messages.every((message) => Object.hasOwn(value, message.id));
792
+ const inputs = (value) => record(value) && Object.keys(value).length === 2 && Array.isArray(value["commands"]) && value["commands"].every((held) => record(held) && Object.keys(held).every((key) => [
793
+ "commandId",
794
+ "inputId",
795
+ "message"
796
+ ].includes(key)) && id(held["commandId"]) && id(held["inputId"]) && (held["message"] === void 0 || isUIMessage(held["message"]))) && Array.isArray(value["waiting"]) && value["waiting"].every((input) => record(input) && Object.keys(input).length === 2 && id(input["inputId"]) && isUIMessage(input["message"]));
789
797
  const compaction = (value) => value === null || record(value) && Object.keys(value).every((key) => [
790
798
  "status",
791
799
  "generationId",
@@ -816,7 +824,7 @@ const progress = (value) => {
816
824
  const indexedEvents = (value, types) => Array.isArray(value) && value.every((event) => record(event) && index(event["index"]) && types.includes(event["type"]) && matches(events[event["type"]], event["payload"]));
817
825
  const restorableBase = (options) => {
818
826
  assertMessageBase(options.base);
819
- return isMessage(restoreMessageBase({
827
+ return isUIMessage(restoreMessageBase({
820
828
  current: options.current,
821
829
  base: options.base
822
830
  }));
@@ -880,6 +888,7 @@ const contextSchema = (app) => ({ "~standard": {
880
888
  "compaction",
881
889
  "modelMetadata",
882
890
  "closed",
891
+ "inputs",
883
892
  "authority",
884
893
  "generation",
885
894
  "activeProjection",
@@ -892,7 +901,7 @@ const contextSchema = (app) => ({ "~standard": {
892
901
  const metadata = value["modelMetadata"];
893
902
  const generation = value["generation"];
894
903
  const current = value["activeProjection"];
895
- if (!Array.isArray(messages) || !messages.every(isMessage) || !compaction(value["compaction"]) || typeof value["closed"] !== "boolean" || !authority(value["authority"]) || !record(metadata) || !Object.entries(metadata).every(([modelId, entry]) => record(entry) && (entry["status"] === "pending" || entry["status"] === "unavailable" || entry["status"] === "resolved" && matches(events["ai.model.metadata.resolved"], {
904
+ if (!Array.isArray(messages) || !messages.every(isUIMessage) || !compaction(value["compaction"]) || typeof value["closed"] !== "boolean" || !inputs(value["inputs"]) || !authority(value["authority"]) || !record(metadata) || !Object.entries(metadata).every(([modelId, entry]) => record(entry) && (entry["status"] === "pending" || entry["status"] === "unavailable" || entry["status"] === "resolved" && matches(events["ai.model.metadata.resolved"], {
896
905
  modelId,
897
906
  limits: entry["limits"]
898
907
  }))) || !indexes(value["messageIndexes"], messages)) return invalid;
@@ -1002,6 +1011,10 @@ const createContextReducer = ({ contract }) => {
1002
1011
  compaction: null,
1003
1012
  modelMetadata: {},
1004
1013
  closed: false,
1014
+ inputs: {
1015
+ commands: [],
1016
+ waiting: []
1017
+ },
1005
1018
  authority: null,
1006
1019
  generation: null,
1007
1020
  activeProjection: null,
@@ -1010,7 +1023,7 @@ const createContextReducer = ({ contract }) => {
1010
1023
  };
1011
1024
  const reducer$1 = reducer({
1012
1025
  contract,
1013
- name: app === void 0 ? "a2.ai.context.v6" : `${app.name}+a2.ai.context.v6`,
1026
+ name: app === void 0 ? "a2.ai.context.v7" : `${app.name}+a2.ai.context.v7`,
1014
1027
  initialState,
1015
1028
  stateSchema: contextSchema(app)
1016
1029
  }).fold((state, event) => {
@@ -1046,15 +1059,69 @@ const reduceContext = (state, event) => {
1046
1059
  };
1047
1060
  return view.closed ? endGeneration(next) : next;
1048
1061
  }
1062
+ case "ai.control.requested": {
1063
+ const command = event.payload;
1064
+ const held = command.action === "send" || command.action === "steer" || command.action === "edit" ? {
1065
+ commandId: event.id,
1066
+ inputId: command.message.id,
1067
+ message: command.message
1068
+ } : command.action === "remove" ? {
1069
+ commandId: event.id,
1070
+ inputId: command.inputId
1071
+ } : void 0;
1072
+ if (held === void 0) return state;
1073
+ return {
1074
+ ...state,
1075
+ inputs: {
1076
+ ...state.inputs,
1077
+ commands: [...state.inputs.commands, held]
1078
+ }
1079
+ };
1080
+ }
1081
+ case "ai.control.decided": {
1082
+ const receipt = event.payload;
1083
+ const held = receipt.work ? void 0 : state.inputs.commands.find((command) => command.commandId === receipt.commandId);
1084
+ if (held === void 0) return state;
1085
+ const commands = state.inputs.commands.filter((command) => command !== held);
1086
+ if (receipt.outcome !== "applied") return {
1087
+ ...state,
1088
+ inputs: {
1089
+ ...state.inputs,
1090
+ commands
1091
+ }
1092
+ };
1093
+ const waiting = state.inputs.waiting.filter((input) => input.inputId !== held.inputId);
1094
+ if (held.message !== void 0) waiting.push({
1095
+ inputId: held.inputId,
1096
+ message: held.message
1097
+ });
1098
+ return {
1099
+ ...state,
1100
+ inputs: {
1101
+ commands,
1102
+ waiting
1103
+ }
1104
+ };
1105
+ }
1049
1106
  case "ai.session.closed": return endGeneration({
1050
1107
  ...state,
1051
1108
  closed: true,
1109
+ inputs: {
1110
+ commands: [],
1111
+ waiting: []
1112
+ },
1052
1113
  compaction: state.compaction?.status === "running" ? state.activeProjection?.baseCompaction ?? null : state.compaction
1053
1114
  });
1054
1115
  case "ai.message.created": {
1055
- const { message } = event.payload;
1116
+ const { messageId } = event.payload;
1117
+ const message = state.inputs.waiting.find((input) => input.inputId === messageId)?.message ?? state.inputs.commands.find((command) => command.commandId === messageId)?.message;
1118
+ if (message === void 0) throw unexplainedAdmission(messageId);
1056
1119
  return {
1057
1120
  ...state,
1121
+ inputs: {
1122
+ commands: state.inputs.commands.filter((command) => command.commandId !== messageId),
1123
+ waiting: state.inputs.waiting.filter((input) => input.inputId !== messageId)
1124
+ },
1058
1125
  messages: upsertMessage(state.messages, message),
1059
1126
  activeProjection: state.activeProjection?.responseMessageId === message.id ? {
1060
1127
  ...state.activeProjection,
@@ -1711,7 +1778,12 @@ async function* consumeGeneration(options) {
1711
1778
  chunks: mergeDeltas(pending.splice(0))
1712
1779
  });
1713
1780
  const reader = options.source.stream.getReader();
1714
- let pendingChunk = reader.read();
1781
+ const readAhead = () => {
1782
+ const read = reader.read();
1783
+ read.catch(() => {});
1784
+ return read;
1785
+ };
1786
+ let pendingChunk = readAhead();
1715
1787
  try {
1716
1788
  for (;;) {
1717
1789
  let result;
@@ -1730,7 +1802,7 @@ async function* consumeGeneration(options) {
1730
1802
  pending.push(chunk);
1731
1803
  if (chunk.type === "finish") streamedFinishReason = chunk.finishReason;
1732
1804
  if (chunk.type === "error") streamedError = chunk.errorText;
1733
- pendingChunk = reader.read();
1805
+ pendingChunk = readAhead();
1734
1806
  if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
1735
1807
  yield batch();
1736
1808
  lastFlush = Date.now();
@@ -1740,7 +1812,6 @@ async function* consumeGeneration(options) {
1740
1812
  if (pending.length > 0) yield batch();
1741
1813
  throw error;
1742
1814
  } finally {
1743
- pendingChunk.catch(() => {});
1744
1815
  try {
1745
1816
  await reader.cancel();
1746
1817
  } catch {} finally {
@@ -2058,6 +2129,7 @@ function createHandlers(options) {
2058
2129
  if (harness !== void 0) for (const [name, tool] of Object.entries(options.tools ?? {})) {
2059
2130
  const needsApproval = tool.needsApproval;
2060
2131
  if (needsApproval !== void 0 && needsApproval !== false) throw new TypeError(`a harness runs tools without approval; '${name}' sets needsApproval`);
2132
+ if (tool.type === "provider") throw new TypeError(`a harness runs only tools A2 executes; '${name}' is provider-defined`);
2061
2133
  }
2062
2134
  const configuredCompaction = loop.compaction ?? (loop.generate === void 0 && harness === void 0 ? {} : false);
2063
2135
  const staticCompaction = typeof configuredCompaction === "function" ? void 0 : validateCompaction({