@ai-workbench/remote-agent 0.8.21 → 0.8.23
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 +27 -156
- package/dist/acp.d.ts +20 -15
- package/dist/acp.js +197 -224
- package/dist/agents.d.ts +0 -1
- package/dist/agents.js +2 -2
- package/dist/broker-runtime.d.ts +3 -5
- package/dist/broker-runtime.js +45 -342
- package/dist/broker.d.ts +6 -31
- package/dist/broker.js +42 -113
- package/dist/catalog-ssh.d.ts +2 -31
- package/dist/catalog-ssh.js +9 -39
- package/dist/claude-local.d.ts +1 -1
- package/dist/claude-local.js +40 -105
- package/dist/client.js +34 -127
- package/dist/close-diagnostics.d.ts +6 -20
- package/dist/close-diagnostics.js +13 -57
- package/dist/connect-contract.d.ts +4 -8
- package/dist/conversation-details.d.ts +0 -29
- package/dist/conversation-details.js +8 -99
- package/dist/employee-endpoint.d.ts +1 -3
- package/dist/employee-endpoint.js +8 -39
- package/dist/employee-exec.d.ts +4 -3
- package/dist/employee-exec.js +25 -13
- package/dist/employee-info.d.ts +5 -1
- package/dist/employee-info.js +16 -5
- package/dist/employee-location.js +3 -4
- package/dist/employee-mcp.js +44 -69
- package/dist/employee-native.d.ts +1 -3
- package/dist/employee-native.js +6 -27
- package/dist/employee-presentation.d.ts +0 -5
- package/dist/employee-presentation.js +0 -15
- package/dist/employee-provider.d.ts +3 -11
- package/dist/employee-provider.js +69 -205
- package/dist/employee-reasons.js +1 -5
- package/dist/employee-shell.d.ts +2 -1
- package/dist/employee-shell.js +10 -13
- package/dist/employee-ssh.d.ts +1 -3
- package/dist/employee-ssh.js +13 -16
- package/dist/employee-tools.d.ts +5 -5
- package/dist/failure-evidence.d.ts +2 -10
- package/dist/failure-evidence.js +12 -61
- package/dist/host-workspace.d.ts +4 -2
- package/dist/host-workspace.js +1 -1
- package/dist/index.d.ts +13 -43
- package/dist/index.js +190 -489
- package/dist/input.js +2 -3
- package/dist/local-user-turn.js +5 -6
- package/dist/mapping.d.ts +0 -3
- package/dist/mapping.js +0 -34
- package/dist/model-catalog.d.ts +2 -41
- package/dist/model-catalog.js +24 -282
- package/dist/model-configuration.d.ts +0 -3
- package/dist/model-configuration.js +8 -47
- package/dist/model-presentation.d.ts +1 -0
- package/dist/model-presentation.js +2 -2
- package/dist/permissions.d.ts +15 -0
- package/dist/permissions.js +30 -0
- package/dist/preflight-diagnostic.js +11 -22
- package/dist/preflight-payload.d.ts +1 -1
- package/dist/preflight-payload.js +2 -2
- package/dist/presentation.js +21 -41
- package/dist/provider.d.ts +12 -34
- package/dist/provider.js +17 -92
- package/dist/readiness.js +1 -7
- package/dist/remote-details.d.ts +41 -17
- package/dist/remote-details.js +84 -381
- package/dist/workspace-source.d.ts +1 -25
- package/dist/workspace-source.js +1 -138
- package/package.json +11 -12
- package/dist/catalog-diagnostics.d.ts +0 -16
- package/dist/catalog-diagnostics.js +0 -108
- package/dist/host-layout.d.ts +0 -8
- package/dist/host-layout.js +0 -9
- package/dist/job.d.ts +0 -17
- package/dist/job.js +0 -57
- package/dist/model-defaults.d.ts +0 -29
- package/dist/model-defaults.js +0 -62
- package/dist/oneshot.d.ts +0 -13
- package/dist/oneshot.js +0 -78
- package/dist/protocol-boundary.d.ts +0 -13
- package/dist/protocol-boundary.js +0 -49
- package/dist/provider-locale.d.ts +0 -19
- package/dist/provider-locale.js +0 -73
- package/dist/recovery-checkpoint.d.ts +0 -6
- package/dist/recovery-checkpoint.js +0 -71
- package/dist/recovery.d.ts +0 -51
- package/dist/recovery.js +0 -61
- package/dist/sdk-ssh-wrapper.d.ts +0 -2
- package/dist/sdk-ssh-wrapper.js +0 -34
- package/dist/stage-timing.d.ts +0 -31
- package/dist/stage-timing.js +0 -61
- package/dist/workspace-notice.d.ts +0 -47
- package/dist/workspace-notice.js +0 -273
- package/dist/workspace-source-confirmation.d.ts +0 -4
- package/dist/workspace-source-confirmation.js +0 -12
package/dist/claude-local.js
CHANGED
|
@@ -6,7 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import {
|
|
9
|
+
import { EMPLOYEE_EXECUTION_NOTICE as NOTICE } from "./employee-presentation.js";
|
|
10
10
|
import { CloseDiagnostics, closeCode } from "./close-diagnostics.js";
|
|
11
11
|
import { isDeepStrictEqual } from 'node:util';
|
|
12
12
|
import { openEmployeeLease } from "./employee-endpoint.js";
|
|
@@ -17,7 +17,7 @@ import { sshIo } from "./broker.js";
|
|
|
17
17
|
import { NeedError, shellQuote } from "./remote.js";
|
|
18
18
|
import { awaitL } from "./provider.js";
|
|
19
19
|
const failure = (code) => new NeedError(code, '当前本机执行上下文无法确认,本轮已停止;没有改在主机执行。', 'L当前本地绑定、N员工端点与S同代桥');
|
|
20
|
-
/** Local source only. Host
|
|
20
|
+
/** Local source only. Host source remains with the host provider. */
|
|
21
21
|
export function createClaudeLocalProvider(options) {
|
|
22
22
|
const entries = new Map();
|
|
23
23
|
const admitted = new WeakMap();
|
|
@@ -38,22 +38,17 @@ export function createClaudeLocalProvider(options) {
|
|
|
38
38
|
const item = matches[0];
|
|
39
39
|
if (item.source !== 'local')
|
|
40
40
|
return;
|
|
41
|
-
if (item.status !== 'selected' || !item.bindingId || api.getContractInfo?.().
|
|
41
|
+
if (item.status !== 'selected' || !item.bindingId || api.getContractInfo?.().localWorkspaceContext !== 1 || typeof local.getLocalWorkspaceContext !== 'function')
|
|
42
42
|
throw failure('EMPLOYEE_CONTEXT_UNAVAILABLE');
|
|
43
43
|
const context = unwrap(await awaitL(() => local.getLocalWorkspaceContext({ instanceId: connection.instanceId, epoch: connection.epoch, workspaceId: workspace.workspaceId, expectedBindingId: item.bindingId }), request.signal));
|
|
44
44
|
if (context.instanceId !== connection.instanceId || context.epoch !== connection.epoch || context.workspaceId !== workspace.workspaceId || context.bindingId !== item.bindingId || context.source !== 'local' || !isDeepStrictEqual(context.directory, workspace.directory))
|
|
45
45
|
throw failure('EMPLOYEE_CONTEXT_CHANGED');
|
|
46
46
|
const proof = await options.readUserTurn(request);
|
|
47
47
|
proof.assertCurrent();
|
|
48
|
-
const accepted = unwrap(await awaitL(() => api.acceptLocalWorkspaceUserTurn({ instanceId: connection.instanceId, epoch: connection.epoch, sessionId: request.sessionId, provider: 'claude', purpose: 'user-turn', workspaceId: workspace.workspaceId, expectedBindingId: item.bindingId, expectedDirectory: { device: workspace.directory.device, inode: workspace.directory.inode }, expectedUserTurn: { ...proof.expectedUserTurn } }), request.signal));
|
|
49
|
-
proof.assertCurrent();
|
|
50
48
|
request.signal?.throwIfAborted();
|
|
51
49
|
const now = api.getConnectionState();
|
|
52
50
|
if (options.getConnect() !== api || now.phase !== 'connected' || now.instanceId !== connection.instanceId || now.epoch !== connection.epoch)
|
|
53
51
|
throw failure('CONNECTION_LOST');
|
|
54
|
-
if (!accepted || accepted.version !== 1 || typeof accepted.acceptanceId !== 'string' || !accepted.acceptanceId.trim() || accepted.instanceId !== connection.instanceId || accepted.epoch !== connection.epoch || accepted.sessionId !== request.sessionId || accepted.provider !== 'claude' || accepted.workspaceId !== workspace.workspaceId || accepted.bindingId !== item.bindingId || accepted.source !== 'local' || !isDeepStrictEqual(accepted.directory, workspace.directory) || accepted.userTurn?.turn !== proof.expectedUserTurn.turn || accepted.userTurn.userMessageId !== proof.expectedUserTurn.userMessageId || accepted.userTurn.startSequence !== proof.startSequence || accepted.userTurn.userMessageSequence !== proof.userMessageSequence)
|
|
55
|
-
throw failure('REMOTE_DETAIL_RESPONSE_INVALID');
|
|
56
|
-
Object.defineProperty(request, 'localAcceptanceId', { value: accepted.acceptanceId, enumerable: true });
|
|
57
52
|
request.detailChannel = await options.details(request, api, connection, target);
|
|
58
53
|
admitted.set(request, { api, workspace: structuredClone(workspace), bindingId: item.bindingId, connection: structuredClone(connection) });
|
|
59
54
|
}
|
|
@@ -94,37 +89,17 @@ export function createClaudeLocalProvider(options) {
|
|
|
94
89
|
throw failure('SETTINGS_REQUIRED');
|
|
95
90
|
const scope = { target, workspace, bindingId: item.bindingId, instanceId: connection.instanceId, epoch: connection.epoch, maximum, control, grace };
|
|
96
91
|
let entry = entries.get(request.sessionId);
|
|
97
|
-
if (entry
|
|
98
|
-
|
|
92
|
+
if (entry) {
|
|
93
|
+
entries.delete(request.sessionId);
|
|
94
|
+
void entry.runtime.dispose?.({ layer: 'runtime', initiator: 'claude.next-round', reason: 'NEXT_ROUND', action: 'dispose' }).catch(() => console.warn('CLAUDE_CLEANUP_FAILED'));
|
|
95
|
+
entry = undefined;
|
|
96
|
+
}
|
|
99
97
|
const details = request.detailChannel;
|
|
100
|
-
const closeDiagnostics =
|
|
98
|
+
const closeDiagnostics = new CloseDiagnostics(event => details.record(event));
|
|
101
99
|
closeDiagnostics.bind(event => details.record(event));
|
|
102
100
|
request.closeDiagnostics = closeDiagnostics;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const trusted = async (ignoreCancellation = false) => {
|
|
106
|
-
if (!ignoreCancellation)
|
|
107
|
-
request.signal?.throwIfAborted();
|
|
108
|
-
if (options.getConnect() !== api)
|
|
109
|
-
throw failure('CONNECTION_LOST');
|
|
110
|
-
const now = api.getConnectionState();
|
|
111
|
-
if (now.phase !== 'connected' || now.instanceId !== connection.instanceId || now.epoch !== connection.epoch)
|
|
112
|
-
throw failure('CONNECTION_LOST');
|
|
113
|
-
};
|
|
114
|
-
const openRequest = (row, cold = false) => ({ ...request, detailChannel: details, bindingId: item.bindingId, workspaceId: workspace.workspaceId, bindingRevision: state.revision, ...cold ? { recoveryCheckpointId: undefined } : {}, validateContext: async (cleanup = false) => { await trusted(cleanup); if (!cleanup)
|
|
115
|
-
await row.lease.validate(request.signal); } });
|
|
116
|
-
if (entry) {
|
|
117
|
-
entry.details = details;
|
|
118
|
-
entry.request = request;
|
|
119
|
-
await entry.lease.validate(request.signal);
|
|
120
|
-
const base = await options.open(target, { name: workspace.workspaceId, path: target.session.root }, openRequest(entry));
|
|
121
|
-
if (base.acpTransport !== entry.runtime.acpTransport)
|
|
122
|
-
throw failure('SESSION_TRANSPORT_MISMATCH');
|
|
123
|
-
entry.runtime.details = details;
|
|
124
|
-
entry.runtime.restartNotice = base.restartNotice;
|
|
125
|
-
entry.runtime.restartInputIds = base.restartInputIds;
|
|
126
|
-
return entry.runtime;
|
|
127
|
-
}
|
|
101
|
+
details.record({ kind: 'session', severity: 'info', code: 'CLAUDE_EMPLOYEE_VALIDATION_POLICY', summary: '本机执行不可改到主机:每轮开始从L读取一次本机工作区上下文,核验所选物理目录、N端点与主机目标,并新建本轮工具通道。之后不逐消息、审批或工具重复核验;既有连接、位置、网络事件和通道关闭只终止本轮,下一轮重新核验。' });
|
|
102
|
+
const openRequest = () => ({ ...request, detailChannel: details, bindingId: item.bindingId, workspaceId: workspace.workspaceId, bindingRevision: state.revision });
|
|
128
103
|
let io;
|
|
129
104
|
const transport = async () => { if (!io) {
|
|
130
105
|
const name = '@ai-workbench/connect';
|
|
@@ -134,61 +109,44 @@ export function createClaudeLocalProvider(options) {
|
|
|
134
109
|
io = sshIo(target, l.sshArgs, control);
|
|
135
110
|
} return io; };
|
|
136
111
|
let lease, mcp, base;
|
|
137
|
-
let
|
|
112
|
+
let privateTail = Promise.resolve();
|
|
138
113
|
try {
|
|
139
|
-
const unscoped = (observation) => {
|
|
140
|
-
|
|
114
|
+
const unscoped = (observation) => { privateTail = privateTail.then(() => options.recordUnscoped(request, observation)).catch(() => { console.warn('CLAUDE_DIAGNOSTIC_DROPPED'); }); };
|
|
115
|
+
const network = options.getNetwork();
|
|
141
116
|
lease = await (options.ports?.lease ?? openEmployeeLease)({ closeDiagnostics, getConnect: () => options.getConnect(), getNetwork: options.getNetwork, readWorkspace: () => options.readWorkspace(request.sessionId), host: target, sessionId: request.sessionId, control: async (_target, command, signal) => (await transport()).run(command, signal), detail: unscoped }, request.signal);
|
|
142
|
-
const shell = (options.ports?.shell ?? createEmployeeShell)({ closeDiagnostics, target: lease.target, maxBytes: maximum, killGraceMs: grace, attach: async (spec) => (await transport()).attach([spec.command, ...spec.args].map(shellQuote).join(' ')) });
|
|
117
|
+
const shell = (options.ports?.shell ?? createEmployeeShell)({ closeDiagnostics, target: lease.target, sshArgs: lease.sshArgs, maxBytes: maximum, killGraceMs: grace, attach: async (spec) => (await transport()).attach([spec.command, ...spec.args].map(shellQuote).join(' ')) });
|
|
143
118
|
mcp = await (options.ports?.mcp ?? openEmployeeMcp)({ closeDiagnostics, lease, maxBytes: maximum, killGraceMs: grace, runShell: shell, activeSignal: () => entry?.active?.signal, captureDetail: () => { const sink = entry?.active?.details; if (!sink)
|
|
144
119
|
throw failure('EMPLOYEE_NO_ACTIVE_TURN'); return observation => sink.record(observation); }, detail: unscoped });
|
|
145
120
|
entry = { runtime: undefined, scope, lease, details, request };
|
|
146
121
|
const row = entry;
|
|
147
|
-
base = await options.open(target, { name: workspace.workspaceId, path: target.session.root }, openRequest(
|
|
122
|
+
base = await options.open(target, { name: workspace.workspaceId, path: target.session.root }, openRequest());
|
|
123
|
+
// The old per-step identity check runs once when this round finishes opening.
|
|
124
|
+
if (options.getConnect() !== api || options.getNetwork() !== network)
|
|
125
|
+
throw failure('EMPLOYEE_CONTEXT_CHANGED');
|
|
148
126
|
const underlying = base, endpoint = mcp;
|
|
149
127
|
const close = (detach, cause = { layer: 'runtime', initiator: detach ? 'claude.runtime-detach' : 'claude.runtime-dispose', reason: detach ? 'RUNTIME_DETACH' : 'RUNTIME_DISPOSE', action: detach ? 'detach' : 'dispose' }) => {
|
|
128
|
+
// Unlink synchronously. A slow/failed cleanup must not hold the next round.
|
|
129
|
+
if (entries.get(request.sessionId) === row)
|
|
130
|
+
entries.delete(request.sessionId);
|
|
150
131
|
closeDiagnostics.record(cause);
|
|
151
132
|
return row.closing ??= Promise.resolve().then(async () => {
|
|
152
133
|
row.active = undefined;
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
await endpoint.close({ layer: 'relay', initiator: 'claude.cleanup-mcp', reason: 'RUNTIME_CLEANUP', action: 'close' });
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
errors.push(error);
|
|
159
|
-
}
|
|
160
|
-
await privateTail;
|
|
161
|
-
if (privateFailure)
|
|
162
|
-
errors.push(privateFailure);
|
|
163
|
-
try {
|
|
164
|
-
await (detach ? underlying.detach?.(cause) : underlying.dispose?.(cause));
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
errors.push(error);
|
|
168
|
-
}
|
|
169
|
-
try {
|
|
170
|
-
await row.details.flush();
|
|
134
|
+
const clean = async (action) => { try {
|
|
135
|
+
await action();
|
|
171
136
|
}
|
|
172
|
-
catch
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
catch (error) {
|
|
179
|
-
errors.push(error);
|
|
180
|
-
}
|
|
181
|
-
if (errors.length)
|
|
182
|
-
throw new AggregateError(errors, 'EMPLOYEE_CLEANUP_UNCONFIRMED');
|
|
183
|
-
if (entries.get(request.sessionId) === row)
|
|
184
|
-
entries.delete(request.sessionId);
|
|
137
|
+
catch {
|
|
138
|
+
console.warn('CLAUDE_CLEANUP_FAILED');
|
|
139
|
+
} };
|
|
140
|
+
await clean(() => endpoint.close({ layer: 'relay', initiator: 'claude.cleanup-mcp', reason: 'RUNTIME_CLEANUP', action: 'close' }));
|
|
141
|
+
await clean(() => row.lease.close({ layer: 'endpoint', initiator: 'claude.cleanup-lease', reason: 'RUNTIME_CLEANUP', action: 'close' }));
|
|
142
|
+
await clean(() => detach ? underlying.detach?.(cause) : underlying.dispose?.(cause));
|
|
185
143
|
});
|
|
186
144
|
};
|
|
187
145
|
const executionSignal = AbortSignal.any([lease.signal, ...mcp.signal ? [mcp.signal] : []]);
|
|
188
|
-
row.runtime = { ...underlying, closeDiagnostics, cwd: workspace.directory.path, details, connection: { ...connection }, binding: { bindingId: item.bindingId, workspaceId: workspace.workspaceId, revision: state.revision }, execution: { hostCwd: target.session.root, routing: mcp.routing, notice: NOTICE, signal: executionSignal, scope: { source: 'local', cwd: workspace.directory.path, hostCwd: target.session.root, uid: lease.target.endpoint.remoteUid, claudeAcpVersion: '0.94.0', sdkVersion: '0.3.303', unsupported: ['subagents', 'internal-direct-calls', 'binary-read', 'background-shell'] }, validate: async (signal) => {
|
|
189
|
-
throw failure('TURN_BUSY');
|
|
146
|
+
row.runtime = { ...underlying, closeDiagnostics, cwd: workspace.directory.path, details, connection: { ...connection }, binding: { bindingId: item.bindingId, workspaceId: workspace.workspaceId, revision: state.revision }, execution: { hostCwd: target.session.root, routing: mcp.routing, notice: NOTICE, signal: executionSignal, scope: { source: 'local', cwd: workspace.directory.path, hostCwd: target.session.root, uid: lease.target.endpoint.remoteUid, claudeAcpVersion: '0.94.0', sdkVersion: '0.3.303', unsupported: ['subagents', 'internal-direct-calls', 'binary-read', 'background-shell'] }, validate: async (signal) => { signal?.throwIfAborted(); executionSignal.throwIfAborted(); }, beginTurn: async (signal) => { signal?.throwIfAborted(); executionSignal.throwIfAborted(); if (row.closing || row.active)
|
|
147
|
+
throw failure('TURN_BUSY'); row.active = { signal, details: row.details }; }, endTurn: () => { row.active = undefined; } }, dispose: cause => close(false, cause), detach: cause => close(true, cause) };
|
|
190
148
|
entries.set(request.sessionId, row);
|
|
191
|
-
lease.signal.addEventListener('abort', () => { void close(false, { layer: 'endpoint', initiator: 'claude.lease-abort', reason: closeCode(lease?.signal.reason, 'EMPLOYEE_CONTEXT_CHANGED'), action: 'abort' }).catch(() =>
|
|
149
|
+
lease.signal.addEventListener('abort', () => { void close(false, { layer: 'endpoint', initiator: 'claude.lease-abort', reason: closeCode(lease?.signal.reason, 'EMPLOYEE_CONTEXT_CHANGED'), action: 'abort' }).catch(() => console.warn('CLAUDE_CLEANUP_FAILED')); }, { once: true });
|
|
192
150
|
if (lease.signal.aborted) {
|
|
193
151
|
await close(false, { layer: 'endpoint', initiator: 'claude.open-aborted', reason: closeCode(lease.signal.reason, 'EMPLOYEE_CONTEXT_CHANGED'), action: 'abort' });
|
|
194
152
|
throw failure('EMPLOYEE_CONTEXT_CHANGED');
|
|
@@ -198,37 +156,14 @@ export function createClaudeLocalProvider(options) {
|
|
|
198
156
|
catch (error) {
|
|
199
157
|
closeDiagnostics.record({ layer: 'runtime', initiator: 'claude.open-failure', reason: closeCode(error, 'EMPLOYEE_OPEN_FAILED'), action: 'failure' });
|
|
200
158
|
const cause = { layer: 'runtime', initiator: 'claude.open-cleanup', reason: 'EMPLOYEE_OPEN_FAILED', action: 'close' };
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
await base?.dispose?.(cause);
|
|
211
|
-
}
|
|
212
|
-
catch (e) {
|
|
213
|
-
cleanup.push(e);
|
|
214
|
-
}
|
|
215
|
-
await privateTail;
|
|
216
|
-
if (privateFailure)
|
|
217
|
-
cleanup.push(privateFailure);
|
|
218
|
-
try {
|
|
219
|
-
await details.flush();
|
|
220
|
-
}
|
|
221
|
-
catch (e) {
|
|
222
|
-
cleanup.push(e);
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
closeDiagnostics.assertRecorded();
|
|
226
|
-
}
|
|
227
|
-
catch (e) {
|
|
228
|
-
cleanup.push(e);
|
|
159
|
+
for (const clean of [() => mcp?.close(cause), () => lease?.close(cause), () => base?.dispose?.(cause)]) {
|
|
160
|
+
try {
|
|
161
|
+
await clean();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
console.warn('CLAUDE_CLEANUP_FAILED');
|
|
165
|
+
}
|
|
229
166
|
}
|
|
230
|
-
if (cleanup.length)
|
|
231
|
-
throw new AggregateError([error, ...cleanup], 'EMPLOYEE_OPEN_CLEANUP_UNCONFIRMED');
|
|
232
167
|
throw error;
|
|
233
168
|
}
|
|
234
169
|
};
|
package/dist/client.js
CHANGED
|
@@ -1,146 +1,53 @@
|
|
|
1
|
-
/* Official locale and provider-registration updates only; no custom UI or refresh. */
|
|
2
1
|
window.__ModuleLoader__.load({id:'@ai-workbench/remote-agent',factory(require){
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const messages={"en":{"administratorNote":"Settings → Built-in plugins → 远程助手参数. Eight named settings with defaults, scope and effects.","providerName":"Codex (Remote Host)"},"zh":{"administratorNote":"管理员:设置 → 内置插件 → 远程助手参数。八项设置含默认值、范围、生效和后果说明。","providerName":"Codex(远程主机)"}};
|
|
3
|
+
const settingsFields=[{"key":"acpMaxMessageBytes","name":"完整消息与工具输出容量(字节)","kind":"number","min":1,"max":9007199254740991,"range":"1~9007199254740991 的整数;0不能关闭上限","scope":"两位远程助手的完整会话消息;也用于模型目录、本机 MCP、员工执行 RPC 和工具输出容量。","when":"当轮快照不变;下一次独立目录查询读取新值;已有会话需新建后使用。","consequence":"调大可接收更大消息、占用更多内存;调小更早拒绝超限。超限明确停止,不截断、不自动重试;不能用0取消完整消息保护。","defaultValue":4194304},{"key":"brokerMaxFrameBytes","name":"主机会话回传单段容量(字节)","kind":"number","min":1,"max":9007199254740991,"range":"1~9007199254740991 的整数;0不能关闭上限","scope":"本插件接收主机会话单段输出的容量;不会替主机修改它自己的输出策略。","when":"当轮快照不变;更改后已有会话需新建。","consequence":"调大允许更大回传段、占用更多内存;调小可能拒绝主机合法输出。超限停止,不截断;不能关闭协议帧核验。","defaultValue":2097152},{"key":"brokerControlMaxBytes","name":"主机会话管理回复容量(字节)","kind":"number","min":1,"max":9007199254740991,"range":"1~9007199254740991 的整数;0不能关闭上限","scope":"主机会话管理命令的标准输出、标准错误各自容量;也用于本机执行桥的管理回复。","when":"当轮快照不变;更改后已有会话需新建。","consequence":"调大允许更长管理回复、占用更多内存;调小可能无法核实状态。超限明确停止,不能把不完整回复当成功;0不表示无限。","defaultValue":1048576},{"key":"employeeShellKillGraceMs","name":"本机命令外层连接清理宽限(毫秒)","kind":"number","min":0,"max":2147483647,"range":"0~2147483647 的整数","scope":"仅 Claude 本机 Bash 取消后,本插件自己的外层 SSH 进程清理;不控制员工命令或 Codex 执行器。","when":"当轮快照不变;更改后已有会话需新建。","consequence":"大于0时先请求外层连接退出,再等待所设毫秒后强制清理;0表示不留宽限、立即强制清理外层连接,不是关闭取消。没有员工端退出确认时结果仍未知,不能据此自动重放或声称命令已停止。","defaultValue":1000}];
|
|
4
|
+
/* Public settings.plugins.tab + shared ConfigForms. No private client services. */
|
|
6
5
|
function mountRuntimeSettings(ctx,React,fields){
|
|
7
6
|
const h=React.createElement;
|
|
8
7
|
const invariants=[
|
|
9
|
-
['
|
|
10
|
-
['
|
|
11
|
-
['
|
|
12
|
-
['
|
|
13
|
-
|
|
14
|
-
['未知结果与旧审批保护','始终开启,不可关闭','断线或取消不等于员工命令已结束;不自动重放未知操作,不代答旧审批,清理未确认时不擅自重开。'],
|
|
15
|
-
['本机执行位置','始终开启,不可关闭','已提供的本机工具通道不悄悄回退主机;文件夹不是权限隔离根,也不新增目录白名单。Claude子代理及内部直接调用尚未完整覆盖,可能仍在主机,每轮另有范围提示。']
|
|
8
|
+
['执行位置','始终开启,不可关闭','每轮开始核对文件夹来源和位置。本机执行组件报告就绪后才开通道;本机通道失败就结束本轮,绝不改在主机执行。'],
|
|
9
|
+
['凭据保护','始终开启,不可关闭','订阅凭据只在主机,不放进命令参数、日志或技术详情。'],
|
|
10
|
+
['权限跟随当前档位','始终开启,不可关闭','完全权限不问(Claude bypassPermissions / Codex agent-full-access);工作区内修改先问(Claude default / Codex workspace-write);仅可查看只读(Claude plan / Codex read-only)。'],
|
|
11
|
+
['会话来源','始终开启,不可关闭','同一会话使用一个工作区来源,换来源请新建会话。断线恢复后可在原会话继续;旧资源清理和技术详情记录不会阻止下一轮。']
|
|
12
|
+
|
|
16
13
|
];
|
|
17
14
|
const unavailable={status:'unavailable',mode:'memory',writable:false};
|
|
18
15
|
const unavailableForm={subscribe:()=>()=>{},getSnapshot:()=>unavailable};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
setSnapshot(value);
|
|
33
|
-
}catch{if(alive.current&&!controller.signal.aborted&&mine===serial.current){setFailure('目录诊断暂时无法读取。请检查本机服务连接,再点“刷新本机诊断”;这不会重新查询主机或发送原话。');}}
|
|
34
|
-
finally{if(alive.current&&mine===serial.current)setBusy(false);}
|
|
35
|
-
};
|
|
36
|
-
React.useEffect(()=>{alive.current=true;void load();const off=ctx.connection.state.subscribe(()=>{void load();});return()=>{alive.current=false;serial.current++;active.current?.abort();off();};},[]);
|
|
37
|
-
const ns=snapshot?.settingsNamespace,form=typeof ns==='string'&&ns?ctx.configForms.get(ns):unavailableForm;
|
|
38
|
-
if(!formIdentities.has(form))formIdentities.set(form,++nextFormIdentity);
|
|
39
|
-
return h('div',null,
|
|
40
|
-
h('section',{'aria-label':'模型目录诊断'},h('h2',null,'模型目录诊断'),
|
|
41
|
-
h('p',null,'这里显示本客户端最近的目录查询记录。读取本页只查看本机记录,不会重试主机查询、登录或发送待发送的原话。每次查询都保留本机诊断历史,不自动过期;记录容量沿用下方会话消息容量,超限会明确停止并标明记录不完整。'),
|
|
42
|
-
h('button',{type:'button',disabled:busy,onClick:()=>{void load();}},busy?'正在读取本机诊断…':'刷新本机诊断'),
|
|
43
|
-
failure&&h('p',{role:'alert'},failure),snapshot?.storageError&&h('p',{role:'alert'},snapshot.storageError),
|
|
44
|
-
...(snapshot?.providers??[]).map(provider=>{
|
|
45
|
-
const record=snapshot.records.find(item=>item.provider===(provider.provider==='claude-code-workbench'?'claude':'codex'));
|
|
46
|
-
return h('article',{key:provider.provider},h('h3',null,provider.name),
|
|
47
|
-
h('p',null,provider.messages.length?'本提供方有目录或模型设置诊断,请展开技术详情。':record?'当前没有新增的模型设置诊断;以下为最近一次查询记录,不代表此刻连接可用。':'尚无本客户端的目录查询记录;旧版本未保存的错误无法还原。'),
|
|
48
|
-
record&&h('p',null,'最近查询:'+record.startedAt+';'+(record.stage==='started'?'尚未确认结束':record.failureCode||record.hostErrorCode?'未完成':'已返回主机响应')+'。'),
|
|
49
|
-
(record||provider.messages.length>0)&&h('details',null,h('summary',null,'技术详情('+provider.name+')'),
|
|
50
|
-
...provider.messages.map((message,index)=>h('pre',{key:index,style:{whiteSpace:'pre-wrap',overflowWrap:'anywhere'}},message)),
|
|
51
|
-
record&&h('pre',{style:{whiteSpace:'pre-wrap',overflowWrap:'anywhere'}},JSON.stringify(record,null,2))));
|
|
52
|
-
})),
|
|
53
|
-
showParameters&&!ns&&h('p',{role:'status'},'尚未确认本插件的官方配置入口,参数暂不可编辑;上方目录诊断仍可读取。'),
|
|
54
|
-
showParameters&&h(Panel,{key:(ns??'unavailable')+':'+formIdentities.get(form),form}));
|
|
16
|
+
function RuntimePanel(){
|
|
17
|
+
const [ns,setNamespace]=React.useState(null);
|
|
18
|
+
React.useEffect(()=>{let active=true;
|
|
19
|
+
void ctx.remote.llm.listConfigurableProviders().then(result=>{
|
|
20
|
+
if(!active)return;
|
|
21
|
+
const rows=result?.ok===true&&Array.isArray(result.value)?result.value.filter(row=>['claude-code-workbench','codex-remote'].includes(row.provider)):[];
|
|
22
|
+
const names=[...new Set(rows.map(row=>row.settingsNs).filter(value=>typeof value==='string'&&value))];
|
|
23
|
+
setNamespace(names.length===1?names[0]:null);
|
|
24
|
+
},()=>{if(active)setNamespace(null);});
|
|
25
|
+
return()=>{active=false;};
|
|
26
|
+
},[]);
|
|
27
|
+
const form=ns?ctx.configForms.get(ns):unavailableForm;
|
|
28
|
+
return h('div',null,!ns&&h('p',{role:'status'},'尚未读取到本插件的官方配置入口,参数当前值暂不可用。'),h(Panel,{key:ns??'unavailable',form}));
|
|
55
29
|
}
|
|
56
30
|
function Panel({form}){
|
|
57
31
|
const snapshot=React.useSyncExternalStore(fn=>form.subscribe(fn),()=>form.getSnapshot(),()=>form.getSnapshot());
|
|
58
|
-
const
|
|
59
|
-
const revision=React.useRef(undefined),saving=React.useRef(false),alive=React.useRef(true),prefix=React.useId();
|
|
60
|
-
React.useEffect(()=>{alive.current=true;return()=>{alive.current=false;};},[]);
|
|
61
|
-
const ready=snapshot.status==='ready',writable=ready&&snapshot.writable===true&&snapshot.mode==='host'&&snapshot.revision!==undefined;
|
|
62
|
-
const changed=Object.keys(draft).length>0,stale=changed&&revision.current!==snapshot.revision;
|
|
63
|
-
const edit=(key,value)=>{if(saving.current||!writable)return;if(!changed)revision.current=snapshot.revision;setDraft(previous=>({...previous,[key]:value}));setNotice('修改尚未保存。');};
|
|
64
|
-
const discard=()=>{if(saving.current)return;setDraft({});revision.current=undefined;setNotice('已放弃未保存修改,显示当前配置。');};
|
|
65
|
-
const save=async()=>{
|
|
66
|
-
if(saving.current||!writable||!changed||stale)return;
|
|
67
|
-
const ops=[];
|
|
68
|
-
for(const field of fields){const entry=draft[field.key];if(!entry)continue;
|
|
69
|
-
if(field.readOnly){setNotice('固定约束不能修改。');return;}
|
|
70
|
-
if(entry.op==='unset'){ops.push({op:'unset',path:[field.key]});continue;}
|
|
71
|
-
const raw=entry.value,value=field.kind==='number'?Number(raw):raw;
|
|
72
|
-
if(field.kind==='number'&&(String(raw).trim()===''||!Number.isSafeInteger(value)||value<field.min||value>field.max)||field.kind==='select'&&!field.options.some(option=>option.value===value)){setNotice(field.name+'的值不在所列范围内,未保存。');return;}
|
|
73
|
-
ops.push({op:'set',path:[field.key],value});
|
|
74
|
-
}
|
|
75
|
-
saving.current=true;setBusy(true);setNotice('正在保存,请稍候。');
|
|
76
|
-
try{
|
|
77
|
-
const accepted=await form.mutate(ops,revision.current);
|
|
78
|
-
if(!alive.current)return;
|
|
79
|
-
if(accepted===true){setDraft({});revision.current=undefined;setNotice('已保存。运行中的这一轮不变;请按各项生效说明使用新会话。');}
|
|
80
|
-
else setNotice('未保存:配置已变化或官方设置服务拒绝了修改。草稿已保留;核对当前值后可放弃草稿重新修改,不会自动重试。');
|
|
81
|
-
}catch{if(alive.current)setNotice('保存失败,草稿已保留。请检查连接及官方配置状态;不会自动重试。');}
|
|
82
|
-
finally{saving.current=false;if(alive.current)setBusy(false);}
|
|
83
|
-
};
|
|
84
|
-
const stateMessage=snapshot.mode!=='host'?'当前连接不提供持久配置写入,本页不能保存。':!ready?'尚未取得官方配置,当前不能修改。':!snapshot.writable?'当前配置由更高层覆盖或不可写,本页不能修改。':stale?'其他地方已更新配置;本页草稿保留。请放弃草稿后按最新值重新修改。':'';
|
|
32
|
+
const ready=snapshot.status==='ready';
|
|
85
33
|
return h('section',{'aria-label':'远程助手参数',style:{maxWidth:850}},
|
|
86
34
|
h('h2',null,'远程助手参数'),
|
|
87
|
-
h('p',null,'
|
|
88
|
-
|
|
89
|
-
h('
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const control=field.readOnly?h('p',{'aria-label':field.name+'当前值'},String(value??'尚未读取')+'(固定,不可切换)'):field.kind==='select'?h('select',{id,value:value??'',disabled:!writable||busy,'aria-describedby':description,onChange:event=>edit(field.key,{op:'set',value:event.target.value})},value===undefined&&h('option',{value:'',disabled:true},'尚未读取'),...field.options.map(option=>h('option',{key:option.value,value:option.value},option.name))):h('input',{id,type:'number',step:1,min:field.min,max:field.max,value:value??'',disabled:!writable||busy,'aria-describedby':description,onChange:event=>edit(field.key,{op:'set',value:event.target.value})});
|
|
96
|
-
return h('fieldset',{key:field.key,style:{marginBottom:18,padding:14,border:'1px solid currentColor',borderRadius:6}},
|
|
97
|
-
h('legend',null,field.name),h('label',{htmlFor:field.readOnly?undefined:id},field.name+'('+field.key+')'),
|
|
98
|
-
h('div',null,control),h('div',{id:description},
|
|
99
|
-
h('p',null,'默认值:'+field.defaultValue+';范围:'+field.range),
|
|
100
|
-
h('p',null,'生效范围:'+field.scope),h('p',null,'生效时间:'+field.when),h('p',null,'调整或关闭的后果:'+field.consequence)),
|
|
101
|
-
entry?.op==='unset'&&h('p',null,hasBase?'将移除本项覆盖,恢复所示继承值;尚未保存。':'将移除本项覆盖;继承层未设此项,保存后由官方配置确定(默认值:'+field.defaultValue+')。输入框仍显示保存前值,尚未保存。'),
|
|
102
|
-
!field.readOnly&&h('button',{type:'button',disabled:!writable||busy,onClick:()=>edit(field.key,{op:'unset'})},'恢复继承值:'+field.name));
|
|
103
|
-
}),
|
|
104
|
-
h('div',null,h('button',{type:'submit',disabled:!writable||!changed||busy||stale},busy?'正在保存…':'保存修改'),h('button',{type:'button',disabled:!changed||busy,onClick:discard},'放弃未保存修改'))),
|
|
105
|
-
h('p',{role:'status','aria-live':'polite'},notice),h('h3',null,'必须保持的正确性约束'),
|
|
35
|
+
h('p',null,'这里只显示本客户端当前配置下的 Claude(远程主机)和 Codex(远程主机)参数。请通过 DSH 官方 Config 配置文件入口修改。关闭整个插件请使用官方插件管理。'),
|
|
36
|
+
!ready&&h('p',{role:'status'},'尚未取得官方配置,暂时无法显示当前值。'),
|
|
37
|
+
...fields.map(field=>h('fieldset',{key:field.key,style:{marginBottom:18,padding:14,border:'1px solid currentColor',borderRadius:6}},
|
|
38
|
+
h('legend',null,field.name),h('p',null,field.key),
|
|
39
|
+
h('p',{'aria-label':field.name+'当前值'},String(ready?snapshot.value?.[field.key]??'尚未读取':'尚未读取')),
|
|
40
|
+
h('p',null,'默认值:'+field.defaultValue+';范围:'+field.range),
|
|
41
|
+
h('p',null,'生效范围:'+field.scope),h('p',null,'生效时间:'+field.when),h('p',null,'调整或关闭的后果:'+field.consequence))),
|
|
42
|
+
h('h3',null,'必须保持的正确性约束'),
|
|
106
43
|
h('p',null,'以下保护默认开启且不能关闭。关闭会使输入、审批、执行位置或记录失去可信归属,并非可以放宽的偏好。'),
|
|
107
44
|
h('dl',null,...invariants.flatMap(([name,state,why])=>[h('dt',{key:name},name+':'+state),h('dd',{key:name+'-why'},why)])));
|
|
108
45
|
}
|
|
109
|
-
ctx.effect(()=>ctx.slots.inject('settings.models.footer',()=>ctx.slots.register({name:'settings.models.footer',id:'ai-workbench-remote-model-diagnostics',order:90,inject:()=>({showParameters:false})},RuntimePanel)));
|
|
110
46
|
ctx.effect(()=>ctx.slots.inject('settings.plugins.tab',()=>ctx.slots.register({name:'settings.plugins.tab',id:'ai-workbench-remote-parameters',order:90,label:()=> '远程助手参数',inject:()=>({})},RuntimePanel)));
|
|
111
47
|
}
|
|
112
48
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let disposed=false,connected=false,previous,generation=0,request=new AbortController(),queue=Promise.resolve();
|
|
118
|
-
const sync=()=>{
|
|
119
|
-
if(disposed||!connected)return;
|
|
120
|
-
const active=ctx.locale.getSnapshot().active;
|
|
121
|
-
const language=active.split('-')[0].toLowerCase()==='zh'?'zh':'en';
|
|
122
|
-
if(language===previous)return;
|
|
123
|
-
previous=language;
|
|
124
|
-
const mine=generation,signal=request.signal;
|
|
125
|
-
queue=queue.then(async()=>{
|
|
126
|
-
if(disposed||signal.aborted||mine!==generation)return;
|
|
127
|
-
const result=await ctx.connection.rpc.call('/api','awCodexPresentation/setLocale',{args:{language}},signal);
|
|
128
|
-
signal.throwIfAborted();
|
|
129
|
-
if(result?.ok!==true||result.value?.language!==language||result.value?.name!==messages[language].providerName){
|
|
130
|
-
throw Object.assign(new Error('Provider-name locale sync was not confirmed'),{code:result?.ok===false&&typeof result.error?.code==='string'?result.error.code:'LOCALE_SYNC_INVALID'});
|
|
131
|
-
}
|
|
132
|
-
}).catch(error=>{
|
|
133
|
-
if(!disposed&&!signal.aborted&&mine===generation)ctx.logger.warn('Codex provider-name locale update failed',typeof error?.code==='string'?error.code:'LOCALE_SYNC_FAILED');
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
const link=()=>{
|
|
137
|
-
const next=ctx.connection.state.getSnapshot()==='connected';
|
|
138
|
-
if(next===connected)return;
|
|
139
|
-
connected=next;generation++;previous=undefined;request.abort();request=new AbortController();
|
|
140
|
-
if(connected)sync();
|
|
141
|
-
};
|
|
142
|
-
const offLocale=ctx.locale.subscribe(sync),offConnection=ctx.connection.state.subscribe(link);link();
|
|
143
|
-
return()=>{disposed=true;offLocale();offConnection();request.abort();};
|
|
144
|
-
},'Codex provider names follow the official locale');
|
|
145
|
-
}};
|
|
49
|
+
return{inject:['locale'],apply(ctx){
|
|
50
|
+
ctx.inject(['slots','configForms','remote'],settings=>mountRuntimeSettings(settings,require('react'),settingsFields));
|
|
51
|
+
ctx.effect(()=>ctx.locale.register('ai-workbench-codex',messages),'Remote assistant translations');
|
|
52
|
+
}};
|
|
146
53
|
}});
|
|
@@ -17,35 +17,21 @@ export interface CloseCause {
|
|
|
17
17
|
changes?: readonly CloseChange[];
|
|
18
18
|
}
|
|
19
19
|
export type CloseObserver = (cause: CloseCause) => void;
|
|
20
|
-
export { closeCode, closeErrorCodes,
|
|
21
|
-
/**
|
|
20
|
+
export { closeCode, closeErrorCodes, withFirstFailureEvidence, failureEvidenceSummary, primaryFailureCode } from './failure-evidence.ts';
|
|
21
|
+
/** One first-close observation per runtime, including its child resources. */
|
|
22
22
|
export declare class CloseDiagnostics {
|
|
23
23
|
private sink;
|
|
24
|
-
readonly
|
|
25
|
-
private state;
|
|
26
|
-
readonly id: `${string}-${string}-${string}-${string}-${string}`;
|
|
27
|
-
private sequence;
|
|
28
|
-
private pending;
|
|
29
|
-
private firstValue?;
|
|
24
|
+
private readonly state;
|
|
30
25
|
private readonly started;
|
|
31
26
|
stage: string;
|
|
32
|
-
constructor(sink: (detail: DetailObservation) => void,
|
|
33
|
-
|
|
34
|
-
error: unknown;
|
|
35
|
-
};
|
|
27
|
+
constructor(sink: (detail: DetailObservation) => void, state?: {
|
|
28
|
+
first?: Readonly<Record<string, unknown>>;
|
|
36
29
|
});
|
|
37
|
-
run<T>(
|
|
38
|
-
get recordingFailureCode(): string | undefined;
|
|
30
|
+
run<T>(_operation: string, work: () => Promise<T>): Promise<T>;
|
|
39
31
|
get first(): Readonly<Record<string, unknown>> | undefined;
|
|
40
32
|
bind(sink: (detail: DetailObservation) => void): void;
|
|
41
|
-
/** A delayed resource callback retains its original accepted detail channel. */
|
|
42
33
|
capture(): CloseObserver;
|
|
43
34
|
child(sink?: (detail: DetailObservation) => void): CloseDiagnostics;
|
|
44
35
|
record(cause: CloseCause): void;
|
|
45
36
|
private recordUsing;
|
|
46
|
-
assertRecorded(): void;
|
|
47
37
|
}
|
|
48
|
-
/** Run after owned cleanup; expose both the primary error code and persistence failure. */
|
|
49
|
-
export declare function flushCloseDiagnostics(diagnostics: CloseDiagnostics, flush: (() => Promise<void>) | undefined, primary?: {
|
|
50
|
-
error: unknown;
|
|
51
|
-
}): Promise<void>;
|
|
@@ -1,74 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { closeCode, closeErrorCodes, withFailureEvidence } from "./failure-evidence.js";
|
|
4
|
-
export { closeCode, closeErrorCodes, closeFailure, withFailureEvidence, withFirstFailureEvidence, failureEvidenceSummary, primaryFailureCode } from "./failure-evidence.js";
|
|
5
|
-
/** Assign order before abort/kill; persist through the existing asynchronous D19 outbox. */
|
|
1
|
+
export { closeCode, closeErrorCodes, withFirstFailureEvidence, failureEvidenceSummary, primaryFailureCode } from "./failure-evidence.js";
|
|
2
|
+
/** One first-close observation per runtime, including its child resources. */
|
|
6
3
|
export class CloseDiagnostics {
|
|
7
4
|
sink;
|
|
8
|
-
parentId;
|
|
9
5
|
state;
|
|
10
|
-
id = randomUUID();
|
|
11
|
-
sequence = 0;
|
|
12
|
-
pending = new Map();
|
|
13
|
-
firstValue;
|
|
14
6
|
started = performance.now();
|
|
15
7
|
stage = 'runtime-admission';
|
|
16
|
-
constructor(sink,
|
|
8
|
+
constructor(sink, state = {}) {
|
|
17
9
|
this.sink = sink;
|
|
18
|
-
this.parentId = parentId;
|
|
19
10
|
this.state = state;
|
|
20
11
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
const count = this.pending.get(operation) - 1;
|
|
26
|
-
if (count)
|
|
27
|
-
this.pending.set(operation, count);
|
|
28
|
-
else
|
|
29
|
-
this.pending.delete(operation);
|
|
30
|
-
} }
|
|
31
|
-
get recordingFailureCode() { return this.state.failure ? closeCode(this.state.failure.error, 'CLOSE_RECORD_FAILED') : undefined; }
|
|
32
|
-
get first() { return this.firstValue; }
|
|
12
|
+
run(_operation, work) { return work(); }
|
|
13
|
+
get first() { return this.state.first; }
|
|
33
14
|
bind(sink) { this.sink = sink; }
|
|
34
|
-
/** A delayed resource callback retains its original accepted detail channel. */
|
|
35
15
|
capture() { const sink = this.sink; return cause => this.recordUsing(sink, cause); }
|
|
36
|
-
child(sink = this.sink) {
|
|
16
|
+
child(sink = this.sink) { return new CloseDiagnostics(sink, this.state); }
|
|
37
17
|
record(cause) { this.recordUsing(this.sink, cause); }
|
|
38
18
|
recordUsing(sink, cause) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const payload = Object.freeze({
|
|
42
|
-
this.
|
|
19
|
+
if (this.state.first)
|
|
20
|
+
return;
|
|
21
|
+
const payload = Object.freeze({ layer: cause.layer, initiator: cause.initiator, reason: cause.reason, ...cause.remoteReason ? { remoteReason: cause.remoteReason } : {}, ...cause.exitCode !== undefined ? { exitCode: cause.exitCode } : {}, ...cause.signal !== undefined ? { signal: cause.signal } : {}, stage: this.stage, clock: 'client-monotonic', elapsedMs: performance.now() - this.started });
|
|
22
|
+
this.state.first = payload;
|
|
43
23
|
try {
|
|
44
|
-
sink({ kind: 'session', severity: 'warning', code:
|
|
24
|
+
sink({ kind: 'session', severity: 'warning', code: 'REMOTE_CLOSE_REASON', summary: '首次观察到的关闭原因', payload });
|
|
45
25
|
}
|
|
46
|
-
catch
|
|
47
|
-
|
|
26
|
+
catch {
|
|
27
|
+
console.warn('[remote-agent] 首次关闭原因未保存');
|
|
48
28
|
}
|
|
49
29
|
}
|
|
50
|
-
assertRecorded() { if (this.state.failure)
|
|
51
|
-
throw new NeedError('CONVERSATION_DETAILS_FAILED', '关闭原因保存未完成;首次观察原因 ' + String(this.firstValue?.reason ?? 'CLOSE_REASON_UNKNOWN') + '。', '关闭诊断保存代码 ' + this.recordingFailureCode + ';不将保存失败当作原始关闭原因'); }
|
|
52
|
-
}
|
|
53
|
-
/** Run after owned cleanup; expose both the primary error code and persistence failure. */
|
|
54
|
-
export async function flushCloseDiagnostics(diagnostics, flush, primary) {
|
|
55
|
-
const reporting = [];
|
|
56
|
-
try {
|
|
57
|
-
await flush?.();
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
reporting.push(...closeErrorCodes(error, 'CLOSE_FLUSH_FAILED'));
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
diagnostics.assertRecorded();
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
reporting.push(diagnostics.recordingFailureCode ?? closeCode(error, 'CLOSE_RECORD_FAILED'));
|
|
67
|
-
}
|
|
68
|
-
if (reporting.length) {
|
|
69
|
-
const primaryCodes = primary ? closeErrorCodes(primary.error, 'REMOTE_CLEANUP_UNCONFIRMED') : [];
|
|
70
|
-
throw withFailureEvidence(primary?.error ?? new NeedError(primaryCodes[0] ?? 'REMOTE_CLEANUP_UNCONFIRMED', '关闭收尾未确认', '核对实际结果'), reporting.map(code => new NeedError(code, '诊断保存未确认', '只读核对')), 'diagnostic', diagnostics.first?.reason);
|
|
71
|
-
}
|
|
72
|
-
if (primary)
|
|
73
|
-
throw primary.error;
|
|
74
30
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { ConnectNeed } from '@ai-workbench/connect/api';
|
|
2
2
|
import type { ConnectTarget } from './provider.ts';
|
|
3
|
-
import type { ContractInfo, Provider, WorkspaceUse
|
|
3
|
+
import type { ContractInfo, Provider, WorkspaceUse } from '@ai-workbench/connect/api';
|
|
4
4
|
/** 正式L0.7公开类型;消费子集不表示已安装或已通过真实产品门。 */
|
|
5
|
-
export type {
|
|
6
|
-
import type {
|
|
5
|
+
export type { HostWorkspaceResolveContext, HostSessionProjectTarget } from '@ai-workbench/connect/api';
|
|
6
|
+
import type { HostWorkspaceResolveContext, HostSessionProjectTarget } from '@ai-workbench/connect/api';
|
|
7
7
|
export type LoginProvider = Provider;
|
|
8
|
-
export type WorkspaceNoticeReceipt = Extract<LWorkspaceNoticeReceipt, {
|
|
9
|
-
surface: 'provider';
|
|
10
|
-
}>;
|
|
11
8
|
export type ConnectResult<T> = {
|
|
12
9
|
ok: true;
|
|
13
10
|
value: T;
|
|
@@ -19,10 +16,9 @@ export type ConnectResult<T> = {
|
|
|
19
16
|
requires: string;
|
|
20
17
|
} & Partial<Pick<ConnectNeed, 'officialReason' | 'layer' | 'actor' | 'action' | 'lastSeen'>>;
|
|
21
18
|
};
|
|
22
|
-
export type SessionProjectResolution =
|
|
19
|
+
export type SessionProjectResolution = HostSessionProjectTarget;
|
|
23
20
|
export interface NativeWorkspaceApi {
|
|
24
21
|
getContractInfo(): ContractInfo;
|
|
25
22
|
ensureReady(provider: LoginProvider): Promise<ConnectResult<ConnectTarget>>;
|
|
26
23
|
resolveSessionProject(sessionId: string, use: WorkspaceUse, location?: HostWorkspaceResolveContext): Promise<ConnectResult<SessionProjectResolution>>;
|
|
27
|
-
acknowledgeWorkspaceNotice(receipt: WorkspaceNoticeReceipt): Promise<ConnectResult<void>>;
|
|
28
24
|
}
|