newmark-agent 0.4.5 → 0.4.7

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 (40) hide show
  1. package/assets/app-icon-dark.svg +6 -0
  2. package/dist/assets/app-icon-dark.svg +6 -0
  3. package/dist/cli-commands.d.ts +1 -1
  4. package/dist/cli-commands.js +90 -7
  5. package/dist/cli-discovery.js +10 -0
  6. package/dist/conversation-utility-host.bundle.cjs +293 -148
  7. package/dist/core/agent.d.ts +38 -4
  8. package/dist/core/agent.js +231 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +49 -83
  11. package/dist/core/config.js +3 -0
  12. package/dist/core/dshCompatibility.d.ts +23 -6
  13. package/dist/core/dshCompatibility.js +99 -1
  14. package/dist/core/installUpdate.d.ts +67 -0
  15. package/dist/core/installUpdate.js +268 -0
  16. package/dist/core/mobilePairing.d.ts +47 -0
  17. package/dist/core/mobilePairing.js +221 -0
  18. package/dist/core/subagent.d.ts +10 -3
  19. package/dist/core/subagent.js +23 -8
  20. package/dist/core/toolPolicy.js +11 -3
  21. package/dist/launcher.js +14 -11
  22. package/dist/main.js +64 -3
  23. package/dist/preload.js +5 -0
  24. package/dist/providers/chat-completions.adapter.js +6 -2
  25. package/dist/providers/responses.adapter.js +1 -0
  26. package/dist/server.d.ts +1 -0
  27. package/dist/server.js +721 -3
  28. package/dist/toolchain/registry-seeder.js +3 -1
  29. package/dist/tools/index.js +11 -5
  30. package/dist/tools/nativeTools.js +1 -1
  31. package/dist/tui/src/adapters/core-runtime-adapter.js +17 -1
  32. package/dist/tui/src/app.js +41 -0
  33. package/dist/tui/src/data.js +1 -0
  34. package/dist/tui/src/render.js +11 -0
  35. package/dist/tui/src/settings-schema.js +3 -1
  36. package/dist/tui/src/state.js +21 -1
  37. package/dist/ui/index.html +530 -101
  38. package/dist/ui/lucide-sprite.svg +10 -0
  39. package/dist/wsl-agent-host.bundle.cjs +293 -148
  40. package/package.json +14 -8
