@walkhi/code-relax 0.1.0-beta.30 → 0.1.0-beta.31
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 +95 -89
- package/dist/bin/cleanup-accounts.mjs +63 -63
- package/dist/bin/codex-remote.mjs +374 -367
- package/dist/bin/manage-relay-admin.mjs +20 -20
- package/dist/bin/migrate-relay-db.mjs +109 -109
- package/dist/bin/publish-android-release.mjs +62 -58
- package/dist/bin/self-relay-admin.mjs +8 -8
- package/dist/bin/self-relay-demo.mjs +33 -33
- package/dist/bin/self-relay-server.mjs +66 -66
- package/dist/build-platform.json +1 -1
- package/dist/get-lan-networks.ps1 +40 -40
- package/dist/get-shared-service-url.ps1 +6 -6
- package/dist/shared/app-server-events.cjs +422 -412
- 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 +54 -54
- package/dist/shared/p2p-data-channel.cjs +72 -72
- package/dist/shared/p2p-diagnostics.cjs +28 -28
- package/dist/shared/p2p-file-channel.cjs +74 -74
- package/dist/shared/plan-pricing.cjs +32 -32
- package/dist/shared/question-attention.cjs +40 -0
- package/dist/shared/server-requests.cjs +219 -219
- package/dist/shared/timeline-sync.cjs +173 -173
- package/dist/skills/code-relax/SKILL.md +22 -22
- package/dist/src/archived-threads.mjs +22 -0
- package/dist/src/background-process.mjs +18 -18
- package/dist/src/bounded-process-log.mjs +63 -63
- package/dist/src/celsius-client.mjs +107 -107
- package/dist/src/code-browser.mjs +100 -100
- package/dist/src/desktop-launcher.mjs +46 -46
- 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 +461 -461
- package/dist/src/dots-message-images.mjs +319 -319
- package/dist/src/dots-profile.mjs +77 -77
- package/dist/src/dots-room-activity.mjs +94 -94
- 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/dots-task-files.mjs +52 -52
- package/dist/src/execution-health.mjs +26 -26
- 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 +265 -263
- package/dist/src/managed-app-server.mjs +244 -244
- package/dist/src/message-images.mjs +9 -9
- package/dist/src/message-upload-files.mjs +74 -74
- package/dist/src/onboarding.mjs +12 -12
- package/dist/src/plan-pricing.mjs +47 -47
- package/dist/src/platform/README.md +12 -12
- package/dist/src/platform/codex-protocol.mjs +46 -46
- package/dist/src/platform/codex-runtime.mjs +3 -3
- package/dist/src/platform/credential-store.mjs +3 -3
- package/dist/src/platform/desktop-host.mjs +3 -3
- package/dist/src/platform/desktop-project-registration.mjs +3 -3
- package/dist/src/platform/desktop-project-store.mjs +208 -149
- package/dist/src/platform/file-opener.mjs +9 -0
- package/dist/src/platform/linux/README.md +3 -3
- package/dist/src/platform/linux/codex-runtime.mjs +56 -0
- package/dist/src/platform/linux/credential-store.mjs +28 -0
- package/dist/src/platform/linux/desktop-host.mjs +83 -0
- package/dist/src/platform/linux/desktop-installation.mjs +36 -0
- package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
- package/dist/src/platform/linux/network-info.mjs +11 -0
- package/dist/src/platform/linux/process.mjs +42 -0
- package/dist/src/platform/linux/project-locations.mjs +17 -0
- package/dist/src/platform/linux/resident-startup.mjs +52 -0
- package/dist/src/platform/linux/retained-runtime.mjs +76 -0
- package/dist/src/platform/linux/service-host.mjs +18 -0
- package/dist/src/platform/linux/system.mjs +49 -0
- package/dist/src/platform/linux/user-environment.mjs +7 -0
- package/dist/src/platform/macos/README.md +3 -3
- package/dist/src/platform/macos/codex-runtime.mjs +125 -125
- package/dist/src/platform/macos/credential-store.mjs +10 -10
- package/dist/src/platform/macos/desktop-host.mjs +82 -82
- package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
- package/dist/src/platform/macos/network-info.mjs +8 -28
- package/dist/src/platform/macos/project-locations.mjs +8 -8
- package/dist/src/platform/macos/resident-startup.mjs +84 -84
- package/dist/src/platform/macos/retained-runtime.mjs +127 -127
- package/dist/src/platform/macos/service-host.mjs +18 -18
- package/dist/src/platform/macos/system.mjs +29 -29
- package/dist/src/platform/macos/user-environment.mjs +7 -7
- package/dist/src/platform/native-package.mjs +58 -46
- package/dist/src/platform/network-info.mjs +3 -3
- package/dist/src/platform/path-identity.mjs +5 -5
- package/dist/src/platform/posix/network-candidates.mjs +23 -0
- package/dist/src/platform/posix/runtime-paths.mjs +25 -25
- package/dist/src/platform/project-locations.mjs +3 -3
- package/dist/src/platform/runtime-paths.mjs +8 -8
- package/dist/src/platform/select.mjs +5 -5
- package/dist/src/platform/service-host.mjs +3 -3
- package/dist/src/platform/user-environment.mjs +3 -3
- package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
- package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
- package/dist/src/platform/windows/background-process.mjs +30 -30
- 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-helper.mjs +179 -179
- package/dist/src/platform/windows/desktop-host.mjs +58 -58
- package/dist/src/platform/windows/desktop-info.ps1 +14 -14
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
- 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/network-info.mjs +34 -34
- package/dist/src/platform/windows/project-locations.mjs +16 -16
- package/dist/src/platform/windows/resident-start.js +20 -20
- package/dist/src/platform/windows/resident-start.ps1 +7 -7
- package/dist/src/platform/windows/resident-startup.ps1 +83 -83
- package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
- package/dist/src/platform/windows/runtime-paths.mjs +20 -20
- package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
- package/dist/src/platform/windows/service-host.mjs +31 -31
- package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
- package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
- package/dist/src/platform/windows/user-environment.mjs +50 -50
- package/dist/src/platform/windows/user-environment.ps1 +41 -41
- package/dist/src/project-archive.mjs +30 -0
- package/dist/src/project-create.mjs +121 -119
- package/dist/src/project-management.mjs +59 -69
- package/dist/src/project-remove.mjs +55 -0
- package/dist/src/project-update.mjs +56 -0
- package/dist/src/runtime-discovery.mjs +47 -47
- package/dist/src/self-relay/account-cleanup.mjs +30 -30
- package/dist/src/self-relay/account-connections.mjs +52 -52
- package/dist/src/self-relay/admin-cloud.mjs +101 -101
- package/dist/src/self-relay/admin-page.mjs +80 -80
- package/dist/src/self-relay/admin-server.mjs +74 -74
- package/dist/src/self-relay/admin-state.mjs +127 -127
- package/dist/src/self-relay/admin-style.mjs +14 -14
- package/dist/src/self-relay/android-releases.mjs +61 -61
- package/dist/src/self-relay/client.mjs +61 -61
- package/dist/src/self-relay/connector.mjs +439 -438
- package/dist/src/self-relay/debug-allowlist.mjs +33 -33
- package/dist/src/self-relay/demo.mjs +37 -37
- package/dist/src/self-relay/device-info.mjs +27 -0
- package/dist/src/self-relay/device-store.mjs +92 -84
- 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/lan-authorizations.mjs +71 -42
- package/dist/src/self-relay/lifecycle.mjs +264 -250
- package/dist/src/self-relay/long-pairing.mjs +40 -40
- package/dist/src/self-relay/mysql-state.mjs +118 -105
- package/dist/src/self-relay/p2p-probe.mjs +159 -159
- package/dist/src/self-relay/privacy-page.mjs +26 -26
- package/dist/src/self-relay/public-releases.mjs +34 -0
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
- package/dist/src/self-relay/server.mjs +1006 -932
- package/dist/src/self-relay/usage-daily.mjs +55 -55
- package/dist/src/self-relay/usage-meter.mjs +141 -141
- package/dist/src/self-relay/windows-route-helper.mjs +75 -75
- package/dist/src/self-relay/wire.mjs +36 -36
- package/dist/src/server.mjs +1861 -1778
- package/dist/src/service-control.mjs +82 -82
- package/dist/src/service-doctor.mjs +110 -110
- package/dist/src/service-health.mjs +33 -33
- package/dist/src/service-install.mjs +91 -91
- package/dist/src/service-lifecycle.mjs +138 -138
- package/dist/src/service-restart.mjs +81 -81
- package/dist/src/shared-app-server.mjs +615 -611
- package/dist/src/shared-catalog.mjs +132 -132
- package/dist/src/shared-recovery.mjs +146 -146
- package/dist/src/shared-thread-stream.mjs +164 -164
- package/dist/src/skill-install.mjs +51 -51
- package/dist/src/thread-title-generation.mjs +150 -150
- package/dist/src/turn-model-history.mjs +47 -47
- package/dist/src/usage-reset.mjs +17 -17
- package/dist/src/workstation-downloads.mjs +120 -120
- package/dist/tools/rtc-route-helper.ps1 +99 -99
- package/dist/tools/start-rtc-route-helper.ps1 +15 -15
- package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
- package/dist/web/vendor/markdown-it.umd.min.js +13 -13
- package/package.json +7 -6
- package/tools/postinstall.mjs +127 -127
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
const itemTime = item => item.dotsReceipt?.sentAt ? Date.parse(item.dotsReceipt.sentAt)
|
|
2
|
-
: item.startedAtMs ?? item.receivedAtMs;
|
|
3
|
-
const terminal = status => ['resolved', 'expired'].includes(status);
|
|
4
|
-
|
|
5
|
-
// Approval records have their own official room source. Keep them with the
|
|
6
|
-
// account-scoped hosted cache, then place them in the chat's observed positions.
|
|
7
|
-
export class DotsApprovalTimeline {
|
|
8
|
-
constructor(server) { this.server = server; this.states = new Map(); }
|
|
9
|
-
|
|
10
|
-
async state(threadId) {
|
|
11
|
-
const scope = this.server.historyScope(threadId);
|
|
12
|
-
if (!scope) throw new Error('读取点点审批历史需要账号身份。');
|
|
13
|
-
let state = this.states.get(scope);
|
|
14
|
-
if (!state) {
|
|
15
|
-
state = { scope, threadId, epoch: this.server.epoch, records: new Map() };
|
|
16
|
-
state.ready = (async () => {
|
|
17
|
-
const saved = await this.server.historyCache?.get(scope, 'approval-records');
|
|
18
|
-
for (const record of saved?.data || []) {
|
|
19
|
-
// A display record never reactivates an old pending request.
|
|
20
|
-
state.records.set(this.key(record), terminal(record.status) ? record : { ...record, status: 'expired' });
|
|
21
|
-
}
|
|
22
|
-
})();
|
|
23
|
-
this.states.set(scope, state);
|
|
24
|
-
}
|
|
25
|
-
await state.ready;
|
|
26
|
-
this.check(threadId, state);
|
|
27
|
-
return state;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
check(threadId, state) {
|
|
31
|
-
if (state.scope !== this.server.historyScope(threadId) || state.epoch !== this.server.epoch) throw new Error('点点审批历史的账号连接已变化。');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
key(item) { return JSON.stringify([typeof item.requestId, item.requestId]); }
|
|
35
|
-
|
|
36
|
-
merge(state, incoming) {
|
|
37
|
-
let changed = false;
|
|
38
|
-
for (const item of incoming) {
|
|
39
|
-
const key = this.key(item), previous = state.records.get(key);
|
|
40
|
-
const merged = { ...previous, ...item, id: previous?.id || item.id,
|
|
41
|
-
receivedAtMs: previous?.receivedAtMs ?? item.receivedAtMs,
|
|
42
|
-
...(previous?.result && item.result == null ? { result: previous.result } : {}),
|
|
43
|
-
...(previous?.afterItemId ? { afterItemId: previous.afterItemId } : {}) };
|
|
44
|
-
if (JSON.stringify(previous) !== JSON.stringify(merged)) { state.records.set(key, merged); changed = true; }
|
|
45
|
-
}
|
|
46
|
-
return changed;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
save(state) {
|
|
50
|
-
return this.server.historyCache?.put(state.scope, 'approval-records', { data: [...state.records.values()] });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
targets(sourceThreadId) {
|
|
54
|
-
return [...this.states.values()].filter(state => state.scope === this.server.historyScope(state.threadId)
|
|
55
|
-
&& [...state.records.values()].some(record => record.params.threadId === sourceThreadId)).map(state => state.threadId);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async observe(threadId, records, turns) {
|
|
59
|
-
const state = await this.state(threadId);
|
|
60
|
-
const incoming = records.map(record => {
|
|
61
|
-
const turn = turns?.find(turn => turn.id === record.turnId);
|
|
62
|
-
const afterItemId = turn?.items.filter(item => item.type !== 'approval').at(-1)?.id;
|
|
63
|
-
return { ...record, ...(afterItemId ? { afterItemId } : {}) };
|
|
64
|
-
});
|
|
65
|
-
if (this.merge(state, incoming)) await this.save(state);
|
|
66
|
-
this.check(threadId, state);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
overlay(threadId, turns, range = {}) {
|
|
70
|
-
const state = this.states.get(this.server.historyScope(threadId));
|
|
71
|
-
if (!state) return turns;
|
|
72
|
-
const ids = new Set([...state.records.values()].map(item => item.id));
|
|
73
|
-
const result = turns.map(turn => ({ ...turn, items: turn.items.filter(item => item.type !== 'approval' || !ids.has(item.id)) }));
|
|
74
|
-
for (const approval of [...state.records.values()].sort((a, b) => a.receivedAtMs - b.receivedAtMs)) {
|
|
75
|
-
if (Number.isFinite(range.fromMs) && approval.receivedAtMs < range.fromMs
|
|
76
|
-
|| Number.isFinite(range.toMs) && approval.receivedAtMs > range.toMs) continue;
|
|
77
|
-
let turn = result.find(turn => turn.id === approval.roomMessageId || turn.id === approval.turnId);
|
|
78
|
-
if (!turn) {
|
|
79
|
-
if (!Number.isFinite(range.fromMs)) continue;
|
|
80
|
-
const positions = result.flatMap(turn => turn.items.map(item => ({ turn, item, time: itemTime(item) })))
|
|
81
|
-
.filter(position => Number.isFinite(position.time));
|
|
82
|
-
turn = positions.filter(position => position.time <= approval.receivedAtMs).at(-1)?.turn || positions[0]?.turn;
|
|
83
|
-
}
|
|
84
|
-
if (!turn) continue;
|
|
85
|
-
const after = turn.items.findIndex(item => item.id === approval.afterItemId);
|
|
86
|
-
if (after >= 0) {
|
|
87
|
-
let index = after + 1;
|
|
88
|
-
while (turn.items[index]?.type === 'approval' && turn.items[index].receivedAtMs <= approval.receivedAtMs) index++;
|
|
89
|
-
turn.items.splice(index, 0, approval);
|
|
90
|
-
} else {
|
|
91
|
-
const next = turn.items.findIndex(item => Number.isFinite(itemTime(item)) && itemTime(item) > approval.receivedAtMs);
|
|
92
|
-
turn.items.splice(next < 0 ? turn.items.length : next, 0, approval);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
clear() { this.states.clear(); }
|
|
99
|
-
}
|
|
1
|
+
const itemTime = item => item.dotsReceipt?.sentAt ? Date.parse(item.dotsReceipt.sentAt)
|
|
2
|
+
: item.startedAtMs ?? item.receivedAtMs;
|
|
3
|
+
const terminal = status => ['resolved', 'expired'].includes(status);
|
|
4
|
+
|
|
5
|
+
// Approval records have their own official room source. Keep them with the
|
|
6
|
+
// account-scoped hosted cache, then place them in the chat's observed positions.
|
|
7
|
+
export class DotsApprovalTimeline {
|
|
8
|
+
constructor(server) { this.server = server; this.states = new Map(); }
|
|
9
|
+
|
|
10
|
+
async state(threadId) {
|
|
11
|
+
const scope = this.server.historyScope(threadId);
|
|
12
|
+
if (!scope) throw new Error('读取点点审批历史需要账号身份。');
|
|
13
|
+
let state = this.states.get(scope);
|
|
14
|
+
if (!state) {
|
|
15
|
+
state = { scope, threadId, epoch: this.server.epoch, records: new Map() };
|
|
16
|
+
state.ready = (async () => {
|
|
17
|
+
const saved = await this.server.historyCache?.get(scope, 'approval-records');
|
|
18
|
+
for (const record of saved?.data || []) {
|
|
19
|
+
// A display record never reactivates an old pending request.
|
|
20
|
+
state.records.set(this.key(record), terminal(record.status) ? record : { ...record, status: 'expired' });
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
this.states.set(scope, state);
|
|
24
|
+
}
|
|
25
|
+
await state.ready;
|
|
26
|
+
this.check(threadId, state);
|
|
27
|
+
return state;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
check(threadId, state) {
|
|
31
|
+
if (state.scope !== this.server.historyScope(threadId) || state.epoch !== this.server.epoch) throw new Error('点点审批历史的账号连接已变化。');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
key(item) { return JSON.stringify([typeof item.requestId, item.requestId]); }
|
|
35
|
+
|
|
36
|
+
merge(state, incoming) {
|
|
37
|
+
let changed = false;
|
|
38
|
+
for (const item of incoming) {
|
|
39
|
+
const key = this.key(item), previous = state.records.get(key);
|
|
40
|
+
const merged = { ...previous, ...item, id: previous?.id || item.id,
|
|
41
|
+
receivedAtMs: previous?.receivedAtMs ?? item.receivedAtMs,
|
|
42
|
+
...(previous?.result && item.result == null ? { result: previous.result } : {}),
|
|
43
|
+
...(previous?.afterItemId ? { afterItemId: previous.afterItemId } : {}) };
|
|
44
|
+
if (JSON.stringify(previous) !== JSON.stringify(merged)) { state.records.set(key, merged); changed = true; }
|
|
45
|
+
}
|
|
46
|
+
return changed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
save(state) {
|
|
50
|
+
return this.server.historyCache?.put(state.scope, 'approval-records', { data: [...state.records.values()] });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
targets(sourceThreadId) {
|
|
54
|
+
return [...this.states.values()].filter(state => state.scope === this.server.historyScope(state.threadId)
|
|
55
|
+
&& [...state.records.values()].some(record => record.params.threadId === sourceThreadId)).map(state => state.threadId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async observe(threadId, records, turns) {
|
|
59
|
+
const state = await this.state(threadId);
|
|
60
|
+
const incoming = records.map(record => {
|
|
61
|
+
const turn = turns?.find(turn => turn.id === record.turnId);
|
|
62
|
+
const afterItemId = turn?.items.filter(item => item.type !== 'approval').at(-1)?.id;
|
|
63
|
+
return { ...record, ...(afterItemId ? { afterItemId } : {}) };
|
|
64
|
+
});
|
|
65
|
+
if (this.merge(state, incoming)) await this.save(state);
|
|
66
|
+
this.check(threadId, state);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
overlay(threadId, turns, range = {}) {
|
|
70
|
+
const state = this.states.get(this.server.historyScope(threadId));
|
|
71
|
+
if (!state) return turns;
|
|
72
|
+
const ids = new Set([...state.records.values()].map(item => item.id));
|
|
73
|
+
const result = turns.map(turn => ({ ...turn, items: turn.items.filter(item => item.type !== 'approval' || !ids.has(item.id)) }));
|
|
74
|
+
for (const approval of [...state.records.values()].sort((a, b) => a.receivedAtMs - b.receivedAtMs)) {
|
|
75
|
+
if (Number.isFinite(range.fromMs) && approval.receivedAtMs < range.fromMs
|
|
76
|
+
|| Number.isFinite(range.toMs) && approval.receivedAtMs > range.toMs) continue;
|
|
77
|
+
let turn = result.find(turn => turn.id === approval.roomMessageId || turn.id === approval.turnId);
|
|
78
|
+
if (!turn) {
|
|
79
|
+
if (!Number.isFinite(range.fromMs)) continue;
|
|
80
|
+
const positions = result.flatMap(turn => turn.items.map(item => ({ turn, item, time: itemTime(item) })))
|
|
81
|
+
.filter(position => Number.isFinite(position.time));
|
|
82
|
+
turn = positions.filter(position => position.time <= approval.receivedAtMs).at(-1)?.turn || positions[0]?.turn;
|
|
83
|
+
}
|
|
84
|
+
if (!turn) continue;
|
|
85
|
+
const after = turn.items.findIndex(item => item.id === approval.afterItemId);
|
|
86
|
+
if (after >= 0) {
|
|
87
|
+
let index = after + 1;
|
|
88
|
+
while (turn.items[index]?.type === 'approval' && turn.items[index].receivedAtMs <= approval.receivedAtMs) index++;
|
|
89
|
+
turn.items.splice(index, 0, approval);
|
|
90
|
+
} else {
|
|
91
|
+
const next = turn.items.findIndex(item => Number.isFinite(itemTime(item)) && itemTime(item) > approval.receivedAtMs);
|
|
92
|
+
turn.items.splice(next < 0 ? turn.items.length : next, 0, approval);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clear() { this.states.clear(); }
|
|
99
|
+
}
|
package/dist/src/dots-avatar.mjs
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import sharp from 'sharp';
|
|
2
|
-
|
|
3
|
-
// Resolve Desktop's saved snapshot on the workstation. Signed URLs never go to the phone.
|
|
4
|
-
export function createDotsAvatarReader(localServer) {
|
|
5
|
-
const cache = new Map();
|
|
6
|
-
return async (profile, identity) => {
|
|
7
|
-
if (!profile.avatarFileId) return '';
|
|
8
|
-
const key = JSON.stringify([identity.accountId, identity.userId, profile.avatarFileId]);
|
|
9
|
-
if (!cache.has(key)) {
|
|
10
|
-
const work = (async () => {
|
|
11
|
-
const auth = await localServer.request('getAuthStatus', { includeToken: true, refreshToken: false });
|
|
12
|
-
const principal = JSON.parse(Buffer.from(auth.authToken.split('.')[1], 'base64url'))['https://api.openai.com/auth'];
|
|
13
|
-
if (principal?.chatgpt_account_id !== identity.accountId || principal.chatgpt_user_id !== identity.userId) {
|
|
14
|
-
throw new Error('头像账号已变化,请刷新对话。');
|
|
15
|
-
}
|
|
16
|
-
const response = await fetch(`https://chatgpt.com/backend-api/files/download/${encodeURIComponent(profile.avatarFileId)}?download_intent=false&inline=true`, {
|
|
17
|
-
headers: { Authorization: `Bearer ${auth.authToken}`, 'ChatGPT-Account-Id': identity.accountId },
|
|
18
|
-
redirect: 'error', signal: AbortSignal.timeout(15000),
|
|
19
|
-
});
|
|
20
|
-
if (!response.ok) { await response.body?.cancel(); throw new Error(`点点头像资料读取失败(${response.status})`); }
|
|
21
|
-
const { download_url: downloadUrl } = await response.json();
|
|
22
|
-
const url = new URL(downloadUrl);
|
|
23
|
-
const authenticated = url.origin === 'https://chatgpt.com' && url.pathname === '/backend-api/estuary/content';
|
|
24
|
-
if (url.protocol !== 'https:' || !(authenticated || url.hostname.endsWith('.oaiusercontent.com'))) throw new Error('点点头像下载地址无效。');
|
|
25
|
-
const image = await fetch(url, { redirect: 'error', signal: AbortSignal.timeout(15000),
|
|
26
|
-
...(authenticated ? { headers: { Authorization: `Bearer ${auth.authToken}`, 'ChatGPT-Account-Id': identity.accountId } } : {}) });
|
|
27
|
-
if (!image.ok) { await image.body?.cancel(); throw new Error(`点点头像下载失败(${image.status})`); }
|
|
28
|
-
const chunks = []; let size = 0;
|
|
29
|
-
for await (const chunk of image.body) {
|
|
30
|
-
size += chunk.length;
|
|
31
|
-
if (size > 4 * 1024 * 1024) throw new Error('点点头像超过 4 MiB。');
|
|
32
|
-
chunks.push(chunk);
|
|
33
|
-
}
|
|
34
|
-
const thumbnail = await sharp(Buffer.concat(chunks)).resize(96, 96, { fit: 'inside', withoutEnlargement: true }).webp().toBuffer();
|
|
35
|
-
return `data:image/webp;base64,${thumbnail.toString('base64')}`;
|
|
36
|
-
})();
|
|
37
|
-
cache.set(key, work);
|
|
38
|
-
work.catch(() => { if (cache.get(key) === work) cache.delete(key); });
|
|
39
|
-
}
|
|
40
|
-
return cache.get(key);
|
|
41
|
-
};
|
|
42
|
-
}
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
|
|
3
|
+
// Resolve Desktop's saved snapshot on the workstation. Signed URLs never go to the phone.
|
|
4
|
+
export function createDotsAvatarReader(localServer) {
|
|
5
|
+
const cache = new Map();
|
|
6
|
+
return async (profile, identity) => {
|
|
7
|
+
if (!profile.avatarFileId) return '';
|
|
8
|
+
const key = JSON.stringify([identity.accountId, identity.userId, profile.avatarFileId]);
|
|
9
|
+
if (!cache.has(key)) {
|
|
10
|
+
const work = (async () => {
|
|
11
|
+
const auth = await localServer.request('getAuthStatus', { includeToken: true, refreshToken: false });
|
|
12
|
+
const principal = JSON.parse(Buffer.from(auth.authToken.split('.')[1], 'base64url'))['https://api.openai.com/auth'];
|
|
13
|
+
if (principal?.chatgpt_account_id !== identity.accountId || principal.chatgpt_user_id !== identity.userId) {
|
|
14
|
+
throw new Error('头像账号已变化,请刷新对话。');
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(`https://chatgpt.com/backend-api/files/download/${encodeURIComponent(profile.avatarFileId)}?download_intent=false&inline=true`, {
|
|
17
|
+
headers: { Authorization: `Bearer ${auth.authToken}`, 'ChatGPT-Account-Id': identity.accountId },
|
|
18
|
+
redirect: 'error', signal: AbortSignal.timeout(15000),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) { await response.body?.cancel(); throw new Error(`点点头像资料读取失败(${response.status})`); }
|
|
21
|
+
const { download_url: downloadUrl } = await response.json();
|
|
22
|
+
const url = new URL(downloadUrl);
|
|
23
|
+
const authenticated = url.origin === 'https://chatgpt.com' && url.pathname === '/backend-api/estuary/content';
|
|
24
|
+
if (url.protocol !== 'https:' || !(authenticated || url.hostname.endsWith('.oaiusercontent.com'))) throw new Error('点点头像下载地址无效。');
|
|
25
|
+
const image = await fetch(url, { redirect: 'error', signal: AbortSignal.timeout(15000),
|
|
26
|
+
...(authenticated ? { headers: { Authorization: `Bearer ${auth.authToken}`, 'ChatGPT-Account-Id': identity.accountId } } : {}) });
|
|
27
|
+
if (!image.ok) { await image.body?.cancel(); throw new Error(`点点头像下载失败(${image.status})`); }
|
|
28
|
+
const chunks = []; let size = 0;
|
|
29
|
+
for await (const chunk of image.body) {
|
|
30
|
+
size += chunk.length;
|
|
31
|
+
if (size > 4 * 1024 * 1024) throw new Error('点点头像超过 4 MiB。');
|
|
32
|
+
chunks.push(chunk);
|
|
33
|
+
}
|
|
34
|
+
const thumbnail = await sharp(Buffer.concat(chunks)).resize(96, 96, { fit: 'inside', withoutEnlargement: true }).webp().toBuffer();
|
|
35
|
+
return `data:image/webp;base64,${thumbnail.toString('base64')}`;
|
|
36
|
+
})();
|
|
37
|
+
cache.set(key, work);
|
|
38
|
+
work.catch(() => { if (cache.get(key) === work) cache.delete(key); });
|
|
39
|
+
}
|
|
40
|
+
return cache.get(key);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { dotsImageReference } from './dots-message-images.mjs';
|
|
3
|
-
|
|
4
|
-
const nonempty = value => typeof value === 'string' && value.trim().length > 0;
|
|
5
|
-
|
|
6
|
-
export function dotsChatRevision(turns) {
|
|
7
|
-
const latest = dotsChatTailRevision(turns);
|
|
8
|
-
const message = dotsChatMessageRevision(turns);
|
|
9
|
-
// A message can keep streaming while a later agent action is already visible.
|
|
10
|
-
return !latest || latest === message ? latest
|
|
11
|
-
: createHash('sha256').update(JSON.stringify([latest, message])).digest('hex');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// A bounded disk-head probe compares the same last visible item on both sides.
|
|
15
|
-
// The full transcript revision also tracks an earlier streaming message.
|
|
16
|
-
export function dotsChatTailRevision(turns) {
|
|
17
|
-
return chatItemRevision(turns.at(-1)?.items.at(-1));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function dotsChatMessageRevision(turns) {
|
|
21
|
-
for (let turnIndex = turns.length - 1; turnIndex >= 0; turnIndex--) {
|
|
22
|
-
const items = turns[turnIndex].items;
|
|
23
|
-
for (let itemIndex = items.length - 1; itemIndex >= 0; itemIndex--) {
|
|
24
|
-
const item = items[itemIndex];
|
|
25
|
-
if (item.type === 'userMessage' || item.type === 'agentMessage') return chatItemRevision(item);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
// A recent window containing only agent actions cannot acknowledge a chat message.
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function chatItemRevision(item) {
|
|
33
|
-
if (!item) return '';
|
|
34
|
-
const content = item.type === 'userMessage' ? item.content.map(part => {
|
|
35
|
-
const reference = dotsImageReference(part);
|
|
36
|
-
if (!reference?.url) return part;
|
|
37
|
-
// Renewing an official storage signature does not create a new message.
|
|
38
|
-
const url = new URL(reference.url);
|
|
39
|
-
return { ...part, url: url.origin + url.pathname };
|
|
40
|
-
}) : item.type === 'subAgentActivity' ? [item.kind, item.agentThreadId, item.agentPath]
|
|
41
|
-
: [item.text || '', item.attachments || []];
|
|
42
|
-
// Identity and displayed content survive reconnects; internal work does not affect this marker.
|
|
43
|
-
return createHash('sha256').update(JSON.stringify([item.id, item.type, content])).digest('hex');
|
|
44
|
-
}
|
|
45
|
-
// Execution events remain separate from room user/agent messages.
|
|
46
|
-
export function projectDotsChatItem(item) {
|
|
47
|
-
if (item?.type !== 'subAgentActivity' || !nonempty(item.id) || !nonempty(item.agentThreadId)
|
|
48
|
-
|| !['started', 'interacted', 'completed', 'interrupted'].includes(item.kind)) return null;
|
|
49
|
-
return { type: item.type, id: item.id, kind: item.kind, agentThreadId: item.agentThreadId, agentPath: item.agentPath || '' };
|
|
50
|
-
}
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { dotsImageReference } from './dots-message-images.mjs';
|
|
3
|
+
|
|
4
|
+
const nonempty = value => typeof value === 'string' && value.trim().length > 0;
|
|
5
|
+
|
|
6
|
+
export function dotsChatRevision(turns) {
|
|
7
|
+
const latest = dotsChatTailRevision(turns);
|
|
8
|
+
const message = dotsChatMessageRevision(turns);
|
|
9
|
+
// A message can keep streaming while a later agent action is already visible.
|
|
10
|
+
return !latest || latest === message ? latest
|
|
11
|
+
: createHash('sha256').update(JSON.stringify([latest, message])).digest('hex');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A bounded disk-head probe compares the same last visible item on both sides.
|
|
15
|
+
// The full transcript revision also tracks an earlier streaming message.
|
|
16
|
+
export function dotsChatTailRevision(turns) {
|
|
17
|
+
return chatItemRevision(turns.at(-1)?.items.at(-1));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function dotsChatMessageRevision(turns) {
|
|
21
|
+
for (let turnIndex = turns.length - 1; turnIndex >= 0; turnIndex--) {
|
|
22
|
+
const items = turns[turnIndex].items;
|
|
23
|
+
for (let itemIndex = items.length - 1; itemIndex >= 0; itemIndex--) {
|
|
24
|
+
const item = items[itemIndex];
|
|
25
|
+
if (item.type === 'userMessage' || item.type === 'agentMessage') return chatItemRevision(item);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// A recent window containing only agent actions cannot acknowledge a chat message.
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function chatItemRevision(item) {
|
|
33
|
+
if (!item) return '';
|
|
34
|
+
const content = item.type === 'userMessage' ? item.content.map(part => {
|
|
35
|
+
const reference = dotsImageReference(part);
|
|
36
|
+
if (!reference?.url) return part;
|
|
37
|
+
// Renewing an official storage signature does not create a new message.
|
|
38
|
+
const url = new URL(reference.url);
|
|
39
|
+
return { ...part, url: url.origin + url.pathname };
|
|
40
|
+
}) : item.type === 'subAgentActivity' ? [item.kind, item.agentThreadId, item.agentPath]
|
|
41
|
+
: [item.text || '', item.attachments || []];
|
|
42
|
+
// Identity and displayed content survive reconnects; internal work does not affect this marker.
|
|
43
|
+
return createHash('sha256').update(JSON.stringify([item.id, item.type, content])).digest('hex');
|
|
44
|
+
}
|
|
45
|
+
// Execution events remain separate from room user/agent messages.
|
|
46
|
+
export function projectDotsChatItem(item) {
|
|
47
|
+
if (item?.type !== 'subAgentActivity' || !nonempty(item.id) || !nonempty(item.agentThreadId)
|
|
48
|
+
|| !['started', 'interacted', 'completed', 'interrupted'].includes(item.kind)) return null;
|
|
49
|
+
return { type: item.type, id: item.id, kind: item.kind, agentThreadId: item.agentThreadId, agentPath: item.agentPath || '' };
|
|
50
|
+
}
|