mastracode 0.26.0-alpha.5 → 0.26.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/dist/acp/agent.d.ts +4 -4
  3. package/dist/acp/agent.d.ts.map +1 -1
  4. package/dist/acp/event-mapper.d.ts +3 -3
  5. package/dist/acp/event-mapper.d.ts.map +1 -1
  6. package/dist/acp/server.d.ts +2 -2
  7. package/dist/acp/server.d.ts.map +1 -1
  8. package/dist/{acp-7DQDVR3M.js → acp-G5OLW7EV.js} +12 -12
  9. package/dist/acp-G5OLW7EV.js.map +1 -0
  10. package/dist/{acp-NTTZEFVN.cjs → acp-YSYEXVBE.cjs} +15 -15
  11. package/dist/acp-YSYEXVBE.cjs.map +1 -0
  12. package/dist/acp.cjs +2 -2
  13. package/dist/acp.js +1 -1
  14. package/dist/agents/instructions.d.ts.map +1 -1
  15. package/dist/agents/mastracode-gateway.d.ts +8 -1
  16. package/dist/agents/mastracode-gateway.d.ts.map +1 -1
  17. package/dist/agents/memory.d.ts +1 -1
  18. package/dist/agents/model.d.ts +1 -1
  19. package/dist/agents/modes/build.d.ts +2 -2
  20. package/dist/agents/modes/build.d.ts.map +1 -1
  21. package/dist/agents/modes/explore.d.ts +2 -2
  22. package/dist/agents/modes/explore.d.ts.map +1 -1
  23. package/dist/agents/modes/plan.d.ts +2 -2
  24. package/dist/agents/modes/plan.d.ts.map +1 -1
  25. package/dist/agents/prompts/plan.d.ts +5 -4
  26. package/dist/agents/prompts/plan.d.ts.map +1 -1
  27. package/dist/agents/thread-caveman-state.d.ts +2 -2
  28. package/dist/agents/thread-caveman-state.d.ts.map +1 -1
  29. package/dist/agents/tool-availability.d.ts +5 -1
  30. package/dist/agents/tool-availability.d.ts.map +1 -1
  31. package/dist/{chunk-QO4KIOLG.js → chunk-GT3A3LAA.js} +221 -117
  32. package/dist/chunk-GT3A3LAA.js.map +1 -0
  33. package/dist/{chunk-IU477VVQ.js → chunk-IFUIWWGJ.js} +8 -8
  34. package/dist/chunk-IFUIWWGJ.js.map +1 -0
  35. package/dist/{chunk-DVBJN36L.cjs → chunk-KGBCGF6C.cjs} +47 -23
  36. package/dist/chunk-KGBCGF6C.cjs.map +1 -0
  37. package/dist/{chunk-L67CU2OP.js → chunk-LUSQ75XP.js} +233 -136
  38. package/dist/chunk-LUSQ75XP.js.map +1 -0
  39. package/dist/{chunk-F3TKSK3C.js → chunk-NDCWIAKG.js} +43 -21
  40. package/dist/chunk-NDCWIAKG.js.map +1 -0
  41. package/dist/{chunk-UW25EMMX.cjs → chunk-NUKVNLFS.cjs} +1160 -1063
  42. package/dist/chunk-NUKVNLFS.cjs.map +1 -0
  43. package/dist/{chunk-DPRNPXVC.cjs → chunk-VBH3L4LD.cjs} +8 -8
  44. package/dist/chunk-VBH3L4LD.cjs.map +1 -0
  45. package/dist/{chunk-MUZ2ZLWN.cjs → chunk-Z3HFSXTB.cjs} +256 -152
  46. package/dist/chunk-Z3HFSXTB.cjs.map +1 -0
  47. package/dist/cli.cjs +33 -33
  48. package/dist/cli.cjs.map +1 -1
  49. package/dist/cli.js +18 -18
  50. package/dist/cli.js.map +1 -1
  51. package/dist/evals/context-builder.d.ts +5 -5
  52. package/dist/evals/context-builder.d.ts.map +1 -1
  53. package/dist/headless.d.ts +3 -3
  54. package/dist/headless.d.ts.map +1 -1
  55. package/dist/index.cjs +10 -10
  56. package/dist/index.d.ts +36 -34
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1 -1
  59. package/dist/mcp/config.d.ts +6 -2
  60. package/dist/mcp/config.d.ts.map +1 -1
  61. package/dist/processors/plan-rejection-abort.d.ts.map +1 -1
  62. package/dist/tools/request-sandbox-access.d.ts.map +1 -1
  63. package/dist/tools/utils.d.ts +2 -2
  64. package/dist/tui/commands/api-keys.d.ts +15 -0
  65. package/dist/tui/commands/api-keys.d.ts.map +1 -1
  66. package/dist/tui/commands/clone.d.ts +1 -1
  67. package/dist/tui/commands/goal.d.ts +2 -2
  68. package/dist/tui/commands/goal.d.ts.map +1 -1
  69. package/dist/tui/commands/types.d.ts +3 -3
  70. package/dist/tui/commands/types.d.ts.map +1 -1
  71. package/dist/tui/components/assistant-message.d.ts +3 -3
  72. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  73. package/dist/tui/components/help-overlay.d.ts +1 -1
  74. package/dist/tui/components/help-overlay.d.ts.map +1 -1
  75. package/dist/tui/components/login-selector.d.ts +1 -1
  76. package/dist/tui/components/om-progress.d.ts +2 -2
  77. package/dist/tui/components/om-progress.d.ts.map +1 -1
  78. package/dist/tui/components/plan-approval-inline.d.ts.map +1 -1
  79. package/dist/tui/components/task-progress.d.ts +1 -1
  80. package/dist/tui/components/thread-selector.d.ts +4 -4
  81. package/dist/tui/components/thread-selector.d.ts.map +1 -1
  82. package/dist/tui/event-dispatch.d.ts +3 -3
  83. package/dist/tui/event-dispatch.d.ts.map +1 -1
  84. package/dist/tui/handlers/agent-lifecycle.d.ts +1 -1
  85. package/dist/tui/handlers/agent-lifecycle.d.ts.map +1 -1
  86. package/dist/tui/handlers/message.d.ts +4 -4
  87. package/dist/tui/handlers/message.d.ts.map +1 -1
  88. package/dist/tui/handlers/prompts.d.ts +1 -1
  89. package/dist/tui/handlers/prompts.d.ts.map +1 -1
  90. package/dist/tui/handlers/types.d.ts +3 -2
  91. package/dist/tui/handlers/types.d.ts.map +1 -1
  92. package/dist/tui/mastra-tui.d.ts +3 -3
  93. package/dist/tui/mastra-tui.d.ts.map +1 -1
  94. package/dist/tui/render-messages.d.ts +4 -3
  95. package/dist/tui/render-messages.d.ts.map +1 -1
  96. package/dist/tui/setup.d.ts +1 -1
  97. package/dist/tui/setup.d.ts.map +1 -1
  98. package/dist/tui/state.d.ts +24 -10
  99. package/dist/tui/state.d.ts.map +1 -1
  100. package/dist/tui/status-line.d.ts.map +1 -1
  101. package/dist/tui.cjs +19 -19
  102. package/dist/tui.js +2 -2
  103. package/dist/utils/plan-diff.d.ts +25 -0
  104. package/dist/utils/plan-diff.d.ts.map +1 -0
  105. package/dist/utils/plans.d.ts +41 -12
  106. package/dist/utils/plans.d.ts.map +1 -1
  107. package/package.json +16 -16
  108. package/dist/acp-7DQDVR3M.js.map +0 -1
  109. package/dist/acp-NTTZEFVN.cjs.map +0 -1
  110. package/dist/chunk-DPRNPXVC.cjs.map +0 -1
  111. package/dist/chunk-DVBJN36L.cjs.map +0 -1
  112. package/dist/chunk-F3TKSK3C.js.map +0 -1
  113. package/dist/chunk-IU477VVQ.js.map +0 -1
  114. package/dist/chunk-L67CU2OP.js.map +0 -1
  115. package/dist/chunk-MUZ2ZLWN.cjs.map +0 -1
  116. package/dist/chunk-QO4KIOLG.js.map +0 -1
  117. package/dist/chunk-UW25EMMX.cjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { tintHex, mastra, theme, getMarkdownTheme, CHAT_INDENT, BOX_INDENT, getTermWidth, ensureTerminalGlyphContrast, TERM_WIDTH_BUFFER, getEditorTheme, loadSettings, MEMORY_GATEWAY_PROVIDER, getAvailableModePacks, resolveThreadActiveModelPackId, saveSettings, getAvailableOmPacks, ONBOARDING_VERSION, THREAD_ACTIVE_MODEL_PACK_ID_KEY, BOX_INDENT_STR, ThreadLockError, getPlanFilename, getSelectListTheme, luminance, extendedColors, mastraBrand, savePlanSnapshot, MEMORY_GATEWAY_DEFAULT_URL, getThemeMode, applyThemeMode, createBrowserFromSettings, setProfileProvider, getCustomProviderId, OBSERVABILITY_AUTH_PREFIX, checkProfileProviderMismatch, getSettingsListTheme, savePlanToDisk, toCustomProviderModelId } from './chunk-F3TKSK3C.js';
