@agen-ai/provider-acp 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -14
- package/dist/capabilities.js +2 -2
- package/dist/connection/contracts.d.ts +3 -1
- package/dist/connection/initialize.js +25 -9
- package/dist/connection/negotiation.d.ts +2 -1
- package/dist/connection/negotiation.js +8 -1
- package/dist/host/interactions.d.ts +6 -1
- package/dist/host/interactions.js +42 -16
- package/dist/host/router.d.ts +3 -2
- package/dist/host/router.js +23 -20
- package/dist/instance.js +19 -9
- package/dist/node/contracts.d.ts +2 -2
- package/dist/node/index.js +1 -1
- package/dist/node/launch.js +3 -3
- package/dist/session/continuation.d.ts +33 -0
- package/dist/session/continuation.js +185 -0
- package/dist/session/descendants.d.ts +29 -0
- package/dist/session/descendants.js +97 -0
- package/dist/session/mapping.js +1 -1
- package/dist/session/native.d.ts +1 -0
- package/dist/session/native.js +8 -0
- package/dist/session/opening.js +1 -1
- package/dist/session/requests/elicitation.d.ts +1 -1
- package/dist/session/requests/elicitation.js +1 -1
- package/dist/session/requests/grokWithdrawals.d.ts +28 -0
- package/dist/session/requests/grokWithdrawals.js +135 -0
- package/dist/session/requests/pending.d.ts +17 -4
- package/dist/session/requests/pending.js +63 -7
- package/dist/session/requests/resolution.js +4 -2
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +140 -32
- package/dist/session/turn.d.ts +6 -1
- package/dist/sessionInteractions/broker.d.ts +11 -3
- package/dist/sessionInteractions/broker.js +35 -13
- package/dist/sessionInteractions/extensions.d.ts +2 -1
- package/dist/sessionInteractions/extensions.js +21 -1
- package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
- package/dist/sessionInteractions/grokFolderTrust.js +0 -1
- package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
- package/dist/sessionInteractions/grokPlanReview.js +63 -0
- package/dist/sessionInteractions/request.d.ts +15 -7
- package/dist/sessionInteractions/request.js +46 -18
- 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
|
|
5
|
-
API. The coordinated
|
|
6
|
-
|
|
4
|
+
Agent Client Protocol wire version 1 into the existing Agent Protocol V12 session
|
|
5
|
+
API. The coordinated release is `0.4.3`; 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
416
|
-
|
|
415
|
+
The adapter exposes the exact `sessionInteractions.mappings` tuple
|
|
416
|
+
`{ openingOperation: 'create', requestKind: 'decision' }`, exclusive producer
|
|
417
|
+
ownership and, in current 0.4.3 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.3 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
|
|
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
|
|
765
|
-
and measures length in Unicode code points;
|
|
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
|
|
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.
|
package/dist/capabilities.js
CHANGED
|
@@ -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:
|
|
12
|
+
protocolVersion: 12,
|
|
13
13
|
providerKey,
|
|
14
|
-
sessions: { create: true, resume: sessionInteractions
|
|
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([
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
131
|
-
|
|
132
|
-
for (const
|
|
133
|
-
this.
|
|
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
|
|
136
|
-
await this.leases.close(
|
|
137
|
-
|
|
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
|
package/dist/host/router.d.ts
CHANGED
|
@@ -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
|