@salesforce/sfdx-agent-sdk 0.76.0 → 0.77.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 CHANGED
@@ -3,6 +3,11 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-sdk` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.77.0] - 2026-09-08
7
+
8
+ ### Fixes
9
+ - **agent-sdk**: AgentManager.shutdown disposes agents instead of destroying them @W-23632695@ ([#794](https://github.com/forcedotcom/agentic-dx/pull/794))
10
+
6
11
  ## [0.76.0] - 2026-09-08
7
12
 
8
13
  ### Features
package/README.md CHANGED
@@ -52,8 +52,9 @@ for await (const event of eventStream) {
52
52
  }
53
53
  }
54
54
 
55
- // 4. Shut down. The harness is torn down; persisted identity files are NOT
56
- // removed, so a subsequent `createAgentManager` call restores them.
55
+ // 4. Shut down. Agents are disposed (in-memory release) and the harness is torn down;
56
+ // persisted identity, threads, message history, and session context are NOT removed,
57
+ // so a subsequent `createAgentManager` restores the agents and their chat sessions.
57
58
  await manager.shutdown();
58
59
  ```
59
60
 
@@ -110,7 +111,7 @@ explicitly. The `createAgent` config parameter narrows automatically when the ha
110
111
  | `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
111
112
  | `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included — query `getRestoreFailures()` separately. |
112
113
  | `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
113
- | `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) restart `createAgentManager` over the same root to bring them back. |
114
+ | `shutdown` | `() => Promise<void>` | Dispose all live agents (release in-memory resources) and shut down the harness. Persisted threads, message history, and session context are NOT deleted; restart `createAgentManager` over the same root to restore the agents and their chat sessions. |
114
115
  | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
115
116
  | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
116
117
  | `onWireCommunication` | `(callback: WireCommunicationEventCallback) => Unsubscribe` | Subscribe to wire-level communication events from the harness. Opt-in diagnostic channel that surfaces outbound LLM requests, responses, and harness-specific monitoring metadata. Subscriber-gated end-to-end — harnesses pay no cost when nobody listens. See "Wire-Communication Events" below for the event-shape catalog and the harness-asymmetric coverage (Mastra emits per-call request/response pairs; Claude emits a per-stream pointer to a debug log file). |
@@ -136,7 +136,11 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
136
136
  * restore-failure entry.
137
137
  */
138
138
  destroyAgent(agentId: string): Promise<void>;
139
- /** Shuts down the harness and destroys all live agents. */
139
+ /**
140
+ * Disposes every live agent (in-memory release) and shuts down the harness. Persisted
141
+ * threads, message history, and per-thread session context are NOT deleted, so a subsequent
142
+ * `createAgentManager` over the same storage root restores the agents and their chat sessions.
143
+ */
140
144
  shutdown(): Promise<void>;
141
145
  /**
142
146
  * Harness-specific extensions namespace, typed off the harness subtype `H`.
@@ -128,7 +128,13 @@ export class DefaultAgentManager {
128
128
  return;
129
129
  }
130
130
  for (const [agentId, agent] of this.agents) {
131
- await agent.destroy();
131
+ // Graceful shutdown releases in-memory resources but MUST NOT delete persisted
132
+ // threads / message history / session context — `dispose()`, not `destroy()`. A
133
+ // subsequent createAgentManager over the same storageRootFolder then restores every
134
+ // agent's chat sessions and seeded context. `harness.shutdown()` below releases the
135
+ // harness-side per-agent runtime (coordinators, MCP clients, storage handle). Using
136
+ // `destroy()` here deleted the very state boot-restore exists to bring back (W-23632695).
137
+ agent.dispose();
132
138
  this.router.unregisterAgent(agentId);
133
139
  }
134
140
  this.agents.clear();
package/dist/agent.d.ts CHANGED
@@ -345,6 +345,31 @@ export declare class DefaultAgent implements Agent {
345
345
  * - MUST delegate to `this.harness.destroyAgent(this.config.agentId)` to clean up the agent's harness resources.
346
346
  */
347
347
  destroy(): Promise<void>;
