jetbrains-pi-acp 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/README.zh-CN.md +5 -3
- package/extensions/acp.ts +43 -6
- package/package.json +1 -1
- package/src/agent.js +6 -4
- package/src/commands.js +33 -3
- package/src/i18n.js +8 -0
- package/src/interactions.js +5 -0
- package/src/protocol.js +9 -6
- package/src/session-index.js +6 -6
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Requires **Node.js 22.19.0+** and a JetBrains IDE with ACP support, such as Inte
|
|
|
11
11
|
1. Sign in to your model provider from a terminal:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx --yes jetbrains-pi-acp@0.1.
|
|
14
|
+
npx --yes jetbrains-pi-acp@0.1.9 --setup
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
In pi, run `/login`, complete sign-in, then exit pi and return to the IDE. Credentials are managed by pi.
|
|
@@ -23,7 +23,7 @@ Requires **Node.js 22.19.0+** and a JetBrains IDE with ACP support, such as Inte
|
|
|
23
23
|
"agent_servers": {
|
|
24
24
|
"pi": {
|
|
25
25
|
"command": "npx",
|
|
26
|
-
"args": ["--yes", "jetbrains-pi-acp@0.1.
|
|
26
|
+
"args": ["--yes", "jetbrains-pi-acp@0.1.9"],
|
|
27
27
|
"use_idea_mcp": true,
|
|
28
28
|
"use_custom_mcp": true
|
|
29
29
|
}
|
|
@@ -37,6 +37,7 @@ The IDE must be able to find `npx`; use its absolute path if needed. Menu locati
|
|
|
37
37
|
|
|
38
38
|
## Features
|
|
39
39
|
|
|
40
|
+
- `/goal <objective>` and `/goal resume` continue automatically until the model verifies completion and calls `goal_complete` to clear the goal. After 20 unfinished rounds, execution pauses; use `/goal resume` to continue. Stop cancels execution; `/goal pause` or pausing in the Air goal panel prevents further rounds. Completion is judged by the executing model, without an independent auditor. Setting or resuming a goal in the Air panel only updates its state; send `/goal resume` to start execution.
|
|
40
41
|
- ACP v1 and v2 draft support, with streaming responses, reasoning, tool status, terminal output, and usage reporting.
|
|
41
42
|
- Native pi session history: list, load, resume, rename, fork, and delete sessions. Use `/tree` to view the session tree in a selection form and switch context while preserving previous branches. Selecting a user message returns to before it was sent and shows the original text for resending (reattach any attachments). Switching does not revert files, remove existing IDE messages, or generate a branch summary. Clients without form support only display the tree.
|
|
42
43
|
- Model and reasoning-level selection, plus full-access, standard, read-only, and plan modes.
|
|
@@ -52,7 +53,7 @@ The IDE must be able to find `npx`; use its absolute path if needed. Menu locati
|
|
|
52
53
|
Append pi options after `--` in the agent's `args`, for example:
|
|
53
54
|
|
|
54
55
|
```json
|
|
55
|
-
["--yes", "jetbrains-pi-acp@0.1.
|
|
56
|
+
["--yes", "jetbrains-pi-acp@0.1.9", "--", "--model", "provider/model-id", "--thinking", "medium"]
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
Use configuration options here, not initial prompts, interactive mode, or session-selection arguments. The adapter manages RPC mode and sessions.
|
|
@@ -72,6 +73,7 @@ Restart the agent connection after updating the adapter or changing startup opti
|
|
|
72
73
|
- Only commands the model runs with `interactive: true` accept input. Other commands get an empty stdin (`/dev/null`) like native pi, so commands that read stdin by mistake exit instead of hanging.
|
|
73
74
|
- Diffs have size limits and may be unavailable for binary files or older history. File reports scan at most 20,000 entries or two seconds per snapshot and cannot reliably attribute concurrent external changes.
|
|
74
75
|
- MCP SSE, audio, and binary embedded resources are unsupported. Image support depends on the model.
|
|
76
|
+
- RPC does not support custom terminal panels from extensions. With `pi-mcp-adapter`, `/mcp` falls back to text status, and command notifications appear in chat; these notifications are not saved in pi session history.
|
|
75
77
|
- ACP v2 follows a draft schema. Client UI behavior, real provider sign-in, and Windows hardware still need verification.
|
|
76
78
|
|
|
77
79
|
## Development
|
package/README.zh-CN.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
1. 在终端中登录模型提供商:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx --yes jetbrains-pi-acp@0.1.
|
|
14
|
+
npx --yes jetbrains-pi-acp@0.1.9 --setup
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
在 pi 中运行 `/login`,完成登录后退出 pi 并返回 IDE。凭据由 pi 管理。
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"agent_servers": {
|
|
24
24
|
"pi": {
|
|
25
25
|
"command": "npx",
|
|
26
|
-
"args": ["--yes", "jetbrains-pi-acp@0.1.
|
|
26
|
+
"args": ["--yes", "jetbrains-pi-acp@0.1.9"],
|
|
27
27
|
"use_idea_mcp": true,
|
|
28
28
|
"use_custom_mcp": true
|
|
29
29
|
}
|
|
@@ -44,6 +44,7 @@ IDE 必须能找到 `npx`,必要时使用其绝对路径。菜单位置随 IDE
|
|
|
44
44
|
- 界面文案(配置名称、模式说明、命令说明、授权选项、报错等)按系统语言自动切换中英文,给模型看的工具描述和系统提示固定使用英文。语言判断依次读取非空的 `LC_ALL`、`LC_MESSAGES`、`LANG`,未设置时使用系统默认语言;中文使用中文,其余语言使用英文。更改系统语言后需重启适配器。
|
|
45
45
|
- 展示内置 `edit/write` 工具的文件 diff,可协商 Air Git patch 和每轮文件报告。文件报告快照遵守 `.gitignore` 等 Git 忽略规则,保留已跟踪文件;非 Git 项目或 Git 不可用时使用文件系统扫描,跳过 `node_modules/`、`vendor/`、`target/`、`build/`、`dist/`、`coverage/`、`.cache/`、`.venv/` 等常见依赖、构建和缓存目录(包括嵌套目录)。快照始终跳过 `.DS_Store`。
|
|
46
46
|
- 转发 stdio/HTTP MCP 配置及 IDE 工具;支持子 agent、后台 shell 任务和定向停止。
|
|
47
|
+
- `/goal <目标>` 和 `/goal resume` 会自动持续执行,直到模型验证完成并调用 `goal_complete` 清除目标;连续 20 轮仍未完成会暂停,可用 `/goal resume` 继续。停止按钮取消当前执行,`/goal pause` 或 Air 目标面板的暂停操作阻止后续轮次。完成由执行模型判断,不包含独立审计。Air 面板设置或恢复目标仅同步状态,启动执行请发送 `/goal resume`。
|
|
47
48
|
- 按客户端能力提供 steering、终端 stdin、表单和 URL 交互。Air v1 可在命令执行期间发送 `/stdin 文本` 和 `/eof`。
|
|
48
49
|
|
|
49
50
|
**默认完全访问,自动允许工具调用。** 标准访问对内置只读工具以外的工具逐次授权;只读和规划模式拒绝这些工具。权限模式不是操作系统沙箱,安装的 pi 扩展仍属于受信任代码。具体功能取决于客户端协商能力。
|
|
@@ -53,7 +54,7 @@ IDE 必须能找到 `npx`,必要时使用其绝对路径。菜单位置随 IDE
|
|
|
53
54
|
在 Agent 的 `args` 末尾添加 `--`,随后传递 pi 参数,例如:
|
|
54
55
|
|
|
55
56
|
```json
|
|
56
|
-
["--yes", "jetbrains-pi-acp@0.1.
|
|
57
|
+
["--yes", "jetbrains-pi-acp@0.1.9", "--", "--model", "provider/model-id", "--thinking", "medium"]
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
这里只传配置选项,不传初始提示、交互模式或会话选择参数。RPC 模式和会话由适配器管理。
|
|
@@ -73,6 +74,7 @@ IDE 必须能找到 `npx`,必要时使用其绝对路径。菜单位置随 IDE
|
|
|
73
74
|
- 只有模型以 `interactive: true` 调用的命令可以接收输入;其他命令与 pi 原生一致,stdin 为空(`/dev/null`),误读 stdin 的命令会直接结束,不会卡住。
|
|
74
75
|
- diff 有大小限制,二进制文件或旧历史可能无法展示。每轮文件报告的单次快照最多扫描 20,000 项或两秒,无法准确区分外部并发修改。
|
|
75
76
|
- 不支持 MCP SSE、音频和二进制内嵌资源,图片支持取决于模型。
|
|
77
|
+
- RPC 不支持扩展的终端自定义面板。`pi-mcp-adapter` 的 `/mcp` 会降级为文字状态,命令通知在聊天中显示;这些通知不会写入 pi 会话历史。
|
|
76
78
|
- ACP v2 仍为草案,客户端界面展示、真实提供商账号登录及 Windows 实机仍需验证。
|
|
77
79
|
- 每轮 token 在 v1 提示响应的 `usage`、v2 完成状态的 `usage` 中返回;未上报用量时省略,不单列思考 token 或每轮费用。费用取自 pi 的模型计价数据;上下文用量不可用时不发送 `usage_update`,IDE 展示取决于客户端支持。
|
|
78
80
|
|
package/extensions/acp.ts
CHANGED
|
@@ -121,7 +121,9 @@ export default function (pi: any) {
|
|
|
121
121
|
const mutations = new Map<string, { path: string; conflicted: boolean; oldText?: string | null }>();
|
|
122
122
|
let planMode = false;
|
|
123
123
|
let goal: { status: string; objective: string } | null = null;
|
|
124
|
+
let runningGoal: typeof goal = null;
|
|
124
125
|
pi.on('before_agent_start', (event: any) => {
|
|
126
|
+
runningGoal = goal;
|
|
125
127
|
const sections = { ...event.systemPromptOptions.sections };
|
|
126
128
|
delete sections.acp_client_instructions;
|
|
127
129
|
delete sections.acp_plan_mode;
|
|
@@ -130,7 +132,7 @@ export default function (pi: any) {
|
|
|
130
132
|
...sections,
|
|
131
133
|
...(config.customInstructions ? { acp_client_instructions: config.customInstructions } : {}),
|
|
132
134
|
...(planMode ? { acp_plan_mode: 'Only analyze the problem and produce an actionable plan. Do not modify files, run commands, or use other tools with side effects.' } : {}),
|
|
133
|
-
...(goal?.status === 'active' ? { acp_goal: `Long-term goal for this session: ${goal.objective}
|
|
135
|
+
...(goal?.status === 'active' ? { acp_goal: `Long-term goal for this session: ${goal.objective}\nKeep working until the goal is fully achieved and verified, then call the goal_complete tool. Do not call it before that.` } : {}),
|
|
134
136
|
};
|
|
135
137
|
});
|
|
136
138
|
pi.registerCommand('__acp_mode', {
|
|
@@ -160,6 +162,18 @@ export default function (pi: any) {
|
|
|
160
162
|
goal = value;
|
|
161
163
|
},
|
|
162
164
|
});
|
|
165
|
+
pi.registerTool({
|
|
166
|
+
name: 'goal_complete', label: t('goalCompleteLabel'),
|
|
167
|
+
description: 'Mark the current long-term goal as achieved. Call only when the goal is fully done and verified.',
|
|
168
|
+
parameters: Type.Object({ summary: Type.String({ maxLength: 10000 }) }),
|
|
169
|
+
execute: async (_id: string, input: any, _signal: AbortSignal, _update: any, ctx: any) => {
|
|
170
|
+
if (goal?.status !== 'active' || goal !== runningGoal) throw new Error(t('noGoalSet'));
|
|
171
|
+
pi.appendEntry('acp_goal', null);
|
|
172
|
+
goal = null;
|
|
173
|
+
ctx.ui.notify(`PI_ACP_GOAL:null`, 'info');
|
|
174
|
+
return { content: [{ type: 'text', text: 'Goal completed' }], details: { summary: input.summary } };
|
|
175
|
+
},
|
|
176
|
+
});
|
|
163
177
|
pi.registerTool({
|
|
164
178
|
name: 'update_plan', label: t('updatePlanLabel'),
|
|
165
179
|
description: 'Update the execution plan and progress of the current task with structured steps. Only records the plan; performs no file or command operations.',
|
|
@@ -185,7 +199,7 @@ export default function (pi: any) {
|
|
|
185
199
|
return { message: { ...event.message, acpMessageId: id } };
|
|
186
200
|
}
|
|
187
201
|
});
|
|
188
|
-
const archiveCache = new Map<string, { modified: number; archived: boolean }>();
|
|
202
|
+
const archiveCache = new Map<string, { modified: number; archived: boolean; children: string[]; records: string[] }>();
|
|
189
203
|
for (const [name, server] of Object.entries(config.mcpServers)) {
|
|
190
204
|
pi.registerMcpServer(name, server);
|
|
191
205
|
}
|
|
@@ -219,19 +233,42 @@ export default function (pi: any) {
|
|
|
219
233
|
const currentPaths = new Set(sessions.map(session => session.path));
|
|
220
234
|
const idsByPath = new Map(sessions.map(session => [session.path, session.id]));
|
|
221
235
|
for (const path of archiveCache.keys()) if (!currentPaths.has(path)) archiveCache.delete(path);
|
|
222
|
-
|
|
236
|
+
const infos = sessions.map(session => {
|
|
223
237
|
const modified = statSync(session.path).mtimeMs;
|
|
224
238
|
let cached = archiveCache.get(session.path);
|
|
225
239
|
if (!cached || cached.modified !== modified) {
|
|
226
|
-
const
|
|
240
|
+
const manager = SessionManager.open(session.path);
|
|
241
|
+
const archived = manager.getBranch().findLast((entry: any) =>
|
|
227
242
|
entry.type === 'custom' && entry.customType === 'acp_archive')?.data === true;
|
|
228
|
-
|
|
243
|
+
const entries = manager.getEntries() as any[];
|
|
244
|
+
const children = entries.flatMap(entry =>
|
|
245
|
+
entry.type === 'custom' && entry.customType === 'acp_task' && entry.data?.kind === 'agent' &&
|
|
246
|
+
entry.data.owner === session.id && typeof entry.data.sessionId === 'string' ? [entry.data.sessionId] : []);
|
|
247
|
+
const records = entries.flatMap(entry =>
|
|
248
|
+
entry.type === 'custom' && entry.customType === 'subagents:record' && typeof entry.data?.id === 'string' ? [entry.data.id] : []);
|
|
249
|
+
cached = { modified, archived, children, records };
|
|
229
250
|
archiveCache.set(session.path, cached);
|
|
230
251
|
}
|
|
231
252
|
return { sessionId: session.id, cwd: session.cwd, title: session.name || session.firstMessage,
|
|
232
253
|
updatedAt: session.modified.toISOString(), path: session.path, archived: cached.archived,
|
|
233
254
|
...(idsByPath.has(session.parentSessionPath) ? { forkedFrom: idsByPath.get(session.parentSessionPath) } : {}) };
|
|
234
|
-
})
|
|
255
|
+
});
|
|
256
|
+
// 子 agent 会话标记 subagent,由适配器从列表中过滤:
|
|
257
|
+
// 1) 适配器自己的子 agent:父会话的 acp_task 引用其 ID(自有格式,稳定);
|
|
258
|
+
// 2) 旧版 @tintinweb/pi-subagents 写在顶层目录的历史会话:名称为 `类型#8位hex`,
|
|
259
|
+
// 且父会话有 id 以该 hex 开头的 subagents:record。
|
|
260
|
+
// 当前 pi-subagents 的子会话在嵌套目录或临时目录,SessionManager.list 本就不返回。
|
|
261
|
+
// shortcut: 规则 2 只为旧包的历史数据,不再有此类会话后可整段删除。
|
|
262
|
+
const children = new Set(sessions.flatMap(session => archiveCache.get(session.path)?.children ?? []));
|
|
263
|
+
const recordsByPath = new Map(sessions.map(session => [session.path, archiveCache.get(session.path)?.records ?? []]));
|
|
264
|
+
const isSubagent = (session: any) => {
|
|
265
|
+
const hex = /#([0-9a-f]{8})$/.exec(session.name ?? '')?.[1];
|
|
266
|
+
return children.has(session.id) || (!!hex && !!session.parentSessionPath &&
|
|
267
|
+
!!recordsByPath.get(session.parentSessionPath)?.some(id => id.startsWith(hex)));
|
|
268
|
+
};
|
|
269
|
+
const subagents = new Set(sessions.filter(isSubagent).map(session => session.id));
|
|
270
|
+
ctx.ui.notify(`PI_ACP_SESSIONS:${JSON.stringify(infos.map(info =>
|
|
271
|
+
subagents.has(info.sessionId) ? { ...info, subagent: true } : info))}`, 'info');
|
|
235
272
|
},
|
|
236
273
|
});
|
|
237
274
|
pi.on('tool_call', async (event: any, ctx: any) => {
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -385,10 +385,12 @@ export class Agent {
|
|
|
385
385
|
return turn;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
async prompt(session, prompt, steering, fileReportRequest) {
|
|
388
|
+
async prompt(session, prompt, steering, fileReportRequest, pursueGoal = false) {
|
|
389
389
|
const created = !session.turn;
|
|
390
390
|
if (created) this.beginTurn(session, steering, fileReportRequest);
|
|
391
391
|
const turn = session.turn;
|
|
392
|
+
if (created) turn.extensionCommand = /^\/\S/.test(prompt.message);
|
|
393
|
+
if (pursueGoal) Object.assign(turn, { pursueGoal: true, goalCreatedAt: session.goal.createdAt, goalRounds: 0 });
|
|
392
394
|
turn.pending++;
|
|
393
395
|
// 串行提交,避免前一条提示尚在预处理时再次启动运行。
|
|
394
396
|
const submission = (turn.submission ?? Promise.resolve()).catch(() => {}).then(async () => {
|
|
@@ -633,10 +635,10 @@ export class Agent {
|
|
|
633
635
|
await this.sendUsage(session, await session.pi.request('get_session_stats'));
|
|
634
636
|
}
|
|
635
637
|
|
|
636
|
-
//
|
|
638
|
+
// v2 初始命令随响应返回,v1 通过通知发布;失败只记录日志。
|
|
637
639
|
async publishSession(session) {
|
|
638
640
|
await Promise.all([
|
|
639
|
-
this.commands.publish(session).catch(logError('publishCommandsFailed')),
|
|
641
|
+
this.commands.publish(session, this.protocolVersion !== 2).catch(logError('publishCommandsFailed')),
|
|
640
642
|
this.refreshUsage(session).catch(logError('refreshUsageFailed')),
|
|
641
643
|
]);
|
|
642
644
|
}
|
|
@@ -915,7 +917,7 @@ export class Agent {
|
|
|
915
917
|
case 'agent_settled':
|
|
916
918
|
if (event.aborted) session.turn.cancelled = true;
|
|
917
919
|
session.turn.usage = this.refreshUsage(session).catch(logError('refreshUsageFailed'));
|
|
918
|
-
|
|
920
|
+
void this.commands.settleGoal(session);
|
|
919
921
|
break;
|
|
920
922
|
}
|
|
921
923
|
}
|
package/src/commands.js
CHANGED
|
@@ -22,6 +22,8 @@ export function treeOptions(tree, leafId) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// 适配器自己实现的斜杠命令,以及发布给客户端的命令列表。
|
|
25
|
+
const MAX_GOAL_TURNS = 20;
|
|
26
|
+
|
|
25
27
|
export class Commands {
|
|
26
28
|
constructor(agent) {
|
|
27
29
|
this.agent = agent;
|
|
@@ -43,6 +45,31 @@ export class Commands {
|
|
|
43
45
|
return true;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
async settleGoal(session) {
|
|
49
|
+
const turn = session.turn;
|
|
50
|
+
const goal = session.goal;
|
|
51
|
+
if (!turn.pursueGoal || turn.cancelled || turn.error || turn.stopReason !== 'end_turn' ||
|
|
52
|
+
goal?.status !== 'active' || goal.createdAt !== turn.goalCreatedAt) return turn.settle();
|
|
53
|
+
turn.pending++;
|
|
54
|
+
try {
|
|
55
|
+
if (++turn.goalRounds >= MAX_GOAL_TURNS) {
|
|
56
|
+
await this.agent.goalSession({ sessionId: session.id, action: 'pause' });
|
|
57
|
+
this.agent.update(session, { sessionUpdate: 'agent_message_chunk',
|
|
58
|
+
content: { type: 'text', text: t('goalTurnLimit', { turns: turn.goalRounds }) } });
|
|
59
|
+
turn.settle();
|
|
60
|
+
} else {
|
|
61
|
+
const accepted = await session.pi.request('prompt', { message: t('goalContinue', goal) });
|
|
62
|
+
if (accepted?.disposition === 'handled') turn.settle();
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
turn.error = error;
|
|
66
|
+
turn.settle();
|
|
67
|
+
} finally {
|
|
68
|
+
turn.pending--;
|
|
69
|
+
turn.finish();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
async run(session, text, fileReportRequest) {
|
|
47
74
|
const finish = result => {
|
|
48
75
|
if (fileReportRequest) this.agent.sendFileReport(session, { fileReportRequest, changedPaths: new Set(), starts: 0,
|
|
@@ -59,6 +86,8 @@ export class Commands {
|
|
|
59
86
|
...(['pause', 'resume', 'clear'].includes(argument)
|
|
60
87
|
? { action: argument } : { action: 'set', objective: argument }),
|
|
61
88
|
}));
|
|
89
|
+
// 设置或恢复后立即开工,否则 Air 面板显示 Running 而没有任何模型轮次在跑。
|
|
90
|
+
if (session.goal?.status === 'active') return this.agent.prompt(session, { message: t('goalKickoff', session.goal) }, false, fileReportRequest, true);
|
|
62
91
|
}
|
|
63
92
|
return finish({ stopReason: 'end_turn' });
|
|
64
93
|
}
|
|
@@ -160,7 +189,7 @@ export class Commands {
|
|
|
160
189
|
}
|
|
161
190
|
}
|
|
162
191
|
|
|
163
|
-
async publish(session) {
|
|
192
|
+
async publish(session, notify = true) {
|
|
164
193
|
const result = await session.pi.request('get_commands');
|
|
165
194
|
const availableCommands = new Map([
|
|
166
195
|
['status', { name: 'status', description: t('cmdStatus'), input: null }],
|
|
@@ -181,7 +210,8 @@ export class Commands {
|
|
|
181
210
|
availableCommands.set(command.name, { name: command.name,
|
|
182
211
|
description: command.description || command.name, input: null });
|
|
183
212
|
}
|
|
184
|
-
|
|
185
|
-
|
|
213
|
+
session.availableCommands = [...availableCommands.values()];
|
|
214
|
+
if (notify) this.agent.update(session, { sessionUpdate: 'available_commands_update',
|
|
215
|
+
availableCommands: session.availableCommands });
|
|
186
216
|
}
|
|
187
217
|
}
|
package/src/i18n.js
CHANGED
|
@@ -164,6 +164,10 @@ const zh = {
|
|
|
164
164
|
needOneTerminal: '需要恰好一个可输入的运行中终端',
|
|
165
165
|
goalStatus: '长期目标({status}):{objective}',
|
|
166
166
|
noGoal: '尚未设置长期目标',
|
|
167
|
+
goalKickoff: '开始推进长期目标:{objective}',
|
|
168
|
+
goalContinue: '继续推进长期目标:{objective}。若已完全达成并验证,请调用 goal_complete;否则继续工作。',
|
|
169
|
+
goalTurnLimit: '已连续运行 {turns} 轮仍未完成目标,已暂停自动续跑。可发送 /goal resume 继续。',
|
|
170
|
+
goalCompleteLabel: '完成目标',
|
|
167
171
|
statusModel: '模型:{model}',
|
|
168
172
|
statusThinking: '推理级别:{level}',
|
|
169
173
|
statusMessages: '消息:{messages},工具调用:{toolCalls}',
|
|
@@ -376,6 +380,10 @@ const en = {
|
|
|
376
380
|
needOneTerminal: 'Exactly one running terminal that accepts input is required',
|
|
377
381
|
goalStatus: 'Goal ({status}): {objective}',
|
|
378
382
|
noGoal: 'No goal set',
|
|
383
|
+
goalKickoff: 'Start working on the goal: {objective}',
|
|
384
|
+
goalContinue: 'Continue working on the goal: {objective}. If it is fully achieved and verified, call goal_complete; otherwise keep working.',
|
|
385
|
+
goalTurnLimit: 'The goal is still unfinished after {turns} consecutive turns; auto-continue is paused. Send /goal resume to continue.',
|
|
386
|
+
goalCompleteLabel: 'Complete goal',
|
|
379
387
|
statusModel: 'Model: {model}',
|
|
380
388
|
statusThinking: 'Thinking level: {level}',
|
|
381
389
|
statusMessages: 'Messages: {messages}, tool calls: {toolCalls}',
|
package/src/interactions.js
CHANGED
|
@@ -100,6 +100,11 @@ export class Interactions {
|
|
|
100
100
|
notifyHandlers[prefix](this.agent, session, message.slice(prefix.length));
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
|
+
// RPC 扩展命令(如 /mcp)通过 notify 返回文字,需作为本轮回复展示。
|
|
104
|
+
if (message && session.turn?.extensionCommand && !session.turn.cancelled && !session.turn.settled && !session.turn.starts) {
|
|
105
|
+
this.agent.update(session, { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: `${message}\n` } });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
103
108
|
const notices = this.agent.clientCapabilities?.session?.notices;
|
|
104
109
|
if (notices && typeof notices === 'object' && !Array.isArray(notices)) {
|
|
105
110
|
this.agent.update(session, { sessionUpdate: 'notice',
|
package/src/protocol.js
CHANGED
|
@@ -6,6 +6,8 @@ import { invalid } from './shared.js';
|
|
|
6
6
|
import { t } from './i18n.js';
|
|
7
7
|
|
|
8
8
|
const options = values => values.map(({ id, ...option }) => ({ configId: id, ...option }));
|
|
9
|
+
const commands = values => values.map(command => ({ ...command,
|
|
10
|
+
...(command.input ? { input: { ...command.input, type: 'text' } } : {}) }));
|
|
9
11
|
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
12
|
const failure = error => ({ code: Number.isInteger(error.code) ? error.code : -32603, message: error.message });
|
|
11
13
|
|
|
@@ -255,10 +257,12 @@ export class ProtocolAgent {
|
|
|
255
257
|
} });
|
|
256
258
|
} else if (record.result?.configOptions) {
|
|
257
259
|
const { models, modes, ...result } = record.result;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
const setup = ['session/new', 'session/resume', 'session/fork'].includes(request?.method);
|
|
261
|
+
const sessionId = result.sessionId ?? request?.sessionId;
|
|
262
|
+
const availableCommands = setup ? this.agent.sessions.get(sessionId)?.availableCommands : undefined;
|
|
263
|
+
this.send({ ...record, result: { ...result, configOptions: options(result.configOptions),
|
|
264
|
+
...(availableCommands ? { availableCommands: commands(availableCommands) } : {}) } });
|
|
265
|
+
if (setup) this.state(sessionId, 'idle');
|
|
262
266
|
} else this.send(record);
|
|
263
267
|
if (record.result && ['session/close', 'session/delete'].includes(request?.method)) this.states.delete(request.sessionId);
|
|
264
268
|
return;
|
|
@@ -288,8 +292,7 @@ export class ProtocolAgent {
|
|
|
288
292
|
if (update.sessionUpdate === 'current_mode_update') return;
|
|
289
293
|
if (update.sessionUpdate === 'config_option_update') update = { ...update, configOptions: options(update.configOptions) };
|
|
290
294
|
if (update.sessionUpdate === 'available_commands_update') update = { ...update,
|
|
291
|
-
availableCommands: update.availableCommands
|
|
292
|
-
...(command.input ? { input: { ...command.input, type: 'text' } } : {}) })) };
|
|
295
|
+
availableCommands: commands(update.availableCommands) };
|
|
293
296
|
if (update.sessionUpdate === 'plan') update = { sessionUpdate: 'plan_update',
|
|
294
297
|
plan: { type: 'items', planId: 'pi-plan', entries: update.entries } };
|
|
295
298
|
if (['user_message_chunk', 'agent_message_chunk', 'agent_thought_chunk'].includes(update.sessionUpdate)) {
|
package/src/session-index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { t } from './i18n.js';
|
|
|
7
7
|
|
|
8
8
|
const executeFile = promisify(execFile);
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// 历史会话索引:列表、查找、归档及列表变化订阅;列表默认不含子 agent 会话,仅内部查找时包含。
|
|
11
11
|
// 历史由临时启动的 discovery pi 进程通过 /__acp_list 读取。
|
|
12
12
|
export class SessionIndex {
|
|
13
13
|
constructor(agent) {
|
|
@@ -16,7 +16,7 @@ export class SessionIndex {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async find(sessionId, cwd) {
|
|
19
|
-
const { sessions } = await this.list(cwd === undefined ? {} : { cwd }, { includeArchived: true });
|
|
19
|
+
const { sessions } = await this.list(cwd === undefined ? {} : { cwd }, { includeArchived: true, includeSubagents: true });
|
|
20
20
|
return sessions.find(info => info.sessionId === sessionId && (cwd === undefined || resolve(info.cwd) === resolve(cwd)));
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ export class SessionIndex {
|
|
|
31
31
|
}) };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async read(context, params) {
|
|
34
|
+
async read(context, params, { includeSubagents = false } = {}) {
|
|
35
35
|
let cwds = [context.cwd];
|
|
36
36
|
if (params._meta?.jetbrains?.air?.list?.includeWorktrees === true) {
|
|
37
37
|
try {
|
|
@@ -44,10 +44,10 @@ export class SessionIndex {
|
|
|
44
44
|
context.history = undefined;
|
|
45
45
|
await context.pi.request('prompt', { message: `/__acp_list ${JSON.stringify({ all: params.cwd === undefined, cwds })}` });
|
|
46
46
|
if (!context.history) throw new Error(t('piNoHistory'));
|
|
47
|
-
return context.history;
|
|
47
|
+
return includeSubagents ? context.history : context.history.filter(info => !info.subagent);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
async list(params, { includeArchived = false } = {}) {
|
|
50
|
+
async list(params, { includeArchived = false, includeSubagents = false } = {}) {
|
|
51
51
|
if (params.cursor !== undefined && (typeof params.cursor !== 'string' || !/^\d+$/.test(params.cursor))) {
|
|
52
52
|
throw invalid(t('listCursorInvalid'));
|
|
53
53
|
}
|
|
@@ -65,7 +65,7 @@ export class SessionIndex {
|
|
|
65
65
|
}
|
|
66
66
|
const created = await this.agent.newSession({ cwd: params.cwd ?? process.cwd(), mcpServers: [] }, { discovery: true });
|
|
67
67
|
try {
|
|
68
|
-
const history = (await this.read(created, params)).filter(info =>
|
|
68
|
+
const history = (await this.read(created, params, { includeSubagents })).filter(info =>
|
|
69
69
|
archived === 'all' || (info.archived === true) === (archived === 'archived'));
|
|
70
70
|
const sessions = history.slice(offset, offset + limit);
|
|
71
71
|
return { sessions, ...(offset + limit < history.length ? { nextCursor: String(offset + limit) } : {}) };
|