@ai-workbench/remote-agent 0.8.0 → 0.8.2
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 +11 -1
- package/dist/acp.d.ts +8 -0
- package/dist/acp.js +59 -21
- package/dist/auth-reason.d.ts +1 -0
- package/dist/auth-reason.js +27 -0
- package/dist/connect-contract.d.ts +2 -1
- package/dist/conversation-details.d.ts +47 -0
- package/dist/conversation-details.js +319 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +110 -10
- package/dist/input.js +2 -2
- package/dist/login-diagnostic.d.ts +38 -0
- package/dist/login-diagnostic.js +29 -0
- package/dist/mapping.d.ts +11 -5
- package/dist/mapping.js +105 -141
- package/dist/presentation.js +4 -0
- package/dist/provider.d.ts +13 -4
- package/dist/provider.js +87 -17
- package/dist/readiness.js +7 -5
- package/dist/remote-details.d.ts +39 -0
- package/dist/remote-details.js +381 -0
- package/dist/remote.d.ts +3 -0
- package/dist/remote.js +2 -0
- package/dist/workspace-notice.d.ts +22 -1
- package/dist/workspace-notice.js +148 -129
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
在 DSH「插件 → 添加插件」填 `@ai-workbench/remote-agent@0.8.
|
|
1
|
+
在 DSH「插件 → 添加插件」填 `@ai-workbench/remote-agent@0.8.2`(由编排发布后可用),或运行 `dsh plugin --profile desktop add @ai-workbench/remote-agent@0.8.2`。
|
|
2
2
|
|
|
3
3
|
# 远程ACP插件
|
|
4
4
|
|
|
@@ -52,3 +52,13 @@ Codex单次模式首版仅文本,不转接中途审批;图片或系统/开
|
|
|
52
52
|
## 验证范围
|
|
53
53
|
|
|
54
54
|
当前准备结果与限制记入源码仓`docs/evidence/2026-10-21-X-npm-prep/`(不进npm包)。先前真实emp-a协议诊断及固定三件套GUI是不同轮次,不能代替本次新包安装门。L完整绑定/登录、真实模型、物理断网、HanaMesh及leo E以实际已执行证据为准。主机命令安装来自S,X不修改S仓/安装脚本或登录状态。
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
D19(0.8.1):相邻正文增量合成连续文本块,工作区首次说明合入真实回复,按官方持久记录核对后才回报“已记入可核对回复”。这不代表界面已经显示。
|
|
58
|
+
|
|
59
|
+
技术详情由 L 0.7.11 的唯一“远程主机连接”设置节展示,正文只放模型文本。命令目录保留完整描述;未知事件和工具输入输出保留字段、JSON类型及处理结果,未获准原文明确脱敏,数字用量可核对。没有 `remoteConversationDetails:1` 时明确提示“技术详情暂不可用”,本轮不发送模型请求。X 在官方当前 profile 的 `ai-workbench-remote-agent/diagnostics/` 保存私有诊断日志(目录700、文件600、落盘同步);写入/归属/顺序/容量失败中止本轮,不自动重发、换代重投、过期或淘汰。结果未知时只读核对原事件完整回执,未找到仍保持未知。X本轮无新增设置/阈值;L的单事件容量和分页设置在L设置节可见。
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
0.8.2 登录检查:需要 L `~0.7.11`,失败来源及固定分支保留到 L“登录前技术详情”(`loginDiagnostics:1`),员工正文保持原文。按 L 明确的本品牌 queued/checking/authorizing 状态订阅等待;不以全局checking猜测在途,不新增刷新、轮询、超时或重试。每轮ensureReady仍是合同规定的显式检查,L的新可信结果可以替代旧检查,较早返回不得覆盖它。
|
|
63
|
+
|
|
64
|
+
L安全规范的officialReason原串完整保留;旧/不支持格式明确记OFFICIAL_REASON_UNSUPPORTED与字节数/SHA,秘密原文不保存。L0.7.11的LOGIN_INSTANCE_MISMATCH记录通过observedLoginInstanceId与原始login.instanceId保存实际观测ID,事件及connection仍绑定当前可信ID/代次;L技术详情和X私有日志均保留真实双ID,不归一、不清空快照。复合未知官方原因以固定summary完整保存OFFICIAL_REASON_UNSUPPORTED及bytes/SHA,不交原文。X私有原快照先同步落盘,再追加L;ACK未知只读核实原事件,跨代不重写/重发。此版本的工程验证不替代leo员工路径复验,真实故障根因尚未证实。
|
package/dist/acp.d.ts
CHANGED
|
@@ -2,12 +2,14 @@ import * as stable from '@agentclientprotocol/sdk';
|
|
|
2
2
|
import * as acp from '@agentclientprotocol/sdk/unstable';
|
|
3
3
|
import { NeedError, type Need } from './remote.ts';
|
|
4
4
|
import type { BrokerTransport } from './broker.ts';
|
|
5
|
+
import { type SanitizedObservation } from './conversation-details.ts';
|
|
5
6
|
export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
|
|
6
7
|
export interface AcpHooks {
|
|
7
8
|
onUpdate?: (notification: acp.SessionNotification) => void;
|
|
8
9
|
onResponse?: (response: acp.PromptResponse) => void;
|
|
9
10
|
approve?: (request: acp.RequestPermissionRequest, signal?: AbortSignal) => Promise<ApprovalOutcome>;
|
|
10
11
|
onNeed?: (need: Need) => void;
|
|
12
|
+
onDetail?: (detail: Readonly<SanitizedObservation>) => void;
|
|
11
13
|
broker?: BrokerTransport;
|
|
12
14
|
}
|
|
13
15
|
export interface AcpStartContext {
|
|
@@ -29,6 +31,12 @@ export declare class AcpSession {
|
|
|
29
31
|
private updating;
|
|
30
32
|
private cwd?;
|
|
31
33
|
private systemPrompt;
|
|
34
|
+
private reports;
|
|
35
|
+
private reportingFailure;
|
|
36
|
+
get diagnostics(): readonly Readonly<SanitizedObservation>[];
|
|
37
|
+
private detail;
|
|
38
|
+
private need;
|
|
39
|
+
private checkReporting;
|
|
32
40
|
constructor(transport: acp.Stream | acp.AgentApp | stable.AgentApp, hooks: AcpHooks);
|
|
33
41
|
private observe;
|
|
34
42
|
get id(): string | undefined;
|
package/dist/acp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as acp from '@agentclientprotocol/sdk/unstable';
|
|
2
2
|
import { NeedError } from "./remote.js";
|
|
3
|
+
import { sanitizeObservation } from "./conversation-details.js";
|
|
3
4
|
/** ACP v1 with explicitly negotiated SDK 2.0 lifecycle/context extensions. */
|
|
4
5
|
export class AcpSession {
|
|
5
6
|
connection;
|
|
@@ -15,19 +16,45 @@ export class AcpSession {
|
|
|
15
16
|
updating = false;
|
|
16
17
|
cwd;
|
|
17
18
|
systemPrompt = '';
|
|
19
|
+
reports = [];
|
|
20
|
+
reportingFailure;
|
|
21
|
+
get diagnostics() { return this.reports.map(entry => sanitizeObservation(entry)); }
|
|
22
|
+
detail(observation) {
|
|
23
|
+
const safe = sanitizeObservation(observation);
|
|
24
|
+
this.reports.push(safe);
|
|
25
|
+
try {
|
|
26
|
+
this.hooks.onDetail?.(safe);
|
|
27
|
+
}
|
|
28
|
+
catch (cause) {
|
|
29
|
+
this.reportingFailure ??= { cause };
|
|
30
|
+
this.connection?.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
need(need) {
|
|
34
|
+
this.detail({ kind: 'need', severity: 'action-required', code: need.code, summary: need.message, payload: { requires: need.requires } });
|
|
35
|
+
this.hooks.onNeed?.(need);
|
|
36
|
+
}
|
|
37
|
+
checkReporting() { if (this.reportingFailure)
|
|
38
|
+
throw new NeedError('CONVERSATION_DETAILS_FAILED', '技术详情未能记录;本轮未继续发送', '可用的技术详情记录渠道'); }
|
|
18
39
|
constructor(transport, hooks) {
|
|
19
40
|
this.hooks = hooks;
|
|
20
41
|
const app = acp.client({ name: '@ai-workbench/remote-agent' })
|
|
21
42
|
.onNotification(acp.methods.client.session.update, ({ params: notification }) => {
|
|
22
|
-
if (this.
|
|
23
|
-
|
|
43
|
+
if (this.sessionId && notification.sessionId !== this.sessionId) {
|
|
44
|
+
this.detail({ kind: 'session', severity: 'warning', code: 'ACP_FOREIGN_SESSION_UPDATE', summary: '收到不属于当前远端会话的通知,未作为正文处理', method: 'session/update', payload: notification });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const meta = record(notification._meta);
|
|
48
|
+
if (meta && Object.keys(meta).length)
|
|
49
|
+
this.detail({ kind: 'extension', severity: 'info', code: 'ACP_NOTIFICATION_META', summary: '远端通知扩展', method: 'session/update', payload: notification._meta });
|
|
50
|
+
if (this.busy) {
|
|
51
|
+
// Only actual turn output acknowledges prompt acceptance.
|
|
24
52
|
if (['agent_message_chunk', 'agent_thought_chunk', 'tool_call', 'tool_call_update'].includes(notification.update.sessionUpdate))
|
|
25
53
|
this.hooks.broker?.acknowledge(acp.methods.agent.session.prompt);
|
|
26
|
-
const meta = record(notification._meta);
|
|
27
|
-
if (meta && Object.keys(meta).length)
|
|
28
|
-
this.hooks.onNeed?.({ code: 'ACP_EXTENSION_UNSUPPORTED', message: `首版不支持:远端通知扩展字段 ${Object.keys(meta).map(safeLabel).join('、')};扩展值未展开`, requires: '对应的公开扩展映射' });
|
|
29
54
|
this.hooks.onUpdate?.(notification);
|
|
30
55
|
}
|
|
56
|
+
else
|
|
57
|
+
this.detail({ kind: notification.update.sessionUpdate === 'available_commands_update' ? 'commands' : 'session', severity: 'info', code: 'ACP_IDLE_UPDATE', summary: '远端会话建立、恢复或空闲时的轨迹', method: 'session/update', payload: notification });
|
|
31
58
|
})
|
|
32
59
|
.onRequest(acp.methods.client.session.requestPermission, async ({ params: request, signal: rpcSignal, requestId }) => {
|
|
33
60
|
if (request.sessionId !== this.sessionId || !this.busy || this.turnSignal?.aborted)
|
|
@@ -35,7 +62,7 @@ export class AcpSession {
|
|
|
35
62
|
this.hooks.broker?.acknowledge(acp.methods.agent.session.prompt);
|
|
36
63
|
const toolName = request.toolCall.name ?? record(request.toolCall._meta?.claudeCode)?.toolName;
|
|
37
64
|
if (toolName === 'AskUserQuestion') {
|
|
38
|
-
this.
|
|
65
|
+
this.need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问;已取消,未提交答案', requires: '可承载此提问的官方交互接口' });
|
|
39
66
|
return { outcome: { outcome: 'cancelled' } };
|
|
40
67
|
}
|
|
41
68
|
const eligible = request.options.filter(option => {
|
|
@@ -43,11 +70,13 @@ export class AcpSession {
|
|
|
43
70
|
const sameKind = request.options.filter(candidate => candidate.kind === option.kind);
|
|
44
71
|
const unique = single && sameKind.length === 1 && request.options.filter(candidate => candidate.optionId === option.optionId).length === 1;
|
|
45
72
|
if (!unique)
|
|
46
|
-
this.
|
|
73
|
+
this.detail({ kind: 'tool', severity: 'info', code: 'APPROVAL_OPTION_NOT_OFFERED', summary: '此远端审批选项未映射到单次审批', method: 'session/request_permission', payload: option });
|
|
47
74
|
return unique;
|
|
48
75
|
});
|
|
49
|
-
if (!eligible.length)
|
|
76
|
+
if (!eligible.length) {
|
|
77
|
+
this.need({ code: 'APPROVAL_OPTION_UNSUPPORTED', message: '远端没有可唯一映射的单次审批选项,此动作未获授权', requires: '唯一对应的允许一次或拒绝一次选项' });
|
|
50
78
|
return { outcome: { outcome: 'cancelled' } };
|
|
79
|
+
}
|
|
51
80
|
const presented = { ...request, options: eligible };
|
|
52
81
|
const token = this.turnToken;
|
|
53
82
|
const id = this.sessionId;
|
|
@@ -78,7 +107,7 @@ export class AcpSession {
|
|
|
78
107
|
}
|
|
79
108
|
}
|
|
80
109
|
catch {
|
|
81
|
-
this.
|
|
110
|
+
this.need({ code: 'APPROVAL_FAILED', message: '审批渠道失败或连接已结束;此动作未获授权', requires: '可用的 DSH 审批渠道和原连接' });
|
|
82
111
|
}
|
|
83
112
|
const kind = outcome === 'allowed-once' ? 'allow_once' : outcome === 'rejected' ? 'reject_once' : undefined;
|
|
84
113
|
const option = kind && eligible.find(item => item.kind === kind);
|
|
@@ -87,23 +116,24 @@ export class AcpSession {
|
|
|
87
116
|
broker.authorizeResponse(requestId, generation);
|
|
88
117
|
return { outcome: { outcome: 'selected', optionId: option.optionId } };
|
|
89
118
|
}
|
|
90
|
-
this.
|
|
119
|
+
this.need({ code: outcome === 'allowed-once' ? 'APPROVAL_OPTION_UNSUPPORTED' : 'APPROVAL_UNAVAILABLE',
|
|
91
120
|
message: '此动作未获授权;审批已取消、不可用或没有可映射的单次选项', requires: '本次动作的有效审批' });
|
|
92
121
|
return { outcome: { outcome: 'cancelled' } };
|
|
93
122
|
})
|
|
94
123
|
.onRequest(acp.methods.client.elicitation.create, () => {
|
|
95
|
-
this.
|
|
124
|
+
this.need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问或信息收集;已取消,未提交答案', requires: '可承载此提问的官方交互接口' });
|
|
96
125
|
return { action: 'cancel' };
|
|
97
126
|
})
|
|
98
127
|
.onNotification(acp.methods.client.elicitation.complete, () => {
|
|
99
|
-
this.
|
|
128
|
+
this.need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问完成通知;未提交答案', requires: '可承载此提问的官方交互接口' });
|
|
100
129
|
})
|
|
101
|
-
.onRequest('*', () => {
|
|
102
|
-
this.
|
|
130
|
+
.onRequest('*', ({ method, params }) => {
|
|
131
|
+
this.detail({ kind: 'extension', severity: 'action-required', code: 'ACP_UNKNOWN_REQUEST', summary: '未知远端交互请求未获授权', method, payload: params });
|
|
132
|
+
this.need({ code: 'ACP_EXTENSION_UNSUPPORTED', message: '首版不支持:远端请求的扩展交互;未授权此动作', requires: '受支持的公开 ACP 交互接口' });
|
|
103
133
|
throw new acp.RequestError(-32601, '不支持此扩展交互');
|
|
104
134
|
})
|
|
105
|
-
.onNotification('*', () => {
|
|
106
|
-
this.
|
|
135
|
+
.onNotification('*', ({ method, params }) => {
|
|
136
|
+
this.detail({ kind: 'extension', severity: 'warning', code: 'ACP_UNKNOWN_NOTIFICATION', summary: '收到未知远端扩展通知,已保留技术记录', method, payload: params });
|
|
107
137
|
});
|
|
108
138
|
if ('readable' in transport && 'writable' in transport)
|
|
109
139
|
this.connection = app.connect(this.observe(transport));
|
|
@@ -126,8 +156,7 @@ export class AcpSession {
|
|
|
126
156
|
const params = record(raw?.params);
|
|
127
157
|
const update = record(params?.update);
|
|
128
158
|
if (raw?.method === acp.methods.client.session.update && typeof update?.sessionUpdate === 'string' && !knownUpdates.has(update.sessionUpdate)) {
|
|
129
|
-
|
|
130
|
-
this.hooks.onNeed?.({ code: 'ACP_UPDATE_UNSUPPORTED', message: `首版不支持:远端事件 ${safeLabel(update.sessionUpdate)};未展开未知事件内容`, requires: '对应的公开 ACP 显示映射' });
|
|
159
|
+
this.detail({ kind: 'extension', severity: 'warning', code: 'ACP_UNKNOWN_UPDATE', summary: '收到未知远端会话事件,已保留结构', method: 'session/update', payload: params });
|
|
131
160
|
return;
|
|
132
161
|
}
|
|
133
162
|
controller.enqueue(message);
|
|
@@ -166,12 +195,13 @@ export class AcpSession {
|
|
|
166
195
|
signal?.addEventListener('abort', abort, { once: true });
|
|
167
196
|
try {
|
|
168
197
|
const hello = await this.connection.agent.request(acp.methods.agent.initialize, {
|
|
169
|
-
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.
|
|
198
|
+
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.1' },
|
|
170
199
|
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, session: { notices: {} } },
|
|
171
200
|
}, { cancellationSignal: signal });
|
|
172
201
|
this.hooks.broker?.acknowledge(acp.methods.agent.initialize);
|
|
173
202
|
if (hello.protocolVersion !== acp.PROTOCOL_VERSION)
|
|
174
203
|
throw new NeedError('ACP_VERSION_MISMATCH', 'ACP 协议版本不兼容', '支持 ACP v1 的远端 agent');
|
|
204
|
+
this.checkReporting();
|
|
175
205
|
this.negotiated = hello.agentCapabilities ?? {};
|
|
176
206
|
const systemPrompt = context?.systemPrompt;
|
|
177
207
|
if (systemPrompt && !this.negotiated.sessionCapabilities?.systemPrompt) {
|
|
@@ -197,6 +227,7 @@ export class AcpSession {
|
|
|
197
227
|
this.method = acp.methods.agent.session.new;
|
|
198
228
|
}
|
|
199
229
|
this.hooks.broker?.acknowledge(this.method);
|
|
230
|
+
this.checkReporting();
|
|
200
231
|
this.ready = true;
|
|
201
232
|
this.cwd = cwd;
|
|
202
233
|
this.systemPrompt = systemPrompt ?? '';
|
|
@@ -205,6 +236,7 @@ export class AcpSession {
|
|
|
205
236
|
catch (error) {
|
|
206
237
|
if (signal?.aborted)
|
|
207
238
|
throw new NeedError('ABORTED', '会话建立已取消;尚无本轮输出', '重新连接主机');
|
|
239
|
+
this.checkReporting();
|
|
208
240
|
throw acpNeed(error);
|
|
209
241
|
}
|
|
210
242
|
finally {
|
|
@@ -212,6 +244,7 @@ export class AcpSession {
|
|
|
212
244
|
}
|
|
213
245
|
}
|
|
214
246
|
async updateContext(context, signal) {
|
|
247
|
+
this.checkReporting();
|
|
215
248
|
if (!this.ready || !this.sessionId || !this.cwd)
|
|
216
249
|
throw new NeedError('SESSION_REQUIRED', '我需要先建立远端会话', 'ACP 会话');
|
|
217
250
|
if (this.busy || this.updating)
|
|
@@ -233,12 +266,14 @@ export class AcpSession {
|
|
|
233
266
|
this.hooks.broker?.acknowledge(method);
|
|
234
267
|
this.systemPrompt = text;
|
|
235
268
|
this.ready = true;
|
|
236
|
-
this.
|
|
269
|
+
this.need({ code: 'SYSTEM_PROMPT_UPDATE_DEFERRED', message: '系统与开发者上下文变更已通过原会话恢复接口传送。Claude 恢复会话沿用首次系统提示,新要求在下次上下文整理后生效;不能据此认定本轮已立即生效。', requires: '远端实际应用新要求;Claude 须完成下一次上下文整理' });
|
|
270
|
+
this.checkReporting();
|
|
237
271
|
return method;
|
|
238
272
|
}
|
|
239
273
|
catch (error) {
|
|
240
274
|
if (signal?.aborted)
|
|
241
275
|
throw new NeedError('ABORTED', '上下文更新已取消;原会话状态需确认', '重新连接原会话');
|
|
276
|
+
this.checkReporting();
|
|
242
277
|
throw acpNeed(error);
|
|
243
278
|
}
|
|
244
279
|
finally {
|
|
@@ -254,6 +289,7 @@ export class AcpSession {
|
|
|
254
289
|
signal?.throwIfAborted();
|
|
255
290
|
const prompt = typeof input === 'string' ? [{ type: 'text', text: input }] : input;
|
|
256
291
|
this.validatePrompt(prompt);
|
|
292
|
+
this.checkReporting();
|
|
257
293
|
this.busy = true;
|
|
258
294
|
this.turnSignal = signal;
|
|
259
295
|
this.turnToken = Symbol('turn');
|
|
@@ -262,7 +298,7 @@ export class AcpSession {
|
|
|
262
298
|
const cancel = () => {
|
|
263
299
|
rejectAbort?.(new NeedError('ABORTED', '已请求取消本轮,远端状态需确认', '确认主机取消状态'));
|
|
264
300
|
void this.connection.agent.notify(acp.methods.agent.session.cancel, { sessionId: this.sessionId }).catch(() => {
|
|
265
|
-
this.
|
|
301
|
+
this.need({ code: 'CANCEL_UNCONFIRMED', message: '连接中断,无法确认主机已取消本轮', requires: '恢复连接以确认远端状态' });
|
|
266
302
|
});
|
|
267
303
|
};
|
|
268
304
|
signal?.addEventListener('abort', cancel, { once: true });
|
|
@@ -272,12 +308,14 @@ export class AcpSession {
|
|
|
272
308
|
this.hooks.broker?.acknowledge(acp.methods.agent.session.prompt);
|
|
273
309
|
signal?.throwIfAborted();
|
|
274
310
|
this.hooks.broker?.completePrompt();
|
|
311
|
+
this.checkReporting();
|
|
275
312
|
this.hooks.onResponse?.(result);
|
|
276
313
|
return result.stopReason;
|
|
277
314
|
}
|
|
278
315
|
catch (error) {
|
|
279
316
|
if (signal?.aborted)
|
|
280
317
|
throw new NeedError('ABORTED', '本轮已取消;已请求主机停止', '确认主机取消状态');
|
|
318
|
+
this.checkReporting();
|
|
281
319
|
throw acpNeed(error);
|
|
282
320
|
}
|
|
283
321
|
finally {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validOfficialReason(value: unknown): value is string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** L 0.7.10 officialReason v1 consumer validation; preserve canonical bytes only. */
|
|
2
|
+
const markers = ['AW_REMOTE_COMMAND_KILL_FAILED', 'AUTH_STATUS_JSON_INVALID:SyntaxError', 'Permission denied', 'Host key verification failed', 'Connection refused', 'Connection closed', 'command not found', 'No such file or directory'];
|
|
3
|
+
const names = ['Error', 'TypeError', 'SyntaxError', 'RangeError', 'AbortError', 'DOMException', 'UnknownError'];
|
|
4
|
+
const codes = ['ENOENT', 'EPERM', 'ESRCH', 'EACCES', 'EINVAL', 'EPIPE', 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ABORT_ERR', 'UNKNOWN'];
|
|
5
|
+
const stages = ['ssh-status', 'ssh-result', 'remote-frame', 'auth-status', 'connect-check-login', 'integration-pre-login', 'process-spawn', 'process-stdin', 'process-output', 'process-runner'];
|
|
6
|
+
const reasons = ['CANCELLED', 'SSH_EXIT_NONZERO', 'SSH_STDERR_NONEMPTY', 'FRAME_TRUNCATED', 'FRAME_JSON_INVALID', 'FRAME_OBJECT_INVALID', 'FRAME_VERSION_INVALID', 'FRAME_NONCE_INVALID', 'FRAME_AFTER_END', 'FRAME_KEYS_INVALID', 'FRAME_START_MISSING', 'FRAME_BASE64_INVALID', 'REMOTE_COMMAND_SIGNAL', 'FRAME_END_EXIT_INVALID', 'FRAME_SEQUENCE_OR_VALUE_INVALID', 'FRAME_END_MISSING', 'STATUS_ARGUMENT_INVALID', 'STATUS_EXECUTION_FAILED', 'STATUS_JSON_INVALID', 'STATUS_OBJECT_INVALID', 'STATUS_KEYS_INVALID', 'STATUS_VERSION_INVALID', 'STATUS_PROVIDER_INVALID', 'STATUS_EXIT_MISMATCH', 'STATUS_LOGGED_IN_INVALID', 'STATUS_STDERR_NONEMPTY', 'STATUS_EXIT_INVALID', 'STATUS_EXCEPTION', 'STATUS_SUPERSEDED', 'SSH_PORT_MISSING', 'STDIN_FAILED', 'SPAWN_FAILED', 'OUTPUT_LIMIT_EXCEEDED', 'PROCESS_SIGNAL'];
|
|
7
|
+
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
8
|
+
const integer = (v) => Number.isSafeInteger(v) && Number(v) >= 0;
|
|
9
|
+
const member = (list, v) => typeof v === 'string' && list.includes(v);
|
|
10
|
+
function summary(v) {
|
|
11
|
+
return object(v) && Object.keys(v).every(k => ['bytes', 'sha256', 'markers', 'cleanupErrors'].includes(k)) && integer(v.bytes) && typeof v.sha256 === 'string' && /^[a-f0-9]{64}$/.test(v.sha256) && Array.isArray(v.markers) && v.markers.every(m => member(markers, m)) && (v.cleanupErrors === undefined || Array.isArray(v.cleanupErrors) && v.cleanupErrors.every(e => object(e) && Object.keys(e).sort().join(',') === 'errorCode,errorName' && member(['Error', 'TypeError', 'RangeError', 'UnknownError'], e.errorName) && member(['EPERM', 'EACCES', 'EINVAL', 'UNKNOWN'], e.errorCode)));
|
|
12
|
+
}
|
|
13
|
+
export function validOfficialReason(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return false;
|
|
16
|
+
let v;
|
|
17
|
+
try {
|
|
18
|
+
v = JSON.parse(value);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (!object(v) || JSON.stringify(v) !== value || v.version !== 1 || !member(stages, v.stage) || !member(reasons, v.reason))
|
|
24
|
+
return false;
|
|
25
|
+
const fields = { version: x => x === 1, stage: x => member(stages, x), reason: x => member(reasons, x), errorName: x => member(names, x), errorCode: x => member(codes, x), exitCode: x => x === null || typeof x === 'number' && Number.isFinite(x) && Number.isInteger(x), signal: x => member(['SIGTERM', 'SIGKILL', 'SIGABRT', 'SIGHUP', 'SIGINT', 'SIGSEGV', 'OTHER'], x), index: integer, unexpectedCount: integer, maxOutputBytes: x => integer(x) && Number(x) > 0, observedBytes: integer, stderr: summary, output: summary, missing: x => Array.isArray(x) && x.every(k => member(['nonce', 'protocolVersion', 'type', 'data', 'exitCode', 'signal', 'loggedIn', 'provider'], k)), loggedInType: x => member(['null', 'undefined', 'object', 'boolean', 'number', 'string', 'function', 'symbol', 'bigint'], x), exitCodeType: x => member(['undefined', 'object', 'boolean', 'number', 'string', 'function', 'symbol', 'bigint'], x) };
|
|
26
|
+
return Object.entries(v).every(([key, item]) => Object.hasOwn(fields, key) && fields[key](item));
|
|
27
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConnectNeed } from '@ai-workbench/connect/api';
|
|
1
2
|
import type { ConnectTarget } from './provider.ts';
|
|
2
3
|
import type { ContractInfo, Provider, WorkspaceUse, WorkspaceNoticeReceipt as LWorkspaceNoticeReceipt } from '@ai-workbench/connect/api';
|
|
3
4
|
/** 正式L0.7公开类型;消费子集不表示已安装或已通过真实产品门。 */
|
|
@@ -16,7 +17,7 @@ export type ConnectResult<T> = {
|
|
|
16
17
|
code: string;
|
|
17
18
|
message: string;
|
|
18
19
|
requires: string;
|
|
19
|
-
}
|
|
20
|
+
} & Pick<ConnectNeed, 'officialReason'>;
|
|
20
21
|
};
|
|
21
22
|
export interface NativeWorkspaceApi {
|
|
22
23
|
getContractInfo(): ContractInfo;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export interface DetailScope {
|
|
5
|
+
instanceId: string;
|
|
6
|
+
epoch: number;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
provider: 'claude' | 'codex';
|
|
9
|
+
turn: number;
|
|
10
|
+
step: number;
|
|
11
|
+
}
|
|
12
|
+
export interface DetailObservation {
|
|
13
|
+
kind: 'extension' | 'commands' | 'tool' | 'usage' | 'notice' | 'need' | 'session';
|
|
14
|
+
severity: 'info' | 'warning' | 'action-required';
|
|
15
|
+
code: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
method?: string;
|
|
18
|
+
payload?: unknown;
|
|
19
|
+
}
|
|
20
|
+
export type SanitizedObservation = Omit<DetailObservation, 'payload'> & {
|
|
21
|
+
payload: JsonValue;
|
|
22
|
+
};
|
|
23
|
+
export interface DetailRecord extends Omit<DetailObservation, 'payload'>, DetailScope {
|
|
24
|
+
version: 1;
|
|
25
|
+
eventId: string;
|
|
26
|
+
sequence: number;
|
|
27
|
+
payload: JsonValue;
|
|
28
|
+
}
|
|
29
|
+
export type DetailSink = {
|
|
30
|
+
append(record: Readonly<DetailRecord>): Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
/** Only objects created by this module are trusted as redaction markers. */
|
|
33
|
+
export declare function isSanitizedRedaction(value: unknown): boolean;
|
|
34
|
+
/** Sanitize an internal observation without attaching any L wire semantics. */
|
|
35
|
+
export declare function sanitizeObservation(observation: DetailObservation, protectModelMaterial?: boolean): Readonly<SanitizedObservation>;
|
|
36
|
+
/** Internal diagnostic recorder; a sink is not an assertion that an L wire API exists. */
|
|
37
|
+
export declare class ConversationDetails {
|
|
38
|
+
private readonly scope;
|
|
39
|
+
private readonly sink;
|
|
40
|
+
private readonly records;
|
|
41
|
+
private tail;
|
|
42
|
+
private firstFailure;
|
|
43
|
+
constructor(scope: DetailScope, sink: DetailSink);
|
|
44
|
+
record(observation: DetailObservation): void;
|
|
45
|
+
flush(): Promise<void>;
|
|
46
|
+
snapshot(): readonly DetailRecord[];
|
|
47
|
+
}
|