Function bodies 284 total
BaseAgent.constructor method · typescript · L102-L114 (13 LOC)src/agents/base-agent.ts
constructor(config: BaseAgentConfig) {
this.apiKey = config.apiKey;
this.model = config.model;
this.apiBaseUrl = config.apiBaseUrl;
this.permissionMode = config.permissionMode ?? 'bypassPermissions';
// Detect provider from Config
const agentConfig = Config.getAgentConfig();
this.provider = agentConfig.provider;
// Create logger with agent name
this.logger = createLogger(this.getAgentName());
}BaseAgent.createSdkOptions method · typescript · L132-L161 (30 LOC)src/agents/base-agent.ts
protected createSdkOptions(extra: SdkOptionsExtra = {}): Record<string, unknown> {
const sdkOptions: Record<string, unknown> = {
cwd: extra.cwd ?? Config.getWorkspaceDir(),
permissionMode: this.permissionMode,
settingSources: ['project'],
};
// Add allowed/disallowed tools
if (extra.allowedTools) {
sdkOptions.allowedTools = extra.allowedTools;
}
if (extra.disallowedTools) {
sdkOptions.disallowedTools = extra.disallowedTools;
}
// Add MCP servers
if (extra.mcpServers) {
sdkOptions.mcpServers = extra.mcpServers;
}
// Set environment
sdkOptions.env = buildSdkEnv(this.apiKey, this.apiBaseUrl, Config.getGlobalEnv());
// Set model
if (this.model) {
sdkOptions.model = this.model;
}
return sdkOptions;
}BaseAgent.createQueryStream method · typescript · L228-L268 (41 LOC)src/agents/base-agent.ts
protected createQueryStream(
input: AsyncGenerator<SDKUserMessage>,
sdkOptions: Record<string, unknown>
): QueryStreamResult {
const queryResult = query({
prompt: input,
options: sdkOptions as Parameters<typeof query>[0]['options'],
});
const self = this;
const iterator = queryResult[Symbol.asyncIterator]();
async function* wrappedIterator(): AsyncGenerator<IteratorYieldResult> {
while (true) {
const result = await iterator.next();
if (result.done) {
break;
}
const message = result.value;
const parsed = parseSDKMessage(message);
// Log SDK message with full details for debugging
self.logger.debug({
provider: self.provider,
messageType: parsed.type,
contentLength: parsed.content?.length || 0,
toolName: parsed.metadata?.toolName,
rawMessage: message,
}, 'SDK message received');
yield { parsed, raw: messBaseAgent.handleIteratorError method · typescript · L279-L292 (14 LOC)src/agents/base-agent.ts
protected handleIteratorError(error: unknown, operation: string): AgentMessage {
const agentError = new AgentExecutionError(`${this.getAgentName()} ${operation} failed`, {
cause: error instanceof Error ? error : new Error(String(error)),
agent: this.getAgentName(),
recoverable: true,
});
this.logger.error({ err: formatError(agentError) }, `${operation} failed`);
return {
content: `❌ Error: ${error instanceof Error ? error.message : String(error)}`,
role: 'assistant',
messageType: 'error',
};
}BaseAgent.formatMessage method · typescript · L302-L309 (8 LOC)src/agents/base-agent.ts
protected formatMessage(parsed: ReturnType<typeof parseSDKMessage>): AgentMessage {
return {
content: parsed.content,
role: 'assistant',
messageType: parsed.type,
metadata: parsed.metadata,
};
}Evaluator.initialize method · typescript · L67-L84 (18 LOC)src/agents/evaluator.ts
async initialize(): Promise<void> {
if (this.initialized) {
return;
}
// Load skill (required)
this.skill = await loadSkillOrThrow('evaluator');
this.logger.debug(
{
skillName: this.skill.name,
toolCount: this.skill.allowedTools.length,
},
'Evaluator skill loaded'
);
this.initialized = true;
this.logger.debug('Evaluator initialized');
}Evaluator.buildEvaluationPrompt method · typescript · L159-L255 (97 LOC)src/agents/evaluator.ts
private buildEvaluationPrompt(taskId: string, iteration: number): string {
const taskMdPath = this.fileManager.getTaskSpecPath(taskId);
const evaluationPath = this.fileManager.getEvaluationPath(taskId, iteration);
let previousExecutionPath: string | null = null;
if (iteration > 1) {
previousExecutionPath = this.fileManager.getExecutionPath(taskId, iteration - 1);
}
let prompt = `# Evaluator Task
## Context
- Task ID: ${taskId}
- Iteration: ${iteration}
## Your Job
1. Read the task specification:
\`${taskMdPath}\`
`;
if (previousExecutionPath) {
prompt += `
2. Read the previous execution output:
\`${previousExecutionPath}\`
`;
} else {
prompt += `
2. This is the first iteration - no previous execution exists.
`;
}
prompt += `
3. Evaluate if the task is complete based on Expected Results
4. Write your evaluation to:
\`${evaluationPath}\`
## Output Format for evaluation.md
\`\`\`markdown
# Evaluation: IterationOpen data scored by Repobility · https://repobility.com
Executor.constructor method · typescript · L86-L98 (13 LOC)src/agents/executor.ts
constructor(config: ExecutorConfig) {
super(config);
this.config = config;
this.fileManager = new TaskFileManager();
this.logger.debug(
{
provider: this.provider,
model: this.model,
},
'Executor initialized'
);
}Executor.buildTaskPrompt method · typescript · L248-L301 (54 LOC)src/agents/executor.ts
private buildTaskPrompt(taskId: string, iteration: number, evaluationContent: string): string {
const taskMdPath = this.fileManager.getTaskSpecPath(taskId);
const executionPath = this.fileManager.getExecutionPath(taskId, iteration);
const parts: string[] = [];
parts.push('# Task Execution');
parts.push('');
parts.push(`Task ID: ${taskId}`);
parts.push(`Iteration: ${iteration}`);
parts.push('');
// Add evaluation guidance if available
if (evaluationContent) {
parts.push('## Evaluation Guidance');
parts.push('');
parts.push('The Evaluator has assessed the task. Here is the evaluation:');
parts.push('');
parts.push('```');
parts.push(evaluationContent);
parts.push('```');
parts.push('');
parts.push('---');
parts.push('');
}
parts.push('## Your Job');
parts.push('');
parts.push(`1. Read the task specification: \`${taskMdPath}\``);
parts.push('2. Execute the task basExecutor.createExecutionFile method · typescript · L306-L328 (23 LOC)src/agents/executor.ts
private async createExecutionFile(
taskId: string,
iteration: number,
output: string,
error?: string
): Promise<void> {
const timestamp = new Date().toISOString();
const content = `# Execution: Iteration ${iteration}
**Timestamp**: ${timestamp}
**Status**: ${error ? 'Failed' : 'Completed'}
## Execution Output
${output || '(No output)'}
${error ? `## Error\n\n${error}\n` : ''}
`;
await this.fileManager.writeExecution(taskId, iteration, content);
this.logger.debug({ taskId, iteration }, 'Execution file created');
}Executor.findCreatedFiles method · typescript · L333-L349 (17 LOC)src/agents/executor.ts
private async findCreatedFiles(workspaceDir: string): Promise<string[]> {
const files: string[] = [];
try {
const entries = await fs.readdir(workspaceDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name !== 'summary.md') {
files.push(entry.name);
}
}
} catch (err) {
this.logger.warn({ err }, 'Failed to list workspace files');
}
return files;
}AgentFactory.getBaseConfig method · typescript · L60-L69 (10 LOC)src/agents/factory.ts
private static getBaseConfig(options: AgentCreateOptions = {}): BaseAgentConfig {
const defaultConfig = Config.getAgentConfig();
return {
apiKey: options.apiKey ?? defaultConfig.apiKey,
model: options.model ?? defaultConfig.model,
apiBaseUrl: options.apiBaseUrl ?? defaultConfig.apiBaseUrl,
permissionMode: options.permissionMode ?? 'bypassPermissions',
};
}AgentFactory.createEvaluator method · typescript · L87-L94 (8 LOC)src/agents/factory.ts
static createEvaluator(options: AgentCreateOptions = {}, subdirectory?: string): Evaluator {
const config: EvaluatorConfig = {
...this.getBaseConfig(options),
subdirectory,
};
return new Evaluator(config);
}AgentFactory.createExecutor method · typescript · L113-L120 (8 LOC)src/agents/factory.ts
static createExecutor(options: AgentCreateOptions = {}, abortSignal?: AbortSignal): Executor {
const config: ExecutorConfig = {
...this.getBaseConfig(options),
abortSignal,
};
return new Executor(config);
}AgentFactory.createPilot method · typescript · L156-L171 (16 LOC)src/agents/factory.ts
static createPilot(
callbacks: PilotCallbacks,
options: AgentCreateOptions = {},
isCliMode = false
): Pilot {
const config: PilotConfig = {
callbacks,
isCliMode,
apiKey: options.apiKey,
model: options.model,
apiBaseUrl: options.apiBaseUrl,
permissionMode: options.permissionMode,
};
return new Pilot(config);
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
Pilot.constructor method · typescript · L151-L156 (6 LOC)src/agents/pilot.ts
constructor(config: PilotConfig) {
super(config);
this.callbacks = config.callbacks;
this.isCliMode = config.isCliMode ?? false;
}Pilot.executeOnce method · typescript · L174-L280 (107 LOC)src/agents/pilot.ts
async executeOnce(
chatId: string,
text: string,
messageId: string,
senderOpenId?: string
): Promise<void> {
this.logger.info({ chatId, messageId, textLength: text.length }, 'CLI mode: executing one-shot query');
// Add MCP servers
const mcpServers: Record<string, unknown> = {};
// CLI mode doesn't need Feishu MCP server
// Merge configured external MCP servers from config file
const configuredMcpServers = Config.getMcpServersConfig();
if (configuredMcpServers) {
for (const [name, config] of Object.entries(configuredMcpServers)) {
mcpServers[name] = {
type: 'stdio',
command: config.command,
args: config.args || [],
...(config.env && { env: config.env }),
};
}
}
// Build SDK options using BaseAgent's createSdkOptions
const sdkOptions = this.createSdkOptions({
disallowedTools: ['AskUserQuestion'],
mcpServers,
});
// Build enhanced contentPilot.processMessage method · typescript · L297-L332 (36 LOC)src/agents/pilot.ts
processMessage(
chatId: string,
text: string,
messageId: string,
senderOpenId?: string,
attachments?: FileReference[]
): void {
this.logger.debug({ chatId, messageId, textLength: text.length, hasAttachments: !!attachments }, 'Processing message');
// Get or create state for this chatId (handles health check and restart)
const state = this.getOrCreateState(chatId);
// Update last activity
state.lastActivity = Date.now();
// Set this message as the current thread root for replies
// All bot responses will be threaded to this user message
state.currentThreadRootId = messageId;
this.logger.debug({ chatId, messageId }, 'Set current thread root for replies');
// Push message to the queue
state.messageQueue.push({ text, messageId, senderOpenId, attachments });
// Log health status for debugging
if (state.closed || !state.started) {
this.logger.warn(
{ chatId, closed: state.closed, started: state.staPilot.getOrCreateState method · typescript · L343-L396 (54 LOC)src/agents/pilot.ts
private getOrCreateState(chatId: string): PerChatIdState {
const existing = this.states.get(chatId);
if (existing) {
// Already active → reuse
if (!existing.closed && existing.started) {
this.logger.debug({ chatId }, 'Reusing existing active state');
return existing;
}
// Exists but not started → start it
if (!existing.started && !existing.closed) {
this.logger.info({ chatId }, 'Starting existing idle state');
this.startAgentLoop(chatId).catch((err) => {
this.logger.error({ err, chatId }, 'Failed to start Agent loop');
});
return existing;
}
// Exists but closed → restart (preserve queued messages)
if (existing.closed) {
this.logger.info({ chatId }, 'Restarting closed state');
existing.closed = false;
existing.started = false;
this.startAgentLoop(chatId).catch((err) => {
this.logger.error({ err, chatId }, 'Failed to restart AgePilot.buildEnhancedContent method · typescript · L406-L472 (67 LOC)src/agents/pilot.ts
private buildEnhancedContent(chatId: string, msg: QueuedMessage): string {
// Check if this is a skill command (starts with /)
const isSkillCommand = msg.text.trimStart().startsWith('/');
if (isSkillCommand) {
// For skill commands: command first, then minimal context for skill to use
const contextInfo = msg.senderOpenId
? `
---
**Chat ID:** ${chatId}
**Message ID:** ${msg.messageId}
**Sender Open ID:** ${msg.senderOpenId}${this.buildAttachmentsInfo(msg.attachments)}`
: `
---
**Chat ID:** ${chatId}
**Message ID:** ${msg.messageId}${this.buildAttachmentsInfo(msg.attachments)}`;
return `${msg.text}${contextInfo}`;
}
// For regular messages: context FIRST, then user message
if (msg.senderOpenId) {
return `You are responding in a Feishu chat.
**Chat ID:** ${chatId}
**Message ID:** ${msg.messageId}
**Sender Open ID:** ${msg.senderOpenId}
---
## @ Mention the User
To notify the user in your FINAL response, use:
\`\`\`Pilot.buildAttachmentsInfo method · typescript · L477-L500 (24 LOC)src/agents/pilot.ts
private buildAttachmentsInfo(attachments?: FileReference[]): string {
if (!attachments || attachments.length === 0) {
return '';
}
const attachmentList = attachments
.map((att, index) => {
const sizeInfo = att.size ? ` (${(att.size / 1024).toFixed(1)} KB)` : '';
return `${index + 1}. **${att.fileName}**${sizeInfo}
- File ID: \`${att.id}\`
- Local path: \`${att.storageKey}\`
- MIME type: ${att.mimeType || 'unknown'}`;
})
.join('\n');
return `
--- Attachments ---
The user has attached ${attachments.length} file(s). These files have been downloaded to local storage:
${attachmentList}
You can read these files using the Read tool with the local paths above.`;
}Pilot.startAgentLoop method · typescript · L557-L700 (144 LOC)src/agents/pilot.ts
private async startAgentLoop(chatId: string): Promise<void> {
const state = this.states.get(chatId);
if (!state) {
return;
}
// Prevent duplicate starts
if (state.started) {
this.logger.warn({ chatId }, 'Agent loop already started');
return;
}
state.started = true;
state.closed = false;
// Add MCP servers
// Start with internal SDK MCP servers
const mcpServers: Record<string, unknown> = {};
// Only add Feishu MCP server if NOT in CLI mode
// CLI mode doesn't need Feishu integration (no Feishu API calls)
if (!this.isCliMode) {
mcpServers['feishu-context'] = createFeishuSdkMcpServer();
}
// Merge configured external MCP servers from config file
const configuredMcpServers = Config.getMcpServersConfig();
if (configuredMcpServers) {
for (const [name, config] of Object.entries(configuredMcpServers)) {
mcpServers[name] = {
type: 'stdio',
command: config.commPilot.clearQueue method · typescript · L718-L731 (14 LOC)src/agents/pilot.ts
clearQueue(chatId: string): void {
const state = this.states.get(chatId);
if (state) {
state.closed = true;
if (state.messageResolver) {
state.messageResolver();
}
if (state.queryInstance) {
state.queryInstance.close();
}
}
this.states.delete(chatId);
this.logger.debug({ chatId }, 'State cleared');
}All rows above produced by Repobility · https://repobility.com
Pilot.reset method · typescript · L741-L756 (16 LOC)src/agents/pilot.ts
reset(chatId: string): void {
const state = this.states.get(chatId);
if (state) {
state.closed = true;
if (state.messageResolver) {
state.messageResolver();
}
if (state.queryInstance) {
state.queryInstance.close();
}
this.states.delete(chatId);
this.logger.info({ chatId }, 'State reset for chatId');
} else {
this.logger.debug({ chatId }, 'No state to reset for chatId');
}
}Pilot.clearPendingFiles method · typescript · L766-L772 (7 LOC)src/agents/pilot.ts
clearPendingFiles(chatId: string): void {
const state = this.states.get(chatId);
if (state) {
state.pendingWriteFiles.clear();
}
this.logger.debug({ chatId }, 'Pending files cleared');
}Pilot.resetAll method · typescript · L780-L795 (16 LOC)src/agents/pilot.ts
resetAll(): void {
this.logger.info('Resetting all states');
for (const [, state] of this.states) {
state.closed = true;
if (state.messageResolver) {
state.messageResolver();
}
if (state.queryInstance) {
state.queryInstance.close();
}
}
this.states.clear();
this.logger.info('All states reset');
}Pilot.getActiveSessionCount method · typescript · L800-L808 (9 LOC)src/agents/pilot.ts
getActiveSessionCount(): number {
let count = 0;
for (const state of this.states.values()) {
if (state.started && !state.closed) {
count++;
}
}
return count;
}Pilot.shutdown method · typescript · L813-L830 (18 LOC)src/agents/pilot.ts
async shutdown(): Promise<void> {
await Promise.resolve(); // No-op to satisfy linter
this.logger.info('Shutting down Pilot');
// Close all states
for (const [, state] of this.states) {
state.closed = true;
if (state.messageResolver) {
state.messageResolver();
}
if (state.queryInstance) {
state.queryInstance.close();
}
}
this.states.clear();
this.logger.info('Pilot shutdown complete');
}Reporter.initialize method · typescript · L63-L80 (18 LOC)src/agents/reporter.ts
async initialize(): Promise<void> {
if (this.initialized) {
return;
}
// Load skill (required)
this.skill = await loadSkillOrThrow('reporter');
this.logger.debug(
{
skillName: this.skill.name,
toolCount: this.skill.allowedTools.length,
},
'Reporter skill loaded'
);
this.initialized = true;
this.logger.debug('Reporter initialized');
}Reporter.buildEventFeedbackPrompt method · typescript · L240-L260 (21 LOC)src/agents/reporter.ts
static buildEventFeedbackPrompt(params: {
event: TaskProgressEvent;
taskId: string;
iteration: number;
chatId?: string;
}): string {
const { event, taskId, iteration, chatId } = params;
switch (event.type) {
case 'output':
return Reporter.buildOutputPrompt(event, taskId, iteration, chatId);
case 'start':
return Reporter.buildStartPrompt(event, taskId, iteration, chatId);
case 'complete':
return Reporter.buildCompletePrompt(event, taskId, iteration, chatId);
case 'error':
return Reporter.buildErrorPrompt(event, taskId, iteration, chatId);
default:
return '';
}
}Reporter.buildOutputPrompt method · typescript · L270-L302 (33 LOC)src/agents/reporter.ts
private static buildOutputPrompt(
event: TaskProgressEvent & { type: 'output' },
_taskId: string,
_iteration: number,
chatId?: string
): string {
if (!chatId) {
// No chatId - return minimal prompt for CLI mode
return `## Progress
${event.content.substring(0, 300)}${event.content.length > 300 ? '...' : ''}`;
}
// Truncate content for brevity
const content = event.content.substring(0, 500);
return `## 进度更新
${content}
---
**发送反馈** (使用 send_user_feedback):
**要求**:
- 🎯 **精简** - 一句话说清楚做了什么
- 📄 **格式** - 用 emoji + 简短描述,例如: \`📄 读取 src/foo.ts\`
- ⚡ **合并** - 如果是连续的小操作,合并报告
**Chat ID**: \`${chatId}\`
直接调用工具发送,不要输出额外文字。`;
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
Reporter.buildStartPrompt method · typescript · L309-L338 (30 LOC)src/agents/reporter.ts
private static buildStartPrompt(
event: TaskProgressEvent & { type: 'start' },
_taskId: string,
_iteration: number,
chatId?: string
): string {
if (!chatId) {
return `⚡ 开始: ${event.title}`;
}
return `## 任务开始
**任务**: ${event.title}
---
用 send_user_feedback 发送简短通知:
\`\`\`
send_user_feedback({
format: "text",
content: "⚡ ${event.title}",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`
直接调用工具。`;
}Reporter.send_user_feedback method · typescript · L328-L335 (8 LOC)src/agents/reporter.ts
send_user_feedback({
format: "text",
content: "⚡ ${event.title}",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`Reporter.buildCompletePrompt method · typescript · L345-L377 (33 LOC)src/agents/reporter.ts
private static buildCompletePrompt(
event: TaskProgressEvent & { type: 'complete' },
_taskId: string,
_iteration: number,
chatId?: string
): string {
if (!chatId) {
return `✅ 完成: ${event.summaryFile}`;
}
const filesInfo = event.files.length > 0 ? `\n**文件**: ${event.files.join(', ')}` : '';
return `## 任务完成
**摘要**: ${event.summaryFile}${filesInfo}
---
1. 如有报告文件,用 send_file_to_feishu 发送
2. 用 send_user_feedback 发送完成通知
\`\`\`
send_user_feedback({
format: "text",
content: "✅ 任务完成",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`
直接调用工具。`;
}Reporter.send_user_feedback method · typescript · L367-L374 (8 LOC)src/agents/reporter.ts
send_user_feedback({
format: "text",
content: "✅ 任务完成",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`Reporter.buildErrorPrompt method · typescript · L384-L415 (32 LOC)src/agents/reporter.ts
private static buildErrorPrompt(
event: TaskProgressEvent & { type: 'error' },
_taskId: string,
_iteration: number,
chatId?: string
): string {
if (!chatId) {
return `❌ 错误: ${event.error}`;
}
const escapedError = event.error.replace(/"/g, '\\"');
return `## 任务失败
**错误**: ${event.error}
---
用 send_user_feedback 发送错误通知:
\`\`\`
send_user_feedback({
format: "text",
content: "❌ ${escapedError}",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`
直接调用工具。`;
}Reporter.send_user_feedback method · typescript · L405-L412 (8 LOC)src/agents/reporter.ts
send_user_feedback({
format: "text",
content: "❌ ${escapedError}",
chatId: "${chatId}"
})
\`\`\`
**Chat ID**: \`${chatId}\`Reporter.buildReportPrompt method · typescript · L420-L488 (69 LOC)src/agents/reporter.ts
static buildReportPrompt(
taskMdContent: string,
iteration: number,
workerOutput: string | undefined,
evaluationContent: string
): string {
let prompt = `${taskMdContent}
---
## Current Iteration: ${iteration}
`;
// Add Executor output if available
const hasExecutorOutput = workerOutput && workerOutput.trim().length > 0;
if (hasExecutorOutput) {
prompt += `## Executor's Previous Output (Iteration ${iteration - 1})
\`\`\`
${workerOutput}
\`\`\`
---
`;
} else {
prompt += `## Executor's Previous Output
*No Executor output yet - this is the first iteration.*
---
`;
}
// Add evaluation result (markdown format)
prompt += `## Evaluator's Assessment
${evaluationContent}
---
`;
// Add report instructions
prompt += `### Your Reporting Task
**Your Job:**
1. Read the Evaluator's assessment above
2. Format user feedback based on the evaluation
3. Use send_user_feedback to send feedback to user
**What to includeFeishuChannel.constructor method · typescript · L72-L98 (27 LOC)src/channels/feishu-channel.ts
constructor(config: FeishuChannelConfig = {}) {
super();
this.id = config.id || 'feishu';
this.appId = config.appId || Config.FEISHU_APP_ID;
this.appSecret = config.appSecret || Config.FEISHU_APP_SECRET;
this.taskTracker = new TaskTracker();
// Initialize FileHandler
this.fileHandler = new FileHandler(
attachmentManager,
async (fileKey: string, messageType: string, fileName?: string, messageId?: string) => {
if (!this.client) {
logger.error({ fileKey }, 'Client not initialized for file download');
return { success: false };
}
try {
const filePath = await downloadFile(this.client, fileKey, messageType, fileName, messageId);
return { success: true, filePath };
} catch (error) {
logger.error({ err: error, fileKey, messageType }, 'File download failed');
return { success: false };
}
}
);
logger.info({ id: this.id }, 'FeishuChannel creOpen data scored by Repobility · https://repobility.com
FeishuChannel.async method · typescript · L82-L94 (13 LOC)src/channels/feishu-channel.ts
async (fileKey: string, messageType: string, fileName?: string, messageId?: string) => {
if (!this.client) {
logger.error({ fileKey }, 'Client not initialized for file download');
return { success: false };
}
try {
const filePath = await downloadFile(this.client, fileKey, messageType, fileName, messageId);
return { success: true, filePath };
} catch (error) {
logger.error({ err: error, fileKey, messageType }, 'File download failed');
return { success: false };
}
}FeishuChannel.sendMessage method · typescript · L112-L146 (35 LOC)src/channels/feishu-channel.ts
async sendMessage(message: OutgoingMessage): Promise<void> {
if (!this.messageSender) {
this.getClient();
}
const sender = this.messageSender;
if (!sender) {
throw new Error('MessageSender not initialized');
}
switch (message.type) {
case 'text':
await sender.sendText(message.chatId, message.text || '', message.threadId);
break;
case 'card':
await sender.sendCard(
message.chatId,
message.card || {},
message.description,
message.threadId
);
break;
case 'file':
// TODO: Pass threadId when Issue #68 is implemented
await sender.sendFile(message.chatId, message.filePath || '');
break;
case 'done':
// Task completion signal, no actual message to send
// This is used for REST sync mode and internal signaling
logger.debug({ chatId: message.chatId }, 'Task completed (done signal)');
break;
deFeishuChannel.start method · typescript · L148-L192 (45 LOC)src/channels/feishu-channel.ts
async start(): Promise<void> {
if (this._status === 'running') {
logger.warn('FeishuChannel already running');
return;
}
this._status = 'starting';
// Initialize message logger
await messageLogger.init();
// Create event dispatcher
this.eventDispatcher = new lark.EventDispatcher({}).register({
'im.message.receive_v1': async (data: unknown) => {
try {
await this.handleMessageReceive(data as FeishuEventData);
} catch (error) {
logger.error({ err: error }, 'Failed to handle message receive');
}
},
'im.message.message_read_v1': async () => {},
'im.chat.access_event.bot_p2p_chat_entered_v1': async () => {},
});
// Create WebSocket client
const sdkLogger = {
error: (...msg: unknown[]) => logger.error({ context: 'LarkSDK' }, String(msg)),
warn: (...msg: unknown[]) => logger.warn({ context: 'LarkSDK' }, String(msg)),
info: (...msg: unknown[]) => loggerFeishuChannel.stop method · typescript · L194-L210 (17 LOC)src/channels/feishu-channel.ts
async stop(): Promise<void> {
if (this._status === 'stopped') {
return;
}
this._status = 'stopping';
this.wsClient = undefined;
this.client = undefined;
this.messageSender = undefined;
// Clean up old attachments to prevent memory leaks
attachmentManager.cleanupOldAttachments();
this._status = 'stopped';
logger.info('FeishuChannel stopped');
}FeishuChannel.getClient method · typescript · L246-L258 (13 LOC)src/channels/feishu-channel.ts
private getClient(): lark.Client {
if (!this.client) {
this.client = new lark.Client({
appId: this.appId,
appSecret: this.appSecret,
});
this.messageSender = new MessageSender({
client: this.client,
logger,
});
}
return this.client;
}FeishuChannel.extractOpenId method · typescript · L263-L275 (13 LOC)src/channels/feishu-channel.ts
private extractOpenId(sender?: { sender_type?: string; sender_id?: unknown }): string | undefined {
if (!sender?.sender_id) {
return undefined;
}
if (typeof sender.sender_id === 'object' && sender.sender_id !== null) {
const senderId = sender.sender_id as { open_id?: string };
return senderId.open_id;
}
if (typeof sender.sender_id === 'string') {
return sender.sender_id;
}
return undefined;
}RestChannel.constructor method · typescript · L120-L130 (11 LOC)src/channels/rest-channel.ts
constructor(config: RestChannelConfig = {}) {
super();
this.id = config.id || 'rest';
this.port = config.port || 3000;
this.host = config.host || '0.0.0.0';
this.apiPrefix = config.apiPrefix || '/api';
this.authToken = config.authToken;
this.enableCors = config.enableCors ?? true;
logger.info({ id: this.id, port: this.port }, 'RestChannel created');
}RestChannel.sendMessage method · typescript · L144-L182 (39 LOC)src/channels/rest-channel.ts
async sendMessage(message: OutgoingMessage): Promise<void> {
const messageId = this.chatToMessage.get(message.chatId);
// Handle 'done' type - task completion signal for sync mode
if (message.type === 'done') {
const pending = this.pendingResponses.get(message.chatId);
if (pending) {
// Get buffered response
const buffer = messageId ? this.responseBuffers.get(messageId) : undefined;
const responseText = buffer ? buffer.join('\n') : '';
// Clear timeout and resolve
clearTimeout(pending.timeout);
pending.resolve(responseText);
// Cleanup maps
this.pendingResponses.delete(message.chatId);
if (messageId) {
this.responseBuffers.delete(messageId);
}
this.chatToMessage.delete(message.chatId);
logger.debug({ chatId: message.chatId, messageId }, 'Task completed, sync response resolved');
}
return;
}
// For sync mode: buffer text responses
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
RestChannel.start method · typescript · L184-L212 (29 LOC)src/channels/rest-channel.ts
async start(): Promise<void> {
if (this._status === 'running') {
logger.warn('RestChannel already running');
return;
}
this._status = 'starting';
this.server = http.createServer((req, res) => {
this.handleRequest(req, res).catch((error) => {
logger.error({ err: error }, 'Failed to handle request');
this.sendError(res, 500, 'Internal server error');
});
});
return new Promise((resolve, reject) => {
this.server!.listen(this.port, this.host, () => {
this._status = 'running';
logger.info({ port: this.port, host: this.host }, 'RestChannel started');
resolve();
});
this.server!.on('error', (error) => {
this._status = 'error';
logger.error({ err: error }, 'Failed to start RestChannel');
reject(error);
});
});
}RestChannel.stop method · typescript · L214-L242 (29 LOC)src/channels/rest-channel.ts
async stop(): Promise<void> {
if (this._status === 'stopped') {
return;
}
this._status = 'stopping';
// Clear all pending responses
for (const [_chatId, pending] of this.pendingResponses) {
clearTimeout(pending.timeout);
pending.reject(new Error('Channel stopped'));
}
this.pendingResponses.clear();
this.responseBuffers.clear();
this.chatToMessage.clear();
return new Promise((resolve) => {
if (this.server) {
this.server.close(() => {
this._status = 'stopped';
logger.info('RestChannel stopped');
resolve();
});
} else {
this._status = 'stopped';
resolve();
}
});
}RestChannel.handleRequest method · typescript · L258-L308 (51 LOC)src/channels/rest-channel.ts
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
// Set CORS headers if enabled
if (this.enableCors) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// Check authentication
if (this.authToken) {
const authHeader = req.headers.authorization;
if (!authHeader || authHeader !== `Bearer ${this.authToken}`) {
this.sendError(res, 401, 'Unauthorized');
return;
}
}
const url = req.url?.split('?')[0] || '/';
// Route requests
if (url === `${this.apiPrefix}/health` && req.method === 'GET') {
this.handleHealth(req, res);
return;
}
if (url === `${this.apiPrpage 1 / 6next ›