@automatalabs/acp-agents 0.21.1 → 0.22.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.
- package/dist/acp-client.d.ts +51 -0
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +127 -2
- package/dist/auth/auth-profiles.d.ts +63 -0
- package/dist/auth/auth-profiles.d.ts.map +1 -0
- package/dist/auth/auth-profiles.js +45 -0
- package/dist/auth/auth-store.d.ts +112 -0
- package/dist/auth/auth-store.d.ts.map +1 -0
- package/dist/auth/auth-store.js +186 -0
- package/dist/auth/auth-types.d.ts +93 -0
- package/dist/auth/auth-types.d.ts.map +1 -0
- package/dist/auth/auth-types.js +108 -0
- package/dist/backend.d.ts +7 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backends/claude.d.ts +2 -0
- package/dist/backends/claude.d.ts.map +1 -1
- package/dist/backends/claude.js +3 -0
- package/dist/backends/codex.d.ts +3 -0
- package/dist/backends/codex.d.ts.map +1 -1
- package/dist/backends/codex.js +4 -0
- package/dist/backends/opencode.d.ts +2 -0
- package/dist/backends/opencode.d.ts.map +1 -1
- package/dist/backends/opencode.js +3 -0
- package/dist/capabilities.d.ts +7 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +17 -0
- package/dist/client-handlers.d.ts +9 -0
- package/dist/client-handlers.d.ts.map +1 -1
- package/dist/client-handlers.js +17 -0
- package/dist/errors-map.d.ts.map +1 -1
- package/dist/errors-map.js +26 -2
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/permissions.d.ts +27 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +23 -1
- package/dist/pool.d.ts +20 -0
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +58 -16
- package/dist/protocol-coverage.d.ts +75 -0
- package/dist/protocol-coverage.d.ts.map +1 -1
- package/dist/protocol-coverage.js +58 -0
- package/dist/runner.d.ts +120 -3
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +307 -20
- package/package.json +3 -3
package/dist/runner.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { type AcpEventListener, type AcpEventName } from "./events.js";
|
|
|
6
6
|
import type { Backend } from "./backend.js";
|
|
7
7
|
import { InteractiveSession, type InteractiveSessionOptions } from "./interactive.js";
|
|
8
8
|
import { type BackendRegistry, type CustomBackendConfig } from "./registry.js";
|
|
9
|
+
import { type AuthMethodDescriptor, type AuthResolution, type AuthResolver } from "./auth/auth-types.js";
|
|
10
|
+
import { type AuthMethodType, type BackendAuthState } from "./auth/auth-store.js";
|
|
9
11
|
import type { ElicitationResolver, PermissionResolver } from "./permissions.js";
|
|
10
12
|
interface LifecycleRoutingOptions {
|
|
11
13
|
/** Model spec used only to select the backend process. */
|
|
@@ -73,6 +75,60 @@ export interface ReattachSessionOptions extends InteractiveSessionOptions {
|
|
|
73
75
|
/** Alias for onPermissionRequest for hosts that name the resolver by role. */
|
|
74
76
|
permissionResolver?: PermissionResolver;
|
|
75
77
|
}
|
|
78
|
+
/** Options for AcpAgentRunner.completeAuth() (§1.3, §4.1). */
|
|
79
|
+
export interface CompleteAuthOptions extends AuthMethodsOptions {
|
|
80
|
+
/** A method id from describeAuthMethods(). */
|
|
81
|
+
methodId: string;
|
|
82
|
+
/** The host-collected resolution (env values / gateway meta / completed / cancelled) (§1.3). */
|
|
83
|
+
resolution: AuthResolution;
|
|
84
|
+
/** Event/telemetry label used in strict capability errors. */
|
|
85
|
+
label?: string;
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
}
|
|
88
|
+
/** The outcome of a host-completed auth step (§1.3, §4.1). Carries no secret. */
|
|
89
|
+
export type AuthOutcome = {
|
|
90
|
+
status: "authenticated" | "cancelled";
|
|
91
|
+
methodId: string;
|
|
92
|
+
recycled: boolean;
|
|
93
|
+
};
|
|
94
|
+
/** Redacted status view surfaced by the controller, MCP tool, and web (canonical shape; §2.14). */
|
|
95
|
+
export interface AuthStatusSnapshot {
|
|
96
|
+
backendId: string;
|
|
97
|
+
poolKey: string;
|
|
98
|
+
state: BackendAuthState;
|
|
99
|
+
authenticated: boolean;
|
|
100
|
+
canResume: boolean;
|
|
101
|
+
methods: {
|
|
102
|
+
id: string;
|
|
103
|
+
type: AuthMethodType;
|
|
104
|
+
name?: string;
|
|
105
|
+
}[];
|
|
106
|
+
}
|
|
107
|
+
/** The `runner.auth` controller — the auth verbs as one addressable object (§2.10, §4.1). */
|
|
108
|
+
export interface AuthController {
|
|
109
|
+
/** Alias of describeAuthMethods(). */
|
|
110
|
+
methods(opts?: AuthMethodsOptions): Promise<AuthMethodDescriptor[]>;
|
|
111
|
+
/** Alias of completeAuth(). */
|
|
112
|
+
authenticate(opts: CompleteAuthOptions): Promise<AuthOutcome>;
|
|
113
|
+
/** Clears the AuthStore for the backend, zeroizes secrets (§2.14), and recycles the pool. */
|
|
114
|
+
logout(opts?: LogoutOptions): Promise<void>;
|
|
115
|
+
/** Redacted, synchronous snapshot — ids/types/names + state only, NEVER secrets (§2.14). */
|
|
116
|
+
status(opts?: {
|
|
117
|
+
backend?: string;
|
|
118
|
+
}): AuthStatusSnapshot[];
|
|
119
|
+
/** Cold-resume re-arm predicate (§2.13): true iff state ∈ {authenticated,credentials_held} or diskBacked. */
|
|
120
|
+
canResume(backendId: string): boolean;
|
|
121
|
+
}
|
|
122
|
+
/** Structural capability interface the MCP composition root duck-types to register auth tools
|
|
123
|
+
* without widening the frozen `AgentRunner` seam (§4.1). `AcpAgentRunner` implements it. */
|
|
124
|
+
export interface AuthCapableRunner {
|
|
125
|
+
describeAuthMethods(opts?: AuthMethodsOptions): Promise<AuthMethodDescriptor[]>;
|
|
126
|
+
completeAuth(opts: CompleteAuthOptions): Promise<AuthOutcome>;
|
|
127
|
+
/** Ids of every configured backend (built-ins + AcpRunnerOptions.backends), whether or not it
|
|
128
|
+
* yet has a BackendAuthMachine. */
|
|
129
|
+
listBackends(): string[];
|
|
130
|
+
readonly auth: AuthController;
|
|
131
|
+
}
|
|
76
132
|
/** Constructor options for the runner: pool sizing, client-side handlers, and the custom-backend
|
|
77
133
|
* registry. `backends` merges over (and wins against) env-declared AGENTPRISM_BACKENDS entries. */
|
|
78
134
|
export interface AcpRunnerOptions extends AcpPoolOptions {
|
|
@@ -85,13 +141,27 @@ export interface AcpRunnerOptions extends AcpPoolOptions {
|
|
|
85
141
|
/** Runner-wide ACP elicitation responder. When set, initialize advertises unstable
|
|
86
142
|
* elicitation form/url support on every connection; sessions may override the resolver. */
|
|
87
143
|
onElicitation?: ElicitationResolver;
|
|
144
|
+
/** Which auth method TYPES this host can complete (§1.2). When set, initialize advertises the
|
|
145
|
+
* matching client auth capability (`auth.terminal` + top-level `_meta["terminal-auth"]`, and/or
|
|
146
|
+
* `auth._meta.gateway`) on every connection, fixed for the connection lifetime. Unset (and
|
|
147
|
+
* `onAuth` unset) omits the `auth` capability entirely — the default-OFF, zero-behavior-change
|
|
148
|
+
* baseline. When `onAuth` is set but this is unset it derives to `{ terminal: false, gateway: true }`
|
|
149
|
+
* (§1.2). A native-TTY CLI host passes `{ terminal: true, gateway: true }`. */
|
|
150
|
+
authCapabilities?: {
|
|
151
|
+
terminal?: boolean;
|
|
152
|
+
gateway?: boolean;
|
|
153
|
+
};
|
|
154
|
+
/** Inline auth resolver (§1.3, §2.11). When set, a -32000 at session/new resolves-and-retries-once
|
|
155
|
+
* and the run NEVER pauses; when unset, a -32000 run pauses with reason:"auth_required" (§2.12,
|
|
156
|
+
* PR4). Mutually exclusive with pause by construction. */
|
|
157
|
+
onAuth?: AuthResolver;
|
|
88
158
|
}
|
|
89
159
|
/**
|
|
90
160
|
* ACP-backed AgentRunner implementation. The caller that constructs an AcpAgentRunner owns it:
|
|
91
161
|
* pass it into managers/runs as needed, then call dispose() (or use `await using`) when that
|
|
92
162
|
* owner is done with the pooled and dedicated backend processes.
|
|
93
163
|
*/
|
|
94
|
-
export declare class AcpAgentRunner implements AgentRunner {
|
|
164
|
+
export declare class AcpAgentRunner implements AgentRunner, AuthCapableRunner {
|
|
95
165
|
private readonly pool;
|
|
96
166
|
/** The resolved custom-backend registry (env + option, validated at construction). */
|
|
97
167
|
private readonly backends;
|
|
@@ -105,6 +175,18 @@ export declare class AcpAgentRunner implements AgentRunner {
|
|
|
105
175
|
private readonly clientHandlers;
|
|
106
176
|
private readonly permissionResolver;
|
|
107
177
|
private readonly elicitationResolver;
|
|
178
|
+
/** Client auth advertisement, derived ONCE at construction and fixed for every connection this
|
|
179
|
+
* runner opens (pooled and dedicated). Undefined => the `auth` capability is omitted (§1.2). */
|
|
180
|
+
private readonly authCapabilities;
|
|
181
|
+
/** Inline auth resolver (§2.11). When set, run() resolves a -32000 and retries once instead of
|
|
182
|
+
* surfacing AUTH_REQUIRED. Undefined => the (PR4) pause-and-resume path. */
|
|
183
|
+
private readonly onAuth;
|
|
184
|
+
/** The single per-runner auth store (§2.2). Holds every backend's `BackendAuthMachine`; the only
|
|
185
|
+
* home for credential material in the library. Threaded into the pool and every dedicated
|
|
186
|
+
* connection so all connection types reconcile to the same intent. */
|
|
187
|
+
private readonly authStore;
|
|
188
|
+
/** The auth verbs as one addressable object (§2.10). */
|
|
189
|
+
readonly auth: AuthController;
|
|
108
190
|
private readonly structuredOutputTools;
|
|
109
191
|
/** FIFO turn queue per pooled connection for injected-tool schema runs (see the injection
|
|
110
192
|
* site for why concurrent injected sessions on one process cannot be isolated). */
|
|
@@ -143,15 +225,33 @@ export declare class AcpAgentRunner implements AgentRunner {
|
|
|
143
225
|
openSession(opts: InteractiveSessionOptions): Promise<InteractiveSession>;
|
|
144
226
|
/** Return the selected backend's initialize-advertised authentication methods. */
|
|
145
227
|
authMethods(opts?: AuthMethodsOptions): Promise<AuthMethod[]>;
|
|
146
|
-
/** Drive ACP authenticate on the selected backend.
|
|
228
|
+
/** Drive ACP authenticate on the selected backend. REBUILT off dispose-after-authenticate (§2.9):
|
|
229
|
+
* instead of opening a dedicated connection and disposing it in `finally` — which lost any
|
|
230
|
+
* in-process (gateway) credential the agent stored on that process (gap 3) — this records the
|
|
231
|
+
* credential into the durable `AuthStore` and recycles the pool. A method with `_meta` records an
|
|
232
|
+
* in-process/disk intent replayed on every pooled connection's initialize; a bare method with no
|
|
233
|
+
* `_meta` fires the one-shot `agent-login` RPC so the agent runs its own login. */
|
|
147
234
|
authenticate(opts: AuthenticateOptions): Promise<AuthenticateResponse | void>;
|
|
235
|
+
/** Proactively enumerate the selected backend's advertised methods, already type-dispatched (§1.3)
|
|
236
|
+
* and label-enriched by the backend's `AuthProfile.describe` (§3.1, identity for a profile-less
|
|
237
|
+
* custom backend). A read-only probe: opens a dedicated connection, reads the initialize-advertised
|
|
238
|
+
* methods, runs the base dispatcher through the profile seam, and disposes. */
|
|
239
|
+
describeAuthMethods(opts?: AuthMethodsOptions): Promise<AuthMethodDescriptor[]>;
|
|
240
|
+
/** Record the host-collected resolution into the AuthStore, advance the generation, and recycle
|
|
241
|
+
* the pool (§2.9/§2.6) so a subsequent run() always lands on a current connection. */
|
|
242
|
+
completeAuth(opts: CompleteAuthOptions): Promise<AuthOutcome>;
|
|
243
|
+
/** Ids of every configured backend (built-ins + AcpRunnerOptions.backends). */
|
|
244
|
+
listBackends(): string[];
|
|
148
245
|
/** List configurable providers from the selected backend. */
|
|
149
246
|
listProviders(opts?: ListProvidersOptions): Promise<ListProvidersResponse>;
|
|
150
247
|
/** Configure one provider on the selected backend. */
|
|
151
248
|
setProvider(opts: SetProviderOptions): Promise<SetProviderResponse | void>;
|
|
152
249
|
/** Disable one provider on the selected backend. */
|
|
153
250
|
disableProvider(opts: DisableProviderOptions): Promise<DisableProviderResponse | void>;
|
|
154
|
-
/** Logout through the selected backend.
|
|
251
|
+
/** Logout through the selected backend. REBUILT (§2.9): first clear the AuthStore machine
|
|
252
|
+
* (zeroizing `authenticateMeta`/`envValues`, §2.14) and recycle the pool so no pooled process
|
|
253
|
+
* replays a stale gateway credential, THEN issue the agent `logout` RPC only where advertised
|
|
254
|
+
* (gated on `supportsLogout`; opencode advertises none → store-clear + recycle, no RPC, §3.4). */
|
|
155
255
|
logout(opts?: LogoutOptions): Promise<LogoutResponse | void>;
|
|
156
256
|
/** List persisted ACP sessions from the selected backend. */
|
|
157
257
|
listSessions(opts?: ListSessionsOptions): Promise<ListSessionsResponse>;
|
|
@@ -171,6 +271,23 @@ export declare class AcpAgentRunner implements AgentRunner {
|
|
|
171
271
|
[Symbol.asyncDispose](): Promise<void>;
|
|
172
272
|
private createInteractiveSession;
|
|
173
273
|
private createDedicatedConnection;
|
|
274
|
+
/** Read the selected backend's initialize-advertised auth methods on a dedicated connection and
|
|
275
|
+
* build their type-dispatched descriptors (§1.3). A read-only probe; the connection is disposed. */
|
|
276
|
+
private probeAuthMethods;
|
|
277
|
+
/** The shared write path (§2.9) behind completeAuth, the inline resolver (§2.11), and the rebuilt
|
|
278
|
+
* legacy authenticate(). Records the payload/methodType from the outcome but DERIVES `klass` from
|
|
279
|
+
* the chosen method's type + `_meta` shape (§2.1) — never from the resolution outcome. */
|
|
280
|
+
private applyResolution;
|
|
281
|
+
/** Inline resolve-and-retry-once at the run() session-acquisition seam (§2.11). Builds the
|
|
282
|
+
* AuthContext from the backend's advertised methods, invokes `onAuth`, and applies the result.
|
|
283
|
+
* Returns false on a cancelled/absent resolution (the caller propagates AUTH_REQUIRED). */
|
|
284
|
+
private resolveInlineAuth;
|
|
285
|
+
/** Redacted status snapshots (§2.10/§4.1). Enumerates every configured backend when `backend` is
|
|
286
|
+
* omitted; never exposes secrets. */
|
|
287
|
+
private authStatus;
|
|
288
|
+
private snapshotFor;
|
|
289
|
+
/** Cold-resume re-arm predicate (§2.13). */
|
|
290
|
+
canResume(backendId: string): boolean;
|
|
174
291
|
/** Build the backend choice, model-selection spec, tool policy, and session/new options in one
|
|
175
292
|
* place for run() and openSession() so new AcpSessionOptions fields cannot drift by path. */
|
|
176
293
|
private prepareSession;
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAmBA,OAAO,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Timeout and abort are the ENGINE's job: we honor opts.signal (wired to ACP session/cancel)
|
|
18
18
|
// and re-throw on abort, but never implement our own timeout.
|
|
19
19
|
import { isAbsolute } from "node:path";
|
|
20
|
-
import { WorkflowError, WorkflowErrorCode, } from "@automatalabs/shared-types";
|
|
20
|
+
import { isWorkflowError, WorkflowError, WorkflowErrorCode, } from "@automatalabs/shared-types";
|
|
21
21
|
import { PooledConnection } from "./acp-client.js";
|
|
22
22
|
import { AcpAgentPool } from "./pool.js";
|
|
23
23
|
import { TypedEventEmitter, } from "./events.js";
|
|
@@ -28,6 +28,8 @@ import { OpenCodeBackend } from "./backends/opencode.js";
|
|
|
28
28
|
import { CustomAcpBackend } from "./backends/custom.js";
|
|
29
29
|
import { registryWithRunBackends, resolveBackendRegistry, } from "./registry.js";
|
|
30
30
|
import { mapThrownError } from "./errors-map.js";
|
|
31
|
+
import { buildAuthDescriptor, } from "./auth/auth-types.js";
|
|
32
|
+
import { AuthStore, classifyCredential, } from "./auth/auth-store.js";
|
|
31
33
|
import { resolveStructuredOutput } from "./structured-output.js";
|
|
32
34
|
import { STRUCTURED_OUTPUT_SERVER_NAME, StructuredOutputToolHost, } from "./structured-tool.js";
|
|
33
35
|
import { buildRunPrompt, mergeTurnMeta, promptWithImages, validatePromptImages, } from "./prompt.js";
|
|
@@ -51,6 +53,18 @@ export class AcpAgentRunner {
|
|
|
51
53
|
clientHandlers;
|
|
52
54
|
permissionResolver;
|
|
53
55
|
elicitationResolver;
|
|
56
|
+
/** Client auth advertisement, derived ONCE at construction and fixed for every connection this
|
|
57
|
+
* runner opens (pooled and dedicated). Undefined => the `auth` capability is omitted (§1.2). */
|
|
58
|
+
authCapabilities;
|
|
59
|
+
/** Inline auth resolver (§2.11). When set, run() resolves a -32000 and retries once instead of
|
|
60
|
+
* surfacing AUTH_REQUIRED. Undefined => the (PR4) pause-and-resume path. */
|
|
61
|
+
onAuth;
|
|
62
|
+
/** The single per-runner auth store (§2.2). Holds every backend's `BackendAuthMachine`; the only
|
|
63
|
+
* home for credential material in the library. Threaded into the pool and every dedicated
|
|
64
|
+
* connection so all connection types reconcile to the same intent. */
|
|
65
|
+
authStore = new AuthStore();
|
|
66
|
+
/** The auth verbs as one addressable object (§2.10). */
|
|
67
|
+
auth;
|
|
54
68
|
structuredOutputTools = new StructuredOutputToolHost();
|
|
55
69
|
/** FIFO turn queue per pooled connection for injected-tool schema runs (see the injection
|
|
56
70
|
* site for why concurrent injected sessions on one process cannot be isolated). */
|
|
@@ -66,13 +80,31 @@ export class AcpAgentRunner {
|
|
|
66
80
|
this.clientHandlers = options.clientHandlers;
|
|
67
81
|
this.permissionResolver = options.onPermissionRequest;
|
|
68
82
|
this.elicitationResolver = options.onElicitation;
|
|
83
|
+
this.onAuth = options.onAuth;
|
|
84
|
+
// Default derivation (§1.2): explicit `authCapabilities` wins; else, when an `onAuth` resolver is
|
|
85
|
+
// present, derive `{ terminal: false, gateway: true }` (gateway is cheap and non-destructive;
|
|
86
|
+
// terminal needs a real TTY a generic programmatic host lacks); else omit `auth` entirely — the
|
|
87
|
+
// default-OFF, byte-identical baseline.
|
|
88
|
+
this.authCapabilities =
|
|
89
|
+
options.authCapabilities ?? (options.onAuth ? { terminal: false, gateway: true } : undefined);
|
|
69
90
|
this.pool = new AcpAgentPool(options, {
|
|
70
91
|
onEvent: this.emitEvent,
|
|
71
92
|
permissionResolver: options.onPermissionRequest,
|
|
72
93
|
elicitationResolver: options.onElicitation,
|
|
73
94
|
advertiseElicitation: Boolean(options.onElicitation),
|
|
95
|
+
authCapabilities: this.authCapabilities,
|
|
96
|
+
authStore: this.authStore,
|
|
74
97
|
});
|
|
75
98
|
this.backends = resolveBackendRegistry(options.backends);
|
|
99
|
+
this.auth = {
|
|
100
|
+
methods: (opts) => this.describeAuthMethods(opts),
|
|
101
|
+
authenticate: (opts) => this.completeAuth(opts),
|
|
102
|
+
logout: async (opts) => {
|
|
103
|
+
await this.logout(opts ?? {});
|
|
104
|
+
},
|
|
105
|
+
status: (opts) => this.authStatus(opts),
|
|
106
|
+
canResume: (backendId) => this.canResume(backendId),
|
|
107
|
+
};
|
|
76
108
|
}
|
|
77
109
|
/**
|
|
78
110
|
* Listen in on the live ACP stream. `name` is an ACP `sessionUpdate` discriminant
|
|
@@ -125,28 +157,55 @@ export class AcpAgentRunner {
|
|
|
125
157
|
await disposeBestEffort(connection);
|
|
126
158
|
}
|
|
127
159
|
}
|
|
128
|
-
/** Drive ACP authenticate on the selected backend.
|
|
160
|
+
/** Drive ACP authenticate on the selected backend. REBUILT off dispose-after-authenticate (§2.9):
|
|
161
|
+
* instead of opening a dedicated connection and disposing it in `finally` — which lost any
|
|
162
|
+
* in-process (gateway) credential the agent stored on that process (gap 3) — this records the
|
|
163
|
+
* credential into the durable `AuthStore` and recycles the pool. A method with `_meta` records an
|
|
164
|
+
* in-process/disk intent replayed on every pooled connection's initialize; a bare method with no
|
|
165
|
+
* `_meta` fires the one-shot `agent-login` RPC so the agent runs its own login. */
|
|
129
166
|
async authenticate(opts) {
|
|
130
167
|
if (this.disposed)
|
|
131
168
|
throw new Error("ACP agent runner is disposed");
|
|
132
169
|
validateRequiredString(opts.methodId, opts.label, "authenticate", "methodId");
|
|
133
170
|
opts.signal?.throwIfAborted();
|
|
134
171
|
const backend = selectBackend(opts, this.backends);
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
172
|
+
const { methods } = await this.probeAuthMethods(backend);
|
|
173
|
+
const resolution = opts.meta
|
|
174
|
+
? { outcome: "meta", methodId: opts.methodId, meta: opts.meta }
|
|
175
|
+
: { outcome: "agent-login", methodId: opts.methodId };
|
|
176
|
+
await this.applyResolution(backend, resolution, methods, opts.methodId, opts.label);
|
|
177
|
+
opts.signal?.throwIfAborted();
|
|
178
|
+
if (this.disposed)
|
|
179
|
+
throw new Error("ACP agent runner is disposed");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
/** Proactively enumerate the selected backend's advertised methods, already type-dispatched (§1.3)
|
|
183
|
+
* and label-enriched by the backend's `AuthProfile.describe` (§3.1, identity for a profile-less
|
|
184
|
+
* custom backend). A read-only probe: opens a dedicated connection, reads the initialize-advertised
|
|
185
|
+
* methods, runs the base dispatcher through the profile seam, and disposes. */
|
|
186
|
+
async describeAuthMethods(opts = {}) {
|
|
187
|
+
if (this.disposed)
|
|
188
|
+
throw new Error("ACP agent runner is disposed");
|
|
189
|
+
const backend = selectBackend(opts, this.backends);
|
|
190
|
+
return (await this.probeAuthMethods(backend)).descriptors;
|
|
191
|
+
}
|
|
192
|
+
/** Record the host-collected resolution into the AuthStore, advance the generation, and recycle
|
|
193
|
+
* the pool (§2.9/§2.6) so a subsequent run() always lands on a current connection. */
|
|
194
|
+
async completeAuth(opts) {
|
|
195
|
+
if (this.disposed)
|
|
196
|
+
throw new Error("ACP agent runner is disposed");
|
|
197
|
+
validateRequiredString(opts.methodId, opts.label, "completeAuth", "methodId");
|
|
198
|
+
opts.signal?.throwIfAborted();
|
|
199
|
+
const backend = selectBackend(opts, this.backends);
|
|
200
|
+
const { methods } = await this.probeAuthMethods(backend);
|
|
201
|
+
return this.applyResolution(backend, opts.resolution, methods, opts.methodId, opts.label);
|
|
202
|
+
}
|
|
203
|
+
/** Ids of every configured backend (built-ins + AcpRunnerOptions.backends). */
|
|
204
|
+
listBackends() {
|
|
205
|
+
const ids = new Set(["claude", "codex", "opencode"]);
|
|
206
|
+
for (const name of this.backends.keys())
|
|
207
|
+
ids.add(name);
|
|
208
|
+
return [...ids];
|
|
150
209
|
}
|
|
151
210
|
/** List configurable providers from the selected backend. */
|
|
152
211
|
async listProviders(opts = {}) {
|
|
@@ -220,14 +279,28 @@ export class AcpAgentRunner {
|
|
|
220
279
|
await disposeBestEffort(connection);
|
|
221
280
|
}
|
|
222
281
|
}
|
|
223
|
-
/** Logout through the selected backend.
|
|
282
|
+
/** Logout through the selected backend. REBUILT (§2.9): first clear the AuthStore machine
|
|
283
|
+
* (zeroizing `authenticateMeta`/`envValues`, §2.14) and recycle the pool so no pooled process
|
|
284
|
+
* replays a stale gateway credential, THEN issue the agent `logout` RPC only where advertised
|
|
285
|
+
* (gated on `supportsLogout`; opencode advertises none → store-clear + recycle, no RPC, §3.4). */
|
|
224
286
|
async logout(opts = {}) {
|
|
225
287
|
if (this.disposed)
|
|
226
288
|
throw new Error("ACP agent runner is disposed");
|
|
227
289
|
opts.signal?.throwIfAborted();
|
|
228
290
|
const backend = selectBackend(opts, this.backends);
|
|
291
|
+
const poolKey = backend.poolKey ?? backend.id;
|
|
292
|
+
this.authStore.machineFor(poolKey, backend.authProfile).send({ t: "logout" });
|
|
293
|
+
this.pool.recycle(poolKey);
|
|
229
294
|
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
230
295
|
try {
|
|
296
|
+
// await ready + negotiate before reading the logout advertisement.
|
|
297
|
+
await connection.authMethods();
|
|
298
|
+
if (!connection.capabilities?.supportsLogout) {
|
|
299
|
+
opts.signal?.throwIfAborted();
|
|
300
|
+
if (this.disposed)
|
|
301
|
+
throw new Error("ACP agent runner is disposed");
|
|
302
|
+
return; // logout unadvertised — store already cleared + recycled, no RPC (§3.4)
|
|
303
|
+
}
|
|
231
304
|
const request = {
|
|
232
305
|
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
233
306
|
};
|
|
@@ -328,7 +401,7 @@ export class AcpAgentRunner {
|
|
|
328
401
|
let structuredToolActive = false;
|
|
329
402
|
let releaseStructuredToolTurn;
|
|
330
403
|
try {
|
|
331
|
-
|
|
404
|
+
const prepare = async (connection) => {
|
|
332
405
|
let sessionOptions = prepared.sessionOptions;
|
|
333
406
|
if (shouldInjectStructuredOutputTool(schema, prepared.backend, connection.capabilities)) {
|
|
334
407
|
// Injected runs are SERIALIZED per connection, and the server name stays CONSTANT.
|
|
@@ -357,7 +430,43 @@ export class AcpAgentRunner {
|
|
|
357
430
|
};
|
|
358
431
|
}
|
|
359
432
|
return sessionOptions;
|
|
360
|
-
}
|
|
433
|
+
};
|
|
434
|
+
// Inline resolve-and-retry-once (§2.11): when `onAuth` is set, a -32000 at session/new is
|
|
435
|
+
// resolved via the resolver and the acquire retried EXACTLY once — the run never pauses. A
|
|
436
|
+
// second -32000 propagates as AUTH_REQUIRED. When `onAuth` is unset this loop runs once and
|
|
437
|
+
// the error propagates unchanged (the PR4 pause-and-resume path), byte-identical to today.
|
|
438
|
+
let authRetried = false;
|
|
439
|
+
for (;;) {
|
|
440
|
+
try {
|
|
441
|
+
session = await this.pool.acquirePrepared(prepared.backend, prepare, {
|
|
442
|
+
signal: opts.signal,
|
|
443
|
+
label: opts.label,
|
|
444
|
+
});
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
if (this.onAuth && !authRetried && !opts.signal?.aborted && isAuthRequiredError(error)) {
|
|
449
|
+
authRetried = true;
|
|
450
|
+
// Discard the failed attempt's partial structured-tool registration so the retry's
|
|
451
|
+
// prepare re-registers cleanly (the failure happened at session/new, after prepare ran).
|
|
452
|
+
releaseStructuredToolTurn?.();
|
|
453
|
+
releaseStructuredToolTurn = undefined;
|
|
454
|
+
try {
|
|
455
|
+
structuredTool?.release();
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// best-effort cleanup between attempts.
|
|
459
|
+
}
|
|
460
|
+
structuredTool = undefined;
|
|
461
|
+
structuredToolActive = false;
|
|
462
|
+
const resolved = await this.resolveInlineAuth(prepared.backend, opts, error);
|
|
463
|
+
if (!resolved)
|
|
464
|
+
throw error; // cancelled/unresolved -> propagate AUTH_REQUIRED
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
361
470
|
const activeSession = session;
|
|
362
471
|
opts.signal?.throwIfAborted();
|
|
363
472
|
// Hand the host the re-attach identity BEFORE any turn runs: the session id plus the
|
|
@@ -563,9 +672,139 @@ export class AcpAgentRunner {
|
|
|
563
672
|
permissionResolver: this.permissionResolver,
|
|
564
673
|
elicitationResolver: this.elicitationResolver,
|
|
565
674
|
advertiseElicitation: Boolean(this.elicitationResolver),
|
|
675
|
+
authCapabilities: this.authCapabilities,
|
|
676
|
+
// Same store as the pool: a dedicated connection re-primes the durable intent at its own
|
|
677
|
+
// initialize (§2.5) — the direct fix for the dispose-after-authenticate bug (gap 3).
|
|
678
|
+
authStore: this.authStore,
|
|
566
679
|
clientHandlers: this.clientHandlers,
|
|
567
680
|
});
|
|
568
681
|
}
|
|
682
|
+
/** Read the selected backend's initialize-advertised auth methods on a dedicated connection and
|
|
683
|
+
* build their type-dispatched descriptors (§1.3). A read-only probe; the connection is disposed. */
|
|
684
|
+
async probeAuthMethods(backend) {
|
|
685
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
686
|
+
try {
|
|
687
|
+
const methods = await connection.authMethods();
|
|
688
|
+
return { methods, descriptors: describeMethods(methods, backend) };
|
|
689
|
+
}
|
|
690
|
+
finally {
|
|
691
|
+
await disposeBestEffort(connection);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/** The shared write path (§2.9) behind completeAuth, the inline resolver (§2.11), and the rebuilt
|
|
695
|
+
* legacy authenticate(). Records the payload/methodType from the outcome but DERIVES `klass` from
|
|
696
|
+
* the chosen method's type + `_meta` shape (§2.1) — never from the resolution outcome. */
|
|
697
|
+
async applyResolution(backend, resolution, advertised, methodIdHint, label) {
|
|
698
|
+
const poolKey = backend.poolKey ?? backend.id;
|
|
699
|
+
const machine = this.authStore.machineFor(poolKey, backend.authProfile);
|
|
700
|
+
if (resolution.outcome === "cancelled") {
|
|
701
|
+
return { status: "cancelled", methodId: methodIdHint ?? "", recycled: false };
|
|
702
|
+
}
|
|
703
|
+
const methodId = resolutionMethodId(resolution) ?? methodIdHint ?? inferMethodId(resolution, advertised);
|
|
704
|
+
if (!methodId) {
|
|
705
|
+
throw new WorkflowError("completeAuth requires a methodId to record the resolution", WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: label });
|
|
706
|
+
}
|
|
707
|
+
const chosen = advertised.find((m) => m.id === methodId);
|
|
708
|
+
const methodType = chosen ? authMethodType(chosen) : resolution.outcome === "env" ? "env_var" : "agent";
|
|
709
|
+
const advertisedMeta = chosen ? authMethodMeta(chosen) : undefined;
|
|
710
|
+
if (resolution.outcome === "agent-login") {
|
|
711
|
+
// The sole path from which the bare-`agent` login RPC ever fires (§2.9 step 3): the agent runs
|
|
712
|
+
// its own login on a live connection and persists to its native store, so every subsequent
|
|
713
|
+
// fresh initialize re-reads it and needs no replay.
|
|
714
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
715
|
+
try {
|
|
716
|
+
await connection.authenticate({ methodId }, label);
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
throw mapThrownError(error, { label, backendId: backend.id, authMethods: [...advertised] });
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
await disposeBestEffort(connection);
|
|
723
|
+
}
|
|
724
|
+
const intent = { backendId: backend.id, poolKey, methodId, methodType, klass: "disk", diskBacked: true };
|
|
725
|
+
machine.send({ t: "host_authenticate", intent });
|
|
726
|
+
machine.send({ t: "apply_ok", connectionId: "host", generation: machine.generation });
|
|
727
|
+
this.pool.recycle(poolKey);
|
|
728
|
+
return { status: "authenticated", methodId, recycled: true };
|
|
729
|
+
}
|
|
730
|
+
// meta / env / completed: derive klass from the chosen advertised method (§2.1), never the outcome.
|
|
731
|
+
const { klass, diskBacked } = classifyCredential(methodType, advertisedMeta);
|
|
732
|
+
// A `meta` payload passes through the backend's pure-data `AuthProfile.buildMeta` (§3.1) so the
|
|
733
|
+
// agent's expected authenticate `_meta` shape is honored; a custom backend (no profile), or one
|
|
734
|
+
// whose advertised method could not be matched, records the host-supplied `meta` verbatim
|
|
735
|
+
// (conformance-by-absence). buildMeta is SECRET-preserving — it only reshapes the payload, never
|
|
736
|
+
// logs it (§2.14/Principle 9).
|
|
737
|
+
let authenticateMeta;
|
|
738
|
+
if (resolution.outcome === "meta") {
|
|
739
|
+
const buildMeta = backend.authProfile?.buildMeta;
|
|
740
|
+
authenticateMeta = buildMeta && chosen ? buildMeta(chosen, resolution) : resolution.meta;
|
|
741
|
+
}
|
|
742
|
+
const envValues = resolution.outcome === "env" ? resolution.values : undefined;
|
|
743
|
+
const intent = {
|
|
744
|
+
backendId: backend.id,
|
|
745
|
+
poolKey,
|
|
746
|
+
methodId,
|
|
747
|
+
methodType,
|
|
748
|
+
klass,
|
|
749
|
+
diskBacked,
|
|
750
|
+
...(authenticateMeta ? { authenticateMeta } : {}),
|
|
751
|
+
...(envValues ? { envValues } : {}),
|
|
752
|
+
};
|
|
753
|
+
machine.send({ t: "host_authenticate", intent });
|
|
754
|
+
this.pool.recycle(poolKey);
|
|
755
|
+
return { status: "authenticated", methodId, recycled: true };
|
|
756
|
+
}
|
|
757
|
+
/** Inline resolve-and-retry-once at the run() session-acquisition seam (§2.11). Builds the
|
|
758
|
+
* AuthContext from the backend's advertised methods, invokes `onAuth`, and applies the result.
|
|
759
|
+
* Returns false on a cancelled/absent resolution (the caller propagates AUTH_REQUIRED). */
|
|
760
|
+
async resolveInlineAuth(backend, opts, error) {
|
|
761
|
+
const { methods, descriptors } = await this.probeAuthMethods(backend);
|
|
762
|
+
// Mark the machine's authenticated->auth_required transition on the protocol signal (§2.3).
|
|
763
|
+
this.authStore
|
|
764
|
+
.machineFor(backend.poolKey ?? backend.id, backend.authProfile)
|
|
765
|
+
.send({ t: "auth_required_tripped", connectionId: "run", error });
|
|
766
|
+
const ctx = {
|
|
767
|
+
backendId: backend.id,
|
|
768
|
+
...(opts.label ? { label: opts.label } : {}),
|
|
769
|
+
methods: descriptors,
|
|
770
|
+
cause: "required",
|
|
771
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
772
|
+
};
|
|
773
|
+
const resolution = await this.onAuth(ctx);
|
|
774
|
+
if (resolution.outcome === "cancelled")
|
|
775
|
+
return false;
|
|
776
|
+
await this.applyResolution(backend, resolution, methods, undefined, opts.label);
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
/** Redacted status snapshots (§2.10/§4.1). Enumerates every configured backend when `backend` is
|
|
780
|
+
* omitted; never exposes secrets. */
|
|
781
|
+
authStatus(opts) {
|
|
782
|
+
const ids = opts?.backend ? [opts.backend] : this.listBackends();
|
|
783
|
+
return ids.map((id) => this.snapshotFor(id));
|
|
784
|
+
}
|
|
785
|
+
snapshotFor(backendId) {
|
|
786
|
+
const backend = selectBackend({ model: backendId }, this.backends);
|
|
787
|
+
const poolKey = backend.poolKey ?? backend.id;
|
|
788
|
+
const machine = this.authStore.existing(poolKey);
|
|
789
|
+
const state = machine?.state ?? "unauthenticated";
|
|
790
|
+
return {
|
|
791
|
+
backendId: backend.id,
|
|
792
|
+
poolKey,
|
|
793
|
+
state,
|
|
794
|
+
authenticated: state === "authenticated",
|
|
795
|
+
canResume: machine?.canResume() ?? false,
|
|
796
|
+
methods: (machine?.advertised ?? []).map((m) => ({
|
|
797
|
+
id: m.id,
|
|
798
|
+
type: authMethodType(m),
|
|
799
|
+
...(m.name ? { name: m.name } : {}),
|
|
800
|
+
})),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/** Cold-resume re-arm predicate (§2.13). */
|
|
804
|
+
canResume(backendId) {
|
|
805
|
+
const backend = selectBackend({ model: backendId }, this.backends);
|
|
806
|
+
return this.authStore.existing(backend.poolKey ?? backend.id)?.canResume() ?? false;
|
|
807
|
+
}
|
|
569
808
|
/** Build the backend choice, model-selection spec, tool policy, and session/new options in one
|
|
570
809
|
* place for run() and openSession() so new AcpSessionOptions fields cannot drift by path. */
|
|
571
810
|
prepareSession(opts, config) {
|
|
@@ -773,6 +1012,54 @@ function validateRequiredString(value, label, methodName, fieldName) {
|
|
|
773
1012
|
throw new WorkflowError(`${methodName} requires ${fieldName} to be a non-empty string`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: label });
|
|
774
1013
|
}
|
|
775
1014
|
}
|
|
1015
|
+
/** The mapped run error is a WorkflowError; the auth-required classification already ran in the
|
|
1016
|
+
* pool via mapThrownError (§1.5), so the inline seam keys on the code, not the raw -32000. */
|
|
1017
|
+
function isAuthRequiredError(error) {
|
|
1018
|
+
return isWorkflowError(error) && error.code === WorkflowErrorCode.AUTH_REQUIRED;
|
|
1019
|
+
}
|
|
1020
|
+
/** The SDK types a missing `type` discriminant as `agent`. */
|
|
1021
|
+
/** Dispatch each advertised method through the base §1.3 dispatcher, then hand the result to the
|
|
1022
|
+
* backend's pure-data `AuthProfile.describe` (§3.1) for label enrichment. A custom backend has NO
|
|
1023
|
+
* profile, so the base descriptor is returned verbatim (conformance-by-absence, §3.5). The profile
|
|
1024
|
+
* may only relabel; it never changes the `type` discriminant the base dispatcher chose (§3.1). */
|
|
1025
|
+
function describeMethods(methods, backend) {
|
|
1026
|
+
const spawn = backend.spawnConfig();
|
|
1027
|
+
const profile = backend.authProfile;
|
|
1028
|
+
return methods.map((method) => {
|
|
1029
|
+
const base = buildAuthDescriptor(method, spawn);
|
|
1030
|
+
return profile ? profile.describe(method, base) : base;
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
function authMethodType(method) {
|
|
1034
|
+
return (("type" in method ? method.type : undefined) ?? "agent");
|
|
1035
|
+
}
|
|
1036
|
+
/** The advertised method's `_meta`, or undefined. This is agent-PUBLISHED metadata (e.g.
|
|
1037
|
+
* `gateway.protocol`), never a credential — the credential is only the resolver's payload. */
|
|
1038
|
+
function authMethodMeta(method) {
|
|
1039
|
+
const meta = method._meta;
|
|
1040
|
+
return meta ?? undefined;
|
|
1041
|
+
}
|
|
1042
|
+
/** A resolution that names its own method (meta / agent-login), else undefined. */
|
|
1043
|
+
function resolutionMethodId(resolution) {
|
|
1044
|
+
if (resolution.outcome === "meta" || resolution.outcome === "agent-login")
|
|
1045
|
+
return resolution.methodId;
|
|
1046
|
+
if (resolution.outcome === "env" || resolution.outcome === "completed")
|
|
1047
|
+
return resolution.methodId;
|
|
1048
|
+
return undefined;
|
|
1049
|
+
}
|
|
1050
|
+
/** Infer the target method for an env/completed resolution that did not name one: match by outcome
|
|
1051
|
+
* against the advertised methods (there is typically exactly one env_var / terminal method). */
|
|
1052
|
+
function inferMethodId(resolution, advertised) {
|
|
1053
|
+
if (resolution.outcome === "env") {
|
|
1054
|
+
return advertised.find((m) => authMethodType(m) === "env_var")?.id ?? advertised.find((m) => authMethodType(m) === "agent")?.id;
|
|
1055
|
+
}
|
|
1056
|
+
if (resolution.outcome === "completed") {
|
|
1057
|
+
return (advertised.find((m) => authMethodType(m) === "terminal")?.id ??
|
|
1058
|
+
advertised.find((m) => authMethodType(m) === "agent")?.id ??
|
|
1059
|
+
advertised[0]?.id);
|
|
1060
|
+
}
|
|
1061
|
+
return undefined;
|
|
1062
|
+
}
|
|
776
1063
|
async function disposeBestEffort(connection) {
|
|
777
1064
|
try {
|
|
778
1065
|
await connection.dispose();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@agentclientprotocol/claude-agent-acp": "0.57.0",
|
|
31
31
|
"@agentclientprotocol/sdk": "^1.2.1",
|
|
32
|
-
"@automatalabs/codex-acp": "1.5.
|
|
32
|
+
"@automatalabs/codex-acp": "1.5.1",
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.29",
|
|
34
34
|
"typebox": "1.3.2",
|
|
35
|
-
"@automatalabs/shared-types": "0.
|
|
35
|
+
"@automatalabs/shared-types": "0.14.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -b",
|