@ai-workbench/remote-agent 0.8.1 → 0.8.5
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 +19 -4
- package/dist/acp.d.ts +25 -0
- package/dist/acp.js +159 -17
- package/dist/approval-label.d.ts +6 -0
- package/dist/approval-label.js +45 -0
- package/dist/auth-reason.d.ts +1 -0
- package/dist/auth-reason.js +27 -0
- package/dist/broker-runtime.d.ts +10 -0
- package/dist/broker-runtime.js +302 -0
- package/dist/broker.d.ts +29 -6
- package/dist/broker.js +141 -141
- package/dist/connect-contract.d.ts +2 -1
- package/dist/conversation-details.d.ts +1 -0
- package/dist/conversation-details.js +58 -8
- package/dist/index.d.ts +31 -14
- package/dist/index.js +248 -42
- package/dist/input.d.ts +4 -0
- package/dist/input.js +4 -1
- package/dist/login-diagnostic.d.ts +38 -0
- package/dist/login-diagnostic.js +29 -0
- package/dist/model-catalog.d.ts +55 -0
- package/dist/model-catalog.js +254 -0
- package/dist/model-configuration.d.ts +51 -0
- package/dist/model-configuration.js +212 -0
- package/dist/presentation.js +4 -0
- package/dist/protocol-boundary.d.ts +13 -0
- package/dist/protocol-boundary.js +49 -0
- package/dist/provider.d.ts +21 -3
- package/dist/provider.js +135 -41
- package/dist/readiness.js +7 -5
- package/dist/recovery-checkpoint.d.ts +6 -0
- package/dist/recovery-checkpoint.js +71 -0
- package/dist/recovery.d.ts +34 -0
- package/dist/recovery.js +52 -0
- package/dist/remote-details.d.ts +17 -1
- package/dist/remote-details.js +137 -6
- package/dist/remote.d.ts +3 -0
- package/dist/remote.js +2 -0
- package/dist/rpc-namespace.d.ts +3 -0
- package/dist/rpc-namespace.js +50 -0
- package/dist/stage-timing.d.ts +27 -0
- package/dist/stage-timing.js +39 -0
- package/package.json +4 -4
package/dist/remote-details.js
CHANGED
|
@@ -23,7 +23,7 @@ function kind(observation) {
|
|
|
23
23
|
return observation.severity === 'action-required' ? 'action-required' : 'unknown';
|
|
24
24
|
return { extension: 'extensions', tool: 'tools', need: 'action-required', notice: 'extensions', session: 'extensions' }[observation.kind] ?? observation.kind;
|
|
25
25
|
}
|
|
26
|
-
async function privateDirectory(path) {
|
|
26
|
+
export async function privateDirectory(path) {
|
|
27
27
|
if (!isAbsolute(path))
|
|
28
28
|
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
29
29
|
// Existing parent/profile ownership is the host's responsibility; reject symlinks
|
|
@@ -57,7 +57,7 @@ async function privateDirectory(path) {
|
|
|
57
57
|
if ((info.mode & 0o077) !== 0 || (process.getuid && info.uid !== process.getuid()))
|
|
58
58
|
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
59
59
|
}
|
|
60
|
-
async function durableWrite(path, value) {
|
|
60
|
+
export async function durableWrite(path, value) {
|
|
61
61
|
const file = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
62
62
|
try {
|
|
63
63
|
await file.writeFile(JSON.stringify(value) + '\n');
|
|
@@ -74,7 +74,7 @@ async function durableWrite(path, value) {
|
|
|
74
74
|
await parent.close();
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
async function readJournal(path) {
|
|
77
|
+
export async function readJournal(path) {
|
|
78
78
|
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
79
79
|
try {
|
|
80
80
|
const info = await file.stat();
|
|
@@ -88,9 +88,139 @@ async function readJournal(path) {
|
|
|
88
88
|
}
|
|
89
89
|
export function createRemoteDetails(options) {
|
|
90
90
|
const root = join(options.profileDir, 'ai-workbench-remote-agent', 'diagnostics');
|
|
91
|
-
const scopes = new Map();
|
|
91
|
+
const scopes = new Map(), loginScopes = new Map();
|
|
92
92
|
const initialize = async () => { await privateDirectory(join(options.profileDir, 'ai-workbench-remote-agent')); await privateDirectory(root); };
|
|
93
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
async recordLoginFailure(request) {
|
|
95
|
+
await initialize();
|
|
96
|
+
const eventId = randomUUID(), diagnostic = request.diagnostic;
|
|
97
|
+
// The original observed IDs and full approved snapshot are durable before any L call.
|
|
98
|
+
await durableWrite(join(root, eventId + '.json'), { version: 1, eventId, kind: 'login-check', code: request.code, sessionHash: request.sessionId ? hash(request.sessionId) : null, diagnostic, delivery: 'pending' });
|
|
99
|
+
if (!request.sessionId)
|
|
100
|
+
throw failure('REMOTE_DETAIL_SCOPE_INVALID');
|
|
101
|
+
const sessionId = request.sessionId, provider = diagnostic.provider, api = options.getApi(), connection = diagnostic.connection;
|
|
102
|
+
const stable = () => { const now = api?.getConnectionState(); if (!api || options.getApi() !== api || now?.phase !== 'connected' || now.instanceId !== connection.instanceId || now.epoch !== connection.epoch)
|
|
103
|
+
throw failure('CONNECTION_LOST'); };
|
|
104
|
+
stable();
|
|
105
|
+
if (api.getContractInfo?.().loginDiagnostics !== 1 || !['appendLoginDiagnostic', 'getLoginDiagnosticReceipt', 'listLoginDiagnostics'].every(key => typeof api[key] === 'function'))
|
|
106
|
+
throw failure('TECHNICAL_DETAILS_UNAVAILABLE', '技术详情暂不可用,请更新远程主机连接组件。');
|
|
107
|
+
const parent = join(root, 'login');
|
|
108
|
+
await privateDirectory(parent);
|
|
109
|
+
const key = hash(sessionId + '\0' + provider), dir = join(parent, key);
|
|
110
|
+
await privateDirectory(dir);
|
|
111
|
+
const scope = loginScopes.get(key) ?? { tail: Promise.resolve(), maximum: 0, loaded: false };
|
|
112
|
+
loginScopes.set(key, scope);
|
|
113
|
+
const operation = scope.tail.then(async () => {
|
|
114
|
+
stable();
|
|
115
|
+
const generation = connection.instanceId + '\0' + connection.epoch;
|
|
116
|
+
if (!scope.loaded || scope.generation !== generation) {
|
|
117
|
+
const names = await readdir(dir);
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
const match = /^(\d+)-([0-9a-f-]+)\.json$/.exec(name);
|
|
120
|
+
if (!match)
|
|
121
|
+
continue;
|
|
122
|
+
const sequence = Number(match[1]);
|
|
123
|
+
if (!Number.isSafeInteger(sequence))
|
|
124
|
+
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
125
|
+
scope.maximum = Math.max(scope.maximum, sequence);
|
|
126
|
+
const saved = await readJournal(join(dir, name));
|
|
127
|
+
if (!object(saved) || !object(saved.detail) || saved.detail.sequence !== sequence || saved.detail.eventId !== match[2] || saved.detail.sessionId !== sessionId || saved.detail.provider !== provider)
|
|
128
|
+
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
129
|
+
const settled = 'settled-' + match[2] + '.json';
|
|
130
|
+
if (names.includes(settled)) {
|
|
131
|
+
const receipt = await readJournal(join(dir, settled));
|
|
132
|
+
if (!object(receipt) || receipt.eventId !== match[2] || !['recorded', 'rejected'].includes(receipt.state))
|
|
133
|
+
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
stable();
|
|
137
|
+
const receipt = unwrap(await api.getLoginDiagnosticReceipt({ eventId: match[2], sessionId, provider }));
|
|
138
|
+
stable();
|
|
139
|
+
if (receipt.eventId !== match[2] || !receipt.recorded)
|
|
140
|
+
throw failure('REMOTE_DETAIL_DELIVERY_UNKNOWN');
|
|
141
|
+
if (!isDeepStrictEqual(receipt.detail, saved.detail))
|
|
142
|
+
throw failure('REMOTE_DETAIL_RECEIPT_INVALID');
|
|
143
|
+
await durableWrite(join(dir, settled), { eventId: match[2], state: 'recorded' });
|
|
144
|
+
}
|
|
145
|
+
let cursor, previous = -1, count = 0, expected;
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
do {
|
|
148
|
+
stable();
|
|
149
|
+
const page = unwrap(await api.listLoginDiagnostics({ sessionId, provider, ...(cursor ? { cursor } : {}) }));
|
|
150
|
+
stable();
|
|
151
|
+
if (!object(page) || !Array.isArray(page.items) || ![page.total, page.unknownCount, page.actionRequiredCount].every(n => Number.isSafeInteger(n) && n >= 0))
|
|
152
|
+
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
153
|
+
const counts = { total: page.total, unknownCount: page.unknownCount, actionRequiredCount: page.actionRequiredCount };
|
|
154
|
+
if (expected && !isDeepStrictEqual(expected, counts))
|
|
155
|
+
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
156
|
+
expected = counts;
|
|
157
|
+
for (const entry of page.items) {
|
|
158
|
+
if (entry.sessionId !== sessionId || entry.provider !== provider || !Number.isSafeInteger(entry.sequence) || entry.sequence < 0 || entry.sequence <= previous)
|
|
159
|
+
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
160
|
+
previous = entry.sequence;
|
|
161
|
+
scope.maximum = Math.max(scope.maximum, entry.sequence);
|
|
162
|
+
count++;
|
|
163
|
+
}
|
|
164
|
+
cursor = page.nextCursor;
|
|
165
|
+
if (cursor !== undefined && (typeof cursor !== 'string' || !cursor || seen.has(cursor)))
|
|
166
|
+
throw failure('REMOTE_DETAIL_CURSOR_INVALID');
|
|
167
|
+
if (cursor)
|
|
168
|
+
seen.add(cursor);
|
|
169
|
+
} while (cursor);
|
|
170
|
+
if (count !== expected?.total)
|
|
171
|
+
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
172
|
+
scope.loaded = true;
|
|
173
|
+
scope.generation = generation;
|
|
174
|
+
}
|
|
175
|
+
const mismatch = diagnostic.reason === 'LOGIN_INSTANCE_MISMATCH', unsupported = !!diagnostic.officialReasonRejected;
|
|
176
|
+
const reason = mismatch ? 'LOGIN_INSTANCE_MISMATCH' : unsupported ? 'OFFICIAL_REASON_UNSUPPORTED' : diagnostic.reason;
|
|
177
|
+
// L 0.7.11: observed evidence never changes the current connection scope.
|
|
178
|
+
const login = { phase: diagnostic.login.phase, instanceId: diagnostic.login.instanceId, logins: Object.fromEntries(Object.entries(diagnostic.login.logins).filter(([, v]) => typeof v === 'boolean' || v === 'absent')), queuedProviders: diagnostic.login.queuedProviders, checkingProviders: diagnostic.login.checkingProviders };
|
|
179
|
+
const payload = { reason, connection: { ...connection }, login, ...(mismatch ? { observedLoginInstanceId: diagnostic.login.instanceId } : {}), ...(diagnostic.officialReason ? { officialReason: diagnostic.officialReason } : {}), ...(unsupported && !mismatch ? { officialReasonRejected: diagnostic.officialReasonRejected } : {}) };
|
|
180
|
+
// The two payload reasons are exclusive in L's contract. Preserve both facts
|
|
181
|
+
// using the approved fixed summary and full digest, without inventing a field.
|
|
182
|
+
const rejected = diagnostic.officialReasonRejected;
|
|
183
|
+
const summary = '登录检查:' + diagnostic.reason + (rejected ? '; OFFICIAL_REASON_UNSUPPORTED; bytes=' + rejected.bytes + '; SHA256=' + rejected.sha256 : '');
|
|
184
|
+
const context = request.context ?? { turn: null, step: null };
|
|
185
|
+
if (!identifier(context.turn) || !identifier(context.step))
|
|
186
|
+
throw failure('REMOTE_DETAIL_SCOPE_INVALID');
|
|
187
|
+
const sequence = scope.maximum + 1;
|
|
188
|
+
if (!Number.isSafeInteger(sequence))
|
|
189
|
+
throw failure('REMOTE_DETAIL_SEQUENCE_INVALID');
|
|
190
|
+
scope.maximum = sequence;
|
|
191
|
+
const detail = { version: 1, eventId, instanceId: connection.instanceId, epoch: connection.epoch, sessionId, provider, turn: context.turn, step: context.step, sequence, kind: 'login-check', severity: 'action-required', code: request.code, summary, method: diagnostic.source, payload };
|
|
192
|
+
await durableWrite(join(dir, sequence + '-' + eventId + '.json'), { state: 'pending', detail });
|
|
193
|
+
stable();
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
196
|
+
response = await api.appendLoginDiagnostic(structuredClone(detail));
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
stable();
|
|
200
|
+
const receipt = unwrap(await api.getLoginDiagnosticReceipt({ eventId, sessionId, provider }));
|
|
201
|
+
stable();
|
|
202
|
+
if (receipt.eventId !== eventId)
|
|
203
|
+
throw failure('REMOTE_DETAIL_RECEIPT_INVALID');
|
|
204
|
+
if (!receipt.recorded)
|
|
205
|
+
throw failure('REMOTE_DETAIL_DELIVERY_UNKNOWN');
|
|
206
|
+
if (!isDeepStrictEqual(receipt.detail, detail))
|
|
207
|
+
throw failure('REMOTE_DETAIL_RECEIPT_INVALID');
|
|
208
|
+
await durableWrite(join(dir, 'settled-' + eventId + '.json'), { eventId, state: 'recorded' });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
stable();
|
|
212
|
+
if (response?.ok === false)
|
|
213
|
+
await durableWrite(join(dir, 'settled-' + eventId + '.json'), { eventId, state: 'rejected', code: response.error.code });
|
|
214
|
+
const ack = unwrap(response);
|
|
215
|
+
if (ack.eventId !== eventId || ack.recorded !== true)
|
|
216
|
+
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
217
|
+
await durableWrite(join(dir, 'settled-' + eventId + '.json'), { eventId, state: 'recorded' });
|
|
218
|
+
});
|
|
219
|
+
scope.tail = operation.then(() => { }, () => { scope.loaded = false; });
|
|
220
|
+
await operation;
|
|
221
|
+
return { eventId, recorded: true };
|
|
222
|
+
},
|
|
223
|
+
async open(request) {
|
|
94
224
|
if (!request.sessionId || !request.identity || !request.connection.instanceId || !Number.isSafeInteger(request.connection.epoch) || request.connection.epoch < 0 || !['claude', 'codex'].includes(request.provider))
|
|
95
225
|
throw failure('REMOTE_DETAIL_SCOPE_INVALID');
|
|
96
226
|
try {
|
|
@@ -246,5 +376,6 @@ export function createRemoteDetails(options) {
|
|
|
246
376
|
async flush() { await tail; if (firstFailure)
|
|
247
377
|
throw firstFailure.error; stable(); },
|
|
248
378
|
};
|
|
249
|
-
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
250
381
|
}
|
package/dist/remote.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LoginDiagnostic } from './login-diagnostic.ts';
|
|
1
2
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
3
|
/** Development-only direct SSH. Product transports must consume L's exported sshArgs (v0.2). */
|
|
3
4
|
export interface DevelopmentSshTarget {
|
|
@@ -16,6 +17,8 @@ export interface Need {
|
|
|
16
17
|
export declare class NeedError extends Error {
|
|
17
18
|
code: string;
|
|
18
19
|
requires: string;
|
|
20
|
+
technical?: LoginDiagnostic;
|
|
21
|
+
officialReason?: string;
|
|
19
22
|
constructor(code: string, message: string, requires: string);
|
|
20
23
|
}
|
|
21
24
|
export declare function shellQuote(value: string): string;
|
package/dist/remote.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { NeedError } from "./remote.js";
|
|
3
|
+
/** Public Stream only. Server request/approval IDs are never changed. */
|
|
4
|
+
export function isolateRpcIds(stream) {
|
|
5
|
+
const prefix = randomUUID() + '/';
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
const readable = stream.readable.pipeThrough(new TransformStream({ transform(message, controller) {
|
|
8
|
+
const raw = message;
|
|
9
|
+
if (!raw.method && Object.hasOwn(raw, 'id')) {
|
|
10
|
+
if (typeof raw.id !== 'string' || !pending.has(raw.id))
|
|
11
|
+
throw new NeedError('ACP_FOREIGN_RESPONSE', '收到不属于当前客户端的协议响应', '原会话的完整客户端状态');
|
|
12
|
+
const id = pending.get(raw.id);
|
|
13
|
+
pending.delete(raw.id);
|
|
14
|
+
controller.enqueue({ ...raw, id });
|
|
15
|
+
}
|
|
16
|
+
else
|
|
17
|
+
controller.enqueue(message);
|
|
18
|
+
} }));
|
|
19
|
+
const writable = new WritableStream({ async write(message) {
|
|
20
|
+
const raw = message;
|
|
21
|
+
let outgoing = raw;
|
|
22
|
+
if (typeof raw.method === 'string' && Object.hasOwn(raw, 'id')) {
|
|
23
|
+
const id = prefix + JSON.stringify(raw.id);
|
|
24
|
+
pending.set(id, raw.id);
|
|
25
|
+
outgoing = { ...raw, id };
|
|
26
|
+
}
|
|
27
|
+
else if (raw.method === '$/cancel_request' && raw.params && typeof raw.params === 'object') {
|
|
28
|
+
const params = raw.params;
|
|
29
|
+
outgoing = { ...raw, params: { ...params, requestId: prefix + JSON.stringify(params.requestId) } };
|
|
30
|
+
}
|
|
31
|
+
const writer = stream.writable.getWriter();
|
|
32
|
+
try {
|
|
33
|
+
await writer.write(outgoing);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
writer.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
}, async close() { const writer = stream.writable.getWriter(); try {
|
|
39
|
+
await writer.close();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
writer.releaseLock();
|
|
43
|
+
} }, async abort(reason) { const writer = stream.writable.getWriter(); try {
|
|
44
|
+
await writer.abort(reason);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
writer.releaseLock();
|
|
48
|
+
} } });
|
|
49
|
+
return { readable, writable };
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DetailObservation } from './conversation-details.ts';
|
|
2
|
+
export declare const STAGES: Readonly<{
|
|
3
|
+
readonly 'login-ready': "登录就绪检查";
|
|
4
|
+
readonly 'connection-confirm': "核对当前连接状态";
|
|
5
|
+
readonly 'input-prepare': "准备本轮输入";
|
|
6
|
+
readonly 'workspace-resolve': "解析工作区";
|
|
7
|
+
readonly 'details-open': "打开技术详情";
|
|
8
|
+
readonly 'details-flush': "等待技术详情持久确认";
|
|
9
|
+
readonly 'workspace-notice': "记录工作区说明";
|
|
10
|
+
readonly 'broker-list': "查询主机会话";
|
|
11
|
+
readonly 'broker-start': "启动主机会话";
|
|
12
|
+
readonly 'broker-attach': "连接主机会话";
|
|
13
|
+
readonly 'acp-initialize': "初始化远程模型";
|
|
14
|
+
readonly 'acp-session-new': "新建模型会话";
|
|
15
|
+
readonly 'acp-session-load': "加载模型会话";
|
|
16
|
+
readonly 'acp-session-resume': "恢复模型会话";
|
|
17
|
+
readonly 'acp-prompt': "模型处理本轮(含等待审批)";
|
|
18
|
+
}>;
|
|
19
|
+
export type Stage = keyof typeof STAGES;
|
|
20
|
+
/** Passive client observations; no thresholds, retries, or remote-clock claims. */
|
|
21
|
+
export declare class StageTiming {
|
|
22
|
+
private readonly started;
|
|
23
|
+
private pending;
|
|
24
|
+
private sink?;
|
|
25
|
+
bind(sink: (detail: DetailObservation) => void): void;
|
|
26
|
+
run<T>(phase: Stage, operation: () => Promise<T>): Promise<T>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const STAGES = Object.freeze({
|
|
2
|
+
'login-ready': '登录就绪检查', 'connection-confirm': '核对当前连接状态', 'input-prepare': '准备本轮输入', 'workspace-resolve': '解析工作区',
|
|
3
|
+
'details-open': '打开技术详情', 'details-flush': '等待技术详情持久确认', 'workspace-notice': '记录工作区说明',
|
|
4
|
+
'broker-list': '查询主机会话', 'broker-start': '启动主机会话', 'broker-attach': '连接主机会话',
|
|
5
|
+
'acp-initialize': '初始化远程模型', 'acp-session-new': '新建模型会话',
|
|
6
|
+
'acp-session-load': '加载模型会话', 'acp-session-resume': '恢复模型会话',
|
|
7
|
+
'acp-prompt': '模型处理本轮(含等待审批)',
|
|
8
|
+
});
|
|
9
|
+
/** Passive client observations; no thresholds, retries, or remote-clock claims. */
|
|
10
|
+
export class StageTiming {
|
|
11
|
+
started = performance.now();
|
|
12
|
+
pending = [];
|
|
13
|
+
sink;
|
|
14
|
+
bind(sink) {
|
|
15
|
+
this.sink = sink;
|
|
16
|
+
const pending = this.pending;
|
|
17
|
+
this.pending = [];
|
|
18
|
+
for (const detail of pending)
|
|
19
|
+
sink(detail);
|
|
20
|
+
}
|
|
21
|
+
async run(phase, operation) {
|
|
22
|
+
const started = performance.now();
|
|
23
|
+
let outcome = 'failed';
|
|
24
|
+
try {
|
|
25
|
+
const result = await operation();
|
|
26
|
+
outcome = 'ok';
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
const ended = performance.now();
|
|
31
|
+
const durationMs = ended - started, elapsedMs = ended - this.started;
|
|
32
|
+
const detail = { kind: 'session', severity: outcome === 'ok' ? 'info' : 'warning', code: 'REMOTE_STAGE_TIMING', summary: STAGES[phase] + ':' + durationMs.toFixed(1) + '毫秒', payload: { phase, durationMs, elapsedMs, outcome, clock: 'client-monotonic' } };
|
|
33
|
+
if (this.sink)
|
|
34
|
+
this.sink(detail);
|
|
35
|
+
else
|
|
36
|
+
this.pending.push(detail);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-workbench/remote-agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"cordis.patch.yml"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"test": "npm run build && node --test lib/test/*.test.js",
|
|
22
|
+
"test": "npm run build && node --test --test-concurrency=1 lib/test/*.test.js",
|
|
23
23
|
"typecheck": "node node_modules/typescript/bin/tsc --noEmit",
|
|
24
24
|
"build": "node node_modules/typescript/bin/tsc && node node_modules/typescript/bin/tsc -p tsconfig.build.json && chmod 755 lib/src/sdk-ssh-wrapper.js dist/sdk-ssh-wrapper.js && node tools/build-client.mjs",
|
|
25
25
|
"probe:host": "node tools/probe-host.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"zod": "4.6.7"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.
|
|
39
|
+
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.13.tgz",
|
|
40
40
|
"@deepseek-ai/cordis": "4.0.4",
|
|
41
41
|
"@deepseek-ai/cordis-plugin-group": "1.0.4",
|
|
42
42
|
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@ai-workbench/connect": "~0.7.
|
|
59
|
+
"@ai-workbench/connect": "~0.7.13",
|
|
60
60
|
"@deepseek-ai/cordis": "4.0.4",
|
|
61
61
|
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
62
62
|
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|