@ai-workbench/remote-agent 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/acp.d.ts +8 -0
- package/dist/acp.js +59 -21
- package/dist/auth-reason.d.ts +1 -0
- package/dist/auth-reason.js +27 -0
- package/dist/connect-contract.d.ts +2 -1
- package/dist/conversation-details.d.ts +47 -0
- package/dist/conversation-details.js +319 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +110 -10
- package/dist/input.js +2 -2
- package/dist/login-diagnostic.d.ts +38 -0
- package/dist/login-diagnostic.js +29 -0
- package/dist/mapping.d.ts +11 -5
- package/dist/mapping.js +105 -141
- package/dist/presentation.js +4 -0
- package/dist/provider.d.ts +13 -4
- package/dist/provider.js +87 -17
- package/dist/readiness.js +7 -5
- package/dist/remote-details.d.ts +39 -0
- package/dist/remote-details.js +381 -0
- package/dist/remote.d.ts +3 -0
- package/dist/remote.js +2 -0
- package/dist/workspace-notice.d.ts +22 -1
- package/dist/workspace-notice.js +148 -129
- package/package.json +3 -3
package/dist/remote.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LoginDiagnostic } from './login-diagnostic.ts';
|
|
1
2
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
3
|
/** Development-only direct SSH. Product transports must consume L's exported sshArgs (v0.2). */
|
|
3
4
|
export interface DevelopmentSshTarget {
|
|
@@ -16,6 +17,8 @@ export interface Need {
|
|
|
16
17
|
export declare class NeedError extends Error {
|
|
17
18
|
code: string;
|
|
18
19
|
requires: string;
|
|
20
|
+
technical?: LoginDiagnostic;
|
|
21
|
+
officialReason?: string;
|
|
19
22
|
constructor(code: string, message: string, requires: string);
|
|
20
23
|
}
|
|
21
24
|
export declare function shellQuote(value: string): string;
|
package/dist/remote.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type RequestMessage } from '@deepseek-ai/dsh-llm';
|
|
2
3
|
import { type AgentDefinition } from './agents.ts';
|
|
3
4
|
import type { ConnectApi, RuntimeRequest } from './provider.ts';
|
|
4
5
|
import type { WorkspaceNotice, SessionProjectTarget } from './connect-contract.ts';
|
|
@@ -20,7 +21,27 @@ export interface WorkspaceNoticeMarker {
|
|
|
20
21
|
export declare function parseWorkspaceNoticeMarker(message: unknown): WorkspaceNoticeMarker | undefined;
|
|
21
22
|
/** Only this plugin's complete, source-bound notice may be excluded from remote model history. */
|
|
22
23
|
export declare function isWorkspaceNoticeMessage(message: unknown): boolean;
|
|
24
|
+
/** A verified first block in the actual model reply, never a second final message. */
|
|
25
|
+
export interface WorkspaceNoticePresentation {
|
|
26
|
+
readonly kind: 'workspace-notice';
|
|
27
|
+
readonly version: 2;
|
|
28
|
+
readonly provider: string;
|
|
29
|
+
readonly loginProvider: 'claude' | 'codex';
|
|
30
|
+
readonly sessionId: string;
|
|
31
|
+
readonly noticeId: string;
|
|
32
|
+
readonly claimId: string;
|
|
33
|
+
readonly workspaceName: string;
|
|
34
|
+
readonly bindingId: string;
|
|
35
|
+
readonly workspaceId: string;
|
|
36
|
+
readonly turn: number;
|
|
37
|
+
readonly step: number;
|
|
38
|
+
readonly blockIndex: 0;
|
|
39
|
+
readonly text: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function workspaceNoticeFromMessage(message: unknown): WorkspaceNoticePresentation | undefined;
|
|
42
|
+
/** Preserve the model answer and exact remote replay pointer; ordinary text is never searched. */
|
|
43
|
+
export declare function withoutWorkspaceNotice<T extends RequestMessage>(message: T): T;
|
|
23
44
|
export declare function createWorkspaceNoticeConsumer(ctx: Context, agent: AgentDefinition): {
|
|
24
45
|
reconcile: (request: RuntimeRequest, api: ConnectApi) => Promise<void>;
|
|
25
|
-
deliver: (notice: WorkspaceNotice, resolved: SessionProjectTarget, request: RuntimeRequest, api: ConnectApi) => Promise<
|
|
46
|
+
deliver: (notice: WorkspaceNotice, resolved: SessionProjectTarget, request: RuntimeRequest, api: ConnectApi) => Promise<WorkspaceNoticePresentation | undefined>;
|
|
26
47
|
};
|
package/dist/workspace-notice.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
-
import {
|
|
2
|
+
import { BlockAssembler, expandAssistantStream } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
4
|
import { AGENTS } from "./agents.js";
|
|
5
5
|
import { NeedError } from "./remote.js";
|
|
@@ -32,55 +32,86 @@ export function parseWorkspaceNoticeMarker(message) {
|
|
|
32
32
|
}
|
|
33
33
|
/** Only this plugin's complete, source-bound notice may be excluded from remote model history. */
|
|
34
34
|
export function isWorkspaceNoticeMessage(message) { return parseWorkspaceNoticeMarker(message) !== undefined; }
|
|
35
|
+
function rawPresentation(message) {
|
|
36
|
+
const response = rawMarker(message), presentation = response && object(response.presentation) ? response.presentation : undefined;
|
|
37
|
+
return presentation && object(presentation.workspaceNotice) ? presentation.workspaceNotice : undefined;
|
|
38
|
+
}
|
|
39
|
+
function shape(value) {
|
|
40
|
+
if (!object(value))
|
|
41
|
+
return false;
|
|
42
|
+
const agent = AGENTS.find(a => a.id === value.provider);
|
|
43
|
+
return !!agent && value.kind === 'workspace-notice' && value.version === 2 && value.loginProvider === agent.loginProvider
|
|
44
|
+
&& ['sessionId', 'noticeId', 'claimId', 'bindingId', 'workspaceId'].every(k => string(value[k])) && workspaceName(value.workspaceName)
|
|
45
|
+
&& Number.isSafeInteger(value.turn) && value.turn > 0 && Number.isSafeInteger(value.step) && value.step > 0
|
|
46
|
+
&& value.blockIndex === 0 && value.text === noticeText(value.workspaceName) + '\n\n';
|
|
47
|
+
}
|
|
48
|
+
export function workspaceNoticeFromMessage(message) {
|
|
49
|
+
if (!object(message) || message.role !== 'assistant' || !object(message.source) || message.source.kind !== 'model')
|
|
50
|
+
return;
|
|
51
|
+
const marker = rawPresentation(message), pointer = rawMarker(message);
|
|
52
|
+
if (!shape(marker) || message.source.provider !== marker.provider || pointer?.agent !== marker.provider || pointer.dshSessionId !== marker.sessionId
|
|
53
|
+
|| pointer.bindingId !== marker.bindingId || pointer.workspaceId !== marker.workspaceId || !Array.isArray(message.content))
|
|
54
|
+
return;
|
|
55
|
+
const block = message.content[marker.blockIndex];
|
|
56
|
+
if (!object(block) || block.type !== 'text' || block.text !== marker.text)
|
|
57
|
+
return;
|
|
58
|
+
return marker;
|
|
59
|
+
}
|
|
60
|
+
/** Preserve the model answer and exact remote replay pointer; ordinary text is never searched. */
|
|
61
|
+
export function withoutWorkspaceNotice(message) {
|
|
62
|
+
const marker = workspaceNoticeFromMessage(message);
|
|
63
|
+
if (!marker)
|
|
64
|
+
return message;
|
|
65
|
+
return { ...message, content: message.content.filter((_block, index) => index !== marker.blockIndex) };
|
|
66
|
+
}
|
|
35
67
|
function unknown(cause) {
|
|
36
|
-
return Object.assign(new NeedError('PROJECT_NOTICE_DELIVERY_UNKNOWN', '
|
|
68
|
+
return Object.assign(new NeedError('PROJECT_NOTICE_DELIVERY_UNKNOWN', '工作区说明的最终回复持久回执无法核对;请联系管理员核对,本次未继续派发远端请求。', '完整的官方最终会话记录和L工作区说明回执'), cause === undefined ? {} : { cause });
|
|
37
69
|
}
|
|
38
70
|
function requireServices(ctx, request) {
|
|
39
71
|
const id = string(request.sessionId) ? SessionId(request.sessionId) : undefined;
|
|
40
|
-
const registry = ctx.get('agents'), store = ctx.get('sessions');
|
|
41
|
-
|
|
42
|
-
const persistence = ctx.get('sessionPersistence');
|
|
43
|
-
const live = id && typeof registry?.get === 'function' ? registry.get(id) : undefined;
|
|
44
|
-
const session = live?.session;
|
|
72
|
+
const registry = ctx.get('agents'), store = ctx.get('sessions'), persistence = ctx.get('sessionPersistence');
|
|
73
|
+
const live = id && typeof registry?.get === 'function' ? registry.get(id) : undefined, session = live?.session;
|
|
45
74
|
if (!id || !live || live.id !== id || !session || session.id !== id || typeof store?.get !== 'function' || store.get(id) !== session
|
|
46
75
|
|| typeof session.snapshotEvents !== 'function' || typeof session.append !== 'function' || typeof store.flush !== 'function' || typeof persistence?.open !== 'function') {
|
|
47
|
-
throw new NeedError('DEPENDENCY_REQUIRED', '
|
|
76
|
+
throw new NeedError('DEPENDENCY_REQUIRED', '我需要当前真实会话及官方持久化读取能力,才能确认工作区说明;本次未启动远端。', 'DSH agents、sessions、sessionPersistence公共能力');
|
|
48
77
|
}
|
|
49
78
|
return { session, flush: () => store.flush(session), persistence };
|
|
50
79
|
}
|
|
51
80
|
function requireAcknowledge(api) {
|
|
52
81
|
const method = api.acknowledgeWorkspaceNotice;
|
|
53
82
|
if (typeof method !== 'function')
|
|
54
|
-
throw new NeedError('DEPENDENCY_REQUIRED', '
|
|
83
|
+
throw new NeedError('DEPENDENCY_REQUIRED', '我需要工作区说明回执能力;本次未启动远端。', 'L acknowledgeWorkspaceNotice公共接口');
|
|
55
84
|
return method.bind(api);
|
|
56
85
|
}
|
|
57
|
-
|
|
58
|
-
const
|
|
86
|
+
function receipt(marker, outcome, deliveryId) {
|
|
87
|
+
const base = { noticeId: marker.noticeId, surface: 'provider', claimId: marker.claimId, sessionId: marker.sessionId, provider: marker.loginProvider };
|
|
88
|
+
if (outcome === 'delivered' && string(deliveryId))
|
|
89
|
+
return { ...base, outcome, deliveryId };
|
|
90
|
+
return { ...base, outcome: 'unknown' };
|
|
91
|
+
}
|
|
92
|
+
async function acknowledge(method, value) {
|
|
93
|
+
const result = await method(value);
|
|
59
94
|
if (!object(result) || result.ok !== true)
|
|
60
95
|
throw unknown(result);
|
|
61
96
|
}
|
|
62
|
-
function
|
|
63
|
-
const base = { noticeId: marker.noticeId, surface: 'provider', claimId: marker.claimId, sessionId: marker.sessionId, provider: marker.loginProvider };
|
|
64
|
-
return outcome === 'delivered' ? { ...base, outcome, deliveryId: marker.deliveryId } : { ...base, outcome };
|
|
65
|
-
}
|
|
66
|
-
async function reportUnknown(method, marker, cause) {
|
|
97
|
+
async function reportUnknown(api, marker, cause) {
|
|
67
98
|
let failure = cause;
|
|
68
99
|
try {
|
|
69
|
-
await acknowledge(
|
|
100
|
+
await acknowledge(requireAcknowledge(api), receipt(marker, 'unknown'));
|
|
70
101
|
}
|
|
71
|
-
catch (
|
|
72
|
-
failure = new AggregateError([cause,
|
|
102
|
+
catch (error) {
|
|
103
|
+
failure = new AggregateError([cause, error], '说明核对和未知状态记录均未完成');
|
|
73
104
|
}
|
|
74
105
|
throw unknown(failure);
|
|
75
106
|
}
|
|
76
|
-
async function readDurable(services
|
|
107
|
+
async function readDurable(services) {
|
|
77
108
|
if (!await services.flush())
|
|
78
109
|
throw unknown();
|
|
79
110
|
const handle = await services.persistence.open(services.session.id, 'read');
|
|
80
111
|
try {
|
|
81
112
|
if (!handle || handle.id !== services.session.id || handle.header?.id !== services.session.id || handle.access !== 'read' || typeof handle.read !== 'function' || typeof handle.close !== 'function')
|
|
82
113
|
throw unknown();
|
|
83
|
-
const result = await handle.read(
|
|
114
|
+
const result = await handle.read();
|
|
84
115
|
if (!result || !Array.isArray(result.events))
|
|
85
116
|
throw unknown();
|
|
86
117
|
return result.events;
|
|
@@ -90,44 +121,22 @@ async function readDurable(services, offset, length) {
|
|
|
90
121
|
await handle.close();
|
|
91
122
|
}
|
|
92
123
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
function validateFinal(event, marker, events) {
|
|
125
|
+
if (event.surfaceOp !== 'append' || event.data.interrupted === true || !Array.isArray(event.data.stream)
|
|
126
|
+
|| event.data.turn !== marker.turn || event.data.step !== marker.step || !isDeepStrictEqual(workspaceNoticeFromMessage(event.data.message), marker))
|
|
127
|
+
throw unknown();
|
|
128
|
+
// Chat renders the last final for a step. An earlier append is not enough.
|
|
129
|
+
if (events.some(e => e.type === 'assistant/message' && e.surfaceOp === 'append' && e.seq > event.seq && e.data.turn === marker.turn && e.data.step === marker.step))
|
|
96
130
|
throw unknown();
|
|
97
131
|
const assembler = new BlockAssembler();
|
|
98
132
|
for (const member of expandAssistantStream(event.data.stream))
|
|
99
133
|
assembler.push(member.chunk);
|
|
100
|
-
if (assembler.finish.kind
|
|
101
|
-
|| !isDeepStrictEqual(assembler.replayState, event.data.message.source.replayState)
|
|
134
|
+
if (!['stop', 'max-tokens'].includes(assembler.finish.kind) || !isDeepStrictEqual(assembler.blocks(), event.data.message.content)
|
|
135
|
+
|| !isDeepStrictEqual(assembler.replayState, event.data.message.source.replayState))
|
|
102
136
|
throw unknown();
|
|
103
|
-
return marker;
|
|
104
137
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
for (const event of events) {
|
|
108
|
-
if (event.type !== 'assistant/message' || event.data.message.source.provider !== agent.id)
|
|
109
|
-
continue;
|
|
110
|
-
const raw = rawMarker(event.data.message);
|
|
111
|
-
if (raw?.kind !== 'workspace-notice')
|
|
112
|
-
continue;
|
|
113
|
-
// A fork may retain an ancestor's notice. It is never this Session's claim.
|
|
114
|
-
if (string(raw.sessionId) && raw.sessionId !== sessionId)
|
|
115
|
-
continue;
|
|
116
|
-
const marker = validateRecord(event, sessionId);
|
|
117
|
-
if (marker.provider !== agent.id || marker.loginProvider !== agent.loginProvider)
|
|
118
|
-
throw unknown();
|
|
119
|
-
found.push({ event, marker });
|
|
120
|
-
}
|
|
121
|
-
const notices = new Map();
|
|
122
|
-
for (const { marker } of found) {
|
|
123
|
-
if (notices.has(marker.noticeId))
|
|
124
|
-
throw unknown();
|
|
125
|
-
notices.set(marker.noticeId, marker.deliveryId);
|
|
126
|
-
}
|
|
127
|
-
return found;
|
|
128
|
-
}
|
|
129
|
-
function activeRequest(session, definition) {
|
|
130
|
-
let turn, step, model, provider;
|
|
138
|
+
function activeRequest(session, agent) {
|
|
139
|
+
let turn, step, provider;
|
|
131
140
|
for (const event of session.snapshotEvents())
|
|
132
141
|
switch (event.type) {
|
|
133
142
|
case 'turn/start':
|
|
@@ -148,107 +157,117 @@ function activeRequest(session, definition) {
|
|
|
148
157
|
break;
|
|
149
158
|
case 'request/header':
|
|
150
159
|
provider = event.data.header.config.provider;
|
|
151
|
-
model = event.data.header.config.model;
|
|
152
160
|
break;
|
|
153
161
|
}
|
|
154
|
-
if (!Number.isSafeInteger(turn) || turn < 1 || !Number.isSafeInteger(step) || step < 1 || provider !==
|
|
162
|
+
if (!Number.isSafeInteger(turn) || turn < 1 || !Number.isSafeInteger(step) || step < 1 || provider !== agent.id)
|
|
155
163
|
throw unknown();
|
|
156
|
-
return { turn: turn, step: step
|
|
157
|
-
}
|
|
158
|
-
function buildRecord(services, definition, notice, resolved) {
|
|
159
|
-
const { session } = services, { turn, step, model } = activeRequest(session, definition), text = noticeText(notice.workspaceName);
|
|
160
|
-
const base = createAssistantMessage({ content: [{ type: 'text', text }], source: { provider: definition.id, model } });
|
|
161
|
-
const marker = { kind: 'workspace-notice', version: 1, provider: definition.id, loginProvider: definition.loginProvider,
|
|
162
|
-
sessionId: session.id, noticeId: notice.noticeId, claimId: notice.claimId, deliveryId: base.id, messageId: base.id,
|
|
163
|
-
workspaceName: notice.workspaceName, bindingId: resolved.bindingId, workspaceId: resolved.workspaceId };
|
|
164
|
-
const chunks = [{ type: 'block-start', index: 0, blockType: 'text' }, { type: 'text-delta', index: 0, text },
|
|
165
|
-
{ type: 'block-end', index: 0, block: { type: 'text', text } }, { type: 'finish', reason: { kind: 'stop' }, replayState: { response: marker } }];
|
|
166
|
-
const accumulator = new AssistantStreamAccumulator(), assembler = new BlockAssembler();
|
|
167
|
-
for (const chunk of chunks)
|
|
168
|
-
assembler.push(accumulator.push({ time: Date.now(), chunk }).chunk);
|
|
169
|
-
const message = freezeMessage({ ...base, source: { ...base.source, replayState: assembler.replayState } });
|
|
170
|
-
// This public append is the actual ordinary-reply consumption point, not an emitted-yield guess.
|
|
171
|
-
const event = session.append('assistant/message', { turn, step, message, stream: [...accumulator.snapshot()] }, { surfaceOp: 'append' });
|
|
172
|
-
return { event, marker };
|
|
164
|
+
return { turn: turn, step: step };
|
|
173
165
|
}
|
|
174
166
|
export function createWorkspaceNoticeConsumer(ctx, agent) {
|
|
167
|
+
const pending = new Map();
|
|
175
168
|
const flights = new Map();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const services = requireServices(ctx, request), method = requireAcknowledge(api);
|
|
179
|
-
let stored;
|
|
169
|
+
const failures = new Map();
|
|
170
|
+
const verify = async (sessionId, api, marker, event) => {
|
|
180
171
|
try {
|
|
181
|
-
const events = await readDurable(services);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (live.some(item => !stored.some(record => record.marker.deliveryId === item.marker.deliveryId && isDeepStrictEqual(record.event, item.event))))
|
|
172
|
+
const services = requireServices(ctx, { mode: 'session', sessionId }), events = await readDurable(services);
|
|
173
|
+
const current = event ? events.find(e => e.seq === event.seq) : undefined;
|
|
174
|
+
if (!event || current?.type !== 'assistant/message' || !isDeepStrictEqual(current, event))
|
|
185
175
|
throw unknown();
|
|
176
|
+
validateFinal(current, marker, events);
|
|
177
|
+
await acknowledge(requireAcknowledge(api), receipt(marker, 'delivered', current.data.message.id));
|
|
178
|
+
pending.delete(sessionId);
|
|
179
|
+
failures.delete(sessionId);
|
|
186
180
|
}
|
|
187
181
|
catch (error) {
|
|
188
|
-
|
|
182
|
+
return reportUnknown(api, marker, error);
|
|
189
183
|
}
|
|
190
|
-
|
|
184
|
+
};
|
|
185
|
+
ctx.on('session/event', (session, event) => {
|
|
186
|
+
const preparation = pending.get(session.id);
|
|
187
|
+
if (!preparation)
|
|
188
|
+
return;
|
|
189
|
+
const { marker, api } = preparation;
|
|
190
|
+
let relevant;
|
|
191
|
+
if (event.type === 'assistant/message' && event.data.turn === marker.turn && event.data.step === marker.step && event.data.message.source.provider === agent.id)
|
|
192
|
+
relevant = event;
|
|
193
|
+
else if (event.type === 'turn/end' && event.data.turn === marker.turn && !session.snapshotEvents().some(e => e.type === 'assistant/message' && e.data.turn === marker.turn && e.data.step === marker.step && e.data.message.source.provider === agent.id))
|
|
194
|
+
relevant = undefined;
|
|
195
|
+
else
|
|
196
|
+
return;
|
|
197
|
+
if (flights.has(session.id))
|
|
198
|
+
return;
|
|
199
|
+
// Wait for the synchronous event dispatch to enqueue the writer, then flush.
|
|
200
|
+
const operation = Promise.resolve().then(() => verify(session.id, api, marker, relevant));
|
|
201
|
+
flights.set(session.id, operation);
|
|
202
|
+
void operation.catch(error => { failures.set(session.id, error); });
|
|
203
|
+
});
|
|
204
|
+
const reconcile = async (request, api) => {
|
|
205
|
+
request.signal?.throwIfAborted();
|
|
206
|
+
const services = requireServices(ctx, request);
|
|
207
|
+
requireAcknowledge(api);
|
|
208
|
+
const flight = flights.get(services.session.id);
|
|
209
|
+
if (flight) {
|
|
191
210
|
try {
|
|
192
|
-
await
|
|
211
|
+
await flight;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (flights.get(services.session.id) === flight)
|
|
216
|
+
flights.delete(services.session.id);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const events = await readDurable(services), seen = new Set();
|
|
220
|
+
let found = false;
|
|
221
|
+
for (const event of services.session.snapshotEvents()) {
|
|
222
|
+
if (event.type !== 'assistant/message')
|
|
223
|
+
continue;
|
|
224
|
+
const raw = rawPresentation(event.data.message);
|
|
225
|
+
if (!shape(raw) || raw.provider !== agent.id || raw.sessionId !== services.session.id)
|
|
226
|
+
continue;
|
|
227
|
+
if (seen.has(raw.noticeId))
|
|
228
|
+
return reportUnknown(api, raw, unknown());
|
|
229
|
+
seen.add(raw.noticeId);
|
|
230
|
+
found = true;
|
|
231
|
+
const durable = events.find(e => e.seq === event.seq);
|
|
232
|
+
if (durable?.type !== 'assistant/message' || !isDeepStrictEqual(durable, event))
|
|
233
|
+
return reportUnknown(api, raw, unknown());
|
|
234
|
+
try {
|
|
235
|
+
validateFinal(durable, raw, events);
|
|
236
|
+
await acknowledge(requireAcknowledge(api), receipt(raw, 'delivered', durable.data.message.id));
|
|
237
|
+
failures.delete(services.session.id);
|
|
238
|
+
pending.delete(services.session.id);
|
|
193
239
|
}
|
|
194
240
|
catch (error) {
|
|
195
|
-
|
|
241
|
+
return reportUnknown(api, raw, error);
|
|
196
242
|
}
|
|
197
243
|
}
|
|
244
|
+
if (!found && failures.has(services.session.id))
|
|
245
|
+
throw unknown(failures.get(services.session.id));
|
|
198
246
|
request.signal?.throwIfAborted();
|
|
199
247
|
};
|
|
200
248
|
const deliver = async (notice, resolved, request, api) => {
|
|
201
249
|
request.signal?.throwIfAborted();
|
|
202
|
-
const services = requireServices(ctx, request)
|
|
250
|
+
const services = requireServices(ctx, request);
|
|
251
|
+
requireAcknowledge(api);
|
|
203
252
|
if (!notice || !string(notice.noticeId) || !string(notice.claimId) || notice.messageKey !== 'remote-workspace-binding' || !workspaceName(notice.workspaceName)
|
|
204
253
|
|| resolved.sessionId !== services.session.id || !string(resolved.bindingId) || !string(resolved.workspaceId) || resolved.project?.name !== notice.workspaceName)
|
|
205
254
|
throw unknown();
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
for (const event of snapshot) {
|
|
215
|
-
if (event.type !== 'assistant/message' || event.data.message.source.provider === agent.id || !AGENTS.some(item => item.id === event.data.message.source.provider))
|
|
216
|
-
continue;
|
|
217
|
-
const raw = rawMarker(event.data.message);
|
|
218
|
-
if (raw?.kind === 'workspace-notice' && raw.noticeId === notice.noticeId && raw.sessionId === services.session.id)
|
|
219
|
-
throw unknown();
|
|
220
|
-
}
|
|
221
|
-
const records = ownRecords(snapshot, services.session.id, agent);
|
|
222
|
-
const prior = records.find(item => item.marker.noticeId === notice.noticeId);
|
|
223
|
-
if (prior) {
|
|
224
|
-
if (prior.marker.claimId !== notice.claimId || prior.marker.workspaceId !== resolved.workspaceId || prior.marker.bindingId !== resolved.bindingId || prior.marker.workspaceName !== notice.workspaceName)
|
|
225
|
-
throw unknown();
|
|
226
|
-
pending = prior;
|
|
227
|
-
}
|
|
228
|
-
else
|
|
229
|
-
pending = buildRecord(services, agent, notice, resolved);
|
|
230
|
-
const persisted = await readDurable(services, pending.event.seq, 1);
|
|
231
|
-
const event = persisted[0];
|
|
232
|
-
if (event?.type !== 'assistant/message' || !isDeepStrictEqual(event, pending.event))
|
|
233
|
-
throw unknown();
|
|
234
|
-
validateRecord(event, services.session.id);
|
|
235
|
-
await acknowledge(method, receipt(pending.marker, 'delivered'));
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
if (pending)
|
|
239
|
-
return reportUnknown(method, pending.marker, error);
|
|
240
|
-
throw unknown(error);
|
|
241
|
-
}
|
|
242
|
-
request.signal?.throwIfAborted();
|
|
243
|
-
})();
|
|
244
|
-
flights.set(key, operation);
|
|
245
|
-
try {
|
|
246
|
-
await operation;
|
|
255
|
+
const { turn, step } = activeRequest(services.session, agent);
|
|
256
|
+
const marker = { kind: 'workspace-notice', version: 2, provider: agent.id, loginProvider: agent.loginProvider, sessionId: services.session.id,
|
|
257
|
+
noticeId: notice.noticeId, claimId: notice.claimId, workspaceName: notice.workspaceName, bindingId: resolved.bindingId, workspaceId: resolved.workspaceId, turn, step, blockIndex: 0, text: noticeText(notice.workspaceName) + '\n\n' };
|
|
258
|
+
const prior = pending.get(services.session.id);
|
|
259
|
+
if (prior) {
|
|
260
|
+
if (!isDeepStrictEqual(prior.marker, marker))
|
|
261
|
+
throw unknown();
|
|
262
|
+
return prior.marker;
|
|
247
263
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
264
|
+
const recorded = services.session.snapshotEvents().filter((e) => e.type === 'assistant/message').find(e => workspaceNoticeFromMessage(e.data.message)?.noticeId === notice.noticeId);
|
|
265
|
+
if (recorded) {
|
|
266
|
+
await reconcile(request, api);
|
|
267
|
+
return undefined;
|
|
251
268
|
}
|
|
269
|
+
pending.set(services.session.id, { marker, api });
|
|
270
|
+
return marker;
|
|
252
271
|
};
|
|
253
272
|
return { reconcile, deliver };
|
|
254
273
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-workbench/remote-agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"zod": "4.6.7"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.
|
|
39
|
+
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.11.tgz",
|
|
40
40
|
"@deepseek-ai/cordis": "4.0.4",
|
|
41
41
|
"@deepseek-ai/cordis-plugin-group": "1.0.4",
|
|
42
42
|
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@ai-workbench/connect": "~0.7.
|
|
59
|
+
"@ai-workbench/connect": "~0.7.11",
|
|
60
60
|
"@deepseek-ai/cordis": "4.0.4",
|
|
61
61
|
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
62
62
|
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|