circuschief 0.6.0 → 0.8.0
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/package.json +1 -1
- package/packages/server/src/agents/adapters/CodexAdapter.js +5 -4
- package/packages/server/src/api/canvas.js +22 -57
- package/packages/server/src/api/index.js +2 -0
- package/packages/server/src/api/kanban.js +4 -2
- package/packages/server/src/api/projects-helpers.js +20 -3
- package/packages/server/src/api/projects-session-helpers.js +35 -4
- package/packages/server/src/api/projects.js +11 -0
- package/packages/server/src/api/providers.js +11 -1
- package/packages/server/src/api/sessions-commands.js +35 -17
- package/packages/server/src/api/sessions-draft.js +1 -0
- package/packages/server/src/api/sessions-lifecycle.js +10 -10
- package/packages/server/src/api/sessions-patch.js +4 -0
- package/packages/server/src/api/sessions.js +6 -5
- package/packages/server/src/api/settings.js +52 -4
- package/packages/server/src/database.js +0 -2
- package/packages/server/src/db/ConversationRepository.js +16 -3
- package/packages/server/src/db/DatabaseManager.js +5 -1
- package/packages/server/src/db/ProjectDefaultsRepository.js +50 -40
- package/packages/server/src/db/ProviderRepository.js +87 -32
- package/packages/server/src/db/SessionRepository.js +13 -8
- package/packages/server/src/db/SettingsRepository.js +44 -16
- package/packages/server/src/db/conversation-helpers.js +1 -0
- package/packages/server/src/db/index.js +0 -3
- package/packages/server/src/db/migrations/index.js +36 -200
- package/packages/server/src/db/seedBaselineData.js +137 -0
- package/packages/server/src/db/session-helpers.js +9 -3
- package/packages/server/src/middleware/sessionLookup.js +81 -8
- package/packages/server/src/schema.sql +157 -132
- package/packages/server/src/scripts/backupDatabase.js +21 -0
- package/packages/server/src/scripts/dbUtils.js +81 -0
- package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
- package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
- package/packages/server/src/services/agentCallLogger.js +1 -1
- package/packages/server/src/services/codexSpawnHelper.js +9 -0
- package/packages/server/src/services/commandButtonPrompts.js +48 -0
- package/packages/server/src/services/commandRunner.js +7 -1
- package/packages/server/src/services/draftSessionService.js +2 -0
- package/packages/server/src/services/e2eSpawnCapture.js +147 -0
- package/packages/server/src/services/gitCommitAttribution.js +120 -0
- package/packages/server/src/services/gitService.js +11 -2
- package/packages/server/src/services/gitSessionSetup.js +11 -1
- package/packages/server/src/services/kanbanTriggers.js +6 -3
- package/packages/server/src/services/nodeSpawnHelper.js +9 -0
- package/packages/server/src/services/prUrlService.js +3 -3
- package/packages/server/src/services/queryParamBuilder.js +90 -0
- package/packages/server/src/services/sessionDuplicator.js +1 -5
- package/packages/server/src/services/sessionExecution.js +56 -106
- package/packages/server/src/services/sessionPrompts.js +16 -47
- package/packages/server/src/services/sessionProvider.js +16 -8
- package/packages/server/src/services/streamEventCallbacks.js +72 -40
- package/packages/server/src/services/streamEventHandler.js +13 -2
- package/packages/server/src/services/streamUsageHandler.js +6 -0
- package/packages/server/src/services/summaryClaudeClient.js +37 -12
- package/packages/server/src/services/summaryModelClient.js +154 -0
- package/packages/server/src/services/summaryModelResolver.js +148 -0
- package/packages/server/src/services/summaryService.js +11 -7
- package/packages/server/src/services/summaryStaleCheck.js +23 -4
- package/packages/server/src/services/templateTriggerService.js +3 -1
- package/packages/server/src/services/usageTracker.js +5 -1
- package/packages/server/src/services/visibleFinalErrorMessage.js +123 -0
- package/packages/shared/src/constants.js +4 -2
- package/packages/shared/src/contracts/commandButtons.js +16 -2
- package/packages/shared/src/contracts/projects.js +4 -2
- package/packages/shared/src/contracts/providers.js +60 -0
- package/packages/shared/src/contracts/sessions.js +2 -1
- package/packages/shared/src/contracts/templates.js +2 -2
- package/packages/shared/src/types.js +1 -9
- package/packages/shared/src/utils.js +11 -19
- package/packages/web/dist/assets/ActiveSessionsView-B0XHqLmv.js +1 -0
- package/packages/web/dist/assets/{AgentLogsView-Cdw4nmvd.js → AgentLogsView-DmsjUMlB.js} +2 -2
- package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
- package/packages/web/dist/assets/{CommandButtonDetailView-DnFhJY5A.js → CommandButtonDetailView-CdSCPp78.js} +1 -1
- package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-CQkmdczf.js → GeneralSettingsView-D1nI8_zk.js} +1 -1
- package/packages/web/dist/assets/InputWithButton-CAkttyqx.js +1 -0
- package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-PfYR3KJo.js → InterpolationHelp-BO1j9Z3_.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +2 -0
- package/packages/web/dist/assets/{ModelSelector-BZOT1Jc6.css → ModelSelector-BSxKUSus.css} +1 -1
- package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +1 -0
- package/packages/web/dist/assets/NewSessionView-BDPb-1qr.css +1 -0
- package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +3 -0
- package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +1 -0
- package/packages/web/dist/assets/{ProjectListView-CuYMmd3O.js → ProjectListView-DcNyuINs.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-CNaA4Maf.js → ProjectNewView-B5YV62hv.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
- package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +1 -0
- package/packages/web/dist/assets/{QuickResponsesPanel-DIBQFj0W.css → QuickResponsesPanel-BlFDvnZ2.css} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-BqMYSHb0.js → QuickResponsesPanel-BzSYcCSP.js} +1 -1
- package/packages/web/dist/assets/{ResizableTextarea-wYF3K2RO.js → ResizableTextarea-B3YIdIXv.js} +1 -1
- package/packages/web/dist/assets/{SessionCard-bLaQEWWX.js → SessionCard-CjE1tXiT.js} +1 -1
- package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-B6AxyREh.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BpUALRKn.css +1 -0
- package/packages/web/dist/assets/SessionListView-B5_6gW49.css +1 -0
- package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-DTnDAF95.js → SessionLogStream-LlZ3z_Xj.js} +1 -1
- package/packages/web/dist/assets/{SettingsView-DNLUSsHV.js → SettingsView-CTGiGvR2.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-CRGFaO8t.js → SlashCommandWizard-Cy04d7-o.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
- package/packages/web/dist/assets/SummarySettingsView-BR2ZjEa3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-l2bxHmZZ.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +1 -0
- package/packages/web/dist/assets/{commandButtons-Bbjf3fCt.js → commandButtons-BfqR-fqq.js} +1 -1
- package/packages/web/dist/assets/index-1zziPL6l.js +1 -0
- package/packages/web/dist/assets/index-7kzHPxSF.js +1 -0
- package/packages/web/dist/assets/index-B0N_obMc.js +1 -0
- package/packages/web/dist/assets/index-BNk_gdfI.js +1 -0
- package/packages/web/dist/assets/{index-gmCCsCQ1.css → index-BY174HVJ.css} +1 -1
- package/packages/web/dist/assets/index-CSqaAH-0.js +1 -0
- package/packages/web/dist/assets/index-C_q4WlK8.js +1 -0
- package/packages/web/dist/assets/index-D1wpU4y0.js +7 -0
- package/packages/web/dist/assets/index-D5zCA8sD.js +1 -0
- package/packages/web/dist/assets/index-DGR8ELWY.js +1 -0
- package/packages/web/dist/assets/index-DHga8pXo.js +1 -0
- package/packages/web/dist/assets/index-DSby02Wl.js +1 -0
- package/packages/web/dist/assets/{index-Cf6vdW-B.js → index-DgkC10TW.js} +3 -3
- package/packages/web/dist/assets/index-DqjXJTVI.js +1 -0
- package/packages/web/dist/assets/{index-Bs7Qf5D6.js → index-DtfUt785.js} +2 -2
- package/packages/web/dist/assets/index-_4S2uLDI.js +1 -0
- package/packages/web/dist/assets/{index-BQL_L4gL.js → index-fK8FIZgP.js} +15 -14
- package/packages/web/dist/assets/index-gmiZeFXN.js +1 -0
- package/packages/web/dist/assets/index-irD539ZM.js +3 -0
- package/packages/web/dist/assets/index-yq-E1Y00.js +1 -0
- package/packages/web/dist/assets/{projects-CPt3AB7U.js → projects-DXYQNJIi.js} +1 -1
- package/packages/web/dist/assets/{providers-ChfeMvUq.js → providers-1bnH-exJ.js} +1 -1
- package/packages/web/dist/assets/sessions-6zGUlFrt.js +1 -0
- package/packages/web/dist/assets/settings-MbfRir0d.js +1 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/server/src/api/sessions-notes.js +0 -51
- package/packages/server/src/db/SessionNoteRepository.js +0 -60
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +0 -109
- package/packages/server/src/db/migrations/conversationsMigrations.js +0 -183
- package/packages/server/src/db/migrations/kanbanMigrations.js +0 -99
- package/packages/server/src/db/migrations/miscMigrations.js +0 -369
- package/packages/server/src/db/migrations/projectsMigrations.js +0 -99
- package/packages/server/src/db/migrations/sessionsMigrations.js +0 -282
- package/packages/web/dist/assets/ActiveSessionsView-UCbQrF1b.js +0 -1
- package/packages/web/dist/assets/ApiClient-CWbXWDUY.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-J48eh3zw.js +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-bXbPo4Zw.js +0 -1
- package/packages/web/dist/assets/InputWithButton-XyM3k6lN.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-P8F5kO-o.js +0 -2
- package/packages/web/dist/assets/ModelSelector-CowKfGMP.js +0 -1
- package/packages/web/dist/assets/NewSessionView-D_Hi7M9g.css +0 -1
- package/packages/web/dist/assets/NewSessionView-DkjFLvHU.js +0 -3
- package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-embVT7NC.js +0 -1
- package/packages/web/dist/assets/ProvidersView-C7rydtOd.js +0 -1
- package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-BTQEKhwJ.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-Cv-xMzXp.css +0 -1
- package/packages/web/dist/assets/SessionDetailView-CvQOUsW2.js +0 -36
- package/packages/web/dist/assets/SessionFormOptions-3pzbgI2Q.js +0 -1
- package/packages/web/dist/assets/SessionFormOptions-DhhIkjIS.css +0 -1
- package/packages/web/dist/assets/SessionListView-Dranfb72.js +0 -1
- package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
- package/packages/web/dist/assets/SummarySettingsView-C7G_suHp.js +0 -1
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +0 -1
- package/packages/web/dist/assets/TemplateDetailView-B78_DLMR.js +0 -1
- package/packages/web/dist/assets/index--V7c-VZf.js +0 -1
- package/packages/web/dist/assets/index-8Q04yd7H.js +0 -1
- package/packages/web/dist/assets/index-B47XRBDH.js +0 -1
- package/packages/web/dist/assets/index-BXbgZrhS.js +0 -1
- package/packages/web/dist/assets/index-CGhDVPen.js +0 -1
- package/packages/web/dist/assets/index-CKcRO1A6.js +0 -1
- package/packages/web/dist/assets/index-CTq-SLIW.js +0 -1
- package/packages/web/dist/assets/index-CYyos3iC.js +0 -1
- package/packages/web/dist/assets/index-CsCREAxF.js +0 -1
- package/packages/web/dist/assets/index-DJTTk_8T.js +0 -3
- package/packages/web/dist/assets/index-DPqUJ5JK.js +0 -1
- package/packages/web/dist/assets/index-EwAe1dKg.js +0 -1
- package/packages/web/dist/assets/index-JBA8axyA.js +0 -1
- package/packages/web/dist/assets/index-JkVHFtK5.js +0 -7
- package/packages/web/dist/assets/index-gMPUwT55.js +0 -1
- package/packages/web/dist/assets/index-wadc_0zT.js +0 -1
- package/packages/web/dist/assets/sessions-CwPsJOb1.js +0 -1
- package/packages/web/dist/assets/settings-BOj6wq6t.js +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { callClaude, SESSION_SUMMARY_SCHEMA } from './summaryClaudeClient.js';
|
|
3
|
+
import { agentCallLogger } from './agentCallLogger.js';
|
|
4
|
+
import { buildProviderEnv } from './sessionProvider.js';
|
|
5
|
+
import { resolveSummaryModel } from './summaryModelResolver.js';
|
|
6
|
+
|
|
7
|
+
export { SESSION_SUMMARY_SCHEMA };
|
|
8
|
+
|
|
9
|
+
export async function callSummaryModel(prompt, recentMessages, sessionStatus, options = {}) {
|
|
10
|
+
const resolution = options.resolvedModel || resolveSummaryModel(options.summarySettings || {});
|
|
11
|
+
if (resolution.kind === 'openai') {
|
|
12
|
+
return callOpenAISummaryModel(prompt, resolution, options);
|
|
13
|
+
}
|
|
14
|
+
return callAnthropicSummaryModel({ prompt, recentMessages, sessionStatus, resolution, options });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function callAnthropicSummaryModel({ prompt, recentMessages, sessionStatus, resolution, options }) {
|
|
18
|
+
const providerEnv = resolution.provider && !resolution.provider.isBuiltIn
|
|
19
|
+
? { ...process.env, ...buildProviderEnv(resolution.provider) }
|
|
20
|
+
: null;
|
|
21
|
+
|
|
22
|
+
return callClaude(prompt, recentMessages, sessionStatus, {
|
|
23
|
+
...options,
|
|
24
|
+
model: resolution.model,
|
|
25
|
+
providerId: resolution.providerId,
|
|
26
|
+
selectionReason: resolution.selectionReason,
|
|
27
|
+
...(providerEnv ? { env: providerEnv } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function callOpenAISummaryModel(prompt, resolution, options) {
|
|
32
|
+
const { logMeta = null, systemPrompt = null, jsonSchema = null } = options || {};
|
|
33
|
+
const schema = jsonSchema || SESSION_SUMMARY_SCHEMA;
|
|
34
|
+
const provider = resolution.provider;
|
|
35
|
+
const client = createOpenAIClient(provider);
|
|
36
|
+
|
|
37
|
+
const callId = startOpenAISummaryLog(logMeta, resolution, prompt.length);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await createOpenAIChatCompletion(client, {
|
|
41
|
+
model: resolution.model,
|
|
42
|
+
prompt,
|
|
43
|
+
systemPrompt,
|
|
44
|
+
schema,
|
|
45
|
+
structured: true,
|
|
46
|
+
}).catch(async (error) => {
|
|
47
|
+
if (!isStructuredOutputUnsupported(error)) throw error;
|
|
48
|
+
return createOpenAIChatCompletion(client, {
|
|
49
|
+
model: resolution.model,
|
|
50
|
+
prompt: withJsonOnlyInstruction(prompt, schema),
|
|
51
|
+
systemPrompt,
|
|
52
|
+
schema,
|
|
53
|
+
structured: false,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
logOpenAIUsage(callId, response.usage);
|
|
58
|
+
|
|
59
|
+
if (callId) {
|
|
60
|
+
agentCallLogger.completeCall(callId, { success: true });
|
|
61
|
+
}
|
|
62
|
+
return extractOpenAIContent(response);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (callId) {
|
|
65
|
+
agentCallLogger.completeCall(callId, { success: false, error });
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function startOpenAISummaryLog(logMeta, resolution, promptLength) {
|
|
72
|
+
if (!logMeta) return null;
|
|
73
|
+
return agentCallLogger.startCall({
|
|
74
|
+
sessionId: logMeta.sessionId,
|
|
75
|
+
conversationId: logMeta.conversationId || null,
|
|
76
|
+
agentType: 'summary',
|
|
77
|
+
model: resolution.model,
|
|
78
|
+
callType: logMeta.callType,
|
|
79
|
+
promptLength,
|
|
80
|
+
metadata: {
|
|
81
|
+
...(resolution.providerId ? { providerId: resolution.providerId } : {}),
|
|
82
|
+
...(resolution.selectionReason ? { selectionReason: resolution.selectionReason } : {}),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function logOpenAIUsage(callId, usage) {
|
|
88
|
+
if (!callId || !usage) return;
|
|
89
|
+
agentCallLogger.updateUsage(callId, {
|
|
90
|
+
inputTokens: usage.prompt_tokens || 0,
|
|
91
|
+
outputTokens: usage.completion_tokens || 0,
|
|
92
|
+
thinkingTokens: 0,
|
|
93
|
+
cacheReadInputTokens: 0,
|
|
94
|
+
cacheCreationInputTokens: 0,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createOpenAIClient(provider) {
|
|
99
|
+
const options = {
|
|
100
|
+
apiKey: provider?.authToken || process.env.OPENAI_API_KEY || 'missing',
|
|
101
|
+
};
|
|
102
|
+
if (provider?.baseUrl) options.baseURL = provider.baseUrl;
|
|
103
|
+
if (provider?.apiTimeoutMs) options.timeout = provider.apiTimeoutMs;
|
|
104
|
+
return new OpenAI(options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createOpenAIChatCompletion(client, { model, prompt, systemPrompt, schema, structured }) {
|
|
108
|
+
const messages = [];
|
|
109
|
+
if (systemPrompt) messages.push({ role: 'system', content: systemPrompt });
|
|
110
|
+
messages.push({ role: 'user', content: prompt });
|
|
111
|
+
|
|
112
|
+
const request = {
|
|
113
|
+
model,
|
|
114
|
+
messages,
|
|
115
|
+
temperature: 0,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (structured) {
|
|
119
|
+
request.response_format = {
|
|
120
|
+
type: 'json_schema',
|
|
121
|
+
json_schema: {
|
|
122
|
+
name: 'session_summary',
|
|
123
|
+
schema,
|
|
124
|
+
strict: false,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
} else {
|
|
128
|
+
request.response_format = { type: 'json_object' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return client.chat.completions.create(request);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function extractOpenAIContent(response) {
|
|
135
|
+
const content = response?.choices?.[0]?.message?.content;
|
|
136
|
+
if (Array.isArray(content)) {
|
|
137
|
+
return content
|
|
138
|
+
.map((part) => part?.text || part?.content || '')
|
|
139
|
+
.join('');
|
|
140
|
+
}
|
|
141
|
+
return content || '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isStructuredOutputUnsupported(error) {
|
|
145
|
+
const message = `${error?.message || ''} ${error?.code || ''} ${error?.type || ''}`.toLowerCase();
|
|
146
|
+
return [400, 404, 422].includes(error?.status)
|
|
147
|
+
|| message.includes('response_format')
|
|
148
|
+
|| message.includes('json_schema')
|
|
149
|
+
|| message.includes('unsupported');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function withJsonOnlyInstruction(prompt, schema) {
|
|
153
|
+
return `${prompt}\n\nReturn only a valid JSON object matching this JSON Schema:\n${JSON.stringify(schema)}`;
|
|
154
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { modelProviders, sessions } from '../database.js';
|
|
2
|
+
import { ACTIVITY_FIELDS_SQL } from '../db/session-helpers.js';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_ANTHROPIC_SUMMARY_MODEL = 'claude-haiku-4-5-20251001';
|
|
5
|
+
export const DEFAULT_OPENAI_SUMMARY_MODEL = 'gpt-5.4-mini';
|
|
6
|
+
export const BUILT_IN_ANTHROPIC_PROVIDER_ID = 'anthropic-default';
|
|
7
|
+
export const BUILT_IN_OPENAI_PROVIDER_ID = 'openai-default';
|
|
8
|
+
|
|
9
|
+
export const CHEAPEST_SUMMARY_MODEL_BY_BUILT_IN_PROVIDER = Object.freeze({
|
|
10
|
+
[BUILT_IN_ANTHROPIC_PROVIDER_ID]: DEFAULT_ANTHROPIC_SUMMARY_MODEL,
|
|
11
|
+
[BUILT_IN_OPENAI_PROVIDER_ID]: DEFAULT_OPENAI_SUMMARY_MODEL,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const ANTHROPIC_TIER_NAMES = new Set(['sonnet', 'opus', 'haiku']);
|
|
15
|
+
|
|
16
|
+
export function isKnownBuiltInAnthropicModel(modelId) {
|
|
17
|
+
if (!modelId || typeof modelId !== 'string') return false;
|
|
18
|
+
if (ANTHROPIC_TIER_NAMES.has(modelId.toLowerCase())) return true;
|
|
19
|
+
if (modelId === DEFAULT_ANTHROPIC_SUMMARY_MODEL) return true;
|
|
20
|
+
|
|
21
|
+
const anthropicProvider = modelProviders.getById(BUILT_IN_ANTHROPIC_PROVIDER_ID);
|
|
22
|
+
return Boolean(
|
|
23
|
+
anthropicProvider?.models?.some((model) => model.modelId === modelId)
|
|
24
|
+
|| modelId.startsWith('claude-')
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveSummaryModel(summarySettings = {}) {
|
|
29
|
+
const summaryModel = summarySettings?.summaryModel || '';
|
|
30
|
+
const summaryProviderId = summarySettings?.summaryProviderId || null;
|
|
31
|
+
|
|
32
|
+
if (summaryModel) {
|
|
33
|
+
return resolveExplicitSummaryModel(summaryModel, summaryProviderId);
|
|
34
|
+
}
|
|
35
|
+
if (summaryProviderId) {
|
|
36
|
+
throw new Error('summaryModel is required when summaryProviderId is set');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const recentFamily = findRecentBuiltInProviderFamily();
|
|
40
|
+
if (recentFamily === BUILT_IN_OPENAI_PROVIDER_ID) {
|
|
41
|
+
return {
|
|
42
|
+
model: DEFAULT_OPENAI_SUMMARY_MODEL,
|
|
43
|
+
provider: modelProviders.getById(BUILT_IN_OPENAI_PROVIDER_ID),
|
|
44
|
+
providerId: BUILT_IN_OPENAI_PROVIDER_ID,
|
|
45
|
+
kind: 'openai',
|
|
46
|
+
isDefault: true,
|
|
47
|
+
selectionReason: 'recent-built-in-provider',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (recentFamily === BUILT_IN_ANTHROPIC_PROVIDER_ID) {
|
|
52
|
+
return defaultAnthropicResolution('recent-built-in-provider');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return defaultAnthropicResolution('fallback');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveExplicitSummaryModel(summaryModel, summaryProviderId) {
|
|
59
|
+
if (!summaryProviderId) {
|
|
60
|
+
throw new Error('summaryProviderId is required when summaryModel is set');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const provider = modelProviders.getById(summaryProviderId);
|
|
64
|
+
if (!provider) {
|
|
65
|
+
throw new Error(`Summary provider not found: ${summaryProviderId}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ownsModel = provider.models?.some((model) => model.modelId === summaryModel);
|
|
69
|
+
if (!ownsModel) {
|
|
70
|
+
throw new Error(`Summary provider ${summaryProviderId} does not own model ${summaryModel}`);
|
|
71
|
+
}
|
|
72
|
+
return providerResolution(summaryModel, provider, 'explicit');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function providerResolution(model, provider, selectionReason) {
|
|
76
|
+
const kind = provider.kind || 'anthropic';
|
|
77
|
+
return {
|
|
78
|
+
model,
|
|
79
|
+
provider,
|
|
80
|
+
providerId: provider.id,
|
|
81
|
+
kind,
|
|
82
|
+
isDefault: false,
|
|
83
|
+
selectionReason,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function defaultAnthropicResolution(selectionReason) {
|
|
88
|
+
return {
|
|
89
|
+
model: DEFAULT_ANTHROPIC_SUMMARY_MODEL,
|
|
90
|
+
provider: null,
|
|
91
|
+
providerId: null,
|
|
92
|
+
kind: 'anthropic',
|
|
93
|
+
isDefault: true,
|
|
94
|
+
selectionReason,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findRecentBuiltInProviderFamily() {
|
|
99
|
+
for (const usage of getRecentSessionModelUsage()) {
|
|
100
|
+
const providerId = usage.providerId || null;
|
|
101
|
+
if (providerId) {
|
|
102
|
+
const provider = modelProviders.getById(providerId);
|
|
103
|
+
const family = builtInFamilyForProvider(provider);
|
|
104
|
+
if (family) return family;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const model = usage.model || null;
|
|
109
|
+
if (!model) continue;
|
|
110
|
+
|
|
111
|
+
const provider = modelProviders.getProviderByModelId(model);
|
|
112
|
+
const family = builtInFamilyForProvider(provider);
|
|
113
|
+
if (family) return family;
|
|
114
|
+
|
|
115
|
+
if (!provider && isKnownBuiltInAnthropicModel(model)) {
|
|
116
|
+
return BUILT_IN_ANTHROPIC_PROVIDER_ID;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function builtInFamilyForProvider(provider) {
|
|
123
|
+
if (!provider?.isBuiltIn) return null;
|
|
124
|
+
if (provider.id === BUILT_IN_OPENAI_PROVIDER_ID || provider.kind === 'openai') return BUILT_IN_OPENAI_PROVIDER_ID;
|
|
125
|
+
if (provider.id === BUILT_IN_ANTHROPIC_PROVIDER_ID || (provider.kind || 'anthropic') === 'anthropic') {
|
|
126
|
+
return BUILT_IN_ANTHROPIC_PROVIDER_ID;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getRecentSessionModelUsage(limit = 50) {
|
|
132
|
+
const rows = sessions.db
|
|
133
|
+
.prepare(
|
|
134
|
+
`SELECT s.model, s.provider_id, ${ACTIVITY_FIELDS_SQL}
|
|
135
|
+
FROM sessions s
|
|
136
|
+
ORDER BY COALESCE(last_activity_at, s.updated_at, s.created_at) DESC,
|
|
137
|
+
s.updated_at DESC,
|
|
138
|
+
s.created_at DESC,
|
|
139
|
+
s.rowid DESC
|
|
140
|
+
LIMIT ?`
|
|
141
|
+
)
|
|
142
|
+
.all(limit);
|
|
143
|
+
|
|
144
|
+
return rows.map((row) => ({
|
|
145
|
+
model: row.model || null,
|
|
146
|
+
providerId: row.provider_id || null,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { sessions, messages, sessionSummaries, projects, settings } from '../database.js';
|
|
15
15
|
import { createConcurrencyGuard } from './withConcurrencyGuard.js';
|
|
16
|
-
import {
|
|
16
|
+
import { callSummaryModel } from './summaryModelClient.js';
|
|
17
17
|
import {
|
|
18
18
|
MAX_MESSAGES,
|
|
19
19
|
MIN_MESSAGES_FOR_SUMMARY,
|
|
@@ -200,18 +200,19 @@ function updateSessionFromSummary(sessionId, session, summaryData) {
|
|
|
200
200
|
if (summaryData.sessionTitle || summaryData.prUrl) {
|
|
201
201
|
const updateData = {};
|
|
202
202
|
const freshSession = sessions.getById(sessionId);
|
|
203
|
+
const shouldApplySummaryPrUrl = summaryData.prUrl && !freshSession.prUrlAutoLinkDisabled;
|
|
203
204
|
|
|
204
205
|
if (summaryData.sessionTitle && !freshSession.manuallyNamed) {
|
|
205
206
|
updateData.name = summaryData.sessionTitle;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
if (
|
|
209
|
+
if (shouldApplySummaryPrUrl) {
|
|
209
210
|
updateData.prUrl = summaryData.prUrl;
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
const updatedSession = sessions.update(sessionId, updateData);
|
|
213
214
|
|
|
214
|
-
if (
|
|
215
|
+
if (shouldApplySummaryPrUrl) {
|
|
215
216
|
propagatePrUrlToParent(sessionId, summaryData.prUrl);
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -299,10 +300,11 @@ async function _doGenerateSummary(sessionId, retryCount = 0, force = false, user
|
|
|
299
300
|
// Build conversation context and prompt
|
|
300
301
|
const { prompt } = fetchConversationContext(sessionId, { existingSummary, recentMessages, session, globalSettings });
|
|
301
302
|
|
|
302
|
-
// Call
|
|
303
|
-
const responseText = await
|
|
303
|
+
// Call the configured summary model.
|
|
304
|
+
const responseText = await callSummaryModel(prompt, recentMessages, session.status, {
|
|
304
305
|
logMeta: { sessionId, callType: 'generateSessionSummary' },
|
|
305
306
|
systemPrompt: SUMMARY_SYSTEM_PROMPT,
|
|
307
|
+
summarySettings: globalSettings,
|
|
306
308
|
});
|
|
307
309
|
|
|
308
310
|
// Parse response and retry if needed
|
|
@@ -520,7 +522,7 @@ export function propagatePrUrlToParent(sessionId, prUrl) {
|
|
|
520
522
|
if (!rootId || rootId === sessionId) return;
|
|
521
523
|
|
|
522
524
|
const root = sessions.getById(rootId);
|
|
523
|
-
if (!root || root.prUrl) return; // Don't overwrite existing PR URL
|
|
525
|
+
if (!root || root.prUrl || root.prUrlAutoLinkDisabled) return; // Don't overwrite existing or user-cleared PR URL
|
|
524
526
|
|
|
525
527
|
sessions.update(root.id, { prUrl });
|
|
526
528
|
|
|
@@ -535,8 +537,10 @@ export {
|
|
|
535
537
|
MAX_MESSAGES, MIN_MESSAGES_FOR_SUMMARY, MAX_RETRIES, DEFAULT_SESSION_TITLE_PROMPT,
|
|
536
538
|
SUMMARY_SYSTEM_PROMPT, formatMessages, buildIncrementalPrompt, parseSummaryResponse,
|
|
537
539
|
stripMarkdownCodeBlock as _stripMarkdownCodeBlock, trackMessageMetadata as _trackMessageMetadata,
|
|
540
|
+
updateSessionFromSummary as _updateSessionFromSummary,
|
|
538
541
|
};
|
|
539
|
-
export {
|
|
542
|
+
export { callSummaryModel };
|
|
543
|
+
export { callClaude } from './summaryClaudeClient.js';
|
|
540
544
|
export { parsePrUrl, validatePrUrl, extractPrUrlIfNeeded, enrichPrData as _enrichPrData };
|
|
541
545
|
export { getChildSessions, buildChildSessionContext, aggregateFilesModified };
|
|
542
546
|
|
|
@@ -3,10 +3,24 @@
|
|
|
3
3
|
* Extracted from summaryService.js for modularity.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { sessionSummaries, messages } from '../database.js';
|
|
6
|
+
import { sessionSummaries, messages, sessions } from '../database.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Check if
|
|
9
|
+
* Check if any descendant session has a summary newer than the given timestamp.
|
|
10
|
+
* @param {string} sessionId
|
|
11
|
+
* @param {number} generatedAt
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
function hasNewerDescendantSummary(sessionId, generatedAt) {
|
|
15
|
+
const descendantIds = sessions.getAllDescendantIds(sessionId);
|
|
16
|
+
if (descendantIds.length === 0) return false;
|
|
17
|
+
|
|
18
|
+
const descendantSummaries = sessionSummaries.getBySessionIds(descendantIds);
|
|
19
|
+
return descendantSummaries.some(ds => ds.generatedAt > generatedAt);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a summary is stale (message count, last message ID, or descendant summaries have changed)
|
|
10
24
|
* @param {string} sessionId
|
|
11
25
|
* @returns {boolean}
|
|
12
26
|
*/
|
|
@@ -27,9 +41,14 @@ export function isSummaryStale(sessionId) {
|
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
// Also validate count as a secondary check (defensive programming)
|
|
30
|
-
|
|
44
|
+
if (allMessages.length !== summary.messageCount) return true;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
// Fallback to count-based staleness detection for old summaries
|
|
34
|
-
|
|
48
|
+
if (allMessages.length !== summary.messageCount) return true;
|
|
49
|
+
|
|
50
|
+
// Check if any descendant session has a newer summary
|
|
51
|
+
if (hasNewerDescendantSummary(sessionId, summary.generatedAt)) return true;
|
|
52
|
+
|
|
53
|
+
return false;
|
|
35
54
|
}
|
|
@@ -4,7 +4,7 @@ import { setupGitForSession } from './gitSessionSetup.js';
|
|
|
4
4
|
import { runSession } from './sessionManager.js';
|
|
5
5
|
import { broadcastToProject } from '../websocket.js';
|
|
6
6
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
7
|
-
import { resolveAgentTypeFromModel } from './sessionProvider.js';
|
|
7
|
+
import { resolveAgentTypeFromModel, resolveProviderMetadataFromModel } from './sessionProvider.js';
|
|
8
8
|
|
|
9
9
|
const liquid = new Liquid();
|
|
10
10
|
|
|
@@ -100,6 +100,8 @@ async function resolveWorkingDirectory(parentSession, project, settings, newSess
|
|
|
100
100
|
gitBranch: settings.gitBranch,
|
|
101
101
|
sessionId: newSessionId,
|
|
102
102
|
worktreeBasePath: project.worktreePath || null,
|
|
103
|
+
commitAttributionOverride:
|
|
104
|
+
resolveProviderMetadataFromModel(settings.model)?.commitAttributionOverride ?? null,
|
|
103
105
|
});
|
|
104
106
|
return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
|
|
105
107
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @type {Map<string, {inputTokens: number, outputTokens: number, lastMessageOutput: number, cacheReadInputTokens: number, cacheCreationInputTokens: number}>}
|
|
1
|
+
/** @type {Map<string, {inputTokens: number, outputTokens: number, thinkingTokens: number, lastMessageOutput: number, cacheReadInputTokens: number, cacheCreationInputTokens: number}>}
|
|
2
2
|
* Current turn usage - accumulates across multiple messages within a turn
|
|
3
3
|
* Keyed by conversationId (Issue #175)
|
|
4
4
|
* - inputTokens: MAX seen across all messages (larger context with tool results)
|
|
@@ -31,6 +31,7 @@ function updateTurnUsage(conversationId, usage, eventType) {
|
|
|
31
31
|
const current = currentTurnUsage.get(conversationId) || {
|
|
32
32
|
inputTokens: 0,
|
|
33
33
|
outputTokens: 0,
|
|
34
|
+
thinkingTokens: 0,
|
|
34
35
|
lastMessageOutput: 0,
|
|
35
36
|
cacheReadInputTokens: 0,
|
|
36
37
|
cacheCreationInputTokens: 0,
|
|
@@ -46,11 +47,13 @@ function updateTurnUsage(conversationId, usage, eventType) {
|
|
|
46
47
|
estimatedOutputTokens.delete(conversationId);
|
|
47
48
|
// 4. For input tokens, keep the MAX (larger context with tool results)
|
|
48
49
|
current.inputTokens = Math.max(current.inputTokens, usage.input_tokens || 0);
|
|
50
|
+
current.thinkingTokens = Math.max(current.thinkingTokens, usage.thinking_tokens || usage.thinkingTokens || 0);
|
|
49
51
|
current.cacheReadInputTokens = Math.max(current.cacheReadInputTokens, usage.cache_read_input_tokens || 0);
|
|
50
52
|
current.cacheCreationInputTokens = Math.max(current.cacheCreationInputTokens, usage.cache_creation_input_tokens || 0);
|
|
51
53
|
} else if (eventType === 'message_delta') {
|
|
52
54
|
// OUTPUT STREAMING - output_tokens is cumulative within this message
|
|
53
55
|
current.lastMessageOutput = usage.output_tokens || 0;
|
|
56
|
+
current.thinkingTokens = Math.max(current.thinkingTokens, usage.thinking_tokens || usage.thinkingTokens || 0);
|
|
54
57
|
// Clear estimate when actual output tokens arrive
|
|
55
58
|
estimatedOutputTokens.delete(conversationId);
|
|
56
59
|
}
|
|
@@ -61,6 +64,7 @@ function updateTurnUsage(conversationId, usage, eventType) {
|
|
|
61
64
|
return {
|
|
62
65
|
inputTokens: current.inputTokens,
|
|
63
66
|
outputTokens: current.outputTokens + current.lastMessageOutput,
|
|
67
|
+
thinkingTokens: current.thinkingTokens,
|
|
64
68
|
cacheReadInputTokens: current.cacheReadInputTokens,
|
|
65
69
|
cacheCreationInputTokens: current.cacheCreationInputTokens,
|
|
66
70
|
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { sessions, conversations, messages } from '../database.js';
|
|
2
|
+
import { broadcastToSession } from '../websocket.js';
|
|
3
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
|
+
|
|
5
|
+
export function normalizeFinalErrorMessage(error) {
|
|
6
|
+
if (error?.message) {
|
|
7
|
+
return error.message;
|
|
8
|
+
}
|
|
9
|
+
if (typeof error === 'string') {
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
if (error == null) {
|
|
13
|
+
return 'Unknown error';
|
|
14
|
+
}
|
|
15
|
+
return String(error);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeMessageContent(content) {
|
|
19
|
+
return (content || '').trim().replace(/\s+/g, ' ');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildVisibleErrorContent(agentType, errorMessage) {
|
|
23
|
+
if (agentType === 'codex') {
|
|
24
|
+
return `Codex failed before completing this turn:\n\n${errorMessage}`;
|
|
25
|
+
}
|
|
26
|
+
if (agentType === 'claude-code') {
|
|
27
|
+
return `Claude Code failed before completing this turn:\n\n${errorMessage}`;
|
|
28
|
+
}
|
|
29
|
+
return `The agent failed before completing this turn:\n\n${errorMessage}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a visible error message already exists to prevent duplicates.
|
|
34
|
+
*
|
|
35
|
+
* Uses two strategies to detect existing failures:
|
|
36
|
+
*
|
|
37
|
+
* Strategy 1: Exact match with generated content
|
|
38
|
+
* - Checks if the latest message is an assistant message with content exactly matching
|
|
39
|
+
* what we would generate (e.g., "Codex failed before completing this turn:\n\nerror")
|
|
40
|
+
* - This catches cases where we already created the formatted error message
|
|
41
|
+
*
|
|
42
|
+
* Strategy 2: Raw error in messages after latest user message
|
|
43
|
+
* - Finds the latest user message, then checks all subsequent assistant messages
|
|
44
|
+
* - Returns true if any assistant message contains the raw error text
|
|
45
|
+
* - This catches cases where the agent itself already reported the error
|
|
46
|
+
* (e.g., "Run failed: usage limit reached" contains "usage limit reached")
|
|
47
|
+
*
|
|
48
|
+
* @param {Array} conversationMessages - All messages in the conversation
|
|
49
|
+
* @param {string} generatedContent - The formatted error content we would create
|
|
50
|
+
* @param {string} rawErrorMessage - The raw error message text
|
|
51
|
+
* @returns {boolean} True if a duplicate error message already exists
|
|
52
|
+
*/
|
|
53
|
+
function hasExistingVisibleFailure(conversationMessages, generatedContent, rawErrorMessage) {
|
|
54
|
+
const normalizedGenerated = normalizeMessageContent(generatedContent);
|
|
55
|
+
const normalizedError = normalizeMessageContent(rawErrorMessage);
|
|
56
|
+
const latestMessage = conversationMessages[conversationMessages.length - 1];
|
|
57
|
+
|
|
58
|
+
// Strategy 1: Check if the latest message is an exact match with our generated content
|
|
59
|
+
if (
|
|
60
|
+
latestMessage?.role === 'assistant' &&
|
|
61
|
+
normalizeMessageContent(latestMessage.content) === normalizedGenerated
|
|
62
|
+
) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find the index of the latest user message
|
|
67
|
+
let latestUserIndex = -1;
|
|
68
|
+
for (let i = conversationMessages.length - 1; i >= 0; i -= 1) {
|
|
69
|
+
if (conversationMessages[i].role === 'user') {
|
|
70
|
+
latestUserIndex = i;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Strategy 2: Check if any assistant message after the latest user message contains the raw error
|
|
76
|
+
return conversationMessages
|
|
77
|
+
.slice(latestUserIndex + 1)
|
|
78
|
+
.some((message) => {
|
|
79
|
+
if (message.role !== 'assistant') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const normalizedContent = normalizeMessageContent(message.content);
|
|
83
|
+
return normalizedContent === normalizedGenerated ||
|
|
84
|
+
(normalizedError && normalizedContent.includes(normalizedError));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveErrorConversationId(sessionId, activeConversationIds) {
|
|
89
|
+
const activeConversationId = activeConversationIds.get(sessionId);
|
|
90
|
+
if (activeConversationId) {
|
|
91
|
+
return activeConversationId;
|
|
92
|
+
}
|
|
93
|
+
const activeConversation = conversations.ensureActiveConversation(sessionId);
|
|
94
|
+
if (activeConversation?.id) {
|
|
95
|
+
activeConversationIds.set(sessionId, activeConversation.id);
|
|
96
|
+
return activeConversation.id;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createVisibleFinalErrorMessage(sessionId, error, activeConversationIds) {
|
|
102
|
+
const conversationId = resolveErrorConversationId(sessionId, activeConversationIds);
|
|
103
|
+
if (!conversationId) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const session = sessions.getById(sessionId);
|
|
108
|
+
const errorMessage = normalizeFinalErrorMessage(error);
|
|
109
|
+
const content = buildVisibleErrorContent(session?.agentType, errorMessage);
|
|
110
|
+
const conversationMessages = messages.getByConversationId(conversationId) || [];
|
|
111
|
+
|
|
112
|
+
if (hasExistingVisibleFailure(conversationMessages, content, errorMessage)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const message = messages.create(sessionId, 'assistant', content, { conversationId });
|
|
117
|
+
sessions.touch(sessionId);
|
|
118
|
+
broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_MESSAGE, {
|
|
119
|
+
message,
|
|
120
|
+
conversationId,
|
|
121
|
+
});
|
|
122
|
+
return message;
|
|
123
|
+
}
|
|
@@ -4,8 +4,7 @@ export const API_PREFIX = '/api';
|
|
|
4
4
|
export const WS_PATH = '/ws';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* These weights represent relative costs compared to input tokens (1.0 = base rate)
|
|
7
|
+
* Legacy token cost weights kept for settings API compatibility.
|
|
9
8
|
*/
|
|
10
9
|
export const DEFAULT_TOKEN_COST_WEIGHTS = {
|
|
11
10
|
input: 1.0, // Base rate
|
|
@@ -21,6 +20,9 @@ export const WS_RECONNECT_MAX_DELAY = 30000;
|
|
|
21
20
|
|
|
22
21
|
export const TOAST_DURATION = 5000;
|
|
23
22
|
|
|
23
|
+
/** Default delay (in minutes) before auto-rescheduling a session. */
|
|
24
|
+
export const DEFAULT_RESCHEDULE_DELAY_MINUTES = 60;
|
|
25
|
+
|
|
24
26
|
export const DEFAULT_SYSTEM_PROMPT = `You are Claude Code, an AI coding assistant. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have full access to the shell and can execute any commands needed to assist the user. Be helpful, accurate, and thorough.
|
|
25
27
|
|
|
26
28
|
IMPORTANT: Your working directory is already set correctly for this session. NEVER use \`cd\` to change to a hardcoded project path before running commands (e.g., \`cd /path/to/project && git status\`). This bypasses git worktree isolation and causes commands to run in the wrong directory. Always run commands directly without changing directory.
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
const OPTION_TOKEN_DASHES = /(^|\s)([-\u2010-\u2015\u2212]+)(?=[A-Za-z0-9-])/g;
|
|
4
|
+
const UNICODE_DASHES = /[\u2010-\u2015\u2212]/g;
|
|
5
|
+
const HAS_UNICODE_DASH = /[\u2010-\u2015\u2212]/;
|
|
6
|
+
|
|
7
|
+
export function normalizeCommandOptionDashes(command) {
|
|
8
|
+
return command.replace(OPTION_TOKEN_DASHES, (match, prefix, dashes) => {
|
|
9
|
+
if (!HAS_UNICODE_DASH.test(dashes)) {
|
|
10
|
+
return match;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return `${prefix}${dashes.replace(UNICODE_DASHES, '-')}`;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
export const CreateCommandButtonRequest = z.object({
|
|
4
18
|
label: z.string().min(1, 'Label is required'),
|
|
5
|
-
command: z.string().min(1, 'Command is required'),
|
|
19
|
+
command: z.string().min(1, 'Command is required').transform(normalizeCommandOptionDashes),
|
|
6
20
|
sortOrder: z.number().int().optional().default(0),
|
|
7
21
|
showOnList: z.boolean().optional().default(false),
|
|
8
22
|
});
|
|
9
23
|
|
|
10
24
|
export const UpdateCommandButtonRequest = z.object({
|
|
11
25
|
label: z.string().min(1).optional(),
|
|
12
|
-
command: z.string().min(1).optional(),
|
|
26
|
+
command: z.string().min(1).transform(normalizeCommandOptionDashes).optional(),
|
|
13
27
|
sortOrder: z.number().int().optional(),
|
|
14
28
|
showOnList: z.boolean().optional(),
|
|
15
29
|
}).refine(obj => Object.keys(obj).length > 0, 'At least one field must be provided for update');
|
|
@@ -46,9 +46,10 @@ export const ProjectSessionDefaultsRequest = z.object({
|
|
|
46
46
|
thinkingEnabled: z.boolean().nullable().optional(),
|
|
47
47
|
effortLevel: z.enum(['low', 'medium', 'high', 'max', 'auto']).nullable().optional(),
|
|
48
48
|
startImmediately: z.boolean().nullable().optional(),
|
|
49
|
-
gitMode: z.enum(['branch', 'worktree']).nullable().optional(),
|
|
49
|
+
gitMode: z.enum(['branch', 'worktree', 'current']).nullable().optional(),
|
|
50
50
|
gitBranch: z.string().nullable().optional(),
|
|
51
51
|
model: z.string().nullable().optional(),
|
|
52
|
+
providerId: z.string().nullable().optional(),
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
export const ProjectSessionDefaultsResponse = z.object({
|
|
@@ -58,9 +59,10 @@ export const ProjectSessionDefaultsResponse = z.object({
|
|
|
58
59
|
thinkingEnabled: z.boolean().nullable(),
|
|
59
60
|
effortLevel: z.enum(['low', 'medium', 'high', 'max', 'auto']).nullable(),
|
|
60
61
|
startImmediately: z.boolean().nullable(),
|
|
61
|
-
gitMode: z.enum(['branch', 'worktree']).nullable(),
|
|
62
|
+
gitMode: z.enum(['branch', 'worktree', 'current']).nullable(),
|
|
62
63
|
gitBranch: z.string().nullable(),
|
|
63
64
|
model: z.string().nullable(),
|
|
65
|
+
providerId: z.string().nullable(),
|
|
64
66
|
createdAt: z.number(),
|
|
65
67
|
updatedAt: z.number(),
|
|
66
68
|
});
|