cc-viewer 1.8.13 → 1.8.15
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 +6 -5
- package/cli.js +19 -0
- package/dist/assets/{App-R6odqLoE.js → App-DxDo_tCQ.js} +2 -2
- package/dist/assets/{MdxEditorPanel-CTqsETwc.js → MdxEditorPanel-Ddpu5ELR.js} +1 -1
- package/dist/assets/Mobile-DA0yMMgI.js +1 -0
- package/dist/assets/{ProxyStatsModal-CM2H8JYh.js → ProxyStatsModal-DKKfSpXx.js} +1 -1
- package/dist/assets/index-BCpkzaVk.js +2 -0
- package/dist/assets/index-Nvk8r3tO.css +1 -0
- package/dist/assets/{seqResourceLoaders-Xx6a8E03.css → seqResourceLoaders-B_FSQh3U.css} +1 -1
- package/dist/assets/seqResourceLoaders-BkzxH2jr.js +2 -0
- package/dist/index.html +2 -2
- package/node_modules/@ccv/core/src/context-rules.js +5 -0
- package/package.json +1 -1
- package/server/i18n.js +2 -2
- package/server/interceptor.js +176 -21
- package/server/lib/builtin-model-prompts.js +12 -1
- package/server/lib/create_system_prompt.js +94 -1
- package/server/lib/ensure-hooks.js +34 -3
- package/server/lib/interceptor-core.js +144 -0
- package/server/lib/launch-config.js +83 -3
- package/server/lib/model-system-prompts.js +6 -1
- package/server/lib/proxy/proxy-retry.js +57 -2
- package/server/lib/resume-watchdog.js +108 -0
- package/server/lib/session-id.js +33 -0
- package/server/lib/shell-hook-inspect.js +48 -0
- package/server/lib/system-prompt-files.js +14 -0
- package/server/lib/system-prompt-live.js +622 -0
- package/server/lib/task-bridge.js +10 -3
- package/server/lib/task-state.js +32 -0
- package/server/lib/v2/identity.js +3 -24
- package/server/lib/v2/session-select.js +11 -8
- package/server/lib/v2/v2-writer.js +13 -5
- package/server/proxy.js +32 -6
- package/server/routes/events.js +9 -3
- package/server/routes/preferences.js +6 -1
- package/server/routes/workspaces.js +3 -2
- package/server/server.js +35 -5
- package/server/system-prompt-templates/presets/GLM-5.2.md +2 -0
- package/server/system-prompt-templates/presets/GLM-5.3.md +2 -0
- package/server/system-prompt-templates/presets/{Qwen-3.7-Max.md → Qwen-3.md} +3 -1
- package/server/system-prompt-templates/presets/deepseek-v4-flash.md +2 -0
- package/server/system-prompt-templates/presets/deepseek-v4-pro.md +2 -0
- package/server/system-prompt-templates/presets/index.json +7 -7
- package/server/system-prompt-templates/presets/kimi-k2.7-code.md +3 -0
- package/server/system-prompt-templates/presets/kimi-k3.md +2 -0
- package/dist/assets/Mobile-o_QrQ_eI.js +0 -1
- package/dist/assets/index--yjDHxJD.js +0 -2
- package/dist/assets/index-D1yGak8I.css +0 -1
- package/dist/assets/seqResourceLoaders-BgeRQt8C.js +0 -2
package/server/lib/task-state.js
CHANGED
|
@@ -34,6 +34,14 @@ function _touch(task) {
|
|
|
34
34
|
export function applyTaskEvent(payload) {
|
|
35
35
|
if (!payload || typeof payload !== 'object') return;
|
|
36
36
|
const { hookEventName } = payload;
|
|
37
|
+
// New-prompt reset. MUST stay above the taskId guard (this event has no
|
|
38
|
+
// task_id) and MUST NOT move _sessionId — a foreign/malformed prompt event
|
|
39
|
+
// must not change the shouldResetTasks comparison base. resetTasks() nulls
|
|
40
|
+
// the tag anyway, so the next TaskCreated re-tags the session.
|
|
41
|
+
if (hookEventName === 'UserPromptSubmit') {
|
|
42
|
+
if (shouldResetTasksOnPrompt(payload, _sessionId)) resetTasks();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
37
45
|
const taskId = payload.taskId != null ? String(payload.taskId) : null;
|
|
38
46
|
// Validate BEFORE touching the session tag: a malformed/unknown event must
|
|
39
47
|
// not move the shouldResetTasks comparison base.
|
|
@@ -139,6 +147,30 @@ export function shouldResetTasks(payload, currentSessionId) {
|
|
|
139
147
|
return false;
|
|
140
148
|
}
|
|
141
149
|
|
|
150
|
+
/**
|
|
151
|
+
* New-prompt reset gate (pure, unit-tested). Claude Code fires
|
|
152
|
+
* UserPromptSubmit on every user prompt (queued-message drains included) with
|
|
153
|
+
* NO task_id; the previous turn's checklist is stale by definition, so the
|
|
154
|
+
* shared list resets and the model's next TaskUpdate rebuilds whatever it is
|
|
155
|
+
* still working on (stub semantics).
|
|
156
|
+
* Conservative guards: a teammate/subagent process inherits CCVIEWER_PORT and
|
|
157
|
+
* POSTs to the same /api/task-event, so only a prompt bearing the session we
|
|
158
|
+
* are already tracking may wipe the shared list. (agent_id is NOT reliably
|
|
159
|
+
* present on this event — the session check is the load-bearing one.)
|
|
160
|
+
* Known trade-off: while the tag is null (no main-agent task event seen yet,
|
|
161
|
+
* e.g. right after a reset — teammate TaskCreated carries agentId and does not
|
|
162
|
+
* re-tag), ANY prompt passes the gate. A teammate prompt in that window still
|
|
163
|
+
* clears the list; it self-heals on the next TaskUpdate. See task-state.test.js
|
|
164
|
+
* ("null-tag window") which locks this behavior deliberately.
|
|
165
|
+
*/
|
|
166
|
+
export function shouldResetTasksOnPrompt(payload, currentSessionId) {
|
|
167
|
+
const { agentId, sessionId } = payload || {};
|
|
168
|
+
if (agentId) return false;
|
|
169
|
+
if (!sessionId) return false;
|
|
170
|
+
if (currentSessionId && sessionId !== currentSessionId) return false;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
142
174
|
/** Full-snapshot view for SSE broadcast; insertion order preserved. */
|
|
143
175
|
export function getTaskSnapshot() {
|
|
144
176
|
return { sessionId: _sessionId, tasks: [..._tasks.values()] };
|
|
@@ -13,30 +13,9 @@
|
|
|
13
13
|
// prefix (server/lib/interceptor-core.js TEAMMATE_PROMPT_PREFIX_LEN).
|
|
14
14
|
export const SPAWN_PROMPT_PREFIX_LEN = 60;
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Parse a raw metadata.user_id string into { sessionId, encoding } or null.
|
|
20
|
-
* Encodings (spec §8):
|
|
21
|
-
* - 'json': '{"device_id":…,"account_uuid":…,"session_id":"<uuid>"}'
|
|
22
|
-
* - 'delimited': 'user_<hash>_account_<acct?>_session_<uuid>'
|
|
23
|
-
*/
|
|
24
|
-
export function parseUserId(userIdRaw) {
|
|
25
|
-
if (typeof userIdRaw !== 'string' || userIdRaw === '') return null;
|
|
26
|
-
try {
|
|
27
|
-
const obj = JSON.parse(userIdRaw);
|
|
28
|
-
if (obj && typeof obj.session_id === 'string' && obj.session_id !== '') {
|
|
29
|
-
return { sessionId: obj.session_id, encoding: 'json' };
|
|
30
|
-
}
|
|
31
|
-
return null; // valid JSON but no session_id — treat as unparseable
|
|
32
|
-
} catch { /* not JSON → try the delimited form */ }
|
|
33
|
-
const idx = userIdRaw.lastIndexOf('_session_');
|
|
34
|
-
if (idx >= 0) {
|
|
35
|
-
const tail = userIdRaw.slice(idx + '_session_'.length);
|
|
36
|
-
if (UUID_RE.test(tail)) return { sessionId: tail, encoding: 'delimited' };
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
16
|
+
// parseUserId lives in the shared leaf session-id.js (so lib/proxy can use it
|
|
17
|
+
// without an R3 cross-subsystem edge); re-exported here for existing consumers.
|
|
18
|
+
export { parseUserId } from '../session-id.js';
|
|
40
19
|
|
|
41
20
|
const REMINDER_OPEN = '<system-reminder>';
|
|
42
21
|
const REMINDER_CLOSE = '</system-reminder>';
|
|
@@ -142,8 +142,10 @@ export function sessionHasMainTurn(dir) {
|
|
|
142
142
|
* COMPLETED_TURN_SCAN_BUDGET — wide enough that a heavy multi-agent first turn
|
|
143
143
|
* whose done lands megabytes past the head is still found. Stronger than
|
|
144
144
|
* `sessionHasMainTurn`: a session with only an in-flight first main request (req
|
|
145
|
-
* written, response still streaming) returns false
|
|
146
|
-
*
|
|
145
|
+
* written, response still streaming) returns false. This no longer decides the
|
|
146
|
+
* cold-load source on its own — getLiveLogSource ORs `sessionHasMainTurn` onto
|
|
147
|
+
* it to also accept an in-flight first main req on the v3 wire, whose conv
|
|
148
|
+
* prefix is renderable at request initiation (2026-09-13 refresh-blank fix).
|
|
147
149
|
* @param {string} dir - absolute session dir
|
|
148
150
|
* @returns {boolean}
|
|
149
151
|
*/
|
|
@@ -243,11 +245,12 @@ export function isDiscardableSession(dir, meta) {
|
|
|
243
245
|
* the identity UUID (meta.sessionId) alongside the dir so a `-c` continuation
|
|
244
246
|
* can adopt this session's folder while preserving its identity.
|
|
245
247
|
*
|
|
246
|
-
* `excludeDir` skips one absolute session dir entirely
|
|
247
|
-
* its current
|
|
248
|
-
* has-a-main-req
|
|
249
|
-
*
|
|
250
|
-
*
|
|
248
|
+
* `excludeDir` skips one absolute session dir entirely. getLiveLogSource passes
|
|
249
|
+
* its current dir: the caller's activated gate and this picker now share the
|
|
250
|
+
* same has-a-main-req predicate, so exclusion is no longer selection logic —
|
|
251
|
+
* it closes the TOCTOU race where a main req line lands between the caller's
|
|
252
|
+
* gate check and this picker's scan (the dir would otherwise be re-selected
|
|
253
|
+
* as the newest, nullifying the fallback).
|
|
251
254
|
*
|
|
252
255
|
* `skipForeignLive` (multi-window isolation, 2026-07-17) drops candidates
|
|
253
256
|
* whose `owner.lock` is held by ANOTHER live process — a parallel ccv window's
|
|
@@ -294,7 +297,7 @@ export function latestMainSession(projectDir, { excludeDir = '', skipForeignLive
|
|
|
294
297
|
* Absolute dir of the newest readable, non-teammate session that HAS a main
|
|
295
298
|
* turn, or '' if there is none. Thin wrapper over {@link latestMainSession}.
|
|
296
299
|
* @param {string} projectDir - absolute LOG_DIR/<project>
|
|
297
|
-
* @param {{excludeDir?: string}} [opts] - see {@link latestMainSession}
|
|
300
|
+
* @param {{excludeDir?: string, skipForeignLive?: boolean}} [opts] - see {@link latestMainSession}
|
|
298
301
|
* @returns {string} absolute session dir, or ''
|
|
299
302
|
*/
|
|
300
303
|
export function latestMainSessionDir(projectDir, opts) {
|
|
@@ -437,17 +437,25 @@ export class V2Writer {
|
|
|
437
437
|
// count_tokens/heartbeat probes wear main-agent shapes but aren't the user's
|
|
438
438
|
// turn (same gate as the resume-switch above); adopted (-c) sessions skip —
|
|
439
439
|
// their pendings are resumeExpected, reserved for the SessionStart-hook bind.
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
440
|
+
// The latch only locks on a SUCCESSFUL consume: a session's first main-shaped
|
|
441
|
+
// request can be a small-model side call (title/compression) whose system text
|
|
442
|
+
// the live layer already rewrote to that model's persona (interceptor.js:1124-
|
|
443
|
+
// 1151 rewrites requestEntry.body BEFORE this ingest) — it matches no pending,
|
|
444
|
+
// and latching there would permanently deny the real main-model turns that
|
|
445
|
+
// follow (observed: a session whose 8 later PRO-persona requests never rebound,
|
|
446
|
+
// leaving every later `-c` on the F2 no-record path). Unmatched requests retry;
|
|
447
|
+
// a genuinely injection-less session still consumes its EMPTY pending on some
|
|
448
|
+
// later request (the fallback just records "no injection", so deferring it is
|
|
449
|
+
// harmless). The live-sessions-dir gate keeps the offline converter (staging
|
|
450
|
+
// dirs) from consuming LIVE pendings. Fully caught: a lost bind degrades to
|
|
451
|
+
// the no-record resume path, never to a dropped log entry.
|
|
444
452
|
if (!adoptTarget && entry.mainAgent && !entry.isCountTokens && !entry.isHeartbeat
|
|
445
453
|
&& this._sessionsDirName === 'sessions'
|
|
446
454
|
&& s && !s.sysPromptBindDone) {
|
|
447
|
-
s.sysPromptBindDone = true;
|
|
448
455
|
try {
|
|
449
456
|
const pend = consumePendingForWireByKey(project, systemTextOfBody(entry.body), this._logDir);
|
|
450
457
|
if (pend) {
|
|
458
|
+
s.sysPromptBindDone = true;
|
|
451
459
|
writeSnapshotByKey(project, sid, { entries: pend.entries, model: pend.model, boundVia: 'wire' }, { logDir: this._logDir });
|
|
452
460
|
}
|
|
453
461
|
} catch (err) { reportSwallowed('v2-write.sys-prompt-bind', err); }
|
package/server/proxy.js
CHANGED
|
@@ -9,6 +9,8 @@ import { extractApiErrorMessage, formatProxyRequestError } from './lib/proxy/pro
|
|
|
9
9
|
import { getProxyDispatcher } from './lib/proxy/proxy-env.js';
|
|
10
10
|
import { getClaudeConfigDir } from '../findcc.js';
|
|
11
11
|
import { isAnthropicApiPath, classifyProxyRole } from './lib/interceptor-core.js';
|
|
12
|
+
import { parseAgentId, findHeader } from './lib/v2/agent-id.js';
|
|
13
|
+
import { liveSystemPromptEnabled } from './lib/system-prompt-live.js';
|
|
12
14
|
import { executeRequest, extractModel } from './lib/proxy/proxy-retry.js';
|
|
13
15
|
import { buildRecord, appendRecord, dailyFilePath, todayStr, emitProxyStatsUpdate } from './lib/proxy/proxy-stats.js';
|
|
14
16
|
import { reportSwallowed } from '@ccv/core/error-report';
|
|
@@ -134,13 +136,29 @@ export function startProxy() {
|
|
|
134
136
|
const _reqPath = (() => { try { return new URL(req.url || '/', 'http://x').pathname; } catch { return req.url || ''; } })();
|
|
135
137
|
const _isUtility = /\/messages\/count_tokens$/.test(_reqPath) || /^\/api\/eval\/sdk-/.test(_reqPath);
|
|
136
138
|
let roleProfile;
|
|
137
|
-
|
|
139
|
+
let _role; // live system 改写的 role 门(仅 main 生效);未分类 → undefined → main 语义
|
|
140
|
+
const _needClassify = body.length > 0 && isAnthropicApiPath(_reqPath) && !_isUtility;
|
|
141
|
+
// 角色分类在两种情况下都必须做:显式角色分配(选路)或 live system 已启用
|
|
142
|
+
// (否则默认配置下 subagent/teammate 会以 role=undefined 穿过 proxy 侧门被注入主 system)。
|
|
143
|
+
if (_needClassify && (interceptor.hasExplicitRoleAssignments() || liveSystemPromptEnabled())) {
|
|
138
144
|
let parsedBody = null;
|
|
139
145
|
try { parsedBody = JSON.parse(body.toString('utf8')); } catch { /* 非 JSON body → 保持 main 语义 */ }
|
|
140
146
|
if (parsedBody && typeof parsedBody === 'object') {
|
|
141
147
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
// Header 硬判据优先(与 interceptor fetch hook 同构,review P0):
|
|
149
|
+
// x-claude-code-agent-id 是 SDK 命名队友(name@…)/ 匿名子代理(hex)
|
|
150
|
+
// 的判别信号,不依赖 system 文本 —— body 正则对「SDK 身份行 + 无
|
|
151
|
+
// billing 标记」的形态失效,会把 header-only 子代理误判 'main' 而注入
|
|
152
|
+
// 主 persona。named→teammate、anon→subagent,覆盖 body 分类。
|
|
153
|
+
// Header hard-signal first (mirrors the interceptor fetch hook): the
|
|
154
|
+
// agent-id header marks sub-agent streams without relying on body markers.
|
|
155
|
+
const _agent = parseAgentId(findHeader(req.headers, 'x-claude-code-agent-id'));
|
|
156
|
+
_role = _agent
|
|
157
|
+
? (_agent.named ? 'teammate' : 'subagent')
|
|
158
|
+
: classifyProxyRole(parsedBody, {});
|
|
159
|
+
if (interceptor.hasExplicitRoleAssignments()) {
|
|
160
|
+
roleProfile = interceptor.getEffectiveRoleProfile(_role);
|
|
161
|
+
}
|
|
144
162
|
} catch (err) { reportSwallowed('proxy.role-classify', err); }
|
|
145
163
|
}
|
|
146
164
|
}
|
|
@@ -166,7 +184,7 @@ export function startProxy() {
|
|
|
166
184
|
// 模型替换由重试引擎用 resolveProfileModel 纯函数完成(interceptor 对 trace 请求会再跑一次,幂等 no-op)。
|
|
167
185
|
// 非大模型 API 请求走原逻辑(同样用 x-cc-viewer-trace 让 interceptor 记录,但不经重试引擎)。
|
|
168
186
|
if (isAnthropicApiPath(fullUrl)) {
|
|
169
|
-
await handleLlmApiRequest(req, res, fullUrl, fetchOptions, body, proxyDispatcher, roleProfile);
|
|
187
|
+
await handleLlmApiRequest(req, res, fullUrl, fetchOptions, body, proxyDispatcher, roleProfile, { role: _role, isUtility: _isUtility });
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
190
|
|
|
@@ -256,7 +274,7 @@ export function startProxy() {
|
|
|
256
274
|
// 每请求取最新值,UI 改 retry-config.json 后下一个请求即生效,无需重启。
|
|
257
275
|
const _retryConfigGetter = () => interceptor._retryConfigState;
|
|
258
276
|
|
|
259
|
-
async function handleLlmApiRequest(req, res, fullUrl, fetchOptions, body, proxyDispatcher, roleProfile) {
|
|
277
|
+
async function handleLlmApiRequest(req, res, fullUrl, fetchOptions, body, proxyDispatcher, roleProfile, liveCtx = {}) {
|
|
260
278
|
const statsEnabled = process.env.CCV_PROXY_STATS !== 'off';
|
|
261
279
|
const retryConfig = _retryConfigGetter(); // live binding:每请求取最新重试配置
|
|
262
280
|
// 角色解析后的有效 profile(上游/模型替换/统计归属)。undefined = 未分类 → 沿用 main 活跃 profile。
|
|
@@ -299,7 +317,15 @@ async function handleLlmApiRequest(req, res, fullUrl, fetchOptions, body, proxyD
|
|
|
299
317
|
url: fullUrl,
|
|
300
318
|
fetchOptions: retryFetchOptions,
|
|
301
319
|
retryConfig,
|
|
302
|
-
ctx: {
|
|
320
|
+
ctx: {
|
|
321
|
+
dispatcher: proxyDispatcher,
|
|
322
|
+
profile,
|
|
323
|
+
signal: clientAbort.signal,
|
|
324
|
+
// live system 改写上下文(system-prompt-live.js):role 门 + 项目键。
|
|
325
|
+
role: liveCtx.role,
|
|
326
|
+
isUtility: liveCtx.isUtility === true,
|
|
327
|
+
projectKey: interceptor._projectName || '',
|
|
328
|
+
},
|
|
303
329
|
});
|
|
304
330
|
|
|
305
331
|
const { response, attempts, retryCodes, durationMs, finalStatus, succeeded, upstreamStatus } = result;
|
package/server/routes/events.js
CHANGED
|
@@ -6,7 +6,7 @@ import { LOG_DIR } from '../../findcc.js';
|
|
|
6
6
|
import { streamRawEntriesAsync } from '../lib/log-stream.js';
|
|
7
7
|
import { migrationStatus } from '../lib/v2/migrate-prompt.js';
|
|
8
8
|
import { reportSwallowed } from '@ccv/core/error-report';
|
|
9
|
-
import { sseHead, sseWrite, needsDrain, wireEnd, awaitWireDrain } from '../lib/wire-compress.js';
|
|
9
|
+
import { sseHead, sseWrite, needsDrain, wireEnd, awaitWireDrain, isWireV3Enabled } from '../lib/wire-compress.js';
|
|
10
10
|
import { readV2ColdBundle } from '../lib/v2/meta-rows.js';
|
|
11
11
|
import { readV2SingleEntry } from '../lib/v2/adapter.js';
|
|
12
12
|
import { enrichRawIfNeeded } from '../lib/enrich-plan-input.js';
|
|
@@ -248,7 +248,11 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
248
248
|
|
|
249
249
|
// S6b: the cold-load source is the current v2 session dir when the v2
|
|
250
250
|
// writer is active (adapter stream), else the v1 file.
|
|
251
|
-
|
|
251
|
+
// serveInFlight follows the wire: the legacy entry stream ships the
|
|
252
|
+
// in-flight placeholder, which the client batch gate still blocks — the
|
|
253
|
+
// previous conversation is the better cold load there (interceptor.js
|
|
254
|
+
// getLiveLogSource header comment).
|
|
255
|
+
const coldLoadResult = v3Cold ? null : await streamRawEntriesAsync(getLiveLogSource({ serveInFlight: !!deps.wireV3 }), async (raw) => {
|
|
252
256
|
// 直接发送原始 JSON 字符串,不做 parse/reconstruct/stringify
|
|
253
257
|
// ExitPlanMode V2 空 input 的条目按需补全 plan / planFilePath,其它原样透传
|
|
254
258
|
if (res.destroyed || !res.writable) return;
|
|
@@ -438,7 +442,9 @@ async function requests(req, res) {
|
|
|
438
442
|
try {
|
|
439
443
|
sseWrite(res, '[');
|
|
440
444
|
let first = true;
|
|
441
|
-
|
|
445
|
+
// No deps bag on this route — read the wire flag from the same env source
|
|
446
|
+
// server.js uses (isWireV3Enabled), keeping serveInFlight wire-consistent.
|
|
447
|
+
await streamRawEntriesAsync(getLiveLogSource({ serveInFlight: isWireV3Enabled(process.env.CCV_WIRE_V3) }), (raw) => {
|
|
442
448
|
if (!first) sseWrite(res, ',');
|
|
443
449
|
sseWrite(res, enrichRawIfNeeded(raw));
|
|
444
450
|
first = false;
|
|
@@ -11,6 +11,7 @@ import { reportSwallowed } from '@ccv/core/error-report';
|
|
|
11
11
|
import { setLang } from '../i18n.js';
|
|
12
12
|
import { reconcileVoicePackPrefs as vpReconcile } from '../lib/voice-pack-manager.js';
|
|
13
13
|
import { readClaudeProjectModel } from '../lib/context-watcher.js';
|
|
14
|
+
import { inspectShellHook } from '../lib/shell-hook-inspect.js';
|
|
14
15
|
import { sendEventToClients } from '../lib/log-watcher.js';
|
|
15
16
|
import { listPlatforms } from '../lib/im/im-config.js';
|
|
16
17
|
import { mutatePrefs, applyPrefsPatch, readPrefsRaw } from '../lib/prefs-store.js';
|
|
@@ -274,7 +275,11 @@ function claudeSettingsGet(req, res, parsedUrl, isLocal, deps) {
|
|
|
274
275
|
// 错显 200K,详见 src/utils/helpers.js resolveCalibrationTokens)。
|
|
275
276
|
const projectCwd = process.env.CCV_PROJECT_DIR || process.cwd();
|
|
276
277
|
const claudeSettings = deps.claudeSettings;
|
|
277
|
-
|
|
278
|
+
// L1: shell hook 状态(只读;不传模板构造器 → stale 恒 false,stale 精确判定由 cli.js
|
|
279
|
+
// 侧 refreshShellHookState 负责,这里回答「装没装」足够面板提示用)。失败退化 null。
|
|
280
|
+
let shellHook = null;
|
|
281
|
+
try { shellHook = inspectShellHook(null); } catch (err) { reportSwallowed('prefs.shellHook', err); }
|
|
282
|
+
res.end(JSON.stringify({ env, model: claudeSettings.model || null, showThinkingSummaries: claudeSettings.showThinkingSummaries || false, claudeAvailable: process.env.CCV_CLAUDE_MISSING !== '1', claudeProjectModel: readClaudeProjectModel(projectCwd), shellHook }));
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
function claudeSettingsPost(req, res, parsedUrl, isLocal, deps) {
|
|
@@ -99,8 +99,9 @@ function workspacesLaunch(req, res, parsedUrl, isLocal, deps) {
|
|
|
99
99
|
// 流式分段广播以刷新会话区域,避免全量加载 OOM
|
|
100
100
|
// S6b: the live source is the v2 session dir when the v2 writer is
|
|
101
101
|
// active (a fresh workspace has no session yet → empty stream, the live
|
|
102
|
-
// feed picks up from the first request).
|
|
103
|
-
|
|
102
|
+
// feed picks up from the first request). Legacy-shaped reload frames —
|
|
103
|
+
// serveInFlight follows the wire flag, same as /events.
|
|
104
|
+
const wsReloadSource = getLiveLogSource({ serveInFlight: !!deps.wireV3 });
|
|
104
105
|
const wsReloadTotal = await countLogEntries(wsReloadSource);
|
|
105
106
|
deps.clients.forEach(client => {
|
|
106
107
|
try { sseWrite(client, `event: load_start\ndata: ${JSON.stringify({ total: wsReloadTotal, incremental: false })}\n\n`); } catch {}
|
package/server/server.js
CHANGED
|
@@ -66,18 +66,32 @@ function execWithStdin(cmd, args, input, options) {
|
|
|
66
66
|
const child = spawn(cmd, args, { ...options, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
67
67
|
let stdout = '';
|
|
68
68
|
let stderr = '';
|
|
69
|
+
let settled = false;
|
|
70
|
+
const settle = (fn, val) => { if (!settled) { settled = true; fn(val); } };
|
|
69
71
|
child.stdout.on('data', d => { stdout += d; });
|
|
70
72
|
child.stderr.on('data', d => { stderr += d; });
|
|
71
|
-
child.on('error', reject);
|
|
73
|
+
child.on('error', (err) => settle(reject, err));
|
|
72
74
|
child.on('close', code => {
|
|
73
75
|
// git check-ignore exits 1 when no files are ignored — treat as success
|
|
74
|
-
resolve
|
|
76
|
+
settle(resolve, stdout);
|
|
75
77
|
});
|
|
78
|
+
// A child that exits before consuming its stdin (git check-ignore outside a
|
|
79
|
+
// repo, a killed-on-timeout child, a missing binary) closes the read end of
|
|
80
|
+
// the stdin pipe; a subsequent write then throws `write EPIPE`. Without an
|
|
81
|
+
// 'error' listener on child.stdin that EPIPE escapes as an uncaughtException
|
|
82
|
+
// (the recurring server.test.js teardown flake) — swallow it into the
|
|
83
|
+
// promise's settle path instead.
|
|
84
|
+
child.stdin.on('error', () => settle(resolve, stdout));
|
|
76
85
|
if (options?.timeout) {
|
|
77
|
-
setTimeout(() => { try { child.kill(); } catch {} reject
|
|
86
|
+
setTimeout(() => { try { child.kill(); } catch {} settle(reject, new Error('timeout')); }, options.timeout);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
child.stdin.write(input);
|
|
90
|
+
child.stdin.end();
|
|
91
|
+
} catch {
|
|
92
|
+
// Synchronous EPIPE/ERR_STREAM_DESTROYED when the child already exited.
|
|
93
|
+
settle(resolve, stdout);
|
|
78
94
|
}
|
|
79
|
-
child.stdin.write(input);
|
|
80
|
-
child.stdin.end();
|
|
81
95
|
});
|
|
82
96
|
}
|
|
83
97
|
import { _initPromise, _projectName, _logDir, _v2Writer, streamingState, resetStreamingState, PROFILE_PATH, RETRY_CONFIG_PATH, _retryConfigState, setLivePort, getImLiveText, resetImLiveText, markSessionStart } from './interceptor.js';
|
|
@@ -602,6 +616,8 @@ const deps = {
|
|
|
602
616
|
onSessionStartNotify: markSessionStart,
|
|
603
617
|
// Task checklist (task-bridge.js → /api/task-event): apply to the shared
|
|
604
618
|
// in-memory reducer, then debounce-broadcast a full snapshot over SSE.
|
|
619
|
+
// UserPromptSubmit events reset the list inside the reducer, so the same
|
|
620
|
+
// path broadcasts the empty snapshot — no separate reset route/hook.
|
|
605
621
|
onTaskEvent: (payload) => {
|
|
606
622
|
try {
|
|
607
623
|
applyTaskEvent(payload);
|
|
@@ -1237,6 +1253,20 @@ export async function startViewer() {
|
|
|
1237
1253
|
try { await imCore.stopAll(); } catch { /* no in-process instances */ }
|
|
1238
1254
|
imProcMgr.reconcileImProcesses().catch((e) => console.error('[CC Viewer] IM reconcile failed:', e?.message || e));
|
|
1239
1255
|
}
|
|
1256
|
+
// L2: 裸续接检测(transcript 在写但请求未经 ccv → 提示注入丢失)。仅 CLI 模式
|
|
1257
|
+
// (面板场景);检测失败只退化为不提示,绝不影响启动。
|
|
1258
|
+
if (isCliMode) {
|
|
1259
|
+
try {
|
|
1260
|
+
const { startResumeWatchdog } = await import('./lib/resume-watchdog.js');
|
|
1261
|
+
startResumeWatchdog({
|
|
1262
|
+
cwd: process.env.CCV_PROJECT_DIR || process.cwd(),
|
|
1263
|
+
onHit: (hit) => {
|
|
1264
|
+
try { sendEventToClients(clients, 'resume_bypassed', { uuid: hit.uuid, ts: Date.now() }); }
|
|
1265
|
+
catch (e) { reportSwallowed('resume-watchdog.notify', e); }
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
} catch (e) { reportSwallowed('resume-watchdog.start', e); }
|
|
1269
|
+
}
|
|
1240
1270
|
resolve(server);
|
|
1241
1271
|
} catch (err) {
|
|
1242
1272
|
console.error('[CC Viewer] server start callback error:', err?.message || err);
|
|
@@ -20,6 +20,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
20
20
|
- When an approach fails, diagnose the error before trying something else; don't repeat the same failing action.
|
|
21
21
|
- Avoid security vulnerabilities (injection, XSS, path traversal, and the OWASP top 10); fix any insecure code you write.
|
|
22
22
|
- Validate changes by running the relevant tests or code path before reporting completion.
|
|
23
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
23
24
|
|
|
24
25
|
# Using tools
|
|
25
26
|
- Prefer the dedicated tool for reading files, editing files, searching contents, and running commands over ad-hoc shell commands.
|
|
@@ -34,6 +35,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
34
35
|
|
|
35
36
|
# Executing actions with care
|
|
36
37
|
Weigh reversibility and blast radius. Local, reversible actions like editing files or running tests are fine. Confirm with the user before hard-to-reverse or shared-system actions: deleting files or branches, force-pushing, resetting, sending messages, or posting to external services. Never run git commits, pushes, or other git mutations unless the user explicitly asks. Investigate unexpected state before overwriting it.
|
|
38
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
37
39
|
|
|
38
40
|
# Tone and style
|
|
39
41
|
- Keep output brief and direct; lead with the answer or action. No filler, and no emojis unless the user asks.
|
|
@@ -22,6 +22,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
22
22
|
- When an approach fails, diagnose the error before trying something else; don't repeat the same failing action.
|
|
23
23
|
- Avoid security vulnerabilities (injection, XSS, path traversal, and the OWASP top 10); fix any insecure code you write.
|
|
24
24
|
- Validate changes by running the relevant tests or code path before reporting completion.
|
|
25
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
25
26
|
|
|
26
27
|
# Using tools
|
|
27
28
|
- Prefer the dedicated tool for reading files, editing files, searching contents, and running commands over ad-hoc shell commands.
|
|
@@ -36,6 +37,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
36
37
|
|
|
37
38
|
# Executing actions with care
|
|
38
39
|
Weigh reversibility and blast radius. Local, reversible actions like editing files or running tests are fine. Confirm with the user before hard-to-reverse or shared-system actions: deleting files or branches, force-pushing, resetting, sending messages, or posting to external services. Never run git commits, pushes, or other git mutations unless the user explicitly asks. Investigate unexpected state before overwriting it.
|
|
40
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
39
41
|
|
|
40
42
|
# Tone and style
|
|
41
43
|
- Keep output brief and direct; lead with the answer or action. No filler, and no emojis unless the user asks.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
Preset: Qwen-3
|
|
2
|
+
Preset: Qwen-3 (category: Global)
|
|
3
3
|
Self-contained template: a tuned preamble plus its own dynamic sections
|
|
4
4
|
(a boundary marker, an OS-only # Environment, and a verbatim # Memory; no Git).
|
|
5
5
|
Edit this file directly.
|
|
@@ -20,6 +20,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
20
20
|
- Do not give up too early: when blocked, try alternative approaches before asking the user for help.
|
|
21
21
|
- Guard against security vulnerabilities (injection, XSS, SSRF, path traversal, and the rest of the OWASP top 10); fix insecure code immediately.
|
|
22
22
|
- Confirm changes work by running the relevant tests, type checks, or the affected path before declaring completion.
|
|
23
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
23
24
|
|
|
24
25
|
# Using tools
|
|
25
26
|
- Use the dedicated tool for each operation — reading, editing, searching, running commands — instead of improvised shell commands, so your work stays reviewable.
|
|
@@ -34,6 +35,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
34
35
|
|
|
35
36
|
# Executing actions with care
|
|
36
37
|
Consider each action's reversibility and blast radius. Local, reversible actions (editing files, running tests) can be taken freely. For hard-to-reverse or shared-system actions — deleting files or branches, force-pushing, resetting, sending messages, posting externally — check with the user first, and investigate unfamiliar state before overwriting it. Never run git mutations (commit, push, reset, rebase) unless the user explicitly asks.
|
|
38
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
37
39
|
|
|
38
40
|
# Tone and style
|
|
39
41
|
- Keep text output brief and direct; lead with the answer or action and skip filler. No emojis unless the user requests them.
|
|
@@ -19,10 +19,12 @@ IMPORTANT: Do not guess URLs; use ones the user provides or ones found in local
|
|
|
19
19
|
- If something fails, read the error and fix the real cause instead of retrying blindly.
|
|
20
20
|
- Don't write insecure code (injection, XSS, path traversal, etc.); fix it if you do.
|
|
21
21
|
- Confirm before irreversible or shared-system actions (deleting, force-push, sending, posting); never commit unless explicitly asked. Local edits and tests are fine to run freely.
|
|
22
|
+
- Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly.
|
|
22
23
|
|
|
23
24
|
# Tools
|
|
24
25
|
- Use the dedicated tool for reading, editing, searching, and running commands rather than ad-hoc shell.
|
|
25
26
|
- Batch independent tool calls together.
|
|
27
|
+
- Tool results and user messages may include <system-reminder> tags. They carry information from the system, not from the user.
|
|
26
28
|
|
|
27
29
|
# Working with teammates
|
|
28
30
|
- Teammates sometimes finish without reporting back — never wait passively.
|
|
@@ -21,6 +21,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
21
21
|
- Be careful not to introduce security vulnerabilities (injection, XSS, SSRF, path traversal, and the rest of the OWASP top 10). If you notice insecure code you wrote, fix it immediately.
|
|
22
22
|
- Verify your work: run the project's tests, type checks, or the affected code path before claiming a change is complete.
|
|
23
23
|
- Persist until the task is handled end to end: do not stop at analysis or a partial fix; carry the change through implementation and verification before reporting back.
|
|
24
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
24
25
|
|
|
25
26
|
# Using tools
|
|
26
27
|
- Prefer the dedicated tool for each job (reading files, editing files, searching contents, running commands) over ad-hoc shell equivalents, so the user can follow your work.
|
|
@@ -35,6 +36,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
35
36
|
|
|
36
37
|
# Executing actions with care
|
|
37
38
|
Consider the reversibility and blast radius of each action. Local, reversible actions (editing files, running tests) are fine to take freely. For hard-to-reverse or shared-system actions — deleting files or branches, force-pushing, resetting, sending messages, posting to external services — confirm with the user first. Never commit or push unless the user explicitly asks. Never revert or overwrite changes you did not make — the worktree may contain the user's concurrent edits. Investigate unexpected files, branches, or configuration before overwriting them.
|
|
39
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
38
40
|
|
|
39
41
|
# Tone and style
|
|
40
42
|
- Keep text output brief and direct. Lead with the answer or action, not the reasoning. Skip filler and preamble.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Hand-editable manifest of built-in system-prompt presets. Each preset file (presets/<id>.md) is a self-contained template (its own boundary + OS-only Environment + verbatim Memory; no Git). `match` is a case-insensitive model-id substring
|
|
2
|
+
"$comment": "Hand-editable manifest of built-in system-prompt presets. Each preset file (presets/<id>.md) is a self-contained template (its own boundary + OS-only Environment + verbatim Memory; no Git). `match` is a case-insensitive model-id substring with THREE effects: (1) auto-select a preset when adding a model in the Edit System Prompt modal, (2) pick the built-in prompt injected at spawn when the resolved model matches (see builtin-model-prompts.js), and (3) the preset's tombstone identity — the normalized-uppercase match is the key recorded in .builtin-disabled.json, so renaming `match` silently voids existing disables unless a TOMBSTONE_RENAMES mapping is added. `defaultMode` seeds the tab's override/append toggle.",
|
|
3
3
|
"categories": {
|
|
4
4
|
"Global": [
|
|
5
5
|
{
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"id": "GLM-5.3",
|
|
31
31
|
"title": "GLM-5.3",
|
|
32
32
|
"file": "GLM-5.3.md",
|
|
33
|
-
"description": "Forked from the GLM-5.2 preset for GLM-5.3: action-default, changes through tools, parallel tool calls
|
|
33
|
+
"description": "Forked from the GLM-5.2 preset for GLM-5.3: action-default, changes through tools, parallel tool calls.",
|
|
34
34
|
"match": "glm-5.3",
|
|
35
35
|
"defaultMode": "override"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
|
-
"id": "Qwen-3
|
|
39
|
-
"title": "Qwen 3
|
|
40
|
-
"file": "Qwen-3.
|
|
41
|
-
"description": "Thorough coding agent prompt
|
|
42
|
-
"match": "qwen-3
|
|
38
|
+
"id": "Qwen-3",
|
|
39
|
+
"title": "Qwen 3",
|
|
40
|
+
"file": "Qwen-3.md",
|
|
41
|
+
"description": "Thorough coding agent prompt for the Qwen 3 family (3.5/3.7/3.8 Max and siblings): minimal-change discipline and persistence.",
|
|
42
|
+
"match": "qwen-3",
|
|
43
43
|
"defaultMode": "override"
|
|
44
44
|
},
|
|
45
45
|
{
|
|
@@ -23,10 +23,12 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
23
23
|
- Do not introduce security vulnerabilities (injection, XSS, path traversal, and the rest of the OWASP top 10); fix insecure code you write immediately.
|
|
24
24
|
- Iterate to green: run the relevant tests or code path, read the failure, fix the cause, and run again.
|
|
25
25
|
- When an approach is blocked, try a different one before handing the problem back to the user.
|
|
26
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
26
27
|
|
|
27
28
|
# Using tools
|
|
28
29
|
- A change shown only in your reply does not exist on disk — create and edit files with tools, never by pasting code into the conversation.
|
|
29
30
|
- Do not narrate tool calls; the calls themselves show the user what you are doing.
|
|
31
|
+
- Prefer the dedicated tool for reading, editing, searching, and running commands over ad-hoc shell equivalents.
|
|
30
32
|
- Send independent tool calls together in one response instead of one at a time.
|
|
31
33
|
- Track multi-step work explicitly and mark each step done as you finish it.
|
|
32
34
|
- Every ten tool calls, write one line saying what you have confirmed and what is still missing; if you cannot, stop calling tools and report what you have.
|
|
@@ -39,6 +41,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
39
41
|
|
|
40
42
|
# Executing actions with care
|
|
41
43
|
Consider the reversibility and blast radius of each action. Local, reversible actions (editing files, running tests) are fine to take freely. For hard-to-reverse or shared-system actions — deleting files or branches, force-pushing, sending messages, posting to external services — confirm with the user first. Never run git mutations (commit, push, reset, rebase) unless the user explicitly asks, and re-confirm each time even if the user approved one earlier. Investigate unexpected state before overwriting it.
|
|
44
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
42
45
|
|
|
43
46
|
# Tone and style
|
|
44
47
|
- Be thorough in your actions, not in your explanations: report what changed and where, and stop.
|
|
@@ -23,6 +23,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
23
23
|
- Do not introduce security vulnerabilities (injection, XSS, path traversal, and the rest of the OWASP top 10); fix insecure code you write immediately.
|
|
24
24
|
- Iterate to green: run the relevant tests or code path, read the failure, fix the cause, and run again.
|
|
25
25
|
- When an approach is blocked, try a different one before handing the problem back to the user.
|
|
26
|
+
- Write code that reads like the surrounding code: match its comment density, naming, and idiom.
|
|
26
27
|
|
|
27
28
|
# Using tools
|
|
28
29
|
- A change shown only in your reply does not exist on disk — create and edit files with tools, never by pasting code into the conversation.
|
|
@@ -39,6 +40,7 @@ IMPORTANT: Never generate or guess URLs unless you are confident they help the u
|
|
|
39
40
|
|
|
40
41
|
# Executing actions with care
|
|
41
42
|
Consider the reversibility and blast radius of each action. Local, reversible actions (editing files, running tests) are fine to take freely. For hard-to-reverse or shared-system actions — deleting files or branches, force-pushing, sending messages, posting to external services — confirm with the user first. Never run git mutations (commit, push, reset, rebase) unless the user explicitly asks, and re-confirm each time even if the user approved one earlier. Investigate unexpected state before overwriting it.
|
|
43
|
+
Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
|
|
42
44
|
|
|
43
45
|
# Tone and style
|
|
44
46
|
- Be thorough in your actions, not in your explanations: report what changed and where, and stop.
|