@@ -474,7 +474,9 @@ export declare class Agent {
474
474
  private workspaceConversationKey;
475
475
  private workspaceConversationStorePath;
476
476
  private workspaceConversationStateKey;
477
+ private workspaceConversationStateKeyFor;
477
478
  private workspaceConversationPrefix;
479
+ private workspaceConversationPrefixFor;
478
480
  private safeConversationId;
479
481
  subscribeWorkEvents(fn: (event: AgentWorkEvent) => void): () => void;
480
482
  subscribePeerWorkEvents(fn: (event: AgentWorkEvent) => void): () => void;
@@ -620,6 +622,30 @@ export declare class Agent {
620
622
  order: number;
621
623
  branchCommunication: boolean;
622
624
  }>;
625
+ private subagentConcurrencyLimit;
626
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
627
+ activeConversationIdForWorkspace(ws: WorkspaceInfo | null): string;
628
+ /**
629
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
630
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
631
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
632
+ */
633
+ getPersistedConversationWorkRuns(conversationId: string, ws?: WorkspaceInfo | null): ConversationWorkRun[];
634
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
635
+ hasConversationInWorkspace(conversationId: string, ws: WorkspaceInfo | null): boolean;
636
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
637
+ listWorkspaceConversationStates(ws: WorkspaceInfo | null): Array<{
638
+ id: string;
639
+ key: string;
640
+ title: string;
641
+ messageCount: number;
642
+ historyCount: number;
643
+ updatedAt: string;
644
+ pinned: boolean;
645
+ pinnedAt: string;
646
+ order: number;
647
+ branchCommunication: boolean;
648
+ }>;
623
649
  private normalizeLinkedPlan;
624
650
  private subagentManagerKey;
625
651
  private bindConversationSubagents;
@@ -632,6 +658,7 @@ export declare class Agent {
632
658
  getConversationSnapshot(conversationId?: string, options?: {
633
659
  window?: number;
634
660
  before?: number;
661
+ workspace?: WorkspaceInfo | null;
635
662
  }): ConversationSnapshot;
636
663
  inspectConversationBranch(conversationId: string, branchId: string, branchGroupId?: string): ConversationSnapshot;
637
664
  ensureConversationSnapshot(conversationId?: string): ConversationSnapshot;
@@ -640,8 +667,13 @@ export declare class Agent {
640
667
  setBranchCommunication(enabled: boolean): boolean;
641
668
  isBranchCommunicationEnabled(): boolean;
642
669
  switchConversationBranch(conversationId: string, branchId: string, branchGroupId?: string): ConversationSnapshot;
643
- setConversationPinned(id: string, pinned: boolean): boolean;
644
- renameConversation(id: string, title: string): boolean;
670
+ setConversationPinned(id: string, pinned: boolean, ws?: WorkspaceInfo | null): boolean;
671
+ renameConversation(id: string, title: string, ws?: WorkspaceInfo | null): boolean;
672
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
673
+ createConversationInWorkspace(ws: WorkspaceInfo, title?: string): {
674
+ id: string;
675
+ title: string;
676
+ };
645
677
  /**
646
678
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
647
679
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -675,6 +707,8 @@ export declare class Agent {
675
707
  */
676
708
  private maybeAutoRenameConversationFromRun;
677
709
  reorderConversations(ids: string[]): boolean;
710
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
711
+ reorderWorkspaceConversationGroup(ids: string[], ws: WorkspaceInfo | null): boolean;
678
712
  flushConversationState(): void;
679
713
  private titleFromMessages;
680
714
  private hasUserConversationTitle;
@@ -892,7 +926,7 @@ export declare class Agent {
892
926
  * large markdown payload and manifest use promise-based filesystem I/O so
893
927
  * independent workspaces can archive in parallel without freezing Electron.
894
928
  */
895
- archiveConversationAsync(conversationId: string): Promise<string | null>;
929
+ archiveConversationAsync(conversationId: string, ws?: WorkspaceInfo | null): Promise<string | null>;
896
930
  private archiveConversationAsyncUnlocked;
897
931
  private finalizeAsyncConversationArchive;
898
932
  private listStoredConversationIds;
@@ -997,7 +1031,7 @@ export declare class Agent {
997
1031
  private withTimeout;
998
1032
  private normalizeSubagentModelSelection;
999
1033
  handleSubagent(args: string): Promise<string>;
1000
- handleSubagentEnvelope(args: string): Promise<NewmarkSubagentToolResult>;
1034
+ handleSubagentEnvelope(args: string, requireRunningBuild?: boolean): Promise<NewmarkSubagentToolResult>;
1001
1035
  private resolveSubagentPreset;
1002
1036
  private buildSubagentPrompt;
1003
1037
  handleSubagentContinue(args: string): Promise<string>;
@@ -380,7 +380,7 @@ class Agent {
380
380
  this.tools = new index_1.ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
381
381
  this.skills = new skills_1.SkillsManager(rootPath);
382
382
  this.memoryLab = new memoryLab_1.MemoryLabManager(rootPath, this.config.getStr('general', 'language'));
383
- this.subagents = new subagent_1.SubagentManager({ rootAgentId: this.runtimeActorId });
383
+ this.subagents = new subagent_1.SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
384
384
  if (this.mode === 'goal' && !this.goal) {
385
385
  this.goal = new GoalStateImpl('Set your objective');
386
386
  }
@@ -925,6 +925,7 @@ class Agent {
925
925
  }
926
926
  setIntelligence(tier, persist = false) {
927
927
  this.intelligence = normalizeIntelligenceTier(tier);
928
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
928
929
  if (persist) {
929
930
  this.config.set('models', 'default_intelligence', this.intelligence);
930
931
  this.config.save();
@@ -1158,13 +1159,18 @@ class Agent {
1158
1159
  return path.join(ws.path, 'conversations', 'state.json');
1159
1160
  }
1160
1161
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
1161
- const prefix = this.workspaceConversationPrefix();
1162
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
1163
+ }
1164
+ workspaceConversationStateKeyFor(conversationId, ws) {
1165
+ const prefix = this.workspaceConversationPrefixFor(ws);
1162
1166
  if (!prefix)
1163
1167
  return null;
1164
1168
  return `${prefix}-${this.safeConversationId(conversationId)}`;
1165
1169
  }
1166
1170
  workspaceConversationPrefix() {
1167
- const ws = this.workspace.current;
1171
+ return this.workspaceConversationPrefixFor(this.workspace.current);
1172
+ }
1173
+ workspaceConversationPrefixFor(ws) {
1168
1174
  if (!ws)
1169
1175
  return null;
1170
1176
  const supplied = String(ws.conversationStatePrefix || '').trim();
@@ -2851,8 +2857,54 @@ class Agent {
2851
2857
  }
2852
2858
  }
2853
2859
  listConversationStates() {
2854
- const stored = this.readStoredConversationState();
2855
- const prefix = this.workspaceConversationPrefix() || '';
2860
+ return this.listWorkspaceConversationStates(this.workspace.current);
2861
+ }
2862
+ subagentConcurrencyLimit() {
2863
+ return this.intelligence === 'ultra' ? 16 : 4;
2864
+ }
2865
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
2866
+ activeConversationIdForWorkspace(ws) {
2867
+ const targetWs = ws || this.workspace.current;
2868
+ if (!targetWs)
2869
+ return 'default';
2870
+ const currentWs = this.workspace.current;
2871
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
2872
+ if (isCurrent)
2873
+ return this.safeConversationId(this.activeConversationId || 'default');
2874
+ const stored = this.readStoredConversationState(targetWs);
2875
+ return this.safeConversationId(stored.activeConversationId || 'default');
2876
+ }
2877
+ /**
2878
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
2879
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
2880
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
2881
+ */
2882
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
2883
+ const targetWs = ws || this.workspace.current;
2884
+ const clean = this.safeConversationId(conversationId || 'default');
2885
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
2886
+ const stored = this.readStoredConversationState(targetWs);
2887
+ const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : undefined;
2888
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
2889
+ const runtimeNodeId = String(tree?.activeNodeId || '');
2890
+ const viewedNodeId = tree
2891
+ ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId))
2892
+ : '';
2893
+ const viewedNode = tree?.nodes[viewedNodeId];
2894
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
2895
+ }
2896
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
2897
+ hasConversationInWorkspace(conversationId, ws) {
2898
+ const targetWs = ws || this.workspace.current;
2899
+ const stateKey = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || 'default'), targetWs);
2900
+ if (!stateKey)
2901
+ return false;
2902
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey);
2903
+ }
2904
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
2905
+ listWorkspaceConversationStates(ws) {
2906
+ const stored = this.readStoredConversationState(ws);
2907
+ const prefix = this.workspaceConversationPrefixFor(ws) || '';
2856
2908
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key]) => !prefix || key.startsWith(prefix));
2857
2909
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
2858
2910
  const legacyOrder = [...scopedEntries].sort(([, a], [, b]) => {
@@ -2863,7 +2915,7 @@ class Agent {
2863
2915
  return String(b.updatedAt || '').localeCompare(String(a.updatedAt || ''));
2864
2916
  });
2865
2917
  legacyOrder.forEach(([, value], index) => { value.order = index; });
2866
- this.writeStoredConversationState(stored);
2918
+ this.writeStoredConversationState(stored, ws);
2867
2919
  }
