cc-viewer 1.7.0 → 1.7.2
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/dist/assets/App-B224r-8M.js +2 -0
- package/dist/assets/{MdxEditorPanel-C5Q6uUyH.js → MdxEditorPanel-BUubiE6Y.js} +1 -1
- package/dist/assets/{Mobile-MoBp9c8k.js → Mobile-CKmfRRvo.js} +1 -1
- package/dist/assets/index-CjsQEQc2.js +2 -0
- package/dist/assets/seqResourceLoaders-C-T_EMiO.js +2 -0
- package/dist/assets/{seqResourceLoaders-CxuD1CYl.css → seqResourceLoaders-DFrHmgnK.css} +1 -1
- package/dist/index.html +1 -1
- package/package.json +2 -1
- package/server/lib/log-management.js +1 -0
- package/server/lib/log-watcher.js +29 -3
- package/server/lib/stats-worker.js +21 -2
- package/server/lib/v2/adapter.js +123 -18
- package/server/lib/v2/journal.js +9 -5
- package/server/lib/v2/jsonl-read.js +127 -0
- package/server/lib/v2/live-feed.js +222 -20
- package/server/lib/v2/meta-rows.js +361 -0
- package/server/lib/v2/replay.js +5 -5
- package/server/lib/v2/session-select.js +69 -2
- package/server/lib/v2/v2-writer.js +15 -0
- package/server/lib/wire-compress.js +167 -0
- package/server/routes/events.js +96 -26
- package/server/routes/im.js +1 -1
- package/server/routes/logs.js +15 -12
- package/server/routes/v2.js +60 -0
- package/server/routes/workspaces.js +7 -6
- package/server/server.js +23 -1
- package/server/workspace-registry.js +5 -0
- package/src/utils/apiUrl.js +26 -0
- package/src/utils/askFallback.js +21 -0
- package/src/utils/askOptionDesc.js +55 -0
- package/src/utils/askPortalMatcher.js +54 -0
- package/src/utils/autoApproveOptions.js +27 -0
- package/src/utils/avatarAnimationPostPass.js +49 -0
- package/src/utils/builtinPresets.js +18 -0
- package/src/utils/clearCheckpoint.js +7 -0
- package/src/utils/commandValidator.js +45 -0
- package/src/utils/contentFilter.js +620 -0
- package/src/utils/contextRaw.js +18 -0
- package/src/utils/contextSidebarNavigation.js +16 -0
- package/src/utils/contextTurns.js +117 -0
- package/src/utils/displayScaleHelper.js +47 -0
- package/src/utils/effectiveModel.js +13 -0
- package/src/utils/entry-slim.js +563 -0
- package/src/utils/entryCache.js +144 -0
- package/src/utils/errorReport.js +6 -0
- package/src/utils/fileExpandedPathsStorage.js +70 -0
- package/src/utils/fileIcons.jsx +36 -0
- package/src/utils/fileOpen.js +37 -0
- package/src/utils/formatters.js +48 -0
- package/src/utils/gitApi.js +50 -0
- package/src/utils/gitTreeBuilder.js +19 -0
- package/src/utils/helpers.js +846 -0
- package/src/utils/identityHeal.js +99 -0
- package/src/utils/imConnState.js +59 -0
- package/src/utils/imOrigin.js +24 -0
- package/src/utils/imTr.js +7 -0
- package/src/utils/imageCompress.js +83 -0
- package/src/utils/imageDownscale.js +28 -0
- package/src/utils/imageResize.js +112 -0
- package/src/utils/ingestPipeline.js +47 -0
- package/src/utils/lazyWithReload.js +53 -0
- package/src/utils/markdown.js +30 -0
- package/src/utils/markdownIncremental.js +39 -0
- package/src/utils/markdownProfiler.js +113 -0
- package/src/utils/mdExtensionDetect.js +64 -0
- package/src/utils/memoryLinkParser.js +44 -0
- package/src/utils/modalMask.js +15 -0
- package/src/utils/monotime.js +6 -0
- package/src/utils/pinnedMenu.js +55 -0
- package/src/utils/presetShortcuts.js +14 -0
- package/src/utils/projectAlias.js +135 -0
- package/src/utils/promptClassifier.js +112 -0
- package/src/utils/promptDetect.js +196 -0
- package/src/utils/promptNav.js +60 -0
- package/src/utils/ptyChunkBuilder.js +251 -0
- package/src/utils/quickMenuHoverIntent.js +35 -0
- package/src/utils/rateLimitParser.js +119 -0
- package/src/utils/readResultPool.js +106 -0
- package/src/utils/refreshCachedItemProp.js +30 -0
- package/src/utils/requestType.js +208 -0
- package/src/utils/resizeCalc.js +21 -0
- package/src/utils/resolveLocalized.js +17 -0
- package/src/utils/searchApi.js +47 -0
- package/src/utils/searchReplace.js +51 -0
- package/src/utils/seqResourceLoaders.js +79 -0
- package/src/utils/sessionManager.js +471 -0
- package/src/utils/sessionMerge.js +180 -0
- package/src/utils/skillModalController.js +111 -0
- package/src/utils/skillsParser.js +116 -0
- package/src/utils/slashCommandLabels.js +80 -0
- package/src/utils/splitDragCalc.js +73 -0
- package/src/utils/stickyBottomController.js +643 -0
- package/src/utils/svgSanitize.js +48 -0
- package/src/utils/systemTags.js +47 -0
- package/src/utils/tClaude.js +20 -0
- package/src/utils/teamModalBuilder.js +351 -0
- package/src/utils/teamSessionParser.js +190 -0
- package/src/utils/teammateAvatars.js +194 -0
- package/src/utils/teammateDetector.js +121 -0
- package/src/utils/termDiag.js +114 -0
- package/src/utils/terminalClipboard.js +117 -0
- package/src/utils/terminalWriteQueue.js +398 -0
- package/src/utils/thinkingMerge.js +49 -0
- package/src/utils/toolCatalog.js +29 -0
- package/src/utils/toolResultBuilder.js +393 -0
- package/src/utils/toolResultClassifier.js +16 -0
- package/src/utils/toolResultCore.js +167 -0
- package/src/utils/toolsDiff.js +44 -0
- package/src/utils/toolsXmlFormatter.js +3 -0
- package/src/utils/ultraplanController.js +109 -0
- package/src/utils/ultraplanExperts.js +86 -0
- package/src/utils/ultraplanTemplates.js +154 -0
- package/src/utils/userImageRefs.js +49 -0
- package/src/utils/v3Assembler.js +112 -0
- package/src/utils/v3Rows.js +48 -0
- package/src/utils/voicePackPlayer.js +233 -0
- package/src/utils/webSearchGrouping.js +111 -0
- package/src/utils/workflowFormat.js +52 -0
- package/src/utils/workflowRuns.js +118 -0
- package/src/utils/workflowStore.js +94 -0
- package/dist/assets/App-DTkacfRW.js +0 -2
- package/dist/assets/index-Ac8vnV3v.js +0 -2
- package/dist/assets/seqResourceLoaders-Cd_TAXTb.js +0 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// UltraPlan 纯逻辑控制器(从 ChatView / TerminalPanel 各自抽出的重复方法收口为一份)。
|
|
2
|
+
//
|
|
3
|
+
// 依赖注入的纯逻辑类,不依赖 antd / React / DOM 之外的东西,可直接在 node:test 下 import。
|
|
4
|
+
// host 适配器把宿主组件的 state / props / 上传 / 提示 / 关闭编辑器桥接进来;宿主的 6 个
|
|
5
|
+
// ultraplan 方法退化为一行委托。state 仍留在宿主 component.state,行为不变。
|
|
6
|
+
//
|
|
7
|
+
// host 接口:
|
|
8
|
+
// getState() → 宿主 this.state(读 ultraplanFiles / customUltraplanExperts / ultraplanVariant)
|
|
9
|
+
// setState(updater) → 转发宿主 this.setState,原样透传对象式与 functional updater
|
|
10
|
+
// onUpdatePreferences(p) → 包 this.props.onUpdatePreferences?.(p)(props 无该回调则不调)
|
|
11
|
+
// uploadFile(file) → 包 uploadFileAndGetPath(file)(注入以避免 import TerminalPanel 形成循环依赖)
|
|
12
|
+
// messageError(msg) → 包 antd message.error(msg)(注入以保持本模块 antd-free)
|
|
13
|
+
// closeEditor() → 包宿主各自的 closeCustomUltraplanEditor()(两边写不同 state 字段,故留宿主)
|
|
14
|
+
|
|
15
|
+
import { visibleExpertKeys } from './ultraplanExperts.js';
|
|
16
|
+
|
|
17
|
+
export class UltraplanController {
|
|
18
|
+
constructor(host) {
|
|
19
|
+
this.host = host;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleUpload = () => {
|
|
23
|
+
const input = document.createElement('input');
|
|
24
|
+
input.type = 'file';
|
|
25
|
+
input.onchange = async (e) => {
|
|
26
|
+
const file = e.target.files?.[0];
|
|
27
|
+
if (!file) return;
|
|
28
|
+
try {
|
|
29
|
+
const path = await this.host.uploadFile(file);
|
|
30
|
+
this.host.setState(prev => ({
|
|
31
|
+
ultraplanFiles: [...prev.ultraplanFiles, { name: file.name, path }],
|
|
32
|
+
}));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error('Ultraplan upload failed:', err);
|
|
35
|
+
this.host.messageError(err?.message || 'Upload failed');
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
input.click();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
handlePaste = async (e) => {
|
|
42
|
+
const items = e.clipboardData?.items;
|
|
43
|
+
if (!items) return;
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
if (item.type.startsWith('image/')) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const file = item.getAsFile();
|
|
48
|
+
if (!file) return;
|
|
49
|
+
try {
|
|
50
|
+
const path = await this.host.uploadFile(file);
|
|
51
|
+
const name = file.name || `paste-${Date.now()}.png`;
|
|
52
|
+
this.host.setState(prev => ({
|
|
53
|
+
ultraplanFiles: [...prev.ultraplanFiles, { name, path }],
|
|
54
|
+
}));
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('Ultraplan paste upload failed:', err);
|
|
57
|
+
this.host.messageError(err?.message || 'Upload failed');
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
handleRemoveFile = (idx) => {
|
|
65
|
+
this.host.setState(prev => ({
|
|
66
|
+
ultraplanFiles: prev.ultraplanFiles.filter((_, i) => i !== idx),
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
persistExperts = (experts) => {
|
|
71
|
+
this.host.setState({ customUltraplanExperts: experts });
|
|
72
|
+
this.host.onUpdatePreferences({ customUltraplanExperts: experts });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
saveExpert = (item) => {
|
|
76
|
+
const existing = this.host.getState().customUltraplanExperts;
|
|
77
|
+
const idx = existing.findIndex(e => e.id === item.id);
|
|
78
|
+
const next = idx >= 0
|
|
79
|
+
? existing.map(e => (e.id === item.id ? item : e))
|
|
80
|
+
: [...existing, item];
|
|
81
|
+
this.persistExperts(next);
|
|
82
|
+
this.host.closeEditor();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
deleteExpert = (id) => {
|
|
86
|
+
const st = this.host.getState();
|
|
87
|
+
const next = st.customUltraplanExperts.filter(e => e.id !== id);
|
|
88
|
+
this.persistExperts(next);
|
|
89
|
+
// 如果当前选中的就是被删的,回退到首个可见专家(无可见则 codeExpert)
|
|
90
|
+
if (st.ultraplanVariant === 'custom:' + id) {
|
|
91
|
+
const visible = visibleExpertKeys(next, st.ultraplanExpertOrder, st.ultraplanExpertHidden);
|
|
92
|
+
this.host.setState({ ultraplanVariant: visible[0] || 'codeExpert' });
|
|
93
|
+
}
|
|
94
|
+
this.host.closeEditor();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 「管理专家」弹窗的显隐 / 排序落盘:写 state + 同步 preferences(多端/刷新一致)。
|
|
98
|
+
// 若当前选中变体因隐藏而不再可见,回退到首个可见键(无可见则 codeExpert)。
|
|
99
|
+
persistExpertLayout = ({ order, hidden }) => {
|
|
100
|
+
const st = this.host.getState();
|
|
101
|
+
const patch = { ultraplanExpertOrder: order, ultraplanExpertHidden: hidden };
|
|
102
|
+
const visible = visibleExpertKeys(st.customUltraplanExperts, order, hidden);
|
|
103
|
+
if (!visible.includes(st.ultraplanVariant)) {
|
|
104
|
+
patch.ultraplanVariant = visible[0] || 'codeExpert';
|
|
105
|
+
}
|
|
106
|
+
this.host.setState(patch);
|
|
107
|
+
this.host.onUpdatePreferences({ ultraplanExpertOrder: order, ultraplanExpertHidden: hidden });
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// UltraPlan「专家」列表的纯逻辑:把内置专家(codeExpert / researchExpert)与用户自定义专家
|
|
2
|
+
// (customUltraplanExperts)合成一份「有序 + 可显隐」的统一列表,供 PC popover、移动端弹窗、
|
|
3
|
+
// 以及「管理专家」弹窗共用同一份真源。无 React / antd / i18n 依赖,可直接在 node:test 下 import。
|
|
4
|
+
//
|
|
5
|
+
// 变体键(key)约定(与发送侧 TerminalPanel 的解析一致,保持稳定):
|
|
6
|
+
// · 内置:'codeExpert' / 'researchExpert'
|
|
7
|
+
// · 自定义:'custom:' + item.id
|
|
8
|
+
//
|
|
9
|
+
// 持久化(preferences.json):
|
|
10
|
+
// · ultraplanExpertOrder: string[] —— 全量有序键;缺省=自然序。
|
|
11
|
+
// · ultraplanExpertHidden: string[] —— 被隐藏的键;缺省=全部可见。
|
|
12
|
+
// 二者都按「当前 customExperts」重算:指向已删除自定义专家的陈旧键一律忽略,绝不抛错。
|
|
13
|
+
|
|
14
|
+
// 内置专家键,固定排在自然序最前。
|
|
15
|
+
export const BUILTIN_EXPERT_KEYS = ['codeExpert', 'researchExpert'];
|
|
16
|
+
|
|
17
|
+
function safeCustomList(customExperts) {
|
|
18
|
+
return Array.isArray(customExperts) ? customExperts.filter(e => e && e.id != null) : [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 自然序:内置在前,自定义按其数组顺序在后。
|
|
22
|
+
export function naturalExpertKeys(customExperts) {
|
|
23
|
+
return [
|
|
24
|
+
...BUILTIN_EXPERT_KEYS,
|
|
25
|
+
...safeCustomList(customExperts).map(e => 'custom:' + e.id),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 有效有序键:先取 order 中「仍存在」的键(去重),再把剩余自然序键追加到末尾。
|
|
30
|
+
// → 新建/未知键自动落到末尾;order 里的陈旧键被过滤。
|
|
31
|
+
export function orderedExpertKeys(customExperts, order) {
|
|
32
|
+
const natural = naturalExpertKeys(customExperts);
|
|
33
|
+
const naturalSet = new Set(natural);
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const known = [];
|
|
36
|
+
for (const k of Array.isArray(order) ? order : []) {
|
|
37
|
+
if (naturalSet.has(k) && !seen.has(k)) { seen.add(k); known.push(k); }
|
|
38
|
+
}
|
|
39
|
+
for (const k of natural) {
|
|
40
|
+
if (!seen.has(k)) { seen.add(k); known.push(k); }
|
|
41
|
+
}
|
|
42
|
+
return known;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 有序描述符列表,供渲染:{ key, kind:'builtin'|'custom', item|null, hidden:boolean }。
|
|
46
|
+
export function buildExpertList(customExperts, order, hidden) {
|
|
47
|
+
const hiddenSet = new Set(Array.isArray(hidden) ? hidden : []);
|
|
48
|
+
const byKey = new Map(safeCustomList(customExperts).map(e => ['custom:' + e.id, e]));
|
|
49
|
+
return orderedExpertKeys(customExperts, order).map(key => {
|
|
50
|
+
const kind = key.startsWith('custom:') ? 'custom' : 'builtin';
|
|
51
|
+
return {
|
|
52
|
+
key,
|
|
53
|
+
kind,
|
|
54
|
+
item: kind === 'custom' ? (byKey.get(key) || null) : null,
|
|
55
|
+
hidden: hiddenSet.has(key),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 可见键(过滤掉 hidden 的有序键),即真正出现在 tab 条上的专家。
|
|
61
|
+
export function visibleExpertKeys(customExperts, order, hidden) {
|
|
62
|
+
return buildExpertList(customExperts, order, hidden)
|
|
63
|
+
.filter(d => !d.hidden)
|
|
64
|
+
.map(d => d.key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 拖拽排序:把 keys[from] 移动到「落点行」下标 to 之前,返回新数组(纯函数,不改原数组)。
|
|
68
|
+
// to 是被拖到其位置那一行的当前下标;移除 from 后右侧整体左移一位,故 from<to 时落点修正为 to-1。
|
|
69
|
+
// from === to / 越界 / from==null 视为无变化,返回原数组的拷贝。
|
|
70
|
+
export function reorderKeys(keys, from, to) {
|
|
71
|
+
const arr = Array.isArray(keys) ? [...keys] : [];
|
|
72
|
+
if (from == null || to == null || from < 0 || from >= arr.length || from === to) return arr;
|
|
73
|
+
const [moved] = arr.splice(from, 1);
|
|
74
|
+
arr.splice(from < to ? to - 1 : to, 0, moved);
|
|
75
|
+
return arr;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 当前可见(未隐藏)专家数。list = buildExpertList(...) 的结果。
|
|
79
|
+
export function countVisible(list) {
|
|
80
|
+
return (Array.isArray(list) ? list : []).filter(d => d && !d.hidden).length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 是否还能再隐藏一个:隐藏后仍至少保留 1 个可见专家才允许(管理弹窗护栏)。
|
|
84
|
+
export function canHideOne(list) {
|
|
85
|
+
return countVisible(list) > 1;
|
|
86
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ultraplan prompt templates for local Claude Code execution.
|
|
3
|
+
*
|
|
4
|
+
* Two expert roles:
|
|
5
|
+
* - codeExpert: Multi-agent code planning & implementation (from subagents template)
|
|
6
|
+
* - researchExpert: Multi-agent research & analysis
|
|
7
|
+
*
|
|
8
|
+
* Assembly logic mirrors ~/claude-code/commands/ultraplan.tsx:63-73
|
|
9
|
+
* buildUltraplanPrompt(blurb, seedPlan?)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const ULTRAPLAN_VARIANTS = {
|
|
13
|
+
|
|
14
|
+
codeExpert: `<system-reminder>
|
|
15
|
+
[SCOPED INSTRUCTION] The following instructions apply only to the next 1–3 interactions. Once the task is complete, these instructions should gradually decrease in priority and no longer affect subsequent interactions. You should be adept at utilizing tools such as \`AskUserQuestion\`, \`EnterPlanMode\`, and \`TeamCreate\`, rather than relying solely on plain text processing. Before execution, you must ensure that the \`EnterPlanMode\`, \`ExitPlanMode\`, \`SendMessage\`, \`TaskCreate\`, \`TaskUpdate\`, \`TaskStop\`, \`TaskGet\`, \`TaskOutput\` and \`TaskList\` tools are loaded.
|
|
16
|
+
|
|
17
|
+
Pre-requisite: Use \`AskUserQuestion\` to clarify user intent whenever the request is ambiguous (target element, interaction style, scope of platforms, etc.). Skip only if the intent is unambiguous.
|
|
18
|
+
|
|
19
|
+
Leverage a multi-agent exploration mechanism to formulate a highly detailed implementation plan.
|
|
20
|
+
|
|
21
|
+
Instructions:
|
|
22
|
+
1. Use the \`Agent\` tool to spawn parallel agents that simultaneously explore different aspects of the codebase:
|
|
23
|
+
- If necessary, assign a preliminary researcher to use the \`webSearch\` tool to first investigate cutting-edge solutions in the relevant industry domain;
|
|
24
|
+
- One agent responsible for understanding the relevant existing code and architecture;
|
|
25
|
+
- One agent responsible for identifying all files that need to be modified;
|
|
26
|
+
- One agent responsible for identifying potential risks, edge cases, and dependencies;
|
|
27
|
+
- You may add other roles or deploy additional agents beyond the three listed above; the maximum number of concurrently dispatched agents is 5.
|
|
28
|
+
|
|
29
|
+
2. Synthesize the findings from all agents into a detailed, step-by-step implementation plan.
|
|
30
|
+
|
|
31
|
+
3. Use the \`Agent\` tool to spawn 2-3 review agents that examine the plan from different perspectives, checking for missing steps, potential risks, or corresponding mitigation strategies.
|
|
32
|
+
|
|
33
|
+
4. Integrate the feedback gathered during the review process, then call \`ExitPlanMode\` to submit your final plan.
|
|
34
|
+
|
|
35
|
+
5. Once \`ExitPlanMode\` returns a result:
|
|
36
|
+
- If approved: proceed to execute the plan within this session.
|
|
37
|
+
- If rejected: revise the plan based on the feedback provided and call \`ExitPlanMode\` again.
|
|
38
|
+
- If an error occurs (including receiving a "Not in Plan Mode" message): do **not** follow the suggestions provided in the error message; instead, prompt the user for further instructions.
|
|
39
|
+
|
|
40
|
+
Your final plan must include the following elements:
|
|
41
|
+
- A clear summary of the implementation strategy;
|
|
42
|
+
- An ordered list of files to be created or modified, with precise details of the required changes for each file;
|
|
43
|
+
- A step-by-step execution sequence;
|
|
44
|
+
- Testing and validation procedures;
|
|
45
|
+
- Potential risks and their corresponding mitigation strategies;
|
|
46
|
+
|
|
47
|
+
6. After the final plan has been successfully executed:
|
|
48
|
+
First run \`git diff --quiet && git diff --cached --quiet\` (or equivalent) to detect whether the working tree actually has non-trivial changes; if there are no real changes (or only whitespace/comment-only edits), skip the UltraReview step.
|
|
49
|
+
Otherwise, if the project is managed with Git:
|
|
50
|
+
Initiate a team (\`TeamCreate\`), dynamically allocating the number of teammates based on task complexity (5 is recommended);
|
|
51
|
+
If the \`TeamCreate\` tool is unavailable, spawn teammates directly with the \`Agent\` tool (note: as teammates, not plain subagents);
|
|
52
|
+
Task: Conduct a Code Review of the current git changes from multiple perspectives;
|
|
53
|
+
Pre-requisites:
|
|
54
|
+
- The git repository may be located in a subdirectory of the current directory; prefer \`git rev-parse --show-toplevel\` (fall back to recursive lookup) before proceeding;
|
|
55
|
+
- In the case of multiple repositories, tasks may be executed separately;
|
|
56
|
+
The team's goal is to analyze the current Git change log and validate each modification from different perspectives, specifically including:
|
|
57
|
+
- Whether requirements/objectives have been met and functionality is complete;
|
|
58
|
+
- Whether newly added code introduces side effects, breaks existing functionality, or poses potential risks;
|
|
59
|
+
- Code quality: naming, readability, complexity, technical debt, maintainability;
|
|
60
|
+
- Testing and documentation: whether there is adequate test coverage, and whether critical logic has necessary comments or documentation;
|
|
61
|
+
- Dependencies and compatibility: whether new dependencies or version compatibility issues have been introduced;
|
|
62
|
+
Workflow:
|
|
63
|
+
- Each teammate, according to their own role, covers the review dimensions one by one and independently outputs a report;
|
|
64
|
+
- After consolidating the reports, perform a cross-review to identify conflicts or shared concerns;
|
|
65
|
+
- Distill specific, actionable modification suggestions and annotate them with priority levels (P0/P1/P2/P3);
|
|
66
|
+
- Upon completion, adopt P0 items, and selectively adopt P1 items when they are concrete and low-risk; defer P2/P3 to backlog;
|
|
67
|
+
- After execution is complete, close the team (\`TeamDelete\`);
|
|
68
|
+
- If the \`TeamDelete\` tool is unavailable, notify each teammate to exit individually;
|
|
69
|
+
</system-reminder>`,
|
|
70
|
+
|
|
71
|
+
researchExpert: `<system-reminder>
|
|
72
|
+
[SCOPED INSTRUCTION] The following instructions are intended for the next 1–3 interactions. Once the task is complete, these instructions should be gradually deprioritized and no longer influence subsequent interactions.You should be adept at utilizing tools such as \`AskUserQuestion\`, \`EnterPlanMode\` and \`WebSearch\`, rather than relying solely on plain text processing. Before execution, you must ensure that the \`EnterPlanMode\`, \`ExitPlanMode\`, \`TaskCreate\`, \`TaskUpdate\`, \`TaskStop\`, \`TaskGet\`, \`TaskOutput\` and \`TaskList\` tools are loaded.
|
|
73
|
+
|
|
74
|
+
Pre-requisite: Use \`AskUserQuestion\` to clarify the research scope, target audience, and deliverable format whenever the user's intent is ambiguous. Skip only if the intent is unambiguous.
|
|
75
|
+
|
|
76
|
+
Leverage a multi-agent exploration mechanism to formulate an exceptionally detailed implementation plan.
|
|
77
|
+
|
|
78
|
+
Instructions:
|
|
79
|
+
1. Utilize the Agent tool to spawn parallel agents that simultaneously explore various facets of the requirements:
|
|
80
|
+
- If necessary, deploy a preliminary investigator to conduct an initial survey of industry-specific solutions using \`webSearch\`;
|
|
81
|
+
- If necessary, deploy a specialized investigator to research authoritative sources—such as academic papers, news articles, and research reports—using \`webSearch\`;
|
|
82
|
+
- Assign an agent to synthesize the target solution, while simultaneously verifying the rigor and credibility of the gathered papers, news, and research reports;
|
|
83
|
+
- If necessary, assign an agent to analyze competitor data to provide supplementary analytical perspectives;
|
|
84
|
+
- If necessary, assign an agent to handle the implementation of a product demo (generating outputs such as HTML, Markdown, etc.);
|
|
85
|
+
- If the task is sufficiently complex, you may assign additional teammates to the roles defined above, or introduce other specialized roles; you are permitted to schedule up to 5 teammates concurrently.
|
|
86
|
+
|
|
87
|
+
2. Synthesize the findings from the aforementioned agents into a comprehensive, step-by-step implementation plan.
|
|
88
|
+
|
|
89
|
+
3. Utilize the Agent tool to spawn a set of parallel review agents; these agents shall scrutinize the plan from multiple roles and perspectives to identify any omitted steps and to propose reasonable additions or optimizations.
|
|
90
|
+
|
|
91
|
+
4. Consolidate the feedback received from the review agents, then invoke \`ExitPlanMode\` to submit your final plan.
|
|
92
|
+
|
|
93
|
+
5. Upon receiving the result from \`ExitPlanMode\`:
|
|
94
|
+
- If Approved: Proceed to execute the plan within this current session.
|
|
95
|
+
- If Rejected: Revise the plan based on the provided feedback, and then invoke \`ExitPlanMode\` once again.
|
|
96
|
+
- If an Error Occurs (including the message "Not in Plan Mode"): Do *not* follow the suggestions provided by the error message; instead, prompt the user for further instructions.
|
|
97
|
+
|
|
98
|
+
Your final plan must include the following elements:
|
|
99
|
+
- A clear summary of the proposed implementation strategy;
|
|
100
|
+
- An ordered list of files to be created or modified, specifying the exact changes required for each;
|
|
101
|
+
- A step-by-step sequence for executing the implementation;
|
|
102
|
+
- Identification of potential risks and corresponding mitigation strategies;
|
|
103
|
+
- Creative ideation and suggestions for advanced enhancements;
|
|
104
|
+
- If a product demo was generated, place the corresponding demo output in an appropriate location and notify the user.
|
|
105
|
+
</system-reminder>`,
|
|
106
|
+
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Wrap user-authored custom instruction body with the same scoped-instruction
|
|
111
|
+
* preamble used by the built-in variants. Produces a full <system-reminder>
|
|
112
|
+
* block ready to be inlined into a Claude Code prompt.
|
|
113
|
+
*/
|
|
114
|
+
export function buildCustomTemplate(content) {
|
|
115
|
+
const body = (content || '').trim();
|
|
116
|
+
if (!body) return '';
|
|
117
|
+
// 用户已自带外壳(预填的样板壳或手写)时不再重复包裹;用 startsWith 而非 includes,
|
|
118
|
+
// 避免正文里只是「提到」<system-reminder> 字样就误判而漏掉作用域声明。
|
|
119
|
+
if (body.startsWith('<system-reminder>')) return body;
|
|
120
|
+
return `<system-reminder>
|
|
121
|
+
[SCOPED INSTRUCTION] The following instructions apply only to the next 1–3 interactions. Once the task is complete, these instructions should gradually decrease in priority and no longer affect subsequent interactions. You should be adept at utilizing tools such as \`AskUserQuestion\`, \`EnterPlanMode\`, and \`TeamCreate\`, rather than relying solely on plain text processing.
|
|
122
|
+
|
|
123
|
+
${body}
|
|
124
|
+
</system-reminder>`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Assemble a local ultraplan prompt.
|
|
129
|
+
* Mirrors ~/claude-code/commands/ultraplan.tsx:63-73 buildUltraplanPrompt()
|
|
130
|
+
*
|
|
131
|
+
* @param {string} userPrompt - User's task description
|
|
132
|
+
* @param {'codeExpert'|'researchExpert'|'custom'} variant - Template variant
|
|
133
|
+
* @param {string} [seedPlan] - Optional draft plan to refine
|
|
134
|
+
* @param {string} [customContent] - Required when variant === 'custom': the user-authored body
|
|
135
|
+
* @returns {string} Assembled prompt ready to send to Claude Code
|
|
136
|
+
*/
|
|
137
|
+
export function buildLocalUltraplan(userPrompt, variant = 'codeExpert', seedPlan, customContent) {
|
|
138
|
+
let template;
|
|
139
|
+
if (variant === 'custom') {
|
|
140
|
+
template = buildCustomTemplate(customContent);
|
|
141
|
+
if (!template) return '';
|
|
142
|
+
} else {
|
|
143
|
+
template = ULTRAPLAN_VARIANTS[variant] || ULTRAPLAN_VARIANTS.codeExpert;
|
|
144
|
+
}
|
|
145
|
+
const parts = [];
|
|
146
|
+
if (seedPlan) {
|
|
147
|
+
parts.push('Here is a draft plan to refine:', '', seedPlan, '');
|
|
148
|
+
}
|
|
149
|
+
parts.push(template);
|
|
150
|
+
if (userPrompt) {
|
|
151
|
+
parts.push('', userPrompt);
|
|
152
|
+
}
|
|
153
|
+
return parts.join('\n');
|
|
154
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// 从用户消息文本里提取「应渲染为图片」的引用,供 ChatMessage 把路径换成 <img>。
|
|
2
|
+
//
|
|
3
|
+
// 识别三种写法(均需以图片扩展名结尾才算数):
|
|
4
|
+
// 1. [Image: source: /path/to/x.png] 或 [Image #N: …] —— Claude CLI 的占位写法
|
|
5
|
+
// 2. "/tmp/cc-viewer-uploads/x.png" —— 引号包裹的上传路径(拖拽/合成器流程)
|
|
6
|
+
// 3. /tmp/cc-viewer-uploads/x.png —— 裸路径(终端粘贴流程直接拼进提示词,无引号)
|
|
7
|
+
//
|
|
8
|
+
// 第 3 种是历史缺口:终端粘贴时路径被原样拼到文本里、没有引号,旧正则只认引号包裹的写法,
|
|
9
|
+
// 于是这类图片只显示成纯文本路径。上传目录前缀(/tmp/cc-viewer-uploads/ 及 macOS realpath
|
|
10
|
+
// 解析出的 /private 变体)足够特异,去掉引号要求不会误伤正常文案。
|
|
11
|
+
|
|
12
|
+
// 路径必须以图片扩展名结尾才渲染(与服务端 /api/file-raw 支持的类型一致)。
|
|
13
|
+
export const IMAGE_EXTS = /\.(?:png|jpe?g|gif|webp|avif|svg|bmp|ico|icns)$/i;
|
|
14
|
+
|
|
15
|
+
// 上传目录前缀:/tmp/cc-viewer-uploads/ 与 macOS 下 realpath 解析出的 /private/tmp/... 两种。
|
|
16
|
+
const UPLOAD = String.raw`(?:\/private)?\/tmp\/cc-viewer-uploads\/`;
|
|
17
|
+
const EXT = String.raw`(?:png|jpe?g|gif|webp|avif|svg|bmp|ico|icns)`;
|
|
18
|
+
|
|
19
|
+
// 三选一(顺序有意义:引号分支在裸路径分支之前,确保引号被一并吃掉,不会残留成文本):
|
|
20
|
+
// group1 = [Image …] 内的路径;group2 = 双引号路径;group3 = 单引号路径;group4 = 裸上传路径。
|
|
21
|
+
// 引号分支(双/单)都排在裸路径分支之前,确保整对引号被一并吃掉,不残留成文本。
|
|
22
|
+
const PATTERN =
|
|
23
|
+
String.raw`\[Image(?:\s*#\d+)?(?::?\s*source)?:\s*([^\]]+)\]` +
|
|
24
|
+
String.raw`|"(${UPLOAD}[^"]+?)"` +
|
|
25
|
+
String.raw`|'(${UPLOAD}[^']+?)'` +
|
|
26
|
+
String.raw`|(${UPLOAD}[^\s"'\]\)]+?\.${EXT})`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 扫描文本,返回按出现顺序排列的图片引用。
|
|
30
|
+
* @param {string} text
|
|
31
|
+
* @returns {Array<{ path: string, raw: string, index: number }>}
|
|
32
|
+
* path = 用于请求的文件路径(已 trim)
|
|
33
|
+
* raw = 命中的原始子串(用于 fallback 文案,以及计算文本切片边界)
|
|
34
|
+
* index = raw 在 text 中的起始下标
|
|
35
|
+
*/
|
|
36
|
+
export function findUserImageRefs(text) {
|
|
37
|
+
if (!text || typeof text !== 'string') return [];
|
|
38
|
+
// 每次新建正则,避免共享 lastIndex 带来的跨调用状态污染。
|
|
39
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
40
|
+
const refs = [];
|
|
41
|
+
let m;
|
|
42
|
+
while ((m = re.exec(text)) !== null) {
|
|
43
|
+
if (m[0] === '') { re.lastIndex++; continue; } // 防御零宽匹配死循环
|
|
44
|
+
const path = (m[1] || m[2] || m[3] || m[4] || '').trim();
|
|
45
|
+
if (!path || !IMAGE_EXTS.test(path)) continue;
|
|
46
|
+
refs.push({ path, raw: m[0], index: m.index });
|
|
47
|
+
}
|
|
48
|
+
return refs;
|
|
49
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire v3 (V3.S5) — client-side entry assembler.
|
|
3
|
+
*
|
|
4
|
+
* Rebuilds v1-shape entries from the native wire (metadata rows + raw conv
|
|
5
|
+
* events + raw responses lines) and feeds them to the EXISTING ingest
|
|
6
|
+
* pipeline, so every downstream consumer (sessionMerge guards, ChatView,
|
|
7
|
+
* team modal, tool-result maps) keeps its exact semantics. Entries are built
|
|
8
|
+
* in the shape today's live wire delivers AFTER server-side reconstruction:
|
|
9
|
+
* full accumulated messages (shared references — cheap), no tools/system
|
|
10
|
+
* (rows carry classification; the detail view fetches full bodies on demand).
|
|
11
|
+
*
|
|
12
|
+
* Conv replay mirrors server/lib/v2/conversation-store.js line shapes:
|
|
13
|
+
* {t:'snapshot', msgs} | {t:'append', msgs} | {t:'ctl', op:'replace-tail', msg}
|
|
14
|
+
* | {t:'ctl', op:'compact'} (state no-op; the paired snapshot carries state)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Per-channel replayed state, keyed `${sessionId}\x00${channel}`. */
|
|
18
|
+
export function createV3Assembler() {
|
|
19
|
+
const channels = new Map(); // key → { state: [], applied: [], ptr }
|
|
20
|
+
const resps = new Map(); // `${sessionId}\x00${seq}` → responses line
|
|
21
|
+
|
|
22
|
+
const chan = (sessionId, channel) => {
|
|
23
|
+
const k = `${sessionId}\x00${channel}`;
|
|
24
|
+
let c = channels.get(k);
|
|
25
|
+
if (!c) { c = { state: [], events: [], ptr: 0 }; channels.set(k, c); }
|
|
26
|
+
return c;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const applyEvent = (c, ev) => {
|
|
30
|
+
if (!ev || !ev.t) return;
|
|
31
|
+
if (ev.t === 'snapshot') {
|
|
32
|
+
c.state = Array.isArray(ev.msgs) ? ev.msgs.slice() : [];
|
|
33
|
+
} else if (ev.t === 'append') {
|
|
34
|
+
if (Array.isArray(ev.msgs)) c.state.push(...ev.msgs);
|
|
35
|
+
else if (ev.msg) c.state.push(ev.msg);
|
|
36
|
+
} else if (ev.t === 'ctl' && ev.op === 'replace-tail') {
|
|
37
|
+
if (c.state.length > 0 && ev.msg) c.state[c.state.length - 1] = ev.msg;
|
|
38
|
+
} // ctl compact: state no-op (its snapshot line carries the state)
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
/** Buffer conv events (cold frames carry arrays; live frames one line). */
|
|
43
|
+
addConvLines(sessionId, channel, lines) {
|
|
44
|
+
const c = chan(sessionId, channel);
|
|
45
|
+
for (const ev of Array.isArray(lines) ? lines : [lines]) c.events.push(ev);
|
|
46
|
+
},
|
|
47
|
+
addRespLines(sessionId, lines) {
|
|
48
|
+
for (const line of Array.isArray(lines) ? lines : [lines]) {
|
|
49
|
+
if (line && Number.isInteger(line.seq)) resps.set(`${sessionId}\x00${line.seq}`, line);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the v1-shape entry for one row. Applies the row's channel events
|
|
55
|
+
* up to and including row.seq (events are seq-ordered per channel), then
|
|
56
|
+
* copies the state by reference. Rows MUST be fed in per-session seq
|
|
57
|
+
* order (the wire already delivers them so).
|
|
58
|
+
*/
|
|
59
|
+
buildEntry(row) {
|
|
60
|
+
let messages = [];
|
|
61
|
+
let isSnapshot = false;
|
|
62
|
+
if (row.conv) {
|
|
63
|
+
const c = chan(row.sessionId, row.conv);
|
|
64
|
+
while (c.ptr < c.events.length && c.events[c.ptr].seq <= row.seq) {
|
|
65
|
+
applyEvent(c, c.events[c.ptr++]);
|
|
66
|
+
}
|
|
67
|
+
messages = c.state.slice(); // refs shared with the channel state
|
|
68
|
+
isSnapshot = row.evt === 'snapshot';
|
|
69
|
+
}
|
|
70
|
+
const entry = {
|
|
71
|
+
timestamp: row.timestamp,
|
|
72
|
+
url: row.url,
|
|
73
|
+
method: row.method || 'POST',
|
|
74
|
+
body: { model: row.model, messages },
|
|
75
|
+
mainAgent: row.mainAgent === true,
|
|
76
|
+
isHeartbeat: row.kind === 'heartbeat',
|
|
77
|
+
isCountTokens: row.kind === 'countTokens',
|
|
78
|
+
duration: row.duration,
|
|
79
|
+
_seq: row.seq,
|
|
80
|
+
_seqEpoch: `v2:${row.sessionId}`,
|
|
81
|
+
_totalMessageCount: messages.length,
|
|
82
|
+
_v3Assembled: true,
|
|
83
|
+
};
|
|
84
|
+
if (row.teammate) entry.teammate = row.teammate;
|
|
85
|
+
if (row.proxyUrl) entry.proxyUrl = row.proxyUrl;
|
|
86
|
+
if (isSnapshot) entry._isCheckpoint = true;
|
|
87
|
+
if (row.inProgress) {
|
|
88
|
+
entry.inProgress = true;
|
|
89
|
+
entry.requestId = `${row.sessionId}-${row.seq}`;
|
|
90
|
+
} else {
|
|
91
|
+
const resp = resps.get(`${row.sessionId}\x00${row.seq}`);
|
|
92
|
+
entry.response = {
|
|
93
|
+
...(row.status != null && { status: row.status }),
|
|
94
|
+
...(resp && resp.headers && { headers: resp.headers }),
|
|
95
|
+
body: resp ? resp.body : null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return entry;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/** Cold path: build the whole window (rows in wire order). */
|
|
102
|
+
buildColdEntries(rows) {
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
try { out.push(this.buildEntry(row)); } catch { /* torn row: skip, next since-read heals */ }
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
reset() { channels.clear(); resps.clear(); },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire v3 (V3.S3a) — metadata-row adapters for the request list.
|
|
3
|
+
*
|
|
4
|
+
* A row is adapted into the minimal entry-shaped object the existing list
|
|
5
|
+
* pipeline reads (visibleRequests relevance filter, RequestList row fields,
|
|
6
|
+
* selection helpers), so the flagged path swaps the DATA SOURCE instead of
|
|
7
|
+
* forking every consumer. Deep fields (body.messages/system/tools,
|
|
8
|
+
* response.body.content) are intentionally absent — the detail view fetches
|
|
9
|
+
* them on demand (V3.S3b `/api/v2-entry`), and `_v3Row` marks the shape for
|
|
10
|
+
* the few call sites that must branch.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Adapt one server metadata row to the list-item shape. */
|
|
14
|
+
export function rowToListItem(row) {
|
|
15
|
+
const hasResponse = row.status != null || row.usage;
|
|
16
|
+
return {
|
|
17
|
+
timestamp: row.timestamp,
|
|
18
|
+
url: row.url,
|
|
19
|
+
proxyUrl: row.proxyUrl,
|
|
20
|
+
duration: row.duration,
|
|
21
|
+
inProgress: row.inProgress === true,
|
|
22
|
+
isHeartbeat: row.kind === 'heartbeat',
|
|
23
|
+
isCountTokens: row.kind === 'countTokens',
|
|
24
|
+
mainAgent: row.mainAgent === true,
|
|
25
|
+
teammate: row.teammate,
|
|
26
|
+
body: { model: row.model },
|
|
27
|
+
response: hasResponse ? { status: row.status ?? 0, body: row.usage ? { usage: row.usage } : {} } : null,
|
|
28
|
+
// detail-fetch identity (V3.S3b): (sessionId, seq) → /api/v2-entry
|
|
29
|
+
_seq: row.seq,
|
|
30
|
+
_seqEpoch: `v2:${row.sessionId}`,
|
|
31
|
+
_v3Row: row,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Type tag for a list item: server-computed on the rows path (falling back to
|
|
37
|
+
* kind-derived coarse type while a live row's classification is pending),
|
|
38
|
+
* client classifyRequest on the legacy path. Returns {type, subType}.
|
|
39
|
+
*/
|
|
40
|
+
export function listItemType(req, nextReq, classifyRequest) {
|
|
41
|
+
const row = req._v3Row;
|
|
42
|
+
if (!row) return classifyRequest(req, nextReq);
|
|
43
|
+
if (row.typeTag) return { type: row.typeTag.type, subType: row.typeTag.subType ?? undefined };
|
|
44
|
+
if (row.mainAgent) return { type: 'MainAgent', subType: undefined };
|
|
45
|
+
if (row.kind === 'teammate') return { type: 'Teammate', subType: undefined };
|
|
46
|
+
if (row.kind === 'countTokens') return { type: 'Count', subType: undefined };
|
|
47
|
+
return { type: 'SubAgent', subType: undefined };
|
|
48
|
+
}
|