newmark-agent 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/config.example.json +6 -0
  2. package/dist/cli-commands.d.ts +7 -0
  3. package/dist/cli-commands.js +206 -15
  4. package/dist/cli-discovery.d.ts +15 -0
  5. package/dist/cli-discovery.js +182 -0
  6. package/dist/cli-help.d.ts +4 -0
  7. package/dist/cli-help.js +46 -0
  8. package/dist/conversation-utility-host.bundle.cjs +548 -137
  9. package/dist/core/agent.d.ts +18 -3
  10. package/dist/core/agent.js +236 -34
  11. package/dist/core/agentKernelRunner.js +72 -10
  12. package/dist/core/browserControl.d.ts +8 -0
  13. package/dist/core/browserUsePageAdapter.d.ts +3 -0
  14. package/dist/core/browserUsePageAdapter.js +19 -2
  15. package/dist/core/computerUseSession.d.ts +44 -0
  16. package/dist/core/computerUseSession.js +105 -0
  17. package/dist/core/config.d.ts +7 -2
  18. package/dist/core/config.js +24 -6
  19. package/dist/core/conversationKernel.d.ts +1 -0
  20. package/dist/core/conversationKernel.js +39 -1
  21. package/dist/core/electronBrowserUseHost.js +7 -0
  22. package/dist/core/electronUtilityAgentClient.js +84 -2
  23. package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
  24. package/dist/core/electronUtilityRuntimePool.js +62 -0
  25. package/dist/core/flow-runner.js +1 -1
  26. package/dist/core/modelValidationStore.d.ts +4 -1
  27. package/dist/core/modelValidationStore.js +7 -1
  28. package/dist/core/utilityHostToolRouter.d.ts +7 -0
  29. package/dist/core/utilityHostToolRouter.js +25 -33
  30. package/dist/core/workspace.d.ts +6 -0
  31. package/dist/core/workspace.js +14 -0
  32. package/dist/core/wslAgentRuntimePool.d.ts +4 -0
  33. package/dist/core/wslAgentRuntimePool.js +56 -0
  34. package/dist/launcher.js +51 -10
  35. package/dist/llm/provider.d.ts +8 -5
  36. package/dist/llm/provider.js +85 -33
  37. package/dist/main.js +297 -61
  38. package/dist/preload.js +10 -2
  39. package/dist/providers/chat-completions.adapter.js +1 -5
  40. package/dist/providers/provider-events.d.ts +7 -0
  41. package/dist/providers/provider-events.js +44 -0
  42. package/dist/providers/responses.adapter.js +1 -3
  43. package/dist/tools/index.js +36 -49
  44. package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
  45. package/dist/tui/src/app.js +47 -13
  46. package/dist/tui/src/render.js +23 -7
  47. package/dist/tui/src/state.js +61 -9
  48. package/dist/ui/index.html +545 -122
  49. package/dist/wsl-agent-host.bundle.cjs +548 -137
  50. package/package.json +14 -5
