mstro-app 0.4.46 → 0.4.50

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 (50) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  2. package/dist/server/cli/headless/claude-invoker-process.js +3 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  4. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  5. package/dist/server/cli/headless/runner.js +1 -0
  6. package/dist/server/cli/headless/runner.js.map +1 -1
  7. package/dist/server/cli/headless/types.d.ts +7 -1
  8. package/dist/server/cli/headless/types.d.ts.map +1 -1
  9. package/dist/server/cli/improvisation-retry.d.ts +1 -1
  10. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  11. package/dist/server/cli/improvisation-retry.js +3 -1
  12. package/dist/server/cli/improvisation-retry.js.map +1 -1
  13. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  14. package/dist/server/cli/improvisation-session-manager.js +5 -1
  15. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  16. package/dist/server/cli/improvisation-types.d.ts +6 -0
  17. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  18. package/dist/server/cli/improvisation-types.js.map +1 -1
  19. package/dist/server/services/plan/composer.d.ts.map +1 -1
  20. package/dist/server/services/plan/composer.js +9 -7
  21. package/dist/server/services/plan/composer.js.map +1 -1
  22. package/dist/server/services/settings.d.ts +17 -0
  23. package/dist/server/services/settings.d.ts.map +1 -1
  24. package/dist/server/services/settings.js +18 -2
  25. package/dist/server/services/settings.js.map +1 -1
  26. package/dist/server/services/websocket/git-worktree-handlers.js +1 -1
  27. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  28. package/dist/server/services/websocket/session-handlers.js +2 -2
  29. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  30. package/dist/server/services/websocket/session-initialization.js +4 -4
  31. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  32. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  33. package/dist/server/services/websocket/settings-handlers.js +4 -1
  34. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  35. package/dist/server/services/websocket/tab-handlers.js +2 -2
  36. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  37. package/package.json +1 -1
  38. package/server/cli/headless/claude-invoker-process.ts +4 -0
  39. package/server/cli/headless/runner.ts +1 -0
  40. package/server/cli/headless/types.ts +7 -1
  41. package/server/cli/improvisation-retry.ts +3 -0
  42. package/server/cli/improvisation-session-manager.ts +5 -1
  43. package/server/cli/improvisation-types.ts +6 -0
  44. package/server/services/plan/composer.ts +10 -7
  45. package/server/services/settings.ts +29 -2
  46. package/server/services/websocket/git-worktree-handlers.ts +1 -1
  47. package/server/services/websocket/session-handlers.ts +2 -2
  48. package/server/services/websocket/session-initialization.ts +4 -4
  49. package/server/services/websocket/settings-handlers.ts +4 -1
  50. package/server/services/websocket/tab-handlers.ts +2 -2
@@ -97,6 +97,7 @@ export class ImprovisationSessionManager extends EventEmitter {
97
97
  verbose: options.verbose || false,
98
98
  noColor: options.noColor || false,
99
99
  model: options.model,
100
+ effortLevel: options.effortLevel,
100
101
  };
101
102
 
102
103
  this.sessionId = this.options.sessionId;
@@ -157,6 +158,7 @@ export class ImprovisationSessionManager extends EventEmitter {
157
158
  sequence_number: sequenceNumber,
158
159
  is_resumed_session: this.isResumedSession,
159
160
  model: this.options.model || 'default',
161
+ effort_level: this.options.effortLevel || 'auto',
160
162
  });
161
163
 
162
164
  // Save pending movement immediately so history survives page refresh
@@ -337,7 +339,7 @@ export class ImprovisationSessionManager extends EventEmitter {
337
339
 
338
340
  if (shouldRetrySignalCrash(result, state, session, maxRetries, promptWithAttachments, callbacks)) { this.syncSessionStateBack(session); return true; }
339
341
  if (shouldRetryContextLoss(result, state, session, useResume, nativeTimeouts, maxRetries, promptWithAttachments, callbacks)) { this.syncSessionStateBack(session); return true; }
340
- if (applyToolTimeoutRetry(state, maxRetries, promptWithAttachments, callbacks, this.options.model)) return true;
342
+ if (applyToolTimeoutRetry(state, maxRetries, promptWithAttachments, callbacks, this.options.model, this.options.effortLevel)) return true;
341
343
  if (await shouldRetryPrematureCompletion(result, state, session, maxRetries, callbacks)) { this.syncSessionStateBack(session); return true; }
342
344
  this.syncSessionStateBack(session);
343
345
  return false;
@@ -418,6 +420,7 @@ export class ImprovisationSessionManager extends EventEmitter {
418
420
  sequence_number: sequenceNumber,
419
421
  duration_ms: Date.now() - execStart,
420
422
  model: this.options.model || 'default',
423
+ effort_level: this.options.effortLevel || 'auto',
421
424
  });