1
+ import { tintHex, mastra, theme, getMarkdownTheme, CHAT_INDENT, BOX_INDENT, getTermWidth, ensureTerminalGlyphContrast, TERM_WIDTH_BUFFER, getEditorTheme, loadSettings, MEMORY_GATEWAY_PROVIDER, getAvailableModePacks, resolveThreadActiveModelPackId, saveSettings, getAvailableOmPacks, ONBOARDING_VERSION, THREAD_ACTIVE_MODEL_PACK_ID_KEY, BOX_INDENT_STR, ThreadLockError, resolvePlanPath, readPlanFile, getSelectListTheme, luminance, extendedColors, mastraBrand, MEMORY_GATEWAY_DEFAULT_URL, getThemeMode, applyThemeMode, createBrowserFromSettings, setProfileProvider, getCustomProviderId, OBSERVABILITY_AUTH_PREFIX, checkProfileProviderMismatch, getSettingsListTheme, approvePlanFile, toCustomProviderModelId } from './chunk-NDCWIAKG.js';
2
2
  import { getOAuthProviders, detectProject, DEFAULT_CONFIG_DIR, getUserId, getCurrentGitBranchAsync, getUserName, PROVIDER_DEFAULT_MODELS } from './chunk-Z5EMG7I2.js';
3
3
  import { MC_TOOLS, getToolCategory, TOOL_CATEGORIES } from './chunk-UOFNLVKF.js';
4
4
  import { exec, spawn, execFile, execSync, execFileSync } from 'child_process';
@@ -10,7 +10,8 @@ import path4__default, { extname, join } from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { formatSkillActivation } from '@mastra/core/workspace';
13
- import { defaultOMProgressState, parseSubagentMeta, assignTaskIds } from '@mastra/core/harness';
13
+ import { defaultOMProgressState, parseSubagentMeta } from '@mastra/core/agent-controller';
14
+ import { assignTaskIds } from '@mastra/core/signals';
14
15
  import { TASKS_STATE_ID } from '@mastra/core/tools';
15
16
  import stripAnsi2 from 'strip-ansi';
16
17
  import { safeStringify } from '@mastra/core/utils';
@@ -1174,7 +1175,7 @@ function getInstallCommand(pm, version) {
1174
1175
  }
1175
1176
  function getCurrentVersion() {
1176
1177
  {
1177
- return "0.26.0-alpha.5";
1178
+ return "0.26.0";
1178
1179
  }
1179
1180
  }
