newmark-agent 0.4.6 → 0.4.8

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 (43) 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.js +2 -1
  4. package/dist/cli-discovery.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +577 -166
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +40 -4
  8. package/dist/core/agent.js +251 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +74 -91
  11. package/dist/core/config.js +1 -1
  12. package/dist/core/conversationKernel.d.ts +40 -0
  13. package/dist/core/conversationKernel.js +219 -8
  14. package/dist/core/electronUtilityAgentClient.d.ts +8 -0
  15. package/dist/core/electronUtilityAgentClient.js +5 -1
  16. package/dist/core/electronUtilityRuntimePool.d.ts +15 -0
  17. package/dist/core/electronUtilityRuntimePool.js +11 -0
  18. package/dist/core/installUpdate.js +11 -8
  19. package/dist/core/mobilePairing.d.ts +1 -0
  20. package/dist/core/mobilePairing.js +15 -1
  21. package/dist/core/subagent.d.ts +10 -3
  22. package/dist/core/subagent.js +23 -8
  23. package/dist/core/toolPolicy.js +14 -3
  24. package/dist/core/utilityAgentProtocol.d.ts +29 -1
  25. package/dist/core/utilityHostToolRouter.js +18 -0
  26. package/dist/core/wslAgentClient.d.ts +8 -0
  27. package/dist/core/wslAgentClient.js +5 -1
  28. package/dist/core/wslAgentProtocol.d.ts +18 -1
  29. package/dist/core/wslAgentRuntimePool.d.ts +15 -0
  30. package/dist/core/wslAgentRuntimePool.js +11 -0
  31. package/dist/launcher.js +14 -11
  32. package/dist/main.js +207 -9
  33. package/dist/providers/chat-completions.adapter.js +6 -2
  34. package/dist/providers/responses.adapter.js +1 -0
  35. package/dist/server.d.ts +33 -1
  36. package/dist/server.js +696 -48
  37. package/dist/toolchain/registry-seeder.js +3 -1
  38. package/dist/tools/index.js +57 -6
  39. package/dist/tools/nativeTools.js +2 -1
  40. package/dist/ui/index.html +88 -101
  41. package/dist/wsl-agent-host.bundle.cjs +577 -166
  42. package/dist/wsl-agent-host.js +3 -0
  43. package/package.json +4 -2
@@ -79,6 +79,9 @@ async function handle(request) {
79
79
  target,
80
80
  });
81
81
  }
82
+ if (request.method === 'queue_action') {
83
+ return kernel.queueAction(checkedTarget(request.params.target), request.params.action, request.params.input);
84
+ }
82
85
  if (request.method === 'checkpoint')
83
86
  return kernel.checkpoint(checkedTarget(request.params.target));