422
425
  this.queueOutput(`\n❌ Error: ${errorMessage}\n`);
423
426
  this.flushOutputQueue();
@@ -497,6 +500,7 @@ export class ImprovisationSessionManager extends EventEmitter {
497
500
  sequence_number: sequenceNumber,
498
501
  tool_count: result.toolUseHistory?.length || 0,
499
502
  model: this.options.model || 'default',
503
+ effort_level: this.options.effortLevel || 'auto',
500
504
  });
501
505
  this.emit('onSessionUpdate', this.getHistory());
502
506
  }
@@ -17,6 +17,12 @@ export interface ImprovisationOptions {
17
17
  noColor: boolean;
18
18
  /** Claude model for main execution (e.g., 'opus', 'sonnet'). 'default' = no --model flag. */
19
19
  model?: string;
20
+ /**
21
+ * Effort level for main execution (e.g., 'low', 'medium', 'high', 'xhigh', 'max').
22
+ * 'auto' (or undefined) = no --effort flag (Claude Code uses per-model default).
23
+ * Unsupported levels gracefully fall back to the highest supported level per model.
24
+ */
25
+ effortLevel?: string;
20
26
  }
21
27
 
22
28
  // File attachment for multimodal prompts (images)
@@ -89,7 +89,7 @@ function prepareAttachmentPrompt(
89
89
  (warning) => {
90
90
  ctx.broadcastToAll({
91
91
  type: 'planPromptProgress',
92
- data: { message: warning },
92
+ data: { message: warning, boardId: effectiveBoardId ?? null },
93
93
  });
94
94
  },
95
95
  );
@@ -287,10 +287,12 @@ User request: ${userPrompt}`;
287
287
  const { prompt: finalPrompt, imageAttachments, sessionId: attachmentSessionId } =
288
288
  prepareAttachmentPrompt(ctx, enrichedPrompt, attachments, workingDir, cc.effectiveBoardId);
289
289
 
290
+ const streamBoardId = cc.effectiveBoardId ?? null;
291
+
290
292
  try {
291
293
  ctx.broadcastToAll({
292
294
  type: 'planPromptProgress',
293
- data: { message: 'Starting project planning...' },
295
+ data: { message: 'Starting project planning...', boardId: streamBoardId },
294
296
  });
295
297
 
296
298
  const runner = new ResilientRunner({
@@ -305,7 +307,7 @@ User request: ${userPrompt}`;
305
307
  outputCallback: (text: string) => {
306
308
  ctx.send(ws, {
307
309
  type: 'planPromptStreaming',
308
- data: { token: text },
310
+ data: { token: text, boardId: streamBoardId },
309
311
  });
310
312
  },
