cc-viewer 1.7.23 → 1.8.1
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/README.md +20 -0
- package/cli.js +92 -11
- package/concepts/ar/GlobalSettings.md +2 -2
- package/concepts/ar/ToolsFirst.md +1 -1
- package/concepts/da/GlobalSettings.md +2 -2
- package/concepts/da/ToolsFirst.md +1 -1
- package/concepts/de/GlobalSettings.md +2 -2
- package/concepts/de/ToolsFirst.md +1 -1
- package/concepts/en/GlobalSettings.md +2 -2
- package/concepts/en/ToolsFirst.md +1 -1
- package/concepts/es/GlobalSettings.md +2 -2
- package/concepts/es/ToolsFirst.md +1 -1
- package/concepts/fr/GlobalSettings.md +2 -2
- package/concepts/fr/ToolsFirst.md +1 -1
- package/concepts/it/GlobalSettings.md +2 -2
- package/concepts/it/ToolsFirst.md +1 -1
- package/concepts/ja/GlobalSettings.md +2 -2
- package/concepts/ja/ToolsFirst.md +1 -1
- package/concepts/ko/GlobalSettings.md +2 -2
- package/concepts/ko/ToolsFirst.md +1 -1
- package/concepts/no/GlobalSettings.md +2 -2
- package/concepts/no/ToolsFirst.md +1 -1
- package/concepts/pl/GlobalSettings.md +2 -2
- package/concepts/pl/ToolsFirst.md +1 -1
- package/concepts/pt-BR/GlobalSettings.md +2 -2
- package/concepts/pt-BR/ToolsFirst.md +1 -1
- package/concepts/ru/GlobalSettings.md +2 -2
- package/concepts/ru/ToolsFirst.md +1 -1
- package/concepts/th/GlobalSettings.md +2 -2
- package/concepts/th/ToolsFirst.md +1 -1
- package/concepts/tr/GlobalSettings.md +2 -2
- package/concepts/tr/ToolsFirst.md +1 -1
- package/concepts/uk/GlobalSettings.md +2 -2
- package/concepts/uk/ToolsFirst.md +1 -1
- package/concepts/zh/GlobalSettings.md +2 -2
- package/concepts/zh/ToolsFirst.md +1 -1
- package/concepts/zh-TW/GlobalSettings.md +2 -2
- package/concepts/zh-TW/ToolsFirst.md +1 -1
- package/dist/assets/App-CqQJDjfT.js +2 -0
- package/dist/assets/App-ssYfrjwF.css +1 -0
- package/dist/assets/{MdxEditorPanel-e5DlfcqJ.js → MdxEditorPanel-DXwb5tSD.js} +1 -1
- package/dist/assets/{Mobile-B8xwCLgr.js → Mobile-DOjKURO3.js} +1 -1
- package/dist/assets/{ProxyStatsModal-_w677xPM.js → ProxyStatsModal-BEnxiRXF.js} +1 -1
- package/dist/assets/index-CH_N7vvX.js +2 -0
- package/dist/assets/seqResourceLoaders-BJhO6zhW.js +2 -0
- package/dist/assets/seqResourceLoaders-DI0G5nu1.css +41 -0
- package/dist/index.html +1 -1
- package/findcc.js +17 -8
- package/node_modules/@ccv/core/package.json +13 -0
- package/{server/lib → node_modules/@ccv/core/src}/approval-modal-prefs.js +1 -1
- package/node_modules/@ccv/core/src/clearCheckpoint.js +7 -0
- package/{src/utils → node_modules/@ccv/core/src}/contentFilter.js +207 -141
- package/{server/lib → node_modules/@ccv/core/src}/context-rules.js +2 -2
- package/{server/lib → node_modules/@ccv/core/src}/delta-reconstructor.js +60 -4
- package/{server/lib → node_modules/@ccv/core/src}/error-report.js +5 -4
- package/{server/lib → node_modules/@ccv/core/src}/session-boundary.js +3 -3
- package/{src/utils → node_modules/@ccv/core/src}/teammateDetector.js +9 -0
- package/{server/lib → node_modules/@ccv/core/src}/tools-xml-formatter.js +3 -3
- package/{server/lib → node_modules/@ccv/core/src}/v2-transcript-normalizer.js +1 -1
- package/{server/lib → node_modules/@ccv/core/src}/voice-pack-events.js +2 -2
- package/package.json +11 -97
- package/server/_paths.js +28 -7
- package/server/i18n.js +60 -0
- package/server/interceptor.js +36 -11
- package/server/lib/adapters/dingtalk-adapter.js +2 -2
- package/server/lib/adapters/discord-adapter.js +2 -2
- package/server/lib/adapters/feishu-adapter.js +2 -2
- package/server/lib/adapters/wecom-adapter.js +2 -2
- package/server/lib/approval-policy.js +29 -0
- package/server/lib/{ask-store.js → ask/ask-store.js} +3 -3
- package/server/lib/{perm-bridge.js → ask/perm-bridge.js} +5 -5
- package/server/lib/builtin-model-prompts.js +198 -0
- package/server/lib/chat-queue.js +341 -0
- package/server/lib/cli-inject.js +1 -1
- package/server/lib/context-watcher.js +3 -3
- package/server/lib/create_system_prompt.js +0 -9
- package/server/lib/enrich-plan-input.js +1 -1
- package/server/lib/ensure-hooks.js +2 -2
- package/server/lib/file-access-policy.js +6 -1
- package/server/lib/file-api.js +17 -1
- package/server/lib/{dingtalk-bridge.js → im/dingtalk-bridge.js} +1 -1
- package/server/lib/{im-append-system.js → im/im-append-system.js} +5 -13
- package/server/lib/{im-bridge-core.js → im/im-bridge-core.js} +12 -6
- package/server/lib/{im-config.js → im/im-config.js} +1 -1
- package/server/lib/im/im-deny.js +4 -0
- package/server/lib/{im-lang.js → im/im-lang.js} +1 -1
- package/server/lib/{im-lock.js → im/im-lock.js} +2 -2
- package/server/lib/{im-process-manager.js → im/im-process-manager.js} +1 -1
- package/server/lib/{im-senders.js → im/im-senders.js} +1 -1
- package/server/lib/{im-skills.js → im/im-skills.js} +2 -2
- package/server/lib/interceptor-core.js +8 -5
- package/server/lib/kv-cache-analyzer.js +6 -5
- package/server/lib/launch-config.js +329 -0
- package/server/lib/log-file-utils.js +21 -0
- package/server/lib/log-management.js +6 -17
- package/server/lib/log-stream.js +1 -1
- package/server/lib/log-watcher.js +1 -1
- package/server/lib/model-system-prompts.js +18 -2
- package/server/lib/prefs-store.js +2 -2
- package/server/lib/project-prefs.js +6 -6
- package/server/lib/project-state.js +15 -0
- package/server/lib/{proxy-retry.js → proxy/proxy-retry.js} +2 -2
- package/server/lib/{proxy-stats.js → proxy/proxy-stats.js} +3 -3
- package/server/lib/sdk-adapter.js +6 -74
- package/server/lib/sdk-manager.js +383 -278
- package/server/lib/skills-api.js +1 -1
- package/server/lib/spawn-model-resolver.js +21 -6
- package/server/lib/stats-worker.js +2 -2
- package/server/lib/system-prompt-files.js +53 -15
- package/server/lib/system-prompt-render.js +23 -7
- package/server/lib/system-prompt-snapshots.js +452 -0
- package/server/lib/updater.js +63 -3
- package/server/lib/user-prompt-extract.js +5 -5
- package/server/lib/v2/adapter.js +3 -3
- package/server/lib/v2/conversation-store.js +1 -1
- package/server/lib/v2/convert.js +3 -3
- package/server/lib/v2/jsonl-read.js +1 -1
- package/server/lib/v2/live-feed.js +3 -3
- package/server/lib/v2/meta-rows.js +3 -3
- package/server/lib/v2/replay.js +1 -1
- package/server/lib/v2/session-list.js +1 -1
- package/server/lib/v2/session-owner.js +1 -1
- package/server/lib/v2/v2-writer.js +26 -1
- package/server/lib/v2/verify.js +1 -1
- package/server/lib/voice-pack-manager.js +4 -5
- package/server/lib/wire-compress.js +1 -1
- package/server/proxy.js +5 -5
- package/server/pty-manager.js +285 -184
- package/server/routes/ask-perm.js +1 -1
- package/server/routes/dingtalk.js +3 -3
- package/server/routes/events.js +2 -2
- package/server/routes/expert.js +74 -13
- package/server/routes/im.js +5 -5
- package/server/routes/preferences.js +3 -3
- package/server/routes/workspaces.js +1 -1
- package/server/server.js +207 -58
- package/server/system-prompt-templates/systemPromptModel.md +0 -8
- package/server/system-prompt-templates/systemPromptVariables.ar.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.da.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.de.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.es.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.fr.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.it.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.ja.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.ko.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.no.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.pl.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.pt-BR.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.ru.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.th.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.tr.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.uk.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.zh-TW.md +0 -7
- package/server/system-prompt-templates/systemPromptVariables.zh.md +0 -7
- package/ultraAgents/README.md +3 -0
- package/ultraAgents/test-analysis-expert.json +45 -0
- package/dist/assets/App-BWajJyJh.css +0 -1
- package/dist/assets/App-CusqoEAB.js +0 -2
- package/dist/assets/index-D6FmFsIz.js +0 -2
- package/dist/assets/seqResourceLoaders-3K77Znll.js +0 -2
- package/dist/assets/seqResourceLoaders-BgD_Rwvc.css +0 -41
- package/src/utils/apiUrl.js +0 -26
- package/src/utils/askFallback.js +0 -21
- package/src/utils/askOptionDesc.js +0 -55
- package/src/utils/askPortalMatcher.js +0 -54
- package/src/utils/autoApproveOptions.js +0 -27
- package/src/utils/avatarAnimationPostPass.js +0 -49
- package/src/utils/builtinPresets.js +0 -18
- package/src/utils/clearCheckpoint.js +0 -7
- package/src/utils/commandValidator.js +0 -45
- package/src/utils/contextRaw.js +0 -18
- package/src/utils/contextSidebarNavigation.js +0 -16
- package/src/utils/contextTurns.js +0 -117
- package/src/utils/displayScaleHelper.js +0 -47
- package/src/utils/effectiveModel.js +0 -17
- package/src/utils/entry-slim.js +0 -563
- package/src/utils/entryCache.js +0 -144
- package/src/utils/errorReport.js +0 -6
- package/src/utils/fileExpandedPathsStorage.js +0 -70
- package/src/utils/fileIcons.jsx +0 -36
- package/src/utils/fileOpen.js +0 -37
- package/src/utils/formatters.js +0 -48
- package/src/utils/gitApi.js +0 -50
- package/src/utils/gitTreeBuilder.js +0 -19
- package/src/utils/helpers.js +0 -854
- package/src/utils/identityHeal.js +0 -108
- package/src/utils/imConnState.js +0 -59
- package/src/utils/imOrigin.js +0 -24
- package/src/utils/imTr.js +0 -7
- package/src/utils/imageCompress.js +0 -83
- package/src/utils/imageDownscale.js +0 -28
- package/src/utils/imageResize.js +0 -112
- package/src/utils/ingestPipeline.js +0 -47
- package/src/utils/isProxyMode.js +0 -11
- package/src/utils/lazyWithReload.js +0 -53
- package/src/utils/markdown.js +0 -30
- package/src/utils/markdownIncremental.js +0 -39
- package/src/utils/markdownProfiler.js +0 -113
- package/src/utils/mdExtensionDetect.js +0 -64
- package/src/utils/memoryLinkParser.js +0 -44
- package/src/utils/modalMask.js +0 -15
- package/src/utils/modelSuggestions.js +0 -48
- package/src/utils/monotime.js +0 -6
- package/src/utils/pinnedMenu.js +0 -55
- package/src/utils/presetShortcuts.js +0 -14
- package/src/utils/projectAlias.js +0 -135
- package/src/utils/promptClassifier.js +0 -112
- package/src/utils/promptDetect.js +0 -196
- package/src/utils/promptNav.js +0 -60
- package/src/utils/ptyChunkBuilder.js +0 -251
- package/src/utils/quickMenuHoverIntent.js +0 -35
- package/src/utils/rateLimitParser.js +0 -119
- package/src/utils/readResultPool.js +0 -106
- package/src/utils/refreshCachedItemProp.js +0 -30
- package/src/utils/resizeCalc.js +0 -21
- package/src/utils/resolveLocalized.js +0 -17
- package/src/utils/searchApi.js +0 -47
- package/src/utils/searchReplace.js +0 -51
- package/src/utils/seqResourceLoaders.js +0 -79
- package/src/utils/sessionManager.js +0 -482
- package/src/utils/sessionMerge.js +0 -239
- package/src/utils/skillModalController.js +0 -111
- package/src/utils/skillsParser.js +0 -116
- package/src/utils/slashCommandLabels.js +0 -80
- package/src/utils/splitDragCalc.js +0 -73
- package/src/utils/stickyBottomController.js +0 -643
- package/src/utils/svgSanitize.js +0 -48
- package/src/utils/systemTags.js +0 -47
- package/src/utils/tClaude.js +0 -20
- package/src/utils/teamModalBuilder.js +0 -351
- package/src/utils/teamSessionParser.js +0 -190
- package/src/utils/teammateAvatars.js +0 -194
- package/src/utils/termDiag.js +0 -114
- package/src/utils/terminalClipboard.js +0 -117
- package/src/utils/terminalWriteQueue.js +0 -398
- package/src/utils/thinkingMerge.js +0 -49
- package/src/utils/toolCatalog.js +0 -30
- package/src/utils/toolResultBuilder.js +0 -411
- package/src/utils/toolResultClassifier.js +0 -16
- package/src/utils/toolResultCore.js +0 -194
- package/src/utils/toolsDiff.js +0 -44
- package/src/utils/toolsXmlFormatter.js +0 -3
- package/src/utils/ultraplanController.js +0 -109
- package/src/utils/ultraplanExperts.js +0 -86
- package/src/utils/ultraplanTemplates.js +0 -154
- package/src/utils/userImageRefs.js +0 -49
- package/src/utils/v3Assembler.js +0 -125
- package/src/utils/v3Rows.js +0 -49
- package/src/utils/voicePackPlayer.js +0 -233
- package/src/utils/webSearchGrouping.js +0 -111
- package/src/utils/workflowFormat.js +0 -52
- package/src/utils/workflowRuns.js +0 -118
- package/src/utils/workflowStore.js +0 -94
- /package/{src/utils → node_modules/@ccv/core/src}/requestType.js +0 -0
- /package/server/lib/{ask-bridge.js → ask/ask-bridge.js} +0 -0
- /package/server/lib/{ask-constants.js → ask/ask-constants.js} +0 -0
- /package/server/lib/{ask-reaper.js → ask/ask-reaper.js} +0 -0
- /package/server/lib/{dingtalk-config.js → im/dingtalk-config.js} +0 -0
- /package/server/lib/{im-log-watcher.js → im/im-log-watcher.js} +0 -0
- /package/server/lib/{proxy-env.js → proxy/proxy-env.js} +0 -0
- /package/server/lib/{proxy-errors.js → proxy/proxy-errors.js} +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-config.js — Launch-time configuration shared by the PTY and SDK spawns.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from pty-manager.js so the SDK link (sdk-manager.js / cli.js runSdkMode)
|
|
5
|
+
* produces byte-identical system-prompt injection to the PTY link:
|
|
6
|
+
* - withDefaultThinkingDisplay: inject `--thinking-display summarized` by default
|
|
7
|
+
* - parseResumeArgs: continuation-flag parsing (-c/--continue/-r/--resume/--fork-session)
|
|
8
|
+
* - resolveLaunchSystemPrompt: the full system-prompt pipeline — resume pin
|
|
9
|
+
* (snapshot replay, never re-render variables) or fresh build (CC_SYSTEM.md /
|
|
10
|
+
* CC_APPEND_SYSTEM.md sentinels, model-specific match, ${...} template render),
|
|
11
|
+
* plus the pending record consumed by the wire-side binding (v2-writer Bind A).
|
|
12
|
+
*
|
|
13
|
+
* Everything here is spawn-shape logic only — no PTY, no SDK. Callers own their
|
|
14
|
+
* mode-specific concerns (PTY: rejection self-heal sets, spawn notices; SDK: args →
|
|
15
|
+
* options.extraArgs conversion).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { randomBytes } from 'node:crypto';
|
|
21
|
+
import { LOG_DIR } from '../../findcc.js';
|
|
22
|
+
import { reportSwallowed } from '@ccv/core/error-report';
|
|
23
|
+
import {
|
|
24
|
+
buildSystemPromptFileArgs, hasArg, isNonEmptyFile,
|
|
25
|
+
SYSTEM_PROMPT_FILE, APPEND_SYSTEM_PROMPT_FILE,
|
|
26
|
+
} from './system-prompt-files.js';
|
|
27
|
+
import { renderSystemPromptFileArgs, renderedPromptDir } from './system-prompt-render.js';
|
|
28
|
+
import { appendPending, readSnapshot, resolveContinueTargetUuid } from './system-prompt-snapshots.js';
|
|
29
|
+
import { MODEL_PROMPT_DIR, listModelPrompts } from './model-system-prompts.js';
|
|
30
|
+
import { resolveSpawnModel } from './spawn-model-resolver.js';
|
|
31
|
+
|
|
32
|
+
// Opus 4.7 默认不再返回 thinking;为所有非显式覆写的调用加上 summarized。
|
|
33
|
+
// 纯函数:仅根据 args 决定是否注入;用户已显式传入 `--thinking-display` 时原样返回。
|
|
34
|
+
export function withDefaultThinkingDisplay(args) {
|
|
35
|
+
if (!Array.isArray(args)) return args;
|
|
36
|
+
const hasFlag = args.some(a =>
|
|
37
|
+
a === '--thinking-display' || (typeof a === 'string' && a.startsWith('--thinking-display='))
|
|
38
|
+
);
|
|
39
|
+
return hasFlag ? args : [...args, '--thinking-display', 'summarized'];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Parse continuation flags from claude args: -c/--continue, -r/--resume (value as the
|
|
43
|
+
// next token or in = form), --fork-session. Returns null for non-continuation launches;
|
|
44
|
+
// picker=true means a valueless -r (interactive picker — target unknowable at spawn).
|
|
45
|
+
// Same flag set as cli.js markContinueEnv / routes/workspaces.js.
|
|
46
|
+
export function parseResumeArgs(args) {
|
|
47
|
+
if (!Array.isArray(args)) return null;
|
|
48
|
+
let continued = false;
|
|
49
|
+
let resumeValue = null;
|
|
50
|
+
let picker = false;
|
|
51
|
+
let fork = false;
|
|
52
|
+
for (let i = 0; i < args.length; i++) {
|
|
53
|
+
const a = args[i];
|
|
54
|
+
if (a === '-c' || a === '--continue') { continued = true; continue; }
|
|
55
|
+
if (a === '--fork-session') { fork = true; continue; }
|
|
56
|
+
if (a === '-r' || a === '--resume') {
|
|
57
|
+
continued = true;
|
|
58
|
+
const next = args[i + 1];
|
|
59
|
+
if (typeof next === 'string' && next && !next.startsWith('-')) { resumeValue = next; i++; }
|
|
60
|
+
else picker = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const m = typeof a === 'string' && a.match(/^(?:-r|--resume)=(.+)$/);
|
|
64
|
+
if (m) { continued = true; resumeValue = m[1]; }
|
|
65
|
+
}
|
|
66
|
+
return continued ? { resumeValue, picker, fork } : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Materialize snapshot entries into temp files. Each spawn gets its own subdirectory
|
|
70
|
+
// under renderedPromptDir (per-process): two sequential spawns pinning DIFFERENT
|
|
71
|
+
// conversations would otherwise overwrite the same basename before claude reads it.
|
|
72
|
+
// Returns the same {args, loaded, entries} shape renderSystemPromptFileArgs produces.
|
|
73
|
+
export function materializePinnedEntries(entries) {
|
|
74
|
+
const args = [];
|
|
75
|
+
const loaded = [];
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const e of entries) {
|
|
78
|
+
try {
|
|
79
|
+
const dir = join(renderedPromptDir(), `pin-${randomBytes(4).toString('hex')}`);
|
|
80
|
+
mkdirSync(dir, { recursive: true });
|
|
81
|
+
const target = join(dir, e.basename);
|
|
82
|
+
writeFileSync(target, e.content, 'utf-8');
|
|
83
|
+
args.push(e.flag, target);
|
|
84
|
+
loaded.push(e.basename);
|
|
85
|
+
out.push({ flag: e.flag, basename: e.basename, content: e.content });
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// A dropped entry breaks the byte-identity this feature exists to protect —
|
|
88
|
+
// warn visibly AND report: silent degradation here is a future cache miss.
|
|
89
|
+
console.warn(`[CC Viewer] system-prompt pin materialize failed for ${e?.basename}:`, err?.message || err);
|
|
90
|
+
reportSwallowed('pty-pin.materialize', err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { args, loaded, entries: out };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Manual-first: a user-passed synonymous flag suppresses the matching pinned entry
|
|
97
|
+
// (same semantics as the fresh buildSystemPromptFileArgs path). All entries
|
|
98
|
+
// suppressed → no injection.
|
|
99
|
+
export function suppressManuallyFlaggedPinned(entries, userArgs) {
|
|
100
|
+
return entries.filter(e => {
|
|
101
|
+
const pair = e.flag === '--system-prompt-file'
|
|
102
|
+
? ['--system-prompt', '--system-prompt-file']
|
|
103
|
+
: ['--append-system-prompt', '--append-system-prompt-file'];
|
|
104
|
+
return !hasArg(userArgs, ...pair);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The F2 no-record notice only matters to users who HAVE injection configured right
|
|
109
|
+
// now (sentinel files or a model-prompt dir) — for everyone else a resume silently
|
|
110
|
+
// injecting nothing is exactly the status quo, and the line would be pure noise.
|
|
111
|
+
// Fidelity note (accepted gap): this check does NOT see the built-in preset layer —
|
|
112
|
+
// users whose only injection is a default-effective built-in (no dirs, no sentinels)
|
|
113
|
+
// get no F2 notice on resume either. Deliberate: making built-in hits count would turn
|
|
114
|
+
// the notice into near-constant noise for every built-in user resuming old sessions.
|
|
115
|
+
export function injectionConfigured(spawnDir, logDir = LOG_DIR) {
|
|
116
|
+
try {
|
|
117
|
+
return isNonEmptyFile(join(spawnDir, SYSTEM_PROMPT_FILE))
|
|
118
|
+
|| isNonEmptyFile(join(spawnDir, APPEND_SYSTEM_PROMPT_FILE))
|
|
119
|
+
|| existsSync(join(spawnDir, MODEL_PROMPT_DIR))
|
|
120
|
+
|| existsSync(join(logDir, MODEL_PROMPT_DIR));
|
|
121
|
+
} catch { return false; }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Default model reader with the NODE_TEST_CONTEXT guard (dev-shell model env vars must
|
|
125
|
+
// not leak into unit tests). pty-manager passes its own test-seam reader instead.
|
|
126
|
+
function _defaultModelReader(spawnDir, env, opts) {
|
|
127
|
+
return env.NODE_TEST_CONTEXT ? null : resolveSpawnModel(spawnDir, env, opts);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve this launch's system-prompt injection.
|
|
132
|
+
*
|
|
133
|
+
* Resume-pin semantics (see pty-manager header / system-prompt-snapshots.js):
|
|
134
|
+
* target identified + snapshot record → re-inject the recorded bytes verbatim;
|
|
135
|
+
* target identified + no record → inject NOTHING (F2 — never alter the system
|
|
136
|
+
* text an existing context already has);
|
|
137
|
+
* target unidentifiable (picker/-c with no transcripts) → normal fresh pipeline.
|
|
138
|
+
*
|
|
139
|
+
* @param {object} p
|
|
140
|
+
* @param {string} p.spawnDir — directory claude runs with
|
|
141
|
+
* @param {string[]} p.extraArgs — final user args (post settings-merge, post thinking-display);
|
|
142
|
+
* used for synonymous-flag suppression of injected entries
|
|
143
|
+
* @param {object} [p.env] — process env of the launch (model resolution + opt-out checks)
|
|
144
|
+
* @param {object|null} [p.launchSettings] — merged --settings object (model resolution signal)
|
|
145
|
+
* @param {object|null|undefined} [p.resume] — explicit resume intent {resumeValue, picker, fork};
|
|
146
|
+
* `undefined` parses from extraArgs (PTY path); explicit null forces the fresh pipeline
|
|
147
|
+
* @param {function} [p.modelReader] — (spawnDir, env, {launchSettings}) => modelId|null
|
|
148
|
+
* @param {boolean} [p.insideLogDir] — IM-worker spawns skip the whole pin/match machinery
|
|
149
|
+
* @param {string} [p.logDir]
|
|
150
|
+
* @param {boolean} [p.suppressInjection] — caller-side suppression (PTY: rejected-binary
|
|
151
|
+
* set / skip-once token). Zeroes the injection and drops the pending record.
|
|
152
|
+
* @param {boolean} [p.persistPending] — record the effective injection for the wire-side
|
|
153
|
+
* binding (v2-writer Bind A). PTY passes true; SDK also passes true (its wire traffic
|
|
154
|
+
* flows through the same proxy). Set false only for dry-run callers.
|
|
155
|
+
* @returns {{
|
|
156
|
+
* sysPrompt: {args: string[], loaded: string[], model: string|null, entries: object[], suppressed?: string, pinned?: boolean, noRecord?: boolean, noRecordNotice?: boolean},
|
|
157
|
+
* resume: object|null,
|
|
158
|
+
* resolvedModelId: string|null,
|
|
159
|
+
* diagnostic: null|'no-match'|'no-model'|'builtin-disabled',
|
|
160
|
+
* }}
|
|
161
|
+
*/
|
|
162
|
+
export function resolveLaunchSystemPrompt(p) {
|
|
163
|
+
const {
|
|
164
|
+
spawnDir,
|
|
165
|
+
extraArgs,
|
|
166
|
+
env = process.env,
|
|
167
|
+
launchSettings = null,
|
|
168
|
+
resume: resumeIntent,
|
|
169
|
+
modelReader = _defaultModelReader,
|
|
170
|
+
insideLogDir = false,
|
|
171
|
+
logDir = LOG_DIR,
|
|
172
|
+
suppressInjection = false,
|
|
173
|
+
persistPending = true,
|
|
174
|
+
} = p;
|
|
175
|
+
|
|
176
|
+
const out = {
|
|
177
|
+
sysPrompt: { args: [], loaded: [], model: null, entries: [] },
|
|
178
|
+
resume: null,
|
|
179
|
+
resolvedModelId: null,
|
|
180
|
+
diagnostic: null,
|
|
181
|
+
};
|
|
182
|
+
if (!spawnDir) return out;
|
|
183
|
+
|
|
184
|
+
// Continuation launches (-c/--continue/-r/--resume): pin the resumed conversation's
|
|
185
|
+
// original injection — never re-render variables. IM workers (insideLogDir) skip the
|
|
186
|
+
// whole snapshot machinery: their persona file must be re-read on every launch.
|
|
187
|
+
const resume = insideLogDir ? null : (resumeIntent !== undefined ? resumeIntent : parseResumeArgs(extraArgs));
|
|
188
|
+
out.resume = resume;
|
|
189
|
+
let pendingRec = null;
|
|
190
|
+
let pinned = null;
|
|
191
|
+
if (resume && env.CCV_DISABLE_AUTO_SYSTEM_PROMPT !== '1') {
|
|
192
|
+
const targetUuid = resume.resumeValue ?? resolveContinueTargetUuid(spawnDir);
|
|
193
|
+
if (targetUuid) {
|
|
194
|
+
const snap = readSnapshot(spawnDir, targetUuid);
|
|
195
|
+
if (snap) {
|
|
196
|
+
// Manual-flag suppression BEFORE materialize (suppressed entries never hit disk).
|
|
197
|
+
const kept = suppressManuallyFlaggedPinned(snap.entries, extraArgs);
|
|
198
|
+
const m = materializePinnedEntries(kept);
|
|
199
|
+
pinned = { args: m.args, loaded: m.loaded, model: snap.model, entries: m.entries, pinned: true };
|
|
200
|
+
// The pending carries the SNAPSHOT's entries (post-suppression), not the
|
|
201
|
+
// materialized subset — a transient temp-write failure must not degrade the
|
|
202
|
+
// permanent record via Bind B.
|
|
203
|
+
pendingRec = { entries: kept, model: snap.model, resumeExpected: true, resolvedUuid: targetUuid, fork: resume.fork };
|
|
204
|
+
} else {
|
|
205
|
+
// F2: target identified but no snapshot → inject NOTHING this launch (never
|
|
206
|
+
// alter the system text an existing context already has).
|
|
207
|
+
pinned = { args: [], loaded: [], model: null, entries: [], pinned: true, noRecord: true, noRecordNotice: injectionConfigured(spawnDir, logDir) };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Bare -r (picker): the target is unknowable at spawn → normal pipeline, and NO
|
|
211
|
+
// pending. -c with no transcripts (targetUuid null): claude fails and the caller's
|
|
212
|
+
// retry respawns without -c — treat as fresh below.
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (pinned) {
|
|
216
|
+
out.sysPrompt = pinned;
|
|
217
|
+
} else {
|
|
218
|
+
// launchSettings is this launch's own live configuration — launchers like cfuse
|
|
219
|
+
// deliver ANTHROPIC_MODEL exclusively inside it, so it must participate in model
|
|
220
|
+
// resolution or model-specific entries silently never match.
|
|
221
|
+
const resolvedModelId = insideLogDir ? null : modelReader(spawnDir, env, { launchSettings });
|
|
222
|
+
out.resolvedModelId = resolvedModelId;
|
|
223
|
+
let sysPrompt = buildSystemPromptFileArgs(spawnDir, extraArgs, env, {
|
|
224
|
+
modelId: resolvedModelId,
|
|
225
|
+
globalModelDir: join(logDir, MODEL_PROMPT_DIR),
|
|
226
|
+
});
|
|
227
|
+
if (suppressInjection) {
|
|
228
|
+
sysPrompt = { args: [], loaded: [], model: null, entries: [] };
|
|
229
|
+
} else if (sysPrompt.builtinDisabled) {
|
|
230
|
+
// The resolved model hit a built-in preset that the user tombstone-disabled —
|
|
231
|
+
// distinct from 'no-match' (a likely misnamed file): this is an intentional opt-out.
|
|
232
|
+
out.diagnostic = 'builtin-disabled';
|
|
233
|
+
} else if (resolvedModelId && !sysPrompt.model && !sysPrompt.suppressed
|
|
234
|
+
&& (existsSync(join(spawnDir, MODEL_PROMPT_DIR)) || existsSync(join(logDir, MODEL_PROMPT_DIR)))) {
|
|
235
|
+
// A system_prompt dir is configured but the resolved model matched no entry
|
|
236
|
+
// (likely a misnamed file).
|
|
237
|
+
out.diagnostic = 'no-match';
|
|
238
|
+
} else if (!resolvedModelId && !sysPrompt.suppressed && !insideLogDir
|
|
239
|
+
&& (listModelPrompts(join(spawnDir, MODEL_PROMPT_DIR)).length > 0
|
|
240
|
+
|| listModelPrompts(join(logDir, MODEL_PROMPT_DIR)).length > 0)) {
|
|
241
|
+
// Entries exist but NO model signal resolved at all, so matching never ran.
|
|
242
|
+
out.diagnostic = 'no-model';
|
|
243
|
+
}
|
|
244
|
+
// Resolve `${...}` template variables in the injected files. Skipped entirely when
|
|
245
|
+
// suppression zeroed the args (a rejected binary never pays the render cost).
|
|
246
|
+
if (sysPrompt.args.length > 0) {
|
|
247
|
+
sysPrompt = renderSystemPromptFileArgs(sysPrompt, { cwd: spawnDir, modelId: resolvedModelId });
|
|
248
|
+
} else {
|
|
249
|
+
sysPrompt = { ...sysPrompt, entries: [] };
|
|
250
|
+
}
|
|
251
|
+
out.sysPrompt = sysPrompt;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Unified suppression (pin and fresh alike): nothing was injected → nothing can be bound.
|
|
255
|
+
if (suppressInjection) {
|
|
256
|
+
out.sysPrompt = { args: [], loaded: [], model: null, entries: [] };
|
|
257
|
+
pendingRec = null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Record this launch's effective injection for the binding channels (wire Bind A /
|
|
261
|
+
// SessionStart Bind B). Empty pendings are never queued (see pty-manager history).
|
|
262
|
+
const effectiveEntries = (out.sysPrompt.entries || []).filter(e => e && !e.unavailable);
|
|
263
|
+
if (pendingRec && pendingRec.entries.length === 0) pendingRec = null;
|
|
264
|
+
const rec = pendingRec || (!resume && !pinned && effectiveEntries.length > 0
|
|
265
|
+
? { entries: effectiveEntries, model: out.sysPrompt.model ?? null, resumeExpected: false, resolvedUuid: null, fork: false }
|
|
266
|
+
: null);
|
|
267
|
+
if (persistPending && rec) {
|
|
268
|
+
try { appendPending(spawnDir, rec, logDir); } catch (err) { reportSwallowed('launch-config.appendPending', err); }
|
|
269
|
+
}
|
|
270
|
+
out.pendingRec = rec;
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Convert a flat launch-args array (['--system-prompt-file', path, ...]) into the SDK
|
|
276
|
+
* query() options.extraArgs shape ({ 'system-prompt-file': path, ... }). Boolean flags
|
|
277
|
+
* (no value) map to null. Unknown pairing (trailing flag) maps to null — the SDK
|
|
278
|
+
* appends it bare, mirroring claude argv semantics.
|
|
279
|
+
*/
|
|
280
|
+
export function launchArgsToExtraArgs(args) {
|
|
281
|
+
const out = {};
|
|
282
|
+
if (!Array.isArray(args)) return out;
|
|
283
|
+
for (let i = 0; i < args.length; i++) {
|
|
284
|
+
const a = args[i];
|
|
285
|
+
if (typeof a !== 'string' || !a.startsWith('--')) continue;
|
|
286
|
+
const eq = a.indexOf('=');
|
|
287
|
+
if (eq > 2) { out[a.slice(2, eq)] = a.slice(eq + 1); continue; }
|
|
288
|
+
const key = a.slice(2);
|
|
289
|
+
const next = args[i + 1];
|
|
290
|
+
if (typeof next === 'string' && !next.startsWith('--')) { out[key] = next; i++; }
|
|
291
|
+
else out[key] = null;
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Split user CLI args for the SDK link: flags with SDK-native options are lifted out
|
|
298
|
+
* (--model / -c / --continue / -r / --resume / --fork-session — also needed at the
|
|
299
|
+
* sdk-manager level for resume-pin semantics); everything else rides along in `rest`
|
|
300
|
+
* (later converted to options.extraArgs verbatim). A valueless `-r` (interactive
|
|
301
|
+
* picker) can't work headless — reported via `pickerResume` so the caller can warn.
|
|
302
|
+
*/
|
|
303
|
+
export function splitSdkLaunchArgs(args) {
|
|
304
|
+
const out = { model: null, continue: false, resume: null, forkSession: false, pickerResume: false, rest: [] };
|
|
305
|
+
if (!Array.isArray(args)) return out;
|
|
306
|
+
for (let i = 0; i < args.length; i++) {
|
|
307
|
+
const a = args[i];
|
|
308
|
+
if (typeof a !== 'string') { out.rest.push(a); continue; }
|
|
309
|
+
if (a === '-c' || a === '--continue') { out.continue = true; continue; }
|
|
310
|
+
if (a === '--fork-session') { out.forkSession = true; continue; }
|
|
311
|
+
if (a === '-r' || a === '--resume') {
|
|
312
|
+
const next = args[i + 1];
|
|
313
|
+
if (typeof next === 'string' && next && !next.startsWith('-')) { out.resume = next; i++; }
|
|
314
|
+
else out.pickerResume = true;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
let m = a.match(/^(?:-r|--resume)=(.+)$/);
|
|
318
|
+
if (m) { out.resume = m[1]; continue; }
|
|
319
|
+
m = a.match(/^--model=(.+)$/);
|
|
320
|
+
if (m) { out.model = m[1]; continue; }
|
|
321
|
+
if (a === '--model') {
|
|
322
|
+
const next = args[i + 1];
|
|
323
|
+
if (typeof next === 'string' && next && !next.startsWith('-')) { out.model = next; i++; continue; }
|
|
324
|
+
out.rest.push(a); continue; // valueless --model: pass through, claude surfaces its own usage error
|
|
325
|
+
}
|
|
326
|
+
out.rest.push(a);
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Log-file naming primitives — shared leaf extracted from log-management.js to
|
|
2
|
+
// break the static cycle log-management.js ↔ v2/adapter.js (v2 read/write paths
|
|
3
|
+
// need these primitives but must not import the v1 session listing, which itself
|
|
4
|
+
// imports the v2 adapter). Keep this module dependency-free.
|
|
5
|
+
|
|
6
|
+
export function isLogFileName(name) {
|
|
7
|
+
return name.endsWith('.jsonl');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 解析日志文件名里的时间戳 `YYYYMMDD_HHMMSS`(带不带 `<pid>__` 前缀都适用)。
|
|
11
|
+
// 用于「按时间排序 / 判最新」——文件名整串排序会把 `<pid>__` 前缀('1' < 'c')的最新文件排到最底,
|
|
12
|
+
// 必须按时间戳排。无法解析时返回 ''(排到最后)。v2 convert 消费,防漂移。
|
|
13
|
+
export function parseLogTs(name) {
|
|
14
|
+
const m = name.match(/_(\d{8}_\d{6})\.jsonl$/);
|
|
15
|
+
return m ? m[1] : '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// A session whose journal moved within this window is treated as live and
|
|
19
|
+
// refused by deleteLogFiles (cross-process guard; the in-process guard is the
|
|
20
|
+
// caller's own writer state).
|
|
21
|
+
export const LIVE_SESSION_MTIME_MS = 5 * 60 * 1000;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { existsSync, realpathSync, readdirSync, readFileSync, statSync, mkdirSync, renameSync } from 'node:fs';
|
|
2
2
|
import { readFile, stat, readdir } from 'node:fs/promises';
|
|
3
3
|
import { join, sep, dirname, basename } from 'node:path';
|
|
4
|
-
import { reconstructEntries } from '
|
|
4
|
+
import { reconstructEntries } from '@ccv/core/delta-reconstructor';
|
|
5
5
|
import { sanitizePathComponent } from './v2/layout.js';
|
|
6
6
|
import { listV2Sessions } from './v2/adapter.js';
|
|
7
7
|
import { summarizeSessionPage } from './v2/session-list.js';
|
|
8
8
|
import { listSessionIds } from './v2/replay.js';
|
|
9
|
+
import { isLogFileName, LIVE_SESSION_MTIME_MS } from './log-file-utils.js';
|
|
9
10
|
|
|
10
11
|
// wire-v2 S5 addressing (spec §12): 'v2:<project>/<session_id>' in every
|
|
11
12
|
// existing ?file= parameter slot. Components must survive the same whitelist
|
|
@@ -63,17 +64,10 @@ export function validateLogPath(logDir, file) {
|
|
|
63
64
|
return realPath;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// 解析日志文件名里的时间戳 `YYYYMMDD_HHMMSS`(带不带 `<pid>__` 前缀都适用)。
|
|
71
|
-
// 用于「按时间排序 / 判最新」——文件名整串排序会把 `<pid>__` 前缀('1' < 'c')的最新文件排到最底,
|
|
72
|
-
// 必须按时间戳排。无法解析时返回 ''(排到最后)。v2 convert 消费,防漂移。
|
|
73
|
-
export function parseLogTs(name) {
|
|
74
|
-
const m = name.match(/_(\d{8}_\d{6})\.jsonl$/);
|
|
75
|
-
return m ? m[1] : '';
|
|
76
|
-
}
|
|
67
|
+
// isLogFileName / parseLogTs / LIVE_SESSION_MTIME_MS live in log-file-utils.js
|
|
68
|
+
// (shared leaf, cycle break: v2/* must not import this module). Re-exported here
|
|
69
|
+
// so existing consumers and tests keep their module path.
|
|
70
|
+
export { isLogFileName, parseLogTs, LIVE_SESSION_MTIME_MS } from './log-file-utils.js';
|
|
77
71
|
|
|
78
72
|
// meta.startTs (ISO) → the v1 list's compact local-time stamp 'YYYYMMDD_HHMMSS'
|
|
79
73
|
// so formatTimestamp / the desc sort work on v2 items unchanged.
|
|
@@ -371,8 +365,3 @@ export function deleteLogFiles(logDir, files, opts = {}) {
|
|
|
371
365
|
}
|
|
372
366
|
return results;
|
|
373
367
|
}
|
|
374
|
-
|
|
375
|
-
// A session whose journal moved within this window is treated as live and
|
|
376
|
-
// refused by deleteLogFiles (cross-process guard; the in-process guard is the
|
|
377
|
-
// caller's own writer state).
|
|
378
|
-
export const LIVE_SESSION_MTIME_MS = 5 * 60 * 1000;
|
package/server/lib/log-stream.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
14
14
|
import { open as fsOpen, stat as fsStat } from 'node:fs/promises';
|
|
15
|
-
import { isCheckpointEntry, isDeltaEntry, reconstructSegment } from '
|
|
15
|
+
import { isCheckpointEntry, isDeltaEntry, reconstructSegment } from '@ccv/core/delta-reconstructor';
|
|
16
16
|
import { isV2SessionDir, iterateV2RawEntries, iterateV2RawEntriesAsync, readV2WindowedEntries, streamV2WindowedEntries } from './v2/adapter.js';
|
|
17
17
|
|
|
18
18
|
const READ_CHUNK_SIZE = 1024 * 1024; // 1MB
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { isMainAgentEntry, extractCachedContent } from './kv-cache-analyzer.js';
|
|
3
3
|
import { buildContextWindowEvent, getContextSizeForModel } from './context-watcher.js';
|
|
4
|
-
import { reconstructEntries } from '
|
|
4
|
+
import { reconstructEntries } from '@ccv/core/delta-reconstructor';
|
|
5
5
|
import { enrichEntry } from './enrich-plan-input.js';
|
|
6
6
|
import { enrichEntry as enrichWorkflowEntry } from './enrich-workflow.js';
|
|
7
7
|
import { sseWrite } from './wire-compress.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { isNonEmptyFile } from './
|
|
3
|
+
import { isNonEmptyFile } from './file-api.js';
|
|
4
4
|
|
|
5
5
|
// 「按模型定制 system prompt」的文件夹与文件名语法。
|
|
6
6
|
// 全局目录 <LOG_DIR>/system_prompt/ 与工作区目录 <workspace>/system_prompt/ 各放一套;
|
|
@@ -261,10 +261,26 @@ function modelIdVariants(id) {
|
|
|
261
261
|
return MODEL_ID_ALIASES[id] || [id];
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// 供内置预设匹配复用的导出包装:入参任意大小写,先剥 `[1m]` 类方括号后缀
|
|
265
|
+
// (spawn 渲染管线对 model.name 做同样的剥离;上游 resolver 正常会剥,这里兜底),
|
|
266
|
+
// 再 toLowerCase 展开别名——避免大写 env(如 K3)或带后缀的裸名(k3[1m])绕过别名表。
|
|
267
|
+
// 单一别名源,勿在别处复制 MODEL_ID_ALIASES。
|
|
268
|
+
// Exported wrapper for the built-in preset matcher: strips a trailing bracket suffix
|
|
269
|
+
// and lowercases before expanding, so uppercase ids (K3) or suffixed shorthands
|
|
270
|
+
// (k3[1m]) cannot bypass the alias table.
|
|
271
|
+
export function expandModelIdVariants(modelId) {
|
|
272
|
+
if (!modelId || typeof modelId !== 'string') return [];
|
|
273
|
+
const stripped = modelId.replace(/\s*\[[^\]]*\]$/, '').toLowerCase();
|
|
274
|
+
return modelIdVariants(stripped);
|
|
275
|
+
}
|
|
276
|
+
|
|
264
277
|
export function matchModelPrompt(modelId, candidates) {
|
|
265
278
|
if (!modelId || typeof modelId !== 'string') return null;
|
|
266
279
|
if (!Array.isArray(candidates)) return null;
|
|
267
|
-
|
|
280
|
+
// 与内置层(builtin-model-prompts.js)同一展开:剥 `[1m]` 类后缀 + lowercase + 别名。
|
|
281
|
+
// 若用户层不剥后缀而内置层剥(k3[1m] + 用户有 KIMI-K3_SYSTEM.md),用户文件会
|
|
282
|
+
// 静默 miss、内置反客为主 —— 违反「用户文件永远优先」,两层必须同语义。
|
|
283
|
+
const variants = expandModelIdVariants(modelId);
|
|
268
284
|
for (const cand of candidates) {
|
|
269
285
|
if (!cand || !cand.dir) continue;
|
|
270
286
|
const hits = listModelPrompts(cand.dir).filter((e) => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// preferences.json holds UI prefs AND the (base64) LAN password (auth) AND per-project
|
|
4
4
|
// forks (prefsByProject). Multiple writers touch it — the global POST /api/preferences,
|
|
5
5
|
// lib/auth.js, and the new project-prefs routes — so every write here goes through ONE
|
|
6
|
-
// async file lock + atomic tmp→rename, mirroring server/lib/ask-store.js. Same-process
|
|
6
|
+
// async file lock + atomic tmp→rename, mirroring server/lib/ask/ask-store.js. Same-process
|
|
7
7
|
// callers serialize via withFileLockAsync's per-lockPath Promise chain; cross-process
|
|
8
8
|
// callers mutex on the lock file. This prevents a concurrent writer from clobbering the
|
|
9
9
|
// password-bearing file or losing a fork update.
|
|
@@ -12,7 +12,7 @@ import { join, dirname } from 'node:path';
|
|
|
12
12
|
import { randomBytes } from 'node:crypto';
|
|
13
13
|
import { renameSyncWithRetry } from './file-api.js';
|
|
14
14
|
import { withFileLockAsync } from './async-file-lock.js';
|
|
15
|
-
import { mergeApprovalModalPrefs } from '
|
|
15
|
+
import { mergeApprovalModalPrefs } from '@ccv/core/approval-modal-prefs';
|
|
16
16
|
import { reconcileVoicePackPrefs } from './voice-pack-manager.js';
|
|
17
17
|
import { LOG_DIR } from '../../findcc.js';
|
|
18
18
|
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// A project "has a fork" iff a key exists for it (hasOwnProperty). Removing a fork
|
|
10
10
|
// (toggle off) deletes the key so the project inherits global again.
|
|
11
11
|
import { basename } from 'node:path';
|
|
12
|
-
import { mergeApprovalModalPrefs } from '
|
|
13
|
-
import { listPlatforms } from './im-config.js';
|
|
14
|
-
import {
|
|
12
|
+
import { mergeApprovalModalPrefs } from '@ccv/core/approval-modal-prefs';
|
|
13
|
+
import { listPlatforms } from './im/im-config.js';
|
|
14
|
+
import { getProjectName } from './project-state.js';
|
|
15
15
|
|
|
16
16
|
// SINGLE SOURCE of "what must never live inside a fork": secrets (auth password + IM creds),
|
|
17
17
|
// machine-level paths (logDir/claudeConfigDir), and the internal forks map. snapshotForFork /
|
|
@@ -33,17 +33,17 @@ export function getCurrentProjectKey() {
|
|
|
33
33
|
|
|
34
34
|
/** Human-readable project name for display (interceptor's sanitized basename). */
|
|
35
35
|
export function getCurrentProjectName() {
|
|
36
|
-
return
|
|
36
|
+
return getProjectName() || basename(getCurrentProjectKey());
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Whether the server is currently serving a concrete project. In workspace mode the
|
|
41
|
-
* project is unknown until a workspace is launched (
|
|
41
|
+
* project is unknown until a workspace is launched (project-state name / env are
|
|
42
42
|
* empty) — refuse to fork under the server's own cwd in that window.
|
|
43
43
|
*/
|
|
44
44
|
export function hasActiveProject() {
|
|
45
45
|
if (process.env.CCV_WORKSPACE_MODE === '1') {
|
|
46
|
-
return !!(process.env.CCV_PROJECT_DIR ||
|
|
46
|
+
return !!(process.env.CCV_PROJECT_DIR || getProjectName());
|
|
47
47
|
}
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Mutable "current project name" state — shared leaf (same pattern as findcc's
|
|
2
|
+
// LOG_DIR). server/interceptor.js owns the value and pushes it at every rebind
|
|
3
|
+
// (module init, initForWorkspace, resetWorkspace); lib readers such as
|
|
4
|
+
// project-prefs.js must not import the interceptor (a stateful L2 module), so
|
|
5
|
+
// they read through this leaf instead.
|
|
6
|
+
|
|
7
|
+
let _currentProjectName = '';
|
|
8
|
+
|
|
9
|
+
export function getProjectName() {
|
|
10
|
+
return _currentProjectName;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function setProjectName(name) {
|
|
14
|
+
_currentProjectName = typeof name === 'string' ? name : '';
|
|
15
|
+
}
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
// - Streaming responses: only read status + headers to decide whether to retry; never retry after the body has
|
|
16
16
|
// started being sent (retry-before-first-byte strategy).
|
|
17
17
|
// - race/stagger use AbortController; cancelled requests must be released correctly.
|
|
18
|
-
import { resolveProfileModel } from '
|
|
18
|
+
import { resolveProfileModel } from '../interceptor-core.js';
|
|
19
19
|
import { readFileSync, existsSync } from 'node:fs';
|
|
20
|
-
import { reportSwallowed } from '
|
|
20
|
+
import { reportSwallowed } from '@ccv/core/error-report';
|
|
21
21
|
|
|
22
22
|
// ── Configuration ─────────────────────────────────────────────────
|
|
23
23
|
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// - Fully separated from cc-viewer's existing session logs (*.jsonl written by interceptor), no cross-contamination.
|
|
9
9
|
import { mkdirSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
-
import { AsyncWriteQueue } from '
|
|
12
|
-
import { reportSwallowed } from '
|
|
11
|
+
import { AsyncWriteQueue } from '../async-write-queue.js';
|
|
12
|
+
import { reportSwallowed } from '@ccv/core/error-report';
|
|
13
13
|
|
|
14
14
|
// ── Detail record schema ──────────────────────────────────────────────
|
|
15
15
|
// Appends one JSON line after each proxied LLM API request completes. Fields align with llm-retry-proxy's retry records.
|
|
@@ -136,7 +136,7 @@ export function flushRecords() {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// ── Stats-update notifier (dependency inversion, review P2) ────────────────
|
|
139
|
-
// proxy.js used to dynamically import('
|
|
139
|
+
// proxy.js used to dynamically import('../server.js') to reach the statsWorker
|
|
140
140
|
// singleton — if a future proxy-only process did that first, server.js's
|
|
141
141
|
// module-load side effects would boot a second viewer. The owner (server.js)
|
|
142
142
|
// now registers its notify callback here at load time; proxy.js just emits.
|
|
@@ -1,81 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sdk-adapter.js —
|
|
2
|
+
* sdk-adapter.js — SDK-mode approval helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* NOTE: SDK-mode display entries are no longer synthesized here. The SDK child
|
|
5
|
+
* process talks to the API through cc-viewer's loopback proxy, so entries reach
|
|
6
|
+
* the frontend via the same wire path as PTY mode (fetch hook → V2Writer →
|
|
7
|
+
* live-feed → SSE). Only the approval-close mapping below remains in use
|
|
8
|
+
* (server.js interruptTurn fan-out).
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
// Minimal tool stubs — only used as fallback if SDK doesn't provide real tools
|
|
10
|
-
const STUB_TOOLS = [
|
|
11
|
-
{ name: 'Bash' }, { name: 'Edit' }, { name: 'Read' },
|
|
12
|
-
{ name: 'Write' }, { name: 'Glob' }, { name: 'Agent' },
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Convert an SDK assistant message + accumulated conversation into a JSONL entry.
|
|
17
|
-
*
|
|
18
|
-
* @param {object} assistantMsg — SDKAssistantMessage (has .message: BetaMessage)
|
|
19
|
-
* @param {Array} messages — Accumulated conversation messages [{role, content}]
|
|
20
|
-
* @param {string} model — Model name
|
|
21
|
-
* @param {string} projectName — Project name
|
|
22
|
-
* @param {object} [opts] — Additional options
|
|
23
|
-
* @param {string} [opts.timestamp] — Stable timestamp for dedup (same per turn)
|
|
24
|
-
* @param {boolean} [opts.inProgress] — Whether response is still streaming
|
|
25
|
-
* @param {string} [opts.requestId] — Request ID for in-progress entries
|
|
26
|
-
* @param {Array} [opts.tools] — Real tools list from SDK init message
|
|
27
|
-
* @returns {object} JSONL entry compatible with frontend expectations
|
|
28
|
-
*/
|
|
29
|
-
export function sdkToJSONLEntry(assistantMsg, messages, model, projectName, opts = {}) {
|
|
30
|
-
const respBody = assistantMsg?.message || null;
|
|
31
|
-
const entry = {
|
|
32
|
-
timestamp: opts.timestamp || new Date().toISOString(),
|
|
33
|
-
project: projectName || 'sdk',
|
|
34
|
-
url: 'https://api.anthropic.com/v1/messages?beta=true',
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: {},
|
|
37
|
-
body: {
|
|
38
|
-
model: model || respBody?.model || 'claude-opus-4-6',
|
|
39
|
-
system: [{ type: 'text', text: 'You are Claude Code' }],
|
|
40
|
-
tools: opts.tools || STUB_TOOLS,
|
|
41
|
-
messages: messages || [],
|
|
42
|
-
metadata: {},
|
|
43
|
-
},
|
|
44
|
-
response: opts.inProgress ? null : {
|
|
45
|
-
status: 200,
|
|
46
|
-
statusText: 'OK',
|
|
47
|
-
headers: {},
|
|
48
|
-
body: respBody,
|
|
49
|
-
},
|
|
50
|
-
duration: 0,
|
|
51
|
-
isStream: false,
|
|
52
|
-
mainAgent: true,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (opts.inProgress) {
|
|
56
|
-
entry.inProgress = true;
|
|
57
|
-
entry.requestId = opts.requestId || `sdk_${Date.now()}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return entry;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Build a streaming status event payload.
|
|
65
|
-
*/
|
|
66
|
-
export function buildStreamingStatus(active, meta = {}) {
|
|
67
|
-
if (active) {
|
|
68
|
-
return {
|
|
69
|
-
active: true,
|
|
70
|
-
model: meta.model || null,
|
|
71
|
-
startTime: meta.startTime || Date.now(),
|
|
72
|
-
bytesReceived: 0,
|
|
73
|
-
chunksReceived: 0,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return { active: false };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
11
|
/**
|
|
80
12
|
* Map an SDK approval `kind` to the WS broadcast message type that closes that
|
|
81
13
|
* kind's modal on every client. Used when interruptTurn() drains pending approvals
|