@ai-workbench/remote-agent 0.8.27 → 0.8.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/acp.d.ts +2 -0
- package/dist/acp.js +26 -3
- package/dist/broker-ownership.d.ts +10 -0
- package/dist/broker-ownership.js +59 -0
- package/dist/broker-runtime.js +5 -2
- package/dist/index.js +30 -22
- package/dist/input.d.ts +7 -1
- package/dist/input.js +53 -34
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +9 -5
- package/dist/provider.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# AI Workbench 远程助手 · @ai-workbench/remote-agent@0.8.
|
|
1
|
+
# AI Workbench 远程助手 · @ai-workbench/remote-agent@0.8.29 · 插件 → 添加插件
|
|
2
2
|
|
|
3
3
|
通过官方 DSH 插件入口使用 Claude(远程主机)与 Codex(远程主机)。登录和工作区来源由“远程主机连接”组件提供;员工在原生工作区和模型菜单中选择文件夹与型号。
|
|
4
4
|
|
|
5
5
|
文件和命令在所选的本机或主机目录执行,订阅凭据留在主机。插件不改写 DSH 的默认模型,不为员工创建同名主机目录,已有主机会话只提取一次原有主机来源并保存。
|
|
6
6
|
|
|
7
|
-
需要远程主机连接组件 0.7.29 或更新的 0.7.x
|
|
7
|
+
需要远程主机连接组件 0.7.29 或更新的 0.7.x、联网助手 0.4.9,以及支持公开会话归属接口的主机组件 S0.4.26。主机来源保存于官方 storage.domain 的会话元数据表,每轮仅由连接组件检查一次主机目录,不检查本机占位目录。
|
|
8
8
|
|
|
9
9
|
Claude 本机 Bash 会单独消费员工桥代理的控制诊断,不把它交给模型当作命令输出;真实传输中断仍提示结果未知。命令自身的标准输出、标准错误与退出码照常返回。
|
|
10
10
|
|
|
@@ -14,7 +14,9 @@ Claude 本机 Bash 会单独消费员工桥代理的控制诊断,不把它交
|
|
|
14
14
|
|
|
15
15
|
每轮启动一个新助手进程,按原生会话编号恢复上下文;不接回旧进程或重放上一轮输入。主机会话始终使用创建时保存的主机和目录;工作区之后的选择只影响新会话。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
同一 DSH 会话切换 Claude/Codex 时,切回会接本品牌最近的远端会话,并补充它尚未见过的可见对话;首次使用某品牌则带入全部可见历史。工具内部状态、推理和技术详情不转交。历史超出既有 `acpMaxMessageBytes` 容量时,只带能容纳的最近完整轮,并在技术详情说明省略数量;当前输入保持完整,超限仍明确失败。没有新增设置、重试或时限。
|
|
18
|
+
|
|
19
|
+
断线会结束本轮;已派发的操作可能已部分执行,请先检查文件。恢复连接后可在原会话继续。旧进程清理和技术详情记录尽力而为,不阻止发起下一轮。续接明确遇到原线程被占用时,使用 S0.4.26 的会话归属和原子结束接口:只有唯一匹配的旧进程确实已断开、主机明确确认退出后,才接回同一线程一次;不会新建替代线程、重发用户输入或抢占活跃会话。旧版无归属进程不能安全清理,需等待主机按现有策略回收;失败或释放结果未知则说明本轮输入未发送。原生本机会话恢复的兼容性及实际设备结果见本轮交付证据,未验证项不作为已通过。
|
|
18
20
|
|
|
19
21
|
DSH 启动和打开模型菜单时读取一次主机型号列表。发送时不再查询目录或预核所选型号;所选型号不可用时,请在模型菜单重新选择。
|
|
20
22
|
|
package/dist/acp.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { BrokerTransport } from './broker.ts';
|
|
|
6
6
|
import { type SanitizedObservation } from './conversation-details.ts';
|
|
7
7
|
import { type ModelConfigurationSnapshot } from './model-configuration.ts';
|
|
8
8
|
import type { EmployeeRouting } from './employee-relay.ts';
|
|
9
|
+
import type { SessionOwnership } from './broker-ownership.ts';
|
|
9
10
|
export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
|
|
10
11
|
export interface AcpHooks {
|
|
11
12
|
closeDiagnostics?: CloseDiagnostics;
|
|
@@ -25,6 +26,7 @@ export interface AcpHooks {
|
|
|
25
26
|
onEmployeeNotice?: (notice: string) => void;
|
|
26
27
|
onDetail?: (detail: Readonly<SanitizedObservation>) => void;
|
|
27
28
|
broker?: BrokerTransport;
|
|
29
|
+
sessionOwnership?: SessionOwnership;
|
|
28
30
|
employeeExecution?: (params: unknown, signal?: AbortSignal) => Promise<Record<string, unknown>>;
|
|
29
31
|
}
|
|
30
32
|
export interface EmployeeExecutionMeta {
|
package/dist/acp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { employeeExecutionInfo, employeeMcpSummary, employeeHttpNotice } from "./employee-info.js";
|
|
2
2
|
import { employeeAdapterCodes, employeeOpenCauseCodes } from "./employee-reasons.js";
|
|
3
|
-
import { CloseDiagnostics, withFirstFailureEvidence } from "./close-diagnostics.js";
|
|
3
|
+
import { CloseDiagnostics, closeCode, withFirstFailureEvidence } from "./close-diagnostics.js";
|
|
4
4
|
import * as acp from '@agentclientprotocol/sdk/unstable';
|
|
5
5
|
import { NeedError } from "./remote.js";
|
|
6
6
|
import { employeeFailure } from "./presentation.js";
|
|
@@ -505,17 +505,37 @@ export class AcpSession {
|
|
|
505
505
|
this.employeeRouting = routing;
|
|
506
506
|
const params = { cwd, mcpServers: routing ? structuredClone(routing.mcpServers) : [], ...((routing || this.employeeMeta) ? { _meta: { ...(this.employeeMeta ? { awEmployeeExecution: this.employeeMeta } : {}), ...(routing ? { claudeCode: { options: employeeClaudeOptions(routing) } } : {}) } } : {}), ...(systemPrompt && !(resumeId && this.codexNewSessionOnly) ? { systemPrompt: { mode: 'append', content: [{ type: 'text', text: systemPrompt }] } } : {}) };
|
|
507
507
|
const configRevision = this.resetConfiguration();
|
|
508
|
+
const restore = async (operation) => {
|
|
509
|
+
try {
|
|
510
|
+
return await operation();
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
if (!resumeId || acpNeed(error).code !== 'REMOTE_SESSION_BUSY' || !this.hooks.sessionOwnership)
|
|
514
|
+
throw error;
|
|
515
|
+
this.detail({ kind: 'session', severity: 'info', code: 'REMOTE_SESSION_BUSY', summary: '原远端会话被占用,正在核对同一会话与线程的已断开旧进程;尚未发送用户输入。' });
|
|
516
|
+
try {
|
|
517
|
+
await this.hooks.sessionOwnership.stopDetached(resumeId, signal);
|
|
518
|
+
signal?.throwIfAborted();
|
|
519
|
+
}
|
|
520
|
+
catch (failure) {
|
|
521
|
+
this.detail({ kind: 'session', severity: 'warning', code: 'REMOTE_SESSION_RELEASE_UNCONFIRMED', summary: '旧会话释放未获确认,本轮没有再次接续或发送用户输入。', payload: { reason: failure instanceof NeedError ? failure.requires : closeCode(failure, 'BROKER_CONTROL_FAILED') } });
|
|
522
|
+
throw failure;
|
|
523
|
+
}
|
|
524
|
+
this.detail({ kind: 'session', severity: 'info', code: 'REMOTE_SESSION_RELEASE_CONFIRMED', summary: '主机已确认原会话的已断开旧进程退出,继续接回同一远端会话一次。' });
|
|
525
|
+
return operation(); // Exactly one continuation, only after the explicit release receipt.
|
|
526
|
+
}
|
|
527
|
+
};
|
|
508
528
|
if (resumeId) {
|
|
509
529
|
this.sessionId = resumeId;
|
|
510
530
|
if (this.negotiated.sessionCapabilities?.resume) {
|
|
511
|
-
const result = await this.stage('acp-session-resume', () => this.connection.agent.request(acp.methods.agent.session.resume, { ...params, sessionId: resumeId }, { cancellationSignal: signal }));
|
|
531
|
+
const result = await restore(() => this.stage('acp-session-resume', () => this.connection.agent.request(acp.methods.agent.session.resume, { ...params, sessionId: resumeId }, { cancellationSignal: signal })));
|
|
512
532
|
this.lifecycleConfiguration(result, configRevision);
|
|
513
533
|
this.method = acp.methods.agent.session.resume;
|
|
514
534
|
}
|
|
515
535
|
else {
|
|
516
536
|
if (!this.negotiated.loadSession)
|
|
517
537
|
throw new NeedError('RESUME_UNSUPPORTED', '远端未声明会话恢复或加载能力,不能恢复原会话', '支持 session/resume 或 session/load 的 agent');
|
|
518
|
-
const result = await this.stage('acp-session-load', () => this.connection.agent.request(acp.methods.agent.session.load, { ...params, sessionId: resumeId }, { cancellationSignal: signal }));
|
|
538
|
+
const result = await restore(() => this.stage('acp-session-load', () => this.connection.agent.request(acp.methods.agent.session.load, { ...params, sessionId: resumeId }, { cancellationSignal: signal })));
|
|
519
539
|
this.lifecycleConfiguration(result, configRevision);
|
|
520
540
|
this.method = acp.methods.agent.session.load;
|
|
521
541
|
}
|
|
@@ -528,6 +548,7 @@ export class AcpSession {
|
|
|
528
548
|
this.lifecycleConfiguration(result, configRevision);
|
|
529
549
|
this.method = acp.methods.agent.session.new;
|
|
530
550
|
}
|
|
551
|
+
await this.hooks.sessionOwnership?.bind(this.sessionId, signal);
|
|
531
552
|
this.ready = true;
|
|
532
553
|
this.cwd = cwd;
|
|
533
554
|
this.systemPrompt = systemPrompt ?? '';
|
|
@@ -605,6 +626,8 @@ export function acpNeed(error) {
|
|
|
605
626
|
if (error instanceof NeedError)
|
|
606
627
|
return error;
|
|
607
628
|
if (error instanceof acp.RequestError) {
|
|
629
|
+
if (record(error.data)?.reason === 'thread_active_writer')
|
|
630
|
+
return new NeedError('REMOTE_SESSION_BUSY', employeeFailure('REMOTE_SESSION_BUSY'), '原远端线程仍被占用;需要按同一DSH会话和远端线程定位已断开的旧broker并确认释放;RPC 错误码 ' + error.code);
|
|
608
631
|
// Host SDK1.9 uses Internal error + data.details for thrown adapter errors.
|
|
609
632
|
// Only fixed codes cross this boundary; arbitrary detail strings are never copied.
|
|
610
633
|
for (const value of [error.message, record(error.data)?.details]) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BrokerIo } from './broker.ts';
|
|
2
|
+
export interface SessionOwnership {
|
|
3
|
+
bind(remoteSessionId: string, signal?: AbortSignal): Promise<void>;
|
|
4
|
+
stopDetached(remoteSessionId: string, signal?: AbortSignal): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
/** S0426 public control only: no process/lock scans or client ownership journal. */
|
|
7
|
+
export declare function brokerOwnership(io: BrokerIo, command: (args: string[]) => string, started: {
|
|
8
|
+
id: string;
|
|
9
|
+
instanceId: string;
|
|
10
|
+
}, receipt: unknown, dshSessionId: string, provider: 'codex' | 'claude'): SessionOwnership;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { controlJson } from "./broker.js";
|
|
2
|
+
import { NeedError } from "./remote.js";
|
|
3
|
+
import { closeCode } from "./close-diagnostics.js";
|
|
4
|
+
const record = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
|
|
5
|
+
const line = (v) => typeof v === 'string' && v.length > 0 && !/[\r\n\0]/.test(v);
|
|
6
|
+
const same = (value, expected) => { const row = record(value); return row?.dshSessionId === expected.dshSessionId && row.provider === expected.provider && row.remoteSessionId === expected.remoteSessionId; };
|
|
7
|
+
const busy = (reason) => new NeedError('REMOTE_SESSION_BUSY', '原会话仍被上一轮占用,本轮输入未发送。', '已断开旧会话的安全释放尚未确认;' + reason + (reason === 'OWNERSHIP_NOT_FOUND_OR_LEGACY' ? ';没有可证明归属的旧会话,不结束任何旧进程,请等待主机按现有策略回收。' : ''));
|
|
8
|
+
/** S0426 public control only: no process/lock scans or client ownership journal. */
|
|
9
|
+
export function brokerOwnership(io, command, started, receipt, dshSessionId, provider) {
|
|
10
|
+
let owner = { dshSessionId, provider, remoteSessionId: null };
|
|
11
|
+
const initial = record(receipt);
|
|
12
|
+
if (!line(dshSessionId) || initial?.ownershipProtocolVersion !== 1 || !same(initial.ownership, owner))
|
|
13
|
+
throw new NeedError('BROKER_OWNERSHIP_UNAVAILABLE', '主机会话缺少归属能力,本轮输入未发送;请联系管理员更新主机组件。', 'S >=0.4.26 / ownershipProtocolVersion:1');
|
|
14
|
+
const flags = (value) => ['--dsh-session-id', value.dshSessionId, '--provider', value.provider, '--remote-session-id', value.remoteSessionId];
|
|
15
|
+
const confirmed = (value, id, instanceId, expected) => value?.id === id && value.instanceId === instanceId && value.ownershipProtocolVersion === 1 && same(value.ownership, expected);
|
|
16
|
+
return {
|
|
17
|
+
async bind(remoteSessionId, signal) {
|
|
18
|
+
signal?.throwIfAborted();
|
|
19
|
+
if (!line(remoteSessionId))
|
|
20
|
+
throw new NeedError('BROKER_OWNERSHIP_INVALID', '远端会话编号无法绑定,本轮输入未发送。', 'S0426单行原生会话编号');
|
|
21
|
+
const next = { ...owner, remoteSessionId };
|
|
22
|
+
const value = record(controlJson(await io.run(command(['bind', started.id, '--if-instance', started.instanceId, ...flags(next), ...(owner.remoteSessionId === null ? [] : ['--if-remote-session-id', owner.remoteSessionId])]), signal)));
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
if (value?.type !== 'bound' || !confirmed(value, started.id, started.instanceId, next))
|
|
25
|
+
throw new NeedError('BROKER_OWNERSHIP_UNCONFIRMED', '主机未确认会话归属,本轮输入未发送。', 'S0426精确归属绑定回执');
|
|
26
|
+
owner = next;
|
|
27
|
+
},
|
|
28
|
+
async stopDetached(remoteSessionId, signal) {
|
|
29
|
+
try {
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
if (!line(remoteSessionId))
|
|
32
|
+
throw busy('OWNERSHIP_INVALID');
|
|
33
|
+
const wanted = { ...owner, remoteSessionId };
|
|
34
|
+
const entries = controlJson(await io.run(command(['list']), signal));
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
if (!Array.isArray(entries) || entries.some(row => !record(row)))
|
|
37
|
+
throw busy('OWNERSHIP_LIST_INVALID');
|
|
38
|
+
const matches = entries.map(record).filter(row => same(row?.ownership, wanted));
|
|
39
|
+
if (matches.length !== 1)
|
|
40
|
+
throw busy(matches.length ? 'OWNERSHIP_AMBIGUOUS' : 'OWNERSHIP_NOT_FOUND_OR_LEGACY');
|
|
41
|
+
const row = matches[0];
|
|
42
|
+
if (row.id === started.id || !line(row.id) || !line(row.instanceId) || row.ownershipProtocolVersion !== 1)
|
|
43
|
+
throw busy('OWNERSHIP_UNAVAILABLE');
|
|
44
|
+
const lifecycle = record(row.lifecycle);
|
|
45
|
+
if (!['running', 'exited'].includes(row.state) || lifecycle?.attached !== false || !line(lifecycle.detachedAt))
|
|
46
|
+
throw busy('SESSION_NOT_DETACHED');
|
|
47
|
+
const value = record(controlJson(await io.run(command(['stop-detached', row.id, '--if-instance', row.instanceId, ...flags(wanted)]), signal)));
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
if (value?.type !== 'killed' || !confirmed(value, row.id, row.instanceId, wanted) || value.agentExited !== true || value.processGroupExited !== true)
|
|
50
|
+
throw busy('STOP_UNCONFIRMED');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof NeedError && error.code === 'REMOTE_SESSION_BUSY')
|
|
54
|
+
throw error;
|
|
55
|
+
throw busy(closeCode(error, 'BROKER_CONTROL_FAILED'));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
package/dist/broker-runtime.js
CHANGED
|
@@ -12,6 +12,7 @@ import { BrokerTransport, brokerCommand, brokerId, sshIo, controlJson } from "./
|
|
|
12
12
|
import { CODEX_AGENT } from "./agents.js";
|
|
13
13
|
import { NeedError } from "./remote.js";
|
|
14
14
|
import { identity } from "./provider.js";
|
|
15
|
+
import { brokerOwnership } from "./broker-ownership.js";
|
|
15
16
|
/** Each invocation owns a new agent. Context resumes by native session ID, never by reattaching a broker. */
|
|
16
17
|
export function brokerFactory(_get, _ask, configuration, provided) {
|
|
17
18
|
return async (target, project, request) => {
|
|
@@ -25,7 +26,7 @@ export function brokerFactory(_get, _ask, configuration, provided) {
|
|
|
25
26
|
const command = (args) => brokerCommand(target, args);
|
|
26
27
|
const diagnostics = request.closeDiagnostics ?? new CloseDiagnostics(detail => request.detailChannel?.record(detail));
|
|
27
28
|
const id = brokerId(request.sessionId + '/' + randomUUID(), agent.id);
|
|
28
|
-
const startCommand = command(['start', id, '--cwd', project.path, '--settings', target.session.settingsPath, '--', agent.command]);
|
|
29
|
+
const startCommand = command(['start', id, '--cwd', project.path, '--settings', target.session.settingsPath, '--dsh-session-id', request.sessionId, '--provider', agent.loginProvider, '--', agent.command]);
|
|
29
30
|
// environments.toml overrides CODEX_EXEC_SERVER_URL. Refuse that host default rather
|
|
30
31
|
// than allowing a local-workspace resume to select a host executor.
|
|
31
32
|
const employeeStart = request.employeeScope && agent.loginProvider === 'codex';
|
|
@@ -47,7 +48,9 @@ export function brokerFactory(_get, _ask, configuration, provided) {
|
|
|
47
48
|
};
|
|
48
49
|
const aborted = () => channel.fail(new NeedError('ABORTED', '本轮已取消。', '确认主机状态'), { layer: 'broker', initiator: 'broker.open-signal', reason: 'ABORTED', action: 'abort' });
|
|
49
50
|
request.signal?.addEventListener('abort', aborted, { once: true });
|
|
51
|
+
let sessionOwnership;
|
|
50
52
|
try {
|
|
53
|
+
sessionOwnership = brokerOwnership(io, command, { id, instanceId: started.instanceId }, started, request.sessionId, agent.loginProvider);
|
|
51
54
|
request.signal?.throwIfAborted();
|
|
52
55
|
await channel.connect();
|
|
53
56
|
}
|
|
@@ -58,6 +61,6 @@ export function brokerFactory(_get, _ask, configuration, provided) {
|
|
|
58
61
|
finally {
|
|
59
62
|
request.signal?.removeEventListener('abort', aborted);
|
|
60
63
|
}
|
|
61
|
-
return { identity: identity(target), cwd: project.path, closeDiagnostics: diagnostics, broker: channel, acpTransport: channel, dispose, detach: dispose };
|
|
64
|
+
return { identity: identity(target), cwd: project.path, closeDiagnostics: diagnostics, broker: channel, sessionOwnership, acpTransport: channel, dispose, detach: dispose };
|
|
62
65
|
};
|
|
63
66
|
}
|
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,
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
|
|
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
|
|
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 =
|
|
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));
|
|
@@ -363,7 +363,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
363
363
|
let failure;
|
|
364
364
|
const push = (chunks) => { queue.push(...chunks); wake?.(); wake = undefined; };
|
|
365
365
|
let promptPermit;
|
|
366
|
-
const hooks = { closeDiagnostics: runtime.closeDiagnostics, broker: runtime.broker, employeeExecution: runtime.employeeExecution, ...this.permissions ? { promptGuard: () => {
|
|
366
|
+
const hooks = { closeDiagnostics: runtime.closeDiagnostics, broker: runtime.broker, sessionOwnership: runtime.sessionOwnership, employeeExecution: runtime.employeeExecution, ...this.permissions ? { promptGuard: () => {
|
|
367
367
|
if (!promptPermit)
|
|
368
368
|
throw new NeedError('PERMISSION_POLICY_CHANGED', '当前权限没有保持确认,本次没有继续派发。', '当前权限代次和同值回执');
|
|
369
369
|
return promptPermit;
|
|
@@ -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
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
96
|
+
counts.imageBlocks++;
|
|
86
97
|
}
|
|
98
|
+
blocks.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
|
|
87
99
|
}
|
|
88
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/presentation.d.ts
CHANGED
|
@@ -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;
|
package/dist/presentation.js
CHANGED
|
@@ -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,11 @@ export function employeeFailure(code, brand = 'codex') {
|
|
|
41
41
|
HOST_TRUST_REQUIRED: '远程主机连接信息还未准备好,请联系管理员。',
|
|
42
42
|
POLICY_DENIED: '这个账号暂时不能使用,请联系公司账号管理员。',
|
|
43
43
|
SESSION_CONTEXT_MISMATCH: '远程主机连接已改变,请重新开始本次检查。',
|
|
44
|
-
|
|
44
|
+
REMOTE_SESSION_BUSY: '原会话仍被主机上的上一轮占用,' + (promptDispatched ? '本轮请求未能完成' : '本轮输入未发送') + ';请稍后在原会话重新发送。若持续占用,请联系管理员检查上一轮主机会话。',
|
|
45
|
+
BROKER_OWNERSHIP_UNAVAILABLE: '主机会话组件缺少安全续接能力,本轮输入未发送;请联系管理员更新主机组件。',
|
|
46
|
+
BROKER_OWNERSHIP_UNCONFIRMED: '主机尚未确认这条会话的归属,本轮输入未发送;请检查远程主机连接后在原会话重试。',
|
|
47
|
+
BROKER_OWNERSHIP_INVALID: '远端会话编号无法确认,本轮输入未发送;请联系管理员查看技术详情。',
|
|
48
|
+
ABORTED: promptDispatched ? '本轮已停止。操作可能已部分执行,请先确认文件;恢复连接后可在原会话继续。' : '本轮已停止,本轮输入未发送;恢复连接后可在原会话重新发送。',
|
|
45
49
|
CONFIRMATION_REQUIRED: '请先选择是否打开授权窗口。',
|
|
46
50
|
CONTEXT_UNSUPPORTED: '远程 Codex 暂不支持这类会话内容,请联系管理员。',
|
|
47
51
|
CONTENT_UNSUPPORTED: '远程 Codex 暂不支持这类附件,请联系管理员。',
|
|
@@ -79,6 +83,6 @@ export function employeeFailure(code, brand = 'codex') {
|
|
|
79
83
|
if (code === 'AW_EMPLOYEE_BINARY_MISSING')
|
|
80
84
|
return '本机执行组件没有装齐,本轮没有执行,请重新安装插件。';
|
|
81
85
|
if (code.startsWith('AW_EMPLOYEE_') || code.startsWith('EMPLOYEE_'))
|
|
82
|
-
return '你电脑上的执行连接已中断,本轮已停止;操作可能已部分执行,请先确认文件,恢复后可在原会话继续。';
|
|
83
|
-
return '本轮没有完成。请检查连接,恢复后可在原会话继续;操作可能已部分执行,请先确认文件。';
|
|
86
|
+
return promptDispatched ? '你电脑上的执行连接已中断,本轮已停止;操作可能已部分执行,请先确认文件,恢复后可在原会话继续。' : '你电脑上的执行连接尚未就绪,本轮输入未发送;请检查连接,恢复后可在原会话重新发送。';
|
|
87
|
+
return promptDispatched ? '本轮没有完成。请检查连接,恢复后可在原会话继续;操作可能已部分执行,请先确认文件。' : '本轮输入未发送。请检查连接,恢复后可在原会话继续。';
|
|
84
88
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { SourceAdmissionStage } from './workspace-source.ts';
|
|
|
14
14
|
import type { LocalWorkspaceContext } from '@ai-workbench/connect/api';
|
|
15
15
|
import type { EmployeeRouting } from './employee-relay.ts';
|
|
16
16
|
import type { HostWorkspacePresentation } from './input.ts';
|
|
17
|
+
import type { SessionOwnership } from './broker-ownership.ts';
|
|
17
18
|
export interface EmployeeExecution {
|
|
18
19
|
hostCwd: string;
|
|
19
20
|
routing: EmployeeRouting;
|
|
@@ -37,6 +38,7 @@ export interface Runtime {
|
|
|
37
38
|
readable: ReadableStream<Uint8Array>;
|
|
38
39
|
} | AgentApp | ExtendedAgentApp;
|
|
39
40
|
broker?: BrokerTransport;
|
|
41
|
+
sessionOwnership?: SessionOwnership;
|
|
40
42
|
loginRequired?: () => Promise<void>;
|
|
41
43
|
dispose?: (cause?: CloseCause) => Promise<void>;
|
|
42
44
|
binding?: {
|