vibe-coding-master 0.2.4 → 0.2.5
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/backend/adapters/claude-adapter.js +9 -2
- package/dist/backend/api/claude-hook-routes.js +7 -0
- package/dist/backend/server.js +2 -1
- package/dist/backend/services/app-settings-service.js +49 -1
- package/dist/backend/services/claude-hook-service.js +61 -11
- package/dist/backend/services/harness-service.js +6 -5
- package/dist/backend/services/round-service.js +40 -7
- package/dist/backend/services/session-service.js +16 -2
- package/dist/shared/types/app-settings.js +17 -0
- package/dist/shared/types/session.js +37 -1
- package/dist-frontend/assets/index-BRrFbDjL.js +90 -0
- package/dist-frontend/assets/index-DQ-pEUma.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/gateway-design.md +585 -0
- package/docs/product-design.md +49 -7
- package/docs/v0.2-implementation-plan.md +78 -68
- package/docs/vcm-cc-best-practices.md +18 -4
- package/package.json +1 -1
- package/scripts/install-vcm-harness.mjs +3 -2
- package/dist-frontend/assets/index-CfZ3VYXY.js +0 -90
- package/dist-frontend/assets/index-CfqduxVB.css +0 -32
|
@@ -17,19 +17,26 @@ export function createClaudeAdapter(runner) {
|
|
|
17
17
|
}
|
|
18
18
|
return result.stdout.trim();
|
|
19
19
|
},
|
|
20
|
-
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false) {
|
|
20
|
+
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default") {
|
|
21
21
|
const args = ["--agent", role];
|
|
22
22
|
if (claudeSessionId) {
|
|
23
23
|
args.push(resume ? "--resume" : "--session-id", claudeSessionId);
|
|
24
24
|
}
|
|
25
|
+
args.push("--model", model);
|
|
25
26
|
if (permissionMode === "bypassPermissions") {
|
|
26
27
|
args.push("--permission-mode", "bypassPermissions");
|
|
27
28
|
}
|
|
28
29
|
return {
|
|
29
30
|
command,
|
|
30
31
|
args,
|
|
31
|
-
display:
|
|
32
|
+
display: [command, ...args].map(formatDisplayArg).join(" ")
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
function formatDisplayArg(value) {
|
|
38
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
42
|
+
}
|
|
@@ -5,4 +5,11 @@ export function registerClaudeHookRoutes(app, deps) {
|
|
|
5
5
|
app.post("/api/hooks/claude-code/stop", async (request) => {
|
|
6
6
|
return deps.claudeHookService.handleStopHook(request.body);
|
|
7
7
|
});
|
|
8
|
+
app.post("/api/hooks/claude-code/permission-request", async (request, reply) => {
|
|
9
|
+
const result = await deps.claudeHookService.handlePermissionRequestHook(request.body);
|
|
10
|
+
if (!result) {
|
|
11
|
+
return reply.code(204).send();
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
});
|
|
8
15
|
}
|
package/dist/backend/server.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { ROLE_NAMES } from "../../shared/constants.js";
|
|
5
|
+
import { createDefaultLaunchTemplate } from "../../shared/types/app-settings.js";
|
|
6
|
+
import { CLAUDE_MODEL_OPTIONS } from "../../shared/types/session.js";
|
|
4
7
|
const MAX_RECENT_REPOSITORIES = 5;
|
|
5
8
|
export function createAppSettingsService(deps) {
|
|
6
9
|
const settingsPath = deps.settingsPath ?? path.join(homedir(), ".vcm", "settings.json");
|
|
@@ -184,7 +187,9 @@ function normalizePreferences(input) {
|
|
|
184
187
|
: candidate.roundCompletionAlerts;
|
|
185
188
|
return {
|
|
186
189
|
themeMode: normalizeThemeMode(candidate.themeMode),
|
|
187
|
-
flowPauseAlerts: rawFlowPauseAlerts !== false
|
|
190
|
+
flowPauseAlerts: rawFlowPauseAlerts !== false,
|
|
191
|
+
permissionRequestMode: normalizePermissionRequestMode(candidate.permissionRequestMode),
|
|
192
|
+
launchTemplate: normalizeLaunchTemplate(candidate.launchTemplate)
|
|
188
193
|
};
|
|
189
194
|
}
|
|
190
195
|
function normalizeThemeMode(input) {
|
|
@@ -193,6 +198,49 @@ function normalizeThemeMode(input) {
|
|
|
193
198
|
}
|
|
194
199
|
return "system";
|
|
195
200
|
}
|
|
201
|
+
function normalizePermissionRequestMode(input) {
|
|
202
|
+
if (input === "allowAll") {
|
|
203
|
+
return input;
|
|
204
|
+
}
|
|
205
|
+
return "off";
|
|
206
|
+
}
|
|
207
|
+
function normalizeLaunchTemplate(input) {
|
|
208
|
+
const defaults = createDefaultLaunchTemplate();
|
|
209
|
+
if (!isObject(input)) {
|
|
210
|
+
return defaults;
|
|
211
|
+
}
|
|
212
|
+
const rawRoles = isObject(input.roles) ? input.roles : {};
|
|
213
|
+
const roles = {};
|
|
214
|
+
for (const role of ROLE_NAMES) {
|
|
215
|
+
roles[role] = normalizeRoleLaunchTemplateEntry(rawRoles[role], defaults.roles[role]);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
version: 1,
|
|
219
|
+
roles,
|
|
220
|
+
autoOrchestration: input.autoOrchestration !== false,
|
|
221
|
+
translationEnabled: input.translationEnabled !== false
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function normalizeRoleLaunchTemplateEntry(input, fallback) {
|
|
225
|
+
const candidate = isObject(input) ? input : {};
|
|
226
|
+
return {
|
|
227
|
+
permissionMode: normalizeClaudePermissionMode(candidate.permissionMode, fallback.permissionMode),
|
|
228
|
+
model: normalizeClaudeModel(candidate.model, fallback.model)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function normalizeClaudePermissionMode(input, fallback) {
|
|
232
|
+
if (input === "bypassPermissions" || input === "default") {
|
|
233
|
+
return input;
|
|
234
|
+
}
|
|
235
|
+
return fallback;
|
|
236
|
+
}
|
|
237
|
+
function normalizeClaudeModel(input, fallback) {
|
|
238
|
+
if (typeof input !== "string") {
|
|
239
|
+
return fallback;
|
|
240
|
+
}
|
|
241
|
+
const model = CLAUDE_MODEL_OPTIONS.find((option) => option.value === input);
|
|
242
|
+
return model?.value ?? fallback;
|
|
243
|
+
}
|
|
196
244
|
function normalizeTranslationConfig(input) {
|
|
197
245
|
if (!input || typeof input !== "object") {
|
|
198
246
|
return undefined;
|
|
@@ -86,6 +86,23 @@ export function createClaudeHookService(deps) {
|
|
|
86
86
|
throwUnsupportedEvent(eventName);
|
|
87
87
|
}
|
|
88
88
|
const context = await getHookContext(input);
|
|
89
|
+
const scopedRouteDispatchInput = {
|
|
90
|
+
repoRoot: context.project.repoRoot,
|
|
91
|
+
taskRepoRoot: context.taskRepoRoot,
|
|
92
|
+
stateRepoRoot: context.taskRepoRoot,
|
|
93
|
+
stateRoot: context.config.stateRoot,
|
|
94
|
+
handoffDir: context.task.handoffDir,
|
|
95
|
+
taskSlug: input.taskSlug,
|
|
96
|
+
stoppedRole: input.role
|
|
97
|
+
};
|
|
98
|
+
const settleRouteDispatchInput = {
|
|
99
|
+
repoRoot: context.project.repoRoot,
|
|
100
|
+
taskRepoRoot: context.taskRepoRoot,
|
|
101
|
+
stateRepoRoot: context.taskRepoRoot,
|
|
102
|
+
stateRoot: context.config.stateRoot,
|
|
103
|
+
handoffDir: context.task.handoffDir,
|
|
104
|
+
taskSlug: input.taskSlug
|
|
105
|
+
};
|
|
89
106
|
const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
|
|
90
107
|
taskSlug: input.taskSlug,
|
|
91
108
|
role: input.role,
|
|
@@ -99,7 +116,17 @@ export function createClaudeHookService(deps) {
|
|
|
99
116
|
stateRoot: context.config.stateRoot,
|
|
100
117
|
taskSlug: input.taskSlug,
|
|
101
118
|
role: input.role,
|
|
102
|
-
eventName
|
|
119
|
+
eventName,
|
|
120
|
+
settleGuard: async () => {
|
|
121
|
+
const pending = await deps.messageService.listPendingRouteFiles(settleRouteDispatchInput);
|
|
122
|
+
if (pending.length === 0) {
|
|
123
|
+
return { action: "pause" };
|
|
124
|
+
}
|
|
125
|
+
const retried = await deps.messageService.scanAndDispatchPendingRouteFiles(settleRouteDispatchInput);
|
|
126
|
+
return retried.some((result) => result.delivered)
|
|
127
|
+
? { action: "continue", reason: "pending route message dispatched" }
|
|
128
|
+
: { action: "pause" };
|
|
129
|
+
}
|
|
103
130
|
});
|
|
104
131
|
if (session) {
|
|
105
132
|
await deps.translationService.recordConversationBoundary({
|
|
@@ -112,15 +139,7 @@ export function createClaudeHookService(deps) {
|
|
|
112
139
|
occurredAt: session.lastStopAt ?? session.updatedAt
|
|
113
140
|
});
|
|
114
141
|
}
|
|
115
|
-
const dispatched = await deps.messageService.scanAndDispatchPendingRouteFiles(
|
|
116
|
-
repoRoot: context.project.repoRoot,
|
|
117
|
-
taskRepoRoot: context.taskRepoRoot,
|
|
118
|
-
stateRepoRoot: context.taskRepoRoot,
|
|
119
|
-
stateRoot: context.config.stateRoot,
|
|
120
|
-
handoffDir: context.task.handoffDir,
|
|
121
|
-
taskSlug: input.taskSlug,
|
|
122
|
-
stoppedRole: input.role
|
|
123
|
-
});
|
|
142
|
+
const dispatched = await deps.messageService.scanAndDispatchPendingRouteFiles(scopedRouteDispatchInput);
|
|
124
143
|
return {
|
|
125
144
|
ok: true,
|
|
126
145
|
eventName,
|
|
@@ -130,6 +149,36 @@ export function createClaudeHookService(deps) {
|
|
|
130
149
|
dispatchedCount: dispatched.filter((result) => result.delivered).length
|
|
131
150
|
};
|
|
132
151
|
}
|
|
152
|
+
async function handlePermissionRequestHook(input) {
|
|
153
|
+
if (!isRoleName(input.role)) {
|
|
154
|
+
throw new VcmError({
|
|
155
|
+
code: "HOOK_ROLE_INVALID",
|
|
156
|
+
message: `Unknown hook role: ${input.role}`,
|
|
157
|
+
statusCode: 400
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const eventName = input.event.hook_event_name;
|
|
161
|
+
if (eventName !== "PermissionRequest") {
|
|
162
|
+
throw new VcmError({
|
|
163
|
+
code: "HOOK_EVENT_UNSUPPORTED",
|
|
164
|
+
message: `Unsupported Claude Code permission hook event: ${String(eventName)}`,
|
|
165
|
+
statusCode: 400,
|
|
166
|
+
hint: "Use this endpoint for Claude Code PermissionRequest hooks only."
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const preferences = await deps.appSettings.getPreferences();
|
|
170
|
+
if (preferences.permissionRequestMode !== "allowAll") {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
hookSpecificOutput: {
|
|
175
|
+
hookEventName: "PermissionRequest",
|
|
176
|
+
decision: {
|
|
177
|
+
behavior: "allow"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
133
182
|
return {
|
|
134
183
|
async handleHook(input) {
|
|
135
184
|
const eventName = parseHookEvent(input.event.hook_event_name);
|
|
@@ -138,7 +187,8 @@ export function createClaudeHookService(deps) {
|
|
|
138
187
|
}
|
|
139
188
|
return handleStopHook(input);
|
|
140
189
|
},
|
|
141
|
-
handleStopHook
|
|
190
|
+
handleStopHook,
|
|
191
|
+
handlePermissionRequestHook
|
|
142
192
|
};
|
|
143
193
|
}
|
|
144
194
|
function parseHookEvent(value) {
|
|
@@ -26,7 +26,8 @@ const MANAGED_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=(\d+))? -->[\s\S]*?<!
|
|
|
26
26
|
const HASH_MANAGED_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=(\d+))?\n[\s\S]*?# VCM:END/m;
|
|
27
27
|
const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
|
|
28
28
|
const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
|
|
29
|
-
const
|
|
29
|
+
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
30
|
+
const VCM_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "PermissionRequest"];
|
|
30
31
|
const HARNESS_FILES = [
|
|
31
32
|
{
|
|
32
33
|
kind: "root-claude",
|
|
@@ -397,7 +398,7 @@ async function analyzeClaudeSettingsFile(fs, repoRoot) {
|
|
|
397
398
|
path: CLAUDE_SETTINGS_PATH,
|
|
398
399
|
action,
|
|
399
400
|
reason: exists
|
|
400
|
-
? "Claude Code hook settings do not contain the VCM
|
|
401
|
+
? "Claude Code hook settings do not contain the VCM hook bridge."
|
|
401
402
|
: "Claude Code hook settings are missing; VCM will create them."
|
|
402
403
|
},
|
|
403
404
|
nextContent: action === "ok" ? undefined : nextContent
|
|
@@ -432,7 +433,7 @@ function withVcmClaudeHooks(settings) {
|
|
|
432
433
|
: [];
|
|
433
434
|
hooks[eventName] = [
|
|
434
435
|
...existingMatchers.filter((entry) => !isVcmHookMatcher(entry)),
|
|
435
|
-
createVcmHookMatcher()
|
|
436
|
+
createVcmHookMatcher(eventName)
|
|
436
437
|
];
|
|
437
438
|
}
|
|
438
439
|
return {
|
|
@@ -440,12 +441,12 @@ function withVcmClaudeHooks(settings) {
|
|
|
440
441
|
hooks
|
|
441
442
|
};
|
|
442
443
|
}
|
|
443
|
-
function createVcmHookMatcher() {
|
|
444
|
+
function createVcmHookMatcher(eventName) {
|
|
444
445
|
return {
|
|
445
446
|
hooks: [
|
|
446
447
|
{
|
|
447
448
|
type: "command",
|
|
448
|
-
command: VCM_HOOK_COMMAND,
|
|
449
|
+
command: eventName === "PermissionRequest" ? VCM_PERMISSION_REQUEST_HOOK_COMMAND : VCM_HOOK_COMMAND,
|
|
449
450
|
timeout: 5
|
|
450
451
|
}
|
|
451
452
|
]
|
|
@@ -51,7 +51,7 @@ export function createRoundService(deps) {
|
|
|
51
51
|
await save(input, next);
|
|
52
52
|
return next;
|
|
53
53
|
}
|
|
54
|
-
async function settleIfStillCurrent(input, roundId, settleDeadlineAt) {
|
|
54
|
+
async function settleIfStillCurrent(input, roundId, settleDeadlineAt, settleGuard) {
|
|
55
55
|
await withTaskLock(input, async () => {
|
|
56
56
|
const state = await load(input);
|
|
57
57
|
const current = state.currentRound;
|
|
@@ -60,7 +60,30 @@ export function createRoundService(deps) {
|
|
|
60
60
|
current.settleDeadlineAt !== settleDeadlineAt) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
const timestamp = maxIsoTimestamp(now(), settleDeadlineAt);
|
|
64
|
+
if (settleGuard) {
|
|
65
|
+
const decision = await runSettleGuard(settleGuard, {
|
|
66
|
+
...input,
|
|
67
|
+
role: current.activeRole,
|
|
68
|
+
roundId,
|
|
69
|
+
settleDeadlineAt
|
|
70
|
+
});
|
|
71
|
+
if (decision.action === "continue") {
|
|
72
|
+
const nextRound = {
|
|
73
|
+
...current,
|
|
74
|
+
settleDeadlineAt: addMilliseconds(timestamp, settleMs)
|
|
75
|
+
};
|
|
76
|
+
const next = {
|
|
77
|
+
...state,
|
|
78
|
+
currentRound: nextRound,
|
|
79
|
+
updatedAt: timestamp
|
|
80
|
+
};
|
|
81
|
+
await save(input, next);
|
|
82
|
+
scheduleSettleTimer(input, nextRound, timestamp, settleGuard);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await settleIfNeeded(input, state, timestamp);
|
|
64
87
|
});
|
|
65
88
|
}
|
|
66
89
|
function clearSettleTimer(input) {
|
|
@@ -81,7 +104,7 @@ export function createRoundService(deps) {
|
|
|
81
104
|
settleTimers.delete(key);
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
|
-
function scheduleSettleTimer(input, round, timestamp) {
|
|
107
|
+
function scheduleSettleTimer(input, round, timestamp, settleGuard) {
|
|
85
108
|
if (round.status !== "settling" || !round.settleDeadlineAt) {
|
|
86
109
|
clearSettleTimer(input);
|
|
87
110
|
return;
|
|
@@ -90,7 +113,7 @@ export function createRoundService(deps) {
|
|
|
90
113
|
const delayMs = Math.max(0, new Date(round.settleDeadlineAt).getTime() - new Date(timestamp).getTime());
|
|
91
114
|
const timer = setTimer(() => {
|
|
92
115
|
settleTimers.delete(getRoundStatePath(input));
|
|
93
|
-
void settleIfStillCurrent(input, round.id, round.settleDeadlineAt ?? "").catch(() => undefined);
|
|
116
|
+
void settleIfStillCurrent(input, round.id, round.settleDeadlineAt ?? "", settleGuard).catch(() => undefined);
|
|
94
117
|
}, delayMs);
|
|
95
118
|
settleTimers.set(getRoundStatePath(input), timer);
|
|
96
119
|
}
|
|
@@ -112,8 +135,7 @@ export function createRoundService(deps) {
|
|
|
112
135
|
async getTaskRoundState(input) {
|
|
113
136
|
return withTaskLock(input, async () => {
|
|
114
137
|
const timestamp = now();
|
|
115
|
-
|
|
116
|
-
return toTaskRoundState(state, timestamp);
|
|
138
|
+
return toTaskRoundState(await load(input), timestamp);
|
|
117
139
|
});
|
|
118
140
|
},
|
|
119
141
|
async recordClaudeHookEvent(input) {
|
|
@@ -136,7 +158,7 @@ export function createRoundService(deps) {
|
|
|
136
158
|
clearSettleTimer(input);
|
|
137
159
|
}
|
|
138
160
|
else if (next.currentRound) {
|
|
139
|
-
scheduleSettleTimer(input, next.currentRound, timestamp);
|
|
161
|
+
scheduleSettleTimer(input, next.currentRound, timestamp, input.settleGuard);
|
|
140
162
|
}
|
|
141
163
|
return toTaskRoundState(next, timestamp);
|
|
142
164
|
});
|
|
@@ -314,6 +336,17 @@ function appendUniqueRole(roles, role) {
|
|
|
314
336
|
function addMilliseconds(value, milliseconds) {
|
|
315
337
|
return new Date(new Date(value).getTime() + milliseconds).toISOString();
|
|
316
338
|
}
|
|
339
|
+
function maxIsoTimestamp(left, right) {
|
|
340
|
+
return Date.parse(left) >= Date.parse(right) ? left : right;
|
|
341
|
+
}
|
|
342
|
+
async function runSettleGuard(settleGuard, input) {
|
|
343
|
+
try {
|
|
344
|
+
return await settleGuard(input);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return { action: "pause" };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
317
350
|
function getDurationMs(start, end) {
|
|
318
351
|
const startMs = Date.parse(start);
|
|
319
352
|
const endMs = Date.parse(end);
|
|
@@ -18,6 +18,7 @@ export function createSessionService(deps) {
|
|
|
18
18
|
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
19
19
|
const persisted = await loadPersistedRoleRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug, role);
|
|
20
20
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
21
|
+
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
21
22
|
const claudeSessionId = launchMode === "resume"
|
|
22
23
|
? persisted?.claudeSessionId
|
|
23
24
|
: randomUUID();
|
|
@@ -32,7 +33,7 @@ export function createSessionService(deps) {
|
|
|
32
33
|
const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
|
|
33
34
|
? persisted.transcriptPath
|
|
34
35
|
: claudeTranscriptPath(taskRepoRoot, claudeSessionId);
|
|
35
|
-
const startCommand = deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume");
|
|
36
|
+
const startCommand = deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model);
|
|
36
37
|
const runtimeSession = await deps.runtime.createSession({
|
|
37
38
|
taskSlug,
|
|
38
39
|
role,
|
|
@@ -61,6 +62,7 @@ export function createSessionService(deps) {
|
|
|
61
62
|
activityStatus: "idle",
|
|
62
63
|
command: startCommand.display,
|
|
63
64
|
permissionMode,
|
|
65
|
+
model,
|
|
64
66
|
cwd: taskRepoRoot,
|
|
65
67
|
terminalBackend: "node-pty",
|
|
66
68
|
pid: runtimeSession.pid,
|
|
@@ -240,7 +242,8 @@ async function loadPersistedRoleRecord(fs, repoRoot, stateRoot, taskSlug, role)
|
|
|
240
242
|
return record
|
|
241
243
|
? {
|
|
242
244
|
...record,
|
|
243
|
-
permissionMode: normalizeClaudePermissionMode(record.permissionMode)
|
|
245
|
+
permissionMode: normalizeClaudePermissionMode(record.permissionMode),
|
|
246
|
+
model: normalizeClaudeModel(record.model)
|
|
244
247
|
}
|
|
245
248
|
: undefined;
|
|
246
249
|
}
|
|
@@ -291,3 +294,14 @@ function normalizeClaudePermissionMode(value) {
|
|
|
291
294
|
}
|
|
292
295
|
return "default";
|
|
293
296
|
}
|
|
297
|
+
function normalizeClaudeModel(value) {
|
|
298
|
+
if (value === "best"
|
|
299
|
+
|| value === "fable"
|
|
300
|
+
|| value === "opus"
|
|
301
|
+
|| value === "opus[1m]"
|
|
302
|
+
|| value === "claude-opus-4-8"
|
|
303
|
+
|| value === "claude-opus-4-8[1m]") {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
return "default";
|
|
307
|
+
}
|
|
@@ -1 +1,18 @@
|
|
|
1
|
+
import { ROLE_NAMES } from "../constants.js";
|
|
1
2
|
export const THEME_MODES = ["system", "light", "dark"];
|
|
3
|
+
export const PERMISSION_REQUEST_MODES = ["off", "allowAll"];
|
|
4
|
+
export function createDefaultLaunchTemplate() {
|
|
5
|
+
const roles = {};
|
|
6
|
+
for (const role of ROLE_NAMES) {
|
|
7
|
+
roles[role] = {
|
|
8
|
+
permissionMode: "default",
|
|
9
|
+
model: "default"
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
roles,
|
|
15
|
+
autoOrchestration: true,
|
|
16
|
+
translationEnabled: true
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -1 +1,37 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const CLAUDE_MODEL_OPTIONS = [
|
|
2
|
+
{
|
|
3
|
+
value: "default",
|
|
4
|
+
label: "Default",
|
|
5
|
+
description: "Account default"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
value: "best",
|
|
9
|
+
label: "Best",
|
|
10
|
+
description: "Fable 5 or latest Opus"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: "fable",
|
|
14
|
+
label: "Fable 5",
|
|
15
|
+
description: "1M context, v2.1.170+"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "opus",
|
|
19
|
+
label: "Opus",
|
|
20
|
+
description: "latest Opus"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "opus[1m]",
|
|
24
|
+
label: "Opus 1M",
|
|
25
|
+
description: "Force 1M context"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "claude-opus-4-8",
|
|
29
|
+
label: "Opus 4.8",
|
|
30
|
+
description: "Current Opus"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "claude-opus-4-8[1m]",
|
|
34
|
+
label: "Opus 4.8 1M",
|
|
35
|
+
description: "Opus 4.8 + 1M context"
|
|
36
|
+
}
|
|
37
|
+
];
|