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;
|
|
@@ -327623,8 +327628,10 @@ var ConfigManager = class {
|
|
|
327623
327628
|
rootPath;
|
|
327624
327629
|
config;
|
|
327625
327630
|
workspaceOverrides;
|
|
327626
|
-
|
|
327631
|
+
readOnly;
|
|
327632
|
+
constructor(rootPath, options = {}) {
|
|
327627
327633
|
this.rootPath = rootPath;
|
|
327634
|
+
this.readOnly = options.readOnly === true;
|
|
327628
327635
|
this.workspaceOverrides = /* @__PURE__ */ new Map();
|
|
327629
327636
|
this.config = this.load();
|
|
327630
327637
|
}
|
|
@@ -327639,17 +327646,19 @@ var ConfigManager = class {
|
|
|
327639
327646
|
const raw = JSON.parse(readJsonText(cp));
|
|
327640
327647
|
const normalized = normalizeConfigShape(raw, true);
|
|
327641
327648
|
if (isCorruptConfig(raw, normalized)) {
|
|
327649
|
+
if (this.readOnly) return defaultConfig();
|
|
327642
327650
|
this.backupConfig(cp, "invalid-shape");
|
|
327643
327651
|
return this.writeRecoveredConfig(cp);
|
|
327644
327652
|
}
|
|
327645
327653
|
if (migrateProviderIdsInConfig(normalized)) {
|
|
327646
327654
|
try {
|
|
327647
|
-
fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
|
|
327655
|
+
if (!this.readOnly) fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
|
|
327648
327656
|
} catch {
|
|
327649
327657
|
}
|
|
327650
327658
|
}
|
|
327651
327659
|
return normalized;
|
|
327652
327660
|
} catch {
|
|
327661
|
+
if (this.readOnly) return defaultConfig();
|
|
327653
327662
|
this.backupConfig(cp, "invalid-json");
|
|
327654
327663
|
return this.writeRecoveredConfig(cp);
|
|
327655
327664
|
}
|
|
@@ -327695,10 +327704,12 @@ var ConfigManager = class {
|
|
|
327695
327704
|
this.config[section][key3] = { value: normalizedValue };
|
|
327696
327705
|
}
|
|
327697
327706
|
save() {
|
|
327707
|
+
if (this.readOnly) return;
|
|
327698
327708
|
const j2 = JSON.stringify(this.config, null, 2);
|
|
327699
327709
|
fs3.writeFileSync(path3.join(this.rootPath, "config.json"), j2, "utf-8");
|
|
327700
327710
|
}
|
|
327701
327711
|
saveTo(targetPath) {
|
|
327712
|
+
if (this.readOnly) return;
|
|
327702
327713
|
const j2 = JSON.stringify(this.config, null, 2);
|
|
327703
327714
|
fs3.writeFileSync(targetPath, j2, "utf-8");
|
|
327704
327715
|
}
|
|
@@ -327923,12 +327934,14 @@ var ConfigManager = class {
|
|
|
327923
327934
|
return providers;
|
|
327924
327935
|
}
|
|
327925
327936
|
writeRecoveredConfig(configPath) {
|
|
327937
|
+
if (this.readOnly) return defaultConfig();
|
|
327926
327938
|
const config = loadExampleConfig();
|
|
327927
327939
|
fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
|
|
327928
327940
|
fs3.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
327929
327941
|
return config;
|
|
327930
327942
|
}
|
|
327931
327943
|
backupConfig(configPath, reason) {
|
|
327944
|
+
if (this.readOnly) return;
|
|
327932
327945
|
try {
|
|
327933
327946
|
if (!fs3.existsSync(configPath)) return;
|
|
327934
327947
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -328273,7 +328286,10 @@ function defaultConfig() {
|
|
|
328273
328286
|
general: {
|
|
328274
328287
|
tone: { _description: "Conversation style", _type: "choice", _values: ["strict_simple", "casual_friendly"], value: "strict_simple" },
|
|
328275
328288
|
language: { _description: "Default language", _type: "choice", _values: ["en", "zh", "auto"], value: "auto" },
|
|
328276
|
-
|
|
328289
|
+
// A first-run desktop window must have a deterministic close/exit
|
|
328290
|
+
// contract. Users who explicitly choose minimize-to-tray keep that
|
|
328291
|
+
// choice, but a fresh install must not hide the process on OS close.
|
|
328292
|
+
close_behavior: { _description: "Close behavior", _type: "choice", _values: ["minimize", "exit"], value: "exit" },
|
|
328277
328293
|
default_input: { _description: "Default input mode", _type: "choice", _values: ["guide", "next"], value: "guide" },
|
|
328278
328294
|
auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
|
|
328279
328295
|
},
|
|
@@ -328612,6 +328628,38 @@ function providerAbortError(signal) {
|
|
|
328612
328628
|
if (!error.name || error.name === "Error") error.name = "AbortError";
|
|
328613
328629
|
return error;
|
|
328614
328630
|
}
|
|
328631
|
+
function providerStreamTimeoutError(timeoutMs) {
|
|
328632
|
+
const error = new Error("Stream read timeout");
|
|
328633
|
+
error.name = "TimeoutError";
|
|
328634
|
+
error.message = `Stream read timeout after ${timeoutMs}ms`;
|
|
328635
|
+
return error;
|
|
328636
|
+
}
|
|
328637
|
+
async function readProviderStreamChunk(reader, signal, timeoutMs = 3e4) {
|
|
328638
|
+
if (signal.aborted) throw providerAbortError(signal);
|
|
328639
|
+
let timer;
|
|
328640
|
+
let onAbort;
|
|
328641
|
+
const abortPromise = new Promise((_3, reject) => {
|
|
328642
|
+
onAbort = () => reject(providerAbortError(signal));
|
|
328643
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
328644
|
+
});
|
|
328645
|
+
const timeoutPromise = new Promise((_3, reject) => {
|
|
328646
|
+
timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
|
|
328647
|
+
});
|
|
328648
|
+
try {
|
|
328649
|
+
return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
|
|
328650
|
+
} catch (error) {
|
|
328651
|
+
if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
|
|
328652
|
+
try {
|
|
328653
|
+
await reader.cancel(error);
|
|
328654
|
+
} catch {
|
|
328655
|
+
}
|
|
328656
|
+
}
|
|
328657
|
+
throw error;
|
|
328658
|
+
} finally {
|
|
328659
|
+
if (timer) clearTimeout(timer);
|
|
328660
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
328661
|
+
}
|
|
328662
|
+
}
|
|
328615
328663
|
function parseProviderSse(raw) {
|
|
328616
328664
|
const events = [];
|
|
328617
328665
|
for (const block of String(raw || "").replace(/\r\n/g, "\n").split(/\n\n+/)) {
|
|
@@ -328847,12 +328895,7 @@ var ChatCompletionsAdapter = class {
|
|
|
328847
328895
|
let emittedTool = false;
|
|
328848
328896
|
try {
|
|
328849
328897
|
while (true) {
|
|
328850
|
-
|
|
328851
|
-
const readPromise = reader.read();
|
|
328852
|
-
const timeoutPromise = new Promise(
|
|
328853
|
-
(_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
|
|
328854
|
-
);
|
|
328855
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
328898
|
+
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
328856
328899
|
if (done) break;
|
|
328857
328900
|
buffer += decoder.decode(value, { stream: true });
|
|
328858
328901
|
const lines = buffer.split("\n");
|
|
@@ -329090,8 +329133,7 @@ var ResponsesAdapter = class {
|
|
|
329090
329133
|
let streamError = "";
|
|
329091
329134
|
try {
|
|
329092
329135
|
while (true) {
|
|
329093
|
-
|
|
329094
|
-
const { done, value } = await reader.read();
|
|
329136
|
+
const { done, value } = await readProviderStreamChunk(reader, signal);
|
|
329095
329137
|
if (done) break;
|
|
329096
329138
|
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
329097
329139
|
const blocks = buffer.split(/\n\n+/);
|
|
@@ -329301,6 +329343,16 @@ function createProviderAdapter(providerId, apiMode) {
|
|
|
329301
329343
|
}
|
|
329302
329344
|
|
|
329303
329345
|
// src/llm/provider.ts
|
|
329346
|
+
var DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 9e4;
|
|
329347
|
+
var MIN_PROVIDER_REQUEST_TIMEOUT_MS = 50;
|
|
329348
|
+
function providerTimeoutError(timeoutMs) {
|
|
329349
|
+
const error = new Error(`Provider request timed out after ${timeoutMs}ms`);
|
|
329350
|
+
error.name = "TimeoutError";
|
|
329351
|
+
return error;
|
|
329352
|
+
}
|
|
329353
|
+
function isProviderTimeoutError(error) {
|
|
329354
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
329355
|
+
}
|
|
329304
329356
|
function abortFailure(signal) {
|
|
329305
329357
|
const reason = signal?.reason;
|
|
329306
329358
|
const error = reason instanceof Error ? reason : new Error(reason ? String(reason) : "LLM request aborted");
|
|
@@ -329330,13 +329382,14 @@ function parseProviderSse2(raw) {
|
|
|
329330
329382
|
return events;
|
|
329331
329383
|
}
|
|
329332
329384
|
var LLMProvider = class _LLMProvider {
|
|
329333
|
-
constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false) {
|
|
329385
|
+
constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329334
329386
|
this.name = name50;
|
|
329335
329387
|
this.baseUrl = baseUrl;
|
|
329336
329388
|
this.apiKey = apiKey;
|
|
329337
329389
|
this.explicitProtocol = explicitProtocol;
|
|
329338
329390
|
this.openAIMode = openAIMode;
|
|
329339
329391
|
this.useProviderAdaptersV2 = useProviderAdaptersV2;
|
|
329392
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
329340
329393
|
}
|
|
329341
329394
|
name;
|
|
329342
329395
|
baseUrl;
|
|
@@ -329344,8 +329397,25 @@ var LLMProvider = class _LLMProvider {
|
|
|
329344
329397
|
explicitProtocol;
|
|
329345
329398
|
openAIMode;
|
|
329346
329399
|
useProviderAdaptersV2;
|
|
329400
|
+
requestTimeoutMs;
|
|
329347
329401
|
static nodeHttpTransport = null;
|
|
329348
329402
|
static powershellTransport = null;
|
|
329403
|
+
effectiveRequestTimeout(timeoutMs) {
|
|
329404
|
+
const requested = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
329405
|
+
const configured = Number.isFinite(this.requestTimeoutMs) && this.requestTimeoutMs > 0 ? this.requestTimeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
329406
|
+
return Math.max(MIN_PROVIDER_REQUEST_TIMEOUT_MS, Math.min(requested, configured));
|
|
329407
|
+
}
|
|
329408
|
+
async withRequestTimeout(promise, timeoutMs, signal) {
|
|
329409
|
+
let timer;
|
|
329410
|
+
const timeoutPromise = new Promise((_3, reject) => {
|
|
329411
|
+
timer = setTimeout(() => reject(providerTimeoutError(timeoutMs)), timeoutMs);
|
|
329412
|
+
});
|
|
329413
|
+
try {
|
|
329414
|
+
return await abortable(Promise.race([promise, timeoutPromise]), signal);
|
|
329415
|
+
} finally {
|
|
329416
|
+
if (timer) clearTimeout(timer);
|
|
329417
|
+
}
|
|
329418
|
+
}
|
|
329349
329419
|
intelligenceConfig(tier) {
|
|
329350
329420
|
switch (tier) {
|
|
329351
329421
|
case "low":
|
|
@@ -329447,6 +329517,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329447
329517
|
};
|
|
329448
329518
|
}
|
|
329449
329519
|
async postJsonWithFetchFallback(url, headers, body, timeoutMs = 12e4, signal) {
|
|
329520
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329450
329521
|
if (this.isPlainHttpLoopback(url)) {
|
|
329451
329522
|
const pathname = (() => {
|
|
329452
329523
|
try {
|
|
@@ -329456,7 +329527,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329456
329527
|
}
|
|
329457
329528
|
})();
|
|
329458
329529
|
this.transportDiagnostic("loopback:start", pathname);
|
|
329459
|
-
const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
|
|
329530
|
+
const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
329460
329531
|
this.transportDiagnostic("loopback:complete", `status=${local.status} bytes=${Buffer.byteLength(local.body || "")}`);
|
|
329461
329532
|
return {
|
|
329462
329533
|
ok: local.status >= 200 && local.status < 300,
|
|
@@ -329470,7 +329541,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329470
329541
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
329471
329542
|
if (signal?.aborted) forwardAbort();
|
|
329472
329543
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
329473
|
-
const timer = setTimeout(() => abort.abort(),
|
|
329544
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
329474
329545
|
try {
|
|
329475
329546
|
const response = await fetch(url, {
|
|
329476
329547
|
method: "POST",
|
|
@@ -329481,8 +329552,9 @@ var LLMProvider = class _LLMProvider {
|
|
|
329481
329552
|
return response;
|
|
329482
329553
|
} catch (e3) {
|
|
329483
329554
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329555
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
329484
329556
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
329485
|
-
const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
|
|
329557
|
+
const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
329486
329558
|
return {
|
|
329487
329559
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
329488
329560
|
status: fallback.status,
|
|
@@ -329496,14 +329568,16 @@ var LLMProvider = class _LLMProvider {
|
|
|
329496
329568
|
}
|
|
329497
329569
|
}
|
|
329498
329570
|
async getJsonWithFetchFallback(url, headers, timeoutMs = 3e4) {
|
|
329571
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329499
329572
|
const abort = new AbortController();
|
|
329500
|
-
const timer = setTimeout(() => abort.abort(),
|
|
329573
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
329501
329574
|
try {
|
|
329502
329575
|
const response = await fetch(url, { method: "GET", headers, signal: abort.signal });
|
|
329503
329576
|
return response;
|
|
329504
329577
|
} catch (e3) {
|
|
329578
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
329505
329579
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
329506
|
-
const fallback = await this.nodeHttpJson("GET", url, headers);
|
|
329580
|
+
const fallback = await this.nodeHttpJson("GET", url, headers, "", void 0, effectiveTimeout);
|
|
329507
329581
|
return {
|
|
329508
329582
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
329509
329583
|
status: fallback.status,
|
|
@@ -329516,14 +329590,16 @@ var LLMProvider = class _LLMProvider {
|
|
|
329516
329590
|
}
|
|
329517
329591
|
}
|
|
329518
329592
|
shouldUseNodeHttpFallback(error) {
|
|
329519
|
-
return error instanceof TypeError && /fetch failed/i.test(error.message)
|
|
329593
|
+
return error instanceof TypeError && /fetch failed/i.test(error.message);
|
|
329520
329594
|
}
|
|
329521
|
-
nodeHttpJson(method, urlValue, headers, body = "", signal) {
|
|
329595
|
+
nodeHttpJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329596
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329522
329597
|
if (_LLMProvider.nodeHttpTransport) {
|
|
329523
|
-
return
|
|
329598
|
+
return this.withRequestTimeout(_LLMProvider.nodeHttpTransport(method, urlValue, headers, body), effectiveTimeout, signal).catch((error) => {
|
|
329524
329599
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329600
|
+
if (isProviderTimeoutError(error)) throw error;
|
|
329525
329601
|
if (process.platform === "win32") {
|
|
329526
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
329602
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
329527
329603
|
}
|
|
329528
329604
|
throw error;
|
|
329529
329605
|
});
|
|
@@ -329568,8 +329644,8 @@ var LLMProvider = class _LLMProvider {
|
|
|
329568
329644
|
else fail(new Error("Node HTTP response closed before completion"));
|
|
329569
329645
|
});
|
|
329570
329646
|
});
|
|
329571
|
-
req.setTimeout(
|
|
329572
|
-
req.destroy(
|
|
329647
|
+
req.setTimeout(effectiveTimeout, () => {
|
|
329648
|
+
req.destroy(providerTimeoutError(effectiveTimeout));
|
|
329573
329649
|
});
|
|
329574
329650
|
req.on("error", reject);
|
|
329575
329651
|
const onAbort = () => req.destroy(abortFailure(signal));
|
|
@@ -329580,15 +329656,17 @@ var LLMProvider = class _LLMProvider {
|
|
|
329580
329656
|
req.end();
|
|
329581
329657
|
}).catch((error) => {
|
|
329582
329658
|
if (signal?.aborted) throw abortFailure(signal);
|
|
329659
|
+
if (isProviderTimeoutError(error)) throw error;
|
|
329583
329660
|
if (process.platform === "win32") {
|
|
329584
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
329661
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
329585
329662
|
}
|
|
329586
329663
|
throw error;
|
|
329587
329664
|
});
|
|
329588
329665
|
}
|
|
329589
|
-
powershellJson(method, urlValue, headers, body = "", signal) {
|
|
329666
|
+
powershellJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
329667
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
329590
329668
|
if (_LLMProvider.powershellTransport) {
|
|
329591
|
-
return _LLMProvider.powershellTransport(method, urlValue, headers, body);
|
|
329669
|
+
return this.withRequestTimeout(_LLMProvider.powershellTransport(method, urlValue, headers, body), effectiveTimeout, signal);
|
|
329592
329670
|
}
|
|
329593
329671
|
return new Promise((resolve16, reject) => {
|
|
329594
329672
|
const headerJson = JSON.stringify(headers);
|
|
@@ -329617,7 +329695,7 @@ var LLMProvider = class _LLMProvider {
|
|
|
329617
329695
|
" $raw = $headerJson | ConvertFrom-Json",
|
|
329618
329696
|
" foreach ($p in $raw.PSObject.Properties) { $headers[$p.Name] = [string]$p.Value }",
|
|
329619
329697
|
"}",
|
|
329620
|
-
|
|
329698
|
+
`'$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec = ${Math.max(1, Math.ceil(effectiveTimeout / 1e3))} }`,
|
|
329621
329699
|
'if ($method -eq "POST") { $params["Body"] = $bodyJson }',
|
|
329622
329700
|
'if ($method -eq "POST") { $params["ContentType"] = "application/json; charset=utf-8" }',
|
|
329623
329701
|
"$resp = Invoke-WebRequest @params",
|
|
@@ -329649,8 +329727,8 @@ var LLMProvider = class _LLMProvider {
|
|
|
329649
329727
|
const timer = setTimeout(() => {
|
|
329650
329728
|
child.kill();
|
|
329651
329729
|
cleanup();
|
|
329652
|
-
reject(
|
|
329653
|
-
},
|
|
329730
|
+
reject(providerTimeoutError(effectiveTimeout));
|
|
329731
|
+
}, effectiveTimeout + 5e3);
|
|
329654
329732
|
child.stdout.setEncoding("utf8");
|
|
329655
329733
|
child.stderr.setEncoding("utf8");
|
|
329656
329734
|
child.stdout.on("data", (chunk) => {
|
|
@@ -330098,10 +330176,10 @@ ${responsePath}
|
|
|
330098
330176
|
return this.shouldUseResponsesFallback(Number(match[1]), errorText);
|
|
330099
330177
|
}
|
|
330100
330178
|
/**
|
|
330101
|
-
* Loopback-aware transport injected into adapter `execute`.
|
|
330102
|
-
*
|
|
330103
|
-
*
|
|
330104
|
-
*
|
|
330179
|
+
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
330180
|
+
* requests retain the fetch-to-node fallback for transport failures, while
|
|
330181
|
+
* a local deadline is returned directly so one request cannot become a
|
|
330182
|
+
* second Windows fallback request.
|
|
330105
330183
|
*/
|
|
330106
330184
|
buildProviderAdapterTransport() {
|
|
330107
330185
|
return async (request, signal) => {
|
|
@@ -330110,7 +330188,8 @@ ${responsePath}
|
|
|
330110
330188
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330111
330189
|
if (signal?.aborted) forwardAbort();
|
|
330112
330190
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330113
|
-
const
|
|
330191
|
+
const effectiveTimeout = this.effectiveRequestTimeout(12e4);
|
|
330192
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
330114
330193
|
try {
|
|
330115
330194
|
try {
|
|
330116
330195
|
return await fetch(request.url, {
|
|
@@ -330121,6 +330200,7 @@ ${responsePath}
|
|
|
330121
330200
|
});
|
|
330122
330201
|
} catch (error) {
|
|
330123
330202
|
if (signal?.aborted) throw abortFailure(signal);
|
|
330203
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
330124
330204
|
if (!this.shouldUseNodeHttpFallback(error)) throw error;
|
|
330125
330205
|
const fallbackHeaders = { ...request.headers };
|
|
330126
330206
|
delete fallbackHeaders["Accept"];
|
|
@@ -330128,7 +330208,7 @@ ${responsePath}
|
|
|
330128
330208
|
request.url,
|
|
330129
330209
|
fallbackHeaders,
|
|
330130
330210
|
{ ...request.body, stream: false },
|
|
330131
|
-
|
|
330211
|
+
effectiveTimeout,
|
|
330132
330212
|
signal
|
|
330133
330213
|
);
|
|
330134
330214
|
return this.toTransportResponse(fallback);
|
|
@@ -330238,7 +330318,8 @@ ${responsePath}
|
|
|
330238
330318
|
const forwardAbort = () => abort.abort(signal?.reason);
|
|
330239
330319
|
if (signal?.aborted) forwardAbort();
|
|
330240
330320
|
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
330241
|
-
const
|
|
330321
|
+
const effectiveTimeout = this.effectiveRequestTimeout(12e4);
|
|
330322
|
+
const timeout = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
330242
330323
|
let reader = null;
|
|
330243
330324
|
try {
|
|
330244
330325
|
let response;
|
|
@@ -330250,9 +330331,10 @@ ${responsePath}
|
|
|
330250
330331
|
signal: abort.signal
|
|
330251
330332
|
});
|
|
330252
330333
|
} catch (e3) {
|
|
330334
|
+
if (signal?.aborted) throw abortFailure(signal);
|
|
330335
|
+
if (abort.signal.aborted) throw abortFailure(abort.signal);
|
|
330253
330336
|
if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
|
|
330254
330337
|
clearTimeout(timeout);
|
|
330255
|
-
if (signal?.aborted) throw abortFailure(signal);
|
|
330256
330338
|
yield* this.githubModelsChatNonStreaming(url, body, signal);
|
|
330257
330339
|
return;
|
|
330258
330340
|
}
|
|
@@ -330272,13 +330354,9 @@ ${responsePath}
|
|
|
330272
330354
|
let currentReasoningContent = "";
|
|
330273
330355
|
let contentPolicyBlocked = false;
|
|
330274
330356
|
let emittedContent = false;
|
|
330357
|
+
const streamSignal = signal || new AbortController().signal;
|
|
330275
330358
|
while (true) {
|
|
330276
|
-
|
|
330277
|
-
const readPromise = reader.read();
|
|
330278
|
-
const timeoutPromise = new Promise(
|
|
330279
|
-
(_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
|
|
330280
|
-
);
|
|
330281
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
330359
|
+
const { done, value } = await readProviderStreamChunk(reader, streamSignal);
|
|
330282
330360
|
if (done) break;
|
|
330283
330361
|
buffer += decoder.decode(value, { stream: true });
|
|
330284
330362
|
const lines = buffer.split("\n");
|
|
@@ -335773,10 +335851,102 @@ function browserVisualFallback(runtimeKey, observationId) {
|
|
|
335773
335851
|
return browserFallbacks.get(key(runtimeKey, observationId)) || null;
|
|
335774
335852
|
}
|
|
335775
335853
|
|
|
335854
|
+
// src/core/computerUseSession.ts
|
|
335855
|
+
var COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
|
|
335856
|
+
var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
335857
|
+
var ComputerUseSessionRegistry = class {
|
|
335858
|
+
constructor(ttlMs = COMPUTER_USE_LOCK_TTL_MS) {
|
|
335859
|
+
this.ttlMs = ttlMs;
|
|
335860
|
+
}
|
|
335861
|
+
ttlMs;
|
|
335862
|
+
enabledByRuntime = /* @__PURE__ */ new Map();
|
|
335863
|
+
activeLease = null;
|
|
335864
|
+
authorize(action, scope, dryRun = false) {
|
|
335865
|
+
const normalizedAction = String(action || "").trim().toLowerCase();
|
|
335866
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335867
|
+
const now2 = Date.now();
|
|
335868
|
+
this.clearExpired(now2);
|
|
335869
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
335870
|
+
return this.occupiedError(normalizedAction, scope.ownerLabel, this.activeLease.ownerLabel);
|
|
335871
|
+
}
|
|
335872
|
+
if (normalizedAction === "takeover_stop") return null;
|
|
335873
|
+
const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
|
|
335874
|
+
const readOnly = normalizedAction === "observe" || normalizedAction === "app_list" || normalizedAction === "app_observe" || normalizedAction === "wait";
|
|
335875
|
+
if (!enabled && !readOnly && !dryRun && normalizedAction !== "takeover_start") {
|
|
335876
|
+
return JSON.stringify({
|
|
335877
|
+
ok: false,
|
|
335878
|
+
action: normalizedAction,
|
|
335879
|
+
error: `ComputerUse is disabled for ${scope.ownerLabel}. Enable ComputerUse for this conversation before sending desktop operations.`,
|
|
335880
|
+
computer_use_enabled: false,
|
|
335881
|
+
requested_owner: scope.ownerLabel
|
|
335882
|
+
}, null, 2);
|
|
335883
|
+
}
|
|
335884
|
+
if (normalizedAction === "takeover_start") this.enabledByRuntime.set(runtimeKey, true);
|
|
335885
|
+
if (!this.activeLease) {
|
|
335886
|
+
this.activeLease = { ...scope, runtimeKey, updatedAt: now2 };
|
|
335887
|
+
} else {
|
|
335888
|
+
this.activeLease.updatedAt = now2;
|
|
335889
|
+
}
|
|
335890
|
+
return null;
|
|
335891
|
+
}
|
|
335892
|
+
complete(action, scope) {
|
|
335893
|
+
const normalizedAction = String(action || "").trim().toLowerCase();
|
|
335894
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335895
|
+
if (normalizedAction === "takeover_stop") {
|
|
335896
|
+
if (!this.activeLease || this.activeLease.runtimeKey === runtimeKey) this.activeLease = null;
|
|
335897
|
+
this.enabledByRuntime.set(runtimeKey, false);
|
|
335898
|
+
return;
|
|
335899
|
+
}
|
|
335900
|
+
if (this.activeLease?.runtimeKey === runtimeKey) this.activeLease.updatedAt = Date.now();
|
|
335901
|
+
}
|
|
335902
|
+
setEnabled(scope, enabled) {
|
|
335903
|
+
const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
|
|
335904
|
+
this.clearExpired();
|
|
335905
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
335906
|
+
return { ok: false, error: this.occupiedError("toggle", scope.ownerLabel, this.activeLease.ownerLabel), state: this.state(runtimeKey) };
|
|
335907
|
+
}
|
|
335908
|
+
this.enabledByRuntime.set(runtimeKey, enabled !== false);
|
|
335909
|
+
if (enabled === false && this.activeLease?.runtimeKey === runtimeKey) this.activeLease = null;
|
|
335910
|
+
return { ok: true, state: this.state(runtimeKey) };
|
|
335911
|
+
}
|
|
335912
|
+
state(runtimeKey) {
|
|
335913
|
+
const key3 = String(runtimeKey || "").trim() || "conversation:default";
|
|
335914
|
+
this.clearExpired();
|
|
335915
|
+
const lease = this.activeLease;
|
|
335916
|
+
return {
|
|
335917
|
+
runtimeKey: key3,
|
|
335918
|
+
enabled: this.enabledByRuntime.get(key3) !== false,
|
|
335919
|
+
occupied: !!lease,
|
|
335920
|
+
...lease ? { ownerLabel: lease.ownerLabel, updatedAt: lease.updatedAt } : {}
|
|
335921
|
+
};
|
|
335922
|
+
}
|
|
335923
|
+
cancelTarget(runtimeKey) {
|
|
335924
|
+
const key3 = String(runtimeKey || "").trim();
|
|
335925
|
+
if (!key3) return false;
|
|
335926
|
+
const hadActiveLease = this.activeLease?.runtimeKey === key3;
|
|
335927
|
+
if (hadActiveLease) this.activeLease = null;
|
|
335928
|
+
this.enabledByRuntime.set(key3, false);
|
|
335929
|
+
return hadActiveLease;
|
|
335930
|
+
}
|
|
335931
|
+
clearExpired(now2 = Date.now()) {
|
|
335932
|
+
if (this.activeLease && now2 - this.activeLease.updatedAt > this.ttlMs) {
|
|
335933
|
+
this.activeLease = null;
|
|
335934
|
+
}
|
|
335935
|
+
}
|
|
335936
|
+
occupiedError(action, requestedOwner, activeOwner) {
|
|
335937
|
+
return JSON.stringify({
|
|
335938
|
+
ok: false,
|
|
335939
|
+
action,
|
|
335940
|
+
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.`,
|
|
335941
|
+
lock_owner: activeOwner,
|
|
335942
|
+
requested_owner: requestedOwner
|
|
335943
|
+
}, null, 2);
|
|
335944
|
+
}
|
|
335945
|
+
};
|
|
335946
|
+
var defaultComputerUseSessionRegistry = new ComputerUseSessionRegistry();
|
|
335947
|
+
|
|
335776
335948
|
// src/tools/index.ts
|
|
335777
335949
|
var globSync = require_index_min().sync;
|
|
335778
|
-
var computerUseLock = null;
|
|
335779
|
-
var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
335780
335950
|
function normalizeComputerUseAction(action) {
|
|
335781
335951
|
return String(action || "").trim().toLowerCase();
|
|
335782
335952
|
}
|
|
@@ -335861,49 +336031,24 @@ async function abortableToolDelay(durationMs, signal) {
|
|
|
335861
336031
|
if (signal?.aborted) abort();
|
|
335862
336032
|
});
|
|
335863
336033
|
}
|
|
335864
|
-
function
|
|
335865
|
-
|
|
335866
|
-
|
|
335867
|
-
|
|
335868
|
-
|
|
335869
|
-
function computerUseLockError(action, owner) {
|
|
335870
|
-
return JSON.stringify({
|
|
335871
|
-
ok: false,
|
|
335872
|
-
action,
|
|
335873
|
-
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.`,
|
|
335874
|
-
lock_owner: computerUseLock?.owner || "",
|
|
335875
|
-
requested_owner: owner
|
|
335876
|
-
}, null, 2);
|
|
335877
|
-
}
|
|
335878
|
-
function acquireComputerUseLock(action, owner, wsPath) {
|
|
335879
|
-
const now2 = Date.now();
|
|
335880
|
-
clearStaleComputerUseLock(now2);
|
|
335881
|
-
if (computerUseLock && computerUseLock.owner !== owner) {
|
|
335882
|
-
return computerUseLockError(action, owner);
|
|
335883
|
-
}
|
|
335884
|
-
computerUseLock = {
|
|
335885
|
-
owner,
|
|
335886
|
-
workspacePath: path15.resolve(wsPath || process.cwd()),
|
|
335887
|
-
acquiredAt: computerUseLock?.owner === owner ? computerUseLock.acquiredAt : now2,
|
|
335888
|
-
updatedAt: now2
|
|
336034
|
+
function computerUseSessionScope(context, wsPath, owner) {
|
|
336035
|
+
return {
|
|
336036
|
+
runtimeKey: browserUseScope(context, wsPath).runtimeKey,
|
|
336037
|
+
ownerLabel: owner,
|
|
336038
|
+
workspacePath: path15.resolve(wsPath || process.cwd())
|
|
335889
336039
|
};
|
|
335890
|
-
return null;
|
|
335891
336040
|
}
|
|
335892
|
-
function
|
|
335893
|
-
|
|
335894
|
-
if (computerUseLock && computerUseLock.owner !== owner) {
|
|
335895
|
-
return computerUseLockError(action, owner);
|
|
335896
|
-
}
|
|
335897
|
-
if (computerUseLock?.owner === owner) computerUseLock = null;
|
|
335898
|
-
return null;
|
|
336041
|
+
function acquireComputerUseLock(action, owner, wsPath, context = {}, dryRun = false) {
|
|
336042
|
+
return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner), dryRun);
|
|
335899
336043
|
}
|
|
335900
|
-
function
|
|
335901
|
-
|
|
335902
|
-
|
|
335903
|
-
return computerUseLockError(action, owner);
|
|
335904
|
-
}
|
|
336044
|
+
function releaseComputerUseLock(action, owner, context = {}, wsPath = context.workspacePath || "") {
|
|
336045
|
+
const scope = computerUseSessionScope(context, wsPath, owner);
|
|
336046
|
+
defaultComputerUseSessionRegistry.complete(action, scope);
|
|
335905
336047
|
return null;
|
|
335906
336048
|
}
|
|
336049
|
+
function assertComputerUseLockOwner(action, owner, context = {}, wsPath = context.workspacePath || "") {
|
|
336050
|
+
return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner));
|
|
336051
|
+
}
|
|
335907
336052
|
var ToolExecutor = class {
|
|
335908
336053
|
constructor(root2, config, ssh, workspace) {
|
|
335909
336054
|
this.config = config;
|
|
@@ -336454,7 +336599,7 @@ var ToolExecutor = class {
|
|
|
336454
336599
|
case "computer_use": {
|
|
336455
336600
|
const action = normalizeComputerUseAction(g2("action"));
|
|
336456
336601
|
const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
|
|
336457
|
-
const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner) : acquireComputerUseLock(action, owner, wsPath);
|
|
336602
|
+
const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner, context, wsPath) : acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
|
|
336458
336603
|
if (lockGuard) return lockGuard;
|
|
336459
336604
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
336460
336605
|
try {
|
|
@@ -336468,7 +336613,7 @@ var ToolExecutor = class {
|
|
|
336468
336613
|
}, 12e4, context.signal);
|
|
336469
336614
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
336470
336615
|
} finally {
|
|
336471
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336616
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336472
336617
|
}
|
|
336473
336618
|
}
|
|
336474
336619
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
|
|
@@ -336485,7 +336630,7 @@ var ToolExecutor = class {
|
|
|
336485
336630
|
const result = await requestUtilityHostTool("computer_use", args, trustedComputerUseContext, 12e4, context.signal);
|
|
336486
336631
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
336487
336632
|
} finally {
|
|
336488
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336633
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336489
336634
|
}
|
|
336490
336635
|
}
|
|
336491
336636
|
const output = await runComputerUse({
|
|
@@ -336526,7 +336671,7 @@ var ToolExecutor = class {
|
|
|
336526
336671
|
durationMs: Number(step.duration_ms || 0)
|
|
336527
336672
|
})) : void 0
|
|
336528
336673
|
});
|
|
336529
|
-
if (action === "takeover_stop") releaseComputerUseLock(action, owner);
|
|
336674
|
+
if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
|
|
336530
336675
|
return output;
|
|
336531
336676
|
}
|
|
336532
336677
|
case "terminal_takeover": {
|
|
@@ -337007,9 +337152,17 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
|
|
|
337007
337152
|
}
|
|
337008
337153
|
}
|
|
337009
337154
|
async browserRun(request, signal, context = {}, workspacePath = this.root) {
|
|
337155
|
+
const scope = browserUseScope(context, workspacePath);
|
|
337156
|
+
const scopedRequest = {
|
|
337157
|
+
...request,
|
|
337158
|
+
target: {
|
|
337159
|
+
workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
|
|
337160
|
+
conversationId: context.conversationId || "default",
|
|
337161
|
+
runtimeKey: scope.runtimeKey
|
|
337162
|
+
}
|
|
337163
|
+
};
|
|
337010
337164
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
337011
|
-
const
|
|
337012
|
-
const result2 = await requestWindowsHostTool("browser_control", request, {
|
|
337165
|
+
const result2 = await requestWindowsHostTool("browser_control", scopedRequest, {
|
|
337013
337166
|
conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || "default",
|
|
337014
337167
|
workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
|
|
337015
337168
|
actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
|
|
@@ -337019,10 +337172,10 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
|
|
|
337019
337172
|
return this.formatBrowserResult(result2);
|
|
337020
337173
|
}
|
|
337021
337174
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
|
|
337022
|
-
const result2 = await requestUtilityHostTool("browser_control",
|
|
337175
|
+
const result2 = await requestUtilityHostTool("browser_control", scopedRequest, void 0, 3e4, signal);
|
|
337023
337176
|
return this.formatBrowserResult(result2);
|
|
337024
337177
|
}
|
|
337025
|
-
const result = await BrowserControl.run(
|
|
337178
|
+
const result = await BrowserControl.run(scopedRequest, signal);
|
|
337026
337179
|
return this.formatBrowserResult(result);
|
|
337027
337180
|
}
|
|
337028
337181
|
formatBrowserResult(result) {
|
|
@@ -337849,6 +338002,19 @@ var WorkspaceManager = class {
|
|
|
337849
338002
|
this.saveState();
|
|
337850
338003
|
}
|
|
337851
338004
|
}
|
|
338005
|
+
/**
|
|
338006
|
+
* Re-read the registry and persisted current-workspace pointer after another
|
|
338007
|
+
* Newmark entrypoint updates Work/*.json. This intentionally does not create
|
|
338008
|
+
* a workspace: a refresh must reflect the shared on-disk state exactly.
|
|
338009
|
+
*/
|
|
338010
|
+
reloadFromStorage() {
|
|
338011
|
+
if (this.detached) return this.current;
|
|
338012
|
+
this.scan();
|
|
338013
|
+
this.validate();
|
|
338014
|
+
this.current = null;
|
|
338015
|
+
this.restoreCurrent();
|
|
338016
|
+
return this.current;
|
|
338017
|
+
}
|
|
337852
338018
|
saveInternal() {
|
|
337853
338019
|
if (this.detached) return;
|
|
337854
338020
|
const p = path16.join(this.rootPath, "Work", "Local.json");
|
|
@@ -339900,6 +340066,10 @@ var ProviderRunError = class extends Error {
|
|
|
339900
340066
|
function kernelTurnFailed(agent, turn) {
|
|
339901
340067
|
return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
|
|
339902
340068
|
}
|
|
340069
|
+
function providerTurnIsEmpty(turn) {
|
|
340070
|
+
return /provider returned an empty response/i.test(`${turn.errorMessage}
|
|
340071
|
+
${turn.text}`);
|
|
340072
|
+
}
|
|
339903
340073
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
339904
340074
|
const last = messages[messages.length - 1];
|
|
339905
340075
|
if (last?.role !== "assistant") return;
|
|
@@ -339935,17 +340105,35 @@ function normalizePublicProviderError(error, secrets = []) {
|
|
|
339935
340105
|
}
|
|
339936
340106
|
return raw.slice(0, 1200);
|
|
339937
340107
|
}
|
|
340108
|
+
function throwIfKernelAborted(signal) {
|
|
340109
|
+
if (!signal?.aborted) return;
|
|
340110
|
+
const reason = signal.reason;
|
|
340111
|
+
if (reason instanceof Error) {
|
|
340112
|
+
reason.name = "AbortError";
|
|
340113
|
+
throw reason;
|
|
340114
|
+
}
|
|
340115
|
+
const error = new Error(reason ? String(reason) : "Agent run aborted");
|
|
340116
|
+
error.name = "AbortError";
|
|
340117
|
+
throw error;
|
|
340118
|
+
}
|
|
339938
340119
|
async function runAgentKernel(agent) {
|
|
339939
340120
|
const stopContextTimer = performanceTimer("context_prepare", { conversationId: agent.activeConversationId });
|
|
340121
|
+
const processSignal = agent.activeProcessSignal();
|
|
340122
|
+
if (processSignal?.aborted) {
|
|
340123
|
+
stopContextTimer();
|
|
340124
|
+
throwIfKernelAborted(processSignal);
|
|
340125
|
+
}
|
|
339940
340126
|
if (!agent.engineModel()) {
|
|
340127
|
+
const message = "No LLM configured. Add provider in Settings > Models.";
|
|
339941
340128
|
agent.status = "error";
|
|
339942
340129
|
agent.saveWorkspaceConversationState();
|
|
339943
|
-
|
|
340130
|
+
throw new Error(message);
|
|
339944
340131
|
}
|
|
339945
340132
|
const [{ Agent: NativeAgent }, KernelStreamCompat] = await Promise.all([
|
|
339946
340133
|
Promise.resolve().then(() => (init_agentKernel(), agentKernel_exports)),
|
|
339947
340134
|
Promise.resolve().then(() => (init_stream_types(), stream_types_exports))
|
|
339948
340135
|
]);
|
|
340136
|
+
throwIfKernelAborted(processSignal);
|
|
339949
340137
|
const toolProvisioning = new ToolProvisionSession([], []);
|
|
339950
340138
|
let activeToolSurfaceIdentity = "";
|
|
339951
340139
|
let activeToolSurfaceNotice = "";
|
|
@@ -339971,6 +340159,7 @@ async function runAgentKernel(agent) {
|
|
|
339971
340159
|
const initialToolSurface = refreshToolSurface(true);
|
|
339972
340160
|
const assembledContext = agent.assembleContextV2(initialToolSurface.systemPromptNotice);
|
|
339973
340161
|
const systemPrompt = assembledContext.text;
|
|
340162
|
+
throwIfKernelAborted(processSignal);
|
|
339974
340163
|
let providerRequestCount = 0;
|
|
339975
340164
|
let bootstrappedCompressionAt = agent.lastCompression?.at || "";
|
|
339976
340165
|
stopContextTimer();
|
|
@@ -339991,6 +340180,24 @@ async function runAgentKernel(agent) {
|
|
|
339991
340180
|
kernel2.state.tools = toKernelTools(agent, initialToolSurface.definitions, toolProvisioning);
|
|
339992
340181
|
kernel2.state.messages = toKernelMessages(agent);
|
|
339993
340182
|
agent.attachAgentKernelRuntime(kernel2);
|
|
340183
|
+
let detachProcessAbort = () => {
|
|
340184
|
+
};
|
|
340185
|
+
if (processSignal) {
|
|
340186
|
+
const abortKernel = () => kernel2.abort();
|
|
340187
|
+
if (processSignal.aborted) {
|
|
340188
|
+
kernel2.abort();
|
|
340189
|
+
} else {
|
|
340190
|
+
processSignal.addEventListener("abort", abortKernel, { once: true });
|
|
340191
|
+
detachProcessAbort = () => processSignal.removeEventListener("abort", abortKernel);
|
|
340192
|
+
}
|
|
340193
|
+
}
|
|
340194
|
+
try {
|
|
340195
|
+
throwIfKernelAborted(processSignal);
|
|
340196
|
+
} catch (error) {
|
|
340197
|
+
detachProcessAbort();
|
|
340198
|
+
agent.attachAgentKernelRuntime(null);
|
|
340199
|
+
throw error;
|
|
340200
|
+
}
|
|
339994
340201
|
const tokens = [];
|
|
339995
340202
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
339996
340203
|
let lastAssistant = null;
|
|
@@ -340011,10 +340218,13 @@ async function runAgentKernel(agent) {
|
|
|
340011
340218
|
}
|
|
340012
340219
|
await kernel2.prompt(promptMessages);
|
|
340013
340220
|
const assistant = lastAssistant;
|
|
340221
|
+
const text = assistant ? KernelMessageText(assistant) : "";
|
|
340222
|
+
const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
|
|
340223
|
+
const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
|
|
340014
340224
|
return {
|
|
340015
|
-
text:
|
|
340225
|
+
text: emptyResponse ? "[Error] Provider returned an empty response." : text,
|
|
340016
340226
|
stopReason: String(assistant?.stopReason || ""),
|
|
340017
|
-
errorMessage: String(assistant?.errorMessage || "")
|
|
340227
|
+
errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
|
|
340018
340228
|
};
|
|
340019
340229
|
} finally {
|
|
340020
340230
|
unsubscribe();
|
|
@@ -340049,6 +340259,16 @@ async function runAgentKernel(agent) {
|
|
|
340049
340259
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340050
340260
|
tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
340051
340261
|
}
|
|
340262
|
+
let emptyResponseRetries = 0;
|
|
340263
|
+
while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
|
|
340264
|
+
removeTrailingFailedAssistant(agent, kernel2.state.messages);
|
|
340265
|
+
emptyResponseRetries += 1;
|
|
340266
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
|
|
340267
|
+
tokens.push({ type: "text", text: notice });
|
|
340268
|
+
agent.recordWorkStatus(notice);
|
|
340269
|
+
await agent.waitForPlannedRouteRetry();
|
|
340270
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
340271
|
+
}
|
|
340052
340272
|
let routeRetries = 0;
|
|
340053
340273
|
while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
|
|
340054
340274
|
const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
|
|
@@ -340081,6 +340301,7 @@ async function runAgentKernel(agent) {
|
|
|
340081
340301
|
else agent.pendingOptions = agent.pendingOptions.filter((question) => !isPlanExecutionQuestion(question));
|
|
340082
340302
|
}
|
|
340083
340303
|
} finally {
|
|
340304
|
+
detachProcessAbort();
|
|
340084
340305
|
agent.attachAgentKernelRuntime(null);
|
|
340085
340306
|
}
|
|
340086
340307
|
agent.status = "idle";
|
|
@@ -340247,20 +340468,18 @@ async function transformContext(agent, messages, signal) {
|
|
|
340247
340468
|
const provider = agent.engineModel();
|
|
340248
340469
|
if (!provider || !compressionModel) return messages;
|
|
340249
340470
|
const newmarkMessages = publicHistoryFromKernelMessages(messages);
|
|
340250
|
-
const beforeCompression = JSON.stringify(newmarkMessages);
|
|
340251
340471
|
const compressionAt = agent.lastCompression?.at || "";
|
|
340252
|
-
await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
340472
|
+
let compressed = await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
340253
340473
|
if (processSignal?.aborted) return messages;
|
|
340254
|
-
|
|
340255
|
-
|
|
340256
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
340474
|
+
if (compressed && agent.estimateContextTokens(newmarkMessages) >= Math.floor(agent.contextWindow(compressionModel).maxTokens * 0.82)) {
|
|
340475
|
+
compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
|
|
340257
340476
|
}
|
|
340258
340477
|
const windowMax = agent.contextWindow(compressionModel).maxTokens;
|
|
340259
340478
|
const conservativeTokens = agent.estimateContextTokens(newmarkMessages);
|
|
340260
340479
|
if (conservativeTokens >= Math.floor(windowMax * 0.9) && !processSignal?.aborted) {
|
|
340261
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
340480
|
+
compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
|
|
340262
340481
|
}
|
|
340263
|
-
if (
|
|
340482
|
+
if (!compressed) return messages;
|
|
340264
340483
|
const durableMessages = toKernelMessagesFromHistory(newmarkMessages, agent);
|
|
340265
340484
|
if (agent.lastCompression?.at && agent.lastCompression.at !== compressionAt) {
|
|
340266
340485
|
agent.recordContextCompressionStep();
|
|
@@ -342151,9 +342370,11 @@ function key2(model) {
|
|
|
342151
342370
|
}
|
|
342152
342371
|
var FileModelValidationCache = class {
|
|
342153
342372
|
filePath;
|
|
342373
|
+
readOnly;
|
|
342154
342374
|
records = /* @__PURE__ */ new Map();
|
|
342155
|
-
constructor(rootPath) {
|
|
342375
|
+
constructor(rootPath, options = {}) {
|
|
342156
342376
|
this.filePath = path20.join(rootPath, "model-validation", "records.json");
|
|
342377
|
+
this.readOnly = options.readOnly === true;
|
|
342157
342378
|
this.load();
|
|
342158
342379
|
}
|
|
342159
342380
|
get(modelKey2) {
|
|
@@ -342162,10 +342383,12 @@ var FileModelValidationCache = class {
|
|
|
342162
342383
|
}
|
|
342163
342384
|
set(record) {
|
|
342164
342385
|
this.records.set(record.modelKey || key2(record.model), JSON.parse(JSON.stringify(record)));
|
|
342386
|
+
if (this.readOnly) return;
|
|
342165
342387
|
this.save();
|
|
342166
342388
|
}
|
|
342167
342389
|
delete(modelKey2) {
|
|
342168
342390
|
if (!this.records.delete(modelKey2)) return;
|
|
342391
|
+
if (this.readOnly) return;
|
|
342169
342392
|
this.save();
|
|
342170
342393
|
}
|
|
342171
342394
|
load() {
|
|
@@ -344041,7 +344264,7 @@ var Agent4 = class _Agent {
|
|
|
344041
344264
|
this.subagentName = options.subagentName || "";
|
|
344042
344265
|
this.subagentPrompt = options.subagentPrompt || "";
|
|
344043
344266
|
this.linkedPlanAccess = options.linkedPlanAccess;
|
|
344044
|
-
this.config = new ConfigManager(rootPath);
|
|
344267
|
+
this.config = new ConfigManager(rootPath, { readOnly: options.readOnlyConfig === true });
|
|
344045
344268
|
this.compressionHistoryArchive = new CompressionHistoryArchive(rootPath);
|
|
344046
344269
|
this.contextV2 = new AgentContextManager(rootPath, this.config);
|
|
344047
344270
|
this.agentRunService = this.config.contextFlag("agent_runtime_v2") ? new AgentRunService(path28.join(rootPath, ".newmark-context-v2")) : null;
|
|
@@ -344572,7 +344795,10 @@ var Agent4 = class _Agent {
|
|
|
344572
344795
|
}
|
|
344573
344796
|
const previousAuto = this.model === "auto" ? this.resolvedDeployment : null;
|
|
344574
344797
|
const qualified = parseDeploymentSelectionValue2(requested);
|
|
344575
|
-
const
|
|
344798
|
+
const legacyQualified = requested.includes("/") ? this.config.allModels().filter(
|
|
344799
|
+
(model2) => `${model2.provider_id}/${model2.name}` === requested || `${model2.provider}/${model2.name}` === requested
|
|
344800
|
+
) : [];
|
|
344801
|
+
const current = qualified ? this.config.findDeployment(qualified) : legacyQualified.length === 1 ? legacyQualified[0] : requested ? this.config.findModel(requested) : void 0;
|
|
344576
344802
|
this.model = current?.name || requested;
|
|
344577
344803
|
this.fixedDeployment = current ? this.deploymentRef(current) : qualified;
|
|
344578
344804
|
this.resolvedDeployment = null;
|
|
@@ -345689,7 +345915,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345689
345915
|
this.saveWorkspaceConversationState(true);
|
|
345690
345916
|
return true;
|
|
345691
345917
|
}
|
|
345692
|
-
finishConversationWorkRun(runId, status, endedAt = this.nowIso()) {
|
|
345918
|
+
finishConversationWorkRun(runId, status, endedAt = this.nowIso(), errorMessage = "") {
|
|
345693
345919
|
const run = this.workRuns.find((item) => item.runId === String(runId || ""));
|
|
345694
345920
|
if (!run) return false;
|
|
345695
345921
|
this.syncAgentRunTerminal(run.runId, status, endedAt);
|
|
@@ -345733,7 +345959,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345733
345959
|
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
345734
345960
|
this.emitWorkEvent({
|
|
345735
345961
|
type: status === "completed" ? "done" : status === "error" ? "error" : "status",
|
|
345736
|
-
content: status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
|
|
345962
|
+
content: status === "error" ? String(errorMessage || "").trim() || "Agent run failed." : status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
|
|
345737
345963
|
status,
|
|
345738
345964
|
runId: run.runId,
|
|
345739
345965
|
conversationId: run.target.conversationId,
|
|
@@ -345878,6 +346104,7 @@ ${String(event.toolArgs || "")}`;
|
|
|
345878
346104
|
this.activeAgentKernelRuntime = runtime;
|
|
345879
346105
|
this.awaitingAgentKernelRuntime = false;
|
|
345880
346106
|
if (!runtime) return;
|
|
346107
|
+
if (this.activeProcessAbortController?.signal.aborted) runtime.abort?.();
|
|
345881
346108
|
const queued = this.pendingAgentKernelQueue.splice(0);
|
|
345882
346109
|
for (const item of queued) {
|
|
345883
346110
|
const accepted = this.forwardAgentKernelQueueMessage(item.content, item.queueMode, item.clientMessageId, item.runId, item.images, item.hiddenUserInput);
|
|
@@ -347039,6 +347266,25 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
347039
347266
|
this.loadWorkspaceConversationState();
|
|
347040
347267
|
return selected;
|
|
347041
347268
|
}
|
|
347269
|
+
refreshWorkspaceRegistryFromStorage() {
|
|
347270
|
+
const before = JSON.stringify({
|
|
347271
|
+
internal: this.workspace.internal,
|
|
347272
|
+
external: this.workspace.external,
|
|
347273
|
+
current: this.workspace.current
|
|
347274
|
+
});
|
|
347275
|
+
const selected = this.workspace.reloadFromStorage();
|
|
347276
|
+
const after = JSON.stringify({
|
|
347277
|
+
internal: this.workspace.internal,
|
|
347278
|
+
external: this.workspace.external,
|
|
347279
|
+
current: this.workspace.current
|
|
347280
|
+
});
|
|
347281
|
+
if (before === after) return selected;
|
|
347282
|
+
if (selected) this.config.loadWorkspaceConfig(selected.path);
|
|
347283
|
+
else this.config.clearWorkspaceOverrides();
|
|
347284
|
+
this.workspaceConversations.clear();
|
|
347285
|
+
this.loadWorkspaceConversationState();
|
|
347286
|
+
return selected;
|
|
347287
|
+
}
|
|
347042
347288
|
setConversation(id) {
|
|
347043
347289
|
const clean = this.safeConversationId(id || "default");
|
|
347044
347290
|
if (this.workspaceConversationKey() === this.loadedWorkspaceConversationKey) {
|
|
@@ -348032,28 +348278,25 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
348032
348278
|
this.saveWorkspaceConversationState(true);
|
|
348033
348279
|
return { text, hiddenUserInput: true, goalContinuation: true };
|
|
348034
348280
|
}
|
|
348035
|
-
|
|
348281
|
+
buildSessionArchive(messages, mode, model, archiveDir) {
|
|
348036
348282
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
|
|
348037
|
-
const
|
|
348038
|
-
|
|
348039
|
-
const filename = `session_${stamp}.md`;
|
|
348040
|
-
const outPath = path28.join(archiveDir, filename);
|
|
348041
|
-
let md = `# Newmark Session \u2014 ${stamp}
|
|
348283
|
+
const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
|
|
348284
|
+
let markdown = `# Newmark Session \u2014 ${stamp}
|
|
348042
348285
|
|
|
348043
348286
|
`;
|
|
348044
|
-
|
|
348287
|
+
markdown += `**Mode**: ${mode}
|
|
348045
348288
|
**Model**: ${model}
|
|
348046
348289
|
`;
|
|
348047
|
-
|
|
348290
|
+
markdown += `**Messages**: ${messages.length}
|
|
348048
348291
|
|
|
348049
348292
|
---
|
|
348050
348293
|
|
|
348051
348294
|
`;
|
|
348052
|
-
if (this.goal)
|
|
348295
|
+
if (this.goal) markdown += `**Goal**: ${this.goal.objective}
|
|
348053
348296
|
|
|
348054
348297
|
`;
|
|
348055
348298
|
for (const msg of messages) {
|
|
348056
|
-
|
|
348299
|
+
markdown += `**[${msg.role}] ${msg.timestamp}**
|
|
348057
348300
|
|
|
348058
348301
|
${msg.content}
|
|
348059
348302
|
|
|
@@ -348062,13 +348305,35 @@ ${msg.content}
|
|
|
348062
348305
|
const archived = archiveConversationImageAttachment(this.rootPath, archiveDir, attachment);
|
|
348063
348306
|
if (!archived) continue;
|
|
348064
348307
|
const alt = archived.name.replace(/[\]\r\n]/g, " ").trim() || "Submitted image";
|
|
348065
|
-
|
|
348308
|
+
markdown += `
|
|
348066
348309
|
|
|
348067
348310
|
`;
|
|
348068
348311
|
}
|
|
348069
348312
|
}
|
|
348070
|
-
|
|
348071
|
-
|
|
348313
|
+
return { filename, markdown };
|
|
348314
|
+
}
|
|
348315
|
+
writeSessionArchive(messages, mode, model) {
|
|
348316
|
+
const archiveDir = this.archiveDir();
|
|
348317
|
+
fs25.mkdirSync(archiveDir, { recursive: true });
|
|
348318
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
348319
|
+
fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
|
|
348320
|
+
return archive.filename;
|
|
348321
|
+
}
|
|
348322
|
+
async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
|
|
348323
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
348324
|
+
await fs25.promises.mkdir(archiveDir, { recursive: true });
|
|
348325
|
+
const outPath = path28.join(archiveDir, archive.filename);
|
|
348326
|
+
const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
|
|
348327
|
+
try {
|
|
348328
|
+
await fs25.promises.writeFile(tempPath, archive.markdown, "utf-8");
|
|
348329
|
+
await fs25.promises.rename(tempPath, outPath);
|
|
348330
|
+
} finally {
|
|
348331
|
+
try {
|
|
348332
|
+
await fs25.promises.unlink(tempPath);
|
|
348333
|
+
} catch {
|
|
348334
|
+
}
|
|
348335
|
+
}
|
|
348336
|
+
return archive.filename;
|
|
348072
348337
|
}
|
|
348073
348338
|
archiveSession() {
|
|
348074
348339
|
return this.writeSessionArchive(this.chatMessages, this.modeName(), this.model);
|
|
@@ -348136,6 +348401,93 @@ ${msg.content}
|
|
|
348136
348401
|
}
|
|
348137
348402
|
return filename;
|
|
348138
348403
|
}
|
|
348404
|
+
/**
|
|
348405
|
+
* Non-blocking archive writer used by the desktop IPC path. The conversation
|
|
348406
|
+
* state merge remains synchronous and lock-protected, but the potentially
|
|
348407
|
+
* large markdown payload and manifest use promise-based filesystem I/O so
|
|
348408
|
+
* independent workspaces can archive in parallel without freezing Electron.
|
|
348409
|
+
*/
|
|
348410
|
+
async archiveConversationAsync(conversationId) {
|
|
348411
|
+
return await this.archiveConversationAsyncUnlocked(conversationId);
|
|
348412
|
+
}
|
|
348413
|
+
async archiveConversationAsyncUnlocked(conversationId) {
|
|
348414
|
+
const ws = this.workspace.current;
|
|
348415
|
+
if (!ws) return null;
|
|
348416
|
+
const clean = this.safeConversationId(conversationId || "default");
|
|
348417
|
+
const stateKey2 = this.workspaceConversationStateKey(clean);
|
|
348418
|
+
if (!stateKey2) return null;
|
|
348419
|
+
const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
|
|
348420
|
+
const archiveDir = path28.join(ws.path, "archive");
|
|
348421
|
+
const workspacePrefix = this.workspaceConversationPrefix() || "";
|
|
348422
|
+
const archiveMode = this.modeName();
|
|
348423
|
+
const archiveModel = this.model;
|
|
348424
|
+
const cachedStored = this.readStoredConversationState(ws);
|
|
348425
|
+
const stored = JSON.parse(JSON.stringify(cachedStored || {}));
|
|
348426
|
+
const persisted = stored.conversations?.[stateKey2];
|
|
348427
|
+
if (persisted) this.normalizeConversationTree(persisted);
|
|
348428
|
+
const memory = this.workspaceConversations.get(memoryKey);
|
|
348429
|
+
const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
|
|
348430
|
+
const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
|
|
348431
|
+
const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
|
|
348432
|
+
const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
|
|
348433
|
+
const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
|
|
348434
|
+
const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
|
|
348435
|
+
title: this.titleFromMessages(messages, clean),
|
|
348436
|
+
chatMessages: messages,
|
|
348437
|
+
history: sourceHistory,
|
|
348438
|
+
plan: memory?.plan,
|
|
348439
|
+
linkedPlan: memory?.linkedPlan,
|
|
348440
|
+
subagentState: memory?.subagentState,
|
|
348441
|
+
workRuns: memory?.workRuns,
|
|
348442
|
+
continuations: memory?.continuations,
|
|
348443
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
348444
|
+
};
|
|
348445
|
+
const manifest = {
|
|
348446
|
+
version: 2,
|
|
348447
|
+
kind: "newmark-conversation-archive",
|
|
348448
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
348449
|
+
conversationId: clean,
|
|
348450
|
+
workspaceId: ws.id,
|
|
348451
|
+
workspaceName: ws.name,
|
|
348452
|
+
workspacePath: ws.path,
|
|
348453
|
+
workspaceInternal: ws.isInternal,
|
|
348454
|
+
statePrefix: workspacePrefix,
|
|
348455
|
+
entry: this.conversationEntryForDisk(archiveEntry)
|
|
348456
|
+
};
|
|
348457
|
+
const manifestPath = this.archiveManifestPath(path28.join(archiveDir, filename));
|
|
348458
|
+
const manifestTempPath = `${manifestPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
|
|
348459
|
+
try {
|
|
348460
|
+
await fs25.promises.writeFile(manifestTempPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
348461
|
+
await fs25.promises.rename(manifestTempPath, manifestPath);
|
|
348462
|
+
} finally {
|
|
348463
|
+
try {
|
|
348464
|
+
await fs25.promises.unlink(manifestTempPath);
|
|
348465
|
+
} catch {
|
|
348466
|
+
}
|
|
348467
|
+
}
|
|
348468
|
+
this.finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws);
|
|
348469
|
+
return filename;
|
|
348470
|
+
}
|
|
348471
|
+
finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws) {
|
|
348472
|
+
let nextActiveId = "";
|
|
348473
|
+
this.mutateStoredConversationState(ws, (latest) => {
|
|
348474
|
+
latest.conversations = latest.conversations || {};
|
|
348475
|
+
delete latest.conversations[stateKey2];
|
|
348476
|
+
const prefix = stateKey2.slice(0, Math.max(0, stateKey2.length - clean.length - 1)) + "-";
|
|
348477
|
+
const remaining = Object.keys(latest.conversations).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
|
|
348478
|
+
const currentActiveId = this.safeConversationId(latest.activeConversationId || this.activeConversationId || "default");
|
|
348479
|
+
if (clean === currentActiveId) latest.activeConversationId = remaining[0] || "default";
|
|
348480
|
+
nextActiveId = latest.activeConversationId || remaining[0] || "default";
|
|
348481
|
+
return latest;
|
|
348482
|
+
});
|
|
348483
|
+
this.workspaceConversations.delete(memoryKey);
|
|
348484
|
+
const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
|
|
348485
|
+
this.workspaceConversations.delete(duplicateMemoryKey);
|
|
348486
|
+
if (clean === this.safeConversationId(this.activeConversationId || "default")) {
|
|
348487
|
+
this.activeConversationId = nextActiveId || "default";
|
|
348488
|
+
this.loadWorkspaceConversationState();
|
|
348489
|
+
}
|
|
348490
|
+
}
|
|
348139
348491
|
listStoredConversationIds(stored) {
|
|
348140
348492
|
const prefix = `${this.workspaceConversationPrefix() || ""}-`;
|
|
348141
348493
|
return Object.keys(stored.conversations || {}).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
|
|
@@ -348661,9 +349013,9 @@ ${msg.content}
|
|
|
348661
349013
|
const provider = this.config.findProvider(providerId);
|
|
348662
349014
|
return all.filter((m2) => m2.provider_id === (provider?.id || providerId));
|
|
348663
349015
|
}
|
|
348664
|
-
async validateModels(selectedNames) {
|
|
349016
|
+
async validateModels(selectedNames, options = {}) {
|
|
348665
349017
|
if (this.modelValidationPromise) return this.modelValidationPromise;
|
|
348666
|
-
const validation = this.runModelValidation(selectedNames);
|
|
349018
|
+
const validation = this.runModelValidation(selectedNames, options.persist !== false);
|
|
348667
349019
|
this.modelValidationPromise = validation;
|
|
348668
349020
|
try {
|
|
348669
349021
|
return await validation;
|
|
@@ -348681,7 +349033,7 @@ ${msg.content}
|
|
|
348681
349033
|
recentChecks: this.modelValidationProgress.recentChecks.map((item) => ({ ...item }))
|
|
348682
349034
|
};
|
|
348683
349035
|
}
|
|
348684
|
-
async runModelValidation(selectedNames) {
|
|
349036
|
+
async runModelValidation(selectedNames, persist = true) {
|
|
348685
349037
|
const selectedModels = this.config.modelsForSelections(selectedNames);
|
|
348686
349038
|
if (!selectedModels.length) {
|
|
348687
349039
|
this.modelValidationProgress = {
|
|
@@ -348699,7 +349051,7 @@ ${msg.content}
|
|
|
348699
349051
|
}
|
|
348700
349052
|
const results = [];
|
|
348701
349053
|
const catalogByProvider = /* @__PURE__ */ new Map();
|
|
348702
|
-
const cache = new FileModelValidationCache(this.rootPath);
|
|
349054
|
+
const cache = new FileModelValidationCache(this.rootPath, { readOnly: !persist });
|
|
348703
349055
|
const checksPerModel = 11;
|
|
348704
349056
|
let currentModel = "";
|
|
348705
349057
|
let currentModelChecks = 0;
|
|
@@ -348831,7 +349183,7 @@ ${msg.content}
|
|
|
348831
349183
|
completedModels: this.modelValidationProgress.completedModels + 1
|
|
348832
349184
|
};
|
|
348833
349185
|
}
|
|
348834
|
-
this.config.save();
|
|
349186
|
+
if (persist) this.config.save();
|
|
348835
349187
|
this.modelValidationProgress = {
|
|
348836
349188
|
...this.modelValidationProgress,
|
|
348837
349189
|
running: false,
|
|
@@ -349083,7 +349435,8 @@ ${String(input2.content || "").slice(0, 18e3)}`;
|
|
|
349083
349435
|
const text = typeof input2 === "string" ? input2 : String(input2.text || "");
|
|
349084
349436
|
const inputEnvelope = typeof input2 === "string" ? null : input2;
|
|
349085
349437
|
const hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
|
|
349086
|
-
this.
|
|
349438
|
+
const explicitFixedModel = this.model !== "" && this.model !== "auto";
|
|
349439
|
+
if (!explicitFixedModel) this.ensureUsableModelSelection();
|
|
349087
349440
|
const clientMessageId = String(inputEnvelope?.clientMessageId || "").trim();
|
|
349088
349441
|
const inputRunId = String(inputEnvelope?.runId || this.activeWorkRunId || "").trim();
|
|
349089
349442
|
const rawImages = typeof input2 === "string" ? [] : Array.isArray(input2.images) ? input2.images : [];
|
|
@@ -349171,7 +349524,13 @@ ${String(input2.content || "").slice(0, 18e3)}`;
|
|
|
349171
349524
|
await this.evaluateAndSwitch(displayText, inputEnvelope?.routePolicy);
|
|
349172
349525
|
}
|
|
349173
349526
|
if (this.model && this.modelIsUnavailable(this.model)) {
|
|
349527
|
+
const requestedModel = this.model;
|
|
349174
349528
|
this.switchToFallbackModel();
|
|
349529
|
+
if (this.modelIsUnavailable(this.model)) {
|
|
349530
|
+
const message = `[Error] Model '${requestedModel || "unknown"}' is unavailable or not configured. Select a configured model or enable a valid provider before sending.`;
|
|
349531
|
+
this.status = "error";
|
|
349532
|
+
throw new Error(message);
|
|
349533
|
+
}
|
|
349175
349534
|
}
|
|
349176
349535
|
if (this.engine === "opencode") {
|
|
349177
349536
|
if (images.length) return [{ type: "text", text: "[Vision unavailable] The OpenCode engine does not accept Newmark image attachments." }];
|
|
@@ -350164,11 +350523,11 @@ Falling back to built-in engine.` }];
|
|
|
350164
350523
|
}
|
|
350165
350524
|
}
|
|
350166
350525
|
async maybeCompress(msgs, provider, signal, compressionModel, force = false) {
|
|
350167
|
-
if (signal?.aborted) return;
|
|
350168
|
-
if (!this.config.getBool("context", "auto_compress")) return;
|
|
350526
|
+
if (signal?.aborted) return false;
|
|
350527
|
+
if (!this.config.getBool("context", "auto_compress")) return false;
|
|
350169
350528
|
const total = msgs.reduce((sum, m2) => sum + (typeof m2.content === "string" ? m2.content.length : JSON.stringify(m2.content || "").length), 0);
|
|
350170
350529
|
const budget = this.compressionBudget(msgs);
|
|
350171
|
-
if (budget.estimatedTokens < budget.triggerTokens && !force) return;
|
|
350530
|
+
if (budget.estimatedTokens < budget.triggerTokens && !force) return false;
|
|
350172
350531
|
if (!force && this.lastCompression && String(msgs[0]?.content || "").includes(this.lastCompression.summary)) {
|
|
350173
350532
|
const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
|
|
350174
350533
|
const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
|
|
@@ -350176,16 +350535,16 @@ Falling back to built-in engine.` }];
|
|
|
350176
350535
|
const tokenGrowth = baselineTokens ? Math.max(0, budget.estimatedTokens - baselineTokens) : Number.POSITIVE_INFINITY;
|
|
350177
350536
|
const minCharGrowth = Math.max(12e3, Math.floor(baselineChars * 0.25));
|
|
350178
350537
|
const minTokenGrowth = Math.max(1024, Math.floor(budget.triggerTokens * 0.2));
|
|
350179
|
-
if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return;
|
|
350538
|
+
if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return false;
|
|
350180
350539
|
}
|
|
350181
350540
|
const originalMessageCount = msgs.length;
|
|
350182
350541
|
const configuredKeepLast = this.config.getNum("context", "keep_recent_messages") || 10;
|
|
350183
|
-
if (msgs.length <= 1) return;
|
|
350542
|
+
if (msgs.length <= 1) return false;
|
|
350184
350543
|
const continuationAnchorTokens = this.estimateContextTokens([this.postCompressionContinuationMessage()]);
|
|
350185
350544
|
const recentBudget = Math.max(64, budget.targetTokens - budget.summaryTokens - continuationAnchorTokens);
|
|
350186
350545
|
const recent = this.recentContextSuffix(msgs, configuredKeepLast, recentBudget);
|
|
350187
350546
|
const recentStart = Math.max(0, msgs.length - recent.length);
|
|
350188
|
-
if (recentStart <= 0) return;
|
|
350547
|
+
if (recentStart <= 0) return false;
|
|
350189
350548
|
const middle = msgs.slice(0, recentStart);
|
|
350190
350549
|
const currentInstruction = this.latestUserHistoryText(recent);
|
|
350191
350550
|
const compression = await this.buildCompressionSummary(
|
|
@@ -350197,7 +350556,7 @@ Falling back to built-in engine.` }];
|
|
|
350197
350556
|
compressionModel || this.activeModelName(),
|
|
350198
350557
|
currentInstruction
|
|
350199
350558
|
);
|
|
350200
|
-
if (signal?.aborted) return;
|
|
350559
|
+
if (signal?.aborted) return false;
|
|
350201
350560
|
const compressed = [{
|
|
350202
350561
|
role: "system",
|
|
350203
350562
|
content: compression.summary
|
|
@@ -350222,6 +350581,7 @@ Falling back to built-in engine.` }];
|
|
|
350222
350581
|
};
|
|
350223
350582
|
this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
|
|
350224
350583
|
this.persistCompressedHistory(compression.summary, recent.length, msgs);
|
|
350584
|
+
return true;
|
|
350225
350585
|
}
|
|
350226
350586
|
async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = "") {
|
|
350227
350587
|
const workspacePath = this.workspace.current?.path || this.rootPath;
|
|
@@ -351079,8 +351439,26 @@ var GoalStateImpl = class {
|
|
|
351079
351439
|
return s3;
|
|
351080
351440
|
}
|
|
351081
351441
|
checkComplete(response) {
|
|
351082
|
-
const
|
|
351083
|
-
|
|
351442
|
+
const lines = String(response || "").replace(/\r\n?/g, "\n").split("\n");
|
|
351443
|
+
const completionMarkers = "(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)";
|
|
351444
|
+
const explicitLinePatterns = [
|
|
351445
|
+
new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, "i"),
|
|
351446
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
|
|
351447
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i
|
|
351448
|
+
];
|
|
351449
|
+
const completionContext = new RegExp(completionMarkers, "i");
|
|
351450
|
+
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");
|
|
351451
|
+
const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, "i");
|
|
351452
|
+
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");
|
|
351453
|
+
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");
|
|
351454
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
351455
|
+
const line = lines[index];
|
|
351456
|
+
if (!explicitLinePatterns.some((pattern) => pattern.test(line))) continue;
|
|
351457
|
+
const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(" ");
|
|
351458
|
+
if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context))) continue;
|
|
351459
|
+
return true;
|
|
351460
|
+
}
|
|
351461
|
+
return false;
|
|
351084
351462
|
}
|
|
351085
351463
|
};
|
|
351086
351464
|
|
|
@@ -351277,6 +351655,7 @@ var ConversationKernel = class {
|
|
|
351277
351655
|
runtime.runner.recordGuideReceipt(deferred2);
|
|
351278
351656
|
this.emitQueueUpdate(runtime);
|
|
351279
351657
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351658
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351280
351659
|
return deferred2;
|
|
351281
351660
|
}
|
|
351282
351661
|
if (runtime.guideAcceptanceClosedRunId === runtime.runId) {
|
|
@@ -351308,6 +351687,7 @@ var ConversationKernel = class {
|
|
|
351308
351687
|
createdAt: deferred2.createdAt
|
|
351309
351688
|
}]);
|
|
351310
351689
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351690
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351311
351691
|
return deferred2;
|
|
351312
351692
|
}
|
|
351313
351693
|
const queued = runtime.runner.queueActiveKernelMessage(safeEnvelope.text, "steer", clientMessageId, runtime.runId, safeEnvelope.images);
|
|
@@ -351338,6 +351718,7 @@ var ConversationKernel = class {
|
|
|
351338
351718
|
createdAt: deferred.createdAt
|
|
351339
351719
|
}]);
|
|
351340
351720
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
351721
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
351341
351722
|
return deferred;
|
|
351342
351723
|
}
|
|
351343
351724
|
checkpoint(target) {
|
|
@@ -351536,7 +351917,12 @@ var ConversationKernel = class {
|
|
|
351536
351917
|
if (runtime.runId === runId && runtime.stopRequestedRunId === runId) {
|
|
351537
351918
|
stopped = true;
|
|
351538
351919
|
} else {
|
|
351539
|
-
runtime.runner.finishConversationWorkRun(
|
|
351920
|
+
runtime.runner.finishConversationWorkRun(
|
|
351921
|
+
runId,
|
|
351922
|
+
"error",
|
|
351923
|
+
void 0,
|
|
351924
|
+
error instanceof Error ? error.message : String(error)
|
|
351925
|
+
);
|
|
351540
351926
|
throw error;
|
|
351541
351927
|
}
|
|
351542
351928
|
} finally {
|
|
@@ -351545,6 +351931,8 @@ var ConversationKernel = class {
|
|
|
351545
351931
|
if (runtime.stopRequestedRunId === runId) {
|
|
351546
351932
|
stopped = true;
|
|
351547
351933
|
this.settleCooperativeStop(runtime, runId);
|
|
351934
|
+
} else if (runtime.pendingNextTurn.length > 0) {
|
|
351935
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
351548
351936
|
}
|
|
351549
351937
|
}
|
|
351550
351938
|
}
|
|
@@ -351679,7 +352067,8 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
351679
352067
|
guideAcceptanceClosedRunId: "",
|
|
351680
352068
|
guideReceipts: /* @__PURE__ */ new Map(),
|
|
351681
352069
|
guideEnvelopes: /* @__PURE__ */ new Map(),
|
|
351682
|
-
goalContinuationTimer: void 0
|
|
352070
|
+
goalContinuationTimer: void 0,
|
|
352071
|
+
pendingContinuationRunId: void 0
|
|
351683
352072
|
};
|
|
351684
352073
|
runner.setGoalContinuationGate(() => {
|
|
351685
352074
|
this.queueState(runtime);
|
|
@@ -351751,6 +352140,28 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
351751
352140
|
});
|
|
351752
352141
|
}, 250);
|
|
351753
352142
|
}
|
|
352143
|
+
schedulePendingRuntimeContinuation(runtime, runId) {
|
|
352144
|
+
if (runtime.pendingContinuationRunId === runId) return;
|
|
352145
|
+
runtime.pendingContinuationRunId = runId;
|
|
352146
|
+
const active = runtime.activePromise;
|
|
352147
|
+
if (active) {
|
|
352148
|
+
const continueAfterSettlement = () => {
|
|
352149
|
+
runtime.pendingContinuationRunId = void 0;
|
|
352150
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
352151
|
+
};
|
|
352152
|
+
void active.then(continueAfterSettlement, continueAfterSettlement);
|
|
352153
|
+
return;
|
|
352154
|
+
}
|
|
352155
|
+
setImmediate(() => {
|
|
352156
|
+
runtime.pendingContinuationRunId = void 0;
|
|
352157
|
+
if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
|
|
352158
|
+
const next = runtime.pendingNextTurn.shift();
|
|
352159
|
+
if (!next) return;
|
|
352160
|
+
const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
|
|
352161
|
+
void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
|
|
352162
|
+
});
|
|
352163
|
+
});
|
|
352164
|
+
}
|
|
351754
352165
|
startGoalDrivenBuild(runtime) {
|
|
351755
352166
|
if (runtime.goalContinuationTimer) {
|
|
351756
352167
|
clearTimeout(runtime.goalContinuationTimer);
|