311
313
  toolUseCallback: (() => {
@@ -315,7 +317,7 @@ User request: ${userPrompt}`;
315
317
  if (message) {
316
318
  ctx.broadcastToAll({
317
319
  type: 'planPromptProgress',
318
- data: { message },
320
+ data: { message, boardId: streamBoardId },
319
321
  });
320
322
  }
321
323
  };
@@ -326,14 +328,14 @@ User request: ${userPrompt}`;
326
328
 
327
329
  ctx.broadcastToAll({
328
330
  type: 'planPromptProgress',
329
- data: { message: 'Claude is planning your project...' },
331
+ data: { message: 'Claude is planning your project...', boardId: streamBoardId },
330
332
  });
331
333
 
332
334
  const result = await runner.run();
333
335
 
334
336
  ctx.broadcastToAll({
335
337
  type: 'planPromptProgress',
336
- data: { message: 'Finalizing project plan...' },
338
+ data: { message: 'Finalizing project plan...', boardId: streamBoardId },
337
339
  });
338
340
 
339
341
  ctx.send(ws, {
@@ -342,6 +344,7 @@ User request: ${userPrompt}`;
342
344
  response: result.completed ? 'Prompt executed successfully.' : (result.error || 'Unknown error'),
343
345
  success: result.completed,
344
346
  error: result.error || null,
347
+ boardId: streamBoardId,
345
348
  },
346
349
  });
347
350
 
@@ -353,7 +356,7 @@ User request: ${userPrompt}`;
353
356
  } catch (error) {
354
357
  ctx.send(ws, {
355
358
  type: 'planError',
356
- data: { error: error instanceof Error ? error.message : String(error) },
359
+ data: { error: error instanceof Error ? error.message : String(error), boardId: streamBoardId },
357
360
  });
358
361
  } finally {
359
362
  cleanupAttachments(workingDir, attachmentSessionId);
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * Structure:
10
10
  * {
11
- * "model": "opus"
11
+ * "model": "opus",
12
+ * "effortLevel": "auto"
12
13
  * }
13
14
  */
14
15
 
@@ -26,12 +27,22 @@ export interface MstroSettings {
26
27
  * - Any other string is passed as --model <value>
27
28
  */
28
29
  model: string
30
+ /**
31
+ * Effort level for main execution, passed to Claude Code as --effort.
32
+ * - 'auto' means don't pass --effort (let Claude Code use its per-model default:
33
+ * xhigh on Opus 4.7, high on Sonnet 4.6).
34
+ * - Any other string is passed as --effort <value>. Claude Code silently falls
35
+ * back to the highest supported level when a model doesn't support the value
36
+ * (e.g. xhigh → high on Sonnet 4.6). Haiku ignores it entirely.
37
+ */
38
+ effortLevel: string
29
39
  /** Per-repo preferred PR base branch, keyed by normalized remote URL */
30
40
  prBaseBranches?: Record<string, string>
31
41
  }
32
42
 
33
43
  const DEFAULT_SETTINGS: MstroSettings = {
34
- model: 'opus'
44
+ model: 'opus',
45
+ effortLevel: 'auto'
35
46
  }
36
47
 
37
48
  /**
@@ -90,6 +101,22 @@ export function setModel(model: string): void {
90
101
  saveSettings(settings)
91
102
  }
92
103
 
104
+ /**
105
+ * Get the current effort level setting
106
+ */
107
+ export function getEffortLevel(): string {
108
+ return getSettings().effortLevel
109
+ }
110
+
111
+ /**
112
+ * Update just the effort level setting
113
+ */
114
+ export function setEffortLevel(effortLevel: string): void {
115
+ const settings = getSettings()
116
+ settings.effortLevel = effortLevel
117
+ saveSettings(settings)
118
+ }
119
+
93
120
  /** Normalize a remote URL into a stable key (e.g. "github.com/owner/repo") */
94
121
  function normalizeRemoteUrl(remoteUrl: string): string {
95
122
  return remoteUrl
@@ -213,7 +213,7 @@ async function handleTabWorktreeSwitch(ctx: HandlerContext, ws: WSContext, msg:
213
213
  if (isBoardId(resolvedTabId)) {
214
214
  persistBoardWorktree(workingDir, resolvedTabId, null, null);
215
215
  }
216
- ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath: workingDir, branch: '' } });
216
+ ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath: null, branch: null } });
217
217
  handleGitStatus(ctx, ws, resolvedTabId, workingDir);
218
218
  return;
