@ottocode/server 0.1.234 → 0.1.236
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/openapi/paths/config.ts +94 -0
- package/src/routes/config/debug.ts +39 -0
- package/src/routes/config/index.ts +2 -0
- package/src/routes/config/utils.ts +3 -16
- 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 +73 -49
- 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 +22 -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 +23 -22
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { hasToolCall,
|
|
2
|
-
import {
|
|
1
|
+
import { hasToolCall, streamText } from 'ai';
|
|
2
|
+
import { messageParts } from '@ottocode/database/schema';
|
|
3
3
|
import { eq } from 'drizzle-orm';
|
|
4
4
|
import { publish, subscribe } from '../../events/bus.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { time } from '../debug/index.ts';
|
|
6
6
|
import { toErrorPayload } from '../errors/handling.ts';
|
|
7
7
|
import {
|
|
8
8
|
type RunOpts,
|
|
9
|
-
enqueueAssistantRun,
|
|
10
9
|
setRunning,
|
|
11
10
|
dequeueJob,
|
|
12
11
|
cleanupSession,
|
|
@@ -41,7 +40,6 @@ import {
|
|
|
41
40
|
createOauthCodexTextGuardState,
|
|
42
41
|
consumeOauthCodexTextDelta,
|
|
43
42
|
} from '../stream/text-guard.ts';
|
|
44
|
-
import { decideOauthCodexContinuation } from './oauth-codex-continuation.ts';
|
|
45
43
|
import { createTurnDumpCollector } from '../debug/turn-dump.ts';
|
|
46
44
|
|
|
47
45
|
export {
|
|
@@ -56,17 +54,9 @@ export {
|
|
|
56
54
|
const DEFAULT_TRACED_TOOL_INPUTS = new Set(['write', 'apply_patch']);
|
|
57
55
|
|
|
58
56
|
function shouldTraceToolInput(name: string): boolean {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (['1', 'true', 'yes', 'on', 'all'].includes(normalized)) {
|
|
63
|
-
return DEFAULT_TRACED_TOOL_INPUTS.has(name);
|
|
64
|
-
}
|
|
65
|
-
const tokens = raw
|
|
66
|
-
.split(/[\s,]+/)
|
|
67
|
-
.map((token) => token.trim().toLowerCase())
|
|
68
|
-
.filter(Boolean);
|
|
69
|
-
return tokens.includes('all') || tokens.includes(name.toLowerCase());
|
|
57
|
+
void DEFAULT_TRACED_TOOL_INPUTS;
|
|
58
|
+
void name;
|
|
59
|
+
return false;
|
|
70
60
|
}
|
|
71
61
|
|
|
72
62
|
function summarizeTraceValue(value: unknown, max = 160): string {
|
|
@@ -89,11 +79,7 @@ export async function runSessionLoop(sessionId: string) {
|
|
|
89
79
|
|
|
90
80
|
try {
|
|
91
81
|
await runAssistant(job);
|
|
92
|
-
} catch
|
|
93
|
-
debugLog(
|
|
94
|
-
`[RUNNER] runAssistant threw (swallowed to keep loop alive): ${_err instanceof Error ? _err.message : String(_err)}`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
82
|
+
} catch {}
|
|
97
83
|
}
|
|
98
84
|
|
|
99
85
|
setRunning(sessionId, false);
|
|
@@ -101,12 +87,6 @@ export async function runSessionLoop(sessionId: string) {
|
|
|
101
87
|
}
|
|
102
88
|
|
|
103
89
|
async function runAssistant(opts: RunOpts) {
|
|
104
|
-
const separator = '='.repeat(72);
|
|
105
|
-
debugLog(separator);
|
|
106
|
-
debugLog(
|
|
107
|
-
`[RUNNER] Starting turn for session ${opts.sessionId}, message ${opts.assistantMessageId}`,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
90
|
const setup = await setupRunner(opts);
|
|
111
91
|
const {
|
|
112
92
|
cfg,
|
|
@@ -203,10 +183,6 @@ async function runAssistant(opts: RunOpts) {
|
|
|
203
183
|
}
|
|
204
184
|
}
|
|
205
185
|
|
|
206
|
-
debugLog(
|
|
207
|
-
`[RUNNER] messagesWithSystemInstructions length: ${messagesWithSystemInstructions.length}`,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
186
|
const dump = createTurnDumpCollector({
|
|
211
187
|
sessionId: opts.sessionId,
|
|
212
188
|
messageId: opts.assistantMessageId,
|
|
@@ -282,11 +258,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
282
258
|
try {
|
|
283
259
|
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
284
260
|
if (name === 'finish') _finishObserved = true;
|
|
285
|
-
} catch
|
|
286
|
-
debugLog(
|
|
287
|
-
`[RUNNER] finish observer error: ${err instanceof Error ? err.message : String(err)}`,
|
|
288
|
-
);
|
|
289
|
-
}
|
|
261
|
+
} catch {}
|
|
290
262
|
}
|
|
291
263
|
});
|
|
292
264
|
|
|
@@ -364,10 +336,9 @@ async function runAssistant(opts: RunOpts) {
|
|
|
364
336
|
const onFinish = createFinishHandler(opts, db, completeAssistantMessage);
|
|
365
337
|
const isCopilotResponsesApi =
|
|
366
338
|
opts.provider === 'copilot' && !opts.model.startsWith('gpt-5-mini');
|
|
367
|
-
const stopWhenCondition =
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
: hasToolCall('finish');
|
|
339
|
+
const stopWhenCondition = isCopilotResponsesApi
|
|
340
|
+
? undefined
|
|
341
|
+
: hasToolCall('finish');
|
|
371
342
|
|
|
372
343
|
try {
|
|
373
344
|
const result = streamText({
|
|
@@ -401,29 +372,19 @@ async function runAssistant(opts: RunOpts) {
|
|
|
401
372
|
if (part.type === 'tool-input-start') {
|
|
402
373
|
if (shouldTraceToolInput(part.toolName)) {
|
|
403
374
|
tracedToolInputNamesById.set(part.id, part.toolName);
|
|
404
|
-
debugLog(
|
|
405
|
-
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-start tool=${part.toolName} callId=${part.id}`,
|
|
406
|
-
);
|
|
407
375
|
}
|
|
408
376
|
continue;
|
|
409
377
|
}
|
|
410
378
|
|
|
411
379
|
if (part.type === 'tool-input-delta') {
|
|
412
380
|
const toolName = tracedToolInputNamesById.get(part.id);
|
|
413
|
-
if (toolName)
|
|
414
|
-
debugLog(
|
|
415
|
-
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-delta tool=${toolName} callId=${part.id} delta=${summarizeTraceValue(part.delta)}`,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
381
|
+
if (toolName) void summarizeTraceValue(part.delta);
|
|
418
382
|
continue;
|
|
419
383
|
}
|
|
420
384
|
|
|
421
385
|
if (part.type === 'tool-input-end') {
|
|
422
386
|
const toolName = tracedToolInputNamesById.get(part.id);
|
|
423
387
|
if (toolName) {
|
|
424
|
-
debugLog(
|
|
425
|
-
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-end tool=${toolName} callId=${part.id}`,
|
|
426
|
-
);
|
|
427
388
|
tracedToolInputNamesById.delete(part.id);
|
|
428
389
|
}
|
|
429
390
|
continue;
|
|
@@ -432,18 +393,14 @@ async function runAssistant(opts: RunOpts) {
|
|
|
432
393
|
if (part.type === 'tool-call') {
|
|
433
394
|
if (shouldTraceToolInput(part.toolName)) {
|
|
434
395
|
tracedToolInputNamesById.delete(part.toolCallId);
|
|
435
|
-
|
|
436
|
-
`[TOOL_INPUT_TRACE][runner] fullStream tool-call tool=${part.toolName} callId=${part.toolCallId} input=${summarizeTraceValue(part.input)}`,
|
|
437
|
-
);
|
|
396
|
+
void summarizeTraceValue(part.input);
|
|
438
397
|
}
|
|
439
398
|
continue;
|
|
440
399
|
}
|
|
441
400
|
|
|
442
401
|
if (part.type === 'tool-result') {
|
|
443
402
|
if (shouldTraceToolInput(part.toolName)) {
|
|
444
|
-
|
|
445
|
-
`[TOOL_INPUT_TRACE][runner] fullStream tool-result tool=${part.toolName} callId=${part.toolCallId} output=${summarizeTraceValue(part.output)}`,
|
|
446
|
-
);
|
|
403
|
+
void summarizeTraceValue(part.output);
|
|
447
404
|
}
|
|
448
405
|
continue;
|
|
449
406
|
}
|
|
@@ -550,11 +507,6 @@ async function runAssistant(opts: RunOpts) {
|
|
|
550
507
|
}
|
|
551
508
|
|
|
552
509
|
const fs = firstToolSeen();
|
|
553
|
-
if (oauthTextGuard?.dropped) {
|
|
554
|
-
debugLog(
|
|
555
|
-
'[RUNNER] Dropped pseudo tool-call text leaked by OpenAI OAuth stream',
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
510
|
if (!fs && !_finishObserved) {
|
|
559
511
|
publish({
|
|
560
512
|
type: 'finish-step',
|
|
@@ -581,10 +533,6 @@ async function runAssistant(opts: RunOpts) {
|
|
|
581
533
|
streamRawFinishReason = undefined;
|
|
582
534
|
}
|
|
583
535
|
|
|
584
|
-
debugLog(
|
|
585
|
-
`[RUNNER] Stream finished. finishSeen=${_finishObserved}, firstToolSeen=${fs}, trailingAssistantTextAfterTool=${_trailingAssistantTextAfterTool}, finishReason=${streamFinishReason}, rawFinishReason=${streamRawFinishReason}`,
|
|
586
|
-
);
|
|
587
|
-
|
|
588
536
|
if (dump) {
|
|
589
537
|
const finalTextSnapshot = latestAssistantText || accumulated;
|
|
590
538
|
if (finalTextSnapshot.length > 0) {
|
|
@@ -601,98 +549,6 @@ async function runAssistant(opts: RunOpts) {
|
|
|
601
549
|
aborted: _abortedByUser,
|
|
602
550
|
});
|
|
603
551
|
}
|
|
604
|
-
|
|
605
|
-
const MAX_CONTINUATIONS = 6;
|
|
606
|
-
const continuationCount = opts.continuationCount ?? 0;
|
|
607
|
-
const continuationDecision = decideOauthCodexContinuation({
|
|
608
|
-
provider: opts.provider,
|
|
609
|
-
isOpenAIOAuth,
|
|
610
|
-
finishObserved: _finishObserved,
|
|
611
|
-
abortedByUser: _abortedByUser,
|
|
612
|
-
continuationCount,
|
|
613
|
-
maxContinuations: MAX_CONTINUATIONS,
|
|
614
|
-
finishReason: streamFinishReason,
|
|
615
|
-
rawFinishReason: streamRawFinishReason,
|
|
616
|
-
firstToolSeen: fs,
|
|
617
|
-
hasTrailingAssistantText: _trailingAssistantTextAfterTool,
|
|
618
|
-
endedWithToolActivity: _endedWithToolActivity,
|
|
619
|
-
lastToolName: _lastToolName,
|
|
620
|
-
droppedPseudoToolText: oauthTextGuard?.dropped ?? false,
|
|
621
|
-
lastAssistantText: latestAssistantText,
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
if (continuationDecision.shouldContinue) {
|
|
625
|
-
const sessRows = await db
|
|
626
|
-
.select()
|
|
627
|
-
.from(sessions)
|
|
628
|
-
.where(eq(sessions.id, opts.sessionId))
|
|
629
|
-
.limit(1);
|
|
630
|
-
const sessionInputTokens = Number(sessRows[0]?.totalInputTokens ?? 0);
|
|
631
|
-
const MAX_SESSION_INPUT_TOKENS = 800_000;
|
|
632
|
-
if (sessionInputTokens > MAX_SESSION_INPUT_TOKENS) {
|
|
633
|
-
debugLog(
|
|
634
|
-
`[RUNNER] Token budget exceeded (${sessionInputTokens} > ${MAX_SESSION_INPUT_TOKENS}), stopping continuation.`,
|
|
635
|
-
);
|
|
636
|
-
} else {
|
|
637
|
-
debugLog(
|
|
638
|
-
`[RUNNER] WARNING: Stream ended without finish. reason=${continuationDecision.reason ?? 'unknown'}, finishReason=${streamFinishReason}, rawFinishReason=${streamRawFinishReason}, firstToolSeen=${fs}. Auto-continuing.`,
|
|
639
|
-
);
|
|
640
|
-
|
|
641
|
-
debugLog(
|
|
642
|
-
`[RUNNER] Auto-continuing (${continuationCount + 1}/${MAX_CONTINUATIONS})...`,
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
try {
|
|
646
|
-
await completeAssistantMessage({}, opts, db);
|
|
647
|
-
} catch (err) {
|
|
648
|
-
debugLog(
|
|
649
|
-
`[RUNNER] completeAssistantMessage failed before continuation: ${err instanceof Error ? err.message : String(err)}`,
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const continuationMessageId = crypto.randomUUID();
|
|
654
|
-
await db.insert(messages).values({
|
|
655
|
-
id: continuationMessageId,
|
|
656
|
-
sessionId: opts.sessionId,
|
|
657
|
-
role: 'assistant',
|
|
658
|
-
status: 'pending',
|
|
659
|
-
agent: opts.agent,
|
|
660
|
-
provider: opts.provider,
|
|
661
|
-
model: opts.model,
|
|
662
|
-
createdAt: Date.now(),
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
publish({
|
|
666
|
-
type: 'message.created',
|
|
667
|
-
sessionId: opts.sessionId,
|
|
668
|
-
payload: {
|
|
669
|
-
id: continuationMessageId,
|
|
670
|
-
role: 'assistant',
|
|
671
|
-
agent: opts.agent,
|
|
672
|
-
provider: opts.provider,
|
|
673
|
-
model: opts.model,
|
|
674
|
-
},
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
enqueueAssistantRun(
|
|
678
|
-
{
|
|
679
|
-
...opts,
|
|
680
|
-
assistantMessageId: continuationMessageId,
|
|
681
|
-
continuationCount: continuationCount + 1,
|
|
682
|
-
},
|
|
683
|
-
runSessionLoop,
|
|
684
|
-
);
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (
|
|
689
|
-
continuationDecision.reason === 'max-continuations-reached' &&
|
|
690
|
-
!_finishObserved
|
|
691
|
-
) {
|
|
692
|
-
debugLog(
|
|
693
|
-
`[RUNNER] Max continuations (${MAX_CONTINUATIONS}) reached, stopping.`,
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
552
|
} catch (err) {
|
|
697
553
|
unsubscribeFinish();
|
|
698
554
|
dump?.recordError(err);
|
|
@@ -715,17 +571,10 @@ async function runAssistant(opts: RunOpts) {
|
|
|
715
571
|
errorCode === 'context_length_exceeded' ||
|
|
716
572
|
apiErrorType === 'invalid_request_error';
|
|
717
573
|
|
|
718
|
-
debugLog(
|
|
719
|
-
`[RUNNER] isPromptTooLong: ${isPromptTooLong}, isCompactCommand: ${opts.isCompactCommand}`,
|
|
720
|
-
);
|
|
721
|
-
|
|
722
574
|
if (isPromptTooLong && !opts.isCompactCommand) {
|
|
723
|
-
debugLog('[RUNNER] Prompt too long - auto-compacting');
|
|
724
575
|
try {
|
|
725
576
|
const pruneResult = await pruneSession(db, opts.sessionId);
|
|
726
|
-
|
|
727
|
-
`[RUNNER] Auto-pruned ${pruneResult.pruned} parts, saved ~${pruneResult.saved} tokens`,
|
|
728
|
-
);
|
|
577
|
+
void pruneResult;
|
|
729
578
|
|
|
730
579
|
publish({
|
|
731
580
|
type: 'error',
|
|
@@ -739,20 +588,10 @@ async function runAssistant(opts: RunOpts) {
|
|
|
739
588
|
|
|
740
589
|
try {
|
|
741
590
|
await completeAssistantMessage({}, opts, db);
|
|
742
|
-
} catch
|
|
743
|
-
debugLog(
|
|
744
|
-
`[RUNNER] completeAssistantMessage failed after prune: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
745
|
-
);
|
|
746
|
-
}
|
|
591
|
+
} catch {}
|
|
747
592
|
return;
|
|
748
|
-
} catch
|
|
749
|
-
debugLog(
|
|
750
|
-
`[RUNNER] Auto-prune failed: ${pruneErr instanceof Error ? pruneErr.message : String(pruneErr)}`,
|
|
751
|
-
);
|
|
752
|
-
}
|
|
593
|
+
} catch {}
|
|
753
594
|
}
|
|
754
|
-
|
|
755
|
-
debugLog(`[RUNNER] Error during stream: ${payload.message}`);
|
|
756
595
|
publish({
|
|
757
596
|
type: 'error',
|
|
758
597
|
sessionId: opts.sessionId,
|
|
@@ -773,26 +612,13 @@ async function runAssistant(opts: RunOpts) {
|
|
|
773
612
|
db,
|
|
774
613
|
);
|
|
775
614
|
await completeAssistantMessage({}, opts, db);
|
|
776
|
-
} catch
|
|
777
|
-
debugLog(
|
|
778
|
-
`[RUNNER] Failed to complete message after error: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
779
|
-
);
|
|
780
|
-
}
|
|
615
|
+
} catch {}
|
|
781
616
|
throw err;
|
|
782
617
|
} finally {
|
|
783
618
|
if (dump) {
|
|
784
619
|
try {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
} catch (dumpErr) {
|
|
788
|
-
debugLog(
|
|
789
|
-
`[RUNNER] Failed to write debug dump: ${dumpErr instanceof Error ? dumpErr.message : String(dumpErr)}`,
|
|
790
|
-
);
|
|
791
|
-
}
|
|
620
|
+
await dump.flush(cfg.projectRoot);
|
|
621
|
+
} catch {}
|
|
792
622
|
}
|
|
793
|
-
debugLog(
|
|
794
|
-
`[RUNNER] Turn complete for session ${opts.sessionId}, message ${opts.assistantMessageId}`,
|
|
795
|
-
);
|
|
796
|
-
debugLog(separator);
|
|
797
623
|
}
|
|
798
624
|
}
|
|
@@ -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
|
}
|