84
87
  if (request.method === 'context_compress') {
@@ -346,6 +346,7 @@ export declare class Agent {
346
346
  private memoryLabRebuildState;
347
347
  private memoryLabRebuildError;
348
348
  private displayImageDescriptionCache;
349
+ private latestCapturedImageInputs;
349
350
  private activeProcessAbortController;
350
351
  private automationManager;
351
352
  private activeAgentKernelRuntime;
@@ -474,7 +475,9 @@ export declare class Agent {
474
475
  private workspaceConversationKey;
475
476
  private workspaceConversationStorePath;
476
477
  private workspaceConversationStateKey;
478
+ private workspaceConversationStateKeyFor;
477
479
  private workspaceConversationPrefix;
480
+ private workspaceConversationPrefixFor;
478
481
  private safeConversationId;
479
482
  subscribeWorkEvents(fn: (event: AgentWorkEvent) => void): () => void;
480
483
  subscribePeerWorkEvents(fn: (event: AgentWorkEvent) => void): () => void;
@@ -620,6 +623,31 @@ export declare class Agent {
620
623
  order: number;
621
624
  branchCommunication: boolean;
622
625
  }>;
626
+ registerCapturedImageInput(dataUrl: string, name?: string): ConversationImageAttachment | null;
627
+ private subagentConcurrencyLimit;
628
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
629
+ activeConversationIdForWorkspace(ws: WorkspaceInfo | null): string;
630
+ /**
631
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
632
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
633
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
634
+ */
635
+ getPersistedConversationWorkRuns(conversationId: string, ws?: WorkspaceInfo | null): ConversationWorkRun[];
636
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
637
+ hasConversationInWorkspace(conversationId: string, ws: WorkspaceInfo | null): boolean;
638
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
639
+ listWorkspaceConversationStates(ws: WorkspaceInfo | null): Array<{
640
+ id: string;
641
+ key: string;
642
+ title: string;
643
+ messageCount: number;
644
+ historyCount: number;
645
+ updatedAt: string;
646
+ pinned: boolean;
647
+ pinnedAt: string;
648
+ order: number;
649
+ branchCommunication: boolean;
650
+ }>;
623
651
  private normalizeLinkedPlan;
624
652
  private subagentManagerKey;
625
653
  private bindConversationSubagents;
@@ -632,6 +660,7 @@ export declare class Agent {
632
660
  getConversationSnapshot(conversationId?: string, options?: {
633
661
  window?: number;
634
662
  before?: number;
663
+ workspace?: WorkspaceInfo | null;
635
664
  }): ConversationSnapshot;
636
665
  inspectConversationBranch(conversationId: string, branchId: string, branchGroupId?: string): ConversationSnapshot;
637
666
  ensureConversationSnapshot(conversationId?: string): ConversationSnapshot;
@@ -640,8 +669,13 @@ export declare class Agent {
640
669
  setBranchCommunication(enabled: boolean): boolean;
641
670
  isBranchCommunicationEnabled(): boolean;
642
671
  switchConversationBranch(conversationId: string, branchId: string, branchGroupId?: string): ConversationSnapshot;
643
- setConversationPinned(id: string, pinned: boolean): boolean;
644
- renameConversation(id: string, title: string): boolean;
672
+ setConversationPinned(id: string, pinned: boolean, ws?: WorkspaceInfo | null): boolean;
673
+ renameConversation(id: string, title: string, ws?: WorkspaceInfo | null): boolean;
674
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
675
+ createConversationInWorkspace(ws: WorkspaceInfo, title?: string): {
676
+ id: string;
677
+ title: string;
678
+ };
645
679
  /**
646
680
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
647
681
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -675,6 +709,8 @@ export declare class Agent {
675
709
  */
676
710
  private maybeAutoRenameConversationFromRun;
677
711
  reorderConversations(ids: string[]): boolean;
712
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
713
+ reorderWorkspaceConversationGroup(ids: string[], ws: WorkspaceInfo | null): boolean;
678
714
  flushConversationState(): void;
679
715
  private titleFromMessages;
680
716
  private hasUserConversationTitle;
@@ -892,7 +928,7 @@ export declare class Agent {
892
928
  * large markdown payload and manifest use promise-based filesystem I/O so
893
929
  * independent workspaces can archive in parallel without freezing Electron.
894
930
  */
895
- archiveConversationAsync(conversationId: string): Promise<string | null>;
931
+ archiveConversationAsync(conversationId: string, ws?: WorkspaceInfo | null): Promise<string | null>;
896
932
  private archiveConversationAsyncUnlocked;
897
933
  private finalizeAsyncConversationArchive;
898
934
  private listStoredConversationIds;
@@ -997,7 +1033,7 @@ export declare class Agent {
997
1033
  private withTimeout;
998
1034
  private normalizeSubagentModelSelection;
999
1035
  handleSubagent(args: string): Promise<string>;
1000
- handleSubagentEnvelope(args: string): Promise<NewmarkSubagentToolResult>;
1036
+ handleSubagentEnvelope(args: string, requireRunningBuild?: boolean): Promise<NewmarkSubagentToolResult>;
1001
1037
  private resolveSubagentPreset;
1002
1038
  private buildSubagentPrompt;
1003
1039
  handleSubagentContinue(args: string): Promise<string>;
@@ -241,6 +241,7 @@ class Agent {
241
241
  memoryLabRebuildState = 'idle';
242
242
  memoryLabRebuildError = '';
243
243
  displayImageDescriptionCache = new Map();
244
+ latestCapturedImageInputs = [];
244
245
  activeProcessAbortController = null;
245
246
  automationManager = null;
246
247
  activeAgentKernelRuntime = null;
@@ -380,7 +381,7 @@ class Agent {
380
381
  this.tools = new index_1.ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
381
382
  this.skills = new skills_1.SkillsManager(rootPath);
382
383
  this.memoryLab = new memoryLab_1.MemoryLabManager(rootPath, this.config.getStr('general', 'language'));
383
- this.subagents = new subagent_1.SubagentManager({ rootAgentId: this.runtimeActorId });
384
+ this.subagents = new subagent_1.SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
384
385
  if (this.mode === 'goal' && !this.goal) {
385
386
  this.goal = new GoalStateImpl('Set your objective');
386
387
  }
@@ -925,6 +926,7 @@ class Agent {
925
926
  }
926
927
  setIntelligence(tier, persist = false) {
927
928
  this.intelligence = normalizeIntelligenceTier(tier);
929
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
928
930
  if (persist) {
929
931
  this.config.set('models', 'default_intelligence', this.intelligence);
930
932
  this.config.save();
@@ -1158,13 +1160,18 @@ class Agent {
1158
1160
  return path.join(ws.path, 'conversations', 'state.json');
1159
1161
  }
1160
1162
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
1161
- const prefix = this.workspaceConversationPrefix();
1163
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
1164
+ }
1165
+ workspaceConversationStateKeyFor(conversationId, ws) {
1166
+ const prefix = this.workspaceConversationPrefixFor(ws);
1162
1167
  if (!prefix)
1163
1168
  return null;
1164
1169
  return `${prefix}-${this.safeConversationId(conversationId)}`;
1165
1170
  }
1166
1171
  workspaceConversationPrefix() {
1167
- const ws = this.workspace.current;
1172
+ return this.workspaceConversationPrefixFor(this.workspace.current);
1173
+ }
1174
+ workspaceConversationPrefixFor(ws) {
1168
1175
  if (!ws)
1169
1176
  return null;
1170
1177
  const supplied = String(ws.conversationStatePrefix || '').trim();
@@ -2851,8 +2858,62 @@ class Agent {
2851
2858
  }
2852
2859
  }
2853
2860
  listConversationStates() {
2854
- const stored = this.readStoredConversationState();
2855
- const prefix = this.workspaceConversationPrefix() || '';
2861
+ return this.listWorkspaceConversationStates(this.workspace.current);
2862
+ }
2863
+ registerCapturedImageInput(dataUrl, name = 'active-screenshot.jpg') {
2864
+ if (!String(dataUrl || '').startsWith('data:image/'))
2865
+ return null;
2866
+ const prepared = this.prepareSubmittedConversationImages([{ dataUrl, name, type: dataUrl.slice(5, dataUrl.indexOf(';')) }]);
2867
+ const attachment = prepared.attachments[0] || null;
2868
+ this.latestCapturedImageInputs = attachment ? [attachment] : [];
2869
+ return attachment;
2870
+ }
2871
+ subagentConcurrencyLimit() {
2872
+ return this.intelligence === 'ultra' ? 16 : 4;
2873
+ }
2874
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
2875
+ activeConversationIdForWorkspace(ws) {
2876
+ const targetWs = ws || this.workspace.current;
2877
+ if (!targetWs)
2878
+ return 'default';
2879
+ const currentWs = this.workspace.current;
2880
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
2881
+ if (isCurrent)
2882
+ return this.safeConversationId(this.activeConversationId || 'default');
2883
+ const stored = this.readStoredConversationState(targetWs);
2884
+ return this.safeConversationId(stored.activeConversationId || 'default');
2885
+ }
2886
+ /**
2887
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
2888
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
2889
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
2890
+ */
2891
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
2892
+ const targetWs = ws || this.workspace.current;
2893
+ const clean = this.safeConversationId(conversationId || 'default');
2894
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
2895
+ const stored = this.readStoredConversationState(targetWs);
2896
+ const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : undefined;
2897
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
2898
+ const runtimeNodeId = String(tree?.activeNodeId || '');
2899
+ const viewedNodeId = tree
2900
+ ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId))
2901
+ : '';
2902
+ const viewedNode = tree?.nodes[viewedNodeId];
2903
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
2904
+ }
2905
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
2906
+ hasConversationInWorkspace(conversationId, ws) {
2907
+ const targetWs = ws || this.workspace.current;
2908
+ const stateKey = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || 'default'), targetWs);
2909
+ if (!stateKey)
2910
+ return false;
2911
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey);
2912
+ }
2913
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
2914
+ listWorkspaceConversationStates(ws) {
2915
+ const stored = this.readStoredConversationState(ws);
2916
+ const prefix = this.workspaceConversationPrefixFor(ws) || '';
2856
2917
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key]) => !prefix || key.startsWith(prefix));
2857
2918
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
2858
2919
  const legacyOrder = [...scopedEntries].sort(([, a], [, b]) => {
@@ -2863,7 +2924,7 @@ class Agent {
2863
2924
  return String(b.updatedAt || '').localeCompare(String(a.updatedAt || ''));
2864
2925
  });
2865
2926
  legacyOrder.forEach(([, value], index) => { value.order = index; });
2866
- this.writeStoredConversationState(stored);
2927
+ this.writeStoredConversationState(stored, ws);
2867
2928
  }
2868
2929
  const rows = [];
2869
2930
  for (const [key, value] of Object.entries(stored.conversations || {})) {
@@ -2922,6 +2983,7 @@ class Agent {
2922
2983
  this.subagents = (0, subagent_1.sharedSubagentManager)(this.subagentManagerKey(clean), {
2923
2984
  conversationId: clean,
2924
2985
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
2986
+ concurrency: this.subagentConcurrencyLimit(),
2925
2987
  state,
2926
2988
  executor: job => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
2927
2989
  persist: subagentState => this.persistSubagentState(clean, subagentState),
@@ -2995,16 +3057,18 @@ class Agent {
2995
3057
  }
2996
3058
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
2997
3059
  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
- })();
3060
+ const ws = options.workspace || this.workspace.current;
3061
+ const currentWs = this.workspace.current;
3062
+ const isActiveWorkspace = (!ws && !currentWs)
3063
+ || (!!ws && !!currentWs && path.resolve(ws.path) === path.resolve(currentWs.path));
3064
+ const isActiveConversation = isActiveWorkspace
3065
+ && clean === this.safeConversationId(this.activeConversationId || 'default');
3066
+ const stateKey = this.workspaceConversationStateKeyFor(clean, ws);
3067
+ const memoryKey = ws
3068
+ ? `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`
3069
+ : null;
3006
3070
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : undefined;
3007
- const stored = this.readStoredConversationState();
3071
+ const stored = this.readStoredConversationState(ws);
3008
3072
  const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : undefined;
3009
3073
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
3010
3074
  const runtimeNodeId = String(tree?.activeNodeId || '');
@@ -3039,7 +3103,7 @@ class Agent {
3039
3103
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : (viewedNode?.continuations || persisted?.continuations || memory?.continuations));
3040
3104
  return {
3041
3105
  conversationId: clean,
3042
- conversations: this.listConversationStates(),
3106
+ conversations: this.listWorkspaceConversationStates(ws),
3043
3107
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : (persisted?.plan || memory?.plan)),
3044
3108
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : (persisted?.linkedPlan || memory?.linkedPlan)),
3045
3109
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : (persisted?.subagentState || memory?.subagentState)),
@@ -3051,11 +3115,11 @@ class Agent {
3051
3115
  continuations,
3052
3116
  modelSelection: isActiveConversation
3053
3117
  ? this.currentConversationModelSelection()
3054
- : (persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection()),
3118
+ : (persisted?.modelSelection || memory?.modelSelection || { kind: 'auto' }),
3055
3119
  flowSelection: isActiveConversation
3056
3120
  ? this.currentConversationFlowSelection()
3057
3121
  : (persisted?.flowSelection || memory?.flowSelection || null),
3058
- inputMode: this.inputMode,
3122
+ inputMode: isActiveConversation ? this.inputMode : (persisted?.inputMode || memory?.inputMode || 'guide'),
3059
3123
  mode: isActiveConversation ? this.mode : (persisted?.mode || memory?.mode || 'build'),
3060
3124
  goal: isActiveConversation ? this.serializeGoal() : (persisted?.goal || memory?.goal || null),
3061
3125
  branches: this.branchGroupMetadata(tree),
@@ -3388,13 +3452,19 @@ class Agent {
3388
3452
  this.setConversationFromStorage(clean);
3389
3453
  return this.getConversationSnapshot(clean);
3390
3454
  }
3391
- setConversationPinned(id, pinned) {
3455
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
3456
+ const targetWs = ws || this.workspace.current;
3457
+ if (!targetWs)
3458
+ return false;
3392
3459
  const clean = this.safeConversationId(id || 'default');
3393
- this.saveWorkspaceConversationState();
3394
- const stateKey = this.workspaceConversationStateKey(clean);
3460
+ const currentWs = this.workspace.current;
3461
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3462
+ if (isCurrent)
3463
+ this.saveWorkspaceConversationState();
3464
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
3395
3465
  if (!stateKey)
3396
3466
  return false;
3397
- const stored = this.readStoredConversationState();
3467
+ const stored = this.readStoredConversationState(targetWs);
3398
3468
  stored.conversations = stored.conversations || {};
3399
3469
  const existing = stored.conversations[stateKey];
3400
3470
  if (!existing)
@@ -3406,26 +3476,72 @@ class Agent {
3406
3476
  .map(([, value]) => Number(value.order));
3407
3477
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
3408
3478
  existing.updatedAt = existing.updatedAt || new Date().toISOString();
3409
- this.writeStoredConversationState(stored);
3479
+ this.writeStoredConversationState(stored, targetWs);
3410
3480
  return true;
3411
3481
  }
3412
- renameConversation(id, title) {
3482
+ renameConversation(id, title, ws = this.workspace.current) {
3483
+ const targetWs = ws || this.workspace.current;
3484
+ if (!targetWs)
3485
+ return false;
3413
3486
  const clean = this.safeConversationId(id || 'default');
3414
3487
  const nextTitle = String(title || '').replace(/\s+/g, ' ').trim().slice(0, 80);
3415
3488
  if (!nextTitle)
3416
3489
  return false;
3417
- this.saveWorkspaceConversationState();
3418
- const stateKey = this.workspaceConversationStateKey(clean);
3490
+ const currentWs = this.workspace.current;
3491
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3492
+ if (isCurrent)
3493
+ this.saveWorkspaceConversationState();
3494
+ const stateKey = this.workspaceConversationStateKeyFor(clean, targetWs);
3419
3495
  if (!stateKey)
3420
3496
  return false;
3421
- const stored = this.readStoredConversationState();
3497
+ const stored = this.readStoredConversationState(targetWs);
3422
3498
  const existing = stored.conversations?.[stateKey];
3423
3499
  if (!existing)
3424
3500
  return false;
3425
3501
  existing.title = nextTitle;
3426
- this.writeStoredConversationState(stored);
3502
+ this.writeStoredConversationState(stored, targetWs);
3427
3503
  return true;
3428
3504
  }
3505
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
3506
+ createConversationInWorkspace(ws, title = '') {
3507
+ const existing = this.listWorkspaceConversationStates(ws);
3508
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`);
3509
+ const resolvedTitle = String(title || '').replace(/\s+/g, ' ').trim().slice(0, 80)
3510
+ || `New chat ${existing.length + 1}`;
3511
+ const stateKey = this.workspaceConversationStateKeyFor(id, ws);
3512
+ if (!stateKey)
3513
+ throw new Error('Conversation workspace is unavailable.');
3514
+ const unpinnedOrders = existing.filter(item => !item.pinned).map(item => Number(item.order || 0));
3515
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
3516
+ const now = new Date().toISOString();
3517
+ this.mutateStoredConversationState(ws, latest => {
3518
+ latest.version = 3;
3519
+ latest.activeConversationId = id;
3520
+ latest.conversations = latest.conversations || {};
3521
+ latest.conversations[stateKey] = {
3522
+ title: resolvedTitle,
3523
+ chatMessages: [],
3524
+ history: [],
3525
+ plan: { items: [] },
3526
+ linkedPlan: { markdown: '', revision: 0 },
3527
+ workRuns: [],
3528
+ continuations: [],
3529
+ inputMode: this.defaultInputMode(),
3530
+ mode: 'build',
3531
+ updatedAt: now,
3532
+ pinned: false,
3533
+ pinnedAt: '',
3534
+ order,
3535
+ branchCommunication: false,
3536
+ };
3537
+ return latest;
3538
+ });
3539
+ const currentWs = this.workspace.current;
3540
+ if (currentWs && path.resolve(currentWs.path) === path.resolve(ws.path)) {
3541
+ this.setConversationFromStorage(id);
3542
+ }
3543
+ return { id, title: resolvedTitle };
3544
+ }
3429
3545
  /**
3430
3546
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
3431
3547
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -3560,6 +3676,39 @@ class Agent {
3560
3676
  this.writeStoredConversationState(stored);
3561
3677
  return true;
3562
3678
  }
3679
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
3680
+ reorderWorkspaceConversationGroup(ids, ws) {
3681
+ const targetWs = ws || this.workspace.current;
3682
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2)
3683
+ return false;
3684
+ const normalized = ids.map(id => this.safeConversationId(String(id || '')));
3685
+ if (normalized.some(id => !id) || new Set(normalized).size !== normalized.length)
3686
+ return false;
3687
+ const currentWs = this.workspace.current;
3688
+ const isCurrent = !!currentWs && path.resolve(currentWs.path) === path.resolve(targetWs.path);
3689
+ if (isCurrent)
3690
+ this.saveWorkspaceConversationState();
3691
+ let accepted = false;
3692
+ this.mutateStoredConversationState(targetWs, latest => {
3693
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || '';
3694
+ const entries = Object.entries(latest.conversations || {})
3695
+ .filter(([key]) => !prefix || key.startsWith(prefix));
3696
+ const entryById = new Map(entries.map(([key, value]) => [key.slice(prefix.length + 1) || key, value]));
3697
+ const requested = normalized.map(id => entryById.get(id));
3698
+ if (requested.some(entry => !entry))
3699
+ return latest;
3700
+ if (new Set(requested.map(entry => !!entry.pinned)).size !== 1)
3701
+ return latest;
3702
+ const orderSlots = requested.map(entry => Number(entry.order));
3703
+ if (orderSlots.some(order => !Number.isFinite(order)))
3704
+ return latest;
3705
+ orderSlots.sort((a, b) => a - b);
3706
+ normalized.forEach((id, index) => { entryById.get(id).order = orderSlots[index]; });
3707
+ accepted = true;
3708
+ return latest;
3709
+ });
3710
+ return accepted;
3711
+ }
3563
3712
  flushConversationState() {
3564
3713
  this.saveWorkspaceConversationState();
3565
3714
  }
@@ -5678,17 +5827,17 @@ class Agent {
5678
5827
  this.saveWorkspaceConversationState(true);
5679
5828
  return { text, hiddenUserInput: true, goalContinuation: true };
5680
5829
  }
5681
- buildSessionArchive(messages, mode, model, archiveDir) {
5830
+ buildSessionArchive(messages, context, archiveDir) {
5682
5831
  const stamp = new Date().toISOString().replace(/[:.]/g, '').replace('T', '_').replace('Z', '');
5683
5832
  // Millisecond-only names collide when a user clicks several archive
5684
5833
  // buttons in one event-loop turn. Keep the readable timestamp and add a
5685
5834
  // cryptographic suffix so every request owns an independent file.
5686
5835
  const filename = `session_${stamp}_${crypto.randomUUID().slice(0, 8)}.md`;
5687
5836
  let markdown = `# Newmark Session — ${stamp}\n\n`;
5688
- markdown += `**Mode**: ${mode}\n**Model**: ${model}\n`;
5837
+ markdown += `**Mode**: ${context.mode}\n**Model**: ${context.model}\n`;
5689
5838
  markdown += `**Messages**: ${messages.length}\n\n---\n\n`;
5690
- if (this.goal)
5691
- markdown += `**Goal**: ${this.goal.objective}\n\n`;
5839
+ if (context.goal?.objective)
5840
+ markdown += `**Goal**: ${context.goal.objective}\n\n`;
5692
5841
  for (const msg of messages) {
5693
5842
  markdown += `**[${msg.role}] ${msg.timestamp}**\n\n${msg.content}\n\n`;
5694
5843
  for (const attachment of (0, conversationAttachments_1.hydrateConversationImageAttachments)(this.rootPath, msg.attachments)) {
@@ -5704,12 +5853,12 @@ class Agent {
5704
5853
  writeSessionArchive(messages, mode, model) {
5705
5854
  const archiveDir = this.archiveDir();
5706
5855
  fs.mkdirSync(archiveDir, { recursive: true });
5707
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
5856
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
5708
5857
  fs.writeFileSync(path.join(archiveDir, archive.filename), archive.markdown, 'utf-8');
5709
5858
  return archive.filename;
5710
5859
  }
5711
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
5712
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
5860
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
5861
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
5713
5862
  await fs.promises.mkdir(archiveDir, { recursive: true });
5714
5863
  const outPath = path.join(archiveDir, archive.filename);
5715
5864
  const tempPath = `${outPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
@@ -5803,25 +5952,23 @@ class Agent {
5803
5952
  * large markdown payload and manifest use promise-based filesystem I/O so
5804
5953
  * independent workspaces can archive in parallel without freezing Electron.
5805
5954
  */
5806
- async archiveConversationAsync(conversationId) {
5955
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
5807
5956
  // Archive payloads intentionally start in parallel. The final state
5808
5957
  // mutation below operates on the latest locked disk snapshot, so no
5809
5958
  // JavaScript queue is needed for independent targets in one workspace.
5810
- return await this.archiveConversationAsyncUnlocked(conversationId);
5959
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
5811
5960
  }
5812
- async archiveConversationAsyncUnlocked(conversationId) {
5813
- const ws = this.workspace.current;
5961
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
5962
+ const ws = targetWorkspace || this.workspace.current;
5814
5963
  if (!ws)
5815
5964
  return null;
5816
5965
  const clean = this.safeConversationId(conversationId || 'default');
5817
- const stateKey = this.workspaceConversationStateKey(clean);
5966
+ const stateKey = this.workspaceConversationStateKeyFor(clean, ws);
5818
5967
  if (!stateKey)
5819
5968
  return null;
5820
5969
  const memoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
5821
5970
  const archiveDir = path.join(ws.path, 'archive');
5822
- const workspacePrefix = this.workspaceConversationPrefix() || '';
5823
- const archiveMode = this.modeName();
5824
- const archiveModel = this.model;
5971
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || '';
5825
5972
  // readStoredConversationState returns a cache object. Clone it before any
5826
5973
  // asynchronous gap so concurrent archive requests cannot mutate one
5827
5974
  // another's source snapshot.
@@ -5831,13 +5978,23 @@ class Agent {
5831
5978
  if (persisted)
5832
5979
  this.normalizeConversationTree(persisted);
5833
5980
  const memory = this.workspaceConversations.get(memoryKey);
5981
+ const archiveMode = persisted?.mode || memory?.mode || 'build';
5982
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
5983
+ const archiveModel = archiveSelection?.kind === 'deployment'
5984
+ ? archiveSelection.modelId
5985
+ : archiveSelection?.kind === 'auto' ? 'auto' : 'auto';
5986
+ const archiveGoal = persisted?.goal || memory?.goal || null;
5834
5987
  const persistedMessagesAvailable = persisted?.chatMessages !== undefined;
5835
5988
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
5836
5989
  const sourceHistory = persistedMessagesAvailable
5837
5990
  ? (persisted?.history ?? [])
5838
5991
  : (memory?.history ?? persisted?.history ?? []);
5839
5992
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
5840
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
5993
+ const filename = await this.writeSessionArchiveAsync(messages, {
5994
+ mode: archiveMode,
5995
+ model: archiveModel,
5996
+ goal: archiveGoal,
5997
+ }, archiveDir);
5841
5998
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
5842
5999
  title: this.titleFromMessages(messages, clean),
5843
6000
  chatMessages: messages,
@@ -5897,7 +6054,9 @@ class Agent {
5897
6054
  this.workspaceConversations.delete(memoryKey);
5898
6055
  const duplicateMemoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
5899
6056
  this.workspaceConversations.delete(duplicateMemoryKey);
5900
- if (clean === this.safeConversationId(this.activeConversationId || 'default')) {
6057
+ const currentWs = this.workspace.current;
6058
+ const isCurrentWorkspace = !!currentWs && path.resolve(currentWs.path) === path.resolve(ws.path);
6059
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || 'default')) {
5901
6060
  this.activeConversationId = nextActiveId || 'default';
5902
6061
  this.loadWorkspaceConversationState();
5903
6062
  }
@@ -7345,7 +7504,7 @@ class Agent {
7345
7504
  const settled = await this.waitForSubagentSettlement(accepted.data.id);
7346
7505
  return `${accepted.output}\n${settled?.result || settled?.error || ''}`.trim();
7347
7506
  }
7348
- async handleSubagentEnvelope(args) {
7507
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
7349
7508
  try {
7350
7509
  const params = JSON.parse(args);
7351
7510
  const preset = this.resolveSubagentPreset(params);
@@ -7368,7 +7527,39 @@ class Agent {
7368
7527
  && requestedModel === activeDeployment.modelId
7369
7528
  ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}`
7370
7529
  : 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)));
7530
+ const buildRunId = this.currentWorkRunId();
7531
+ const runningBuild = buildRunId
7532
+ ? this.workRuns.find(run => run.runId === buildRunId && run.status === 'running')
7533
+ : undefined;
7534
+ const limit = this.subagentConcurrencyLimit();
7535
+ if (requireRunningBuild && !runningBuild) {
7536
+ return {
7537
+ ok: false,
7538
+ output: '[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.',
7539
+ error: 'SubAgent tool calls require a running Build Block.',
7540
+ metadata: { kind: 'subagent', buildRunId: buildRunId || '', limit, terminated: true },
7541
+ };
7542
+ }
7543
+ if (buildRunId && !runningBuild) {
7544
+ return {
7545
+ ok: false,
7546
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
7547
+ error: 'SubAgent creation requires a running Build Block.',
7548
+ metadata: { kind: 'subagent', buildRunId, limit, terminated: true },
7549
+ };
7550
+ }
7551
+ if (runningBuild) {
7552
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
7553
+ if (activeForBuild >= limit) {
7554
+ return {
7555
+ ok: false,
7556
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === 'ultra' ? 'Ultra' : 'non-Ultra'} hard limit (${limit}); no SubAgent was created or queued.`,
7557
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
7558
+ metadata: { kind: 'subagent', buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true },
7559
+ };
7560
+ }
7561
+ }
7562
+ 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
7563
  const sa = this.subagents.get(id);
7373
7564
  if (sa && preset) {
7374
7565
  sa.metadata = {
@@ -8200,6 +8391,17 @@ class Agent {
8200
8391
  }
8201
8392
  latestSubmittedImages(attachmentId = '') {
8202
8393
  const normalizedId = String(attachmentId || '').trim();
8394
+ const captured = (0, conversationAttachments_1.hydrateConversationImageAttachments)(this.rootPath, this.latestCapturedImageInputs).flatMap(attachment => attachment.dataUrl
8395
+ ? [{ id: attachment.id, dataUrl: attachment.dataUrl }]
8396
+ : []);
8397
+ if (normalizedId) {
8398
+ const capturedMatch = captured.find(item => item.id === normalizedId);
8399
+ if (capturedMatch)
8400
+ return [capturedMatch];
8401
+ }
8402
+ else if (captured.length) {
8403
+ return captured;
8404
+ }
8203
8405
  for (let index = this.chatMessages.length - 1; index >= 0; index -= 1) {
8204
8406
  const message = this.chatMessages[index];
8205
8407
  if (message?.role !== 'user')
@@ -8830,7 +9032,7 @@ class Agent {
8830
9032
  if (this.intelligence === 'ultra') {
8831
9033
  parts.push([
8832
9034
  '[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.',
9035
+ '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
9036
  'Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review.',
8835
9037
  ].join('\n'));
8836
9038
  }
@@ -8937,7 +9139,7 @@ class Agent {
8937
9139
  '- 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
9140
  '- 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
9141
  '- 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.`,
9142
+ `- 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
9143
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
8942
9144
  '- 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
9145
  ].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 {};