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
|
@@ -328670,7 +328670,7 @@ function providerStreamTimeoutError(timeoutMs) {
|
|
|
328670
328670
|
error.message = `Stream read timeout after ${timeoutMs}ms`;
|
|
328671
328671
|
return error;
|
|
328672
328672
|
}
|
|
328673
|
-
async function readProviderStreamChunk(reader, signal, timeoutMs =
|
|
328673
|
+
async function readProviderStreamChunk(reader, signal, timeoutMs = 0) {
|
|
328674
328674
|
if (signal.aborted) throw providerAbortError(signal);
|
|
328675
328675
|
let timer;
|
|
328676
328676
|
let onAbort;
|
|
@@ -328678,9 +328678,9 @@ async function readProviderStreamChunk(reader, signal, timeoutMs = 3e4) {
|
|
|
328678
328678
|
onAbort = () => reject(providerAbortError(signal));
|
|
328679
328679
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
328680
328680
|
});
|
|
328681
|
-
const timeoutPromise = new Promise((_3, reject) => {
|
|
328681
|
+
const timeoutPromise = timeoutMs > 0 ? new Promise((_3, reject) => {
|
|
328682
328682
|
timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
|
|
328683
|
-
});
|
|
328683
|
+
}) : new Promise(() => void 0);
|
|
328684
328684
|
try {
|
|
328685
328685
|
return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
|
|
328686
328686
|
} catch (error) {
|
|
@@ -328709,6 +328709,30 @@ function parseProviderSse(raw) {
|
|
|
328709
328709
|
}
|
|
328710
328710
|
return events;
|
|
328711
328711
|
}
|
|
328712
|
+
function assembleCompatibleToolArguments(parts) {
|
|
328713
|
+
const nonEmpty = (parts || []).map(String).filter((part) => part && part !== "null");
|
|
328714
|
+
if (!nonEmpty.length) return "{}";
|
|
328715
|
+
const isJsonObject = (value) => {
|
|
328716
|
+
try {
|
|
328717
|
+
const parsed = JSON.parse(value);
|
|
328718
|
+
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed);
|
|
328719
|
+
} catch {
|
|
328720
|
+
return false;
|
|
328721
|
+
}
|
|
328722
|
+
};
|
|
328723
|
+
const incremental = nonEmpty.join("");
|
|
328724
|
+
if (isJsonObject(incremental)) return incremental;
|
|
328725
|
+
let compatible = "";
|
|
328726
|
+
for (const incoming of nonEmpty) {
|
|
328727
|
+
if (!compatible) compatible = incoming;
|
|
328728
|
+
else if (incoming === compatible) continue;
|
|
328729
|
+
else if (incoming.startsWith(compatible)) compatible = incoming;
|
|
328730
|
+
else if (compatible.startsWith(incoming)) continue;
|
|
328731
|
+
else compatible += incoming;
|
|
328732
|
+
}
|
|
328733
|
+
if (isJsonObject(compatible)) return compatible;
|
|
328734
|
+
return [...nonEmpty].reverse().find(isJsonObject) || compatible;
|
|
328735
|
+
}
|
|
328712
328736
|
function isContentPolicyBlocked(json) {
|
|
328713
328737
|
const choices = Array.isArray(json.choices) ? json.choices : [];
|
|
328714
328738
|
const choice = choices[0] || {};
|
|
@@ -328937,6 +328961,8 @@ var ChatCompletionsAdapter = class {
|
|
|
328937
328961
|
let contentPolicyBlocked = false;
|
|
328938
328962
|
let emittedContent = false;
|
|
328939
328963
|
let emittedTool = false;
|
|
328964
|
+
let emittedReasoning = false;
|
|
328965
|
+
let explicitCompletion = false;
|
|
328940
328966
|
try {
|
|
328941
328967
|
while (true) {
|
|
328942
328968
|
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
@@ -328948,7 +328974,10 @@ var ChatCompletionsAdapter = class {
|
|
|
328948
328974
|
const trimmed = line.trim();
|
|
328949
328975
|
if (!trimmed.startsWith("data: ")) continue;
|
|
328950
328976
|
const data = trimmed.slice(6);
|
|
328951
|
-
if (data === "[DONE]")
|
|
328977
|
+
if (data === "[DONE]") {
|
|
328978
|
+
explicitCompletion = true;
|
|
328979
|
+
continue;
|
|
328980
|
+
}
|
|
328952
328981
|
let json;
|
|
328953
328982
|
try {
|
|
328954
328983
|
json = JSON.parse(data);
|
|
@@ -328961,11 +328990,16 @@ var ChatCompletionsAdapter = class {
|
|
|
328961
328990
|
}
|
|
328962
328991
|
if (isContentPolicyBlocked(json)) contentPolicyBlocked = true;
|
|
328963
328992
|
const choices = Array.isArray(json.choices) ? json.choices : [];
|
|
328964
|
-
const
|
|
328993
|
+
const choice = choices[0];
|
|
328994
|
+
if (choice?.finish_reason !== void 0 && choice.finish_reason !== null) explicitCompletion = true;
|
|
328995
|
+
const delta = choice?.delta;
|
|
328965
328996
|
if (!delta) continue;
|
|
328966
328997
|
if (delta.reasoning_content) {
|
|
328967
328998
|
const reasoning = this.extractText(delta.reasoning_content);
|
|
328968
|
-
if (reasoning)
|
|
328999
|
+
if (reasoning) {
|
|
329000
|
+
emittedReasoning = true;
|
|
329001
|
+
yield { type: "reasoning.summary.delta", delta: reasoning };
|
|
329002
|
+
}
|
|
328969
329003
|
}
|
|
328970
329004
|
const textDelta = this.extractText(delta.content);
|
|
328971
329005
|
if (textDelta) {
|
|
@@ -329010,13 +329044,17 @@ var ChatCompletionsAdapter = class {
|
|
|
329010
329044
|
type: "tool_call.completed",
|
|
329011
329045
|
id: currentToolCall.id,
|
|
329012
329046
|
name: currentToolCall.name,
|
|
329013
|
-
arguments: currentToolCall.argumentParts
|
|
329047
|
+
arguments: assembleCompatibleToolArguments(currentToolCall.argumentParts)
|
|
329014
329048
|
};
|
|
329015
329049
|
}
|
|
329016
329050
|
} else if (!emittedContent && !emittedTool && contentPolicyBlocked) {
|
|
329017
329051
|
yield { type: "response.failed", error: "[Error] Content policy refusal (content_filter)." };
|
|
329018
329052
|
return;
|
|
329019
329053
|
}
|
|
329054
|
+
if (!explicitCompletion && !emittedContent && !emittedTool && !emittedReasoning) {
|
|
329055
|
+
yield { type: "response.failed", error: "[LLM Error] Chat stream ended before an explicit completion." };
|
|
329056
|
+
return;
|
|
329057
|
+
}
|
|
329020
329058
|
yield { type: "response.completed" };
|
|
329021
329059
|
} finally {
|
|
329022
329060
|
reader.releaseLock();
|
|
@@ -329213,6 +329251,7 @@ var ResponsesAdapter = class {
|
|
|
329213
329251
|
const key3 = `${String(payload.item_id || "")}:${String(payload.summary_index || 0)}`;
|
|
329214
329252
|
const delta = this.extractText(payload.delta);
|
|
329215
329253
|
if (delta) {
|
|
329254
|
+
emittedContent = true;
|
|
329216
329255
|
reasoningSummaries.set(key3, (reasoningSummaries.get(key3) || "") + delta);
|
|
329217
329256
|
yield { type: "reasoning.summary.delta", delta };
|
|
329218
329257
|
}
|
|
@@ -329243,7 +329282,7 @@ var ResponsesAdapter = class {
|
|
|
329243
329282
|
calls.set(key3, {
|
|
329244
329283
|
id: String(item.call_id || item.id || key3),
|
|
329245
329284
|
name: String(item.name || ""),
|
|
329246
|
-
|
|
329285
|
+
argumentParts: item.arguments ? [String(item.arguments)] : [],
|
|
329247
329286
|
emitted: false
|
|
329248
329287
|
});
|
|
329249
329288
|
}
|
|
@@ -329251,9 +329290,9 @@ var ResponsesAdapter = class {
|
|
|
329251
329290
|
}
|
|
329252
329291
|
if (eventType === "response.function_call_arguments.delta") {
|
|
329253
329292
|
const key3 = String(payload.item_id || payload.call_id || payload.output_index || "");
|
|
329254
|
-
const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""),
|
|
329293
|
+
const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""), argumentParts: [], emitted: false };
|
|
329255
329294
|
const delta = String(payload.delta || "");
|
|
329256
|
-
call.
|
|
329295
|
+
if (delta) call.argumentParts.push(delta);
|
|
329257
329296
|
calls.set(key3, call);
|
|
329258
329297
|
yield { type: "tool_call.arguments.delta", id: call.id, delta };
|
|
329259
329298
|
continue;
|
|
@@ -329265,19 +329304,20 @@ var ResponsesAdapter = class {
|
|
|
329265
329304
|
const call = calls.get(key3) || {
|
|
329266
329305
|
id: String(item.call_id || item.id || key3),
|
|
329267
329306
|
name: String(item.name || ""),
|
|
329268
|
-
|
|
329307
|
+
argumentParts: item.arguments ? [String(item.arguments)] : [],
|
|
329269
329308
|
emitted: false
|
|
329270
329309
|
};
|
|
329271
329310
|
call.id = String(item.call_id || call.id);
|
|
329272
329311
|
call.name = String(item.name || call.name);
|
|
329273
|
-
|
|
329312
|
+
if (typeof item.arguments === "string" && item.arguments) call.argumentParts.push(item.arguments);
|
|
329274
329313
|
if (!call.emitted) {
|
|
329275
329314
|
call.emitted = true;
|
|
329315
|
+
const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
|
|
329276
329316
|
yield { type: "tool_call.started", id: call.id, name: call.name };
|
|
329277
|
-
if (
|
|
329278
|
-
yield { type: "tool_call.arguments.delta", id: call.id, delta:
|
|
329317
|
+
if (argumentsJson !== "{}") {
|
|
329318
|
+
yield { type: "tool_call.arguments.delta", id: call.id, delta: argumentsJson };
|
|
329279
329319
|
}
|
|
329280
|
-
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments:
|
|
329320
|
+
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
|
|
329281
329321
|
}
|
|
329282
329322
|
calls.set(key3, call);
|
|
329283
329323
|
}
|
|
@@ -329302,8 +329342,14 @@ var ResponsesAdapter = class {
|
|
|
329302
329342
|
} else if (!completed) {
|
|
329303
329343
|
yield { type: "response.failed", error: "[LLM Error] Responses stream ended before response.completed." };
|
|
329304
329344
|
} else if (!emittedContent && calls.size === 0) {
|
|
329305
|
-
yield { type: "response.failed", error: "[Error]
|
|
329345
|
+
yield { type: "response.failed", error: "[Error] Provider returned an empty response." };
|
|
329306
329346
|
} else {
|
|
329347
|
+
for (const call of calls.values()) {
|
|
329348
|
+
if (call.emitted) continue;
|
|
329349
|
+
const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
|
|
329350
|
+
yield { type: "tool_call.started", id: call.id, name: call.name };
|
|
329351
|
+
yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
|
|
329352
|
+
}
|
|
329307
329353
|
yield { type: "response.completed" };
|
|
329308
329354
|
}
|
|
329309
329355
|
} finally {
|
|
@@ -330182,7 +330228,7 @@ ${responsePath}
|
|
|
330182
330228
|
* `provider_adapters_v2` context flag. Request serialization and SSE
|
|
330183
330229
|
* normalization are delegated to the shared provider adapters while the
|
|
330184
330230
|
* transport orchestration (loopback node-http, fetch -> node-http fallback,
|
|
330185
|
-
*
|
|
330231
|
+
* cancellation-only streaming and the 4xx Chat -> Responses downgrade) stay here.
|
|
330186
330232
|
* The emitted request body and StreamToken stream are byte-equivalent to
|
|
330187
330233
|
* the legacy inlined path.
|
|
330188
330234
|
*/
|
|
@@ -330319,9 +330365,9 @@ ${responsePath}
|
|
|
330319
330365
|
}
|
|
330320
330366
|
/**
|
|
330321
330367
|
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
330322
|
-
* requests retain the fetch-to-node fallback for transport failures
|
|
330323
|
-
*
|
|
330324
|
-
*
|
|
330368
|
+
* requests retain the fetch-to-node fallback for transport failures. They
|
|
330369
|
+
* have no response deadline; only caller cancellation or a concrete
|
|
330370
|
+
* transport/provider failure may end the request.
|
|
330325
330371
|
*/
|
|
330326
330372
|
buildProviderAdapterTransport() {
|
|
330327
330373
|
return async (request, signal) => {
|
|
@@ -330331,7 +330377,7 @@ ${responsePath}
|
|
|
330331
330377
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330332
330378
|
if (signal?.aborted) forwardAbort();
|
|
330333
330379
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330334
|
-
const effectiveTimeout =
|
|
330380
|
+
const effectiveTimeout = 0;
|
|
330335
330381
|
const timer = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
|
|
330336
330382
|
try {
|
|
330337
330383
|
try {
|
|
@@ -330476,7 +330522,7 @@ ${responsePath}
|
|
|
330476
330522
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330477
330523
|
if (signal?.aborted) forwardAbort();
|
|
330478
330524
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330479
|
-
const effectiveTimeout =
|
|
330525
|
+
const effectiveTimeout = 0;
|
|
330480
330526
|
const timeout = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
|
|
330481
330527
|
let reader = null;
|
|
330482
330528
|
try {
|
|
@@ -330508,10 +330554,14 @@ ${responsePath}
|
|
|
330508
330554
|
}
|
|
330509
330555
|
const decoder = new TextDecoder();
|
|
330510
330556
|
let buffer = "";
|
|
330511
|
-
|
|
330557
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
330558
|
+
const toolCallOrder = [];
|
|
330559
|
+
let syntheticToolIndex = 0;
|
|
330560
|
+
let lastToolIndex = 0;
|
|
330512
330561
|
let currentReasoningContent = "";
|
|
330513
330562
|
let contentPolicyBlocked = false;
|
|
330514
330563
|
let emittedContent = false;
|
|
330564
|
+
let explicitCompletion = false;
|
|
330515
330565
|
const streamSignal = signal || new AbortController().signal;
|
|
330516
330566
|
while (true) {
|
|
330517
330567
|
const { done, value } = await readProviderStreamChunk(reader, streamSignal);
|
|
@@ -330523,7 +330573,10 @@ ${responsePath}
|
|
|
330523
330573
|
const trimmed = line.trim();
|
|
330524
330574
|
if (!trimmed.startsWith("data: ")) continue;
|
|
330525
330575
|
const data = trimmed.slice(6);
|
|
330526
|
-
if (data === "[DONE]")
|
|
330576
|
+
if (data === "[DONE]") {
|
|
330577
|
+
explicitCompletion = true;
|
|
330578
|
+
continue;
|
|
330579
|
+
}
|
|
330527
330580
|
try {
|
|
330528
330581
|
const json = JSON.parse(data);
|
|
330529
330582
|
if (json.usage) yield { type: "usage", text: "", usage: extractProviderUsage(json) };
|
|
@@ -330541,24 +330594,44 @@ ${responsePath}
|
|
|
330541
330594
|
}
|
|
330542
330595
|
if (delta.tool_calls) {
|
|
330543
330596
|
for (const tc of delta.tool_calls) {
|
|
330544
|
-
|
|
330545
|
-
|
|
330546
|
-
|
|
330547
|
-
|
|
330548
|
-
|
|
330549
|
-
|
|
330550
|
-
|
|
330597
|
+
const rawIndex = Number(tc.index);
|
|
330598
|
+
const index = Number.isInteger(rawIndex) && rawIndex >= 0 ? rawIndex : tc.id ? syntheticToolIndex++ : lastToolIndex;
|
|
330599
|
+
lastToolIndex = index;
|
|
330600
|
+
let call = toolCalls.get(index);
|
|
330601
|
+
if (!call && (tc.id || tc.function?.name)) {
|
|
330602
|
+
call = { id: tc.id || "", name: tc.function?.name || "", argumentParts: [] };
|
|
330603
|
+
toolCalls.set(index, call);
|
|
330604
|
+
toolCallOrder.push(index);
|
|
330551
330605
|
}
|
|
330606
|
+
if (!call) continue;
|
|
330607
|
+
if (tc.id && !call.id) call.id = tc.id;
|
|
330608
|
+
if (tc.function?.name && !call.name) call.name = tc.function.name;
|
|
330609
|
+
if (tc.function?.arguments) call.argumentParts.push(tc.function.arguments);
|
|
330552
330610
|
}
|
|
330553
330611
|
}
|
|
330554
330612
|
} catch {
|
|
330555
330613
|
}
|
|
330556
330614
|
}
|
|
330557
330615
|
}
|
|
330558
|
-
if (
|
|
330559
|
-
|
|
330616
|
+
if (toolCallOrder.length) {
|
|
330617
|
+
for (const index of toolCallOrder) {
|
|
330618
|
+
const call = toolCalls.get(index);
|
|
330619
|
+
if (!call) continue;
|
|
330620
|
+
yield {
|
|
330621
|
+
type: "tool_call",
|
|
330622
|
+
text: "",
|
|
330623
|
+
toolCall: {
|
|
330624
|
+
id: call.id,
|
|
330625
|
+
name: call.name,
|
|
330626
|
+
arguments: assembleCompatibleToolArguments(call.argumentParts)
|
|
330627
|
+
},
|
|
330628
|
+
reasoningContent: currentReasoningContent || void 0
|
|
330629
|
+
};
|
|
330630
|
+
}
|
|
330560
330631
|
} else if (!emittedContent && contentPolicyBlocked) {
|
|
330561
330632
|
yield { type: "text", text: "[Error] Content policy refusal (content_filter)." };
|
|
330633
|
+
} else if (!explicitCompletion && !emittedContent && !currentReasoningContent) {
|
|
330634
|
+
yield { type: "text", text: "[LLM Error] GitHub Models stream ended before an explicit completion." };
|
|
330562
330635
|
}
|
|
330563
330636
|
} finally {
|
|
330564
330637
|
reader?.releaseLock();
|
|
@@ -340511,6 +340584,22 @@ function createToolchainCore() {
|
|
|
340511
340584
|
return { registry: new ToolRegistry(), catalog: new CapabilityCatalog() };
|
|
340512
340585
|
}
|
|
340513
340586
|
|
|
340587
|
+
// src/core/emptyResponseRetry.ts
|
|
340588
|
+
var EMPTY_RESPONSE_RETRY_DELAYS_MS = [200, 800, 2e3, 1e4, 6e4];
|
|
340589
|
+
var MAX_EMPTY_RESPONSE_RETRIES = EMPTY_RESPONSE_RETRY_DELAYS_MS.length;
|
|
340590
|
+
var MAX_CONSECUTIVE_EMPTY_RESPONSES = MAX_EMPTY_RESPONSE_RETRIES + 1;
|
|
340591
|
+
function emptyResponseRetryDelayMs(consecutiveEmptyResponses) {
|
|
340592
|
+
return EMPTY_RESPONSE_RETRY_DELAYS_MS[Math.max(0, consecutiveEmptyResponses - 1)] ?? 0;
|
|
340593
|
+
}
|
|
340594
|
+
function observeEmptyResponseOutcome(consecutiveEmptyResponses, emptyResponse) {
|
|
340595
|
+
const nextCount = emptyResponse ? consecutiveEmptyResponses + 1 : 0;
|
|
340596
|
+
return {
|
|
340597
|
+
consecutiveEmptyResponses: nextCount,
|
|
340598
|
+
retry: emptyResponse && nextCount <= MAX_EMPTY_RESPONSE_RETRIES,
|
|
340599
|
+
terminate: emptyResponse && nextCount > MAX_EMPTY_RESPONSE_RETRIES
|
|
340600
|
+
};
|
|
340601
|
+
}
|
|
340602
|
+
|
|
340514
340603
|
// src/core/agentKernelRunner.ts
|
|
340515
340604
|
var publicStreamFilters = /* @__PURE__ */ new WeakMap();
|
|
340516
340605
|
var brokerOnlyAssistantBuffers = /* @__PURE__ */ new WeakMap();
|
|
@@ -340662,7 +340751,7 @@ function kernelTurnFailed(agent, turn) {
|
|
|
340662
340751
|
return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
|
|
340663
340752
|
}
|
|
340664
340753
|
function providerTurnIsEmpty(turn) {
|
|
340665
|
-
return /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340754
|
+
return !turn.activity && /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340666
340755
|
${turn.text}`);
|
|
340667
340756
|
}
|
|
340668
340757
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
@@ -340671,6 +340760,12 @@ function removeTrailingFailedAssistant(agent, messages) {
|
|
|
340671
340760
|
const text = KernelMessageText(last);
|
|
340672
340761
|
if (last.stopReason === "error" || agent.isLlmErrorText(text)) messages.pop();
|
|
340673
340762
|
}
|
|
340763
|
+
function removeTrailingThoughtOnlyAssistant(messages) {
|
|
340764
|
+
const last = messages[messages.length - 1];
|
|
340765
|
+
if (last?.role !== "assistant") return;
|
|
340766
|
+
const hasToolCall = last.content.some((content) => content.type === "toolCall");
|
|
340767
|
+
if (!KernelMessageText(last).trim() && !hasToolCall) messages.pop();
|
|
340768
|
+
}
|
|
340674
340769
|
function normalizePublicProviderError(error, secrets = []) {
|
|
340675
340770
|
let raw = "";
|
|
340676
340771
|
if (error instanceof Error) {
|
|
@@ -340796,8 +340891,17 @@ async function runAgentKernel(agent) {
|
|
|
340796
340891
|
const tokens = [];
|
|
340797
340892
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
340798
340893
|
let lastAssistant = null;
|
|
340894
|
+
let observedActivity = false;
|
|
340895
|
+
let observedThought = false;
|
|
340799
340896
|
const unsubscribe = kernel2.subscribe(async (event) => {
|
|
340800
340897
|
await handleKernelEvent(agent, event, tokens);
|
|
340898
|
+
if (event.type === "message_update") {
|
|
340899
|
+
const delta = event.assistantMessageEvent;
|
|
340900
|
+
const deltaText = typeof delta.delta === "string" ? delta.delta : "";
|
|
340901
|
+
const thoughtDelta = delta.type === "thinking_delta" && !!deltaText.trim();
|
|
340902
|
+
observedThought = observedThought || thoughtDelta;
|
|
340903
|
+
observedActivity = observedActivity || thoughtDelta || delta.type === "text_delta" && !!deltaText.trim() || delta.type === "toolcall_end";
|
|
340904
|
+
}
|
|
340801
340905
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
340802
340906
|
lastAssistant = event.message;
|
|
340803
340907
|
}
|
|
@@ -340815,11 +340919,13 @@ async function runAgentKernel(agent) {
|
|
|
340815
340919
|
const assistant = lastAssistant;
|
|
340816
340920
|
const text = assistant ? KernelMessageText(assistant) : "";
|
|
340817
340921
|
const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
|
|
340818
|
-
const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
|
|
340922
|
+
const emptyResponse = !assistant || !text.trim() && !hasToolCall && !observedActivity && String(assistant?.stopReason || "") !== "aborted";
|
|
340819
340923
|
return {
|
|
340820
340924
|
text: emptyResponse ? "[Error] Provider returned an empty response." : text,
|
|
340821
340925
|
stopReason: String(assistant?.stopReason || ""),
|
|
340822
|
-
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
|
|
340926
|
+
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : "")),
|
|
340927
|
+
activity: observedActivity || !!text.trim() || hasToolCall,
|
|
340928
|
+
thoughtOnly: observedThought && !text.trim() && !hasToolCall && !["error", "aborted"].includes(String(assistant?.stopReason || ""))
|
|
340823
340929
|
};
|
|
340824
340930
|
} finally {
|
|
340825
340931
|
unsubscribe();
|
|
@@ -340857,16 +340963,30 @@ async function runAgentKernel(agent) {
|
|
|
340857
340963
|
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340858
340964
|
}
|
|
340859
340965
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340860
|
-
|
|
340966
|
+
const notice = `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.`;
|
|
340967
|
+
tokens.unshift({ type: "text", text: notice });
|
|
340968
|
+
agent.emitWorkEvent({
|
|
340969
|
+
type: "status",
|
|
340970
|
+
content: notice,
|
|
340971
|
+
fallback: { from: modelBeforeKernelRun, to: agent.model, providerId: agent.activeDeployment()?.providerId }
|
|
340972
|
+
});
|
|
340861
340973
|
}
|
|
340862
|
-
let
|
|
340863
|
-
|
|
340974
|
+
let consecutiveEmptyResponses = 0;
|
|
340975
|
+
for (; ; ) {
|
|
340976
|
+
const emptyResponseState = observeEmptyResponseOutcome(consecutiveEmptyResponses, providerTurnIsEmpty(lastTurn));
|
|
340977
|
+
consecutiveEmptyResponses = emptyResponseState.consecutiveEmptyResponses;
|
|
340978
|
+
if (lastTurn.thoughtOnly) {
|
|
340979
|
+
removeTrailingThoughtOnlyAssistant(kernel2.state.messages);
|
|
340980
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
340981
|
+
continue;
|
|
340982
|
+
}
|
|
340983
|
+
if (!emptyResponseState.retry) break;
|
|
340864
340984
|
removeTrailingFailedAssistant(agent, kernel2.state.messages);
|
|
340865
|
-
|
|
340866
|
-
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${
|
|
340985
|
+
const retryNumber = consecutiveEmptyResponses;
|
|
340986
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${retryNumber}/${MAX_EMPTY_RESPONSE_RETRIES}) after ${emptyResponseRetryDelayMs(consecutiveEmptyResponses)}ms.`;
|
|
340867
340987
|
tokens.push({ type: "text", text: notice });
|
|
340868
340988
|
agent.recordWorkStatus(notice);
|
|
340869
|
-
await agent.waitForPlannedRouteRetry();
|
|
340989
|
+
await agent.waitForPlannedRouteRetry(emptyResponseRetryDelayMs(consecutiveEmptyResponses));
|
|
340870
340990
|
lastTurn = await runWithCompressionResume([], false);
|
|
340871
340991
|
}
|
|
340872
340992
|
let routeRetries = 0;
|
|
@@ -340878,6 +340998,11 @@ async function runAgentKernel(agent) {
|
|
|
340878
340998
|
const notice = routeTransitionNotice(agent, previous);
|
|
340879
340999
|
tokens.push({ type: "text", text: notice });
|
|
340880
341000
|
agent.recordWorkStatus(notice);
|
|
341001
|
+
agent.emitWorkEvent({
|
|
341002
|
+
type: "status",
|
|
341003
|
+
content: notice,
|
|
341004
|
+
fallback: { from: previous, to: agent.model, providerId: agent.activeDeployment()?.providerId }
|
|
341005
|
+
});
|
|
340881
341006
|
kernel2.state.model = toKernelModel(agent);
|
|
340882
341007
|
const fallbackToolSurface = refreshToolSurface(true);
|
|
340883
341008
|
kernel2.state.systemPrompt = [agent.buildSystemPrompt(), fallbackToolSurface.systemPromptNotice].filter(Boolean).join("\n\n");
|
|
@@ -341057,7 +341182,7 @@ async function runAgentKernel(agent) {
|
|
|
341057
341182
|
return;
|
|
341058
341183
|
}
|
|
341059
341184
|
if (textStarted) finalContent.push({ type: "text", text });
|
|
341060
|
-
if (!finalContent.length) {
|
|
341185
|
+
if (!finalContent.length && !thinking.trim()) {
|
|
341061
341186
|
text = "[Error] Provider returned an empty response.";
|
|
341062
341187
|
finalContent.push({ type: "text", text });
|
|
341063
341188
|
}
|
|
@@ -343302,14 +343427,16 @@ var AutoRouter = class {
|
|
|
343302
343427
|
retryDelayMs
|
|
343303
343428
|
});
|
|
343304
343429
|
}
|
|
343430
|
+
if (failure.allowModelFallback === false) return attempts.slice(0, remainingAttempts);
|
|
343305
343431
|
const selection = decision.requestedSelection;
|
|
343306
343432
|
if (selection.kind === "fixed") return attempts.slice(0, remainingAttempts);
|
|
343307
343433
|
const scope = selection.kind === "auto" ? selection.scope : { kind: "provider", providerId: current.providerId };
|
|
343308
343434
|
const subset = selection.kind === "auto" ? selection.subset : void 0;
|
|
343309
343435
|
const currentGroup = current.logicalModelGroupId;
|
|
343436
|
+
const currentProviderId = current.providerId;
|
|
343310
343437
|
const now2 = this.now();
|
|
343311
343438
|
const attemptedDeployments = decision.attempts.map((attempt) => attempt.deployment);
|
|
343312
|
-
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");
|
|
343439
|
+
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");
|
|
343313
343440
|
const equivalent = currentGroup ? eligible.find((candidate) => candidate.deployment.logicalModelGroupId === currentGroup && !candidate.fallbackOnly) : void 0;
|
|
343314
343441
|
const fallback = eligible.find((candidate) => candidate.fallbackOnly);
|
|
343315
343442
|
const rankedAlternates = decision.rankedCandidates.map((ranked) => eligible.find((candidate) => sameDeployment(candidate.deployment, ranked.deployment))).filter((candidate) => !!candidate && candidate !== equivalent && candidate !== fallback && !candidate.fallbackOnly);
|
|
@@ -345639,7 +345766,12 @@ var Agent4 = class _Agent {
|
|
|
345639
345766
|
beginRouteAttempt() {
|
|
345640
345767
|
this.routeAttemptStartedAt = Date.now();
|
|
345641
345768
|
}
|
|
345642
|
-
async waitForPlannedRouteRetry() {
|
|
345769
|
+
async waitForPlannedRouteRetry(explicitDelayMs) {
|
|
345770
|
+
if (explicitDelayMs !== void 0) {
|
|
345771
|
+
if (explicitDelayMs <= 0) return;
|
|
345772
|
+
await new Promise((resolve16) => setTimeout(resolve16, explicitDelayMs));
|
|
345773
|
+
return;
|
|
345774
|
+
}
|
|
345643
345775
|
const waitBudgetMs = Math.max(0, Math.min(15e3, this.lastRouteDecision?.retryBudgetMs ?? 5e3));
|
|
345644
345776
|
const delay = Math.max(0, Math.min(waitBudgetMs, this.lastRouteRetryDelayMs));
|
|
345645
345777
|
this.lastRouteRetryDelayMs = 0;
|
|
@@ -346747,7 +346879,8 @@ ${String(event.toolArgs || "")}`;
|
|
|
346747
346879
|
sequence,
|
|
346748
346880
|
status: input.status,
|
|
346749
346881
|
guide: !isToolEvent && input.guide ? this.normalizeGuideReceipt(input.guide) : void 0,
|
|
346750
|
-
displayImage: isToolEvent ? this.hydrateDisplayImage(input.displayImage) : void 0
|
|
346882
|
+
displayImage: isToolEvent ? this.hydrateDisplayImage(input.displayImage) : void 0,
|
|
346883
|
+
fallback: input.fallback
|
|
346751
346884
|
};
|
|
346752
346885
|
if (activeRun && this.isPersistablePublicWorkEvent(event)) {
|
|
346753
346886
|
activeRun.sequence = Number(sequence || activeRun.sequence + 1);
|
|
@@ -349666,9 +349799,15 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
349666
349799
|
}
|
|
349667
349800
|
compressionBuildBlockStart(messages) {
|
|
349668
349801
|
const activeRunId = this.currentWorkRunId();
|
|
349669
|
-
if (
|
|
349670
|
-
|
|
349671
|
-
|
|
349802
|
+
if (activeRunId) {
|
|
349803
|
+
const index = messages.findIndex((message) => String(message.run_id || message.runId || "") === activeRunId);
|
|
349804
|
+
if (index >= 0) return index;
|
|
349805
|
+
}
|
|
349806
|
+
let lastRunBoundary = -1;
|
|
349807
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
349808
|
+
if (String(messages[index]?.run_id || messages[index]?.runId || "")) lastRunBoundary = index + 1;
|
|
349809
|
+
}
|
|
349810
|
+
return lastRunBoundary >= 0 ? lastRunBoundary : messages.length;
|
|
349672
349811
|
}
|
|
349673
349812
|
recentContextSuffix(messages, maxMessages, tokenBudget) {
|
|
349674
349813
|
if (!messages.length) return [];
|
|
@@ -350597,17 +350736,12 @@ ${msg.content}
|
|
|
350597
350736
|
currentAttempt.sideEffectBoundary = this.routeSideEffectCommitted;
|
|
350598
350737
|
if (this.routeAttemptStartedAt > 0) currentAttempt.durationMs = Math.max(0, Date.now() - this.routeAttemptStartedAt);
|
|
350599
350738
|
}
|
|
350600
|
-
if (!fallbackEnabled) {
|
|
350601
|
-
this.lastRouteDecision.finalStatus = "failed";
|
|
350602
|
-
this.routeAttemptStartedAt = 0;
|
|
350603
|
-
this.persistRouteDecision(this.lastRouteDecision);
|
|
350604
|
-
return null;
|
|
350605
|
-
}
|
|
350606
350739
|
if (!this.pendingAutoAttempts.length) {
|
|
350607
350740
|
this.pendingAutoAttempts = this.autoRouter.planAttempts(this.lastRouteDecision, this.autoRouteCandidates(), {
|
|
350608
350741
|
error: failure,
|
|
350609
350742
|
streamCommitted: this.routeStreamCommitted,
|
|
350610
|
-
sideEffectCommitted: this.routeSideEffectCommitted
|
|
350743
|
+
sideEffectCommitted: this.routeSideEffectCommitted,
|
|
350744
|
+
allowModelFallback: fallbackEnabled
|
|
350611
350745
|
});
|
|
350612
350746
|
}
|
|
350613
350747
|
const next2 = this.pendingAutoAttempts.shift();
|
|
@@ -350642,7 +350776,6 @@ ${msg.content}
|
|
|
350642
350776
|
if (!next?.name) return null;
|
|
350643
350777
|
this.model = next.name;
|
|
350644
350778
|
this.fixedDeployment = this.deploymentRef(next);
|
|
350645
|
-
if (next.provider) this.config.set("models", "auto_switch_anchor_provider", next.provider);
|
|
350646
350779
|
return current;
|
|
350647
350780
|
}
|
|
350648
350781
|
isLlmErrorText(text) {
|
|
@@ -350650,12 +350783,10 @@ ${msg.content}
|
|
|
350650
350783
|
}
|
|
350651
350784
|
scopedSwitchModels(currentModelName) {
|
|
350652
350785
|
const all = this.config.allModels();
|
|
350653
|
-
|
|
350654
|
-
const
|
|
350655
|
-
|
|
350656
|
-
|
|
350657
|
-
const provider = this.config.findProvider(providerId);
|
|
350658
|
-
return all.filter((m2) => m2.provider_id === (provider?.id || providerId));
|
|
350786
|
+
const current = currentModelName === "auto" ? this.activeModelConfig() : this.config.findModel(currentModelName);
|
|
350787
|
+
const providerId = current?.provider_id || this.fixedDeployment?.providerId || (currentModelName !== "auto" ? this.activeDeployment()?.providerId : void 0);
|
|
350788
|
+
if (!providerId) return [];
|
|
350789
|
+
return all.filter((m2) => m2.provider_id === providerId);
|
|
350659
350790
|
}
|
|
350660
350791
|
async validateModels(selectedNames, options = {}) {
|
|
350661
350792
|
if (this.modelValidationPromise) return this.modelValidationPromise;
|
|
@@ -352037,7 +352168,10 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
|
|
|
352037
352168
|
if (!sa) return "[Subagent] Not found.";
|
|
352038
352169
|
const requestedModel = this.normalizeSubagentModelSelection(sa.model && sa.model !== "default" ? sa.model : this.modelSelectionValue() || this.model);
|
|
352039
352170
|
const requestedDeployment = parseDeploymentSelectionValue2(requestedModel);
|
|
352040
|
-
|
|
352171
|
+
let assignedModel = requestedModel === "auto" ? this.activeModelConfig() : requestedDeployment ? this.config.findDeployment(requestedDeployment) : this.config.findModel(requestedModel);
|
|
352172
|
+
if (!assignedModel && !requestedDeployment && requestedModel !== "auto") {
|
|
352173
|
+
assignedModel = this.activeModelConfig();
|
|
352174
|
+
}
|
|
352041
352175
|
const model = assignedModel?.name || (requestedModel === "auto" ? this.activeModelName() : requestedModel);
|
|
352042
352176
|
const activeModel = this.activeModelConfig();
|
|
352043
352177
|
const activeProvider = this.engineModel();
|
|
@@ -353466,6 +353600,32 @@ var ConversationKernel = class {
|
|
|
353466
353600
|
followUp: queued?.followUp.slice() || []
|
|
353467
353601
|
};
|
|
353468
353602
|
}
|
|
353603
|
+
/**
|
|
353604
|
+
* dev-0.5.6: 排队 followUp 消息 drain 时转成普通用户消息载荷。
|
|
353605
|
+
*
|
|
353606
|
+
* 入队时(enqueueSameSession)为去重/编辑保留了 clientMessageId,并把
|
|
353607
|
+
* runId 固定为入队时运行的 runId;若原样传给 process,写入 chatMessages
|
|
353608
|
+
* 的用户消息会携带旧 runId + clientMessageId,渲染端(PC renderTranscript /
|
|
353609
|
+
* 移动端 projectRemoteConversationItems)会把「clientMessageId + runId 命中
|
|
353610
|
+
* workRuns」的消息误判为 guide 消息而跳过气泡,导致排队自动发送的消息不
|
|
353611
|
+
* 显示在用户输入时间线。清除这两个字段后 process 走普通用户消息分支
|
|
353612
|
+
* (chatMessages.push,mode 取 visibleMode,runId 取当前 run)。
|
|
353613
|
+
*/
|
|
353614
|
+
drainQueuedFollowUpMessage(message) {
|
|
353615
|
+
if (typeof message === "string") return message;
|
|
353616
|
+
const text = String(message.text || "");
|
|
353617
|
+
const images = message.images;
|
|
353618
|
+
const attachments = message.attachments;
|
|
353619
|
+
const visible = message.visibleUserInput;
|
|
353620
|
+
const visibleMode = message.visibleMode;
|
|
353621
|
+
return {
|
|
353622
|
+
text,
|
|
353623
|
+
...images?.length ? { images } : {},
|
|
353624
|
+
...attachments?.length ? { attachments } : {},
|
|
353625
|
+
...visible ? { visibleUserInput: visible } : {},
|
|
353626
|
+
...visibleMode ? { visibleMode } : {}
|
|
353627
|
+
};
|
|
353628
|
+
}
|
|
353469
353629
|
queueItems(target) {
|
|
353470
353630
|
const runtime = this.findRuntime(target);
|
|
353471
353631
|
if (!runtime) return [];
|
|
@@ -354207,7 +354367,7 @@ ${batchText}`,
|
|
|
354207
354367
|
};
|
|
354208
354368
|
lastTokens = await this.runSingle(runtime, batchMessage, "steer");
|
|
354209
354369
|
} else {
|
|
354210
|
-
lastTokens = await this.runSingle(runtime, next.message, next.queueMode);
|
|
354370
|
+
lastTokens = await this.runSingle(runtime, this.drainQueuedFollowUpMessage(next.message), next.queueMode);
|
|
354211
354371
|
}
|
|
354212
354372
|
}
|
|
354213
354373
|
const rootMessage = runtime.runner.subagents.readRootInbox()[0];
|
|
@@ -354666,7 +354826,11 @@ ${text}`;
|
|
|
354666
354826
|
type: "queue_update",
|
|
354667
354827
|
content: "Conversation queue updated.",
|
|
354668
354828
|
conversationId: runtime.id,
|
|
354669
|
-
queue: this.queued(runtime.target)
|
|
354829
|
+
queue: this.queued(runtime.target),
|
|
354830
|
+
// Structured rows with stable kernel ids so every consumer (PC UI and
|
|
354831
|
+
// the paired mobile client) can render/edit/delete the same items.
|
|
354832
|
+
queueItems: this.queueItems(runtime.target),
|
|
354833
|
+
queuePaused: runtime.queuePaused === true
|
|
354670
354834
|
});
|
|
354671
354835
|
}
|
|
354672
354836
|
clearQueued(runtime) {
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -464,7 +464,7 @@ export declare class Agent {
|
|
|
464
464
|
markRouteToolExecuted(name: string, rawArgs?: string): void;
|
|
465
465
|
routeTransitionKind(): PlannedRouteAttempt['kind'] | '';
|
|
466
466
|
beginRouteAttempt(): void;
|
|
467
|
-
waitForPlannedRouteRetry(): Promise<void>;
|
|
467
|
+
waitForPlannedRouteRetry(explicitDelayMs?: number): Promise<void>;
|
|
468
468
|
recordRouteSuccess(latencyMs?: number, throughput?: number): void;
|
|
469
469
|
recordRouteToolOutcome(valid: boolean): void;
|
|
470
470
|
updateProviders(value: unknown): void;
|