@ai-workbench/remote-agent 0.8.27 → 0.8.28

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 CHANGED
@@ -1,4 +1,4 @@
1
- # AI Workbench 远程助手 · @ai-workbench/remote-agent@0.8.27 · 插件 → 添加插件
1
+ # AI Workbench 远程助手 · @ai-workbench/remote-agent@0.8.28 · 插件 → 添加插件
2
2
 
3
3
  通过官方 DSH 插件入口使用 Claude(远程主机)与 Codex(远程主机)。登录和工作区来源由“远程主机连接”组件提供;员工在原生工作区和模型菜单中选择文件夹与型号。
4
4
 
@@ -14,7 +14,9 @@ Claude 本机 Bash 会单独消费员工桥代理的控制诊断,不把它交
14
14
 
15
15
  每轮启动一个新助手进程,按原生会话编号恢复上下文;不接回旧进程或重放上一轮输入。主机会话始终使用创建时保存的主机和目录;工作区之后的选择只影响新会话。
16
16
 
17
- 断线会结束本轮;操作可能已部分执行,请先检查文件。恢复连接后可在原会话继续。旧进程清理和技术详情记录尽力而为,不阻止下一轮。原生本机会话恢复的兼容性及实际设备结果见本轮交付证据,未验证项不作为已通过。
17
+ 同一 DSH 会话切换 Claude/Codex 时,切回会接本品牌最近的远端会话,并补充它尚未见过的可见对话;首次使用某品牌则带入全部可见历史。工具内部状态、推理和技术详情不转交。历史超出既有 `acpMaxMessageBytes` 容量时,只带能容纳的最近完整轮,并在技术详情说明省略数量;当前输入保持完整,超限仍明确失败。没有新增设置、重试或时限。
18
+
19
+ 断线会结束本轮;已派发的操作可能已部分执行,请先检查文件。恢复连接后可在原会话继续。旧进程清理和技术详情记录尽力而为,不阻止发起下一轮;如果上一轮仍占用原生线程,续接会明确失败并说明本轮输入未发送,不新建替代线程。自动定位、清理同一会话/线程的已断开旧进程仍需 S 提供公开能力,当前不会自动杀旧进程或冒称已恢复。原生本机会话恢复的兼容性及实际设备结果见本轮交付证据,未验证项不作为已通过。
18
20
 
19
21
  DSH 启动和打开模型菜单时读取一次主机型号列表。发送时不再查询目录或预核所选型号;所选型号不可用时,请在模型菜单重新选择。
20
22
 
