@ottocode/server 0.1.233 → 0.1.235
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 +3 -3
- package/src/routes/config/utils.ts +3 -15
- package/src/routes/git/commit.ts +0 -3
- package/src/routes/terminals.ts +0 -21
- package/src/routes/tunnel.ts +0 -5
- package/src/runtime/agent/mcp-prepare-step.ts +1 -7
- package/src/runtime/agent/registry.ts +1 -8
- package/src/runtime/agent/runner-setup.ts +1 -48
- package/src/runtime/agent/runner.ts +20 -194
- package/src/runtime/debug/index.ts +3 -91
- package/src/runtime/debug/state.ts +5 -61
- package/src/runtime/debug/turn-dump.ts +0 -4
- package/src/runtime/message/compaction-auto.ts +1 -21
- package/src/runtime/message/compaction-mark.ts +75 -27
- package/src/runtime/message/compaction-prune.ts +0 -3
- package/src/runtime/message/history-builder.ts +2 -23
- package/src/runtime/message/service.ts +5 -64
- package/src/runtime/message/tool-history-tracker.ts +0 -3
- package/src/runtime/prompt/builder.ts +0 -2
- package/src/runtime/provider/oauth-adapter.ts +5 -5
- package/src/runtime/stream/error-handler.ts +1 -31
- package/src/runtime/stream/finish-handler.ts +3 -20
- package/src/runtime/stream/step-finish.ts +5 -26
- package/src/runtime/tools/approval.ts +0 -18
- package/src/runtime/utils/token.ts +1 -10
- package/src/tools/adapter.ts +8 -21
|
@@ -5,70 +5,7 @@
|
|
|
5
5
|
* centralized debug-state and logger modules.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { time as timeNew, debug as debugNew } from '@ottocode/sdk';
|
|
10
|
-
|
|
11
|
-
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
12
|
-
|
|
13
|
-
const SYNONYMS: Record<string, string> = {
|
|
14
|
-
debug: 'log',
|
|
15
|
-
logs: 'log',
|
|
16
|
-
logging: 'log',
|
|
17
|
-
trace: 'log',
|
|
18
|
-
verbose: 'log',
|
|
19
|
-
log: 'log',
|
|
20
|
-
time: 'timing',
|
|
21
|
-
timing: 'timing',
|
|
22
|
-
timings: 'timing',
|
|
23
|
-
perf: 'timing',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type DebugConfig = { flags: Set<string> };
|
|
27
|
-
|
|
28
|
-
let cachedConfig: DebugConfig | null = null;
|
|
29
|
-
|
|
30
|
-
function isTruthy(raw: string | undefined): boolean {
|
|
31
|
-
if (!raw) return false;
|
|
32
|
-
const trimmed = raw.trim().toLowerCase();
|
|
33
|
-
if (!trimmed) return false;
|
|
34
|
-
return TRUTHY.has(trimmed) || trimmed === 'all';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeToken(token: string): string {
|
|
38
|
-
const trimmed = token.trim().toLowerCase();
|
|
39
|
-
if (!trimmed) return '';
|
|
40
|
-
if (TRUTHY.has(trimmed) || trimmed === 'all') return 'all';
|
|
41
|
-
return SYNONYMS[trimmed] ?? trimmed;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function parseDebugConfig(): DebugConfig {
|
|
45
|
-
const flags = new Set<string>();
|
|
46
|
-
const sources = [process.env.OTTO_DEBUG, process.env.DEBUG_OTTO];
|
|
47
|
-
let sawValue = false;
|
|
48
|
-
for (const raw of sources) {
|
|
49
|
-
if (typeof raw !== 'string') continue;
|
|
50
|
-
const trimmed = raw.trim();
|
|
51
|
-
if (!trimmed) continue;
|
|
52
|
-
sawValue = true;
|
|
53
|
-
const tokens = trimmed.split(/[\s,]+/);
|
|
54
|
-
let matched = false;
|
|
55
|
-
for (const token of tokens) {
|
|
56
|
-
const normalized = normalizeToken(token);
|
|
57
|
-
if (!normalized) continue;
|
|
58
|
-
matched = true;
|
|
59
|
-
flags.add(normalized);
|
|
60
|
-
}
|
|
61
|
-
if (!matched && isTruthy(trimmed)) flags.add('all');
|
|
62
|
-
}
|
|
63
|
-
if (isTruthy(process.env.OTTO_DEBUG_TIMING)) flags.add('timing');
|
|
64
|
-
if (!flags.size && sawValue) flags.add('all');
|
|
65
|
-
return { flags };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getDebugConfig(): DebugConfig {
|
|
69
|
-
if (!cachedConfig) cachedConfig = parseDebugConfig();
|
|
70
|
-
return cachedConfig;
|
|
71
|
-
}
|
|
8
|
+
import { time as timeNew } from '@ottocode/sdk';
|
|
72
9
|
|
|
73
10
|
/**
|
|
74
11
|
* Check if debug mode is enabled for a specific flag
|
|
@@ -77,33 +14,8 @@ function getDebugConfig(): DebugConfig {
|
|
|
77
14
|
* @deprecated Use isDebugEnabled from debug-state.ts instead
|
|
78
15
|
*/
|
|
79
16
|
export function isDebugEnabled(flag?: string): boolean {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return isDebugEnabledNew();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// For specific flags like 'timing', check both new state and legacy env vars
|
|
86
|
-
if (flag === 'timing') {
|
|
87
|
-
// If new debug state is enabled OR timing flag is set
|
|
88
|
-
if (isDebugEnabledNew()) return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Legacy flag checking
|
|
92
|
-
const config = getDebugConfig();
|
|
93
|
-
if (config.flags.has('all')) return true;
|
|
94
|
-
if (flag) return config.flags.has(flag);
|
|
95
|
-
return config.flags.has('log');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Log debug message
|
|
100
|
-
* Now uses the centralized logger
|
|
101
|
-
*
|
|
102
|
-
* @deprecated Use logger.debug from logger.ts instead
|
|
103
|
-
*/
|
|
104
|
-
export function debugLog(...args: unknown[]) {
|
|
105
|
-
if (!isDebugEnabled('log')) return;
|
|
106
|
-
debugNew(args.map((arg) => String(arg)).join(' '));
|
|
17
|
+
void flag;
|
|
18
|
+
return false;
|
|
107
19
|
}
|
|
108
20
|
|
|
109
21
|
/**
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
* Runtime debug state management
|
|
3
3
|
*
|
|
4
4
|
* Centralizes debug flag state that can be set either via:
|
|
5
|
-
* - Environment variables (OTTO_DEBUG, DEBUG_OTTO)
|
|
6
5
|
* - Runtime configuration (CLI --debug flag)
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
|
-
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
10
|
-
|
|
11
8
|
type DebugState = {
|
|
12
9
|
enabled: boolean;
|
|
13
10
|
traceEnabled: boolean;
|
|
@@ -23,66 +20,16 @@ const state: DebugState = {
|
|
|
23
20
|
runtimeTraceOverride: null,
|
|
24
21
|
};
|
|
25
22
|
|
|
26
|
-
type GlobalDebugFlags = {
|
|
27
|
-
__OTTO_DEBUG_ENABLED__?: boolean;
|
|
28
|
-
__OTTO_TRACE_ENABLED__?: boolean;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const globalFlags = globalThis as GlobalDebugFlags;
|
|
32
|
-
|
|
33
|
-
function syncGlobalFlags() {
|
|
34
|
-
globalFlags.__OTTO_DEBUG_ENABLED__ = state.enabled;
|
|
35
|
-
globalFlags.__OTTO_TRACE_ENABLED__ = state.traceEnabled;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check if environment variables indicate debug mode
|
|
40
|
-
*/
|
|
41
|
-
function checkEnvDebug(): boolean {
|
|
42
|
-
const sources = [process.env.OTTO_DEBUG, process.env.DEBUG_OTTO];
|
|
43
|
-
for (const value of sources) {
|
|
44
|
-
if (!value) continue;
|
|
45
|
-
const trimmed = value.trim().toLowerCase();
|
|
46
|
-
if (TRUTHY.has(trimmed) || trimmed === 'all') {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check if environment variables indicate trace mode
|
|
55
|
-
*/
|
|
56
|
-
function checkEnvTrace(): boolean {
|
|
57
|
-
const sources = [process.env.OTTO_TRACE, process.env.TRACE_OTTO];
|
|
58
|
-
for (const value of sources) {
|
|
59
|
-
if (!value) continue;
|
|
60
|
-
const trimmed = value.trim().toLowerCase();
|
|
61
|
-
if (TRUTHY.has(trimmed)) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function checkEnvDevtools(): boolean {
|
|
69
|
-
const raw = process.env.OTTO_DEVTOOLS;
|
|
70
|
-
if (!raw) return false;
|
|
71
|
-
const trimmed = raw.trim().toLowerCase();
|
|
72
|
-
return TRUTHY.has(trimmed);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
23
|
/**
|
|
76
24
|
* Initialize debug state from environment
|
|
77
25
|
*/
|
|
78
26
|
function initialize() {
|
|
79
27
|
if (state.runtimeOverride === null) {
|
|
80
|
-
state.enabled =
|
|
28
|
+
state.enabled = false;
|
|
81
29
|
}
|
|
82
30
|
if (state.runtimeTraceOverride === null) {
|
|
83
|
-
state.traceEnabled =
|
|
31
|
+
state.traceEnabled = false;
|
|
84
32
|
}
|
|
85
|
-
syncGlobalFlags();
|
|
86
33
|
}
|
|
87
34
|
|
|
88
35
|
/**
|
|
@@ -104,7 +51,7 @@ export function isTraceEnabled(): boolean {
|
|
|
104
51
|
}
|
|
105
52
|
|
|
106
53
|
export function isDevtoolsEnabled(): boolean {
|
|
107
|
-
return
|
|
54
|
+
return false;
|
|
108
55
|
}
|
|
109
56
|
|
|
110
57
|
/**
|
|
@@ -116,7 +63,6 @@ export function isDevtoolsEnabled(): boolean {
|
|
|
116
63
|
export function setDebugEnabled(enabled: boolean): void {
|
|
117
64
|
state.enabled = enabled;
|
|
118
65
|
state.runtimeOverride = enabled;
|
|
119
|
-
syncGlobalFlags();
|
|
120
66
|
}
|
|
121
67
|
|
|
122
68
|
/**
|
|
@@ -128,7 +74,6 @@ export function setDebugEnabled(enabled: boolean): void {
|
|
|
128
74
|
export function setTraceEnabled(enabled: boolean): void {
|
|
129
75
|
state.traceEnabled = enabled;
|
|
130
76
|
state.runtimeTraceOverride = enabled;
|
|
131
|
-
syncGlobalFlags();
|
|
132
77
|
}
|
|
133
78
|
|
|
134
79
|
/**
|
|
@@ -137,9 +82,8 @@ export function setTraceEnabled(enabled: boolean): void {
|
|
|
137
82
|
export function resetDebugState(): void {
|
|
138
83
|
state.runtimeOverride = null;
|
|
139
84
|
state.runtimeTraceOverride = null;
|
|
140
|
-
state.enabled =
|
|
141
|
-
state.traceEnabled =
|
|
142
|
-
syncGlobalFlags();
|
|
85
|
+
state.enabled = false;
|
|
86
|
+
state.traceEnabled = false;
|
|
143
87
|
}
|
|
144
88
|
|
|
145
89
|
/**
|
|
@@ -3,11 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { isDebugEnabled } from './state.ts';
|
|
5
5
|
|
|
6
|
-
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
7
|
-
|
|
8
6
|
function isDumpEnabled(): boolean {
|
|
9
|
-
const explicit = process.env.OTTO_DEBUG_DUMP;
|
|
10
|
-
if (explicit) return TRUTHY.has(explicit.trim().toLowerCase());
|
|
11
7
|
return isDebugEnabled();
|
|
12
8
|
}
|
|
13
9
|
|
|
@@ -5,7 +5,6 @@ import { streamText } from 'ai';
|
|
|
5
5
|
import { resolveModel } from '../provider/index.ts';
|
|
6
6
|
import { getAuth } from '@ottocode/sdk';
|
|
7
7
|
import { loadConfig } from '@ottocode/sdk';
|
|
8
|
-
import { debugLog } from '../debug/index.ts';
|
|
9
8
|
import { getModelLimits } from './compaction-limits.ts';
|
|
10
9
|
import { buildCompactionContext } from './compaction-context.ts';
|
|
11
10
|
import { getCompactionSystemPrompt } from './compaction-detect.ts';
|
|
@@ -29,16 +28,11 @@ export async function performAutoCompaction(
|
|
|
29
28
|
error?: string;
|
|
30
29
|
compactMessageId?: string;
|
|
31
30
|
}> {
|
|
32
|
-
debugLog(`[compaction] Starting auto-compaction for session ${sessionId}`);
|
|
33
|
-
|
|
34
31
|
try {
|
|
35
32
|
const limits = getModelLimits(provider, modelId);
|
|
36
33
|
const contextTokenLimit = limits
|
|
37
34
|
? Math.max(Math.floor(limits.context * 0.5), 15000)
|
|
38
35
|
: 15000;
|
|
39
|
-
debugLog(
|
|
40
|
-
`[compaction] Model ${modelId} context limit: ${limits?.context ?? 'unknown'}, using ${contextTokenLimit} tokens for compaction`,
|
|
41
|
-
);
|
|
42
36
|
|
|
43
37
|
const context = await buildCompactionContext(
|
|
44
38
|
db,
|
|
@@ -46,14 +40,10 @@ export async function performAutoCompaction(
|
|
|
46
40
|
contextTokenLimit,
|
|
47
41
|
);
|
|
48
42
|
if (!context || context.length < 100) {
|
|
49
|
-
debugLog('[compaction] Not enough context to compact');
|
|
50
43
|
return { success: false, error: 'Not enough context to compact' };
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
const cfg = await loadConfig();
|
|
54
|
-
debugLog(
|
|
55
|
-
`[compaction] Using session model ${provider}/${modelId} for auto-compaction`,
|
|
56
|
-
);
|
|
57
47
|
|
|
58
48
|
const auth = await getAuth(
|
|
59
49
|
provider as Parameters<typeof getAuth>[0],
|
|
@@ -61,10 +51,6 @@ export async function performAutoCompaction(
|
|
|
61
51
|
);
|
|
62
52
|
const oauth = detectOAuth(provider, auth);
|
|
63
53
|
|
|
64
|
-
debugLog(
|
|
65
|
-
`[compaction] OAuth: needsSpoof=${oauth.needsSpoof}, isOpenAIOAuth=${oauth.isOpenAIOAuth}`,
|
|
66
|
-
);
|
|
67
|
-
|
|
68
54
|
const model = await resolveModel(
|
|
69
55
|
provider as Parameters<typeof resolveModel>[0],
|
|
70
56
|
modelId,
|
|
@@ -130,25 +116,19 @@ export async function performAutoCompaction(
|
|
|
130
116
|
.where(eq(messageParts.id, compactPartId));
|
|
131
117
|
|
|
132
118
|
if (!summary || summary.length < 50) {
|
|
133
|
-
debugLog('[compaction] Failed to generate summary');
|
|
134
119
|
return { success: false, error: 'Failed to generate summary' };
|
|
135
120
|
}
|
|
136
121
|
|
|
137
|
-
debugLog(`[compaction] Generated summary: ${summary.slice(0, 100)}...`);
|
|
138
|
-
|
|
139
122
|
const compactResult = await markSessionCompacted(
|
|
140
123
|
db,
|
|
141
124
|
sessionId,
|
|
142
125
|
assistantMessageId,
|
|
143
126
|
);
|
|
144
|
-
|
|
145
|
-
`[compaction] Marked ${compactResult.compacted} parts as compacted, saved ~${compactResult.saved} tokens`,
|
|
146
|
-
);
|
|
127
|
+
void compactResult;
|
|
147
128
|
|
|
148
129
|
return { success: true, summary, compactMessageId: assistantMessageId };
|
|
149
130
|
} catch (err) {
|
|
150
131
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
151
|
-
debugLog(`[compaction] Auto-compaction failed: ${errorMsg}`);
|
|
152
132
|
return { success: false, error: errorMsg };
|
|
153
133
|
}
|
|
154
134
|
}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import type { getDb } from '@ottocode/database';
|
|
2
2
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
3
3
|
import { eq, asc, and, lt } from 'drizzle-orm';
|
|
4
|
-
import { debugLog } from '../debug/index.ts';
|
|
5
4
|
import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
|
|
6
5
|
|
|
7
6
|
const PROTECTED_TOOLS = ['skill'];
|
|
8
7
|
|
|
8
|
+
type PartInfo = {
|
|
9
|
+
id: string;
|
|
10
|
+
tokens: number;
|
|
11
|
+
toolCallId: string | null;
|
|
12
|
+
type: 'tool_call' | 'tool_result';
|
|
13
|
+
index: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type CompactUnit = {
|
|
17
|
+
partIds: string[];
|
|
18
|
+
tokens: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
9
21
|
export async function markSessionCompacted(
|
|
10
22
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
11
23
|
sessionId: string,
|
|
12
24
|
compactMessageId: string,
|
|
13
25
|
): Promise<{ compacted: number; saved: number }> {
|
|
14
|
-
debugLog(`[compaction] Marking session ${sessionId} as compacted`);
|
|
15
|
-
|
|
16
26
|
const compactMsg = await db
|
|
17
27
|
.select()
|
|
18
28
|
.from(messages)
|
|
@@ -20,7 +30,6 @@ export async function markSessionCompacted(
|
|
|
20
30
|
.limit(1);
|
|
21
31
|
|
|
22
32
|
if (!compactMsg.length) {
|
|
23
|
-
debugLog('[compaction] Compact message not found');
|
|
24
33
|
return { compacted: 0, saved: 0 };
|
|
25
34
|
}
|
|
26
35
|
|
|
@@ -37,8 +46,7 @@ export async function markSessionCompacted(
|
|
|
37
46
|
)
|
|
38
47
|
.orderBy(asc(messages.createdAt));
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
const allToolParts: PartInfo[] = [];
|
|
49
|
+
const allCompactUnits: CompactUnit[] = [];
|
|
42
50
|
let totalToolTokens = 0;
|
|
43
51
|
|
|
44
52
|
for (const msg of oldMessages) {
|
|
@@ -48,6 +56,8 @@ export async function markSessionCompacted(
|
|
|
48
56
|
.where(eq(messageParts.messageId, msg.id))
|
|
49
57
|
.orderBy(asc(messageParts.index));
|
|
50
58
|
|
|
59
|
+
const eligibleParts: PartInfo[] = [];
|
|
60
|
+
|
|
51
61
|
for (const part of parts) {
|
|
52
62
|
if (part.type !== 'tool_call' && part.type !== 'tool_result') continue;
|
|
53
63
|
if (part.toolName && PROTECTED_TOOLS.includes(part.toolName)) continue;
|
|
@@ -69,43 +79,81 @@ export async function markSessionCompacted(
|
|
|
69
79
|
|
|
70
80
|
const tokens = estimateTokens(contentStr);
|
|
71
81
|
totalToolTokens += tokens;
|
|
72
|
-
|
|
82
|
+
eligibleParts.push({
|
|
83
|
+
id: part.id,
|
|
84
|
+
tokens,
|
|
85
|
+
toolCallId: part.toolCallId,
|
|
86
|
+
type: part.type,
|
|
87
|
+
index: part.index,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const pairedCallIds = new Set<string>();
|
|
92
|
+
const callsById = new Map<string, PartInfo[]>();
|
|
93
|
+
const resultsById = new Map<string, PartInfo[]>();
|
|
94
|
+
|
|
95
|
+
for (const part of eligibleParts) {
|
|
96
|
+
if (!part.toolCallId) continue;
|
|
97
|
+
const bucket = part.type === 'tool_call' ? callsById : resultsById;
|
|
98
|
+
const items = bucket.get(part.toolCallId) ?? [];
|
|
99
|
+
items.push(part);
|
|
100
|
+
bucket.set(part.toolCallId, items);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [toolCallId, callParts] of callsById) {
|
|
104
|
+
const resultParts = resultsById.get(toolCallId);
|
|
105
|
+
if (!resultParts?.length) continue;
|
|
106
|
+
|
|
107
|
+
const pairParts = [...callParts, ...resultParts].sort(
|
|
108
|
+
(a, b) => a.index - b.index,
|
|
109
|
+
);
|
|
110
|
+
pairedCallIds.add(toolCallId);
|
|
111
|
+
allCompactUnits.push({
|
|
112
|
+
partIds: pairParts.map((part) => part.id),
|
|
113
|
+
tokens: pairParts.reduce((sum, part) => sum + part.tokens, 0),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const part of eligibleParts) {
|
|
118
|
+
if (part.toolCallId && pairedCallIds.has(part.toolCallId)) continue;
|
|
119
|
+
allCompactUnits.push({
|
|
120
|
+
partIds: [part.id],
|
|
121
|
+
tokens: part.tokens,
|
|
122
|
+
});
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
125
|
|
|
76
126
|
const tokensToFree = Math.max(0, totalToolTokens - PRUNE_PROTECT);
|
|
77
127
|
|
|
78
|
-
const toCompact:
|
|
128
|
+
const toCompact: CompactUnit[] = [];
|
|
79
129
|
let freedTokens = 0;
|
|
80
130
|
|
|
81
|
-
for (const
|
|
131
|
+
for (const unit of allCompactUnits) {
|
|
82
132
|
if (freedTokens >= tokensToFree) break;
|
|
83
|
-
freedTokens +=
|
|
84
|
-
toCompact.push(
|
|
133
|
+
freedTokens += unit.tokens;
|
|
134
|
+
toCompact.push(unit);
|
|
85
135
|
}
|
|
86
136
|
|
|
87
|
-
debugLog(
|
|
88
|
-
`[compaction] Found ${toCompact.length} parts to compact (oldest first), saving ~${freedTokens} tokens`,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
137
|
if (toCompact.length > 0) {
|
|
92
138
|
const compactedAt = Date.now();
|
|
93
139
|
|
|
94
|
-
for (const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`[compaction] Failed to mark part ${part.id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
103
|
-
);
|
|
140
|
+
for (const unit of toCompact) {
|
|
141
|
+
for (const partId of unit.partIds) {
|
|
142
|
+
try {
|
|
143
|
+
await db
|
|
144
|
+
.update(messageParts)
|
|
145
|
+
.set({ compactedAt })
|
|
146
|
+
.where(eq(messageParts.id, partId));
|
|
147
|
+
} catch {}
|
|
104
148
|
}
|
|
105
149
|
}
|
|
106
150
|
|
|
107
|
-
|
|
151
|
+
const compactedParts = toCompact.reduce(
|
|
152
|
+
(sum, unit) => sum + unit.partIds.length,
|
|
153
|
+
0,
|
|
154
|
+
);
|
|
155
|
+
return { compacted: compactedParts, saved: freedTokens };
|
|
108
156
|
}
|
|
109
157
|
|
|
110
|
-
return { compacted:
|
|
158
|
+
return { compacted: 0, saved: freedTokens };
|
|
111
159
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { getDb } from '@ottocode/database';
|
|
2
2
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
3
3
|
import { eq, desc } from 'drizzle-orm';
|
|
4
|
-
import { debugLog } from '../debug/index.ts';
|
|
5
4
|
import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
|
|
6
5
|
|
|
7
6
|
const PROTECTED_TOOLS = ['skill'];
|
|
@@ -10,8 +9,6 @@ export async function pruneSession(
|
|
|
10
9
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
11
10
|
sessionId: string,
|
|
12
11
|
): Promise<{ pruned: number; saved: number }> {
|
|
13
|
-
debugLog(`[compaction] Auto-pruning session ${sessionId}`);
|
|
14
|
-
|
|
15
12
|
const allMessages = await db
|
|
16
13
|
.select()
|
|
17
14
|
.from(messages)
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
import type { getDb } from '@ottocode/database';
|
|
9
9
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
10
10
|
import { eq, asc } from 'drizzle-orm';
|
|
11
|
-
import { debugLog } from '../debug/index.ts';
|
|
12
11
|
import { ToolHistoryTracker } from './tool-history-tracker.ts';
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -43,15 +42,8 @@ export async function buildHistoryMessages(
|
|
|
43
42
|
m.status !== 'error'
|
|
44
43
|
) {
|
|
45
44
|
if (parts.length === 0) {
|
|
46
|
-
debugLog(
|
|
47
|
-
`[buildHistoryMessages] Skipping empty assistant message ${m.id} with status ${m.status}`,
|
|
48
|
-
);
|
|
49
45
|
continue;
|
|
50
46
|
}
|
|
51
|
-
|
|
52
|
-
debugLog(
|
|
53
|
-
`[buildHistoryMessages] Including non-complete assistant message ${m.id} (status: ${m.status}) with ${parts.length} parts to preserve context`,
|
|
54
|
-
);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
if (m.role === 'user') {
|
|
@@ -179,9 +171,6 @@ export async function buildHistoryMessages(
|
|
|
179
171
|
let result = toolResultsById.get(obj.callId);
|
|
180
172
|
|
|
181
173
|
if (!result) {
|
|
182
|
-
debugLog(
|
|
183
|
-
`[buildHistoryMessages] Synthesizing error result for incomplete tool call ${obj.name}#${obj.callId}`,
|
|
184
|
-
);
|
|
185
174
|
result = {
|
|
186
175
|
name: obj.name,
|
|
187
176
|
callId: obj.callId,
|
|
@@ -265,16 +254,6 @@ async function _logPendingToolParts(
|
|
|
265
254
|
}
|
|
266
255
|
} catch {}
|
|
267
256
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
`[buildHistoryMessages] Pending tool calls for assistant message ${messageId}: ${pendingCalls.join(', ')}`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
} catch (err) {
|
|
274
|
-
debugLog(
|
|
275
|
-
`[buildHistoryMessages] Failed to inspect pending tool calls for ${messageId}: ${
|
|
276
|
-
err instanceof Error ? err.message : String(err)
|
|
277
|
-
}`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
257
|
+
void pendingCalls;
|
|
258
|
+
} catch {}
|
|
280
259
|
}
|