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.
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-process.js +3 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +1 -0
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts +7 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.d.ts +1 -1
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +3 -1
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +5 -1
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +6 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-types.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +9 -7
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/settings.d.ts +17 -0
- package/dist/server/services/settings.d.ts.map +1 -1
- package/dist/server/services/settings.js +18 -2
- package/dist/server/services/settings.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +2 -2
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +4 -4
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +4 -1
- package/dist/server/services/websocket/settings-handlers.js.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +2 -2
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/claude-invoker-process.ts +4 -0
- package/server/cli/headless/runner.ts +1 -0
- package/server/cli/headless/types.ts +7 -1
- package/server/cli/improvisation-retry.ts +3 -0
- package/server/cli/improvisation-session-manager.ts +5 -1
- package/server/cli/improvisation-types.ts +6 -0
- package/server/services/plan/composer.ts +10 -7
- package/server/services/settings.ts +29 -2
- package/server/services/websocket/git-worktree-handlers.ts +1 -1
- package/server/services/websocket/session-handlers.ts +2 -2
- package/server/services/websocket/session-initialization.ts +4 -4
- package/server/services/websocket/settings-handlers.ts +4 -1
- 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:
|
|
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;
|