codemini-cli 0.2.7 → 0.2.9

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.
@@ -0,0 +1,20 @@
1
+ export function describeSkillActivity(copy, name, { done = false, failed = false } = {}) {
2
+ if (failed) return `${copy.runtime.skillFailed}: /${name}`;
3
+ if (done) return `${copy.toolActivity.doneSkill}: /${name}`;
4
+ return `${copy.toolActivity.doingSkill}: /${name}`;
5
+ }
6
+
7
+ export function describeAutoSkillActivity(copy, names) {
8
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
9
+ if (safeNames.length === 0) return '';
10
+ return copy.runtime.autoSkillInjected(safeNames);
11
+ }
12
+
13
+ export function formatAutoSkillBadge(copy, names) {
14
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
15
+ if (safeNames.length === 0) return '';
16
+ const [first, ...rest] = safeNames;
17
+ const suffix = rest.length > 0 ? ` +${rest.length}` : '';
18
+ const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
19
+ return `${prefix} /${first}${suffix}`;
20
+ }
@@ -0,0 +1,29 @@
1
+ import { classifyCommandIntent } from '../../core/shell.js';
2
+
3
+ export function parseToolDisplayName(name) {
4
+ const raw = String(name || '').trim();
5
+ const match = raw.match(/^([^(]+)\((.*)\)$/);
6
+ return {
7
+ raw,
8
+ base: match ? match[1] : raw,
9
+ target: match ? match[2] : ''
10
+ };
11
+ }
12
+
13
+ export function trimText(text, max = 72) {
14
+ const value = String(text || '').trim();
15
+ if (!value) return '';
16
+ return value.length > max ? `${value.slice(0, Math.max(0, max - 3)).trimEnd()}...` : value;
17
+ }
18
+
19
+ export function makeBlocked(copy, target) {
20
+ return `${copy.toolActivity.blocked}: ${target}`;
21
+ }
22
+
23
+ export function makePhase(copy, doneLabel, doingLabel, target) {
24
+ return target ? `${doneLabel}: ${target}` : doneLabel || doingLabel;
25
+ }
26
+
27
+ export function classifyRunIntent(target) {
28
+ return classifyCommandIntent(target);
29
+ }
@@ -0,0 +1,17 @@
1
+ import { parseToolDisplayName } from './common.js';
2
+ import { describeCommandToolActivity } from './presenters/command.js';
3
+ import { describeFileToolActivity } from './presenters/files.js';
4
+ import { describeMiscToolActivity } from './presenters/misc.js';
5
+ import { describeSystemToolActivity } from './presenters/system.js';
6
+
7
+ export { isCodeGenerationActivityName } from './presenters/misc.js';
8
+
9
+ export function describeToolActivity(copy, name, options = {}) {
10
+ const parsed = parseToolDisplayName(name);
11
+ return (
12
+ describeSystemToolActivity(copy, parsed, options) ||
13
+ describeCommandToolActivity(copy, parsed, options) ||
14
+ describeFileToolActivity(copy, parsed, options) ||
15
+ describeMiscToolActivity(copy, parsed, name, options)
16
+ );
17
+ }
@@ -0,0 +1,29 @@
1
+ import { classifyRunIntent, makeBlocked, trimText } from '../common.js';
2
+
3
+ function phaseText(copy, blocked, done, target, doingLabel, doneLabel) {
4
+ if (blocked) return makeBlocked(copy, target);
5
+ return done ? `${doneLabel}: ${target}` : `${doingLabel}: ${target}`;
6
+ }
7
+
8
+ export function describeCommandToolActivity(copy, parsed, { done = false, blocked = false } = {}) {
9
+ const target = parsed.target || 'command';
10
+ const intent = classifyRunIntent(parsed.target);
11
+
12
+ if (parsed.base === 'run' || parsed.base === 'start_service') {
13
+ if (intent.kind === 'install') return phaseText(copy, blocked, done, target, copy.toolActivity.doingInstall, copy.toolActivity.doneInstall);
14
+ if (intent.kind === 'build') return phaseText(copy, blocked, done, target, copy.toolActivity.doingBuild, copy.toolActivity.doneBuild);
15
+ if (intent.kind === 'test') return phaseText(copy, blocked, done, target, copy.toolActivity.doingTest, copy.toolActivity.doneTest);
16
+ if (intent.kind === 'frontend-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingFrontend, copy.toolActivity.doneFrontend);
17
+ if (intent.kind === 'backend-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingBackend, copy.toolActivity.doneBackend);
18
+ if (intent.kind === 'database-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDatabase, copy.toolActivity.doneDatabase);
19
+ if (intent.kind === 'docker-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDocker, copy.toolActivity.doneDocker);
20
+ if (intent.kind === 'service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
21
+ if (parsed.base === 'run') return phaseText(copy, blocked, done, trimText(target, 72) || parsed.base, copy.toolActivity.doingCommand, copy.toolActivity.doneCommand);
22
+ }
23
+
24
+ if (parsed.base === 'start_service' || parsed.base === 'list_services' || parsed.base === 'get_service_status' || parsed.base === 'get_service_logs' || parsed.base === 'stop_service') {
25
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
26
+ }
27
+
28
+ return '';
29
+ }
@@ -0,0 +1,26 @@
1
+ import { makeBlocked, trimText } from '../common.js';
2
+
3
+ function describePathTool(copy, parsed, labels, { done = false, blocked = false } = {}) {
4
+ const safeTarget = trimText(parsed.target, 72) || '.';
5
+ if (blocked) return makeBlocked(copy, `${parsed.base}(${safeTarget})`);
6
+ return done ? `${labels.done}: ${safeTarget}` : `${labels.doing}: ${safeTarget}`;
7
+ }
8
+
9
+ export function describeFileToolActivity(copy, parsed, options = {}) {
10
+ if (parsed.base === 'read') {
11
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneRead, doing: copy.toolActivity.doingRead }, options);
12
+ }
13
+ if (parsed.base === 'edit') {
14
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneEdit, doing: copy.toolActivity.doingEdit }, options);
15
+ }
16
+ if (parsed.base === 'write') {
17
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneWrite, doing: copy.toolActivity.doingWrite }, options);
18
+ }
19
+ if (parsed.base === 'patch') {
20
+ return describePathTool(copy, parsed, { done: copy.toolActivity.donePatch, doing: copy.toolActivity.doingPatch }, options);
21
+ }
22
+ if (parsed.base === 'list' || parsed.base === 'glob' || parsed.base === 'grep') {
23
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneList, doing: copy.toolActivity.doingList }, options);
24
+ }
25
+ return '';
26
+ }
@@ -0,0 +1,19 @@
1
+ import { makeBlocked } from '../common.js';
2
+
3
+ export function isCodeGenerationActivityName(name) {
4
+ return String(name || '').trim() === 'Code generation';
5
+ }
6
+
7
+ export function describeMiscToolActivity(copy, parsed, rawName, { done = false, blocked = false } = {}) {
8
+ if (isCodeGenerationActivityName(rawName)) {
9
+ if (blocked) return `${copy.toolActivity.blocked}: code generation`;
10
+ return done ? copy.toolActivity.doneCodeGeneration : copy.toolActivity.doingCodeGeneration;
11
+ }
12
+ if (parsed.base === 'create_task') {
13
+ return blocked ? makeBlocked(copy, 'create_task') : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
14
+ }
15
+ if (parsed.base === 'update_task') {
16
+ return blocked ? makeBlocked(copy, 'update_task') : done ? copy.toolActivity.doneUpdateTask : copy.toolActivity.doingUpdateTask;
17
+ }
18
+ return blocked ? `${copy.toolActivity.blocked}: ${parsed.raw}` : done ? `${copy.toolActivity.doneGeneric}: ${parsed.raw}` : `${copy.toolActivity.doingGeneric}: ${parsed.raw}`;
19
+ }
@@ -0,0 +1,14 @@
1
+ import { makeBlocked, trimText } from '../common.js';
2
+
3
+ export function describeSystemToolActivity(copy, parsed, { done = false, blocked = false } = {}) {
4
+ if (parsed.base === 'project_index') {
5
+ if (blocked) return `${copy.toolActivity.blocked}: project index`;
6
+ return done ? copy.toolActivity.doneProjectIndex : copy.toolActivity.doingProjectIndex;
7
+ }
8
+ if (parsed.base === 'file_index') {
9
+ const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
10
+ if (blocked) return makeBlocked(copy, safeTarget);
11
+ return done ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}` : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
12
+ }
13
+ return '';
14
+ }
@@ -0,0 +1,37 @@
1
+ export function trimText(text, max = 48) {
2
+ const value = String(text || '').trim();
3
+ if (!value) return '';
4
+ return value.length > max ? `${value.slice(0, Math.max(0, max - 3)).trimEnd()}...` : value;
5
+ }
6
+
7
+ export function parseToolDisplayName(name) {
8
+ const raw = String(name || '').trim();
9
+ const match = raw.match(/^([^(]+)\((.*)\)$/);
10
+ return {
11
+ raw,
12
+ base: match ? match[1] : raw,
13
+ target: match ? match[2] : ''
14
+ };
15
+ }
16
+
17
+ export function isEnglishCopy(copy) {
18
+ return String(copy?.roleLabels?.coder || '').trim() === 'CODER' && String(copy?.roleLabels?.you || '').trim() === 'YOU';
19
+ }
20
+
21
+ export function renderLocalizedEntry(entry, copy, context) {
22
+ if (!entry) return '';
23
+ const locale = isEnglishCopy(copy) ? 'en' : 'zh';
24
+ const renderer = entry[locale];
25
+ return typeof renderer === 'function' ? renderer(context) : '';
26
+ }
27
+
28
+ export function getLastToolActivity(msg, statuses = []) {
29
+ const allowed = new Set((Array.isArray(statuses) ? statuses : []).map((status) => String(status)));
30
+ const segments = Array.isArray(msg?.segments) ? msg.segments : [];
31
+ for (let idx = segments.length - 1; idx >= 0; idx -= 1) {
32
+ const segment = segments[idx];
33
+ if (segment?.type !== 'tool' && segment?.type !== 'system_tool') continue;
34
+ if (allowed.size === 0 || allowed.has(String(segment.status || ''))) return segment;
35
+ }
36
+ return null;
37
+ }
@@ -0,0 +1,109 @@
1
+ function buildPreludeEntry() {
2
+ return {
3
+ en: ({ changeKind, target, verb }) => {
4
+ if (changeKind === 'readme') return `I'll inspect the project structure first, then write the README.`;
5
+ if (changeKind === 'doc') return `I'll inspect the existing context first, then update the document.`;
6
+ if (target) return `I'll inspect ${target} first, then ${verb} it.`;
7
+ return `I'll inspect the current code first, then make the change.`;
8
+ },
9
+ zh: ({ changeKind, target, verbZh }) => {
10
+ if (changeKind === 'readme') return '我先看一下项目内容和结构,再开始写 README。';
11
+ if (changeKind === 'doc') return '我先看一下现有内容,再开始整理这份文档。';
12
+ if (target) return `我先看一下 ${target} 的上下文,再开始${verbZh}。`;
13
+ return '我先确认当前代码上下文,再动手修改。';
14
+ }
15
+ };
16
+ }
17
+
18
+ function buildCompletionEntry() {
19
+ return {
20
+ en: ({ changeKind, target }) => {
21
+ if (changeKind === 'readme') return 'The README is in place. If you want, I can also add a quick start, feature summary, or usage example.';
22
+ if (changeKind === 'doc') return 'The document is updated. If you want, I can also polish the wording, structure, or add an example section.';
23
+ if (changeKind === 'test') {
24
+ return target
25
+ ? `${target} is ready. If you want, I can keep going with verification, more test coverage, or a quick review of edge cases.`
26
+ : 'That test-related change is ready. If you want, I can keep going with verification or edge-case review.';
27
+ }
28
+ return target
29
+ ? `${target} is ready. If you want, I can also add tests, update docs, or do a quick edge-case pass.`
30
+ : 'That part is ready. If you want, I can also add tests, update docs, or do a quick edge-case pass.';
31
+ },
32
+ zh: ({ changeKind, target }) => {
33
+ if (changeKind === 'readme') return 'README 已经写好了。要不要我顺手再补一个快速开始、功能概览,或者使用示例?';
34
+ if (changeKind === 'doc') return '文档已经更新好了。要不要我继续顺一下语气、结构,或者补一段示例?';
35
+ if (changeKind === 'test') {
36
+ return target
37
+ ? `${target} 已经处理好了。要不要我继续补验证、扩一下测试覆盖,或者再过一遍边界情况?`
38
+ : '这部分测试相关修改已经处理好了。要不要我继续补验证,或者再过一遍边界情况?';
39
+ }
40
+ return target
41
+ ? `${target} 已经处理好了。要不要我继续补测试、更新文档,或者再检查一遍边界情况?`
42
+ : '这部分已经处理好了。要不要我继续补测试、更新文档,或者再检查一遍边界情况?';
43
+ }
44
+ };
45
+ }
46
+
47
+ function buildBridgeEntry() {
48
+ return {
49
+ en: ({ changeKind, nextTarget, verb, hasContext }) => {
50
+ if (changeKind === 'readme') return hasContext ? 'I have enough context now, so I can write the README.' : 'I can write the README next.';
51
+ if (changeKind === 'doc') return hasContext ? 'I have enough context now, so I can update the document.' : 'I can update the document next.';
52
+ if (nextTarget) return hasContext ? `I have enough context now, so I'll ${verb} ${nextTarget}.` : `I'll ${verb} ${nextTarget} next.`;
53
+ return hasContext ? 'I have enough context now, so I can make the change.' : 'I can make the change next.';
54
+ },
55
+ zh: ({ changeKind, nextTarget, verbZh, hasContext }) => {
56
+ if (changeKind === 'readme') return hasContext ? '相关内容我已经看过了,现在开始写 README。' : '现在开始写 README。';
57
+ if (changeKind === 'doc') return hasContext ? '需要的上下文我已经看过了,现在开始整理这份文档。' : '现在开始整理这份文档。';
58
+ if (nextTarget) return hasContext ? `需要的上下文我已经看过了,现在${verbZh} ${nextTarget}。` : `现在${verbZh} ${nextTarget}。`;
59
+ return hasContext ? '需要的上下文我已经看过了,现在开始修改。' : '现在开始修改。';
60
+ }
61
+ };
62
+ }
63
+
64
+ export function inferChangeKind(target) {
65
+ const lowerTarget = String(target || '').toLowerCase();
66
+ if (lowerTarget.includes('readme')) return 'readme';
67
+ if (lowerTarget.endsWith('.md')) return 'doc';
68
+ if (/test|spec/i.test(lowerTarget)) return 'test';
69
+ return 'generic';
70
+ }
71
+
72
+ export function createChangePresenter({ verb, verbZh }) {
73
+ return {
74
+ prelude: buildPreludeEntry(),
75
+ completion: buildCompletionEntry(),
76
+ bridges: {
77
+ inspect: buildBridgeEntry(),
78
+ search: {
79
+ en: ({ changeKind, nextTarget, verb }) => {
80
+ if (changeKind === 'readme') return 'I found what I needed, so I can write the README.';
81
+ if (changeKind === 'doc') return 'I found what I needed, so I can update the document.';
82
+ if (nextTarget) return `I found the right spot, so I'll ${verb} ${nextTarget}.`;
83
+ return 'I found the right spot, so I can make the change.';
84
+ },
85
+ zh: ({ changeKind, nextTarget, verbZh }) => {
86
+ if (changeKind === 'readme') return '相关位置我已经找到了,现在开始写 README。';
87
+ if (changeKind === 'doc') return '相关位置我已经找到了,现在开始整理这份文档。';
88
+ if (nextTarget) return `相关位置我已经找到了,现在${verbZh} ${nextTarget}。`;
89
+ return '相关位置我已经找到了,现在开始修改。';
90
+ }
91
+ },
92
+ run: {
93
+ en: ({ changeKind, nextTarget, verb }) => {
94
+ if (changeKind === 'readme') return 'I have the result I needed, so I can write the README next.';
95
+ if (changeKind === 'doc') return 'I have the result I needed, so I can update the document next.';
96
+ if (nextTarget) return `I have the result I needed, so I'll ${verb} ${nextTarget}.`;
97
+ return 'I have the result I needed, so I can make the follow-up change.';
98
+ },
99
+ zh: ({ changeKind, nextTarget, verbZh }) => {
100
+ if (changeKind === 'readme') return '结果我已经拿到了,现在开始写 README。';
101
+ if (changeKind === 'doc') return '结果我已经拿到了,现在开始整理这份文档。';
102
+ if (nextTarget) return `结果我已经拿到了,现在${verbZh} ${nextTarget}。`;
103
+ return '结果我已经拿到了,现在继续做后续修改。';
104
+ }
105
+ }
106
+ },
107
+ meta: { verb, verbZh }
108
+ };
109
+ }
@@ -0,0 +1,3 @@
1
+ import { createChangePresenter } from './change.js';
2
+
3
+ export const editPresenter = createChangePresenter({ verb: 'update', verbZh: '修改' });
@@ -0,0 +1,10 @@
1
+ export const genericPresenter = {
2
+ prelude: {
3
+ en: () => `I'll check the relevant project context first.`,
4
+ zh: () => '我先查看相关上下文。'
5
+ },
6
+ completion: {
7
+ en: () => 'The requested work is done. If you want, I can keep going with tests, docs, or a quick review.',
8
+ zh: () => '这部分已经处理好了。你要是愿意,我可以继续补测试、文档,或者再帮你快速检查一遍。'
9
+ }
10
+ };
@@ -0,0 +1,11 @@
1
+ export const globPresenter = {
2
+ prelude: {
3
+ en: ({ target }) => (target ? `I'll inspect the ${target} directory first.` : 'I\'ll inspect the relevant directory first.'),
4
+ zh: ({ target }) => (target ? `我先查看 ${target} 目录里的内容。` : '我先查看相关目录内容。')
5
+ },
6
+ completion: {
7
+ en: () => 'I have the relevant context now. Do you want me to make the change next, or summarize the findings first?',
8
+ zh: () => '相关上下文我已经看完了。接下来你要我直接动手改,还是先把结论整理给你?'
9
+ },
10
+ meta: { bridgeGroup: 'inspect' }
11
+ };
@@ -0,0 +1,11 @@
1
+ export const grepPresenter = {
2
+ prelude: {
3
+ en: () => `I'll search the relevant code first.`,
4
+ zh: () => '我先搜索相关代码位置。'
5
+ },
6
+ completion: {
7
+ en: () => 'I found the relevant spots. Do you want me to make the change next, or summarize the findings first?',
8
+ zh: () => '相关位置我已经找到了。接下来你要我直接动手改,还是先把结论整理给你?'
9
+ },
10
+ meta: { bridgeGroup: 'search' }
11
+ };
@@ -0,0 +1,11 @@
1
+ export const listPresenter = {
2
+ prelude: {
3
+ en: ({ target }) => (target ? `I'll inspect the ${target} directory first.` : 'I\'ll inspect the relevant directory first.'),
4
+ zh: ({ target }) => (target ? `我先查看 ${target} 目录里的内容。` : '我先查看相关目录内容。')
5
+ },
6
+ completion: {
7
+ en: () => 'I have the relevant context now. Do you want me to make the change next, or summarize the findings first?',
8
+ zh: () => '相关上下文我已经看完了。接下来你要我直接动手改,还是先把结论整理给你?'
9
+ },
10
+ meta: { bridgeGroup: 'inspect' }
11
+ };
@@ -0,0 +1,3 @@
1
+ import { createChangePresenter } from './change.js';
2
+
3
+ export const patchPresenter = createChangePresenter({ verb: 'patch', verbZh: '修改' });
@@ -0,0 +1,11 @@
1
+ export const readPresenter = {
2
+ prelude: {
3
+ en: ({ target }) => (target ? `I'll inspect ${target} first.` : 'I\'ll inspect the relevant file first.'),
4
+ zh: ({ target }) => (target ? `我先查看 ${target} 的内容。` : '我先查看相关文件内容。')
5
+ },
6
+ completion: {
7
+ en: () => 'I have the relevant context now. Do you want me to make the change next, or summarize the findings first?',
8
+ zh: () => '相关上下文我已经看完了。接下来你要我直接动手改,还是先把结论整理给你?'
9
+ },
10
+ meta: { bridgeGroup: 'inspect' }
11
+ };
@@ -0,0 +1,29 @@
1
+ export const runPresenter = {
2
+ prelude: {
3
+ en: () => `I'll verify the current project state first.`,
4
+ zh: () => '我先检查当前项目状态。'
5
+ },
6
+ completion: {
7
+ en: () => 'That step is finished. Do you want me to act on the result next, or summarize what it means first?',
8
+ zh: () => '这一步已经跑完了。接下来要我根据结果继续处理,还是先把结论整理给你?'
9
+ },
10
+ bridges: {
11
+ 'generic-change': {
12
+ en: () => 'I have the result I needed, so I can make the follow-up change.',
13
+ zh: () => '结果我已经拿到了,现在继续做后续修改。'
14
+ },
15
+ 'doc-change': {
16
+ en: () => 'I have the result I needed, so I can make the follow-up change.',
17
+ zh: () => '结果我已经拿到了,现在继续做后续修改。'
18
+ },
19
+ 'readme-change': {
20
+ en: () => 'I have the result I needed, so I can make the follow-up change.',
21
+ zh: () => '结果我已经拿到了,现在继续做后续修改。'
22
+ },
23
+ 'test-change': {
24
+ en: () => 'I have the result I needed, so I can make the follow-up change.',
25
+ zh: () => '结果我已经拿到了,现在继续做后续修改。'
26
+ }
27
+ },
28
+ meta: { bridgeGroup: 'run' }
29
+ };
@@ -0,0 +1,3 @@
1
+ import { createChangePresenter } from './change.js';
2
+
3
+ export const writePresenter = createChangePresenter({ verb: 'write', verbZh: '写' });
@@ -0,0 +1,67 @@
1
+ import { getLastToolActivity, parseToolDisplayName, renderLocalizedEntry, trimText } from './tool-narration/common.js';
2
+ import { inferChangeKind } from './tool-narration/presenters/change.js';
3
+ import { editPresenter } from './tool-narration/presenters/edit.js';
4
+ import { genericPresenter } from './tool-narration/presenters/generic.js';
5
+ import { globPresenter } from './tool-narration/presenters/glob.js';
6
+ import { grepPresenter } from './tool-narration/presenters/grep.js';
7
+ import { listPresenter } from './tool-narration/presenters/list.js';
8
+ import { patchPresenter } from './tool-narration/presenters/patch.js';
9
+ import { readPresenter } from './tool-narration/presenters/read.js';
10
+ import { runPresenter } from './tool-narration/presenters/run.js';
11
+ import { writePresenter } from './tool-narration/presenters/write.js';
12
+
13
+ const BASE_PRESENTERS = {
14
+ read: readPresenter,
15
+ list: listPresenter,
16
+ glob: globPresenter,
17
+ grep: grepPresenter,
18
+ write: writePresenter,
19
+ edit: editPresenter,
20
+ patch: patchPresenter,
21
+ generate_diff: patchPresenter,
22
+ run: runPresenter
23
+ };
24
+
25
+ function resolveNarrationContext(name) {
26
+ const { base, target } = parseToolDisplayName(name);
27
+ const presenter = BASE_PRESENTERS[base] || genericPresenter;
28
+ const changeKind = inferChangeKind(target);
29
+ return {
30
+ base,
31
+ target: trimText(target, 48),
32
+ presenter,
33
+ bridgeGroup: presenter?.meta?.bridgeGroup || 'generic',
34
+ verb: presenter?.meta?.verb || 'update',
35
+ verbZh: presenter?.meta?.verbZh || '修改',
36
+ changeKind
37
+ };
38
+ }
39
+
40
+ export function buildPreToolNotice(name, copy) {
41
+ const context = resolveNarrationContext(name);
42
+ return renderLocalizedEntry(context.presenter.prelude, copy, context);
43
+ }
44
+
45
+ export function buildInterToolNotice(previousActivity, nextToolName, copy) {
46
+ const previousContext = resolveNarrationContext(previousActivity?.name);
47
+ const nextContext = resolveNarrationContext(nextToolName);
48
+ const bridge = nextContext.presenter?.bridges?.[previousContext.bridgeGroup];
49
+ return renderLocalizedEntry(bridge, copy, {
50
+ ...nextContext,
51
+ nextTarget: nextContext.target,
52
+ hasContext: previousContext.bridgeGroup !== 'generic'
53
+ });
54
+ }
55
+
56
+ export function buildSyntheticCompletionText(msg, copy) {
57
+ const activity = getLastToolActivity(msg, ['done', 'running']);
58
+ if (!activity) {
59
+ return renderLocalizedEntry(genericPresenter.completion, copy, {});
60
+ }
61
+
62
+ const context = resolveNarrationContext(activity.name);
63
+ return renderLocalizedEntry(context.presenter.completion, copy, {
64
+ ...context,
65
+ target: trimText(context.target, 56)
66
+ });
67
+ }