mastracode 0.47.2-alpha.2 → 0.48.0-alpha.3

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 (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/cli.cjs +114 -8
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +114 -8
  5. package/dist/cli.js.map +1 -1
  6. package/dist/docs/SKILL.md +1 -1
  7. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  8. package/dist/tui/command-dispatch.d.ts.map +1 -1
  9. package/dist/tui/commands/context.d.ts.map +1 -1
  10. package/dist/tui/commands/factory.d.ts +8 -0
  11. package/dist/tui/commands/factory.d.ts.map +1 -0
  12. package/dist/tui/commands/feedback.d.ts.map +1 -1
  13. package/dist/tui/commands/goal.d.ts.map +1 -1
  14. package/dist/tui/commands/index.d.ts +1 -0
  15. package/dist/tui/commands/index.d.ts.map +1 -1
  16. package/dist/tui/commands/models.d.ts.map +1 -1
  17. package/dist/tui/commands/om.d.ts.map +1 -1
  18. package/dist/tui/commands/permissions.d.ts.map +1 -1
  19. package/dist/tui/commands/prune.d.ts.map +1 -1
  20. package/dist/tui/commands/resource.d.ts.map +1 -1
  21. package/dist/tui/commands/skills.d.ts.map +1 -1
  22. package/dist/tui/commands/subagents.d.ts.map +1 -1
  23. package/dist/tui/commands/types.d.ts +4 -3
  24. package/dist/tui/commands/types.d.ts.map +1 -1
  25. package/dist/tui/commands/workflows.d.ts.map +1 -1
  26. package/dist/tui/components/help-overlay.d.ts.map +1 -1
  27. package/dist/tui/components/simple-select.d.ts +4 -0
  28. package/dist/tui/components/simple-select.d.ts.map +1 -0
  29. package/dist/tui/display.d.ts.map +1 -1
  30. package/dist/tui/mastra-tui.d.ts.map +1 -1
  31. package/dist/tui/model-packs/apply.d.ts +1 -1
  32. package/dist/tui/model-packs/apply.d.ts.map +1 -1
  33. package/dist/tui/runtime.d.ts +54 -0
  34. package/dist/tui/runtime.d.ts.map +1 -0
  35. package/dist/tui/setup.d.ts.map +1 -1
  36. package/dist/tui/state.d.ts +22 -3
  37. package/dist/tui/state.d.ts.map +1 -1
  38. package/dist/tui/status-line.d.ts.map +1 -1
  39. package/dist/tui/thread-startup.d.ts.map +1 -1
  40. package/dist/{tui-B5689bGt.cjs → tui-BpzUjTFI.cjs} +417 -82
  41. package/dist/tui-BpzUjTFI.cjs.map +1 -0
  42. package/dist/{tui-CLkPtYAM.js → tui-DCld2CJR.js} +417 -82
  43. package/dist/tui-DCld2CJR.js.map +1 -0
  44. package/dist/tui.cjs +1 -1
  45. package/dist/tui.js +1 -1
  46. package/package.json +9 -9
  47. package/dist/tui-B5689bGt.cjs.map +0 -1
  48. package/dist/tui-CLkPtYAM.js.map +0 -1
@@ -20,10 +20,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
20
20
  enumerable: true
21
21
  }) : target, mod));
22
22
  //#endregion
23
+ let child_process = require("child_process");
23
24
  let fs = require("fs");
24
25
  fs = __toESM(fs, 1);
25
26
  let _mastra_code_sdk_onboarding_pack_activation = require("@mastra/code-sdk/onboarding/pack-activation");
26
27
  let _mastra_code_sdk_onboarding_settings = require("@mastra/code-sdk/onboarding/settings");
28
+ let _mastra_code_sdk_platform_index = require("@mastra/code-sdk/platform/index");
27
29
  let _mastra_code_sdk_utils_thread_lock = require("@mastra/code-sdk/utils/thread-lock");
28
30
  let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
29
31
  let os = require("os");
@@ -33,7 +35,6 @@ path = __toESM(path, 1);
33
35
  let _mastra_code_sdk_theme_palette = require("@mastra/code-sdk/theme-palette");
34
36
  let chalk = require("chalk");
35
37
  chalk = __toESM(chalk, 1);
36
- let child_process = require("child_process");
37
38
  let _mastra_code_sdk_auth_account_rotation_processor = require("@mastra/code-sdk/auth/account-rotation-processor");
38
39
  let _mastra_code_sdk_auth_storage = require("@mastra/code-sdk/auth/storage");
39
40
  let _mastra_code_sdk_onboarding_index = require("@mastra/code-sdk/onboarding/index");
@@ -80,6 +81,7 @@ let fs_promises = require("fs/promises");
80
81
  let _mastra_code_sdk_providers_model_ids = require("@mastra/code-sdk/providers/model-ids");
81
82
  let _mastra_stagehand = require("@mastra/stagehand");
82
83
  let _mastra_code_sdk_auth_index = require("@mastra/code-sdk/auth/index");
84
+ let _mastra_code_sdk_factory_client = require("@mastra/code-sdk/factory/client");
83
85
  let _mastra_core_llm = require("@mastra/core/llm");
84
86
  let _mastra_code_sdk_utils_project = require("@mastra/code-sdk/utils/project");
85
87
  let _mastra_github_signals = require("@mastra/github-signals");
@@ -3039,6 +3041,57 @@ function promptForApiKeyIfNeeded(ui, model, authStorage) {
3039
3041
  });
3040
3042
  }
3041
3043
  //#endregion
3044
+ //#region src/tui/runtime.ts
3045
+ /**
3046
+ * Returns the in-process runtime for local-only features. The command
3047
+ * dispatcher keeps local-only commands from running in Factory mode, so
3048
+ * reaching this without a local runtime is a bug.
3049
+ */
3050
+ function requireLocal(state, feature) {
3051
+ if (!state.local) throw new Error(`${feature} is not available in Factory mode`);
3052
+ return state.local;
3053
+ }
3054
+ const LOCAL_ONLY_COMMANDS = /* @__PURE__ */ new Set([
3055
+ "thread:tag-dir",
3056
+ "sandbox",
3057
+ "workflows",
3058
+ "workflow",
3059
+ "skills",
3060
+ "packs",
3061
+ "models",
3062
+ "custom-providers",
3063
+ "subagents",
3064
+ "memory",
3065
+ "om",
3066
+ "knowledge",
3067
+ "voice",
3068
+ "connect",
3069
+ "login",
3070
+ "logout",
3071
+ "context",
3072
+ "ctx",
3073
+ "prune",
3074
+ "resource",
3075
+ "hooks",
3076
+ "mcp",
3077
+ "setup",
3078
+ "browser",
3079
+ "gateway",
3080
+ "memory-gateway",
3081
+ "api-keys",
3082
+ "plugins",
3083
+ "feedback",
3084
+ "observability",
3085
+ "goal",
3086
+ "schedules",
3087
+ "diff",
3088
+ "github",
3089
+ "settings"
3090
+ ]);
3091
+ function isLocalOnlyCommand(command) {
3092
+ return LOCAL_ONLY_COMMANDS.has(command) || command.startsWith("goal/") || command.startsWith("skill/");
3093
+ }
3094
+ //#endregion
3042
3095
  //#region src/tui/commands/goal.ts
