Skip to content

Commit 8dc5bd4

Browse files
author
linyuan.yang
committed
acp
1 parent 7107438 commit 8dc5bd4

11 files changed

Lines changed: 45 additions & 28 deletions

File tree

packages/admin/src/i18n/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,9 @@ export default {
616616
acp_session_persistent: 'Persistent - process stays alive across turns',
617617
acp_session_transient: 'Transient - session closed after each turn',
618618
acp_session_mode_hint: 'Persistent reuses the process and session across conversations; Transient closes the session after each turn',
619+
acp_init_timeout: 'Init Timeout (seconds)',
620+
acp_init_timeout_placeholder: 'Empty or 0 = no timeout',
621+
acp_init_timeout_hint: 'How long to wait for the external ACP process to start and complete protocol initialization. Leave empty or set to 0 to wait indefinitely (useful for slow first-time npx installs)',
619622
acp_env: 'Environment Variables',
620623
acp_env_add: 'Add',
621624
sub_updated: 'Sub-agent updated',

packages/admin/src/i18n/zh.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,9 @@ export default {
616616
acp_session_persistent: '持久(Persistent) - 进程常驻,多轮复用',
617617
acp_session_transient: '临时(Transient) - 每轮结束后关闭会话',
618618
acp_session_mode_hint: '持久模式下进程和会话在多次对话间复用;临时模式每次对话结束后关闭会话',
619+
acp_init_timeout: '初始化超时(秒)',
620+
acp_init_timeout_placeholder: '留空或 0 = 不超时',
621+
acp_init_timeout_hint: '外部 ACP 进程启动并完成协议初始化的等待时间。留空或填 0 表示不超时(npx 首次安装较慢时可用)',
619622
acp_env: '环境变量',
620623
acp_env_add: '添加',
621624
sub_updated: '子智能体已更新',

packages/admin/src/views/agents/AgentsView.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ async function saveMcpParams() {
388388
<SInfoRow :label="t('agents.acp_session_mode')">
389389
<span class="type-tag">{{ row.sessionMode || 'persistent' }}</span>
390390
</SInfoRow>
391+
<SInfoRow v-if="row.initTimeout" :label="t('agents.acp_init_timeout')">{{ row.initTimeout }}s</SInfoRow>
391392
<SInfoRow v-if="row.env && Object.keys(row.env).length" :label="t('agents.acp_env')">
392393
<div v-for="(v, k) in row.env" :key="k" class="env-line">{{ k }}={{ v }}</div>
393394
</SInfoRow>

packages/admin/src/views/agents/modals/AgentModal.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const form = ref({
6767
args: [] as string[],
6868
env: [] as { key: string; value: string }[],
6969
sessionMode: ACPSessionMode.Persistent as ACPSessionMode,
70+
initTimeout: undefined as number | undefined,
7071
})
7172
const tempSubAgents = ref<SubAgentRef[]>([])
7273
@@ -93,6 +94,7 @@ function open(id?: string) {
9394
args: Array.isArray(a.args) ? [...a.args] : [],
9495
env: a.env ? Object.entries(a.env).map(([key, value]) => ({ key, value })) : [],
9596
sessionMode: (a as any).sessionMode || ACPSessionMode.Persistent,
97+
initTimeout: (a as any).initTimeout ?? undefined,
9698
}
9799
tempSubAgents.value = Array.isArray(a.agents) ? [...a.agents] : []
98100
} else {
@@ -103,7 +105,7 @@ function open(id?: string) {
103105
systemPrompt: '',
104106
insightScope: InsightScope.Disabled, insightExtractor: '', insightExtractorPromptFile: '',
105107
todoScope: TodoScope.Disabled, todoExtractor: '', todoExtractorPromptFile: '',
106-
autoApproveAllTools: false, modelCallTimeout: undefined, command: '', args: [], env: [], sessionMode: ACPSessionMode.Persistent,
108+
autoApproveAllTools: false, modelCallTimeout: undefined, command: '', args: [], env: [], sessionMode: ACPSessionMode.Persistent, initTimeout: undefined,
107109
}
108110
}
109111
showModal.value = true
@@ -161,6 +163,7 @@ async function save() {
161163
const envEntries = form.value.env.filter(e => e.key.trim())
162164
if (envEntries.length) config.env = Object.fromEntries(envEntries.map(e => [e.key.trim(), e.value]))
163165
if (form.value.sessionMode !== ACPSessionMode.Persistent) config.sessionMode = form.value.sessionMode
166+
if (form.value.initTimeout != null && form.value.initTimeout > 0) config.initTimeout = form.value.initTimeout
164167
}
165168
// 保留在专属页面配置的字段(generative/acp 模式无工具/技能,不保留)
166169
const existing = editingId.value ? agents.value[editingId.value] : null
@@ -345,6 +348,9 @@ defineExpose({ open })
345348
<option :value="ACPSessionMode.Transient">{{ t('agents.acp_session_transient') }}</option>
346349
</SSelect>
347350
</SFormItem>
351+
<SFormItem :label="t('agents.acp_init_timeout')" :hint="t('agents.acp_init_timeout_hint')">
352+
<SInput v-model.number="form.initTimeout" type="number" :placeholder="t('agents.acp_init_timeout_placeholder')" />
353+
</SFormItem>
348354
<SFormItem :label="t('agents.acp_env')">
349355
<div v-for="(item, i) in form.env" :key="i" style="display:flex;gap:6px;margin-bottom:4px">
350356
<SInput v-model="item.key" placeholder="KEY" style="flex:1" />

packages/sbot.commons/src/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ export interface AgentConfig {
185185
args?: string[]
186186
env?: Record<string, string>
187187
sessionMode?: ACPSessionMode
188+
/** ACP process initialization timeout in seconds. <=0 or unset = no timeout. */
189+
initTimeout?: number
188190
// store
189191
storeSource?: AgentStoreSource
190192
}

packages/sbot/src/Agent/AgentFactory.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
AgentServiceBase, SingleAgentService, GenerativeAgentService,
3-
TransientACPAgentService, PersistentACPAgentService, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath,
3+
TransientACPAgentService, PersistentACPAgentService, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath, T_ACPInitTimeout,
44
IModelService,
55
IAgentSaverService, AgentMemorySaver, ILoggerService,
66
ConversationCompactor, IConversationCompactor, T_SummaryModelService,
@@ -262,18 +262,20 @@ export class AgentFactory {
262262
): Promise<AgentServiceBase> {
263263
const workPath = options.workPath ?? process.cwd();
264264
const sessionMode = entry.sessionMode ?? ACPSessionMode.Persistent;
265+
const initTimeoutMs = (entry.initTimeout ?? 0) * 1000;
265266
const acpArgs = {
266267
[T_ACPCommand]: entry.command,
267268
[T_ACPArgs]: entry.args ?? [],
268269
[T_ACPWorkPath]: workPath,
270+
[T_ACPInitTimeout]: initTimeoutMs,
269271
...(entry.env && { [T_ACPEnv]: entry.env }),
270272
};
271273

272274
if (sessionMode === ACPSessionMode.Persistent) {
273275
const pool = ACPAgentPool.getInstance();
274276
const key = `${options.agentId}:${options.dbSessionId}`;
275277
const agentName = entry.name ?? options.agentId;
276-
const configHash = JSON.stringify({ command: entry.command, args: entry.args ?? [], env: entry.env ?? {}, workPath });
278+
const configHash = JSON.stringify({ command: entry.command, args: entry.args ?? [], env: entry.env ?? {}, workPath, initTimeout: initTimeoutMs });
277279

278280
const cached = await pool.tryGet(key, configHash);
279281
if (cached) return cached;

packages/sbot/src/Core/Config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export interface ACPAgentEntry extends BaseAgentEntry {
8787
args?: string[];
8888
env?: Record<string, string>;
8989
sessionMode?: ACPSessionMode;
90+
initTimeout?: number; // ACP 进程初始化超时(秒),不设置或 <=0 表示不超时
9091
}
9192

9293
/**

packages/scorpio.ai/src/Agents/ACP/ACPAgentServiceBase.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ export const T_ACPCommand = Symbol("scorpio:T_ACPCommand");
1515
export const T_ACPArgs = Symbol("scorpio:T_ACPArgs");
1616
export const T_ACPEnv = Symbol("scorpio:T_ACPEnv");
1717
export const T_ACPWorkPath = Symbol("scorpio:T_ACPWorkPath");
18-
19-
const ACP_INIT_TIMEOUT_MS = 30_000;
20-
const ACP_CLOSE_SESSION_TIMEOUT_MS = 5_000;
18+
export const T_ACPInitTimeout = Symbol("scorpio:T_ACPInitTimeout");
2119

2220
interface ACPStreamState {
2321
callback: IAgentCallback;
@@ -31,6 +29,7 @@ export abstract class ACPAgentServiceBase extends AgentServiceBase {
3129
protected args: string[];
3230
protected env: Record<string, string>;
3331
protected workPath: string;
32+
protected initTimeoutMs: number;
3433

3534
protected childProcess: ChildProcess | null = null;
3635
protected connection: ClientSideConnection | null = null;
@@ -43,7 +42,8 @@ export abstract class ACPAgentServiceBase extends AgentServiceBase {
4342
command: string,
4443
args: string[],
4544
workPath: string,
46-
env?: Record<string, string>,
45+
env: Record<string, string> | undefined,
46+
initTimeoutMs: number,
4747
loggerService?: ILoggerService,
4848
agentSaver?: IAgentSaverService,
4949
memoryServices?: IMemoryService[],
@@ -53,6 +53,7 @@ export abstract class ACPAgentServiceBase extends AgentServiceBase {
5353
this.args = args;
5454
this.env = env ?? {};
5555
this.workPath = workPath;
56+
this.initTimeoutMs = initTimeoutMs;
5657
if (workPath && !existsSync(workPath)) {
5758
mkdirSync(workPath, { recursive: true });
5859
}
@@ -180,18 +181,17 @@ export abstract class ACPAgentServiceBase extends AgentServiceBase {
180181
childProcess.once("error", onStartupError);
181182

182183
try {
183-
await this.withTimeout(
184-
Promise.race([
185-
this.connection.initialize({
186-
protocolVersion: 1,
187-
clientCapabilities: {},
188-
clientInfo: { name: "sbot", version: "1.0.0" },
189-
}),
190-
startupError,
191-
]),
192-
ACP_INIT_TIMEOUT_MS,
193-
`ACP initialize timed out after ${ACP_INIT_TIMEOUT_MS}ms`,
194-
);
184+
const initialize = Promise.race([
185+
this.connection.initialize({
186+
protocolVersion: 1,
187+
clientCapabilities: {},
188+
clientInfo: { name: "sbot", version: "1.0.0" },
189+
}),
190+
startupError,
191+
]);
192+
await (this.initTimeoutMs > 0
193+
? this.withTimeout(initialize, this.initTimeoutMs, `ACP initialize timed out after ${this.initTimeoutMs}ms`)
194+
: initialize);
195195
this.initialized = true;
196196
} catch (e) {
197197
childProcess.kill();
@@ -223,11 +223,7 @@ export abstract class ACPAgentServiceBase extends AgentServiceBase {
223223
const sessionId = this.sessionId;
224224
if (!sessionId || !this.connection) return;
225225

226-
await this.withTimeout(
227-
this.connection.closeSession({ sessionId }),
228-
ACP_CLOSE_SESSION_TIMEOUT_MS,
229-
`ACP closeSession timed out after ${ACP_CLOSE_SESSION_TIMEOUT_MS}ms`,
230-
).catch(e => {
226+
await this.connection.closeSession({ sessionId }).catch(e => {
231227
this.logger?.debug(`[ACP] closeSession ignored: ${e?.message ?? e}`);
232228
});
233229
if (this.sessionId === sessionId) this.sessionId = null;

packages/scorpio.ai/src/Agents/ACP/PersistentACPAgentService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { IAgentSaverService } from "../../Saver";
44
import { ILoggerService } from "../../Logger";
55
import { IMemoryService } from "../../Memory";
66
import type { MessageContent } from "../../Saver/IAgentSaverService";
7-
import { ACPAgentServiceBase, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath } from "./ACPAgentServiceBase";
7+
import { ACPAgentServiceBase, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath, T_ACPInitTimeout } from "./ACPAgentServiceBase";
88

99
export class PersistentACPAgentService extends ACPAgentServiceBase {
1010
private _pooled = false;
@@ -21,12 +21,13 @@ export class PersistentACPAgentService extends ACPAgentServiceBase {
2121
@inject(T_ACPCommand) command: string,
2222
@inject(T_ACPArgs) args: string[],
2323
@inject(T_ACPWorkPath) workPath: string,
24+
@inject(T_ACPInitTimeout) initTimeoutMs: number,
2425
@inject(T_ACPEnv, { optional: true }) env?: Record<string, string>,
2526
@inject(ILoggerService, { optional: true }) loggerService?: ILoggerService,
2627
@inject(IAgentSaverService, { optional: true }) agentSaver?: IAgentSaverService,
2728
@inject(IMemoryService, { optional: true }) memoryServices?: IMemoryService[],
2829
) {
29-
super(command, args, workPath, env, loggerService, agentSaver, memoryServices);
30+
super(command, args, workPath, env, initTimeoutMs, loggerService, agentSaver, memoryServices);
3031
}
3132

3233
isAlive(): boolean {

packages/scorpio.ai/src/Agents/ACP/TransientACPAgentService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ import { IAgentSaverService } from "../../Saver";
44
import { ILoggerService } from "../../Logger";
55
import { IMemoryService } from "../../Memory";
66
import type { MessageContent } from "../../Saver/IAgentSaverService";
7-
import { ACPAgentServiceBase, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath } from "./ACPAgentServiceBase";
7+
import { ACPAgentServiceBase, T_ACPCommand, T_ACPArgs, T_ACPEnv, T_ACPWorkPath, T_ACPInitTimeout } from "./ACPAgentServiceBase";
88

99
export class TransientACPAgentService extends ACPAgentServiceBase {
1010
constructor(
1111
@inject(T_ACPCommand) command: string,
1212
@inject(T_ACPArgs) args: string[],
1313
@inject(T_ACPWorkPath) workPath: string,
14+
@inject(T_ACPInitTimeout) initTimeoutMs: number,
1415
@inject(T_ACPEnv, { optional: true }) env?: Record<string, string>,
1516
@inject(ILoggerService, { optional: true }) loggerService?: ILoggerService,
1617
@inject(IAgentSaverService, { optional: true }) agentSaver?: IAgentSaverService,
1718
@inject(IMemoryService, { optional: true }) memoryServices?: IMemoryService[],
1819
) {
19-
super(command, args, workPath, env, loggerService, agentSaver, memoryServices);
20+
super(command, args, workPath, env, initTimeoutMs, loggerService, agentSaver, memoryServices);
2021
}
2122

2223
protected override async preparePrompt(query: MessageContent): Promise<schema.ContentBlock[]> {

0 commit comments

Comments
 (0)