commit
22c5c7a1bb
22
build.ts
22
build.ts
|
|
@ -8,15 +8,19 @@ const outdir = 'dist'
|
|||
const { rmSync } = await import('fs')
|
||||
rmSync(outdir, { recursive: true, force: true })
|
||||
|
||||
// Step 1.5: Generate review builtin files
|
||||
console.log('Generating review builtin files...')
|
||||
const { spawnSync: genSpawnSync } = await import('child_process')
|
||||
const genResult = genSpawnSync('bun', ['run', 'scripts/generate-review-builtin.ts'], {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
if (genResult.status !== 0) {
|
||||
console.warn('Warning: generate-review-builtin.ts failed, using existing files')
|
||||
// Step 1.5: Generate review builtin files (skip with SKIP_REVIEW_BUILTIN=1)
|
||||
if (process.env.SKIP_REVIEW_BUILTIN) {
|
||||
console.log('Skipping review builtin generation (SKIP_REVIEW_BUILTIN is set)')
|
||||
} else {
|
||||
console.log('Generating review builtin files...')
|
||||
const { spawnSync: genSpawnSync } = await import('child_process')
|
||||
const genResult = genSpawnSync('bun', ['run', 'scripts/generate-review-builtin.ts'], {
|
||||
stdio: 'inherit',
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -73,14 +73,18 @@ const inspectArgs = process.env.BUN_INSPECT
|
|||
// npm, etc.) and on all platforms.
|
||||
const bunCmd = process.execPath;
|
||||
|
||||
// Generate review builtin files before dev launch
|
||||
console.log('[dev] Generating review builtin files...');
|
||||
const genResult = Bun.spawnSync(['bun', 'run', 'scripts/generate-review-builtin.ts'], {
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
cwd: projectRoot,
|
||||
});
|
||||
if (!genResult.success) {
|
||||
console.warn('[dev] Warning: generate-review-builtin.ts failed, using existing files');
|
||||
// Generate review builtin files before dev launch (skip with SKIP_REVIEW_BUILTIN=1)
|
||||
if (process.env.SKIP_REVIEW_BUILTIN) {
|
||||
console.log('[dev] Skipping review builtin generation (SKIP_REVIEW_BUILTIN is set)');
|
||||
} else {
|
||||
console.log('[dev] Generating review builtin files...');
|
||||
const genResult = Bun.spawnSync(['bun', 'run', 'scripts/generate-review-builtin.ts'], {
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
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)];
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ import {
|
|||
DEFAULT_OUTPUT_STYLE_NAME,
|
||||
getAllOutputStyles,
|
||||
} 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 {
|
||||
getSettings_DEPRECATED,
|
||||
getSettingsWithSources,
|
||||
|
|
@ -4360,29 +4360,44 @@ function runHeadlessStreaming(
|
|||
trackReceivedMessageUuid(userMsg.uuid as UUID)
|
||||
}
|
||||
|
||||
enqueue({
|
||||
mode: 'prompt' as const,
|
||||
// file_attachments rides the protobuf catchall from the web composer.
|
||||
// Same-ref no-op when absent (no 'file_attachments' key).
|
||||
value: await resolveAndPrepend(
|
||||
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}`)
|
||||
})
|
||||
}),
|
||||
}))
|
||||
const rawContent = typeof userMsg.content === 'string' ? userMsg.content : ''
|
||||
const isTaskNotification = rawContent.trimStart().startsWith(
|
||||
`<${TASK_NOTIFICATION_TAG}>`,
|
||||
)
|
||||
|
||||
if (isTaskNotification) {
|
||||
enqueue({
|
||||
mode: 'task-notification' as const,
|
||||
value: rawContent,
|
||||
uuid: userMsg.uuid as `${string}-${string}-${string}-${string}-${string}`,
|
||||
priority: (userMsg as { priority?: string })
|
||||
.priority as import('src/types/textInputTypes.js').QueuePriority,
|
||||
})
|
||||
} else {
|
||||
enqueue({
|
||||
mode: 'prompt' as const,
|
||||
// file_attachments rides the protobuf catchall from the web composer.
|
||||
// Same-ref no-op when absent (no 'file_attachments' key).
|
||||
value: await resolveAndPrepend(
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export class EventBus {
|
|||
const sid = dataObj?.session_id ?? dataObj?.sessionID
|
||||
if (typeof sid === 'string') {
|
||||
const sessionCwd = this.sessionCwds.get(sid)
|
||||
if (sessionCwd && sessionCwd !== client.cwdFilter) {
|
||||
if (sessionCwd !== client.cwdFilter) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,14 +221,12 @@ export function createSessionRoutes(
|
|||
const url = new URL(c.req.url)
|
||||
const limit = parseInt(url.searchParams.get('limit') ?? '50', 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 dir = (headerDir ? decodeURIComponent(headerDir) : undefined)
|
||||
?? url.searchParams.get('dir')
|
||||
?? 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 列表
|
||||
let historySessions: Awaited<ReturnType<typeof listSessionsImpl>> = []
|
||||
|
|
@ -271,7 +269,6 @@ export function createSessionRoutes(
|
|||
|
||||
// 补充内存中有但磁盘还没落盘的活跃 session(刚创建还没写过消息的)
|
||||
const historyIds = new Set(historySessions.map(s => s.sessionId))
|
||||
const canonicalDir = dir ? await canonicalizePath(dir).catch(() => dir) : undefined
|
||||
for (const handle of sessionManager.getAllSessions()) {
|
||||
if (!historyIds.has(handle.sessionId)) {
|
||||
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([
|
||||
'/exit', '/quit', '/bye',
|
||||
'/clear', '/reset',
|
||||
|
|
@ -292,7 +313,7 @@ export function createSessionRoutes(
|
|||
'/help', '/version',
|
||||
'/compact',
|
||||
])
|
||||
const filtered = merged.filter(s => {
|
||||
const filtered = cwdFiltered.filter(s => {
|
||||
const t = s.title.trim()
|
||||
if (!t) return false
|
||||
if (BORING_COMMANDS.has(t)) return false
|
||||
|
|
@ -308,7 +329,10 @@ export function createSessionRoutes(
|
|||
.get('/session/status', async c => {
|
||||
const headerDir = c.req.header('x-csc-directory')
|
||||
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)
|
||||
})
|
||||
.get('/session/:sessionID', async c => {
|
||||
|
|
@ -318,6 +342,7 @@ export function createSessionRoutes(
|
|||
const info = handle.getInfo()
|
||||
return c.json({
|
||||
...info,
|
||||
busy_status: handle.getEffectiveBusyStatus(),
|
||||
message_count: handle.messageCount,
|
||||
usage: handle.usage,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -141,6 +141,16 @@ export class SessionHandle implements DisposableChildProcess {
|
|||
return this._lastMessageUuid
|
||||
}
|
||||
|
||||
getEffectiveBusyStatus(): SessionBusyStatus {
|
||||
if (this._prompting) {
|
||||
return { type: 'busy' }
|
||||
}
|
||||
if (this._status === 'stopped') {
|
||||
return { type: 'idle' }
|
||||
}
|
||||
return this._busyStatus
|
||||
}
|
||||
|
||||
get ready(): boolean {
|
||||
return this._status === 'running'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@ export class SessionManager {
|
|||
return this.sessions.size
|
||||
}
|
||||
|
||||
getLoadedIndexCount(): number {
|
||||
return this.loadedIndex.size
|
||||
}
|
||||
|
||||
getSession(id: string): SessionHandle | undefined {
|
||||
return this.sessions.get(id)
|
||||
}
|
||||
|
|
@ -163,7 +167,20 @@ export class SessionManager {
|
|||
const handleCwd = await canonicalizePath(handle.cwd)
|
||||
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
|
||||
}
|
||||
|
|
@ -220,9 +237,8 @@ export class SessionManager {
|
|||
|
||||
this.sessions.set(sessionId, handle)
|
||||
if (!opts.silent) {
|
||||
void canonicalizePath(cwd).then(canonical => {
|
||||
this.eventBus.registerSessionCwd(sessionId, canonical)
|
||||
})
|
||||
const canonical = await canonicalizePath(cwd).catch(() => cwd)
|
||||
this.eventBus.registerSessionCwd(sessionId, canonical)
|
||||
this.eventBus.publishSessionEvent(sessionId, 'created', {
|
||||
status: 'starting',
|
||||
created_at: Date.now(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user