fraim 2.0.169 → 2.0.171

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.
@@ -59,6 +59,7 @@ const conversation_store_1 = require("./conversation-store");
59
59
  const managed_browser_1 = require("./managed-browser");
60
60
  const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
61
61
  let personaHiringModule;
62
+ let managerHiringModule;
62
63
  function loadPersonaHiringModule() {
63
64
  const cached = personaHiringModule;
64
65
  if (cached !== undefined) {
@@ -77,6 +78,24 @@ function loadPersonaHiringModule() {
77
78
  return null;
78
79
  }
79
80
  }
81
+ function loadManagerHiringModule() {
82
+ const cached = managerHiringModule;
83
+ if (cached !== undefined) {
84
+ return cached;
85
+ }
86
+ try {
87
+ // Server deployments include the manager-hiring catalog. The npm client
88
+ // package intentionally does not, so Hub bootstrap must not require it.
89
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
90
+ const loaded = require('../config/ai-manager-hiring');
91
+ managerHiringModule = loaded;
92
+ return loaded;
93
+ }
94
+ catch {
95
+ managerHiringModule = null;
96
+ return null;
97
+ }
98
+ }
80
99
  function loadPersonaCapabilityModule() {
81
100
  try {
82
101
  // Server deployments include the persona catalog. The npm client package
@@ -101,6 +120,13 @@ function buildHubPersonaHireUrl(personaKey, hireMode = 'job') {
101
120
  function buildHubPersonaAvatarUrl(personaKey) {
102
121
  return loadPersonaHiringModule()?.buildPersonaAvatarUrl(personaKey) ?? '';
103
122
  }
123
+ function buildHubManagerHiringCatalog() {
124
+ return loadManagerHiringModule()?.buildManagerHiringCatalog() ?? {
125
+ qualities: [],
126
+ services: [],
127
+ roles: {},
128
+ };
129
+ }
104
130
  async function getHubWorkspacePersonaState(dbService, userId, apiKey) {
105
131
  try {
106
132
  // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -1037,7 +1063,7 @@ class AiHubServer {
1037
1063
  // Issue #538 — source of truth for the "Hire a human manager" UI. Lazy-required
1038
1064
  // (not a top-level import) so the lightweight client CLI paths that import this
1039
1065
  // module for findAvailablePort do not eagerly load server-only config (repro #422).
1040
- managerHiring: require('../config/ai-manager-hiring').buildManagerHiringCatalog(),
1066
+ managerHiring: buildHubManagerHiringCatalog(),
1041
1067
  // Issue #540: server-authoritative manager team (personas assigned by this manager).
1042
1068
  managerTeam,
1043
1069
  };
@@ -384,7 +384,7 @@ const runAddIDE = async (options) => {
384
384
  const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
385
385
  if (detectedIDEs.length === 0) {
386
386
  console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected on your system.'));
387
- console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, Kiro, Cursor, VSCode, Codex, Windsurf'));
387
+ console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, Kiro, Cursor, VSCode, Codex, Grok, Windsurf'));
388
388
  console.log(chalk_1.default.blue('\n💡 Install an IDE and run this command again.'));
389
389
  return;
390
390
  }
@@ -446,7 +446,7 @@ const runAddIDE = async (options) => {
446
446
  exports.runAddIDE = runAddIDE;
447
447
  exports.addIDECommand = new commander_1.Command('add-ide')
448
448
  .description('Add FRAIM configuration to additional IDEs')
449
- .option('--ide <name>', 'Configure specific IDE (claude, claude-code, claude-desktop, claude-cowork, antigravity, gemini, gemini-cli, kiro, cursor, vscode, codex, windsurf)')
449
+ .option('--ide <name>', 'Configure specific IDE (claude, claude-code, claude-desktop, claude-cowork, antigravity, gemini, gemini-cli, kiro, cursor, vscode, codex, grok, windsurf)')
450
450
  .option('--all', 'Configure all detected IDEs')
451
451
  .option('--list', 'List all supported IDEs and their detection status')
452
452
  .action(exports.runAddIDE);
@@ -677,7 +677,7 @@ const runSetup = async (options) => {
677
677
  name: 'choice',
678
678
  message: 'How would you like to work with your AI employees?',
679
679
  choices: [
680
- { title: 'In my IDE (Claude Code, Cursor, or another AI tool)', value: 'ide' },
680
+ { title: 'In my IDE (Claude Code, Cursor, Grok, or another AI tool)', value: 'ide' },
681
681
  { title: 'In FRAIM Hub (browser-based — no terminal needed)', value: 'hub' },
682
682
  ],
683
683
  });
@@ -35,8 +35,8 @@ function checkIDEsDetected() {
35
35
  return {
36
36
  status: 'warning',
37
37
  message: 'No IDEs detected',
38
- suggestion: 'Install Claude Desktop, Cursor, or another supported IDE',
39
- details: { supportedIDEs: ['Claude Desktop', 'Cursor', 'Windsurf', 'VS Code', 'Zed', 'Codex'] }
38
+ suggestion: 'Install Claude Desktop, Cursor, Grok, or another supported IDE',
39
+ details: { supportedIDEs: ['Claude Desktop', 'Cursor', 'Windsurf', 'VS Code', 'Zed', 'Codex', 'Grok'] }
40
40
  };
41
41
  }
42
42
  };
@@ -2,7 +2,7 @@
2
2
  // IDE Format Adapters - transform logical server structure to IDE-specific formats
3
3
  // Uses the centralized registry to determine server types
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.IDE_FORMATS = exports.CodexFormat = exports.WindsurfFormat = exports.ClaudeCodeFormat = exports.ClaudeFormat = exports.GeminiCliFormat = exports.VSCodeFormat = exports.KiroFormat = exports.StandardFormat = void 0;
5
+ exports.IDE_FORMATS = exports.GrokFormat = exports.CodexFormat = exports.WindsurfFormat = exports.ClaudeCodeFormat = exports.ClaudeFormat = exports.GeminiCliFormat = exports.VSCodeFormat = exports.KiroFormat = exports.StandardFormat = void 0;
6
6
  exports.getIDEFormat = getIDEFormat;
7
7
  const mcp_server_registry_1 = require("./mcp-server-registry");
8
8
  const provider_registry_1 = require("../providers/provider-registry");
@@ -252,6 +252,13 @@ class CodexFormat {
252
252
  }
253
253
  }
254
254
  exports.CodexFormat = CodexFormat;
255
+ class GrokFormat extends CodexFormat {
256
+ constructor() {
257
+ super(...arguments);
258
+ this.name = 'grok';
259
+ }
260
+ }
261
+ exports.GrokFormat = GrokFormat;
255
262
  // Format registry
256
263
  exports.IDE_FORMATS = {
257
264
  standard: new StandardFormat(),
@@ -261,7 +268,8 @@ exports.IDE_FORMATS = {
261
268
  claude: new ClaudeFormat(),
262
269
  'claude-code': new ClaudeCodeFormat(),
263
270
  windsurf: new WindsurfFormat(),
264
- codex: new CodexFormat()
271
+ codex: new CodexFormat(),
272
+ grok: new GrokFormat()
265
273
  };
266
274
  function getIDEFormat(configType) {
267
275
  const format = exports.IDE_FORMATS[configType];
@@ -187,7 +187,9 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
187
187
  }
188
188
  const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
189
189
  const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
190
- const serversToAdd = getAllMCPServerIds();
190
+ const baseServerIds = getAllMCPServerIds();
191
+ const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
192
+ const serversToAdd = [...baseServerIds, ...providerServerIds];
191
193
  const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
192
194
  fs_1.default.writeFileSync(configPath, mergeResult.content);
193
195
  mergeResult.addedServers.forEach(server => {
@@ -254,7 +256,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
254
256
  const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
255
257
  if (detectedIDEs.length === 0 && (!selectedIDEs || selectedIDEs.length === 0)) {
256
258
  console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
257
- console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, Kiro, Cursor, VSCode, Codex, Windsurf'));
259
+ console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, Kiro, Cursor, VSCode, Codex, Grok, Windsurf'));
258
260
  console.log(chalk_1.default.blue('\n💡 You can install an IDE and run setup again later.'));
259
261
  console.log(chalk_1.default.gray(' Or continue with manual MCP configuration.'));
260
262
  if (process.env.FRAIM_NON_INTERACTIVE) {
@@ -79,6 +79,13 @@ const detectCodexSurface = () => {
79
79
  ];
80
80
  return checkMultiplePaths(paths);
81
81
  };
82
+ const detectGrokSurface = () => {
83
+ const paths = [
84
+ '~/.grok',
85
+ '~/.grok/config.toml'
86
+ ];
87
+ return checkMultiplePaths(paths);
88
+ };
82
89
  exports.IDE_CONFIGS = [
83
90
  {
84
91
  name: 'Claude Code',
@@ -180,6 +187,17 @@ exports.IDE_CONFIGS = [
180
187
  detectMethod: detectCodexSurface,
181
188
  description: 'Codex AI development environment'
182
189
  },
190
+ {
191
+ name: 'Grok',
192
+ configPath: '~/.grok/config.toml',
193
+ configFormat: 'toml',
194
+ configType: 'grok',
195
+ invocationProfile: 'grok-skill',
196
+ detectMethod: detectGrokSurface,
197
+ supportsConfigBootstrap: true,
198
+ aliases: ['grok', 'grok-cli', 'grok cli', 'grok build'],
199
+ description: 'xAI Grok Build CLI local settings'
200
+ },
183
201
  {
184
202
  name: 'Windsurf',
185
203
  configPath: '~/.codeium/windsurf/mcp_config.json',
@@ -219,6 +237,8 @@ const isDetectedForMode = (ide, mode) => {
219
237
  return availableByVersionProbe('claude');
220
238
  case 'codex':
221
239
  return availableByVersionProbe('codex');
240
+ case 'grok':
241
+ return availableByVersionProbe('grok');
222
242
  case 'gemini-cli':
223
243
  return detectGeminiCli();
224
244
  default:
@@ -35,7 +35,7 @@ function describeOnboardingInvocationSurfaces(installedIDEs) {
35
35
  return `${ide.name}: onboard this project`;
36
36
  if (profile === 'instructions-only')
37
37
  return `${ide.name}: "onboard this project"`;
38
- // claude-slash, vscode-prompt, codex-skill, windsurf-command, kiro-hashtag, gemini-command
38
+ // claude-slash, vscode-prompt, codex-skill, grok-skill, windsurf-command, kiro-hashtag, gemini-command
39
39
  return `${ide.name}: /fraim onboard this project`;
40
40
  });
41
41
  }
@@ -55,7 +55,7 @@ async function installSlashCommands(homeDir) {
55
55
  }
56
56
  /**
57
57
  * Install FRAIM invocation artifacts for non-Claude IDEs.
58
- * Supports: Cursor, Codex, Gemini CLI, Windsurf, Kiro
58
+ * Supports: Cursor, Codex, Grok, Gemini CLI, Windsurf, Kiro
59
59
  * Does not overwrite existing files.
60
60
  */
61
61
  async function installGlobalRules(homeDir) {
@@ -68,6 +68,10 @@ async function installGlobalRules(homeDir) {
68
68
  if (fs_1.default.existsSync(codexDir)) {
69
69
  installFileIfMissing(path_1.default.join(codexDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildCodexSkillContent)(), 'Codex FRAIM skill (~/.codex/skills/fraim/SKILL.md)');
70
70
  }
71
+ const grokDir = path_1.default.join(home, '.grok');
72
+ if (fs_1.default.existsSync(grokDir)) {
73
+ installFileIfMissing(path_1.default.join(grokDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildGrokSkillContent)(), 'Grok FRAIM skill (~/.grok/skills/fraim/SKILL.md)');
74
+ }
71
75
  const geminiDir = path_1.default.join(home, '.gemini');
72
76
  if (fs_1.default.existsSync(geminiDir)) {
73
77
  installFileIfMissing(path_1.default.join(geminiDir, 'commands', 'fraim.toml'), (0, ide_invocation_surfaces_1.buildGeminiCommandContent)(), 'Gemini CLI FRAIM command (~/.gemini/commands/fraim.toml)');
@@ -7,6 +7,7 @@ exports.buildClaudeCommandShimContent = buildClaudeCommandShimContent;
7
7
  exports.buildClaudeSlashCommandContent = buildClaudeSlashCommandContent;
8
8
  exports.buildCursorMentionRuleContent = buildCursorMentionRuleContent;
9
9
  exports.buildCodexSkillContent = buildCodexSkillContent;
10
+ exports.buildGrokSkillContent = buildGrokSkillContent;
10
11
  exports.buildWindsurfCommandContent = buildWindsurfCommandContent;
11
12
  exports.buildKiroCommandContent = buildKiroCommandContent;
12
13
  exports.buildAntigravityCommandContent = buildAntigravityCommandContent;
@@ -95,13 +96,18 @@ ${buildFraimInvocationBody('generic-tool-discovery')}
95
96
  `;
96
97
  }
97
98
  function buildCodexSkillContent() {
98
- return `# FRAIM
99
-
99
+ return `# FRAIM
100
+
100
101
  ${buildFraimInvocationBody('codex-tool-search')}`;
101
102
  }
103
+ function buildGrokSkillContent() {
104
+ return `# FRAIM
105
+
106
+ ${buildFraimInvocationBody('generic-tool-discovery')}`;
107
+ }
102
108
  function buildWindsurfCommandContent() {
103
- return `# FRAIM
104
-
109
+ return `# FRAIM
110
+
105
111
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
106
112
  }
107
113
  function buildKiroCommandContent() {
@@ -139,6 +145,8 @@ function describeInvocationSurface(ideName, invocationProfile) {
139
145
  return `${ideName}: /fraim via workspace prompt`;
140
146
  case 'codex-skill':
141
147
  return `${ideName}: /fraim, $fraim`;
148
+ case 'grok-skill':
149
+ return `${ideName}: /fraim`;
142
150
  case 'windsurf-command':
143
151
  return `${ideName}: /fraim`;
144
152
  case 'kiro-hashtag':
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateGeminiCliMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
3
+ exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateGeminiCliMCPServers = exports.generateVSCodeMCPServers = exports.generateGrokMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
4
4
  const mcp_server_builder_1 = require("../mcp/mcp-server-builder");
5
5
  const ide_formats_1 = require("../mcp/ide-formats");
6
6
  const normalizeTokens = (tokenInput) => {
@@ -151,6 +151,15 @@ const generateCodexMCPServers = async (fraimKey, tokenInput, providerConfigs) =>
151
151
  return format.transform(builder.getServers());
152
152
  };
153
153
  exports.generateCodexMCPServers = generateCodexMCPServers;
154
+ const generateGrokMCPServers = async (fraimKey, tokenInput, providerConfigs) => {
155
+ const tokens = normalizeTokens(tokenInput);
156
+ const builder = new mcp_server_builder_1.MCPServerBuilder();
157
+ builder.addBaseServers(fraimKey);
158
+ await addProviderServers(builder, tokens, providerConfigs);
159
+ const format = (0, ide_formats_1.getIDEFormat)('grok');
160
+ return format.transform(builder.getServers());
161
+ };
162
+ exports.generateGrokMCPServers = generateGrokMCPServers;
154
163
  const generateVSCodeMCPServers = async (fraimKey, tokenInput, providerConfigs) => {
155
164
  const tokens = normalizeTokens(tokenInput);
156
165
  const builder = new mcp_server_builder_1.MCPServerBuilder();
@@ -194,6 +203,8 @@ const generateMCPConfig = async (configType, fraimKey, tokenInput, providerConfi
194
203
  return await (0, exports.generateGeminiCliMCPServers)(fraimKey, tokenInput, providerConfigs);
195
204
  case 'codex':
196
205
  return await (0, exports.generateCodexMCPServers)(fraimKey, tokenInput, providerConfigs);
206
+ case 'grok':
207
+ return await (0, exports.generateGrokMCPServers)(fraimKey, tokenInput, providerConfigs);
197
208
  case 'windsurf':
198
209
  return await (0, exports.generateWindsurfMCPServers)(fraimKey, tokenInput, providerConfigs);
199
210
  default:
@@ -15,6 +15,7 @@ const CLAUDE_FRAIM_COMMAND_PATH = path_1.default.join('.claude', 'commands', 'fr
15
15
  const CLAUDE_FRAIM_SKILL_PATH = path_1.default.join('.claude', 'skills', 'fraim', 'SKILL.md');
16
16
  const VSCODE_FRAIM_PROMPT_PATH = path_1.default.join('.github', 'prompts', 'fraim.prompt.md');
17
17
  const CODEX_FRAIM_SKILL_PATH = path_1.default.join('.codex', 'skills', 'fraim', 'SKILL.md');
18
+ const GROK_FRAIM_SKILL_PATH = path_1.default.join('.grok', 'skills', 'fraim', 'SKILL.md');
18
19
  const GEMINI_FRAIM_COMMAND_PATH = path_1.default.join('.gemini', 'commands', 'fraim.toml');
19
20
  const GEMINI_PROJECT_INSTRUCTIONS_PATH = path_1.default.join('.gemini', 'GEMINI.md');
20
21
  const WINDSURF_FRAIM_COMMAND_PATH = path_1.default.join('.windsurf', 'commands', 'fraim.md');
@@ -118,6 +119,7 @@ ${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discover
118
119
  { path: CLAUDE_FRAIM_SKILL_PATH, content: (0, ide_invocation_surfaces_1.buildClaudeSkillContent)() },
119
120
  { path: CLAUDE_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildClaudeCommandShimContent)() },
120
121
  { path: CODEX_FRAIM_SKILL_PATH, content: (0, ide_invocation_surfaces_1.buildCodexSkillContent)() },
122
+ { path: GROK_FRAIM_SKILL_PATH, content: (0, ide_invocation_surfaces_1.buildGrokSkillContent)() },
121
123
  { path: GEMINI_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildGeminiCommandContent)() },
122
124
  { path: GEMINI_PROJECT_INSTRUCTIONS_PATH, content: geminiProjectInstructions },
123
125
  { path: WINDSURF_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildWindsurfCommandContent)() },
@@ -140,6 +142,7 @@ function ensureAgentAdapterFiles(projectRoot) {
140
142
  || file.path === CLAUDE_FRAIM_SKILL_PATH
141
143
  || file.path === CLAUDE_FRAIM_COMMAND_PATH
142
144
  || file.path === CODEX_FRAIM_SKILL_PATH
145
+ || file.path === GROK_FRAIM_SKILL_PATH
143
146
  || file.path === GEMINI_FRAIM_COMMAND_PATH
144
147
  || file.path === GEMINI_PROJECT_INSTRUCTIONS_PATH
145
148
  || file.path === WINDSURF_FRAIM_COMMAND_PATH
@@ -187,6 +187,11 @@ class InheritanceParser {
187
187
  const parentMeta = JSON.parse(parentMatch[1]);
188
188
  const mergedMeta = { ...parentMeta, ...childMeta };
189
189
  delete mergedMeta.extends; // Remove extends from final merged content
190
+ // Deep-merge phase routing so child only overrides the entries it specifies;
191
+ // parent routing for untouched phases flows through unchanged.
192
+ if (parentMeta.phases && childMeta.phases) {
193
+ mergedMeta.phases = { ...parentMeta.phases, ...childMeta.phases };
194
+ }
190
195
  // 2. Extract Body (everything after frontmatter)
191
196
  const childBody = this.stripRedundantParentImports(child.substring(childMatch[0].length).trim(), typeof childMeta.extends === 'string' ? childMeta.extends : undefined);
192
197
  const parentBody = parent.substring(parentMatch[0].length).trim();
@@ -174,8 +174,8 @@ function normalizeRows(rows) {
174
174
  ...(existingById.get(canonical.id) || {}),
175
175
  }));
176
176
  }
177
- function buildConfiguredSurfaces() {
178
- const ides = (0, ide_detector_1.detectInstalledIDEs)();
177
+ function buildRunnableAgentSurfaces() {
178
+ const ides = (0, ide_detector_1.detectInstalledIDEs)('cli-runnable');
179
179
  const hints = (0, ide_global_integration_1.describeOnboardingInvocationSurfaces)(ides);
180
180
  return ides.map((ide, index) => ({
181
181
  id: ide.configType,
@@ -333,7 +333,7 @@ class FirstRunSessionService {
333
333
  byId.set(surface.id, surface);
334
334
  }
335
335
  if (this.fakeMode !== 'no-agents') {
336
- for (const surface of buildConfiguredSurfaces()) {
336
+ for (const surface of buildRunnableAgentSurfaces()) {
337
337
  byId.set(surface.id, surface);
338
338
  }
339
339
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.169",
3
+ "version": "2.0.171",
4
4
  "description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -3529,8 +3529,9 @@ function rerunLastJob() {
3529
3529
  openPalette();
3530
3530
  return;
3531
3531
  }
3532
- showRerunToast('Re-running: ' + (state.lastRun.job.title || state.lastRun.job.id));
3533
- startRun(state.lastRun.job, state.lastRun.instructions, state.lastRun.employeeId);
3532
+ const lastRun = state.lastRun;
3533
+ startRun(lastRun.job, lastRun.instructions, lastRun.employeeId);
3534
+ showRerunToast('Re-running: ' + (lastRun.job.title || lastRun.job.id));
3534
3535
  }
3535
3536
 
3536
3537
  function showRerunToast(msg) {
@@ -3538,7 +3539,7 @@ function showRerunToast(msg) {
3538
3539
  t.className = 'cp-rerun-toast';
3539
3540
  t.textContent = msg;
3540
3541
  document.body.appendChild(t);
3541
- setTimeout(() => { if (t.parentNode) t.parentNode.removeChild(t); }, 2500);
3542
+ setTimeout(() => { if (t.parentNode) t.parentNode.removeChild(t); }, 5000);
3542
3543
  }
3543
3544
 
3544
3545
  // ---------------------------------------------------------------------------
@@ -3978,6 +3979,9 @@ async function startRun(job, instructions, employeeId, preassignedConvId) {
3978
3979
  };
3979
3980
  upsertConversation(conv);
3980
3981
  state.activeId = conv.id;
3982
+ // Issue #539: make Cmd/Ctrl+Shift+R available as soon as the run is queued,
3983
+ // not only after the server responds with the run id.
3984
+ state.lastRun = { job, instructions, employeeId };
3981
3985
  persistConversations();
3982
3986
  renderRail();
3983
3987
  renderActive();
@@ -4008,9 +4012,8 @@ async function startRun(job, instructions, employeeId, preassignedConvId) {
4008
4012
  renderRail();
4009
4013
  renderActive();
4010
4014
  startPolling();
4011
- // Issue #539: record last run for Cmd+Shift+R re-run and update in-memory
4012
- // preferences so the next palette open shows the correct recent section.
4013
- state.lastRun = { job, instructions, employeeId };
4015
+ // Issue #539: update in-memory preferences so the next palette open shows
4016
+ // the correct recent section.
4014
4017
  if (state.bootstrap && state.bootstrap.preferences) {
4015
4018
  const prefs = state.bootstrap.preferences;
4016
4019
  const nextIds = [job.id, ...(prefs.recentJobIds || []).filter((id) => id !== job.id)].slice(0, 8);