@ai-workbench/remote-agent 0.0.0-stage → 0.8.1

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.
Files changed (46) hide show
  1. package/README.md +58 -2
  2. package/cordis.patch.yml +3 -0
  3. package/dist/acp.d.ts +54 -0
  4. package/dist/acp.js +341 -0
  5. package/dist/agents.d.ts +15 -0
  6. package/dist/agents.js +4 -0
  7. package/dist/broker.d.ts +67 -0
  8. package/dist/broker.js +397 -0
  9. package/dist/client.js +36 -0
  10. package/dist/connect-contract.d.ts +26 -0
  11. package/dist/connect-contract.js +1 -0
  12. package/dist/conversation-details.d.ts +47 -0
  13. package/dist/conversation-details.js +319 -0
  14. package/dist/host-layout.d.ts +8 -0
  15. package/dist/host-layout.js +9 -0
  16. package/dist/index.d.ts +114 -0
  17. package/dist/index.js +360 -0
  18. package/dist/input.d.ts +12 -0
  19. package/dist/input.js +49 -0
  20. package/dist/interaction.d.ts +19 -0
  21. package/dist/interaction.js +1 -0
  22. package/dist/job.d.ts +17 -0
  23. package/dist/job.js +57 -0
  24. package/dist/mapping.d.ts +46 -0
  25. package/dist/mapping.js +230 -0
  26. package/dist/oneshot.d.ts +13 -0
  27. package/dist/oneshot.js +78 -0
  28. package/dist/presentation.d.ts +6 -0
  29. package/dist/presentation.js +45 -0
  30. package/dist/provider-locale.d.ts +19 -0
  31. package/dist/provider-locale.js +73 -0
  32. package/dist/provider.d.ts +144 -0
  33. package/dist/provider.js +164 -0
  34. package/dist/readiness.d.ts +28 -0
  35. package/dist/readiness.js +192 -0
  36. package/dist/remote-details.d.ts +26 -0
  37. package/dist/remote-details.js +250 -0
  38. package/dist/remote.d.ts +34 -0
  39. package/dist/remote.js +51 -0
  40. package/dist/sdk-ssh-wrapper.d.ts +2 -0
  41. package/dist/sdk-ssh-wrapper.js +34 -0
  42. package/dist/workspace-notice.d.ts +47 -0
  43. package/dist/workspace-notice.js +273 -0
  44. package/locale/en.json +10 -0
  45. package/locale/zh.json +10 -0
  46. package/package.json +97 -4
