mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
feat: add orchestrator execution engine, observability panel, and LSP document caching
Wire FlowExecutor into orchestrator routes for actual flow execution with
pause/resume/stop lifecycle management. Add CLI session audit system with
audit-routes backend and Observability tab in IssueHub frontend. Introduce
cli-session-mux for cross-workspace session routing and QueueSendToOrchestrator
UI component. Normalize frontend API response handling for { data: ... }
wrapper format and propagate projectPath through flow hooks.
In codex-lens, add per-server opened-document cache in StandaloneLspManager
to avoid redundant didOpen notifications (using didChange for updates), and
skip warmup delay for already-warmed LSP server instances in ChainSearchEngine.
This commit is contained in:
@@ -177,6 +177,14 @@ export class CliSessionManager {
|
||||
return Array.from(this.sessions.values()).map(({ pty: _pty, buffer: _buffer, bufferBytes: _bytes, ...rest }) => rest);
|
||||
}
|
||||
|
||||
getProjectRoot(): string {
|
||||
return this.projectRoot;
|
||||
}
|
||||
|
||||
hasSession(sessionKey: string): boolean {
|
||||
return this.sessions.has(sessionKey);
|
||||
}
|
||||
|
||||
getSession(sessionKey: string): CliSession | null {
|
||||
const session = this.sessions.get(sessionKey);
|
||||
if (!session) return null;
|
||||
@@ -398,3 +406,15 @@ export function getCliSessionManager(projectRoot: string = process.cwd()): CliSe
|
||||
managersByRoot.set(resolved, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the manager that owns a given sessionKey.
|
||||
* Useful for cross-workspace routing (tmux-like send) where the executor
|
||||
* may not share the same workflowDir/projectRoot as the target session.
|
||||
*/
|
||||
export function findCliSessionManager(sessionKey: string): CliSessionManager | null {
|
||||
for (const manager of managersByRoot.values()) {
|
||||
if (manager.hasSession(sessionKey)) return manager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
24
ccw/src/core/services/cli-session-mux.ts
Normal file
24
ccw/src/core/services/cli-session-mux.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* CliSessionMux
|
||||
*
|
||||
* A tiny indirection layer used by FlowExecutor (and potentially others) to
|
||||
* route commands to existing PTY sessions in a testable way.
|
||||
*
|
||||
* Why this exists:
|
||||
* - ESM module namespace exports are immutable, which makes it hard to mock
|
||||
* named exports in node:test without special loaders.
|
||||
* - Exporting a mutable object lets tests override behavior by swapping
|
||||
* functions on the object.
|
||||
*/
|
||||
|
||||
import type { CliSessionManager } from './cli-session-manager.js';
|
||||
import { findCliSessionManager, getCliSessionManager } from './cli-session-manager.js';
|
||||
|
||||
export const cliSessionMux: {
|
||||
findCliSessionManager: (sessionKey: string) => CliSessionManager | null;
|
||||
getCliSessionManager: (projectRoot?: string) => CliSessionManager;
|
||||
} = {
|
||||
findCliSessionManager,
|
||||
getCliSessionManager,
|
||||
};
|
||||
|
||||
@@ -19,7 +19,8 @@ import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { broadcastToClients } from '../websocket.js';
|
||||
import { executeCliTool } from '../../tools/cli-executor-core.js';
|
||||
import { getCliSessionManager } from './cli-session-manager.js';
|
||||
import { cliSessionMux } from './cli-session-mux.js';
|
||||
import { appendCliSessionAudit } from './cli-session-audit.js';
|
||||
import type {
|
||||
Flow,
|
||||
FlowNode,
|
||||
@@ -255,16 +256,46 @@ export class NodeRunner {
|
||||
};
|
||||
}
|
||||
|
||||
const manager = getCliSessionManager(this.context.workingDir || process.cwd());
|
||||
const manager = cliSessionMux.findCliSessionManager(targetSessionKey)
|
||||
?? cliSessionMux.getCliSessionManager(this.context.workingDir || process.cwd());
|
||||
if (!manager.hasSession(targetSessionKey)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Target session not found: ${targetSessionKey}`
|
||||
};
|
||||
}
|
||||
const routed = manager.execute(targetSessionKey, {
|
||||
tool,
|
||||
prompt: instruction,
|
||||
mode,
|
||||
workingDir: this.context.workingDir,
|
||||
resumeKey: data.resumeKey,
|
||||
resumeStrategy: data.resumeStrategy === 'promptConcat' ? 'promptConcat' : 'nativeResume'
|
||||
});
|
||||
|
||||
// Best-effort: record audit event so Observability panel includes orchestrator-routed executions.
|
||||
try {
|
||||
const session = manager.getSession(targetSessionKey);
|
||||
appendCliSessionAudit({
|
||||
type: 'session_execute',
|
||||
timestamp: new Date().toISOString(),
|
||||
projectRoot: manager.getProjectRoot(),
|
||||
sessionKey: targetSessionKey,
|
||||
tool,
|
||||
resumeKey: data.resumeKey,
|
||||
workingDir: session?.workingDir,
|
||||
details: {
|
||||
executionId: routed.executionId,
|
||||
mode,
|
||||
resumeStrategy: data.resumeStrategy ?? 'nativeResume',
|
||||
delivery: 'sendToSession',
|
||||
flowId: this.context.flowId,
|
||||
nodeId: node.id
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const outputKey = data.outputName || `${node.id}_output`;
|
||||
this.context.variables[outputKey] = {
|
||||
delivery: 'sendToSession',
|
||||
|
||||
Reference in New Issue
Block a user