package/dist/acp.js CHANGED
@@ -605,6 +605,8 @@ export function acpNeed(error) {
605
605
  if (error instanceof NeedError)
606
606
  return error;
607
607
  if (error instanceof acp.RequestError) {
608
+ if (record(error.data)?.reason === 'thread_active_writer')
609
+ return new NeedError('REMOTE_SESSION_BUSY', employeeFailure('REMOTE_SESSION_BUSY'), '原远端线程仍被占用;需要按同一DSH会话和远端线程定位已断开的旧broker并确认释放;RPC 错误码 ' + error.code);
608
610
  // Host SDK1.9 uses Internal error + data.details for thrown adapter errors.
609
611
  // Only fixed codes cross this boundary; arbitrary detail strings are never copied.
610
612
  for (const value of [error.message, record(error.data)?.details]) {
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { StreamMapper } from "./mapping.js";
12
12
  import { connectProvider } from "./provider.js";
13
13
  import { brokerFactory } from "./broker.js";
14
14
  import { AGENTS, CODEX_AGENT } from "./agents.js";
15
- import { prepareInput, projectHostWorkspaceInput } from "./input.js";
15
+ import { prepareInput, prepareContinuation } from "./input.js";
16
16
  import { NeedError } from "./remote.js";
17
17
  import { employeeFailure } from "./presentation.js";
18
18
  import { createRemoteDetails } from "./remote-details.js";
@@ -53,16 +53,16 @@ function modelResolutionLabel(configuration) {
53
53
  : `具体型号:${configuration.resolvedModel}`;
54
54
  }
55
55
  function readPointer(options, agent) {
56
- const last = options.messages.findLast(message => message.role === 'assistant');
57
- if (!last || last.role !== 'assistant')
58
- return;
59
- if (last.source.provider !== options.provider)
60
- return;
61
- const envelope = last.source.replayState;
62
- const pointer = envelope?.response;
63
- if (pointer?.version !== 2 || pointer.agent !== agent.id || pointer.dshSessionId !== options.sessionId || ![pointer.identity, pointer.cwd, pointer.sessionId].every(value => typeof value === 'string' && value.length > 0))
64
- return;
65
- return pointer;
56
+ for (let index = options.messages.length - 1; index >= 0; index--) {
57
+ const message = options.messages[index];
58
+ if (message.role !== 'assistant' || message.source.provider !== options.provider)
59
+ continue;
60
+ const envelope = message.source.replayState;
61
+ const pointer = envelope?.response;
62
+ if (pointer?.version !== 2 || pointer.agent !== agent.id || pointer.dshSessionId !== options.sessionId || ![pointer.identity, pointer.cwd, pointer.sessionId].every(value => typeof value === 'string' && value.length > 0) || pointer.contextSha256 !== undefined && typeof pointer.contextSha256 !== 'string')
63
+ continue;
64
+ return { pointer: pointer, index };
65
+ }
66
66
  }
67
67
  // A reporting failure can wrap the original admission error. Preserve its
68
68
  // already-safe login snapshot even when a newer preflight delivery is unknown.
@@ -197,16 +197,17 @@ export class CodexRemoteAdapter extends LlmAdapter {
197
197
  throw failure.reason;
198
198
  }
199
199
  async *stream(options) {
200
+ let promptDispatched = false;
200
201
  try {
201
- yield* this.generate(options);
202
+ yield* this.generate(options, () => { promptDispatched = true; });
202
203
  }
203
204
  catch (error) {
204
205
  if (error instanceof ModelConfigurationError)
205
- throw new LlmError(employeeFailure(error.code, this.agent.loginProvider), error.code, { cause: error });
206
+ throw new LlmError(employeeFailure(error.code, this.agent.loginProvider, promptDispatched), error.code, { cause: error });
206
207
  if (error instanceof NeedError) {
207
208
  const rpc = /RPC 错误码 (-?\d+)/u.exec(error.requires)?.[1];
208
209
  const evidence = failureEvidenceSummary(error);
209
- const message = connectNeedMessage(error) ?? employeeFailure(error.code, this.agent.loginProvider);
210
+ const message = connectNeedMessage(error) ?? employeeFailure(error.code, this.agent.loginProvider, promptDispatched);
210
211
  const login = originalLoginFailure(error);
211
212
  let diagnostic = login?.technical ? JSON.stringify(login.technical) : '';
212
213
  if (login?.technical && this.reportLoginFailure) {
@@ -220,10 +221,10 @@ export class CodexRemoteAdapter extends LlmAdapter {
220
221
  }
221
222
  throw new LlmError(message, error.code, { cause: new Error(`诊断代码:${error.code};${error.requires}` + (evidence ? ';' + evidence : '') + (diagnostic ? ';' + diagnostic : '')) });
222
223
  }
223
- throw new LlmError(employeeFailure('TURN_FAILED', this.agent.loginProvider), 'TURN_FAILED', { cause: error });
224
+ throw new LlmError(employeeFailure('TURN_FAILED', this.agent.loginProvider, promptDispatched), 'TURN_FAILED', { cause: error });
224
225
  }
225
226
  }
226
- async *generate(original) {
227
+ async *generate(original, onPromptDispatched) {
227
228
  if (original.purpose)
228
229
  throw new NeedError('PURPOSE_UNSUPPORTED', '此远程模型不处理后台标题或上下文整理请求,未发送模型请求', '独立的后台请求能力');
229
230
  if (this.lifetime.signal.aborted)
@@ -272,12 +273,10 @@ export class CodexRemoteAdapter extends LlmAdapter {
272
273
  try {
273
274
  if (!Number.isSafeInteger(config.acpMaxMessageBytes) || config.acpMaxMessageBytes < 1)
274
275
  throw new NeedError('SETTINGS_REQUIRED', '远程模型连接设置无效,请联系管理员检查', '有效的acpMaxMessageBytes');
275
- const previous = readPointer(options, this.agent);
276
+ const checkpoint = readPointer(options, this.agent), previous = checkpoint?.pointer;
276
277
  employeeNoticeShown = !!key && this.employeeNoticeSessions.has(key) || false;
277
278
  if (employeeNoticeShown && key)
278
279
  this.employeeNoticeSessions.add(key);
279
- const last = options.messages.findLast(message => message.role === 'assistant');
280
- const missingPointer = !!last && !previous;
281
280
  live = undefined;
282
281
  const configuration = JSON.stringify([config.acpMaxMessageBytes, config.brokerMaxFrameBytes, config.brokerControlMaxBytes, config.employeeShellKillGraceMs]);
283
282
  Object.assign(request, { acpMaxMessageBytes: config.acpMaxMessageBytes, resumeSessionId: previous?.sessionId, sessionId: options.sessionId, mode, hostSource: previous?.hostSource, cwd: previous?.cwd, identity: previous?.identity, bindingId: previous?.bindingId, workspaceId: previous?.workspaceId, agent: this.agent, signal, brokerSettings: Object.freeze({ brokerMaxFrameBytes: config.brokerMaxFrameBytes, brokerControlMaxBytes: config.brokerControlMaxBytes }) });
@@ -307,7 +306,6 @@ export class CodexRemoteAdapter extends LlmAdapter {
307
306
  // New Codex transport scopes are independent of the native context digest.
308
307
  if (contextChanged)
309
308
  request.contextSha256 = undefined;
310
- const claudeContextChanged = !!previous && !codex && previous.contextSha256 !== contextSha256;
311
309
  if (mode === 'session' && Buffer.byteLength(JSON.stringify({ prompt: input.prompt, systemPrompt: input.systemPrompt }), 'utf8') > config.acpMaxMessageBytes)
312
310
  throw new NeedError('ACP_MESSAGE_TOO_LARGE', '本轮输入超过已设置的会话消息容量,未截断或发送', '管理员调整acpMaxMessageBytes');
313
311
  selected = this.modelPresentation.find(options.model) ?? { id: options.model, value: options.model, name: options.model, resolvedModel: null, reasoning: { options: [], defaultValue: null, defaultKnown: false }, source: null };
@@ -327,12 +325,14 @@ export class CodexRemoteAdapter extends LlmAdapter {
327
325
  }
328
326
  if (previous && !runtime.hostSource && (previous.identity !== runtime.identity || previous.cwd !== runtime.cwd || previous.bindingId && previous.bindingId !== runtime.binding?.bindingId || previous.workspaceId && previous.workspaceId !== runtime.binding?.workspaceId))
329
327
  throw new NeedError('SESSION_CONTEXT_MISMATCH', '原会话的员工主机或工作区已改变,请新建会话', '原会话的可信目标和工作区');
330
- input = projectHostWorkspaceInput(input, runtime.hostWorkspace);
328
+ input = await prepareContinuation({ ...options, messages: options.messages.slice((checkpoint?.index ?? -1) + 1) }, input, this.readImage, { maxBytes: config.acpMaxMessageBytes, host: runtime.hostWorkspace });
331
329
  if (runtime.hostWorkspace && mode === 'session' && Buffer.byteLength(JSON.stringify({ prompt: input.prompt, systemPrompt: input.systemPrompt }), 'utf8') > config.acpMaxMessageBytes)
332
330
  throw new NeedError('ACP_MESSAGE_TOO_LARGE', '本轮输入超过已设置的会话消息容量,未截断或发送', '管理员调整acpMaxMessageBytes');
333
331
  const details = runtime.details ?? await createRemoteDetails({ warn: line => console.warn(line) }).open({ sessionId: options.sessionId ?? 'unscoped', provider: this.agent.loginProvider, identity: runtime.identity, connection: runtime.connection });
334
332
  recordInformation();
335
333
  request.recordEarlyFailure = undefined;
334
+ if (input.historyTransfer)
335
+ details.record({ kind: 'session', severity: input.historyTransfer.omittedRounds ? 'warning' : 'info', code: input.historyTransfer.omittedRounds ? 'HISTORY_TRANSFER_TRIMMED' : 'HISTORY_TRANSFER', summary: input.historyTransfer.omittedRounds ? `可见历史超过既有会话消息容量,保留最近${input.historyTransfer.retainedRounds}轮,省略较早${input.historyTransfer.omittedRounds}轮;当前输入完整保留。` : '已补充当前远端会话尚未见过的可见对话,未重放工具或审批。', payload: input.historyTransfer });
336
336
  if (runtime.hostWorkspace)
337
337
  details.record({ kind: 'session', severity: 'info', code: 'HOST_WORKSPACE_CONTEXT', summary: '本轮模型工作区上下文及审批会话目录使用已确认的主机目录。', payload: { source: 'host', cwd: runtime.hostWorkspace.cwd } });
338
338
  const mapper = new StreamMapper(mode === 'session' && !contextChanged ? (previous ? (previous.usageSnapshot ?? null) : undefined) : undefined, observation => details.record(observation));
@@ -438,6 +438,12 @@ export class CodexRemoteAdapter extends LlmAdapter {
438
438
  remoteAttempted = true;
439
439
  try {
440
440
  await session.start(runtime.execution?.hostCwd ?? runtime.cwd, previous?.sessionId, modelSignal, { systemPrompt: input.systemPrompt, ...(runtime.execution ? { employeeRouting: runtime.execution.routing } : {}), ...(codex ? { codexNewSessionOnly: true } : {}), ...(runtime.employeeExecutionMeta ? { employeeExecution: runtime.employeeExecutionMeta } : {}) }, input.prompt);
441
+ }
442
+ catch (error) {
443
+ details.record({ kind: 'session', severity: 'action-required', code: 'SESSION_START_FAILED', summary: '远端会话建立或恢复失败,本轮用户输入未发送。', payload: { operation: previous ? 'resume-or-load' : 'new', reason: closeCode(error, 'ACP_FAILED'), promptDispatched: false } });
444
+ throw error;
445
+ }
446
+ try {
441
447
  const actual = await session.selectModel(selected.value, options.reasoningEffort, modelSignal);
442
448
  // This lease is tied to the trusted host-user and instance/epoch.
443
449
  // Its mapping and this turn's exact setter ACK are different facts.
@@ -532,6 +538,8 @@ export class CodexRemoteAdapter extends LlmAdapter {
532
538
  yield { type: 'finish', reason: { kind: stop === 'max_tokens' || stop === 'max_turn_requests' ? 'max-tokens' : 'stop' }, replayState: { response: pointer } };
533
539
  }
534
540
  catch (error) {
541
+ if (session && session.promptDispatchCount > promptBaseline)
542
+ onPromptDispatched();
535
543
  turnFailure = error;
536
544
  runtime?.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.turn-failure', reason: closeCode(error, signal.aborted ? 'ABORTED' : 'TURN_FAILED'), action: 'failure' });
537
545
  recordInformation();
package/dist/input.d.ts CHANGED
@@ -11,6 +11,9 @@ export interface HistoryTransfer {
11
11
  offloadedImagePlaceholders: number;
12
12
  omittedReasoningBlocks: number;
13
13
  reasoningOmissionReason: 'dsh-internal-reasoning';
14
+ retainedRounds: number;
15
+ omittedRounds: number;
16
+ maxBytes: number;
14
17
  }
15
18
  export interface HostWorkspacePresentation {
16
19
  readonly cwd: string;
@@ -32,7 +35,10 @@ export interface PreparedInput {
32
35
  /** Invocation-only model context; never modifies the native session/history. */
33
36
  export declare function projectHostWorkspaceInput(input: PreparedInput, host?: HostWorkspacePresentation): PreparedInput;
34
37
  /** Role-labelled public history only; never serialize source/replay/tool internals. */
35
- export declare function prepareContinuation(options: GenerateOptions, input: PreparedInput, readImage?: ImageReader): Promise<PreparedInput>;
38
+ export declare function prepareContinuation(options: GenerateOptions, input: PreparedInput, readImage: ImageReader | undefined, capacity: {
39
+ maxBytes: number;
40
+ host?: HostWorkspacePresentation;
41
+ }): Promise<PreparedInput>;
36
42
  /** The attachment service supplies verified bytes; client paths never cross SSH. */
37
43
  export declare function prepareInput(options: GenerateOptions, readImage?: ImageReader): Promise<PreparedInput>;
38
44
  export {};
package/dist/input.js CHANGED
@@ -50,45 +50,64 @@ async function publicBlock(block, options, readImage, history = false) {
50
50
  throw new NeedError(history ? 'HISTORY_CONTENT_UNSUPPORTED' : 'INPUT_TYPE_REQUIRED', `我需要此${history ? '历史' : '输入'}类型的远程转接能力:${block.type};本轮未新建或发送`, '对应类型的公开ACP输入映射;内部推理及工具记录不可降为用户要求');
51
51
  }
52
52
  /** Role-labelled public history only; never serialize source/replay/tool internals. */
53
- export async function prepareContinuation(options, input, readImage) {
54
- const messages = options.messages;
55
- for (const message of messages)
56
- if (!['user', 'assistant', 'system', 'developer'].includes(message.role))
57
- throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
58
- const after = messages.findLastIndex(message => message.role === 'assistant');
59
- const prompt = [];
60
- const runtimeSnapshots = new Map(input.runtimeSnapshots);
61
- const historyTransfer = { scope: 'visible-user-assistant-text-and-images', textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0, reasoningOmissionReason: 'dsh-internal-reasoning' };
62
- for (const message of messages.slice(0, after + 1)) {
63
- if (message.role === 'system' || message.role === 'developer')
53
+ export async function prepareContinuation(options, input, readImage, capacity) {
54
+ const after = options.messages.findLastIndex(message => message.role === 'assistant');
55
+ const groups = [];
56
+ for (const message of options.messages.slice(0, after + 1)) {
57
+ // Runtime snapshots, approval answers and tool state are not employee-visible
58
+ // conversation. Current runtime/system context stays on its original path.
59
+ if (message.role !== 'assistant' && (message.role !== 'user' || message.source?.kind !== 'user'))
64
60
  continue;
65
- if (message.role !== 'user' && message.role !== 'assistant')
66
- throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
67
- prompt.push({ type: 'text', text: `此前可见对话(历史角色:${message.role};仅为历史记录):\n` });
68
- const sections = runtimeSections(message);
69
- for (const block of message.content) {
70
- // DSH explicitly distinguishes internal thinking from visible user text.
71
- if (block.type === 'reasoning') {
72
- historyTransfer.omittedReasoningBlocks++;
73
- continue;
74
- }
75
- const prepared = await publicBlock(block, options, readImage, true);
76
- prompt.push(prepared);
77
- if (sections)
78
- runtimeSnapshots.set(prepared, sections);
79
- if (block.type === 'text')
80
- historyTransfer.textBlocks++;
81
- else if (block.type === 'image') {
82
- if (block.offloaded)
83
- historyTransfer.offloadedImagePlaceholders++;
61
+ if (!message.content.some(block => block.type === 'text' || block.type === 'image'))
62
+ continue;
63
+ if (message.role === 'user' || !groups.length)
64
+ groups.push([]);
65
+ groups.at(-1).push(message);
66
+ }
67
+ const base = projectHostWorkspaceInput(input, capacity.host);
68
+ if (!groups.length)
69
+ return base;
70
+ const historyTransfer = { scope: 'visible-user-assistant-text-and-images', textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0, reasoningOmissionReason: 'dsh-internal-reasoning', retainedRounds: 0, omittedRounds: groups.length, maxBytes: capacity.maxBytes };
71
+ const separator = { type: 'text', text: '此前可见对话结束。以下为当前用户输入:\n' };
72
+ const retained = [];
73
+ // prepareInput guarantees a nonempty current prompt. Each inserted block adds
74
+ // its JSON bytes and one comma; count each round once, not the whole prefix.
75
+ let bytes = Buffer.byteLength(JSON.stringify({ prompt: base.prompt, systemPrompt: base.systemPrompt }), 'utf8') + Buffer.byteLength(JSON.stringify(separator), 'utf8') + 1;
76
+ // Whole consecutive rounds, newest first. The existing byte capacity is the
77
+ // only bound; N is computed from actual encoded text/image sizes, not a cap.
78
+ for (const group of groups.toReversed()) {
79
+ const blocks = [], counts = { textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0 };
80
+ for (const message of group) {
81
+ options.signal?.throwIfAborted();
82
+ blocks.push({ type: 'text', text: `此前可见对话(历史角色:${message.role};仅为历史记录):\n` });
83
+ for (const block of message.content) {
84
+ if (block.type === 'reasoning') {
85
+ counts.omittedReasoningBlocks++;
86
+ continue;
87
+ }
88
+ if (block.type !== 'text' && block.type !== 'image')
89
+ continue;
90
+ blocks.push(await publicBlock(block, options, readImage, true));
91
+ if (block.type === 'text')
92
+ counts.textBlocks++;
93
+ else if (block.offloaded)
94
+ counts.offloadedImagePlaceholders++;
84
95
  else
85
- historyTransfer.imageBlocks++;
96
+ counts.imageBlocks++;
86
97
  }
98
+ blocks.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
87
99
  }
88
- prompt.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
100
+ const additional = Buffer.byteLength(JSON.stringify(blocks), 'utf8') - 1; // Strip [] and add the comma before the retained suffix.
101
+ if (bytes + additional > capacity.maxBytes)
102
+ break;
103
+ bytes += additional;
104
+ retained.push(blocks);
105
+ historyTransfer.retainedRounds++;
106
+ historyTransfer.omittedRounds--;
107
+ for (const key of ['textBlocks', 'imageBlocks', 'offloadedImagePlaceholders', 'omittedReasoningBlocks'])
108
+ historyTransfer[key] += counts[key];
89
109
  }
90
- prompt.push({ type: 'text', text: '此前可见对话结束。以下为当前用户输入:\n' }, ...input.prompt);
91
- return { ...input, prompt, historyTransfer, runtimeSnapshots };
110
+ return { ...base, prompt: retained.length ? [...retained.toReversed().flat(), separator, ...base.prompt] : base.prompt, historyTransfer };
92
111
  }
93
112
  /** The attachment service supplies verified bytes; client paths never cross SSH. */
94
113
  export async function prepareInput(options, readImage) {
@@ -2,4 +2,4 @@
2
2
  export declare const REMOTE_CODEX_NAME = "Codex\uFF08\u8FDC\u7A0B\u4E3B\u673A\uFF09";
3
3
  export declare const CONNECTION_REQUIRED = "\u8FDC\u7A0B\u4E3B\u673A\u8FDE\u63A5\u7EC4\u4EF6\u6CA1\u6709\u88C5\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
4
4
  /** Employee failures use business language; diagnostic codes remain separately available. */
5
- export declare function employeeFailure(code: string, brand?: 'codex' | 'claude'): string;
5
+ export declare function employeeFailure(code: string, brand?: 'codex' | 'claude', promptDispatched?: boolean): string;
@@ -2,7 +2,7 @@
2
2
  export const REMOTE_CODEX_NAME = 'Codex(远程主机)';
3
3
  export const CONNECTION_REQUIRED = '远程主机连接组件没有装好,请联系管理员。';
4
4
  /** Employee failures use business language; diagnostic codes remain separately available. */
5
- export function employeeFailure(code, brand = 'codex') {
5
+ export function employeeFailure(code, brand = 'codex', promptDispatched = true) {
6
6
  const messages = {
7
7
  PERMISSION_POLICY_REQUIRED: '我需要读取这条对话的权限设置,目前没有读到;本轮未执行,你的原话仍保留。',
8
8
  PERMISSION_MODE_UNSUPPORTED: '这个权限档位还没有对应的远程执行能力;我需要主机连接组件支持该档位,本轮未执行,你的原话仍保留。',
@@ -23,7 +23,7 @@ export function employeeFailure(code, brand = 'codex') {
23
23
  EMPLOYEE_CONTEXT_CHANGED: '本机文件夹或连接已变化,本轮已停止;恢复后可在原会话继续。',
24
24
  EMPLOYEE_WORKSPACE_INVALID: '无法核对这条对话所属的本机文件夹,本轮未执行;请在工作区中重新打开对话。',
25
25
  DEPENDENCY_REQUIRED: CONNECTION_REQUIRED,
26
- MODEL_CATALOG_UNAVAILABLE: '所选型号暂时不可用,请打开模型菜单重新选择后再发送。',
26
+ MODEL_CATALOG_UNAVAILABLE: '所选型号暂时不可用,请打开模型菜单重新选择后再发送。' + (!promptDispatched ? ' 本轮输入未发送。' : ''),
27
27
  LOGIN_REQUIRED: '需要登录,请到设置 → 远程主机连接 点登录',
28
28
  LOGIN_COMPLETED: '登录已完成,请重新发送本轮内容。',
29
29
  LOGIN_FAILED: '登录未完成,请到设置 → 远程主机连接 点登录;如仍有问题,请联系管理员。',
@@ -41,7 +41,8 @@ export function employeeFailure(code, brand = 'codex') {
41
41
  HOST_TRUST_REQUIRED: '远程主机连接信息还未准备好,请联系管理员。',
42
42
  POLICY_DENIED: '这个账号暂时不能使用,请联系公司账号管理员。',
43
43
  SESSION_CONTEXT_MISMATCH: '远程主机连接已改变,请重新开始本次检查。',
44
- ABORTED: '本轮已停止。操作可能已部分执行,请先确认文件;恢复连接后可在原会话继续。',
44
+ REMOTE_SESSION_BUSY: '原会话仍被主机上的上一轮占用,' + (promptDispatched ? '本轮请求未能完成' : '本轮输入未发送') + ';请稍后在原会话重新发送。若持续占用,请联系管理员检查上一轮主机会话。',
45
+ ABORTED: promptDispatched ? '本轮已停止。操作可能已部分执行,请先确认文件;恢复连接后可在原会话继续。' : '本轮已停止,本轮输入未发送;恢复连接后可在原会话重新发送。',
45
46
  CONFIRMATION_REQUIRED: '请先选择是否打开授权窗口。',
46
47
  CONTEXT_UNSUPPORTED: '远程 Codex 暂不支持这类会话内容,请联系管理员。',
47
48
  CONTENT_UNSUPPORTED: '远程 Codex 暂不支持这类附件,请联系管理员。',
@@ -79,6 +80,6 @@ export function employeeFailure(code, brand = 'codex') {
79
80
  if (code === 'AW_EMPLOYEE_BINARY_MISSING')
80
81
  return '本机执行组件没有装齐,本轮没有执行,请重新安装插件。';
81
82
  if (code.startsWith('AW_EMPLOYEE_') || code.startsWith('EMPLOYEE_'))
82
- return '你电脑上的执行连接已中断,本轮已停止;操作可能已部分执行,请先确认文件,恢复后可在原会话继续。';
83
- return '本轮没有完成。请检查连接,恢复后可在原会话继续;操作可能已部分执行,请先确认文件。';
83
+ return promptDispatched ? '你电脑上的执行连接已中断,本轮已停止;操作可能已部分执行,请先确认文件,恢复后可在原会话继续。' : '你电脑上的执行连接尚未就绪,本轮输入未发送;请检查连接,恢复后可在原会话重新发送。';
84
+ return promptDispatched ? '本轮没有完成。请检查连接,恢复后可在原会话继续;操作可能已部分执行,请先确认文件。' : '本轮输入未发送。请检查连接,恢复后可在原会话继续。';
84
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-workbench/remote-agent",
3
- "version": "0.8.27",
3
+ "version": "0.8.28",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",