@agen-ai/provider-acp 0.4.1 → 0.4.2

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 (43) hide show
  1. package/README.md +174 -14
  2. package/dist/capabilities.js +2 -2
  3. package/dist/connection/contracts.d.ts +3 -1
  4. package/dist/connection/initialize.js +25 -9
  5. package/dist/connection/negotiation.d.ts +2 -1
  6. package/dist/connection/negotiation.js +8 -1
  7. package/dist/host/interactions.d.ts +6 -1
  8. package/dist/host/interactions.js +42 -16
  9. package/dist/host/router.d.ts +3 -2
  10. package/dist/host/router.js +23 -20
  11. package/dist/instance.js +19 -9
  12. package/dist/node/contracts.d.ts +2 -2
  13. package/dist/node/index.js +1 -1
  14. package/dist/node/launch.js +3 -3
  15. package/dist/session/continuation.d.ts +33 -0
  16. package/dist/session/continuation.js +185 -0
  17. package/dist/session/descendants.d.ts +29 -0
  18. package/dist/session/descendants.js +97 -0
  19. package/dist/session/mapping.js +1 -1
  20. package/dist/session/native.d.ts +1 -0
  21. package/dist/session/native.js +8 -0
  22. package/dist/session/opening.js +1 -1
  23. package/dist/session/requests/elicitation.d.ts +1 -1
  24. package/dist/session/requests/elicitation.js +1 -1
  25. package/dist/session/requests/grokWithdrawals.d.ts +28 -0
  26. package/dist/session/requests/grokWithdrawals.js +135 -0
  27. package/dist/session/requests/pending.d.ts +17 -4
  28. package/dist/session/requests/pending.js +63 -7
  29. package/dist/session/requests/resolution.js +4 -2
  30. package/dist/session/session.d.ts +8 -0
  31. package/dist/session/session.js +140 -32
  32. package/dist/session/turn.d.ts +6 -1
  33. package/dist/sessionInteractions/broker.d.ts +11 -3
  34. package/dist/sessionInteractions/broker.js +35 -13
  35. package/dist/sessionInteractions/extensions.d.ts +2 -1
  36. package/dist/sessionInteractions/extensions.js +21 -1
  37. package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
  38. package/dist/sessionInteractions/grokFolderTrust.js +0 -1
  39. package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
  40. package/dist/sessionInteractions/grokPlanReview.js +63 -0
  41. package/dist/sessionInteractions/request.d.ts +15 -7
  42. package/dist/sessionInteractions/request.js +46 -18
  43. package/package.json +4 -4
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @agen-ai/provider-acp
2
2
 
3
3
  An optional provider for the AgenAI coding-agent runtime. It translates the stable
4
- Agent Client Protocol wire version 1 into the existing Agent Protocol V11 session
5
- API. The coordinated source SDK version is `0.4.1` and remains unpublished;
6
- the published release is `0.4.0`.
4
+ Agent Client Protocol wire version 1 into the existing Agent Protocol V12 session
5
+ API. The coordinated source candidate is unpublished `0.4.2`; published `0.4.1` qualification remains
6
+ historical evidence for its exact V11 artifacts.
7
7
  The package is ESM, MIT licensed, side-effect-free on import and requires Node.js
8
8
  22 or newer. It depends only on the public transport, runtime and protocol packages;
9
9
  the core packages remain independent of ACP.
10
10
 
11
11
  The provider pins the transport's `schema-v1.23.0` stable profile. It translates
12
- ACP into current V11 capabilities and rejects unsupported semantics explicitly.
12
+ ACP into current V12 capabilities and rejects unsupported semantics explicitly.
13
13
  Connection negotiation, configured host facilities, and mapping policy fix one
14
14
  capability envelope per instance; every later connection must match it.
15
15
 
@@ -55,7 +55,7 @@ under a different policy.
55
55
  | Turns and requests | Default text turns, streamed assistant/reasoning text, tool progress, cancellation and once-only exact-action permissions by default, with opt-in complete remembered-choice mappings. One execution retains the original native prompt across request waits and answers. |
56
56
  | Resume | Exact persisted binding using advertised `session/resume`, otherwise advertised `session/load`. Load history is bounded and suppressed from new-turn output. No automatic recreation after failure. |
57
57
  | Configuration | Managed by default. Explicit `configuration: 'selectable'` maps observed boolean/select catalogs, with a session-mode fallback when config options are absent. Unknown metadata differs from observed empty metadata. |
58
- | Forms | Canonical boolean/single-select/multi-select forms that fit V11 constraints. An intent stage preserves decline separately from cancel, followed by answers on the same native waiter. Explicit host form facilities can handle broader native forms. |
58
+ | Forms | Canonical boolean/single-select/multi-select forms that fit V12 constraints. An intent stage preserves decline separately from cancel, followed by answers on the same native waiter. Explicit host form facilities can handle broader native forms. |
59
59
  | Images | Inline base64 PNG/JPEG/WebP only when the peer advertises image input, within image and total turn budgets. No implicit local-file read or URL fetch. |
60
60
  | Plans | Complete replacements of observed turn plans. A mode called “plan” does not establish enforced planning behavior or approval authority. |
61
61
  | File changes | Partial structured observations from native tool diffs. Canonical paths contained by the working directory become sorted relative display paths without filesystem reads. Unfinished tools remain unknown at ordinary turn completion. No final workspace diff or Review artifact is inferred. |
