newmark-agent 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +45 -10
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.js +16 -4
- package/dist/core/agentKernelRunner.js +1 -1
- package/dist/core/conversationKernel.d.ts +8 -0
- package/dist/core/conversationKernel.js +44 -5
- package/dist/core/electronUtilityAgentClient.d.ts +1 -0
- package/dist/core/electronUtilityAgentClient.js +4 -0
- package/dist/core/electronUtilityRuntimePool.d.ts +2 -0
- package/dist/core/electronUtilityRuntimePool.js +11 -0
- package/dist/core/utilityAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentClient.d.ts +1 -0
- package/dist/core/wslAgentClient.js +4 -0
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +2 -0
- package/dist/core/wslAgentRuntimePool.js +12 -0
- package/dist/main.js +36 -7
- package/dist/tools/index.js +1 -1
- package/dist/tui/src/app.js +51 -6
- package/dist/tui/src/data.js +28 -0
- package/dist/tui/src/render.js +67 -30
- package/dist/ui/index.html +11 -0
- package/dist/wsl-agent-host.bundle.cjs +45 -10
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +3 -3
|
@@ -336405,7 +336405,7 @@ var ToolExecutor = class {
|
|
|
336405
336405
|
t3("subagent_result", "Return the persisted transcript, mailbox summary, status, and latest result for a peer agent. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
|
|
336406
336406
|
t3("subagent_close", "Close a same-conversation peer. Root can close any peer; a peer can close only itself. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
|
|
336407
336407
|
t3("linked_plan", "Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.", { action: { type: "string", enum: ["get", "update"] }, markdown: { type: "string" }, expected_revision: { type: "number" } }, ["action"]),
|
|
336408
|
-
t3("build_history_query", "Read the concrete public work details of one historical Build Block.
|
|
336408
|
+
t3("build_history_query", "Read the concrete public work details (tool calls, results, file changes, guides) of one historical Build Block. Call it proactively when the current task continues, fixes, verifies, or depends on earlier work: reuse the returned activity instead of re-investigating from scratch. Do not call it merely to answer completion status already exposed by the prompt. Select by newest-to-oldest history_index, or by run_id returned from an earlier query. Every activity/guide content is bounded to max_chars (default 2000) to keep the read lean and cache-friendly.", { history_index: { type: "number", minimum: 1, description: "1-based historical Build Block index from the request ledger; 1 is the newest previous task." }, run_id: { type: "string", description: "Exact run id returned by an earlier build_history_query result." }, max_events: { type: "number", minimum: 1, maximum: 200, description: "Maximum trailing public work events; defaults to 80." }, max_chars: { type: "number", minimum: 100, maximum: 4e3, description: "Per-event/per-guide content character bound; defaults to 2000." } }, []),
|
|
336409
336409
|
t3("context_compress", "Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.", { keep_recent: { type: "number", minimum: 2, maximum: 60, description: "Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages." }, force: { type: "boolean", description: "Compress even if the context is not yet over the automatic threshold. Defaults to false." } }, []),
|
|
336410
336410
|
t3("context_history_manage", "Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove declares one long-term entry for unload (see below); summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, the protected recent zone, and pending removals. The recent context tail and last user message are protected from remove/summarize unless dangerous is true. For cache-optimization, remove ONLY targets long-term history (never the protected recent tail or last user message) and does NOT unload immediately: the declared entry stays in context for the rest of the current Build Block so the provider prefix cache stays stable, then is physically removed when the Block ends \u2014 applying to subsequent Blocks only.", {
|
|
336411
336411
|
action: { type: "string", enum: ["list", "remove", "summarize", "restore", "search", "read", "status"], description: "list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage." },
|
|
@@ -340900,7 +340900,7 @@ function buildConversationTaskLedger(agent) {
|
|
|
340900
340900
|
"Unfinished Continuation Queue (newest to oldest; summary fields only; use only when the current user instruction authorizes continuation and the task is relevant):",
|
|
340901
340901
|
...unfinishedLines.length ? unfinishedLines : ["(none)"],
|
|
340902
340902
|
...unfinished.length > unfinishedLines.length ? [`(${unfinished.length - unfinishedLines.length} older unfinished run(s) omitted from the bounded prompt ledger.)`] : [],
|
|
340903
|
-
"When
|
|
340903
|
+
"When the current task continues, fixes, verifies, or depends on earlier work in this list, proactively call build_history_query with its history_index before re-investigating. Reuse the returned tool activity and results instead of re-running commands or re-reading files this conversation already examined. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown here."
|
|
340904
340904
|
].join("\n");
|
|
340905
340905
|
}
|
|
340906
340906
|
async function shouldStopAfterTurn(agent, message) {
|
|
@@ -349168,8 +349168,13 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
349168
349168
|
};
|
|
349169
349169
|
}
|
|
349170
349170
|
resolveWindowModel(modelName) {
|
|
349171
|
-
if (modelName
|
|
349172
|
-
|
|
349171
|
+
if (modelName === "auto" || modelName === this.model || modelName === this.activeModelName()) {
|
|
349172
|
+
const active = this.activeModelConfig();
|
|
349173
|
+
if (active) return active;
|
|
349174
|
+
}
|
|
349175
|
+
const byName = this.config.findModel(modelName);
|
|
349176
|
+
if (byName) return byName;
|
|
349177
|
+
return this.config.findModel(this.config.getStr("models", "default_model"));
|
|
349173
349178
|
}
|
|
349174
349179
|
contextMaxTokens(modelName = this.model) {
|
|
349175
349180
|
const model = this.resolveWindowModel(modelName);
|
|
@@ -352333,7 +352338,7 @@ ${custom}`);
|
|
|
352333
352338
|
"- Memory Lab is governed by an explicit Policy chain: pre-think whether memory is needed; prefer bounded memory_lab_query retrieval; then choose ADD/UPDATE/DELETE only when the user authorizes durable memory mutation.",
|
|
352334
352339
|
"- Before memory_lab_update, inspect the target with memory_lab_query or memory_lab_read. For an existing component pass expectedUpdatedAt so concurrent/stale writes fail closed; preserve established tag parent paths.",
|
|
352335
352340
|
"- Use memory_lab_delete only for an explicit user request to forget/remove memory. Prior revisions are retained under Memory Lab/archive and mutation decisions are appended to policy.jsonl for replay.",
|
|
352336
|
-
"- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status.
|
|
352341
|
+
"- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status. When the current task continues, fixes, verifies, or depends on earlier Build Blocks, proactively call build_history_query to read the concrete tool activity and results of the relevant block, and reuse that information instead of re-investigating (re-running commands or re-reading files) from scratch. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown in the prompt.",
|
|
352337
352342
|
"- Linked plan disclosure: a durable conversation-linked Markdown plan exists and can be inspected or updated with linked_plan when explicitly needed or required by Plan mode. Its full Markdown and revision are not injected into every model request.",
|
|
352338
352343
|
"- A memory_lab_update, memory_lab_delete, or memory_lab_reindex call is unfinished until its awaited tool result contains rebuildReceipt.completed=true. The completion receipt is represented by the tool activity inside the current Build block and should not be repeated as a separate completion message.",
|
|
352339
352344
|
`- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; task creates constrained subagents tracked in agent state.`,
|
|
@@ -353131,6 +353136,18 @@ var ConversationKernel = class {
|
|
|
353131
353136
|
if (runtime) runtime.options.mode = mode;
|
|
353132
353137
|
return runner.mode;
|
|
353133
353138
|
}
|
|
353139
|
+
setModel(target, model) {
|
|
353140
|
+
const normalized = this.normalizeTarget(target);
|
|
353141
|
+
const runtime = this.findRuntime(normalized);
|
|
353142
|
+
const runner = runtime?.runner || this.createRunner(normalized);
|
|
353143
|
+
if (!runtime || !runtime.activePromise) {
|
|
353144
|
+
runner.setModel(model);
|
|
353145
|
+
} else {
|
|
353146
|
+
runtime.options.model = model;
|
|
353147
|
+
}
|
|
353148
|
+
runner.saveWorkspaceConversationState(true);
|
|
353149
|
+
return runner.model;
|
|
353150
|
+
}
|
|
353134
353151
|
async toggleGoalPause(target) {
|
|
353135
353152
|
const normalized = this.normalizeTarget(target);
|
|
353136
353153
|
let runtime = this.findRuntime(normalized);
|
|
@@ -353245,14 +353262,15 @@ var ConversationKernel = class {
|
|
|
353245
353262
|
}
|
|
353246
353263
|
async prompt(message, target, options, queueMode = "followUp") {
|
|
353247
353264
|
const normalized = this.normalizeTarget(target);
|
|
353265
|
+
const active = this.findRuntime(normalized);
|
|
353266
|
+
if (active?.activePromise) {
|
|
353267
|
+
this.enqueueSameSession(active, message, queueMode);
|
|
353268
|
+
this.activateAcceptedGoal(active, typeof message === "string" ? "" : message.goalObjective);
|
|
353269
|
+
return active.activePromise;
|
|
353270
|
+
}
|
|
353248
353271
|
const runtime = this.runtime(normalized, options);
|
|
353249
353272
|
runtime.options = { ...options };
|
|
353250
353273
|
this.applyOptions(runtime.runner, options);
|
|
353251
|
-
if (runtime.activePromise) {
|
|
353252
|
-
this.enqueueSameSession(runtime, message, queueMode);
|
|
353253
|
-
this.activateAcceptedGoal(runtime, typeof message === "string" ? "" : message.goalObjective);
|
|
353254
|
-
return runtime.activePromise;
|
|
353255
|
-
}
|
|
353256
353274
|
runtime.generation = (this.generations.get(runtime.runtimeKey) || runtime.generation || 0) + 1;
|
|
353257
353275
|
this.generations.set(runtime.runtimeKey, runtime.generation);
|
|
353258
353276
|
const requestedRunId = typeof message === "string" ? "" : String(message.runId || "").trim().slice(0, 200);
|
|
@@ -353397,7 +353415,21 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
353397
353415
|
this.mirrorHostIfTargetActive(runtime);
|
|
353398
353416
|
return this.result(runtime, lastTokens);
|
|
353399
353417
|
}
|
|
353418
|
+
/**
|
|
353419
|
+
* Apply a model selection recorded while a Build block was running. The
|
|
353420
|
+
* in-flight block never switches mid-block; the switch takes effect the next
|
|
353421
|
+
* time a queued Guide/Next re-enters the block, and only when the pending
|
|
353422
|
+
* selection actually differs from the runner's current selection.
|
|
353423
|
+
*/
|
|
353424
|
+
syncPendingModel(runtime) {
|
|
353425
|
+
const pending3 = String(runtime.options.model || "").trim();
|
|
353426
|
+
if (!pending3) return;
|
|
353427
|
+
if (pending3 === runtime.runner.model || pending3 === runtime.runner.modelSelectionValue()) return;
|
|
353428
|
+
runtime.runner.setModel(pending3);
|
|
353429
|
+
runtime.options.model = runtime.runner.modelSelectionValue();
|
|
353430
|
+
}
|
|
353400
353431
|
async runSingle(runtime, message, continuationMode) {
|
|
353432
|
+
this.syncPendingModel(runtime);
|
|
353401
353433
|
this.consumeQueuedMessage(runtime, typeof message === "string" ? message : message.text);
|
|
353402
353434
|
const timeoutMs = this.processTimeoutMs(runtime);
|
|
353403
353435
|
if (timeoutMs <= 0) {
|
|
@@ -354038,6 +354070,9 @@ async function handle(request) {
|
|
|
354038
354070
|
if (request.method === "set_mode") {
|
|
354039
354071
|
return kernel.setMode(checkedTarget(request.params.target), request.params.mode);
|
|
354040
354072
|
}
|
|
354073
|
+
if (request.method === "set_model") {
|
|
354074
|
+
return kernel.setModel(checkedTarget(request.params.target), request.params.model);
|
|
354075
|
+
}
|
|
354041
354076
|
if (request.method === "set_input_mode") {
|
|
354042
354077
|
return kernel.setInputMode(checkedTarget(request.params.target), request.params.mode);
|
|
354043
354078
|
}
|
|
@@ -93,6 +93,9 @@ async function handle(request) {
|
|
|
93
93
|
if (request.method === 'set_mode') {
|
|
94
94
|
return kernel.setMode(checkedTarget(request.params.target), request.params.mode);
|
|
95
95
|
}
|
|
96
|
+
if (request.method === 'set_model') {
|
|
97
|
+
return kernel.setModel(checkedTarget(request.params.target), request.params.model);
|
|
98
|
+
}
|
|
96
99
|
if (request.method === 'set_input_mode') {
|
|
97
100
|
return kernel.setInputMode(checkedTarget(request.params.target), request.params.mode);
|
|
98
101
|
}
|
package/dist/core/agent.js
CHANGED
|
@@ -5305,9 +5305,21 @@ class Agent {
|
|
|
5305
5305
|
};
|
|
5306
5306
|
}
|
|
5307
5307
|
resolveWindowModel(modelName) {
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5308
|
+
// The context window (display ring, inspector, and compaction trigger) must
|
|
5309
|
+
// resolve the deployment that is actually running. For the active selection
|
|
5310
|
+
// — auto or a fixed model — resolve through the active deployment so a
|
|
5311
|
+
// qualified selection or two same-named models across providers never fall
|
|
5312
|
+
// through to the 128000 default. Only a caller-supplied foreign name (for
|
|
5313
|
+
// example a validation probe against another model) resolves by bare name.
|
|
5314
|
+
if (modelName === 'auto' || modelName === this.model || modelName === this.activeModelName()) {
|
|
5315
|
+
const active = this.activeModelConfig();
|
|
5316
|
+
if (active)
|
|
5317
|
+
return active;
|
|
5318
|
+
}
|
|
5319
|
+
const byName = this.config.findModel(modelName);
|
|
5320
|
+
if (byName)
|
|
5321
|
+
return byName;
|
|
5322
|
+
return this.config.findModel(this.config.getStr('models', 'default_model'));
|
|
5311
5323
|
}
|
|
5312
5324
|
contextMaxTokens(modelName = this.model) {
|
|
5313
5325
|
const model = this.resolveWindowModel(modelName);
|
|
@@ -8858,7 +8870,7 @@ class Agent {
|
|
|
8858
8870
|
'- Memory Lab is governed by an explicit Policy chain: pre-think whether memory is needed; prefer bounded memory_lab_query retrieval; then choose ADD/UPDATE/DELETE only when the user authorizes durable memory mutation.',
|
|
8859
8871
|
'- Before memory_lab_update, inspect the target with memory_lab_query or memory_lab_read. For an existing component pass expectedUpdatedAt so concurrent/stale writes fail closed; preserve established tag parent paths.',
|
|
8860
8872
|
'- Use memory_lab_delete only for an explicit user request to forget/remove memory. Prior revisions are retained under Memory Lab/archive and mutation decisions are appended to policy.jsonl for replay.',
|
|
8861
|
-
'- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status.
|
|
8873
|
+
'- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status. When the current task continues, fixes, verifies, or depends on earlier Build Blocks, proactively call build_history_query to read the concrete tool activity and results of the relevant block, and reuse that information instead of re-investigating (re-running commands or re-reading files) from scratch. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown in the prompt.',
|
|
8862
8874
|
'- Linked plan disclosure: a durable conversation-linked Markdown plan exists and can be inspected or updated with linked_plan when explicitly needed or required by Plan mode. Its full Markdown and revision are not injected into every model request.',
|
|
8863
8875
|
'- A memory_lab_update, memory_lab_delete, or memory_lab_reindex call is unfinished until its awaited tool result contains rebuildReceipt.completed=true. The completion receipt is represented by the tool activity inside the current Build block and should not be repeated as a separate completion message.',
|
|
8864
8876
|
`- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; task creates constrained subagents tracked in agent state.`,
|
|
@@ -818,7 +818,7 @@ function buildConversationTaskLedger(agent) {
|
|
|
818
818
|
'Unfinished Continuation Queue (newest to oldest; summary fields only; use only when the current user instruction authorizes continuation and the task is relevant):',
|
|
819
819
|
...(unfinishedLines.length ? unfinishedLines : ['(none)']),
|
|
820
820
|
...(unfinished.length > unfinishedLines.length ? [`(${unfinished.length - unfinishedLines.length} older unfinished run(s) omitted from the bounded prompt ledger.)`] : []),
|
|
821
|
-
'When
|
|
821
|
+
'When the current task continues, fixes, verifies, or depends on earlier work in this list, proactively call build_history_query with its history_index before re-investigating. Reuse the returned tool activity and results instead of re-running commands or re-reading files this conversation already examined. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown here.',
|
|
822
822
|
].join('\n');
|
|
823
823
|
}
|
|
824
824
|
async function shouldStopAfterTurn(agent, message) {
|
|
@@ -219,6 +219,7 @@ export declare class ConversationKernel {
|
|
|
219
219
|
setWorkRunExpanded(target: ConversationTargetInput, runId: string, expanded: boolean): boolean;
|
|
220
220
|
setInputMode(target: ConversationTargetInput, mode: string): 'guide' | 'next';
|
|
221
221
|
setMode(target: ConversationTargetInput, mode: AgentMode): AgentMode;
|
|
222
|
+
setModel(target: ConversationTargetInput, model: string): string;
|
|
222
223
|
toggleGoalPause(target: ConversationTargetInput): Promise<boolean>;
|
|
223
224
|
clearGoal(target: ConversationTargetInput): boolean;
|
|
224
225
|
updateSetting(section: string, key: string, value: unknown): void;
|
|
@@ -229,6 +230,13 @@ export declare class ConversationKernel {
|
|
|
229
230
|
prompt(message: string | AgentPromptMessage, target: ConversationTargetInput, options: ConversationKernelRunOptions, queueMode?: ConversationQueueMode): Promise<ConversationKernelRunResult>;
|
|
230
231
|
private settleCooperativeStop;
|
|
231
232
|
private run;
|
|
233
|
+
/**
|
|
234
|
+
* Apply a model selection recorded while a Build block was running. The
|
|
235
|
+
* in-flight block never switches mid-block; the switch takes effect the next
|
|
236
|
+
* time a queued Guide/Next re-enters the block, and only when the pending
|
|
237
|
+
* selection actually differs from the runner's current selection.
|
|
238
|
+
*/
|
|
239
|
+
private syncPendingModel;
|
|
232
240
|
private runSingle;
|
|
233
241
|
private processTimeoutMs;
|
|
234
242
|
private runtime;
|
|
@@ -365,6 +365,24 @@ class ConversationKernel {
|
|
|
365
365
|
runtime.options.mode = mode;
|
|
366
366
|
return runner.mode;
|
|
367
367
|
}
|
|
368
|
+
setModel(target, model) {
|
|
369
|
+
const normalized = this.normalizeTarget(target);
|
|
370
|
+
const runtime = this.findRuntime(normalized);
|
|
371
|
+
const runner = runtime?.runner || this.createRunner(normalized);
|
|
372
|
+
if (!runtime || !runtime.activePromise) {
|
|
373
|
+
// No Build block is running: the selection applies immediately.
|
|
374
|
+
runner.setModel(model);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// A Build block is running. The in-flight block keeps its current model
|
|
378
|
+
// until the next Guide/Next re-enters it; record the newly selected model
|
|
379
|
+
// as the pending choice so the next dequeue switches to it. This is the
|
|
380
|
+
// "model switch does not take effect mid-block" contract.
|
|
381
|
+
runtime.options.model = model;
|
|
382
|
+
}
|
|
383
|
+
runner.saveWorkspaceConversationState(true);
|
|
384
|
+
return runner.model;
|
|
385
|
+
}
|
|
368
386
|
async toggleGoalPause(target) {
|
|
369
387
|
const normalized = this.normalizeTarget(target);
|
|
370
388
|
let runtime = this.findRuntime(normalized);
|
|
@@ -486,14 +504,19 @@ class ConversationKernel {
|
|
|
486
504
|
}
|
|
487
505
|
async prompt(message, target, options, queueMode = 'followUp') {
|
|
488
506
|
const normalized = this.normalizeTarget(target);
|
|
507
|
+
const active = this.findRuntime(normalized);
|
|
508
|
+
if (active?.activePromise) {
|
|
509
|
+
// A Build block is already running: queue this message. Queued messages
|
|
510
|
+
// carry no send-time model/mode; the running block keeps its settings and
|
|
511
|
+
// the next dequeue follows the current conversation selection (which
|
|
512
|
+
// setModel/setMode already recorded on runtime.options).
|
|
513
|
+
this.enqueueSameSession(active, message, queueMode);
|
|
514
|
+
this.activateAcceptedGoal(active, typeof message === 'string' ? '' : message.goalObjective);
|
|
515
|
+
return active.activePromise;
|
|
516
|
+
}
|
|
489
517
|
const runtime = this.runtime(normalized, options);
|
|
490
518
|
runtime.options = { ...options };
|
|
491
519
|
this.applyOptions(runtime.runner, options);
|
|
492
|
-
if (runtime.activePromise) {
|
|
493
|
-
this.enqueueSameSession(runtime, message, queueMode);
|
|
494
|
-
this.activateAcceptedGoal(runtime, typeof message === 'string' ? '' : message.goalObjective);
|
|
495
|
-
return runtime.activePromise;
|
|
496
|
-
}
|
|
497
520
|
runtime.generation = (this.generations.get(runtime.runtimeKey) || runtime.generation || 0) + 1;
|
|
498
521
|
this.generations.set(runtime.runtimeKey, runtime.generation);
|
|
499
522
|
const requestedRunId = typeof message === 'string' ? '' : String(message.runId || '').trim().slice(0, 200);
|
|
@@ -657,7 +680,23 @@ class ConversationKernel {
|
|
|
657
680
|
this.mirrorHostIfTargetActive(runtime);
|
|
658
681
|
return this.result(runtime, lastTokens);
|
|
659
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Apply a model selection recorded while a Build block was running. The
|
|
685
|
+
* in-flight block never switches mid-block; the switch takes effect the next
|
|
686
|
+
* time a queued Guide/Next re-enters the block, and only when the pending
|
|
687
|
+
* selection actually differs from the runner's current selection.
|
|
688
|
+
*/
|
|
689
|
+
syncPendingModel(runtime) {
|
|
690
|
+
const pending = String(runtime.options.model || '').trim();
|
|
691
|
+
if (!pending)
|
|
692
|
+
return;
|
|
693
|
+
if (pending === runtime.runner.model || pending === runtime.runner.modelSelectionValue())
|
|
694
|
+
return;
|
|
695
|
+
runtime.runner.setModel(pending);
|
|
696
|
+
runtime.options.model = runtime.runner.modelSelectionValue();
|
|
697
|
+
}
|
|
660
698
|
async runSingle(runtime, message, continuationMode) {
|
|
699
|
+
this.syncPendingModel(runtime);
|
|
661
700
|
this.consumeQueuedMessage(runtime, typeof message === 'string' ? message : message.text);
|
|
662
701
|
const timeoutMs = this.processTimeoutMs(runtime);
|
|
663
702
|
if (timeoutMs <= 0) {
|
|
@@ -102,6 +102,7 @@ export declare class ElectronUtilityAgentClient {
|
|
|
102
102
|
rateAutoRoute(score: number, routeId?: string): Promise<UtilityAutoRouteRatingResult>;
|
|
103
103
|
setWorkRunExpanded(runId: string, expanded: boolean): Promise<boolean>;
|
|
104
104
|
setMode(mode: AgentMode): Promise<AgentMode>;
|
|
105
|
+
setModel(model: string): Promise<string>;
|
|
105
106
|
setInputMode(mode: string): Promise<'guide' | 'next'>;
|
|
106
107
|
toggleGoalPause(): Promise<boolean>;
|
|
107
108
|
clearGoal(): Promise<boolean>;
|
|
@@ -1047,6 +1047,10 @@ class ElectronUtilityAgentClient {
|
|
|
1047
1047
|
await this.start();
|
|
1048
1048
|
return await this.request('set_mode', { target: this.target, mode }, 5_000);
|
|
1049
1049
|
}
|
|
1050
|
+
async setModel(model) {
|
|
1051
|
+
await this.start();
|
|
1052
|
+
return await this.request('set_model', { target: this.target, model }, 5_000);
|
|
1053
|
+
}
|
|
1050
1054
|
async setInputMode(mode) {
|
|
1051
1055
|
await this.start();
|
|
1052
1056
|
return await this.request('set_input_mode', { target: this.target, mode }, 5_000);
|
|
@@ -18,6 +18,7 @@ export interface ElectronTargetRuntimeClient {
|
|
|
18
18
|
rateAutoRoute?(score: number, routeId?: string): Promise<UtilityAutoRouteRatingResult>;
|
|
19
19
|
setWorkRunExpanded(runId: string, expanded: boolean): Promise<boolean>;
|
|
20
20
|
setMode?(mode: AgentMode): Promise<AgentMode>;
|
|
21
|
+
setModel?(model: string): Promise<string>;
|
|
21
22
|
setInputMode?(mode: string): Promise<'guide' | 'next'>;
|
|
22
23
|
toggleGoalPause?(): Promise<boolean>;
|
|
23
24
|
clearGoal?(): Promise<boolean>;
|
|
@@ -79,6 +80,7 @@ export declare class ElectronUtilityRuntimePool {
|
|
|
79
80
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
80
81
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
|
|
81
82
|
setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode | null>;
|
|
83
|
+
setModel(target: ConversationRuntimeTarget, model: string): Promise<string | null>;
|
|
82
84
|
toggleGoalPause(target: ConversationRuntimeTarget): Promise<boolean | null>;
|
|
83
85
|
clearGoal(target: ConversationRuntimeTarget): Promise<boolean | null>;
|
|
84
86
|
updateSetting(section: string, key: string, value: unknown): Promise<void>;
|
|
@@ -261,6 +261,17 @@ class ElectronUtilityRuntimePool {
|
|
|
261
261
|
this.release(entry, true);
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
+
async setModel(target, model) {
|
|
265
|
+
const entry = await this.acquireExisting((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
266
|
+
if (!entry?.client.setModel)
|
|
267
|
+
return null;
|
|
268
|
+
try {
|
|
269
|
+
return await entry.client.setModel(model);
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
this.release(entry, true);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
264
275
|
async toggleGoalPause(target) {
|
|
265
276
|
const entry = await this.acquire((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
266
277
|
if (!entry?.client.toggleGoalPause)
|
|
@@ -132,6 +132,13 @@ export type UtilityAgentRequest = {
|
|
|
132
132
|
target: ConversationRuntimeTarget;
|
|
133
133
|
mode: AgentMode;
|
|
134
134
|
};
|
|
135
|
+
} | {
|
|
136
|
+
id: string;
|
|
137
|
+
method: 'set_model';
|
|
138
|
+
params: {
|
|
139
|
+
target: ConversationRuntimeTarget;
|
|
140
|
+
model: string;
|
|
141
|
+
};
|
|
135
142
|
} | {
|
|
136
143
|
id: string;
|
|
137
144
|
method: 'set_input_mode';
|
|
@@ -79,6 +79,7 @@ export declare class WslAgentClient {
|
|
|
79
79
|
rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
80
80
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
81
81
|
setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
|
|
82
|
+
setModel(target: ConversationRuntimeTarget, model: string): Promise<string>;
|
|
82
83
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next'>;
|
|
83
84
|
toggleGoalPause(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
84
85
|
clearGoal(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
@@ -337,6 +337,10 @@ class WslAgentClient {
|
|
|
337
337
|
await this.start();
|
|
338
338
|
return await this.request('set_mode', { target: await this.mapTarget(target), mode }, 5_000);
|
|
339
339
|
}
|
|
340
|
+
async setModel(target, model) {
|
|
341
|
+
await this.start();
|
|
342
|
+
return await this.request('set_model', { target: await this.mapTarget(target), model }, 5_000);
|
|
343
|
+
}
|
|
340
344
|
async setInputMode(target, mode) {
|
|
341
345
|
await this.start();
|
|
342
346
|
return await this.request('set_input_mode', { target: await this.mapTarget(target), mode }, 5_000);
|
|
@@ -144,6 +144,13 @@ export type WslAgentRequest = {
|
|
|
144
144
|
target: ConversationRuntimeTarget;
|
|
145
145
|
mode: AgentMode;
|
|
146
146
|
};
|
|
147
|
+
} | {
|
|
148
|
+
id: string;
|
|
149
|
+
method: 'set_model';
|
|
150
|
+
params: {
|
|
151
|
+
target: ConversationRuntimeTarget;
|
|
152
|
+
model: string;
|
|
153
|
+
};
|
|
147
154
|
} | {
|
|
148
155
|
id: string;
|
|
149
156
|
method: 'set_input_mode';
|
|
@@ -18,6 +18,7 @@ export interface WslTargetRuntimeClient {
|
|
|
18
18
|
rateAutoRoute?(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
19
19
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
20
20
|
setMode?(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
|
|
21
|
+
setModel?(target: ConversationRuntimeTarget, model: string): Promise<string>;
|
|
21
22
|
setInputMode?(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next'>;
|
|
22
23
|
toggleGoalPause?(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
23
24
|
clearGoal?(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
@@ -81,6 +82,7 @@ export declare class WslAgentRuntimePool {
|
|
|
81
82
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
82
83
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
|
|
83
84
|
setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode | null>;
|
|
85
|
+
setModel(target: ConversationRuntimeTarget, model: string): Promise<string | null>;
|
|
84
86
|
toggleGoalPause(target: ConversationRuntimeTarget): Promise<boolean | null>;
|
|
85
87
|
clearGoal(target: ConversationRuntimeTarget): Promise<boolean | null>;
|
|
86
88
|
updateSetting(section: string, key: string, value: unknown): Promise<void>;
|
|
@@ -276,6 +276,18 @@ class WslAgentRuntimePool {
|
|
|
276
276
|
this.release(entry, true);
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
+
async setModel(target, model) {
|
|
280
|
+
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
281
|
+
const entry = await this.acquireExisting(normalized);
|
|
282
|
+
if (!entry?.client.setModel)
|
|
283
|
+
return null;
|
|
284
|
+
try {
|
|
285
|
+
return await entry.client.setModel(normalized, model);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
this.release(entry, true);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
279
291
|
async toggleGoalPause(target) {
|
|
280
292
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
281
293
|
const entry = await this.acquire(normalized);
|
package/dist/main.js
CHANGED
|
@@ -735,12 +735,21 @@ function setWindowsConsoleMode(mode) {
|
|
|
735
735
|
const setMode = Number.isInteger(mode);
|
|
736
736
|
const script = [
|
|
737
737
|
'Add-Type -TypeDefinition \'using System; using System.Runtime.InteropServices; public static class NewmarkConsoleMode { [DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(int n); [DllImport("kernel32.dll")] public static extern bool GetConsoleMode(IntPtr h, out uint mode); [DllImport("kernel32.dll")] public static extern bool SetConsoleMode(IntPtr h, uint mode); }\'',
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
738
|
+
// 输出句柄(STD_OUTPUT_HANDLE = -11)必须启用 ENABLE_VIRTUAL_TERMINAL_PROCESSING(0x4),
|
|
739
|
+
// 否则 TUI 的备用屏 ?1049h / 清屏 2J 等 ANSI 序列在 ConHost/传统控制台下不被解析,
|
|
740
|
+
// 主屏保留滚动历史(滚轮能滚回旧帧),光标追踪也随之错位。
|
|
741
|
+
'$out = [NewmarkConsoleMode]::GetStdHandle(-11)',
|
|
742
|
+
'$outMode = [uint32]0',
|
|
743
|
+
'if (-not [NewmarkConsoleMode]::GetConsoleMode($out, [ref]$outMode)) { exit 2 }',
|
|
741
744
|
setMode
|
|
742
|
-
? `$target = [uint32]${mode}; if (-not [NewmarkConsoleMode]::SetConsoleMode($
|
|
743
|
-
: '
|
|
745
|
+
? `$target = [uint32]${mode}; if (-not [NewmarkConsoleMode]::SetConsoleMode($out, $target)) { exit 3 }`
|
|
746
|
+
: 'if (-not [NewmarkConsoleMode]::SetConsoleMode($out, ($outMode -bor 4))) { exit 3 }',
|
|
747
|
+
// 输入句柄(STD_INPUT_HANDLE = -10)启用 ENABLE_VIRTUAL_TERMINAL_INPUT(0x200),
|
|
748
|
+
// 清除 line/echo 让方向键等原始序列可读。
|
|
749
|
+
'$inp = [NewmarkConsoleMode]::GetStdHandle(-10)',
|
|
750
|
+
'$inpMode = [uint32]0',
|
|
751
|
+
'if ([NewmarkConsoleMode]::GetConsoleMode($inp, [ref]$inpMode)) { [NewmarkConsoleMode]::SetConsoleMode($inp, (($inpMode -band (-bnot 7)) -bor 512)) | Out-Null }',
|
|
752
|
+
'Write-Output $outMode',
|
|
744
753
|
].join('; ');
|
|
745
754
|
const result = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
746
755
|
stdio: ['inherit', 'pipe', 'inherit'],
|
|
@@ -1206,6 +1215,11 @@ else if (isTuiArg) {
|
|
|
1206
1215
|
|| path.basename(process.execPath).toLowerCase() === 'newmark console runtime.exe'
|
|
1207
1216
|
|| process.env.NEWMARK_CONSOLE_WRAPPER === '1');
|
|
1208
1217
|
if (isConsoleLauncher && process.env.NEWMARK_TUI_SIDECAR !== '1') {
|
|
1218
|
+
// Console mode is shared by every process attached to the same console:
|
|
1219
|
+
// enable output VT processing (and input VT) before spawning the sidecar so
|
|
1220
|
+
// the TUI's ?1049h alternate-screen sequence is actually parsed by ConHost /
|
|
1221
|
+
// traditional consoles. The sidecar also re-applies it defensively below.
|
|
1222
|
+
setWindowsConsoleMode();
|
|
1209
1223
|
const tuiProcess = (0, child_process_1.spawnSync)(process.execPath, [path.join(__dirname, 'launcher.js'), ...args], {
|
|
1210
1224
|
cwd: process.cwd(),
|
|
1211
1225
|
env: {
|
|
@@ -3162,16 +3176,31 @@ else {
|
|
|
3162
3176
|
});
|
|
3163
3177
|
electron_1.ipcMain.handle('agent:setModel', async (_event, model) => {
|
|
3164
3178
|
if (agent) {
|
|
3165
|
-
|
|
3179
|
+
// Compare the resolved selection (qualified deployment or 'auto')
|
|
3180
|
+
// rather than the bare model name, so switching between two
|
|
3181
|
+
// same-named models on different providers is still recognized as a
|
|
3182
|
+
// real change.
|
|
3183
|
+
const before = agent.modelSelectionValue();
|
|
3166
3184
|
agent.setModel(model, true);
|
|
3185
|
+
const after = agent.modelSelectionValue();
|
|
3167
3186
|
// Compression and kernel reset only make sense when the model actually
|
|
3168
3187
|
// changed. The renderer sends this on every prompt, so an unchanged
|
|
3169
3188
|
// selection must not trigger a context compression round (which can
|
|
3170
3189
|
// run an extra model call on large histories).
|
|
3171
|
-
if (
|
|
3190
|
+
if (after !== before) {
|
|
3172
3191
|
await agent.compressForModelSwitch();
|
|
3173
3192
|
resetConversationKernel();
|
|
3174
3193
|
}
|
|
3194
|
+
// Propagate the selection to the target-bound runtime. A running Build
|
|
3195
|
+
// block keeps its current model until the next Guide/Next re-enters it;
|
|
3196
|
+
// the runtime records the new selection as pending so the context
|
|
3197
|
+
// window and the next dequeue both follow the newly selected model.
|
|
3198
|
+
const target = conversationRuntimeTarget(agent.activeConversationId || 'default');
|
|
3199
|
+
ensureConversationKernel(root)?.setModel(target, model);
|
|
3200
|
+
if (wslBackendEnabled())
|
|
3201
|
+
await ensureWslConversationPool()?.setModel(target, model);
|
|
3202
|
+
else
|
|
3203
|
+
await ensureElectronUtilityPool()?.setModel(target, model);
|
|
3175
3204
|
}
|
|
3176
3205
|
return agent?.model;
|
|
3177
3206
|
});
|
package/dist/tools/index.js
CHANGED
|
@@ -350,7 +350,7 @@ class ToolExecutor {
|
|
|
350
350
|
t('subagent_result', 'Return the persisted transcript, mailbox summary, status, and latest result for a peer agent. Target by exact id (preferred) or name.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name.' } }, []),
|
|
351
351
|
t('subagent_close', 'Close a same-conversation peer. Root can close any peer; a peer can close only itself. Target by exact id (preferred) or name.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name.' } }, []),
|
|
352
352
|
t('linked_plan', 'Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.', { action: { type: 'string', enum: ['get', 'update'] }, markdown: { type: 'string' }, expected_revision: { type: 'number' } }, ['action']),
|
|
353
|
-
t('build_history_query', 'Read the concrete public work details of one historical Build Block.
|
|
353
|
+
t('build_history_query', 'Read the concrete public work details (tool calls, results, file changes, guides) of one historical Build Block. Call it proactively when the current task continues, fixes, verifies, or depends on earlier work: reuse the returned activity instead of re-investigating from scratch. Do not call it merely to answer completion status already exposed by the prompt. Select by newest-to-oldest history_index, or by run_id returned from an earlier query. Every activity/guide content is bounded to max_chars (default 2000) to keep the read lean and cache-friendly.', { history_index: { type: 'number', minimum: 1, description: '1-based historical Build Block index from the request ledger; 1 is the newest previous task.' }, run_id: { type: 'string', description: 'Exact run id returned by an earlier build_history_query result.' }, max_events: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum trailing public work events; defaults to 80.' }, max_chars: { type: 'number', minimum: 100, maximum: 4000, description: 'Per-event/per-guide content character bound; defaults to 2000.' } }, []),
|
|
354
354
|
t('context_compress', 'Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.', { keep_recent: { type: 'number', minimum: 2, maximum: 60, description: 'Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages.' }, force: { type: 'boolean', description: 'Compress even if the context is not yet over the automatic threshold. Defaults to false.' } }, []),
|
|
355
355
|
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove declares one long-term entry for unload (see below); summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, the protected recent zone, and pending removals. The recent context tail and last user message are protected from remove/summarize unless dangerous is true. For cache-optimization, remove ONLY targets long-term history (never the protected recent tail or last user message) and does NOT unload immediately: the declared entry stays in context for the rest of the current Build Block so the provider prefix cache stays stable, then is physically removed when the Block ends — applying to subsequent Blocks only.', {
|
|
356
356
|
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'read', 'status'], description: 'list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage.' },
|
package/dist/tui/src/app.js
CHANGED
|
@@ -51,8 +51,10 @@ const ESC = "\u001b[";
|
|
|
51
51
|
function createPaintScheduler(state, output = process.stdout, renderFrame = render) {
|
|
52
52
|
let pending = false;
|
|
53
53
|
let lastFrame = "";
|
|
54
|
+
let cancelled = false;
|
|
54
55
|
const flush = () => {
|
|
55
56
|
pending = false;
|
|
57
|
+
if (cancelled) return;
|
|
56
58
|
const frame = renderFrame(state);
|
|
57
59
|
if (frame === lastFrame) return false;
|
|
58
60
|
lastFrame = frame;
|
|
@@ -65,6 +67,8 @@ function createPaintScheduler(state, output = process.stdout, renderFrame = rend
|
|
|
65
67
|
setImmediate(flush);
|
|
66
68
|
};
|
|
67
69
|
paint.flush = flush;
|
|
70
|
+
// 退出前调用:丢弃任何排队中的重绘,避免恢复主屏后又被画上一帧 TUI 画面。
|
|
71
|
+
paint.cancel = () => { cancelled = true; pending = false; };
|
|
68
72
|
return paint;
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -108,6 +112,28 @@ function resolveTuiWorkspacePath(args, options = {}) {
|
|
|
108
112
|
return explicitRoot || process.cwd();
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
// 在 Windows 传统控制台(ConHost)下,输出句柄默认不启用
|
|
116
|
+
// ENABLE_VIRTUAL_TERMINAL_PROCESSING,导致备用屏 ?1049h / 清屏 2J 等 ANSI
|
|
117
|
+
// 序列不被解析,主屏保留滚动历史(滚轮能滚回旧帧)。Electron-as-node sidecar
|
|
118
|
+
// 也不会像纯 Node 那样由 libuv 自动启用 VT。这里主动给输出句柄启用 VT 处理,
|
|
119
|
+
// 给输入句柄启用原始输入,作为 main.ts 之外入口的保险。
|
|
120
|
+
function enableWindowsVirtualTerminal() {
|
|
121
|
+
if (process.platform !== 'win32') return;
|
|
122
|
+
try {
|
|
123
|
+
const { spawnSync } = require('node:child_process');
|
|
124
|
+
const script = [
|
|
125
|
+
'Add-Type -TypeDefinition \'using System; using System.Runtime.InteropServices; public static class NewmarkTuiVT { [DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(int n); [DllImport("kernel32.dll")] public static extern bool GetConsoleMode(IntPtr h, out uint m); [DllImport("kernel32.dll")] public static extern bool SetConsoleMode(IntPtr h, uint m); }\'',
|
|
126
|
+
'$o = [NewmarkTuiVT]::GetStdHandle(-11); $om = [uint32]0; if ([NewmarkTuiVT]::GetConsoleMode($o, [ref]$om)) { [NewmarkTuiVT]::SetConsoleMode($o, ($om -bor 4)) | Out-Null }',
|
|
127
|
+
'$i = [NewmarkTuiVT]::GetStdHandle(-10); $im = [uint32]0; if ([NewmarkTuiVT]::GetConsoleMode($i, [ref]$im)) { [NewmarkTuiVT]::SetConsoleMode($i, (($im -band (-bnot 7)) -bor 512)) | Out-Null }',
|
|
128
|
+
].join('; ');
|
|
129
|
+
spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
130
|
+
stdio: 'ignore',
|
|
131
|
+
windowsHide: true,
|
|
132
|
+
timeout: 3000,
|
|
133
|
+
});
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
111
137
|
function start(options = {}) {
|
|
112
138
|
const forcedTerminal = process.env.NEWMARK_FORCE_TTY === "1";
|
|
113
139
|
if ((!process.stdin.isTTY || !process.stdout.isTTY) && !forcedTerminal) {
|
|
@@ -115,6 +141,9 @@ function start(options = {}) {
|
|
|
115
141
|
process.exitCode = 1;
|
|
116
142
|
return;
|
|
117
143
|
}
|
|
144
|
+
// 备用屏依赖输出句柄的 VT 处理;纯 Node 的 libuv 会自动启用,但
|
|
145
|
+
// Electron-as-node sidecar 或强制 TTY 路径不会,这里补上。
|
|
146
|
+
if (forcedTerminal || !process.stdout.isTTY) enableWindowsVirtualTerminal();
|
|
118
147
|
|
|
119
148
|
const args = process.argv.slice(2);
|
|
120
149
|
let adapter;
|
|
@@ -140,24 +169,38 @@ function start(options = {}) {
|
|
|
140
169
|
let timer = null;
|
|
141
170
|
let animationTimer = null;
|
|
142
171
|
let closing = false;
|
|
172
|
+
// 标准 TUI 屏幕模型:进入备用屏幕缓冲(alternate screen buffer)。
|
|
173
|
+
// 备用屏没有主屏的滚动历史:每次刷新用 2J 全量重绘而不是滚动新页面,
|
|
174
|
+
// 鼠标滚轮无法滚回上一次刷新,终端滚动也不会让画面错位(否则高亮行
|
|
175
|
+
// 会随终端滚动"离开屏幕")。
|
|
176
|
+
const enterAltScreen = () => process.stdout.write(`${ESC}?1049h${ESC}?25l${ESC}2J${ESC}H`);
|
|
177
|
+
const leaveAltScreen = () => `${ESC}?25h${ESC}?1049l${ESC}0m${ESC}2J${ESC}H`;
|
|
143
178
|
const paint = createPaintScheduler(state);
|
|
144
179
|
const cleanup = () => {
|
|
145
180
|
if (timer) clearInterval(timer);
|
|
146
181
|
if (animationTimer) clearInterval(animationTimer);
|
|
147
182
|
if (typeof process.stdin.setRawMode === "function") process.stdin.setRawMode(false);
|
|
148
183
|
process.stdin.pause();
|
|
149
|
-
process.stdout.write(`${ESC}?25h${ESC}0m${ESC}2J${ESC}H`);
|
|
150
184
|
};
|
|
151
185
|
const quit = () => {
|
|
152
186
|
if (closing) return;
|
|
153
187
|
closing = true;
|
|
188
|
+
paint.cancel();
|
|
154
189
|
cleanup();
|
|
155
190
|
if (typeof state.adapter.close === "function") state.adapter.close();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
// 退出序列与关闭消息用 fs.writeSync 同步写入 fd:直接进入内核管道,
|
|
192
|
+
// 不经过异步流缓冲,process.exit() 不会丢弃任何字节(此前异步 write
|
|
193
|
+
// 在退出瞬间被 ConPTY/管道丢弃,导致恢复主屏后看不到退出消息)。
|
|
194
|
+
try {
|
|
195
|
+
const fs = require("node:fs");
|
|
196
|
+
const fd = process.stdout.fd || 1;
|
|
197
|
+
fs.writeSync(fd, `${leaveAltScreen()}\n`);
|
|
198
|
+
fs.writeSync(fd, state.adapterKind === "mock"
|
|
199
|
+
? "Newmark TUI demo closed. No state was saved.\n"
|
|
200
|
+
: "Newmark TUI closed. Conversation and settings state are persisted by Newmark.\n");
|
|
201
|
+
} catch {}
|
|
159
202
|
process.exitCode = 0;
|
|
160
|
-
|
|
203
|
+
process.exit(0);
|
|
161
204
|
};
|
|
162
205
|
|
|
163
206
|
function simulateReply(text) {
|
|
@@ -554,8 +597,10 @@ function start(options = {}) {
|
|
|
554
597
|
process.stdin.resume();
|
|
555
598
|
process.stdin.on("keypress", handleKey);
|
|
556
599
|
process.stdout.on("resize", paint);
|
|
557
|
-
|
|
600
|
+
// 兜底:任何退出路径(包括未捕获异常后的 exit 事件)都恢复主屏缓冲与光标。
|
|
601
|
+
process.on("exit", () => process.stdout.write(`${ESC}?25h${ESC}?1049l${ESC}0m`));
|
|
558
602
|
process.on("SIGTERM", quit);
|
|
603
|
+
enterAltScreen();
|
|
559
604
|
paint();
|
|
560
605
|
}
|
|
561
606
|
|
package/dist/tui/src/data.js
CHANGED
|
@@ -204,6 +204,34 @@ const providers = [
|
|
|
204
204
|
}
|
|
205
205
|
];
|
|
206
206
|
|
|
207
|
+
// Demo-only long-list injection: NEWMARK_TUI_DEMO_MODELS=<count> appends a
|
|
208
|
+
// dedicated stress provider with <count> models so the featureless --demo TUI
|
|
209
|
+
// can exercise long model-selection cursor-follow behavior without any runtime.
|
|
210
|
+
const DEMO_MODEL_COUNT = Number(process.env.NEWMARK_TUI_DEMO_MODELS || 0);
|
|
211
|
+
if (Number.isFinite(DEMO_MODEL_COUNT) && DEMO_MODEL_COUNT > 0) {
|
|
212
|
+
providers.push({
|
|
213
|
+
id: "provider-demo-stress",
|
|
214
|
+
name: "Demo Stress",
|
|
215
|
+
base_url: "https://demo.invalid/v1",
|
|
216
|
+
api_key: "",
|
|
217
|
+
has_api_key: false,
|
|
218
|
+
protocol: "openai",
|
|
219
|
+
enabled: true,
|
|
220
|
+
models: Array.from({ length: DEMO_MODEL_COUNT }, (_, index) => ({
|
|
221
|
+
name: `demo-stress-model-${String(index).padStart(3, "0")}`,
|
|
222
|
+
display: `Stress Model ${String(index).padStart(3, "0")}`,
|
|
223
|
+
description: `Long-list cursor-follow stress model ${index} description`,
|
|
224
|
+
max_tokens: 128000,
|
|
225
|
+
vision: false,
|
|
226
|
+
thinking: false,
|
|
227
|
+
enabled: true,
|
|
228
|
+
speed_rating: "unknown",
|
|
229
|
+
capability_rating: "unknown",
|
|
230
|
+
validation: { status: "unavailable", level: "discovered", checked_at: "" }
|
|
231
|
+
}))
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
207
235
|
const flows = [
|
|
208
236
|
{
|
|
209
237
|
name: "release-readiness",
|
package/dist/tui/src/render.js
CHANGED
|
@@ -689,29 +689,34 @@ function modelView(state, width, p) {
|
|
|
689
689
|
const isCurrent = (selection) => selection.kind === current.kind
|
|
690
690
|
&& (selection.kind === "auto"
|
|
691
691
|
|| (selection.providerId === current.providerId && selection.modelId === current.modelId));
|
|
692
|
-
|
|
692
|
+
const rows = [
|
|
693
693
|
...conversationContext(state, p, "Model and reasoning effort"),
|
|
694
|
-
`${p.bold}${tr(state, "Reasoning effort")}${p.reset} ${p.muted}${tr(state, "Shared GUI/TUI request tier · ←/→ changes section")}${p.reset}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
694
|
+
`${p.bold}${tr(state, "Reasoning effort")}${p.reset} ${p.muted}${tr(state, "Shared GUI/TUI request tier · ←/→ changes section")}${p.reset}`
|
|
695
|
+
];
|
|
696
|
+
const tierStart = rows.length;
|
|
697
|
+
INTELLIGENCE_TIERS.forEach((tier, index) => {
|
|
698
|
+
const style = state.focusRegion === "content" && state.contentColumn === 0 && index === state.selected ? `${p.selected}${p.bold}` : "";
|
|
699
|
+
if (style) state.contentFocusLine = tierStart + index;
|
|
700
|
+
rows.push(`${style} ${tier === currentTier ? `${p.cyan}●${p.reset}` : "○"} ${tier}${p.reset}`);
|
|
701
|
+
});
|
|
702
|
+
rows.push(
|
|
700
703
|
"",
|
|
701
704
|
`${p.bold}${tr(state, "Deployment")}${p.reset} ${p.muted}${tr(state, "Used by this conversation, including its Plan and Subagents")}${p.reset}`,
|
|
702
|
-
""
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
705
|
+
""
|
|
706
|
+
);
|
|
707
|
+
const deploymentStart = rows.length;
|
|
708
|
+
options.forEach((option, index) => {
|
|
709
|
+
const style = state.focusRegion === "content" && state.contentColumn === 1 && index === state.selected ? `${p.selected}${p.bold}` : "";
|
|
710
|
+
if (style) state.contentFocusLine = deploymentStart + index * 2;
|
|
711
|
+
const marker = isCurrent(option.selection) ? `${p.cyan}●${p.reset}` : "○";
|
|
712
|
+
rows.push(`${style} ${marker} ${pad(option.label, Math.max(18, Math.min(32, width - 24)))} ${p.muted}${option.provider}${p.reset}`);
|
|
713
|
+
rows.push(` ${p.muted}${truncate(option.description, Math.max(20, width - 5))}${p.reset}`);
|
|
714
|
+
});
|
|
715
|
+
rows.push(
|
|
712
716
|
"",
|
|
713
717
|
`${p.muted}${tr(state, "Enter applies the focused tier or deployment. Effort persists globally; deployments remain per conversation.")}${p.reset}`
|
|
714
|
-
|
|
718
|
+
);
|
|
719
|
+
return rows;
|
|
715
720
|
}
|
|
716
721
|
|
|
717
722
|
function flowBarView(state, width, p) {
|
|
@@ -1250,14 +1255,38 @@ function overlayLines(state, width, p) {
|
|
|
1250
1255
|
return [];
|
|
1251
1256
|
}
|
|
1252
1257
|
|
|
1253
|
-
|
|
1258
|
+
// 实时读取终端窗口尺寸:优先 getWindowSize()(每次调用都查询底层 TTY 尺寸,
|
|
1259
|
+
// 不依赖 Node 缓存的 columns/rows,避免 resize 事件丢失或延迟时按旧尺寸绘制),
|
|
1260
|
+
// 其次回退到缓存 columns/rows,再回退 COLUMNS/LINES 环境变量与默认值。
|
|
1261
|
+
function readWindowSize(fallbackColumns = 100, fallbackRows = 30) {
|
|
1262
|
+
const stdout = process.stdout;
|
|
1263
|
+
if (stdout && typeof stdout.getWindowSize === "function") {
|
|
1264
|
+
try {
|
|
1265
|
+
const [width, height] = stdout.getWindowSize();
|
|
1266
|
+
if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
|
|
1267
|
+
return { columns: Math.max(1, Math.floor(width)), rows: Math.max(1, Math.floor(height)) };
|
|
1268
|
+
}
|
|
1269
|
+
} catch {}
|
|
1270
|
+
}
|
|
1271
|
+
const columns = Number(stdout?.columns) || Number(process.env.COLUMNS) || fallbackColumns;
|
|
1272
|
+
const rows = Number(stdout?.rows) || Number(process.env.LINES) || fallbackRows;
|
|
1273
|
+
return { columns: Math.max(1, Math.floor(columns)), rows: Math.max(1, Math.floor(rows)) };
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function render(state, columns, rows) {
|
|
1277
|
+
const size = (columns !== undefined && rows !== undefined)
|
|
1278
|
+
? { columns: Math.max(1, Math.floor(Number(columns) || 1)), rows: Math.max(1, Math.floor(Number(rows) || 1)) }
|
|
1279
|
+
: readWindowSize();
|
|
1254
1280
|
const p = palette(state);
|
|
1255
|
-
|
|
1256
|
-
|
|
1281
|
+
// 严格约束:直接采用终端实际尺寸,绝不通过 Math.max(52/20) 强制放大。
|
|
1282
|
+
// 终端窗口小于内部最小布局时,内容在输出阶段裁剪到窗口内,否则帧会
|
|
1283
|
+
// 画到窗口之外(用户看不到底部行/右侧列,如小窗口下的模型长菜单)。
|
|
1284
|
+
const width = size.columns;
|
|
1285
|
+
const height = size.rows;
|
|
1257
1286
|
const compact = width < 78;
|
|
1258
1287
|
const sidebarWidth = compact ? 0 : 22;
|
|
1259
1288
|
const contentWidth = width - sidebarWidth - 2;
|
|
1260
|
-
const bodyHeight = height - 3;
|
|
1289
|
+
const bodyHeight = Math.max(1, height - 3);
|
|
1261
1290
|
const sidebar = sidebarWidth ? renderSidebar(state, bodyHeight, sidebarWidth, p) : [];
|
|
1262
1291
|
const content = renderContent(state, contentWidth, bodyHeight, p);
|
|
1263
1292
|
let contentLines = content;
|
|
@@ -1266,8 +1295,11 @@ function render(state, columns = process.stdout.columns || 100, rows = process.s
|
|
|
1266
1295
|
let scroll = Math.max(0, Math.min(maximumScroll, Number(state.contentScroll) || 0));
|
|
1267
1296
|
const focusLine = Number(state.contentFocusLine) || -1;
|
|
1268
1297
|
if (focusLine >= 0) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1298
|
+
// 居中跟随:焦点行尽量保持在视口中上部,上下都留出内容,避免选中行
|
|
1299
|
+
// 贴住视口边缘或被裁掉其相邻行(如两行一组的模型选项主行+描述行)。
|
|
1300
|
+
const targetLine = Math.min(Math.max(0, Math.floor(bodyHeight / 3)), Math.max(0, bodyHeight - 2));
|
|
1301
|
+
if (focusLine < scroll + targetLine) scroll = Math.max(0, focusLine - targetLine);
|
|
1302
|
+
else if (focusLine >= scroll + bodyHeight - 1 - targetLine) scroll = Math.min(maximumScroll, focusLine - (bodyHeight - 1 - targetLine));
|
|
1271
1303
|
}
|
|
1272
1304
|
scroll = Math.max(0, Math.min(maximumScroll, scroll));
|
|
1273
1305
|
state.contentScroll = scroll;
|
|
@@ -1286,20 +1318,25 @@ function render(state, columns = process.stdout.columns || 100, rows = process.s
|
|
|
1286
1318
|
: "";
|
|
1287
1319
|
if (compact) body.unshift(pad(top, width));
|
|
1288
1320
|
const focus = state.focusRegion === "menu" ? "MENU" : "CONTENT";
|
|
1289
|
-
const footer = `${p.panel} ${p.cyan}${focus}${p.reset}${p.panel}${p.muted} · ${truncate(state.notice, width - 42)}${p.reset}${p.panel}${" ".repeat(Math.max(1, width - visibleLength(state.notice) - visibleLength(focus) - 35))}Tab back ? help Q quit ${p.reset}`;
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1321
|
+
const footer = `${p.panel} ${p.cyan}${focus}${p.reset}${p.panel}${p.muted} · ${truncate(state.notice, Math.max(1, width - 42))}${p.reset}${p.panel}${" ".repeat(Math.max(1, width - visibleLength(state.notice) - visibleLength(focus) - 35))}Tab back ? help Q quit ${p.reset}`;
|
|
1322
|
+
const bodyLines = Math.max(1, height - 1);
|
|
1323
|
+
const renderedBody = body.slice(0, bodyLines);
|
|
1324
|
+
while (renderedBody.length < bodyLines) renderedBody.push(pad("", width));
|
|
1325
|
+
// 输出阶段严格边界:每行按可见宽度截断到窗口宽度,行数限制在窗口高度内,
|
|
1326
|
+
// 确保无论内部最小布局如何,帧绝不会画到窗口之外。
|
|
1327
|
+
const boundedBody = renderedBody.map((line) => truncate(line, width));
|
|
1328
|
+
const boundedFooter = truncate(pad(footer, width), width);
|
|
1329
|
+
let output = `${ESC}?25l${ESC}2J${ESC}H${p.paint}${boundedBody.join("\n")}\n${boundedFooter}`;
|
|
1293
1330
|
const overlay = overlayLines(state, width, p);
|
|
1294
1331
|
if (overlay.length) {
|
|
1295
1332
|
const overlayWidth = Math.max(...overlay.map(visibleLength));
|
|
1296
1333
|
const x = Math.max(1, Math.floor((width - overlayWidth) / 2) + 1);
|
|
1297
1334
|
const y = Math.max(2, Math.floor((height - overlay.length) / 2));
|
|
1298
1335
|
overlay.forEach((line, index) => {
|
|
1299
|
-
output += `${ESC}${y + index};${x}H${line}`;
|
|
1336
|
+
output += `${ESC}${y + index};${x}H${truncate(line, width)}`;
|
|
1300
1337
|
});
|
|
1301
1338
|
}
|
|
1302
1339
|
return `${output}${p.final}`;
|
|
1303
1340
|
}
|
|
1304
1341
|
|
|
1305
|
-
module.exports = { render, stripAnsi, visibleLength, wrapText };
|
|
1342
|
+
module.exports = { render, readWindowSize, stripAnsi, visibleLength, wrapText };
|
package/dist/ui/index.html
CHANGED
|
@@ -22825,6 +22825,17 @@ function schedulePostStartupUiRendering() {
|
|
|
22825
22825
|
var modelResult = await api.setModel(state.model);
|
|
22826
22826
|
if (modelResult != null) state._syncedModel = state.model;
|
|
22827
22827
|
}
|
|
22828
|
+
// The context ring/inspector must immediately reflect the newly selected
|
|
22829
|
+
// model's configured context window rather than waiting for the next send.
|
|
22830
|
+
if (api.getState) {
|
|
22831
|
+
var ctxTarget = currentConversationTarget();
|
|
22832
|
+
api.getState(ctxTarget).then(function(s) {
|
|
22833
|
+
if (s && s.contextWindow !== undefined && isActiveConversationTarget(ctxTarget) && isViewingRuntimeConversationBranch(ctxTarget)) {
|
|
22834
|
+
state.contextWindow = s.contextWindow;
|
|
22835
|
+
window.renderContextWindow();
|
|
22836
|
+
}
|
|
22837
|
+
}).catch(function(){});
|
|
22838
|
+
}
|
|
22828
22839
|
});
|
|
22829
22840
|
}
|
|
22830
22841
|
|
|
@@ -336409,7 +336409,7 @@ var ToolExecutor = class {
|
|
|
336409
336409
|
t3("subagent_result", "Return the persisted transcript, mailbox summary, status, and latest result for a peer agent. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
|
|
336410
336410
|
t3("subagent_close", "Close a same-conversation peer. Root can close any peer; a peer can close only itself. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
|
|
336411
336411
|
t3("linked_plan", "Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.", { action: { type: "string", enum: ["get", "update"] }, markdown: { type: "string" }, expected_revision: { type: "number" } }, ["action"]),
|
|
336412
|
-
t3("build_history_query", "Read the concrete public work details of one historical Build Block.
|
|
336412
|
+
t3("build_history_query", "Read the concrete public work details (tool calls, results, file changes, guides) of one historical Build Block. Call it proactively when the current task continues, fixes, verifies, or depends on earlier work: reuse the returned activity instead of re-investigating from scratch. Do not call it merely to answer completion status already exposed by the prompt. Select by newest-to-oldest history_index, or by run_id returned from an earlier query. Every activity/guide content is bounded to max_chars (default 2000) to keep the read lean and cache-friendly.", { history_index: { type: "number", minimum: 1, description: "1-based historical Build Block index from the request ledger; 1 is the newest previous task." }, run_id: { type: "string", description: "Exact run id returned by an earlier build_history_query result." }, max_events: { type: "number", minimum: 1, maximum: 200, description: "Maximum trailing public work events; defaults to 80." }, max_chars: { type: "number", minimum: 100, maximum: 4e3, description: "Per-event/per-guide content character bound; defaults to 2000." } }, []),
|
|
336413
336413
|
t3("context_compress", "Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.", { keep_recent: { type: "number", minimum: 2, maximum: 60, description: "Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages." }, force: { type: "boolean", description: "Compress even if the context is not yet over the automatic threshold. Defaults to false." } }, []),
|
|
336414
336414
|
t3("context_history_manage", "Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove declares one long-term entry for unload (see below); summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, the protected recent zone, and pending removals. The recent context tail and last user message are protected from remove/summarize unless dangerous is true. For cache-optimization, remove ONLY targets long-term history (never the protected recent tail or last user message) and does NOT unload immediately: the declared entry stays in context for the rest of the current Build Block so the provider prefix cache stays stable, then is physically removed when the Block ends \u2014 applying to subsequent Blocks only.", {
|
|
336415
336415
|
action: { type: "string", enum: ["list", "remove", "summarize", "restore", "search", "read", "status"], description: "list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage." },
|
|
@@ -340904,7 +340904,7 @@ function buildConversationTaskLedger(agent) {
|
|
|
340904
340904
|
"Unfinished Continuation Queue (newest to oldest; summary fields only; use only when the current user instruction authorizes continuation and the task is relevant):",
|
|
340905
340905
|
...unfinishedLines.length ? unfinishedLines : ["(none)"],
|
|
340906
340906
|
...unfinished.length > unfinishedLines.length ? [`(${unfinished.length - unfinishedLines.length} older unfinished run(s) omitted from the bounded prompt ledger.)`] : [],
|
|
340907
|
-
"When
|
|
340907
|
+
"When the current task continues, fixes, verifies, or depends on earlier work in this list, proactively call build_history_query with its history_index before re-investigating. Reuse the returned tool activity and results instead of re-running commands or re-reading files this conversation already examined. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown here."
|
|
340908
340908
|
].join("\n");
|
|
340909
340909
|
}
|
|
340910
340910
|
async function shouldStopAfterTurn(agent, message) {
|
|
@@ -349172,8 +349172,13 @@ ${summary}`, segment, "local-summarize", true);
|
|
|
349172
349172
|
};
|
|
349173
349173
|
}
|
|
349174
349174
|
resolveWindowModel(modelName) {
|
|
349175
|
-
if (modelName
|
|
349176
|
-
|
|
349175
|
+
if (modelName === "auto" || modelName === this.model || modelName === this.activeModelName()) {
|
|
349176
|
+
const active = this.activeModelConfig();
|
|
349177
|
+
if (active) return active;
|
|
349178
|
+
}
|
|
349179
|
+
const byName = this.config.findModel(modelName);
|
|
349180
|
+
if (byName) return byName;
|
|
349181
|
+
return this.config.findModel(this.config.getStr("models", "default_model"));
|
|
349177
349182
|
}
|
|
349178
349183
|
contextMaxTokens(modelName = this.model) {
|
|
349179
349184
|
const model = this.resolveWindowModel(modelName);
|
|
@@ -352337,7 +352342,7 @@ ${custom}`);
|
|
|
352337
352342
|
"- Memory Lab is governed by an explicit Policy chain: pre-think whether memory is needed; prefer bounded memory_lab_query retrieval; then choose ADD/UPDATE/DELETE only when the user authorizes durable memory mutation.",
|
|
352338
352343
|
"- Before memory_lab_update, inspect the target with memory_lab_query or memory_lab_read. For an existing component pass expectedUpdatedAt so concurrent/stale writes fail closed; preserve established tag parent paths.",
|
|
352339
352344
|
"- Use memory_lab_delete only for an explicit user request to forget/remove memory. Prior revisions are retained under Memory Lab/archive and mutation decisions are appended to policy.jsonl for replay.",
|
|
352340
|
-
"- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status.
|
|
352345
|
+
"- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status. When the current task continues, fixes, verifies, or depends on earlier Build Blocks, proactively call build_history_query to read the concrete tool activity and results of the relevant block, and reuse that information instead of re-investigating (re-running commands or re-reading files) from scratch. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown in the prompt.",
|
|
352341
352346
|
"- Linked plan disclosure: a durable conversation-linked Markdown plan exists and can be inspected or updated with linked_plan when explicitly needed or required by Plan mode. Its full Markdown and revision are not injected into every model request.",
|
|
352342
352347
|
"- A memory_lab_update, memory_lab_delete, or memory_lab_reindex call is unfinished until its awaited tool result contains rebuildReceipt.completed=true. The completion receipt is represented by the tool activity inside the current Build block and should not be repeated as a separate completion message.",
|
|
352343
352348
|
`- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; task creates constrained subagents tracked in agent state.`,
|
|
@@ -353135,6 +353140,18 @@ var ConversationKernel = class {
|
|
|
353135
353140
|
if (runtime) runtime.options.mode = mode;
|
|
353136
353141
|
return runner.mode;
|
|
353137
353142
|
}
|
|
353143
|
+
setModel(target, model) {
|
|
353144
|
+
const normalized = this.normalizeTarget(target);
|
|
353145
|
+
const runtime = this.findRuntime(normalized);
|
|
353146
|
+
const runner = runtime?.runner || this.createRunner(normalized);
|
|
353147
|
+
if (!runtime || !runtime.activePromise) {
|
|
353148
|
+
runner.setModel(model);
|
|
353149
|
+
} else {
|
|
353150
|
+
runtime.options.model = model;
|
|
353151
|
+
}
|
|
353152
|
+
runner.saveWorkspaceConversationState(true);
|
|
353153
|
+
return runner.model;
|
|
353154
|
+
}
|
|
353138
353155
|
async toggleGoalPause(target) {
|
|
353139
353156
|
const normalized = this.normalizeTarget(target);
|
|
353140
353157
|
let runtime = this.findRuntime(normalized);
|
|
@@ -353249,14 +353266,15 @@ var ConversationKernel = class {
|
|
|
353249
353266
|
}
|
|
353250
353267
|
async prompt(message, target, options, queueMode = "followUp") {
|
|
353251
353268
|
const normalized = this.normalizeTarget(target);
|
|
353269
|
+
const active = this.findRuntime(normalized);
|
|
353270
|
+
if (active?.activePromise) {
|
|
353271
|
+
this.enqueueSameSession(active, message, queueMode);
|
|
353272
|
+
this.activateAcceptedGoal(active, typeof message === "string" ? "" : message.goalObjective);
|
|
353273
|
+
return active.activePromise;
|
|
353274
|
+
}
|
|
353252
353275
|
const runtime = this.runtime(normalized, options);
|
|
353253
353276
|
runtime.options = { ...options };
|
|
353254
353277
|
this.applyOptions(runtime.runner, options);
|
|
353255
|
-
if (runtime.activePromise) {
|
|
353256
|
-
this.enqueueSameSession(runtime, message, queueMode);
|
|
353257
|
-
this.activateAcceptedGoal(runtime, typeof message === "string" ? "" : message.goalObjective);
|
|
353258
|
-
return runtime.activePromise;
|
|
353259
|
-
}
|
|
353260
353278
|
runtime.generation = (this.generations.get(runtime.runtimeKey) || runtime.generation || 0) + 1;
|
|
353261
353279
|
this.generations.set(runtime.runtimeKey, runtime.generation);
|
|
353262
353280
|
const requestedRunId = typeof message === "string" ? "" : String(message.runId || "").trim().slice(0, 200);
|
|
@@ -353401,7 +353419,21 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
353401
353419
|
this.mirrorHostIfTargetActive(runtime);
|
|
353402
353420
|
return this.result(runtime, lastTokens);
|
|
353403
353421
|
}
|
|
353422
|
+
/**
|
|
353423
|
+
* Apply a model selection recorded while a Build block was running. The
|
|
353424
|
+
* in-flight block never switches mid-block; the switch takes effect the next
|
|
353425
|
+
* time a queued Guide/Next re-enters the block, and only when the pending
|
|
353426
|
+
* selection actually differs from the runner's current selection.
|
|
353427
|
+
*/
|
|
353428
|
+
syncPendingModel(runtime) {
|
|
353429
|
+
const pending3 = String(runtime.options.model || "").trim();
|
|
353430
|
+
if (!pending3) return;
|
|
353431
|
+
if (pending3 === runtime.runner.model || pending3 === runtime.runner.modelSelectionValue()) return;
|
|
353432
|
+
runtime.runner.setModel(pending3);
|
|
353433
|
+
runtime.options.model = runtime.runner.modelSelectionValue();
|
|
353434
|
+
}
|
|
353404
353435
|
async runSingle(runtime, message, continuationMode) {
|
|
353436
|
+
this.syncPendingModel(runtime);
|
|
353405
353437
|
this.consumeQueuedMessage(runtime, typeof message === "string" ? message : message.text);
|
|
353406
353438
|
const timeoutMs = this.processTimeoutMs(runtime);
|
|
353407
353439
|
if (timeoutMs <= 0) {
|
|
@@ -354100,6 +354132,9 @@ async function handle(request) {
|
|
|
354100
354132
|
if (request.method === "set_mode") {
|
|
354101
354133
|
return kernel.setMode(requestTarget(request.params), request.params.mode);
|
|
354102
354134
|
}
|
|
354135
|
+
if (request.method === "set_model") {
|
|
354136
|
+
return kernel.setModel(requestTarget(request.params), request.params.model);
|
|
354137
|
+
}
|
|
354103
354138
|
if (request.method === "set_input_mode") {
|
|
354104
354139
|
return kernel.setInputMode(requestTarget(request.params), request.params.mode);
|
|
354105
354140
|
}
|
package/dist/wsl-agent-host.js
CHANGED
|
@@ -206,6 +206,9 @@ async function handle(request) {
|
|
|
206
206
|
if (request.method === 'set_mode') {
|
|
207
207
|
return kernel.setMode(requestTarget(request.params), request.params.mode);
|
|
208
208
|
}
|
|
209
|
+
if (request.method === 'set_model') {
|
|
210
|
+
return kernel.setModel(requestTarget(request.params), request.params.model);
|
|
211
|
+
}
|
|
209
212
|
if (request.method === 'set_input_mode') {
|
|
210
213
|
return kernel.setInputMode(requestTarget(request.params), request.params.mode);
|
|
211
214
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newmark-agent",
|
|
3
3
|
"productName": "Newmark Agent",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"description": "Newmark Agent — Portable AI coding agent with rich GUI and CLI (TypeScript)",
|
|
6
6
|
"homepage": "https://github.com/positer/Newmark-Agent",
|
|
7
7
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"start:dev": "npm run build && electron .",
|
|
34
34
|
"start:cli": "node dist/launcher.js --cli",
|
|
35
35
|
"test:tui": "npm run build && npm run test:tui:built",
|
|
36
|
-
"test:tui:built": "node ../TUI/test/demo.test.js && node ../TUI/test/cursor-follow-stress.test.js && node dist/tests/tuiLauncherVerify.js && node dist/tests/tuiStopRaceVerify.js",
|
|
36
|
+
"test:tui:built": "node ../TUI/test/demo.test.js && node ../TUI/test/cursor-follow-stress.test.js && node ../TUI/test/cursor-follow-pty-gate.js && node dist/tests/tuiLauncherVerify.js && node dist/tests/tuiStopRaceVerify.js",
|
|
37
37
|
"test:ssh-tui-stress": "npm run build && npm run test:ssh-tui-stress:built",
|
|
38
38
|
"test:ssh-tui-stress:built": "node scripts/release-ssh-tui-stress.cjs",
|
|
39
39
|
"test:wsl-tui-stress:built": "node scripts/release-wsl-tui-stress.cjs",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test:desktop": "npm run build && npm run test:desktop:built",
|
|
54
54
|
"test:deletion-safety": "npm run build && npm run test:deletion-safety:built",
|
|
55
55
|
"test:deletion-safety:built": "node scripts/deletion-safety-stress.cjs",
|
|
56
|
-
"test:desktop:built": "node dist/tests/verify.js && node dist/tests/thinkingTierMapCacheStressVerify.js && node dist/tests/computerUseSessionVerify.js && node dist/tests/contextSystemV2Verify.js && node dist/tests/contextSystemV2StressVerify.js && node dist/tests/providerAdapterV2Verify.js && node dist/tests/providerTimeoutRecoveryVerify.js && node dist/tests/agentRuntimeV2Verify.js && node dist/tests/toolchainExposureV2Verify.js && node dist/tests/localOcrFallbackVerify.js && node dist/tests/dev018ModeSubagentStressVerify.js && node dist/tests/conversationBranchStressVerify.js && node dist/tests/conversationArchiveConcurrencyVerify.js && node dist/tests/conversationArchiveRuntimeVerify.js && node dist/tests/dev040ComprehensiveStressVerify.js && node dist/tests/memoryPolicyVerify.js && node dist/tests/newmarkSelectVerify.js && node dist/tests/mcpManagerVerify.js && node dist/tests/dshCompatibilityVerify.js && node dist/tests/performanceOptimizationVerify.js && node scripts/compression-pressure-stress.cjs && node dist/tests/compressionFidelityVerify.js && node dist/tests/responseTrajectoryVerify.js && node dist/tests/normalChatRegressionVerify.js && node dist/tests/dev008-subagent.js && node dist/tests/dev009Verify.js && node dist/tests/runtimeIsolationVerify.js && node dist/tests/runtimePoolCapacityVerify.js && node dist/tests/guideWorkRunVerify.js && node scripts/flow-pause-stop-draft-stress.cjs && node dist/tests/guideUiReconcileVerify.js && node dist/tests/queueAttachmentIsolationVerify.js && node dist/tests/workspaceMenuVerify.js && node dist/tests/userImagePersistenceVerify.js && node dist/tests/displayImageVerify.js && node dist/tests/visualPreferencesVerify.js && node dist/tests/browserUseVerify.js && node dist/tests/toolProcessVerify.js && node dist/tests/toolProvisioningVerify.js && node dist/tests/taskToolsVerify.js && node dist/tests/contextCacheHitStressVerify.js && node dist/tests/startupPrewarmVerify.js && node dist/tests/pdfPreviewServerVerify.js && node dist/tests/editorLifecycleVerify.js && node dist/tests/terminalTakeoverVerify.js && node dist/tests/workspaceFileRouterVerify.js && node dist/tests/computerUsePerformanceVerify.js && node dist/tests/autoRouterVerify.js && node dist/tests/autoAgentIntegrationVerify.js && node dist/tests/autoRouteRatingVerify.js && node dist/tests/providerIdentityVerify.js && node dist/tests/modelValidationVerify.js && node dist/tests/modelValidationAgentIntegrationVerify.js && node scripts/deletion-safety-stress.cjs",
|
|
56
|
+
"test:desktop:built": "node dist/tests/verify.js && node dist/tests/thinkingTierMapCacheStressVerify.js && node dist/tests/computerUseSessionVerify.js && node dist/tests/contextSystemV2Verify.js && node dist/tests/contextSystemV2StressVerify.js && node dist/tests/providerAdapterV2Verify.js && node dist/tests/providerTimeoutRecoveryVerify.js && node dist/tests/agentRuntimeV2Verify.js && node dist/tests/toolchainExposureV2Verify.js && node dist/tests/localOcrFallbackVerify.js && node dist/tests/dev018ModeSubagentStressVerify.js && node dist/tests/conversationBranchStressVerify.js && node dist/tests/conversationArchiveConcurrencyVerify.js && node dist/tests/conversationArchiveRuntimeVerify.js && node dist/tests/dev040ComprehensiveStressVerify.js && node dist/tests/memoryPolicyVerify.js && node dist/tests/newmarkSelectVerify.js && node dist/tests/mcpManagerVerify.js && node dist/tests/dshCompatibilityVerify.js && node dist/tests/performanceOptimizationVerify.js && node scripts/compression-pressure-stress.cjs && node dist/tests/compressionFidelityVerify.js && node dist/tests/responseTrajectoryVerify.js && node dist/tests/normalChatRegressionVerify.js && node dist/tests/dev008-subagent.js && node dist/tests/dev009Verify.js && node dist/tests/runtimeIsolationVerify.js && node dist/tests/runtimePoolCapacityVerify.js && node dist/tests/guideWorkRunVerify.js && node dist/tests/modelSwitchBehaviorVerify.js && node scripts/flow-pause-stop-draft-stress.cjs && node dist/tests/guideUiReconcileVerify.js && node dist/tests/queueAttachmentIsolationVerify.js && node dist/tests/workspaceMenuVerify.js && node dist/tests/userImagePersistenceVerify.js && node dist/tests/displayImageVerify.js && node dist/tests/visualPreferencesVerify.js && node dist/tests/browserUseVerify.js && node dist/tests/toolProcessVerify.js && node dist/tests/toolProvisioningVerify.js && node dist/tests/taskToolsVerify.js && node dist/tests/contextCacheHitStressVerify.js && node dist/tests/startupPrewarmVerify.js && node dist/tests/pdfPreviewServerVerify.js && node dist/tests/editorLifecycleVerify.js && node dist/tests/terminalTakeoverVerify.js && node dist/tests/workspaceFileRouterVerify.js && node dist/tests/computerUsePerformanceVerify.js && node dist/tests/autoRouterVerify.js && node dist/tests/autoAgentIntegrationVerify.js && node dist/tests/autoRouteRatingVerify.js && node dist/tests/providerIdentityVerify.js && node dist/tests/modelValidationVerify.js && node dist/tests/modelValidationAgentIntegrationVerify.js && node scripts/deletion-safety-stress.cjs",
|
|
57
57
|
"test:conversation-branch-stress": "npm run build && node dist/tests/conversationBranchStressVerify.js",
|
|
58
58
|
"test:conversation-archive-concurrency": "npm run build && node dist/tests/conversationArchiveConcurrencyVerify.js",
|
|
59
59
|
"test:memory-policy": "npm run build && node dist/tests/memoryPolicyVerify.js",
|