@khorsheed/dsh-local-agent-kimi 0.2.0-rc.3 → 0.2.1
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/CHANGELOG.md +25 -1
- package/README.en.md +24 -112
- package/README.i18n.yaml +2 -2
- package/README.md +24 -112
- package/lib/client.js +156 -71
- package/lib/index.js +631 -346
- package/lib/types/client/SettingsCard.js +11 -3
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/index.js +2 -2
- package/lib/types/index.js +4 -2
- package/lib/types/kimi-cli-provider.js +39 -26
- package/lib/types/live-driver.d.ts +8 -5
- package/lib/types/live-driver.js +336 -119
- package/lib/types/model-broker.js +42 -1
- package/lib/types/session-mirror.d.ts +115 -52
- package/lib/types/session-mirror.js +286 -210
- package/lib/types/session-view.d.ts +23 -2
- package/lib/types/session-view.js +37 -7
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -2080,13 +2080,16 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2080
2080
|
const sessionId = sessionDir.split("session_").pop() ?? sessionDir;
|
|
2081
2081
|
const lines = [];
|
|
2082
2082
|
const usageRecords = [];
|
|
2083
|
+
const closedSteps = /* @__PURE__ */ new Set();
|
|
2083
2084
|
let model;
|
|
2084
2085
|
let modelTurn;
|
|
2085
2086
|
let lineModel;
|
|
2087
|
+
let lineConfig;
|
|
2086
2088
|
const push = (line) => {
|
|
2087
2089
|
lines.push({
|
|
2088
2090
|
...line,
|
|
2089
|
-
...lineModel === void 0 ? {} : { model: lineModel }
|
|
2091
|
+
...lineModel === void 0 ? {} : { model: lineModel },
|
|
2092
|
+
...line.kind !== "user" && lineConfig !== void 0 ? { requestConfig: { ...lineConfig } } : {}
|
|
2090
2093
|
});
|
|
2091
2094
|
};
|
|
2092
2095
|
let turn = 0;
|
|
@@ -2100,7 +2103,8 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2100
2103
|
if (error.code === "ENOENT") return {
|
|
2101
2104
|
sessionId,
|
|
2102
2105
|
lines,
|
|
2103
|
-
usageRecords
|
|
2106
|
+
usageRecords,
|
|
2107
|
+
closedSteps
|
|
2104
2108
|
};
|
|
2105
2109
|
throw error;
|
|
2106
2110
|
}
|
|
@@ -2117,6 +2121,10 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2117
2121
|
if (typeof event.model === "string" && event.model !== "") {
|
|
2118
2122
|
model = lineModel = event.model;
|
|
2119
2123
|
modelTurn = turn || 1;
|
|
2124
|
+
if (lineConfig !== void 0) lineConfig = {
|
|
2125
|
+
...lineConfig,
|
|
2126
|
+
model: event.model
|
|
2127
|
+
};
|
|
2120
2128
|
}
|
|
2121
2129
|
const usage = usageFromWire(event.usage);
|
|
2122
2130
|
if (usage !== void 0) usageRecords.push({
|
|
@@ -2131,6 +2139,11 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2131
2139
|
model = event.model;
|
|
2132
2140
|
modelTurn = turn || 1;
|
|
2133
2141
|
}
|
|
2142
|
+
lineConfig = lineModel === void 0 ? void 0 : {
|
|
2143
|
+
model: lineModel,
|
|
2144
|
+
...typeof event.thinkingEffort === "string" && event.thinkingEffort !== "" ? { reasoningEffort: event.thinkingEffort } : {},
|
|
2145
|
+
...typeof event.maxTokens === "number" && Number.isFinite(event.maxTokens) ? { maxTokens: event.maxTokens } : {}
|
|
2146
|
+
};
|
|
2134
2147
|
continue;
|
|
2135
2148
|
}
|
|
2136
2149
|
if (event.type === "turn.prompt") {
|
|
@@ -2175,17 +2188,21 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2175
2188
|
turn = Math.max(turn, loopTurn);
|
|
2176
2189
|
turnSeen = true;
|
|
2177
2190
|
}
|
|
2191
|
+
const wireStep = typeof loop.step === "number" ? loop.step : void 0;
|
|
2178
2192
|
const part = loop.part;
|
|
2179
2193
|
const lineTurn = turn === 0 ? 1 : turn;
|
|
2194
|
+
if (loop.type === "step.end" && wireStep !== void 0) closedSteps.add(`${lineTurn}:${wireStep}`);
|
|
2180
2195
|
if (part?.type === "text" && part.text !== void 0 && part.text.trim() !== "") push({
|
|
2181
2196
|
kind: "assistant",
|
|
2182
2197
|
text: part.text,
|
|
2183
|
-
turn: lineTurn
|
|
2198
|
+
turn: lineTurn,
|
|
2199
|
+
...wireStep === void 0 ? {} : { wireStep }
|
|
2184
2200
|
});
|
|
2185
2201
|
else if (part?.type === "think" && part.think !== void 0 && part.think.trim() !== "") push({
|
|
2186
2202
|
kind: "think",
|
|
2187
2203
|
text: part.think,
|
|
2188
|
-
turn: lineTurn
|
|
2204
|
+
turn: lineTurn,
|
|
2205
|
+
...wireStep === void 0 ? {} : { wireStep }
|
|
2189
2206
|
});
|
|
2190
2207
|
else if (loop.type === "tool.call") {
|
|
2191
2208
|
const call = loop;
|
|
@@ -2197,7 +2214,8 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2197
2214
|
id: typeof call.toolCallId === "string" ? call.toolCallId : typeof call.uuid === "string" ? call.uuid : `kimi-tool-${lineIndex}`,
|
|
2198
2215
|
name,
|
|
2199
2216
|
...args === void 0 ? {} : { args },
|
|
2200
|
-
turn: lineTurn
|
|
2217
|
+
turn: lineTurn,
|
|
2218
|
+
...wireStep === void 0 ? {} : { wireStep }
|
|
2201
2219
|
});
|
|
2202
2220
|
if (typeof call.uuid === "string") callsByUuid.set(call.uuid, lineIndex);
|
|
2203
2221
|
if (typeof call.toolCallId === "string") callsByToolCallId.set(call.toolCallId, lineIndex);
|
|
@@ -2230,6 +2248,7 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2230
2248
|
sessionId,
|
|
2231
2249
|
lines,
|
|
2232
2250
|
usageRecords,
|
|
2251
|
+
closedSteps,
|
|
2233
2252
|
...model === void 0 ? {} : { model },
|
|
2234
2253
|
...modelTurn === void 0 ? {} : { modelTurn }
|
|
2235
2254
|
};
|
|
@@ -2238,7 +2257,9 @@ async function readKimiTranscript(sessionDir) {
|
|
|
2238
2257
|
* Map a wire `usage.record` payload onto the shared usage contract. kimi
|
|
2239
2258
|
* reports input (uncached), cache read, cache creation, and output; the
|
|
2240
2259
|
* creation counter is its cache-write analog. Non-numeric or missing
|
|
2241
|
-
* counters are dropped so the record stays usable when kimi omits a field
|
|
2260
|
+
* counters are dropped so the record stays usable when kimi omits a field;
|
|
2261
|
+
* a reported cache-read counter is always written (zero included), so a
|
|
2262
|
+
* reader can tell "no hits" from "not reported".
|
|
2242
2263
|
* @param raw - the event's `usage` value.
|
|
2243
2264
|
* @returns the shared usage record, or undefined when nothing was reported.
|
|
2244
2265
|
*/
|
|
@@ -2258,7 +2279,7 @@ function usageFromWire(raw) {
|
|
|
2258
2279
|
inputTokens: input ?? 0,
|
|
2259
2280
|
outputTokens: output ?? 0
|
|
2260
2281
|
};
|
|
2261
|
-
if (cacheRead !== void 0
|
|
2282
|
+
if (cacheRead !== void 0) usage.cacheReadTokens = cacheRead;
|
|
2262
2283
|
if (cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
|
|
2263
2284
|
return usage;
|
|
2264
2285
|
}
|
|
@@ -2333,6 +2354,13 @@ function renderTranscript(transcript) {
|
|
|
2333
2354
|
* call.
|
|
2334
2355
|
* @module @khorsheed/dsh-local-agent-kimi/session-mirror
|
|
2335
2356
|
*/
|
|
2357
|
+
/**
|
|
2358
|
+
* The synthesized result for a call whose result the wire never recorded
|
|
2359
|
+
* (a settle-pass fold, or a live reservation kimi never logged). The
|
|
2360
|
+
* released-log contract resolves every advertised call before its step
|
|
2361
|
+
* closes, so an interrupted call folds with this error result.
|
|
2362
|
+
*/
|
|
2363
|
+
const KIMI_INTERRUPTED_TOOL_RESULT = "(工具调用被中断,未能记录返回结果)";
|
|
2336
2364
|
/** One user-role message event. */
|
|
2337
2365
|
function userEvent(text) {
|
|
2338
2366
|
return createUserMessage({
|
|
@@ -2354,41 +2382,42 @@ function assistantEvent(blocks, model) {
|
|
|
2354
2382
|
});
|
|
2355
2383
|
}
|
|
2356
2384
|
/**
|
|
2357
|
-
* The step the next fold pass would assign in one turn
|
|
2358
|
-
*
|
|
2359
|
-
*
|
|
2360
|
-
*
|
|
2361
|
-
*
|
|
2385
|
+
* The step the next fold pass would assign in one turn: the smallest step
|
|
2386
|
+
* nothing holds (never written, never reserved at call time — callers skip
|
|
2387
|
+
* their own reservations on top). Filling the smallest free step keeps the
|
|
2388
|
+
* stored log dense: the released-log reader admits only dense, strictly
|
|
2389
|
+
* sequential steps, and a reservation released mid-round (its content rode an
|
|
2390
|
+
* earlier fold) leaves a gap only the next first-free write fills.
|
|
2362
2391
|
* @param childSession - the run's child session.
|
|
2363
2392
|
* @param turn - the round's turn number.
|
|
2364
|
-
* @returns the
|
|
2393
|
+
* @returns the smallest free step in the turn.
|
|
2365
2394
|
*/
|
|
2366
2395
|
function nextKimiSessionStep(childSession, turn) {
|
|
2367
|
-
|
|
2396
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2368
2397
|
for (const event of childSession.snapshotEvents()) {
|
|
2369
2398
|
const data = event.data;
|
|
2370
|
-
if (data.turn === turn && typeof data.step === "number")
|
|
2399
|
+
if (data.turn === turn && typeof data.step === "number") taken.add(data.step);
|
|
2371
2400
|
}
|
|
2401
|
+
let next = 1;
|
|
2402
|
+
while (taken.has(next)) next += 1;
|
|
2372
2403
|
return next;
|
|
2373
2404
|
}
|
|
2374
2405
|
/**
|
|
2375
|
-
*
|
|
2376
|
-
* the
|
|
2377
|
-
*
|
|
2378
|
-
* is never
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
2381
|
-
* cards instead of `[工具 X]` text.
|
|
2406
|
+
* The step a durable write takes: one past the highest step this turn has
|
|
2407
|
+
* opened in the log. The released-log reader admits steps only in order —
|
|
2408
|
+
* one past the last, never below it — so the coordinate is decided when the
|
|
2409
|
+
* step is written, never reserved ahead (a reservation filled later lands
|
|
2410
|
+
* below a higher step already written: a gap, then a step out of order).
|
|
2411
|
+
* Stream and tool reservations only key the live preview.
|
|
2382
2412
|
*/
|
|
2383
|
-
function
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
}];
|
|
2413
|
+
function nextWrittenKimiStep(childSession, turn) {
|
|
2414
|
+
let last = 0;
|
|
2415
|
+
for (const event of childSession.snapshotEvents()) {
|
|
2416
|
+
if (event.type !== "step/start") continue;
|
|
2417
|
+
const data = event.data;
|
|
2418
|
+
if (data.turn === turn && typeof data.step === "number" && data.step > last) last = data.step;
|
|
2419
|
+
}
|
|
2420
|
+
return last + 1;
|
|
2392
2421
|
}
|
|
2393
2422
|
/** The run-progress delta text for one transcript line (any kind). */
|
|
2394
2423
|
function kimiLineProgressText(line) {
|
|
@@ -2396,10 +2425,14 @@ function kimiLineProgressText(line) {
|
|
|
2396
2425
|
return `${`[工具 ${line.name}]${line.args !== void 0 ? ` ${line.args}` : ""}`}${line.result !== void 0 ? ` → ${line.result}` : ""}`;
|
|
2397
2426
|
}
|
|
2398
2427
|
/**
|
|
2399
|
-
* Mirror one kimi session's transcript into
|
|
2400
|
-
* user prompt becomes a `user/message`, each
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2428
|
+
* Mirror one kimi session's transcript into a dsh subagent session: each
|
|
2429
|
+
* user prompt becomes a `user/message`, and each WIRE STEP (one LLM request:
|
|
2430
|
+
* its thinking, reply text and tool calls share the wire's own step number)
|
|
2431
|
+
* folds into one dsh step whose `assistant/message` carries the reasoning,
|
|
2432
|
+
* text and tool-call blocks together — the host session's own shape, so the
|
|
2433
|
+
* standard surfaces (conversation, Context Lens) read one request per LLM
|
|
2434
|
+
* call. Then the events are appended to persistence so the child session is
|
|
2435
|
+
* viewable.
|
|
2403
2436
|
*
|
|
2404
2437
|
* A resumed round (or a live poll) passes the already-mirrored
|
|
2405
2438
|
* transcript-line count so only the delta is appended — re-mirroring earlier
|
|
@@ -2409,22 +2442,29 @@ function kimiLineProgressText(line) {
|
|
|
2409
2442
|
* already in the child session, so a round mirrored across several live polls
|
|
2410
2443
|
* does not restart step 1 per poll.
|
|
2411
2444
|
*
|
|
2412
|
-
* Every folded step is
|
|
2413
|
-
*
|
|
2414
|
-
*
|
|
2415
|
-
*
|
|
2416
|
-
*
|
|
2417
|
-
*
|
|
2418
|
-
*
|
|
2419
|
-
* sequential fold never takes a
|
|
2445
|
+
* Every folded step is one ATOMIC `step/start`–`step/end` triple, written
|
|
2446
|
+
* only when its content lands: the released-log reader admits only dense,
|
|
2447
|
+
* strictly sequential steps (one open at a time — a second `step/start`
|
|
2448
|
+
* while another step is open fails the session's reload validation), and the
|
|
2449
|
+
* live conversation assembler needs the boundary to materialize the step at
|
|
2450
|
+
* all. A fold paired with in-flight streams lands at the earliest consumed
|
|
2451
|
+
* reservation (see {@link KimiMirrorStreams}), after `finalizeBefore` has
|
|
2452
|
+
* closed every stream reservation below it; a sequential fold never takes a
|
|
2453
|
+
* step a stream reserved. Mid-round, a wire step the wire has not CLOSED yet
|
|
2454
|
+
* (no `step.end`) holds the pass — its parts may still arrive — and so does
|
|
2455
|
+
* an unresulted tool call (the reader resolves every advertised call before
|
|
2456
|
+
* the step closes; the result merges into the call's line in place, so the
|
|
2457
|
+
* next pass sees it). The settle pass folds the remainder with a synthesized
|
|
2458
|
+
* interrupted result. Lines the wire never step-tagged (older kimi) fold one
|
|
2459
|
+
* by one, immediately, as before.
|
|
2420
2460
|
*
|
|
2421
2461
|
* Usage accounting is exactly-once ACROSS passes: each `usage.record` is
|
|
2422
|
-
* tagged with the transcript position it occurred at,
|
|
2423
|
-
*
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2462
|
+
* tagged with the transcript position it occurred at, a pass owns the records
|
|
2463
|
+
* between the old offset and its own consumed end, and inside the window each
|
|
2464
|
+
* record rides the unit whose line span encloses it — one record per wire
|
|
2465
|
+
* step, landing on its own step's message. (A record raced PAST its content
|
|
2466
|
+
* encloses into the next unit instead: attribution off by one in a flush
|
|
2467
|
+
* race, totals still exact.)
|
|
2428
2468
|
* @param ctx - host context carrying the session persistence service.
|
|
2429
2469
|
* @param childSession - the dsh subagent session created for this delegation.
|
|
2430
2470
|
* @param homeDir - the `kimi` harness's scoped home.
|
|
@@ -2432,7 +2472,9 @@ function kimiLineProgressText(line) {
|
|
|
2432
2472
|
* recent session (a one-shot `kimi -p` run always creates a fresh one).
|
|
2433
2473
|
* @param fromLines - transcript lines already mirrored into the child session
|
|
2434
2474
|
* (0 for the first round); only lines after this offset are appended.
|
|
2435
|
-
* @returns the new
|
|
2475
|
+
* @returns the new mirror offset and the newly mirrored lines' texts. A held
|
|
2476
|
+
* unit (open wire step or unresulted tool) stops the offset before itself;
|
|
2477
|
+
* the next pass retries from there.
|
|
2436
2478
|
*/
|
|
2437
2479
|
async function mirrorKimiSessionDelta(ctx, childSession, homeDir, kimiSessionId, fromLines = 0, options) {
|
|
2438
2480
|
let workspaces;
|
|
@@ -2485,110 +2527,153 @@ async function mirrorKimiSessionDelta(ctx, childSession, homeDir, kimiSessionId,
|
|
|
2485
2527
|
total: 0,
|
|
2486
2528
|
texts: []
|
|
2487
2529
|
};
|
|
2488
|
-
const newTotal = transcript.lines.length;
|
|
2489
2530
|
const delta = transcript.lines.slice(fromLines);
|
|
2490
2531
|
const observedModel = options?.turn === void 0 || transcript.modelTurn === options.turn ? transcript.model : void 0;
|
|
2491
2532
|
const toolCalls = roundToolCalls(transcript.lines, options?.turn);
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2533
|
+
const units = [];
|
|
2534
|
+
for (const line of delta) {
|
|
2535
|
+
if (line === void 0) continue;
|
|
2536
|
+
const last = units.at(-1);
|
|
2537
|
+
const head = last?.[0];
|
|
2538
|
+
if (last !== void 0 && head !== void 0 && line.kind !== "user" && head.kind !== "user" && line.wireStep !== void 0 && line.wireStep === head.wireStep && line.turn === head.turn) {
|
|
2539
|
+
last.push(line);
|
|
2540
|
+
continue;
|
|
2541
|
+
}
|
|
2542
|
+
units.push([line]);
|
|
2543
|
+
}
|
|
2544
|
+
let holdAt = -1;
|
|
2545
|
+
if (options?.settle !== true) for (let index = 0; index < units.length; index += 1) {
|
|
2546
|
+
const unit = units[index];
|
|
2547
|
+
const head = unit[0];
|
|
2548
|
+
if (head.kind !== "user" && head.wireStep !== void 0 && !transcript.closedSteps.has(`${head.turn}:${head.wireStep}`) || unit.some((line) => line.kind === "tool" && line.result === void 0)) {
|
|
2549
|
+
holdAt = index;
|
|
2550
|
+
break;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
const foldedUnits = holdAt < 0 ? units : units.slice(0, holdAt);
|
|
2554
|
+
const consumedTotal = fromLines + foldedUnits.reduce((n, unit) => n + unit.length, 0);
|
|
2555
|
+
const unitUsage = [];
|
|
2556
|
+
const claimed = [];
|
|
2557
|
+
let unitStart = fromLines;
|
|
2558
|
+
for (const unit of foldedUnits) {
|
|
2559
|
+
const start = unitStart;
|
|
2560
|
+
unitStart += unit.length;
|
|
2561
|
+
const head = unit[0];
|
|
2562
|
+
if (head.kind !== "user" && head.wireStep !== void 0) {
|
|
2563
|
+
claimed.push({
|
|
2564
|
+
from: start,
|
|
2565
|
+
to: unitStart
|
|
2566
|
+
});
|
|
2567
|
+
unitUsage.push(sumUsageRecords(transcript.usageRecords.filter((record) => record.line >= start && record.line < unitStart)));
|
|
2568
|
+
} else unitUsage.push(void 0);
|
|
2569
|
+
}
|
|
2570
|
+
const unclaimedUsage = sumUsageRecords(transcript.usageRecords.filter((record) => record.line >= fromLines && record.line <= consumedTotal && !claimed.some((span) => record.line >= span.from && record.line < span.to)));
|
|
2571
|
+
const deltaUsage = sumUsageRecords(transcript.usageRecords.filter((record) => record.line >= fromLines && record.line <= consumedTotal));
|
|
2572
|
+
const takenSteps = /* @__PURE__ */ new Map();
|
|
2501
2573
|
for (const event of childSession.snapshotEvents()) {
|
|
2502
2574
|
const data = event.data;
|
|
2503
|
-
if (typeof data.turn === "number" && typeof data.step === "number")
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
turn: call.turn,
|
|
2508
|
-
step: call.step,
|
|
2509
|
-
seq: event.seq
|
|
2510
|
-
});
|
|
2511
|
-
} else if (event.type === "tool/result") {
|
|
2512
|
-
const message = event.data.message;
|
|
2513
|
-
const id = message?.toolCallId ?? message?.content?.[0]?.toolCallId;
|
|
2514
|
-
if (id !== void 0) {
|
|
2515
|
-
openCalls.delete(id);
|
|
2516
|
-
settledCalls.add(id);
|
|
2517
|
-
}
|
|
2575
|
+
if (typeof data.turn === "number" && typeof data.step === "number") {
|
|
2576
|
+
const taken = takenSteps.get(data.turn) ?? /* @__PURE__ */ new Set();
|
|
2577
|
+
taken.add(data.step);
|
|
2578
|
+
takenSteps.set(data.turn, taken);
|
|
2518
2579
|
}
|
|
2519
2580
|
}
|
|
2520
2581
|
/**
|
|
2521
|
-
* The
|
|
2522
|
-
*
|
|
2523
|
-
*
|
|
2524
|
-
*
|
|
2525
|
-
* rejects).
|
|
2582
|
+
* The fold's coordinate: the smallest step of the turn that nothing holds
|
|
2583
|
+
* — neither written nor currently reserved. Filling the smallest free step
|
|
2584
|
+
* keeps the stored log dense even when a stream reservation below was
|
|
2585
|
+
* released mid-round (its content rode an earlier fold's step).
|
|
2526
2586
|
*/
|
|
2527
2587
|
const nextFoldStep = (turn) => {
|
|
2528
|
-
|
|
2529
|
-
const reserved
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
while (skip.has(step)) step += 1;
|
|
2533
|
-
}
|
|
2534
|
-
steps.set(turn, step + 1);
|
|
2588
|
+
const taken = new Set(takenSteps.get(turn));
|
|
2589
|
+
for (const reserved of options?.streams?.reservedSteps(turn) ?? []) taken.add(reserved);
|
|
2590
|
+
let step = 1;
|
|
2591
|
+
while (taken.has(step)) step += 1;
|
|
2535
2592
|
return step;
|
|
2536
2593
|
};
|
|
2537
|
-
/**
|
|
2538
|
-
|
|
2594
|
+
/** Record a fold's coordinate chosen by a stream pairing. */
|
|
2595
|
+
const markTaken = (turn, step) => {
|
|
2596
|
+
const written = takenSteps.get(turn) ?? /* @__PURE__ */ new Set();
|
|
2597
|
+
written.add(step);
|
|
2598
|
+
takenSteps.set(turn, written);
|
|
2599
|
+
};
|
|
2600
|
+
/** The window's accounting that no fold carried (dropped units' sums). */
|
|
2601
|
+
let carryUsage;
|
|
2602
|
+
let lastContentUnit = -1;
|
|
2603
|
+
for (let index = foldedUnits.length - 1; index >= 0; index -= 1) if (foldedUnits[index][0]?.kind !== "user") {
|
|
2604
|
+
lastContentUnit = index;
|
|
2605
|
+
break;
|
|
2606
|
+
}
|
|
2539
2607
|
const texts = [];
|
|
2540
|
-
for (let
|
|
2541
|
-
const
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
turn
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2608
|
+
for (let unitIndex = 0; unitIndex < foldedUnits.length; unitIndex += 1) {
|
|
2609
|
+
const unit = foldedUnits[unitIndex];
|
|
2610
|
+
const head = unit[0];
|
|
2611
|
+
let usage = addTokenUsage(carryUsage, unitUsage[unitIndex]);
|
|
2612
|
+
carryUsage = void 0;
|
|
2613
|
+
if (unitIndex === lastContentUnit) usage = addTokenUsage(usage, unclaimedUsage);
|
|
2614
|
+
const turn = head.turn;
|
|
2615
|
+
if (head.kind === "user") {
|
|
2616
|
+
if (userAlreadyAppended(childSession, turn, head.text)) continue;
|
|
2617
|
+
childSession.append("user/message", userEvent(head.text), { surfaceOp: "append" });
|
|
2618
|
+
texts.push(head.text);
|
|
2619
|
+
carryUsage = usage;
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
const toolLines = unit.filter((line) => line.kind === "tool");
|
|
2623
|
+
const reasoning = unit.filter((line) => line.kind === "think").map((line) => line.text).join("");
|
|
2624
|
+
const text = unit.filter((line) => line.kind === "assistant").map((line) => line.text).join("");
|
|
2625
|
+
const kinds = [...reasoning.trim() === "" ? [] : ["think"], ...text.trim() === "" ? [] : ["text"]];
|
|
2626
|
+
const pairing = kinds.length === 0 ? void 0 : options?.streams?.completeStreams(turn, kinds);
|
|
2627
|
+
const sealed = new Set(pairing?.sealed ?? []);
|
|
2628
|
+
const blocks = [];
|
|
2629
|
+
if (reasoning.trim() !== "" && !sealed.has("think")) blocks.push({
|
|
2630
|
+
type: "reasoning",
|
|
2631
|
+
text: reasoning
|
|
2561
2632
|
});
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2633
|
+
if (text.trim() !== "" && !sealed.has("text")) blocks.push({
|
|
2634
|
+
type: "text",
|
|
2635
|
+
text
|
|
2636
|
+
});
|
|
2637
|
+
for (const line of toolLines) blocks.push({
|
|
2638
|
+
type: "tool-call",
|
|
2639
|
+
id: line.id,
|
|
2640
|
+
name: line.name,
|
|
2641
|
+
arguments: line.args ?? ""
|
|
2642
|
+
});
|
|
2643
|
+
for (const line of unit) texts.push(kimiLineProgressText(line));
|
|
2644
|
+
if (blocks.length === 0) {
|
|
2645
|
+
carryUsage = usage;
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
for (const line of toolLines) options?.streams?.releaseTool?.(line);
|
|
2649
|
+
let step = pairing?.step ?? nextFoldStep(turn);
|
|
2650
|
+
options?.streams?.finalizeBefore?.(turn, step);
|
|
2651
|
+
let sequential = nextKimiSessionStep(childSession, turn);
|
|
2652
|
+
const reservedAfter = options?.streams?.reservedSteps(turn) ?? [];
|
|
2653
|
+
while (reservedAfter.includes(sequential)) sequential += 1;
|
|
2654
|
+
step = Math.min(step, sequential);
|
|
2655
|
+
markTaken(turn, step);
|
|
2656
|
+
step = nextWrittenKimiStep(childSession, turn);
|
|
2657
|
+
const requestConfig = unit.map((line) => line.kind !== "user" ? line.requestConfig : void 0).find((config) => config !== void 0);
|
|
2658
|
+
if (requestConfig?.model !== void 0) appendRequestHeaderIfChanged(childSession, {
|
|
2659
|
+
provider: "kimi-cli",
|
|
2660
|
+
model: requestConfig.model,
|
|
2661
|
+
...requestConfig.reasoningEffort === void 0 ? {} : { reasoningEffort: requestConfig.reasoningEffort },
|
|
2662
|
+
...requestConfig.maxTokens === void 0 ? {} : { maxTokens: requestConfig.maxTokens }
|
|
2663
|
+
});
|
|
2664
|
+
const model = [...unit].reverse().find((line) => line.model !== void 0)?.model;
|
|
2665
|
+
childSession.append("step/start", {
|
|
2666
|
+
turn,
|
|
2667
|
+
step
|
|
2668
|
+
});
|
|
2669
|
+
childSession.append("assistant/message", {
|
|
2670
|
+
turn,
|
|
2671
|
+
step,
|
|
2672
|
+
message: assistantEvent(blocks, model),
|
|
2673
|
+
stream: [],
|
|
2674
|
+
...usage === void 0 ? {} : { usage }
|
|
2675
|
+
}, { surfaceOp: "append" });
|
|
2676
|
+
for (const line of toolLines) {
|
|
2592
2677
|
const call = childSession.append("tool/call", {
|
|
2593
2678
|
turn,
|
|
2594
2679
|
step,
|
|
@@ -2596,75 +2681,35 @@ async function mirrorKimiSessionDelta(ctx, childSession, homeDir, kimiSessionId,
|
|
|
2596
2681
|
name: line.name,
|
|
2597
2682
|
arguments: line.args ?? ""
|
|
2598
2683
|
});
|
|
2599
|
-
|
|
2600
|
-
if (line.result !== void 0) childSession.append("tool/result", {
|
|
2684
|
+
childSession.append("tool/result", {
|
|
2601
2685
|
turn,
|
|
2602
2686
|
step,
|
|
2603
2687
|
message: createToolResultMessage({
|
|
2604
2688
|
callId: line.id,
|
|
2605
2689
|
content: [{
|
|
2606
2690
|
type: "text",
|
|
2607
|
-
text: line.result
|
|
2691
|
+
text: line.result ?? "(工具调用被中断,未能记录返回结果)"
|
|
2608
2692
|
}],
|
|
2609
|
-
isError:
|
|
2693
|
+
isError: line.result === void 0
|
|
2610
2694
|
})
|
|
2611
2695
|
}, {
|
|
2612
2696
|
surfaceOp: "append",
|
|
2613
2697
|
sourceEventSeqs: [call.seq]
|
|
2614
2698
|
});
|
|
2615
|
-
childSession.append("step/end", {
|
|
2616
|
-
turn,
|
|
2617
|
-
step
|
|
2618
|
-
});
|
|
2619
|
-
} else {
|
|
2620
|
-
const completion = options?.streams?.completeStream(line);
|
|
2621
|
-
if (completion !== void 0) {
|
|
2622
|
-
steps.set(turn, Math.max(steps.get(turn) ?? 1, completion.step + 1));
|
|
2623
|
-
if (!completion.opened) childSession.append("step/start", {
|
|
2624
|
-
turn,
|
|
2625
|
-
step: completion.step
|
|
2626
|
-
});
|
|
2627
|
-
if (index === lastAssistant && deltaUsage !== void 0) usageAttached = true;
|
|
2628
|
-
childSession.append("assistant/message", {
|
|
2629
|
-
turn,
|
|
2630
|
-
step: completion.step,
|
|
2631
|
-
message: assistantEvent(lineBlocks(line), line.model),
|
|
2632
|
-
stream: [],
|
|
2633
|
-
...index === lastAssistant && deltaUsage !== void 0 ? { usage: deltaUsage } : {}
|
|
2634
|
-
}, { surfaceOp: "append" });
|
|
2635
|
-
childSession.append("step/end", {
|
|
2636
|
-
turn,
|
|
2637
|
-
step: completion.step
|
|
2638
|
-
});
|
|
2639
|
-
texts.push(kimiLineProgressText(line));
|
|
2640
|
-
continue;
|
|
2641
|
-
}
|
|
2642
|
-
const step = nextFoldStep(turn);
|
|
2643
|
-
childSession.append("step/start", {
|
|
2644
|
-
turn,
|
|
2645
|
-
step
|
|
2646
|
-
});
|
|
2647
|
-
if (index === lastAssistant && deltaUsage !== void 0) usageAttached = true;
|
|
2648
|
-
childSession.append("assistant/message", {
|
|
2649
|
-
turn,
|
|
2650
|
-
step,
|
|
2651
|
-
message: assistantEvent(lineBlocks(line), line.model),
|
|
2652
|
-
stream: [],
|
|
2653
|
-
...index === lastAssistant && deltaUsage !== void 0 ? { usage: deltaUsage } : {}
|
|
2654
|
-
}, { surfaceOp: "append" });
|
|
2655
|
-
childSession.append("step/end", {
|
|
2656
|
-
turn,
|
|
2657
|
-
step
|
|
2658
|
-
});
|
|
2659
|
-
texts.push(kimiLineProgressText(line));
|
|
2660
2699
|
}
|
|
2700
|
+
childSession.append("step/end", {
|
|
2701
|
+
turn,
|
|
2702
|
+
step
|
|
2703
|
+
});
|
|
2661
2704
|
}
|
|
2662
2705
|
await persistChildSession(ctx, childSession);
|
|
2706
|
+
const remainder = addTokenUsage(carryUsage, lastContentUnit === -1 ? unclaimedUsage : void 0);
|
|
2663
2707
|
return {
|
|
2664
|
-
total:
|
|
2708
|
+
total: consumedTotal,
|
|
2665
2709
|
texts,
|
|
2666
2710
|
...deltaUsage !== void 0 ? { usage: deltaUsage } : {},
|
|
2667
|
-
...
|
|
2711
|
+
...deltaUsage !== void 0 && remainder === void 0 ? { usageAttached: true } : {},
|
|
2712
|
+
...remainder !== void 0 ? { usageUnattached: remainder } : {},
|
|
2668
2713
|
...observedModel !== void 0 ? { model: observedModel } : {},
|
|
2669
2714
|
...toolCalls !== void 0 ? { toolCalls } : {}
|
|
2670
2715
|
};
|
|
@@ -2693,6 +2738,22 @@ function roundToolCalls(lines, turn) {
|
|
|
2693
2738
|
};
|
|
2694
2739
|
}
|
|
2695
2740
|
/**
|
|
2741
|
+
* Write the fold's request/header when the wire's declared config differs from
|
|
2742
|
+
* the child log's last one (the host logs changed snapshots, not per-call
|
|
2743
|
+
* duplicates): first write is `initial`, a differing one `change`, an
|
|
2744
|
+
* identical one skipped.
|
|
2745
|
+
*/
|
|
2746
|
+
function appendRequestHeaderIfChanged(childSession, config) {
|
|
2747
|
+
const serialized = JSON.stringify(config);
|
|
2748
|
+
let last;
|
|
2749
|
+
for (const event of childSession.snapshotEvents()) if (event.type === "request/header") last = JSON.stringify(event.data.header?.config);
|
|
2750
|
+
if (last === serialized) return;
|
|
2751
|
+
childSession.append("request/header", {
|
|
2752
|
+
header: { config },
|
|
2753
|
+
reason: last === void 0 ? "initial" : "change"
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2696
2757
|
* Whether the turn already carries a user/message with this exact text (the
|
|
2697
2758
|
* live driver's round-start append). Scoped to the events at or after this
|
|
2698
2759
|
* turn's turn/start so identical prompts across rounds stay independent.
|
|
@@ -2727,11 +2788,14 @@ function userAlreadyAppended(childSession, turn, text) {
|
|
|
2727
2788
|
* throttled `mirrorKimiDelta` passes and the settle pass stays authoritative
|
|
2728
2789
|
* (one fold, one offset, zero divergence between the two drivers). Every
|
|
2729
2790
|
* transcript item folds 1:1 into the child session log.
|
|
2730
|
-
* Live output
|
|
2731
|
-
*
|
|
2732
|
-
*
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2791
|
+
* Live output rides the `local-agent/stream` channel: chunk deltas
|
|
2792
|
+
* accumulate per streaming item and publish throttled updates at the item's
|
|
2793
|
+
* reserved (turn, step) — the member live node renders them as-is — while
|
|
2794
|
+
* the item's durable content lands as ONE atomic step/start→…→step/end
|
|
2795
|
+
* triple at that step, written by the completing wire line's fold or at
|
|
2796
|
+
* settle. Durable steps stay dense and strictly sequential (the released-log
|
|
2797
|
+
* reader validates exactly that on every cold open); snapshots never hold a
|
|
2798
|
+
* step open across appends.
|
|
2735
2799
|
*
|
|
2736
2800
|
* Lifecycle mirrors the dsh/codex live drivers' discipline: lazy spawn with an
|
|
2737
2801
|
* initialize handshake (loadSession capability required — crash recovery is
|
|
@@ -3416,25 +3480,17 @@ var KimiAcpLiveDriver = class {
|
|
|
3416
3480
|
/** The round's accumulated assistant text (the run output — chunks are the only source). */
|
|
3417
3481
|
let roundText = "";
|
|
3418
3482
|
/**
|
|
3419
|
-
* The step ledger:
|
|
3420
|
-
*
|
|
3421
|
-
*
|
|
3422
|
-
*
|
|
3423
|
-
*
|
|
3424
|
-
*
|
|
3483
|
+
* The step ledger: the steps in-flight reservations currently hold this
|
|
3484
|
+
* round. A reservation's durable content lands at the fold that consumes
|
|
3485
|
+
* it (possibly carrying several reservations' kinds in one message — the
|
|
3486
|
+
* wire step's fold), which RELEASES the other consumed coordinates for
|
|
3487
|
+
* later folds; only a reservation kimi never completes (a plan stream, an
|
|
3488
|
+
* unwired tool notification) stays held until its settle-time finalize.
|
|
3489
|
+
* The file fold skips held steps for its sequential folds, so a tool card
|
|
3490
|
+
* folded mid-stream lands ABOVE the stream's step and the projection
|
|
3491
|
+
* renders the round's chronological order.
|
|
3425
3492
|
*/
|
|
3426
3493
|
const reservedSteps = [];
|
|
3427
|
-
/**
|
|
3428
|
-
* The streaming items seen this round . kimi's ACP
|
|
3429
|
-
* chunks carry no item id, so the stream key is synthetic per kind per
|
|
3430
|
-
* round (the codex live driver's fallback shape): thought chunks share
|
|
3431
|
-
* one stream, message chunks another. Deltas accumulate into throttled
|
|
3432
|
-
* snapshot assistant/messages appended at the stream's reserved
|
|
3433
|
-
* (turn, step) — the host folds repeated settles at one coordinate into
|
|
3434
|
-
* one live-updating chat node. The wire line that completes the stream's
|
|
3435
|
-
* kind folds at the same step and finalizes it; an entry whose completion
|
|
3436
|
-
* never folds is force-finalized at settle.
|
|
3437
|
-
*/
|
|
3438
3494
|
const streams = /* @__PURE__ */ new Map();
|
|
3439
3495
|
/** The stream the latest delta accumulated into (a kind switch force-snapshots it). */
|
|
3440
3496
|
let activeStream;
|
|
@@ -3462,7 +3518,23 @@ var KimiAcpLiveDriver = class {
|
|
|
3462
3518
|
};
|
|
3463
3519
|
/** A stream's synthetic key: kimi's ACP deltas carry no item id, so kind + turn pairs them. */
|
|
3464
3520
|
let generation = 0;
|
|
3521
|
+
/**
|
|
3522
|
+
* One step reservation per generation, shared by the think and text
|
|
3523
|
+
* streams of the same response segment: the host's own live shape is
|
|
3524
|
+
* reasoning + text on ONE step, and a per-kind step would leave the
|
|
3525
|
+
* consumed reservation's stream frames pointing at a step that never
|
|
3526
|
+
* materializes (an empty trailing step breaks the host's answer
|
|
3527
|
+
* detection, folding the reply into the collapsed process block).
|
|
3528
|
+
*/
|
|
3529
|
+
const generationSteps = /* @__PURE__ */ new Map();
|
|
3465
3530
|
const seenToolCalls = /* @__PURE__ */ new Set();
|
|
3531
|
+
/**
|
|
3532
|
+
* ACP tool_call notifications' chronological reservations (call id →
|
|
3533
|
+
* step, plus the notification's display title). No durable event lands at
|
|
3534
|
+
* notification time: the native lifecycle (advertisement, call, result in
|
|
3535
|
+
* one closed step) is written by the wire line's fold, or at settle with
|
|
3536
|
+
* an interrupted placeholder when kimi never logged the call.
|
|
3537
|
+
*/
|
|
3466
3538
|
const toolSteps = /* @__PURE__ */ new Map();
|
|
3467
3539
|
const streamKey = (kind) => `kimi-stream-${kind}-${turn}-${generation}`;
|
|
3468
3540
|
/** The plan stream's key: a snapshot stream beside the think/text delta streams. */
|
|
@@ -3480,7 +3552,7 @@ var KimiAcpLiveDriver = class {
|
|
|
3480
3552
|
const stream = streams.get(key);
|
|
3481
3553
|
if (stream !== void 0) {
|
|
3482
3554
|
stream.text += text;
|
|
3483
|
-
|
|
3555
|
+
publishStreamSnapshot(stream, false);
|
|
3484
3556
|
}
|
|
3485
3557
|
localAgent?.reportRunProgress(childSession.id, {
|
|
3486
3558
|
kind: "delta",
|
|
@@ -3506,135 +3578,283 @@ var KimiAcpLiveDriver = class {
|
|
|
3506
3578
|
const stream = streams.get(planKey);
|
|
3507
3579
|
if (stream !== void 0 && stream.text !== plan) {
|
|
3508
3580
|
stream.text = plan;
|
|
3509
|
-
|
|
3581
|
+
publishStreamSnapshot(stream, true);
|
|
3510
3582
|
}
|
|
3511
3583
|
};
|
|
3512
3584
|
/**
|
|
3513
|
-
*
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
3516
|
-
* legitimately) and, when `withUsage`, the round's uncarried usage. An
|
|
3517
|
-
* empty stream leaves no boundary at all.
|
|
3585
|
+
* The live-output channel. Snapshots are browser-only previews (never
|
|
3586
|
+
* `local-agent/stream` log events) — durable step content lands once per
|
|
3587
|
+
* stream, at finalization, as the fold's atomic triple.
|
|
3518
3588
|
*/
|
|
3519
3589
|
const streamPublisher = localAgent?.liveStreams === void 0 ? void 0 : new LiveStreamPublisher(localAgent.liveStreams, childSession, turn, persist, (error) => this.ctx.logger.warn(`live checkpoint failed: ${String(error)}`));
|
|
3520
3590
|
const liveFlush = new LiveFlush((error) => this.ctx.logger.warn(`live mirror flush failed: ${String(error)}`), this.config.snapshotMinIntervalMs ?? DEFAULT_SNAPSHOT_MIN_INTERVAL_MS);
|
|
3521
3591
|
const pendingStreamArrival = /* @__PURE__ */ new Map();
|
|
3522
|
-
|
|
3523
|
-
|
|
3592
|
+
/**
|
|
3593
|
+
* Publish one snapshot of a streaming item at its reserved (turn, step)
|
|
3594
|
+
* through the live channel. Batched per stream on a bounded deadline
|
|
3595
|
+
* unless `force`. Never writes durable content.
|
|
3596
|
+
*/
|
|
3597
|
+
const publishStreamSnapshot = (stream, force) => {
|
|
3598
|
+
if (stream.text.trim() === "" || stream.sealed === true) return;
|
|
3524
3599
|
const now = Date.now();
|
|
3525
3600
|
if (!force) {
|
|
3526
3601
|
if (!pendingStreamArrival.has(stream.step)) pendingStreamArrival.set(stream.step, now);
|
|
3527
|
-
liveFlush.schedule(stream.step, () =>
|
|
3602
|
+
liveFlush.schedule(stream.step, () => publishStreamSnapshot(stream, true));
|
|
3528
3603
|
return;
|
|
3529
3604
|
}
|
|
3530
3605
|
liveFlush.cancel(stream.step);
|
|
3531
3606
|
const receivedAt = pendingStreamArrival.get(stream.step) ?? now;
|
|
3532
3607
|
pendingStreamArrival.delete(stream.step);
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3608
|
+
streamPublisher?.preview(stream.step, stream.kind, stream.text, {
|
|
3609
|
+
receivedAt,
|
|
3610
|
+
itemId: stream.kind
|
|
3611
|
+
});
|
|
3612
|
+
stream.lastSnapshotLen = stream.text.length;
|
|
3613
|
+
};
|
|
3614
|
+
/**
|
|
3615
|
+
* Write the stream's durable content as ONE atomic
|
|
3616
|
+
* step/start→assistant/message→step/end triple at its reserved step and
|
|
3617
|
+
* seal the reservation (its completing wire line, when one exists, pairs
|
|
3618
|
+
* and drops). `interrupted` marks a non-completed round's partial (a
|
|
3619
|
+
* cancelled turn reads 已停止 legitimately); `withUsage` attaches the
|
|
3620
|
+
* round's uncarried usage.
|
|
3621
|
+
*/
|
|
3622
|
+
/**
|
|
3623
|
+
* Write the durable content of every stream sharing one step as ONE atomic
|
|
3624
|
+
* step/start→assistant/message→step/end triple (a generation's think and
|
|
3625
|
+
* text merge into a single message — the host's own shape) and seal the
|
|
3626
|
+
* reservations. `interrupted` marks a non-completed round's partial (a
|
|
3627
|
+
* cancelled turn reads 已停止 legitimately); `withUsage` attaches the
|
|
3628
|
+
* round's uncarried usage.
|
|
3629
|
+
*/
|
|
3630
|
+
const finalizeStep = (step, entries, interrupted, withUsage) => {
|
|
3631
|
+
liveFlush.cancel(step);
|
|
3632
|
+
pendingStreamArrival.delete(step);
|
|
3633
|
+
const reasoning = entries.filter(([, stream]) => stream.kind === "think").map(([, stream]) => stream.text).join("");
|
|
3634
|
+
const text = entries.filter(([, stream]) => stream.kind === "text").map(([, stream]) => stream.text).join("");
|
|
3635
|
+
const blocks = [];
|
|
3636
|
+
if (reasoning.trim() !== "") blocks.push({
|
|
3637
|
+
type: "reasoning",
|
|
3638
|
+
text: reasoning
|
|
3639
|
+
});
|
|
3640
|
+
if (text.trim() !== "") blocks.push({
|
|
3641
|
+
type: "text",
|
|
3642
|
+
text
|
|
3643
|
+
});
|
|
3644
|
+
if (blocks.length === 0) {
|
|
3645
|
+
streamPublisher?.finish(step);
|
|
3646
|
+
const keys = new Set(entries.map(([key]) => key));
|
|
3647
|
+
if (!new Set([...streams.entries()].filter(([key, stream]) => !keys.has(key) && stream.sealed !== true).map(([, stream]) => stream.step)).has(step)) {
|
|
3648
|
+
const index = reservedSteps.indexOf(step);
|
|
3649
|
+
if (index >= 0) reservedSteps.splice(index, 1);
|
|
3650
|
+
}
|
|
3651
|
+
for (const [key, stream] of entries) {
|
|
3652
|
+
stream.sealed = true;
|
|
3653
|
+
generationSteps.delete(stream.generation);
|
|
3654
|
+
if (activeStream === key) activeStream = void 0;
|
|
3540
3655
|
}
|
|
3541
|
-
streamPublisher.update(stream.step, stream.kind, stream.text, { receivedAt });
|
|
3542
|
-
stream.lastSnapshotAt = now;
|
|
3543
|
-
stream.lastSnapshotLen = stream.text.length;
|
|
3544
3656
|
return;
|
|
3545
3657
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
stream.opened = true;
|
|
3552
|
-
}
|
|
3658
|
+
const durable = nextWrittenKimiStep(childSession, turn);
|
|
3659
|
+
childSession.append("step/start", {
|
|
3660
|
+
turn,
|
|
3661
|
+
step: durable
|
|
3662
|
+
});
|
|
3553
3663
|
childSession.append("assistant/message", {
|
|
3554
3664
|
turn,
|
|
3555
|
-
step:
|
|
3556
|
-
message: assistantEvent(
|
|
3557
|
-
type: "reasoning",
|
|
3558
|
-
text: stream.text
|
|
3559
|
-
} : {
|
|
3560
|
-
type: "text",
|
|
3561
|
-
text: stream.text
|
|
3562
|
-
}], runtime?.modelConfiguration?.currentModel ?? roundModel),
|
|
3665
|
+
step: durable,
|
|
3666
|
+
message: assistantEvent(blocks, runtime?.modelConfiguration?.currentModel ?? roundModel),
|
|
3563
3667
|
stream: [],
|
|
3564
3668
|
...withUsage && roundUsage !== void 0 ? { usage: roundUsage } : {},
|
|
3565
3669
|
...interrupted ? { interrupted: true } : {}
|
|
3566
3670
|
}, { surfaceOp: "append" });
|
|
3567
3671
|
if (withUsage) roundUsage = void 0;
|
|
3568
|
-
|
|
3569
|
-
|
|
3672
|
+
streamPublisher?.finish(step);
|
|
3673
|
+
childSession.append("step/end", {
|
|
3674
|
+
turn,
|
|
3675
|
+
step: durable
|
|
3676
|
+
});
|
|
3677
|
+
for (const [key, stream] of entries) {
|
|
3678
|
+
stream.sealed = true;
|
|
3679
|
+
generationSteps.delete(stream.generation);
|
|
3680
|
+
if (activeStream === key) activeStream = void 0;
|
|
3681
|
+
}
|
|
3570
3682
|
persist();
|
|
3571
3683
|
};
|
|
3572
3684
|
/**
|
|
3685
|
+
* Write the durable step for an ACP tool call kimi never logged to the
|
|
3686
|
+
* wire (a crash between the notification and the wire flush): the
|
|
3687
|
+
* notification's title stands in for the name and the result is the
|
|
3688
|
+
* shared interrupted placeholder — the native lifecycle in one step.
|
|
3689
|
+
*/
|
|
3690
|
+
/** Whether this turn's log already carries the tool call (folded from the wire). */
|
|
3691
|
+
const toolLogged = (id) => childSession.snapshotEvents().some((event) => event.type === "tool/call" && event.data.turn === turn && event.data.callId === id);
|
|
3692
|
+
const finalizeOrphanTool = (id, entry) => {
|
|
3693
|
+
const name = entry.title ?? "tool";
|
|
3694
|
+
const step = nextWrittenKimiStep(childSession, turn);
|
|
3695
|
+
childSession.append("step/start", {
|
|
3696
|
+
turn,
|
|
3697
|
+
step
|
|
3698
|
+
});
|
|
3699
|
+
childSession.append("assistant/message", {
|
|
3700
|
+
turn,
|
|
3701
|
+
step,
|
|
3702
|
+
message: assistantEvent([{
|
|
3703
|
+
type: "tool-call",
|
|
3704
|
+
id,
|
|
3705
|
+
name,
|
|
3706
|
+
arguments: ""
|
|
3707
|
+
}], runtime?.modelConfiguration?.currentModel ?? roundModel),
|
|
3708
|
+
stream: []
|
|
3709
|
+
}, { surfaceOp: "append" });
|
|
3710
|
+
const call = childSession.append("tool/call", {
|
|
3711
|
+
turn,
|
|
3712
|
+
step,
|
|
3713
|
+
callId: id,
|
|
3714
|
+
name,
|
|
3715
|
+
arguments: ""
|
|
3716
|
+
});
|
|
3717
|
+
childSession.append("tool/result", {
|
|
3718
|
+
turn,
|
|
3719
|
+
step,
|
|
3720
|
+
message: createToolResultMessage({
|
|
3721
|
+
callId: id,
|
|
3722
|
+
content: [{
|
|
3723
|
+
type: "text",
|
|
3724
|
+
text: KIMI_INTERRUPTED_TOOL_RESULT
|
|
3725
|
+
}],
|
|
3726
|
+
isError: true
|
|
3727
|
+
})
|
|
3728
|
+
}, {
|
|
3729
|
+
surfaceOp: "append",
|
|
3730
|
+
sourceEventSeqs: [call.seq]
|
|
3731
|
+
});
|
|
3732
|
+
childSession.append("step/end", {
|
|
3733
|
+
turn,
|
|
3734
|
+
step
|
|
3735
|
+
});
|
|
3736
|
+
};
|
|
3737
|
+
/**
|
|
3738
|
+
* The fold's gap fill: a fold about to write at `step` first finalizes
|
|
3739
|
+
* every live stream reserved below it (ascending), so the stored log's
|
|
3740
|
+
* steps land dense and strictly sequential. Only streams can sit in the
|
|
3741
|
+
* gap: an unresulted tool line holds its fold instead (session-mirror).
|
|
3742
|
+
*/
|
|
3743
|
+
const finalizeBefore = (targetTurn, step) => {
|
|
3744
|
+
if (targetTurn !== turn) return;
|
|
3745
|
+
const pending = [...streams.entries()].filter(([, stream]) => stream.sealed !== true && stream.step < step).sort((a, b) => a[1].step - b[1].step);
|
|
3746
|
+
const byStep = /* @__PURE__ */ new Map();
|
|
3747
|
+
for (const entry of pending) {
|
|
3748
|
+
const group = byStep.get(entry[1].step) ?? [];
|
|
3749
|
+
group.push(entry);
|
|
3750
|
+
byStep.set(entry[1].step, group);
|
|
3751
|
+
}
|
|
3752
|
+
for (const [at, entries] of byStep) finalizeStep(at, entries, false, false);
|
|
3753
|
+
};
|
|
3754
|
+
/**
|
|
3573
3755
|
* Reserve the step one streaming item will occupy — past every folded
|
|
3574
3756
|
* line (the session's own event ledger: the file fold writes events
|
|
3575
|
-
* synchronously) and every earlier reservation. A kind switch
|
|
3576
|
-
* freshness snapshot of the current stream (skipped when its text has
|
|
3577
|
-
* grown since the last one); its
|
|
3578
|
-
* reserved step when the wire line
|
|
3757
|
+
* synchronously) and every earlier reservation. A kind switch publishes
|
|
3758
|
+
* one freshness snapshot of the current stream (skipped when its text has
|
|
3759
|
+
* not grown since the last one); its durable triple still lands at its
|
|
3760
|
+
* own reserved step when the wire line folds or the round settles.
|
|
3579
3761
|
*/
|
|
3580
3762
|
const reserveStream = (key, kind) => {
|
|
3581
3763
|
if (activeStream !== void 0 && activeStream !== key) {
|
|
3582
3764
|
const previous = streams.get(activeStream);
|
|
3583
|
-
if (previous !== void 0 && previous.text.length !== previous.lastSnapshotLen)
|
|
3765
|
+
if (previous !== void 0 && previous.text.length !== previous.lastSnapshotLen) publishStreamSnapshot(previous, true);
|
|
3584
3766
|
activeStream = void 0;
|
|
3585
3767
|
}
|
|
3586
3768
|
let stream = streams.get(key);
|
|
3769
|
+
if (stream?.sealed === true) {
|
|
3770
|
+
streams.delete(key);
|
|
3771
|
+
stream = void 0;
|
|
3772
|
+
}
|
|
3587
3773
|
if (stream === void 0) {
|
|
3588
|
-
let step =
|
|
3589
|
-
|
|
3590
|
-
|
|
3774
|
+
let step = key === planKey ? void 0 : generationSteps.get(generation);
|
|
3775
|
+
if (step === void 0) {
|
|
3776
|
+
step = nextKimiSessionStep(childSession, turn);
|
|
3777
|
+
while (reservedSteps.includes(step)) step += 1;
|
|
3778
|
+
reservedSteps.push(step);
|
|
3779
|
+
if (key !== planKey) generationSteps.set(generation, step);
|
|
3780
|
+
}
|
|
3591
3781
|
stream = {
|
|
3592
3782
|
kind,
|
|
3593
3783
|
step,
|
|
3784
|
+
generation: key === planKey ? -1 : generation,
|
|
3594
3785
|
text: "",
|
|
3595
|
-
|
|
3596
|
-
lastSnapshotLen: 0,
|
|
3597
|
-
opened: false
|
|
3786
|
+
lastSnapshotLen: 0
|
|
3598
3787
|
};
|
|
3599
3788
|
streams.set(key, stream);
|
|
3600
3789
|
}
|
|
3601
3790
|
activeStream = key;
|
|
3602
3791
|
};
|
|
3603
3792
|
/**
|
|
3604
|
-
* The fold-pass coordination surface
|
|
3605
|
-
* fold skips reserved steps for sequential folds
|
|
3606
|
-
*
|
|
3607
|
-
*
|
|
3793
|
+
* The fold-pass coordination surface: the file
|
|
3794
|
+
* fold skips reserved steps for sequential folds, pairs one fold with the
|
|
3795
|
+
* round's in-flight streams of the fold's kinds (consuming both — a wire
|
|
3796
|
+
* step's think and text ride ONE durable step), and closes every
|
|
3797
|
+
* reservation below each fold step first (finalizeBefore) — scoped to
|
|
3798
|
+
* THIS round's turn, so an earlier round's leftover lines never consume
|
|
3799
|
+
* this round's stream.
|
|
3608
3800
|
*/
|
|
3609
3801
|
const mirrorStreams = {
|
|
3610
3802
|
reservedSteps: (t) => t === turn ? reservedSteps : [],
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3803
|
+
finalizeBefore,
|
|
3804
|
+
releaseTool: (line) => {
|
|
3805
|
+
if (line.turn !== turn) return;
|
|
3806
|
+
const entry = toolSteps.get(line.id);
|
|
3807
|
+
if (entry === void 0) return;
|
|
3615
3808
|
toolSteps.delete(line.id);
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
opened: true
|
|
3619
|
-
};
|
|
3809
|
+
const index = reservedSteps.indexOf(entry.step);
|
|
3810
|
+
if (index >= 0) reservedSteps.splice(index, 1);
|
|
3620
3811
|
},
|
|
3621
|
-
|
|
3622
|
-
if (
|
|
3623
|
-
const
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3812
|
+
completeStreams: (t, kinds) => {
|
|
3813
|
+
if (t !== turn) return void 0;
|
|
3814
|
+
const picked = [];
|
|
3815
|
+
for (const kind of kinds) {
|
|
3816
|
+
const entry = [...streams.entries()].find(([key, stream]) => key !== planKey && stream.kind === kind);
|
|
3817
|
+
if (entry !== void 0) picked.push(entry);
|
|
3818
|
+
}
|
|
3819
|
+
if (picked.length === 0) return void 0;
|
|
3820
|
+
const sealed = [];
|
|
3821
|
+
for (const [key, stream] of picked) {
|
|
3822
|
+
streams.delete(key);
|
|
3823
|
+
generationSteps.delete(stream.generation);
|
|
3824
|
+
if (activeStream === key) activeStream = void 0;
|
|
3825
|
+
if (stream.sealed === true) {
|
|
3826
|
+
sealed.push(stream.kind);
|
|
3827
|
+
continue;
|
|
3828
|
+
}
|
|
3829
|
+
liveFlush.cancel(stream.step);
|
|
3830
|
+
}
|
|
3831
|
+
const open = picked.filter(([, stream]) => stream.sealed !== true);
|
|
3832
|
+
if (open.length === 0) return { sealed };
|
|
3833
|
+
const held = new Set([...streams.values()].filter((stream) => stream.sealed !== true).map((stream) => stream.step));
|
|
3834
|
+
for (const [, stream] of open) {
|
|
3835
|
+
if (held.has(stream.step)) continue;
|
|
3836
|
+
const index = reservedSteps.indexOf(stream.step);
|
|
3837
|
+
if (index >= 0) reservedSteps.splice(index, 1);
|
|
3838
|
+
}
|
|
3839
|
+
for (const step of new Set(open.map(([, stream]) => stream.step))) streamPublisher?.finish(step);
|
|
3840
|
+
const written = /* @__PURE__ */ new Set();
|
|
3841
|
+
for (const event of childSession.snapshotEvents()) {
|
|
3842
|
+
const data = event.data;
|
|
3843
|
+
if (event.type === "step/start" && data.turn === turn && typeof data.step === "number") written.add(data.step);
|
|
3844
|
+
}
|
|
3845
|
+
let free = nextKimiSessionStep(childSession, turn);
|
|
3846
|
+
while (reservedSteps.includes(free)) free += 1;
|
|
3847
|
+
const candidates = [...new Set(open.map(([, stream]) => stream.step))].filter((step) => !written.has(step));
|
|
3631
3848
|
return {
|
|
3632
|
-
step:
|
|
3633
|
-
|
|
3849
|
+
step: Math.min(free, ...candidates),
|
|
3850
|
+
sealed
|
|
3634
3851
|
};
|
|
3635
3852
|
}
|
|
3636
3853
|
};
|
|
3637
|
-
const mirrorOptions = () => ({
|
|
3854
|
+
const mirrorOptions = (settle = false) => ({
|
|
3855
|
+
streams: mirrorStreams,
|
|
3856
|
+
...settle ? { settle: true } : {}
|
|
3857
|
+
});
|
|
3638
3858
|
/**
|
|
3639
3859
|
* Observation accounting across mirror passes: a pass whose window
|
|
3640
3860
|
* attached its usage to a folded message is already carried; anything else
|
|
@@ -3643,7 +3863,7 @@ var KimiAcpLiveDriver = class {
|
|
|
3643
3863
|
* the latest.
|
|
3644
3864
|
*/
|
|
3645
3865
|
const noteMirrorUsage = (delta) => {
|
|
3646
|
-
if (delta.
|
|
3866
|
+
if (delta.usageUnattached !== void 0) roundUsage = addTokenUsage(roundUsage, delta.usageUnattached);
|
|
3647
3867
|
if (delta.model !== void 0) roundModel = delta.model;
|
|
3648
3868
|
};
|
|
3649
3869
|
/** Throttled push-triggered mirror pass; the file fold stays the only transcript source. */
|
|
@@ -3695,23 +3915,22 @@ var KimiAcpLiveDriver = class {
|
|
|
3695
3915
|
const stream = streams.get(key);
|
|
3696
3916
|
if (stream !== void 0) {
|
|
3697
3917
|
stream.text += text;
|
|
3698
|
-
|
|
3918
|
+
publishStreamSnapshot(stream, false);
|
|
3699
3919
|
}
|
|
3700
3920
|
}
|
|
3701
3921
|
} else if (kind === "tool_call") {
|
|
3702
3922
|
const id = typeof update["toolCallId"] === "string" ? update["toolCallId"] : void 0;
|
|
3703
3923
|
if (id === void 0 || !seenToolCalls.has(id)) {
|
|
3704
|
-
if (id !== void 0)
|
|
3924
|
+
if (id !== void 0 && toolLogged(id)) seenToolCalls.add(id);
|
|
3925
|
+
else if (id !== void 0) {
|
|
3705
3926
|
seenToolCalls.add(id);
|
|
3706
3927
|
let step = nextKimiSessionStep(childSession, turn);
|
|
3707
|
-
|
|
3928
|
+
while (reservedSteps.includes(step)) step += 1;
|
|
3708
3929
|
reservedSteps.push(step);
|
|
3709
|
-
toolSteps.set(id,
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
step
|
|
3930
|
+
toolSteps.set(id, {
|
|
3931
|
+
step,
|
|
3932
|
+
...typeof update["title"] === "string" ? { title: update["title"] } : {}
|
|
3713
3933
|
});
|
|
3714
|
-
persist();
|
|
3715
3934
|
}
|
|
3716
3935
|
generation++;
|
|
3717
3936
|
}
|
|
@@ -3781,7 +4000,7 @@ var KimiAcpLiveDriver = class {
|
|
|
3781
4000
|
let stableReads = 0;
|
|
3782
4001
|
const deadline = Date.now() + SETTLE_MIRROR_QUIESCE_MS;
|
|
3783
4002
|
for (;;) {
|
|
3784
|
-
const delta = await mirrorKimiDelta(this.ctx, childSession, spec.homeDir, sessionId, mirrorOptions());
|
|
4003
|
+
const delta = await mirrorKimiDelta(this.ctx, childSession, spec.homeDir, sessionId, mirrorOptions(true));
|
|
3785
4004
|
noteMirrorUsage(delta);
|
|
3786
4005
|
localAgent?.reportRunProgress(childSession.id, {
|
|
3787
4006
|
kind: "mirror",
|
|
@@ -3816,25 +4035,28 @@ var KimiAcpLiveDriver = class {
|
|
|
3816
4035
|
} catch (error) {
|
|
3817
4036
|
this.ctx.logger.warn(`subagent-kimi: live settle mirror failed: ${thrown$1(error).message}`);
|
|
3818
4037
|
}
|
|
3819
|
-
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
4038
|
+
const pending = [...[...[...streams.entries()].filter(([, stream]) => stream.sealed !== true).reduce((byStep, entry) => {
|
|
4039
|
+
const group = byStep.get(entry[1].step) ?? [];
|
|
4040
|
+
group.push(entry);
|
|
4041
|
+
byStep.set(entry[1].step, group);
|
|
4042
|
+
return byStep;
|
|
4043
|
+
}, /* @__PURE__ */ new Map()).entries()].map(([step, entries]) => ({
|
|
4044
|
+
step,
|
|
4045
|
+
entries
|
|
4046
|
+
})), ...[...toolSteps.entries()].map(([toolId, tool]) => ({
|
|
4047
|
+
step: tool.step,
|
|
4048
|
+
toolId,
|
|
4049
|
+
tool
|
|
4050
|
+
}))].sort((a, b) => a.step - b.step);
|
|
4051
|
+
const lastStream = pending.map((entry, index) => "entries" in entry ? index : -1).filter((index) => index >= 0).at(-1);
|
|
4052
|
+
pending.forEach((entry, index) => {
|
|
4053
|
+
if ("entries" in entry) finalizeStep(entry.step, entry.entries, settled.stopReason !== "completed", index === lastStream);
|
|
4054
|
+
else if (!toolLogged(entry.toolId)) finalizeOrphanTool(entry.toolId, entry.tool);
|
|
3836
4055
|
});
|
|
4056
|
+
streams.clear();
|
|
3837
4057
|
toolSteps.clear();
|
|
4058
|
+
activeStream = void 0;
|
|
4059
|
+
if (pending.length > 0) persist();
|
|
3838
4060
|
if (settled.stopReason === "completed") childSession.append("turn/end", {
|
|
3839
4061
|
turn,
|
|
3840
4062
|
reason: { kind: "completed" }
|
|
@@ -3867,7 +4089,16 @@ var KimiAcpLiveDriver = class {
|
|
|
3867
4089
|
});
|
|
3868
4090
|
result.then(() => {
|
|
3869
4091
|
if (turnOpened) {
|
|
3870
|
-
|
|
4092
|
+
let usage;
|
|
4093
|
+
for (const event of childSession.snapshotEvents()) {
|
|
4094
|
+
if (event.type !== "assistant/message") continue;
|
|
4095
|
+
const data = event.data;
|
|
4096
|
+
if (data.turn === turn && data.usage !== void 0) usage = addTokenUsage(usage, data.usage);
|
|
4097
|
+
}
|
|
4098
|
+
const round = {
|
|
4099
|
+
...roundModel === void 0 ? {} : { observedModel: roundModel },
|
|
4100
|
+
...usage === void 0 ? {} : { usage }
|
|
4101
|
+
};
|
|
3871
4102
|
localAgent?.recordRoundSettled?.(childSession.id, round);
|
|
3872
4103
|
}
|
|
3873
4104
|
this.armIdleTimer(String(childSession.id));
|
|
@@ -4433,7 +4664,10 @@ async function mirrorKimiAfterExit(spec, stderr) {
|
|
|
4433
4664
|
try {
|
|
4434
4665
|
const kimiSessionId = spec.resume?.cliSessionId ?? kimiSessionIdFromOutput(stderr);
|
|
4435
4666
|
if (spec.resume === void 0) spec.onCliSessionId?.(kimiSessionId);
|
|
4436
|
-
const delta = await mirrorKimiDelta(spec.ctx, spec.childSession, spec.homeDir, kimiSessionId, {
|
|
4667
|
+
const delta = await mirrorKimiDelta(spec.ctx, spec.childSession, spec.homeDir, kimiSessionId, {
|
|
4668
|
+
turn: spec.resume?.turn ?? 1,
|
|
4669
|
+
settle: true
|
|
4670
|
+
});
|
|
4437
4671
|
spec.ctx.get("localAgent")?.reportRunProgress(spec.childSession.id, {
|
|
4438
4672
|
kind: "mirror",
|
|
4439
4673
|
mirroredLines: delta.total
|
|
@@ -4599,29 +4833,6 @@ async function startKimiCliRun(request, spec) {
|
|
|
4599
4833
|
onAbort
|
|
4600
4834
|
}).then((settled) => {
|
|
4601
4835
|
if (settled.stopReason === "completed") spec.onAuthSuccess?.();
|
|
4602
|
-
if (spec.childSession !== void 0) {
|
|
4603
|
-
if (settled.stopReason === "completed") spec.childSession.append("turn/end", {
|
|
4604
|
-
turn,
|
|
4605
|
-
reason: { kind: "completed" }
|
|
4606
|
-
});
|
|
4607
|
-
else if (settled.stopReason === "aborted") spec.childSession.append("turn/end", {
|
|
4608
|
-
turn,
|
|
4609
|
-
reason: {
|
|
4610
|
-
kind: "aborted",
|
|
4611
|
-
reason: { kind: "parent" }
|
|
4612
|
-
}
|
|
4613
|
-
});
|
|
4614
|
-
else spec.childSession.append("turn/end", {
|
|
4615
|
-
turn,
|
|
4616
|
-
reason: {
|
|
4617
|
-
kind: "error",
|
|
4618
|
-
error: {
|
|
4619
|
-
message: "kimi -p exited before the run completed",
|
|
4620
|
-
code: "UNKNOWN"
|
|
4621
|
-
}
|
|
4622
|
-
}
|
|
4623
|
-
});
|
|
4624
|
-
}
|
|
4625
4836
|
return settled;
|
|
4626
4837
|
});
|
|
4627
4838
|
result.then(() => child.done).then(() => {
|
|
@@ -4630,14 +4841,41 @@ async function startKimiCliRun(request, spec) {
|
|
|
4630
4841
|
if (KIMI_AUTH_FAILURE.test(authDetail)) spec.onAuthFailure(authDetail.split("\n").find((line) => line.trim() !== "") ?? "auth failure");
|
|
4631
4842
|
}
|
|
4632
4843
|
enqueueMirror(async () => {
|
|
4633
|
-
const settled = await
|
|
4634
|
-
|
|
4844
|
+
const settled = await result;
|
|
4845
|
+
const mirrored = await mirrorKimiAfterExit(spec, stderr);
|
|
4846
|
+
addRoundUsage(mirrored?.usage);
|
|
4847
|
+
if (spec.childSession !== void 0) {
|
|
4848
|
+
if (settled.stopReason === "completed") spec.childSession.append("turn/end", {
|
|
4849
|
+
turn,
|
|
4850
|
+
reason: { kind: "completed" }
|
|
4851
|
+
});
|
|
4852
|
+
else if (settled.stopReason === "aborted") spec.childSession.append("turn/end", {
|
|
4853
|
+
turn,
|
|
4854
|
+
reason: {
|
|
4855
|
+
kind: "aborted",
|
|
4856
|
+
reason: { kind: "parent" }
|
|
4857
|
+
}
|
|
4858
|
+
});
|
|
4859
|
+
else spec.childSession.append("turn/end", {
|
|
4860
|
+
turn,
|
|
4861
|
+
reason: {
|
|
4862
|
+
kind: "error",
|
|
4863
|
+
error: {
|
|
4864
|
+
message: "kimi -p exited before the run completed",
|
|
4865
|
+
code: "UNKNOWN"
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
if (spec.ctx !== void 0) await persistChildSession(spec.ctx, spec.childSession).catch((error) => {
|
|
4870
|
+
spec.ctx?.logger.warn(`subagent-kimi: turn/end persistence failed: ${thrown(error).message}`);
|
|
4871
|
+
});
|
|
4872
|
+
}
|
|
4635
4873
|
const cliVersion = await spec.cliVersion?.();
|
|
4636
4874
|
spec.onRoundSettled?.({
|
|
4637
|
-
...
|
|
4875
|
+
...mirrored?.model === void 0 ? {} : { observedModel: mirrored.model },
|
|
4638
4876
|
...cliVersion === void 0 ? {} : { cliVersion },
|
|
4639
4877
|
...roundUsage === void 0 ? {} : { usage: roundUsage },
|
|
4640
|
-
...
|
|
4878
|
+
...mirrored?.toolCalls === void 0 ? {} : { toolCalls: mirrored.toolCalls }
|
|
4641
4879
|
});
|
|
4642
4880
|
});
|
|
4643
4881
|
}, () => {});
|
|
@@ -4790,6 +5028,47 @@ function dedupe(candidates) {
|
|
|
4790
5028
|
return out;
|
|
4791
5029
|
}
|
|
4792
5030
|
/**
|
|
5031
|
+
* Scope the served directory to the surface asking for it (the same helper
|
|
5032
|
+
* lives in each provider's broker — fold into the family core when the
|
|
5033
|
+
* directory faces next evolve):
|
|
5034
|
+
*
|
|
5035
|
+
* - memberless (the card's add-model dialog, the invite picker): the
|
|
5036
|
+
* endpoint's served catalog only — the configuration/history extras the
|
|
5037
|
+
* broker's info extension adds for STATUS display (a config-defined model
|
|
5038
|
+
* the endpoint does not serve) must never become add candidates;
|
|
5039
|
+
* - a member whose harness has a curated list: the list IS the vocabulary,
|
|
5040
|
+
* in the list's own order (the same rule `choices` already follows) — a
|
|
5041
|
+
* curated value the endpoint no longer serves stays as a bare row (the
|
|
5042
|
+
* card shows it the same way), and the member's current effective model is
|
|
5043
|
+
* kept visible as a bare entry when it sits outside the list, so a pre-list
|
|
5044
|
+
* binding never renders blank;
|
|
5045
|
+
* - a member without a curated list: the legacy extended vocabulary.
|
|
5046
|
+
*/
|
|
5047
|
+
function scopeServedDirectory(directory, opts) {
|
|
5048
|
+
if (opts.member && opts.curated.length === 0) return directory;
|
|
5049
|
+
const served = directory.entries.filter((entry) => entry.source === "native");
|
|
5050
|
+
if (!opts.member) return {
|
|
5051
|
+
...directory,
|
|
5052
|
+
entries: served
|
|
5053
|
+
};
|
|
5054
|
+
const byValue = new Map(served.flatMap((entry) => entry.resolvedModel === void 0 ? [[entry.value, entry]] : [[entry.value, entry], [entry.resolvedModel, entry]]));
|
|
5055
|
+
const entries = opts.curated.map((value) => byValue.get(value) ?? {
|
|
5056
|
+
value,
|
|
5057
|
+
label: value,
|
|
5058
|
+
source: "configuration"
|
|
5059
|
+
});
|
|
5060
|
+
const current = opts.current?.trim();
|
|
5061
|
+
if (current !== void 0 && current !== "" && !entries.some((entry) => entry.value === current || entry.resolvedModel === current)) entries.push({
|
|
5062
|
+
value: current,
|
|
5063
|
+
label: current,
|
|
5064
|
+
source: "configuration"
|
|
5065
|
+
});
|
|
5066
|
+
return {
|
|
5067
|
+
...directory,
|
|
5068
|
+
entries
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
/**
|
|
4793
5072
|
* The kimi harness's {@link LocalAgentModelBroker}. Also the provider-facing
|
|
4794
5073
|
* member-model ledger: the provider records each delegation's start model
|
|
4795
5074
|
* here and reads the session-level override for its exec-path resolution, so
|
|
@@ -4854,14 +5133,19 @@ var KimiModelBroker = class {
|
|
|
4854
5133
|
}
|
|
4855
5134
|
async modelDirectory(childSessionId, refresh = false) {
|
|
4856
5135
|
if (refresh) await this.deps.catalog?.refresh(childSessionId);
|
|
4857
|
-
|
|
5136
|
+
const info = await this.modelInfo(childSessionId);
|
|
5137
|
+
return scopeServedDirectory(info.directory ?? {
|
|
4858
5138
|
entries: [],
|
|
4859
5139
|
complete: false,
|
|
4860
5140
|
customInput: true,
|
|
4861
5141
|
status: "unsupported",
|
|
4862
5142
|
refreshing: false,
|
|
4863
5143
|
revision: 0
|
|
4864
|
-
}
|
|
5144
|
+
}, {
|
|
5145
|
+
member: childSessionId !== void 0,
|
|
5146
|
+
curated: this.deps.modelList?.(this.deps.homeDir(childSessionId)) ?? [],
|
|
5147
|
+
current: info.effective
|
|
5148
|
+
});
|
|
4865
5149
|
}
|
|
4866
5150
|
async *followModelDirectory(childSessionId, signal) {
|
|
4867
5151
|
if (this.deps.catalog === void 0) {
|
|
@@ -5069,7 +5353,7 @@ const inject = [
|
|
|
5069
5353
|
* the user layer exactly as before.
|
|
5070
5354
|
*/
|
|
5071
5355
|
const SETTINGS_FIELDS = {
|
|
5072
|
-
live: z.boolean().default(
|
|
5356
|
+
live: z.boolean().default(true),
|
|
5073
5357
|
liveMirrorGranularity: z.union([z.const("event"), z.const("token")]).default("token"),
|
|
5074
5358
|
model: z.string(),
|
|
5075
5359
|
recentModels: z.array(z.string()).default([])
|
|
@@ -5163,7 +5447,7 @@ function apply(ctx, config) {
|
|
|
5163
5447
|
const model = vol(config.model);
|
|
5164
5448
|
const recentModels = vol(config.recentModels);
|
|
5165
5449
|
return {
|
|
5166
|
-
live: vol(config.live) ??
|
|
5450
|
+
live: vol(config.live) ?? true,
|
|
5167
5451
|
liveMirrorGranularity: vol(config.liveMirrorGranularity) ?? "token",
|
|
5168
5452
|
...model === void 0 ? {} : { model },
|
|
5169
5453
|
...recentModels === void 0 ? {} : { recentModels }
|
|
@@ -5180,6 +5464,7 @@ function apply(ctx, config) {
|
|
|
5180
5464
|
}
|
|
5181
5465
|
const face = settingsFace(ctx, {
|
|
5182
5466
|
namespace: KIMI_SETTINGS_NAMESPACE,
|
|
5467
|
+
harness: "kimi",
|
|
5183
5468
|
legacySchema: KIMI_SETTINGS_SCHEMA,
|
|
5184
5469
|
legacyBase,
|
|
5185
5470
|
read: readSettings
|