newmark-agent 0.3.12 → 0.4.0

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.
Files changed (52) hide show
  1. package/dist/cli-commands.d.ts +1 -0
  2. package/dist/cli-commands.js +11 -2
  3. package/dist/context/domain/types.d.ts +37 -0
  4. package/dist/context/services/context-orchestrator.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +1147 -131
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +139 -5
  8. package/dist/core/agent.js +962 -82
  9. package/dist/core/agentKernel/agent-loop.js +29 -3
  10. package/dist/core/agentKernel/types.d.ts +7 -0
  11. package/dist/core/agentKernelRunner.d.ts +2 -0
  12. package/dist/core/agentKernelRunner.js +121 -19
  13. package/dist/core/conversationKernel.d.ts +5 -0
  14. package/dist/core/conversationKernel.js +29 -0
  15. package/dist/core/dshCompatibility.d.ts +198 -0
  16. package/dist/core/dshCompatibility.js +600 -0
  17. package/dist/core/electronUtilityAgentClient.d.ts +4 -0
  18. package/dist/core/electronUtilityAgentClient.js +4 -0
  19. package/dist/core/electronUtilityRuntimePool.d.ts +8 -0
  20. package/dist/core/electronUtilityRuntimePool.js +14 -0
  21. package/dist/core/mcpManager.d.ts +1 -0
  22. package/dist/core/mcpManager.js +100 -10
  23. package/dist/core/subagent.d.ts +6 -0
  24. package/dist/core/subagent.js +22 -1
  25. package/dist/core/toolPolicy.d.ts +6 -0
  26. package/dist/core/toolPolicy.js +49 -1
  27. package/dist/core/types.d.ts +1 -1
  28. package/dist/core/utilityAgentProtocol.d.ts +8 -1
  29. package/dist/core/workspace.d.ts +9 -0
  30. package/dist/core/workspace.js +48 -1
  31. package/dist/core/wslAgentClient.d.ts +4 -0
  32. package/dist/core/wslAgentClient.js +4 -0
  33. package/dist/core/wslAgentProtocol.d.ts +8 -1
  34. package/dist/core/wslAgentRuntimePool.d.ts +8 -0
  35. package/dist/core/wslAgentRuntimePool.js +15 -0
  36. package/dist/launcher.js +8 -0
  37. package/dist/llm/provider.d.ts +1 -1
  38. package/dist/llm/provider.js +4 -3
  39. package/dist/main.js +163 -11
  40. package/dist/preload.js +11 -0
  41. package/dist/providers/chat-completions.adapter.js +41 -15
  42. package/dist/providers/provider-adapter.d.ts +3 -0
  43. package/dist/toolchain/registry/tool-registry.d.ts +13 -1
  44. package/dist/toolchain/registry/tool-registry.js +8 -0
  45. package/dist/toolchain/registry-seeder.js +51 -5
  46. package/dist/tools/index.js +11 -2
  47. package/dist/tools/nativeTools.js +5 -1
  48. package/dist/ui/index.html +2530 -205
  49. package/dist/ui/lucide-sprite.svg +26 -0
  50. package/dist/wsl-agent-host.bundle.cjs +1147 -131
  51. package/dist/wsl-agent-host.js +3 -0
  52. package/package.json +4 -2
