newmark-agent 0.3.10 → 0.3.12
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/config.example.json +6 -0
- package/dist/cli-commands.d.ts +7 -0
- package/dist/cli-commands.js +206 -15
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +4 -0
- package/dist/cli-help.js +46 -0
- package/dist/conversation-utility-host.bundle.cjs +548 -137
- package/dist/core/agent.d.ts +18 -3
- package/dist/core/agent.js +236 -34
- package/dist/core/agentKernelRunner.js +72 -10
- package/dist/core/browserControl.d.ts +8 -0
- package/dist/core/browserUsePageAdapter.d.ts +3 -0
- package/dist/core/browserUsePageAdapter.js +19 -2
- package/dist/core/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +1 -0
- package/dist/core/conversationKernel.js +39 -1
- package/dist/core/electronBrowserUseHost.js +7 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
- package/dist/core/electronUtilityRuntimePool.js +62 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.js +14 -0
- package/dist/core/wslAgentRuntimePool.d.ts +4 -0
- package/dist/core/wslAgentRuntimePool.js +56 -0
- package/dist/launcher.js +51 -10
- package/dist/llm/provider.d.ts +8 -5
- package/dist/llm/provider.js +85 -33
- package/dist/main.js +297 -61
- package/dist/preload.js +10 -2
- package/dist/providers/chat-completions.adapter.js +1 -5
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +44 -0
- package/dist/providers/responses.adapter.js +1 -3
- package/dist/tools/index.js +36 -49
- package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
- package/dist/tui/src/app.js +47 -13
- package/dist/tui/src/render.js +23 -7
- package/dist/tui/src/state.js +61 -9
- package/dist/ui/index.html +545 -122
- package/dist/wsl-agent-host.bundle.cjs +548 -137
- package/package.json +14 -5
|
@@ -326959,7 +326959,12 @@ async function runFlowBuild(agent, prompt, options) {
|
|
|
326959
326959
|
if (!options.signal?.aborted && typeof agent.emitWorkEvent === "function") {
|
|
326960
326960
|
agent.emitWorkEvent({ type: "error", content: reportedError.message, runId });
|
|
326961
326961
|
}
|
|
326962
|
-
agent.finishConversationWorkRun(
|
|
326962
|
+
agent.finishConversationWorkRun(
|
|
326963
|
+
runId,
|
|
326964
|
+
options.signal?.aborted ? "interrupted" : "error",
|
|
326965
|
+
void 0,
|
|
326966
|
+
options.signal?.aborted ? "" : reportedError.message
|
|
326967
|
+
);
|
|
326963
326968
|
agent.flushWorkspaceConversationState();
|
|
326964
326969
|
}
|
|
326965
326970
|
throw reportedError;
|
|
@@ -327619,8 +327624,10 @@ var ConfigManager = class {
|
|
|
327619
327624
|
rootPath;
|
|
327620
327625
|
config;
|
|
327621
327626
|
workspaceOverrides;
|
|
327622
|
-
|
|
327627
|
+
readOnly;
|
|
327628
|
+
constructor(rootPath, options = {}) {
|
|
327623
327629
|
this.rootPath = rootPath;
|
|
327630
|
+
this.readOnly = options.readOnly === true;
|
|
327624
327631
|
this.workspaceOverrides = /* @__PURE__ */ new Map();
|
|
327625
327632
|
this.config = this.load();
|
|
327626
327633
|
}
|
|
@@ -327635,17 +327642,19 @@ var ConfigManager = class {
|
|
|
327635
327642
|
const raw = JSON.parse(readJsonText(cp));
|
|
327636
327643
|
const normalized = normalizeConfigShape(raw, true);
|
|
327637
327644
|
if (isCorruptConfig(raw, normalized)) {
|
|
327645
|
+
if (this.readOnly) return defaultConfig();
|
|
327638
327646
|
this.backupConfig(cp, "invalid-shape");
|
|
327639
327647
|
return this.writeRecoveredConfig(cp);
|
|
327640
327648
|
}
|
|
327641
327649
|
if (migrateProviderIdsInConfig(normalized)) {
|
|
327642
327650
|
try {
|
|
327643
|
-
fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
|
|
327651
|
+
if (!this.readOnly) fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
|
|
327644
327652
|
} catch {
|
|
327645
327653
|
}
|
|
327646
327654
|
}
|
|
327647
327655
|
return normalized;
|
|
327648
327656
|
} catch {
|
|
327657
|
+
if (this.readOnly) return defaultConfig();
|
|
327649
327658
|
this.backupConfig(cp, "invalid-json");
|
|
327650
327659
|
return this.writeRecoveredConfig(cp);
|
|
327651
327660
|
}
|
|
@@ -327691,10 +327700,12 @@ var ConfigManager = class {
|
|
|
327691
327700
|
this.config[section][key3] = { value: normalizedValue };
|
|
327692
327701
|
}
|
|
327693
327702
|
save() {
|
|
327703
|
+
if (this.readOnly) return;
|
|
327694
327704
|
const j2 = JSON.stringify(this.config, null, 2);
|
|
327695
327705
|
fs3.writeFileSync(path3.join(this.rootPath, "config.json"), j2, "utf-8");
|
|
327696
327706
|
}
|
|
327697
327707
|
saveTo(targetPath) {
|
|
327708
|
+
if (this.readOnly) return;
|
|
327698
327709
|
const j2 = JSON.stringify(this.config, null, 2);
|
|
327699
327710
|
fs3.writeFileSync(targetPath, j2, "utf-8");
|
|
327700
327711
|
}
|
|
@@ -327919,12 +327930,14 @@ var ConfigManager = class {
|
|
|
327919
327930
|
return providers;
|
|
327920
327931
|
}
|
|
327921
327932
|
writeRecoveredConfig(configPath) {
|
|
327933
|
+
if (this.readOnly) return defaultConfig();
|
|
327922
327934
|
const config = loadExampleConfig();
|
|
327923
327935
|
fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
|
|
327924
327936
|
fs3.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
327925
327937
|
return config;
|
|
327926
327938
|
}
|
|
327927
327939
|
backupConfig(configPath, reason) {
|
|
327940
|
+
if (this.readOnly) return;
|
|
327928
327941
|
try {
|
|
327929
327942
|
if (!fs3.existsSync(configPath)) return;
|
|
327930
327943
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -328269,7 +328282,10 @@ function defaultConfig() {
|
|
|
328269
328282
|
general: {
|
|
328270
328283
|
tone: { _description: "Conversation style", _type: "choice", _values: ["strict_simple", "casual_friendly"], value: "strict_simple" },
|
|
328271
328284
|
language: { _description: "Default language", _type: "choice", _values: ["en", "zh", "auto"], value: "auto" },
|
|
328272
|
-
|
|
328285
|
+
// A first-run desktop window must have a deterministic close/exit
|
|
328286
|
+
// contract. Users who explicitly choose minimize-to-tray keep that
|
|
328287
|
+
// choice, but a fresh install must not hide the process on OS close.
|
|
328288
|
+
close_behavior: { _description: "Close behavior", _type: "choice", _values: ["minimize", "exit"], value: "exit" },
|
|
328273
328289
|
default_input: { _description: "Default input mode", _type: "choice", _values: ["guide", "next"], value: "guide" },
|
|
328274
328290
|
auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
|
|
328275
328291
|
},
|
|
@@ -328608,6 +328624,38 @@ function providerAbortError(signal) {
|
|
|
328608
328624
|
if (!error.name || error.name === "Error") error.name = "AbortError";
|
|
328609
328625
|
return error;
|
|
328610
328626
|
}
|
|
328627
|
+
function providerStreamTimeoutError(timeoutMs) {
|
|
328628
|
+
const error = new Error("Stream read timeout");
|
|
328629
|
+
error.name = "TimeoutError";
|
|
328630
|
+
error.message = `Stream read timeout after ${timeoutMs}ms`;
|
|
328631
|
+
return error;
|
|
328632
|
+
}
|
|
328633
|
+
async function readProviderStreamChunk(reader, signal, timeoutMs = 3e4) {
|
|
328634
|
+
if (signal.aborted) throw providerAbortError(signal);
|
|
328635
|
+
let timer;
|
|
328636
|
+
let onAbort;
|
|
328637
|
+
const abortPromise = new Promise((_3, reject) => {
|
|
328638
|
+
onAbort = () => reject(providerAbortError(signal));
|
|
328639
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
328640
|
+
});
|
|
328641
|
+
const timeoutPromise = new Promise((_3, reject) => {
|
|
328642
|
+
timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
|
|
328643
|
+
});
|
|
328644
|
+
try {
|
|
328645
|
+
return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
|
|
328646
|
+
} catch (error) {
|
|
328647
|
+
if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
|
|
328648
|
+
try {
|
|
328649
|
+
await reader.cancel(error);
|
|
328650
|
+
} catch {
|
|
328651
|
+
}
|
|
328652
|
+
}
|
|
328653
|
+
throw error;
|
|
328654
|
+
} finally {
|
|
328655
|
+
if (timer) clearTimeout(timer);
|
|
328656
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
328657
|
+
}
|
|
328658
|
+
}
|
|
328611
328659
|
function parseProviderSse(raw) {
|
|
328612
328660
|
const events = [];
|
|
328613
328661
|
for (const block of String(raw || "").replace(/\r\n/g, "\n").split(/\n\n+/)) {
|
|
@@ -328843,12 +328891,7 @@ var ChatCompletionsAdapter = class {
|
|
|
328843
328891
|
let emittedTool = false;
|
|
328844
328892
|
try {
|
|
328845
328893
|
while (true) {
|
|
328846
|
-
|
|
328847
|
-
const readPromise = reader.read();
|
|
328848
|
-
const timeoutPromise = new Promise(
|
|
328849
|
-
(_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
|
|
328850
|
-
);
|
|
328851
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
328894
|
+
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
328852
328895
|
if (done) break;
|
|
328853
328896
|
buffer += decoder.decode(value, { stream: true });
|
|
328854
328897
|
const lines = buffer.split("\n");
|
|
@@ -329086,8 +329129,7 @@ var ResponsesAdapter = class {
|
|
|
329086
329129
|
let streamError = "";
|
|
329087
329130
|
try {
|
|
329088
329131
|
while (true) {
|
|
329089
|
-
|
|
329090
|
-
const { done, value } = await reader.read();
|
|
329132
|
+
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
329091
329133
|
if (done) break;
|
|
329092
329134
|
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
329093
329135
|
const blocks = buffer.split(/\n\n+/);
|
|
@@ -329297,6 +329339,16 @@ function createProviderAdapter(providerId, apiMode) {
|
|
|
329297
329339
|
}
|
|
329298
329340
|
|
|
329299
329341
|
// src/llm/provider.ts
|
|
329342
|
+
var DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 9e4;
|
|
329343
|
+
var MIN_PROVIDER_REQUEST_TIMEOUT_MS = 50;
|
|
329344
|
+
function providerTimeoutError(timeoutMs) {
|
|
329345
|
+
const error = new Error(`Provider request timed out after ${timeoutMs}ms`);
|
|
329346
|
+
error.name = "TimeoutError";
|
|
329347
|
+
return error;
|
|
329348
|
+
}
|
|
329349
|
+
function isProviderTimeoutError(error) {
|
|
329350
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
329351
|
+
}
|
|
329300
329352
|
function abortFailure(signal) {
|
|
329301
329353
|
const reason = signal?.reason;
|
|
329302
329354
|
const error = reason instanceof Error ? reason : new Error(reason ? String(reason) : "LLM request aborted");
|
|
@@ -329326,13 +329378,14 @@ function parseProviderSse2(raw) {
|
|
|
329326
329378
|
return events;
|
|
329327
329379
|
}
|
|
329328
329380
|
var LLMProvider = class _LLMProvider {
|
|
329329
|
-
constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false) {
|
|
329381
|
+
constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329330
329382
|
this.name = name50;
|
|
329331
329383
|
this.baseUrl = baseUrl;
|
|
329332
329384
|
this.apiKey = apiKey;
|
|
329333
329385
|
this.explicitProtocol = explicitProtocol;
|
|
329334
329386
|
this.openAIMode = openAIMode;
|
|
329335
329387
|
this.useProviderAdaptersV2 = useProviderAdaptersV2;
|
|
329388
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
329336
329389
|
}
|
|
329337
329390
|
name;
|
|
329338
329391
|
baseUrl;
|
|
@@ -329340,8 +329393,25 @@ var LLMProvider = class _LLMProvider {
|
|
|
329340
329393
|
explicitProtocol;
|
|
329341
329394
|
openAIMode;
|
|
329342
329395
|
useProviderAdaptersV2;
|
|
329396
|
+
requestTimeoutMs;
|
|
329343
329397
|
static nodeHttpTransport = null;
|
|
329344
329398
|
static powershellTransport = null;
|
|
329399
|
+
effectiveRequestTimeout(timeoutMs) {
|
|
329400
|
+
const requested = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
329401
|
+
const configured = Number.isFinite(this.requestTimeoutMs) && this.requestTimeoutMs > 0 ? this.requestTimeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
329402
|
+
return Math.max(MIN_PROVIDER_REQUEST_TIMEOUT_MS, Math.min(requested, configured));
|
|
329403
|
+
}
|
|
329404
|
+
async withRequestTimeout(promise, timeoutMs, signal) {
|
|
329405
|
+
let timer;
|
|
329406
|
+
const timeoutPromise = new Promise((_3, reject) => {
|
|
329407
|
+
timer = setTimeout(() => reject(providerTimeoutError(timeoutMs)), timeoutMs);
|
|
329408
|
+
});
|
|
329409
|
+
try {
|
|
329410
|
+
return await abortable(Promise.race([promise, timeoutPromise]), signal);
|
|
329411
|
+
} finally {
|
|
329412
|
+
if (timer) clearTimeout(timer);
|
|
329413
|
+
}
|
|
329414
|
+
}
|
|
329345
329415
|
intelligenceConfig(tier) {
|
|
329346
329416
|
switch (tier) {
|
|
329347
329417
|
case "low":
|
|
@@ -329443,6 +329513,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329443
329513
|
};
|
|
329444
329514
|
}
|
|
329445
329515
|
async postJsonWithFetchFallback(url, headers, body, timeoutMs = 12e4, signal) {
|
|
329516
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329446
329517
|
if (this.isPlainHttpLoopback(url)) {
|
|
329447
329518
|
const pathname = (() => {
|
|
329448
329519
|
try {
|
|
@@ -329452,7 +329523,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329452
329523
|
}
|
|
329453
329524
|
})();
|
|
329454
329525
|
this.transportDiagnostic("loopback:start", pathname);
|
|
329455
|
-
const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
|
|
329526
|
+
const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
329456
329527
|
this.transportDiagnostic("loopback:complete", `status=${local.status} bytes=${Buffer.byteLength(local.body || "")}`);
|
|
329457
329528
|
return {
|
|
329458
329529
|
ok: local.status >= 200 && local.status < 300,
|
|
@@ -329466,7 +329537,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329466
329537
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
329467
329538
|
if (signal?.aborted) forwardAbort();
|
|
329468
329539
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
329469
|
-
const timer = setTimeout(() => abort.abort(),
|
|
329540
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
329470
329541
|
try {
|
|
329471
329542
|
const response = await fetch(url, {
|
|
329472
329543
|
method: "POST",
|
|
@@ -329477,8 +329548,9 @@ var LLMProvider = class _LLMProvider {
|
|
|
329477
329548
|
return response;
|
|
329478
329549
|
} catch (e3) {
|
|
329479
329550
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329551
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
329480
329552
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
329481
|
-
const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
|
|
329553
|
+
const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
329482
329554
|
return {
|
|
329483
329555
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
329484
329556
|
status: fallback.status,
|
|
@@ -329492,14 +329564,16 @@ var LLMProvider = class _LLMProvider {
|
|
|
329492
329564
|
}
|
|
329493
329565
|
}
|
|
329494
329566
|
async getJsonWithFetchFallback(url, headers, timeoutMs = 3e4) {
|
|
329567
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329495
329568
|
const abort = new AbortController();
|
|
329496
|
-
const timer = setTimeout(() => abort.abort(),
|
|
329569
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
329497
329570
|
try {
|
|
329498
329571
|
const response = await fetch(url, { method: "GET", headers, signal: abort.signal });
|
|
329499
329572
|
return response;
|
|
329500
329573
|
} catch (e3) {
|
|
329574
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
329501
329575
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
329502
|
-
const fallback = await this.nodeHttpJson("GET", url, headers);
|
|
329576
|
+
const fallback = await this.nodeHttpJson("GET", url, headers, "", void 0, effectiveTimeout);
|
|
329503
329577
|
return {
|
|
329504
329578
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
329505
329579
|
status: fallback.status,
|
|
@@ -329512,14 +329586,16 @@ var LLMProvider = class _LLMProvider {
|
|
|
329512
329586
|
}
|
|
329513
329587
|
}
|
|
329514
329588
|
shouldUseNodeHttpFallback(error) {
|
|
329515
|
-
return error instanceof TypeError && /fetch failed/i.test(error.message)
|
|
329589
|
+
return error instanceof TypeError && /fetch failed/i.test(error.message);
|
|
329516
329590
|
}
|
|
329517
|
-
nodeHttpJson(method, urlValue, headers, body = "", signal) {
|
|
329591
|
+
nodeHttpJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329592
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329518
329593
|
if (_LLMProvider.nodeHttpTransport) {
|
|
329519
|
-
return
|
|
329594
|
+
return this.withRequestTimeout(_LLMProvider.nodeHttpTransport(method, urlValue, headers, body), effectiveTimeout, signal).catch((error) => {
|
|
329520
329595
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329596
|
+
if (isProviderTimeoutError(error)) throw error;
|
|
329521
329597
|
if (process.platform === "win32") {
|
|
329522
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
329598
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
329523
329599
|
}
|
|
329524
329600
|
throw error;
|
|
329525
329601
|
});
|
|
@@ -329564,8 +329640,8 @@ var LLMProvider = class _LLMProvider {
|
|
|
329564
329640
|
else fail(new Error("Node HTTP response closed before completion"));
|
|
329565
329641
|
});
|
|
329566
329642
|
});
|
|
329567
|
-
req.setTimeout(
|
|
329568
|
-
req.destroy(
|
|
329643
|
+
req.setTimeout(effectiveTimeout, () => {
|
|
329644
|
+
req.destroy(providerTimeoutError(effectiveTimeout));
|
|
329569
329645
|
});
|
|
329570
329646
|
req.on("error", reject);
|
|
329571
329647
|
const onAbort = () => req.destroy(abortFailure(signal));
|
|
@@ -329576,15 +329652,17 @@ var LLMProvider = class _LLMProvider {
|
|
|
329576
329652
|
req.end();
|
|
329577
329653
|
}).catch((error) => {
|
|
329578
329654
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329655
|
+
if (isProviderTimeoutError(error)) throw error;
|
|
329579
329656
|
if (process.platform === "win32") {
|
|
329580
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
329657
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
329581
329658
|
}
|
|
329582
329659
|
throw error;
|
|
329583
329660
|
});
|
|
329584
329661
|
}
|
|
329585
|
-
powershellJson(method, urlValue, headers, body = "", signal) {
|
|
329662
|
+
powershellJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329663
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329586
329664
|
if (_LLMProvider.powershellTransport) {
|
|
329587
|
-
return _LLMProvider.powershellTransport(method, urlValue, headers, body);
|
|
329665
|
+
return this.withRequestTimeout(_LLMProvider.powershellTransport(method, urlValue, headers, body), effectiveTimeout, signal);
|
|
329588
329666
|
}
|
|
329589
329667
|
return new Promise((resolve16, reject) => {
|
|
329590
329668
|
const headerJson = JSON.stringify(headers);
|
|
@@ -329613,7 +329691,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329613
329691
|
" $raw = $headerJson | ConvertFrom-Json",
|
|
329614
329692
|
" foreach ($p in $raw.PSObject.Properties) { $headers[$p.Name] = [string]$p.Value }",
|
|
329615
329693
|
"}",
|
|
329616
|
-
|
|
329694
|
+
`'$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec = ${Math.max(1, Math.ceil(effectiveTimeout / 1e3))} }`,
|
|
329617
329695
|
'if ($method -eq "POST") { $params["Body"] = $bodyJson }',
|
|
329618
329696
|
'if ($method -eq "POST") { $params["ContentType"] = "application/json; charset=utf-8" }',
|
|
329619
329697
|
"$resp = Invoke-WebRequest @params",
|
|
@@ -329645,8 +329723,8 @@ var LLMProvider = class _LLMProvider {
|
|
|
329645
329723
|
const timer = setTimeout(() => {
|
|
329646
329724
|
child.kill();
|
|
329647
329725
|
cleanup();
|
|
329648
|
-
reject(
|
|
329649
|
-
},
|
|
329726
|
+
reject(providerTimeoutError(effectiveTimeout));
|
|
329727
|
+
}, effectiveTimeout + 5e3);
|
|
329650
329728
|
child.stdout.setEncoding("utf8");
|
|
329651
329729
|
child.stderr.setEncoding("utf8");
|
|
329652
329730
|
child.stdout.on("data", (chunk) => {
|
|
@@ -330094,10 +330172,10 @@ ${responsePath}
|
|
|
330094
330172
|
return this.shouldUseResponsesFallback(Number(match[1]), errorText);
|
|
330095
330173
|
}
|
|
330096
330174
|
/**
|
|
330097
|
-
* Loopback-aware transport injected into adapter `execute`.
|
|
330098
|
-
*
|
|
330099
|
-
*
|
|
330100
|
-
*
|
|
330175
|
+
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
330176
|
+
* requests retain the fetch-to-node fallback for transport failures, while
|
|
330177
|
+
* a local deadline is returned directly so one request cannot become a
|
|
330178
|
+
* second Windows fallback request.
|
|
330101
330179
|
*/
|
|
330102
330180
|
buildProviderAdapterTransport() {
|
|
330103
330181
|
return async (request, signal) => {
|
|
@@ -330106,7 +330184,8 @@ ${responsePath}
|
|
|
330106
330184
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330107
330185
|
if (signal?.aborted) forwardAbort();
|
|
330108
330186
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330109
|
-
const
|
|
330187
|
+
const effectiveTimeout = this.effectiveRequestTimeout(12e4);
|
|
330188
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
330110
330189
|
try {
|
|
330111
330190
|
try {
|
|
330112
330191
|
return await fetch(request.url, {
|
|
@@ -330117,6 +330196,7 @@ ${responsePath}
|
|
|
330117
330196
|
});
|
|
330118
330197
|
} catch (error) {
|
|
330119
330198
|
if (signal?.aborted) throw abortFailure(signal);
|
|
330199
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
330120
330200
|
if (!this.shouldUseNodeHttpFallback(error)) throw error;
|
|
330121
330201
|
const fallbackHeaders = { ...request.headers };
|
|
330122
330202
|
delete fallbackHeaders["Accept"];
|
|
@@ -330124,7 +330204,7 @@ ${responsePath}
|
|
|
330124
330204
|
request.url,
|
|
330125
330205
|
fallbackHeaders,
|
|
330126
330206
|
{ ...request.body, stream: false },
|
|
330127
|
-
|
|
330207
|
+
effectiveTimeout,
|
|
330128
330208
|
signal
|
|
330129
330209
|
);
|
|
330130
330210
|
return this.toTransportResponse(fallback);
|
|
@@ -330234,7 +330314,8 @@ ${responsePath}
|
|
|
330234
330314
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330235
330315
|
if (signal?.aborted) forwardAbort();
|
|
330236
330316
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330237
|
-
const
|
|
330317
|
+
const effectiveTimeout = this.effectiveRequestTimeout(12e4);
|
|
330318
|
+
const timeout = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
330238
330319
|
let reader = null;
|
|
330239
330320
|
try {
|
|
330240
330321
|
let response;
|
|
@@ -330246,9 +330327,10 @@ ${responsePath}
|
|
|
330246
330327
|
signal: abort.signal
|
|
330247
330328
|
});
|
|
330248
330329
|
} catch (e3) {
|
|
330330
|
+
if (signal?.aborted) throw abortFailure(signal);
|
|
330331
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
330249
330332
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
330250
330333
|
clearTimeout(timeout);
|
|
330251
|
-
if (signal?.aborted) throw abortFailure(signal);
|
|
330252
330334
|
yield* this.githubModelsChatNonStreaming(url, body, signal);
|
|
330253
330335
|
return;
|
|
330254
330336
|
}
|
|
@@ -330268,13 +330350,9 @@ ${responsePath}
|
|
|
330268
330350
|
let currentReasoningContent = "";
|
|
330269
330351
|
let contentPolicyBlocked = false;
|
|
330270
330352
|
let emittedContent = false;
|
|
330353
|
+
const streamSignal = signal || new AbortController().signal;
|
|
330271
330354
|
while (true) {
|
|
330272
|
-
|
|
330273
|
-
const readPromise = reader.read();
|
|
330274
|
-
const timeoutPromise = new Promise(
|
|
330275
|
-
(_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
|
|
330276
|
-
);
|
|
330277
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
330355
|
+
const { done, value } = await readProviderStreamChunk(reader, streamSignal);
|
|
330278
330356
|
if (done) break;
|
|
330279
330357
|
buffer += decoder.decode(value, { stream: true });
|
|
330280
330358
|
const lines = buffer.split("\n");
|
|
@@ -335769,10 +335847,102 @@ function browserVisualFallback(runtimeKey, observationId) {
|
|
|
335769
335847
|
return browserFallbacks.get(key(runtimeKey, observationId)) || null;
|
|
335770
335848
|
}
|
|
335771
335849
|
|
|
335850
|
+
// src/core/computerUseSession.ts
|
|
335851
|
+
var COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
|
|
335852
|
+
var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
335853
|
+
var ComputerUseSessionRegistry = class {
|
|
335854
|
+
constructor(ttlMs = COMPUTER_USE_LOCK_TTL_MS) {
|
|
335855
|
+
this.ttlMs = ttlMs;
|
|
335856
|
+
}
|
|
335857
|
+
ttlMs;
|
|
335858
|
+
enabledByRuntime = /* @__PURE__ */ new Map();
|
|
335859
|
+
activeLease = null;
|
|
335860
|
+
authorize(action, scope, dryRun = false) {
|
|
335861
|
+
const normalizedAction = String(action || "").trim().toLowerCase();
|
|
335862
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335863
|
+
const now2 = Date.now();
|
|
335864
|
+
this.clearExpired(now2);
|
|
335865
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
335866
|
+
return this.occupiedError(normalizedAction, scope.ownerLabel, this.activeLease.ownerLabel);
|
|
335867
|
+
}
|
|
335868
|
+
if (normalizedAction === "takeover_stop") return null;
|
|
335869
|
+
const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
|
|
335870
|
+
const readOnly = normalizedAction === "observe" || normalizedAction === "app_list" || normalizedAction === "app_observe" || normalizedAction === "wait";
|
|
335871
|
+
if (!enabled && !readOnly && !dryRun && normalizedAction !== "takeover_start") {
|
|
335872
|
+
return JSON.stringify({
|
|
335873
|
+
ok: false,
|
|
335874
|
+
action: normalizedAction,
|
|
335875
|
+
error: `ComputerUse is disabled for ${scope.ownerLabel}. Enable ComputerUse for this conversation before sending desktop operations.`,
|
|
335876
|
+
computer_use_enabled: false,
|
|
335877
|
+
requested_owner: scope.ownerLabel
|
|
335878
|
+
}, null, 2);
|
|
335879
|
+
}
|
|
335880
|
+
if (normalizedAction === "takeover_start") this.enabledByRuntime.set(runtimeKey, true);
|
|
335881
|
+
if (!this.activeLease) {
|
|
335882
|
+
this.activeLease = { ...scope, runtimeKey, updatedAt: now2 };
|
|
335883
|
+
} else {
|
|
335884
|
+
this.activeLease.updatedAt = now2;
|
|
335885
|
+
}
|
|
335886
|
+
return null;
|
|
335887
|
+
}
|
|
335888
|
+
complete(action, scope) {
|
|
335889
|
+
const normalizedAction = String(action || "").trim().toLowerCase();
|
|
335890
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335891
|
+
if (normalizedAction === "takeover_stop") {
|
|
335892
|
+
if (!this.activeLease || this.activeLease.runtimeKey === runtimeKey) this.activeLease = null;
|
|
335893
|
+
this.enabledByRuntime.set(runtimeKey, false);
|
|
335894
|
+
return;
|
|
335895
|
+
}
|
|
335896
|
+
if (this.activeLease?.runtimeKey === runtimeKey) this.activeLease.updatedAt = Date.now();
|
|
335897
|
+
}
|
|
335898
|
+
setEnabled(scope, enabled) {
|
|
335899
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335900
|
+
this.clearExpired();
|
|
335901
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
335902
|
+
return { ok: false, error: this.occupiedError("toggle", scope.ownerLabel, this.activeLease.ownerLabel), state: this.state(runtimeKey) };
|
|
335903
|
+
}
|
|
335904
|
+
this.enabledByRuntime.set(runtimeKey, enabled !== false);
|
|
335905
|
+
if (enabled === false && this.activeLease?.runtimeKey === runtimeKey) this.activeLease = null;
|
|
335906
|
+
return { ok: true, state: this.state(runtimeKey) };
|
|
335907
|
+
}
|
|
335908
|
+
state(runtimeKey) {
|
|
335909
|
+
const key3 = String(runtimeKey || "").trim() || "conversation:default";
|
|
335910
|
+
this.clearExpired();
|
|
335911
|
+
const lease = this.activeLease;
|
|
335912
|
+
return {
|
|
335913
|
+
runtimeKey: key3,
|
|
335914
|
+
enabled: this.enabledByRuntime.get(key3) !== false,
|
|
335915
|
+
occupied: !!lease,
|
|
335916
|
+
...lease ? { ownerLabel: lease.ownerLabel, updatedAt: lease.updatedAt } : {}
|
|
335917
|
+
};
|
|
335918
|
+
}
|
|
335919
|
+
cancelTarget(runtimeKey) {
|
|
335920
|
+
const key3 = String(runtimeKey || "").trim();
|
|
335921
|
+
if (!key3) return false;
|
|
335922
|
+
const hadActiveLease = this.activeLease?.runtimeKey === key3;
|
|
335923
|
+
if (hadActiveLease) this.activeLease = null;
|
|
335924
|
+
this.enabledByRuntime.set(key3, false);
|
|
335925
|
+
return hadActiveLease;
|
|
335926
|
+
}
|
|
335927
|
+
clearExpired(now2 = Date.now()) {
|
|
335928
|
+
if (this.activeLease && now2 - this.activeLease.updatedAt > this.ttlMs) {
|
|
335929
|
+
this.activeLease = null;
|
|
335930
|
+
}
|
|
335931
|
+
}
|
|
335932
|
+
occupiedError(action, requestedOwner, activeOwner) {
|
|
335933
|
+
return JSON.stringify({
|
|
335934
|
+
ok: false,
|
|
335935
|
+
action,
|
|
335936
|
+
error: `${COMPUTER_USE_OCCUPIED_MARKER}: ComputerUse is already active in ${activeOwner}. Stop it with computer_use takeover_stop or wait before another conversation takes control.`,
|
|
335937
|
+
lock_owner: activeOwner,
|
|
335938
|
+
requested_owner: requestedOwner
|
|
335939
|
+
}, null, 2);
|
|
335940
|
+
}
|
|
335941
|
+
};
|
|
335942
|
+
var defaultComputerUseSessionRegistry = new ComputerUseSessionRegistry();
|
|
335943
|
+
|
|
335772
335944
|
// src/tools/index.ts
|
|
335773
335945
|
var globSync = require_index_min().sync;
|
|
335774
|
-
var computerUseLock = null;
|
|
335775
|
-
var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
335776
335946
|
function normalizeComputerUseAction(action) {
|
|
335777
335947
|
return String(action || "").trim().toLowerCase();
|
|
335778
335948
|
}
|
|
@@ -335857,49 +336027,24 @@ async function abortableToolDelay(durationMs, signal) {
|
|
|
335857
336027
|
if (signal?.aborted) abort();
|
|
335858
336028
|
});
|
|
335859
336029
|
}
|
|
335860
|
-
function
|
|
335861
|
-
|
|
335862
|
-
|
|
335863
|
-
|
|
335864
|
-
|
|
335865
|
-
function computerUseLockError(action, owner) {
|
|
335866
|
-
return JSON.stringify({
|
|
335867
|
-
ok: false,
|
|
335868
|
-
action,
|
|
335869
|
-
error: `ComputerUse is already active in ${computerUseLock?.owner || "another conversation"}. Stop it with computer_use takeover_stop or wait before using ComputerUse from another conversation.`,
|
|
335870
|
-
lock_owner: computerUseLock?.owner || "",
|
|
335871
|
-
requested_owner: owner
|
|
335872
|
-
}, null, 2);
|
|
335873
|
-
}
|
|
335874
|
-
function acquireComputerUseLock(action, owner, wsPath) {
|
|
335875
|
-
const now2 = Date.now();
|
|
335876
|
-
clearStaleComputerUseLock(now2);
|
|
335877
|
-
if (computerUseLock && computerUseLock.owner !== owner) {
|
|
335878
|
-
return computerUseLockError(action, owner);
|
|
335879
|
-
}
|
|
335880
|
-
computerUseLock = {
|
|
335881
|
-
owner,
|
|
335882
|
-
workspacePath: path15.resolve(wsPath || process.cwd()),
|
|
335883
|
-
acquiredAt: computerUseLock?.owner === owner ? computerUseLock.acquiredAt : now2,
|
|
335884
|
-
updatedAt: now2
|
|
336030
|
+
function computerUseSessionScope(context, wsPath, owner) {
|
|
336031
|
+
return {
|
|
336032
|
+
runtimeKey: browserUseScope(context, wsPath).runtimeKey,
|
|
336033
|
+
ownerLabel: owner,
|
|
336034
|
+
workspacePath: path15.resolve(wsPath || process.cwd())
|
|
335885
336035
|
};
|
|
335886
|
-
return null;
|
|
335887
336036
|
}
|
|
335888
|
-
function
|
|
335889
|
-
|
|
335890
|
-
if (computerUseLock && computerUseLock.owner !== owner) {
|
|
335891
|
-
return computerUseLockError(action, owner);
|
|
335892
|
-
}
|
|
335893
|
-
if (computerUseLock?.owner === owner) computerUseLock = null;
|
|
335894
|
-
return null;
|
|
336037
|
+
function acquireComputerUseLock(action, owner, wsPath, context = {}, dryRun = false) {
|
|
336038
|
+
return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner), dryRun);
|
|
335895
336039
|
}
|
|
335896
|
-
function
|
|
335897
|
-
|
|
335898
|
-
|
|
335899
|
-
return computerUseLockError(action, owner);
|
|
335900
|
-
}
|
|
336040
|
+
function releaseComputerUseLock(action, owner, context = {}, wsPath = context.workspacePath || "") {
|
|
336041
|
+
const scope = computerUseSessionScope(context, wsPath, owner);
|
|
336042
|
+
defaultComputerUseSessionRegistry.complete(action, scope);
|
|
335901
336043
|
return null;
|
|
335902
336044
|
}
|
|
336045
|
+
function assertComputerUseLockOwner(action, owner, context = {}, wsPath = context.workspacePath || "") {
|
|
336046
|
+
return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner));
|
|
336047
|
+
}
|
|
335903
336048
|
var ToolExecutor = class {
|
|
335904
336049
|
constructor(root2, config, ssh, workspace) {
|
|
335905
336050
|
this.config = config;
|
|
@@ -336450,7 +336595,7 @@ var ToolExecutor = class {
|
|
|
336450
336595
|
case "computer_use": {
|
|
336451
336596
|
const action = normalizeComputerUseAction(g2("action"));
|
|
336452
336597
|
const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
|
|
336453
|
-
const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner) : acquireComputerUseLock(action, owner, wsPath);
|
|
336598
|
+
const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner, context, wsPath) : acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
|
|
336454
336599
|
if (lockGuard) return lockGuard;
|
|
336455
336600
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
336456
336601
|
try {
|
|
@@ -336464,7 +336609,7 @@ var ToolExecutor = class {
|
|
|
336464
336609
|
}, 12e4, context.signal);
|
|
336465
336610
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
336466
336611
|
} finally {
|
|
336467
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336612
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336468
336613
|
}
|
|
336469
336614
|
}
|
|
336470
336615
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
|
|
@@ -336481,7 +336626,7 @@ var ToolExecutor = class {
|
|
|
336481
336626
|
const result = await requestUtilityHostTool("computer_use", args, trustedComputerUseContext, 12e4, context.signal);
|
|
336482
336627
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
336483
336628
|
} finally {
|
|
336484
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336629
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336485
336630
|
}
|
|
336486
336631
|
}
|
|
336487
336632
|
const output = await runComputerUse({
|
|
@@ -336522,7 +336667,7 @@ var ToolExecutor = class {
|
|
|
336522
336667
|
durationMs: Number(step.duration_ms || 0)
|
|
336523
336668
|
})) : void 0
|
|
336524
336669
|
});
|
|
336525
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336670
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336526
336671
|
return output;
|
|
336527
336672
|
}
|
|
336528
336673
|
case "terminal_takeover": {
|
|
@@ -337003,9 +337148,17 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
|
|
|
337003
337148
|
}
|
|
337004
337149
|
}
|
|
337005
337150
|
async browserRun(request, signal, context = {}, workspacePath = this.root) {
|
|
337151
|
+
const scope = browserUseScope(context, workspacePath);
|
|
337152
|
+
const scopedRequest = {
|
|
337153
|
+
...request,
|
|
337154
|
+
target: {
|
|
337155
|
+
workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
|
|
337156
|
+
conversationId: context.conversationId || "default",
|
|
337157
|
+
runtimeKey: scope.runtimeKey
|
|
337158
|
+
}
|
|
337159
|
+
};
|
|
337006
337160
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
337007
|
-
const
|
|
337008
|
-
const result2 = await requestWindowsHostTool("browser_control", request, {
|
|
337161
|
+
const result2 = await requestWindowsHostTool("browser_control", scopedRequest, {
|
|
337009
337162
|
conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || "default",
|
|
337010
337163
|
workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
|
|
337011
337164
|
actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
|
|
@@ -337015,10 +337168,10 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
|
|
|
337015
337168
|
return this.formatBrowserResult(result2);
|
|
337016
337169
|
}
|
|
337017
337170
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
|
|
337018
|
-
const result2 = await requestUtilityHostTool("browser_control",
|
|
337171
|
+
const result2 = await requestUtilityHostTool("browser_control", scopedRequest, void 0, 3e4, signal);
|
|
337019
337172
|
return this.formatBrowserResult(result2);
|
|
337020
337173
|
}
|
|
337021
|
-
const result = await BrowserControl.run(
|
|
337174
|
+
const result = await BrowserControl.run(scopedRequest, signal);
|
|
337022
337175
|
return this.formatBrowserResult(result);
|
|
337023
337176
|
}
|
|
337024
337177
|
formatBrowserResult(result) {
|
|
@@ -337845,6 +337998,19 @@ var WorkspaceManager = class {
|
|
|
337845
337998
|
this.saveState();
|
|
337846
337999
|
}
|
|
337847
338000
|
}
|
|
338001
|
+
/**
|
|
338002
|
+
* Re-read the registry and persisted current-workspace pointer after another
|
|
338003
|
+
* Newmark entrypoint updates Work/*.json. This intentionally does not create
|
|
338004
|
+
* a workspace: a refresh must reflect the shared on-disk state exactly.
|
|
338005
|
+
*/
|
|
338006
|
+
reloadFromStorage() {
|
|
338007
|
+
if (this.detached) return this.current;
|
|
338008
|
+
this.scan();
|
|
338009
|
+
this.validate();
|
|
338010
|
+
this.current = null;
|
|
338011
|
+
this.restoreCurrent();
|
|
338012
|
+
return this.current;
|
|
338013
|
+
}
|
|
337848
338014
|
saveInternal() {
|
|
337849
338015
|
if (this.detached) return;
|
|
337850
338016
|
const p = path16.join(this.rootPath, "Work", "Local.json");
|
|
@@ -339896,6 +340062,10 @@ var ProviderRunError = class extends Error {
|
|
|
339896
340062
|
function kernelTurnFailed(agent, turn) {
|
|
339897
340063
|
return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
|
|
339898
340064
|
}
|
|
340065
|
+
function providerTurnIsEmpty(turn) {
|
|
340066
|
+
return /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340067
|
+
${turn.text}`);
|
|
340068
|
+
}
|
|
339899
340069
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
339900
340070
|
const last = messages[messages.length - 1];
|
|
339901
340071
|
if (last?.role !== "assistant") return;
|
|
@@ -339931,17 +340101,35 @@ function normalizePublicProviderError(error, secrets = []) {
|
|
|
339931
340101
|
}
|
|
339932
340102
|
return raw.slice(0, 1200);
|
|
339933
340103
|
}
|
|
340104
|
+
function throwIfKernelAborted(signal) {
|
|
340105
|
+
if (!signal?.aborted) return;
|
|
340106
|
+
const reason = signal.reason;
|
|
340107
|
+
if (reason instanceof Error) {
|
|
340108
|
+
reason.name = "AbortError";
|
|
340109
|
+
throw reason;
|
|
340110
|
+
}
|
|
340111
|
+
const error = new Error(reason ? String(reason) : "Agent run aborted");
|
|
340112
|
+
error.name = "AbortError";
|
|
340113
|
+
throw error;
|
|
340114
|
+
}
|
|
339934
340115
|
async function runAgentKernel(agent) {
|
|
339935
340116
|
const stopContextTimer = performanceTimer("context_prepare", { conversationId: agent.activeConversationId });
|
|
340117
|
+
const processSignal = agent.activeProcessSignal();
|
|
340118
|
+
if (processSignal?.aborted) {
|
|
340119
|
+
stopContextTimer();
|
|
340120
|
+
throwIfKernelAborted(processSignal);
|
|
340121
|
+
}
|
|
339936
340122
|
if (!agent.engineModel()) {
|
|
340123
|
+
const message = "No LLM configured. Add provider in Settings > Models.";
|
|
339937
340124
|
agent.status = "error";
|
|
339938
340125
|
agent.saveWorkspaceConversationState();
|
|
339939
|
-
|
|
340126
|
+
throw new Error(message);
|
|
339940
340127
|
}
|
|
339941
340128
|
const [{ Agent: NativeAgent }, KernelStreamCompat] = await Promise.all([
|
|
339942
340129
|
Promise.resolve().then(() => (init_agentKernel(), agentKernel_exports)),
|
|
339943
340130
|
Promise.resolve().then(() => (init_stream_types(), stream_types_exports))
|
|
339944
340131
|
]);
|
|
340132
|
+
throwIfKernelAborted(processSignal);
|
|
339945
340133
|
const toolProvisioning = new ToolProvisionSession([], []);
|
|
339946
340134
|
let activeToolSurfaceIdentity = "";
|
|
339947
340135
|
let activeToolSurfaceNotice = "";
|
|
@@ -339967,6 +340155,7 @@ async function runAgentKernel(agent) {
|
|
|
339967
340155
|
const initialToolSurface = refreshToolSurface(true);
|
|
339968
340156
|
const assembledContext = agent.assembleContextV2(initialToolSurface.systemPromptNotice);
|
|
339969
340157
|
const systemPrompt = assembledContext.text;
|
|
340158
|
+
throwIfKernelAborted(processSignal);
|
|
339970
340159
|
let providerRequestCount = 0;
|
|
339971
340160
|
let bootstrappedCompressionAt = agent.lastCompression?.at || "";
|
|
339972
340161
|
stopContextTimer();
|
|
@@ -339987,6 +340176,24 @@ async function runAgentKernel(agent) {
|
|
|
339987
340176
|
kernel2.state.tools = toKernelTools(agent, initialToolSurface.definitions, toolProvisioning);
|
|
339988
340177
|
kernel2.state.messages = toKernelMessages(agent);
|
|
339989
340178
|
agent.attachAgentKernelRuntime(kernel2);
|
|
340179
|
+
let detachProcessAbort = () => {
|
|
340180
|
+
};
|
|
340181
|
+
if (processSignal) {
|
|
340182
|
+
const abortKernel = () => kernel2.abort();
|
|
340183
|
+
if (processSignal.aborted) {
|
|
340184
|
+
kernel2.abort();
|
|
340185
|
+
} else {
|
|
340186
|
+
processSignal.addEventListener("abort", abortKernel, { once: true });
|
|
340187
|
+
detachProcessAbort = () => processSignal.removeEventListener("abort", abortKernel);
|
|
340188
|
+
}
|
|
340189
|
+
}
|
|
340190
|
+
try {
|
|
340191
|
+
throwIfKernelAborted(processSignal);
|
|
340192
|
+
} catch (error) {
|
|
340193
|
+
detachProcessAbort();
|
|
340194
|
+
agent.attachAgentKernelRuntime(null);
|
|
340195
|
+
throw error;
|
|
340196
|
+
}
|
|
339990
340197
|
const tokens = [];
|
|
339991
340198
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
339992
340199
|
let lastAssistant = null;
|
|
@@ -340007,10 +340214,13 @@ async function runAgentKernel(agent) {
|
|
|
340007
340214
|
}
|
|
340008
340215
|
await kernel2.prompt(promptMessages);
|
|
340009
340216
|
const assistant = lastAssistant;
|
|
340217
|
+
const text = assistant ? KernelMessageText(assistant) : "";
|
|
340218
|
+
const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
|
|
340219
|
+
const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
|
|
340010
340220
|
return {
|
|
340011
|
-
text:
|
|
340221
|
+
text: emptyResponse ? "[Error] Provider returned an empty response." : text,
|
|
340012
340222
|
stopReason: String(assistant?.stopReason || ""),
|
|
340013
|
-
errorMessage: String(assistant?.errorMessage || "")
|
|
340223
|
+
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
|
|
340014
340224
|
};
|
|
340015
340225
|
} finally {
|
|
340016
340226
|
unsubscribe();
|
|
@@ -340045,6 +340255,16 @@ async function runAgentKernel(agent) {
|
|
|
340045
340255
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340046
340256
|
tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
340047
340257
|
}
|
|
340258
|
+
let emptyResponseRetries = 0;
|
|
340259
|
+
while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
|
|
340260
|
+
removeTrailingFailedAssistant(agent, kernel2.state.messages);
|
|
340261
|
+
emptyResponseRetries += 1;
|
|
340262
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
|
|
340263
|
+
tokens.push({ type: "text", text: notice });
|
|
340264
|
+
agent.recordWorkStatus(notice);
|
|
340265
|
+
await agent.waitForPlannedRouteRetry();
|
|
340266
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
340267
|
+
}
|
|
340048
340268
|
let routeRetries = 0;
|
|
340049
340269
|
while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
|
|
340050
340270
|
const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
|
|
@@ -340077,6 +340297,7 @@ async function runAgentKernel(agent) {
|
|
|
340077
340297
|
else agent.pendingOptions = agent.pendingOptions.filter((question) => !isPlanExecutionQuestion(question));
|
|
340078
340298
|
}
|
|
340079
340299
|
} finally {
|
|
340300
|
+
detachProcessAbort();
|
|
340080
340301
|
agent.attachAgentKernelRuntime(null);
|
|
340081
340302
|
}
|
|
340082
340303
|
agent.status = "idle";
|
|
@@ -340243,20 +340464,18 @@ async function transformContext(agent, messages, signal) {
|
|
|
340243
340464
|
const provider = agent.engineModel();
|
|
340244
340465
|
if (!provider || !compressionModel) return messages;
|
|
340245
340466
|
const newmarkMessages = publicHistoryFromKernelMessages(messages);
|
|
340246
|
-
const beforeCompression = JSON.stringify(newmarkMessages);
|
|
340247
340467
|
const compressionAt = agent.lastCompression?.at || "";
|
|
340248
|
-
await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
340468
|
+
let compressed = await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
340249
340469
|
if (processSignal?.aborted) return messages;
|
|
340250
|
-
|
|
340251
|
-
|
|
340252
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
340470
|
+
if (compressed && agent.estimateContextTokens(newmarkMessages) >= Math.floor(agent.contextWindow(compressionModel).maxTokens * 0.82)) {
|
|
340471
|
+
compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
|
|
340253
340472
|
}
|
|
340254
340473
|
const windowMax = agent.contextWindow(compressionModel).maxTokens;
|
|
340255
340474
|
const conservativeTokens = agent.estimateContextTokens(newmarkMessages);
|
|
340256
340475
|
if (conservativeTokens >= Math.floor(windowMax * 0.9) && !processSignal?.aborted) {
|
|
340257
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
340476
|
+
compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
|
|
340258
340477
|
}
|
|
340259
|
-
if (
|
|
340478
|
+
if (!compressed) return messages;
|
|
340260
340479
|
const durableMessages = toKernelMessagesFromHistory(newmarkMessages, agent);
|
|
340261
340480
|
if (agent.lastCompression?.at && agent.lastCompression.at !== compressionAt) {
|
|
340262
340481
|
agent.recordContextCompressionStep();
|
|
@@ -342147,9 +342366,11 @@ function key2(model) {
|
|
|
342147
342366
|
}
|
|
342148
342367
|
var FileModelValidationCache = class {
|
|
342149
342368
|
filePath;
|
|
342369
|
+
readOnly;
|
|
342150
342370
|
records = /* @__PURE__ */ new Map();
|
|
342151
|
-
constructor(rootPath) {
|
|
342371
|
+
constructor(rootPath, options = {}) {
|
|
342152
342372
|
this.filePath = path20.join(rootPath, "model-validation", "records.json");
|
|
342373
|
+
this.readOnly = options.readOnly === true;
|
|
342153
342374
|
this.load();
|
|
342154
342375
|
}
|
|
342155
342376
|
get(modelKey2) {
|
|
@@ -342158,10 +342379,12 @@ var FileModelValidationCache = class {
|
|
|
342158
342379
|
}
|
|
342159
342380
|
set(record) {
|
|
342160
342381
|
this.records.set(record.modelKey || key2(record.model), JSON.parse(JSON.stringify(record)));
|
|
342382
|
+
if (this.readOnly) return;
|
|
342161
342383
|
this.save();
|
|
342162
342384
|
}
|
|
342163
342385
|
delete(modelKey2) {
|
|
342164
342386
|
if (!this.records.delete(modelKey2)) return;
|
|
342387
|
+
if (this.readOnly) return;
|
|
342165
342388
|
this.save();
|
|
342166
342389
|
}
|
|
342167
342390
|
load() {
|
|
@@ -344037,7 +344260,7 @@ var Agent4 = class _Agent {
|
|
|
344037
344260
|
this.subagentName = options.subagentName || "";
|
|
344038
344261
|
this.subagentPrompt = options.subagentPrompt || "";
|
|
344039
344262
|
this.linkedPlanAccess = options.linkedPlanAccess;
|
|
344040
|
-
this.config = new ConfigManager(rootPath);
|
|
344263
|
+
this.config = new ConfigManager(rootPath, { readOnly: options.readOnlyConfig === true });
|
|
344041
344264
|
this.compressionHistoryArchive = new CompressionHistoryArchive(rootPath);
|
|
344042
344265
|
this.contextV2 = new AgentContextManager(rootPath, this.config);
|
|
344043
344266
|
this.agentRunService = this.config.contextFlag("agent_runtime_v2") ? new AgentRunService(path28.join(rootPath, ".newmark-context-v2")) : null;
|
|
@@ -344568,7 +344791,10 @@ var Agent4 = class _Agent {
|
|
|
344568
344791
|
}
|
|
344569
344792
|
const previousAuto = this.model === "auto" ? this.resolvedDeployment : null;
|
|
344570
344793
|
const qualified = parseDeploymentSelectionValue2(requested);
|
|
344571
|
-
const
|
|
344794
|
+
const legacyQualified = requested.includes("/") ? this.config.allModels().filter(
|
|
344795
|
+
(model2) => `${model2.provider_id}/${model2.name}` === requested || `${model2.provider}/${model2.name}` === requested
|
|
344796
|
+
) : [];
|
|
344797
|
+
const current = qualified ? this.config.findDeployment(qualified) : legacyQualified.length === 1 ? legacyQualified[0] : requested ? this.config.findModel(requested) : void 0;
|
|
344572
344798
|
this.model = current?.name || requested;
|
|
344573
344799
|
this.fixedDeployment = current ? this.deploymentRef(current) : qualified;
|
|
344574
344800
|
this.resolvedDeployment = null;
|
|
@@ -345685,7 +345911,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345685
345911
|
this.saveWorkspaceConversationState(true);
|
|
345686
345912
|
return true;
|
|
345687
345913
|
}
|
|
345688
|
-
finishConversationWorkRun(runId, status, endedAt = this.nowIso()) {
|
|
345914
|
+
finishConversationWorkRun(runId, status, endedAt = this.nowIso(), errorMessage = "") {
|
|
345689
345915
|
const run = this.workRuns.find((item) => item.runId === String(runId || ""));
|
|
345690
345916
|
if (!run) return false;
|
|
345691
345917
|
this.syncAgentRunTerminal(run.runId, status, endedAt);
|
|
@@ -345729,7 +345955,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345729
345955
|
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
345730
345956
|
this.emitWorkEvent({
|
|
345731
345957
|
type: status === "completed" ? "done" : status === "error" ? "error" : "status",
|
|
345732
|
-
content: status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
|
|
345958
|
+
content: status === "error" ? String(errorMessage || "").trim() || "Agent run failed." : status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
|
|
345733
345959
|
status,
|
|
345734
345960
|
runId: run.runId,
|
|
345735
345961
|
conversationId: run.target.conversationId,
|
|
@@ -345874,6 +346100,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345874
346100
|
this.activeAgentKernelRuntime = runtime;
|
|
345875
346101
|
this.awaitingAgentKernelRuntime = false;
|
|
345876
346102
|
if (!runtime) return;
|
|
346103
|
+
if (this.activeProcessAbortController?.signal.aborted) runtime.abort?.();
|
|
345877
346104
|
const queued = this.pendingAgentKernelQueue.splice(0);
|
|
345878
346105
|
for (const item of queued) {
|
|
345879
346106
|
const accepted = this.forwardAgentKernelQueueMessage(item.content, item.queueMode, item.clientMessageId, item.runId, item.images, item.hiddenUserInput);
|
|
@@ -347035,6 +347262,25 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
347035
347262
|
this.loadWorkspaceConversationState();
|
|
347036
347263
|
return selected;
|
|
347037
347264
|
}
|
|
347265
|
+
refreshWorkspaceRegistryFromStorage() {
|
|
347266
|
+
const before = JSON.stringify({
|
|
347267
|
+
internal: this.workspace.internal,
|
|
347268
|
+
external: this.workspace.external,
|
|
347269
|
+
current: this.workspace.current
|
|
347270
|
+
});
|
|
347271
|
+
const selected = this.workspace.reloadFromStorage();
|
|
347272
|
+
const after = JSON.stringify({
|
|
347273
|
+
internal: this.workspace.internal,
|
|
347274
|
+
external: this.workspace.external,
|
|
347275
|
+
current: this.workspace.current
|
|
347276
|
+
});
|
|
347277
|
+
if (before === after) return selected;
|
|
347278
|
+
if (selected) this.config.loadWorkspaceConfig(selected.path);
|
|
347279
|
+
else this.config.clearWorkspaceOverrides();
|
|
347280
|
+
this.workspaceConversations.clear();
|
|
347281
|
+
this.loadWorkspaceConversationState();
|
|
347282
|
+
return selected;
|
|
347283
|
+
}
|
|
347038
347284
|
setConversation(id) {
|
|
347039
347285
|
const clean = this.safeConversationId(id || "default");
|
|
347040
347286
|
if (this.workspaceConversationKey() === this.loadedWorkspaceConversationKey) {
|
|
@@ -348028,28 +348274,25 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
348028
348274
|
this.saveWorkspaceConversationState(true);
|
|
348029
348275
|
return { text, hiddenUserInput: true, goalContinuation: true };
|
|
348030
348276
|
}
|
|
348031
|
-
|
|
348277
|
+
buildSessionArchive(messages, mode, model, archiveDir) {
|
|
348032
348278
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
|
|
348033
|
-
const
|
|
348034
|
-
|
|
348035
|
-
const filename = `session_${stamp}.md`;
|
|
348036
|
-
const outPath = path28.join(archiveDir, filename);
|
|
348037
|
-
let md = `# Newmark Session \u2014 ${stamp}
|
|
348279
|
+
const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
|
|
348280
|
+
let markdown = `# Newmark Session \u2014 ${stamp}
|
|
348038
348281
|
|
|
348039
348282
|
`;
|
|
348040
|
-
|
|
348283
|
+
markdown += `**Mode**: ${mode}
|
|
348041
348284
|
**Model**: ${model}
|
|
348042
348285
|
`;
|
|
348043
|
-
|
|
348286
|
+
markdown += `**Messages**: ${messages.length}
|
|
348044
348287
|
|
|
348045
348288
|
---
|
|
348046
348289
|
|
|
348047
348290
|
`;
|
|
348048
|
-
if (this.goal)
|
|
348291
|
+
if (this.goal) markdown += `**Goal**: ${this.goal.objective}
|
|
348049
348292
|
|
|
348050
348293
|
`;
|
|
348051
348294
|
for (const msg of messages) {
|
|
348052
|
-
|
|
348295
|
+
markdown += `**[${msg.role}] ${msg.timestamp}**
|
|
348053
348296
|
|
|
348054
348297
|
${msg.content}
|
|
348055
348298
|
|
|
@@ -348058,13 +348301,35 @@ ${msg.content}
|
|
|
348058
348301
|
const archived = archiveConversationImageAttachment(this.rootPath, archiveDir, attachment);
|
|
348059
348302
|
if (!archived) continue;
|
|
348060
348303
|
const alt = archived.name.replace(/[\]\r\n]/g, " ").trim() || "Submitted image";
|
|
348061
|
-
|
|
348304
|
+
markdown += `
|
|
348062
348305
|
|
|
348063
348306
|
`;
|
|
348064
348307
|
}
|
|
348065
348308
|
}
|
|
348066
|
-
|
|
348067
|
-
|
|
348309
|
+
return { filename, markdown };
|
|
348310
|
+
}
|
|
348311
|
+
writeSessionArchive(messages, mode, model) {
|
|
348312
|
+
const archiveDir = this.archiveDir();
|
|
348313
|
+
fs25.mkdirSync(archiveDir, { recursive: true });
|
|
348314
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
348315
|
+
fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
|
|
348316
|
+
return archive.filename;
|
|
348317
|
+
}
|
|
348318
|
+
async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
|
|
348319
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
348320
|
+
await fs25.promises.mkdir(archiveDir, { recursive: true });
|
|
348321
|
+
const outPath = path28.join(archiveDir, archive.filename);
|
|
348322
|
+
const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
|
|
348323
|
+
try {
|
|
348324
|
+
await fs25.promises.writeFile(tempPath, archive.markdown, "utf-8");
|
|
348325
|
+
await fs25.promises.rename(tempPath, outPath);
|
|
348326
|
+
} finally {
|
|
348327
|
+
try {
|
|
348328
|
+
await fs25.promises.unlink(tempPath);
|
|
348329
|
+
} catch {
|
|
348330
|
+
}
|
|
348331
|
+
}
|
|
348332
|
+
return archive.filename;
|
|
348068
348333
|
}
|
|
348069
348334
|
archiveSession() {
|
|
348070
348335
|
return this.writeSessionArchive(this.chatMessages, this.modeName(), this.model);
|
|
@@ -348132,6 +348397,93 @@ ${msg.content}
|
|
|
348132
348397
|
}
|
|
348133
348398
|
return filename;
|
|
348134
348399
|
}
|
|
348400
|
+
/**
|
|
348401
|
+
* Non-blocking archive writer used by the desktop IPC path. The conversation
|
|
348402
|
+
* state merge remains synchronous and lock-protected, but the potentially
|
|
348403
|
+
* large markdown payload and manifest use promise-based filesystem I/O so
|
|
348404
|
+
* independent workspaces can archive in parallel without freezing Electron.
|
|
348405
|
+
*/
|
|
348406
|
+
async archiveConversationAsync(conversationId) {
|
|
348407
|
+
return await this.archiveConversationAsyncUnlocked(conversationId);
|
|
348408
|
+
}
|
|
348409
|
+
async archiveConversationAsyncUnlocked(conversationId) {
|
|
348410
|
+
const ws = this.workspace.current;
|
|
348411
|
+
if (!ws) return null;
|
|
348412
|
+
const clean = this.safeConversationId(conversationId || "default");
|
|
348413
|
+
const stateKey2 = this.workspaceConversationStateKey(clean);
|
|
348414
|
+
if (!stateKey2) return null;
|
|
348415
|
+
const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
|
|
348416
|
+
const archiveDir = path28.join(ws.path, "archive");
|
|
348417
|
+
const workspacePrefix = this.workspaceConversationPrefix() || "";
|
|
348418
|
+
const archiveMode = this.modeName();
|
|
348419
|
+
const archiveModel = this.model;
|
|
348420
|
+
const cachedStored = this.readStoredConversationState(ws);
|
|
348421
|
+
const stored = JSON.parse(JSON.stringify(cachedStored || {}));
|
|
348422
|
+
const persisted = stored.conversations?.[stateKey2];
|
|
348423
|
+
if (persisted) this.normalizeConversationTree(persisted);
|
|
348424
|
+
const memory = this.workspaceConversations.get(memoryKey);
|
|
348425
|
+
const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
|
|
348426
|
+
const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
|
|
348427
|
+
const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
|
|
348428
|
+
const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
|
|
348429
|
+
const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
|
|
348430
|
+
const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
|
|
348431
|
+
title: this.titleFromMessages(messages, clean),
|
|
348432
|
+
chatMessages: messages,
|
|
348433
|
+
history: sourceHistory,
|
|
348434
|
+
plan: memory?.plan,
|
|
348435
|
+
linkedPlan: memory?.linkedPlan,
|
|
348436
|
+
subagentState: memory?.subagentState,
|
|
348437
|
+
workRuns: memory?.workRuns,
|
|
348438
|
+
continuations: memory?.continuations,
|
|
348439
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
348440
|
+
};
|
|
348441
|
+
const manifest = {
|
|
348442
|
+
version: 2,
|
|
348443
|
+
kind: "newmark-conversation-archive",
|
|
348444
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
348445
|
+
conversationId: clean,
|
|
348446
|
+
workspaceId: ws.id,
|
|
348447
|
+
workspaceName: ws.name,
|
|
348448
|
+
workspacePath: ws.path,
|
|
348449
|
+
workspaceInternal: ws.isInternal,
|
|
348450
|
+
statePrefix: workspacePrefix,
|
|
348451
|
+
entry: this.conversationEntryForDisk(archiveEntry)
|
|
348452
|
+
};
|
|
348453
|
+
const manifestPath = this.archiveManifestPath(path28.join(archiveDir, filename));
|
|
348454
|
+
const manifestTempPath = `${manifestPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
|
|
348455
|
+
try {
|
|
348456
|
+
await fs25.promises.writeFile(manifestTempPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
348457
|
+
await fs25.promises.rename(manifestTempPath, manifestPath);
|
|
348458
|
+
} finally {
|
|
348459
|
+
try {
|
|
348460
|
+
await fs25.promises.unlink(manifestTempPath);
|
|
348461
|
+
} catch {
|
|
348462
|
+
}
|
|
348463
|
+
}
|
|
348464
|
+
this.finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws);
|
|
348465
|
+
return filename;
|
|
348466
|
+
}
|
|
348467
|
+
finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws) {
|
|
348468
|
+
let nextActiveId = "";
|
|
348469
|
+
this.mutateStoredConversationState(ws, (latest) => {
|
|
348470
|
+
latest.conversations = latest.conversations || {};
|
|
348471
|
+
delete latest.conversations[stateKey2];
|
|
348472
|
+
const prefix = stateKey2.slice(0, Math.max(0, stateKey2.length - clean.length - 1)) + "-";
|
|
348473
|
+
const remaining = Object.keys(latest.conversations).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
|
|
348474
|
+
const currentActiveId = this.safeConversationId(latest.activeConversationId || this.activeConversationId || "default");
|
|
348475
|
+
if (clean === currentActiveId) latest.activeConversationId = remaining[0] || "default";
|
|
348476
|
+
nextActiveId = latest.activeConversationId || remaining[0] || "default";
|
|
348477
|
+
return latest;
|
|
348478
|
+
});
|
|
348479
|
+
this.workspaceConversations.delete(memoryKey);
|
|
348480
|
+
const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
|
|
348481
|
+
this.workspaceConversations.delete(duplicateMemoryKey);
|
|
348482
|
+
if (clean === this.safeConversationId(this.activeConversationId || "default")) {
|
|
348483
|
+
this.activeConversationId = nextActiveId || "default";
|
|
348484
|
+
this.loadWorkspaceConversationState();
|
|
348485
|
+
}
|
|
348486
|
+
}
|
|
348135
348487
|
listStoredConversationIds(stored) {
|
|
348136
348488
|
const prefix = `${this.workspaceConversationPrefix() || ""}-`;
|
|
348137
348489
|
return Object.keys(stored.conversations || {}).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
|
|
@@ -348657,9 +349009,9 @@ ${msg.content}
|
|
|
348657
349009
|
const provider = this.config.findProvider(providerId);
|
|
348658
349010
|
return all.filter((m2) => m2.provider_id === (provider?.id || providerId));
|
|
348659
349011
|
}
|
|
348660
|
-
async validateModels(selectedNames) {
|
|
349012
|
+
async validateModels(selectedNames, options = {}) {
|
|
348661
349013
|
if (this.modelValidationPromise) return this.modelValidationPromise;
|
|
348662
|
-
const validation = this.runModelValidation(selectedNames);
|
|
349014
|
+
const validation = this.runModelValidation(selectedNames, options.persist !== false);
|
|
348663
349015
|
this.modelValidationPromise = validation;
|
|
348664
349016
|
try {
|
|
348665
349017
|
return await validation;
|
|
@@ -348677,7 +349029,7 @@ ${msg.content}
|
|
|
348677
349029
|
recentChecks: this.modelValidationProgress.recentChecks.map((item) => ({ ...item }))
|
|
348678
349030
|
};
|
|
348679
349031
|
}
|
|
348680
|
-
async runModelValidation(selectedNames) {
|
|
349032
|
+
async runModelValidation(selectedNames, persist = true) {
|
|
348681
349033
|
const selectedModels = this.config.modelsForSelections(selectedNames);
|
|
348682
349034
|
if (!selectedModels.length) {
|
|
348683
349035
|
this.modelValidationProgress = {
|
|
@@ -348695,7 +349047,7 @@ ${msg.content}
|
|
|
348695
349047
|
}
|
|
348696
349048
|
const results = [];
|
|
348697
349049
|
const catalogByProvider = /* @__PURE__ */ new Map();
|
|
348698
|
-
const cache = new FileModelValidationCache(this.rootPath);
|
|
349050
|
+
const cache = new FileModelValidationCache(this.rootPath, { readOnly: !persist });
|
|
348699
349051
|
const checksPerModel = 11;
|
|
348700
349052
|
let currentModel = "";
|
|
348701
349053
|
let currentModelChecks = 0;
|
|
@@ -348827,7 +349179,7 @@ ${msg.content}
|
|
|
348827
349179
|
completedModels: this.modelValidationProgress.completedModels + 1
|
|
348828
349180
|
};
|
|
348829
349181
|
}
|
|
348830
|
-
this.config.save();
|
|
349182
|
+
if (persist) this.config.save();
|
|
348831
349183
|
this.modelValidationProgress = {
|
|
348832
349184
|
...this.modelValidationProgress,
|
|
348833
349185
|
running: false,
|
|
@@ -349079,7 +349431,8 @@ ${String(input.content || "").slice(0, 18e3)}`;
|
|
|
349079
349431
|
const text = typeof input === "string" ? input : String(input.text || "");
|
|
349080
349432
|
const inputEnvelope = typeof input === "string" ? null : input;
|
|
349081
349433
|
const hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
|
|
349082
|
-
this.
|
|
349434
|
+
const explicitFixedModel = this.model !== "" && this.model !== "auto";
|
|
349435
|
+
if (!explicitFixedModel) this.ensureUsableModelSelection();
|
|
349083
349436
|
const clientMessageId = String(inputEnvelope?.clientMessageId || "").trim();
|
|
349084
349437
|
const inputRunId = String(inputEnvelope?.runId || this.activeWorkRunId || "").trim();
|
|
349085
349438
|
const rawImages = typeof input === "string" ? [] : Array.isArray(input.images) ? input.images : [];
|
|
@@ -349167,7 +349520,13 @@ ${String(input.content || "").slice(0, 18e3)}`;
|
|
|
349167
349520
|
await this.evaluateAndSwitch(displayText, inputEnvelope?.routePolicy);
|
|
349168
349521
|
}
|
|
349169
349522
|
if (this.model && this.modelIsUnavailable(this.model)) {
|
|
349523
|
+
const requestedModel = this.model;
|
|
349170
349524
|
this.switchToFallbackModel();
|
|
349525
|
+
if (this.modelIsUnavailable(this.model)) {
|
|
349526
|
+
const message = `[Error] Model '${requestedModel || "unknown"}' is unavailable or not configured. Select a configured model or enable a valid provider before sending.`;
|
|
349527
|
+
this.status = "error";
|
|
349528
|
+
throw new Error(message);
|
|
349529
|
+
}
|
|
349171
349530
|
}
|
|
349172
349531
|
if (this.engine === "opencode") {
|
|
349173
349532
|
if (images.length) return [{ type: "text", text: "[Vision unavailable] The OpenCode engine does not accept Newmark image attachments." }];
|
|
@@ -350160,11 +350519,11 @@ Falling back to built-in engine.` }];
|
|
|
350160
350519
|
}
|
|
350161
350520
|
}
|
|
350162
350521
|
async maybeCompress(msgs, provider, signal, compressionModel, force = false) {
|
|
350163
|
-
if (signal?.aborted) return;
|
|
350164
|
-
if (!this.config.getBool("context", "auto_compress")) return;
|
|
350522
|
+
if (signal?.aborted) return false;
|
|
350523
|
+
if (!this.config.getBool("context", "auto_compress")) return false;
|
|
350165
350524
|
const total = msgs.reduce((sum, m2) => sum + (typeof m2.content === "string" ? m2.content.length : JSON.stringify(m2.content || "").length), 0);
|
|
350166
350525
|
const budget = this.compressionBudget(msgs);
|
|
350167
|
-
if (budget.estimatedTokens < budget.triggerTokens && !force) return;
|
|
350526
|
+
if (budget.estimatedTokens < budget.triggerTokens && !force) return false;
|
|
350168
350527
|
if (!force && this.lastCompression && String(msgs[0]?.content || "").includes(this.lastCompression.summary)) {
|
|
350169
350528
|
const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
|
|
350170
350529
|
const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
|
|
@@ -350172,16 +350531,16 @@ Falling back to built-in engine.` }];
|
|
|
350172
350531
|
const tokenGrowth = baselineTokens ? Math.max(0, budget.estimatedTokens - baselineTokens) : Number.POSITIVE_INFINITY;
|
|
350173
350532
|
const minCharGrowth = Math.max(12e3, Math.floor(baselineChars * 0.25));
|
|
350174
350533
|
const minTokenGrowth = Math.max(1024, Math.floor(budget.triggerTokens * 0.2));
|
|
350175
|
-
if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return;
|
|
350534
|
+
if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return false;
|
|
350176
350535
|
}
|
|
350177
350536
|
const originalMessageCount = msgs.length;
|
|
350178
350537
|
const configuredKeepLast = this.config.getNum("context", "keep_recent_messages") || 10;
|
|
350179
|
-
if (msgs.length <= 1) return;
|
|
350538
|
+
if (msgs.length <= 1) return false;
|
|
350180
350539
|
const continuationAnchorTokens = this.estimateContextTokens([this.postCompressionContinuationMessage()]);
|
|
350181
350540
|
const recentBudget = Math.max(64, budget.targetTokens - budget.summaryTokens - continuationAnchorTokens);
|
|
350182
350541
|
const recent = this.recentContextSuffix(msgs, configuredKeepLast, recentBudget);
|
|
350183
350542
|
const recentStart = Math.max(0, msgs.length - recent.length);
|
|
350184
|
-
if (recentStart <= 0) return;
|
|
350543
|
+
if (recentStart <= 0) return false;
|
|
350185
350544
|
const middle = msgs.slice(0, recentStart);
|
|
350186
350545
|
const currentInstruction = this.latestUserHistoryText(recent);
|
|
350187
350546
|
const compression = await this.buildCompressionSummary(
|
|
@@ -350193,7 +350552,7 @@ Falling back to built-in engine.` }];
|
|
|
350193
350552
|
compressionModel || this.activeModelName(),
|
|
350194
350553
|
currentInstruction
|
|
350195
350554
|
);
|
|
350196
|
-
if (signal?.aborted) return;
|
|
350555
|
+
if (signal?.aborted) return false;
|
|
350197
350556
|
const compressed = [{
|
|
350198
350557
|
role: "system",
|
|
350199
350558
|
content: compression.summary
|
|
@@ -350218,6 +350577,7 @@ Falling back to built-in engine.` }];
|
|
|
350218
350577
|
};
|
|
350219
350578
|
this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
|
|
350220
350579
|
this.persistCompressedHistory(compression.summary, recent.length, msgs);
|
|
350580
|
+
return true;
|
|
350221
350581
|
}
|
|
350222
350582
|
async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = "") {
|
|
350223
350583
|
const workspacePath = this.workspace.current?.path || this.rootPath;
|
|
@@ -351075,8 +351435,26 @@ var GoalStateImpl = class {
|
|
|
351075
351435
|
return s3;
|
|
351076
351436
|
}
|
|
351077
351437
|
checkComplete(response) {
|
|
351078
|
-
const
|
|
351079
|
-
|
|
351438
|
+
const lines = String(response || "").replace(/\r\n?/g, "\n").split("\n");
|
|
351439
|
+
const completionMarkers = "(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)";
|
|
351440
|
+
const explicitLinePatterns = [
|
|
351441
|
+
new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, "i"),
|
|
351442
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
|
|
351443
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i
|
|
351444
|
+
];
|
|
351445
|
+
const completionContext = new RegExp(completionMarkers, "i");
|
|
351446
|
+
const deferredBefore = new RegExp(`\\b(?:must|will|should|would|can|could)\\s+(?:still\\s+)?(?:contain(?:s|ed)?|include(?:s|d)?|say|state|use|write|appear)\\b.{0,180}${completionMarkers}`, "i");
|
|
351447
|
+
const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, "i");
|
|
351448
|
+
const pendingBefore = new RegExp(`\\b(?:one\\s+step\\s+remains|still\\s+required|one\\s+more\\s+(?:step|call|turn)|not\\s+(?:the\\s+)?final)\\b.{0,180}${completionMarkers}`, "i");
|
|
351449
|
+
const deferredAfter = new RegExp(`${completionMarkers}.{0,180}\\b(?:not\\s+(?:the\\s+)?final|not\\s+yet|one\\s+more\\s+model\\s+call|next\\s+(?:call|step)|not\\s+done)\\b`, "i");
|
|
351450
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
351451
|
+
const line = lines[index];
|
|
351452
|
+
if (!explicitLinePatterns.some((pattern) => pattern.test(line))) continue;
|
|
351453
|
+
const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(" ");
|
|
351454
|
+
if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context))) continue;
|
|
351455
|
+
return true;
|
|
351456
|
+
}
|
|
351457
|
+
return false;
|
|
351080
351458
|
}
|
|
351081
351459
|
};
|
|
351082
351460
|
|
|
@@ -351273,6 +351651,7 @@ var ConversationKernel = class {
|
|
|
351273
351651
|
runtime.runner.recordGuideReceipt(deferred2);
|
|
351274
351652
|
this.emitQueueUpdate(runtime);
|
|
351275
351653
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351654
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351276
351655
|
return deferred2;
|
|
351277
351656
|
}
|
|
351278
351657
|
if (runtime.guideAcceptanceClosedRunId === runtime.runId) {
|
|
@@ -351304,6 +351683,7 @@ var ConversationKernel = class {
|
|
|
351304
351683
|
createdAt: deferred2.createdAt
|
|
351305
351684
|
}]);
|
|
351306
351685
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351686
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351307
351687
|
return deferred2;
|
|
351308
351688
|
}
|
|
351309
351689
|
const queued = runtime.runner.queueActiveKernelMessage(safeEnvelope.text, "steer", clientMessageId, runtime.runId, safeEnvelope.images);
|
|
@@ -351334,6 +351714,7 @@ var ConversationKernel = class {
|
|
|
351334
351714
|
createdAt: deferred.createdAt
|
|
351335
351715
|
}]);
|
|
351336
351716
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351717
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351337
351718
|
return deferred;
|
|
351338
351719
|
}
|
|
351339
351720
|
checkpoint(target) {
|
|
@@ -351532,7 +351913,12 @@ var ConversationKernel = class {
|
|
|
351532
351913
|
if (runtime.runId === runId && runtime.stopRequestedRunId === runId) {
|
|
351533
351914
|
stopped = true;
|
|
351534
351915
|
} else {
|
|
351535
|
-
runtime.runner.finishConversationWorkRun(
|
|
351916
|
+
runtime.runner.finishConversationWorkRun(
|
|
351917
|
+
runId,
|
|
351918
|
+
"error",
|
|
351919
|
+
void 0,
|
|
351920
|
+
error instanceof Error ? error.message : String(error)
|
|
351921
|
+
);
|
|
351536
351922
|
throw error;
|
|
351537
351923
|
}
|
|
351538
351924
|
} finally {
|
|
@@ -351541,6 +351927,8 @@ var ConversationKernel = class {
|
|
|
351541
351927
|
if (runtime.stopRequestedRunId === runId) {
|
|
351542
351928
|
stopped = true;
|
|
351543
351929
|
this.settleCooperativeStop(runtime, runId);
|
|
351930
|
+
} else if (runtime.pendingNextTurn.length > 0) {
|
|
351931
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
351544
351932
|
}
|
|
351545
351933
|
}
|
|
351546
351934
|
}
|
|
@@ -351675,7 +352063,8 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
351675
352063
|
guideAcceptanceClosedRunId: "",
|
|
351676
352064
|
guideReceipts: /* @__PURE__ */ new Map(),
|
|
351677
352065
|
guideEnvelopes: /* @__PURE__ */ new Map(),
|
|
351678
|
-
goalContinuationTimer: void 0
|
|
352066
|
+
goalContinuationTimer: void 0,
|
|
352067
|
+
pendingContinuationRunId: void 0
|
|
351679
352068
|
};
|
|
351680
352069
|
runner.setGoalContinuationGate(() => {
|
|
351681
352070
|
this.queueState(runtime);
|
|
@@ -351747,6 +352136,28 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
351747
352136
|
});
|
|
351748
352137
|
}, 250);
|
|
351749
352138
|
}
|
|
352139
|
+
schedulePendingRuntimeContinuation(runtime, runId) {
|
|
352140
|
+
if (runtime.pendingContinuationRunId === runId) return;
|
|
352141
|
+
runtime.pendingContinuationRunId = runId;
|
|
352142
|
+
const active = runtime.activePromise;
|
|
352143
|
+
if (active) {
|
|
352144
|
+
const continueAfterSettlement = () => {
|
|
352145
|
+
runtime.pendingContinuationRunId = void 0;
|
|
352146
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
352147
|
+
};
|
|
352148
|
+
void active.then(continueAfterSettlement, continueAfterSettlement);
|
|
352149
|
+
return;
|
|
352150
|
+
}
|
|
352151
|
+
setImmediate(() => {
|
|
352152
|
+
runtime.pendingContinuationRunId = void 0;
|
|
352153
|
+
if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
|
|
352154
|
+
const next = runtime.pendingNextTurn.shift();
|
|
352155
|
+
if (!next) return;
|
|
352156
|
+
const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
|
|
352157
|
+
void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
|
|
352158
|
+
});
|
|
352159
|
+
});
|
|
352160
|
+
}
|
|
351750
352161
|
startGoalDrivenBuild(runtime) {
|
|
351751
352162
|
if (runtime.goalContinuationTimer) {
|
|
351752
352163
|
clearTimeout(runtime.goalContinuationTimer);
|