@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.235",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/sdk": "0.1.
|
|
53
|
-
"@ottocode/database": "0.1.
|
|
52
|
+
"@ottocode/sdk": "0.1.235",
|
|
53
|
+
"@ottocode/database": "0.1.235",
|
|
54
54
|
"drizzle-orm": "^0.44.5",
|
|
55
55
|
"hono": "^4.9.9",
|
|
56
56
|
"zod": "^4.3.6"
|
|
@@ -83,11 +83,7 @@ export async function discoverAllAgents(
|
|
|
83
83
|
agentSet.add(agentName);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
} catch
|
|
87
|
-
logger.debug('Failed to load agents.json', {
|
|
88
|
-
error: err instanceof Error ? err.message : String(err),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
86
|
+
} catch {}
|
|
91
87
|
|
|
92
88
|
try {
|
|
93
89
|
const localAgentsPath = join(projectRoot, '.otto', 'agents');
|
|
@@ -100,11 +96,7 @@ export async function discoverAllAgents(
|
|
|
100
96
|
}
|
|
101
97
|
}
|
|
102
98
|
}
|
|
103
|
-
} catch
|
|
104
|
-
logger.debug('Failed to read local agents directory', {
|
|
105
|
-
error: err instanceof Error ? err.message : String(err),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
99
|
+
} catch {}
|
|
108
100
|
|
|
109
101
|
try {
|
|
110
102
|
const globalAgentsPath = getGlobalAgentsDir();
|
|
@@ -117,11 +109,7 @@ export async function discoverAllAgents(
|
|
|
117
109
|
}
|
|
118
110
|
}
|
|
119
111
|
}
|
|
120
|
-
} catch
|
|
121
|
-
logger.debug('Failed to read global agents directory', {
|
|
122
|
-
error: err instanceof Error ? err.message : String(err),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
112
|
+
} catch {}
|
|
125
113
|
|
|
126
114
|
return Array.from(agentSet).sort();
|
|
127
115
|
}
|
package/src/routes/git/commit.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { sessions } from '@ottocode/database/schema';
|
|
|
10
10
|
import { gitCommitSchema, gitGenerateCommitMessageSchema } from './schemas.ts';
|
|
11
11
|
import { validateAndGetGitRoot, parseGitStatus } from './utils.ts';
|
|
12
12
|
import { resolveModel } from '../../runtime/provider/index.ts';
|
|
13
|
-
import { debugLog } from '../../runtime/debug/index.ts';
|
|
14
13
|
import {
|
|
15
14
|
detectOAuth,
|
|
16
15
|
adaptSimpleCall,
|
|
@@ -168,7 +167,6 @@ Commit message:`;
|
|
|
168
167
|
});
|
|
169
168
|
|
|
170
169
|
if (adapted.forceStream) {
|
|
171
|
-
debugLog('[COMMIT] Using streamText for OpenAI OAuth');
|
|
172
170
|
const result = streamText({
|
|
173
171
|
model,
|
|
174
172
|
system: adapted.system,
|
|
@@ -180,7 +178,6 @@ Commit message:`;
|
|
|
180
178
|
text += chunk;
|
|
181
179
|
}
|
|
182
180
|
const message = text.trim();
|
|
183
|
-
debugLog(`[COMMIT] OAuth result: "${message.slice(0, 80)}..."`);
|
|
184
181
|
return c.json({ status: 'ok', data: { message } });
|
|
185
182
|
}
|
|
186
183
|
|
package/src/routes/terminals.ts
CHANGED
|
@@ -18,9 +18,7 @@ export function registerTerminalsRoutes(
|
|
|
18
18
|
|
|
19
19
|
app.post('/v1/terminals', async (c) => {
|
|
20
20
|
try {
|
|
21
|
-
logger.debug('POST /v1/terminals called');
|
|
22
21
|
const body = await c.req.json();
|
|
23
|
-
logger.debug('Creating terminal request received', body);
|
|
24
22
|
const { command, args, purpose, cwd, title } = body;
|
|
25
23
|
|
|
26
24
|
if (!command || !purpose) {
|
|
@@ -36,13 +34,6 @@ export function registerTerminalsRoutes(
|
|
|
36
34
|
}
|
|
37
35
|
const resolvedCwd = cwd || process.cwd();
|
|
38
36
|
|
|
39
|
-
logger.debug('Creating terminal', {
|
|
40
|
-
command: resolvedCommand,
|
|
41
|
-
args,
|
|
42
|
-
purpose,
|
|
43
|
-
cwd: resolvedCwd,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
37
|
const terminal = terminalManager.create({
|
|
47
38
|
command: resolvedCommand,
|
|
48
39
|
args: args || [],
|
|
@@ -52,8 +43,6 @@ export function registerTerminalsRoutes(
|
|
|
52
43
|
title,
|
|
53
44
|
});
|
|
54
45
|
|
|
55
|
-
logger.debug('Terminal created successfully', { id: terminal.id });
|
|
56
|
-
|
|
57
46
|
return c.json({
|
|
58
47
|
terminalId: terminal.id,
|
|
59
48
|
pid: terminal.pid,
|
|
@@ -80,25 +69,18 @@ export function registerTerminalsRoutes(
|
|
|
80
69
|
|
|
81
70
|
const handleTerminalOutput = async (c: Context) => {
|
|
82
71
|
const id = c.req.param('id');
|
|
83
|
-
logger.debug('SSE client connecting to terminal', { id });
|
|
84
72
|
const terminal = terminalManager.get(id);
|
|
85
73
|
|
|
86
74
|
if (!terminal) {
|
|
87
|
-
logger.debug('SSE terminal not found', { id });
|
|
88
75
|
return c.json({ error: 'Terminal not found' }, 404);
|
|
89
76
|
}
|
|
90
77
|
|
|
91
78
|
const activeTerminal = terminal;
|
|
92
79
|
|
|
93
80
|
return streamSSE(c, async (stream) => {
|
|
94
|
-
logger.debug('SSE stream started for terminal', { id });
|
|
95
81
|
const skipHistory = c.req.query('skipHistory') === 'true';
|
|
96
82
|
if (!skipHistory) {
|
|
97
83
|
const history = activeTerminal.read();
|
|
98
|
-
logger.debug('SSE sending terminal history', {
|
|
99
|
-
id,
|
|
100
|
-
lines: history.length,
|
|
101
|
-
});
|
|
102
84
|
for (const line of history) {
|
|
103
85
|
await stream.write(
|
|
104
86
|
`data: ${JSON.stringify({ type: 'data', line })}\n\n`,
|
|
@@ -155,9 +137,6 @@ export function registerTerminalsRoutes(
|
|
|
155
137
|
}
|
|
156
138
|
|
|
157
139
|
function onAbort() {
|
|
158
|
-
logger.debug('SSE client disconnected from terminal', {
|
|
159
|
-
id: activeTerminal.id,
|
|
160
|
-
});
|
|
161
140
|
stream.close();
|
|
162
141
|
finish();
|
|
163
142
|
}
|
package/src/routes/tunnel.ts
CHANGED
|
@@ -47,8 +47,6 @@ export function registerTunnelRoutes(app: Hono) {
|
|
|
47
47
|
port = getServerPort() || 9100;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
logger.debug('Starting tunnel on port:', port);
|
|
51
|
-
|
|
52
50
|
// Kill any stale tunnel processes first
|
|
53
51
|
await killStaleTunnels();
|
|
54
52
|
|
|
@@ -60,7 +58,6 @@ export function registerTunnelRoutes(app: Hono) {
|
|
|
60
58
|
|
|
61
59
|
const url = await activeTunnel.start(port, (msg) => {
|
|
62
60
|
progressMessage = msg;
|
|
63
|
-
logger.debug('Tunnel progress', { message: msg });
|
|
64
61
|
});
|
|
65
62
|
|
|
66
63
|
tunnelUrl = url;
|
|
@@ -109,8 +106,6 @@ export function registerTunnelRoutes(app: Hono) {
|
|
|
109
106
|
tunnelError = null;
|
|
110
107
|
progressMessage = null;
|
|
111
108
|
|
|
112
|
-
logger.debug('External tunnel registered:', url);
|
|
113
|
-
|
|
114
109
|
return c.json({
|
|
115
110
|
ok: true,
|
|
116
111
|
url: tunnelUrl,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Tool } from 'ai';
|
|
2
|
-
import { debugLog } from '../debug/index.ts';
|
|
3
2
|
|
|
4
3
|
export interface MCPPrepareStepState {
|
|
5
4
|
mcpToolsRecord: Record<string, Tool>;
|
|
@@ -57,12 +56,7 @@ export function buildPrepareStep(state: MCPPrepareStepState) {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
const activeTools = [...state.baseToolNames, ...state.loadedMCPTools];
|
|
60
|
-
|
|
61
|
-
if (state.loadedMCPTools.size > 0) {
|
|
62
|
-
debugLog(
|
|
63
|
-
`[MCP prepareStep] step=${stepNumber}, active MCP tools: ${[...state.loadedMCPTools].join(', ')}`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
59
|
+
void stepNumber;
|
|
66
60
|
|
|
67
61
|
return { activeTools };
|
|
68
62
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getGlobalAgentsJsonPath, getGlobalAgentsDir } from '@ottocode/sdk';
|
|
2
|
-
import { debugLog } from '../debug/index.ts';
|
|
3
2
|
import type { ProviderName } from '@ottocode/sdk';
|
|
4
3
|
import { catalog } from '@ottocode/sdk';
|
|
5
4
|
// Embed default agent prompts; only user overrides read from disk.
|
|
@@ -334,13 +333,7 @@ export async function resolveAgentConfig(
|
|
|
334
333
|
const deduped = Array.from(new Set([...tools, ...baseToolSet]));
|
|
335
334
|
const provider = normalizeProvider(entry?.provider);
|
|
336
335
|
const model = normalizeModel(entry?.model);
|
|
337
|
-
|
|
338
|
-
debugLog(
|
|
339
|
-
`[agent] ${name} prompt summary: ${JSON.stringify({
|
|
340
|
-
length: prompt.length,
|
|
341
|
-
lines: prompt.split('\n').length,
|
|
342
|
-
})}`,
|
|
343
|
-
);
|
|
336
|
+
void promptSource;
|
|
344
337
|
return {
|
|
345
338
|
name,
|
|
346
339
|
prompt,
|
|
@@ -11,7 +11,7 @@ import { discoverProjectTools } from '@ottocode/sdk';
|
|
|
11
11
|
import type { Tool } from 'ai';
|
|
12
12
|
import { adaptTools } from '../../tools/adapter.ts';
|
|
13
13
|
import { buildDatabaseTools } from '../../tools/database/index.ts';
|
|
14
|
-
import {
|
|
14
|
+
import { time } from '../debug/index.ts';
|
|
15
15
|
import { isDevtoolsEnabled } from '../debug/state.ts';
|
|
16
16
|
import { buildHistoryMessages } from '../message/history-builder.ts';
|
|
17
17
|
import { getMaxOutputTokens } from '../utils/token.ts';
|
|
@@ -87,7 +87,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
87
87
|
const historyTimer = time('runner:buildHistory');
|
|
88
88
|
let history: Awaited<ReturnType<typeof buildHistoryMessages>>;
|
|
89
89
|
if (opts.isCompactCommand && opts.compactionContext) {
|
|
90
|
-
debugLog('[RUNNER] Using minimal history for /compact command');
|
|
91
90
|
history = [];
|
|
92
91
|
} else {
|
|
93
92
|
history = await buildHistoryMessages(
|
|
@@ -104,34 +103,14 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
104
103
|
.where(eq(sessions.id, opts.sessionId))
|
|
105
104
|
.limit(1);
|
|
106
105
|
const contextSummary = sessionRows[0]?.contextSummary ?? undefined;
|
|
107
|
-
if (contextSummary) {
|
|
108
|
-
debugLog(
|
|
109
|
-
`[RUNNER] Using context summary from compaction (${contextSummary.length} chars)`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
106
|
|
|
113
107
|
const isFirstMessage = !history.some((m) => m.role === 'assistant');
|
|
114
108
|
|
|
115
|
-
debugLog(`[RUNNER] isFirstMessage: ${isFirstMessage}`);
|
|
116
|
-
debugLog(`[RUNNER] userContext provided: ${opts.userContext ? 'YES' : 'NO'}`);
|
|
117
|
-
if (opts.userContext) {
|
|
118
|
-
debugLog(
|
|
119
|
-
`[RUNNER] userContext value: ${opts.userContext.substring(0, 100)}${opts.userContext.length > 100 ? '...' : ''}`,
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
109
|
const systemTimer = time('runner:composeSystemPrompt');
|
|
124
110
|
const { getAuth } = await import('@ottocode/sdk');
|
|
125
111
|
const auth = await getAuth(opts.provider, cfg.projectRoot);
|
|
126
112
|
const oauth = detectOAuth(opts.provider, auth);
|
|
127
113
|
|
|
128
|
-
debugLog(
|
|
129
|
-
`[RUNNER] needsSpoof (OAuth): ${oauth.needsSpoof}, isOpenAIOAuth: ${oauth.isOpenAIOAuth}`,
|
|
130
|
-
);
|
|
131
|
-
debugLog(
|
|
132
|
-
`[RUNNER] spoofPrompt: ${oauth.spoofPrompt ? `present (${opts.provider})` : 'none'}`,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
114
|
const composed = await composeSystemPrompt({
|
|
136
115
|
provider: opts.provider,
|
|
137
116
|
model: opts.model,
|
|
@@ -155,15 +134,8 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
155
134
|
const { system } = adapted;
|
|
156
135
|
const { systemComponents, additionalSystemMessages } = adapted;
|
|
157
136
|
systemTimer.end();
|
|
158
|
-
debugLog(
|
|
159
|
-
`[system] summary: ${JSON.stringify({
|
|
160
|
-
components: systemComponents,
|
|
161
|
-
length: system.length,
|
|
162
|
-
})}`,
|
|
163
|
-
);
|
|
164
137
|
|
|
165
138
|
if (opts.isCompactCommand && opts.compactionContext) {
|
|
166
|
-
debugLog('[RUNNER] Injecting compaction context for /compact command');
|
|
167
139
|
const compactPrompt = getCompactionSystemPrompt();
|
|
168
140
|
additionalSystemMessages.push({
|
|
169
141
|
role: 'system',
|
|
@@ -188,9 +160,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
188
160
|
for (const dt of dbTools) {
|
|
189
161
|
discovered.tools.push(dt);
|
|
190
162
|
}
|
|
191
|
-
debugLog(
|
|
192
|
-
`[tools] Added ${dbTools.length} database tools for research agent (parent: ${parentSessionId ?? 'none'})`,
|
|
193
|
-
);
|
|
194
163
|
}
|
|
195
164
|
|
|
196
165
|
toolsTimer.end({
|
|
@@ -200,12 +169,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
200
169
|
const gated = allTools.filter(
|
|
201
170
|
(tool) => allowedNames.has(tool.name) || tool.name === 'load_mcp_tools',
|
|
202
171
|
);
|
|
203
|
-
debugLog(
|
|
204
|
-
`[tools] ${gated.length} gated tools, ${Object.keys(mcpToolsRecord).length} lazy MCP tools`,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
debugLog(`[RUNNER] About to create model with provider: ${opts.provider}`);
|
|
208
|
-
debugLog(`[RUNNER] About to create model ID: ${opts.model}`);
|
|
209
172
|
|
|
210
173
|
const model = await resolveModel(opts.provider, opts.model, cfg, {
|
|
211
174
|
sessionId: opts.sessionId,
|
|
@@ -218,12 +181,8 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
218
181
|
middleware: devToolsMiddleware(),
|
|
219
182
|
})
|
|
220
183
|
: model;
|
|
221
|
-
debugLog(
|
|
222
|
-
`[RUNNER] Model created: ${JSON.stringify({ id: model.modelId, provider: model.provider })}`,
|
|
223
|
-
);
|
|
224
184
|
|
|
225
185
|
const maxOutputTokens = adapted.maxOutputTokens;
|
|
226
|
-
debugLog(`[RUNNER] maxOutputTokens for ${opts.model}: ${maxOutputTokens}`);
|
|
227
186
|
|
|
228
187
|
const { sharedCtx, firstToolTimer, firstToolSeen } = await setupToolContext(
|
|
229
188
|
opts,
|
|
@@ -253,12 +212,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
253
212
|
});
|
|
254
213
|
mergeProviderOptions(providerOptions, reasoningConfig.providerOptions);
|
|
255
214
|
effectiveMaxOutputTokens = reasoningConfig.effectiveMaxOutputTokens;
|
|
256
|
-
debugLog(
|
|
257
|
-
`[RUNNER] reasoning enabled for ${opts.provider}/${opts.model}: ${reasoningConfig.enabled}, level: ${opts.reasoningLevel ?? 'default'}`,
|
|
258
|
-
);
|
|
259
|
-
debugLog(
|
|
260
|
-
`[RUNNER] final providerOptions: ${JSON.stringify(providerOptions)}`,
|
|
261
|
-
);
|
|
262
215
|
|
|
263
216
|
return {
|
|
264
217
|
cfg,
|
|
@@ -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
|
}
|