3043
3096
  /**
3044
3097
  * /goal command — persistent cross-turn goals (Ralph loop).
@@ -3070,7 +3123,7 @@ async function handleGoalCommand(ctx, args) {
3070
3123
  ctx.showInfo("No goal to pause.");
3071
3124
  return;
3072
3125
  }
3073
- await goalManager.saveToThread(state);
3126
+ await goalManager.saveToThread(requireLocal(state, "/goal"));
3074
3127
  ctx.updateStatusLine();
3075
3128
  ctx.showInfo(`Goal paused: "${stripControlChars(goal.objective)}" (${goal.turnsUsed}/${goal.maxTurns} turns used). Use /goal resume to continue.`);
3076
3129
  return;
@@ -3090,14 +3143,14 @@ async function handleGoalCommand(ctx, args) {
3090
3143
  return;
3091
3144
  }
3092
3145
  goalManager.resume();
3093
- await goalManager.saveToThread(state);
3146
+ await goalManager.saveToThread(requireLocal(state, "/goal"));
3094
3147
  ctx.updateStatusLine();
3095
3148
  const resumedGoal = goalManager.getGoal();
3096
3149
  try {
3097
3150
  await state.session.sendSignal((0, _mastra_code_sdk_goal_signal.createGoalReminderSignal)(resumedGoal)).accepted;
3098
3151
  } catch (err) {
3099
3152
  goalManager.pause();
3100
- await goalManager.saveToThread(state);
3153
+ await goalManager.saveToThread(requireLocal(state, "/goal"));
3101
3154
  if ((0, _mastra_core_agent_controller.isSessionStartupCancelledError)(err)) {
3102
3155
  ctx.showInfo("Interrupted");
3103
3156
  return;
@@ -3108,8 +3161,8 @@ async function handleGoalCommand(ctx, args) {
3108
3161
  }
3109
3162
  if (subCommand === "clear") {
3110
3163
  goalManager.clear();
3111
- if (!await goalManager.deleteFromThread(state)) {
3112
- if (!await goalManager.loadFromThread(state)) {
3164
+ if (!await goalManager.deleteFromThread(requireLocal(state, "/goal"))) {
3165
+ if (!await goalManager.loadFromThread(requireLocal(state, "/goal"))) {
3113
3166
  ctx.updateStatusLine();
3114
3167
  ctx.showError("Could not clear the goal; it may still be active. Try /goal clear again.");
3115
3168
  return;
@@ -3205,7 +3258,7 @@ async function showGoalActionModal(ctx) {
3205
3258
  async function handleJudgeCommand(ctx) {
3206
3259
  const defaults = await promptForJudgeDefaults(ctx, "Judge settings unchanged.");
3207
3260
  if (!defaults) return;
3208
- if (await ctx.state.goalManager.updateJudgeDefaults(ctx.state, defaults.judgeModelId, defaults.maxTurns)) {
3261
+ if (await ctx.state.goalManager.updateJudgeDefaults(requireLocal(ctx.state, "/goal"), defaults.judgeModelId, defaults.maxTurns)) {
3209
3262
  ctx.showInfo(`Judge defaults set: ${defaults.judgeModelId}, ${defaults.maxTurns} max attempts. Current goal updated.`);
3210
3263
  return;
3211
3264
  }
@@ -3305,7 +3358,7 @@ async function setGoal(ctx, objective, judgeModelId, maxTurns) {
3305
3358
  await state.session.thread.create();
3306
3359
  state.pendingNewThread = false;
3307
3360
  }
3308
- goal = await goalManager.setGoal(state, objective, judgeModelId, maxTurns);
3361
+ goal = await goalManager.setGoal(requireLocal(state, "/goal"), objective, judgeModelId, maxTurns);
3309
3362
  } catch (error) {
3310
3363
  if (shouldPersistToCreatedThread) goalManager.consumePersistOnNextThreadCreate();
3311
3364
  throw error;
@@ -3315,7 +3368,7 @@ async function setGoal(ctx, objective, judgeModelId, maxTurns) {
3315
3368
  ctx.showError("Failed to set goal.");
3316
3369
  return null;
3317
3370
  }
3318
- await goalManager.saveToThread(state);
3371
+ await goalManager.saveToThread(requireLocal(state, "/goal"));
3319
3372
  ctx.updateStatusLine();
3320
3373
  return goal;
3321
3374
  }
@@ -3332,7 +3385,7 @@ async function sendGoalReminder(ctx, goal, options = {}) {
3332
3385
  await (options.persistIfIdle ? state.session.sendSignal(signal, { ifIdle: { behavior: "persist" } }) : state.session.sendSignal(signal)).accepted;
3333
3386
  } catch (err) {
3334
3387
  goalManager.pause();
3335
- await goalManager.saveToThread(state);
3388
+ await goalManager.saveToThread(requireLocal(state, "/goal"));
3336
3389
  if ((0, _mastra_core_agent_controller.isSessionStartupCancelledError)(err)) {
3337
3390
  ctx.showInfo("Interrupted");
3338
3391
  return;
@@ -3477,6 +3530,14 @@ function getCommands(modes) {
3477
3530
  key: "/login",
3478
3531
  description: "Sign in with a provider account"
3479
3532
  },
3533
+ {
3534
+ key: "/factory",
3535
+ description: "Switch to Factory mode (connects first if needed)"
3536
+ },
3537
+ {
3538
+ key: "/local",
3539
+ description: "Switch from Factory mode back to local mode"
3540
+ },
3480
3541
  {
3481
3542
  key: "/logout",
3482
3543
  description: "Logout from OAuth provider"
@@ -3824,7 +3885,8 @@ function requestContextForSession(ctx) {
3824
3885
  */