2868
2920
  const rows = [];
2869
2921
  for (const [key, value] of Object.entries(stored.conversations || {})) {
@@ -2922,6 +2974,7 @@ class Agent {
2922
2974
  this.subagents = (0, subagent_1.sharedSubagentManager)(this.subagentManagerKey(clean), {
2923
2975
  conversationId: clean,
2924
2976
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
2977
+ concurrency: this.subagentConcurrencyLimit(),
2925
2978
  state,
2926
2979
  executor: job => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
2927
2980
  persist: subagentState => this.persistSubagentState(clean, subagentState),
@@ -2995,16 +3048,18 @@ class Agent {
2995
3048
  }
2996
3049
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
2997
3050
  const clean = this.safeConversationId(conversationId || 'default');
2998
- const isActiveConversation = clean === this.safeConversationId(this.activeConversationId || 'default');
2999
- const stateKey = this.workspaceConversationStateKey(clean);
3000
- const memoryKey = (() => {
3001
- const ws = this.workspace.current;
3002
- if (!ws)
3003
- return null;
3004
- return `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
3005
- })();
3051
+ const ws = options.workspace || this.workspace.current;
3052
+ const currentWs = this.workspace.current;
3053
+ const isActiveWorkspace = (!ws && !currentWs)
3054
+ || (!!ws && !!currentWs && path.resolve(ws.path) === path.resolve(currentWs.path));
3055
+ const isActiveConversation = isActiveWorkspace
3056
+ && clean === this.safeConversationId(this.activeConversationId || 'default');
3057
+ const stateKey = this.workspaceConversationStateKeyFor(clean, ws);
3058
+ const memoryKey = ws
3059
+ ? `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`
3060
+ : null;
3006
3061
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : undefined;
3007
- const stored = this.readStoredConversationState();
3062
+ const stored = this.readStoredConversationState(ws);
3008
3063
  const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : undefined;
3009
3064
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
3010
3065
  const runtimeNodeId = String(tree?.activeNodeId || '');
@@ -3039,7 +3094,7 @@ class Agent {
3039
3094
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : (viewedNode?.continuations || persisted?.continuations || memory?.continuations));
3040
3095
  return {
3041
3096
  conversationId: clean,
3042
- conversations: this.listConversationStates(),
3097
+ conversations: this.listWorkspaceConversationStates(ws),
3043
3098
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : (persisted?.plan || memory?.plan)),
3044
3099
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : (persisted?.linkedPlan || memory?.linkedPlan)),
3045
3100
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : (persisted?.subagentState || memory?.subagentState)),
@@ -3051,11 +3106,11 @@ class Agent {
3051
3106
  continuations,
3052
3107
  modelSelection: isActiveConversation
3053
3108
  ? this.currentConversationModelSelection()
3054
- : (persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection()),
3109
+ : (persisted?.modelSelection || memory?.modelSelection || { kind: 'auto' }),
3055
3110
  flowSelection: isActiveConversation
3056
3111
  ? this.currentConversationFlowSelection()
3057
3112
  : (persisted?.flowSelection || memory?.flowSelection || null),
3058
- inputMode: this.inputMode,
3113
+ inputMode: isActiveConversation ? this.inputMode : (persisted?.inputMode || memory?.inputMode || 'guide'),
3059
3114
  mode: isActiveConversation ? this.mode : (persisted?.mode || memory?.mode || 'build'),
3060
3115
  goal: isActiveConversation ? this.serializeGoal() : (persisted?.goal || memory?.goal || null),
3061
3116
  branches: this.branchGroupMetadata(tree),
@@ -3388,13 +3443,19 @@ class Agent {
3388
3443
  this.setConversationFromStorage(clean);
3389
3444
  return this.getConversationSnapshot(clean);
3390
3445
  }
3391
- setConversationPinned(id, pinned) {
3446
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
3447
+ const targetWs = ws || this.workspace.current;
3448
+ if (!targetWs)
3449
+ return false;
3392
3450
  const clean = this.safeConversationId(id || 'default');
3393
- this.saveWorkspaceConversationState();
3394
- const stateKey = this.workspaceConversationStateKey(clean);
3451
+ const currentWs = this.workspace.current;
3452
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3453
+ if (isCurrent)
3454
+ this.saveWorkspaceConversationState();
3455
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
3395
3456
  if (!stateKey)
3396
3457
  return false;
3397
- const stored = this.readStoredConversationState();
3458
+ const stored = this.readStoredConversationState(targetWs);
3398
3459
  stored.conversations = stored.conversations || {};
3399
3460
  const existing = stored.conversations[stateKey];
3400
3461
  if (!existing)
@@ -3406,26 +3467,72 @@ class Agent {
3406
3467
  .map(([, value]) => Number(value.order));
3407
3468
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
3408
3469
  existing.updatedAt = existing.updatedAt || new Date().toISOString();
3409
- this.writeStoredConversationState(stored);
3470
+ this.writeStoredConversationState(stored, targetWs);
3410
3471
  return true;
3411
3472
  }
3412
- renameConversation(id, title) {
3473
+ renameConversation(id, title, ws = this.workspace.current) {
3474
+ const targetWs = ws || this.workspace.current;
3475
+ if (!targetWs)
3476
+ return false;
3413
3477
  const clean = this.safeConversationId(id || 'default');
3414
3478
  const nextTitle = String(title || '').replace(/\s+/g, ' ').trim().slice(0, 80);
3415
3479
  if (!nextTitle)
3416
3480
  return false;
3417
- this.saveWorkspaceConversationState();
3418
- const stateKey = this.workspaceConversationStateKey(clean);
3481
+ const currentWs = this.workspace.current;
3482
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3483
+ if (isCurrent)
3484
+ this.saveWorkspaceConversationState();
3485
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
3419
3486
  if (!stateKey)
3420
3487
  return false;
3421
- const stored = this.readStoredConversationState();
3488
+ const stored = this.readStoredConversationState(targetWs);
3422
3489
  const existing = stored.conversations?.[stateKey];
3423
3490
  if (!existing)
3424
3491
  return false;
3425
3492
  existing.title = nextTitle;
3426
- this.writeStoredConversationState(stored);
3493
+ this.writeStoredConversationState(stored, targetWs);
3427
3494
  return true;
3428
3495
  }
3496
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
3497
+ createConversationInWorkspace(ws, title = '') {
3498
+ const existing = this.listWorkspaceConversationStates(ws);
3499
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`);
3500
+ const resolvedTitle = String(title || '').replace(/\s+/g, ' ').trim().slice(0, 80)
3501
+ || `New chat ${existing.length + 1}`;
3502
+ const stateKey = this.workspaceConversationStateKeyFor(id, ws);
3503
+ if (!stateKey)
3504
+ throw new Error('Conversation workspace is unavailable.');
3505
+ const unpinnedOrders = existing.filter(item => !item.pinned).map(item => Number(item.order || 0));
3506
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
3507
+ const now = new Date().toISOString();
3508
+ this.mutateStoredConversationState(ws, latest => {
3509
+ latest.version = 3;
3510
+ latest.activeConversationId = id;
3511
+ latest.conversations = latest.conversations || {};
3512
+ latest.conversations[stateKey] = {
3513
+ title: resolvedTitle,
3514
+ chatMessages: [],
3515
+ history: [],
3516
+ plan: { items: [] },
3517
+ linkedPlan: { markdown: '', revision: 0 },
3518
+ workRuns: [],
3519
+ continuations: [],
3520
+ inputMode: this.defaultInputMode(),
3521
+ mode: 'build',
3522
+ updatedAt: now,
3523
+ pinned: false,
3524
+ pinnedAt: '',
3525
+ order,
3526
+ branchCommunication: false,
3527
+ };
3528
+ return latest;
3529
+ });
3530
+ const currentWs = this.workspace.current;
3531
+ if (currentWs && path.resolve(currentWs.path) === path.resolve(ws.path)) {
3532
+ this.setConversationFromStorage(id);
3533
+ }
3534
+ return { id, title: resolvedTitle };
3535
+ }
3429
3536
  /**
3430
3537
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
3431
3538
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -3560,6 +3667,39 @@ class Agent {
3560
3667
  this.writeStoredConversationState(stored);
3561
3668
  return true;
3562
3669
  }
3670
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
3671
+ reorderWorkspaceConversationGroup(ids, ws) {
3672
+ const targetWs = ws || this.workspace.current;
3673
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2)
3674
+ return false;
3675
+ const normalized = ids.map(id => this.safeConversationId(String(id || '')));
3676
+ if (normalized.some(id => !id) || new Set(normalized).size !== normalized.length)
3677
+ return false;
3678
+ const currentWs = this.workspace.current;
3679
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3680
+ if (isCurrent)
3681
+ this.saveWorkspaceConversationState();
3682
+ let accepted = false;
3683
+ this.mutateStoredConversationState(targetWs, latest => {
3684
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || '';
3685
+ const entries = Object.entries(latest.conversations || {})
3686
+ .filter(([key]) => !prefix || key.startsWith(prefix));
3687
+ const entryById = new Map(entries.map(([key, value]) => [key.slice(prefix.length + 1) || key, value]));
3688
+ const requested = normalized.map(id => entryById.get(id));
3689
+ if (requested.some(entry => !entry))
3690
+ return latest;
3691
+ if (new Set(requested.map(entry => !!entry.pinned)).size !== 1)
3692
+ return latest;
3693
+ const orderSlots = requested.map(entry => Number(entry.order));
3694
+ if (orderSlots.some(order => !Number.isFinite(order)))
3695
+ return latest;
3696
+ orderSlots.sort((a, b) => a - b);
3697
+ normalized.forEach((id, index) => { entryById.get(id).order = orderSlots[index]; });
3698
+ accepted = true;
3699
+ return latest;
3700
+ });
3701
+ return accepted;
3702
+ }
3563
3703
  flushConversationState() {
3564
3704
  this.saveWorkspaceConversationState();
3565
3705
  }
@@ -5678,17 +5818,17 @@ class Agent {
5678
5818
  this.saveWorkspaceConversationState(true);
5679
5819
  return { text, hiddenUserInput: true, goalContinuation: true };
5680
5820
  }
5681
- buildSessionArchive(messages, mode, model, archiveDir) {
5821
+ buildSessionArchive(messages, context, archiveDir) {
5682
5822
  const stamp = new Date().toISOString().replace(/[:.]/g, '').replace('T', '_').replace('Z', '');
5683
5823
  // Millisecond-only names collide when a user clicks several archive
5684
5824
  // buttons in one event-loop turn. Keep the readable timestamp and add a
5685
5825
  // cryptographic suffix so every request owns an independent file.
5686
5826
  const filename = `session_${stamp}_${crypto.randomUUID().slice(0, 8)}.md`;
5687
5827
  let markdown = `# Newmark Session — ${stamp}\n\n`;
5688
- markdown += `**Mode**: ${mode}\n**Model**: ${model}\n`;
5828
+ markdown += `**Mode**: ${context.mode}\n**Model**: ${context.model}\n`;
5689
5829
  markdown += `**Messages**: ${messages.length}\n\n---\n\n`;
5690
- if (this.goal)
5691
- markdown += `**Goal**: ${this.goal.objective}\n\n`;
5830
+ if (context.goal?.objective)
5831
+ markdown += `**Goal**: ${context.goal.objective}\n\n`;
5692
5832
  for (const msg of messages) {
5693
5833
  markdown += `**[${msg.role}] ${msg.timestamp}**\n\n${msg.content}\n\n`;
5694
5834
  for (const attachment of (0, conversationAttachments_1.hydrateConversationImageAttachments)(this.rootPath, msg.attachments)) {
@@ -5704,12 +5844,12 @@ class Agent {
5704
5844
  writeSessionArchive(messages, mode, model) {
5705
5845
  const archiveDir = this.archiveDir();
5706
5846
  fs.mkdirSync(archiveDir, { recursive: true });
5707
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
5847
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
5708
5848
  fs.writeFileSync(path.join(archiveDir, archive.filename), archive.markdown, 'utf-8');
5709
5849
  return archive.filename;
5710
5850
  }
5711
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
5712
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
5851
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
5852
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
5713
5853
  await fs.promises.mkdir(archiveDir, { recursive: true });
5714
5854
  const outPath = path.join(archiveDir, archive.filename);
5715
5855
  const tempPath = `${outPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
@@ -5803,25 +5943,23 @@ class Agent {
5803
5943
  * large markdown payload and manifest use promise-based filesystem I/O so
5804
5944
  * independent workspaces can archive in parallel without freezing Electron.
5805
5945
  */
5806
- async archiveConversationAsync(conversationId) {
5946
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
5807
5947
  // Archive payloads intentionally start in parallel. The final state
5808
5948
  // mutation below operates on the latest locked disk snapshot, so no
5809
5949
  // JavaScript queue is needed for independent targets in one workspace.
5810
- return await this.archiveConversationAsyncUnlocked(conversationId);
5950
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
5811
5951
  }
5812
- async archiveConversationAsyncUnlocked(conversationId) {
5813
- const ws = this.workspace.current;
5952
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
5953
+ const ws = targetWorkspace || this.workspace.current;
5814
5954
  if (!ws)
5815
5955
  return null;
5816
5956
  const clean = this.safeConversationId(conversationId || 'default');
5817
- const stateKey = this.workspaceConversationStateKey(clean);
5957
+ const stateKey = this.workspaceConversationStateKeyFor(clean, ws);
5818
5958
  if (!stateKey)
5819
5959
  return null;
5820
5960
  const memoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
5821
5961
  const archiveDir = path.join(ws.path, 'archive');
5822
- const workspacePrefix = this.workspaceConversationPrefix() || '';
5823
- const archiveMode = this.modeName();
5824
- const archiveModel = this.model;
5962
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || '';
5825
5963
  // readStoredConversationState returns a cache object. Clone it before any
5826
5964
  // asynchronous gap so concurrent archive requests cannot mutate one
5827
5965
  // another's source snapshot.
@@ -5831,13 +5969,23 @@ class Agent {
5831
5969
  if (persisted)
5832
5970
  this.normalizeConversationTree(persisted);
5833
5971
  const memory = this.workspaceConversations.get(memoryKey);
5972
+ const archiveMode = persisted?.mode || memory?.mode || 'build';
5973
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
5974
+ const archiveModel = archiveSelection?.kind === 'deployment'
5975
+ ? archiveSelection.modelId
5976
+ : archiveSelection?.kind === 'auto' ? 'auto' : 'auto';
5977
+ const archiveGoal = persisted?.goal || memory?.goal || null;
5834
5978
  const persistedMessagesAvailable = persisted?.chatMessages !== undefined;
5835
5979
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
5836
5980
  const sourceHistory = persistedMessagesAvailable
5837
5981
  ? (persisted?.history ?? [])
5838
5982
  : (memory?.history ?? persisted?.history ?? []);
5839
5983
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
5840
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
5984
+ const filename = await this.writeSessionArchiveAsync(messages, {
5985
+ mode: archiveMode,
5986
+ model: archiveModel,
5987
+ goal: archiveGoal,
5988
+ }, archiveDir);
5841
5989
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
5842
5990
  title: this.titleFromMessages(messages, clean),
5843
5991
  chatMessages: messages,
@@ -5897,7 +6045,9 @@ class Agent {
5897
6045
  this.workspaceConversations.delete(memoryKey);
5898
6046
  const duplicateMemoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
5899
6047
  this.workspaceConversations.delete(duplicateMemoryKey);
5900
- if (clean === this.safeConversationId(this.activeConversationId || 'default')) {
6048
+ const currentWs = this.workspace.current;
6049
+ const isCurrentWorkspace = !!currentWs && path.resolve(currentWs.path) === path.resolve(ws.path);
6050
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || 'default')) {
5901
6051
  this.activeConversationId = nextActiveId || 'default';
5902
6052
  this.loadWorkspaceConversationState();
5903
6053
  }
@@ -7345,7 +7495,7 @@ class Agent {
7345
7495
  const settled = await this.waitForSubagentSettlement(accepted.data.id);
7346
7496
  return `${accepted.output}\n${settled?.result || settled?.error || ''}`.trim();
7347
7497
  }
7348
- async handleSubagentEnvelope(args) {
7498
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
7349
7499
  try {
7350
7500
  const params = JSON.parse(args);
7351
7501
  const preset = this.resolveSubagentPreset(params);
@@ -7368,7 +7518,39 @@ class Agent {
7368
7518
  && requestedModel === activeDeployment.modelId
7369
7519
  ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}`
7370
7520
  : requestedModel;
7371
- const id = this.subagents.create(name, prompt, peerModel, params.input_mode || params.inputMode || preset?.inputMode || 'guide', peerMode, this.runtimeActorId, peerFlow, peerGoal, Number(params.flow_pc ?? params.flowPc ?? (peerMode === 'flow' ? this.flowPc : 0)));
7521
+ const buildRunId = this.currentWorkRunId();
7522
+ const runningBuild = buildRunId
7523
+ ? this.workRuns.find(run => run.runId === buildRunId && run.status === 'running')
7524
+ : undefined;
7525
+ const limit = this.subagentConcurrencyLimit();
7526
+ if (requireRunningBuild && !runningBuild) {
7527
+ return {
7528
+ ok: false,
7529
+ output: '[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.',
7530
+ error: 'SubAgent tool calls require a running Build Block.',
7531
+ metadata: { kind: 'subagent', buildRunId: buildRunId || '', limit, terminated: true },
7532
+ };
7533
+ }
7534
+ if (buildRunId && !runningBuild) {
7535
+ return {
7536
+ ok: false,
7537
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
7538
+ error: 'SubAgent creation requires a running Build Block.',
7539
+ metadata: { kind: 'subagent', buildRunId, limit, terminated: true },
7540
+ };
7541
+ }
7542
+ if (runningBuild) {
7543
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
7544
+ if (activeForBuild >= limit) {
7545
+ return {
7546
+ ok: false,
7547
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === 'ultra' ? 'Ultra' : 'non-Ultra'} hard limit (${limit}); no SubAgent was created or queued.`,
7548
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
7549
+ metadata: { kind: 'subagent', buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true },
7550
+ };
7551
+ }
7552
+ }
7553
+ const id = this.subagents.create(name, prompt, peerModel, params.input_mode || params.inputMode || preset?.inputMode || 'guide', peerMode, this.runtimeActorId, peerFlow, peerGoal, Number(params.flow_pc ?? params.flowPc ?? (peerMode === 'flow' ? this.flowPc : 0)), runningBuild?.runId || '', this.intelligence);
7372
7554
  const sa = this.subagents.get(id);
7373
7555
  if (sa && preset) {
7374
7556
  sa.metadata = {
@@ -8830,7 +9012,7 @@ class Agent {
8830
9012
  if (this.intelligence === 'ultra') {
8831
9013
  parts.push([
8832
9014
  '[Ultra Intelligence – Orchestrator Role]',
8833
- 'You are the lead orchestrator. Actively decompose complex tasks into parallel sub-tasks. Use the `task` tool to create specialized SubAgents for each distinct sub-task. Coordinate the SubAgent team: assign clear responsibilities, merge results, resolve conflicts, and produce a unified final output. SubAgents are your team; delegate aggressively and manage them as a manager, not just a tool caller.',
9015
+ 'You are the lead orchestrator. Actively decompose complex tasks into parallel sub-tasks. Use the `SubAgent` tool to create specialized SubAgents for each distinct sub-task. Use `task_create` only for the conversation checklist; it never creates a SubAgent. Coordinate the SubAgent team: assign clear responsibilities, merge results, resolve conflicts, and produce a unified final output. SubAgents are your team; delegate aggressively and manage them as a manager, not just a tool caller.',
8834
9016
  'Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review.',
8835
9017
  ].join('\n'));
8836
9018
  }
@@ -8937,7 +9119,7 @@ class Agent {
8937
9119
  '- 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.',
8938
9120
  '- 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.',
8939
9121
  '- 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.',
8940
- `- 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.`,
9122
+ `- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; SubAgent creates constrained peer agents tracked in agent state. task_create is only for the conversation checklist and never creates a SubAgent.`,
8941
9123
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
8942
9124
  '- During Build work, before the first tool call and between materially different tool phases, emit a concise public progress explanation of what you are checking or changing and why. This is visible commentary, not hidden chain-of-thought. Do not wait until the final answer to explain the work.',
8943
9125
  ].join('\n');
@@ -158,6 +158,9 @@ export declare const agentKernelRunnerInternals: {
158
158
  TOOL_PROVISION_NAME: string;
159
159
  INITIAL_TOOL_SCHEMA_LIMIT: number;
160
160
  SUBAGENT_CORE_TOOL_NAMES: Set<string>;
161
+ TASK_CHECKLIST_CORE_TOOL_NAMES: Set<string>;
162
+ BASIC_INITIAL_TOOL_NAMES: Set<string>;
163
+ ALWAYS_AVAILABLE_AGENT_TOOL_NAMES: Set<string>;
161
164
  };
162
165
  declare function imageMimeForPath(imagePath: string): string;
163
166
  export {};