newmark-agent 0.5.4 → 0.5.8
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/dist/conversation-utility-host.bundle.cjs +230 -66
- package/dist/core/agent.d.ts +1 -1
- package/dist/core/agent.js +55 -25
- package/dist/core/agentKernelRunner.js +55 -9
- package/dist/core/autoRouter.d.ts +1 -0
- package/dist/core/autoRouter.js +9 -0
- package/dist/core/conversationKernel.d.ts +12 -0
- package/dist/core/conversationKernel.js +32 -1
- package/dist/core/electronUtilityRuntimePool.js +7 -2
- package/dist/core/emptyResponseRetry.d.ts +16 -0
- package/dist/core/emptyResponseRetry.js +25 -0
- package/dist/core/terminalOutputBuffer.d.ts +24 -0
- package/dist/core/terminalOutputBuffer.js +92 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/wslAgentRuntimePool.js +6 -2
- package/dist/llm/provider.d.ts +4 -4
- package/dist/llm/provider.js +55 -21
- package/dist/main.js +46 -3
- package/dist/preload.js +1 -0
- package/dist/providers/chat-completions.adapter.js +17 -4
- package/dist/providers/provider-events.d.ts +13 -0
- package/dist/providers/provider-events.js +55 -4
- package/dist/providers/responses.adapter.js +23 -9
- package/dist/server.js +21 -0
- package/dist/ui/index.html +2415 -216
- package/dist/ui/lucide-sprite.svg +4 -0
- package/dist/wsl-agent-host.bundle.cjs +230 -66
- package/package.json +10 -3
|
@@ -220,6 +220,10 @@
|
|
|
220
220
|
<symbol id="minus" viewBox="0 0 24 24">
|
|
221
221
|
<path d="M5 12h14" />
|
|
222
222
|
</symbol>
|
|
223
|
+
<symbol id="mouse" viewBox="0 0 24 24">
|
|
224
|
+
<rect x="5" y="2" width="14" height="20" rx="7" />
|
|
225
|
+
<path d="M12 6v4" />
|
|
226
|
+
</symbol>
|
|
223
227
|
<symbol id="move" viewBox="0 0 24 24">
|
|
224
228
|
<path d="M12 2v20" />
|
|
225
229
|
<path d="m15 19-3 3-3-3" />
|
|
@@ -328674,7 +328674,7 @@ function providerStreamTimeoutError(timeoutMs) {
|
|
|
328674
328674
|
error.message = `Stream read timeout after ${timeoutMs}ms`;
|
|
328675
328675
|
return error;
|
|
328676
328676
|
}
|
|
328677
|
-
async function readProviderStreamChunk(reader, signal, timeoutMs =
|
|
328677
|
+
async function readProviderStreamChunk(reader, signal, timeoutMs = 0) {
|
|
328678
328678
|
if (signal.aborted) throw providerAbortError(signal);
|
|
328679
328679
|
let timer;
|
|
328680
328680
|
let onAbort;
|
|
@@ -328682,9 +328682,9 @@ async function readProviderStreamChunk(reader, signal, timeoutMs = 3e4) {
|
|
|
328682
328682
|
onAbort = () => reject(providerAbortError(signal));
|
|
328683
328683
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
328684
328684
|
});
|
|
328685
|
-
const timeoutPromise = new Promise((_3, reject) => {
|
|
328685
|
+
const timeoutPromise = timeoutMs > 0 ? new Promise((_3, reject) => {
|
|
328686
328686
|
timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
|
|
328687
|
-
});
|
|
328687
|
+
}) : new Promise(() => void 0);
|
|
328688
328688
|
try {
|
|
328689
328689
|
return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
|
|
328690
328690
|
} catch (error) {
|
|
@@ -328713,6 +328713,30 @@ function parseProviderSse(raw) {
|
|
|
328713
328713
|
}
|
|
328714
328714
|
return events;
|
|
328715
328715
|
}
|
|
328716
|
+
function assembleCompatibleToolArguments(parts) {
|
|
328717
|
+
const nonEmpty = (parts || []).map(String).filter((part) => part && part !== "null");
|
|
328718
|
+
if (!nonEmpty.length) return "{}";
|
|
328719
|
+
const isJsonObject = (value) => {
|
|
328720
|
+
try {
|
|
328721
|
+
const parsed = JSON.parse(value);
|
|
328722
|
+
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed);
|
|
328723
|
+
} catch {
|
|
328724
|
+
return false;
|
|
328725
|
+
}
|
|
328726
|
+
};
|
|
328727
|
+
const incremental = nonEmpty.join("");
|
|
328728
|
+
if (isJsonObject(incremental)) return incremental;
|
|
328729
|
+
let compatible = "";
|
|
328730
|
+
for (const incoming of nonEmpty) {
|
|
328731
|
+
if (!compatible) compatible = incoming;
|
|
328732
|
+
else if (incoming === compatible) continue;
|
|
328733
|
+
else if (incoming.startsWith(compatible)) compatible = incoming;
|
|
328734
|
+
else if (compatible.startsWith(incoming)) continue;
|
|
328735
|
+
else compatible += incoming;
|
|
328736
|
+
}
|
|
328737
|
+
if (isJsonObject(compatible)) return compatible;
|
|
328738
|
+
return [...nonEmpty].reverse().find(isJsonObject) || compatible;
|
|
328739
|
+
}
|
|
328716
328740
|
function isContentPolicyBlocked(json) {
|
|
328717
328741
|
const choices = Array.isArray(json.choices) ? json.choices : [];
|
|
328718
328742
|
const choice = choices[0] || {};
|
|
@@ -328941,6 +328965,8 @@ var ChatCompletionsAdapter = class {
|
|
|
328941
328965
|
let contentPolicyBlocked = false;
|
|
328942
328966
|
let emittedContent = false;
|
|
328943
328967
|
let emittedTool = false;
|
|
328968
|
+
let emittedReasoning = false;
|
|
328969
|
+
let explicitCompletion = false;
|
|
328944
328970
|
try {
|
|
328945
328971
|
while (true) {
|
|
328946
328972
|
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
@@ -328952,7 +328978,10 @@ var ChatCompletionsAdapter = class {
|
|
|
328952
328978
|
const trimmed = line.trim();
|
|
328953
328979
|
if (!trimmed.startsWith("data: ")) continue;
|
|
328954
328980
|
const data = trimmed.slice(6);
|
|
328955
|
-
if (data === "[DONE]")
|
|
328981
|
+
if (data === "[DONE]") {
|
|
328982
|
+
explicitCompletion = true;
|
|
328983
|
+
continue;
|
|
328984
|
+
}
|
|
328956
328985
|
let json;
|
|
328957
328986
|
try {
|
|
328958
328987
|
json = JSON.parse(data);
|
|
@@ -328965,11 +328994,16 @@ var ChatCompletionsAdapter = class {
|
|
|
328965
328994
|
}
|
|
328966
328995
|
if (isContentPolicyBlocked(json)) contentPolicyBlocked = true;
|
|
328967
328996
|
const choices = Array.isArray(json.choices) ? json.choices : [];
|
|
328968
|
-
const
|
|
328997
|
+
const choice = choices[0];
|
|
328998
|
+
if (choice?.finish_reason !== void 0 && choice.finish_reason !== null) explicitCompletion = true;
|
|
328999
|
+
const delta = choice?.delta;
|
|
328969
329000
|
if (!delta) continue;
|
|
328970
329001
|
if (delta.reasoning_content) {
|
|
328971
329002
|
const reasoning = this.extractText(delta.reasoning_content);
|
|
328972
|
-
if (reasoning)
|
|
329003
|
+
if (reasoning) {
|
|
329004
|
+
emittedReasoning = true;
|
|
329005
|
+
yield { type: "reasoning.summary.delta", delta: reasoning };
|
|
329006
|
+
}
|
|
328973
329007
|
}
|
|
328974
329008
|
const textDelta = this.extractText(delta.content);
|
|
328975
329009
|
if (textDelta) {
|
|
@@ -329014,13 +329048,17 @@ var ChatCompletionsAdapter = class {
|
|
|
329014
329048
|
type: "tool_call.completed",
|
|
329015
329049
|
id: currentToolCall.id,
|
|
329016
329050
|
name: currentToolCall.name,
|
|
329017
|
-
arguments: currentToolCall.argumentParts
|
|
329051
|
+
arguments: assembleCompatibleToolArguments(currentToolCall.argumentParts)
|
|
329018
329052
|
};
|
|
329019
329053
|
}
|
|
329020
329054
|
} else if (!emittedContent && !emittedTool && contentPolicyBlocked) {
|
|
329021
329055
|
yield { type: "response.failed", error: "[Error] Content policy refusal (content_filter)." };
|
|
329022
329056
|
return;
|
|
329023
329057
|
}
|
|
329058
|
+
if (!explicitCompletion && !emittedContent && !emittedTool && !emittedReasoning) {
|
|
329059
|
+
yield { type: "response.failed", error: "[LLM Error] Chat stream ended before an explicit completion." };
|
|
329060
|
+
return;
|
|
329061
|
+
}
|
|
329024
329062
|
yield { type: "response.completed" };
|
|
329025
329063
|
} finally {
|
|
329026
329064
|
reader.releaseLock();
|
|
@@ -329217,6 +329255,7 @@ var ResponsesAdapter = class {
|
|
|
329217
329255
|
const key3 = `${String(payload.item_id || "")}:${String(payload.summary_index || 0)}`;
|
|
329218
329256
|
const delta = this.extractText(payload.delta);
|
|
329219
329257
|
if (delta) {
|
|
329258
|
+
emittedContent = true;
|
|
329220
329259
|
reasoningSummaries.set(key3, (reasoningSummaries.get(key3) || "") + delta);
|
|
329221
329260
|
yield { type: "reasoning.summary.delta", delta };
|
|
329222
329261
|
}
|
|
@@ -329247,7 +329286,7 @@ var ResponsesAdapter = class {
|
|
|
329247
329286
|
calls.set(key3, {
|
|
329248
329287
|
id: String(item.call_id || item.id || key3),
|
|
329249
329288
|
name: String(item.name || ""),
|
|
329250
|
-
|
|
329289
|
+
argumentParts: item.arguments ? [String(item.arguments)] : [],
|
|
329251
329290
|
emitted: false
|
|
329252
329291
|
});
|
|
329253
329292
|
}
|
|
@@ -329255,9 +329294,9 @@ var ResponsesAdapter = class {
|
|
|
329255
329294
|
}
|
|
329256
329295
|
if (eventType === "response.function_call_arguments.delta") {
|
|
329257
329296
|
const key3 = String(payload.item_id || payload.call_id || payload.output_index || "");
|
|
329258
|
-
const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""),
|
|
329297
|
+
const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""), argumentParts: [], emitted: false };
|
|
329259
329298
|
const delta = String(payload.delta || "");
|
|
329260
|
-
call.
|
|
329299
|
+
if (delta) call.argumentParts.push(delta);
|
|
329261
329300
|
calls.set(key3, call);
|
|
329262
329301
|
yield { type: "tool_call.arguments.delta", id: call.id, delta };
|
|
329263
329302
|
continue;
|
|
@@ -329269,19 +329308,20 @@ var ResponsesAdapter = class {
|
|
|
329269
329308
|
const call = calls.get(key3) || {
|
|
329270
329309
|
id: String(item.call_id || item.id || key3),
|
|
329271
329310
|
name: String(item.name || ""),
|
|
329272
|
-
|
|
329311
|
+
argumentParts: item.arguments ? [String(item.arguments)] : [],
|
|
329273
329312
|
emitted: false
|
|
329274
329313
|
};
|
|
329275
329314
|
call.id = String(item.call_id || call.id);
|
|
329276
329315
|
call.name = String(item.name || call.name);
|
|
329277
|
-
|
|
329316
|
+
if (typeof item.arguments === "string" && item.arguments) call.argumentParts.push(item.arguments);
|
|
329278
329317
|
if (!call.emitted) {
|
|
329279
329318
|
call.emitted = true;
|
|
329319
|
+
const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
|
|
329280
329320
|
yield { type: "tool_call.started", id: call.id, name: call.name };
|
|
329281
|
-
if (
|
|
329282
|
-
yield { type: "tool_call.arguments.delta", id: call.id, delta:
|
|
329321
|
+
if (argumentsJson !== "{}") {
|
|
329322
|
+
yield { type: "tool_call.arguments.delta", id: call.id, delta: argumentsJson };
|
|
329283
329323
|
}
|
|
329284
|
-
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments:
|
|
329324
|
+
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
|
|
329285
329325
|
}
|
|
329286
329326
|
calls.set(key3, call);
|
|
329287
329327
|
}
|
|
@@ -329306,8 +329346,14 @@ var ResponsesAdapter = class {
|
|
|
329306
329346
|
} else if (!completed) {
|
|
329307
329347
|
yield { type: "response.failed", error: "[LLM Error] Responses stream ended before response.completed." };
|
|
329308
329348
|
} else if (!emittedContent && calls.size === 0) {
|
|
329309
|
-
yield { type: "response.failed", error: "[Error]
|
|
329349
|
+
yield { type: "response.failed", error: "[Error] Provider returned an empty response." };
|
|
329310
329350
|
} else {
|
|
329351
|
+
for (const call of calls.values()) {
|
|
329352
|
+
if (call.emitted) continue;
|
|
329353
|
+
const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
|
|
329354
|
+
yield { type: "tool_call.started", id: call.id, name: call.name };
|
|
329355
|
+
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
|
|
329356
|
+
}
|
|
329311
329357
|
yield { type: "response.completed" };
|
|
329312
329358
|
}
|
|
329313
329359
|
} finally {
|
|
@@ -330186,7 +330232,7 @@ ${responsePath}
|
|
|
330186
330232
|
* `provider_adapters_v2` context flag. Request serialization and SSE
|
|
330187
330233
|
* normalization are delegated to the shared provider adapters while the
|
|
330188
330234
|
* transport orchestration (loopback node-http, fetch -> node-http fallback,
|
|
330189
|
-
*
|
|
330235
|
+
* cancellation-only streaming and the 4xx Chat -> Responses downgrade) stay here.
|
|
330190
330236
|
* The emitted request body and StreamToken stream are byte-equivalent to
|
|
330191
330237
|
* the legacy inlined path.
|
|
330192
330238
|
*/
|
|
@@ -330323,9 +330369,9 @@ ${responsePath}
|
|
|
330323
330369
|
}
|
|
330324
330370
|
/**
|
|
330325
330371
|
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
330326
|
-
* requests retain the fetch-to-node fallback for transport failures
|
|
330327
|
-
*
|
|
330328
|
-
*
|
|
330372
|
+
* requests retain the fetch-to-node fallback for transport failures. They
|
|
330373
|
+
* have no response deadline; only caller cancellation or a concrete
|
|
330374
|
+
* transport/provider failure may end the request.
|
|
330329
330375
|
*/
|
|
330330
330376
|
buildProviderAdapterTransport() {
|
|
330331
330377
|
return async (request, signal) => {
|
|
@@ -330335,7 +330381,7 @@ ${responsePath}
|
|
|
330335
330381
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330336
330382
|
if (signal?.aborted) forwardAbort();
|
|
330337
330383
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330338
|
-
const effectiveTimeout =
|
|
330384
|
+
const effectiveTimeout = 0;
|
|
330339
330385
|
const timer = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
|
|
330340
330386
|
try {
|
|
330341
330387
|
try {
|
|
@@ -330480,7 +330526,7 @@ ${responsePath}
|
|
|
330480
330526
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330481
330527
|
if (signal?.aborted) forwardAbort();
|
|
330482
330528
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330483
|
-
const effectiveTimeout =
|
|
330529
|
+
const effectiveTimeout = 0;
|
|
330484
330530
|
const timeout = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
|
|
330485
330531
|
let reader = null;
|
|
330486
330532
|
try {
|
|
@@ -330512,10 +330558,14 @@ ${responsePath}
|
|
|
330512
330558
|
}
|
|
330513
330559
|
const decoder = new TextDecoder();
|
|
330514
330560
|
let buffer = "";
|
|
330515
|
-
|
|
330561
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
330562
|
+
const toolCallOrder = [];
|
|
330563
|
+
let syntheticToolIndex = 0;
|
|
330564
|
+
let lastToolIndex = 0;
|
|
330516
330565
|
let currentReasoningContent = "";
|
|
330517
330566
|
let contentPolicyBlocked = false;
|
|
330518
330567
|
let emittedContent = false;
|
|
330568
|
+
let explicitCompletion = false;
|
|
330519
330569
|
const streamSignal = signal || new AbortController().signal;
|
|
330520
330570
|
while (true) {
|
|
330521
330571
|
const { done, value } = await readProviderStreamChunk(reader, streamSignal);
|
|
@@ -330527,7 +330577,10 @@ ${responsePath}
|
|
|
330527
330577
|
const trimmed = line.trim();
|
|
330528
330578
|
if (!trimmed.startsWith("data: ")) continue;
|
|
330529
330579
|
const data = trimmed.slice(6);
|
|
330530
|
-
if (data === "[DONE]")
|
|
330580
|
+
if (data === "[DONE]") {
|
|
330581
|
+
explicitCompletion = true;
|
|
330582
|
+
continue;
|
|
330583
|
+
}
|
|
330531
330584
|
try {
|
|
330532
330585
|
const json = JSON.parse(data);
|
|
330533
330586
|
if (json.usage) yield { type: "usage", text: "", usage: extractProviderUsage(json) };
|
|
@@ -330545,24 +330598,44 @@ ${responsePath}
|
|
|
330545
330598
|
}
|
|
330546
330599
|
if (delta.tool_calls) {
|
|
330547
330600
|
for (const tc of delta.tool_calls) {
|
|
330548
|
-
|
|
330549
|
-
|
|
330550
|
-
|
|
330551
|
-
|
|
330552
|
-
|
|
330553
|
-
|
|
330554
|
-
|
|
330601
|
+
const rawIndex = Number(tc.index);
|
|
330602
|
+
const index = Number.isInteger(rawIndex) && rawIndex >= 0 ? rawIndex : tc.id ? syntheticToolIndex++ : lastToolIndex;
|
|
330603
|
+
lastToolIndex = index;
|
|
330604
|
+
let call = toolCalls.get(index);
|
|
330605
|
+
if (!call && (tc.id || tc.function?.name)) {
|
|
330606
|
+
call = { id: tc.id || "", name: tc.function?.name || "", argumentParts: [] };
|
|
330607
|
+
toolCalls.set(index, call);
|
|
330608
|
+
toolCallOrder.push(index);
|
|
330555
330609
|
}
|
|
330610
|
+
if (!call) continue;
|
|
330611
|
+
if (tc.id && !call.id) call.id = tc.id;
|
|
330612
|
+
if (tc.function?.name && !call.name) call.name = tc.function.name;
|
|
330613
|
+
if (tc.function?.arguments) call.argumentParts.push(tc.function.arguments);
|
|
330556
330614
|
}
|
|
330557
330615
|
}
|
|
330558
330616
|
} catch {
|
|
330559
330617
|
}
|
|
330560
330618
|
}
|
|
330561
330619
|
}
|
|
330562
|
-
if (
|
|
330563
|
-
|
|
330620
|
+
if (toolCallOrder.length) {
|
|
330621
|
+
for (const index of toolCallOrder) {
|
|
330622
|
+
const call = toolCalls.get(index);
|
|
330623
|
+
if (!call) continue;
|
|
330624
|
+
yield {
|
|
330625
|
+
type: "tool_call",
|
|
330626
|
+
text: "",
|
|
330627
|
+
toolCall: {
|
|
330628
|
+
id: call.id,
|
|
330629
|
+
name: call.name,
|
|
330630
|
+
arguments: assembleCompatibleToolArguments(call.argumentParts)
|
|
330631
|
+
},
|
|
330632
|
+
reasoningContent: currentReasoningContent || void 0
|
|
330633
|
+
};
|
|
330634
|
+
}
|
|
330564
330635
|
} else if (!emittedContent && contentPolicyBlocked) {
|
|
330565
330636
|
yield { type: "text", text: "[Error] Content policy refusal (content_filter)." };
|
|
330637
|
+
} else if (!explicitCompletion && !emittedContent && !currentReasoningContent) {
|
|
330638
|
+
yield { type: "text", text: "[LLM Error] GitHub Models stream ended before an explicit completion." };
|
|
330566
330639
|
}
|
|
330567
330640
|
} finally {
|
|
330568
330641
|
reader?.releaseLock();
|
|
@@ -340515,6 +340588,22 @@ function createToolchainCore() {
|
|
|
340515
340588
|
return { registry: new ToolRegistry(), catalog: new CapabilityCatalog() };
|
|
340516
340589
|
}
|
|
340517
340590
|
|
|
340591
|
+
// src/core/emptyResponseRetry.ts
|
|
340592
|
+
var EMPTY_RESPONSE_RETRY_DELAYS_MS = [200, 800, 2e3, 1e4, 6e4];
|
|
340593
|
+
var MAX_EMPTY_RESPONSE_RETRIES = EMPTY_RESPONSE_RETRY_DELAYS_MS.length;
|
|
340594
|
+
var MAX_CONSECUTIVE_EMPTY_RESPONSES = MAX_EMPTY_RESPONSE_RETRIES + 1;
|
|
340595
|
+
function emptyResponseRetryDelayMs(consecutiveEmptyResponses) {
|
|
340596
|
+
return EMPTY_RESPONSE_RETRY_DELAYS_MS[Math.max(0, consecutiveEmptyResponses - 1)] ?? 0;
|
|
340597
|
+
}
|
|
340598
|
+
function observeEmptyResponseOutcome(consecutiveEmptyResponses, emptyResponse) {
|
|
340599
|
+
const nextCount = emptyResponse ? consecutiveEmptyResponses + 1 : 0;
|
|
340600
|
+
return {
|
|
340601
|
+
consecutiveEmptyResponses: nextCount,
|
|
340602
|
+
retry: emptyResponse && nextCount <= MAX_EMPTY_RESPONSE_RETRIES,
|
|
340603
|
+
terminate: emptyResponse && nextCount > MAX_EMPTY_RESPONSE_RETRIES
|
|
340604
|
+
};
|
|
340605
|
+
}
|
|
340606
|
+
|
|
340518
340607
|
// src/core/agentKernelRunner.ts
|
|
340519
340608
|
var publicStreamFilters = /* @__PURE__ */ new WeakMap();
|
|
340520
340609
|
var brokerOnlyAssistantBuffers = /* @__PURE__ */ new WeakMap();
|
|
@@ -340666,7 +340755,7 @@ function kernelTurnFailed(agent, turn) {
|
|
|
340666
340755
|
return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
|
|
340667
340756
|
}
|
|
340668
340757
|
function providerTurnIsEmpty(turn) {
|
|
340669
|
-
return /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340758
|
+
return !turn.activity && /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340670
340759
|
${turn.text}`);
|
|
340671
340760
|
}
|
|
340672
340761
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
@@ -340675,6 +340764,12 @@ function removeTrailingFailedAssistant(agent, messages) {
|
|
|
340675
340764
|
const text = KernelMessageText(last);
|
|
340676
340765
|
if (last.stopReason === "error" || agent.isLlmErrorText(text)) messages.pop();
|
|
340677
340766
|
}
|
|
340767
|
+
function removeTrailingThoughtOnlyAssistant(messages) {
|
|
340768
|
+
const last = messages[messages.length - 1];
|
|
340769
|
+
if (last?.role !== "assistant") return;
|
|
340770
|
+
const hasToolCall = last.content.some((content) => content.type === "toolCall");
|
|
340771
|
+
if (!KernelMessageText(last).trim() && !hasToolCall) messages.pop();
|
|
340772
|
+
}
|
|
340678
340773
|
function normalizePublicProviderError(error, secrets = []) {
|
|
340679
340774
|
let raw = "";
|
|
340680
340775
|
if (error instanceof Error) {
|
|
@@ -340800,8 +340895,17 @@ async function runAgentKernel(agent) {
|
|
|
340800
340895
|
const tokens = [];
|
|
340801
340896
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
340802
340897
|
let lastAssistant = null;
|
|
340898
|
+
let observedActivity = false;
|
|
340899
|
+
let observedThought = false;
|
|
340803
340900
|
const unsubscribe = kernel2.subscribe(async (event) => {
|
|
340804
340901
|
await handleKernelEvent(agent, event, tokens);
|
|
340902
|
+
if (event.type === "message_update") {
|
|
340903
|
+
const delta = event.assistantMessageEvent;
|
|
340904
|
+
const deltaText = typeof delta.delta === "string" ? delta.delta : "";
|
|
340905
|
+
const thoughtDelta = delta.type === "thinking_delta" && !!deltaText.trim();
|
|
340906
|
+
observedThought = observedThought || thoughtDelta;
|
|
340907
|
+
observedActivity = observedActivity || thoughtDelta || delta.type === "text_delta" && !!deltaText.trim() || delta.type === "toolcall_end";
|
|
340908
|
+
}
|
|
340805
340909
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
340806
340910
|
lastAssistant = event.message;
|
|
340807
340911
|
}
|
|
@@ -340819,11 +340923,13 @@ async function runAgentKernel(agent) {
|
|
|
340819
340923
|
const assistant = lastAssistant;
|
|
340820
340924
|
const text = assistant ? KernelMessageText(assistant) : "";
|
|
340821
340925
|
const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
|
|
340822
|
-
const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
|
|
340926
|
+
const emptyResponse = !assistant || !text.trim() && !hasToolCall && !observedActivity && String(assistant?.stopReason || "") !== "aborted";
|
|
340823
340927
|
return {
|
|
340824
340928
|
text: emptyResponse ? "[Error] Provider returned an empty response." : text,
|
|
340825
340929
|
stopReason: String(assistant?.stopReason || ""),
|
|
340826
|
-
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
|
|
340930
|
+
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : "")),
|
|
340931
|
+
activity: observedActivity || !!text.trim() || hasToolCall,
|
|
340932
|
+
thoughtOnly: observedThought && !text.trim() && !hasToolCall && !["error", "aborted"].includes(String(assistant?.stopReason || ""))
|
|
340827
340933
|
};
|
|
340828
340934
|
} finally {
|
|
340829
340935
|
unsubscribe();
|
|
@@ -340861,16 +340967,30 @@ async function runAgentKernel(agent) {
|
|
|
340861
340967
|
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340862
340968
|
}
|
|
340863
340969
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340864
|
-
|
|
340970
|
+
const notice = `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.`;
|
|
340971
|
+
tokens.unshift({ type: "text", text: notice });
|
|
340972
|
+
agent.emitWorkEvent({
|
|
340973
|
+
type: "status",
|
|
340974
|
+
content: notice,
|
|
340975
|
+
fallback: { from: modelBeforeKernelRun, to: agent.model, providerId: agent.activeDeployment()?.providerId }
|
|
340976
|
+
});
|
|
340865
340977
|
}
|
|
340866
|
-
let
|
|
340867
|
-
|
|
340978
|
+
let consecutiveEmptyResponses = 0;
|
|
340979
|
+
for (; ; ) {
|
|
340980
|
+
const emptyResponseState = observeEmptyResponseOutcome(consecutiveEmptyResponses, providerTurnIsEmpty(lastTurn));
|
|
340981
|
+
consecutiveEmptyResponses = emptyResponseState.consecutiveEmptyResponses;
|
|
340982
|
+
if (lastTurn.thoughtOnly) {
|
|
340983
|
+
removeTrailingThoughtOnlyAssistant(kernel2.state.messages);
|
|
340984
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
340985
|
+
continue;
|
|
340986
|
+
}
|
|
340987
|
+
if (!emptyResponseState.retry) break;
|
|
340868
340988
|
removeTrailingFailedAssistant(agent, kernel2.state.messages);
|
|
340869
|
-
|
|
340870
|
-
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${
|
|
340989
|
+
const retryNumber = consecutiveEmptyResponses;
|
|
340990
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${retryNumber}/${MAX_EMPTY_RESPONSE_RETRIES}) after ${emptyResponseRetryDelayMs(consecutiveEmptyResponses)}ms.`;
|
|
340871
340991
|
tokens.push({ type: "text", text: notice });
|
|
340872
340992
|
agent.recordWorkStatus(notice);
|
|
340873
|
-
await agent.waitForPlannedRouteRetry();
|
|
340993
|
+
await agent.waitForPlannedRouteRetry(emptyResponseRetryDelayMs(consecutiveEmptyResponses));
|
|
340874
340994
|
lastTurn = await runWithCompressionResume([], false);
|
|
340875
340995
|
}
|
|
340876
340996
|
let routeRetries = 0;
|
|
@@ -340882,6 +341002,11 @@ async function runAgentKernel(agent) {
|
|
|
340882
341002
|
const notice = routeTransitionNotice(agent, previous);
|
|
340883
341003
|
tokens.push({ type: "text", text: notice });
|
|
340884
341004
|
agent.recordWorkStatus(notice);
|
|
341005
|
+
agent.emitWorkEvent({
|
|
341006
|
+
type: "status",
|
|
341007
|
+
content: notice,
|
|
341008
|
+
fallback: { from: previous, to: agent.model, providerId: agent.activeDeployment()?.providerId }
|
|
341009
|
+
});
|
|
340885
341010
|
kernel2.state.model = toKernelModel(agent);
|
|
340886
341011
|
const fallbackToolSurface = refreshToolSurface(true);
|
|
340887
341012
|
kernel2.state.systemPrompt = [agent.buildSystemPrompt(), fallbackToolSurface.systemPromptNotice].filter(Boolean).join("\n\n");
|
|
@@ -341061,7 +341186,7 @@ async function runAgentKernel(agent) {
|
|
|
341061
341186
|
return;
|
|
341062
341187
|
}
|
|
341063
341188
|
if (textStarted) finalContent.push({ type: "text", text });
|
|
341064
|
-
if (!finalContent.length) {
|
|
341189
|
+
if (!finalContent.length && !thinking.trim()) {
|
|
341065
341190
|
text = "[Error] Provider returned an empty response.";
|
|
341066
341191
|
finalContent.push({ type: "text", text });
|
|
341067
341192
|
}
|
|
@@ -343306,14 +343431,16 @@ var AutoRouter = class {
|
|
|
343306
343431
|
retryDelayMs
|
|
343307
343432
|
});
|
|
343308
343433
|
}
|
|
343434
|
+
if (failure.allowModelFallback === false) return attempts.slice(0, remainingAttempts);
|
|
343309
343435
|
const selection = decision.requestedSelection;
|
|
343310
343436
|
if (selection.kind === "fixed") return attempts.slice(0, remainingAttempts);
|
|
343311
343437
|
const scope = selection.kind === "auto" ? selection.scope : { kind: "provider", providerId: current.providerId };
|
|
343312
343438
|
const subset = selection.kind === "auto" ? selection.subset : void 0;
|
|
343313
343439
|
const currentGroup = current.logicalModelGroupId;
|
|
343440
|
+
const currentProviderId = current.providerId;
|
|
343314
343441
|
const now2 = this.now();
|
|
343315
343442
|
const attemptedDeployments = decision.attempts.map((attempt) => attempt.deployment);
|
|
343316
|
-
const eligible = candidates.filter((candidate) => candidate.enabled && inScope(candidate.deployment, scope) && inSubset(candidate.deployment, subset) && !sameDeployment(candidate.deployment, current) && !attemptedDeployments.some((attempted) => sameDeployment(candidate.deployment, attempted)) && validationEligible(candidate, now2).length === 0 && this.passedInitialHardFilters(decision, candidate) && this.circuitState(candidate.deployment, now2, false) !== "open");
|
|
343443
|
+
const eligible = candidates.filter((candidate) => candidate.enabled && candidate.deployment.providerId === currentProviderId && inScope(candidate.deployment, scope) && inSubset(candidate.deployment, subset) && !sameDeployment(candidate.deployment, current) && !attemptedDeployments.some((attempted) => sameDeployment(candidate.deployment, attempted)) && validationEligible(candidate, now2).length === 0 && this.passedInitialHardFilters(decision, candidate) && this.circuitState(candidate.deployment, now2, false) !== "open");
|
|
343317
343444
|
const equivalent = currentGroup ? eligible.find((candidate) => candidate.deployment.logicalModelGroupId === currentGroup && !candidate.fallbackOnly) : void 0;
|
|
343318
343445
|
const fallback = eligible.find((candidate) => candidate.fallbackOnly);
|
|
343319
343446
|
const rankedAlternates = decision.rankedCandidates.map((ranked) => eligible.find((candidate) => sameDeployment(candidate.deployment, ranked.deployment))).filter((candidate) => !!candidate && candidate !== equivalent && candidate !== fallback && !candidate.fallbackOnly);
|
|
@@ -345643,7 +345770,12 @@ var Agent4 = class _Agent {
|
|
|
345643
345770
|
beginRouteAttempt() {
|
|
345644
345771
|
this.routeAttemptStartedAt = Date.now();
|
|
345645
345772
|
}
|
|
345646
|
-
async waitForPlannedRouteRetry() {
|
|
345773
|
+
async waitForPlannedRouteRetry(explicitDelayMs) {
|
|
345774
|
+
if (explicitDelayMs !== void 0) {
|
|
345775
|
+
if (explicitDelayMs <= 0) return;
|
|
345776
|
+
await new Promise((resolve16) => setTimeout(resolve16, explicitDelayMs));
|
|
345777
|
+
return;
|
|
345778
|
+
}
|
|
345647
345779
|
const waitBudgetMs = Math.max(0, Math.min(15e3, this.lastRouteDecision?.retryBudgetMs ?? 5e3));
|
|
345648
345780
|
const delay = Math.max(0, Math.min(waitBudgetMs, this.lastRouteRetryDelayMs));
|
|
345649
345781
|
this.lastRouteRetryDelayMs = 0;
|
|
@@ -346751,7 +346883,8 @@ ${String(event.toolArgs || "")}`;
|
|
|
346751
346883
|
sequence,
|
|
346752
346884
|
status: input2.status,
|
|
346753
346885
|
guide: !isToolEvent && input2.guide ? this.normalizeGuideReceipt(input2.guide) : void 0,
|
|
346754
|
-
displayImage: isToolEvent ? this.hydrateDisplayImage(input2.displayImage) : void 0
|
|
346886
|
+
displayImage: isToolEvent ? this.hydrateDisplayImage(input2.displayImage) : void 0,
|
|
346887
|
+
fallback: input2.fallback
|
|
346755
346888
|
};
|
|
346756
346889
|
if (activeRun && this.isPersistablePublicWorkEvent(event)) {
|
|
346757
346890
|
activeRun.sequence = Number(sequence || activeRun.sequence + 1);
|
|
@@ -349670,9 +349803,15 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
349670
349803
|
}
|
|
349671
349804
|
compressionBuildBlockStart(messages) {
|
|
349672
349805
|
const activeRunId = this.currentWorkRunId();
|
|
349673
|
-
if (
|
|
349674
|
-
|
|
349675
|
-
|
|
349806
|
+
if (activeRunId) {
|
|
349807
|
+
const index = messages.findIndex((message) => String(message.run_id || message.runId || "") === activeRunId);
|
|
349808
|
+
if (index >= 0) return index;
|
|
349809
|
+
}
|
|
349810
|
+
let lastRunBoundary = -1;
|
|
349811
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
349812
|
+
if (String(messages[index]?.run_id || messages[index]?.runId || "")) lastRunBoundary = index + 1;
|
|
349813
|
+
}
|
|
349814
|
+
return lastRunBoundary >= 0 ? lastRunBoundary : messages.length;
|
|
349676
349815
|
}
|
|
349677
349816
|
recentContextSuffix(messages, maxMessages, tokenBudget) {
|
|
349678
349817
|
if (!messages.length) return [];
|
|
@@ -350601,17 +350740,12 @@ ${msg.content}
|
|
|
350601
350740
|
currentAttempt.sideEffectBoundary = this.routeSideEffectCommitted;
|
|
350602
350741
|
if (this.routeAttemptStartedAt > 0) currentAttempt.durationMs = Math.max(0, Date.now() - this.routeAttemptStartedAt);
|
|
350603
350742
|
}
|
|
350604
|
-
if (!fallbackEnabled) {
|
|
350605
|
-
this.lastRouteDecision.finalStatus = "failed";
|
|
350606
|
-
this.routeAttemptStartedAt = 0;
|
|
350607
|
-
this.persistRouteDecision(this.lastRouteDecision);
|
|
350608
|
-
return null;
|
|
350609
|
-
}
|
|
350610
350743
|
if (!this.pendingAutoAttempts.length) {
|
|
350611
350744
|
this.pendingAutoAttempts = this.autoRouter.planAttempts(this.lastRouteDecision, this.autoRouteCandidates(), {
|
|
350612
350745
|
error: failure,
|
|
350613
350746
|
streamCommitted: this.routeStreamCommitted,
|
|
350614
|
-
sideEffectCommitted: this.routeSideEffectCommitted
|
|
350747
|
+
sideEffectCommitted: this.routeSideEffectCommitted,
|
|
350748
|
+
allowModelFallback: fallbackEnabled
|
|
350615
350749
|
});
|
|
350616
350750
|
}
|
|
350617
350751
|
const next2 = this.pendingAutoAttempts.shift();
|
|
@@ -350646,7 +350780,6 @@ ${msg.content}
|
|
|
350646
350780
|
if (!next?.name) return null;
|
|
350647
350781
|
this.model = next.name;
|
|
350648
350782
|
this.fixedDeployment = this.deploymentRef(next);
|
|
350649
|
-
if (next.provider) this.config.set("models", "auto_switch_anchor_provider", next.provider);
|
|
350650
350783
|
return current;
|
|
350651
350784
|
}
|
|
350652
350785
|
isLlmErrorText(text) {
|
|
@@ -350654,12 +350787,10 @@ ${msg.content}
|
|
|
350654
350787
|
}
|
|
350655
350788
|
scopedSwitchModels(currentModelName) {
|
|
350656
350789
|
const all = this.config.allModels();
|
|
350657
|
-
|
|
350658
|
-
const
|
|
350659
|
-
|
|
350660
|
-
|
|
350661
|
-
const provider = this.config.findProvider(providerId);
|
|
350662
|
-
return all.filter((m2) => m2.provider_id === (provider?.id || providerId));
|
|
350790
|
+
const current = currentModelName === "auto" ? this.activeModelConfig() : this.config.findModel(currentModelName);
|
|
350791
|
+
const providerId = current?.provider_id || this.fixedDeployment?.providerId || (currentModelName !== "auto" ? this.activeDeployment()?.providerId : void 0);
|
|
350792
|
+
if (!providerId) return [];
|
|
350793
|
+
return all.filter((m2) => m2.provider_id === providerId);
|
|
350663
350794
|
}
|
|
350664
350795
|
async validateModels(selectedNames, options = {}) {
|
|
350665
350796
|
if (this.modelValidationPromise) return this.modelValidationPromise;
|
|
@@ -352041,7 +352172,10 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
|
|
|
352041
352172
|
if (!sa) return "[Subagent] Not found.";
|
|
352042
352173
|
const requestedModel = this.normalizeSubagentModelSelection(sa.model && sa.model !== "default" ? sa.model : this.modelSelectionValue() || this.model);
|
|
352043
352174
|
const requestedDeployment = parseDeploymentSelectionValue2(requestedModel);
|
|
352044
|
-
|
|
352175
|
+
let assignedModel = requestedModel === "auto" ? this.activeModelConfig() : requestedDeployment ? this.config.findDeployment(requestedDeployment) : this.config.findModel(requestedModel);
|
|
352176
|
+
if (!assignedModel && !requestedDeployment && requestedModel !== "auto") {
|
|
352177
|
+
assignedModel = this.activeModelConfig();
|
|
352178
|
+
}
|
|
352045
352179
|
const model = assignedModel?.name || (requestedModel === "auto" ? this.activeModelName() : requestedModel);
|
|
352046
352180
|
const activeModel = this.activeModelConfig();
|
|
352047
352181
|
const activeProvider = this.engineModel();
|
|
@@ -353470,6 +353604,32 @@ var ConversationKernel = class {
|
|
|
353470
353604
|
followUp: queued?.followUp.slice() || []
|
|
353471
353605
|
};
|
|
353472
353606
|
}
|
|
353607
|
+
/**
|
|
353608
|
+
* dev-0.5.6: 排队 followUp 消息 drain 时转成普通用户消息载荷。
|
|
353609
|
+
*
|
|
353610
|
+
* 入队时(enqueueSameSession)为去重/编辑保留了 clientMessageId,并把
|
|
353611
|
+
* runId 固定为入队时运行的 runId;若原样传给 process,写入 chatMessages
|
|
353612
|
+
* 的用户消息会携带旧 runId + clientMessageId,渲染端(PC renderTranscript /
|
|
353613
|
+
* 移动端 projectRemoteConversationItems)会把「clientMessageId + runId 命中
|
|
353614
|
+
* workRuns」的消息误判为 guide 消息而跳过气泡,导致排队自动发送的消息不
|
|
353615
|
+
* 显示在用户输入时间线。清除这两个字段后 process 走普通用户消息分支
|
|
353616
|
+
* (chatMessages.push,mode 取 visibleMode,runId 取当前 run)。
|
|
353617
|
+
*/
|
|
353618
|
+
drainQueuedFollowUpMessage(message) {
|
|
353619
|
+
if (typeof message === "string") return message;
|
|
353620
|
+
const text = String(message.text || "");
|
|
353621
|
+
const images = message.images;
|
|
353622
|
+
const attachments = message.attachments;
|
|
353623
|
+
const visible = message.visibleUserInput;
|
|
353624
|
+
const visibleMode = message.visibleMode;
|
|
353625
|
+
return {
|
|
353626
|
+
text,
|
|
353627
|
+
...images?.length ? { images } : {},
|
|
353628
|
+
...attachments?.length ? { attachments } : {},
|
|
353629
|
+
...visible ? { visibleUserInput: visible } : {},
|
|
353630
|
+
...visibleMode ? { visibleMode } : {}
|
|
353631
|
+
};
|
|
353632
|
+
}
|
|
353473
353633
|
queueItems(target) {
|
|
353474
353634
|
const runtime = this.findRuntime(target);
|
|
353475
353635
|
if (!runtime) return [];
|
|
@@ -354211,7 +354371,7 @@ ${batchText}`,
|
|
|
354211
354371
|
};
|
|
354212
354372
|
lastTokens = await this.runSingle(runtime, batchMessage, "steer");
|
|
354213
354373
|
} else {
|
|
354214
|
-
lastTokens = await this.runSingle(runtime, next.message, next.queueMode);
|
|
354374
|
+
lastTokens = await this.runSingle(runtime, this.drainQueuedFollowUpMessage(next.message), next.queueMode);
|
|
354215
354375
|
}
|
|
354216
354376
|
}
|
|
354217
354377
|
const rootMessage = runtime.runner.subagents.readRootInbox()[0];
|
|
@@ -354670,7 +354830,11 @@ ${text}`;
|
|
|
354670
354830
|
type: "queue_update",
|
|
354671
354831
|
content: "Conversation queue updated.",
|
|
354672
354832
|
conversationId: runtime.id,
|
|
354673
|
-
queue: this.queued(runtime.target)
|
|
354833
|
+
queue: this.queued(runtime.target),
|
|
354834
|
+
// Structured rows with stable kernel ids so every consumer (PC UI and
|
|
354835
|
+
// the paired mobile client) can render/edit/delete the same items.
|
|
354836
|
+
queueItems: this.queueItems(runtime.target),
|
|
354837
|
+
queuePaused: runtime.queuePaused === true
|
|
354674
354838
|
});
|
|
354675
354839
|
}
|
|
354676
354840
|
clearQueued(runtime) {
|