3825
3886
  async function collectSkillsCatalog(ctx) {
3826
3887
  let workspace = ctx.getResolvedWorkspace();
3827
- if (!workspace && ctx.controller.hasWorkspace()) workspace = await ctx.controller.resolveWorkspace({ session: ctx.state.session });
3888
+ const local = requireLocal(ctx.state, "/context");
3889
+ if (!workspace && local.controller.hasWorkspace()) workspace = await local.controller.resolveWorkspace({ session: local.session });
3828
3890
  if (!workspace?.skills) return void 0;
3829
3891
  const listed = await workspace.skills.list();
3830
3892
  if (listed.length === 0) return void 0;
@@ -4461,7 +4523,7 @@ async function handleVoiceCommand(ctx, args = []) {
4461
4523
  return;
4462
4524
  }
4463
4525
  if (arg === "status") {
4464
- await showStatus$1(ctx);
4526
+ await showStatus$2(ctx);
4465
4527
  return;
4466
4528
  }
4467
4529
  await runMenu(ctx);
@@ -4533,7 +4595,7 @@ function engineLabel(voice) {
4533
4595
  const entry = (0, _mastra_code_sdk_voice_stt_registry.resolveSTTModel)(voice.provider, voice.model);
4534
4596
  return `cloud · ${entry.provider}/${entry.model}`;
4535
4597
  }
4536
- async function showStatus$1(ctx) {
4598
+ async function showStatus$2(ctx) {
4537
4599
  const voice = (0, _mastra_code_sdk_onboarding_settings.loadSettings)().voice;
4538
4600
  const lines = [`Voice input: ${ctx.state.voiceController?.isEnabled() ? "on" : "off"}`, ` Engine: ${engineLabel(voice)}`];
4539
4601
  if (voice.engine === "cloud") {
@@ -4589,7 +4651,7 @@ async function runMenu(ctx) {
4589
4651
  await chooseModel(ctx);
4590
4652
  return;
4591
4653
  case "Status":
4592
- await showStatus$1(ctx);
4654
+ await showStatus$2(ctx);
4593
4655
  return;
4594
4656
  }
4595
4657
  }
@@ -4871,7 +4933,10 @@ async function handlePermissionsCommand(ctx, args) {
4871
4933
  async function showPermissions(ctx) {
4872
4934
  const { TOOL_CATEGORIES, getToolsForCategory } = await import("@mastra/code-sdk/permissions");
4873
4935
  const rules = ctx.state.session.permissions.getRules();
4874
- const grants = ctx.state.session.getGrants();
4936
+ const grants = ctx.state.local?.session.getGrants() ?? {
4937
+ categories: [],
4938
+ tools: []
4939
+ };
4875
4940
  const isYolo = ctx.state.session.state.get()?.yolo === true;
4876
4941
  const lines = [];
4877
4942
  lines.push("Tool Approval Permissions");
@@ -5704,8 +5769,9 @@ function showFormattedError(state, event) {
5704
5769
  }
5705
5770
  const lines = [new _earendil_works_pi_tui.Text(theme.fg("error", errorText), 1, 0)];
5706
5771
  const isObservationalMemoryError = /observational memory|\bOM (?:observation|reflection)/i.test(error.message);
5707
- const omRole = /reflect/i.test(error.message) ? state.session.om.reflector : state.session.om.observer;
5708
- const hint = withOMGuidance(getErrorHint(parsed.type), isObservationalMemoryError ? omRole.modelId() : void 0);
5772
+ const om = state.local?.session.om;
5773
+ const omRole = /reflect/i.test(error.message) ? om?.reflector : om?.observer;
5774
+ const hint = withOMGuidance(state.factory && parsed.type === "auth" ? `Add the provider credential in Factory (${state.factory.url})` : getErrorHint(parsed.type), isObservationalMemoryError ? omRole?.modelId() : void 0);
5709
5775
  if (hint) lines.push(new _earendil_works_pi_tui.Text(theme.fg("muted", ` Hint: ${hint}`), 1, 0));
5710
5776
  const component = new InfoMessageComponent(lines);
5711
5777
  insertChatComponentWithBoundarySpacing(state.chatContainer, component, getInsertIndexBeforePrompt(state));
@@ -6093,7 +6159,7 @@ async function applyPackToSession(ctx, packId, options = {}) {
6093
6159
  await ctx.state.session.model.switch(selection.modelId, { ...options.thinkingLevel !== void 0 ? { thinkingLevel: options.thinkingLevel } : {} });
6094
6160
  if (!application.isCurrent()) return cancelled();
6095
6161
  for (const [agentType, modelId] of Object.entries(selection.subagentModels)) {
6096
- await ctx.state.session.subagents.model.set({
6162
+ await ctx.state.local?.session.subagents.model.set({
6097
6163
  modelId,
6098
6164
  agentType
6099
6165
  });
@@ -6125,6 +6191,7 @@ async function resolveActivePackId(ctx, settings) {
6125
6191
  };
6126
6192
  }
6127
6193
  async function applyCurrentThreadPack(ctx, options = {}) {
6194
+ if (!ctx.state.local) return { applied: true };
6128
6195
  const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
6129
6196
  const modeId = options.modeId ?? ctx.state.session.mode.get();
6130
6197
  const resolution = options.packId === void 0 ? await resolveActivePackId(ctx, settings) : {
@@ -6167,6 +6234,7 @@ async function switchModeWithPack(ctx, modeId) {
6167
6234
  if (!(await applyCurrentThreadPack(ctx, { modeId })).applied) throw new Error("Mode switch was superseded before its model selection completed.");
6168
6235
  }
6169
6236
  async function reconcilePackAfterModeChange(ctx, modeId) {
6237
+ if (!ctx.state.local) return;
6170
6238
  const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
6171
6239
  const resolution = await resolveActivePackId(ctx, settings);
6172
6240
  if (!resolution?.packId || ctx.state.session.mode.get() !== modeId) return;
@@ -12232,7 +12300,8 @@ function escapeSkillBoundary(value) {
12232
12300
  }
12233
12301
  async function resolveWorkspace(ctx) {
12234
12302
  let workspace = ctx.getResolvedWorkspace();
12235
- if (!workspace && ctx.controller.hasWorkspace()) workspace = await ctx.controller.resolveWorkspace({ session: ctx.state.session });
12303
+ const local = requireLocal(ctx.state, "Skills");
12304
+ if (!workspace && local.controller.hasWorkspace()) workspace = await local.controller.resolveWorkspace({ session: local.session });
12236
12305
  return workspace;
12237
12306
  }
12238
12307
  async function handleSkillsCommand(ctx) {
@@ -12433,8 +12502,8 @@ async function handleCloneCommand(ctx) {
12433
12502
  //#endregion
12434
12503
  //#region src/tui/commands/resource.ts
12435
12504
  async function handleResourceCommand(ctx, args) {
12436
- const { state, controller } = ctx;
12437
- const { session } = state;
12505
+ const { state } = ctx;
12506
+ const { controller, session } = requireLocal(state, "/resource");
12438
12507
  const sub = args[0]?.trim();
12439
12508
  const current = session.identity.getResourceId();
12440
12509
  const defaultId = session.identity.getDefaultResourceId();
@@ -12923,7 +12992,7 @@ async function handleThreadsCommand(ctx) {
12923
12992
  resolve();
12924
12993
  return;
12925
12994
  }
12926
- if (thread.resourceId !== currentResourceId) await state.controller.setResourceId(state.session, { resourceId: thread.resourceId });
12995
+ if (state.local && thread.resourceId !== currentResourceId) await state.local.controller.setResourceId(state.local.session, { resourceId: thread.resourceId });
12927
12996
  try {
12928
12997
  await state.session.thread.switch({ threadId: thread.id });
12929
12998
  if (state.options.backgroundToolsEnabled) await state.waitForAgentControllerEvents?.();
@@ -13280,6 +13349,31 @@ async function handleModelCommand(ctx) {
13280
13349
  ctx.showInfo("No connected models. Use /connect to add a provider account or API key.");
13281
13350
  return;
13282
13351
  }
13352
+ if (ctx.state.factory) return new Promise((resolve) => {
13353
+ const selector = new ModelSelectorComponent({
13354
+ tui: ctx.state.ui,
13355
+ models: connected,
13356
+ currentModelId,
13357
+ onSelect: async (model) => {
13358
+ ctx.state.ui.hideOverlay();
13359
+ try {
13360
+ await ctx.state.session.model.switch(model.id);
13361
+ ctx.updateStatusLine();
13362
+ ctx.showInfo(`Switched to ${model.id}`);
13363
+ } catch (error) {
13364
+ ctx.showError(`Failed to switch model: ${error instanceof Error ? error.message : String(error)}`);
13365
+ } finally {
13366
+ resolve();
13367
+ }
13368
+ },
13369
+ onCancel: () => {
13370
+ ctx.state.ui.hideOverlay();
13371
+ resolve();
13372
+ }
13373
+ });
13374
+ showModalOverlay(ctx.state.ui, selector, { maxHeight: "75%" });
13375
+ selector.focused = true;
13376
+ });
13283
13377
  const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
13284
13378
  const threadId = ctx.state.session.thread.getId?.();
13285
13379
  const activePackId = (0, _mastra_code_sdk_onboarding_settings.parseThreadSettings)((threadId ? (await ctx.state.session.thread.list()).find((item) => item.id === threadId) : void 0)?.metadata).activeModelPackId ?? settings.models.activeModelPackId;
@@ -13776,7 +13870,7 @@ function updateStatusLine(state) {
13776
13870
  const status = isObserving ? "observing" : "reflecting";
13777
13871
  if (state.omStatusLineModel?.status !== status) state.omStatusLineModel = {
13778
13872
  status,
13779
- modelId: getEffectiveOMRoleModelId(state.session, isObserving ? "observer" : "reflector")
13873
+ modelId: state.local ? getEffectiveOMRoleModelId(state.local.session, isObserving ? "observer" : "reflector") : void 0
13780
13874
  };
13781
13875
  } else state.omStatusLineModel = void 0;
13782
13876
  const rawModelId = (isJudging ? state.activeGoalJudge?.modelId : showOMMode ? state.omStatusLineModel?.modelId : state.session.model.get()) ?? "";
@@ -13842,7 +13936,7 @@ function updateStatusLine(state) {
13842
13936
  plain: indicator.plain.trimEnd() + pct,
13843
13937
  styled: (sweep ? applyGradientSweep(indicator.plain.trimEnd(), sweep.getOffset(), displayState.bufferingMessages ? modeColor ?? theme.getTheme().accent : mastra.blue, sweep.getFadeProgress()) : indicator.styled.trimEnd()) + theme.fg("dim", pct)
13844
13938
  } : null;
13845
- const branch = state.projectInfo.gitBranch;
13939
+ const branch = state.factory?.branch ?? state.projectInfo.gitBranch;
13846
13940
  const threadTitle = state.currentThreadTitle && !isGenericTitle(state.currentThreadTitle) ? state.currentThreadTitle : null;
13847
13941
  const activeGithubPrSubscriptions = state.activeGithubPrSubscriptions ?? [];
13848
13942
  const githubPrLabel = activeGithubPrSubscriptions.length > 0 ? formatGithubPrLabel(state, activeGithubPrSubscriptions) : null;
@@ -13865,7 +13959,7 @@ function updateStatusLine(state) {
13865
13959
  plain: branch ? `${path} (${branch})` : path,
13866
13960
  styled: theme.fg("muted", path) + (branch ? theme.fg("dim", ` (${branch})`) : "")
13867
13961
  });
13868
- const path = shortPath(state.projectInfo.rootPath ?? process.cwd());
13962
+ const path = state.factory ? `${state.factory.projectName} / ${state.factory.repositorySlug}` : shortPath(state.projectInfo.rootPath ?? process.cwd());
13869
13963
  text = (fit === "full" ? [withBranch(path)] : compactPaths(path).map(withBranch)).find((candidate) => fits(candidate.plain)) ?? null;
13870
13964
  if (!text && fit === "any" && room >= 10) {
13871
13965
  const value = branch ? fits(branch) ? branch : truncateEnd(branch, room) : truncateLastSegment(compactPaths(path).at(-1), room);
@@ -15417,7 +15511,7 @@ async function showSubagentModelListForScope(ctx, scope, agentType, agentTypeLab
15417
15511
  ctx.showInfo("No models available. Check your Mastra configuration.");
15418
15512
  return;
15419
15513
  }
15420
- const currentSubagentModel = ctx.state.session.subagents.model.get({ agentType });
15514
+ const currentSubagentModel = requireLocal(ctx.state, "/subagents").session.subagents.model.get({ agentType });
15421
15515
  const scopeLabel = scope === "global" ? `${agentTypeLabel} · Global` : `${agentTypeLabel} · Thread`;
15422
15516
  return new Promise((resolve) => {
15423
15517
  const selector = new ModelSelectorComponent({
@@ -15429,7 +15523,7 @@ async function showSubagentModelListForScope(ctx, scope, agentType, agentTypeLab
15429
15523
  ctx.state.ui.hideOverlay();
15430
15524
  await promptForApiKeyIfNeeded(ctx.state.ui, model, ctx.authStorage);
15431
15525
  try {
15432
- await ctx.state.session.subagents.model.set({
15526
+ await requireLocal(ctx.state, "/subagents").session.subagents.model.set({
15433
15527
  modelId: model.id,
15434
15528
  agentType
15435
15529
  });
@@ -15868,21 +15962,22 @@ function persistOmCavemanObservations(enabled) {
15868
15962
  }
15869
15963
  async function handleOMCommand(ctx) {
15870
15964
  const availableModels = await ctx.state.controller.listAvailableModels();
15965
+ const { session } = requireLocal(ctx.state, "/om");
15871
15966
  const agentControllerState = ctx.state.session.state.get();
15872
- const observerModelId = getEffectiveOMRoleModelId(ctx.state.session, "observer") ?? "";
15873
- const reflectorModelId = getEffectiveOMRoleModelId(ctx.state.session, "reflector") ?? "";
15967
+ const observerModelId = getEffectiveOMRoleModelId(session, "observer") ?? "";
15968
+ const reflectorModelId = getEffectiveOMRoleModelId(session, "reflector") ?? "";
15874
15969
  const packMemoryModelId = (0, _mastra_code_sdk_agents_model.getActiveRouteMemoryModelId)(agentControllerState, ctx.state.session.thread.getId());
15875
15970
  const autoModelId = (0, _mastra_code_sdk_onboarding_packs.resolveAutoOMModelId)(ctx.state.session.model.get() ?? void 0);
15876
15971
  const config = {
15877
- observerModel: ctx.state.session.om.observer.model() ?? "auto",
15972
+ observerModel: session.om.observer.model() ?? "auto",
15878
15973
  observerModelId,
15879
15974
  observerAutoModelId: autoModelId,
15880
- reflectorModel: ctx.state.session.om.reflector.model() ?? "auto",
15975
+ reflectorModel: session.om.reflector.model() ?? "auto",
15881
15976
  reflectorModelId,
15882
15977
  reflectorAutoModelId: autoModelId,
15883
15978
  packMemoryModelId,
15884
- observationThreshold: ctx.state.session.om.observer.threshold() ?? 3e4,
15885
- reflectionThreshold: ctx.state.session.om.reflector.threshold() ?? 4e4,
15979
+ observationThreshold: session.om.observer.threshold() ?? 3e4,
15980
+ reflectionThreshold: session.om.reflector.threshold() ?? 4e4,
15886
15981
  cavemanObservations: agentControllerState?.cavemanObservations ?? false,
15887
15982
  observeAttachments: agentControllerState?.observeAttachments ?? "auto"
15888
15983
  };
@@ -15890,25 +15985,25 @@ async function handleOMCommand(ctx) {
15890
15985
  const settings = new OMSettingsComponent(config, {
15891
15986
  onObserverModelChange: async (model) => {
15892
15987
  await promptForApiKeyIfNeeded(ctx.state.ui, model, ctx.authStorage);
15893
- await ctx.state.session.om.observer.switchModel({ modelId: model.id });
15988
+ await session.om.observer.switchModel({ modelId: model.id });
15894
15989
  persistOmRoleOverride("observer", model.id);
15895
15990
  ctx.showInfo(`Observer model → ${model.id}`);
15896
15991
  },
15897
15992
  onObserverAuto: async () => {
15898
- await ctx.state.session.om.observer.switchModel({ modelId: "auto" });
15993
+ await session.om.observer.switchModel({ modelId: "auto" });
15899
15994
  persistOmRoleAuto("observer");
15900
- ctx.showInfo(`Observer model → Auto (${getEffectiveOMRoleModelId(ctx.state.session, "observer") ?? "unavailable"})`);
15995
+ ctx.showInfo(`Observer model → Auto (${getEffectiveOMRoleModelId(session, "observer") ?? "unavailable"})`);
15901
15996
  },
15902
15997
  onReflectorModelChange: async (model) => {
15903
15998
  await promptForApiKeyIfNeeded(ctx.state.ui, model, ctx.authStorage);
15904
- await ctx.state.session.om.reflector.switchModel({ modelId: model.id });
15999
+ await session.om.reflector.switchModel({ modelId: model.id });
15905
16000
  persistOmRoleOverride("reflector", model.id);
15906
16001
  ctx.showInfo(`Reflector model → ${model.id}`);
15907
16002
  },
15908
16003
  onReflectorAuto: async () => {
15909
- await ctx.state.session.om.reflector.switchModel({ modelId: "auto" });
16004
+ await session.om.reflector.switchModel({ modelId: "auto" });
15910
16005
  persistOmRoleAuto("reflector");
15911
- ctx.showInfo(`Reflector model → Auto (${getEffectiveOMRoleModelId(ctx.state.session, "reflector") ?? "unavailable"})`);
16006
+ ctx.showInfo(`Reflector model → Auto (${getEffectiveOMRoleModelId(session, "reflector") ?? "unavailable"})`);
15912
16007
  },
15913
16008
  onObservationThresholdChange: async (value) => {
15914
16009
  await ctx.state.session.state.set({ observationThreshold: value });
@@ -17527,7 +17622,7 @@ function applyBrowserToAgents(ctx, browser, browserSettings, chatModelId) {
17527
17622
  const modes = ctx.controller.listModes();
17528
17623
  let agentControllerState;
17529
17624
  for (const mode of modes) resolveModeAgent(mode, agentControllerState ??= ctx.state.session.state.get())?.setBrowser?.(browser);
17530
- ctx.controller.setBrowser?.(browser);
17625
+ ctx.state.local?.controller.setBrowser(browser);
17531
17626
  ctx.state.session.state.set({
17532
17627
  [ACTIVE_BROWSER_KEY]: browserSettings ? (0, _mastra_code_sdk_onboarding_settings.toActiveBrowserSettings)(browserSettings) : void 0,
17533
17628
  [ACTIVE_BROWSER_MODEL_KEY]: browserSettings?.enabled ? (0, _mastra_code_sdk_onboarding_settings.resolveStagehandModel)(browserSettings, { chatModelId }) : void 0
@@ -18751,6 +18846,196 @@ async function handleConnectCommand(ctx) {
18751
18846
  else if (method === "api-key") await handleApiKeysCommand(ctx);
18752
18847
  }
18753
18848
  //#endregion
18849
+ //#region src/tui/components/simple-select.ts
18850
+ /** Modal single-choice picker. Resolves with the chosen value, or null on cancel. */
18851
+ function promptSelect(tui, title, items) {
18852
+ return new Promise((resolve) => {
18853
+ const container = new _earendil_works_pi_tui.Box(4, 2, (text) => theme.bg("overlayBg", text));
18854
+ container.addChild(new _earendil_works_pi_tui.Text(theme.bold(theme.fg("accent", title)), 0, 0));
18855
+ container.addChild(new _earendil_works_pi_tui.Spacer(1));
18856
+ const selectList = new _earendil_works_pi_tui.SelectList(items, Math.min(items.length, 12), getSelectListTheme());
18857
+ const close = (value) => {
18858
+ tui.hideOverlay();
18859
+ tui.requestRender();
18860
+ resolve(value);
18861
+ };
18862
+ selectList.onSelect = (item) => close(item.value);
18863
+ selectList.onCancel = () => close(null);
18864
+ container.addChild(selectList);
18865
+ container.addChild(new _earendil_works_pi_tui.Spacer(1));
18866
+ container.addChild(new _earendil_works_pi_tui.Text(theme.fg("dim", "↑↓ navigate · Enter select · Esc cancel"), 0, 0));
18867
+ container.handleInput = (data) => selectList.handleInput(data);
18868
+ showModalOverlay(tui, container, {
18869
+ widthPercent: .8,
18870
+ maxHeight: "60%"
18871
+ });
18872
+ });
18873
+ }
18874
+ //#endregion
18875
+ //#region src/tui/commands/factory.ts
18876
+ const USAGE$1 = "Usage: /factory [connect|status|local|disconnect]";
18877
+ /**
18878
+ * `/factory` switches into the saved Factory (connecting first if there is none);
18879
+ * `/factory local` (or `/local`) switches back without forgetting the connection.
18880
+ */
18881
+ async function handleFactoryCommand(ctx, args) {
18882
+ const sub = args[0];
18883
+ if (sub === "status") return showStatus$1(ctx);
18884
+ if (sub === "disconnect") return disconnect(ctx);
18885
+ if (sub === "local") return switchToLocal(ctx);
18886
+ if (sub === "connect") return connect(ctx);
18887
+ if (sub !== void 0) return ctx.showError(USAGE$1);
18888
+ if (!(0, _mastra_code_sdk_onboarding_settings.loadSettings)().factory) return connect(ctx);
18889
+ if (inFactoryMode(ctx)) return showStatus$1(ctx);
18890
+ return switchToFactory(ctx);
18891
+ }
18892
+ async function handleLocalCommand(ctx) {
18893
+ return switchToLocal(ctx);
18894
+ }
18895
+ function inFactoryMode(ctx) {
18896
+ return !ctx.state.local;
18897
+ }
18898
+ function showStatus$1(ctx) {
18899
+ const factory = (0, _mastra_code_sdk_onboarding_settings.loadSettings)().factory;
18900
+ if (!factory) return ctx.showInfo("Not connected to a Factory. Run /factory to connect.");
18901
+ const where = `${factory.factoryProjectName} / ${factory.repositorySlug} on ${factory.branch} (${factory.projectName} · ${factory.environmentName}, ${factory.url})`;
18902
+ ctx.showInfo(inFactoryMode(ctx) ? `Factory mode: ${where}. Run /local to work locally.` : `Local mode. Saved Factory: ${where}. Run /factory to switch to it.`);
18903
+ }
18904
+ function setActive(active) {
18905
+ const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
18906
+ if (!settings.factory) return;
18907
+ settings.factory = {
18908
+ ...settings.factory,
18909
+ active
18910
+ };
18911
+ (0, _mastra_code_sdk_onboarding_settings.saveSettings)(settings);
18912
+ }
18913
+ /** Restart into the mode settings now select, or tell the user to restart when that isn't possible. */
18914
+ async function restartInto(ctx, mode) {
18915
+ const relaunch = ctx.state.options.relaunch;
18916
+ if (!relaunch) return ctx.showInfo(`Restart Mastra Code to switch to ${mode}.`);
18917
+ ctx.showInfo(`Switching to ${mode}…`);
18918
+ await relaunch();
18919
+ }
18920
+ async function switchToFactory(ctx) {
18921
+ setActive(true);
18922
+ await restartInto(ctx, "Factory mode");
18923
+ }
18924
+ async function switchToLocal(ctx) {
18925
+ if (!inFactoryMode(ctx)) {
18926
+ setActive(false);
18927
+ return ctx.showInfo("Already in local mode.");
18928
+ }
18929
+ setActive(false);
18930
+ await restartInto(ctx, "local mode");
18931
+ }
18932
+ async function disconnect(ctx) {
18933
+ const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
18934
+ if (!settings.factory) return ctx.showInfo("Not connected to a Factory.");
18935
+ settings.factory = null;
18936
+ (0, _mastra_code_sdk_onboarding_settings.saveSettings)(settings);
18937
+ if (inFactoryMode(ctx)) return restartInto(ctx, "local mode");
18938
+ ctx.showInfo("Disconnected from Factory.");
18939
+ }
18940
+ async function ensureSession(ctx) {
18941
+ const existing = await (0, _mastra_code_sdk_platform_index.getPlatformSession)();
18942
+ if (existing) return existing;
18943
+ ctx.showInfo("Opening your browser to sign in to Mastra Platform…");
18944
+ try {
18945
+ const creds = await (0, _mastra_code_sdk_platform_index.loginToPlatform)({ onUrl: (url) => ctx.showInfo(`If the browser did not open, visit: ${url}`) });
18946
+ ctx.showInfo(`Signed in as ${creds.user.email}`);
18947
+ return {
18948
+ token: creds.token,
18949
+ orgId: creds.currentOrgId ?? creds.organizationId
18950
+ };
18951
+ } catch (error) {
18952
+ ctx.showError(`Mastra Platform login failed: ${error.message}`);
18953
+ return null;
18954
+ }
18955
+ }
18956
+ async function connect(ctx) {
18957
+ const ui = ctx.state.ui;
18958
+ let session = await ensureSession(ctx);
18959
+ if (!session) return;
18960
+ try {
18961
+ const orgs = await (0, _mastra_code_sdk_platform_index.fetchPlatformOrgs)(session);
18962
+ if (orgs.length > 1) {
18963
+ const orgId = await promptSelect(ui, "Select organization:", orgs.map((org) => ({
18964
+ value: org.id,
18965
+ label: ` ${org.name}`,
18966
+ description: org.id === session.orgId ? "current" : void 0
18967
+ })));
18968
+ if (!orgId) return;
18969
+ session = {
18970
+ ...session,
18971
+ orgId
18972
+ };
18973
+ } else if (orgs.length === 1 && orgs[0].id !== session.orgId) session = {
18974
+ ...session,
18975
+ orgId: orgs[0].id
18976
+ };
18977
+ const projects = await (0, _mastra_code_sdk_platform_index.fetchPlatformProjects)(session);
18978
+ if (projects.length === 0) return ctx.showError("No projects found in this organization.");
18979
+ const projectId = await promptSelect(ui, "Select project:", projects.map((project) => ({
18980
+ value: project.id,
18981
+ label: ` ${project.name}`
18982
+ })));
18983
+ const project = projects.find((p) => p.id === projectId);
18984
+ if (!project) return;
18985
+ const environments = (await (0, _mastra_code_sdk_platform_index.fetchPlatformEnvironments)(session, project.id)).filter(_mastra_code_sdk_platform_index.resolveFactoryUrl);
18986
+ if (environments.length === 0) return ctx.showError(`${project.name} has no deployed environments. Deploy it with \`mastra deploy\` first.`);
18987
+ const environmentId = environments.length === 1 ? environments[0].id : await promptSelect(ui, "Select environment:", environments.map((env) => ({
18988
+ value: env.id,
18989
+ label: ` ${env.name}`,
18990
+ description: (0, _mastra_code_sdk_platform_index.resolveFactoryUrl)(env)
18991
+ })));
18992
+ const environment = environments.find((env) => env.id === environmentId);
18993
+ if (!environment) return;
18994
+ const address = {
18995
+ url: (0, _mastra_code_sdk_platform_index.resolveFactoryUrl)(environment),
18996
+ session
18997
+ };
18998
+ const factoryProjects = (await (0, _mastra_code_sdk_factory_client.listFactoryProjects)(address)).filter((p) => p.repositories.length > 0);
18999
+ if (factoryProjects.length === 0) return ctx.showError("This Factory has no projects with linked repositories. Link one in the Factory UI first.");
19000
+ const factoryProjectId = factoryProjects.length === 1 ? factoryProjects[0].id : await promptSelect(ui, "Select Factory project:", factoryProjects.map((p) => ({
19001
+ value: p.id,
19002
+ label: ` ${p.name}`
19003
+ })));
19004
+ const factoryProject = factoryProjects.find((p) => p.id === factoryProjectId);
19005
+ if (!factoryProject) return;
19006
+ const repositories = factoryProject.repositories;
19007
+ const projectRepositoryId = repositories.length === 1 ? repositories[0].projectRepositoryId : await promptSelect(ui, "Select repository:", repositories.map((r) => ({
19008
+ value: r.projectRepositoryId,
19009
+ label: ` ${r.slug}`,
19010
+ description: r.branch
19011
+ })));
19012
+ const repository = repositories.find((r) => r.projectRepositoryId === projectRepositoryId);
19013
+ if (!repository) return;
19014
+ const userSession = await (0, _mastra_code_sdk_factory_client.createFactoryUserSession)(address, repository.projectRepositoryId);
19015
+ const settings = (0, _mastra_code_sdk_onboarding_settings.loadSettings)();
19016
+ settings.factory = {
19017
+ url: address.url,
19018
+ orgId: session.orgId,
19019
+ projectId: project.id,
19020
+ projectName: project.name,
19021
+ environmentId: environment.id,
19022
+ environmentName: environment.name,
19023
+ factoryProjectId: factoryProject.id,
19024
+ factoryProjectName: factoryProject.name,
19025
+ projectRepositoryId: repository.projectRepositoryId,
19026
+ repositorySlug: repository.slug,
19027
+ sessionId: userSession.sessionId,
19028
+ branch: userSession.branch,
19029
+ active: true
19030
+ };
19031
+ (0, _mastra_code_sdk_onboarding_settings.saveSettings)(settings);
19032
+ ctx.showInfo(`Connected to Factory ${factoryProject.name} / ${repository.slug} — new session on ${userSession.branch} (${address.url}).`);
19033
+ await restartInto(ctx, "Factory mode");
19034
+ } catch (error) {
19035
+ ctx.showError(`Failed to connect to Factory: ${error.message}`);
19036
+ }
19037
+ }
19038
+ //#endregion
18754
19039
  //#region src/tui/commands/review.ts
18755
19040
  async function handleReviewCommand(ctx, args) {
18756
19041
  if (!ctx.state.session.model.hasSelection()) {
@@ -19505,7 +19790,7 @@ async function handleFeedbackCommand(ctx, args) {
19505
19790
  ctx.showError("No active session to attach feedback to.");
19506
19791
  return;
19507
19792
  }
19508
- const observability = ctx.controller.getMastra()?.observability;
19793
+ const observability = requireLocal(ctx.state, "/feedback").controller.getMastra()?.observability;
19509
19794
  if (!observability?.addFeedback) {
19510
19795
  ctx.showError("Observability not configured — cannot save feedback.");
19511
19796
  return;
@@ -21098,7 +21383,7 @@ async function handleWorkflowsCommand(ctx, args, rawArgsText) {
21098
21383
  help(ctx);
21099
21384
  return;
21100
21385
  }
21101
- const mastra = ctx.controller.getMastra();
21386
+ const mastra = requireLocal(ctx.state, "/workflows").controller.getMastra();
21102
21387
  if (!mastra) {
21103
21388
  ctx.showError("Workflows: no Mastra instance attached to this controller.");
21104
21389
  return;
@@ -21247,8 +21532,8 @@ async function handlePruneCommand(ctx, args = []) {
21247
21532
  ];
21248
21533
  const quiesceResults = await Promise.allSettled([
21249
21534
  ctx.mcpManager?.disconnect(),
21250
- ctx.controller.getMastra()?.stopWorkers(),
21251
- ctx.controller.stopIntervals()
21535
+ requireLocal(ctx.state, "/prune").controller.getMastra()?.stopWorkers(),
21536
+ requireLocal(ctx.state, "/prune").controller.stopIntervals()
21252
21537
  ]);
21253
21538
  for (const [i, result] of quiesceResults.entries()) if (result.status === "rejected") log(`Warning: failed to quiesce a background writer (${quiesceSteps[i]}): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
21254
21539
  await (0, _mastra_code_sdk_utils_storage_maintenance.runStorageMaintenance)({
@@ -21391,9 +21676,12 @@ function canRunSlashCommandDuringGoalJudge(command, args) {
21391
21676
  /**
21392
21677
  * Slash command dispatcher: routes command strings to extracted handlers.
21393
21678
  */
21679
+ /** Commands that need the in-process runtime (local agent, storage, auth, workspace, managers). */
21394
21680
  const TRACKED_COMMANDS = /* @__PURE__ */ new Set([
21395
21681
  "connect",
21396
21682
  "login",
21683
+ "factory",
21684
+ "local",
21397
21685
  "model",
21398
21686
  "models",
21399
21687
  "packs",
@@ -21459,6 +21747,10 @@ async function dispatchSlashCommand(input, state, buildCtx) {
21459
21747
  }
21460
21748
  const ctx = buildCtx();
21461
21749
  trackCommand(ctx, command);
21750
+ if (!state.local && isLocalOnlyCommand(command)) {
21751
+ showInfo(state, `/${command} is not available in Factory mode.`);
21752
+ return true;
21753
+ }
21462
21754
  if (command.startsWith("goal/")) {
21463
21755
  await handleGoalSourceCommand(state, command.slice(5), args, ctx);
21464
21756
  return true;
@@ -21538,6 +21830,12 @@ async function dispatchSlashCommand(input, state, buildCtx) {
21538
21830
  case "login":
21539
21831
  await handleLoginCommand(ctx, "login");
21540
21832
  return true;
21833
+ case "factory":
21834
+ await handleFactoryCommand(ctx, args);
21835
+ return true;
21836
+ case "local":
21837
+ await handleLocalCommand(ctx);
21838
+ return true;
21541
21839
  case "logout":
21542
21840
  await handleLoginCommand(ctx, "logout");
21543
21841
  return true;
@@ -21653,7 +21951,8 @@ async function handleGoalSourceCommand(state, sourceName, args, ctx) {
21653
21951
  if (goalSkill) {
21654
21952
  try {
21655
21953
  let workspace = ctx.getResolvedWorkspace();
21656
- if (!workspace && ctx.controller?.hasWorkspace?.()) workspace = await ctx.controller.resolveWorkspace({ session: ctx.state.session });
21954
+ const local = requireLocal(state, "Goal skills");
21955
+ if (!workspace && local.controller.hasWorkspace()) workspace = await local.controller.resolveWorkspace({ session: local.session });
21657
21956
  const skill = await workspace?.skills?.get(goalSkill.path || goalSkill.name);
21658
21957
  if (!skill || skill.metadata?.goal !== true) {
21659
21958
  showError(state, `Unknown goal command: ${sourceName}`);
@@ -24991,6 +25290,11 @@ function createTUIState(options) {
24991
25290
  result = {
24992
25291
  controller: options.controller,
24993
25292
  session: options.session,
25293
+ local: options.runtime === "factory" ? void 0 : {
25294
+ controller: options.controller,
25295
+ session: options.session
25296
+ },
25297
+ factory: options.runtime === "factory" ? options.factory : void 0,
24994
25298
  options,
24995
25299
  hookManager: options.hookManager,
24996
25300
  analytics: options.analytics,
@@ -25364,7 +25668,7 @@ async function dispatchEvent(event, ectx, state) {
25364
25668
  state.taskToolInsertIndex = -1;
25365
25669
  await ectx.renderExistingMessages(isCurrent);
25366
25670
  if (!isCurrent()) break;
25367
- await state.controller.loadOMProgress(state.session, isCurrent);
25671
+ await state.local?.controller.loadOMProgress(state.local.session, isCurrent);
25368
25672
  if (!isCurrent()) break;
25369
25673
  (0, _mastra_code_sdk_utils_project.getCurrentGitBranchAsync)(state.projectInfo.rootPath).then((freshBranch) => {
25370
25674
  if (freshBranch && isCurrent()) {
@@ -25381,7 +25685,7 @@ async function dispatchEvent(event, ectx, state) {
25381
25685
  state.activeGithubPrSubscriptions = getGithubPrSubscriptionsFromMetadata(metadata);
25382
25686
  state.githubPrPollingActive = false;
25383
25687
  state.githubPrGradientAnimator?.stop();
25384
- await state.goalManager.loadFromThread(state, isCurrent);
25688
+ if (state.local) await state.goalManager.loadFromThread(state.local, isCurrent);
25385
25689
  if (!isCurrent()) break;
25386
25690
  if (!state.goalManager.getGoal()) state.goalManager.loadFromThreadMetadata(metadata);
25387
25691
  }
@@ -25397,8 +25701,9 @@ async function dispatchEvent(event, ectx, state) {
25397
25701
  state.activeGithubPrSubscriptions = getGithubPrSubscriptionsFromMetadata(event.thread.metadata);
25398
25702
  state.githubPrPollingActive = false;
25399
25703
  state.githubPrGradientAnimator?.stop();
25400
- if (state.goalManager?.consumePersistOnNextThreadCreate() ?? false) state.goalManager?.saveToThread(state).catch(() => {});
25401
- else state.goalManager?.loadFromThreadMetadata(event.thread.metadata);
25704
+ if (state.goalManager?.consumePersistOnNextThreadCreate() ?? false) {
25705
+ if (state.local) state.goalManager?.saveToThread(state.local).catch(() => {});
25706
+ } else state.goalManager?.loadFromThreadMetadata(event.thread.metadata);
25402
25707
  const tState = state.session.state.get();
25403
25708
  if (typeof tState?.escapeAsCancel === "boolean") state.editor.escapeEnabled = tState.escapeAsCancel;
25404
25709
  if (!await clearThreadState(state, isCurrent)) break;
@@ -26253,7 +26558,8 @@ async function pauseStoredGoal(state, pausedReason) {
26253
26558
  const threadId = state.session.thread.getId();
26254
26559
  if (!threadId) return;
26255
26560
  try {
26256
- const agent = state.controller.getCurrentAgent(state.session);
26561
+ if (!state.local) return;
26562
+ const agent = state.local.controller.getCurrentAgent(state.local.session);
26257
26563
  if ((await agent.getObjective({ threadId }))?.status !== "active") return;
26258
26564
  await agent.updateObjectiveOptions({
26259
26565
  threadId,
@@ -26273,7 +26579,7 @@ function abortActiveGoalJudge(state) {
26273
26579
  const pausedReason = "Judge evaluation was interrupted.";
26274
26580
  if (state.goalManager.getGoal()) {
26275
26581
  state.goalManager.pause(pausedReason);
26276
- state.goalManager.saveToThread(state);
26582
+ if (state.local) state.goalManager.saveToThread(state.local);
26277
26583
  } else pauseStoredGoal(state, pausedReason);
26278
26584
  state.activeGoalJudge = void 0;
26279
26585
  state.ui.requestRender();
@@ -26282,7 +26588,12 @@ function abortActiveGoalJudge(state) {
26282
26588
  function buildLayout(state, refreshModelAuthStatus) {
26283
26589
  const appName = state.options.appName || "Mastra Code";
26284
26590
  const version = state.options.version || "0.1.0";
26285
- const info = [
26591
+ const info = state.factory ? [
26592
+ `Factory: ${state.factory.projectName}`,
26593
+ `Repository: ${state.factory.repositorySlug}`,
26594
+ `Branch: ${state.factory.branch}`,
26595
+ `URL: ${state.factory.url}`
26596
+ ] : [
26286
26597
  `Project: ${state.projectInfo.name}`,
26287
26598
  `Resource ID: ${state.projectInfo.resourceId}`,
26288
26599
  state.projectInfo.gitBranch ? `Branch: ${state.projectInfo.gitBranch}` : null,
@@ -26448,6 +26759,14 @@ function setupAutocomplete(state) {
26448
26759
  name: "login",
26449
26760
  description: "Sign in with a provider account"
26450
26761
  },
26762
+ {
26763
+ name: "factory",
26764
+ description: "Switch to Factory mode, connecting first if needed (connect|status|local|disconnect)"
26765
+ },
26766
+ {
26767
+ name: "local",
26768
+ description: "Switch from Factory mode back to local mode"
26769
+ },
26451
26770
  {
26452
26771
  name: "skills",
26453
26772
  description: "List available skills"
@@ -26740,8 +27059,9 @@ function setupAutocomplete(state) {
26740
27059
  name: `goal/${skill.name}`,
26741
27060
  description: skill.description ? `Goal skill: ${skill.description}` : `Goal skill: ${skill.name}`
26742
27061
  });
27062
+ const available = state.local ? slashCommands : slashCommands.filter((cmd) => !isLocalOnlyCommand(cmd.name));
26743
27063
  const fdPath = detectFdPath();
26744
- state.autocompleteProvider = new _earendil_works_pi_tui.CombinedAutocompleteProvider(slashCommands, process.cwd(), fdPath);
27064
+ state.autocompleteProvider = new _earendil_works_pi_tui.CombinedAutocompleteProvider(available, process.cwd(), fdPath);
26745
27065
  state.editor.setAutocompleteProvider(state.autocompleteProvider);
26746
27066
  }
26747
27067
  async function loadCustomSlashCommands(state) {
@@ -26765,8 +27085,9 @@ async function loadCustomSlashCommands(state) {
26765
27085
  */
26766
27086
  async function loadSkillCommands(state) {
26767
27087
  try {
26768
- let workspace = state.controller.getWorkspace() ?? state.workspace;
26769
- if (!workspace && state.controller.hasWorkspace()) workspace = await state.controller.resolveWorkspace({ session: state.session });
27088
+ const local = state.local;
27089
+ let workspace = local?.controller.getWorkspace() ?? state.workspace;
27090
+ if (!workspace && local?.controller.hasWorkspace()) workspace = await local.controller.resolveWorkspace({ session: local.session });
26770
27091
  if (!workspace?.skills) {
26771
27092
  state.skillCommands = [];
26772
27093
  state.goalSkillCommands = [];
@@ -27141,26 +27462,29 @@ var SimpleProgressComponent = class extends _earendil_works_pi_tui.Container {
27141
27462
  * is returned so the caller can tell the user once the UI is ready.
27142
27463
  */
27143
27464
  async function resumeThreadOnStartup(state, requestedThreadId) {
27465
+ const local = state.local;
27466
+ if (!local) return resumeFactoryThread(state);
27467
+ const { session, controller } = local;
27144
27468
  const currentPath = state.projectInfo.rootPath;
27145
- const allThreads = await state.session.thread.list(requestedThreadId ? { allResources: true } : void 0);
27146
- const activeThreadId = state.session.thread.getId();
27469
+ const allThreads = await session.thread.list(requestedThreadId ? { allResources: true } : void 0);
27470
+ const activeThreadId = session.thread.getId();
27147
27471
  if (requestedThreadId) {
27148
27472
  const thread = allThreads.find((candidate) => candidate.id === requestedThreadId);
27149
27473
  if (!thread) {
27150
- await startPendingNewThread(state);
27474
+ await startPendingNewThread(state, local);
27151
27475
  return {
27152
27476
  kind: "missing",
27153
27477
  threadId: requestedThreadId
27154
27478
  };
27155
27479
  }
27156
- const originalResourceId = state.session.identity.getResourceId();
27157
- if (thread.resourceId !== originalResourceId) await state.controller.setResourceId(state.session, { resourceId: thread.resourceId });
27480
+ const originalResourceId = session.identity.getResourceId();
27481
+ if (thread.resourceId !== originalResourceId) await controller.setResourceId(session, { resourceId: thread.resourceId });
27158
27482
  if (requestedThreadId !== activeThreadId) try {
27159
- await state.session.thread.switch({ threadId: requestedThreadId });
27483
+ await session.thread.switch({ threadId: requestedThreadId });
27160
27484
  } catch (error) {
27161
27485
  if (!(error instanceof _mastra_code_sdk_utils_thread_lock.ThreadLockError)) throw error;
27162
- if (state.session.identity.getResourceId() !== originalResourceId) await state.controller.setResourceId(state.session, { resourceId: originalResourceId });
27163
- await startPendingNewThread(state);
27486
+ if (session.identity.getResourceId() !== originalResourceId) await controller.setResourceId(session, { resourceId: originalResourceId });
27487
+ await startPendingNewThread(state, local);
27164
27488
  return {
27165
27489
  kind: "locked",
27166
27490
  threadId: thread.id,
@@ -27173,14 +27497,14 @@ async function resumeThreadOnStartup(state, requestedThreadId) {
27173
27497
  }
27174
27498
  const threads = allThreads.filter((thread) => thread.metadata?.projectPath === currentPath);
27175
27499
  if (threads.length === 0) {
27176
- if (await cloneDriftedThread(state)) return;
27500
+ if (await cloneDriftedThread(state, local)) return;
27177
27501
  state.pendingNewThread = true;
27178
27502
  return;
27179
27503
  }
27180
27504
  for (const thread of [...threads].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())) {
27181
- if (thread.id === state.session.thread.getId()) return;
27505
+ if (thread.id === session.thread.getId()) return;
27182
27506
  try {
27183
- await state.session.thread.switch({ threadId: thread.id });
27507
+ await session.thread.switch({ threadId: thread.id });
27184
27508
  return;
27185
27509
  } catch (error) {
27186
27510
  if (error instanceof _mastra_code_sdk_utils_thread_lock.ThreadLockError) continue;
@@ -27189,20 +27513,27 @@ async function resumeThreadOnStartup(state, requestedThreadId) {
27189
27513
  }
27190
27514
  state.pendingNewThread = true;
27191
27515
  }
27516
+ /** Factory sessions own their threads, so local project-path tagging and resource IDs don't apply. */
27517
+ async function resumeFactoryThread(state) {
27518
+ if (state.session.thread.getId()) return;
27519
+ const [latest] = (await state.session.thread.list()).sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
27520
+ if (latest) await state.session.thread.switch({ threadId: latest.id });
27521
+ else state.pendingNewThread = true;
27522
+ }
27192
27523
  /**
27193
27524
  * Leave the session waiting for a new thread. Session creation may already have
27194
27525
  * bound the latest project thread; unbind it (and release its lock) so the UI
27195
27526
  * and exit hint don't show a thread the user didn't ask for.
27196
27527
  */
27197
- async function startPendingNewThread(state) {
27198
- state.session.thread.cleanupSubscription();
27199
- await state.session.thread.clearAndReleaseLock();
27528
+ async function startPendingNewThread(state, { session }) {
27529
+ session.thread.cleanupSubscription();
27530
+ await session.thread.clearAndReleaseLock();
27200
27531
  state.pendingNewThread = true;
27201
27532
  }
27202
- async function cloneDriftedThread(state) {
27533
+ async function cloneDriftedThread(state, { session }) {
27203
27534
  const currentPath = state.projectInfo.rootPath;
27204
- const currentResourceId = state.session.identity.getResourceId();
27205
- const thread = [...(await state.session.thread.list({
27535
+ const currentResourceId = session.identity.getResourceId();
27536
+ const thread = [...(await session.thread.list({
27206
27537
  allResources: true,
27207
27538
  metadata: { projectPath: currentPath }
27208
27539
  })).filter((thread) => thread.resourceId !== currentResourceId)].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())[0];
@@ -27241,7 +27572,7 @@ async function cloneDriftedThread(state) {
27241
27572
  await new Promise((resolve) => setTimeout(resolve, 50));
27242
27573
  progress.updateStatus("Loading cloned thread...");
27243
27574
  state.ui.requestRender();
27244
- await state.session.thread.cloneToCurrentResource({
27575
+ await session.thread.cloneToCurrentResource({
27245
27576
  threadId: thread.id,
27246
27577
  expectedResourceId: thread.resourceId,
27247
27578
  expectedProjectPath: currentPath
@@ -27292,7 +27623,7 @@ async function syncInitialThreadState(state) {
27292
27623
  setCurrentThreadTitle(state, initThread?.title);
27293
27624
  state.fallbackStatus = persistedFallbackStatus;
27294
27625
  state.activeGithubPrSubscriptions = getGithubPrSubscriptionsFromMetadata(metadata);
27295
- await state.goalManager.loadFromThread(state);
27626
+ if (state.local) await state.goalManager.loadFromThread(state.local);
27296
27627
  if (state.session.thread.getId() !== initThreadId) return;
27297
27628
  if (!state.goalManager.getGoal()) state.goalManager.loadFromThreadMetadata(metadata);
27298
27629
  }
@@ -27434,7 +27765,7 @@ var MastraTUI = class MastraTUI {
27434
27765
  }
27435
27766
  async abortBackgroundActivity(activity) {
27436
27767
  try {
27437
- const manager = this.state.controller.getMastra()?.backgroundTaskManager;
27768
+ const manager = this.state.local?.controller.getMastra()?.backgroundTaskManager;
27438
27769
  if (!manager) return;
27439
27770
  await manager.cancel(activity.taskId);
27440
27771
  } catch (error) {
@@ -27728,8 +28059,11 @@ var MastraTUI = class MastraTUI {
27728
28059
  }
27729
28060
  async init() {
27730
28061
  if (this.state.isInitialized) return;
27731
- await this.state.controller.init();
27732
- await this.state.controller.getMastra()?.startWorkers();
28062
+ const local = this.state.local;
28063
+ if (local) {
28064
+ await local.controller.init();
28065
+ await local.controller.getMastra()?.startWorkers();
28066
+ }
27733
28067
  if (this.state.pluginManager && !this.cleanupPluginReloadListener) this.cleanupPluginReloadListener = this.state.pluginManager.onReload((plugins) => this.refreshPluginRuntimeState(plugins).catch((err) => {
27734
28068
  const msg = err instanceof Error ? err.message : String(err);
27735
28069
  process.stderr.write(`[plugin runtime refresh] ${msg}\n`);
@@ -27771,7 +28105,7 @@ var MastraTUI = class MastraTUI {
27771
28105
  subscribeToAgentController(this.state, (event) => this.handleEvent(event));
27772
28106
  if (this.state.options.backgroundToolsEnabled) this.cleanupBackgroundCompletionListener = this.state.options.backgroundCompletionEvents?.subscribe((event) => this.handleBackgroundCompletion(event));
27773
28107
  if (this.state.session.state.get()?.escapeAsCancel === false) this.state.editor.escapeEnabled = false;
27774
- await this.state.controller.loadOMProgress(this.state.session);
28108
+ await this.state.local?.controller.loadOMProgress(this.state.local.session);
27775
28109
  await syncInitialThreadState(this.state);
27776
28110
  this.state.isInitialized = true;
27777
28111
  if (this.state.mcpManager?.hasServers()) this.state.mcpManager.initInBackground().then((result) => {
@@ -27925,12 +28259,13 @@ var MastraTUI = class MastraTUI {
27925
28259
  });
27926
28260
  }
27927
28261
  emitErrorFeedback(errorMessage) {
27928
- const controller = this.state.controller;
28262
+ const local = this.state.local;
28263
+ if (!local) return;
27929
28264
  const traceId = this.state.session.run.getTraceId() ?? void 0;
27930
28265
  const runId = this.state.session.getCurrentRunId() ?? void 0;
27931
28266
  const threadId = this.state.session.thread.getId() ?? void 0;
27932
28267
  if (!traceId && !runId && !threadId) return;
27933
- const observability = controller.getMastra()?.observability;
28268
+ const observability = local.controller.getMastra()?.observability;
27934
28269
  if (!observability?.addFeedback) return;
27935
28270
  const comment = errorMessage.length > 500 ? errorMessage.slice(0, 500) + "…" : errorMessage;
27936
28271
  observability.addFeedback({
@@ -28161,7 +28496,7 @@ var MastraTUI = class MastraTUI {
28161
28496
  * Get the workspace, preferring controller-owned workspace over the direct option.
28162
28497
  */
28163
28498
  getResolvedWorkspace() {
28164
- return this.state.controller.getWorkspace() ?? this.state.workspace;
28499
+ return this.state.local?.controller.getWorkspace() ?? this.state.workspace;
28165
28500
  }
28166
28501
  buildCommandContext() {
28167
28502
  return {
@@ -28604,4 +28939,4 @@ Object.defineProperty(exports, "theme", {
28604
28939
  }
28605
28940
  });
28606
28941
 
28607
- //# sourceMappingURL=tui-B5689bGt.cjs.map
28942
+ //# sourceMappingURL=tui-BpzUjTFI.cjs.map