@@ -0,0 +1,164 @@
1
+ import { NeedError } from "./remote.js";
2
+ import { CONNECTION_REQUIRED, TEST_HOST_NOTICE } from "./presentation.js";
3
+ import { CODEX_AGENT } from "./agents.js";
4
+ export function requireLoginCoordinator(api) {
5
+ if (!['getConnectionState', 'getLoginPromptState', 'requestLoginPrompt', 'refreshLoginPrompts'].every(key => typeof api[key] === 'function'))
6
+ throw new NeedError('CONNECT_API_MISMATCH', '我需要 connect-api 0.6.1 的统一登录询问与连接状态能力', 'L >=0.6.1');
7
+ }
8
+ export function requireLoginState(state) {
9
+ const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
10
+ const need = (v) => object(v) && typeof v.code === 'string' && v.code.length > 0 && ['enrollment', 'network', 'ssh', 'auth', 'integration', 'settings', 'projects'].includes(v.layer) && typeof v.message === 'string' && typeof v.requires === 'string' && ['employee', 'administrator', 'ai-administrator', 'plugin'].includes(v.actor) && object(v.action) && typeof v.action.id === 'string' && typeof v.action.label === 'string' && typeof v.action.available === 'boolean';
11
+ if (!Array.isArray(state.checkingProviders) || state.checkingProviders.some(p => p !== 'codex' && p !== 'claude') || !object(state.providerNeeds) || Object.entries(state.providerNeeds).some(([p, v]) => !['codex', 'claude'].includes(p) || !need(v)))
12
+ throw new NeedError('CONNECT_API_MISMATCH', '我需要按助手区分的真实登录状态', 'L >=0.6.1');
13
+ }
14
+ /** Cancel only this consumer's wait; L owns the shared flight and its lifetime. */
15
+ export function awaitL(operation, signal) {
16
+ signal?.throwIfAborted();
17
+ const shared = operation();
18
+ if (!signal)
19
+ return shared;
20
+ return new Promise((resolve, reject) => {
21
+ const clear = () => signal.removeEventListener('abort', abort);
22
+ const abort = () => { clear(); reject(new NeedError('ABORTED', 'consumer wait cancelled', 'employee action')); };
23
+ signal.addEventListener('abort', abort, { once: true });
24
+ void shared.then(value => { clear(); resolve(value); }, error => { clear(); reject(error); });
25
+ if (signal.aborted)
26
+ abort();
27
+ });
28
+ }
29
+ function verifiedLogin(api, brand, expected) {
30
+ requireLoginCoordinator(api);
31
+ const connection = api.getConnectionState(), login = api.getLoginPromptState();
32
+ if (!connection.instanceId || !Number.isSafeInteger(connection.epoch) || connection.epoch < 0 || connection.phase !== 'connected' || expected && (connection.instanceId !== expected.instanceId || connection.epoch !== expected.epoch))
33
+ throw new NeedError('CONNECTION_LOST', 'connection binding changed', 'current employee connection');
34
+ requireLoginState(login);
35
+ if (login.instanceId !== connection.instanceId || login.checkingProviders.includes(brand) || login.providerNeeds[brand] || login.phase === 'blocked' || login.phase === 'disposed' || login.phase === 'authorizing' && login.provider === brand || typeof login.logins?.[brand] !== 'boolean')
36
+ throw new NeedError('AUTH_STATUS_INVALID', '无法确认当前品牌的登录状态', 'L核验的本品牌登录状态');
37
+ if (!login.logins[brand])
38
+ throw new NeedError('LOGIN_REQUIRED', '需要登录,请到设置 → 远程主机连接 点登录', '本人登录');
39
+ return { ...connection };
40
+ }
41
+ export function unwrap(result) {
42
+ if (!result.ok)
43
+ throw new NeedError(result.error.code, result.error.message, result.error.requires);
44
+ return result.value;
45
+ }
46
+ export function validateTarget(t) {
47
+ if (!t || !t.knownHostsFile?.startsWith('/') || !t.proxyCommand || t.session?.protocolVersion !== 1
48
+ || ![t.projectRoot, t.session.command, t.session.root, t.session.settingsPath].every(p => typeof p === 'string' && p.startsWith('/'))) {
49
+ throw new NeedError('CONNECT_API_MISMATCH', '我需要 connect-api 0.3.0 的可信主机、项目和 session 能力;旧目标不能继续使用', 'L 提供的完整 0.3.0 目标');
50
+ }
51
+ hostContextNotice(t);
52
+ }
53
+ /** Only the declared public check identifiers/statuses, never provider evidence. */
54
+ export function hostContextNotice(t) {
55
+ const h = t.hostContext;
56
+ if (h === undefined)
57
+ return;
58
+ const checks = (items) => Array.isArray(items) && items.every(item => item && typeof item.id === 'string' && typeof item.status === 'string');
59
+ if (!h || (h.enforcement !== 'test' && h.enforcement !== 'production') || (h.checkExitCode !== 0 && h.checkExitCode !== 1) || !checks(h.failedChecks) || !checks(h.unverifiedChecks))
60
+ throw new NeedError('CONNECT_API_MISMATCH', '主机检查背景无效,不能假定已通过生产检查', 'L 提供的有效 hostContext');
61
+ if (h.enforcement !== 'test')
62
+ return;
63
+ return TEST_HOST_NOTICE;
64
+ }
65
+ export function identity(t) { return `${t.username}@${t.host}:${t.port}`; }
66
+ /** Reject draft.3 and legacy runtime rather than inventing workspace bindings. */
67
+ export function requireNativeWorkspace(api) {
68
+ if (typeof api.getContractInfo !== 'function')
69
+ throw new NeedError('DEPENDENCY_REQUIRED', '我需要新版远程主机连接组件提供原生工作区绑定', 'L connect-api 0.7.0 / native-local-v1');
70
+ const info = api.getContractInfo();
71
+ if (info?.apiVersion !== '0.7.0' || info.status !== 'implemented' || info.workspaceBinding !== 'native-local-v1' || !['ensureReady', 'resolveSessionProject', 'acknowledgeWorkspaceNotice'].every(key => typeof api[key] === 'function'))
72
+ throw new NeedError('DEPENDENCY_REQUIRED', '我需要新版远程主机连接组件提供原生工作区绑定', 'L connect-api 0.7.0 / native-local-v1');
73
+ }
74
+ /** Authorization stays in L; ensureReady only checks and records the need. */
75
+ export async function offerLogin(connect, request, currentApi, agent = CODEX_AGENT) {
76
+ requireNativeWorkspace(connect);
77
+ request.signal?.throwIfAborted();
78
+ const before = connect.getConnectionState();
79
+ const target = unwrap(await awaitL(() => connect.ensureReady(agent.loginProvider), request.signal));
80
+ validateTarget(target);
81
+ request.signal?.throwIfAborted();
82
+ if (currentApi && currentApi() !== connect)
83
+ throw new NeedError('CONNECTION_LOST', '远程主机连接组件已更换', '当前员工连接');
84
+ verifiedLogin(connect, agent.loginProvider, before);
85
+ }
86
+ export function sameTarget(a, b) {
87
+ return identity(a) === identity(b) && a.projectRoot === b.projectRoot && a.knownHostsFile === b.knownHostsFile && a.proxyCommand === b.proxyCommand && a.session.command === b.session.command && a.session.root === b.session.root && a.session.settingsPath === b.session.settingsPath;
88
+ }
89
+ export function connectProvider(get, options = {}) {
90
+ const agent = options.agent ?? CODEX_AGENT;
91
+ const admissions = new WeakMap();
92
+ const preflight = async (request) => {
93
+ request.signal?.throwIfAborted();
94
+ const api = get();
95
+ if (!api)
96
+ throw new NeedError('DEPENDENCY_REQUIRED', CONNECTION_REQUIRED, 'aiWorkbenchConnect');
97
+ requireNativeWorkspace(api);
98
+ requireLoginCoordinator(api);
99
+ if (request.mode === 'one-shot' && !agent.oneShot)
100
+ throw new NeedError('MODE_UNSUPPORTED', '此远程模型仅支持会话模式', '使用会话模式');
101
+ const before = api.getConnectionState();
102
+ const target = unwrap(await awaitL(() => api.ensureReady(agent.loginProvider), request.signal));
103
+ validateTarget(target);
104
+ request.signal?.throwIfAborted();
105
+ if (get() !== api)
106
+ throw new NeedError('CONNECTION_LOST', '远程主机连接组件已更换', '当前员工连接');
107
+ const connection = verifiedLogin(api, agent.loginProvider, before);
108
+ if (request.identity && request.identity !== identity(target))
109
+ throw new NeedError('SESSION_CONTEXT_MISMATCH', '员工主机与原会话不一致,请新建会话', '原员工主机');
110
+ admissions.set(request, { api, target, connection });
111
+ };
112
+ const resolve = async (request) => {
113
+ if (!admissions.has(request))
114
+ await preflight(request);
115
+ const admitted = admissions.get(request);
116
+ admissions.delete(request);
117
+ const { api, target, connection } = admitted;
118
+ const stable = () => { request.signal?.throwIfAborted(); if (get() !== api)
119
+ throw new NeedError('CONNECTION_LOST', '远程主机连接组件已更换', '当前员工连接'); verifiedLogin(api, agent.loginProvider, connection); };
120
+ stable();
121
+ if (!request.sessionId)
122
+ throw new NeedError('PROJECT_BINDING_REQUIRED', '我需要当前会话和已选择的工作区,请使用原生工作区入口', 'DSH会话与原生工作区');
123
+ await options.notice?.reconcile(request, api);
124
+ stable();
125
+ const resolved = unwrap(await awaitL(() => api.resolveSessionProject(request.sessionId, { provider: agent.loginProvider, purpose: 'user-turn' }), request.signal));
126
+ stable();
127
+ if (!resolved || resolved.instanceId !== connection.instanceId || resolved.epoch !== connection.epoch)
128
+ throw new NeedError('CONNECTION_LOST', '工作区解析回复已失效', '当前连接代次');
129
+ validateTarget(resolved.target);
130
+ const project = resolved.project;
131
+ if (resolved.sessionId !== request.sessionId || !resolved.workspaceId || !resolved.bindingId || !Number.isSafeInteger(resolved.bindingRevision) || resolved.bindingRevision < 1 || !sameTarget(target, resolved.target) || !project || typeof project.name !== 'string' || !project.name || project.name === '.' || project.name === '..' || /[\\/\x00-\x1f\x7f]/.test(project.name) || project.path !== resolved.target.projectRoot + '/' + project.name)
132
+ throw new NeedError('PROJECT_BINDING_INVALID', '工作区绑定与当前会话或可信主机不一致', 'L核验的原生工作区绑定');
133
+ if (request.cwd && request.cwd !== project.path || request.bindingId && request.bindingId !== resolved.bindingId || request.workspaceId && request.workspaceId !== resolved.workspaceId)
134
+ throw new NeedError('SESSION_CONTEXT_MISMATCH', '原会话的工作区绑定已改变,请新建会话', '原工作区绑定');
135
+ const details = await options.details?.(request, api, connection, target);
136
+ stable();
137
+ let workspaceNotice = undefined;
138
+ if (resolved.notice) {
139
+ if (!options.notice)
140
+ throw new NeedError('DEPENDENCY_REQUIRED', '我需要官方会话持久记录来确认工作区说明', '官方会话与持久化服务');
141
+ workspaceNotice = await options.notice.deliver(resolved.notice, resolved, request, api);
142
+ stable();
143
+ }
144
+ if (!options.open)
145
+ throw new NeedError('RUNTIME_REQUIRED', '我需要主机运行适配器', '主机broker适配');
146
+ const runtime = await options.open(resolved.target, project, { ...request, agent });
147
+ try {
148
+ stable();
149
+ }
150
+ catch (error) {
151
+ await runtime.dispose?.();
152
+ throw error;
153
+ }
154
+ // The transport can outlive a turn; presentation belongs only to this invocation.
155
+ runtime.workspaceNotice = workspaceNotice || undefined;
156
+ runtime.details = details ?? runtime.details;
157
+ runtime.binding = { bindingId: resolved.bindingId, workspaceId: resolved.workspaceId, revision: resolved.bindingRevision };
158
+ runtime.connection = { ...connection };
159
+ runtime.loginRequired = () => offerLogin(api, request, get, agent);
160
+ return runtime;
161
+ };
162
+ resolve.preflight = preflight;
163
+ return resolve;
164
+ }
@@ -0,0 +1,28 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import { type ConnectApi } from './provider.ts';
3
+ import { type AgentDefinition } from './agents.ts';
4
+ export interface TechnicalDetail {
5
+ label: string;
6
+ value: string;
7
+ }
8
+ export interface ReadinessState {
9
+ phase: 'checking' | 'ready' | 'login-required' | 'blocked';
10
+ loginStatus: 'logged-in' | 'not-logged-in' | 'unknown';
11
+ title: string;
12
+ message: string;
13
+ technical: TechnicalDetail[];
14
+ }
15
+ export declare function createReadiness(get: () => ConnectApi | undefined, agent?: AgentDefinition): {
16
+ run(operation: string, payload: unknown, signal: AbortSignal): Promise<ReadinessState>;
17
+ };
18
+ export interface ReadinessWatch {
19
+ cursor: string;
20
+ available: boolean;
21
+ message: string;
22
+ }
23
+ /** One latest semantic snapshot, no history queue, polling or auth-check feedback loop. */
24
+ export declare function createReadinessWatch(subscribe: (connection: () => void, prompt: () => void) => () => void, get: () => ConnectApi | undefined, agent?: AgentDefinition): {
25
+ watch(after: unknown, signal: AbortSignal): Promise<ReadinessWatch>;
26
+ dispose: () => void;
27
+ };
28
+ export declare function installReadiness(ctx: Context, get: () => ConnectApi | undefined, agent?: AgentDefinition): void;
@@ -0,0 +1,192 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { randomUUID } from 'node:crypto';
36
+ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
37
+ import { unwrap, awaitL, requireLoginCoordinator, requireLoginState } from "./provider.js";
38
+ import { NeedError } from "./remote.js";
39
+ import { CONNECTION_REQUIRED, employeeFailure } from "./presentation.js";
40
+ import { CODEX_AGENT } from "./agents.js";
41
+ function details(target) {
42
+ const h = target.hostContext;
43
+ if (!h)
44
+ return [];
45
+ return [{ label: '主机配置', value: h.enforcement }, { label: '检查退出码', value: String(h.checkExitCode) }, ...h.failedChecks.map(item => ({ label: '未通过检查', value: `${item.id}(${item.status})` })), ...h.unverifiedChecks.map(item => ({ label: '未核实检查', value: `${item.id}(${item.status})` }))];
46
+ }
47
+ function blocked(code, technical = [], agent = CODEX_AGENT) {
48
+ return { phase: 'blocked', loginStatus: 'unknown', title: `无法确认 ${agent.name.zh} 登录状态`, message: employeeFailure(code), technical: [...technical, { label: '诊断代码', value: code }] };
49
+ }
50
+ /** L owns both evidence and inquiry. Missing per-brand evidence is never false. */
51
+ function snapshot(api, agent = CODEX_AGENT) {
52
+ const brand = agent.loginProvider;
53
+ const denied = (code, technical = []) => blocked(code, technical, agent);
54
+ requireLoginCoordinator(api);
55
+ const connection = api.getConnectionState();
56
+ if (connection.phase !== 'connected')
57
+ return denied(connection.need?.code ?? (connection.phase === 'disposed' ? 'DISPOSED' : 'CONNECTION_LOST'));
58
+ const state = api.getLoginPromptState();
59
+ if (!connection.instanceId || state.instanceId !== connection.instanceId)
60
+ return denied('AUTH_STATUS_INVALID');
61
+ requireLoginState(state);
62
+ const technical = details({ hostContext: state.technicalDetails?.hostContext });
63
+ if (state.phase === 'blocked' || state.phase === 'disposed')
64
+ return denied(state.technicalDetails?.code ?? 'AUTH_STATUS_INVALID', technical);
65
+ if (state.checkingProviders.includes(brand) || state.phase === 'authorizing' && state.provider === brand)
66
+ return { phase: 'checking', loginStatus: 'unknown', title: `正在检查 ${agent.name.zh} 登录状态`, message: '远程主机连接正在检查登录状态。', technical };
67
+ if (state.providerNeeds[brand])
68
+ return denied(state.providerNeeds[brand].code, technical);
69
+ if (typeof state.logins?.[brand] !== 'boolean')
70
+ return denied('AUTH_STATUS_INVALID', technical);
71
+ return state.logins[brand] ? { phase: 'ready', loginStatus: 'logged-in', title: `${agent.name.zh}已登录`, message: '本次远程主机连接和主机登录检查已完成。', technical } : { phase: 'login-required', loginStatus: 'not-logged-in', title: `${agent.name.zh} 尚未登录`, message: '需要登录,请到设置 → 远程主机连接 点登录', technical };
72
+ }
73
+ export function createReadiness(get, agent = CODEX_AGENT) {
74
+ return { async run(operation, payload, signal) {
75
+ try {
76
+ signal.throwIfAborted();
77
+ if (!['status', 'request', 'refresh'].includes(operation) || !payload || typeof payload !== 'object' || Array.isArray(payload) || Object.keys(payload).length)
78
+ throw new NeedError('INVALID_REQUEST', 'invalid readiness request', 'readiness request');
79
+ const api = get();
80
+ if (!api)
81
+ return blocked('DEPENDENCY_REQUIRED', [{ label: '组件标识', value: 'ai-workbench-connect' }, { label: '管理员处理', value: '安装并启用连接组件;检查 aiWorkbenchConnect 服务' }]);
82
+ requireLoginCoordinator(api);
83
+ const before = api.getConnectionState();
84
+ if (operation === 'request')
85
+ unwrap(await awaitL(() => api.requestLoginPrompt(agent.loginProvider, 'automatic'), signal));
86
+ if (operation === 'refresh')
87
+ unwrap(await awaitL(() => api.refreshLoginPrompts(), signal));
88
+ signal.throwIfAborted();
89
+ const current = api.getConnectionState();
90
+ if (get() !== api || current.instanceId !== before.instanceId || current.epoch !== before.epoch)
91
+ throw new NeedError('CONNECTION_LOST', 'connection epoch changed during check', 'current employee connection');
92
+ return snapshot(api, agent);
93
+ }
94
+ catch (error) {
95
+ return blocked(signal.aborted ? 'ABORTED' : error instanceof NeedError ? error.code : 'READINESS_FAILED', [], agent);
96
+ }
97
+ } };
98
+ }
99
+ /** One latest semantic snapshot, no history queue, polling or auth-check feedback loop. */
100
+ export function createReadinessWatch(subscribe, get, agent = CODEX_AGENT) {
101
+ const epoch = randomUUID(), lifetime = new AbortController();
102
+ let revision = 0, key = '', owner, lastEpoch = -1, lastPrompt = -1, instance = '';
103
+ let current = { cursor: `${epoch}:0`, available: false, message: CONNECTION_REQUIRED };
104
+ const waiters = new Set();
105
+ const changed = () => {
106
+ const api = get();
107
+ let value, available = false;
108
+ try {
109
+ if (!api) {
110
+ value = blocked('DEPENDENCY_REQUIRED');
111
+ }
112
+ else {
113
+ requireLoginCoordinator(api);
114
+ const connection = api.getConnectionState(), prompt = api.getLoginPromptState();
115
+ if (api !== owner || connection.instanceId !== instance) {
116
+ owner = api;
117
+ instance = connection.instanceId;
118
+ lastEpoch = -1;
119
+ lastPrompt = -1;
120
+ }
121
+ if (connection.epoch < lastEpoch)
122
+ return;
123
+ if (prompt.instanceId === instance) {
124
+ if (connection.epoch === lastEpoch && prompt.revision < lastPrompt)
125
+ return;
126
+ lastPrompt = prompt.revision;
127
+ }
128
+ lastEpoch = connection.epoch;
129
+ available = connection.phase === 'connected';
130
+ value = snapshot(api, agent);
131
+ }
132
+ }
133
+ catch (error) {
134
+ value = blocked(error instanceof NeedError ? error.code : 'READINESS_FAILED');
135
+ }
136
+ const next = JSON.stringify([instance, lastEpoch, available, value]);
137
+ if (next === key)
138
+ return;
139
+ key = next;
140
+ current = { cursor: `${epoch}:${++revision}`, available, message: available ? '' : value.message };
141
+ for (const waiter of [...waiters])
142
+ waiter();
143
+ };
144
+ const off = subscribe(changed, changed);
145
+ changed();
146
+ return {
147
+ async watch(after, signal) {
148
+ if (after !== null && typeof after !== 'string')
149
+ throw new NeedError('INVALID_REQUEST', 'invalid readiness cursor', 'readiness cursor');
150
+ const cancelled = AbortSignal.any([signal, lifetime.signal]);
151
+ cancelled.throwIfAborted();
152
+ if (after !== current.cursor)
153
+ return { ...current };
154
+ return new Promise((resolve, reject) => {
155
+ const clear = () => { waiters.delete(update); cancelled.removeEventListener('abort', abort); };
156
+ const update = () => { clear(); resolve({ ...current }); };
157
+ const abort = () => { clear(); reject(new NeedError('ABORTED', 'readiness watch cancelled', 'new check')); };
158
+ waiters.add(update);
159
+ cancelled.addEventListener('abort', abort, { once: true });
160
+ if (cancelled.aborted)
161
+ abort();
162
+ });
163
+ }, dispose: () => { off(); lifetime.abort(); },
164
+ };
165
+ }
166
+ let ReadinessRemote = (() => {
167
+ let _classSuper = TypertRemoteService;
168
+ let _instanceExtraInitializers = [];
169
+ let _run_decorators;
170
+ let _watch_decorators;
171
+ return class ReadinessRemote extends _classSuper {
172
+ static {
173
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
174
+ _run_decorators = [Remote];
175
+ _watch_decorators = [Remote];
176
+ __esDecorate(this, null, _run_decorators, { kind: "method", name: "run", static: false, private: false, access: { has: obj => "run" in obj, get: obj => obj.run }, metadata: _metadata }, null, _instanceExtraInitializers);
177
+ __esDecorate(this, null, _watch_decorators, { kind: "method", name: "watch", static: false, private: false, access: { has: obj => "watch" in obj, get: obj => obj.watch }, metadata: _metadata }, null, _instanceExtraInitializers);
178
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
179
+ }
180
+ readiness = __runInitializers(this, _instanceExtraInitializers);
181
+ changes;
182
+ constructor(ctx, get, agent) {
183
+ super(ctx, 'awCodexReadiness');
184
+ this.readiness = createReadiness(get, agent);
185
+ this.changes = createReadinessWatch((connection, prompt) => { const a = ctx.on('ai-workbench/connection-state', connection), b = ctx.on('ai-workbench/login-prompt-state', prompt); return () => { a(); b(); }; }, get, agent);
186
+ ctx.effect(() => () => this.changes.dispose());
187
+ }
188
+ async run(operation, payload, signal) { return this.readiness.run(operation, payload, signal); }
189
+ async watch(after, signal) { return this.changes.watch(after, signal); }
190
+ };
191
+ })();
192
+ export function installReadiness(ctx, get, agent = CODEX_AGENT) { new ReadinessRemote(ctx, get, agent); }
@@ -0,0 +1,26 @@
1
+ import { type DetailObservation } from './conversation-details.ts';
2
+ import type { ConnectApi as LConnectApi, ConnectionState } from '@ai-workbench/connect/api';
3
+ export type { RemoteConversationDetail as RemoteDetail } from '@ai-workbench/connect/api';
4
+ type Connection = Pick<ConnectionState, 'instanceId' | 'epoch' | 'phase'>;
5
+ export type RemoteDetailsApi = Pick<LConnectApi, 'getConnectionState'> & Partial<Pick<LConnectApi, 'getContractInfo' | 'appendRemoteConversationDetail' | 'getRemoteConversationDetailReceipt' | 'listRemoteConversationDetails'>>;
6
+ export interface DetailChannel {
7
+ record(observation: DetailObservation): void;
8
+ beginTurn(ids: {
9
+ turn: string | number | null;
10
+ step: string | number | null;
11
+ }): void;
12
+ endTurn(): void;
13
+ flush(): Promise<void>;
14
+ }
15
+ export interface DetailRequest {
16
+ sessionId: string;
17
+ provider: 'claude' | 'codex';
18
+ identity: string;
19
+ connection: Connection;
20
+ }
21
+ export declare function createRemoteDetails(options: {
22
+ profileDir: string;
23
+ getApi: () => RemoteDetailsApi | undefined;
24
+ }): {
25
+ open(request: DetailRequest): Promise<DetailChannel>;
26
+ };