@@ -28,6 +28,7 @@ export interface McpServerInput {
28
28
  export declare class McpManager {
29
29
  private readonly filePath;
30
30
  private state;
31
+ private preserveCorruptSource;
31
32
  constructor(root: string);
32
33
  list(): Array<Omit<McpServerConfig, 'env' | 'headers'> & {
33
34
  envKeys: string[];
@@ -45,10 +45,10 @@ function stringRecord(value, label) {
45
45
  const output = {};
46
46
  for (const [key, item] of Object.entries(value)) {
47
47
  const cleanKey = String(key || '').trim();
48
- if (!cleanKey || cleanKey.includes('\r') || cleanKey.includes('\n') || cleanKey.includes(String.fromCharCode(0)))
48
+ if (!cleanKey || cleanKey.includes('\r') || cleanKey.includes('\n') || cleanKey.includes(String.fromCharCode(0)) || ['__proto__', 'prototype', 'constructor'].includes(cleanKey))
49
49
  throw new Error(`${label} contains an invalid key.`);
50
50
  const cleanValue = String(item ?? '');
51
- if (cleanValue.includes(String.fromCharCode(0)))
51
+ if (cleanValue.includes(String.fromCharCode(0)) || (label === 'headers' && /[\r\n]/.test(cleanValue)))
52
52
  throw new Error(`${label} contains an invalid value.`);
53
53
  output[cleanKey] = cleanValue;
54
54
  }
@@ -59,11 +59,83 @@ function stringArray(value) {
59
59
  return undefined;
60
60
  if (!Array.isArray(value))
61
61
  throw new Error('args must be a JSON array of strings.');
62
- return value.map(item => String(item)).filter(item => item.length <= 4000).slice(0, 100);
62
+ if (value.length > 100 || value.some(item => typeof item !== 'string' || item.length > 4000 || item.includes(String.fromCharCode(0)))) {
63
+ throw new Error('args must contain at most 100 strings, each no longer than 4000 characters.');
64
+ }
65
+ return [...value];
66
+ }
67
+ function publicHttpUrl(value) {
68
+ if (!value)
69
+ return undefined;
70
+ try {
71
+ const parsed = new URL(value);
72
+ if (parsed.username)
73
+ parsed.username = '<redacted>';
74
+ if (parsed.password)
75
+ parsed.password = '<redacted>';
76
+ for (const key of Array.from(parsed.searchParams.keys())) {
77
+ if (/(?:api.?key|authorization|access.?token|secret|password|credential)/i.test(key))
78
+ parsed.searchParams.set(key, '<redacted>');
79
+ }
80
+ return parsed.toString();
81
+ }
82
+ catch {
83
+ return value;
84
+ }
85
+ }
86
+ function parseHttpUrl(value) {
87
+ let parsed;
88
+ try {
89
+ parsed = new URL(value);
90
+ }
91
+ catch {
92
+ throw new Error('An HTTP MCP server requires an http(s) URL.');
93
+ }
94
+ if (!['http:', 'https:'].includes(parsed.protocol))
95
+ throw new Error('An HTTP MCP server requires an http(s) URL.');
96
+ if (parsed.username || parsed.password)
97
+ throw new Error('Put HTTP credentials in headers, not in the MCP URL.');
98
+ return value;
99
+ }
100
+ function safeLoadedServer(value) {
101
+ try {
102
+ if (!value || typeof value !== 'object' || Array.isArray(value))
103
+ return undefined;
104
+ const item = value;
105
+ const id = String(item.id || '').trim();
106
+ const name = String(item.name || '').trim();
107
+ const transport = item.transport === 'http' ? 'http' : item.transport === 'stdio' ? 'stdio' : undefined;
108
+ if (!id || !name || !transport)
109
+ return undefined;
110
+ const command = String(item.command || '').trim();
111
+ const url = String(item.url || '').trim();
112
+ if (transport === 'stdio' && !command)
113
+ return undefined;
114
+ if (transport === 'http')
115
+ parseHttpUrl(url);
116
+ return {
117
+ id: id.slice(0, 200),
118
+ name: name.slice(0, 120),
119
+ enabled: item.enabled === true,
120
+ transport,
121
+ command: transport === 'stdio' ? command.slice(0, 2000) : undefined,
122
+ args: transport === 'stdio' ? (stringArray(item.args) || []) : undefined,
123
+ cwd: transport === 'stdio' ? String(item.cwd || '').trim().slice(0, 4000) || undefined : undefined,
124
+ url: transport === 'http' ? url.slice(0, 4000) : undefined,
125
+ env: transport === 'stdio' ? (stringRecord(item.env, 'env') || {}) : undefined,
126
+ headers: transport === 'http' ? (stringRecord(item.headers, 'headers') || {}) : undefined,
127
+ createdAt: String(item.createdAt || new Date(0).toISOString()),
128
+ updatedAt: String(item.updatedAt || item.createdAt || new Date(0).toISOString()),
129
+ };
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
63
134
  }
64
135
  class McpManager {
65
136
  filePath;
66
137
  state;
138
+ preserveCorruptSource = false;
67
139
  constructor(root) {
68
140
  this.filePath = path.join(root, 'MCP.json');
69
141
  this.state = this.load();
@@ -74,6 +146,7 @@ class McpManager {
74
146
  return {
75
147
  ...publicServer,
76
148
  args: [...(server.args || [])],
149
+ url: publicHttpUrl(server.url),
77
150
  envKeys: Object.keys(env || {}).sort(),
78
151
  headerKeys: Object.keys(headers || {}).sort(),
79
152
  };
@@ -81,22 +154,33 @@ class McpManager {
81
154
  }
82
155
  upsert(input) {
83
156
  const id = String(input.id || '').trim();
84
- const existing = this.state.servers.find(server => server.id === id);
157
+ if (input.transport !== undefined && input.transport !== 'stdio' && input.transport !== 'http')
158
+ throw new Error('transport must be stdio or http.');
159
+ let existing = this.state.servers.find(server => server.id === id);
85
160
  const name = String(input.name ?? existing?.name ?? '').trim().slice(0, 120);
86
161
  if (!name)
87
162
  throw new Error('MCP server name is required.');
163
+ if (/[\r\n\0]/.test(name))
164
+ throw new Error('MCP server name contains invalid characters.');
88
165
  const transport = input.transport === 'http' ? 'http' : (input.transport === 'stdio' ? 'stdio' : existing?.transport || 'stdio');
89
166
  const command = String(input.command ?? existing?.command ?? '').trim();
90
- const url = String(input.url ?? existing?.url ?? '').trim();
167
+ let url = String(input.url ?? existing?.url ?? '').trim();
91
168
  if (transport === 'stdio' && !command)
92
169
  throw new Error('A stdio MCP server requires a command.');
93
- if (transport === 'http' && !/^https?:\/\//i.test(url))
94
- throw new Error('An HTTP MCP server requires an http(s) URL.');
170
+ if (transport === 'stdio' && /[\r\n\0]/.test(command))
171
+ throw new Error('command contains an invalid value.');
172
+ if (transport === 'http')
173
+ url = parseHttpUrl(url);
174
+ if (!existing && !id) {
175
+ existing = this.state.servers.find(server => server.name.toLocaleLowerCase() === name.toLocaleLowerCase()
176
+ && server.transport === transport
177
+ && (transport === 'stdio' ? server.command === command : server.url === url));
178
+ }
95
179
  const now = new Date().toISOString();
96
180
  const server = {
97
181
  id: existing?.id || `mcp-${(0, crypto_1.randomUUID)()}`,
98
182
  name,
99
- enabled: typeof input.enabled === 'boolean' ? input.enabled : existing?.enabled !== false,
183
+ enabled: typeof input.enabled === 'boolean' ? input.enabled : existing?.enabled === true,
100
184
  transport,
101
185
  command: transport === 'stdio' ? command.slice(0, 2000) : undefined,
102
186
  args: transport === 'stdio' ? (stringArray(input.args) ?? existing?.args ?? []) : undefined,
@@ -134,15 +218,21 @@ class McpManager {
134
218
  load() {
135
219
  try {
136
220
  const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
137
- return { version: 1, servers: Array.isArray(parsed.servers) ? parsed.servers.filter(Boolean) : [] };
221
+ return { version: 1, servers: Array.isArray(parsed.servers) ? parsed.servers.map(safeLoadedServer).filter((server) => !!server) : [] };
138
222
  }
139
223
  catch {
224
+ this.preserveCorruptSource = fs.existsSync(this.filePath);
140
225
  return { version: 1, servers: [] };
141
226
  }
142
227
  }
143
228
  save() {
144
229
  fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
145
- const tempPath = `${this.filePath}.${process.pid}.tmp`;
230
+ if (this.preserveCorruptSource) {
231
+ const backupPath = `${this.filePath}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`;
232
+ fs.copyFileSync(this.filePath, backupPath);
233
+ this.preserveCorruptSource = false;
234
+ }
235
+ const tempPath = `${this.filePath}.${process.pid}-${(0, crypto_1.randomUUID)()}.tmp`;
146
236
  fs.writeFileSync(tempPath, JSON.stringify(this.state, null, 2), 'utf8');
147
237
  fs.renameSync(tempPath, this.filePath);
148
238
  }
@@ -223,6 +223,12 @@ export declare class SubagentManager {
223
223
  toRecord(idOrName: string): NewmarkSubagentRecord | undefined;
224
224
  toToolResult(idOrName: string, output: string, ok?: boolean): NewmarkSubagentToolResult;
225
225
  getResult(name: string): string;
226
+ /**
227
+ * 有界结果 transcript:subagent_result 注入主 Agent 上下文时,不再放大完整
228
+ * 消息历史(含所有中间 tool call/result 洪水)。只保留最近若干条非 tool 消息,
229
+ * 按字符上限截断,杜绝上下文回归。完整历史按需走 subagent_read(max_chars)。
230
+ */
231
+ boundedResultTranscript(idOrName: string): string;
226
232
  listActive(): SubagentInstance[];
227
233
  listAll(): SubagentInstance[];
228
234
  pauseScheduling(): void;
@@ -193,7 +193,7 @@ class SubagentManager {
193
193
  fromAgentId,
194
194
  toAgentId: target.id,
195
195
  kind,
196
- body,
196
+ body: truncateText(body, 32000),
197
197
  correlationId: details.correlationId,
198
198
  replyTo: details.replyTo,
199
199
  createdAt: now(),
@@ -481,6 +481,27 @@ class SubagentManager {
481
481
  return '';
482
482
  return record.result || record.messages.filter(message => message.role === 'assistant').map(message => message.content).join('\n');
483
483
  }
484
+ /**
485
+ * 有界结果 transcript:subagent_result 注入主 Agent 上下文时,不再放大完整
486
+ * 消息历史(含所有中间 tool call/result 洪水)。只保留最近若干条非 tool 消息,
487
+ * 按字符上限截断,杜绝上下文回归。完整历史按需走 subagent_read(max_chars)。
488
+ */
489
+ boundedResultTranscript(idOrName) {
490
+ const record = this.get(idOrName);
491
+ if (!record)
492
+ return '';
493
+ const MAX_MSG = 8; // 最近保留的消息条数
494
+ const MAX_CHARS = 8000; // 总字符上限
495
+ const messages = record.messages
496
+ .filter(message => message.role === 'assistant' || message.role === 'user' || message.role === 'system')
497
+ .slice(-MAX_MSG)
498
+ .map(message => `[${message.role}] ${truncateText(String(message.content || ''), 1200)}`);
499
+ let text = messages.join('\n');
500
+ if (text.length > MAX_CHARS) {
501
+ text = text.slice(0, MAX_CHARS) + `\n[...transcript truncated: ${record.messages.length} total messages, ${record.messages.length - MAX_MSG} older omitted; use subagent_read for full history...]`;
502
+ }
503
+ return text || '(no transcript)';
504
+ }
484
505
  listActive() { return this.listAll().filter(item => item.status !== 'closed'); }
485
506
  listAll() { return [...this.subs.values()].map(cloneRecord); }
486
507
  pauseScheduling() {
@@ -14,6 +14,12 @@ export interface ToolPolicyDecision {
14
14
  }
15
15
  export declare const PLAN_COMPUTER_USE_ACTIONS: readonly ["observe", "app_list", "app_observe"];
16
16
  export declare const PLAN_BROWSER_USE_ACTIONS: readonly ["observe", "navigate", "wait", "extract"];
17
+ /** 判断一个工具是否可参与并行调度。缺省 false(独占)。
18
+ * 优先看 toolchain registry 推断的 riskLevel('read' 工具天然并发安全,
19
+ * seedToolchainFromDefinitions 的 inferRiskLevel 自动推断),显式白名单作兜底。
20
+ * 这样后续新增工具只需在 ToolExecutor.definitions() 加 schema,seed 时其
21
+ * riskLevel 被自动推断,'read' 工具自动并发安全——无需再改本文件。 */
22
+ export declare function isConcurrencySafeTool(name: string, riskLevel?: string): boolean;
17
23
  export declare function isReadOnlyScopedToolAction(name: string, action: string): boolean;
18
24
  export declare function toolAvailability(name: string): ToolAvailability;
19
25
  export declare function evaluateToolPolicy(request: ToolPolicyRequest): ToolPolicyDecision;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PLAN_BROWSER_USE_ACTIONS = exports.PLAN_COMPUTER_USE_ACTIONS = void 0;
4
+ exports.isConcurrencySafeTool = isConcurrencySafeTool;
4
5
  exports.isReadOnlyScopedToolAction = isReadOnlyScopedToolAction;
5
6
  exports.toolAvailability = toolAvailability;
6
7
  exports.evaluateToolPolicy = evaluateToolPolicy;
@@ -16,6 +17,11 @@ const MODE_SCOPED_TOOLS = new Set([
16
17
  'build_history_query',
17
18
  'context_compress',
18
19
  'context_history_manage',
20
+ 'compress_tool_result',
21
+ 'background_tool',
22
+ 'read_tool_result',
23
+ 'goal_manage',
24
+ 'conversation_rename',
19
25
  'question',
20
26
  'task',
21
27
  'subagent_list',
@@ -23,6 +29,10 @@ const MODE_SCOPED_TOOLS = new Set([
23
29
  'subagent_send',
24
30
  'subagent_result',
25
31
  'subagent_close',
32
+ 'branch_list',
33
+ 'branch_send',
34
+ 'branch_read',
35
+ 'branch_create',
26
36
  ]);
27
37
  const PLAN_READ_ONLY_TOOLS = new Set([
28
38
  'pwd',
@@ -51,12 +61,50 @@ const PLAN_READ_ONLY_TOOLS = new Set([
51
61
  'subagent_send',
52
62
  'subagent_result',
53
63
  'subagent_close',
64
+ 'branch_list',
65
+ 'branch_read',
54
66
  'question',
55
67
  ]);
56
68
  exports.PLAN_COMPUTER_USE_ACTIONS = ['observe', 'app_list', 'app_observe'];
57
69
  exports.PLAN_BROWSER_USE_ACTIONS = ['observe', 'navigate', 'wait', 'extract'];
58
70
  const PLAN_COMPUTER_USE_ACTION_SET = new Set(exports.PLAN_COMPUTER_USE_ACTIONS);
59
71
  const PLAN_BROWSER_USE_ACTION_SET = new Set(exports.PLAN_BROWSER_USE_ACTIONS);
72
+ /**
73
+ * 并发安全工具集合(DSH isConcurrencySafe 语义的 Newmark 落地)。
74
+ *
75
+ * 只有确定性无副作用的只读工具才允许与兄弟 tool call 并发执行;任何写入、
76
+ * shell 命令、浏览器交互、子代理编排、以及会改变对话/文件/外部状态的工具
77
+ * 都保持独占串行,避免盲目 Promise.all 引发竞态。缺省保守:不在集合中的
78
+ * 工具一律视为独占。
79
+ *
80
+ * 注意:read/grep/glob/pwd 是同一进程内的内存/文件系统只读,可安全重叠;
81
+ * web_search/web_fetch 只读网络,可重叠;git_status/file_audit/repo_security_audit
82
+ * 只读审计,可重叠。其余(含 memory_lab_read 等读接口)因可能触发内部缓存/
83
+ * 重建副作用,默认保守串行。
84
+ */
85
+ const CONCURRENCY_SAFE_TOOLS = new Set([
86
+ 'pwd',
87
+ 'read',
88
+ 'glob',
89
+ 'grep',
90
+ 'web_search',
91
+ 'web_fetch',
92
+ 'git_status',
93
+ 'file_audit',
94
+ 'repo_security_audit',
95
+ ]);
96
+ /** 判断一个工具是否可参与并行调度。缺省 false(独占)。
97
+ * 优先看 toolchain registry 推断的 riskLevel('read' 工具天然并发安全,
98
+ * seedToolchainFromDefinitions 的 inferRiskLevel 自动推断),显式白名单作兜底。
99
+ * 这样后续新增工具只需在 ToolExecutor.definitions() 加 schema,seed 时其
100
+ * riskLevel 被自动推断,'read' 工具自动并发安全——无需再改本文件。 */
101
+ function isConcurrencySafeTool(name, riskLevel) {
102
+ const toolName = String(name || '').trim();
103
+ if (CONCURRENCY_SAFE_TOOLS.has(toolName))
104
+ return true;
105
+ // 从 toolchain registry 派生的 riskLevel:read 工具默认并发安全。
106
+ return riskLevel === 'read';
107
+ }
60
108
  function isReadOnlyScopedToolAction(name, action) {
61
109
  if (name === 'computer_use')
62
110
  return PLAN_COMPUTER_USE_ACTION_SET.has(action);
@@ -97,7 +145,7 @@ function evaluateToolPolicy(request) {
97
145
  }
98
146
  }
99
147
  if (request.isSubagent) {
100
- if (name === 'skill_download' || name === 'question' || name.startsWith('automation_')) {
148
+ if (name === 'skill_download' || name === 'question' || name.startsWith('automation_') || name === 'goal_manage' || name === 'conversation_rename') {
101
149
  return { ...base, allowed: false, reason: `[Subagent sandbox] Tool '${name}' is disabled for peer agents.` };
102
150
  }
103
151
  }
@@ -100,7 +100,7 @@ export interface ChatMessage {
100
100
  export interface AgentWorkEvent {
101
101
  id: string;
102
102
  conversationId: string;
103
- type: 'start' | 'text' | 'response' | 'final_response' | 'tool_call' | 'tool_result' | 'status' | 'done' | 'error' | 'queue_update' | 'guide';
103
+ type: 'start' | 'text' | 'response' | 'final_response' | 'tool_call' | 'tool_result' | 'thought' | 'thought_result' | 'status' | 'done' | 'error' | 'queue_update' | 'guide';
104
104
  content: string;
105
105
  mode: string;
106
106
  model: string;
@@ -1,6 +1,6 @@
1
1
  import { BrowserControlRequest, BrowserControlResult } from './browserControl';
2
2
  import { BrowserUseRequest } from './browserUse';
3
- import { AgentPromptMessage, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationRuntimeState, ConversationStopResult } from './conversationKernel';
3
+ import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationRuntimeState, ConversationStopResult } from './conversationKernel';
4
4
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
5
5
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
6
6
  import type { AutoRouteRatingResult, ConversationSnapshot } from './agent';
@@ -102,6 +102,13 @@ export type UtilityAgentRequest = {
102
102
  params: {
103
103
  target: ConversationRuntimeTarget;
104
104
  };
105
+ } | {
106
+ id: string;
107
+ method: 'context_compress';
108
+ params: {
109
+ target: ConversationRuntimeTarget;
110
+ options?: ConversationContextCompressOptions;
111
+ };
105
112
  } | {
106
113
  id: string;
107
114
  method: 'rate_auto_route';
@@ -23,6 +23,15 @@ export interface WorkspaceManagerOptions {
23
23
  }
24
24
  /** Normalize persisted Windows/WSL aliases and recover paths damaged by cross-host path.resolve calls. */
25
25
  export declare function normalizeHostWorkspacePath(input: string, platform?: NodeJS.Platform): string;
26
+ /**
27
+ * An installed package is never a writable user workspace. Older builds could
28
+ * persist the install directory as an external workspace when the GUI was
29
+ * started from its shortcut working directory; restoring that entry then
30
+ * tried to create `<install>\\conversations` under Program Files. Keep this
31
+ * policy in the workspace layer so GUI, TUI, CLI, and detached runtimes all
32
+ * recover the same way.
33
+ */
34
+ export declare function isProtectedInstallWorkspacePath(candidate: string): boolean;
26
35
  export declare class WorkspaceManager {
27
36
  rootPath: string;
28
37
  private config;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.WorkspaceManager = void 0;
37
37
  exports.normalizeHostWorkspacePath = normalizeHostWorkspacePath;
38
+ exports.isProtectedInstallWorkspacePath = isProtectedInstallWorkspacePath;
38
39
  const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
40
41
  const crypto = __importStar(require("crypto"));
@@ -64,6 +65,34 @@ function normalizeHostWorkspacePath(input, platform = process.platform) {
64
65
  }
65
66
  return path.posix.resolve(raw || '.');
66
67
  }
68
+ function isPathInside(parent, child) {
69
+ try {
70
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
71
+ return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * An installed package is never a writable user workspace. Older builds could
79
+ * persist the install directory as an external workspace when the GUI was
80
+ * started from its shortcut working directory; restoring that entry then
81
+ * tried to create `<install>\\conversations` under Program Files. Keep this
82
+ * policy in the workspace layer so GUI, TUI, CLI, and detached runtimes all
83
+ * recover the same way.
84
+ */
85
+ function isProtectedInstallWorkspacePath(candidate) {
86
+ const value = String(candidate || '').trim();
87
+ if (!value)
88
+ return false;
89
+ const roots = [path.dirname(process.execPath)];
90
+ if (process.platform === 'win32') {
91
+ roots.push(process.env.ProgramFiles || '', process.env['ProgramFiles(x86)'] || '', process.env.ProgramW6432 || '');
92
+ }
93
+ const resolved = path.resolve(value);
94
+ return roots.filter(Boolean).some(root => isPathInside(root, resolved));
95
+ }
67
96
  class WorkspaceManager {
68
97
  rootPath;
69
98
  config;
@@ -135,9 +164,18 @@ class WorkspaceManager {
135
164
  catch { /* empty */ }
136
165
  try {
137
166
  const ext = JSON.parse(fs.readFileSync(path.join(w, 'External.json'), 'utf-8'));
138
- this.external = Array.isArray(ext)
167
+ const normalized = Array.isArray(ext)
139
168
  ? ext.map(item => this.normalizeExternalWorkspace(item, changed => { externalChanged = externalChanged || changed; }))
140
169
  : [];
170
+ this.external = normalized.filter(workspace => {
171
+ if (!isProtectedInstallWorkspacePath(workspace.path))
172
+ return true;
173
+ // Never restore or persist the installation directory as a user
174
+ // workspace. It is a migration boundary for registries written by
175
+ // older versions and by package-level pressure tests.
176
+ externalChanged = true;
177
+ return false;
178
+ });
141
179
  }
142
180
  catch { /* empty */ }
143
181
  // Scan for directories not in Local.json
@@ -344,6 +382,15 @@ class WorkspaceManager {
344
382
  }
345
383
  restoreCurrent() {
346
384
  const stateCurrent = this.readState().current || null;
385
+ if (stateCurrent?.path && isProtectedInstallWorkspacePath(stateCurrent.path)) {
386
+ // The previous current workspace may have been filtered from
387
+ // External.json above. Clear the pointer instead of falling back to an
388
+ // arbitrary external directory; Agent can create/reuse a user-root
389
+ // internal workspace according to its normal configuration.
390
+ this.current = null;
391
+ this.saveState();
392
+ return;
393
+ }
347
394
  const stored = this.findWorkspace(stateCurrent);
348
395
  if (stored) {
349
396
  this.current = stored;
@@ -72,6 +72,10 @@ export declare class WslAgentClient {
72
72
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<WslConversationRewindResult>;
73
73
  enqueueGuide(target: ConversationRuntimeTarget, envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
74
74
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
75
+ contextCompress(target: ConversationRuntimeTarget, options?: {
76
+ keepRecent?: number;
77
+ force?: boolean;
78
+ }): Promise<Record<string, unknown>>;
75
79
  rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
76
80
  setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
77
81
  setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
@@ -317,6 +317,10 @@ class WslAgentClient {
317
317
  await this.start();
318
318
  return await this.request('checkpoint', { target: await this.mapTarget(target) }, 5_000);
319
319
  }
320
+ async contextCompress(target, options = {}) {
321
+ await this.start();
322
+ return await this.request('context_compress', { target: await this.mapTarget(target), options }, 120_000);
323
+ }
320
324
  async rateAutoRoute(target, score, routeId = '') {
321
325
  await this.start();
322
326
  return await this.request('rate_auto_route', {
@@ -1,5 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
- import { AgentPromptMessage, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
2
+ import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
3
3
  import { ConversationRuntimeTarget } from './conversationTarget';
4
4
  import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
5
5
  import { BrowserUseRequest } from './browserUse';
@@ -114,6 +114,13 @@ export type WslAgentRequest = {
114
114
  params: {
115
115
  target: ConversationRuntimeTarget;
116
116
  };
117
+ } | {
118
+ id: string;
119
+ method: 'context_compress';
120
+ params: {
121
+ target: ConversationRuntimeTarget;
122
+ options?: ConversationContextCompressOptions;
123
+ };
117
124
  } | {
118
125
  id: string;
119
126
  method: 'rate_auto_route';
@@ -11,6 +11,10 @@ export interface WslTargetRuntimeClient {
11
11
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslAgentStopResult>;
12
12
  enqueueGuide(target: ConversationRuntimeTarget, envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
13
13
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
14
+ contextCompress?(target: ConversationRuntimeTarget, options?: {
15
+ keepRecent?: number;
16
+ force?: boolean;
17
+ }): Promise<Record<string, unknown>>;
14
18
  rateAutoRoute?(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
15
19
  setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
16
20
  setMode?(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
@@ -69,6 +73,10 @@ export declare class WslAgentRuntimePool {
69
73
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslPoolStopResult>;
70
74
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
71
75
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
76
+ contextCompress(target: ConversationRuntimeTarget, options?: {
77
+ keepRecent?: number;
78
+ force?: boolean;
79
+ }): Promise<Record<string, unknown>>;
72
80
  rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
73
81
  setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
74
82
  setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
@@ -214,6 +214,21 @@ class WslAgentRuntimePool {
214
214
  this.release(entry, true);
215
215
  }
216
216
  }
217
+ async contextCompress(target, options = {}) {
218
+ const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
219
+ const entry = await this.acquire(normalized);
220
+ try {
221
+ if (entry.stopIntent)
222
+ return { ok: false, error: 'Context compression is unavailable while this conversation is stopping.' };
223
+ if (!entry.client.contextCompress)
224
+ return { ok: false, error: 'Context compression is unavailable in this runtime.' };
225
+ entry.lastSnapshot = null;
226
+ return await entry.client.contextCompress(normalized, options);
227
+ }
228
+ finally {
229
+ this.release(entry, true);
230
+ }
231
+ }
217
232
  async rateAutoRoute(target, score, routeId = '') {
218
233
  const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
219
234
  const entry = await this.acquireExisting(normalized);
package/dist/launcher.js CHANGED
@@ -49,6 +49,7 @@ const cli_help_1 = require("./cli-help");
49
49
  const rawArgs = process.argv.slice(2);
50
50
  const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
51
51
  const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
52
+ const cliCommand = args.find(a => cli_commands_1.CLI_COMMANDS.includes(a));
52
53
  const isEdit = args[0] === 'edit';
53
54
  const editFile = isEdit ? args[1] : '';
54
55
  const isFlow = args[0] === 'flow';
@@ -64,6 +65,13 @@ if (invalidArgument) {
64
65
  console.error(`Invalid Newmark argument: ${invalidArgument}`);
65
66
  process.exit(2);
66
67
  }
68
+ // Command help is a terminating, read-only discovery operation. Resolve it
69
+ // before first-run initialization or GUI forwarding so `Newmark.exe send
70
+ // --help` cannot accidentally enter Electron or instantiate an Agent.
71
+ if (hasCliCommand && cliCommand && (0, cli_commands_1.cliHelpRequested)(args)) {
72
+ console.log((0, cli_commands_1.cliCommandHelp)(cliCommand));
73
+ process.exit(0);
74
+ }
67
75
  function pathArgValue(values, key) {
68
76
  const prefix = `${key}=`;
69
77
  const inlineIdx = values.findIndex(a => a.startsWith(prefix));
@@ -97,7 +97,7 @@ export declare class LLMProvider {
97
97
  private toTransportResponse;
98
98
  private toNormalizedMessages;
99
99
  private toNormalizedTools;
100
- chatStreamWithTools(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, tools: unknown[], signal?: AbortSignal, reasoningTier?: string): AsyncGenerator<StreamToken>;
100
+ chatStreamWithTools(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, tools: unknown[], signal?: AbortSignal, reasoningTier?: string, sessionId?: string): AsyncGenerator<StreamToken>;
101
101
  /**
102
102
  * GitHub Models streaming path. Preserved as a dedicated implementation
103
103
  * because the provider adapters (V2) do not serialize the GitHub Models
@@ -843,7 +843,7 @@ class LLMProvider {
843
843
  * The emitted request body and StreamToken stream are byte-equivalent to
844
844
  * the legacy inlined path.
845
845
  */
846
- async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
846
+ async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
847
847
  const mode = this.openAITransportMode();
848
848
  if (mode === 'responses') {
849
849
  yield* this.adapterResponsesBridge(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
@@ -861,6 +861,7 @@ class LLMProvider {
861
861
  maxOutputTokens: maxTokens,
862
862
  apiKey: this.apiKey,
863
863
  baseUrl: this.cleanBaseUrl(),
864
+ ...(sessionId ? { sessionId } : {}),
864
865
  };
865
866
  const serialized = await adapter.serializeRequest(request);
866
867
  serialized.body.stream = mode === 'chat' ? false : true;
@@ -1080,7 +1081,7 @@ class LLMProvider {
1080
1081
  };
1081
1082
  });
1082
1083
  }
1083
- async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
1084
+ async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
1084
1085
  if (signal?.aborted)
1085
1086
  throw abortFailure(signal);
1086
1087
  if (this.protocol() === 'anthropic') {
@@ -1092,7 +1093,7 @@ class LLMProvider {
1092
1093
  return;
1093
1094
  }
1094
1095
  if (this.useProviderAdaptersV2) {
1095
- yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
1096
+ yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId);
1096
1097
  return;
1097
1098
  }
1098
1099
  throw new Error('LLMProvider legacy OpenAI streaming was removed in dev-0.3.0: enable provider_adapters_v2 (useProviderAdaptersV2).');