@@ -16,6 +16,7 @@ class ElectronUtilityRuntimePool {
16
16
  restarting = new Set();
17
17
  quarantined = new Map();
18
18
  disposing = new Set();
19
+ forceStopPromises = new Map();
19
20
  capacityTail = Promise.resolve();
20
21
  accessSequence = 0;
21
22
  constructor(root, hostScript, createClient = target => new electronUtilityAgentClient_1.ElectronUtilityAgentClient(root, hostScript, target), options = {}) {
@@ -342,6 +343,67 @@ class ElectronUtilityRuntimePool {
342
343
  if (entry)
343
344
  await this.stopEntry(entry);
344
345
  }
346
+ /**
347
+ * Immediately terminate a target runtime for destructive lifecycle actions.
348
+ * Archive is deliberately stronger than the user-facing two-click Stop
349
+ * contract: the target may be running, already stopping, or holding an
350
+ * active prompt lease. Give the kernel only the short checkpoint window
351
+ * owned by forceTerminateEntry, then hard-stop and evict the client so no
352
+ * delayed runtime event can keep the archived target resident.
353
+ */
354
+ async forceStopTarget(target) {
355
+ const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
356
+ const existing = this.forceStopPromises.get(normalized.runtimeKey);
357
+ if (existing)
358
+ return existing;
359
+ const operation = this.forceStopTargetInternal(normalized);
360
+ this.forceStopPromises.set(normalized.runtimeKey, operation);
361
+ try {
362
+ await operation;
363
+ }
364
+ finally {
365
+ if (this.forceStopPromises.get(normalized.runtimeKey) === operation) {
366
+ this.forceStopPromises.delete(normalized.runtimeKey);
367
+ }
368
+ }
369
+ }
370
+ async forceStopTargetInternal(target) {
371
+ let entry;
372
+ await this.serializeCapacity(async () => {
373
+ entry = this.entries.get(target.runtimeKey);
374
+ if (entry)
375
+ this.disposing.add(target.runtimeKey);
376
+ });
377
+ if (!entry)
378
+ return;
379
+ const intent = entry.stopIntent || {
380
+ runId: entry.lastRunId,
381
+ generation: entry.lastGeneration,
382
+ checkpointed: false,
383
+ forcePromise: null,
384
+ };
385
+ if (!entry.stopIntent)
386
+ entry.stopIntent = intent;
387
+ try {
388
+ await this.forceTerminateEntry(entry, intent);
389
+ // forceStop is expected to disconnect the utility child. If a client
390
+ // reports a stale connected flag, retry the hard boundary once before
391
+ // declaring archive unsafe.
392
+ if (entry.client.status().connected)
393
+ await entry.client.forceStop();
394
+ if (entry.client.status().connected) {
395
+ throw new Error(`Electron utility runtime ${target.runtimeKey} remained connected after force stop`);
396
+ }
397
+ if (this.entries.get(target.runtimeKey) === entry) {
398
+ this.entries.delete(target.runtimeKey);
399
+ entry.unsubscribe();
400
+ entry.client.setHostToolHandler(null);
401
+ }
402
+ }
403
+ finally {
404
+ this.disposing.delete(target.runtimeKey);
405
+ }
406
+ }
345
407
  async stopAll() {
346
408
  const targets = Array.from(this.entries.values(), entry => entry.target);
347
409
  const results = await Promise.allSettled(targets.map(target => this.stopTarget(target)));
@@ -126,7 +126,7 @@ async function runFlowBuild(agent, prompt, options) {
126
126
  if (!options.signal?.aborted && typeof agent.emitWorkEvent === 'function') {
127
127
  agent.emitWorkEvent({ type: 'error', content: reportedError.message, runId });
128
128
  }
129
- agent.finishConversationWorkRun(runId, options.signal?.aborted ? 'interrupted' : 'error');
129
+ agent.finishConversationWorkRun(runId, options.signal?.aborted ? 'interrupted' : 'error', undefined, options.signal?.aborted ? '' : reportedError.message);
130
130
  agent.flushWorkspaceConversationState();
131
131
  }
132
132
  throw reportedError;
@@ -2,8 +2,11 @@ import { ModelValidationCache, ModelValidationRecord } from './modelValidation';
2
2
  /** Durable validation evidence. The file contains probe metadata only, never prompts, tool arguments or credentials. */
3
3
  export declare class FileModelValidationCache implements ModelValidationCache {
4
4
  private readonly filePath;
5
+ private readonly readOnly;
5
6
  private readonly records;
6
- constructor(rootPath: string);
7
+ constructor(rootPath: string, options?: {
8
+ readOnly?: boolean;
9
+ });
7
10
  get(modelKey: string): ModelValidationRecord | undefined;
8
11
  set(record: ModelValidationRecord): void;
9
12
  delete(modelKey: string): void;
@@ -42,9 +42,11 @@ function key(model) {
42
42
  /** Durable validation evidence. The file contains probe metadata only, never prompts, tool arguments or credentials. */
43
43
  class FileModelValidationCache {
44
44
  filePath;
45
+ readOnly;
45
46
  records = new Map();
46
- constructor(rootPath) {
47
+ constructor(rootPath, options = {}) {
47
48
  this.filePath = path.join(rootPath, 'model-validation', 'records.json');
49
+ this.readOnly = options.readOnly === true;
48
50
  this.load();
49
51
  }
50
52
  get(modelKey) {
@@ -53,11 +55,15 @@ class FileModelValidationCache {
53
55
  }
54
56
  set(record) {
55
57
  this.records.set(record.modelKey || key(record.model), JSON.parse(JSON.stringify(record)));
58
+ if (this.readOnly)
59
+ return;
56
60
  this.save();
57
61
  }
58
62
  delete(modelKey) {
59
63
  if (!this.records.delete(modelKey))
60
64
  return;
65
+ if (this.readOnly)
66
+ return;
61
67
  this.save();
62
68
  }
63
69
  load() {
@@ -2,6 +2,7 @@ import { BrowserControl } from './browserControl';
2
2
  import { BrowserUseReceipt, BrowserUseRequest } from './browserUse';
3
3
  import { UtilityHostToolRequest } from './utilityAgentProtocol';
4
4
  import { runComputerUse } from '../tools/computerUse';
5
+ import { ComputerUseSessionState } from './computerUseSession';
5
6
  import { runTerminalTakeover } from '../tools/terminalTakeover';
6
7
  export interface UtilityHostToolRouterOptions {
7
8
  persistenceRoot: string;
@@ -15,6 +16,12 @@ export interface UtilityHostToolRouterOptions {
15
16
  }
16
17
  export type RoutedUtilityHostToolHandler = ((request: UtilityHostToolRequest, signal?: AbortSignal) => Promise<unknown>) & {
17
18
  cancelTarget(runtimeKey: string): void;
19
+ computerUseState(runtimeKey: string): ComputerUseSessionState;
20
+ setComputerUseEnabled(runtimeKey: string, enabled: boolean, ownerLabel?: string): {
21
+ ok: boolean;
22
+ state: ComputerUseSessionState;
23
+ error?: string;
24
+ };
18
25
  };
19
26
  /**
20
27
  * Routes every desktop-global capability in the Electron main process.
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const browserUse_1 = require("./browserUse");
40
40
  const toolPolicy_1 = require("./toolPolicy");
41
41
  const computerUse_1 = require("../tools/computerUse");
42
+ const computerUseSession_1 = require("./computerUseSession");
42
43
  const terminalTakeover_1 = require("../tools/terminalTakeover");
43
44
  const ROOT_AGENT_ACTOR_ID = '00000000-0000-4000-8000-000000000001';
44
45
  const AUTOMATION_TOOLS = new Set([
@@ -54,10 +55,8 @@ const AUTOMATION_TOOLS = new Set([
54
55
  * one authoritative owner lock rather than one lock per child process.
55
56
  */
56
57
  function createUtilityHostToolHandler(options) {
57
- let computerUseLease = null;
58
58
  const terminalOwners = new Map();
59
59
  const ephemeralScreenshots = new Map();
60
- const lockTtlMs = 10 * 60 * 1000;
61
60
  const handler = async (request, signal) => {
62
61
  throwIfAborted(signal);
63
62
  validateTargetContext(request);
@@ -73,7 +72,14 @@ function createUtilityHostToolHandler(options) {
73
72
  if (request.tool === 'browser_control') {
74
73
  if (request.args.action === 'use')
75
74
  throw new Error('Isolated Browser-Use must use the target-bound browser_use host RPC');
76
- const result = await (options.runBrowser || browserControl_1.BrowserControl.run.bind(browserControl_1.BrowserControl))(request.args, signal);
75
+ const result = await (options.runBrowser || browserControl_1.BrowserControl.run.bind(browserControl_1.BrowserControl))({
76
+ ...request.args,
77
+ target: {
78
+ workspaceId: request.target.workspaceId,
79
+ conversationId: request.target.conversationId,
80
+ runtimeKey: request.target.runtimeKey,
81
+ },
82
+ }, signal);
77
83
  throwIfAborted(signal);
78
84
  return result;
79
85
  }
@@ -124,20 +130,14 @@ function createUtilityHostToolHandler(options) {
124
130
  const action = String(args.action || '').trim().toLowerCase();
125
131
  const trustedComputerUseContext = request.context;
126
132
  const owner = `${request.target.runtimeKey}:${String(request.context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID)}`;
127
- const now = Date.now();
128
- if (computerUseLease && now - computerUseLease.updatedAt > lockTtlMs)
129
- computerUseLease = null;
130
- if (action === 'takeover_stop') {
131
- if (computerUseLease && computerUseLease.owner !== owner) {
132
- return computerUseLockError(action, owner, computerUseLease.owner);
133
- }
134
- }
135
- else if (computerUseLease && computerUseLease.owner !== owner) {
136
- return computerUseLockError(action, owner, computerUseLease.owner);
137
- }
138
- else {
139
- computerUseLease = { owner, runtimeKey: request.target.runtimeKey, workspacePath: request.target.workspacePath, updatedAt: now };
140
- }
133
+ const sessionScope = {
134
+ runtimeKey: request.target.runtimeKey,
135
+ ownerLabel: owner,
136
+ workspacePath: request.target.workspacePath,
137
+ };
138
+ const lockGuard = computerUseSession_1.defaultComputerUseSessionRegistry.authorize(action, sessionScope, args.dry_run === true || args.dryRun === true);
139
+ if (lockGuard)
140
+ return lockGuard;
141
141
  let retainedScreenshotPath = '';
142
142
  try {
143
143
  const result = await (options.runComputer || computerUse_1.runComputerUse)({
@@ -214,10 +214,7 @@ function createUtilityHostToolHandler(options) {
214
214
  }
215
215
  catch { }
216
216
  }
217
- if (action === 'takeover_stop' && (!computerUseLease || computerUseLease.owner === owner))
218
- computerUseLease = null;
219
- else if (computerUseLease?.owner === owner)
220
- computerUseLease.updatedAt = Date.now();
217
+ computerUseSession_1.defaultComputerUseSessionRegistry.complete(action, sessionScope);
221
218
  }
222
219
  };
223
220
  handler.cancelTarget = (runtimeKey) => {
@@ -238,9 +235,9 @@ function createUtilityHostToolHandler(options) {
238
235
  (0, terminalTakeover_1.stopTerminalTakeoverSession)(session.id, terminalOwner, 'runtime-force-restart');
239
236
  }
240
237
  }
241
- if (computerUseLease?.runtimeKey === runtimeKey) {
242
- const lease = computerUseLease;
243
- computerUseLease = null;
238
+ const hadComputerUseLease = computerUseSession_1.defaultComputerUseSessionRegistry.cancelTarget(runtimeKey);
239
+ if (hadComputerUseLease) {
240
+ const lease = { workspacePath: options.persistenceRoot, owner: runtimeKey };
244
241
  void (options.runComputer || computerUse_1.runComputerUse)({
245
242
  action: 'takeover_stop',
246
243
  workspacePath: lease.workspacePath,
@@ -249,6 +246,10 @@ function createUtilityHostToolHandler(options) {
249
246
  }).catch(() => undefined);
250
247
  }
251
248
  };
249
+ handler.computerUseState = (runtimeKey) => computerUseSession_1.defaultComputerUseSessionRegistry.state(runtimeKey);
250
+ handler.setComputerUseEnabled = (runtimeKey, enabled, ownerLabel = `conversation:${runtimeKey}`) => {
251
+ return computerUseSession_1.defaultComputerUseSessionRegistry.setEnabled({ runtimeKey, ownerLabel, workspacePath: options.persistenceRoot }, enabled);
252
+ };
252
253
  return handler;
253
254
  }
254
255
  function throwIfAborted(signal) {
@@ -307,13 +308,4 @@ function evaluateUtilityHostToolPolicy(request) {
307
308
  args,
308
309
  });
309
310
  }
310
- function computerUseLockError(action, requestedOwner, activeOwner) {
311
- return JSON.stringify({
312
- ok: false,
313
- action,
314
- error: `Computer Use is already active in ${activeOwner}. Stop it with computer_use takeover_stop or wait before another conversation takes control.`,
315
- lock_owner: activeOwner,
316
- requested_owner: requestedOwner,
317
- }, null, 2);
318
- }
319
311
  //# sourceMappingURL=utilityHostToolRouter.js.map
@@ -52,6 +52,12 @@ export declare class WorkspaceManager {
52
52
  private saveState;
53
53
  private findWorkspace;
54
54
  private restoreCurrent;
55
+ /**
56
+ * Re-read the registry and persisted current-workspace pointer after another
57
+ * Newmark entrypoint updates Work/*.json. This intentionally does not create
58
+ * a workspace: a refresh must reflect the shared on-disk state exactly.
59
+ */
60
+ reloadFromStorage(): WorkspaceInfo | null;
55
61
  private saveInternal;
56
62
  private saveExternal;
57
63
  private sleepSync;
@@ -358,6 +358,20 @@ class WorkspaceManager {
358
358
  this.saveState();
359
359
  }
360
360
  }
361
+ /**
362
+ * Re-read the registry and persisted current-workspace pointer after another
363
+ * Newmark entrypoint updates Work/*.json. This intentionally does not create
364
+ * a workspace: a refresh must reflect the shared on-disk state exactly.
365
+ */
366
+ reloadFromStorage() {
367
+ if (this.detached)
368
+ return this.current;
369
+ this.scan();
370
+ this.validate();
371
+ this.current = null;
372
+ this.restoreCurrent();
373
+ return this.current;
374
+ }
361
375
  saveInternal() {
362
376
  if (this.detached)
363
377
  return;
@@ -56,6 +56,7 @@ export declare class WslAgentRuntimePool {
56
56
  private hostToolHandler;
57
57
  private restarting;
58
58
  private disposing;
59
+ private forceStopPromises;
59
60
  private capacityTail;
60
61
  private accessSequence;
61
62
  constructor(distro: string, windowsRoot: string, windowsHostScript: string, createClient?: WslTargetRuntimeClientFactory, options?: WslAgentRuntimePoolOptions);
@@ -90,6 +91,9 @@ export declare class WslAgentRuntimePool {
90
91
  hasActiveWorkspace(target: ConversationRuntimeTarget): Promise<boolean>;
91
92
  stopWorkspace(target: ConversationRuntimeTarget): Promise<void>;
92
93
  stopTarget(target: ConversationRuntimeTarget): Promise<void>;
94
+ /** Hard-stop a target for destructive lifecycle actions such as archive. */
95
+ forceStopTarget(target: ConversationRuntimeTarget): Promise<void>;
96
+ private forceStopTargetInternal;
93
97
  stopAll(): Promise<void>;
94
98
  private throwStopFailures;
95
99
  private stopEntry;
@@ -20,6 +20,7 @@ class WslAgentRuntimePool {
20
20
  hostToolHandler = null;
21
21
  restarting = new Set();
22
22
  disposing = new Set();
23
+ forceStopPromises = new Map();
23
24
  capacityTail = Promise.resolve();
24
25
  accessSequence = 0;
25
26
  constructor(distro, windowsRoot, windowsHostScript, createClient = (target) => new wslAgentClient_1.WslAgentClient(distro, windowsRoot, windowsHostScript, target), options = {}) {
@@ -356,6 +357,61 @@ class WslAgentRuntimePool {
356
357
  if (entry)
357
358
  await this.stopEntry(entry);
358
359
  }
360
+ /** Hard-stop a target for destructive lifecycle actions such as archive. */
361
+ async forceStopTarget(target) {
362
+ const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
363
+ const existing = this.forceStopPromises.get(normalized.runtimeKey);
364
+ if (existing)
365
+ return existing;
366
+ const operation = this.forceStopTargetInternal(normalized);
367
+ this.forceStopPromises.set(normalized.runtimeKey, operation);
368
+ try {
369
+ await operation;
370
+ }
371
+ finally {
372
+ if (this.forceStopPromises.get(normalized.runtimeKey) === operation) {
373
+ this.forceStopPromises.delete(normalized.runtimeKey);
374
+ }
375
+ }
376
+ }
377
+ async forceStopTargetInternal(target) {
378
+ let entry;
379
+ await this.serializeCapacity(async () => {
380
+ entry = this.entries.get(target.runtimeKey);
381
+ if (entry)
382
+ this.disposing.add(target.runtimeKey);
383
+ });
384
+ if (!entry)
385
+ return;
386
+ const intent = entry.stopIntent || {
387
+ runId: entry.lastRunId,
388
+ generation: entry.lastGeneration,
389
+ checkpointed: false,
390
+ forcePromise: null,
391
+ };
392
+ if (!entry.stopIntent)
393
+ entry.stopIntent = intent;
394
+ try {
395
+ await this.forceTerminateEntry(entry, intent);
396
+ if (entry.client.status().connected) {
397
+ if (entry.client.forceStopRuntimeGroup)
398
+ await entry.client.forceStopRuntimeGroup();
399
+ else
400
+ await entry.client.forceRestartRuntimeGroup();
401
+ }
402
+ if (entry.client.status().connected) {
403
+ throw new Error(`WSL runtime ${target.runtimeKey} remained connected after force stop`);
404
+ }
405
+ if (this.entries.get(target.runtimeKey) === entry) {
406
+ this.entries.delete(target.runtimeKey);
407
+ entry.unsubscribe();
408
+ entry.client.setHostToolHandler(null);
409
+ }
410
+ }
411
+ finally {
412
+ this.disposing.delete(target.runtimeKey);
413
+ }
414
+ }
359
415
  async stopAll() {
360
416
  const targets = Array.from(this.entries.values(), entry => entry.target);
361
417
  const results = await Promise.allSettled(targets.map(target => this.stopTarget(target)));
package/dist/launcher.js CHANGED
@@ -43,15 +43,27 @@ const agent_1 = require("./core/agent");
43
43
  const flow_1 = require("./core/flow");
44
44
  const flow_runner_1 = require("./core/flow-runner");
45
45
  const cli_commands_1 = require("./cli-commands");
46
- const args = process.argv.slice(2);
46
+ const cli_discovery_1 = require("./cli-discovery");
47
+ const installUpdate_1 = require("./core/installUpdate");
48
+ const cli_help_1 = require("./cli-help");
49
+ const rawArgs = process.argv.slice(2);
50
+ const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
51
+ const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
52
+ const isEdit = args[0] === 'edit';
53
+ const editFile = isEdit ? args[1] : '';
54
+ const isFlow = args[0] === 'flow';
55
+ const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
56
+ const isVersionArg = !hasCliCommand && (0, cli_discovery_1.isVersionArgument)(args);
57
+ const isReadOnlyValidation = hasCliCommand && args.includes('validate-models') && !args.includes('--persist');
47
58
  const isTui = args.some(arg => arg.toLowerCase() === '--tui');
48
59
  const isGui = args.some(arg => arg.toLowerCase() === '--gui');
49
60
  const isCli = args.includes('--cli');
50
61
  const isServer = args.includes('--server');
51
- const isEdit = args[0] === 'edit';
52
- const editFile = isEdit ? args[1] : '';
53
- const isFlow = args[0] === 'flow';
54
- const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
62
+ const invalidArgument = (0, cli_discovery_1.invalidTopLevelArgument)(args);
63
+ if (invalidArgument) {
64
+ console.error(`Invalid Newmark argument: ${invalidArgument}`);
65
+ process.exit(2);
66
+ }
55
67
  function pathArgValue(values, key) {
56
68
  const prefix = `${key}=`;
57
69
  const inlineIdx = values.findIndex(a => a.startsWith(prefix));
@@ -85,6 +97,15 @@ function pathArgValue(values, key) {
85
97
  }
86
98
  return best || parts.join(' ') || undefined;
87
99
  }
100
+ function resolveTuiWorkspacePath(values, root) {
101
+ const explicitWorkspace = pathArgValue(values, '--workspace');
102
+ if (explicitWorkspace)
103
+ return explicitWorkspace;
104
+ // An explicitly isolated runtime must not silently register the caller's
105
+ // cwd as an external workspace. Keep the opt-in --workspace escape hatch,
106
+ // while making the safe one-argument form fully self-contained.
107
+ return pathArgValue(values, '--root') ? root : process.cwd();
108
+ }
88
109
  function userRuntimeRoot() {
89
110
  return path.join(os.homedir(), '.Newmark');
90
111
  }
@@ -169,11 +190,31 @@ function writableRuntimeRoot(candidate) {
169
190
  }
170
191
  const explicitRoot = pathArgValue(args, '--root');
171
192
  const root = explicitRoot ? writableRuntimeRoot(explicitRoot) : userRuntimeRoot();
172
- function firstRunInit(r) {
193
+ if (isHelpArg) {
194
+ console.log(isFlow ? (0, cli_help_1.newmarkFlowHelpText)() : isEdit ? (0, cli_help_1.newmarkEditHelpText)() : (0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
195
+ process.exit(0);
196
+ }
197
+ if (isVersionArg) {
198
+ console.log((0, installUpdate_1.currentAppVersion)());
199
+ process.exit(0);
200
+ }
201
+ const unknownCommand = !hasCliCommand && !isTui && !isGui && !isCli && !isServer && !isFlow && !isEdit
202
+ ? (0, cli_discovery_1.unknownTopLevelCommand)(args)
203
+ : undefined;
204
+ if (unknownCommand) {
205
+ console.error(`Unknown Newmark command or argument: ${unknownCommand}. Run --help to see the supported entrypoints.`);
206
+ process.exit(2);
207
+ }
208
+ function firstRunInit(r, options = {}) {
173
209
  fs.mkdirSync(r, { recursive: true });
174
- migrateLegacyRuntimeRoot(r);
210
+ // Legacy AppData migration is only valid for the canonical user runtime.
211
+ // An explicit --root is an isolation boundary (tests, portable roots, and
212
+ // caller-selected workspaces) and must never inherit the user's sessions,
213
+ // archives, providers, or workspaces.
214
+ if (path.resolve(r) === path.resolve(userRuntimeRoot()))
215
+ migrateLegacyRuntimeRoot(r);
175
216
  const { ensureRootConfig } = require('./core/config');
176
- ensureRootConfig(r);
217
+ ensureRootConfig(r, options);
177
218
  if (!fs.existsSync(path.join(r, 'agent.md'))) {
178
219
  fs.writeFileSync(path.join(r, 'agent.md'), '# Newmark Agent\n\nYou are a powerful coding assistant.\n', 'utf-8');
179
220
  }
@@ -258,13 +299,13 @@ function launchGui() {
258
299
  console.error('Unable to locate the Newmark GUI runtime. Reinstall newmark-agent with optional dependencies enabled, or install a Newmark desktop package.');
259
300
  process.exit(1);
260
301
  }
261
- firstRunInit(root);
302
+ firstRunInit(root, { readOnly: isReadOnlyValidation });
262
303
  if (isGui) {
263
304
  launchGui();
264
305
  }
265
306
  else if (isTui) {
266
307
  const { start } = require('./tui/src/app');
267
- start({ root, workspacePath: process.cwd(), desktopDist: __dirname });
308
+ start({ root, workspacePath: resolveTuiWorkspacePath(args, root), desktopDist: __dirname });
268
309
  }
269
310
  else if (hasCliCommand) {
270
311
  (0, cli_commands_1.runCliCommand)(root, args).then(handled => {
@@ -28,9 +28,12 @@ export declare class LLMProvider {
28
28
  explicitProtocol?: ProviderProtocol | undefined;
29
29
  openAIMode: OpenAITransportMode | boolean;
30
30
  useProviderAdaptersV2: boolean;
31
+ requestTimeoutMs: number;
31
32
  static nodeHttpTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
32
33
  static powershellTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
33
- constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean);
34
+ constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean, requestTimeoutMs?: number);
35
+ private effectiveRequestTimeout;
36
+ private withRequestTimeout;
34
37
  intelligenceConfig(tier: string): IntelligenceConfig;
35
38
  private reasoningEffort;
36
39
  private applyChatReasoningEffort;
@@ -85,10 +88,10 @@ export declare class LLMProvider {
85
88
  private toStreamUsage;
86
89
  private shouldDowngradeToResponses;
87
90
  /**
88
- * Loopback-aware transport injected into adapter `execute`. Mirrors the
89
- * legacy orchestration exactly: streaming requests go through fetch with a
90
- * 120s timeout and degrade to a non-streaming node-http request on fetch
91
- * failure; non-streaming requests reuse postJsonWithFetchFallback.
91
+ * Loopback-aware transport injected into adapter `execute`. Streaming
92
+ * requests retain the fetch-to-node fallback for transport failures, while
93
+ * a local deadline is returned directly so one request cannot become a
94
+ * second Windows fallback request.
92
95
  */
93
96
  private buildProviderAdapterTransport;
94
97
  private toTransportResponse;