219
219
  }
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  import type { FileAttachment, ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
5
- import { getModel } from '../settings.js';
5
+ import { getEffortLevel, getModel } from '../settings.js';
6
6
  import type { HandlerContext } from './handler-context.js';
7
7
  import { runQualityScan } from './quality-service.js';
8
8
  import { resolveSkillPrompt } from './skill-handlers.js';
@@ -240,7 +240,7 @@ export function handleSessionMessage(ctx: HandlerContext, ws: WSContext, msg: We
240
240
  case 'new': {
241
241
  const oldSession = requireSession(ctx, ws, tabId);
242
242
  const oldSessionId = oldSession.getSessionInfo().sessionId;
243
- const newSession = oldSession.startNewSession({ model: getModel() });
243
+ const newSession = oldSession.startNewSession({ model: getModel(), effortLevel: getEffortLevel() });
244
244
  oldSession.destroy();
245
245
  ctx.sessions.delete(oldSessionId);
246
246
  setupSessionListeners(ctx, newSession, ws, tabId);
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  import { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
5
- import { getModel } from '../settings.js';
5
+ import { getEffortLevel, getModel } from '../settings.js';
6
6
  import type { HandlerContext } from './handler-context.js';
7
7
  import { buildOutputHistory, setupSessionListeners } from './session-handlers.js';
8
8
  import type { SessionRegistry } from './session-registry.js';
@@ -78,7 +78,7 @@ export async function initializeTab(ctx: HandlerContext, ws: WSContext, tabId: s
78
78
  }
79
79
 
80
80
  // 3. Create new session
81
- const session = new ImprovisationSessionManager({ workingDir, model: getModel() });
81
+ const session = new ImprovisationSessionManager({ workingDir, model: getModel(), effortLevel: getEffortLevel() });
82
82
  setupSessionListeners(ctx, session, ws, tabId);
83
83
 
84
84
  const sessionId = session.getSessionInfo().sessionId;
@@ -134,10 +134,10 @@ export async function resumeHistoricalSession(
134
134
  let isNewSession = false;
135
135
 
136
136
  try {
137
- session = ImprovisationSessionManager.resumeFromHistory(workingDir, historicalSessionId, { model: getModel() });
137
+ session = ImprovisationSessionManager.resumeFromHistory(workingDir, historicalSessionId, { model: getModel(), effortLevel: getEffortLevel() });
138
138
  } catch (error: unknown) {
139
139
  console.warn(`[WebSocketImproviseHandler] Could not resume session ${historicalSessionId}: ${error instanceof Error ? error.message : String(error)}. Creating new session.`);
140
- session = new ImprovisationSessionManager({ workingDir, model: getModel() });
140
+ session = new ImprovisationSessionManager({ workingDir, model: getModel(), effortLevel: getEffortLevel() });
141
141
  isNewSession = true;
142
142
  }
143
143
 
@@ -4,7 +4,7 @@
4
4
  import { spawn } from 'node:child_process';
5
5
  import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
- import { getSettings, setModel } from '../settings.js';
7
+ import { getSettings, setEffortLevel, setModel } from '../settings.js';
8
8
  import type { HandlerContext } from './handler-context.js';
9
9
  import type { WebSocketMessage, WSContext } from './types.js';
10
10
 
@@ -16,6 +16,9 @@ export function handleUpdateSettings(ctx: HandlerContext, _ws: WSContext, msg: W
16
16
  if (msg.data?.model !== undefined) {
17
17
  setModel(msg.data.model);
18
18
  }
19
+ if (msg.data?.effortLevel !== undefined) {
20
+ setEffortLevel(msg.data.effortLevel);
21
+ }
19
22
  ctx.broadcastToAll({ type: 'settingsUpdated', data: getSettings() });
20
23
  }
21
24
 
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  import { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
5
- import { getModel } from '../settings.js';
5
+ import { getEffortLevel, getModel } from '../settings.js';
6
6
  import type { HandlerContext } from './handler-context.js';
7
7
  import { buildOutputHistory, setupSessionListeners } from './session-handlers.js';
8
8
  import type { WebSocketMessage, WSContext } from './types.js';
@@ -130,7 +130,7 @@ export async function handleCreateTab(ctx: HandlerContext, ws: WSContext, workin
130
130
  return;
131
131
  }
132
132
 
133
- const session = new ImprovisationSessionManager({ workingDir, model: getModel() });
133
+ const session = new ImprovisationSessionManager({ workingDir, model: getModel(), effortLevel: getEffortLevel() });
134
134
  setupSessionListeners(ctx, session, ws, tabId);
135
135
 
136
136
  const sessionId = session.getSessionInfo().sessionId;