348
+ /**
349
+ * Release this agent's in-memory resources WITHOUT deleting any persisted state.
350
+ * Used by {@link DefaultAgentManager.shutdown} on a graceful process stop.
351
+ *
352
+ * Unlike {@link destroy} (the `DELETE /agents` verb), `dispose` does NOT call
353
+ * `harness.destroyThread` / `harness.destroyAgent`, so the agent's threads, message
354
+ * history, and per-thread session context stay on the storage root and a subsequent
355
+ * `createAgentManager` over that root restores them. The harness's own per-agent
356
+ * runtime (coordinators, MCP clients, storage handle) is released by
357
+ * `harness.shutdown()`, which the manager calls after disposing every agent.
358
+ *
359
+ * Emits NO `agent-destroyed` telemetry — the agent is being released for restart,
360
+ * not destroyed. Idempotent via the shared `disposed` guard.
361
+ *
362
+ * Not on the public {@link Agent} interface — only {@link DefaultAgentManager} calls
363
+ * it, and it holds the concrete `DefaultAgent` (same reason {@link restoreSessions}
364
+ * is DefaultAgent-only). See W-23632695.
365
+ */
366
+ dispose(): void;
367
+ /**
368
+ * Tear down inbound + parent telemetry/log forwarding and dispose this agent's own
369
+ * buses, then flip `disposed`. Shared by {@link destroy} and {@link dispose} so the
370
+ * two teardown verbs can't drift on forwarding / bus cleanup.
371
+ */
372
+ private teardownForwardingAndBuses;
348
373
  onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
349
374
  onLog(callback: (record: LogRecord) => void): Unsubscribe;
350
375
  /**
package/dist/agent.js CHANGED
@@ -387,6 +387,42 @@ export class DefaultAgent {
387
387
  message: 'Agent destroyed',
388
388
  context: { event_type: 'agent-destroyed', agentId: this.agentId },
389
389
  }, agentDestroyedAt);
390
+ this.teardownForwardingAndBuses();
391
+ }
392
+ /**
393
+ * Release this agent's in-memory resources WITHOUT deleting any persisted state.
394
+ * Used by {@link DefaultAgentManager.shutdown} on a graceful process stop.
395
+ *
396
+ * Unlike {@link destroy} (the `DELETE /agents` verb), `dispose` does NOT call
397
+ * `harness.destroyThread` / `harness.destroyAgent`, so the agent's threads, message
398
+ * history, and per-thread session context stay on the storage root and a subsequent
399
+ * `createAgentManager` over that root restores them. The harness's own per-agent
400
+ * runtime (coordinators, MCP clients, storage handle) is released by
401
+ * `harness.shutdown()`, which the manager calls after disposing every agent.
402
+ *
403
+ * Emits NO `agent-destroyed` telemetry — the agent is being released for restart,
404
+ * not destroyed. Idempotent via the shared `disposed` guard.
405
+ *
406
+ * Not on the public {@link Agent} interface — only {@link DefaultAgentManager} calls
407
+ * it, and it holds the concrete `DefaultAgent` (same reason {@link restoreSessions}
408
+ * is DefaultAgent-only). See W-23632695.
409
+ */
410
+ dispose() {
411
+ if (this.disposed) {
412
+ return;
413
+ }
414
+ for (const [sessionId, session] of this.sessions) {
415
+ this.detachSession(sessionId, session, false);
416
+ }
417
+ this.sessions.clear();
418
+ this.teardownForwardingAndBuses();
419
+ }
420
+ /**
421
+ * Tear down inbound + parent telemetry/log forwarding and dispose this agent's own
422
+ * buses, then flip `disposed`. Shared by {@link destroy} and {@link dispose} so the
423
+ * two teardown verbs can't drift on forwarding / bus cleanup.
424
+ */
425
+ teardownForwardingAndBuses() {
390
426
  for (const unsub of this.inboundUnsubs)
391
427
  unsub();
392
428
  for (const unsub of this.parentUnsubs)
@@ -456,8 +492,17 @@ export class DefaultAgent {
456
492
  }, sessionCreatedAt);