1180
1181
  async function fetchLatestVersion() {
@@ -1933,7 +1934,7 @@ var GoalManager = class {
1933
1934
  // ---------------------------------------------------------------------------
1934
1935
  getAgent(state) {
1935
1936
  try {
1936
- return state.harness.getCurrentAgent(state.session);
1937
+ return state.controller.getCurrentAgent(state.session);
1937
1938
  } catch {
1938
1939
  return void 0;
1939
1940
  }
@@ -2311,7 +2312,7 @@ function getJudgeDefaults() {
2311
2312
  }
2312
2313
  async function promptForJudgeDefaults(ctx, cancelMessage) {
2313
2314
  const { state } = ctx;
2314
- const availableModels = await state.harness.listAvailableModels();
2315
+ const availableModels = await state.controller.listAvailableModels();
2315
2316
  if (availableModels.length === 0) {
2316
2317
  ctx.showError("No models available. Cannot set goal judge defaults.");
2317
2318
  return null;
@@ -2644,7 +2645,7 @@ function describeShellPassthroughInvocation(invocation) {
2644
2645
  function handleHelpCommand(ctx) {
2645
2646
  const shellInvocation = resolveShellPassthroughInvocation("", loadSettings().shellPassthrough);
2646
2647
  const text = buildHelpText({
2647
- modes: ctx.harness.listModes().length,
2648
+ modes: ctx.controller.listModes().length,
2648
2649
  customSlashCommands: ctx.customSlashCommands,
2649
2650
  shellModeLabel: describeShellPassthroughInvocation(shellInvocation)
2650
2651
  });
@@ -3759,7 +3760,7 @@ function applyCurrentModeColorToRenderedTools(ctx) {
3759
3760
  ctx.state.ui.requestRender();
3760
3761
  }
3761
3762
  async function handleModeCommand(ctx, args) {
3762
- const modes = ctx.harness.listModes();
3763
+ const modes = ctx.controller.listModes();
3763
3764
  if (modes.length <= 1) {
3764
3765
  ctx.showInfo("Only one mode available");
3765
3766
  return;
@@ -4432,6 +4433,48 @@ var OMOutputComponent = class extends Container {
4432
4433
  return "other";
4433
4434
  }
4434
4435
  };
4436
+
4437
+ // src/utils/plan-diff.ts
4438
+ function generatePlanDiff(oldText, newText) {
4439
+ const oldLines = oldText.split(/\r?\n/);
4440
+ const newLines = newText.split(/\r?\n/);
4441
+ const n = oldLines.length;
4442
+ const m = newLines.length;
4443
+ const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
4444
+ for (let i2 = n - 1; i2 >= 0; i2--) {
4445
+ for (let j2 = m - 1; j2 >= 0; j2--) {
4446
+ lcs[i2][j2] = oldLines[i2] === newLines[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
4447
+ }
4448
+ }
4449
+ const entries = [];
4450
+ let i = 0;
4451
+ let j = 0;
4452
+ while (i < n && j < m) {
4453
+ if (oldLines[i] === newLines[j]) {
4454
+ entries.push({ type: "context", text: oldLines[i] });
4455
+ i++;
4456
+ j++;
4457
+ } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
4458
+ entries.push({ type: "removed", text: oldLines[i] });
4459
+ i++;
4460
+ } else {
4461
+ entries.push({ type: "added", text: newLines[j] });
4462
+ j++;
4463
+ }
4464
+ }
4465
+ while (i < n) entries.push({ type: "removed", text: oldLines[i++] });
4466
+ while (j < m) entries.push({ type: "added", text: newLines[j++] });
4467
+ return entries;
4468
+ }
4469
+ function shouldShowDiff(previousPlan, plan, maxChangedRatio = 0.5) {
4470
+ if (!previousPlan || previousPlan === plan) return false;
4471
+ const entries = generatePlanDiff(previousPlan, plan);
4472
+ const changed = entries.filter((e) => e.type !== "context").length;
4473
+ if (changed === 0) return false;
4474
+ return changed / Math.max(entries.length, 1) <= maxChangedRatio;
4475
+ }
4476
+
4477
+ // src/tui/components/plan-approval-inline.ts
4435
4478
  var PlanContentBox = class {
4436
4479
  constructor(plan) {
4437
4480
  this.plan = plan;
@@ -4494,25 +4537,6 @@ var PlanDiffBox = class {
4494
4537
  return [top, ...body, bottom];
4495
4538
  }
4496
4539
  };
4497
- function generatePlanDiff(oldText, newText) {
4498
- const oldLines = oldText.split("\n");
4499
- const newLines = newText.split("\n");
4500
- const entries = [];
4501
- const maxLines = Math.max(oldLines.length, newLines.length);
4502
- for (let i = 0; i < maxLines; i++) {
4503
- if (i >= oldLines.length) {
4504
- entries.push({ type: "added", text: newLines[i] });
4505
- } else if (i >= newLines.length) {
4506
- entries.push({ type: "removed", text: oldLines[i] });
4507
- } else if (oldLines[i] !== newLines[i]) {
4508
- entries.push({ type: "removed", text: oldLines[i] });
4509
- entries.push({ type: "added", text: newLines[i] });
4510
- } else {
4511
- entries.push({ type: "context", text: oldLines[i] });
4512
- }
4513
- }
4514
- return entries;
4515
- }
4516
4540
  var PlanApprovalInlineComponent = class _PlanApprovalInlineComponent extends Container {
4517
4541
  constructor(options, ui) {
4518
4542
  super();
@@ -4582,12 +4606,8 @@ var PlanApprovalInlineComponent = class _PlanApprovalInlineComponent extends Con
4582
4606
  updateArgs(args) {
4583
4607
  if (!args || typeof args !== "object" || this.resolved) return;
4584
4608
  const partial = args;
4585
- if (typeof partial.title === "string") {
4586
- this.planTitle = partial.title || "Untitled plan";
4587
- this.planFilename = getPlanFilename(this.planTitle);
4588
- }
4589
- if (typeof partial.plan === "string") {
4590
- this.planContent = partial.plan;
4609
+ if (typeof partial.path === "string" && partial.path) {
4610
+ this.planFilename = partial.path;
4591
4611
  }
4592
4612
  if (this.mode === "streaming") {
4593
4613
  this.renderStreaming();
@@ -4640,7 +4660,7 @@ var PlanApprovalInlineComponent = class _PlanApprovalInlineComponent extends Con
4640
4660
  renderPlanHeader(prefix = "") {
4641
4661
  this.contentBox.addChild(new Text(`${prefix}${theme.bold(theme.fg("accent", `Plan: ${this.planTitle}`))}`, 0, 0));
4642
4662
  if (this.planFilename) {
4643
- this.contentBox.addChild(new Text(theme.fg("dim", `.mastracode/plans/${this.planFilename}`), 0, 0));
4663
+ this.contentBox.addChild(new Text(theme.fg("dim", this.planFilename), 0, 0));
4644
4664
  }
4645
4665
  this.contentBox.addChild(new Spacer(1));
4646
4666
  }
@@ -4711,7 +4731,7 @@ var PlanResultComponent = class extends Container {
4711
4731
  const status = options.isApproved ? "Approved" : options.feedback ? "Changes requested" : "Rejected";
4712
4732
  contentBox.addChild(new Text(theme.bold(theme.fg("accent", `Plan: ${options.title}`)), 0, 0));
4713
4733
  if (options.planFilename) {
4714
- contentBox.addChild(new Text(theme.fg("dim", `.mastracode/plans/${options.planFilename}`), 0, 0));
4734
+ contentBox.addChild(new Text(theme.fg("dim", options.planFilename), 0, 0));
4715
4735
  }
4716
4736
  contentBox.addChild(new Spacer(1));
4717
4737
  contentBox.addChild(new PlanContentBox(options.plan));
@@ -8899,10 +8919,16 @@ async function renderExistingMessages(state) {
8899
8919
  if (content.name === "submit_plan" && toolResult?.type === "tool_result") {
8900
8920
  const args = content.args;
8901
8921
  let resultText = "";
8922
+ let submittedPlan;
8902
8923
  if (typeof toolResult.result === "string") {
8903
8924
  resultText = toolResult.result;
8904
- } else if (typeof toolResult.result === "object" && toolResult.result !== null && "content" in toolResult.result && typeof toolResult.result.content === "string") {
8905
- resultText = toolResult.result.content;
8925
+ } else if (typeof toolResult.result === "object" && toolResult.result !== null) {
8926
+ if ("content" in toolResult.result && typeof toolResult.result.content === "string") {
8927
+ resultText = toolResult.result.content;
8928
+ }
8929
+ if ("submittedPlan" in toolResult.result && typeof toolResult.result.submittedPlan === "object") {
8930
+ submittedPlan = toolResult.result.submittedPlan;
8931
+ }
8906
8932
  }
8907
8933
  const isApproved = resultText.startsWith("Plan approved");
8908
8934
  let feedback;
@@ -8910,17 +8936,24 @@ async function renderExistingMessages(state) {
8910
8936
  const feedbackMatch = resultText.match(/User feedback:\s*(.+?)(?:\n|$)/);
8911
8937
  feedback = feedbackMatch?.[1] || "Revision requested";
8912
8938
  }
8913
- if (args?.title && args?.plan) {
8939
+ const submittedPath = submittedPlan?.path || args?.path;
8940
+ if (submittedPath) {
8941
+ const sessionState = state.session.state.get();
8942
+ const projectPath = sessionState?.projectPath;
8943
+ const recoverAbsPath = !submittedPlan?.plan ? resolvePlanPath(projectPath ?? process.cwd(), submittedPath) : void 0;
8944
+ const recovered = recoverAbsPath ? await readPlanFile(recoverAbsPath) : void 0;
8945
+ const planBody = submittedPlan?.plan ?? recovered?.plan ?? "";
8946
+ const planTitle = submittedPlan?.title || recovered?.title || "Implementation Plan";
8914
8947
  const planResult = new PlanResultComponent({
8915
- title: args.title,
8916
- plan: args.plan,
8917
- planFilename: getPlanFilename(args.title),
8948
+ title: planTitle,
8949
+ plan: planBody,
8950
+ planFilename: submittedPath,
8918
8951
  isApproved,
8919
8952
  feedback
8920
8953
  });
8921
8954
  state.chatContainer.addChild(planResult);
8922
8955
  replacedWithInline = true;
8923
- state.previousPlanSnapshot = { title: args.title, plan: args.plan };
8956
+ state.previousPlanSnapshot = { path: submittedPath, plan: planBody };
8924
8957
  }
8925
8958
  }
8926
8959
  if (!replacedWithInline) {
@@ -9047,8 +9080,8 @@ function escapeSkillBoundary(value) {
9047
9080
  }
9048
9081
  async function resolveWorkspace(ctx) {
9049
9082
  let workspace = ctx.getResolvedWorkspace();
9050
- if (!workspace && ctx.harness.hasWorkspace()) {
9051
- workspace = await ctx.harness.resolveWorkspace({ session: ctx.state.session });
9083
+ if (!workspace && ctx.controller.hasWorkspace()) {
9084
+ workspace = await ctx.controller.resolveWorkspace({ session: ctx.state.session });
9052
9085
  }
9053
9086
  return workspace;
9054
9087
  }
@@ -9418,13 +9451,13 @@ async function handleCloneCommand(ctx) {
9418
9451
 
9419
9452
  // src/tui/commands/resource.ts
9420
9453
  async function handleResourceCommand(ctx, args) {
9421
- const { state, harness } = ctx;
9454
+ const { state, controller } = ctx;
9422
9455
  const { session } = state;
9423
9456
  const sub = args[0]?.trim();
9424
9457
  const current = session.identity.getResourceId();
9425
9458
  const defaultId = session.identity.getDefaultResourceId();
9426
9459
  if (!sub) {
9427
- const knownIds = await harness.getKnownResourceIds(session);
9460
+ const knownIds = await controller.getKnownResourceIds(session);
9428
9461
  const isOverridden = current !== defaultId;
9429
9462
  const lines = [
9430
9463
  `Current: ${current}${isOverridden ? ` (auto-detected: ${defaultId})` : ""}`,
@@ -9445,7 +9478,7 @@ async function handleResourceCommand(ctx, args) {
9445
9478
  ctx.showInfo(`Already on resource: ${current}`);
9446
9479
  return;
9447
9480
  }
9448
- await harness.setResourceId(session, { resourceId: newId });
9481
+ await controller.setResourceId(session, { resourceId: newId });
9449
9482
  const threads = await session.thread.list();
9450
9483
  const latest = [...threads].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
9451
9484
  if (latest) {
@@ -9964,7 +9997,7 @@ async function handleThreadsCommand(ctx) {
9964
9997
  return;
9965
9998
  }
9966
9999
  if (thread.resourceId !== currentResourceId) {
9967
- await state.harness.setResourceId(state.session, { resourceId: thread.resourceId });
10000
+ await state.controller.setResourceId(state.session, { resourceId: thread.resourceId });
9968
10001
  }
9969
10002
  try {
9970
10003
  await state.session.thread.switch({ threadId: thread.id });
@@ -10088,8 +10121,8 @@ async function handleThreadTagDirCommand(ctx) {
10088
10121
  }
10089
10122
  }
10090
10123
  async function sandboxAddPath(ctx, rawPath) {
10091
- const harnessState = ctx.state.session.state.get();
10092
- const currentPaths = harnessState?.sandboxAllowedPaths ?? [];
10124
+ const controllerWithState = ctx.state.session.state.get();
10125
+ const currentPaths = controllerWithState?.sandboxAllowedPaths ?? [];
10093
10126
  const resolved = path4__default.resolve(rawPath);
10094
10127
  if (currentPaths.includes(resolved)) {
10095
10128
  ctx.showInfo(`Path already allowed: ${resolved}`);
@@ -10138,8 +10171,8 @@ async function showSandboxAddPrompt(ctx) {
10138
10171
  });
10139
10172
  }
10140
10173
  async function handleSandboxCommand(ctx, args) {
10141
- const harnessState = ctx.state.session.state.get();
10142
- const currentPaths = harnessState?.sandboxAllowedPaths ?? [];
10174
+ const agentControllerState = ctx.state.session.state.get();
10175
+ const currentPaths = agentControllerState?.sandboxAllowedPaths ?? [];
10143
10176
  const subcommand = args[0]?.toLowerCase();
10144
10177
  if (subcommand === "add" && args.length > 1) {
10145
10178
  await sandboxAddPath(ctx, args.slice(1).join(" ").trim());
@@ -10693,7 +10726,7 @@ function updateStatusLine(state) {
10693
10726
  const showOMMode = !isJudging && (isObserving || isReflecting);
10694
10727
  let modeBadge = "";
10695
10728
  let modeBadgeWidth = 0;
10696
- const modes = state.harness.listModes();
10729
+ const modes = state.controller.listModes();
10697
10730
  const currentMode = modes.length > 1 ? state.session.mode.resolve() : void 0;
10698
10731
  const judgeModeColor = mastra.blue;
10699
10732
  const currentModeColor = currentMode?.metadata?.color;
@@ -10851,6 +10884,13 @@ function updateStatusLine(state) {
10851
10884
  if (ref) {
10852
10885
  parts.push({ plain: ref, styled: ref });
10853
10886
  }
10887
+ if (state.tokensPerSec > 0) {
10888
+ const tpsLabel = `${state.tokensPerSec} tok/s`;
10889
+ parts.push({
10890
+ plain: tpsLabel,
10891
+ styled: theme.fg("dim", tpsLabel)
10892
+ });
10893
+ }
10854
10894
  if (opts.showQueue && queuedLabel) {
10855
10895
  parts.push({
10856
10896
  plain: queuedLabel,
@@ -11027,7 +11067,7 @@ function deserializePack(input) {
11027
11067
  }
11028
11068
  }
11029
11069
  async function selectModel(ctx, title, modeColor, currentModelId) {
11030
- const availableModels = await ctx.state.harness.listAvailableModels();
11070
+ const availableModels = await ctx.state.controller.listAvailableModels();
11031
11071
  if (availableModels.length === 0) return void 0;
11032
11072
  return new Promise((resolve3) => {
11033
11073
  const selector = new ModelSelectorComponent({
@@ -11262,8 +11302,8 @@ function removeCustomPackFromSettings(settings, packId) {
11262
11302
  }
11263
11303
  }
11264
11304
  async function applyPack(ctx, pack, previousPackId) {
11265
- const harness = ctx.state.harness;
11266
- const modes = harness.listModes();
11305
+ const controller = ctx.state.controller;
11306
+ const modes = controller.listModes();
11267
11307
  for (const mode of modes) {
11268
11308
  const modelId = pack.models[mode.id];
11269
11309
  if (modelId) {
@@ -11416,8 +11456,8 @@ async function askImportCollision(ctx, existingName) {
11416
11456
  });
11417
11457
  }
11418
11458
  async function handleModelsPackCommand(ctx) {
11419
- const harness = ctx.state.harness;
11420
- const models = await harness.listAvailableModels();
11459
+ const controller = ctx.state.controller;
11460
+ const models = await controller.listAvailableModels();
11421
11461
  const hasEnv = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
11422
11462
  const accessLevel = (storageProviderId) => {
11423
11463
  const cred = ctx.authStorage?.get(storageProviderId);
@@ -11843,7 +11883,7 @@ var BUILT_IN_SUBAGENT_TYPES = [
11843
11883
  }
11844
11884
  ];
11845
11885
  async function showSubagentModelListForScope(ctx, scope, agentType, agentTypeLabel) {
11846
- const availableModels = await ctx.state.harness.listAvailableModels();
11886
+ const availableModels = await ctx.state.controller.listAvailableModels();
11847
11887
  if (availableModels.length === 0) {
11848
11888
  ctx.showInfo("No models available. Check your Mastra configuration.");
11849
11889
  return;
@@ -11911,8 +11951,8 @@ async function showSubagentScopeThenList(ctx, agentType, agentTypeLabel) {
11911
11951
  }
11912
11952
  }
11913
11953
  function getConfiguredSubagentTypes(ctx) {
11914
- const harnessWithConfig = ctx.state.harness;
11915
- const configuredSubagents = harnessWithConfig.config?.subagents;
11954
+ const controllerWithConfig = ctx.state.controller;
11955
+ const configuredSubagents = controllerWithConfig.config?.subagents;
11916
11956
  return configuredSubagents && configuredSubagents.length > 0 ? configuredSubagents.map((subagent) => ({
11917
11957
  id: subagent.id,
11918
11958
  label: subagent.name,
@@ -12231,15 +12271,15 @@ function persistOmObserveAttachments(value) {
12231
12271
  saveSettings(settings);
12232
12272
  }
12233
12273
  async function handleOMCommand(ctx) {
12234
- const availableModels = await ctx.state.harness.listAvailableModels();
12235
- const harnessState = ctx.state.session.state.get();
12274
+ const availableModels = await ctx.state.controller.listAvailableModels();
12275
+ const agentControllerState = ctx.state.session.state.get();
12236
12276
  const config = {
12237
12277
  observerModelId: ctx.state.session.om.observer.modelId() ?? "",
12238
12278
  reflectorModelId: ctx.state.session.om.reflector.modelId() ?? "",
12239
12279
  observationThreshold: ctx.state.session.om.observer.threshold() ?? 3e4,
12240
12280
  reflectionThreshold: ctx.state.session.om.reflector.threshold() ?? 4e4,
12241
- cavemanObservations: harnessState?.cavemanObservations ?? false,
12242
- observeAttachments: harnessState?.observeAttachments ?? "auto"
12281
+ cavemanObservations: agentControllerState?.cavemanObservations ?? false,
12282
+ observeAttachments: agentControllerState?.observeAttachments ?? "auto"
12243
12283
  };
12244
12284
  return new Promise((resolve3) => {
12245
12285
  const settings = new OMSettingsComponent(
@@ -12648,12 +12688,17 @@ var SettingsComponent = class extends Box {
12648
12688
  this.settingsList.handleInput(data);
12649
12689
  }
12650
12690
  };
12691
+ function getAuthProviderId(provider) {
12692
+ return provider === "openai" ? "openai-codex" : provider;
12693
+ }
12651
12694
  function getProviderList(ctx, models) {
12652
12695
  const seen = /* @__PURE__ */ new Map();
12653
12696
  for (const model of models) {
12654
12697
  if (seen.has(model.provider)) continue;
12655
12698
  let source = "none";
12656
- if (ctx.authStorage?.hasStoredApiKey(model.provider)) {
12699
+ if (ctx.authStorage?.isLoggedIn(getAuthProviderId(model.provider))) {
12700
+ source = "oauth";
12701
+ } else if (ctx.authStorage?.hasStoredApiKey(model.provider)) {
12657
12702
  source = "stored";
12658
12703
  } else if (model.apiKeyEnvVar && process.env[model.apiKeyEnvVar]) {
12659
12704
  source = "env";
@@ -12670,6 +12715,8 @@ function getProviderList(ctx, models) {
12670
12715
  }
12671
12716
  function statusLabel(info) {
12672
12717
  switch (info.source) {
12718
+ case "oauth":
12719
+ return theme.fg("success", "\u2713") + theme.fg("dim", " (oauth)");
12673
12720
  case "env":
12674
12721
  return theme.fg("success", "\u2713") + theme.fg("dim", " (env)");
12675
12722
  case "stored":
@@ -12685,7 +12732,7 @@ function buildItems(providers) {
12685
12732
  }));
12686
12733
  }
12687
12734
  async function handleApiKeysCommand(ctx) {
12688
- let models = await ctx.state.harness.listAvailableModels();
12735
+ let models = await ctx.state.controller.listAvailableModels();
12689
12736
  let providers = getProviderList(ctx, models);
12690
12737
  if (providers.length === 0) {
12691
12738
  ctx.showInfo("No model providers found.");
@@ -12700,7 +12747,9 @@ async function handleApiKeysCommand(ctx) {
12700
12747
  const updateDetail = (providerName) => {
12701
12748
  const info = providers.find((p) => p.provider === providerName);
12702
12749
  if (!info) return;
12703
- if (info.source === "env") {
12750
+ if (info.source === "oauth") {
12751
+ detailText.setText(theme.fg("dim", " Authenticated via OAuth. Press Enter to add an API key override."));
12752
+ } else if (info.source === "env") {
12704
12753
  detailText.setText(
12705
12754
  theme.fg("dim", " Key set via environment variable. Press Enter to store a local override.")
12706
12755
  );
@@ -12738,8 +12787,8 @@ async function handleApiKeysCommand(ctx) {
12738
12787
  if (info.envVar && process.env[info.envVar] === storedKey) {
12739
12788
  delete process.env[info.envVar];
12740
12789
  }
12741
- ctx.state.harness.invalidateAvailableModelsCache();
12742
- models = await ctx.state.harness.listAvailableModels();
12790
+ ctx.state.controller.invalidateAvailableModelsCache();
12791
+ models = await ctx.state.controller.listAvailableModels();
12743
12792
  providers = getProviderList(ctx, models);
12744
12793
  ctx.showInfo(`API key removed for ${info.provider}`);
12745
12794
  rebuildList();
@@ -13070,18 +13119,18 @@ Using the same profile across different providers can cause compatibility issues
13070
13119
  ]);
13071
13120
  return proceed === "Yes";
13072
13121
  }
13073
- function resolveModeAgent(mode, harnessState) {
13122
+ function resolveModeAgent(mode, agentControllerState) {
13074
13123
  const modeAgent = mode.agent;
13075
- return typeof modeAgent === "function" ? modeAgent(harnessState) : modeAgent;
13124
+ return typeof modeAgent === "function" ? modeAgent(agentControllerState) : modeAgent;
13076
13125
  }
13077
13126
  function applyBrowserToAgents(ctx, browser, browserSettings) {
13078
- const modes = ctx.harness.listModes();
13079
- let harnessState;
13127
+ const modes = ctx.controller.listModes();
13128
+ let agentControllerState;
13080
13129
  for (const mode of modes) {
13081
- const agent = resolveModeAgent(mode, harnessState ??= ctx.state.session.state.get());
13130
+ const agent = resolveModeAgent(mode, agentControllerState ??= ctx.state.session.state.get());
13082
13131
  agent?.setBrowser?.(browser);
13083
13132
  }
13084
- ctx.harness.setBrowser?.(browser);
13133
+ ctx.controller.setBrowser?.(browser);
13085
13134
  void ctx.state.session.state.set({ [ACTIVE_BROWSER_KEY]: browserSettings });
13086
13135
  }
13087
13136
  function getBrowserConfigKey(settings) {
@@ -14336,7 +14385,7 @@ async function handleFeedbackCommand(ctx, args) {
14336
14385
  ctx.showError("No active session to attach feedback to.");
14337
14386
  return;
14338
14387
  }
14339
- const mastra2 = ctx.harness.getMastra();
14388
+ const mastra2 = ctx.controller.getMastra();
14340
14389
  const observability = mastra2?.observability;
14341
14390
  if (!observability?.addFeedback) {
14342
14391
  ctx.showError("Observability not configured \u2014 cannot save feedback.");
@@ -14970,8 +15019,8 @@ async function handleGoalSourceCommand(state, sourceName, args, ctx) {
14970
15019
  if (goalSkill) {
14971
15020
  try {
14972
15021
  let workspace = ctx.getResolvedWorkspace();
14973
- if (!workspace && ctx.harness?.hasWorkspace?.()) {
14974
- workspace = await ctx.harness.resolveWorkspace({ session: ctx.state.session });
15022
+ if (!workspace && ctx.controller?.hasWorkspace?.()) {
15023
+ workspace = await ctx.controller.resolveWorkspace({ session: ctx.state.session });
14975
15024
  }
14976
15025
  const skill = await workspace?.skills?.get(goalSkill.path || goalSkill.name);
14977
15026
  if (!skill || skill.metadata?.goal !== true) {
@@ -14999,7 +15048,7 @@ async function handleCustomSlashCommand(state, command, args, ctx, displayText)
14999
15048
  try {
15000
15049
  const processedContent = await processSlashCommand(command, args, process.cwd());
15001
15050
  if (processedContent.trim()) {
15002
- const commandCtx = { ...ctx, state, harness: ctx.harness ?? state.harness };
15051
+ const commandCtx = { ...ctx, state, controller: ctx.controller ?? state.controller };
15003
15052
  if (!isCurrentThreadActive(commandCtx)) {
15004
15053
  const slashComp = new SlashCommandComponent(command.name, processedContent.trim());
15005
15054
  state.allSlashCommandComponents.push(slashComp);
@@ -15145,7 +15194,10 @@ function handleAgentAborted(ctx) {
15145
15194
  if (state.gradientAnimator) {
15146
15195
  state.gradientAnimator.fadeOut();
15147
15196
  }
15148
- if (state.streamingComponent && state.streamingMessage) {
15197
+ if (state.planRejectionAbort) {
15198
+ state.streamingComponent = void 0;
15199
+ state.streamingMessage = void 0;
15200
+ } else if (state.streamingComponent && state.streamingMessage) {
15149
15201
  state.streamingMessage.stopReason = "aborted";
15150
15202
  state.streamingMessage.errorMessage = "Interrupted";
15151
15203
  state.streamingComponent.updateContent(state.streamingMessage);
@@ -15155,6 +15207,7 @@ function handleAgentAborted(ctx) {
15155
15207
  showError(state, "Interrupted");
15156
15208
  }
15157
15209
  state.userInitiatedAbort = false;
15210
+ state.planRejectionAbort = false;
15158
15211
  if (state.activeGoalJudge) {
15159
15212
  removeJudgeComponent(state, state.activeGoalJudge.component);
15160
15213
  state.activeGoalJudge = void 0;
@@ -16014,7 +16067,7 @@ ${theme.fg("dim", `Reason: ${reason}`)}`,
16014
16067
  ctx.notify("sandbox_access", `Sandbox access requested: ${requestedPath}`);
16015
16068
  });
16016
16069
  }
16017
- async function approvePlan(ctx, toolCallId, title, plan) {
16070
+ async function approvePlan(ctx, toolCallId, title, plan, planPath, submittedPath) {
16018
16071
  const { state } = ctx;
16019
16072
  await state.session.state.set({
16020
16073
  activePlan: {
@@ -16023,15 +16076,19 @@ async function approvePlan(ctx, toolCallId, title, plan) {
16023
16076
  approvedAt: (/* @__PURE__ */ new Date()).toISOString()
16024
16077
  }
16025
16078
  });
16026
- savePlanToDisk({
16027
- title,
16028
- plan,
16029
- resourceId: state.session.identity.getResourceId()
16030
- }).catch(() => {
16031
- });
16079
+ if (planPath) {
16080
+ await approvePlanFile({
16081
+ planPath,
16082
+ title,
16083
+ resourceId: state.session.identity.getResourceId()
16084
+ }).catch(() => {
16085
+ });
16086
+ }
16087
+ state.previousPlanSnapshot = void 0;
16088
+ state.lastSubmitPlanComponent = void 0;
16032
16089
  await state.session.respondToToolSuspension({
16033
16090
  toolCallId,
16034
- resumeData: { action: "approved" }
16091
+ resumeData: { action: "approved", path: submittedPath, title, plan }
16035
16092
  });
16036
16093
  }
16037
16094
  function formatPlanGoalObjective(title, plan) {
@@ -16039,34 +16096,42 @@ function formatPlanGoalObjective(title, plan) {
16039
16096
 
16040
16097
  ${plan}`;
16041
16098
  }
16042
- async function handlePlanApproval(ctx, toolCallId, title, plan) {
16099
+ async function handlePlanApproval(ctx, toolCallId, submittedPath) {
16043
16100
  const { state } = ctx;
16044
- const previousPlan = state.previousPlanSnapshot?.plan;
16045
- state.previousPlanSnapshot = { title, plan };
16046
16101
  const projectPath = state.session.state.get()?.projectPath;
16047
- if (projectPath) {
16048
- savePlanSnapshot({ title, plan, projectPath }).catch(() => {
16049
- });
16102
+ const planPath = submittedPath ? resolvePlanPath(projectPath ?? process.cwd(), submittedPath) : void 0;
16103
+ const current = planPath ? await readPlanFile(planPath) : void 0;
16104
+ if (!current) {
16105
+ state.previousPlanSnapshot = void 0;
16106
+ }
16107
+ const plan = current?.plan ?? `\u26A0\uFE0F Could not read the plan file at \`${submittedPath}\`. Make sure it exists before submitting it.`;
16108
+ const resolvedTitle = current?.title || "Implementation Plan";
16109
+ const snapshotKey = submittedPath;
16110
+ const snapshot = state.previousPlanSnapshot;
16111
+ const snapshotPlan = snapshot && snapshot.path === snapshotKey ? snapshot.plan : void 0;
16112
+ const previousPlan = snapshotPlan && shouldShowDiff(snapshotPlan, plan) ? snapshotPlan : void 0;
16113
+ if (current) {
16114
+ state.previousPlanSnapshot = { path: snapshotKey, plan };
16050
16115
  }
16051
16116
  return new Promise((resolve3) => {
16052
- const planFilename = getPlanFilename(title);
16117
+ const planFilename = snapshotKey;
16053
16118
  const approvalOptions = {
16054
16119
  toolCallId,
16055
- title,
16120
+ title: resolvedTitle,
16056
16121
  plan,
16057
16122
  planFilename,
16058
16123
  previousPlan,
16059
16124
  onApprove: async () => {
16060
16125
  state.activeInlinePlanApproval = void 0;
16061
16126
  state.ui.setFocus(state.editor);
16062
- await approvePlan(ctx, toolCallId, title, plan);
16127
+ await approvePlan(ctx, toolCallId, resolvedTitle, plan, planPath, snapshotKey);
16063
16128
  resolve3();
16064
16129
  },
16065
16130
  onGoal: async () => {
16066
16131
  state.activeInlinePlanApproval = void 0;
16067
16132
  state.ui.setFocus(state.editor);
16068
- await approvePlan(ctx, toolCallId, title, plan);
16069
- const objective = formatPlanGoalObjective(title, plan);
16133
+ await approvePlan(ctx, toolCallId, resolvedTitle, plan, planPath, snapshotKey);
16134
+ const objective = formatPlanGoalObjective(resolvedTitle, plan);
16070
16135
  await ctx.startGoal(objective, "Goal cancelled.");
16071
16136
  const goal = state.goalManager.getGoal();
16072
16137
  if (goal?.id) {
@@ -16074,13 +16139,20 @@ async function handlePlanApproval(ctx, toolCallId, title, plan) {
16074
16139
  }
16075
16140
  resolve3();
16076
16141
  },
16077
- onReject: async () => {
16142
+ onReject: () => {
16078
16143
  state.activeInlinePlanApproval = void 0;
16079
16144
  state.ui.setFocus(state.editor);
16080
- await state.session.respondToToolSuspension({
16081
- toolCallId,
16082
- resumeData: { action: "rejected" }
16083
- });
16145
+ void (async () => {
16146
+ try {
16147
+ await state.session.respondToToolSuspension({
16148
+ toolCallId,
16149
+ resumeData: { action: "rejected", path: snapshotKey, title: resolvedTitle, plan }
16150
+ });
16151
+ } finally {
16152
+ state.planRejectionAbort = true;
16153
+ state.session.abort();
16154
+ }
16155
+ })();
16084
16156
  resolve3();
16085
16157
  }
16086
16158
  };
@@ -16110,7 +16182,7 @@ async function handlePlanApproval(ctx, toolCallId, title, plan) {
16110
16182
  state.ui.requestRender();
16111
16183
  state.chatContainer.invalidate();
16112
16184
  state.ui.setFocus(approvalComponent);
16113
- ctx.notify("plan_approval", `Plan "${title}" requires approval`);
16185
+ ctx.notify("plan_approval", `Plan "${resolvedTitle}" requires approval`);
16114
16186
  });
16115
16187
  }
16116
16188
 
@@ -16800,7 +16872,7 @@ function createTUIState(options) {
16800
16872
  const editor = new CustomEditor(ui, getEditorTheme());
16801
16873
  const result = {
16802
16874
  // Core dependencies
16803
- harness: options.harness,
16875
+ controller: options.controller,
16804
16876
  session: options.session,
16805
16877
  options,
16806
16878
  hookManager: options.hookManager,
@@ -16855,6 +16927,9 @@ function createTUIState(options) {
16855
16927
  projectInfo: detectProject(process.cwd()),
16856
16928
  modelAuthStatus: { hasAuth: true },
16857
16929
  githubPrPollingActive: false,
16930
+ // Tokens/sec tracking
16931
+ decodeStartedAt: 0,
16932
+ tokensPerSec: 0,
16858
16933
  // Goal loop
16859
16934
  goalManager: new GoalManager(),
16860
16935
  planStartedGoalId: void 0,
@@ -16865,7 +16940,8 @@ function createTUIState(options) {
16865
16940
  pendingImages: [],
16866
16941
  // Abort tracking
16867
16942
  lastCtrlCTime: 0,
16868
- userInitiatedAbort: false
16943
+ userInitiatedAbort: false,
16944
+ planRejectionAbort: false
16869
16945
  };
16870
16946
  editor.getModeColor = () => {
16871
16947
  if (result.activeGoalJudge) {
@@ -16884,9 +16960,12 @@ function trackInteractivePrompt(ectx, promptType, properties) {
16884
16960
  async function dispatchEvent(event, ectx, state) {
16885
16961
  switch (event.type) {
16886
16962
  case "agent_start":
16963
+ state.tokensPerSec = 0;
16964
+ state.decodeStartedAt = 0;
16887
16965
  handleAgentStart(ectx);
16888
16966
  break;
16889
16967
  case "agent_end":
16968
+ state.decodeStartedAt = 0;
16890
16969
  if (event.reason === "aborted") {
16891
16970
  handleAgentAborted(ectx);
16892
16971
  } else if (event.reason === "error") {
@@ -16899,6 +16978,9 @@ async function dispatchEvent(event, ectx, state) {
16899
16978
  handleMessageStart(ectx, event.message);
16900
16979
  break;
16901
16980
  case "message_update":
16981
+ if (state.decodeStartedAt === 0) {
16982
+ state.decodeStartedAt = Date.now();
16983
+ }
16902
16984
  handleMessageUpdate(ectx, event.message);
16903
16985
  break;
16904
16986
  case "message_end":
@@ -16961,7 +17043,7 @@ async function dispatchEvent(event, ectx, state) {
16961
17043
  }
16962
17044
  state.taskToolInsertIndex = -1;
16963
17045
  await ectx.renderExistingMessages();
16964
- await state.harness.loadOMProgress(state.session);
17046
+ await state.controller.loadOMProgress(state.session);
16965
17047
  getCurrentGitBranchAsync(state.projectInfo.rootPath).then((freshBranch) => {
16966
17048
  if (freshBranch) {
16967
17049
  state.projectInfo.gitBranch = freshBranch;
@@ -17010,8 +17092,23 @@ async function dispatchEvent(event, ectx, state) {
17010
17092
  state.taskToolInsertIndex = -1;
17011
17093
  break;
17012
17094
  }
17013
- case "usage_update":
17095
+ case "usage_update": {
17096
+ const now = Date.now();
17097
+ const stepTokens = (event.usage.completionTokens ?? 0) + (event.usage.reasoningTokens ?? 0);
17098
+ if (state.decodeStartedAt > 0 && stepTokens > 0) {
17099
+ const decodeSec = (now - state.decodeStartedAt) / 1e3;
17100
+ if (decodeSec > 0) {
17101
+ const instantaneous = stepTokens / decodeSec;
17102
+ const alpha = 0.3;
17103
+ const ema = state.tokensPerSec > 0 ? alpha * instantaneous + (1 - alpha) * state.tokensPerSec : instantaneous;
17104
+ state.tokensPerSec = Math.round(ema);
17105
+ }
17106
+ }
17107
+ state.decodeStartedAt = 0;
17108
+ ectx.updateStatusLine();
17109
+ state.ui.requestRender();
17014
17110
  break;
17111
+ }
17015
17112
  // Observational Memory events
17016
17113
  case "om_status":
17017
17114
  break;
@@ -17152,7 +17249,7 @@ async function dispatchEvent(event, ectx, state) {
17152
17249
  payload.selectionMode
17153
17250
  );
17154
17251
  } else if (event.toolName === "submit_plan") {
17155
- await handlePlanApproval(ectx, event.toolCallId, String(payload.title ?? ""), String(payload.plan ?? ""));
17252
+ await handlePlanApproval(ectx, event.toolCallId, String(payload.path ?? ""));
17156
17253
  }
17157
17254
  break;
17158
17255
  }
@@ -17565,7 +17662,7 @@ function setupKeyboardShortcuts(state, callbacks) {
17565
17662
  showInfo(state, "Resolve the plan approval first");
17566
17663
  return;
17567
17664
  }
17568
- const modes = state.harness.listModes();
17665
+ const modes = state.controller.listModes();
17569
17666
  if (modes.length <= 1) return;
17570
17667
  const currentId = state.session.mode.get();
17571
17668
  const currentIndex = modes.findIndex((m) => m.id === currentId);
@@ -17634,7 +17731,7 @@ function buildLayout(state, refreshModelAuthStatus) {
17634
17731
  ].filter(Boolean).map((line) => theme.fg("muted", line)).join("\n");
17635
17732
  const sep = theme.fg("dim", " \xB7 ");
17636
17733
  const hintParts = [];
17637
- if (state.harness.listModes().length > 1) {
17734
+ if (state.controller.listModes().length > 1) {
17638
17735
  hintParts.push(`${theme.fg("accent", "\u21E7+Tab")} ${theme.fg("muted", "cycle modes")}`);
17639
17736
  }
17640
17737
  hintParts.push(`${theme.fg("accent", "/help")} ${theme.fg("muted", "info & shortcuts")}`);
@@ -17749,7 +17846,7 @@ function setupAutocomplete(state) {
17749
17846
  { name: "exit", description: "Exit the TUI" },
17750
17847
  { name: "help", description: "Show available commands" }
17751
17848
  ];
17752
- const modes = state.harness.listModes();
17849
+ const modes = state.controller.listModes();
17753
17850
  if (modes.length > 1) {
17754
17851
  slashCommands.push({ name: "mode", description: "Switch agent mode" });
17755
17852
  }
@@ -17800,9 +17897,9 @@ async function loadCustomSlashCommands(state) {
17800
17897
  }
17801
17898
  async function loadSkillCommands(state) {
17802
17899
  try {
17803
- let workspace = state.harness.getWorkspace() ?? state.workspace;
17804
- if (!workspace && state.harness.hasWorkspace()) {
17805
- workspace = await state.harness.resolveWorkspace({ session: state.session });
17900
+ let workspace = state.controller.getWorkspace() ?? state.workspace;
17901
+ if (!workspace && state.controller.hasWorkspace()) {
17902
+ workspace = await state.controller.resolveWorkspace({ session: state.session });
17806
17903
  }
17807
17904
  if (!workspace?.skills) {
17808
17905
  state.skillCommands = [];
@@ -17849,7 +17946,7 @@ function setupKeyHandlers(state, callbacks) {
17849
17946
  process.off("SIGINT", sigintHandler);
17850
17947
  };
17851
17948
  }
17852
- function subscribeToHarness(state, handleEvent) {
17949
+ function subscribeToAgentController(state, handleEvent) {
17853
17950
  let eventQueue = Promise.resolve();
17854
17951
  const listener = (event) => {
17855
17952
  eventQueue = eventQueue.then(async () => {
@@ -18348,7 +18445,7 @@ var MastraTUI = class _MastraTUI {
18348
18445
  }
18349
18446
  /**
18350
18447
  * Fire off a message without blocking the main loop.
18351
- * Errors are handled via harness events.
18448
+ * Errors are handled via controller events.
18352
18449
  */
18353
18450
  fireMessage(content, images) {
18354
18451
  this.clearIdleCounter();
@@ -18516,8 +18613,8 @@ var MastraTUI = class _MastraTUI {
18516
18613
  // ===========================================================================
18517
18614
  async init() {
18518
18615
  if (this.state.isInitialized) return;
18519
- await this.state.harness.init();
18520
- await this.state.harness.getMastra()?.startWorkers();
18616
+ await this.state.controller.init();
18617
+ await this.state.controller.getMastra()?.startWorkers();
18521
18618
  await loadCustomSlashCommands(this.state);
18522
18619
  setupAutocomplete(this.state);
18523
18620
  refreshSkillsAutocomplete(this.state).catch((err) => {
@@ -18532,12 +18629,12 @@ var MastraTUI = class _MastraTUI {
18532
18629
  });
18533
18630
  this.state.ui.start();
18534
18631
  await promptForThreadSelection(this.state);
18535
- subscribeToHarness(this.state, (event) => this.handleEvent(event));
18632
+ subscribeToAgentController(this.state, (event) => this.handleEvent(event));
18536
18633
  const escState = this.state.session.state.get();
18537
18634
  if (escState?.escapeAsCancel === false) {
18538
18635
  this.state.editor.escapeEnabled = false;
18539
18636
  }
18540
- await this.state.harness.loadOMProgress(this.state.session);
18637
+ await this.state.controller.loadOMProgress(this.state.session);
18541
18638
  await syncInitialThreadState(this.state);
18542
18639
  this.state.isInitialized = true;
18543
18640
  if (this.state.mcpManager?.hasServers()) {
@@ -18569,7 +18666,7 @@ var MastraTUI = class _MastraTUI {
18569
18666
  }, UPDATE_RECHECK_INTERVAL_MS);
18570
18667
  }
18571
18668
  async refreshModelAuthStatus() {
18572
- this.state.modelAuthStatus = await this.state.harness.getCurrentModelAuthStatus(this.state.session);
18669
+ this.state.modelAuthStatus = await this.state.controller.getCurrentModelAuthStatus(this.state.session);
18573
18670
  updateStatusLine(this.state);
18574
18671
  }
18575
18672
  startIdleCounter(idleStartedAt = Date.now(), now = Date.now()) {
@@ -18631,7 +18728,7 @@ var MastraTUI = class _MastraTUI {
18631
18728
  }
18632
18729
  try {
18633
18730
  await dispatchEvent(event, this.getEventContext(), this.state);
18634
- this.captureHarnessAnalytics(event);
18731
+ this.captureAgentControllerAnalytics(event);
18635
18732
  if (event.type === "thread_created") {
18636
18733
  await this.syncThreadActivePackMetadata(event.thread);
18637
18734
  } else if (event.type === "thread_changed") {
@@ -18652,7 +18749,7 @@ var MastraTUI = class _MastraTUI {
18652
18749
  }
18653
18750
  }
18654
18751
  }
18655
- captureHarnessAnalytics(event) {
18752
+ captureAgentControllerAnalytics(event) {
18656
18753
  const analytics = this.state.analytics;
18657
18754
  if (!analytics) {
18658
18755
  return;
@@ -18688,12 +18785,12 @@ var MastraTUI = class _MastraTUI {
18688
18785
  }
18689
18786
  }
18690
18787
  emitErrorFeedback(errorMessage) {
18691
- const harness = this.state.harness;
18788
+ const controller = this.state.controller;
18692
18789
  const traceId = this.state.session.run.getTraceId() ?? void 0;
18693
18790
  const runId = this.state.session.getCurrentRunId() ?? void 0;
18694
18791
  const threadId = this.state.session.thread.getId() ?? void 0;
18695
18792
  if (!traceId && !runId && !threadId) return;
18696
- const mastra2 = harness.getMastra();
18793
+ const mastra2 = controller.getMastra();
18697
18794
  const observability = mastra2?.observability;
18698
18795
  if (!observability?.addFeedback) return;
18699
18796
  const comment = errorMessage.length > 500 ? errorMessage.slice(0, 500) + "\u2026" : errorMessage;
@@ -18747,7 +18844,7 @@ var MastraTUI = class _MastraTUI {
18747
18844
  child.kill();
18748
18845
  }
18749
18846
  async buildProviderAccess() {
18750
- const models = await this.state.harness.listAvailableModels();
18847
+ const models = await this.state.controller.listAvailableModels();
18751
18848
  const hasEnv = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
18752
18849
  const accessLevel = (storageProviderId) => {
18753
18850
  const cred = this.state.authStorage?.get(storageProviderId);
@@ -18891,10 +18988,10 @@ var MastraTUI = class _MastraTUI {
18891
18988
  });
18892
18989
  }
18893
18990
  /**
18894
- * Get the workspace, preferring harness-owned workspace over the direct option.
18991
+ * Get the workspace, preferring controller-owned workspace over the direct option.
18895
18992
  */
18896
18993
  getResolvedWorkspace() {
18897
- return this.state.harness.getWorkspace() ?? this.state.workspace;
18994
+ return this.state.controller.getWorkspace() ?? this.state.workspace;
18898
18995
  }
18899
18996
  // ===========================================================================
18900
18997
  // Observational Memory Settings
@@ -18908,7 +19005,7 @@ var MastraTUI = class _MastraTUI {
18908
19005
  buildCommandContext() {
18909
19006
  return {
18910
19007
  state: this.state,
18911
- harness: this.state.harness,
19008
+ controller: this.state.controller,
18912
19009
  session: this.state.session,
18913
19010
  hookManager: this.state.hookManager,
18914
19011
  mcpManager: this.state.mcpManager,
@@ -19073,7 +19170,7 @@ var MastraTUI = class _MastraTUI {
19073
19170
  });
19074
19171
  },
19075
19172
  onSelectModel: async (title, modeColor) => {
19076
- const availableModels = await this.state.harness.listAvailableModels();
19173
+ const availableModels = await this.state.controller.listAvailableModels();
19077
19174
  if (availableModels.length === 0) return void 0;
19078
19175
  return new Promise((resolveModel) => {
19079
19176
  const selector = new ModelSelectorComponent({
@@ -19103,9 +19200,9 @@ var MastraTUI = class _MastraTUI {
19103
19200
  });
19104
19201
  }
19105
19202
  async applyOnboardingResult(result) {
19106
- const harness = this.state.harness;
19203
+ const controller = this.state.controller;
19107
19204
  const modePack = result.modePack;
19108
- const modes = harness.listModes();
19205
+ const modes = controller.listModes();
19109
19206
  for (const mode of modes) {
19110
19207
  const modelId = modePack.models[mode.id];
19111
19208
  if (modelId) {
@@ -19339,5 +19436,5 @@ Would you like to update now?`;
19339
19436
  };
19340
19437
 
19341
19438
  export { AssistantMessageComponent, LoginDialogComponent, LoginSelectorComponent, MastraTUI, ModelSelectorComponent, OMProgressComponent, ToolExecutionComponentEnhanced, UserMessageComponent, createTUIState, detectTerminalTheme, formatOMStatus, getCurrentVersion };
19342
- //# sourceMappingURL=chunk-L67CU2OP.js.map
19343
- //# sourceMappingURL=chunk-L67CU2OP.js.map
19439
+ //# sourceMappingURL=chunk-LUSQ75XP.js.map
19440
+ //# sourceMappingURL=chunk-LUSQ75XP.js.map