mixdog 0.9.36 → 0.9.38
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 -2
- package/scripts/abort-recovery-test.mjs +118 -0
- package/scripts/compact-smoke.mjs +7 -7
- package/scripts/dispatch-persist-recovery-test.mjs +141 -0
- package/scripts/explore-bench-tmp.mjs +19 -0
- package/scripts/explore-bench.mjs +101 -14
- package/scripts/explore-prompt-policy-test.mjs +31 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +11 -11
- package/scripts/notify-completion-mirror-test.mjs +73 -0
- package/scripts/openai-ws-early-settle-test.mjs +176 -0
- package/scripts/output-style-smoke.mjs +17 -17
- package/scripts/provider-toolcall-test.mjs +333 -14
- package/scripts/recall-bench-cases.json +3 -3
- package/scripts/recall-bench.mjs +1 -1
- package/scripts/recall-quality-cases.json +4 -4
- package/scripts/recall-usecase-cases.json +2 -2
- package/scripts/session-bench.mjs +13 -13
- package/scripts/session-sweep.mjs +266 -0
- package/scripts/steering-drain-buckets-test.mjs +72 -11
- package/scripts/submit-commandbusy-race-test.mjs +114 -0
- package/scripts/tool-smoke.mjs +83 -10
- package/src/app.mjs +2 -1
- package/src/defaults/cycle3-review-prompt.md +9 -0
- package/src/defaults/skills/setup/SKILL.md +93 -293
- package/src/lib/rules-builder.cjs +3 -2
- package/src/output-styles/default.md +2 -2
- package/src/output-styles/extreme-minimal.md +1 -1
- package/src/output-styles/minimal.md +1 -1
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/30-explorer.md +40 -14
- package/src/rules/lead/01-general.md +4 -0
- package/src/rules/shared/01-tool.md +7 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +15 -3
- package/src/runtime/agent/orchestrator/config.mjs +1 -1
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +31 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +13 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +35 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +27 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +36 -37
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +72 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +26 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -28
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +18 -15
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +119 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +113 -2
- package/src/runtime/agent/orchestrator/providers/registry.mjs +44 -5
- package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +78 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +57 -31
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +28 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +30 -55
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +24 -16
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +116 -21
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +55 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +35 -20
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +77 -31
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +25 -3
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +73 -25
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +17 -7
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +14 -12
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +5 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +56 -6
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -0
- package/src/runtime/channels/lib/worker-main.mjs +5 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +104 -0
- package/src/runtime/memory/lib/ko-morph.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +64 -4
- package/src/runtime/memory/lib/memory-text-utils.mjs +4 -4
- package/src/runtime/memory/lib/recall-format.mjs +3 -3
- package/src/runtime/shared/child-spawn-gate.mjs +15 -0
- package/src/runtime/shared/config.mjs +98 -12
- package/src/runtime/shared/process-listener-headroom.mjs +12 -0
- package/src/session-runtime/cwd-plugins.mjs +6 -3
- package/src/session-runtime/model-route-api.mjs +50 -2
- package/src/session-runtime/provider-auth-api.mjs +8 -1
- package/src/session-runtime/provider-models.mjs +3 -3
- package/src/session-runtime/resource-api.mjs +22 -0
- package/src/session-runtime/runtime-core.mjs +56 -10
- package/src/session-runtime/settings-api.mjs +11 -2
- package/src/session-runtime/warmup-schedulers.mjs +7 -1
- package/src/standalone/agent-tool.mjs +15 -9
- package/src/standalone/explore-tool.mjs +4 -1
- package/src/tui/App.jsx +6 -5
- package/src/tui/app/transcript-window.mjs +60 -10
- package/src/tui/app/use-transcript-window.mjs +2 -1
- package/src/tui/components/ContextPanel.jsx +4 -1
- package/src/tui/components/ToolExecution.jsx +15 -12
- package/src/tui/components/TranscriptItem.jsx +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +8 -8
- package/src/tui/dist/index.mjs +482 -151
- package/src/tui/engine/agent-job-feed.mjs +36 -6
- package/src/tui/engine/notification-plan.mjs +3 -3
- package/src/tui/engine/queue-helpers.mjs +8 -0
- package/src/tui/engine/render-timing.mjs +104 -8
- package/src/tui/engine/session-api.mjs +58 -3
- package/src/tui/engine/session-flow.mjs +93 -29
- package/src/tui/engine/tool-card-results.mjs +28 -13
- package/src/tui/engine/turn.mjs +238 -157
- package/src/tui/engine.mjs +17 -8
- package/src/tui/index.jsx +10 -1
- package/src/workflows/default/WORKFLOW.md +8 -4
- package/src/workflows/solo/WORKFLOW.md +8 -4
|
@@ -62,6 +62,8 @@ import {
|
|
|
62
62
|
} from '../src/runtime/agent/orchestrator/providers/anthropic-effort.mjs';
|
|
63
63
|
import { buildAnthropicBetaHeaders } from '../src/runtime/agent/orchestrator/providers/anthropic-betas.mjs';
|
|
64
64
|
import { PATCH_TOOL_DEFS } from '../src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs';
|
|
65
|
+
import { sendViaHttpSse } from '../src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs';
|
|
66
|
+
import { buildRequestBody as buildOpenAIOAuthRequestBody } from '../src/runtime/agent/orchestrator/providers/openai-oauth.mjs';
|
|
65
67
|
|
|
66
68
|
// --- Helpers ---------------------------------------------------------------
|
|
67
69
|
|
|
@@ -100,6 +102,33 @@ function compatResponsesEventStream(events) {
|
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
// Minimal 200-OK Response-like shape for the HTTP/SSE Responses path: frames
|
|
106
|
+
// each event as `event:<type>\ndata:<json>\n\n`, delivered synchronously so the
|
|
107
|
+
// semantic-idle watchdog never arms during the test.
|
|
108
|
+
function httpSseResponse(events) {
|
|
109
|
+
const encoder = new TextEncoder();
|
|
110
|
+
const chunks = events.map((e) => encoder.encode(`event: ${e.type}\ndata: ${JSON.stringify(e)}\n\n`));
|
|
111
|
+
let i = 0;
|
|
112
|
+
return {
|
|
113
|
+
status: 200,
|
|
114
|
+
ok: true,
|
|
115
|
+
headers: new Map(),
|
|
116
|
+
body: {
|
|
117
|
+
getReader() {
|
|
118
|
+
return {
|
|
119
|
+
read() {
|
|
120
|
+
return i < chunks.length
|
|
121
|
+
? Promise.resolve({ done: false, value: chunks[i++] })
|
|
122
|
+
: Promise.resolve({ done: true, value: undefined });
|
|
123
|
+
},
|
|
124
|
+
cancel() { return Promise.resolve(); },
|
|
125
|
+
releaseLock() {},
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
// === 1. openai-compat ======================================================
|
|
104
133
|
// Chat path: parseToolCalls(choice, label) openai-compat.mjs:957
|
|
105
134
|
// Responses path: parseResponsesToolCalls(response,...) openai-compat.mjs:972
|
|
@@ -220,6 +249,126 @@ test('openai-compat/xai Responses stream: response.completed salvages deferred f
|
|
|
220
249
|
assert.deepEqual(captured, [{ id: 'fc_1', name: 'read', arguments: { path: 'a' } }]);
|
|
221
250
|
});
|
|
222
251
|
|
|
252
|
+
// Missing-terminal + partial-tool gate (shared tool-stream-state.mjs tracker):
|
|
253
|
+
// a Responses stream that streams a CUSTOM tool call's input then truncates
|
|
254
|
+
// before response.output_item.done / response.completed. The call never lands
|
|
255
|
+
// in pendingCalls or toolCalls, so the truncation error must be gated as
|
|
256
|
+
// pendingToolUse via the shared active tool-item tracker (activeToolItems),
|
|
257
|
+
// NOT accepted as a text-only partial-final that would drop the in-flight tool.
|
|
258
|
+
test('openai-compat/xai Responses stream: mid custom-tool-input truncation is gated pendingToolUse via shared tracker', async () => {
|
|
259
|
+
const rejected = await consumeCompatResponsesStream(compatResponsesEventStream([
|
|
260
|
+
{ type: 'response.created', response: { id: 'resp_ct', model: 'grok' } },
|
|
261
|
+
{ type: 'response.output_text.delta', delta: 'partial ' },
|
|
262
|
+
{ type: 'response.output_item.added', item: { type: 'custom_tool_call', id: 'ct_item_1' } },
|
|
263
|
+
{ type: 'response.custom_tool_call_input.delta', item_id: 'ct_item_1', delta: '{"x":' },
|
|
264
|
+
// stream truncates here: no output_item.done, no response.completed.
|
|
265
|
+
]), {
|
|
266
|
+
label: 'test',
|
|
267
|
+
parseResponsesToolCalls: compatParseResponsesToolCalls,
|
|
268
|
+
responseOutputText: () => '',
|
|
269
|
+
onTextDelta: () => {},
|
|
270
|
+
}).then(() => null, (e) => e);
|
|
271
|
+
assert.ok(rejected, 'expected the truncated stream to reject');
|
|
272
|
+
assert.equal(rejected.streamStalled, true);
|
|
273
|
+
// The in-flight custom tool must gate partial-final success even though it
|
|
274
|
+
// never reached pendingCalls/toolCalls — the active tracker carries it.
|
|
275
|
+
assert.equal(rejected.pendingToolUse, true);
|
|
276
|
+
assert.equal(rejected.partialContent, 'partial ');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Precision half of the same tracker: once the tool call fully COMPLETES
|
|
280
|
+
// (output_item.done clears the active item) and text keeps streaming, a later
|
|
281
|
+
// truncation is a plain text-only partial — the tracker having been cleared is
|
|
282
|
+
// what lets pendingToolUse fall back to the real emit/pending state.
|
|
283
|
+
test('openai-compat/xai Responses stream: completed tool then trailing-text truncation clears active tracker', async () => {
|
|
284
|
+
let emitted = 0;
|
|
285
|
+
const rejected = await consumeCompatResponsesStream(compatResponsesEventStream([
|
|
286
|
+
{ type: 'response.created', response: { id: 'resp_done', model: 'grok' } },
|
|
287
|
+
{ type: 'response.output_item.added', item: { type: 'custom_tool_call', id: 'ct_done_1' } },
|
|
288
|
+
{ type: 'response.custom_tool_call_input.delta', item_id: 'ct_done_1', delta: '{"x":1}' },
|
|
289
|
+
{ type: 'response.output_item.done', item: { type: 'custom_tool_call', id: 'ct_done_1', name: 'load_tool', input: '{"x":1}' } },
|
|
290
|
+
{ type: 'response.output_text.delta', delta: 'after tool ' },
|
|
291
|
+
// truncates before response.completed.
|
|
292
|
+
]), {
|
|
293
|
+
label: 'test',
|
|
294
|
+
parseResponsesToolCalls: compatParseResponsesToolCalls,
|
|
295
|
+
responseOutputText: () => '',
|
|
296
|
+
onTextDelta: () => {},
|
|
297
|
+
onToolCall: () => { emitted += 1; },
|
|
298
|
+
}).then(() => null, (e) => e);
|
|
299
|
+
assert.ok(rejected, 'expected the truncated stream to reject');
|
|
300
|
+
assert.equal(rejected.streamStalled, true);
|
|
301
|
+
// A tool WAS emitted this turn, so the turn is still unsafe/tool-bearing —
|
|
302
|
+
// pendingToolUse stays true off the emit state, not a stale active latch.
|
|
303
|
+
assert.ok(emitted >= 1);
|
|
304
|
+
assert.equal(rejected.pendingToolUse, true);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Reviewer fix: function output_item.done must delete the pendingCalls itemId
|
|
308
|
+
// before recomputing toolInFlight — otherwise a fully-completed function call
|
|
309
|
+
// keeps pendingCalls.size > 0 forever, and a later max-output cutoff on trailing
|
|
310
|
+
// text is misclassified as a truncated tool-in-flight stall instead of a clean
|
|
311
|
+
// length completion.
|
|
312
|
+
test('openai-compat/xai Responses stream: completed function call clears pendingCalls so max-output cutoff is clean length', async () => {
|
|
313
|
+
const out = await consumeCompatResponsesStream(compatResponsesEventStream([
|
|
314
|
+
{ type: 'response.created', response: { id: 'resp_len', model: 'grok' } },
|
|
315
|
+
{ type: 'response.output_item.added', item: { type: 'function_call', id: 'fi_len', name: 'read', call_id: 'fc_len' } },
|
|
316
|
+
{ type: 'response.function_call_arguments.done', item_id: 'fi_len', arguments: '{"path":"a"}' },
|
|
317
|
+
{ type: 'response.output_item.done', item: { type: 'function_call', id: 'fi_len', call_id: 'fc_len', name: 'read', arguments: '{"path":"a"}' } },
|
|
318
|
+
{ type: 'response.incomplete', response: { incomplete_details: { reason: 'max_output_tokens' } } },
|
|
319
|
+
]), {
|
|
320
|
+
label: 'test',
|
|
321
|
+
parseResponsesToolCalls: compatParseResponsesToolCalls,
|
|
322
|
+
responseOutputText: () => '',
|
|
323
|
+
});
|
|
324
|
+
// The completed call drained pendingCalls + the active tracker, so the
|
|
325
|
+
// max-output cutoff is a clean length truncation (no in-flight-tool stall).
|
|
326
|
+
assert.equal(out.stopReason, 'length');
|
|
327
|
+
assert.deepEqual(out.toolCalls, [{ id: 'fc_len', name: 'read', arguments: { path: 'a' } }]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Reviewer fix (HTTP/SSE): a max_output_tokens cutoff while a function call is
|
|
331
|
+
// still in flight (added + partial args, no output_item.done) must NOT mark a
|
|
332
|
+
// clean length completion — the tool arguments were truncated. Mirror compat:
|
|
333
|
+
// throw a stream-stalled pendingToolUse error so the loop gates/retries.
|
|
334
|
+
test('openai-oauth HTTP/SSE Responses: max-output cutoff with function call in flight → stream-stalled pendingToolUse', async () => {
|
|
335
|
+
const rejected = await sendViaHttpSse({
|
|
336
|
+
auth: { type: 'openai-direct', apiKey: 'k' },
|
|
337
|
+
body: { model: 'gpt', tools: [] },
|
|
338
|
+
useModel: 'gpt',
|
|
339
|
+
fetchFn: async () => httpSseResponse([
|
|
340
|
+
{ type: 'response.created', response: { id: 'r', model: 'gpt' } },
|
|
341
|
+
{ type: 'response.output_item.added', item: { type: 'function_call', id: 'fi_1', name: 'read', call_id: 'fc_1' } },
|
|
342
|
+
{ type: 'response.function_call_arguments.delta', item_id: 'fi_1', delta: '{"path":' },
|
|
343
|
+
{ type: 'response.incomplete', response: { incomplete_details: { reason: 'max_output_tokens' } } },
|
|
344
|
+
]),
|
|
345
|
+
}).then(() => null, (e) => e);
|
|
346
|
+
assert.ok(rejected, 'expected the truncated-tool cutoff to reject');
|
|
347
|
+
assert.equal(rejected.streamStalled, true);
|
|
348
|
+
assert.equal(rejected.pendingToolUse, true);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Reviewer fix (HTTP/SSE): function output_item.done must delete pendingCalls[id]
|
|
352
|
+
// before recomputing _toolInFlight — otherwise the completed call keeps
|
|
353
|
+
// pendingCalls.size > 0 and a later max-output cutoff is misread as a truncated
|
|
354
|
+
// tool. A fully-completed call before the cutoff must be a clean length result.
|
|
355
|
+
test('openai-oauth HTTP/SSE Responses: completed function call clears pendingCalls so max-output cutoff is clean length', async () => {
|
|
356
|
+
const out = await sendViaHttpSse({
|
|
357
|
+
auth: { type: 'openai-direct', apiKey: 'k' },
|
|
358
|
+
body: { model: 'gpt', tools: [] },
|
|
359
|
+
useModel: 'gpt',
|
|
360
|
+
fetchFn: async () => httpSseResponse([
|
|
361
|
+
{ type: 'response.created', response: { id: 'r', model: 'gpt' } },
|
|
362
|
+
{ type: 'response.output_item.added', item: { type: 'function_call', id: 'fi_1', name: 'read', call_id: 'fc_1' } },
|
|
363
|
+
{ type: 'response.function_call_arguments.done', item_id: 'fi_1', arguments: '{"path":"a"}' },
|
|
364
|
+
{ type: 'response.output_item.done', item: { type: 'function_call', id: 'fi_1', call_id: 'fc_1', name: 'read', arguments: '{"path":"a"}' } },
|
|
365
|
+
{ type: 'response.incomplete', response: { incomplete_details: { reason: 'max_output_tokens' } } },
|
|
366
|
+
]),
|
|
367
|
+
});
|
|
368
|
+
assert.equal(out.stopReason, 'length');
|
|
369
|
+
assert.deepEqual(out.toolCalls, [{ id: 'fc_1', name: 'read', arguments: { path: 'a' } }]);
|
|
370
|
+
});
|
|
371
|
+
|
|
223
372
|
test('openai-compat/xai Responses: freeform apply_patch downgrades to function schema', () => {
|
|
224
373
|
const tools = _toResponsesToolsForTest(PATCH_TOOL_DEFS);
|
|
225
374
|
const patch = tools.find((tool) => tool.name === 'apply_patch');
|
|
@@ -229,6 +378,135 @@ test('openai-compat/xai Responses: freeform apply_patch downgrades to function s
|
|
|
229
378
|
assert.deepEqual(patch.parameters?.required, ['patch']);
|
|
230
379
|
});
|
|
231
380
|
|
|
381
|
+
test('openai-compat/xai Responses: load_tool downgrades from tool_search to function schema', () => {
|
|
382
|
+
const loadTool = {
|
|
383
|
+
name: 'load_tool',
|
|
384
|
+
description: 'load tools',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: { names: { type: 'array', items: { type: 'string' } } },
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
const [xaiTool] = _toResponsesToolsForTest([loadTool], { provider: 'xai' });
|
|
391
|
+
assert.equal(xaiTool.type, 'function');
|
|
392
|
+
assert.equal(xaiTool.name, 'load_tool');
|
|
393
|
+
assert.equal(xaiTool.execution, undefined);
|
|
394
|
+
assert.equal(xaiTool.parameters, loadTool.inputSchema);
|
|
395
|
+
|
|
396
|
+
const [openaiTool] = _toResponsesToolsForTest([loadTool], { provider: 'openai' });
|
|
397
|
+
assert.equal(openaiTool.type, 'tool_search');
|
|
398
|
+
assert.equal(openaiTool.execution, 'client');
|
|
399
|
+
assert.equal(openaiTool.name, undefined);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('openai-compat/xai Responses: load_tool history replays as function_call', () => {
|
|
403
|
+
const { input } = _toXaiResponsesInputForTest([
|
|
404
|
+
{ role: 'user', content: 'load a tool' },
|
|
405
|
+
{
|
|
406
|
+
role: 'assistant',
|
|
407
|
+
content: '',
|
|
408
|
+
toolCalls: [{ id: 'call_load_1', name: 'load_tool', arguments: { names: ['read'] }, nativeType: 'tool_search_call' }],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
role: 'tool',
|
|
412
|
+
toolCallId: 'call_load_1',
|
|
413
|
+
content: '{}',
|
|
414
|
+
nativeToolSearch: { openaiTools: [{ name: 'read' }] },
|
|
415
|
+
},
|
|
416
|
+
], {
|
|
417
|
+
xaiResponses: {
|
|
418
|
+
previousResponseId: 'resp_same',
|
|
419
|
+
seenMessageCount: 0,
|
|
420
|
+
model: 'grok-4.5',
|
|
421
|
+
},
|
|
422
|
+
}, { model: 'grok-4.5' });
|
|
423
|
+
assert.equal(input.some((item) => item.type === 'tool_search_call'), false);
|
|
424
|
+
assert.equal(input.some((item) => item.type === 'tool_search_output'), false);
|
|
425
|
+
const call = input.find((item) => item.type === 'function_call' && item.name === 'load_tool');
|
|
426
|
+
assert.equal(call.call_id, 'call_load_1');
|
|
427
|
+
assert.deepEqual(JSON.parse(call.arguments), { names: ['read'] });
|
|
428
|
+
assert.equal(input.some((item) => item.type === 'function_call_output' && item.call_id === 'call_load_1'), true);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('openai-compat/xai Responses: model switch drops prior tool transcript history', () => {
|
|
432
|
+
const { input, previousResponseId, continuationResetReason } = _toXaiResponsesInputForTest([
|
|
433
|
+
{ role: 'system', content: 'sys' },
|
|
434
|
+
{ role: 'user', content: 'before switch' },
|
|
435
|
+
{
|
|
436
|
+
role: 'assistant',
|
|
437
|
+
content: '',
|
|
438
|
+
toolCalls: [{ id: 'call_load_1', name: 'load_tool', arguments: { names: ['read'] }, nativeType: 'tool_search_call' }],
|
|
439
|
+
},
|
|
440
|
+
{ role: 'tool', toolCallId: 'call_load_1', content: '{"loaded":["read"]}' },
|
|
441
|
+
{ role: 'user', content: 'after switch' },
|
|
442
|
+
], {
|
|
443
|
+
xaiResponses: {
|
|
444
|
+
previousResponseId: 'resp_old',
|
|
445
|
+
seenMessageCount: 4,
|
|
446
|
+
model: 'grok-4.20',
|
|
447
|
+
},
|
|
448
|
+
}, { model: 'grok-4.5' });
|
|
449
|
+
assert.equal(previousResponseId, null);
|
|
450
|
+
assert.equal(continuationResetReason, 'model_changed');
|
|
451
|
+
const serialized = JSON.stringify(input);
|
|
452
|
+
assert.equal(serialized.includes('tool_search'), false);
|
|
453
|
+
assert.equal(serialized.includes('function_call'), false);
|
|
454
|
+
assert.equal(serialized.includes('function_call_output'), false);
|
|
455
|
+
assert.deepEqual(input.map((item) => item.role), ['system', 'user', 'user']);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('openai-compat/xai Responses: first Grok request after provider switch drops prior tool transcript history', () => {
|
|
459
|
+
const { input, previousResponseId, continuationResetReason } = _toXaiResponsesInputForTest([
|
|
460
|
+
{ role: 'system', content: 'sys' },
|
|
461
|
+
{ role: 'user', content: 'before switch' },
|
|
462
|
+
{
|
|
463
|
+
role: 'assistant',
|
|
464
|
+
content: '',
|
|
465
|
+
toolCalls: [{ id: 'call_patch_1', name: 'apply_patch', arguments: { patch: '*** Begin Patch\n*** End Patch\n' } }],
|
|
466
|
+
},
|
|
467
|
+
{ role: 'tool', toolCallId: 'call_patch_1', content: 'OK' },
|
|
468
|
+
{ role: 'user', content: 'after switch' },
|
|
469
|
+
], {}, { model: 'grok-4.5' });
|
|
470
|
+
assert.equal(previousResponseId, null);
|
|
471
|
+
assert.equal(continuationResetReason, null);
|
|
472
|
+
const serialized = JSON.stringify(input);
|
|
473
|
+
assert.equal(serialized.includes('function_call'), false);
|
|
474
|
+
assert.equal(serialized.includes('function_call_output'), false);
|
|
475
|
+
assert.deepEqual(input.map((item) => item.role), ['system', 'user', 'user']);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('openai-oauth Responses: load_tool schema and history stay function_call', () => {
|
|
479
|
+
const loadTool = {
|
|
480
|
+
name: 'load_tool',
|
|
481
|
+
description: 'load tools',
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: 'object',
|
|
484
|
+
properties: { names: { type: 'array', items: { type: 'string' } } },
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const body = buildOpenAIOAuthRequestBody([
|
|
488
|
+
{ role: 'system', content: 'sys' },
|
|
489
|
+
{ role: 'user', content: 'load a tool' },
|
|
490
|
+
{
|
|
491
|
+
role: 'assistant',
|
|
492
|
+
content: '',
|
|
493
|
+
toolCalls: [{ id: 'call_load_1', name: 'tool_search', arguments: { names: ['read'] }, nativeType: 'tool_search_call' }],
|
|
494
|
+
},
|
|
495
|
+
{ role: 'tool', toolCallId: 'call_load_1', content: '{}' },
|
|
496
|
+
], 'gpt-5.5', [loadTool], {});
|
|
497
|
+
assert.equal(JSON.stringify(body).includes('tool_search'), false);
|
|
498
|
+
assert.deepEqual(body.tools?.[0], {
|
|
499
|
+
type: 'function',
|
|
500
|
+
name: 'load_tool',
|
|
501
|
+
description: loadTool.description,
|
|
502
|
+
parameters: loadTool.inputSchema,
|
|
503
|
+
});
|
|
504
|
+
const call = body.input.find((item) => item.type === 'function_call' && item.name === 'load_tool');
|
|
505
|
+
assert.equal(call.call_id, 'call_load_1');
|
|
506
|
+
assert.deepEqual(JSON.parse(call.arguments), { names: ['read'] });
|
|
507
|
+
assert.equal(body.input.some((item) => item.type === 'function_call_output' && item.call_id === 'call_load_1'), true);
|
|
508
|
+
});
|
|
509
|
+
|
|
232
510
|
test('openai-compat/xai Responses: custom_tool_call history replays as function_call', () => {
|
|
233
511
|
const rawPatch = '*** Begin Patch\n*** Add File: xai-history.txt\n+ok\n*** End Patch\n';
|
|
234
512
|
const { input } = _toXaiResponsesInputForTest([
|
|
@@ -239,7 +517,13 @@ test('openai-compat/xai Responses: custom_tool_call history replays as function_
|
|
|
239
517
|
toolCalls: [{ id: 'call_patch_1', name: 'apply_patch', arguments: { patch: rawPatch }, nativeType: 'custom_tool_call' }],
|
|
240
518
|
},
|
|
241
519
|
{ role: 'tool', toolCallId: 'call_patch_1', content: 'OK' },
|
|
242
|
-
],
|
|
520
|
+
], {
|
|
521
|
+
xaiResponses: {
|
|
522
|
+
previousResponseId: 'resp_same',
|
|
523
|
+
seenMessageCount: 0,
|
|
524
|
+
model: 'grok-composer-2.5-fast',
|
|
525
|
+
},
|
|
526
|
+
}, { model: 'grok-composer-2.5-fast' });
|
|
243
527
|
assert.equal(input.some((item) => item.type === 'custom_tool_call'), false);
|
|
244
528
|
assert.equal(input.some((item) => item.type === 'custom_tool_call_output'), false);
|
|
245
529
|
const call = input.find((item) => item.type === 'function_call' && item.name === 'apply_patch');
|
|
@@ -1092,6 +1376,35 @@ test('anthropic effort: sonnet-4-6 uses output_config + effort beta, not thinkin
|
|
|
1092
1376
|
assert.ok(beta.includes(EFFORT_BETA_HEADER));
|
|
1093
1377
|
});
|
|
1094
1378
|
|
|
1379
|
+
test('anthropic-oauth: load_tool native references replay as ordinary tool_result after model switches', () => {
|
|
1380
|
+
const body = _buildRequestBodyForCacheSmoke(
|
|
1381
|
+
[
|
|
1382
|
+
{ role: 'user', content: 'load a tool' },
|
|
1383
|
+
{
|
|
1384
|
+
role: 'assistant',
|
|
1385
|
+
content: '',
|
|
1386
|
+
toolCalls: [{ id: 'toolu_load_1', name: 'load_tool', arguments: { names: ['read'] } }],
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
role: 'tool',
|
|
1390
|
+
toolCallId: 'toolu_load_1',
|
|
1391
|
+
content: '{"loaded":["read"]}',
|
|
1392
|
+
nativeToolSearch: { toolReferences: ['read'] },
|
|
1393
|
+
},
|
|
1394
|
+
],
|
|
1395
|
+
'claude-opus-4-8',
|
|
1396
|
+
[],
|
|
1397
|
+
{ effort: 'high' },
|
|
1398
|
+
);
|
|
1399
|
+
const serialized = JSON.stringify(body.messages);
|
|
1400
|
+
assert.equal(serialized.includes('tool_reference'), false);
|
|
1401
|
+
const toolResult = body.messages
|
|
1402
|
+
.flatMap((message) => Array.isArray(message.content) ? message.content : [])
|
|
1403
|
+
.find((block) => block?.type === 'tool_result' && block.tool_use_id === 'toolu_load_1');
|
|
1404
|
+
assert.ok(toolResult);
|
|
1405
|
+
assert.equal(toolResult.content, '{"loaded":["read"]}');
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1095
1408
|
test('anthropic effort: legacy sonnet-4-5 maps effort to thinking budget', () => {
|
|
1096
1409
|
const model = 'claude-sonnet-4-5-20250514';
|
|
1097
1410
|
const body = _buildRequestBodyForCacheSmoke(
|
|
@@ -1270,7 +1583,7 @@ test('canonical frame: full-frame builder leads with type and preserves codex bo
|
|
|
1270
1583
|
assert.equal(frame.previous_response_id, undefined);
|
|
1271
1584
|
});
|
|
1272
1585
|
|
|
1273
|
-
test('canonical frame: delta insert keeps key order, drops
|
|
1586
|
+
test('canonical frame: delta insert keeps key order, drops chained instructions, overrides input', () => {
|
|
1274
1587
|
const body = {
|
|
1275
1588
|
model: 'gpt-5.5',
|
|
1276
1589
|
instructions: 'sys',
|
|
@@ -1279,10 +1592,11 @@ test('canonical frame: delta insert keeps key order, drops empty instructions, o
|
|
|
1279
1592
|
text: { verbosity: 'low' },
|
|
1280
1593
|
};
|
|
1281
1594
|
const delta = _buildResponseCreateFrame(body, { previousResponseId: 'resp_prev', inputOverride: [{ b: 2 }] });
|
|
1282
|
-
assert.deepEqual(Object.keys(delta), ['type', 'model', '
|
|
1595
|
+
assert.deepEqual(Object.keys(delta), ['type', 'model', 'previous_response_id', 'input', 'tool_choice', 'text']);
|
|
1596
|
+
assert.equal(delta.instructions, undefined);
|
|
1283
1597
|
assert.equal(delta.previous_response_id, 'resp_prev');
|
|
1284
1598
|
assert.deepEqual(delta.input, [{ b: 2 }]);
|
|
1285
|
-
// Empty instructions is dropped in delta mode (server resolves via prev id).
|
|
1599
|
+
// Empty instructions is also dropped in delta mode (server resolves via prev id).
|
|
1286
1600
|
const noInstr = _buildResponseCreateFrame({ ...body, instructions: '' }, { previousResponseId: 'resp_prev', inputOverride: [] });
|
|
1287
1601
|
assert.deepEqual(Object.keys(noInstr), ['type', 'model', 'previous_response_id', 'input', 'tool_choice', 'text']);
|
|
1288
1602
|
});
|
|
@@ -1328,11 +1642,11 @@ import {
|
|
|
1328
1642
|
FULL_RESPONSES_TRANSPORT_CAPS,
|
|
1329
1643
|
} from '../src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs';
|
|
1330
1644
|
|
|
1331
|
-
test('transport policy: default (no env) is
|
|
1645
|
+
test('transport policy: default (no env) is auto WS-first / refs continuation ON / HTTP fallback ON', () => {
|
|
1332
1646
|
const p = resolveOpenAiTransportPolicy({});
|
|
1333
|
-
assert.equal(p.mode, '
|
|
1647
|
+
assert.equal(p.mode, 'auto');
|
|
1334
1648
|
assert.equal(p.transport, 'ws');
|
|
1335
|
-
assert.equal(p.allowHttpFallback,
|
|
1649
|
+
assert.equal(p.allowHttpFallback, true);
|
|
1336
1650
|
assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
|
|
1337
1651
|
});
|
|
1338
1652
|
|
|
@@ -1364,10 +1678,11 @@ test('transport policy: http-sse forces the HTTP transport with delta OFF', () =
|
|
|
1364
1678
|
assert.equal(p.delta.optIn, false);
|
|
1365
1679
|
});
|
|
1366
1680
|
|
|
1367
|
-
test('transport policy: unknown MIXDOG_OAI_TRANSPORT falls back to default
|
|
1681
|
+
test('transport policy: unknown MIXDOG_OAI_TRANSPORT falls back to default auto', () => {
|
|
1368
1682
|
const p = resolveOpenAiTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'quantum' });
|
|
1369
|
-
assert.equal(p.mode, '
|
|
1683
|
+
assert.equal(p.mode, 'auto');
|
|
1370
1684
|
assert.equal(p.transport, 'ws');
|
|
1685
|
+
assert.equal(p.allowHttpFallback, true);
|
|
1371
1686
|
});
|
|
1372
1687
|
|
|
1373
1688
|
test('response frame builder can omit transport-only stream/background for codex warmup parity', () => {
|
|
@@ -1383,7 +1698,7 @@ test('transport policy: mode token aliases normalize', () => {
|
|
|
1383
1698
|
assert.equal(_normalizeTransportMode(' ws delta '), 'ws-delta');
|
|
1384
1699
|
assert.equal(_normalizeTransportMode('http/sse'), 'http-sse');
|
|
1385
1700
|
assert.equal(_normalizeTransportMode('sse'), 'http-sse');
|
|
1386
|
-
assert.equal(_normalizeTransportMode('auto'), '
|
|
1701
|
+
assert.equal(_normalizeTransportMode('auto'), 'auto');
|
|
1387
1702
|
assert.equal(_normalizeTransportMode('official'), null);
|
|
1388
1703
|
assert.equal(_normalizeTransportMode(''), null);
|
|
1389
1704
|
assert.equal(_normalizeTransportMode('bogus'), null);
|
|
@@ -1472,17 +1787,19 @@ test('responses transport policy: openai direct caps match oauth (both full)', (
|
|
|
1472
1787
|
assert.deepEqual(direct.delta, { force: false, refs: true, optIn: true });
|
|
1473
1788
|
});
|
|
1474
1789
|
|
|
1475
|
-
test('responses transport policy: xai auto
|
|
1790
|
+
test('responses transport policy: xai auto is WS-first with HTTP fallback', () => {
|
|
1476
1791
|
const p = resolveResponsesTransportPolicy({}, RESPONSES_TRANSPORT_CAPABILITIES.xai);
|
|
1477
|
-
assert.equal(p.mode, '
|
|
1792
|
+
assert.equal(p.mode, 'auto');
|
|
1478
1793
|
assert.equal(p.transport, 'ws');
|
|
1479
|
-
assert.equal(p.allowHttpFallback,
|
|
1794
|
+
assert.equal(p.allowHttpFallback, true);
|
|
1480
1795
|
assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
|
|
1481
1796
|
});
|
|
1482
1797
|
|
|
1483
|
-
test('responses transport policy: explicit auto is
|
|
1798
|
+
test('responses transport policy: explicit auto is WS-first with HTTP fallback', () => {
|
|
1484
1799
|
const p = resolveResponsesTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'auto' }, RESPONSES_TRANSPORT_CAPABILITIES.xai);
|
|
1800
|
+
assert.equal(p.mode, 'auto');
|
|
1485
1801
|
assert.equal(p.transport, 'ws');
|
|
1802
|
+
assert.equal(p.allowHttpFallback, true);
|
|
1486
1803
|
assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
|
|
1487
1804
|
});
|
|
1488
1805
|
|
|
@@ -1570,11 +1887,13 @@ test('grok-oauth: all Grok models inherit global transport; explicit setting is
|
|
|
1570
1887
|
test('responses transport policy: _gateTransportMode down-shifts per capability', () => {
|
|
1571
1888
|
// delta unsupported → ws-delta collapses to ws-full; others pass through.
|
|
1572
1889
|
const noDelta = { ws: true, http: true, delta: false };
|
|
1890
|
+
assert.equal(_gateTransportMode('auto', noDelta), 'ws-full');
|
|
1573
1891
|
assert.equal(_gateTransportMode('ws-delta', noDelta), 'ws-full');
|
|
1574
1892
|
assert.equal(_gateTransportMode('ws-full', noDelta), 'ws-full');
|
|
1575
1893
|
assert.equal(_gateTransportMode('http-sse', noDelta), 'http-sse');
|
|
1576
1894
|
// WS unsupported → WS modes prefer HTTP.
|
|
1577
1895
|
const httpOnly = { ws: false, http: true, delta: false };
|
|
1896
|
+
assert.equal(_gateTransportMode('auto', httpOnly), 'http-sse');
|
|
1578
1897
|
assert.equal(_gateTransportMode('ws-full', httpOnly), 'http-sse');
|
|
1579
1898
|
assert.equal(_gateTransportMode('ws-delta', httpOnly), 'http-sse');
|
|
1580
1899
|
// HTTP unsupported → http-sse prefers full-frame WS.
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
{ "id": "period-24h", "label": "period window 24h", "args": { "period": "24h", "limit": 10 }, "expect": "browse" },
|
|
5
5
|
{ "id": "period-7d", "label": "period window 7d", "args": { "period": "7d", "limit": 10 }, "expect": "browse" },
|
|
6
6
|
{ "id": "scope-all", "label": "all-scope query", "args": { "query": "recall", "projectScope": "all", "limit": 10 }, "expect": "results" },
|
|
7
|
-
{ "id": "recent-dryrun-delta", "label": "recent-work topical: dryrun delta sum", "args": { "query": "
|
|
8
|
-
{ "id": "recent-remote-singleton", "label": "recent-work topical: remote singleton seat", "args": { "query": "remote
|
|
9
|
-
{ "id": "recency-today", "label": "query+period recency: today's work (24h)", "args": { "query": "
|
|
7
|
+
{ "id": "recent-dryrun-delta", "label": "recent-work topical: dryrun delta sum", "args": { "query": "\uB4DC\uB77C\uC774\uB7F0 \uB378\uD0C0 \uD569\uC0B0", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["\uB4DC\uB77C\uC774\uB7F0"], "topN": 5 } },
|
|
8
|
+
{ "id": "recent-remote-singleton", "label": "recent-work topical: remote singleton seat", "args": { "query": "remote \uC2F1\uAE00\uD1A4 \uC88C\uC11D", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["\uC2F1\uAE00\uD1A4"], "topN": 5 } },
|
|
9
|
+
{ "id": "recency-today", "label": "query+period recency: today's work (24h)", "args": { "query": "\uC624\uB298 \uC9C4\uD589\uD55C \uC791\uC5C5", "period": "24h", "limit": 10 }, "expect": { "kind": "results", "recencyOrdered": true } },
|
|
10
10
|
{ "id": "xlang-embedding-crash", "label": "cross-language: embedding worker crash (strict topN)", "args": { "query": "embedding worker crash", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["embedding"], "topN": 3 } }
|
|
11
11
|
]
|
package/scripts/recall-bench.mjs
CHANGED
|
@@ -31,7 +31,7 @@ function hasFlag(name) { return process.argv.includes(`--${name}`); }
|
|
|
31
31
|
const WARN_LATENCY_MS = 3000;
|
|
32
32
|
|
|
33
33
|
const DEFAULT_CASES = [
|
|
34
|
-
{ id: 'kw-ko', label: 'keyword query (ko)', args: { query: '
|
|
34
|
+
{ id: 'kw-ko', label: 'keyword query (ko)', args: { query: '\uBA54\uBAA8\uB9AC \uC7AC\uD604' }, expect: 'results' },
|
|
35
35
|
{ id: 'kw-en', label: 'keyword query (en)', args: { query: 'memory recall pipeline' }, expect: 'results' },
|
|
36
36
|
{ id: 'short-1tok', label: 'short 1-token query', args: { query: 'recall' }, expect: 'results' },
|
|
37
37
|
{ id: 'short-2tok', label: 'short 2-token query', args: { query: 'cycle1 drain' }, expect: 'results' },
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
-
{ "id": "q-kr-recall-phrase", "label": "Korean distinctive phrase: persistent memory", "args": { "query": "
|
|
2
|
+
{ "id": "q-kr-recall-phrase", "label": "Korean distinctive phrase: persistent memory", "args": { "query": "\uC601\uC18D \uBA54\uBAA8\uB9AC" }, "expect": { "kind": "results", "topNContains": ["\uC601\uC18D \uBA54\uBAA8\uB9AC"], "topN": 5 } },
|
|
3
3
|
{ "id": "q-en-recall-pipeline", "label": "English distinctive phrase: recall fast-track builder", "args": { "query": "buildRecallFastTrackQuery" }, "expect": { "kind": "results", "topNContains": ["buildRecallFastTrackQuery"], "topN": 5 } },
|
|
4
4
|
{ "id": "q-cycle1-drain", "label": "cycle1 ECONNRESET topic", "args": { "query": "cycle1 ECONNRESET" }, "expect": { "kind": "results", "topNContains": ["cycle1", "ECONNRESET"], "topN": 5 } },
|
|
5
5
|
{ "id": "q-recall-general", "label": "bare recall keyword", "args": { "query": "recall" }, "expect": { "kind": "results", "topNContains": ["recall"], "topN": 5 } },
|
|
6
|
-
{ "id": "q-uc4-topic-kr", "label": "UC4 topic query with Korean qualifier", "args": { "query": "recall period last
|
|
7
|
-
{ "id": "q-recall-improve-kr", "label": "cache improvement topic", "args": { "query": "
|
|
8
|
-
{ "id": "q-fanout-recall-cache", "label": "fan-out array query cache+recall", "args": { "query": ["
|
|
6
|
+
{ "id": "q-uc4-topic-kr", "label": "UC4 topic query with Korean qualifier", "args": { "query": "recall period last \uAC1C\uC120", "limit": 8 }, "expect": { "kind": "results", "topNContains": ["\uAC1C\uC120"], "topN": 5 } },
|
|
7
|
+
{ "id": "q-recall-improve-kr", "label": "cache improvement topic", "args": { "query": "\uCE90\uC2DC \uAC1C\uC120" }, "expect": { "kind": "results", "topNContains": ["\uAC1C\uC120"], "topN": 5 } },
|
|
8
|
+
{ "id": "q-fanout-recall-cache", "label": "fan-out array query cache+recall", "args": { "query": ["\uCE90\uC2DC \uAC1C\uC120", "recall"], "limit": 5 }, "expect": { "kind": "results", "topNContains": ["\uAC1C\uC120", "recall"], "topN": 10 } },
|
|
9
9
|
{ "id": "q-short-2tok-cycle", "label": "short 2-token cycle1 query", "args": { "query": "cycle1 drain", "limit": 8 }, "expect": { "kind": "results", "topNContains": ["cycle1"], "topN": 3 } },
|
|
10
10
|
{ "id": "q-recall-scope-project", "label": "project-scoped recall query", "args": { "query": "recall", "cwd": "C:\\Project\\mixdog" , "limit": 10 }, "expect": { "kind": "results", "topNContains": ["recall"], "topN": 5 } }
|
|
11
11
|
]
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
{ "id": "uc3-tod-dated", "label": "UC3 dated time window 2026-07-03 09:00~12:00", "args": { "period": "2026-07-03 09:00~12:00", "limit": 10 }, "expect": "browse" },
|
|
9
9
|
{ "id": "uc3-date", "label": "UC3 specific date 2026-07-02", "args": { "period": "2026-07-02", "limit": 10 }, "expect": "browse" },
|
|
10
10
|
{ "id": "uc3-range", "label": "UC3 date range 2026-07-01~2026-07-03", "args": { "period": "2026-07-01~2026-07-03", "limit": 10 }, "expect": "browse" },
|
|
11
|
-
{ "id": "uc4-topic", "label": "UC4 topic query", "args": { "query": "recall period last
|
|
11
|
+
{ "id": "uc4-topic", "label": "UC4 topic query", "args": { "query": "recall period last \uAC1C\uC120", "limit": 8 }, "expect": "results" },
|
|
12
12
|
{ "id": "uc4-topic-time", "label": "UC4 topic + time window", "args": { "query": "recall", "period": "24h", "limit": 8 }, "expect": "results" },
|
|
13
13
|
{ "id": "uc4-topic-tod", "label": "UC4 topic + time-of-day", "args": { "query": "recall", "period": "12:00~15:00", "limit": 8 }, "expect": "results" },
|
|
14
|
-
{ "id": "uc4-fanout", "label": "UC4 fan-out array query", "args": { "query": ["recall
|
|
14
|
+
{ "id": "uc4-fanout", "label": "UC4 fan-out array query", "args": { "query": ["recall \uAC1C\uC120", "battle balance"], "limit": 5 }, "expect": "results" },
|
|
15
15
|
{ "id": "uc5-yesterday", "label": "UC5 calendar yesterday", "args": { "period": "yesterday", "limit": 10 }, "expect": "browse" },
|
|
16
16
|
{ "id": "uc5-thisweek", "label": "UC5 calendar this_week", "args": { "period": "this_week", "limit": 10 }, "expect": "browse" },
|
|
17
17
|
{ "id": "uc6-category", "label": "UC6 category decision 7d", "args": { "period": "7d", "category": "decision", "limit": 10 }, "expect": "browse" }
|
|
@@ -226,11 +226,11 @@ function cacheRatioFromUsage(rows) {
|
|
|
226
226
|
|
|
227
227
|
function cacheBreakExplanation(reason) {
|
|
228
228
|
const r = String(reason || '');
|
|
229
|
-
if (r === 'no_anchor') return 'delta
|
|
230
|
-
if (r === 'input_prefix_mismatch') return '
|
|
231
|
-
if (r.startsWith('response_output_mismatch')) return '
|
|
232
|
-
if (r === 'cache_key_changed') return 'cache key
|
|
233
|
-
return r ? '
|
|
229
|
+
if (r === 'no_anchor') return 'delta \uAE30\uC900\uC810/previous response \uC5C6\uC74C';
|
|
230
|
+
if (r === 'input_prefix_mismatch') return '\uC694\uCCAD prefix\uAC00 \uC774\uC804 turn\uACFC \uB2EC\uB77C\uC9D0';
|
|
231
|
+
if (r.startsWith('response_output_mismatch')) return '\uC774\uC804 \uC751\uB2F5 output \uCCB4\uC778\uC774 \uAE30\uB300\uAC12\uACFC \uB2E4\uB984';
|
|
232
|
+
if (r === 'cache_key_changed') return 'cache key \uBCC0\uACBD';
|
|
233
|
+
return r ? '\uC6D0\uC778 \uBBF8\uBD84\uB958' : '\uC6D0\uC778 \uAE30\uB85D \uC5C6\uC74C';
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
function classifyCacheBreakPhase(row, usageRows, transportRows) {
|
|
@@ -1342,27 +1342,27 @@ function buildExecutiveSummary(report) {
|
|
|
1342
1342
|
const tools = report.summary.total_tool_ms || 0;
|
|
1343
1343
|
const total = headers + stream + tools;
|
|
1344
1344
|
const dominant = [['stream', stream], ['tools', tools], ['headers', headers]].sort((a, b) => b[1] - a[1])[0];
|
|
1345
|
-
lines.push(
|
|
1345
|
+
lines.push(`\uC8FC \uBCD1\uBAA9=${dominant[0]} ${fmtMs(dominant[1])}/${fmtMs(total)}; cache=${fmtPct((report.summary.cache_ratio ?? 0) * 100)}; turns=${report.summary.turns}; tools=${report.summary.tool_calls}`);
|
|
1346
1346
|
if (report.cache.cache_breaks.length) {
|
|
1347
1347
|
const summary = countBy(report.cache.cache_breaks, (b) => `${b.reason || 'unknown'}/${b.phase || 'unknown'}`).slice(0, 3).map(([k, v]) => `${k}×${v}`).join(', ');
|
|
1348
|
-
lines.push(
|
|
1348
|
+
lines.push(`\uCE90\uC2DC \uAE68\uC9D0=${report.cache.cache_breaks.length} (${summary})`);
|
|
1349
1349
|
}
|
|
1350
1350
|
if (report.cache.actual_cache_misses?.length) {
|
|
1351
1351
|
const top = [...report.cache.actual_cache_misses].sort((a, b) => (b.uncached_tokens || 0) - (a.uncached_tokens || 0))[0];
|
|
1352
|
-
lines.push(
|
|
1352
|
+
lines.push(`\uC2E4\uC81C \uCE90\uC2DC \uBBF8\uC2A4=${report.cache.actual_cache_misses.length} (top uncached=${fmtTok(top?.uncached_tokens || 0)})`);
|
|
1353
1353
|
}
|
|
1354
1354
|
if (report.compact?.cache_breaks?.length) {
|
|
1355
1355
|
const summary = countBy(report.compact.cache_breaks, (b) => `${b.reason || 'unknown'}/${b.phase || 'unknown'}`).slice(0, 3).map(([k, v]) => `${k}×${v}`).join(', ');
|
|
1356
|
-
lines.push(
|
|
1356
|
+
lines.push(`\uCEF4\uD329\uD2B8 \uD638\uCD9C=${report.compact.sessions.length} session, cache reset=${report.compact.cache_breaks.length} (${summary})`);
|
|
1357
1357
|
}
|
|
1358
1358
|
if (report.tools.failures.length) {
|
|
1359
1359
|
const summary = countBy(report.tools.failures, (f) => f.category || f.tool || 'unknown').slice(0, 3).map(([k, v]) => `${k}×${v}`).join(', ');
|
|
1360
|
-
lines.push(
|
|
1360
|
+
lines.push(`\uD234 \uC2E4\uD328=${report.tools.failures.length} (${summary})`);
|
|
1361
1361
|
}
|
|
1362
1362
|
const topGrowth = report.tokens.growth_turns[0];
|
|
1363
|
-
if (topGrowth) lines.push(
|
|
1363
|
+
if (topGrowth) lines.push(`\uD1A0\uD070 \uC99D\uD3ED=${topGrowth.agent || '-'} it=${topGrowth.turn_label} promptΔ=${fmtTok(topGrowth.prompt_delta)} out=${fmtTok(topGrowth.output_tokens + topGrowth.thinking_tokens)}`);
|
|
1364
1364
|
const topChurn = report.tools.duplicates[0];
|
|
1365
|
-
if (topChurn) lines.push(
|
|
1365
|
+
if (topChurn) lines.push(`\uD234 \uD5DB\uB3CE=${topChurn.tool}×${topChurn.count} ${compactText(topChurn.target, 90)}`);
|
|
1366
1366
|
return lines;
|
|
1367
1367
|
}
|
|
1368
1368
|
|
|
@@ -1626,7 +1626,7 @@ function renderText(report) {
|
|
|
1626
1626
|
if (report.tools.failures.length) {
|
|
1627
1627
|
lines.push('tool failures:');
|
|
1628
1628
|
for (const f of report.tools.failures.slice(0, 10)) {
|
|
1629
|
-
lines.push(`- ${f.agent || '-'} it=${f.iteration ?? '-'} ${f.tool || '-'} ${fmtMs(f.tool_ms)} category=${f.category || '-'} reason=${f.reason || 'trace
|
|
1629
|
+
lines.push(`- ${f.agent || '-'} it=${f.iteration ?? '-'} ${f.tool || '-'} ${fmtMs(f.tool_ms)} category=${f.category || '-'} reason=${f.reason || 'trace\uC5D0 \uC0C1\uC138 stderr/\uC608\uC678 \uBBF8\uC800\uC7A5'} result=${Math.round(f.bytes / 1024)}KB/${f.lines}l: ${f.target}`);
|
|
1630
1630
|
if (opts.failuresOnly && f.preview) {
|
|
1631
1631
|
const preview = compactText(f.preview, 700);
|
|
1632
1632
|
lines.push(` preview: ${preview}`);
|