@@ -69,7 +69,7 @@ Through-turn branching, same-turn steering, enforced plan mode, persistent
69
69
  SDK-owned tool-approval grants, context usage/compaction, account quota, effective skill/rule
70
70
  inventory, collaboration, generated resources and artifact publication remain
71
71
  unsupported. ACP usage counters can decrease without confirmed compaction and
72
- cannot satisfy V11's context continuity contract. Unsupported media output fails
72
+ cannot satisfy V12's context continuity contract. Unsupported media output fails
73
73
  explicitly rather than disappearing from an otherwise successful transcript.
74
74
  Embedded terminal references also fail explicitly: a mutable, possibly truncated
75
75
  terminal snapshot does not provide an ordered append-only transcript. Agents can
@@ -82,7 +82,7 @@ over other conversations or account state.
82
82
 
83
83
  ## Operational failures and uncertainty
84
84
 
85
- The provider maps supported standard `schema-v1.23.0` prompt errors into V11 `AgentError` values.
85
+ The provider maps supported standard `schema-v1.23.0` prompt errors into V12 `AgentError` values.
86
86
  Standard `-32000` reports `authentication` with an `authenticate` user-action hint; `-32602` reports
87
87
  `invalid_request`; `-32603` reports `provider`; standard parse/request/method errors report `protocol`.
88
88
  Other remote codes remain `unknown` with unknown recovery. Mapping uses the standard error code,
@@ -403,7 +403,7 @@ It does not publish packages, integrate a consumer application or enable private
403
403
 
404
404
  ## Session-lifetime folder decisions
405
405
 
406
- The unpublished `0.4.1` source includes the opt-in mapping on
406
+ The `0.4.1` release includes the opt-in mapping on
407
407
  `createNodeAcpProviderDriver`:
408
408
  `sessionInteractions: { mapping: 'grok-folder-trust-v1' }`. Supply an explicit
409
409
  `executionBoundary` at construction and the generic `interactions.onRequest`
@@ -412,8 +412,9 @@ remains noninteractive. The mapping is absent by default, and ordinary standard
412
412
  unknown-extension handling never enables it implicitly.
413
413
 
414
414
  The selected launch arguments are exactly `['agent', '--no-leader', 'stdio']`.
415
- The adapter exposes create-only `sessionInteractions` support with an exclusive
416
- producer and decision requests. The handler receives a neutral workspace
415
+ The adapter exposes the exact `sessionInteractions.mappings` tuple
416
+ `{ openingOperation: 'create', requestKind: 'decision' }`, exclusive producer
417
+ ownership and, in current 0.4.2 source, `humanWait: ['bounded', 'until_withdrawn_or_closed']`. The handler receives a neutral workspace
417
418
  decision and can keep its response capability after opening returns, before
418
419
  any model turn. Use the generic
