@gkorepanov/ccodex 0.3.0 → 0.3.4
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 +27 -9
- package/RELEASING.md +14 -15
- package/SSH_VERIFY.md +11 -0
- package/archive/README.md +23 -0
- package/compatibility.json +1 -2
- package/config.example.toml +12 -4
- package/dist/claude/asyncQueue.d.ts +3 -0
- package/dist/claude/asyncQueue.js +13 -2
- package/dist/claude/asyncQueue.js.map +1 -1
- package/dist/claude/commandActions.d.ts +6 -0
- package/dist/claude/commandActions.js +57 -0
- package/dist/claude/commandActions.js.map +1 -0
- package/dist/claude/compactCommand.d.ts +2 -0
- package/dist/claude/compactCommand.js +7 -0
- package/dist/claude/compactCommand.js.map +1 -0
- package/dist/claude/goalTools.d.ts +15 -0
- package/dist/claude/goalTools.js +173 -0
- package/dist/claude/goalTools.js.map +1 -0
- package/dist/claude/hookMapper.d.ts +17 -4
- package/dist/claude/hookMapper.js +7 -5
- package/dist/claude/hookMapper.js.map +1 -1
- package/dist/claude/rateLimits.d.ts +72 -0
- package/dist/claude/rateLimits.js +330 -0
- package/dist/claude/rateLimits.js.map +1 -0
- package/dist/claude/responseItemValidation.d.ts +2 -0
- package/dist/claude/responseItemValidation.js +40 -0
- package/dist/claude/responseItemValidation.js.map +1 -0
- package/dist/claude/resultClassifier.d.ts +8 -2
- package/dist/claude/resultClassifier.js +1 -1
- package/dist/claude/resultClassifier.js.map +1 -1
- package/dist/claude/service.d.ts +108 -28
- package/dist/claude/service.js +1276 -668
- package/dist/claude/service.js.map +1 -1
- package/dist/claude/session/backgroundOutput.d.ts +2 -0
- package/dist/claude/session/backgroundOutput.js +29 -0
- package/dist/claude/session/backgroundOutput.js.map +1 -0
- package/dist/claude/session/commands.d.ts +855 -0
- package/dist/claude/session/commands.js +2 -0
- package/dist/claude/session/commands.js.map +1 -0
- package/dist/claude/session/goalState.d.ts +49 -0
- package/dist/claude/session/goalState.js +407 -0
- package/dist/claude/session/goalState.js.map +1 -0
- package/dist/claude/session/mailbox.d.ts +52 -0
- package/dist/claude/session/mailbox.js +190 -0
- package/dist/claude/session/mailbox.js.map +1 -0
- package/dist/claude/session/outputAdapter.d.ts +11 -0
- package/dist/claude/session/outputAdapter.js +31 -0
- package/dist/claude/session/outputAdapter.js.map +1 -0
- package/dist/claude/session/providerFacts.d.ts +47 -0
- package/dist/claude/session/providerFacts.js +84 -0
- package/dist/claude/session/providerFacts.js.map +1 -0
- package/dist/claude/session/providerRuntime.d.ts +1 -0
- package/dist/claude/session/providerRuntime.js +2 -0
- package/dist/claude/session/providerRuntime.js.map +1 -0
- package/dist/claude/session/providerRuntimeFactory.d.ts +49 -0
- package/dist/claude/session/providerRuntimeFactory.js +127 -0
- package/dist/claude/session/providerRuntimeFactory.js.map +1 -0
- package/dist/claude/session/repository.d.ts +57 -0
- package/dist/claude/session/repository.js +161 -0
- package/dist/claude/session/repository.js.map +1 -0
- package/dist/claude/session/runtime.d.ts +46 -0
- package/dist/claude/session/runtime.js +140 -0
- package/dist/claude/session/runtime.js.map +1 -0
- package/dist/claude/session/scopeState.d.ts +61 -0
- package/dist/claude/session/scopeState.js +74 -0
- package/dist/claude/session/scopeState.js.map +1 -0
- package/dist/claude/session/session.d.ts +281 -0
- package/dist/claude/session/session.js +6664 -0
- package/dist/claude/session/session.js.map +1 -0
- package/dist/claude/session/shellRunner.d.ts +13 -0
- package/dist/claude/session/shellRunner.js +103 -0
- package/dist/claude/session/shellRunner.js.map +1 -0
- package/dist/claude/session/usage.d.ts +2 -0
- package/dist/claude/session/usage.js +10 -0
- package/dist/claude/session/usage.js.map +1 -0
- package/dist/claude/sessionRegistry.d.ts +26 -0
- package/dist/claude/sessionRegistry.js +118 -0
- package/dist/claude/sessionRegistry.js.map +1 -0
- package/dist/claude/shellSupervisor.d.ts +1 -0
- package/dist/claude/shellSupervisor.js +69 -0
- package/dist/claude/shellSupervisor.js.map +1 -0
- package/dist/claude/statusCommand.d.ts +21 -0
- package/dist/claude/statusCommand.js +97 -0
- package/dist/claude/statusCommand.js.map +1 -0
- package/dist/claude/toolMapper.d.ts +9 -1
- package/dist/claude/toolMapper.js +92 -16
- package/dist/claude/toolMapper.js.map +1 -1
- package/dist/claude/transcriptBrancher.d.ts +37 -0
- package/dist/claude/transcriptBrancher.js +103 -0
- package/dist/claude/transcriptBrancher.js.map +1 -0
- package/dist/cli/delegate.js +23 -1
- package/dist/cli/delegate.js.map +1 -1
- package/dist/cli/main.js +28 -12
- package/dist/cli/main.js.map +1 -1
- package/dist/codex/generated/ResponseItemRuntimeSchema.d.ts +8 -0
- package/dist/codex/generated/ResponseItemRuntimeSchema.js +7 -0
- package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -0
- package/dist/compatibility/probe.js +11 -6
- package/dist/compatibility/probe.js.map +1 -1
- package/dist/config/config.d.ts +9 -0
- package/dist/config/config.js +80 -11
- package/dist/config/config.js.map +1 -1
- package/dist/daemon/daemon.js +91 -43
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/ownership.d.ts +15 -0
- package/dist/daemon/ownership.js +185 -0
- package/dist/daemon/ownership.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +3 -0
- package/dist/daemon/supervisor.js +37 -1
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/gateway/clientConnection.d.ts +7 -1
- package/dist/gateway/clientConnection.js +435 -48
- package/dist/gateway/clientConnection.js.map +1 -1
- package/dist/gateway/remoteRelay.js +0 -1
- package/dist/gateway/remoteRelay.js.map +1 -1
- package/dist/gateway/server.d.ts +1 -1
- package/dist/gateway/server.js +37 -9
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/subscriptions.d.ts +9 -2
- package/dist/gateway/subscriptions.js +87 -9
- package/dist/gateway/subscriptions.js.map +1 -1
- package/dist/gateway/transientNotice.d.ts +2 -0
- package/dist/gateway/transientNotice.js +42 -1
- package/dist/gateway/transientNotice.js.map +1 -1
- package/dist/handoff/service.d.ts +32 -3
- package/dist/handoff/service.js +304 -26
- package/dist/handoff/service.js.map +1 -1
- package/dist/handoff/titleGeneration.d.ts +28 -0
- package/dist/handoff/titleGeneration.js +151 -0
- package/dist/handoff/titleGeneration.js.map +1 -0
- package/dist/management/doctor.d.ts +1 -1
- package/dist/management/doctor.js +32 -23
- package/dist/management/doctor.js.map +1 -1
- package/dist/management/lifecycle.js +14 -2
- package/dist/management/lifecycle.js.map +1 -1
- package/dist/management/remoteShim.d.ts +10 -0
- package/dist/management/remoteShim.js +65 -0
- package/dist/management/remoteShim.js.map +1 -0
- package/dist/management/setup.d.ts +4 -0
- package/dist/management/setup.js +108 -39
- package/dist/management/setup.js.map +1 -1
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +6 -0
- package/dist/observability/metrics.js.map +1 -1
- package/dist/protocol/errors.d.ts +1 -0
- package/dist/protocol/errors.js +4 -1
- package/dist/protocol/errors.js.map +1 -1
- package/dist/runtime/dependencies.js +2 -2
- package/dist/runtime/dependencies.js.map +1 -1
- package/dist/runtime/providerAvailability.d.ts +24 -0
- package/dist/runtime/providerAvailability.js +107 -0
- package/dist/runtime/providerAvailability.js.map +1 -0
- package/dist/state/stateCommand.d.ts +21 -0
- package/dist/state/stateCommand.js +198 -0
- package/dist/state/stateCommand.js.map +1 -0
- package/dist/state/stockStateTracker.d.ts +14 -0
- package/dist/state/stockStateTracker.js +120 -0
- package/dist/state/stockStateTracker.js.map +1 -0
- package/dist/store/HybridStore.d.ts +118 -2
- package/dist/store/HybridStore.js +23 -1
- package/dist/store/HybridStore.js.map +1 -1
- package/dist/store/memoryStore.d.ts +57 -7
- package/dist/store/memoryStore.js +337 -5
- package/dist/store/memoryStore.js.map +1 -1
- package/dist/store/sqliteStore.d.ts +30 -4
- package/dist/store/sqliteStore.js +395 -42
- package/dist/store/sqliteStore.js.map +1 -1
- package/install.sh +2 -2
- package/package.json +18 -10
- package/dist/claude/interactions.d.ts +0 -23
- package/dist/claude/interactions.js +0 -77
- package/dist/claude/interactions.js.map +0 -1
- package/dist/claude/sessionRuntime.d.ts +0 -102
- package/dist/claude/sessionRuntime.js +0 -1361
- package/dist/claude/sessionRuntime.js.map +0 -1
- /package/{BUILD_BENCHMARKS.md → archive/BUILD_BENCHMARKS.md} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Thread } from "../codex/generated/v2/Thread.js";
|
|
2
2
|
import type { ThreadListParams } from "../codex/generated/v2/ThreadListParams.js";
|
|
3
3
|
import type { Turn } from "../codex/generated/v2/Turn.js";
|
|
4
|
-
import type {
|
|
5
|
-
import type { ClaudeThreadRecord, EventPersistence, HybridStore, PendingRequestRecord } from "./HybridStore.js";
|
|
4
|
+
import type { AppendProviderEvent, ClaudeThreadRecord, EventPersistence, GoalPatch, GoalUsageInput, HybridStore, InternalGoal, PendingRequestRecord, PendingThreadRemoval, ProviderEventDisposition, ProviderEventRecord, ProviderItemCorrelation, ProviderRetractionMutation, StoredEvent, ThreadStateCommit, TurnProviderBoundary } from "./HybridStore.js";
|
|
6
5
|
export declare class SqliteHybridStore implements HybridStore {
|
|
7
6
|
private readonly database;
|
|
8
7
|
private readonly path;
|
|
@@ -13,7 +12,13 @@ export declare class SqliteHybridStore implements HybridStore {
|
|
|
13
12
|
allThreadRecords(): ClaudeThreadRecord[];
|
|
14
13
|
listThreads(params: ThreadListParams): Thread[];
|
|
15
14
|
updateThread(record: ClaudeThreadRecord): void;
|
|
15
|
+
isThreadArchived(threadId: string): boolean;
|
|
16
16
|
setThreadArchived(threadId: string, archived: boolean): void;
|
|
17
|
+
commitThreadsArchived(threadIds: readonly string[], archived: boolean): void;
|
|
18
|
+
beginThreadRemoval(removal: PendingThreadRemoval): void;
|
|
19
|
+
cancelThreadRemoval(rootThreadId: string): void;
|
|
20
|
+
listPendingThreadRemovals(): PendingThreadRemoval[];
|
|
21
|
+
commitThreadRemoval(rootThreadId: string, threadIds: readonly string[]): void;
|
|
17
22
|
deleteThread(threadId: string): void;
|
|
18
23
|
createTurn(threadId: string, turn: Turn): void;
|
|
19
24
|
updateTurn(threadId: string, turn: Turn): void;
|
|
@@ -22,20 +27,41 @@ export declare class SqliteHybridStore implements HybridStore {
|
|
|
22
27
|
setTurnClaudeMessageUuid(threadId: string, turnId: string, messageUuid: string): void;
|
|
23
28
|
getTurnClaudeMessageUuid(threadId: string, turnId: string): string | undefined;
|
|
24
29
|
truncateTurns(threadId: string, keepCount: number): void;
|
|
30
|
+
commitForkedThread(record: ClaudeThreadRecord, turns: readonly Turn[], boundaries: readonly TurnProviderBoundary[]): void;
|
|
31
|
+
commitThreadRollback(record: ClaudeThreadRecord, keepCount: number, boundaries: readonly TurnProviderBoundary[], removedThreadIds?: readonly string[]): void;
|
|
32
|
+
commitThreadState(commit: ThreadStateCommit): number[];
|
|
25
33
|
appendEvent(threadId: string, turnId: string | null, method: string, params: unknown, persistence?: EventPersistence): number;
|
|
34
|
+
eventHighWatermark(threadId: string): number;
|
|
35
|
+
listEventsAfter(threadId: string, sequence: number): StoredEvent[];
|
|
26
36
|
hasProcessedProviderEvent(threadId: string, providerEventId: string): boolean;
|
|
27
37
|
markProviderEventProcessed(threadId: string, providerEventType: string, providerEventId: string): void;
|
|
38
|
+
appendProviderEvent(event: AppendProviderEvent): {
|
|
39
|
+
record: ProviderEventRecord;
|
|
40
|
+
inserted: boolean;
|
|
41
|
+
};
|
|
42
|
+
completeProviderEvent(threadId: string, sequence: number, disposition: Exclude<ProviderEventDisposition, "pending">, error?: string | null): void;
|
|
43
|
+
listProviderEvents(threadId: string, disposition?: ProviderEventDisposition): ProviderEventRecord[];
|
|
44
|
+
pruneProviderEvents(threadId: string, maxEvents: number, maxBytes: number): number;
|
|
45
|
+
linkProviderItems(threadId: string, providerMessageId: string, ownerThreadId: string, turnId: string, itemIds: readonly string[]): void;
|
|
46
|
+
private insertProviderItems;
|
|
47
|
+
listProviderItemCorrelations(threadId: string, providerMessageIds: readonly string[]): ProviderItemCorrelation[];
|
|
48
|
+
deleteProviderItemCorrelations(threadId: string, providerMessageIds: readonly string[]): void;
|
|
49
|
+
commitProviderRetraction(record: ClaudeThreadRecord, providerMessageIds: readonly string[], mutations: readonly ProviderRetractionMutation[], removedThreadIds?: readonly string[]): void;
|
|
28
50
|
createPendingRequest(request: PendingRequestRecord): void;
|
|
29
51
|
getPendingRequest(requestId: string): PendingRequestRecord | undefined;
|
|
30
52
|
findPendingRequestByClaudeId(threadId: string, claudeRequestId: string): PendingRequestRecord | undefined;
|
|
31
53
|
listPendingRequests(threadId: string): PendingRequestRecord[];
|
|
32
54
|
resolvePendingRequest(requestId: string, status: "resolved" | "cancelled", response: unknown): void;
|
|
33
|
-
getGoal(threadId: string):
|
|
34
|
-
setGoal(
|
|
55
|
+
getGoal(threadId: string): InternalGoal | undefined;
|
|
56
|
+
setGoal(threadId: string, patch: GoalPatch): InternalGoal;
|
|
35
57
|
clearGoal(threadId: string): boolean;
|
|
58
|
+
accountGoalUsage(input: GoalUsageInput): InternalGoal | undefined;
|
|
36
59
|
close(): void;
|
|
37
60
|
private transaction;
|
|
61
|
+
private insertEvent;
|
|
62
|
+
private deleteThreadRows;
|
|
38
63
|
private writeTurn;
|
|
64
|
+
private insertTurn;
|
|
39
65
|
private syncItems;
|
|
40
66
|
private backup;
|
|
41
67
|
private migrate;
|
|
@@ -2,6 +2,7 @@ import { chmodSync, copyFileSync, existsSync, mkdirSync, renameSync, unlinkSync,
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
4
|
import { v7 as uuidv7 } from "uuid";
|
|
5
|
+
import { settingsGeneration, withSettingsFrom } from "./HybridStore.js";
|
|
5
6
|
import { filterSortThreads } from "./threadFilter.js";
|
|
6
7
|
function json(value) {
|
|
7
8
|
return JSON.stringify(value);
|
|
@@ -67,6 +68,12 @@ function parseRecord(row, turns) {
|
|
|
67
68
|
tokenUsageTotal: runtime.tokenUsageTotal && typeof runtime.tokenUsageTotal === "object"
|
|
68
69
|
? runtime.tokenUsageTotal
|
|
69
70
|
: { totalTokens: 0, inputTokens: 0, cachedInputTokens: 0, outputTokens: 0, reasoningOutputTokens: 0 },
|
|
71
|
+
tokenUsageLast: runtime.tokenUsageLast && typeof runtime.tokenUsageLast === "object"
|
|
72
|
+
? runtime.tokenUsageLast
|
|
73
|
+
: null,
|
|
74
|
+
modelContextWindow: typeof runtime.modelContextWindow === "number" ? runtime.modelContextWindow : null,
|
|
75
|
+
providerCostUsdTotal: typeof runtime.providerCostUsdTotal === "number" ? runtime.providerCostUsdTotal : 0,
|
|
76
|
+
settingsGeneration: typeof runtime.settingsGeneration === "number" ? runtime.settingsGeneration : 0,
|
|
70
77
|
};
|
|
71
78
|
}
|
|
72
79
|
function parsePending(row) {
|
|
@@ -83,6 +90,21 @@ function parsePending(row) {
|
|
|
83
90
|
resolvedAt: row.resolved_at,
|
|
84
91
|
};
|
|
85
92
|
}
|
|
93
|
+
function parseProviderEvent(row) {
|
|
94
|
+
return {
|
|
95
|
+
sequence: row.sequence,
|
|
96
|
+
threadId: row.thread_id,
|
|
97
|
+
processEpoch: row.process_epoch,
|
|
98
|
+
providerSequence: row.provider_sequence,
|
|
99
|
+
providerEventType: row.provider_event_type,
|
|
100
|
+
providerEventId: row.provider_event_id,
|
|
101
|
+
payload: JSON.parse(row.payload_json),
|
|
102
|
+
disposition: row.disposition,
|
|
103
|
+
error: row.error,
|
|
104
|
+
createdAt: row.created_at,
|
|
105
|
+
projectedAt: row.projected_at,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
86
108
|
export class SqliteHybridStore {
|
|
87
109
|
database;
|
|
88
110
|
path;
|
|
@@ -113,6 +135,10 @@ export class SqliteHybridStore {
|
|
|
113
135
|
collaborationMode: record.collaborationMode,
|
|
114
136
|
outputSchema: record.outputSchema,
|
|
115
137
|
tokenUsageTotal: record.tokenUsageTotal,
|
|
138
|
+
tokenUsageLast: record.tokenUsageLast,
|
|
139
|
+
modelContextWindow: record.modelContextWindow,
|
|
140
|
+
providerCostUsdTotal: record.providerCostUsdTotal ?? 0,
|
|
141
|
+
settingsGeneration: settingsGeneration(record),
|
|
116
142
|
}));
|
|
117
143
|
}
|
|
118
144
|
hasThread(threadId) {
|
|
@@ -149,44 +175,85 @@ export class SqliteHybridStore {
|
|
|
149
175
|
return filterSortThreads(rows.map((row) => JSON.parse(row.thread_json)), params);
|
|
150
176
|
}
|
|
151
177
|
updateThread(record) {
|
|
178
|
+
const current = this.getThreadRecord(record.thread.id, false);
|
|
179
|
+
const merged = current && settingsGeneration(current) > settingsGeneration(record)
|
|
180
|
+
? withSettingsFrom(record, current)
|
|
181
|
+
: record;
|
|
152
182
|
this.database.prepare(`
|
|
153
183
|
UPDATE threads SET
|
|
154
184
|
claude_session_id = ?, model_picker_id = ?, claude_model_value = ?,
|
|
155
|
-
service_tier = ?, cwd = ?,
|
|
185
|
+
service_tier = ?, cwd = ?, updated_at = ?, thread_json = ?,
|
|
156
186
|
approval_policy_json = ?, sandbox_policy_json = ?, base_instructions = ?,
|
|
157
187
|
developer_instructions = ?, personality = ?, resolved_model = ?,
|
|
158
188
|
last_claude_message_uuid = ?, last_completed_turn_id = ?, claude_code_version = ?,
|
|
159
189
|
runtime_settings_json = ?
|
|
160
190
|
WHERE id = ?
|
|
161
|
-
`).run(
|
|
162
|
-
approvalsReviewer:
|
|
163
|
-
reasoningEffort:
|
|
164
|
-
reasoningSummary:
|
|
165
|
-
collaborationMode:
|
|
166
|
-
outputSchema:
|
|
167
|
-
tokenUsageTotal:
|
|
168
|
-
|
|
191
|
+
`).run(merged.claudeSessionId, merged.modelPickerId, merged.claudeModelValue, merged.serviceTier, merged.thread.cwd, merged.thread.updatedAt, json({ ...merged.thread, turns: [] }), json(merged.approvalPolicy), json(merged.sandboxPolicy), merged.baseInstructions, merged.developerInstructions, merged.personality, merged.resolvedModel, merged.lastClaudeMessageUuid, merged.lastCompletedTurnId, merged.claudeCodeVersion, json({
|
|
192
|
+
approvalsReviewer: merged.approvalsReviewer,
|
|
193
|
+
reasoningEffort: merged.reasoningEffort,
|
|
194
|
+
reasoningSummary: merged.reasoningSummary,
|
|
195
|
+
collaborationMode: merged.collaborationMode,
|
|
196
|
+
outputSchema: merged.outputSchema,
|
|
197
|
+
tokenUsageTotal: merged.tokenUsageTotal,
|
|
198
|
+
tokenUsageLast: merged.tokenUsageLast,
|
|
199
|
+
modelContextWindow: merged.modelContextWindow,
|
|
200
|
+
providerCostUsdTotal: merged.providerCostUsdTotal ?? 0,
|
|
201
|
+
settingsGeneration: settingsGeneration(merged),
|
|
202
|
+
}), merged.thread.id);
|
|
203
|
+
}
|
|
204
|
+
isThreadArchived(threadId) {
|
|
205
|
+
const row = this.database.prepare("SELECT archived FROM threads WHERE id = ?").get(threadId);
|
|
206
|
+
return row?.archived === 1;
|
|
169
207
|
}
|
|
170
208
|
setThreadArchived(threadId, archived) {
|
|
171
209
|
this.database.prepare("UPDATE threads SET archived = ? WHERE id = ?").run(archived ? 1 : 0, threadId);
|
|
172
210
|
}
|
|
173
|
-
|
|
211
|
+
commitThreadsArchived(threadIds, archived) {
|
|
212
|
+
const method = archived ? "thread/archived" : "thread/unarchived";
|
|
174
213
|
this.transaction(() => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.database.prepare("DELETE FROM threads WHERE id = ?").run(threadId);
|
|
214
|
+
for (const threadId of threadIds) {
|
|
215
|
+
this.setThreadArchived(threadId, archived);
|
|
216
|
+
this.insertEvent(threadId, null, method, { threadId });
|
|
217
|
+
}
|
|
180
218
|
});
|
|
181
219
|
}
|
|
182
|
-
|
|
220
|
+
beginThreadRemoval(removal) {
|
|
221
|
+
this.database.prepare(`
|
|
222
|
+
INSERT INTO pending_thread_removals (root_thread_id, claude_session_id, cwd, kind)
|
|
223
|
+
VALUES (?, ?, ?, ?)
|
|
224
|
+
ON CONFLICT(root_thread_id) DO UPDATE SET
|
|
225
|
+
claude_session_id = excluded.claude_session_id,
|
|
226
|
+
cwd = excluded.cwd,
|
|
227
|
+
kind = excluded.kind
|
|
228
|
+
`).run(removal.rootThreadId, removal.claudeSessionId, removal.cwd, removal.kind);
|
|
229
|
+
}
|
|
230
|
+
cancelThreadRemoval(rootThreadId) {
|
|
231
|
+
this.database.prepare("DELETE FROM pending_thread_removals WHERE root_thread_id = ?").run(rootThreadId);
|
|
232
|
+
}
|
|
233
|
+
listPendingThreadRemovals() {
|
|
234
|
+
return this.database.prepare(`
|
|
235
|
+
SELECT root_thread_id, claude_session_id, cwd, kind
|
|
236
|
+
FROM pending_thread_removals ORDER BY root_thread_id
|
|
237
|
+
`).all().map((row) => ({
|
|
238
|
+
rootThreadId: row.root_thread_id,
|
|
239
|
+
claudeSessionId: row.claude_session_id,
|
|
240
|
+
cwd: row.cwd,
|
|
241
|
+
kind: row.kind,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
commitThreadRemoval(rootThreadId, threadIds) {
|
|
183
245
|
this.transaction(() => {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.syncItems(threadId, turn);
|
|
246
|
+
for (const threadId of [...threadIds].reverse())
|
|
247
|
+
this.deleteThreadRows(threadId);
|
|
248
|
+
this.database.prepare("DELETE FROM pending_thread_removals WHERE root_thread_id = ?").run(rootThreadId);
|
|
188
249
|
});
|
|
189
250
|
}
|
|
251
|
+
deleteThread(threadId) {
|
|
252
|
+
this.transaction(() => this.deleteThreadRows(threadId));
|
|
253
|
+
}
|
|
254
|
+
createTurn(threadId, turn) {
|
|
255
|
+
this.transaction(() => this.insertTurn(threadId, turn));
|
|
256
|
+
}
|
|
190
257
|
updateTurn(threadId, turn) {
|
|
191
258
|
this.transaction(() => this.writeTurn(threadId, turn));
|
|
192
259
|
}
|
|
@@ -212,25 +279,68 @@ export class SqliteHybridStore {
|
|
|
212
279
|
truncateTurns(threadId, keepCount) {
|
|
213
280
|
this.database.prepare("DELETE FROM turns WHERE thread_id = ? AND ordinal >= ?").run(threadId, keepCount);
|
|
214
281
|
}
|
|
215
|
-
|
|
282
|
+
commitForkedThread(record, turns, boundaries) {
|
|
283
|
+
this.transaction(() => {
|
|
284
|
+
this.createThread(record);
|
|
285
|
+
for (const turn of turns)
|
|
286
|
+
this.insertTurn(record.thread.id, turn);
|
|
287
|
+
for (const boundary of boundaries) {
|
|
288
|
+
this.setTurnClaudeMessageUuid(record.thread.id, boundary.turnId, boundary.messageUuid);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
commitThreadRollback(record, keepCount, boundaries, removedThreadIds = []) {
|
|
293
|
+
this.transaction(() => {
|
|
294
|
+
for (const threadId of removedThreadIds)
|
|
295
|
+
this.deleteThreadRows(threadId);
|
|
296
|
+
this.truncateTurns(record.thread.id, keepCount);
|
|
297
|
+
for (const boundary of boundaries) {
|
|
298
|
+
this.setTurnClaudeMessageUuid(record.thread.id, boundary.turnId, boundary.messageUuid);
|
|
299
|
+
}
|
|
300
|
+
this.updateThread(record);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
commitThreadState(commit) {
|
|
216
304
|
return this.transaction(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
305
|
+
this.updateThread(commit.record);
|
|
306
|
+
if (commit.turn) {
|
|
307
|
+
if (commit.insertTurn)
|
|
308
|
+
this.insertTurn(commit.record.thread.id, commit.turn);
|
|
309
|
+
else
|
|
310
|
+
this.writeTurn(commit.record.thread.id, commit.turn);
|
|
222
311
|
}
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
230
|
-
`).run(uuidv7(), threadId, turnId, method, json(params), persistence?.providerEventType ?? null, persistence?.providerEventId ?? null, persistence?.dedupKey ?? null, Date.now());
|
|
231
|
-
return Number(result.lastInsertRowid);
|
|
312
|
+
if (commit.providerBoundary) {
|
|
313
|
+
const boundary = commit.providerBoundary;
|
|
314
|
+
this.setTurnClaudeMessageUuid(boundary.ownerThreadId, boundary.turnId, boundary.messageUuid);
|
|
315
|
+
this.insertProviderItems(commit.record.thread.id, boundary.messageUuid, boundary.ownerThreadId, boundary.turnId, boundary.itemIds ?? []);
|
|
316
|
+
}
|
|
317
|
+
return commit.events.map((event) => this.insertEvent(commit.record.thread.id, event.turnId, event.method, event.params, event));
|
|
232
318
|
});
|
|
233
319
|
}
|
|
320
|
+
appendEvent(threadId, turnId, method, params, persistence) {
|
|
321
|
+
return this.transaction(() => this.insertEvent(threadId, turnId, method, params, persistence));
|
|
322
|
+
}
|
|
323
|
+
eventHighWatermark(threadId) {
|
|
324
|
+
const row = this.database.prepare("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM events WHERE thread_id = ?")
|
|
325
|
+
.get(threadId);
|
|
326
|
+
return row.sequence;
|
|
327
|
+
}
|
|
328
|
+
listEventsAfter(threadId, sequence) {
|
|
329
|
+
const rows = this.database.prepare(`
|
|
330
|
+
SELECT sequence, thread_id, turn_id, method, params_json, created_at
|
|
331
|
+
FROM events
|
|
332
|
+
WHERE thread_id = ? AND sequence > ? AND method NOT LIKE 'hybrid/%'
|
|
333
|
+
ORDER BY sequence ASC
|
|
334
|
+
`).all(threadId, sequence);
|
|
335
|
+
return rows.map((row) => ({
|
|
336
|
+
sequence: row.sequence,
|
|
337
|
+
threadId: row.thread_id,
|
|
338
|
+
turnId: row.turn_id,
|
|
339
|
+
method: row.method,
|
|
340
|
+
params: JSON.parse(row.params_json),
|
|
341
|
+
createdAt: row.created_at,
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
234
344
|
hasProcessedProviderEvent(threadId, providerEventId) {
|
|
235
345
|
return this.database.prepare(`
|
|
236
346
|
SELECT 1 FROM events
|
|
@@ -242,6 +352,111 @@ export class SqliteHybridStore {
|
|
|
242
352
|
providerEventType, providerEventId, dedupKey: `provider:${providerEventId}`,
|
|
243
353
|
});
|
|
244
354
|
}
|
|
355
|
+
appendProviderEvent(event) {
|
|
356
|
+
return this.transaction(() => {
|
|
357
|
+
const existing = event.providerEventId === null ? undefined : this.database.prepare(`
|
|
358
|
+
SELECT * FROM provider_events WHERE thread_id = ? AND provider_event_id = ?
|
|
359
|
+
`).get(event.threadId, event.providerEventId);
|
|
360
|
+
if (existing)
|
|
361
|
+
return { record: parseProviderEvent(existing), inserted: false };
|
|
362
|
+
const result = this.database.prepare(`
|
|
363
|
+
INSERT INTO provider_events (
|
|
364
|
+
thread_id, process_epoch, provider_sequence, provider_event_type,
|
|
365
|
+
provider_event_id, payload_json, disposition, error, created_at, projected_at
|
|
366
|
+
) VALUES (?, ?, ?, ?, ?, ?, 'pending', NULL, ?, NULL)
|
|
367
|
+
`).run(event.threadId, event.processEpoch, event.providerSequence, event.providerEventType, event.providerEventId, json(event.payload), event.createdAt);
|
|
368
|
+
const row = this.database.prepare("SELECT * FROM provider_events WHERE sequence = ?")
|
|
369
|
+
.get(Number(result.lastInsertRowid));
|
|
370
|
+
return { record: parseProviderEvent(row), inserted: true };
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
completeProviderEvent(threadId, sequence, disposition, error = null) {
|
|
374
|
+
this.database.prepare(`
|
|
375
|
+
UPDATE provider_events SET disposition = ?, error = ?, projected_at = ? WHERE thread_id = ? AND sequence = ?
|
|
376
|
+
`).run(disposition, error, Date.now(), threadId, sequence);
|
|
377
|
+
}
|
|
378
|
+
listProviderEvents(threadId, disposition) {
|
|
379
|
+
const rows = (disposition
|
|
380
|
+
? this.database.prepare("SELECT * FROM provider_events WHERE thread_id = ? AND disposition = ? ORDER BY sequence")
|
|
381
|
+
.all(threadId, disposition)
|
|
382
|
+
: this.database.prepare("SELECT * FROM provider_events WHERE thread_id = ? ORDER BY sequence").all(threadId));
|
|
383
|
+
return rows.map(parseProviderEvent);
|
|
384
|
+
}
|
|
385
|
+
pruneProviderEvents(threadId, maxEvents, maxBytes) {
|
|
386
|
+
if (maxEvents < 1)
|
|
387
|
+
throw new Error("Provider event retention must keep at least one event.");
|
|
388
|
+
const result = this.database.prepare(`
|
|
389
|
+
DELETE FROM provider_events
|
|
390
|
+
WHERE thread_id = ?
|
|
391
|
+
AND disposition IN ('projected', 'stateOnly', 'retainedOnly', 'unsupportedVisible')
|
|
392
|
+
AND sequence IN (
|
|
393
|
+
SELECT sequence FROM (
|
|
394
|
+
SELECT sequence,
|
|
395
|
+
ROW_NUMBER() OVER (ORDER BY sequence DESC) AS ordinal,
|
|
396
|
+
SUM(LENGTH(CAST(payload_json AS BLOB))) OVER (ORDER BY sequence DESC) AS retained_bytes
|
|
397
|
+
FROM provider_events
|
|
398
|
+
WHERE thread_id = ?
|
|
399
|
+
AND disposition IN ('projected', 'stateOnly', 'retainedOnly', 'unsupportedVisible')
|
|
400
|
+
)
|
|
401
|
+
WHERE ordinal > ? OR (ordinal > 1 AND retained_bytes > ?)
|
|
402
|
+
)
|
|
403
|
+
`).run(threadId, threadId, maxEvents, maxBytes);
|
|
404
|
+
return Number(result.changes);
|
|
405
|
+
}
|
|
406
|
+
linkProviderItems(threadId, providerMessageId, ownerThreadId, turnId, itemIds) {
|
|
407
|
+
this.transaction(() => this.insertProviderItems(threadId, providerMessageId, ownerThreadId, turnId, itemIds));
|
|
408
|
+
}
|
|
409
|
+
insertProviderItems(threadId, providerMessageId, ownerThreadId, turnId, itemIds) {
|
|
410
|
+
const insert = this.database.prepare(`
|
|
411
|
+
INSERT OR IGNORE INTO provider_item_correlations (
|
|
412
|
+
thread_id, provider_message_id, owner_thread_id, turn_id, item_id
|
|
413
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
414
|
+
`);
|
|
415
|
+
for (const itemId of itemIds)
|
|
416
|
+
insert.run(threadId, providerMessageId, ownerThreadId, turnId, itemId);
|
|
417
|
+
}
|
|
418
|
+
listProviderItemCorrelations(threadId, providerMessageIds) {
|
|
419
|
+
if (providerMessageIds.length === 0)
|
|
420
|
+
return [];
|
|
421
|
+
const placeholders = providerMessageIds.map(() => "?").join(", ");
|
|
422
|
+
const rows = this.database.prepare(`
|
|
423
|
+
SELECT provider_message_id, owner_thread_id, turn_id, item_id
|
|
424
|
+
FROM provider_item_correlations
|
|
425
|
+
WHERE thread_id = ? AND provider_message_id IN (${placeholders})
|
|
426
|
+
`).all(threadId, ...providerMessageIds);
|
|
427
|
+
return rows.map((row) => ({
|
|
428
|
+
providerMessageId: row.provider_message_id,
|
|
429
|
+
ownerThreadId: row.owner_thread_id,
|
|
430
|
+
turnId: row.turn_id,
|
|
431
|
+
itemId: row.item_id,
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
deleteProviderItemCorrelations(threadId, providerMessageIds) {
|
|
435
|
+
if (providerMessageIds.length === 0)
|
|
436
|
+
return;
|
|
437
|
+
const placeholders = providerMessageIds.map(() => "?").join(", ");
|
|
438
|
+
this.database.prepare(`
|
|
439
|
+
DELETE FROM provider_item_correlations
|
|
440
|
+
WHERE thread_id = ? AND provider_message_id IN (${placeholders})
|
|
441
|
+
`).run(threadId, ...providerMessageIds);
|
|
442
|
+
}
|
|
443
|
+
commitProviderRetraction(record, providerMessageIds, mutations, removedThreadIds = []) {
|
|
444
|
+
this.transaction(() => {
|
|
445
|
+
for (const threadId of removedThreadIds)
|
|
446
|
+
this.deleteThreadRows(threadId);
|
|
447
|
+
for (const mutation of mutations) {
|
|
448
|
+
this.writeTurn(mutation.ownerThreadId, mutation.turn);
|
|
449
|
+
if (mutation.clearBoundary) {
|
|
450
|
+
this.database.prepare(`
|
|
451
|
+
UPDATE turns SET last_claude_message_uuid = NULL
|
|
452
|
+
WHERE id = ? AND thread_id = ?
|
|
453
|
+
`).run(mutation.turn.id, mutation.ownerThreadId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
this.deleteProviderItemCorrelations(record.thread.id, providerMessageIds);
|
|
457
|
+
this.updateThread(record);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
245
460
|
createPendingRequest(request) {
|
|
246
461
|
this.database.prepare(`
|
|
247
462
|
INSERT INTO pending_requests (
|
|
@@ -273,17 +488,72 @@ export class SqliteHybridStore {
|
|
|
273
488
|
}
|
|
274
489
|
getGoal(threadId) {
|
|
275
490
|
const row = this.database.prepare("SELECT goal_json FROM goals WHERE thread_id = ?").get(threadId);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
491
|
+
if (!row)
|
|
492
|
+
return undefined;
|
|
493
|
+
const stored = JSON.parse(row.goal_json);
|
|
494
|
+
if (stored.goalId)
|
|
495
|
+
return stored;
|
|
496
|
+
const migrated = { ...stored, goalId: uuidv7() };
|
|
497
|
+
this.database.prepare("UPDATE goals SET goal_json = ? WHERE thread_id = ?").run(json(migrated), threadId);
|
|
498
|
+
return migrated;
|
|
499
|
+
}
|
|
500
|
+
setGoal(threadId, patch) {
|
|
501
|
+
return this.transaction(() => {
|
|
502
|
+
const previous = this.getGoal(threadId);
|
|
503
|
+
const now = patch.now ?? Math.floor(Date.now() / 1_000);
|
|
504
|
+
const replace = patch.replace === true || !previous;
|
|
505
|
+
if (replace && patch.objective === undefined)
|
|
506
|
+
throw new Error(`cannot create goal for thread ${threadId} without an objective`);
|
|
507
|
+
const goal = {
|
|
508
|
+
threadId,
|
|
509
|
+
goalId: replace ? uuidv7() : previous.goalId,
|
|
510
|
+
objective: patch.objective ?? previous?.objective ?? "",
|
|
511
|
+
status: patch.status ?? (replace ? "active" : previous.status),
|
|
512
|
+
tokenBudget: patch.tokenBudget === undefined ? (replace ? null : previous.tokenBudget) : patch.tokenBudget,
|
|
513
|
+
tokensUsed: replace ? 0 : previous.tokensUsed,
|
|
514
|
+
timeUsedSeconds: replace ? 0 : previous.timeUsedSeconds,
|
|
515
|
+
createdAt: replace ? now : previous.createdAt,
|
|
516
|
+
updatedAt: now,
|
|
517
|
+
};
|
|
518
|
+
if (goal.status === "active" && goal.tokenBudget !== null && goal.tokensUsed >= goal.tokenBudget)
|
|
519
|
+
goal.status = "budgetLimited";
|
|
520
|
+
this.database.prepare(`
|
|
521
|
+
INSERT INTO goals (thread_id, goal_json) VALUES (?, ?)
|
|
522
|
+
ON CONFLICT(thread_id) DO UPDATE SET goal_json = excluded.goal_json
|
|
523
|
+
`).run(threadId, json(goal));
|
|
524
|
+
return goal;
|
|
525
|
+
});
|
|
283
526
|
}
|
|
284
527
|
clearGoal(threadId) {
|
|
285
528
|
return Number(this.database.prepare("DELETE FROM goals WHERE thread_id = ?").run(threadId).changes) > 0;
|
|
286
529
|
}
|
|
530
|
+
accountGoalUsage(input) {
|
|
531
|
+
return this.transaction(() => {
|
|
532
|
+
if (input.checkpointKey) {
|
|
533
|
+
const inserted = this.database.prepare(`
|
|
534
|
+
INSERT OR IGNORE INTO goal_checkpoints (thread_id, goal_id, checkpoint_key)
|
|
535
|
+
VALUES (?, ?, ?)
|
|
536
|
+
`).run(input.threadId, input.expectedGoalId, input.checkpointKey);
|
|
537
|
+
if (Number(inserted.changes) === 0)
|
|
538
|
+
return this.getGoal(input.threadId);
|
|
539
|
+
}
|
|
540
|
+
const previous = this.getGoal(input.threadId);
|
|
541
|
+
if (!previous || previous.goalId !== input.expectedGoalId)
|
|
542
|
+
return previous;
|
|
543
|
+
if (previous.status !== "active" && previous.status !== "budgetLimited")
|
|
544
|
+
return previous;
|
|
545
|
+
const goal = {
|
|
546
|
+
...previous,
|
|
547
|
+
tokensUsed: previous.tokensUsed + Math.max(0, input.tokenDelta),
|
|
548
|
+
timeUsedSeconds: previous.timeUsedSeconds + Math.max(0, input.timeDeltaSeconds),
|
|
549
|
+
updatedAt: Math.floor(Date.now() / 1_000),
|
|
550
|
+
};
|
|
551
|
+
if (goal.status === "active" && goal.tokenBudget !== null && goal.tokensUsed >= goal.tokenBudget)
|
|
552
|
+
goal.status = "budgetLimited";
|
|
553
|
+
this.database.prepare("UPDATE goals SET goal_json = ? WHERE thread_id = ?").run(json(goal), input.threadId);
|
|
554
|
+
return goal;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
287
557
|
close() {
|
|
288
558
|
try {
|
|
289
559
|
this.backup();
|
|
@@ -304,11 +574,43 @@ export class SqliteHybridStore {
|
|
|
304
574
|
throw error;
|
|
305
575
|
}
|
|
306
576
|
}
|
|
577
|
+
insertEvent(threadId, turnId, method, params, persistence) {
|
|
578
|
+
if (persistence?.dedupKey) {
|
|
579
|
+
const found = this.database.prepare("SELECT 1 FROM events WHERE thread_id = ? AND dedup_key = ?")
|
|
580
|
+
.get(threadId, persistence.dedupKey);
|
|
581
|
+
if (found)
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
584
|
+
if (persistence?.turn)
|
|
585
|
+
this.writeTurn(threadId, persistence.turn);
|
|
586
|
+
const result = this.database.prepare(`
|
|
587
|
+
INSERT INTO events (
|
|
588
|
+
event_id, thread_id, turn_id, method, params_json, provider_event_type,
|
|
589
|
+
provider_event_id, dedup_key, created_at
|
|
590
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
591
|
+
`).run(uuidv7(), threadId, turnId, method, json(params), persistence?.providerEventType ?? null, persistence?.providerEventId ?? null, persistence?.dedupKey ?? null, Date.now());
|
|
592
|
+
return Number(result.lastInsertRowid);
|
|
593
|
+
}
|
|
594
|
+
deleteThreadRows(threadId) {
|
|
595
|
+
this.database.prepare("DELETE FROM provider_item_correlations WHERE owner_thread_id = ?").run(threadId);
|
|
596
|
+
this.database.prepare("DELETE FROM goals WHERE thread_id = ?").run(threadId);
|
|
597
|
+
this.database.prepare("DELETE FROM pending_requests WHERE thread_id = ?").run(threadId);
|
|
598
|
+
this.database.prepare("DELETE FROM events WHERE thread_id = ?").run(threadId);
|
|
599
|
+
this.database.prepare("DELETE FROM turns WHERE thread_id = ?").run(threadId);
|
|
600
|
+
this.database.prepare("DELETE FROM threads WHERE id = ?").run(threadId);
|
|
601
|
+
}
|
|
307
602
|
writeTurn(threadId, turn) {
|
|
308
603
|
this.database.prepare("UPDATE turns SET status = ?, turn_json = ? WHERE id = ? AND thread_id = ?")
|
|
309
604
|
.run(turn.status, json(turn), turn.id, threadId);
|
|
310
605
|
this.syncItems(threadId, turn);
|
|
311
606
|
}
|
|
607
|
+
insertTurn(threadId, turn) {
|
|
608
|
+
const ordinalRow = this.database.prepare("SELECT COALESCE(MAX(ordinal), -1) + 1 AS ordinal FROM turns WHERE thread_id = ?")
|
|
609
|
+
.get(threadId);
|
|
610
|
+
this.database.prepare("INSERT INTO turns (id, thread_id, ordinal, status, turn_json) VALUES (?, ?, ?, ?, ?)")
|
|
611
|
+
.run(turn.id, threadId, ordinalRow.ordinal, turn.status, json(turn));
|
|
612
|
+
this.syncItems(threadId, turn);
|
|
613
|
+
}
|
|
312
614
|
syncItems(threadId, turn) {
|
|
313
615
|
this.database.prepare("DELETE FROM items WHERE turn_id = ? AND thread_id = ?").run(turn.id, threadId);
|
|
314
616
|
const insert = this.database.prepare(`
|
|
@@ -356,6 +658,7 @@ export class SqliteHybridStore {
|
|
|
356
658
|
,last_completed_turn_id TEXT
|
|
357
659
|
,claude_code_version TEXT
|
|
358
660
|
,runtime_settings_json TEXT
|
|
661
|
+
,deletion_pending INTEGER NOT NULL DEFAULT 0
|
|
359
662
|
);
|
|
360
663
|
CREATE TABLE IF NOT EXISTS turns (
|
|
361
664
|
id TEXT PRIMARY KEY,
|
|
@@ -409,6 +712,44 @@ export class SqliteHybridStore {
|
|
|
409
712
|
thread_id TEXT PRIMARY KEY,
|
|
410
713
|
goal_json TEXT NOT NULL
|
|
411
714
|
);
|
|
715
|
+
CREATE TABLE IF NOT EXISTS goal_checkpoints (
|
|
716
|
+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
717
|
+
goal_id TEXT NOT NULL,
|
|
718
|
+
checkpoint_key TEXT NOT NULL,
|
|
719
|
+
PRIMARY KEY(thread_id, goal_id, checkpoint_key)
|
|
720
|
+
);
|
|
721
|
+
CREATE TABLE IF NOT EXISTS provider_events (
|
|
722
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
723
|
+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
724
|
+
process_epoch TEXT NOT NULL,
|
|
725
|
+
provider_sequence INTEGER NOT NULL,
|
|
726
|
+
provider_event_type TEXT NOT NULL,
|
|
727
|
+
provider_event_id TEXT,
|
|
728
|
+
payload_json TEXT NOT NULL,
|
|
729
|
+
disposition TEXT NOT NULL,
|
|
730
|
+
error TEXT,
|
|
731
|
+
created_at INTEGER NOT NULL,
|
|
732
|
+
projected_at INTEGER,
|
|
733
|
+
UNIQUE(thread_id, process_epoch, provider_sequence)
|
|
734
|
+
);
|
|
735
|
+
CREATE UNIQUE INDEX IF NOT EXISTS provider_events_thread_event_id
|
|
736
|
+
ON provider_events(thread_id, provider_event_id) WHERE provider_event_id IS NOT NULL;
|
|
737
|
+
CREATE TABLE IF NOT EXISTS provider_item_correlations (
|
|
738
|
+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
739
|
+
provider_message_id TEXT NOT NULL,
|
|
740
|
+
owner_thread_id TEXT NOT NULL,
|
|
741
|
+
turn_id TEXT NOT NULL,
|
|
742
|
+
item_id TEXT NOT NULL,
|
|
743
|
+
PRIMARY KEY(thread_id, provider_message_id, item_id)
|
|
744
|
+
);
|
|
745
|
+
CREATE INDEX IF NOT EXISTS provider_item_correlations_owner
|
|
746
|
+
ON provider_item_correlations(owner_thread_id, turn_id);
|
|
747
|
+
CREATE TABLE IF NOT EXISTS pending_thread_removals (
|
|
748
|
+
root_thread_id TEXT PRIMARY KEY,
|
|
749
|
+
claude_session_id TEXT NOT NULL,
|
|
750
|
+
cwd TEXT NOT NULL,
|
|
751
|
+
kind TEXT NOT NULL
|
|
752
|
+
);
|
|
412
753
|
INSERT OR IGNORE INTO schema_migrations(version) VALUES (1);
|
|
413
754
|
`);
|
|
414
755
|
this.ensureColumn("threads", "resolved_model", "TEXT");
|
|
@@ -416,6 +757,15 @@ export class SqliteHybridStore {
|
|
|
416
757
|
this.ensureColumn("threads", "last_completed_turn_id", "TEXT");
|
|
417
758
|
this.ensureColumn("threads", "claude_code_version", "TEXT");
|
|
418
759
|
this.ensureColumn("threads", "runtime_settings_json", "TEXT");
|
|
760
|
+
this.ensureColumn("threads", "deletion_pending", "INTEGER NOT NULL DEFAULT 0");
|
|
761
|
+
const threadColumns = new Set(this.database.prepare("PRAGMA table_info(threads)").all()
|
|
762
|
+
.map((column) => column.name));
|
|
763
|
+
if (threadColumns.has("claude_session_id") && threadColumns.has("cwd")) {
|
|
764
|
+
this.database.exec(`
|
|
765
|
+
INSERT OR IGNORE INTO pending_thread_removals (root_thread_id, claude_session_id, cwd, kind)
|
|
766
|
+
SELECT id, claude_session_id, cwd, 'delete' FROM threads WHERE deletion_pending = 1
|
|
767
|
+
`);
|
|
768
|
+
}
|
|
419
769
|
this.ensureColumn("turns", "last_claude_message_uuid", "TEXT");
|
|
420
770
|
this.ensureColumn("events", "event_id", "TEXT");
|
|
421
771
|
this.ensureColumn("events", "provider_event_type", "TEXT");
|
|
@@ -429,6 +779,9 @@ export class SqliteHybridStore {
|
|
|
429
779
|
const threadScopedIds = this.database.prepare("SELECT 1 FROM schema_migrations WHERE version = 3").get();
|
|
430
780
|
if (!threadScopedIds)
|
|
431
781
|
this.migrateThreadScopedIds();
|
|
782
|
+
this.database.exec("INSERT OR IGNORE INTO schema_migrations(version) VALUES (4)");
|
|
783
|
+
this.database.exec("INSERT OR IGNORE INTO schema_migrations(version) VALUES (5)");
|
|
784
|
+
this.database.exec("INSERT OR IGNORE INTO schema_migrations(version) VALUES (6)");
|
|
432
785
|
});
|
|
433
786
|
}
|
|
434
787
|
migrateThreadScopedIds() {
|