newmark-agent 0.4.6 → 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.
- package/assets/app-icon-dark.svg +6 -0
- package/dist/assets/app-icon-dark.svg +6 -0
- package/dist/cli-commands.js +2 -1
- package/dist/cli-discovery.js +2 -0
- package/dist/conversation-utility-host.bundle.cjs +291 -149
- package/dist/core/agent.d.ts +38 -4
- package/dist/core/agent.js +231 -49
- package/dist/core/agentKernelRunner.d.ts +3 -0
- package/dist/core/agentKernelRunner.js +49 -83
- package/dist/core/config.js +1 -1
- package/dist/core/installUpdate.js +11 -8
- package/dist/core/mobilePairing.d.ts +1 -0
- package/dist/core/mobilePairing.js +15 -1
- package/dist/core/subagent.d.ts +10 -3
- package/dist/core/subagent.js +23 -8
- package/dist/core/toolPolicy.js +11 -3
- package/dist/launcher.js +14 -11
- package/dist/main.js +15 -3
- package/dist/providers/chat-completions.adapter.js +6 -2
- package/dist/providers/responses.adapter.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +543 -17
- package/dist/toolchain/registry-seeder.js +3 -1
- package/dist/tools/index.js +11 -5
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +88 -101
- package/dist/wsl-agent-host.bundle.cjs +291 -149
- package/package.json +3 -1
package/dist/core/agent.d.ts
CHANGED
|
@@ -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>;
|
package/dist/core/agent.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2855
|
-
|
|
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
|
|
2999
|
-
const
|
|
3000
|
-
const
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
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.
|
|
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 ||
|
|
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.
|
|
3394
|
-
const
|
|
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.
|
|
3418
|
-
const
|
|
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,
|
|
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 (
|
|
5691
|
-
markdown += `**Goal**: ${
|
|
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,
|
|
5712
|
-
const archive = this.buildSessionArchive(messages,
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 `
|
|
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;
|
|
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 {};
|