419
420
  [runtime lifecycle](../agent-runtime/README.md#session-lifetime-interactions)
@@ -478,9 +479,168 @@ other builds/platforms, browser login, native durable recovery, SDK-managed
478
479
  remembered permissions or private execution support. The earlier failed native
479
480
  ownership attempt remains unqualified.
480
481
 
482
+ The current V12 decision descriptor omits `expiresAt` because this native mapping reports no
483
+ provider-owned expiry. The bounded host timer remains a separate policy boundary;
484
+ `humanWait: { kind: 'until_withdrawn_or_closed' }` omits that human timer and
485
+ rejects an explicitly supplied `limits.requestTimeoutMs`. Initialization,
486
+ response observation, authentication and cleanup keep their finite bounds.
487
+ Responses require `{ kind: 'decision', choiceId }`. Generalized runtime terminal/deadline and
488
+ exact support declarations apply to this mapping: an answered terminal records
489
+ `{ kind: 'answered', response: { kind: 'decision', choiceId } }`, and host timeout records
490
+ `{ kind: 'expired', source: 'host_policy' }`. A validated consumer handle exposes the effective
491
+ `deadline` separately from the provider descriptor. Raw driver instances return candidate adapters
492
+ whose interaction callback receives `kind: 'direct'`; registry materialization installs the
493
+ validated consumer facade. Session forms, plan reviews and native continuations
494
+ remain unsupported by `grok-folder-trust-v1`. The retained SDK02 native evidence
495
+ above remains specific to its original finite waiting configuration.
496
+
497
+ ## Restored plan requests in current 0.4.2 source
498
+
499
+ The Node constructor accepts a separate explicit selection:
500
+ `sessionInteractions: { mapping: 'grok-build-1.0.60-session-requests-v1' }`.
501
+ It requires an exclusive descendant-owning `executionBoundary`, exact direct
502
+ arguments `['agent', '--no-leader', 'stdio']`, modern stable ACP
503
+ `schema-v1.23.0`, and initialized native `_meta.agentVersion: '1.0.60'`.
504
+ The version observation is checked on every connection; it does not authenticate
505
+ an executable or its publisher. The host owns executable selection and byte verification.
506
+
507
+ This source mapping declares exactly `resume / plan_review` and
508
+ `resume / plan_review / after_open_return` continuation timing. It uses
509
+ `session/load` for the selected restoration. Install `interactions.onRequest`
510
+ and `nativeContinuations.onExecution` before resuming. The continuation handler
511
+ must begin pulling its sole `execution.outputs` iterator before an approval or
512
+ revision response can enter native work. A caller turn and a native continuation
513
+ share the same execution slot and continuous output validation.
514
+
515
+ The exact `_x.ai/exit_plan_mode` callback supplies a bounded complete plan
516
+ snapshot and three offered choices. Approval maps to `{ outcome: 'approved' }`;
517
+ revision maps to `{ outcome: 'cancelled', feedback }`; abandonment maps to
518
+ `{ outcome: 'abandoned' }` and needs no execution reservation. A revised response
519
+ must identify the offered plan revision and carry its bounded feedback. Missing
520
+ or null plan content cannot become a fabricated review snapshot.
521
+
522
+ Approval and revision continue the native request without sending
523
+ `session/prompt`. The existing session turn/queue owner accepts a fresh
524
+ `_x.ai/queue/changed.runningPromptId` beginning `plan-resume-`, correlates live
525
+ output to that exact prompt, and completes only on its matching
526
+ `_x.ai/session_notification` `turn_completed` update. Normal completion requires
527
+ `stop_reason: 'end_turn'` and observed live output. Exact `stop_reason: 'cancelled'`
528
+ uses the existing admitted cancellation reason to report `canceled`, or `expired`
529
+ for a timeout, including cancellation after promotion before any assistant output.
530
+ An unsolicited native `cancelled` terminal also reports `canceled`, without
531
+ asserting that a caller requested interruption.
532
+ The original execution reader retains that terminal and cleanup. The pinned
533
+ [Grok cancellation source](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-shell/src/session/acp_session_impl/cancel.rs#L842)
534
+ emits this exact cancellation terminal; historical `interrupted` is not an active
535
+ cancellation alias. Errors and foreign prompt/session identities remain failures.
536
+
537
+ Historical replay, a generic interaction-resolved notification and the local
538
+ response receipt establish no continuation completion. Prompt identities observed
539
+ in historical or replayed queue, output and terminal channels remain retained
540
+ under the existing shared queue/state budget, preventing later reuse as a fresh
541
+ promotion. Repeated notifications for the already owned current prompt remain
542
+ valid and do not reserve the same identity twice. Wrong, reused or competing
543
+ identities fail the owned session. Ordinary caller turns remain available after
544
+ that execution and its reader settle.
545
+
546
+ The selected 1.0.60 mapping also isolates direct native child-session observations.
547
+ Only a root-channel `_x.ai/session_notification` `subagent_spawned` announcement
548
+ with exact parent, child and subagent identities records that relationship.
549
+ Announced child queue notifications, stable session updates and native terminal
550
+ observations are validated and discarded before parent metadata, output,
551
+ continuation prompt tracking or completion. Unknown extensions, tool text and
552
+ permission metadata cannot announce a child. Arbitrary foreign sessions,
553
+ conflicting identity reuse and nested child announcements fail the owned session.
554
+
555
+ Each retained relationship shares the existing session queue's count and byte
556
+ budget; each identity is bounded to 256 UTF-8 bytes. Exact repeated announcements
557
+ reserve no extra state. Relationships remain reserved until session stop because
558
+ a child's prompt terminal does not establish child-session retirement. Historical
559
+ relationships are replay-only and cannot become live; discarded load-time child
560
+ observations still consume the opening replay budget. This observation scope adds
561
+ no public collaboration capability or child permission, filesystem, terminal or
562
+ form authority. Parent-addressed permission requests retain their existing root
563
+ session routing, and child-addressed requests remain rejected.
564
+
565
+ For an announced live child with an exact attempt identity, this mapping recognizes
566
+ one ordered native permission cancellation sequence. The child's
567
+ `pending_interaction` must first identify a permission tool. Its exact root-routed
568
+ `session/request_permission` callback must then identify that child through
569
+ `toolCall._meta['x.ai/requestingSessionId']` and the same tool ID. A subsequent
570
+ child `interaction_resolved` followed by a root `subagent_finished` with the exact
571
+ child/subagent/attempt identities and `status: 'cancelled'` withdraws only that
572
+ original callback. The existing request owner queues `request.unavailable` for a
573
+ displayed request before rejecting its reverse callback with `-32800`; an unseen
574
+ queued callback is removed without publishing its ID. A child `turn_completed`
575
+ notification supplies no permission cancellation authority.
576
+
577
+ An `interaction_resolved` notification alone cannot establish withdrawal because
578
+ it also covers answered and failed interactions. If its exact callback has not
579
+ already accepted an answer, new answer entry waits for corroboration under the
580
+ existing finite `cancellationTimeoutMs`; duplicate observations do not refresh
581
+ that deadline. Missing or contradictory corroboration fails and quiesces the
582
+ owned session without manufacturing withdrawal. An already entered answer keeps
583
+ its original receipt. Native callback reservations remain retained until actual
584
+ transport settlement and entered host work finish. Child/tool census entries,
585
+ including retired identities and their possible answer-wait state, share the
586
+ session count and byte budgets until stop. Pre-admission, prior-turn and replayed
587
+ facts cannot attach to or withdraw a future callback. This mapping consumes
588
+ observed native facts; it adds no child cancellation command or child-addressed
589
+ request authority.
590
+
591
+ The selected lifecycle request queue retains at most
592
+ `min(maxPendingRequests, 16)` native turn callbacks and 1 MiB of request material,
593
+ within the existing shared session budget. Lower configured limits still win.
594
+ Queued, displayed, response-entered and canceled callbacks remain counted until
595
+ their actual transport settlement and any entered host callback finish. Generic
596
+ ACP compositions retain the existing configurable request ceiling of 1–256
597
+ and default 16; the selected request-material ceiling does not replace the
598
+ 8 MiB default output budget.
599
+
600
+ Exact standard `$/cancel_request` disables only its correlated reverse callback.
601
+ A displayed turn request emits one `request.unavailable` with reason `withdrawn`
602
+ on its original execution before the next surviving FIFO request opens. A queued
603
+ canceled callback stays unseen. The protocol cancellation response is separate
604
+ from a human denial, and an already entered local delivery receipt keeps its
605
+ truth even if cancellation or a later write failure occurs. Output continues
606
+ through the original reader.
607
+
608
+ Both finite and `until_withdrawn_or_closed` human waiting are accepted for these
609
+ selected session requests. Actual provider expiry remains independent. This
610
+ mapping adds no create/branch session review, outside-turn form, remembered
611
+ approval, durable native recovery or private Hostd authority. Folder-trust
612
+ selection and its create-only restriction remain independent.
613
+
614
+ Controlled public-constructor and pipe-boundary proof establishes generic FIFO,
615
+ queued withdrawal, exact continuation admission and resource bounds. Final MS009
616
+ installed-native proof separately qualifies Grok Build 1.0.60 on Linux x86_64 /
617
+ Node 24.18.0 with modern stable `schema-v1.23.0`: exact active A withdrawal with
618
+ zero A answers, later B once-only delivery/effect, the original output terminal,
619
+ fresh retained approve/revise continuations with no caller prompt, and no-work
620
+ abandonment during its bounded observation. Each selected case independently
621
+ observes the original workload empty after first SDK close while the consumer
622
+ remains alive, before disposal.
623
+
624
+ That current Grok composition explicitly sets `GROK_AUTO_WAKE=0`,
625
+ `GROK_WORKER_THREADS=1`, `GROK_SUBAGENT_WORKER_THREADS=1` and
626
+ `GROK_GIX_STATUS_THREADS=1`. Automatic parent wake remains unqualified. Native
627
+ permission forwarding is observed to serialize; simultaneous live Grok callbacks
628
+ into the SDK FIFO queue remain unqualified under the user-accepted MS009 amendment.
629
+ Generic ACP FIFO and the separate Claude diagnostic's native FIFO proof do not
630
+ extend that Grok claim. The selected human-wait observation is 250 ms, which proves
631
+ that mapping's bounded observation rather than arbitrary native waiting duration.
632
+
633
+ The campaign uses isolated scripted model responses and a constant fake credential;
634
+ it qualifies no real backend authentication, inference or billing. The separate
635
+ Grok 1.0.50 remembered-edit regression retains its historical launch environment
636
+ and recorded task-limit warnings; it makes no capacity-headroom claim. Existing
637
+ Grok 1.0.50 qualification and the legacy `v0.11.4` transport profile retain their
638
+ original scope. The [0.4.2 migration guide](../../MIGRATING-TO-0.4.2.md) identifies
639
+ the executable examples, supported tuples and unsupported native paths.
640
+
481
641
  ## Reserved execution ownership
482
642
 
483
- This surface is included in the unpublished `0.4.1` source candidate.
643
+ This surface is included in the `0.4.1` release.
484
644
 
485
645
  The Node constructor also accepts an explicit `executionBoundary`. This host-owned
486
646
  port is useful when native descendants can create their own sessions or process
@@ -761,8 +921,8 @@ close ends that handle's ownership. The host retains control over its process po
761
921
  Canonical forms preflight the whole schema before presenting a request. They
762
922
  support at most 16 ordinary fields and 100 choices per selection. Free-text fields,
763
923
  nested objects, numeric fields, defaults, patterns/formats, and constraints without
764
- a V11 representation are rejected. Native free text permits blank/whitespace values
765
- and measures length in Unicode code points; V11 text answers use nonblank/UTF-16
924
+ a V12 representation are rejected. Native free text permits blank/whitespace values
925
+ and measures length in Unicode code points; V12 text answers use nonblank/UTF-16
766
926
  bounds. The provider does not narrow those native semantics silently. Enumerated
767
927
  string values can map through opaque canonical selection IDs.
768
928
  A required multi-select must require exactly one minimum selection; an optional
@@ -783,7 +943,7 @@ binding grants no interaction authority. Their host callbacks receive a turn
783
943
  lifetime signal that ends on cancellation, settlement or close.
784
944
 
785
945
  Image input allows at most 16 images, 375,000 decoded bytes per image and 750,000
786
- decoded bytes total, subject to the stricter 1 MiB V11 turn-content bound. Declared
946
+ decoded bytes total, subject to the stricter 1 MiB V12 turn-content bound. Declared
787
947
  media type, dimensions, base64 encoding and byte counts are validated. The provider
788
948
  does not decode pixels or publish generated bytes; hosts needing image-content
789
949
  inspection must perform it before submitting input.
@@ -9,9 +9,9 @@ function freezeCapabilities(value) {
9
9
  }
10
10
  function acpProviderCapabilities(providerKey, negotiated, configuration = "managed", authenticationFlows = [], sessionInteractions, permissionMapping = { mapping: "once-only-v1" }) {
11
11
  return freezeCapabilities(parseAgentCapabilities({
12
- protocolVersion: 11,
12
+ protocolVersion: 12,
13
13
  providerKey,
14
- sessions: { create: true, resume: sessionInteractions === void 0 && (negotiated.agentCapabilities.loadSession === true || negotiated.agentCapabilities.sessionCapabilities?.resume != null), branch: { kind: "unsupported" } },
14
+ sessions: { create: true, resume: sessionInteractions?.mapping !== "grok-folder-trust-v1" && (negotiated.agentCapabilities.loadSession === true || negotiated.agentCapabilities.sessionCapabilities?.resume != null), branch: { kind: "unsupported" } },
15
15
  turns: { interactionModes: ["default"], interrupt: true, steer: { kind: "unsupported" } },
16
16
  requests: {
17
17
  approval: acpPermissionCapability(permissionMapping),
@@ -30,6 +30,8 @@ export interface AcpNegotiatedAgent {
30
30
  readonly authMethods: readonly AcpJsonValue[];
31
31
  }
32
32
  export interface AcpSessionIngress {
33
+ readonly joinCallbacks: () => Promise<void>;
34
+ readonly onExtensionNotification?: (input: AcpNotificationContext) => void | Promise<void>;
33
35
  readonly onNotification: (input: AcpNotificationContext) => void | Promise<void>;
34
36
  readonly onPermissionRequest: (input: AcpReverseRequestContext) => Promise<AcpJsonValue>;
35
37
  readonly onElicitationRequest: (input: AcpReverseRequestContext) => Promise<AcpJsonValue>;
@@ -43,7 +45,7 @@ export interface AcpInitializedConnection {
43
45
  readonly attachSession: (ingress: AcpSessionIngress) => () => void;
44
46
  readonly bindSession: (binding: AcpBoundSession) => void;
45
47
  readonly requestWithScope: (method: string, params: AcpJsonValue, scope: AcpHostInteractionScope, options?: AcpRequestOptions) => Promise<AcpJsonValue>;
46
- readonly hostElicitation?: (context: AcpReverseRequestContext) => Promise<AcpJsonValue>;
48
+ readonly hostElicitation?: (context: AcpReverseRequestContext, observeCallback?: (work: Promise<void>) => void) => Promise<AcpJsonValue>;
47
49
  readonly close: () => Promise<void>;
48
50
  }
49
51
  //# sourceMappingURL=contracts.d.ts.map
@@ -12,6 +12,7 @@ import { SessionInteractionBroker } from "../sessionInteractions/broker.js";
12
12
  import { createSessionInteractionExtensionRegistry } from "../sessionInteractions/extensions.js";
13
13
  import { GROK_FOLDER_TRUST_CAPABILITY_META, GROK_FOLDER_TRUST_METHOD } from "../sessionInteractions/grokFolderTrust.js";
14
14
  import { awaitHost } from "../host/lifecycle.js";
15
+ import { GROK_PLAN_REVIEW_METHOD, GROK_QUEUE_METHOD, GROK_SESSION_NOTIFICATION_METHOD } from "../sessionInteractions/grokPlanReview.js";
15
16
  async function initializeAcpConnection(input) {
16
17
  throwIfAgentOperationAborted(input.context.signal);
17
18
  const initialization = new AbortController();
@@ -52,6 +53,7 @@ async function initializeAcpConnection(input) {
52
53
  const stopped = new AbortController();
53
54
  let client;
54
55
  let ingress;
56
+ let joinSessionCallbacks;
55
57
  let attached = false;
56
58
  let closePromise;
57
59
  let releasePromise;
@@ -63,7 +65,8 @@ async function initializeAcpConnection(input) {
63
65
  host,
64
66
  limits: input.limits,
65
67
  stopped: stopped.signal,
66
- ingress: () => ingress
68
+ ingress: () => ingress,
69
+ ...input.sessionInteractions ? { humanTimeoutMs: () => sessionInteractions?.humanTimeoutMs() } : {}
67
70
  });
68
71
  const release = () => {
69
72
  if (released) return Promise.resolve();
@@ -83,7 +86,11 @@ async function initializeAcpConnection(input) {
83
86
  closePromise = Promise.resolve().then(async () => {
84
87
  try {
85
88
  const stopping = [router.close(), ...client ? [client.dispose()] : [], release()];
86
- const cleanup = await Promise.allSettled([...stopping, ...sessionInteractions ? [sessionInteractions.join()] : []]);
89
+ const cleanup = await Promise.allSettled([
90
+ ...stopping,
91
+ ...joinSessionCallbacks ? [joinSessionCallbacks()] : [],
92
+ ...sessionInteractions ? [sessionInteractions.join()] : []
93
+ ]);
87
94
  const failures = cleanup.filter((result) => result.status === "rejected");
88
95
  if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP connection cleanup failed.");
89
96
  } finally {
@@ -113,7 +120,8 @@ async function initializeAcpConnection(input) {
113
120
  if (input.sessionInteractions) sessionInteractions = new SessionInteractionBroker({
114
121
  stopped: stopped.signal,
115
122
  limits: input.limits,
116
- onFailure: fail
123
+ onFailure: fail,
124
+ selection: input.sessionInteractions
117
125
  });
118
126
  const onPeerEnd = () => {
119
127
  fail(new AcpProviderConnectionError("acp_provider_peer_closed", "The ACP peer closed its stream."));
@@ -140,7 +148,7 @@ async function initializeAcpConnection(input) {
140
148
  schemaChannel: "stable",
141
149
  unknownExtensionPolicy: input.unknownExtensionPolicy,
142
150
  outboundRequestIdPrefix: "agenai-acp-",
143
- ...sessionInteractions ? { extensions: createSessionInteractionExtensionRegistry() } : {},
151
+ ...input.sessionInteractions ? { extensions: createSessionInteractionExtensionRegistry(input.sessionInteractions) } : {},
144
152
  limits: {
145
153
  maxInboundFrameBytes: Math.min(input.limits.maxQueuedBytes, 1024 * 1024),
146
154
  maxOutboundFrameBytes: Math.min(input.limits.maxQueuedBytes, 8 * 1024 * 1024),
@@ -152,6 +160,13 @@ async function initializeAcpConnection(input) {
152
160
  shutdownDrainMs: 0
153
161
  },
154
162
  notificationHandlers: {
163
+ ...input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? Object.fromEntries([GROK_QUEUE_METHOD, GROK_SESSION_NOTIFICATION_METHOD].map((method) => [
164
+ method,
165
+ (notification) => {
166
+ if (!ingress?.onExtensionNotification) throw new AcpProviderConnectionError("acp_provider_session_not_attached", "Native continuation output has no bound owner.");
167
+ return ingress.onExtensionNotification(notification);
168
+ }
169
+ ])) : {},
155
170
  "session/update": (notification) => {
156
171
  if (!ingress) throw new AcpProviderConnectionError("acp_provider_session_not_attached", "ACP output has no bound session.");
157
172
  return ingress.onNotification(notification);
@@ -159,7 +174,7 @@ async function initializeAcpConnection(input) {
159
174
  "elicitation/complete": (notification) => router.complete(notification.params ?? null)
160
175
  },
161
176
  reverseRequestHandlers: {
162
- ...sessionInteractions ? { [GROK_FOLDER_TRUST_METHOD]: sessionInteractions.handleRequest } : {},
177
+ ...sessionInteractions ? { [input.sessionInteractions?.mapping === "grok-folder-trust-v1" ? GROK_FOLDER_TRUST_METHOD : GROK_PLAN_REVIEW_METHOD]: sessionInteractions.handleRequest } : {},
163
178
  ...Object.fromEntries([
164
179
  "session/request_permission",
165
180
  "elicitation/create",
@@ -180,20 +195,20 @@ async function initializeAcpConnection(input) {
180
195
  const response = await createAcpStandardSessionOperations(client).initialize({
181
196
  protocolVersion: 1,
182
197
  clientCapabilities: {
183
- ...sessionInteractions ? { _meta: GROK_FOLDER_TRUST_CAPABILITY_META } : {},
198
+ ...input.sessionInteractions?.mapping === "grok-folder-trust-v1" ? { _meta: GROK_FOLDER_TRUST_CAPABILITY_META } : {},
184
199
  fs: { readTextFile: !!host.readTextFile, writeTextFile: !!host.writeTextFile },
185
200
  terminal: !!host.createTerminal,
186
201
  auth: { terminal: !!host.authenticateInTerminal && !!input.authentication?.terminal },
187
202
  elicitation: { form: {}, ...host.url ? { url: {} } : {} },
188
203
  ...input.configuration === "selectable" ? { session: { configOptions: { boolean: {} } } } : {}
189
204
  },
190
- clientInfo: { name: "@agen-ai/provider-acp", version: "0.4.1" }
205
+ clientInfo: { name: "@agen-ai/provider-acp", version: "0.4.2" }
191
206
  }, {
192
207
  timeoutMs: input.limits.initializationTimeoutMs,
193
208
  signal: initialization.signal
194
209
  });
195
210
  if (stopped.signal.aborted) throw stopped.signal.reason;
196
- const negotiated = readNegotiatedAgent(response, input.permissionMapping);
211
+ const negotiated = readNegotiatedAgent(response, input.permissionMapping, input.sessionInteractions);
197
212
  initialization.signal.removeEventListener("abort", onAbort);
198
213
  return Object.freeze({
199
214
  client,
@@ -202,7 +217,7 @@ async function initializeAcpConnection(input) {
202
217
  ...execution ? { execution } : {},
203
218
  ...sessionInteractions ? { sessionInteractions } : {},
204
219
  bindSession: (binding) => router.bindSession(binding),
205
- ...host.form || host.url ? { hostElicitation: (context) => router.sessionElicitation(context) } : {},
220
+ ...host.form || host.url ? { hostElicitation: (context, observeCallback) => router.sessionElicitation(context, observeCallback) } : {},
206
221
  async requestWithScope(method, params, scope, options = {}) {
207
222
  if (!["authenticate", "session/new", "session/load", "session/resume", "session/set_config_option", "session/set_mode"].includes(method)) {
208
223
  throw new TypeError("This ACP method does not grant request-scoped host interaction authority.");
@@ -219,6 +234,7 @@ async function initializeAcpConnection(input) {
219
234
  if (attached) throw new AcpProviderConnectionError("acp_provider_connection_already_attached", "ACP connections accept exactly one session.");
220
235
  attached = true;
221
236
  ingress = next;
237
+ joinSessionCallbacks = next.joinCallbacks;
222
238
  return () => {
223
239
  ingress = void 0;
224
240
  };
@@ -1,7 +1,8 @@
1
1
  import type { AcpJsonValue } from '@agen-ai/acp-transport';
2
2
  import type { AcpNegotiatedAgent } from './contracts.js';
3
3
  import type { AcpPermissionMappingSelection } from '../permissions/contracts.js';
4
+ import type { AcpSessionInteractionSelection } from '../sessionInteractions/contracts.js';
4
5
  /** The transport has already validated the complete pinned initialize response. */
5
- export declare function readNegotiatedAgent(response: AcpJsonValue, permissionMapping?: AcpPermissionMappingSelection): AcpNegotiatedAgent;
6
+ export declare function readNegotiatedAgent(response: AcpJsonValue, permissionMapping?: AcpPermissionMappingSelection, sessionInteractions?: AcpSessionInteractionSelection): AcpNegotiatedAgent;
6
7
  export declare function negotiatedEnvelope(negotiated: AcpNegotiatedAgent): string;
7
8
  //# sourceMappingURL=negotiation.d.ts.map
@@ -11,7 +11,7 @@ function immutableProtocolData(value) {
11
11
  }
12
12
  return value;
13
13
  }
14
- function readNegotiatedAgent(response, permissionMapping = { mapping: "once-only-v1" }) {
14
+ function readNegotiatedAgent(response, permissionMapping = { mapping: "once-only-v1" }, sessionInteractions) {
15
15
  const value = response;
16
16
  if (value.protocolVersion !== 1) {
17
17
  throw new AcpProviderConnectionError("acp_provider_version_unsupported", "ACP wire version 1 is required.");
@@ -28,6 +28,13 @@ function readNegotiatedAgent(response, permissionMapping = { mapping: "once-only
28
28
  agentInfo = Object.freeze({ name: info.name, version: info.version });
29
29
  }
30
30
  let agentVersion;
31
+ if (sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1") {
32
+ const meta = value._meta;
33
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta) || meta.agentVersion !== "1.0.60") {
34
+ throw new AcpProviderConnectionError("acp_session_profile_mismatch", "The selected session mapping requires exact initialized agentVersion 1.0.60.");
35
+ }
36
+ agentVersion = "1.0.60";
37
+ }
31
38
  if (permissionMapping.mapping === "declared-complete-consequences-v1") {
32
39
  if (agentInfo?.name !== permissionMapping.agentInfo.name || agentInfo.version !== permissionMapping.agentInfo.version) {
33
40
  throw new AcpProviderConnectionError("acp_permission_profile_mismatch", "The initialized ACP identity does not match the selected permission profile.");
@@ -7,15 +7,20 @@ export declare class AcpHostInteractions {
7
7
  private readonly urls;
8
8
  private readonly pendingUrls;
9
9
  private readonly leases;
10
+ private readonly forms;
10
11
  constructor(input: {
11
12
  readonly instanceId: AgentInstanceId;
12
13
  readonly host: AcpHostFacilities;
13
14
  readonly limits: AcpProviderLimits;
14
15
  readonly stopped: AbortSignal;
16
+ readonly humanTimeoutMs?: () => number | undefined;
15
17
  });
16
- request(context: AcpReverseRequestContext, scope: AcpHostInteractionScope, scopeSignal: AbortSignal): Promise<AcpJsonValue>;
18
+ request(context: AcpReverseRequestContext, scope: AcpHostInteractionScope, scopeSignal: AbortSignal, observeCallback?: (work: Promise<void>) => void): Promise<AcpJsonValue>;
19
+ joinRequest(settled: Promise<void>): Promise<void>;
17
20
  complete(id: string): Promise<void>;
18
21
  private releaseUrl;
22
+ private closeForms;
23
+ private closeOwned;
19
24
  close(): Promise<void>;
20
25
  closeScope(signal: AbortSignal): Promise<void>;
21
26
  }
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_AGENT_SESSION_INTERACTION_LIMITS } from "@agen-ai/agent-runtime";
1
2
  import { validateAcpFormContent, validateAcpFormSchema } from "../session/requests/forms.js";
2
3
  import { awaitHost, hostDeadline } from "./lifecycle.js";
3
4
  import { AcpHostLeases } from "./leases.js";
@@ -10,7 +11,8 @@ class AcpHostInteractions {
10
11
  urls = /* @__PURE__ */ new Map();
11
12
  pendingUrls = /* @__PURE__ */ new Map();
12
13
  leases;
13
- async request(context, scope, scopeSignal) {
14
+ forms = /* @__PURE__ */ new Map();
15
+ async request(context, scope, scopeSignal, observeCallback) {
14
16
  const params = hostObject(context.params);
15
17
  const message = hostText(params.message, 4e3);
16
18
  const signal = AbortSignal.any([context.signal, scopeSignal, this.input.stopped]);
@@ -18,15 +20,23 @@ class AcpHostInteractions {
18
20
  if (!this.input.host.form) throw unsupportedHostRequest();
19
21
  const schema = freezeHostData(structuredClone(hostObject(params.requestedSchema)));
20
22
  validateAcpFormSchema(schema);
21
- const deadline2 = hostDeadline(signal, this.input.limits.authenticationTimeoutMs);
23
+ const timeoutMs = scope.kind === "authentication" ? this.input.limits.authenticationTimeoutMs : this.input.humanTimeoutMs ? this.input.humanTimeoutMs() : DEFAULT_AGENT_SESSION_INTERACTION_LIMITS.requestTimeoutMs;
24
+ const lifetime = new AbortController();
25
+ const formSignal = AbortSignal.any([signal, lifetime.signal]);
26
+ const deadline2 = timeoutMs === void 0 ? { signal: formSignal, close() {
27
+ } } : hostDeadline(formSignal, timeoutMs);
28
+ const callback = Promise.resolve().then(() => {
29
+ if (deadline2.signal.aborted) throw deadline2.signal.reason;
30
+ return this.input.host.form({ instanceId: this.input.instanceId, scope, signal: deadline2.signal, message, requestedSchema: schema });
31
+ });
32
+ const actualCallback = callback.then(() => void 0, () => void 0);
33
+ observeCallback?.(actualCallback);
34
+ const completion = Promise.all([actualCallback, context.settled]).then(() => {
35
+ this.forms.delete(context.settled);
36
+ });
37
+ this.forms.set(context.settled, { scopeSignal, lifetime, completion });
22
38
  try {
23
- const result = await awaitHost(this.input.host.form({
24
- instanceId: this.input.instanceId,
25
- scope,
26
- signal: deadline2.signal,
27
- message,
28
- requestedSchema: schema
29
- }), deadline2.signal);
39
+ const result = await awaitHost(callback, deadline2.signal);
30
40
  if (result.action === "accept") {
31
41
  const content = hostObject(result.content);
32
42
  validateAcpFormContent(schema, content);
@@ -101,6 +111,9 @@ class AcpHostInteractions {
101
111
  if (!this.urls.has(id)) deadline.close();
102
112
  }
103
113
  }
114
+ joinRequest(settled) {
115
+ return this.forms.get(settled)?.completion ?? Promise.resolve();
116
+ }
104
117
  async complete(id) {
105
118
  const interaction = this.urls.get(id);
106
119
  if (!interaction) {
@@ -127,18 +140,31 @@ class AcpHostInteractions {
127
140
  interaction.detach();
128
141
  this.urls.delete(id);
129
142
  }
130
- async close() {
131
- await this.leases.close();
132
- for (const interaction of this.urls.values()) interaction.detach();
133
- this.urls.clear();
143
+ async closeForms(scope) {
144
+ const entries = [...this.forms.values()].filter((entry) => scope === void 0 || entry.scopeSignal === scope);
145
+ for (const entry of entries) entry.lifetime.abort(new DOMException("Host form closed.", "AbortError"));
146
+ const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
147
+ try {
148
+ await awaitHost(Promise.all(entries.map((entry) => entry.completion)), deadline.signal);
149
+ } finally {
150
+ deadline.close();
151
+ }
134
152
  }
135
- async closeScope(signal) {
136
- await this.leases.close(signal);
137
- for (const [id, interaction] of this.urls) if (interaction.scopeSignal === signal) {
153
+ async closeOwned(scope) {
154
+ const results = await Promise.allSettled([this.leases.close(scope), this.closeForms(scope)]);
155
+ const failures = results.filter((result) => result.status === "rejected");
156
+ if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP host interaction cleanup failed.");
157
+ for (const [id, interaction] of this.urls) if (scope === void 0 || interaction.scopeSignal === scope) {
138
158
  interaction.detach();
139
159
  this.urls.delete(id);
140
160
  }
141
161
  }
162
+ close() {
163
+ return this.closeOwned();
164
+ }
165
+ closeScope(signal) {
166
+ return this.closeOwned(signal);
167
+ }
142
168
  }
143
169
  export {
144
170
  AcpHostInteractions
@@ -14,13 +14,14 @@ export declare class AcpHostRouter {
14
14
  private readonly terminals;
15
15
  private readonly requests;
16
16
  private binding;
17
- private pending;
17
+ private readonly pending;
18
18
  private bytes;
19
19
  constructor(input: {
20
20
  readonly instanceId: AgentInstanceId;
21
21
  readonly host: AcpHostFacilities;
22
22
  readonly limits: AcpProviderLimits;
23
23
  readonly stopped: AbortSignal;
24
+ readonly humanTimeoutMs?: () => number | undefined;
24
25
  readonly ingress: () => AcpSessionIngress | undefined;
25
26
  });
26
27
  bindSession(binding: AcpBoundSession): void;
@@ -41,7 +42,7 @@ export declare class AcpHostRouter {
41
42
  private requireSession;
42
43
  private hostRequest;
43
44
  complete(value: AcpJsonValue): Promise<void>;
44
- sessionElicitation(context: AcpReverseRequestContext): Promise<AcpJsonValue>;
45
+ sessionElicitation(context: AcpReverseRequestContext, observeCallback?: (work: Promise<void>) => void): Promise<AcpJsonValue>;
45
46
  close(): Promise<void>;
46
47
  }
47
48
  //# sourceMappingURL=router.d.ts.map