@salesforce/sfdx-agent-sdk 0.15.0 → 0.17.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/CHANGELOG.md +22 -0
- package/README.md +312 -99
- package/dist/agent-manager.d.ts +19 -6
- package/dist/agent-manager.js +23 -12
- package/dist/agent.d.ts +25 -8
- package/dist/agent.js +29 -20
- package/dist/chat-session.d.ts +43 -26
- package/dist/chat-session.js +34 -23
- package/dist/harness/agent-harness.d.ts +114 -17
- package/dist/harness/always-active.d.ts +60 -0
- package/dist/harness/always-active.js +58 -0
- package/dist/harness/gen-sink.d.ts +41 -0
- package/dist/harness/gen-sink.js +88 -0
- package/dist/harness/index.d.ts +1 -0
- package/dist/harness/index.js +1 -0
- package/dist/harness/public.d.ts +52 -0
- package/dist/harness/public.js +12 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -4
- package/dist/mcp-config.d.ts +30 -24
- package/dist/mcp-config.js +98 -0
- package/dist/types/redaction.d.ts +171 -0
- package/dist/types/redaction.js +6 -0
- package/package.json +18 -13
package/dist/agent-manager.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type AgentHarness, type ConfigOf } from './harness/agent-harness.js';
|
|
|
3
3
|
import type { HarnessFactory } from './harness/harness-factory.js';
|
|
4
4
|
import { type AgentConfig } from './harness/harness-config.js';
|
|
5
5
|
import { type Agent } from './agent.js';
|
|
6
|
+
import type { HooksForAgent } from './types/redaction.js';
|
|
6
7
|
import type { TelemetryEventCallback } from './types/telemetry-events.js';
|
|
7
8
|
import { type AgentConnectivityResolver } from './agent-connectivity-resolver.js';
|
|
8
9
|
/**
|
|
@@ -124,6 +125,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
124
125
|
private readonly harness;
|
|
125
126
|
private readonly agentIdGenerator;
|
|
126
127
|
private readonly agentConnectivityResolver;
|
|
128
|
+
private readonly hooksForAgent;
|
|
127
129
|
private readonly clock;
|
|
128
130
|
private readonly identityStore;
|
|
129
131
|
private readonly agents;
|
|
@@ -144,7 +146,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
144
146
|
* is private, so this is the only way to obtain an instance, but
|
|
145
147
|
* consumers should always go through {@link createAgentManager}.
|
|
146
148
|
*/
|
|
147
|
-
static __build<H extends AgentHarness>(harness: H, agentConnectivityResolver: AgentConnectivityResolver, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
|
|
149
|
+
static __build<H extends AgentHarness>(harness: H, agentConnectivityResolver: AgentConnectivityResolver, hooksForAgent: HooksForAgent | undefined, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
|
|
148
150
|
private init;
|
|
149
151
|
shutdown(): Promise<void>;
|
|
150
152
|
createAgent(projectRoot: string, config?: ConfigOf<H> & {
|
|
@@ -189,14 +191,25 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
189
191
|
* function returns; failures are queryable via
|
|
190
192
|
* {@link AgentManager.getRestoreFailures}.
|
|
191
193
|
*
|
|
192
|
-
* The optional `
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
194
|
+
* The optional third-positional `options` bag carries the SDK's per-manager
|
|
195
|
+
* opt-ins. Production callers typically leave it unset:
|
|
196
|
+
*
|
|
197
|
+
* - `connectivityResolver` — overrides the default
|
|
198
|
+
* `DefaultAgentConnectivityResolver`; used by e2e tests and custom-auth
|
|
199
|
+
* deployments where the SDK should not run sf-CLI-based org resolution.
|
|
200
|
+
* - `hooksForAgent` — sync callback resolving a per-agent
|
|
201
|
+
* {@link AgentHooks} bag (today carries `onToolResult`); invoked once per
|
|
202
|
+
* `createAgent`, boot-time restore, and `Agent.updateAgentConfig`. The
|
|
203
|
+
* resolved bag threads through to `AgentHarness.createAgent`'s
|
|
204
|
+
* `options.hooks` and reaches the harness's native seam (Claude
|
|
205
|
+
* `PostToolUse`, Mastra `processInputStep`).
|
|
196
206
|
*
|
|
197
207
|
* @throws {AgentSDKError} `INCOMPATIBLE_HARNESS` when either the factory or
|
|
198
208
|
* the constructed harness reports a `protocolVersion` outside
|
|
199
209
|
* {@link SUPPORTED_PROTOCOL_VERSIONS}, or when the harness's reported
|
|
200
210
|
* version disagrees with the factory's.
|
|
201
211
|
*/
|
|
202
|
-
export declare function createAgentManager<H extends AgentHarness = AgentHarness>(storageRootFolder: string, harnessFactory: HarnessFactory<H>,
|
|
212
|
+
export declare function createAgentManager<H extends AgentHarness = AgentHarness>(storageRootFolder: string, harnessFactory: HarnessFactory<H>, options?: {
|
|
213
|
+
connectivityResolver?: AgentConnectivityResolver;
|
|
214
|
+
hooksForAgent?: HooksForAgent;
|
|
215
|
+
}): Promise<AgentManager<H>>;
|
package/dist/agent-manager.js
CHANGED
|
@@ -25,6 +25,7 @@ export class DefaultAgentManager {
|
|
|
25
25
|
harness;
|
|
26
26
|
agentIdGenerator;
|
|
27
27
|
agentConnectivityResolver;
|
|
28
|
+
hooksForAgent;
|
|
28
29
|
clock;
|
|
29
30
|
identityStore;
|
|
30
31
|
agents = new Map();
|
|
@@ -34,9 +35,10 @@ export class DefaultAgentManager {
|
|
|
34
35
|
router;
|
|
35
36
|
unroutedUnsubs;
|
|
36
37
|
disposed = false;
|
|
37
|
-
constructor(harness, agentConnectivityResolver, identityStore, agentIdGenerator, clock, logBus) {
|
|
38
|
+
constructor(harness, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus) {
|
|
38
39
|
this.harness = harness;
|
|
39
40
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
41
|
+
this.hooksForAgent = hooksForAgent;
|
|
40
42
|
this.identityStore = identityStore;
|
|
41
43
|
this.agentIdGenerator = agentIdGenerator;
|
|
42
44
|
this.clock = clock;
|
|
@@ -57,9 +59,9 @@ export class DefaultAgentManager {
|
|
|
57
59
|
* is private, so this is the only way to obtain an instance, but
|
|
58
60
|
* consumers should always go through {@link createAgentManager}.
|
|
59
61
|
*/
|
|
60
|
-
static async __build(harness, agentConnectivityResolver, storageRootFolder, agentIdGenerator, clock, logBus) {
|
|
62
|
+
static async __build(harness, agentConnectivityResolver, hooksForAgent, storageRootFolder, agentIdGenerator, clock, logBus) {
|
|
61
63
|
const identityStore = new AgentIdentityStore(storageRootFolder, harness.harnessId, logBus);
|
|
62
|
-
const manager = new DefaultAgentManager(harness, agentConnectivityResolver, identityStore, agentIdGenerator, clock, logBus);
|
|
64
|
+
const manager = new DefaultAgentManager(harness, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus);
|
|
63
65
|
await manager.init();
|
|
64
66
|
return manager;
|
|
65
67
|
}
|
|
@@ -155,9 +157,10 @@ export class DefaultAgentManager {
|
|
|
155
157
|
throw new Error(`projectRoot is not a directory: "${projectRoot}"`);
|
|
156
158
|
}
|
|
157
159
|
const runtime = await this.agentConnectivityResolver.resolve(projectRoot, config);
|
|
158
|
-
|
|
160
|
+
const hooks = this.hooksForAgent?.(agentId, config) ?? {};
|
|
161
|
+
await this.harness.createAgent(agentId, projectRoot, runtime.llmGatewayClient, toHarnessConfig(config, runtime.orgJwt), { ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}), hooks });
|
|
159
162
|
const agentSlice = this.router.registerAgent(agentId);
|
|
160
|
-
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.llmGatewayClient, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
163
|
+
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.llmGatewayClient, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.hooksForAgent, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
161
164
|
this.agents.set(agentId, agent);
|
|
162
165
|
this.telemetryBus.emit({
|
|
163
166
|
type: 'agent-created',
|
|
@@ -262,17 +265,25 @@ export class DefaultAgentManager {
|
|
|
262
265
|
* function returns; failures are queryable via
|
|
263
266
|
* {@link AgentManager.getRestoreFailures}.
|
|
264
267
|
*
|
|
265
|
-
* The optional `
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
268
|
+
* The optional third-positional `options` bag carries the SDK's per-manager
|
|
269
|
+
* opt-ins. Production callers typically leave it unset:
|
|
270
|
+
*
|
|
271
|
+
* - `connectivityResolver` — overrides the default
|
|
272
|
+
* `DefaultAgentConnectivityResolver`; used by e2e tests and custom-auth
|
|
273
|
+
* deployments where the SDK should not run sf-CLI-based org resolution.
|
|
274
|
+
* - `hooksForAgent` — sync callback resolving a per-agent
|
|
275
|
+
* {@link AgentHooks} bag (today carries `onToolResult`); invoked once per
|
|
276
|
+
* `createAgent`, boot-time restore, and `Agent.updateAgentConfig`. The
|
|
277
|
+
* resolved bag threads through to `AgentHarness.createAgent`'s
|
|
278
|
+
* `options.hooks` and reaches the harness's native seam (Claude
|
|
279
|
+
* `PostToolUse`, Mastra `processInputStep`).
|
|
269
280
|
*
|
|
270
281
|
* @throws {AgentSDKError} `INCOMPATIBLE_HARNESS` when either the factory or
|
|
271
282
|
* the constructed harness reports a `protocolVersion` outside
|
|
272
283
|
* {@link SUPPORTED_PROTOCOL_VERSIONS}, or when the harness's reported
|
|
273
284
|
* version disagrees with the factory's.
|
|
274
285
|
*/
|
|
275
|
-
export async function createAgentManager(storageRootFolder, harnessFactory,
|
|
286
|
+
export async function createAgentManager(storageRootFolder, harnessFactory, options) {
|
|
276
287
|
let stats;
|
|
277
288
|
try {
|
|
278
289
|
stats = await stat(storageRootFolder);
|
|
@@ -306,8 +317,8 @@ export async function createAgentManager(storageRootFolder, harnessFactory, conn
|
|
|
306
317
|
`advertised version ${factoryVersion} (SDK supports: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')}). ` +
|
|
307
318
|
`Update the SDK or harness package.`, AgentSDKErrorType.INCOMPATIBLE_HARNESS);
|
|
308
319
|
}
|
|
309
|
-
const agentConnectivityResolver = connectivityResolver ?? new DefaultAgentConnectivityResolver();
|
|
310
|
-
return DefaultAgentManager.__build(harness, agentConnectivityResolver, storageRootFolder, new UUIDGenerator(), new RealClock(), new LogBus());
|
|
320
|
+
const agentConnectivityResolver = options?.connectivityResolver ?? new DefaultAgentConnectivityResolver();
|
|
321
|
+
return DefaultAgentManager.__build(harness, agentConnectivityResolver, options?.hooksForAgent, storageRootFolder, new UUIDGenerator(), new RealClock(), new LogBus());
|
|
311
322
|
}
|
|
312
323
|
function isSupportedProtocolVersion(version) {
|
|
313
324
|
return (typeof version === 'number' &&
|
package/dist/agent.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type JSONWebToken, type LLMGatewayClient } from '@salesforce/llm-gatewa
|
|
|
7
7
|
import { type AgentConnectivityResolver } from './agent-connectivity-resolver.js';
|
|
8
8
|
import type { AgentIdentityStore } from './internal/agent-identity-store.js';
|
|
9
9
|
import type { TelemetryRouter, TelemetrySlice } from './internal/telemetry-router.js';
|
|
10
|
+
import type { HooksForAgent } from './types/redaction.js';
|
|
10
11
|
import type { TelemetryBus, TelemetryEventCallback } from './types/telemetry-events.js';
|
|
11
12
|
/**
|
|
12
13
|
* Parent bus pair wired at construction time so an agent's events bubble upward into the manager's buses.
|
|
@@ -44,8 +45,13 @@ export interface Agent {
|
|
|
44
45
|
/**
|
|
45
46
|
* Request a reconnect of one MCP server on this agent without recycling
|
|
46
47
|
* any other server, custom tool, instruction, or skill. Useful for
|
|
47
|
-
* recovering a single failed MCP server
|
|
48
|
-
*
|
|
48
|
+
* recovering a single failed MCP server after a transport-level error
|
|
49
|
+
* (e.g. JWT-rotation timing on stdio servers, transient EOF on remote
|
|
50
|
+
* transports). For the diff-driven case — `Agent.updateAgentConfig`
|
|
51
|
+
* applying a new `MCPConfiguration` — the harness already preserves any
|
|
52
|
+
* server whose config is structurally unchanged and cycles only the
|
|
53
|
+
* changed/added/removed servers; an explicit `reconnectMcpServer` call
|
|
54
|
+
* is **not** required there.
|
|
49
55
|
*
|
|
50
56
|
* Throws if `serverName` is not configured on this agent or if the named
|
|
51
57
|
* server is disabled (`enabled: false`).
|
|
@@ -132,6 +138,7 @@ export declare class DefaultAgent implements Agent {
|
|
|
132
138
|
private orgConnection;
|
|
133
139
|
private orgJwt;
|
|
134
140
|
private readonly agentConnectivityResolver;
|
|
141
|
+
private readonly hooksForAgent;
|
|
135
142
|
private readonly identityStore;
|
|
136
143
|
private readonly sessions;
|
|
137
144
|
private readonly sessionSliceUnregisters;
|
|
@@ -152,13 +159,17 @@ export declare class DefaultAgent implements Agent {
|
|
|
152
159
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
153
160
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
154
161
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
162
|
+
* @param hooksForAgent - Per-agent hooks resolver supplied by the SDK consumer at `createAgentManager` time. The
|
|
163
|
+
* agent re-invokes it on every `updateAgentConfig` (with `nextConfig`, and again with `previousConfig` on the
|
|
164
|
+
* rollback path) so the bag the harness sees always reflects the current persisted config. `undefined` when
|
|
165
|
+
* the consumer didn't pass a `hooksForAgent`.
|
|
155
166
|
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
156
167
|
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
157
168
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
158
169
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
159
170
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
160
171
|
*/
|
|
161
|
-
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, llmGatewayClient: LLMGatewayClient, orgConnection: OrgConnection, orgJwt: JSONWebToken, agentConnectivityResolver: AgentConnectivityResolver, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
172
|
+
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, llmGatewayClient: LLMGatewayClient, orgConnection: OrgConnection, orgJwt: JSONWebToken, agentConnectivityResolver: AgentConnectivityResolver, hooksForAgent: HooksForAgent | undefined, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
162
173
|
/**
|
|
163
174
|
* @requirements
|
|
164
175
|
* - MUST return the agent's ID.
|
|
@@ -178,11 +189,17 @@ export declare class DefaultAgent implements Agent {
|
|
|
178
189
|
* @requirements
|
|
179
190
|
* - MUST merge the provided `config` with the internal `config` object.
|
|
180
191
|
* - MUST guarantee that the `agentId` remains unchanged during the merge.
|
|
181
|
-
* - MUST
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
192
|
+
* - MUST apply the merged config to the harness via `this.harness.updateAgent(...)` — a single primitive
|
|
193
|
+
* that preserves any MCP client whose `MCPServerConfig` is structurally equal to the currently-applied one.
|
|
194
|
+
* The destroy+recreate shape this method used pre-#541 closed every MCP client on a model-only or
|
|
195
|
+
* instructions-only or org-connect-only change; the new shape preserves them and only cycles servers
|
|
196
|
+
* that actually changed.
|
|
197
|
+
* - MUST persist the merged config via `this.identityStore.write(...)` after `harness.updateAgent` succeeds
|
|
198
|
+
* and before the in-memory swaps, so a write failure rolls back through the same catch path as an
|
|
199
|
+
* `updateAgent` failure.
|
|
200
|
+
* - MUST preserve the previous in-memory config state if `updateAgent` or persistence fails. Rollback
|
|
201
|
+
* uses the same `harness.updateAgent` primitive against the previous config — the harness re-diffs
|
|
202
|
+
* against its current (possibly partially-updated) state and reverts only the actual deltas.
|
|
186
203
|
*/
|
|
187
204
|
updateAgentConfig(config?: AgentConfig, options?: {
|
|
188
205
|
abortSignal?: AbortSignal;
|
package/dist/agent.js
CHANGED
|
@@ -21,6 +21,7 @@ export class DefaultAgent {
|
|
|
21
21
|
orgConnection;
|
|
22
22
|
orgJwt;
|
|
23
23
|
agentConnectivityResolver;
|
|
24
|
+
hooksForAgent;
|
|
24
25
|
identityStore;
|
|
25
26
|
sessions = new Map();
|
|
26
27
|
sessionSliceUnregisters = new Map();
|
|
@@ -41,13 +42,17 @@ export class DefaultAgent {
|
|
|
41
42
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
42
43
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
43
44
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
45
|
+
* @param hooksForAgent - Per-agent hooks resolver supplied by the SDK consumer at `createAgentManager` time. The
|
|
46
|
+
* agent re-invokes it on every `updateAgentConfig` (with `nextConfig`, and again with `previousConfig` on the
|
|
47
|
+
* rollback path) so the bag the harness sees always reflects the current persisted config. `undefined` when
|
|
48
|
+
* the consumer didn't pass a `hooksForAgent`.
|
|
44
49
|
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
45
50
|
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
46
51
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
47
52
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
48
53
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
49
54
|
*/
|
|
50
|
-
constructor(harness, agentId, projectRoot, config, llmGatewayClient, orgConnection, orgJwt, agentConnectivityResolver, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
|
|
55
|
+
constructor(harness, agentId, projectRoot, config, llmGatewayClient, orgConnection, orgJwt, agentConnectivityResolver, hooksForAgent, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
|
|
51
56
|
this.harness = harness;
|
|
52
57
|
this.agentId = agentId;
|
|
53
58
|
this.projectRoot = projectRoot;
|
|
@@ -56,6 +61,7 @@ export class DefaultAgent {
|
|
|
56
61
|
this.orgConnection = orgConnection;
|
|
57
62
|
this.orgJwt = orgJwt;
|
|
58
63
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
64
|
+
this.hooksForAgent = hooksForAgent;
|
|
59
65
|
this.identityStore = identityStore;
|
|
60
66
|
this.router = router;
|
|
61
67
|
this.clock = clock;
|
|
@@ -100,11 +106,17 @@ export class DefaultAgent {
|
|
|
100
106
|
* @requirements
|
|
101
107
|
* - MUST merge the provided `config` with the internal `config` object.
|
|
102
108
|
* - MUST guarantee that the `agentId` remains unchanged during the merge.
|
|
103
|
-
* - MUST
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* - MUST apply the merged config to the harness via `this.harness.updateAgent(...)` — a single primitive
|
|
110
|
+
* that preserves any MCP client whose `MCPServerConfig` is structurally equal to the currently-applied one.
|
|
111
|
+
* The destroy+recreate shape this method used pre-#541 closed every MCP client on a model-only or
|
|
112
|
+
* instructions-only or org-connect-only change; the new shape preserves them and only cycles servers
|
|
113
|
+
* that actually changed.
|
|
114
|
+
* - MUST persist the merged config via `this.identityStore.write(...)` after `harness.updateAgent` succeeds
|
|
115
|
+
* and before the in-memory swaps, so a write failure rolls back through the same catch path as an
|
|
116
|
+
* `updateAgent` failure.
|
|
117
|
+
* - MUST preserve the previous in-memory config state if `updateAgent` or persistence fails. Rollback
|
|
118
|
+
* uses the same `harness.updateAgent` primitive against the previous config — the harness re-diffs
|
|
119
|
+
* against its current (possibly partially-updated) state and reverts only the actual deltas.
|
|
108
120
|
*/
|
|
109
121
|
async updateAgentConfig(config = {}, options) {
|
|
110
122
|
this.assertNotDisposed();
|
|
@@ -129,13 +141,14 @@ export class DefaultAgent {
|
|
|
129
141
|
// (If modelId is omitted, the resolver pinned the default at creation time.)
|
|
130
142
|
nextClient.setModel(nextModel);
|
|
131
143
|
}
|
|
132
|
-
await this.harness.destroyAgent(this.agentId);
|
|
133
|
-
let nextConfigRegistered = false;
|
|
134
144
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
const nextHooks = this.hooksForAgent?.(this.agentId, nextConfig) ?? {};
|
|
146
|
+
await this.harness.updateAgent(this.agentId, nextClient, toHarnessConfig(nextConfig, nextOrgJwt), {
|
|
147
|
+
...(options?.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}),
|
|
148
|
+
hooks: nextHooks,
|
|
149
|
+
});
|
|
137
150
|
// Persist before the in-memory swaps so a write failure flows through the same
|
|
138
|
-
// catch block as
|
|
151
|
+
// catch block as an updateAgent failure: the rollback re-runs updateAgent against
|
|
139
152
|
// previousConfig and disk state remains the pre-update record.
|
|
140
153
|
await this.identityStore.write(this.agentId, this.projectRoot, nextConfig);
|
|
141
154
|
this.config = nextConfig;
|
|
@@ -158,15 +171,11 @@ export class DefaultAgent {
|
|
|
158
171
|
if (nextClient === previousClient) {
|
|
159
172
|
previousClient.setModel(previousModel);
|
|
160
173
|
}
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (nextConfigRegistered) {
|
|
167
|
-
await this.harness.destroyAgent(this.agentId);
|
|
168
|
-
}
|
|
169
|
-
await this.harness.createAgent(this.agentId, this.projectRoot, previousClient, toHarnessConfig(previousConfig, previousOrgJwt));
|
|
174
|
+
// Re-apply the previous config through the same primitive. The harness re-diffs
|
|
175
|
+
// against its current state — if updateAgent partially applied (e.g. some MCP
|
|
176
|
+
// servers were already cycled), reverting via updateAgent restores them too.
|
|
177
|
+
const previousHooks = this.hooksForAgent?.(this.agentId, previousConfig) ?? {};
|
|
178
|
+
await this.harness.updateAgent(this.agentId, previousClient, toHarnessConfig(previousConfig, previousOrgJwt), { hooks: previousHooks });
|
|
170
179
|
}
|
|
171
180
|
catch {
|
|
172
181
|
// Ignore restoration errors; rethrow the original failure.
|
package/dist/chat-session.d.ts
CHANGED
|
@@ -62,11 +62,17 @@ export interface ChatSession {
|
|
|
62
62
|
*
|
|
63
63
|
* "Client-side tool" means a tool you declared in {@link AgentConfig.tools}
|
|
64
64
|
* without an `execute` function — the SDK registers its name + schema with the
|
|
65
|
-
* model but does not run it. When the model calls one, the chat eventStream
|
|
66
|
-
* emits a `tool-call` event
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
65
|
+
* model but does not run it. When the model calls one, the chat `eventStream`
|
|
66
|
+
* emits a `tool-call` event. Your application runs the tool however it likes
|
|
67
|
+
* (HTTP call, DB query, UI prompt, etc.) and calls this method with the result;
|
|
68
|
+
* the agent loop resumes and the post-resume events (`tool-result`, model
|
|
69
|
+
* follow-up text, terminal `finish`) arrive on the **same** `eventStream`
|
|
70
|
+
* the original {@link chat} call returned. The consumer keeps iterating it.
|
|
71
|
+
*
|
|
72
|
+
* Returns `Promise<void>` once the harness has accepted the result. The
|
|
73
|
+
* promise rejects on pre-stream failure (the `chat()`-returned subscribers
|
|
74
|
+
* still observe `ErrorEvent` + `FinishEvent` before the rejection so the
|
|
75
|
+
* subscribe-side contract holds).
|
|
70
76
|
*
|
|
71
77
|
* Use this method ONLY for client-side tools. Tools provided via
|
|
72
78
|
* {@link AgentConfig.mcpServers} are executed by the harness — their results
|
|
@@ -74,23 +80,22 @@ export interface ChatSession {
|
|
|
74
80
|
* Human-in-the-loop approval of harness-executed tools uses
|
|
75
81
|
* {@link approveToolCall} / {@link declineToolCall}, not this method.
|
|
76
82
|
*
|
|
77
|
-
* On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
|
|
78
|
-
* the returned promise rejects. See the interface-level "Failure handling" notes for details.
|
|
79
|
-
*
|
|
80
83
|
* @param toolResult - The completed tool execution result. `toolCallId` and
|
|
81
84
|
* `toolName` MUST match the values from the originating `tool-call` event.
|
|
82
85
|
*/
|
|
83
|
-
submitToolResult(toolResult: ToolResultInfo): Promise<
|
|
86
|
+
submitToolResult(toolResult: ToolResultInfo): Promise<void>;
|
|
84
87
|
/**
|
|
85
88
|
* Approve a pending tool call, allowing the harness to execute it.
|
|
86
89
|
* Called after receiving a `tool-approval-request` event from the stream.
|
|
87
90
|
*
|
|
88
|
-
* Returns
|
|
89
|
-
* executes the
|
|
90
|
-
*
|
|
91
|
+
* Returns `Promise<void>` once the harness has accepted the approval. The
|
|
92
|
+
* harness then executes the tool and emits the resulting events
|
|
93
|
+
* (`tool-result`, model follow-up text, terminal `finish`) on the **same**
|
|
94
|
+
* `eventStream` the original {@link chat} call returned. The consumer keeps
|
|
95
|
+
* iterating it.
|
|
91
96
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
97
|
+
* The promise rejects on pre-stream failure; subscribers still observe
|
|
98
|
+
* `ErrorEvent` + `FinishEvent` on the chat stream before the rejection.
|
|
94
99
|
*
|
|
95
100
|
* @param toolCallId - ID of the pending tool call to approve.
|
|
96
101
|
* @param options - Optional approval metadata.
|
|
@@ -101,21 +106,20 @@ export interface ChatSession {
|
|
|
101
106
|
*/
|
|
102
107
|
approveToolCall(toolCallId: string, options?: {
|
|
103
108
|
remember?: boolean;
|
|
104
|
-
}): Promise<
|
|
109
|
+
}): Promise<void>;
|
|
105
110
|
/**
|
|
106
111
|
* Decline a pending tool call. The stream resumes with the model
|
|
107
|
-
* acknowledging the decline and potentially suggesting alternatives
|
|
112
|
+
* acknowledging the decline and potentially suggesting alternatives —
|
|
113
|
+
* those events arrive on the **same** `eventStream` the original
|
|
114
|
+
* {@link chat} call returned.
|
|
108
115
|
*
|
|
109
|
-
* Returns
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
|
|
114
|
-
* the returned promise rejects. See the interface-level "Failure handling" notes for details.
|
|
116
|
+
* Returns `Promise<void>` once the harness has accepted the decline. The
|
|
117
|
+
* promise rejects on pre-stream failure; subscribers still observe
|
|
118
|
+
* `ErrorEvent` + `FinishEvent` on the chat stream before the rejection.
|
|
115
119
|
*
|
|
116
120
|
* @param toolCallId - ID of the pending tool call to decline.
|
|
117
121
|
*/
|
|
118
|
-
declineToolCall(toolCallId: string): Promise<
|
|
122
|
+
declineToolCall(toolCallId: string): Promise<void>;
|
|
119
123
|
/**
|
|
120
124
|
* Retrieve message history for this session.
|
|
121
125
|
*
|
|
@@ -246,7 +250,7 @@ export declare class DefaultChatSession implements ChatSession {
|
|
|
246
250
|
* - MUST notify listeners with `ErrorEvent` + `FinishEvent` and re-throw if the harness throws
|
|
247
251
|
* before returning a stream result.
|
|
248
252
|
*/
|
|
249
|
-
submitToolResult(toolResult: ToolResultInfo): Promise<
|
|
253
|
+
submitToolResult(toolResult: ToolResultInfo): Promise<void>;
|
|
250
254
|
/**
|
|
251
255
|
* @requirements
|
|
252
256
|
* - MUST yield each event from the provided `stream`.
|
|
@@ -290,7 +294,7 @@ export declare class DefaultChatSession implements ChatSession {
|
|
|
290
294
|
*/
|
|
291
295
|
approveToolCall(toolCallId: string, _options?: {
|
|
292
296
|
remember?: boolean;
|
|
293
|
-
}): Promise<
|
|
297
|
+
}): Promise<void>;
|
|
294
298
|
/**
|
|
295
299
|
* @requirements
|
|
296
300
|
* - MUST delegate to `this.harness.declineToolCall()`, passing `this.agentId`, `this.threadId`, and `toolCallId`.
|
|
@@ -302,7 +306,7 @@ export declare class DefaultChatSession implements ChatSession {
|
|
|
302
306
|
* - MUST notify listeners with `ErrorEvent` + `FinishEvent` and re-throw if the harness throws
|
|
303
307
|
* before returning a stream result.
|
|
304
308
|
*/
|
|
305
|
-
declineToolCall(toolCallId: string): Promise<
|
|
309
|
+
declineToolCall(toolCallId: string): Promise<void>;
|
|
306
310
|
/**
|
|
307
311
|
* @requirements
|
|
308
312
|
* - MUST delegate to `this.harness.getMessages()`, passing `this.agentId` and `this.threadId`.
|
|
@@ -395,5 +399,18 @@ export declare class DefaultChatSession implements ChatSession {
|
|
|
395
399
|
* measures real elapsed time even for pre-stream rejections.
|
|
396
400
|
*/
|
|
397
401
|
private notifyPreStreamError;
|
|
402
|
+
/**
|
|
403
|
+
* issue #529 contract change: a settle call (`approveToolCall` /
|
|
404
|
+
* `declineToolCall` / `submitToolResult`) rejected. The settle's
|
|
405
|
+
* Promise is the consumer's primary failure surface, but subscribers
|
|
406
|
+
* registered via {@link ChatSession.subscribe} also expect to observe
|
|
407
|
+
* `error + finish` events so a UI bound to the chat stream can
|
|
408
|
+
* render the failure. Emit those without firing
|
|
409
|
+
* `chat-stream-error` telemetry — chat-stream-* telemetry is owned
|
|
410
|
+
* by the chat() lifecycle, not by settle calls (issue #529: one
|
|
411
|
+
* chat-stream-started/completed/error pair per turn, not per
|
|
412
|
+
* settle).
|
|
413
|
+
*/
|
|
414
|
+
private notifySettleRejection;
|
|
398
415
|
private assertNotDisposed;
|
|
399
416
|
}
|
package/dist/chat-session.js
CHANGED
|
@@ -109,16 +109,16 @@ export class DefaultChatSession {
|
|
|
109
109
|
*/
|
|
110
110
|
async submitToolResult(toolResult) {
|
|
111
111
|
this.assertNotDisposed();
|
|
112
|
-
|
|
112
|
+
// issue #529 contract change: settle calls are control messages on the
|
|
113
|
+
// existing chat() turn's stream — they don't open a new stream and
|
|
114
|
+
// they don't emit chat-stream-started/completed. The post-resume
|
|
115
|
+
// events flow through the harness's existing turn sink, which the
|
|
116
|
+
// consumer's chat()-returned eventStream is already iterating.
|
|
113
117
|
try {
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
textStream: result.textStream,
|
|
117
|
-
eventStream: this.wrapEventStream(result.eventStream, startedAt),
|
|
118
|
-
};
|
|
118
|
+
await this.harness.submitToolResult(this.agentId, this.threadId, toolResult);
|
|
119
119
|
}
|
|
120
120
|
catch (err) {
|
|
121
|
-
this.
|
|
121
|
+
this.notifySettleRejection(err);
|
|
122
122
|
throw err;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -246,19 +246,17 @@ export class DefaultChatSession {
|
|
|
246
246
|
*/
|
|
247
247
|
async approveToolCall(toolCallId, _options) {
|
|
248
248
|
this.assertNotDisposed();
|
|
249
|
-
|
|
249
|
+
// issue #529 contract change: see `submitToolResult` for the rationale.
|
|
250
|
+
// Settle is a control message on the existing turn; events flow on
|
|
251
|
+
// the chat()-returned stream.
|
|
250
252
|
try {
|
|
251
|
-
|
|
252
|
-
this.emitToolApprovalResolved(toolCallId, true);
|
|
253
|
-
return {
|
|
254
|
-
textStream: result.textStream,
|
|
255
|
-
eventStream: this.wrapEventStream(result.eventStream, startedAt),
|
|
256
|
-
};
|
|
253
|
+
await this.harness.approveToolCall(this.agentId, this.threadId, toolCallId);
|
|
257
254
|
}
|
|
258
255
|
catch (err) {
|
|
259
|
-
this.
|
|
256
|
+
this.notifySettleRejection(err);
|
|
260
257
|
throw err;
|
|
261
258
|
}
|
|
259
|
+
this.emitToolApprovalResolved(toolCallId, true);
|
|
262
260
|
}
|
|
263
261
|
/**
|
|
264
262
|
* @requirements
|
|
@@ -273,19 +271,15 @@ export class DefaultChatSession {
|
|
|
273
271
|
*/
|
|
274
272
|
async declineToolCall(toolCallId) {
|
|
275
273
|
this.assertNotDisposed();
|
|
276
|
-
|
|
274
|
+
// issue #529 contract change: see `submitToolResult` for the rationale.
|
|
277
275
|
try {
|
|
278
|
-
|
|
279
|
-
this.emitToolApprovalResolved(toolCallId, false);
|
|
280
|
-
return {
|
|
281
|
-
textStream: result.textStream,
|
|
282
|
-
eventStream: this.wrapEventStream(result.eventStream, startedAt),
|
|
283
|
-
};
|
|
276
|
+
await this.harness.declineToolCall(this.agentId, this.threadId, toolCallId);
|
|
284
277
|
}
|
|
285
278
|
catch (err) {
|
|
286
|
-
this.
|
|
279
|
+
this.notifySettleRejection(err);
|
|
287
280
|
throw err;
|
|
288
281
|
}
|
|
282
|
+
this.emitToolApprovalResolved(toolCallId, false);
|
|
289
283
|
}
|
|
290
284
|
/**
|
|
291
285
|
* @requirements
|
|
@@ -527,6 +521,23 @@ export class DefaultChatSession {
|
|
|
527
521
|
error,
|
|
528
522
|
});
|
|
529
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* issue #529 contract change: a settle call (`approveToolCall` /
|
|
526
|
+
* `declineToolCall` / `submitToolResult`) rejected. The settle's
|
|
527
|
+
* Promise is the consumer's primary failure surface, but subscribers
|
|
528
|
+
* registered via {@link ChatSession.subscribe} also expect to observe
|
|
529
|
+
* `error + finish` events so a UI bound to the chat stream can
|
|
530
|
+
* render the failure. Emit those without firing
|
|
531
|
+
* `chat-stream-error` telemetry — chat-stream-* telemetry is owned
|
|
532
|
+
* by the chat() lifecycle, not by settle calls (issue #529: one
|
|
533
|
+
* chat-stream-started/completed/error pair per turn, not per
|
|
534
|
+
* settle).
|
|
535
|
+
*/
|
|
536
|
+
notifySettleRejection(err) {
|
|
537
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
538
|
+
this.chatEventBus.emit({ type: 'error', error });
|
|
539
|
+
this.chatEventBus.emit({ type: 'finish', finishReason: 'error' });
|
|
540
|
+
}
|
|
530
541
|
assertNotDisposed() {
|
|
531
542
|
if (this.disposed) {
|
|
532
543
|
throw new AgentSDKError('ChatSession has been disposed.', AgentSDKErrorType.DISPOSED);
|