@salesforce/sfdx-agent-harness-mastra 0.87.0 → 0.89.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,16 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-harness-mastra` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.89.0] - 2026-09-21
7
+
8
+ ### Features
9
+ - **agent-sdk,harness-mastra**: add GPT-5.6 + Claude Sonnet 5 to model registry; fix stale output caps + compaction max_tokens @W-23949616@ ([#833](https://github.com/forcedotcom/agentic-dx/pull/833))
10
+
11
+ ## [0.88.0] - 2026-09-18
12
+
13
+ ### Fixes
14
+ - defer previous-workspace destroy while a coordinator is parked so a remembered-approval resume doesn't crash @W-24233008@ ([#834](https://github.com/forcedotcom/agentic-dx/pull/834))
15
+
6
16
  ## [0.87.0] - 2026-09-17
7
17
 
8
18
  ### Chores
@@ -242,7 +242,10 @@ export class MastraApprovalCoordinator {
242
242
  });
243
243
  }
244
244
  onTeardown(callback) {
245
- if (this.aborted || this.disposed) {
245
+ // If already terminal, fire now: a callback added after teardown ran
246
+ // would land in an already-cleared set and never fire. `isSettled()`
247
+ // covers natural-finish/timeout/pump-error settles (no abort/dispose flag).
248
+ if (this.aborted || this.disposed || this.isSettled()) {
246
249
  queueMicrotask(callback);
247
250
  return;
248
251
  }
@@ -1257,8 +1257,45 @@ export class MastraHarness {
1257
1257
  state.toolSearchProcessor = toolSearchProcessor;
1258
1258
  state.skillSearchProcessor = skillSearchProcessor;
1259
1259
  state.hooks = options?.hooks ?? {};
1260
- previousSkillSearchProcessor?.dispose();
1261
- await previousWorkspace?.destroy().catch(() => undefined);
1260
+ // Defer tearing down the previous Agent's workspace (+ its skill-search
1261
+ // processor) until every coordinator still bound to that Agent settles;
1262
+ // do NOT destroy synchronously. A coordinator parked on a tool-approval
1263
+ // resumes against the old Agent and reads `workspace.skills` (in
1264
+ // resumeStream, and via the skill-search processor's getter) — if the
1265
+ // workspace is already destroyed, @mastra/core throws
1266
+ // WorkspaceNotReadyError. Common trigger: an "Allow always" approval,
1267
+ // whose rule-persist calls updateAgentConfig (→ this method) before the
1268
+ // approval settles.
1269
+ //
1270
+ // Two things must BOTH stay for this to be correct: the filter +
1271
+ // `onTeardown` registration run in one synchronous tick (so no
1272
+ // coordinator settles between them and is missed), and `onTeardown`'s
1273
+ // own `isSettled()` arm handles an already-settled coordinator. Bounded
1274
+ // because `destroyAgent()`/`shutdown()` force-dispose coordinators, so
1275
+ // the deferred teardown always eventually runs.
1276
+ const disposePreviousAgentResources = async () => {
1277
+ previousSkillSearchProcessor?.dispose();
1278
+ await previousWorkspace?.destroy();
1279
+ };
1280
+ const onTeardownFailed = (error) => {
1281
+ // Guarded: a deferred teardown can reject after shutdown() disposed
1282
+ // the buses; logging there must not become an unhandled rejection.
1283
+ try {
1284
+ this.buses.getLogBus()?.debug('Deferred previous-Agent resource teardown failed', { error });
1285
+ }
1286
+ catch {
1287
+ /* buses already disposed */
1288
+ }
1289
+ };
1290
+ const unsettledCoordinators = [...state.coordinators.values()].filter((c) => !c.isSettled());
1291
+ if (unsettledCoordinators.length === 0) {
1292
+ await disposePreviousAgentResources().catch(onTeardownFailed);
1293
+ }
1294
+ else {
1295
+ void Promise.all(unsettledCoordinators.map((c) => new Promise((resolve) => c.onTeardown(resolve))))
1296
+ .then(() => disposePreviousAgentResources())
1297
+ .catch(onTeardownFailed);
1298
+ }
1262
1299
  }
1263
1300
  catch (error) {
1264
1301
  // Phase 2 failed after Phase 1 mutated state.mcpServers. We don't
@@ -308,6 +308,16 @@ function applyThinkingConfig(params, model, providerHint) {
308
308
  }
309
309
  async function doGenerate(ctx, options) {
310
310
  const { client, commonParams, toolSchemas } = await buildAnthropicRequest(ctx, options);
311
+ // Clamp max_tokens to a summary-safe ceiling on this non-streaming path. The
312
+ // Anthropic SDK refuses a non-streaming request whose max_tokens implies a
313
+ // completion that could exceed 10 minutes (`calculateNonstreamingTimeout`,
314
+ // threshold ~21k tokens). doGenerate's only caller is compactThread's
315
+ // summarize() (see the note below) — a conversation summary never needs the
316
+ // model's full output budget — so a model with a large cap (Opus 4.x @ 128k,
317
+ // Sonnet 4.x @ 64k) would otherwise throw here. `Math.min` leaves a smaller
318
+ // cap untouched; any thinking budget buildAnthropicRequest applied is <= 4096,
319
+ // safely under this floor.
320
+ const summaryMaxTokens = Math.min(commonParams.max_tokens, SUMMARY_MAX_OUTPUT_TOKENS);
311
321
  // Forward the turn's AbortSignal into the SDK's RequestOptions so firing the
312
322
  // controller (POST /cancel, idle timeout, shutdown abortAll) actually aborts
313
323
  // the outbound gateway fetch, not just harness bookkeeping (W-23988781).
@@ -318,7 +328,7 @@ async function doGenerate(ctx, options) {
318
328
  // as COMPACTION_FAILED. Error classification here targets the streaming turn
319
329
  // path (8d) — a raw create() rejection propagating uncoded is correct for the
320
330
  // summarize-only path.
321
- const message = await client.messages.create({ ...commonParams, stream: false }, { signal: options.abortSignal });
331
+ const message = await client.messages.create({ ...commonParams, max_tokens: summaryMaxTokens, stream: false }, { signal: options.abortSignal });
322
332
  const content = [];
323
333
  let sawToolCalls = false;
324
334
  for (const block of message.content) {
@@ -564,6 +574,17 @@ async function doStream(ctx, options) {
564
574
  * API requires the field; Mastra leaves it unset for many turns. 4096 mirrors
565
575
  * the `@ai-sdk/anthropic` provider's default. */
566
576
  const DEFAULT_MAX_TOKENS = 4096;
577
+ /**
578
+ * Ceiling for the non-streaming summary request `doGenerate` issues for `compactThread`.
579
+ * The Anthropic SDK refuses a non-streaming request whose max_tokens implies a >10-minute
580
+ * completion — `expectedTime = 3.6e6 * maxTokens / 128000 ms`, so the guard trips above
581
+ * ~21,333 tokens. 16384 sits safely under that (~461s est., ~23% margin), stays above the
582
+ * max thinking budget (4096), and matches the historical working summary budget for the
583
+ * previous default model (Sonnet 4.6 @ 16384) — so a large per-model output cap
584
+ * (Opus 4.x @ 128k, Sonnet 4.x @ 64k) is clamped here instead of throwing. A conversation
585
+ * summary never needs more.
586
+ */
587
+ const SUMMARY_MAX_OUTPUT_TOKENS = 16384;
567
588
  /**
568
589
  * Translates a V3 `LanguageModelV3Prompt` into Anthropic's Messages API shape
569
590
  * — top-level `system` string + `MessageParam[]` of role-tagged content blocks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-harness-mastra",
3
- "version": "0.87.0",
3
+ "version": "0.89.0",
4
4
  "description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,12 +42,12 @@
42
42
  "openai": "^7.17.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@salesforce/sfdx-agent-sdk": "0.88.0"
45
+ "@salesforce/sfdx-agent-sdk": "0.90.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@eslint/js": "^10.0.1",
49
49
  "@salesforce/harness-conformance": "0.0.1",
50
- "@salesforce/sfdx-agent-sdk": "0.88.0",
50
+ "@salesforce/sfdx-agent-sdk": "0.90.0",
51
51
  "@types/node": "^22.20.1",
52
52
  "@vitest/coverage-istanbul": "^5.0.1",
53
53
  "@vitest/eslint-plugin": "^1.6.27",