457
493
  return session;
458
494
  }
459
- detachSession(threadId, session) {
460
- session.dispose();
495
+ detachSession(threadId, session, emitDestroyed = true) {
496
+ // `emitDestroyed` is false only on the graceful-shutdown release path (`dispose()`): the
497
+ // session is preserved on disk for restore, so it must not emit `session-destroyed` — the
498
+ // session-level analog of `dispose()` suppressing `agent-destroyed` (W-23632695). Every real
499
+ // teardown (destroy / destroyChatSession / compact / clone source) keeps the default `true`.
500
+ if (emitDestroyed) {
501
+ session.dispose();
502
+ }
503
+ else {
504
+ session.releaseWithoutEvent();
505
+ }
461
506
  const unregister = this.sessionSliceUnregisters.get(threadId);
462
507
  if (unregister) {
463
508
  unregister();
@@ -566,6 +566,19 @@ export declare class DefaultChatSession implements ChatSession {
566
566
  onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
567
567
  onLog(callback: (record: LogRecord) => void): Unsubscribe;
568
568
  dispose(): void;
569
+ /**
570
+ * Release this session's in-memory resources WITHOUT emitting `session-destroyed`.
571
+ * Used on the graceful-shutdown release path (`DefaultAgent.dispose()`), where the thread
572
+ * and its session context are preserved on disk for restore, so a "destroyed" signal would
573
+ * misinform telemetry observers. Teardown is otherwise identical to `dispose()`. Not on the
574
+ * public `ChatSession` interface — only `DefaultAgent.detachSession` calls it (W-23632695).
575
+ */
576
+ releaseWithoutEvent(): void;
577
+ /**
578
+ * Shared in-memory teardown for `dispose()` (which first emits `session-destroyed`) and
579
+ * `releaseWithoutEvent()` (silent), so the two paths can't drift on cleanup.
580
+ */
581
+ private teardown;
569
582
  private emitToolApprovalResolved;
570
583
  /**
571
584
  * Clears the per-turn tracking maps at a terminal `finish`. Both maps are
@@ -663,6 +663,26 @@ export class DefaultChatSession {
663
663
  threadId: this.threadId,
664
664
  },
665
665
  }, sessionDestroyedAt);
666
+ this.teardown();
667
+ }
668
+ /**
669
+ * Release this session's in-memory resources WITHOUT emitting `session-destroyed`.
670
+ * Used on the graceful-shutdown release path (`DefaultAgent.dispose()`), where the thread
671
+ * and its session context are preserved on disk for restore, so a "destroyed" signal would
672
+ * misinform telemetry observers. Teardown is otherwise identical to `dispose()`. Not on the
673
+ * public `ChatSession` interface — only `DefaultAgent.detachSession` calls it (W-23632695).
674
+ */
675
+ releaseWithoutEvent() {
676
+ if (this.disposed) {
677
+ return;
678
+ }
679
+ this.teardown();
680
+ }
681
+ /**
682
+ * Shared in-memory teardown for `dispose()` (which first emits `session-destroyed`) and
683
+ * `releaseWithoutEvent()` (silent), so the two paths can't drift on cleanup.
684
+ */
685
+ teardown() {
666
686
  for (const unsub of this.inboundUnsubs)
667
687
  unsub();
668
688
  for (const unsub of this.parentUnsubs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^10.0.1",
51
- "@salesforce/sfdx-agent-harness-claude": "0.72.0",
52
- "@salesforce/sfdx-agent-harness-mastra": "0.75.0",
53
- "@salesforce/sfdx-agent-harness-openai": "0.41.0",
51
+ "@salesforce/sfdx-agent-harness-claude": "0.73.0",
52
+ "@salesforce/sfdx-agent-harness-mastra": "0.76.0",
53
+ "@salesforce/sfdx-agent-harness-openai": "0.42.0",
54
54
  "@types/node": "^22.20.1",
55
55
  "@vitest/coverage-istanbul": "^4.1.11",
56
56
  "@vitest/eslint-plugin": "^1.6.27",