create-walle 0.9.21 → 0.9.22
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/README.md +5 -5
- package/package.json +2 -2
- package/template/claude-task-manager/api-prompts.js +13 -0
- package/template/claude-task-manager/api-reviews.js +5 -2
- package/template/claude-task-manager/db.js +348 -15
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +3 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/git-utils.js +146 -17
- package/template/claude-task-manager/lib/auth-rate-limit.js +23 -3
- package/template/claude-task-manager/lib/auth-rules.js +3 -0
- package/template/claude-task-manager/lib/document-review.js +33 -2
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +83 -0
- package/template/claude-task-manager/lib/mobile-auth-api.js +14 -0
- package/template/claude-task-manager/lib/restart-guard.js +68 -0
- package/template/claude-task-manager/lib/session-standup.js +36 -13
- package/template/claude-task-manager/lib/session-stream.js +11 -4
- package/template/claude-task-manager/lib/transport-security.js +50 -0
- package/template/claude-task-manager/lib/walle-transcript.js +16 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +6 -3
- package/template/claude-task-manager/public/css/reviews.css +10 -0
- package/template/claude-task-manager/public/css/setup.css +13 -0
- package/template/claude-task-manager/public/css/walle.css +145 -0
- package/template/claude-task-manager/public/index.html +539 -44
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +196 -0
- package/template/claude-task-manager/public/js/message-renderer.js +14 -3
- package/template/claude-task-manager/public/js/reviews.js +30 -6
- package/template/claude-task-manager/public/js/setup.js +42 -2
- package/template/claude-task-manager/public/js/stream-view.js +20 -1
- package/template/claude-task-manager/public/js/walle.js +314 -18
- package/template/claude-task-manager/public/m/app.css +789 -11
- package/template/claude-task-manager/public/m/app.js +1070 -67
- package/template/claude-task-manager/public/m/claim.html +9 -2
- package/template/claude-task-manager/public/m/index.html +17 -10
- package/template/claude-task-manager/public/m/sw.js +1 -1
- package/template/claude-task-manager/server.js +365 -95
- package/template/claude-task-manager/session-integrity.js +4 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +86 -35
- package/template/package.json +1 -1
- package/template/wall-e/api-walle.js +19 -1
- package/template/wall-e/brain.js +152 -6
- package/template/wall-e/chat.js +85 -0
- package/template/wall-e/coding-orchestrator.js +106 -12
- package/template/wall-e/http/model-admin.js +131 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +7 -0
- package/template/wall-e/llm/client.js +46 -12
- package/template/wall-e/llm/openai.js +17 -2
- package/template/wall-e/llm/portkey-sync.js +201 -0
- package/template/wall-e/server.js +13 -0
- package/template/website/index.html +10 -10
|
@@ -95,6 +95,7 @@ const {
|
|
|
95
95
|
buildSessionStandupSnapshot,
|
|
96
96
|
isWalleOwnedSession,
|
|
97
97
|
} = require('./lib/session-standup');
|
|
98
|
+
const { summarizeRestartGuard } = require('./lib/restart-guard');
|
|
98
99
|
const { createStandupIncrementalCache } = require('./lib/standup-incremental');
|
|
99
100
|
const {
|
|
100
101
|
buildStandupAttentionContext,
|
|
@@ -166,6 +167,8 @@ const {
|
|
|
166
167
|
isLoopbackRequest,
|
|
167
168
|
loadTlsOptions,
|
|
168
169
|
parseAllowedOrigins,
|
|
170
|
+
primaryProcessIpLockoutExemptions,
|
|
171
|
+
requestClientIp,
|
|
169
172
|
requestOrigin,
|
|
170
173
|
} = require('./lib/transport-security');
|
|
171
174
|
const {
|
|
@@ -185,6 +188,7 @@ const {
|
|
|
185
188
|
clearMicrosoftDevTunnelTraffic,
|
|
186
189
|
detectMicrosoftDevTunnelSetup,
|
|
187
190
|
getMicrosoftDevTunnelProgress,
|
|
191
|
+
logoutMicrosoftDevTunnelUser,
|
|
188
192
|
probeMicrosoftDevTunnelPublicAccess,
|
|
189
193
|
recordMicrosoftDevTunnelTraffic,
|
|
190
194
|
setMicrosoftDevTunnelKeepAwake,
|
|
@@ -712,7 +716,9 @@ const loggedSetupProviderDuplicateSignatures = new Set();
|
|
|
712
716
|
const WALLE_SESSIONS_DIR = walleTranscript.defaultSessionsDir(process.env);
|
|
713
717
|
|
|
714
718
|
const config = loadConfig();
|
|
715
|
-
const authRateLimiter = new AuthRateLimiter(
|
|
719
|
+
const authRateLimiter = new AuthRateLimiter({
|
|
720
|
+
ipLockoutExemptions: primaryProcessIpLockoutExemptions(HOST),
|
|
721
|
+
});
|
|
716
722
|
const AGENT_CLI_CACHE_FILE = path.join(CONFIG_DIR, 'agent-cli-cache.json');
|
|
717
723
|
const {
|
|
718
724
|
detectAgentType,
|
|
@@ -1513,7 +1519,7 @@ function _sendAuthFailure(res, decision) {
|
|
|
1513
1519
|
}
|
|
1514
1520
|
|
|
1515
1521
|
function _remoteIpFromReq(req) {
|
|
1516
|
-
return req
|
|
1522
|
+
return requestClientIp(req);
|
|
1517
1523
|
}
|
|
1518
1524
|
|
|
1519
1525
|
function _auditAuthDecision({ req, auth, rule, decision, route, wsType, action }) {
|
|
@@ -1544,15 +1550,18 @@ function _rateLimitDecision(auth, rule, keyPath) {
|
|
|
1544
1550
|
|
|
1545
1551
|
function _authorizeHttpRequest(req, res, url) {
|
|
1546
1552
|
const remoteIp = _remoteIpFromReq(req);
|
|
1547
|
-
const locked = authRateLimiter.isIpLocked(remoteIp);
|
|
1548
|
-
if (!locked.ok) {
|
|
1549
|
-
_sendAuthFailure(res, { status: 429, ...locked });
|
|
1550
|
-
return false;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
1553
|
const auth = _authContextForRequest(req, url);
|
|
1554
1554
|
req.ctmAuth = auth;
|
|
1555
1555
|
|
|
1556
|
+
if (!auth.isLoopback) {
|
|
1557
|
+
const locked = authRateLimiter.isIpLocked(remoteIp);
|
|
1558
|
+
if (!locked.ok && !auth.authenticated) {
|
|
1559
|
+
_sendAuthFailure(res, { status: 429, ...locked });
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
if (!locked.ok && auth.authenticated) authRateLimiter.recordAuthSuccess(remoteIp);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1556
1565
|
const rule = getHttpAuthRule(req.method, url.pathname);
|
|
1557
1566
|
if (!rule && auth.isLoopback) return true;
|
|
1558
1567
|
|
|
@@ -2559,6 +2568,19 @@ async function handleSetupNetwork(req, res, url) {
|
|
|
2559
2568
|
return true;
|
|
2560
2569
|
}
|
|
2561
2570
|
}
|
|
2571
|
+
if (url.pathname === '/api/setup/network/microsoft-dev-tunnel/logout' && req.method === 'POST') {
|
|
2572
|
+
try {
|
|
2573
|
+
const result = await logoutMicrosoftDevTunnelUser({ configDir: CONFIG_DIR, port: PORT });
|
|
2574
|
+
const status = result.ok ? 200 : 400;
|
|
2575
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
2576
|
+
res.end(JSON.stringify(result));
|
|
2577
|
+
return true;
|
|
2578
|
+
} catch (e) {
|
|
2579
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2580
|
+
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
2581
|
+
return true;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2562
2584
|
if (url.pathname === '/api/setup/network/microsoft-dev-tunnel/stop' && req.method === 'POST') {
|
|
2563
2585
|
const result = await stopMicrosoftDevTunnel({ configDir: CONFIG_DIR, port: PORT });
|
|
2564
2586
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2812,6 +2834,45 @@ function _isLiveTerminalSession(session) {
|
|
|
2812
2834
|
return !!(session && session.ptyProcess && session.status !== 'exited' && session.status !== 'closed');
|
|
2813
2835
|
}
|
|
2814
2836
|
|
|
2837
|
+
function _isDefaultWalleChatSessionId(sessionId) {
|
|
2838
|
+
const id = String(sessionId || '').trim().toLowerCase();
|
|
2839
|
+
return id === 'default' || id === 'walle-default' || id === 'walle-default-chat';
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
async function _sendRemoteDefaultWalleChatMessage(text, body = {}) {
|
|
2843
|
+
if (!String(text || '').trim()) return { ok: false, error: 'message_required' };
|
|
2844
|
+
const attachments = Array.isArray(body.attachments) ? body.attachments : [];
|
|
2845
|
+
const explicitModel = body.model_id || body.model || '';
|
|
2846
|
+
const explicitProvider = body.model_provider || body.provider || '';
|
|
2847
|
+
const upstream = await walleClient.requestJson('/api/wall-e/chat', {
|
|
2848
|
+
method: 'POST',
|
|
2849
|
+
body: {
|
|
2850
|
+
message: text,
|
|
2851
|
+
session_id: 'default',
|
|
2852
|
+
channel: 'ctm',
|
|
2853
|
+
attachments: attachments.length ? attachments : undefined,
|
|
2854
|
+
model: explicitModel || undefined,
|
|
2855
|
+
provider: explicitProvider || undefined,
|
|
2856
|
+
modelPinned: body.modelPinned === true || body.model_pinned === true || !!explicitModel,
|
|
2857
|
+
allowProviderFallback: explicitModel ? false : body.allowProviderFallback === true,
|
|
2858
|
+
},
|
|
2859
|
+
});
|
|
2860
|
+
if (upstream.status >= 400) {
|
|
2861
|
+
return {
|
|
2862
|
+
ok: false,
|
|
2863
|
+
error: upstream.json?.error || upstream.json?.message || `wall_e_chat_failed_${upstream.status}`,
|
|
2864
|
+
status: upstream.status,
|
|
2865
|
+
providerError: upstream.json?.providerError || null,
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
return {
|
|
2869
|
+
ok: true,
|
|
2870
|
+
delivered: true,
|
|
2871
|
+
session_id: 'default',
|
|
2872
|
+
reply: upstream.json?.data?.reply || '',
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2815
2876
|
function _remoteTerminalAgentType(session) {
|
|
2816
2877
|
if (!session || session.type === 'walle') return '';
|
|
2817
2878
|
return normalizeAgentType(session.agentType || session._providerId || '') ||
|
|
@@ -3094,6 +3155,9 @@ async function handleRemoteApi(req, res, url) {
|
|
|
3094
3155
|
},
|
|
3095
3156
|
sendWalleMessage: async (sessionId, text, body = {}) => {
|
|
3096
3157
|
const session = sessions.get(sessionId);
|
|
3158
|
+
if (!session && _isDefaultWalleChatSessionId(sessionId)) {
|
|
3159
|
+
return _sendRemoteDefaultWalleChatMessage(text, body);
|
|
3160
|
+
}
|
|
3097
3161
|
if (!session) return { ok: false, error: 'session_not_found' };
|
|
3098
3162
|
if (session.type !== 'walle') return { ok: false, error: 'not_walle_session' };
|
|
3099
3163
|
if (!text.trim()) return { ok: false, error: 'message_required' };
|
|
@@ -3117,7 +3181,7 @@ async function handleRemoteApi(req, res, url) {
|
|
|
3117
3181
|
setWalleModel: async (sessionId, body = {}) => {
|
|
3118
3182
|
const session = sessions.get(sessionId);
|
|
3119
3183
|
if (!session) return { ok: false, error: 'session_not_found' };
|
|
3120
|
-
if (session
|
|
3184
|
+
if (!_isWalleModelConfigurableSession(session)) return { ok: false, error: 'not_walle_session' };
|
|
3121
3185
|
const pref = _persistWalleSessionModelPreference(session, {
|
|
3122
3186
|
model_id: body.model_id || body.model || '',
|
|
3123
3187
|
model_provider: body.model_provider || body.provider || '',
|
|
@@ -3127,14 +3191,15 @@ async function handleRemoteApi(req, res, url) {
|
|
|
3127
3191
|
source: 'phone',
|
|
3128
3192
|
pinned: body.pinned !== false,
|
|
3129
3193
|
});
|
|
3194
|
+
const selection = _walleModelSelectionForActiveSession(session);
|
|
3130
3195
|
return {
|
|
3131
3196
|
ok: true,
|
|
3132
3197
|
delivered: true,
|
|
3133
|
-
model_id:
|
|
3134
|
-
model_provider:
|
|
3135
|
-
model_registry_id:
|
|
3136
|
-
model_provider_id:
|
|
3137
|
-
model_pinned: !!
|
|
3198
|
+
model_id: selection.model_id || null,
|
|
3199
|
+
model_provider: selection.model_provider || null,
|
|
3200
|
+
model_registry_id: selection.model_registry_id || '',
|
|
3201
|
+
model_provider_id: selection.model_provider_id || '',
|
|
3202
|
+
model_pinned: !!selection.model_pinned,
|
|
3138
3203
|
cleared: !pref,
|
|
3139
3204
|
};
|
|
3140
3205
|
},
|
|
@@ -3192,6 +3257,7 @@ async function handleApi(req, res, url) {
|
|
|
3192
3257
|
let walleProvider = process.env.WALLE_PROVIDER || 'anthropic';
|
|
3193
3258
|
let walleModel = process.env.WALLE_MODEL || '';
|
|
3194
3259
|
let serviceAlerts = [];
|
|
3260
|
+
let serviceAlertSummary = null;
|
|
3195
3261
|
try {
|
|
3196
3262
|
const upstream = await walleClient.requestJson('/api/wall-e/status');
|
|
3197
3263
|
const data = upstream.json?.data || {};
|
|
@@ -3200,6 +3266,7 @@ async function handleApi(req, res, url) {
|
|
|
3200
3266
|
if (data.owner && !ownerName) ownerName = data.owner;
|
|
3201
3267
|
if (typeof data.has_api_key === 'boolean') hasApiKey = hasApiKey || data.has_api_key;
|
|
3202
3268
|
serviceAlerts = Array.isArray(data.service_alerts) ? data.service_alerts : [];
|
|
3269
|
+
if (data.service_alert_summary && typeof data.service_alert_summary === 'object') serviceAlertSummary = data.service_alert_summary;
|
|
3203
3270
|
} catch {}
|
|
3204
3271
|
// Also check env-based provider keys
|
|
3205
3272
|
if (!hasApiKey && walleProvider === 'openai' && process.env.OPENAI_API_KEY) hasApiKey = true;
|
|
@@ -3212,7 +3279,7 @@ async function handleApi(req, res, url) {
|
|
|
3212
3279
|
const authMethod = providerState.authMethod || '';
|
|
3213
3280
|
const codingViaCli = process.env.WALLE_CODING_USE_CLI === 'true';
|
|
3214
3281
|
const deviceLabel = dbModule.getSetting('ui_setup_device_label', 'Owner iPhone');
|
|
3215
|
-
res.end(JSON.stringify({ owner_name: ownerName, has_api_key: hasApiKey, slack_connected: slackConnected, slack_team: slackTeam, needs_setup: !hasApiKey && setup.needsSetup(), version, ctm_data_dir: ctmDataDir, walle_data_dir: walleDataDir, hostname: HOSTNAME, walle_model: walleModel, walle_provider: walleProvider, auth_method: authMethod, coding_via_cli: codingViaCli, service_alerts: serviceAlerts, device_label: deviceLabel }));
|
|
3282
|
+
res.end(JSON.stringify({ owner_name: ownerName, has_api_key: hasApiKey, slack_connected: slackConnected, slack_team: slackTeam, needs_setup: !hasApiKey && setup.needsSetup(), version, ctm_data_dir: ctmDataDir, walle_data_dir: walleDataDir, hostname: HOSTNAME, walle_model: walleModel, walle_provider: walleProvider, auth_method: authMethod, coding_via_cli: codingViaCli, service_alerts: serviceAlerts, service_alert_summary: serviceAlertSummary, device_label: deviceLabel }));
|
|
3216
3283
|
return;
|
|
3217
3284
|
}
|
|
3218
3285
|
if (url.pathname === '/api/setup/test-key' && req.method === 'GET') {
|
|
@@ -6655,6 +6722,14 @@ function apiRecentSessions(req, res, url) {
|
|
|
6655
6722
|
// slug-first verifyLineage and respects the grace window.
|
|
6656
6723
|
});
|
|
6657
6724
|
txn();
|
|
6725
|
+
try {
|
|
6726
|
+
const identityRepair = dbModule.repairCodexRolloutAgentIdentities?.();
|
|
6727
|
+
if (identityRepair && identityRepair.repaired > 0) {
|
|
6728
|
+
console.log(`[session-index] repaired ${identityRepair.repaired} Codex rollout agent identity row(s)`);
|
|
6729
|
+
}
|
|
6730
|
+
} catch (e) {
|
|
6731
|
+
console.error('[session-index] Codex identity repair error:', e.message);
|
|
6732
|
+
}
|
|
6658
6733
|
try {
|
|
6659
6734
|
const repaired = dbModule.repairCodexSessionMetadataFromConversations?.();
|
|
6660
6735
|
if (repaired && repaired.repaired > 0) {
|
|
@@ -9549,6 +9624,22 @@ function _persistTelemetrySessionToken({ ctmSessionId, agentSessionId, existingA
|
|
|
9549
9624
|
* Parse JSONL content and append messages to the provided array.
|
|
9550
9625
|
* Handles user, assistant, and system messages with deduplication.
|
|
9551
9626
|
*/
|
|
9627
|
+
function _jsonlContentText(content) {
|
|
9628
|
+
if (typeof content === 'string') return content;
|
|
9629
|
+
if (!Array.isArray(content)) return '';
|
|
9630
|
+
const parts = [];
|
|
9631
|
+
let imageCount = 0;
|
|
9632
|
+
for (const block of content) {
|
|
9633
|
+
if (!block || typeof block !== 'object') continue;
|
|
9634
|
+
if ((block.type === 'text' || block.type === 'input_text') && block.text) {
|
|
9635
|
+
parts.push(block.text);
|
|
9636
|
+
} else if (block.type === 'image' || block.type === 'input_image' || block.type === 'image_ref') {
|
|
9637
|
+
parts.push(`[Image #${++imageCount}]`);
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
return parts.join('\n');
|
|
9641
|
+
}
|
|
9642
|
+
|
|
9552
9643
|
function _parseJsonlIntoMessages(content, messages, opts = {}) {
|
|
9553
9644
|
if (content.includes('"type":"session_meta"') && content.includes('"originator":"codex-tui"')) {
|
|
9554
9645
|
parseCodexJsonlIntoMessages(content, messages);
|
|
@@ -9572,8 +9663,7 @@ function _parseJsonlIntoMessages(content, messages, opts = {}) {
|
|
|
9572
9663
|
const entry = JSON.parse(line);
|
|
9573
9664
|
if (entry.type === 'user' && entry.message?.role === 'user') {
|
|
9574
9665
|
const c = entry.message.content;
|
|
9575
|
-
let text =
|
|
9576
|
-
: Array.isArray(c) ? c.filter(b => b.type === 'text').map(b => b.text).join('\n') : '';
|
|
9666
|
+
let text = _jsonlContentText(c);
|
|
9577
9667
|
if (!text) continue;
|
|
9578
9668
|
// Detect system/tool messages masquerading as user messages
|
|
9579
9669
|
const isToolResult = Array.isArray(c) && c.some(b => b.type === 'tool_result');
|
|
@@ -11663,11 +11753,6 @@ wss.on('connection', (ws, req) => {
|
|
|
11663
11753
|
socket: { remoteAddress: _remoteIpFromReq(req) },
|
|
11664
11754
|
headers: { 'user-agent': req.headers?.['user-agent'] || '' },
|
|
11665
11755
|
};
|
|
11666
|
-
const locked = authRateLimiter.isIpLocked(reqSnapshot.socket.remoteAddress);
|
|
11667
|
-
if (!locked.ok) {
|
|
11668
|
-
ws.close(4008, locked.code || 'Rate limited');
|
|
11669
|
-
return;
|
|
11670
|
-
}
|
|
11671
11756
|
const cfDecision = cloudflareAccessVerifier.verifyRequestSync(req, { isLocalhost });
|
|
11672
11757
|
if (!cfDecision.ok) {
|
|
11673
11758
|
_auditAuthDecision({
|
|
@@ -11682,6 +11767,14 @@ wss.on('connection', (ws, req) => {
|
|
|
11682
11767
|
return;
|
|
11683
11768
|
}
|
|
11684
11769
|
const auth = _authContextForRequest(req, url);
|
|
11770
|
+
if (!auth.isLoopback) {
|
|
11771
|
+
const locked = authRateLimiter.isIpLocked(reqSnapshot.socket.remoteAddress);
|
|
11772
|
+
if (!locked.ok && !auth.authenticated) {
|
|
11773
|
+
ws.close(4008, locked.code || 'Rate limited');
|
|
11774
|
+
return;
|
|
11775
|
+
}
|
|
11776
|
+
if (!locked.ok && auth.authenticated) authRateLimiter.recordAuthSuccess(reqSnapshot.socket.remoteAddress);
|
|
11777
|
+
}
|
|
11685
11778
|
|
|
11686
11779
|
if (!auth.authenticated) {
|
|
11687
11780
|
if (!auth.isLoopback) authRateLimiter.recordAuthFailure(reqSnapshot.socket.remoteAddress);
|
|
@@ -11721,7 +11814,7 @@ wss.on('connection', (ws, req) => {
|
|
|
11721
11814
|
// browser echoes this in X-CTM-Client-Id when calling PUT /api/settings,
|
|
11722
11815
|
// and we filter it out of broadcastUiPrefs to prevent self-echo.
|
|
11723
11816
|
ws.clientId = crypto.randomUUID();
|
|
11724
|
-
try { ws.send(JSON.stringify({ type: 'hello', clientId: ws.clientId })); } catch {}
|
|
11817
|
+
try { ws.send(JSON.stringify({ type: 'hello', clientId: ws.clientId, appVersion: getAppVersionInfo() })); } catch {}
|
|
11725
11818
|
ws.on('pong', () => { ws.isAlive = true; });
|
|
11726
11819
|
|
|
11727
11820
|
ws.on('message', (raw) => {
|
|
@@ -13368,12 +13461,18 @@ function handleCreate(ws, msg) {
|
|
|
13368
13461
|
model_provider = defaults.model_provider || null;
|
|
13369
13462
|
model_pinned = false;
|
|
13370
13463
|
}
|
|
13371
|
-
const
|
|
13372
|
-
|
|
13373
|
-
const
|
|
13374
|
-
const
|
|
13464
|
+
const requestedChatSessionId = msg.chatSessionId || `walle-${id}`;
|
|
13465
|
+
let jsonlPath = _walleTranscriptPathForSession(id, requestedChatSessionId);
|
|
13466
|
+
const restoredMeta = msg._isRestore ? walleTranscript.readSessionMeta(jsonlPath) : null;
|
|
13467
|
+
const restoredMode = restoredMeta?.agentMode || restoredMeta?.agent_mode || '';
|
|
13468
|
+
const restoredKind = restoredMeta?.agentKind || restoredMeta?.agent_kind || '';
|
|
13469
|
+
const restoredTaskType = restoredMeta?.taskType || restoredMeta?.task_type || '';
|
|
13470
|
+
const agentMode = msg.agentMode || msg.agent_mode || restoredMode || 'coding';
|
|
13471
|
+
const agentKind = msg.agentKind || msg.agent_kind || restoredKind || (agentMode === 'coding' ? 'walle-coding' : 'walle-chat');
|
|
13472
|
+
const taskType = msg.taskType || msg.task_type || restoredTaskType || (agentMode === 'coding' ? 'coding' : 'chat');
|
|
13473
|
+
const chatSessionId = msg.chatSessionId || restoredMeta?.chatSessionId || `walle-${id}`;
|
|
13375
13474
|
const initialMessage = String(msg.initialMessage || msg.initial_message || '').trim();
|
|
13376
|
-
|
|
13475
|
+
if (chatSessionId !== requestedChatSessionId) jsonlPath = _walleTranscriptPathForSession(id, chatSessionId);
|
|
13377
13476
|
const createResult = walleTranscript.createSession(jsonlPath, {
|
|
13378
13477
|
reset: !msg._isRestore,
|
|
13379
13478
|
sessionId: id,
|
|
@@ -13442,6 +13541,11 @@ function handleCreate(ws, msg) {
|
|
|
13442
13541
|
model_registry_id: session.model_registry_id || '',
|
|
13443
13542
|
model_provider_id: session.model_provider_id || '',
|
|
13444
13543
|
model_pinned: !!session.model_pinned,
|
|
13544
|
+
agentMode: session.agentMode,
|
|
13545
|
+
agentKind: session.agentKind,
|
|
13546
|
+
taskType: session.taskType,
|
|
13547
|
+
walle_chat_session: session.agentMode === 'chat' || session.taskType === 'chat',
|
|
13548
|
+
walleChatSession: session.agentMode === 'chat' || session.taskType === 'chat',
|
|
13445
13549
|
}));
|
|
13446
13550
|
}
|
|
13447
13551
|
broadcastSessionList(true);
|
|
@@ -15837,21 +15941,28 @@ function _safePathForCompare(p) {
|
|
|
15837
15941
|
}
|
|
15838
15942
|
|
|
15839
15943
|
function _findSessionWorktree(worktrees, session) {
|
|
15840
|
-
const
|
|
15841
|
-
const sessionPath = _safePathForCompare(explicitPath || '');
|
|
15944
|
+
const sessionPath = _safePathForCompare(session.worktree_path || session.cwd || '');
|
|
15842
15945
|
const branch = session.branch || '';
|
|
15843
|
-
const
|
|
15844
|
-
? worktrees
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15946
|
+
const pathMatches = sessionPath
|
|
15947
|
+
? worktrees
|
|
15948
|
+
.filter(wt => {
|
|
15949
|
+
const wtPath = _safePathForCompare(wt?.path || '');
|
|
15950
|
+
return wtPath && (sessionPath === wtPath || sessionPath.startsWith(wtPath + path.sep));
|
|
15951
|
+
})
|
|
15952
|
+
.sort((a, b) => String(b.path || '').length - String(a.path || '').length)
|
|
15953
|
+
: [];
|
|
15954
|
+
if (pathMatches.length > 0) return pathMatches[0];
|
|
15955
|
+
if (!branch) return null;
|
|
15956
|
+
const branchMatches = worktrees.filter(wt => wt && wt.branch === branch);
|
|
15849
15957
|
return branchMatches.length === 1 ? branchMatches[0] : null;
|
|
15850
15958
|
}
|
|
15851
15959
|
|
|
15852
15960
|
function _worktreeFinishMessage(wt, branch) {
|
|
15853
15961
|
const parts = [];
|
|
15854
|
-
|
|
15962
|
+
const trackedDirty = wt.trackedDirtyFiles != null
|
|
15963
|
+
? Number(wt.trackedDirtyFiles || 0)
|
|
15964
|
+
: Math.max(0, Number(wt.dirtyFiles || 0) - Number(wt.untrackedFiles || 0));
|
|
15965
|
+
if (trackedDirty > 0) parts.push(`${trackedDirty} tracked dirty file(s)`);
|
|
15855
15966
|
if ((wt.unmergedCommits || 0) > 0) parts.push(`${wt.unmergedCommits} unmerged commit(s)`);
|
|
15856
15967
|
if ((wt.behind || 0) > 0) parts.push(`${wt.behind} behind main`);
|
|
15857
15968
|
const detail = parts.length ? parts.join(', ') : (wt.summary || wt.state || 'unfinished work');
|
|
@@ -15872,9 +15983,12 @@ async function _maybeBroadcastWorktreeFinishGate(session, sessionId) {
|
|
|
15872
15983
|
const wtBranch = wt.branch || branch;
|
|
15873
15984
|
if (wtBranch === 'main' || wtBranch === 'master') return;
|
|
15874
15985
|
const dirtyFiles = wt.dirtyFiles || 0;
|
|
15986
|
+
const trackedDirtyFiles = wt.trackedDirtyFiles != null
|
|
15987
|
+
? Number(wt.trackedDirtyFiles || 0)
|
|
15988
|
+
: Math.max(0, Number(wt.dirtyFiles || 0) - Number(wt.untrackedFiles || 0));
|
|
15875
15989
|
const unmergedCommits = wt.unmergedCommits || 0;
|
|
15876
15990
|
const actionableState = ['ahead', 'diverged', 'dirty', 'detached'].includes(wt.state);
|
|
15877
|
-
if (
|
|
15991
|
+
if (trackedDirtyFiles === 0 && unmergedCommits === 0 && !actionableState) return;
|
|
15878
15992
|
broadcastToAll({
|
|
15879
15993
|
type: 'worktree-finish-gate',
|
|
15880
15994
|
sessionId,
|
|
@@ -15885,6 +15999,8 @@ async function _maybeBroadcastWorktreeFinishGate(session, sessionId) {
|
|
|
15885
15999
|
ahead: wt.ahead || 0,
|
|
15886
16000
|
behind: wt.behind || 0,
|
|
15887
16001
|
dirtyFiles,
|
|
16002
|
+
trackedDirtyFiles,
|
|
16003
|
+
untrackedFiles: wt.untrackedFiles || 0,
|
|
15888
16004
|
unmergedCommits,
|
|
15889
16005
|
summary: wt.summary || '',
|
|
15890
16006
|
recommendedAction: wt.recommendedAction || null,
|
|
@@ -15924,31 +16040,51 @@ let _sessionWorktreeStatusRefreshInFlight = false;
|
|
|
15924
16040
|
|
|
15925
16041
|
function _sessionNeedsWorktreeStatus(session) {
|
|
15926
16042
|
if (!session) return false;
|
|
15927
|
-
|
|
15928
|
-
if (branch === 'main' || branch === 'master') return false;
|
|
15929
|
-
return !!(session.worktree_path || _isAgentWorktreePath(session.cwd));
|
|
16043
|
+
return !!(session.worktree_path || session.cwd);
|
|
15930
16044
|
}
|
|
15931
16045
|
|
|
15932
16046
|
function _normalizeSessionWorktreeStatus(session, wt) {
|
|
15933
16047
|
if (!session || !wt) return null;
|
|
15934
16048
|
const branch = wt.branch || session.branch || '';
|
|
15935
|
-
if (!branch
|
|
16049
|
+
if (!branch) return null;
|
|
16050
|
+
const isMain = wt.isMain === true || branch === 'main' || branch === 'master';
|
|
16051
|
+
const mainRemote = isMain && wt.mainRemote ? wt.mainRemote : null;
|
|
15936
16052
|
const dirtyFiles = Number(wt.dirtyFiles || 0);
|
|
15937
|
-
const
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
16053
|
+
const trackedDirtyFiles = wt.trackedDirtyFiles != null
|
|
16054
|
+
? Number(wt.trackedDirtyFiles || 0)
|
|
16055
|
+
: Math.max(0, dirtyFiles - Number(wt.untrackedFiles || 0));
|
|
16056
|
+
const untrackedFiles = Number(wt.untrackedFiles || 0);
|
|
16057
|
+
const unpushedCommits = isMain ? Number(mainRemote?.ahead || wt.unpushedCommits || 0) : 0;
|
|
16058
|
+
const unmergedCommits = isMain ? 0 : Number(wt.unmergedCommits || 0);
|
|
16059
|
+
const ahead = isMain ? unpushedCommits : Number(wt.ahead || 0);
|
|
16060
|
+
const behind = isMain ? Number(mainRemote?.behind || wt.behind || 0) : Number(wt.behind || 0);
|
|
16061
|
+
const status = {
|
|
15941
16062
|
branch,
|
|
15942
16063
|
worktreeName: wt.worktreeName || path.basename(wt.path || session.worktree_path || session.cwd || branch),
|
|
15943
16064
|
worktreePath: wt.path || session.worktree_path || session.cwd || null,
|
|
16065
|
+
isMain,
|
|
15944
16066
|
state: wt.state || 'unknown',
|
|
15945
16067
|
dirtyFiles,
|
|
16068
|
+
trackedDirtyFiles,
|
|
16069
|
+
untrackedFiles,
|
|
15946
16070
|
unmergedCommits,
|
|
16071
|
+
unpushedCommits,
|
|
15947
16072
|
ahead,
|
|
15948
16073
|
behind,
|
|
15949
16074
|
summary: wt.summary || '',
|
|
15950
|
-
needsAttention:
|
|
16075
|
+
needsAttention: trackedDirtyFiles > 0 || unmergedCommits > 0 || unpushedCommits > 0,
|
|
15951
16076
|
};
|
|
16077
|
+
if (mainRemote) {
|
|
16078
|
+
status.mainRemote = {
|
|
16079
|
+
remote: mainRemote.remote || '',
|
|
16080
|
+
ahead: Number(mainRemote.ahead || 0),
|
|
16081
|
+
behind: Number(mainRemote.behind || 0),
|
|
16082
|
+
state: mainRemote.state || '',
|
|
16083
|
+
summary: mainRemote.summary || '',
|
|
16084
|
+
};
|
|
16085
|
+
if (!status.summary && mainRemote.summary) status.summary = mainRemote.summary;
|
|
16086
|
+
}
|
|
16087
|
+
return status;
|
|
15952
16088
|
}
|
|
15953
16089
|
|
|
15954
16090
|
function _sessionWorktreeStatusSnapshot(map) {
|
|
@@ -15957,12 +16093,17 @@ function _sessionWorktreeStatusSnapshot(map) {
|
|
|
15957
16093
|
.map(([id, wt]) => [
|
|
15958
16094
|
id,
|
|
15959
16095
|
wt.branch,
|
|
16096
|
+
wt.isMain,
|
|
15960
16097
|
wt.state,
|
|
15961
16098
|
wt.dirtyFiles,
|
|
16099
|
+
wt.trackedDirtyFiles,
|
|
16100
|
+
wt.untrackedFiles,
|
|
15962
16101
|
wt.unmergedCommits,
|
|
16102
|
+
wt.unpushedCommits,
|
|
15963
16103
|
wt.ahead,
|
|
15964
16104
|
wt.behind,
|
|
15965
16105
|
wt.summary,
|
|
16106
|
+
wt.mainRemote?.state,
|
|
15966
16107
|
wt.needsAttention,
|
|
15967
16108
|
]));
|
|
15968
16109
|
}
|
|
@@ -16064,23 +16205,44 @@ function normalizeWalleClientModelSelection({ model, provider, session }) {
|
|
|
16064
16205
|
|
|
16065
16206
|
function _applyWalleSessionModelPreference(session, pref) {
|
|
16066
16207
|
if (!session || !pref || !pref.model_id) return false;
|
|
16067
|
-
session.
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16208
|
+
if (session.type === 'walle') {
|
|
16209
|
+
session.model_id = pref.model_id;
|
|
16210
|
+
session.model_provider = pref.provider_type || session.model_provider || null;
|
|
16211
|
+
session.model_registry_id = pref.registry_id || '';
|
|
16212
|
+
session.model_provider_id = pref.provider_id || '';
|
|
16213
|
+
session.model_pinned = pref.pinned !== false;
|
|
16214
|
+
return true;
|
|
16215
|
+
}
|
|
16216
|
+
session.walle_model_id = pref.model_id;
|
|
16217
|
+
session.walle_model_provider = pref.provider_type || '';
|
|
16218
|
+
session.walle_model_registry_id = pref.registry_id || '';
|
|
16219
|
+
session.walle_model_provider_id = pref.provider_id || '';
|
|
16220
|
+
session.walle_model_pinned = pref.pinned !== false;
|
|
16072
16221
|
return true;
|
|
16073
16222
|
}
|
|
16074
16223
|
|
|
16224
|
+
function _isWalleModelConfigurableSession(session) {
|
|
16225
|
+
if (!session) return false;
|
|
16226
|
+
if (session.type === 'walle') return true;
|
|
16227
|
+
if (isWalleOwnedSession(session)) return true;
|
|
16228
|
+
try {
|
|
16229
|
+
const identity = session.id ? dbModule.getSessionIdentity?.(session.id) : null;
|
|
16230
|
+
if (identity?.ctm?.provider === 'walle') return true;
|
|
16231
|
+
if (identity?.primaryAgent?.provider === 'walle') return true;
|
|
16232
|
+
} catch {}
|
|
16233
|
+
return false;
|
|
16234
|
+
}
|
|
16235
|
+
|
|
16075
16236
|
function _walleModelPayload(session, extra = {}) {
|
|
16237
|
+
const selection = _walleModelSelectionForActiveSession(session);
|
|
16076
16238
|
return {
|
|
16077
16239
|
type: 'walle-model',
|
|
16078
16240
|
id: session.id,
|
|
16079
|
-
model_id:
|
|
16080
|
-
model_provider:
|
|
16081
|
-
model_registry_id:
|
|
16082
|
-
model_provider_id:
|
|
16083
|
-
model_pinned: !!
|
|
16241
|
+
model_id: selection.model_id || null,
|
|
16242
|
+
model_provider: selection.model_provider || null,
|
|
16243
|
+
model_registry_id: selection.model_registry_id || '',
|
|
16244
|
+
model_provider_id: selection.model_provider_id || '',
|
|
16245
|
+
model_pinned: !!selection.model_pinned,
|
|
16084
16246
|
...extra,
|
|
16085
16247
|
};
|
|
16086
16248
|
}
|
|
@@ -16094,7 +16256,7 @@ function _broadcastWalleModel(session, extra = {}) {
|
|
|
16094
16256
|
}
|
|
16095
16257
|
|
|
16096
16258
|
function _hydrateWalleSessionModelPreference(session, { broadcast = false } = {}) {
|
|
16097
|
-
if (!session
|
|
16259
|
+
if (!_isWalleModelConfigurableSession(session)) return null;
|
|
16098
16260
|
let pref = null;
|
|
16099
16261
|
try {
|
|
16100
16262
|
pref = dbModule.getSessionModelPreference?.(session.id) || null;
|
|
@@ -16108,17 +16270,26 @@ function _hydrateWalleSessionModelPreference(session, { broadcast = false } = {}
|
|
|
16108
16270
|
}
|
|
16109
16271
|
|
|
16110
16272
|
function _persistWalleSessionModelPreference(session, msg = {}) {
|
|
16111
|
-
if (!session
|
|
16273
|
+
if (!_isWalleModelConfigurableSession(session)) return null;
|
|
16274
|
+
const nativeWalleSession = session.type === 'walle';
|
|
16112
16275
|
const rawModel = msg.model_id || msg.model || '';
|
|
16113
16276
|
const rawProvider = msg.model_provider || msg.provider || '';
|
|
16114
16277
|
if (!rawModel) {
|
|
16115
16278
|
try { dbModule.clearSessionModelPreference?.(session.id); } catch (e) { console.error('[walle-session] model preference clear error:', e.message); }
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16279
|
+
if (nativeWalleSession) {
|
|
16280
|
+
session.model_id = null;
|
|
16281
|
+
session.model_provider = null;
|
|
16282
|
+
session.model_registry_id = '';
|
|
16283
|
+
session.model_provider_id = '';
|
|
16284
|
+
session.model_pinned = false;
|
|
16285
|
+
try { dbModule.addStartupTask(session.id, session.label, '', [], session.cwd, null, 'walle', session.chatSessionId); } catch (e) { console.error('[ctm] addStartupTask (walle model clear) error:', e.message); }
|
|
16286
|
+
} else {
|
|
16287
|
+
session.walle_model_id = null;
|
|
16288
|
+
session.walle_model_provider = null;
|
|
16289
|
+
session.walle_model_registry_id = '';
|
|
16290
|
+
session.walle_model_provider_id = '';
|
|
16291
|
+
session.walle_model_pinned = false;
|
|
16292
|
+
}
|
|
16122
16293
|
_broadcastWalleModel(session, { model_source: 'session-preference-cleared' });
|
|
16123
16294
|
broadcastSessionList(true);
|
|
16124
16295
|
return null;
|
|
@@ -16168,30 +16339,32 @@ function _persistWalleSessionModelPreference(session, msg = {}) {
|
|
|
16168
16339
|
}
|
|
16169
16340
|
|
|
16170
16341
|
_applyWalleSessionModelPreference(session, pref);
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16342
|
+
if (nativeWalleSession) {
|
|
16343
|
+
try {
|
|
16344
|
+
dbModule.addStartupTask(session.id, session.label, '', [], session.cwd, session.model_id, 'walle', session.chatSessionId);
|
|
16345
|
+
} catch (e) {
|
|
16346
|
+
console.error('[ctm] addStartupTask (walle model preference) error:', e.message);
|
|
16347
|
+
}
|
|
16348
|
+
try {
|
|
16349
|
+
dbModule.upsertSession(session.id, {
|
|
16350
|
+
agentSessionId: session.chatSessionId,
|
|
16351
|
+
provider: 'walle',
|
|
16352
|
+
cwd: session.cwd || '',
|
|
16353
|
+
projectPath: session.cwd || '',
|
|
16354
|
+
title: session.label || '',
|
|
16355
|
+
jsonlPath: session.jsonlPath || '',
|
|
16356
|
+
model: session.model_id || '',
|
|
16357
|
+
hostname: HOSTNAME,
|
|
16358
|
+
});
|
|
16359
|
+
} catch (e) {
|
|
16360
|
+
console.error('[walle-session] model preference index update error:', e.message);
|
|
16361
|
+
}
|
|
16189
16362
|
}
|
|
16190
16363
|
try {
|
|
16191
16364
|
telemetry.track('walle_session_model_changed', {
|
|
16192
16365
|
session_id: String(session.id || '').slice(0, 8),
|
|
16193
|
-
provider_type: session.model_provider || '',
|
|
16194
|
-
model_id: session.model_id || '',
|
|
16366
|
+
provider_type: nativeWalleSession ? (session.model_provider || '') : (session.walle_model_provider || ''),
|
|
16367
|
+
model_id: nativeWalleSession ? (session.model_id || '') : (session.walle_model_id || ''),
|
|
16195
16368
|
source: msg.source || 'user',
|
|
16196
16369
|
});
|
|
16197
16370
|
} catch {}
|
|
@@ -16716,15 +16889,36 @@ function _sessionActivityIso(value, now = Date.now(), session = null, field = 'a
|
|
|
16716
16889
|
|
|
16717
16890
|
function _walleModelSelectionForActiveSession(session) {
|
|
16718
16891
|
const defaults = resolveWalleDefaultModelSelection({ brain: getWalleBrain(), env: process.env });
|
|
16892
|
+
let pref = null;
|
|
16893
|
+
try {
|
|
16894
|
+
pref = session?.id ? dbModule.getSessionModelPreference?.(session.id) || null : null;
|
|
16895
|
+
} catch (e) {
|
|
16896
|
+
console.error('[walle-session] model preference read error:', e.message);
|
|
16897
|
+
}
|
|
16898
|
+
if (pref?.model_id) {
|
|
16899
|
+
return {
|
|
16900
|
+
model_id: pref.model_id || '',
|
|
16901
|
+
model_provider: pref.provider_type || '',
|
|
16902
|
+
model_registry_id: pref.registry_id || '',
|
|
16903
|
+
model_provider_id: pref.provider_id || '',
|
|
16904
|
+
model_pinned: pref.pinned !== false,
|
|
16905
|
+
};
|
|
16906
|
+
}
|
|
16719
16907
|
if (session?.type === 'walle') {
|
|
16720
16908
|
return {
|
|
16721
16909
|
model_id: session.model_id || defaults.model_id || '',
|
|
16722
16910
|
model_provider: session.model_provider || defaults.model_provider || '',
|
|
16911
|
+
model_registry_id: session.model_registry_id || '',
|
|
16912
|
+
model_provider_id: session.model_provider_id || '',
|
|
16913
|
+
model_pinned: !!session.model_pinned,
|
|
16723
16914
|
};
|
|
16724
16915
|
}
|
|
16725
16916
|
return {
|
|
16726
|
-
model_id: defaults.model_id || session?.model_id || '',
|
|
16727
|
-
model_provider: defaults.model_provider || session?.model_provider || '',
|
|
16917
|
+
model_id: session?.walle_model_id || defaults.model_id || session?.model_id || '',
|
|
16918
|
+
model_provider: session?.walle_model_provider || defaults.model_provider || session?.model_provider || '',
|
|
16919
|
+
model_registry_id: session?.walle_model_registry_id || '',
|
|
16920
|
+
model_provider_id: session?.walle_model_provider_id || '',
|
|
16921
|
+
model_pinned: !!session?.walle_model_pinned,
|
|
16728
16922
|
};
|
|
16729
16923
|
}
|
|
16730
16924
|
|
|
@@ -16757,9 +16951,29 @@ function _sessionPayload(s) {
|
|
|
16757
16951
|
worktreeStatus,
|
|
16758
16952
|
});
|
|
16759
16953
|
const walleModel = walleOwned ? _walleModelSelectionForActiveSession(s) : null;
|
|
16954
|
+
const nativeWalleSession = s.type === 'walle';
|
|
16955
|
+
const walleMode = String(s.agentMode || '').trim().toLowerCase();
|
|
16956
|
+
const walleKind = String(s.agentKind || '').trim().toLowerCase();
|
|
16957
|
+
const walleTaskType = String(s.taskType || '').trim().toLowerCase();
|
|
16958
|
+
const walleChatSession = nativeWalleSession && (
|
|
16959
|
+
walleMode === 'chat'
|
|
16960
|
+
|| walleTaskType === 'chat'
|
|
16961
|
+
|| /wall-?e-chat|walle-chat/.test(walleKind)
|
|
16962
|
+
);
|
|
16760
16963
|
return {
|
|
16761
16964
|
id: s.id,
|
|
16762
16965
|
type: s.type || 'pty',
|
|
16966
|
+
session_type: s.type || 'pty',
|
|
16967
|
+
runtime_type: s.type || 'pty',
|
|
16968
|
+
walle_owned: !!walleOwned,
|
|
16969
|
+
walleOwned: !!walleOwned,
|
|
16970
|
+
native_walle_session: nativeWalleSession,
|
|
16971
|
+
nativeWalleSession,
|
|
16972
|
+
walle_chat_session: walleChatSession,
|
|
16973
|
+
walleChatSession,
|
|
16974
|
+
agentMode: s.agentMode || null,
|
|
16975
|
+
agentKind: s.agentKind || (walleOwned && !nativeWalleSession ? 'walle-coding' : null),
|
|
16976
|
+
taskType: s.taskType || (walleOwned && !nativeWalleSession ? 'coding' : null),
|
|
16763
16977
|
label: s.label,
|
|
16764
16978
|
cmd: s.cmd,
|
|
16765
16979
|
cwd: s.cwd,
|
|
@@ -16777,9 +16991,9 @@ function _sessionPayload(s) {
|
|
|
16777
16991
|
fileSize: fileInfo.fileSize || 0,
|
|
16778
16992
|
model_id: walleModel?.model_id || s.model_id,
|
|
16779
16993
|
model_provider: walleModel?.model_provider || s.model_provider,
|
|
16780
|
-
model_registry_id: s.model_registry_id || '',
|
|
16781
|
-
model_provider_id: s.model_provider_id || '',
|
|
16782
|
-
model_pinned: !!s.model_pinned,
|
|
16994
|
+
model_registry_id: walleModel?.model_registry_id || s.model_registry_id || '',
|
|
16995
|
+
model_provider_id: walleModel?.model_provider_id || s.model_provider_id || '',
|
|
16996
|
+
model_pinned: walleModel?.model_pinned != null ? !!walleModel.model_pinned : !!s.model_pinned,
|
|
16783
16997
|
walle_model_id: walleModel?.model_id || null,
|
|
16784
16998
|
walle_model_provider: walleModel?.model_provider || null,
|
|
16785
16999
|
runtime_model_id: walleOwned ? (s.model_id || null) : null,
|
|
@@ -17660,6 +17874,10 @@ function apiSyncWorktree(req, res) {
|
|
|
17660
17874
|
res.writeHead(409, WORKTREE_JSON_HEADERS);
|
|
17661
17875
|
return res.end(JSON.stringify(result));
|
|
17662
17876
|
}
|
|
17877
|
+
if (result.blocked) {
|
|
17878
|
+
res.writeHead(409, WORKTREE_JSON_HEADERS);
|
|
17879
|
+
return res.end(JSON.stringify(result));
|
|
17880
|
+
}
|
|
17663
17881
|
res.writeHead(result.merged ? 200 : 500, WORKTREE_JSON_HEADERS);
|
|
17664
17882
|
res.end(JSON.stringify(result));
|
|
17665
17883
|
}).catch(e => {
|
|
@@ -17829,18 +18047,31 @@ function apiCreatePR(req, res) {
|
|
|
17829
18047
|
function apiRestartCtm(req, res) {
|
|
17830
18048
|
const reqUrl = new URL(req.url, 'http://localhost');
|
|
17831
18049
|
const force = reqUrl.searchParams.get('force') === 'true';
|
|
17832
|
-
const
|
|
17833
|
-
|
|
18050
|
+
const now = Date.now();
|
|
18051
|
+
const guard = summarizeRestartGuard(sessions.values(), {
|
|
18052
|
+
statusForSession: (session) => _standupLiveStatusForSession(session, now),
|
|
18053
|
+
isWaitingForInput: (session) => _isServerWaitingForInput(session.id, session, now),
|
|
18054
|
+
});
|
|
18055
|
+
if (guard.blockingCount > 0 && !force) {
|
|
18056
|
+
const noun = guard.blockingCount === 1 ? 'session' : 'sessions';
|
|
17834
18057
|
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
17835
18058
|
return res.end(JSON.stringify({
|
|
17836
18059
|
ok: false,
|
|
17837
|
-
error: `Blocked: ${
|
|
17838
|
-
active_sessions:
|
|
18060
|
+
error: `Blocked: ${guard.blockingCount} running/waiting ${noun} would be interrupted. Use ?force=true to override, or wait for the blocking work to settle.`,
|
|
18061
|
+
active_sessions: guard.blockingCount,
|
|
18062
|
+
blocking_sessions: guard.blockers.slice(0, 10),
|
|
18063
|
+
restorable_sessions: guard.restorableCount,
|
|
18064
|
+
total_sessions: guard.totalCount,
|
|
17839
18065
|
}));
|
|
17840
18066
|
}
|
|
17841
18067
|
|
|
17842
18068
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
17843
|
-
res.end(JSON.stringify({
|
|
18069
|
+
res.end(JSON.stringify({
|
|
18070
|
+
ok: true,
|
|
18071
|
+
message: 'CTM server restarting...',
|
|
18072
|
+
restorable_sessions: guard.restorableCount,
|
|
18073
|
+
total_sessions: guard.totalCount,
|
|
18074
|
+
}));
|
|
17844
18075
|
|
|
17845
18076
|
// No bash watcher — launchd KeepAlive handles restart on non-zero exit.
|
|
17846
18077
|
// (Detached bash kill-0 loops trigger Cortex XDR behavioral threat detection.)
|
|
@@ -18174,10 +18405,49 @@ function getCurrentVersion() {
|
|
|
18174
18405
|
return readPackageVersion(path.join(__dirname, '..', 'package.json'));
|
|
18175
18406
|
}
|
|
18176
18407
|
|
|
18408
|
+
function getAppBuildIdentity() {
|
|
18409
|
+
const root = path.join(__dirname, '..');
|
|
18410
|
+
const files = [
|
|
18411
|
+
'package.json',
|
|
18412
|
+
'claude-task-manager/package.json',
|
|
18413
|
+
'claude-task-manager/server.js',
|
|
18414
|
+
'claude-task-manager/public/index.html',
|
|
18415
|
+
'claude-task-manager/public/m/index.html',
|
|
18416
|
+
'claude-task-manager/public/m/app.css',
|
|
18417
|
+
'claude-task-manager/public/m/app.js',
|
|
18418
|
+
'claude-task-manager/public/m/sw.js',
|
|
18419
|
+
'wall-e/package.json',
|
|
18420
|
+
'create-walle/package.json',
|
|
18421
|
+
];
|
|
18422
|
+
const hash = crypto.createHash('sha256');
|
|
18423
|
+
const fileState = [];
|
|
18424
|
+
for (const rel of files) {
|
|
18425
|
+
const full = path.join(root, rel);
|
|
18426
|
+
try {
|
|
18427
|
+
const stat = fs.statSync(full);
|
|
18428
|
+
const entry = `${rel}:${stat.size}:${Math.floor(stat.mtimeMs)}`;
|
|
18429
|
+
fileState.push(entry);
|
|
18430
|
+
hash.update(entry);
|
|
18431
|
+
hash.update('\n');
|
|
18432
|
+
} catch {
|
|
18433
|
+
const entry = `${rel}:missing`;
|
|
18434
|
+
fileState.push(entry);
|
|
18435
|
+
hash.update(entry);
|
|
18436
|
+
hash.update('\n');
|
|
18437
|
+
}
|
|
18438
|
+
}
|
|
18439
|
+
return {
|
|
18440
|
+
buildId: hash.digest('hex').slice(0, 16),
|
|
18441
|
+
files: fileState,
|
|
18442
|
+
};
|
|
18443
|
+
}
|
|
18444
|
+
|
|
18177
18445
|
function getAppVersionInfo() {
|
|
18446
|
+
const build = getAppBuildIdentity();
|
|
18178
18447
|
return {
|
|
18179
18448
|
version: getCurrentVersion(),
|
|
18180
18449
|
product: 'create-walle',
|
|
18450
|
+
buildId: build.buildId,
|
|
18181
18451
|
components: {
|
|
18182
18452
|
ctm: readPackageVersion(path.join(__dirname, 'package.json')),
|
|
18183
18453
|
wallE: readPackageVersion(path.join(__dirname, '..', 'wall-e', 'package.json')),
|