commit
22c5c7a1bb
22
build.ts
22
build.ts
|
|
@ -8,15 +8,19 @@ const outdir = 'dist'
|
||||||
const { rmSync } = await import('fs')
|
const { rmSync } = await import('fs')
|
||||||
rmSync(outdir, { recursive: true, force: true })
|
rmSync(outdir, { recursive: true, force: true })
|
||||||
|
|
||||||
// Step 1.5: Generate review builtin files
|
// Step 1.5: Generate review builtin files (skip with SKIP_REVIEW_BUILTIN=1)
|
||||||
console.log('Generating review builtin files...')
|
if (process.env.SKIP_REVIEW_BUILTIN) {
|
||||||
const { spawnSync: genSpawnSync } = await import('child_process')
|
console.log('Skipping review builtin generation (SKIP_REVIEW_BUILTIN is set)')
|
||||||
const genResult = genSpawnSync('bun', ['run', 'scripts/generate-review-builtin.ts'], {
|
} else {
|
||||||
stdio: 'inherit',
|
console.log('Generating review builtin files...')
|
||||||
cwd: process.cwd(),
|
const { spawnSync: genSpawnSync } = await import('child_process')
|
||||||
})
|
const genResult = genSpawnSync('bun', ['run', 'scripts/generate-review-builtin.ts'], {
|
||||||
if (genResult.status !== 0) {
|
stdio: 'inherit',
|
||||||
console.warn('Warning: generate-review-builtin.ts failed, using existing files')
|
cwd: process.cwd(),
|
||||||
|
})
|
||||||
|
if (genResult.status !== 0) {
|
||||||
|
console.warn('Warning: generate-review-builtin.ts failed, using existing files')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default features that match the official CLI build.
|
// Default features that match the official CLI build.
|
||||||
|
|
|
||||||
|
|
@ -73,14 +73,18 @@ const inspectArgs = process.env.BUN_INSPECT
|
||||||
// npm, etc.) and on all platforms.
|
// npm, etc.) and on all platforms.
|
||||||
const bunCmd = process.execPath;
|
const bunCmd = process.execPath;
|
||||||
|
|
||||||
// Generate review builtin files before dev launch
|
// Generate review builtin files before dev launch (skip with SKIP_REVIEW_BUILTIN=1)
|
||||||
console.log('[dev] Generating review builtin files...');
|
if (process.env.SKIP_REVIEW_BUILTIN) {
|
||||||
const genResult = Bun.spawnSync(['bun', 'run', 'scripts/generate-review-builtin.ts'], {
|
console.log('[dev] Skipping review builtin generation (SKIP_REVIEW_BUILTIN is set)');
|
||||||
stdio: ["inherit", "inherit", "inherit"],
|
} else {
|
||||||
cwd: projectRoot,
|
console.log('[dev] Generating review builtin files...');
|
||||||
});
|
const genResult = Bun.spawnSync(['bun', 'run', 'scripts/generate-review-builtin.ts'], {
|
||||||
if (!genResult.success) {
|
stdio: ["inherit", "inherit", "inherit"],
|
||||||
console.warn('[dev] Warning: generate-review-builtin.ts failed, using existing files');
|
cwd: projectRoot,
|
||||||
|
});
|
||||||
|
if (!genResult.success) {
|
||||||
|
console.warn('[dev] Warning: generate-review-builtin.ts failed, using existing files');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = [bunCmd, ...inspectArgs, "run", ...defineArgs, ...featureArgs, cliPath, ...process.argv.slice(2)];
|
const args = [bunCmd, ...inspectArgs, "run", ...defineArgs, ...featureArgs, cliPath, ...process.argv.slice(2)];
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ import {
|
||||||
DEFAULT_OUTPUT_STYLE_NAME,
|
DEFAULT_OUTPUT_STYLE_NAME,
|
||||||
getAllOutputStyles,
|
getAllOutputStyles,
|
||||||
} from 'src/constants/outputStyles.js'
|
} from 'src/constants/outputStyles.js'
|
||||||
import { TEAMMATE_MESSAGE_TAG, TICK_TAG } from 'src/constants/xml.js'
|
import { TASK_NOTIFICATION_TAG, TEAMMATE_MESSAGE_TAG, TICK_TAG } from 'src/constants/xml.js'
|
||||||
import {
|
import {
|
||||||
getSettings_DEPRECATED,
|
getSettings_DEPRECATED,
|
||||||
getSettingsWithSources,
|
getSettingsWithSources,
|
||||||
|
|
@ -4360,29 +4360,44 @@ function runHeadlessStreaming(
|
||||||
trackReceivedMessageUuid(userMsg.uuid as UUID)
|
trackReceivedMessageUuid(userMsg.uuid as UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueue({
|
const rawContent = typeof userMsg.content === 'string' ? userMsg.content : ''
|
||||||
mode: 'prompt' as const,
|
const isTaskNotification = rawContent.trimStart().startsWith(
|
||||||
// file_attachments rides the protobuf catchall from the web composer.
|
`<${TASK_NOTIFICATION_TAG}>`,
|
||||||
// Same-ref no-op when absent (no 'file_attachments' key).
|
)
|
||||||
value: await resolveAndPrepend(
|
|
||||||
userMsg,
|
if (isTaskNotification) {
|
||||||
(userMsg.message as { content: ContentBlockParam[] }).content,
|
enqueue({
|
||||||
),
|
mode: 'task-notification' as const,
|
||||||
uuid: userMsg.uuid as `${string}-${string}-${string}-${string}-${string}`,
|
value: rawContent,
|
||||||
priority: (userMsg as { priority?: string })
|
uuid: userMsg.uuid as `${string}-${string}-${string}-${string}-${string}`,
|
||||||
.priority as import('src/types/textInputTypes.js').QueuePriority,
|
priority: (userMsg as { priority?: string })
|
||||||
})
|
.priority as import('src/types/textInputTypes.js').QueuePriority,
|
||||||
// Increment prompt count for attribution tracking and save snapshot
|
})
|
||||||
// The snapshot persists promptCount so it survives compaction
|
} else {
|
||||||
if (feature('COMMIT_ATTRIBUTION')) {
|
enqueue({
|
||||||
setAppState(prev => ({
|
mode: 'prompt' as const,
|
||||||
...prev,
|
// file_attachments rides the protobuf catchall from the web composer.
|
||||||
attribution: incrementPromptCount(prev.attribution, snapshot => {
|
// Same-ref no-op when absent (no 'file_attachments' key).
|
||||||
void recordAttributionSnapshot(snapshot).catch(error => {
|
value: await resolveAndPrepend(
|
||||||
logForDebugging(`Attribution: Failed to save snapshot: ${error}`)
|
userMsg,
|
||||||
})
|
(userMsg.message as { content: ContentBlockParam[] }).content,
|
||||||
}),
|
),
|
||||||
}))
|
uuid: userMsg.uuid as `${string}-${string}-${string}-${string}-${string}`,
|
||||||
|
priority: (userMsg as { priority?: string })
|
||||||
|
.priority as import('src/types/textInputTypes.js').QueuePriority,
|
||||||
|
})
|
||||||
|
// Increment prompt count for attribution tracking and save snapshot
|
||||||
|
// The snapshot persists promptCount so it survives compaction
|
||||||
|
if (feature('COMMIT_ATTRIBUTION')) {
|
||||||
|
setAppState(prev => ({
|
||||||
|
...prev,
|
||||||
|
attribution: incrementPromptCount(prev.attribution, snapshot => {
|
||||||
|
void recordAttributionSnapshot(snapshot).catch(error => {
|
||||||
|
logForDebugging(`Attribution: Failed to save snapshot: ${error}`)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void run()
|
void run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class EventBus {
|
||||||
const sid = dataObj?.session_id ?? dataObj?.sessionID
|
const sid = dataObj?.session_id ?? dataObj?.sessionID
|
||||||
if (typeof sid === 'string') {
|
if (typeof sid === 'string') {
|
||||||
const sessionCwd = this.sessionCwds.get(sid)
|
const sessionCwd = this.sessionCwds.get(sid)
|
||||||
if (sessionCwd && sessionCwd !== client.cwdFilter) {
|
if (sessionCwd !== client.cwdFilter) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,14 +221,12 @@ export function createSessionRoutes(
|
||||||
const url = new URL(c.req.url)
|
const url = new URL(c.req.url)
|
||||||
const limit = parseInt(url.searchParams.get('limit') ?? '50', 10)
|
const limit = parseInt(url.searchParams.get('limit') ?? '50', 10)
|
||||||
const offset = parseInt(url.searchParams.get('offset') ?? '0', 10)
|
const offset = parseInt(url.searchParams.get('offset') ?? '0', 10)
|
||||||
// 优先从请求头 x-csc-directory 取工作目录(cs-cloud 通过此头传递),
|
|
||||||
// fallback 到 query string 的 dir 参数
|
|
||||||
const headerDir = c.req.header('x-csc-directory')
|
const headerDir = c.req.header('x-csc-directory')
|
||||||
const dir = (headerDir ? decodeURIComponent(headerDir) : undefined)
|
const dir = (headerDir ? decodeURIComponent(headerDir) : undefined)
|
||||||
?? url.searchParams.get('dir')
|
?? url.searchParams.get('dir')
|
||||||
?? undefined
|
?? undefined
|
||||||
// roots=true 时只返回没有 parentID 的顶层 session(csc 无 parent 概念,全部视为 root)
|
|
||||||
// const rootsOnly = url.searchParams.get('roots') === 'true'
|
const canonicalDir = dir ? await canonicalizePath(dir).catch(() => dir) : undefined
|
||||||
|
|
||||||
// 从磁盘读取历史 session 列表
|
// 从磁盘读取历史 session 列表
|
||||||
let historySessions: Awaited<ReturnType<typeof listSessionsImpl>> = []
|
let historySessions: Awaited<ReturnType<typeof listSessionsImpl>> = []
|
||||||
|
|
@ -271,7 +269,6 @@ export function createSessionRoutes(
|
||||||
|
|
||||||
// 补充内存中有但磁盘还没落盘的活跃 session(刚创建还没写过消息的)
|
// 补充内存中有但磁盘还没落盘的活跃 session(刚创建还没写过消息的)
|
||||||
const historyIds = new Set(historySessions.map(s => s.sessionId))
|
const historyIds = new Set(historySessions.map(s => s.sessionId))
|
||||||
const canonicalDir = dir ? await canonicalizePath(dir).catch(() => dir) : undefined
|
|
||||||
for (const handle of sessionManager.getAllSessions()) {
|
for (const handle of sessionManager.getAllSessions()) {
|
||||||
if (!historyIds.has(handle.sessionId)) {
|
if (!historyIds.has(handle.sessionId)) {
|
||||||
if (canonicalDir) {
|
if (canonicalDir) {
|
||||||
|
|
@ -283,7 +280,31 @@ export function createSessionRoutes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤掉已知的无意义管理命令会话(用户直接输入后立即退出,没有实际对话内容)
|
// Pre-canonicalize all session cwds for consistent cwd filtering
|
||||||
|
const cwdCache = new Map<string, string>()
|
||||||
|
const getCwd = async (p: string) => {
|
||||||
|
const hit = cwdCache.get(p)
|
||||||
|
if (hit !== undefined) return hit
|
||||||
|
const c = await canonicalizePath(p).catch(() => p)
|
||||||
|
cwdCache.set(p, c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by cwd to prevent cross-workspace leakage
|
||||||
|
const cwdFiltered: typeof merged = []
|
||||||
|
for (const s of merged) {
|
||||||
|
if (canonicalDir) {
|
||||||
|
const sessionCwd = s.cwd ?? s.directory ?? ''
|
||||||
|
if (sessionCwd) {
|
||||||
|
const c = await getCwd(sessionCwd)
|
||||||
|
if (c !== canonicalDir) continue
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cwdFiltered.push(s)
|
||||||
|
}
|
||||||
|
|
||||||
const BORING_COMMANDS = new Set([
|
const BORING_COMMANDS = new Set([
|
||||||
'/exit', '/quit', '/bye',
|
'/exit', '/quit', '/bye',
|
||||||
'/clear', '/reset',
|
'/clear', '/reset',
|
||||||
|
|
@ -292,7 +313,7 @@ export function createSessionRoutes(
|
||||||
'/help', '/version',
|
'/help', '/version',
|
||||||
'/compact',
|
'/compact',
|
||||||
])
|
])
|
||||||
const filtered = merged.filter(s => {
|
const filtered = cwdFiltered.filter(s => {
|
||||||
const t = s.title.trim()
|
const t = s.title.trim()
|
||||||
if (!t) return false
|
if (!t) return false
|
||||||
if (BORING_COMMANDS.has(t)) return false
|
if (BORING_COMMANDS.has(t)) return false
|
||||||
|
|
@ -308,7 +329,10 @@ export function createSessionRoutes(
|
||||||
.get('/session/status', async c => {
|
.get('/session/status', async c => {
|
||||||
const headerDir = c.req.header('x-csc-directory')
|
const headerDir = c.req.header('x-csc-directory')
|
||||||
const dir = headerDir ? decodeURIComponent(headerDir) : undefined
|
const dir = headerDir ? decodeURIComponent(headerDir) : undefined
|
||||||
const statuses = sessionManager.getSessionStatuses(dir)
|
const activeCount = sessionManager.getActiveCount()
|
||||||
|
const indexCount = sessionManager.getLoadedIndexCount()
|
||||||
|
const statuses = await sessionManager.getSessionStatuses(dir)
|
||||||
|
process.stderr.write(`[status] dir=${dir ?? '(none)'} active=${activeCount} index=${indexCount} result=${JSON.stringify(statuses)}\n`)
|
||||||
return c.json(statuses)
|
return c.json(statuses)
|
||||||
})
|
})
|
||||||
.get('/session/:sessionID', async c => {
|
.get('/session/:sessionID', async c => {
|
||||||
|
|
@ -318,6 +342,7 @@ export function createSessionRoutes(
|
||||||
const info = handle.getInfo()
|
const info = handle.getInfo()
|
||||||
return c.json({
|
return c.json({
|
||||||
...info,
|
...info,
|
||||||
|
busy_status: handle.getEffectiveBusyStatus(),
|
||||||
message_count: handle.messageCount,
|
message_count: handle.messageCount,
|
||||||
usage: handle.usage,
|
usage: handle.usage,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,16 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
return this._lastMessageUuid
|
return this._lastMessageUuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEffectiveBusyStatus(): SessionBusyStatus {
|
||||||
|
if (this._prompting) {
|
||||||
|
return { type: 'busy' }
|
||||||
|
}
|
||||||
|
if (this._status === 'stopped') {
|
||||||
|
return { type: 'idle' }
|
||||||
|
}
|
||||||
|
return this._busyStatus
|
||||||
|
}
|
||||||
|
|
||||||
get ready(): boolean {
|
get ready(): boolean {
|
||||||
return this._status === 'running'
|
return this._status === 'running'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,10 @@ export class SessionManager {
|
||||||
return this.sessions.size
|
return this.sessions.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLoadedIndexCount(): number {
|
||||||
|
return this.loadedIndex.size
|
||||||
|
}
|
||||||
|
|
||||||
getSession(id: string): SessionHandle | undefined {
|
getSession(id: string): SessionHandle | undefined {
|
||||||
return this.sessions.get(id)
|
return this.sessions.get(id)
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +167,20 @@ export class SessionManager {
|
||||||
const handleCwd = await canonicalizePath(handle.cwd)
|
const handleCwd = await canonicalizePath(handle.cwd)
|
||||||
if (handleCwd !== canonicalCwd) continue
|
if (handleCwd !== canonicalCwd) continue
|
||||||
}
|
}
|
||||||
result[id] = handle.busyStatus
|
result[id] = handle.getEffectiveBusyStatus()
|
||||||
|
}
|
||||||
|
// Supplement with persisted sessions that are no longer in memory
|
||||||
|
// (evicted by idle timeout / LRU). They are always idle.
|
||||||
|
if (canonicalCwd) {
|
||||||
|
for (const [id, entry] of this.loadedIndex) {
|
||||||
|
if (result[id]) continue
|
||||||
|
try {
|
||||||
|
const entryCwd = await canonicalizePath(entry.cwd)
|
||||||
|
if (entryCwd === canonicalCwd) {
|
||||||
|
result[id] = { type: 'idle' }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
@ -220,9 +237,8 @@ export class SessionManager {
|
||||||
|
|
||||||
this.sessions.set(sessionId, handle)
|
this.sessions.set(sessionId, handle)
|
||||||
if (!opts.silent) {
|
if (!opts.silent) {
|
||||||
void canonicalizePath(cwd).then(canonical => {
|
const canonical = await canonicalizePath(cwd).catch(() => cwd)
|
||||||
this.eventBus.registerSessionCwd(sessionId, canonical)
|
this.eventBus.registerSessionCwd(sessionId, canonical)
|
||||||
})
|
|
||||||
this.eventBus.publishSessionEvent(sessionId, 'created', {
|
this.eventBus.publishSessionEvent(sessionId, 'created', {
|
||||||
status: 'starting',
|
status: 'starting',
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user