@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.51
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 +105 -105
- package/dist/bin/cleanup-accounts.mjs +63 -63
- package/dist/bin/codex-remote.mjs +403 -403
- package/dist/bin/publish-android-release.mjs +63 -63
- package/dist/bin/release-upload-server.mjs +190 -190
- package/dist/build-platform.json +1 -1
- package/dist/get-shared-service-url.ps1 +14 -14
- package/dist/shared/code-browser.cjs +159 -159
- package/dist/shared/file-changes.cjs +31 -31
- package/dist/shared/file-citations.cjs +117 -117
- package/dist/shared/file-mentions.cjs +92 -92
- package/dist/shared/message-delivery.cjs +115 -115
- package/dist/shared/p2p-file-channel.cjs +87 -87
- package/dist/shared/server-requests.cjs +311 -311
- package/dist/shared/timeline-sync.cjs +173 -173
- package/dist/skills/code-relax/SKILL.md +22 -22
- package/dist/src/bounded-process-log.mjs +63 -63
- package/dist/src/celsius-client.mjs +107 -107
- package/dist/src/dots-approval-timeline.mjs +99 -99
- package/dist/src/dots-avatar.mjs +42 -42
- package/dist/src/dots-chat-projection.mjs +50 -50
- package/dist/src/dots-chat-server.mjs +483 -483
- package/dist/src/dots-message-images.mjs +319 -319
- package/dist/src/dots-profile.mjs +77 -77
- package/dist/src/dots-room-activity.mjs +172 -172
- package/dist/src/dots-room-client.mjs +135 -135
- package/dist/src/dots-room-projection.mjs +94 -94
- package/dist/src/dots-schedules.mjs +66 -66
- package/dist/src/dots-summary-tasks.mjs +32 -32
- package/dist/src/dots-task-catalog.mjs +430 -430
- package/dist/src/file-transfer-range.mjs +22 -22
- package/dist/src/file-upload-session.mjs +130 -130
- package/dist/src/hosted-history-app-server.mjs +103 -103
- package/dist/src/hosted-history-cache.mjs +113 -113
- package/dist/src/lan-socket-server.mjs +266 -266
- package/dist/src/managed-app-server.mjs +373 -373
- package/dist/src/managed-endpoint.mjs +25 -25
- package/dist/src/message-upload-files.mjs +109 -109
- package/dist/src/onboarding.mjs +12 -12
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/codex-protocol.mjs +46 -46
- package/dist/src/platform/desktop-project-store.mjs +208 -208
- package/dist/src/platform/linux/desktop-host.mjs +87 -87
- package/dist/src/platform/linux/process.mjs +53 -53
- package/dist/src/platform/linux/system.mjs +50 -50
- package/dist/src/platform/linux/user-environment.mjs +9 -9
- package/dist/src/platform/macos/codex-runtime.mjs +125 -125
- package/dist/src/platform/macos/desktop-host.mjs +86 -86
- package/dist/src/platform/macos/user-environment.mjs +9 -9
- package/dist/src/platform/native-package.mjs +58 -58
- package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
- package/dist/src/platform/windows/codex-runtime.mjs +215 -215
- package/dist/src/platform/windows/credential-store.mjs +27 -27
- package/dist/src/platform/windows/credential-store.ps1 +49 -49
- package/dist/src/platform/windows/desktop-host.mjs +65 -65
- package/dist/src/platform/windows/desktop-info.ps1 +38 -38
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
- package/dist/src/platform/windows/dots-profile.mjs +49 -49
- package/dist/src/platform/windows/launch-worker.mjs +41 -41
- package/dist/src/platform/windows/project-locations.mjs +16 -16
- package/dist/src/platform/windows/service-host.mjs +35 -35
- package/dist/src/platform/windows/user-environment.mjs +52 -52
- package/dist/src/project-management.mjs +58 -58
- package/dist/src/runtime-discovery.mjs +47 -47
- package/dist/src/self-relay/account-cleanup.mjs +30 -30
- package/dist/src/self-relay/admin-page.mjs +80 -80
- package/dist/src/self-relay/admin-style.mjs +14 -14
- package/dist/src/self-relay/connector.mjs +439 -439
- package/dist/src/self-relay/demo.mjs +31 -31
- package/dist/src/self-relay/diagnostics.mjs +54 -54
- package/dist/src/self-relay/email-accounts.mjs +183 -183
- package/dist/src/self-relay/file-wire.mjs +13 -13
- package/dist/src/self-relay/p2p-probe.mjs +168 -168
- package/dist/src/self-relay/privacy-page.mjs +26 -26
- package/dist/src/self-relay/public-releases.mjs +67 -67
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
- package/dist/src/self-relay/usage-meter.mjs +141 -141
- package/dist/src/server.mjs +5 -4
- package/dist/src/service-doctor.mjs +148 -148
- package/dist/src/service-install.mjs +97 -97
- package/dist/src/service-lifecycle.mjs +139 -139
- package/dist/src/shared-app-server.mjs +666 -666
- package/dist/src/shared-catalog.mjs +132 -132
- package/dist/src/shared-thread-stream.mjs +165 -165
- package/dist/src/usage-reset.mjs +17 -17
- package/dist/src/workstation-downloads.mjs +120 -120
- package/package.json +4 -4
- package/tools/postinstall.mjs +186 -186
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
import events from '../shared/app-server-events.cjs';
|
|
2
|
-
import { projectSummary } from './project-management.mjs';
|
|
3
|
-
|
|
4
|
-
export function sharedThreadProjectId(thread, projects) {
|
|
5
|
-
return events.catalogThreadProjectId(thread, projects);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function dotsThreadProjectId(thread, projects) {
|
|
9
|
-
if (thread.projectId && projects.some(project => project.id === thread.projectId)) return thread.projectId;
|
|
10
|
-
if (thread.environments?.length) {
|
|
11
|
-
for (const environment of thread.environments) {
|
|
12
|
-
const roots = Array.isArray(environment.runtimeWorkspaceRoots) ? environment.runtimeWorkspaceRoots : [];
|
|
13
|
-
for (const root of roots) {
|
|
14
|
-
if (!root) continue;
|
|
15
|
-
const projectId = events.threadProjectId({ cwd: root }, projects);
|
|
16
|
-
if (projectId) return projectId;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
// Display grouping may use the execution directory when workspace roots
|
|
20
|
-
// do not map to a known project. This never changes the thread's workspace
|
|
21
|
-
// permissions or its native project assignment.
|
|
22
|
-
for (const environment of thread.environments) {
|
|
23
|
-
if (!environment.cwd) continue;
|
|
24
|
-
const projectId = events.threadProjectId({ cwd: environment.cwd }, projects);
|
|
25
|
-
if (projectId) return projectId;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return events.threadProjectId({ cwd: thread.cwd }, projects);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Mobile catalog order comes entirely from app-server authority, independent of Desktop UI state.
|
|
32
|
-
export class SharedCatalog {
|
|
33
|
-
constructor(server, dotsServer, dotsChat) {
|
|
34
|
-
this.server = server; this.dotsServer = dotsServer; this.dotsChat = dotsChat;
|
|
35
|
-
this.states = new Map();
|
|
36
|
-
dotsServer?.on('disconnected', () => this.states.clear());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
threadState(thread, refresh = false) {
|
|
40
|
-
const client = thread.threadSource === 'aeon' ? this.dotsChat : this.dotsServer;
|
|
41
|
-
const observed = client?.threads.get(thread.id);
|
|
42
|
-
if (client?.ready && observed && !observed.needsReload && !observed.hydrating && !observed.refresh) {
|
|
43
|
-
const current = observed.thread;
|
|
44
|
-
return { chatRevision: current.chatRevision,
|
|
45
|
-
...(thread.threadSource === 'aeon' ? { chatMessageRevision: current.chatMessageRevision, updatedAt: current.updatedAt }
|
|
46
|
-
: { lastTurnStatus: current.turns.at(-1)?.status || null }) };
|
|
47
|
-
}
|
|
48
|
-
const identity = this.dotsServer.profileIdentity;
|
|
49
|
-
let entry = this.states.get(thread.id);
|
|
50
|
-
const version = JSON.stringify([thread.updatedAt, thread.status]);
|
|
51
|
-
if (!entry) { entry = { value: {} }; this.states.set(thread.id, entry); }
|
|
52
|
-
if (!entry.work && (refresh || entry.version !== version || Date.now() - (entry.at || 0) >= 30000)) {
|
|
53
|
-
const work = (async () => {
|
|
54
|
-
try {
|
|
55
|
-
const value = thread.threadSource === 'aeon'
|
|
56
|
-
? await this.dotsChat.latestChatState(thread.id)
|
|
57
|
-
: await this.dotsServer.latestTaskState(thread.id, refresh || entry.version !== version);
|
|
58
|
-
if (identity !== this.dotsServer.profileIdentity || this.states.get(thread.id) !== entry) return;
|
|
59
|
-
const changed = JSON.stringify(entry.value) !== JSON.stringify(value);
|
|
60
|
-
entry.value = value; entry.version = version; entry.at = Date.now();
|
|
61
|
-
if (changed) this.dotsServer.emit('notification', {
|
|
62
|
-
method: 'thread/status/changed', params: { threadId: thread.id, ...value },
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
entry.version = version; entry.at = Date.now();
|
|
66
|
-
console.error('云端目录修订读取失败:', thread.id, error.message);
|
|
67
|
-
}
|
|
68
|
-
})().finally(() => { if (entry.work === work) entry.work = null; });
|
|
69
|
-
entry.work = work;
|
|
70
|
-
}
|
|
71
|
-
// The live chat updates its revision before notifying stream listeners.
|
|
72
|
-
// A directory refresh can still be in flight with an older cached value;
|
|
73
|
-
// never return that value after the rendered timeline has advanced.
|
|
74
|
-
if (thread.threadSource === 'aeon') {
|
|
75
|
-
const current = this.dotsChat.threads.get(thread.id)?.thread;
|
|
76
|
-
return current?.chatRevision === undefined ? {} : {
|
|
77
|
-
chatRevision: current.chatRevision, chatMessageRevision: current.chatMessageRevision, updatedAt: current.updatedAt,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return entry.value;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async projects() {
|
|
84
|
-
const native = await this.server.projects();
|
|
85
|
-
const projects = native.map(projectSummary);
|
|
86
|
-
return { projects, native };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async threads(limit, archived = false, scope = 'all', refresh = false) {
|
|
90
|
-
const { projects, native } = await this.projects();
|
|
91
|
-
const threads = []; let cursor;
|
|
92
|
-
if (scope !== 'durable') do {
|
|
93
|
-
const page = await this.server.request('thread/list', { limit: Math.min(100, limit - threads.length),
|
|
94
|
-
sortKey: 'recency_at', sortDirection: 'desc', archived, ...(cursor ? { cursor } : {}) });
|
|
95
|
-
threads.push(...page.data); cursor = page.nextCursor;
|
|
96
|
-
} while (cursor && threads.length < limit);
|
|
97
|
-
const summaries = threads.map(thread => ({ id: thread.id, kind: 'codex', hostId: 'local',
|
|
98
|
-
...(thread.model ? { model: thread.model, reasoningEffort: thread.reasoningEffort } : {}),
|
|
99
|
-
title: thread.name || thread.preview || '', cwd: thread.cwd,
|
|
100
|
-
projectId: sharedThreadProjectId(thread, native), updatedAt: thread.updatedAt,
|
|
101
|
-
recencyAt: thread.recencyAt ?? thread.updatedAt,
|
|
102
|
-
status: typeof thread.status === 'string' ? thread.status : thread.status?.type || 'unknown',
|
|
103
|
-
activeFlags: threadActiveFlags(thread.status) }));
|
|
104
|
-
let catalogError;
|
|
105
|
-
if (scope !== 'local' && this.dotsServer) {
|
|
106
|
-
try {
|
|
107
|
-
const dots = await this.dotsServer.catalog(archived, refresh);
|
|
108
|
-
summaries.push(...dots.map(thread => ({ id: thread.id, kind: thread.threadSource === 'aeon' ? 'dots' : 'codex', hostId: 'durable',
|
|
109
|
-
threadSource: thread.threadSource,
|
|
110
|
-
...(thread.threadSource === 'aeon_child' && thread.model ? { model: thread.model, reasoningEffort: thread.reasoningEffort } : {}),
|
|
111
|
-
...this.threadState(thread, refresh),
|
|
112
|
-
...(thread.threadSource === 'aeon' ? { avatar: thread.avatar || '', color: thread.color || '' } : {}),
|
|
113
|
-
title: thread.name || (thread.threadSource === 'aeon' ? 'Dots' : thread.preview || ''), cwd: thread.cwd,
|
|
114
|
-
projectId: thread.threadSource === 'aeon' ? null : dotsThreadProjectId(thread, native),
|
|
115
|
-
updatedAt: thread.threadSource === 'aeon' ? this.dotsChat.threads.get(thread.id)?.thread.updatedAt || 0 : thread.updatedAt,
|
|
116
|
-
recencyAt: thread.recencyAt ?? thread.updatedAt,
|
|
117
|
-
status: thread.threadSource === 'aeon' ? this.dotsChat?.threads.get(thread.id)?.thread.status || 'unknown'
|
|
118
|
-
: typeof thread.status === 'string' ? thread.status : thread.status?.type || 'unknown',
|
|
119
|
-
activeFlags: thread.threadSource === 'aeon' ? [] : threadActiveFlags(thread.status) })));
|
|
120
|
-
} catch (error) { catalogError = `Dots 对话读取失败:${error.message}`; }
|
|
121
|
-
}
|
|
122
|
-
summaries.sort((a, b) => b.recencyAt - a.recencyAt);
|
|
123
|
-
return { catalogScope: scope, projects, pinnedThreads: [], threads: [...summaries.filter(thread => thread.kind === 'dots'),
|
|
124
|
-
...summaries.filter(thread => thread.kind !== 'dots').slice(0, limit)], ...(catalogError ? { catalogError } : {}) };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function threadActiveFlags(status) {
|
|
129
|
-
return status && typeof status === 'object' && Array.isArray(status.activeFlags)
|
|
130
|
-
? status.activeFlags.filter(flag => typeof flag === 'string')
|
|
131
|
-
: [];
|
|
132
|
-
}
|
|
1
|
+
import events from '../shared/app-server-events.cjs';
|
|
2
|
+
import { projectSummary } from './project-management.mjs';
|
|
3
|
+
|
|
4
|
+
export function sharedThreadProjectId(thread, projects) {
|
|
5
|
+
return events.catalogThreadProjectId(thread, projects);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function dotsThreadProjectId(thread, projects) {
|
|
9
|
+
if (thread.projectId && projects.some(project => project.id === thread.projectId)) return thread.projectId;
|
|
10
|
+
if (thread.environments?.length) {
|
|
11
|
+
for (const environment of thread.environments) {
|
|
12
|
+
const roots = Array.isArray(environment.runtimeWorkspaceRoots) ? environment.runtimeWorkspaceRoots : [];
|
|
13
|
+
for (const root of roots) {
|
|
14
|
+
if (!root) continue;
|
|
15
|
+
const projectId = events.threadProjectId({ cwd: root }, projects);
|
|
16
|
+
if (projectId) return projectId;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Display grouping may use the execution directory when workspace roots
|
|
20
|
+
// do not map to a known project. This never changes the thread's workspace
|
|
21
|
+
// permissions or its native project assignment.
|
|
22
|
+
for (const environment of thread.environments) {
|
|
23
|
+
if (!environment.cwd) continue;
|
|
24
|
+
const projectId = events.threadProjectId({ cwd: environment.cwd }, projects);
|
|
25
|
+
if (projectId) return projectId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return events.threadProjectId({ cwd: thread.cwd }, projects);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Mobile catalog order comes entirely from app-server authority, independent of Desktop UI state.
|
|
32
|
+
export class SharedCatalog {
|
|
33
|
+
constructor(server, dotsServer, dotsChat) {
|
|
34
|
+
this.server = server; this.dotsServer = dotsServer; this.dotsChat = dotsChat;
|
|
35
|
+
this.states = new Map();
|
|
36
|
+
dotsServer?.on('disconnected', () => this.states.clear());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
threadState(thread, refresh = false) {
|
|
40
|
+
const client = thread.threadSource === 'aeon' ? this.dotsChat : this.dotsServer;
|
|
41
|
+
const observed = client?.threads.get(thread.id);
|
|
42
|
+
if (client?.ready && observed && !observed.needsReload && !observed.hydrating && !observed.refresh) {
|
|
43
|
+
const current = observed.thread;
|
|
44
|
+
return { chatRevision: current.chatRevision,
|
|
45
|
+
...(thread.threadSource === 'aeon' ? { chatMessageRevision: current.chatMessageRevision, updatedAt: current.updatedAt }
|
|
46
|
+
: { lastTurnStatus: current.turns.at(-1)?.status || null }) };
|
|
47
|
+
}
|
|
48
|
+
const identity = this.dotsServer.profileIdentity;
|
|
49
|
+
let entry = this.states.get(thread.id);
|
|
50
|
+
const version = JSON.stringify([thread.updatedAt, thread.status]);
|
|
51
|
+
if (!entry) { entry = { value: {} }; this.states.set(thread.id, entry); }
|
|
52
|
+
if (!entry.work && (refresh || entry.version !== version || Date.now() - (entry.at || 0) >= 30000)) {
|
|
53
|
+
const work = (async () => {
|
|
54
|
+
try {
|
|
55
|
+
const value = thread.threadSource === 'aeon'
|
|
56
|
+
? await this.dotsChat.latestChatState(thread.id)
|
|
57
|
+
: await this.dotsServer.latestTaskState(thread.id, refresh || entry.version !== version);
|
|
58
|
+
if (identity !== this.dotsServer.profileIdentity || this.states.get(thread.id) !== entry) return;
|
|
59
|
+
const changed = JSON.stringify(entry.value) !== JSON.stringify(value);
|
|
60
|
+
entry.value = value; entry.version = version; entry.at = Date.now();
|
|
61
|
+
if (changed) this.dotsServer.emit('notification', {
|
|
62
|
+
method: 'thread/status/changed', params: { threadId: thread.id, ...value },
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
entry.version = version; entry.at = Date.now();
|
|
66
|
+
console.error('云端目录修订读取失败:', thread.id, error.message);
|
|
67
|
+
}
|
|
68
|
+
})().finally(() => { if (entry.work === work) entry.work = null; });
|
|
69
|
+
entry.work = work;
|
|
70
|
+
}
|
|
71
|
+
// The live chat updates its revision before notifying stream listeners.
|
|
72
|
+
// A directory refresh can still be in flight with an older cached value;
|
|
73
|
+
// never return that value after the rendered timeline has advanced.
|
|
74
|
+
if (thread.threadSource === 'aeon') {
|
|
75
|
+
const current = this.dotsChat.threads.get(thread.id)?.thread;
|
|
76
|
+
return current?.chatRevision === undefined ? {} : {
|
|
77
|
+
chatRevision: current.chatRevision, chatMessageRevision: current.chatMessageRevision, updatedAt: current.updatedAt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return entry.value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async projects() {
|
|
84
|
+
const native = await this.server.projects();
|
|
85
|
+
const projects = native.map(projectSummary);
|
|
86
|
+
return { projects, native };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async threads(limit, archived = false, scope = 'all', refresh = false) {
|
|
90
|
+
const { projects, native } = await this.projects();
|
|
91
|
+
const threads = []; let cursor;
|
|
92
|
+
if (scope !== 'durable') do {
|
|
93
|
+
const page = await this.server.request('thread/list', { limit: Math.min(100, limit - threads.length),
|
|
94
|
+
sortKey: 'recency_at', sortDirection: 'desc', archived, ...(cursor ? { cursor } : {}) });
|
|
95
|
+
threads.push(...page.data); cursor = page.nextCursor;
|
|
96
|
+
} while (cursor && threads.length < limit);
|
|
97
|
+
const summaries = threads.map(thread => ({ id: thread.id, kind: 'codex', hostId: 'local',
|
|
98
|
+
...(thread.model ? { model: thread.model, reasoningEffort: thread.reasoningEffort } : {}),
|
|
99
|
+
title: thread.name || thread.preview || '', cwd: thread.cwd,
|
|
100
|
+
projectId: sharedThreadProjectId(thread, native), updatedAt: thread.updatedAt,
|
|
101
|
+
recencyAt: thread.recencyAt ?? thread.updatedAt,
|
|
102
|
+
status: typeof thread.status === 'string' ? thread.status : thread.status?.type || 'unknown',
|
|
103
|
+
activeFlags: threadActiveFlags(thread.status) }));
|
|
104
|
+
let catalogError;
|
|
105
|
+
if (scope !== 'local' && this.dotsServer) {
|
|
106
|
+
try {
|
|
107
|
+
const dots = await this.dotsServer.catalog(archived, refresh);
|
|
108
|
+
summaries.push(...dots.map(thread => ({ id: thread.id, kind: thread.threadSource === 'aeon' ? 'dots' : 'codex', hostId: 'durable',
|
|
109
|
+
threadSource: thread.threadSource,
|
|
110
|
+
...(thread.threadSource === 'aeon_child' && thread.model ? { model: thread.model, reasoningEffort: thread.reasoningEffort } : {}),
|
|
111
|
+
...this.threadState(thread, refresh),
|
|
112
|
+
...(thread.threadSource === 'aeon' ? { avatar: thread.avatar || '', color: thread.color || '' } : {}),
|
|
113
|
+
title: thread.name || (thread.threadSource === 'aeon' ? 'Dots' : thread.preview || ''), cwd: thread.cwd,
|
|
114
|
+
projectId: thread.threadSource === 'aeon' ? null : dotsThreadProjectId(thread, native),
|
|
115
|
+
updatedAt: thread.threadSource === 'aeon' ? this.dotsChat.threads.get(thread.id)?.thread.updatedAt || 0 : thread.updatedAt,
|
|
116
|
+
recencyAt: thread.recencyAt ?? thread.updatedAt,
|
|
117
|
+
status: thread.threadSource === 'aeon' ? this.dotsChat?.threads.get(thread.id)?.thread.status || 'unknown'
|
|
118
|
+
: typeof thread.status === 'string' ? thread.status : thread.status?.type || 'unknown',
|
|
119
|
+
activeFlags: thread.threadSource === 'aeon' ? [] : threadActiveFlags(thread.status) })));
|
|
120
|
+
} catch (error) { catalogError = `Dots 对话读取失败:${error.message}`; }
|
|
121
|
+
}
|
|
122
|
+
summaries.sort((a, b) => b.recencyAt - a.recencyAt);
|
|
123
|
+
return { catalogScope: scope, projects, pinnedThreads: [], threads: [...summaries.filter(thread => thread.kind === 'dots'),
|
|
124
|
+
...summaries.filter(thread => thread.kind !== 'dots').slice(0, limit)], ...(catalogError ? { catalogError } : {}) };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function threadActiveFlags(status) {
|
|
129
|
+
return status && typeof status === 'object' && Array.isArray(status.activeFlags)
|
|
130
|
+
? status.activeFlags.filter(flag => typeof flag === 'string')
|
|
131
|
+
: [];
|
|
132
|
+
}
|
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
// Event-driven subscription adapter. Normal desktop-tool polling remains in its own adapter.
|
|
2
|
-
import timelineSync from '../shared/timeline-sync.cjs';
|
|
3
|
-
export const threadListNotificationMethods = new Set([
|
|
4
|
-
'thread/started', 'thread/status/changed', 'thread/name/updated',
|
|
5
|
-
'thread/archived', 'thread/unarchived', 'thread/deleted',
|
|
6
|
-
'thread/project/updated', 'project/changed', 'turn/started', 'turn/completed',
|
|
7
|
-
]);
|
|
8
|
-
export async function streamSharedThread(response, threadId, client, normalize, requestedSync) {
|
|
9
|
-
response.startEvents();
|
|
10
|
-
let closed = false, timer, subscription, releaseActivity, streamReady = false, flushing = false;
|
|
11
|
-
let tracePending = false, pendingReset;
|
|
12
|
-
const pending = new Map(), sentTurns = new Map();
|
|
13
|
-
let fromSeq, toSeq, syncId = requestedSync?.id, epoch = requestedSync?.epoch;
|
|
14
|
-
let finish;
|
|
15
|
-
const done = new Promise(resolve => { finish = resolve; });
|
|
16
|
-
const emit = (name, value) => { if (!closed) return response.event(name, value); };
|
|
17
|
-
let historySync;
|
|
18
|
-
const publishHistorySync = () => {
|
|
19
|
-
if (typeof client.isHistorySyncing !== 'function') return;
|
|
20
|
-
const active = client.isHistorySyncing(threadId);
|
|
21
|
-
// End the notice only after the recovered body has been delivered, not
|
|
22
|
-
// when the upstream read finishes while its update is still being flushed.
|
|
23
|
-
if (!active && (!streamReady || flushing || pendingReset || pending.size)) return;
|
|
24
|
-
if (historySync === active) return;
|
|
25
|
-
historySync = active;
|
|
26
|
-
emit('history-sync', { threadId, active });
|
|
27
|
-
};
|
|
28
|
-
const flush = async () => {
|
|
29
|
-
clearTimeout(timer); timer = null;
|
|
30
|
-
const record = client.threads.get(threadId);
|
|
31
|
-
if (closed || flushing || !streamReady || !record || (!pendingReset && !pending.size)) return;
|
|
32
|
-
flushing = true;
|
|
33
|
-
try {
|
|
34
|
-
if (pendingReset) {
|
|
35
|
-
const snapshot = pendingReset;
|
|
36
|
-
pendingReset = undefined;
|
|
37
|
-
for (const [id, change] of pending) if (change.sequence <= snapshot.sync.seq) pending.delete(id);
|
|
38
|
-
fromSeq = pending.size ? Math.max(fromSeq, snapshot.sync.seq + 1) : undefined;
|
|
39
|
-
if (!pending.size) toSeq = undefined;
|
|
40
|
-
tracePending = [...pending.values()].some(change => change.immediate);
|
|
41
|
-
const normalized = await normalize(snapshot);
|
|
42
|
-
if (closed) return;
|
|
43
|
-
await emit('update', normalized);
|
|
44
|
-
syncId = normalized.sync.id; epoch = normalized.sync.epoch;
|
|
45
|
-
sentTurns.clear();
|
|
46
|
-
for (const turn of normalized.turns) if (!turn.cacheStub) sentTurns.set(turn.id, turn);
|
|
47
|
-
client.traceSync?.('stream-reset-sent', threadId, { seq: normalized.sync.seq,
|
|
48
|
-
turnOrder: normalized.turns.map(turn => turn.id) });
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const batch = structuredClone([...pending.values()].map(change => change.turn));
|
|
52
|
-
const thread = { ...record.thread, turns: [] }, sync = { id: syncId, epoch, fromSeq, toSeq };
|
|
53
|
-
const turnOrder = record.thread.turns.map(turn => turn.id), retained = new Set(turnOrder);
|
|
54
|
-
const trace = tracePending;
|
|
55
|
-
pending.clear(); fromSeq = toSeq = undefined; tracePending = false;
|
|
56
|
-
const base = await normalize({ thread, sync });
|
|
57
|
-
const turns = [], turnPatches = [];
|
|
58
|
-
const normalizedTurns = [];
|
|
59
|
-
for (const turn of batch) {
|
|
60
|
-
if (turn.cacheStub) continue;
|
|
61
|
-
const normalized = (await normalize({ thread: { ...thread, turns: [turn] } })).turns[0];
|
|
62
|
-
const patch = timelineSync.diffTurn(sentTurns.get(turn.id), normalized);
|
|
63
|
-
if (patch) turnPatches.push(patch); else turns.push(normalized);
|
|
64
|
-
normalizedTurns.push(normalized);
|
|
65
|
-
}
|
|
66
|
-
if (closed) return;
|
|
67
|
-
await emit('update', { ...base, turns, ...(turnPatches.length ? { turnPatches } : {}) });
|
|
68
|
-
for (const turn of normalizedTurns) sentTurns.set(turn.id, turn);
|
|
69
|
-
for (const id of sentTurns.keys()) if (!retained.has(id)) sentTurns.delete(id);
|
|
70
|
-
if (trace || turns.length || (base.sync.fromSeq <= record.recoverAfter && record.recoverAfter <= base.sync.toSeq)) {
|
|
71
|
-
client.traceSync?.('stream-update-sent', threadId, { closed, fromSeq: base.sync?.fromSeq, toSeq: base.sync?.toSeq, turnOrder,
|
|
72
|
-
messages: turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])),
|
|
73
|
-
patches: turnPatches.map(patch => ({ turnId: patch.id, entries: (patch.entries || []).map(entry => entry.id), appended: (patch.appended || []).map(entry => entry.id) })) });
|
|
74
|
-
}
|
|
75
|
-
} catch (error) { emit('bridge-error', { error: error.message }); response.end(); finish(); }
|
|
76
|
-
finally {
|
|
77
|
-
flushing = false;
|
|
78
|
-
publishHistorySync();
|
|
79
|
-
if (!closed && (pendingReset || pending.size) && !timer) timer = setTimeout(flush, 0);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
const onTurn = ({ thread, turn, sequence, immediate }) => {
|
|
83
|
-
if (thread.id !== threadId) return;
|
|
84
|
-
if (!fromSeq) fromSeq = sequence;
|
|
85
|
-
toSeq = sequence;
|
|
86
|
-
pending.set(turn.id, { turn, sequence, immediate: immediate || pending.get(turn.id)?.immediate });
|
|
87
|
-
if (immediate) tracePending = true;
|
|
88
|
-
if (immediate) flush(); else if (!timer) timer = setTimeout(flush, 50);
|
|
89
|
-
};
|
|
90
|
-
const onReset = snapshot => {
|
|
91
|
-
if (snapshot.thread.id !== threadId) return;
|
|
92
|
-
pendingReset = structuredClone(snapshot);
|
|
93
|
-
pendingReset.sync.reset = true;
|
|
94
|
-
flush();
|
|
95
|
-
};
|
|
96
|
-
const onSettings = id => { if (id === threadId) emit('settings', client.context(id)); };
|
|
97
|
-
const onRequests = id => { if (id === threadId) emit('approvals', { requests: client.serverRequests.list(threadId) }); };
|
|
98
|
-
const onQueue = id => { if (id === threadId) emit('queue-changed', {}); };
|
|
99
|
-
const onDisconnected = () => { emit('bridge-error', { error: 'Codex 内核连接已断开,请重新连接。' }); response.end(); finish(); };
|
|
100
|
-
const onInvalidated = id => { if (id === threadId) emit('history-invalidated', {}); };
|
|
101
|
-
const onHistorySync = id => { if (id === threadId) publishHistorySync(); };
|
|
102
|
-
const onChatError = value => { if (value.threadId === threadId) emit('chat-error', value); };
|
|
103
|
-
response.once('close', () => { closed = true; finish(); });
|
|
104
|
-
client.on('turn', onTurn); client.on('settings', onSettings); client.on('disconnected', onDisconnected);
|
|
105
|
-
client.on('timeline-reset', onReset);
|
|
106
|
-
client.on('queue-changed', onQueue);
|
|
107
|
-
client.on('requests-changed', onRequests);
|
|
108
|
-
client.on('history-invalidated', onInvalidated);
|
|
109
|
-
client.on('history-sync', onHistorySync);
|
|
110
|
-
client.on('chat-error', onChatError);
|
|
111
|
-
publishHistorySync();
|
|
112
|
-
try {
|
|
113
|
-
const started = performance.now();
|
|
114
|
-
client.traceSync?.('stream-open', threadId, { requestedSeq: requestedSync?.after });
|
|
115
|
-
subscription = await client.acquire(threadId, requestedSync);
|
|
116
|
-
syncId = subscription.record.sync.id; epoch = subscription.record.sync.epoch;
|
|
117
|
-
if (!closed) {
|
|
118
|
-
const initial = structuredClone(subscription.record), catchup = subscription.catchup && structuredClone(subscription.catchup);
|
|
119
|
-
if (pendingReset?.sync.seq <= initial.sync.seq) pendingReset = undefined;
|
|
120
|
-
// Only discard notifications certified by the acquired snapshot/catchup.
|
|
121
|
-
for (const [id, change] of pending) if (change.sequence <= initial.sync.seq) pending.delete(id);
|
|
122
|
-
fromSeq = pending.size ? Math.max(fromSeq, initial.sync.seq + 1) : undefined;
|
|
123
|
-
if (!pending.size) toSeq = undefined;
|
|
124
|
-
tracePending = [...pending.values()].some(change => change.immediate);
|
|
125
|
-
const normalized = await normalize(subscription.confirmed ? { ...initial, thread: { ...initial.thread, turns: [] } } : initial);
|
|
126
|
-
const delivered = subscription.snapshot ? normalized : catchup ? await normalize(catchup) : null;
|
|
127
|
-
const sent = Boolean(delivered) && !closed;
|
|
128
|
-
if (sent) await emit('update', delivered);
|
|
129
|
-
// Confirm every handoff after its catchup/snapshot has been delivered.
|
|
130
|
-
// This also releases a cached selection with a valid, unchanged token;
|
|
131
|
-
// transport subscription alone does not confirm the message boundary.
|
|
132
|
-
if (!closed) await emit('sync-ready', {
|
|
133
|
-
thread: { ...normalized.thread, turns: [] }, sync: normalized.sync,
|
|
134
|
-
});
|
|
135
|
-
client.traceSync?.('stream-initial-sent', threadId, { ms: Math.round(performance.now() - started), closed,
|
|
136
|
-
snapshot: subscription.snapshot, catchup: Boolean(catchup), sent, seq: normalized.sync?.seq,
|
|
137
|
-
deliveredSync: sent ? delivered.sync : undefined,
|
|
138
|
-
messages: sent ? delivered.turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])) : [],
|
|
139
|
-
windowMessages: normalized.turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])) });
|
|
140
|
-
for (const turn of normalized.turns) if (!turn.cacheStub) sentTurns.set(turn.id, turn);
|
|
141
|
-
emit('queue-changed', {});
|
|
142
|
-
onRequests(threadId);
|
|
143
|
-
streamReady = true;
|
|
144
|
-
publishHistorySync();
|
|
145
|
-
if (!closed && typeof client.acquireActivity === 'function') {
|
|
146
|
-
releaseActivity = client.acquireActivity(threadId, value => emit('dots-activity', value));
|
|
147
|
-
}
|
|
148
|
-
flush();
|
|
149
|
-
}
|
|
150
|
-
await done;
|
|
151
|
-
} catch (error) { emit('bridge-error', { error: error.message }); response.end(); }
|
|
152
|
-
finally {
|
|
153
|
-
client.traceSync?.('stream-close', threadId, { ready: streamReady });
|
|
154
|
-
clearTimeout(timer);
|
|
155
|
-
releaseActivity?.();
|
|
156
|
-
client.off('turn', onTurn); client.off('settings', onSettings); client.off('disconnected', onDisconnected);
|
|
157
|
-
client.off('timeline-reset', onReset);
|
|
158
|
-
client.off('queue-changed', onQueue);
|
|
159
|
-
client.off('requests-changed', onRequests);
|
|
160
|
-
client.off('history-invalidated', onInvalidated);
|
|
161
|
-
client.off('history-sync', onHistorySync);
|
|
162
|
-
client.off('chat-error', onChatError);
|
|
163
|
-
if (subscription) await subscription.release();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
1
|
+
// Event-driven subscription adapter. Normal desktop-tool polling remains in its own adapter.
|
|
2
|
+
import timelineSync from '../shared/timeline-sync.cjs';
|
|
3
|
+
export const threadListNotificationMethods = new Set([
|
|
4
|
+
'thread/started', 'thread/status/changed', 'thread/name/updated',
|
|
5
|
+
'thread/archived', 'thread/unarchived', 'thread/deleted',
|
|
6
|
+
'thread/project/updated', 'project/changed', 'turn/started', 'turn/completed',
|
|
7
|
+
]);
|
|
8
|
+
export async function streamSharedThread(response, threadId, client, normalize, requestedSync) {
|
|
9
|
+
response.startEvents();
|
|
10
|
+
let closed = false, timer, subscription, releaseActivity, streamReady = false, flushing = false;
|
|
11
|
+
let tracePending = false, pendingReset;
|
|
12
|
+
const pending = new Map(), sentTurns = new Map();
|
|
13
|
+
let fromSeq, toSeq, syncId = requestedSync?.id, epoch = requestedSync?.epoch;
|
|
14
|
+
let finish;
|
|
15
|
+
const done = new Promise(resolve => { finish = resolve; });
|
|
16
|
+
const emit = (name, value) => { if (!closed) return response.event(name, value); };
|
|
17
|
+
let historySync;
|
|
18
|
+
const publishHistorySync = () => {
|
|
19
|
+
if (typeof client.isHistorySyncing !== 'function') return;
|
|
20
|
+
const active = client.isHistorySyncing(threadId);
|
|
21
|
+
// End the notice only after the recovered body has been delivered, not
|
|
22
|
+
// when the upstream read finishes while its update is still being flushed.
|
|
23
|
+
if (!active && (!streamReady || flushing || pendingReset || pending.size)) return;
|
|
24
|
+
if (historySync === active) return;
|
|
25
|
+
historySync = active;
|
|
26
|
+
emit('history-sync', { threadId, active });
|
|
27
|
+
};
|
|
28
|
+
const flush = async () => {
|
|
29
|
+
clearTimeout(timer); timer = null;
|
|
30
|
+
const record = client.threads.get(threadId);
|
|
31
|
+
if (closed || flushing || !streamReady || !record || (!pendingReset && !pending.size)) return;
|
|
32
|
+
flushing = true;
|
|
33
|
+
try {
|
|
34
|
+
if (pendingReset) {
|
|
35
|
+
const snapshot = pendingReset;
|
|
36
|
+
pendingReset = undefined;
|
|
37
|
+
for (const [id, change] of pending) if (change.sequence <= snapshot.sync.seq) pending.delete(id);
|
|
38
|
+
fromSeq = pending.size ? Math.max(fromSeq, snapshot.sync.seq + 1) : undefined;
|
|
39
|
+
if (!pending.size) toSeq = undefined;
|
|
40
|
+
tracePending = [...pending.values()].some(change => change.immediate);
|
|
41
|
+
const normalized = await normalize(snapshot);
|
|
42
|
+
if (closed) return;
|
|
43
|
+
await emit('update', normalized);
|
|
44
|
+
syncId = normalized.sync.id; epoch = normalized.sync.epoch;
|
|
45
|
+
sentTurns.clear();
|
|
46
|
+
for (const turn of normalized.turns) if (!turn.cacheStub) sentTurns.set(turn.id, turn);
|
|
47
|
+
client.traceSync?.('stream-reset-sent', threadId, { seq: normalized.sync.seq,
|
|
48
|
+
turnOrder: normalized.turns.map(turn => turn.id) });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const batch = structuredClone([...pending.values()].map(change => change.turn));
|
|
52
|
+
const thread = { ...record.thread, turns: [] }, sync = { id: syncId, epoch, fromSeq, toSeq };
|
|
53
|
+
const turnOrder = record.thread.turns.map(turn => turn.id), retained = new Set(turnOrder);
|
|
54
|
+
const trace = tracePending;
|
|
55
|
+
pending.clear(); fromSeq = toSeq = undefined; tracePending = false;
|
|
56
|
+
const base = await normalize({ thread, sync });
|
|
57
|
+
const turns = [], turnPatches = [];
|
|
58
|
+
const normalizedTurns = [];
|
|
59
|
+
for (const turn of batch) {
|
|
60
|
+
if (turn.cacheStub) continue;
|
|
61
|
+
const normalized = (await normalize({ thread: { ...thread, turns: [turn] } })).turns[0];
|
|
62
|
+
const patch = timelineSync.diffTurn(sentTurns.get(turn.id), normalized);
|
|
63
|
+
if (patch) turnPatches.push(patch); else turns.push(normalized);
|
|
64
|
+
normalizedTurns.push(normalized);
|
|
65
|
+
}
|
|
66
|
+
if (closed) return;
|
|
67
|
+
await emit('update', { ...base, turns, ...(turnPatches.length ? { turnPatches } : {}) });
|
|
68
|
+
for (const turn of normalizedTurns) sentTurns.set(turn.id, turn);
|
|
69
|
+
for (const id of sentTurns.keys()) if (!retained.has(id)) sentTurns.delete(id);
|
|
70
|
+
if (trace || turns.length || (base.sync.fromSeq <= record.recoverAfter && record.recoverAfter <= base.sync.toSeq)) {
|
|
71
|
+
client.traceSync?.('stream-update-sent', threadId, { closed, fromSeq: base.sync?.fromSeq, toSeq: base.sync?.toSeq, turnOrder,
|
|
72
|
+
messages: turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])),
|
|
73
|
+
patches: turnPatches.map(patch => ({ turnId: patch.id, entries: (patch.entries || []).map(entry => entry.id), appended: (patch.appended || []).map(entry => entry.id) })) });
|
|
74
|
+
}
|
|
75
|
+
} catch (error) { emit('bridge-error', { error: error.message }); response.end(); finish(); }
|
|
76
|
+
finally {
|
|
77
|
+
flushing = false;
|
|
78
|
+
publishHistorySync();
|
|
79
|
+
if (!closed && (pendingReset || pending.size) && !timer) timer = setTimeout(flush, 0);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const onTurn = ({ thread, turn, sequence, immediate }) => {
|
|
83
|
+
if (thread.id !== threadId) return;
|
|
84
|
+
if (!fromSeq) fromSeq = sequence;
|
|
85
|
+
toSeq = sequence;
|
|
86
|
+
pending.set(turn.id, { turn, sequence, immediate: immediate || pending.get(turn.id)?.immediate });
|
|
87
|
+
if (immediate) tracePending = true;
|
|
88
|
+
if (immediate) flush(); else if (!timer) timer = setTimeout(flush, 50);
|
|
89
|
+
};
|
|
90
|
+
const onReset = snapshot => {
|
|
91
|
+
if (snapshot.thread.id !== threadId) return;
|
|
92
|
+
pendingReset = structuredClone(snapshot);
|
|
93
|
+
pendingReset.sync.reset = true;
|
|
94
|
+
flush();
|
|
95
|
+
};
|
|
96
|
+
const onSettings = id => { if (id === threadId) emit('settings', client.context(id)); };
|
|
97
|
+
const onRequests = id => { if (id === threadId) emit('approvals', { requests: client.serverRequests.list(threadId) }); };
|
|
98
|
+
const onQueue = id => { if (id === threadId) emit('queue-changed', {}); };
|
|
99
|
+
const onDisconnected = () => { emit('bridge-error', { error: 'Codex 内核连接已断开,请重新连接。' }); response.end(); finish(); };
|
|
100
|
+
const onInvalidated = id => { if (id === threadId) emit('history-invalidated', {}); };
|
|
101
|
+
const onHistorySync = id => { if (id === threadId) publishHistorySync(); };
|
|
102
|
+
const onChatError = value => { if (value.threadId === threadId) emit('chat-error', value); };
|
|
103
|
+
response.once('close', () => { closed = true; finish(); });
|
|
104
|
+
client.on('turn', onTurn); client.on('settings', onSettings); client.on('disconnected', onDisconnected);
|
|
105
|
+
client.on('timeline-reset', onReset);
|
|
106
|
+
client.on('queue-changed', onQueue);
|
|
107
|
+
client.on('requests-changed', onRequests);
|
|
108
|
+
client.on('history-invalidated', onInvalidated);
|
|
109
|
+
client.on('history-sync', onHistorySync);
|
|
110
|
+
client.on('chat-error', onChatError);
|
|
111
|
+
publishHistorySync();
|
|
112
|
+
try {
|
|
113
|
+
const started = performance.now();
|
|
114
|
+
client.traceSync?.('stream-open', threadId, { requestedSeq: requestedSync?.after });
|
|
115
|
+
subscription = await client.acquire(threadId, requestedSync);
|
|
116
|
+
syncId = subscription.record.sync.id; epoch = subscription.record.sync.epoch;
|
|
117
|
+
if (!closed) {
|
|
118
|
+
const initial = structuredClone(subscription.record), catchup = subscription.catchup && structuredClone(subscription.catchup);
|
|
119
|
+
if (pendingReset?.sync.seq <= initial.sync.seq) pendingReset = undefined;
|
|
120
|
+
// Only discard notifications certified by the acquired snapshot/catchup.
|
|
121
|
+
for (const [id, change] of pending) if (change.sequence <= initial.sync.seq) pending.delete(id);
|
|
122
|
+
fromSeq = pending.size ? Math.max(fromSeq, initial.sync.seq + 1) : undefined;
|
|
123
|
+
if (!pending.size) toSeq = undefined;
|
|
124
|
+
tracePending = [...pending.values()].some(change => change.immediate);
|
|
125
|
+
const normalized = await normalize(subscription.confirmed ? { ...initial, thread: { ...initial.thread, turns: [] } } : initial);
|
|
126
|
+
const delivered = subscription.snapshot ? normalized : catchup ? await normalize(catchup) : null;
|
|
127
|
+
const sent = Boolean(delivered) && !closed;
|
|
128
|
+
if (sent) await emit('update', delivered);
|
|
129
|
+
// Confirm every handoff after its catchup/snapshot has been delivered.
|
|
130
|
+
// This also releases a cached selection with a valid, unchanged token;
|
|
131
|
+
// transport subscription alone does not confirm the message boundary.
|
|
132
|
+
if (!closed) await emit('sync-ready', {
|
|
133
|
+
thread: { ...normalized.thread, turns: [] }, sync: normalized.sync,
|
|
134
|
+
});
|
|
135
|
+
client.traceSync?.('stream-initial-sent', threadId, { ms: Math.round(performance.now() - started), closed,
|
|
136
|
+
snapshot: subscription.snapshot, catchup: Boolean(catchup), sent, seq: normalized.sync?.seq,
|
|
137
|
+
deliveredSync: sent ? delivered.sync : undefined,
|
|
138
|
+
messages: sent ? delivered.turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])) : [],
|
|
139
|
+
windowMessages: normalized.turns.flatMap(turn => turn.entries.map(entry => [turn.id, entry.id])) });
|
|
140
|
+
for (const turn of normalized.turns) if (!turn.cacheStub) sentTurns.set(turn.id, turn);
|
|
141
|
+
emit('queue-changed', {});
|
|
142
|
+
onRequests(threadId);
|
|
143
|
+
streamReady = true;
|
|
144
|
+
publishHistorySync();
|
|
145
|
+
if (!closed && typeof client.acquireActivity === 'function') {
|
|
146
|
+
releaseActivity = client.acquireActivity(threadId, value => emit('dots-activity', value));
|
|
147
|
+
}
|
|
148
|
+
flush();
|
|
149
|
+
}
|
|
150
|
+
await done;
|
|
151
|
+
} catch (error) { emit('bridge-error', { error: error.message }); response.end(); }
|
|
152
|
+
finally {
|
|
153
|
+
client.traceSync?.('stream-close', threadId, { ready: streamReady });
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
releaseActivity?.();
|
|
156
|
+
client.off('turn', onTurn); client.off('settings', onSettings); client.off('disconnected', onDisconnected);
|
|
157
|
+
client.off('timeline-reset', onReset);
|
|
158
|
+
client.off('queue-changed', onQueue);
|
|
159
|
+
client.off('requests-changed', onRequests);
|
|
160
|
+
client.off('history-invalidated', onInvalidated);
|
|
161
|
+
client.off('history-sync', onHistorySync);
|
|
162
|
+
client.off('chat-error', onChatError);
|
|
163
|
+
if (subscription) await subscription.release();
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/src/usage-reset.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export async function consumeUsageReset(client, body) {
|
|
2
|
-
if (typeof body?.idempotencyKey !== 'string' || !body.idempotencyKey.trim() || body.idempotencyKey.length > 200
|
|
3
|
-
|| typeof body.creditId !== 'string' || !body.creditId.trim() || body.creditId.length > 200) {
|
|
4
|
-
throw Object.assign(new Error('请选择重置卡并确认使用。'), { statusCode: 400, code: 'usage_reset_not_submitted' });
|
|
5
|
-
}
|
|
6
|
-
let usage;
|
|
7
|
-
try { usage = await client.usage(); }
|
|
8
|
-
catch (error) { throw Object.assign(new Error('用量尚未确认,请恢复连接后重新选择重置卡。', { cause: error }),
|
|
9
|
-
{ statusCode: 503, code: 'usage_reset_not_submitted' }); }
|
|
10
|
-
if ((usage.accountId || null) !== (body.accountId || null)) {
|
|
11
|
-
throw Object.assign(new Error('Codex 账号已变化,请重新打开用量详情。'), { statusCode: 409, code: 'usage_reset_not_submitted' });
|
|
12
|
-
}
|
|
13
|
-
// The core owns eligibility and the idempotent redemption outcome.
|
|
14
|
-
return client.request('account/rateLimitResetCredit/consume', {
|
|
15
|
-
idempotencyKey: body.idempotencyKey, creditId: body.creditId,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
1
|
+
export async function consumeUsageReset(client, body) {
|
|
2
|
+
if (typeof body?.idempotencyKey !== 'string' || !body.idempotencyKey.trim() || body.idempotencyKey.length > 200
|
|
3
|
+
|| typeof body.creditId !== 'string' || !body.creditId.trim() || body.creditId.length > 200) {
|
|
4
|
+
throw Object.assign(new Error('请选择重置卡并确认使用。'), { statusCode: 400, code: 'usage_reset_not_submitted' });
|
|
5
|
+
}
|
|
6
|
+
let usage;
|
|
7
|
+
try { usage = await client.usage(); }
|
|
8
|
+
catch (error) { throw Object.assign(new Error('用量尚未确认,请恢复连接后重新选择重置卡。', { cause: error }),
|
|
9
|
+
{ statusCode: 503, code: 'usage_reset_not_submitted' }); }
|
|
10
|
+
if ((usage.accountId || null) !== (body.accountId || null)) {
|
|
11
|
+
throw Object.assign(new Error('Codex 账号已变化,请重新打开用量详情。'), { statusCode: 409, code: 'usage_reset_not_submitted' });
|
|
12
|
+
}
|
|
13
|
+
// The core owns eligibility and the idempotent redemption outcome.
|
|
14
|
+
return client.request('account/rateLimitResetCredit/consume', {
|
|
15
|
+
idempotencyKey: body.idempotencyKey, creditId: body.creditId,
|
|
16
|
+
});
|
|
17
|
+
}
|