@walkhi/code-relax 0.1.0-beta.1 → 0.1.0-beta.11
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 +53 -64
- package/dist/bin/codex-remote.mjs +90 -71
- package/dist/bin/self-relay-admin.mjs +8 -0
- package/dist/bin/self-relay-demo.mjs +1 -1
- package/dist/bin/self-relay-server.mjs +5 -2
- package/dist/shared/app-server-events.cjs +64 -24
- package/dist/shared/file-mentions.cjs +35 -0
- package/dist/shared/p2p-data-channel.cjs +50 -0
- package/dist/skills/code-relax/SKILL.md +1 -1
- package/dist/src/desktop-launcher.mjs +16 -21
- package/dist/src/file-upload-session.mjs +124 -0
- package/dist/src/lan-socket-server.mjs +59 -10
- package/dist/src/managed-app-server.mjs +36 -14
- package/dist/src/message-images.mjs +0 -18
- package/dist/src/onboarding.mjs +6 -8
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/windows/IsolatedProcess.cs +7 -3
- package/dist/src/platform/windows/background-process.mjs +7 -2
- package/dist/src/platform/windows/codex-runtime.mjs +117 -0
- package/dist/src/platform/windows/desktop-host.mjs +17 -3
- package/dist/src/platform/windows/desktop-info.ps1 +2 -4
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
- package/dist/src/platform/windows/launch-worker.mjs +42 -0
- package/dist/src/platform/windows/resident-startup.ps1 +58 -18
- package/dist/src/platform/windows/service-host.mjs +4 -16
- package/dist/src/platform/windows/start-hidden-console.ps1 +18 -8
- package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
- package/dist/src/runtime-discovery.mjs +43 -0
- package/dist/src/self-relay/admin-cloud.mjs +101 -0
- package/dist/src/self-relay/admin-page.mjs +71 -0
- package/dist/src/self-relay/admin-server.mjs +61 -0
- package/dist/src/self-relay/admin-state.mjs +122 -0
- package/dist/src/self-relay/client.mjs +2 -0
- package/dist/src/self-relay/connector.mjs +107 -20
- package/dist/src/self-relay/demo.mjs +4 -5
- package/dist/src/self-relay/lifecycle.mjs +58 -70
- package/dist/src/self-relay/p2p-probe.mjs +123 -83
- package/dist/src/self-relay/server.mjs +224 -29
- package/dist/src/server.mjs +616 -1299
- package/dist/src/service-doctor.mjs +14 -26
- package/dist/src/service-install.mjs +1 -4
- package/dist/src/service-lifecycle.mjs +12 -12
- package/dist/src/shared-app-server.mjs +206 -46
- package/dist/src/shared-catalog.mjs +5 -25
- package/dist/src/shared-recovery.mjs +11 -9
- package/dist/src/shared-thread-stream.mjs +1 -1
- package/dist/src/skill-install.mjs +0 -13
- package/package.json +16 -18
- package/tools/postinstall.mjs +140 -3
- package/dist/src/app-server-client.mjs +0 -468
- package/dist/src/app-server-tasks.mjs +0 -355
- package/dist/src/debug-ui.mjs +0 -131
- package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
- package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
- package/dist/src/platform/windows/desktop-tools.mjs +0 -48
- package/dist/src/platform/windows/launch-worker.ps1 +0 -13
- package/dist/src/thread-catalog.mjs +0 -42
- package/dist/tools/find-desktop-pipe.mjs +0 -10
- package/dist/web/approval-ui.js +0 -188
- package/dist/web/capabilities.js +0 -57
- package/dist/web/chat-transport.js +0 -196
- package/dist/web/chat.css +0 -736
- package/dist/web/chat.js +0 -5149
- package/dist/web/client-platform.js +0 -11
- package/dist/web/harmony-platform.js +0 -51
- package/dist/web/highlight-powershell.min.js +0 -39
- package/dist/web/highlight.min.js +0 -1262
- package/dist/web/history-cache.js +0 -146
- package/dist/web/index.html +0 -222
- package/dist/web/lan-session.js +0 -122
- package/dist/web/p2p-diagnostics.js +0 -28
- package/dist/web/p2p-probe.js +0 -83
- package/dist/web/resources.json +0 -1
- package/dist/web/self-relay-session.js +0 -119
- package/dist/web/server-requests.js +0 -67
- package/dist/web/timeline-sync.js +0 -67
- package/dist/web/vendor/README.md +0 -14
- package/dist/web/vendor/highlight-powershell.min.js +0 -39
- package/dist/web/vendor/highlight.LICENSE.txt +0 -29
- package/dist/web/vendor/highlight.min.js +0 -1262
package/dist/web/p2p-probe.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
(function (root) {
|
|
2
|
-
// Runs only a bounded connectivity probe. Business frames stay on native WSS.
|
|
3
|
-
root.CodeRelaxP2pProbe = class {
|
|
4
|
-
constructor(send, report) {
|
|
5
|
-
this.send = send; this.report = report; this.closed = false; this.queue = Promise.resolve();
|
|
6
|
-
this.diagnostics = new root.CodeRelaxP2pDiagnostics();
|
|
7
|
-
}
|
|
8
|
-
start() {
|
|
9
|
-
if (typeof root.RTCPeerConnection !== 'function' || !root.crypto?.getRandomValues) {
|
|
10
|
-
this.close('unsupported'); return;
|
|
11
|
-
}
|
|
12
|
-
this.id = Array.from(crypto.getRandomValues(new Uint8Array(16)), n => n.toString(16).padStart(2, '0')).join('');
|
|
13
|
-
this.nonce = this.id;
|
|
14
|
-
this.timer = setTimeout(() => this.close('timeout'), 15000);
|
|
15
|
-
this.report('negotiating'); this.signal({ kind: 'start' });
|
|
16
|
-
}
|
|
17
|
-
createPeer(iceServers) {
|
|
18
|
-
const peer = this.peer = new RTCPeerConnection({ iceServers });
|
|
19
|
-
peer.onicecandidate = event => {
|
|
20
|
-
if (event.candidate && !this.closed) this.signal({ kind: 'candidate', candidate: event.candidate.candidate, mid: event.candidate.sdpMid || '0' });
|
|
21
|
-
if (event.candidate && !this.closed) this.diagnostics.candidate('local', event.candidate.candidate);
|
|
22
|
-
};
|
|
23
|
-
peer.oniceconnectionstatechange = () => { if (!this.closed) this.diagnostics.ice = peer.iceConnectionState; };
|
|
24
|
-
peer.onicegatheringstatechange = () => { if (!this.closed) this.diagnostics.gathering = peer.iceGatheringState; };
|
|
25
|
-
peer.onicecandidateerror = () => { if (!this.closed) this.diagnostics.error = 'stun'; };
|
|
26
|
-
peer.onconnectionstatechange = () => {
|
|
27
|
-
if (['failed', 'closed', 'disconnected'].includes(peer.connectionState)) this.close('failed');
|
|
28
|
-
};
|
|
29
|
-
peer.ondatachannel = event => {
|
|
30
|
-
if (this.channel || event.channel.label !== 'code-relax-probe-v1') { event.channel.close(); this.close('failed'); return; }
|
|
31
|
-
const channel = this.channel = event.channel;
|
|
32
|
-
channel.onopen = () => { if (!this.closed) { this.diagnostics.open = true; channel.send(JSON.stringify({ type: 'ping', nonce: this.nonce })); } };
|
|
33
|
-
channel.onerror = () => { this.diagnostics.error = 'channel'; this.close('failed'); };
|
|
34
|
-
channel.onclose = () => this.close('closed');
|
|
35
|
-
let received = 0;
|
|
36
|
-
channel.onmessage = event => {
|
|
37
|
-
if (this.closed) return;
|
|
38
|
-
try {
|
|
39
|
-
if (typeof event.data !== 'string' || event.data.length > 4096 || ++received > 4) throw new Error();
|
|
40
|
-
const message = JSON.parse(event.data);
|
|
41
|
-
if (message.type === 'ping' && /^[\w-]{16,128}$/.test(message.nonce)) {
|
|
42
|
-
channel.send(JSON.stringify({ type: 'pong', nonce: message.nonce })); this.pingReceived = true; this.diagnostics.ping = true;
|
|
43
|
-
} else if (message.type === 'pong' && message.nonce === this.nonce) { this.pongReceived = true; this.diagnostics.pong = true; }
|
|
44
|
-
else throw new Error();
|
|
45
|
-
if (this.pingReceived && this.pongReceived) {
|
|
46
|
-
this.report('verified', this.diagnostics.snapshot('verified')); clearTimeout(this.timer);
|
|
47
|
-
// Allow the remote peer to receive the final pong before closing.
|
|
48
|
-
this.timer = setTimeout(() => this.close('complete'), 1000);
|
|
49
|
-
}
|
|
50
|
-
} catch { this.diagnostics.error = 'frame'; this.close('failed'); }
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
signal(value) { this.send({ type: 'p2p-probe', version: 1, probeId: this.id, ...value }); }
|
|
55
|
-
receive(message) {
|
|
56
|
-
if (this.closed || message.probeId !== this.id || message.version !== 1) return;
|
|
57
|
-
this.queue = this.queue.then(async () => {
|
|
58
|
-
if (this.closed) return;
|
|
59
|
-
if (message.kind === 'offer' && !this.offerReceived) {
|
|
60
|
-
this.offerReceived = true;
|
|
61
|
-
this.createPeer((message.iceServers || []).map(url => ({ urls: url })));
|
|
62
|
-
await this.peer.setRemoteDescription({ type: 'offer', sdp: message.sdp });
|
|
63
|
-
this.diagnostics.offer = true;
|
|
64
|
-
const answer = await this.peer.createAnswer();
|
|
65
|
-
if (this.closed) return;
|
|
66
|
-
await this.peer.setLocalDescription(answer);
|
|
67
|
-
this.diagnostics.answer = true;
|
|
68
|
-
if (!this.closed) this.signal({ kind: 'answer', sdp: answer.sdp });
|
|
69
|
-
} else if (message.kind === 'candidate') {
|
|
70
|
-
this.diagnostics.candidate('remote', message.candidate);
|
|
71
|
-
await this.peer.addIceCandidate({ candidate: message.candidate, sdpMid: message.mid });
|
|
72
|
-
} else this.close('failed');
|
|
73
|
-
}).catch(() => { this.diagnostics.error = message.kind === 'candidate' ? 'candidate' : 'description'; this.close('failed'); });
|
|
74
|
-
}
|
|
75
|
-
close(state = 'closed') {
|
|
76
|
-
if (this.closed) return;
|
|
77
|
-
this.closed = true; clearTimeout(this.timer);
|
|
78
|
-
this.channel?.close(); this.peer?.close();
|
|
79
|
-
if (this.id) this.signal({ kind: 'close' });
|
|
80
|
-
if (!(this.pingReceived && this.pongReceived)) this.report(state, this.diagnostics.snapshot(state));
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
})(globalThis);
|
package/dist/web/resources.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
["server-requests.js","approval-ui.js","chat.css","highlight.min.js","highlight-powershell.min.js","capabilities.js","timeline-sync.js","harmony-platform.js","client-platform.js","lan-session.js","history-cache.js","p2p-diagnostics.js","p2p-probe.js","self-relay-session.js","chat-transport.js","chat.js"]
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
(function (root) {
|
|
2
|
-
let socketSerial = 0;
|
|
3
|
-
const nativeViews = new Map();
|
|
4
|
-
root.computerConnectionEvent = (key, event, value) => nativeViews.get(key)?.emit(event, JSON.parse(value));
|
|
5
|
-
class NativeView extends EventTarget {
|
|
6
|
-
constructor() {
|
|
7
|
-
super(); this.key = `${Date.now()}-${++socketSerial}`; this.readyState = 0; nativeViews.set(this.key, this);
|
|
8
|
-
queueMicrotask(() => { if (this.readyState === 0) { this.readyState = 1; this.emit('open', {}); } });
|
|
9
|
-
}
|
|
10
|
-
emit(type, value) {
|
|
11
|
-
if (this.readyState === 3) return;
|
|
12
|
-
if (type === 'message' && value.type === 'p2p-probe') { this.probe?.receive(value); return; }
|
|
13
|
-
if (type === 'message' && value.type === 'p2p-restart') { this.startProbe(); return; }
|
|
14
|
-
if (type === 'close') this.probe?.close();
|
|
15
|
-
if (type === 'close') { this.readyState = 3; nativeViews.delete(this.key); }
|
|
16
|
-
const event = type === 'message' ? new MessageEvent(type, { data: JSON.stringify(value) }) : new Event(type);
|
|
17
|
-
if (type === 'close') Object.defineProperty(event, 'code', { value: value.code });
|
|
18
|
-
this.dispatchEvent(event); this[`on${type}`]?.(event);
|
|
19
|
-
}
|
|
20
|
-
// WebRTC is diagnostic-only. Never start it on connection readiness; the
|
|
21
|
-
// native diagnostics dialog explicitly emits p2p-restart for one test.
|
|
22
|
-
startProbe() {
|
|
23
|
-
this.probe?.close('replaced');
|
|
24
|
-
this.probe = new root.CodeRelaxP2pProbe(
|
|
25
|
-
signal => root.codexNative.computerTransport(JSON.stringify({ type: 'p2p-probe', key: this.key, payload: JSON.stringify(signal) })),
|
|
26
|
-
(state, diagnostic) => {
|
|
27
|
-
if (diagnostic) root.codexNative.computerTransport(JSON.stringify({ type: 'p2p-diagnostic', key: this.key, payload: diagnostic }));
|
|
28
|
-
root.codexNative.computerTransport(JSON.stringify({ type: 'p2p-status', key: this.key, payload: state }));
|
|
29
|
-
});
|
|
30
|
-
try { this.probe.start(); }
|
|
31
|
-
catch { this.probe.diagnostics.error = 'initialization'; this.probe.close('failed'); }
|
|
32
|
-
}
|
|
33
|
-
send(value) {
|
|
34
|
-
const frame = JSON.parse(value);
|
|
35
|
-
if (frame.type === 'data') root.codexNative.computerTransport(JSON.stringify({ type: 'send', key: this.key, payload: frame.payload }));
|
|
36
|
-
else root.codexNative.computerTransport(JSON.stringify({ type: 'attach', key: this.key }));
|
|
37
|
-
}
|
|
38
|
-
close() {
|
|
39
|
-
this.probe?.close();
|
|
40
|
-
root.codexNative.computerTransport(JSON.stringify({ type: 'detach', key: this.key }));
|
|
41
|
-
this.emit('close', { code: 1000 });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Reuse LAN request, subscription and disconnect semantics; replace only the envelope.
|
|
45
|
-
root.CodexSelfRelaySession = class extends root.CodexLanSession {
|
|
46
|
-
constructor() { super(''); this.config = root.codexSelfRelayConfig; }
|
|
47
|
-
createSocket() { return root.codexNative?.computerTransport ? new NativeView() : super.createSocket(); }
|
|
48
|
-
connect() { return this.terminalError ? Promise.reject(this.terminalError) : super.connect(); }
|
|
49
|
-
socketUrl() {
|
|
50
|
-
const url = new URL(this.config.url);
|
|
51
|
-
if ((url.protocol !== 'wss:' && !(url.protocol === 'ws:' && url.hostname === '127.0.0.1')) ||
|
|
52
|
-
url.pathname !== '/relay' || url.search || url.hash || url.username || url.password) throw new Error('无效的Code Relax 中继地址');
|
|
53
|
-
return url.href;
|
|
54
|
-
}
|
|
55
|
-
authenticationMessage() {
|
|
56
|
-
const { room, code, credential, clientId } = this.config;
|
|
57
|
-
return code ? { type: 'pair', room, code, clientId, ...(credential ? { credential } : {}) } : { type: 'resume', room, credential, clientId };
|
|
58
|
-
}
|
|
59
|
-
closeError(event) {
|
|
60
|
-
const reasons = { 4401: '设备授权不存在或已撤销,请重新扫码授权', 4408: '配对码已过期或使用,请生成新的首次授权码',
|
|
61
|
-
4406: 'Code Relax 中继协议需要升级,请更新工作站和手机', 4409: '连接已由同一设备的新连接接替', 4410: '工作站暂时离线,正在等待恢复,设备授权仍保留',
|
|
62
|
-
1011: '工作站 Bridge 暂时不可用,正在恢复连接' };
|
|
63
|
-
const error = new TypeError(reasons[event.code] || `Code Relax 中继连接已断开(${event.code}),正在恢复`);
|
|
64
|
-
error.code = event.code;
|
|
65
|
-
if (event.code === 4401) root.codexHistoryCache?.clear().catch(() => {});
|
|
66
|
-
if ([1008, 4401, 4406, 4408, 4409, 4429].includes(event.code)) this.terminalError = error;
|
|
67
|
-
return error;
|
|
68
|
-
}
|
|
69
|
-
encodeMessage(message) {
|
|
70
|
-
const frame = { type: 'data', payload: JSON.stringify(message) };
|
|
71
|
-
if (new TextEncoder().encode(JSON.stringify(frame)).length > 8 * 1024 * 1024) throw new Error('Code Relax 中继私测消息超过 8 MiB,请减少图片或内容');
|
|
72
|
-
return frame;
|
|
73
|
-
}
|
|
74
|
-
async pause() {
|
|
75
|
-
const socket = this.socket;
|
|
76
|
-
if (!socket || socket.readyState === 3) return super.pause();
|
|
77
|
-
const closed = new Promise(resolve => socket.addEventListener('close', resolve, { once: true }));
|
|
78
|
-
await super.pause(); await closed;
|
|
79
|
-
}
|
|
80
|
-
decodeMessage(message) {
|
|
81
|
-
if (message.type === 'paired') {
|
|
82
|
-
if (message.room !== this.config.room) throw new Error('配对工作站不匹配');
|
|
83
|
-
if (this.config.clientId && message.protocolVersion !== 3) {
|
|
84
|
-
this.terminalError = Object.assign(new TypeError('Code Relax 中继协议需要升级'), { code: 4406 }); throw this.terminalError;
|
|
85
|
-
}
|
|
86
|
-
if (message.credential && this.config.credential && message.credential !== this.config.credential) throw new Error('设备凭据与已保存身份不一致');
|
|
87
|
-
if (message.credential) this.config.credential = message.credential;
|
|
88
|
-
if (Number.isSafeInteger(message.revision) && message.revision >= 0) {
|
|
89
|
-
if (this.config.revision !== undefined && this.config.revision !== message.revision) root.codexHistoryCache?.clear().catch(() => {});
|
|
90
|
-
this.config.revision = message.revision;
|
|
91
|
-
}
|
|
92
|
-
delete this.config.code;
|
|
93
|
-
root.codexNative?.selfRelayPaired?.(message.room, this.config.credential, this.config.revision || 0);
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
if (message.type === 'lan-candidates' && Array.isArray(message.candidates)) {
|
|
97
|
-
root.codexNative?.selfRelayLanCandidates?.(JSON.stringify(message.candidates));
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
if (message.type === 'computer-info' && typeof message.hostName === 'string') {
|
|
101
|
-
root.codexNative?.selfRelayComputerInfo?.(message.hostName);
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
if (message.type === 'ready') return message;
|
|
105
|
-
if (message.type === 'data' && typeof message.payload === 'string') {
|
|
106
|
-
const started = performance.now();
|
|
107
|
-
const decoded = JSON.parse(message.payload);
|
|
108
|
-
const path = this.pending.get(decoded.id)?.path || '';
|
|
109
|
-
const route = /\/timeline(?:\?|$)/.test(path) ? 'timeline' : /\/history\?/.test(path) ? 'history'
|
|
110
|
-
: /^\/api\/threads\?/.test(path) ? 'list' : '';
|
|
111
|
-
if (route && root.codexNative?.reportSyncMetric) {
|
|
112
|
-
root.codexNative.reportSyncMetric(`stage=parse;route=${route};ms=${Math.round(performance.now() - started)};chars=${message.payload.length}`);
|
|
113
|
-
}
|
|
114
|
-
return decoded;
|
|
115
|
-
}
|
|
116
|
-
throw new Error('无效的Code Relax 中继消息');
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
})(globalThis);
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
(function (root, factory) {
|
|
2
|
-
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
-
else root.CodexServerRequests = factory();
|
|
4
|
-
})(globalThis, function () {
|
|
5
|
-
const command = 'item/commandExecution/requestApproval';
|
|
6
|
-
const file = 'item/fileChange/requestApproval';
|
|
7
|
-
const input = 'item/tool/requestUserInput';
|
|
8
|
-
const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
9
|
-
function decisions(request) {
|
|
10
|
-
if (request.method === command) return request.params.availableDecisions ?? ['accept', 'acceptForSession', 'decline', 'cancel'];
|
|
11
|
-
if (request.method === file) return ['accept', 'acceptForSession', 'decline', 'cancel'];
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
function validate(request, result) {
|
|
15
|
-
if ([command, file].includes(request.method)) {
|
|
16
|
-
if (!result || Object.keys(result).length !== 1 || !decisions(request).some(value => equal(value, result.decision))) throw new Error('审批选项不属于当前请求');
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (request.method === input) {
|
|
20
|
-
const questions = request.params.questions || [];
|
|
21
|
-
if (!result?.answers || Object.keys(result).length !== 1 || Object.keys(result.answers).length !== questions.length
|
|
22
|
-
|| questions.some(q => !Array.isArray(result.answers[q.id]?.answers) || result.answers[q.id].answers.some(a => typeof a !== 'string'))) throw new Error('回答格式无效');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
throw new Error('此类请求需在工作站处理');
|
|
26
|
-
}
|
|
27
|
-
class Store {
|
|
28
|
-
constructor(epoch, changed = () => {}) { this.epoch = epoch; this.changed = changed; this.requests = new Map(); }
|
|
29
|
-
list(threadId) { return [...this.requests.values()].filter(r => r.params.threadId === threadId).map(r => ({ ...r })); }
|
|
30
|
-
receive(message) {
|
|
31
|
-
if (!['string', 'number'].includes(typeof message.id) || !message.params?.threadId || this.requests.has(message.id)) return;
|
|
32
|
-
const request = { id: message.id, method: message.method, params: message.params, epoch: this.epoch, status: 'pending' };
|
|
33
|
-
this.requests.set(message.id, request); this.changed(request.params.threadId);
|
|
34
|
-
}
|
|
35
|
-
notification(method, params) {
|
|
36
|
-
if (method === 'serverRequest/resolved') {
|
|
37
|
-
const request = this.requests.get(params.requestId);
|
|
38
|
-
if (request?.params.threadId === params.threadId) { this.requests.delete(params.requestId); this.changed(params.threadId); }
|
|
39
|
-
} else if (method === 'turn/completed' || method === 'thread/closed') {
|
|
40
|
-
for (const [id, r] of this.requests) if (r.params.threadId === params.threadId && (method === 'thread/closed' || r.params.turnId === params.turn?.id)) this.requests.delete(id);
|
|
41
|
-
this.changed(params.threadId);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
forget(threadId) {
|
|
45
|
-
for (const [id, request] of this.requests) if (request.params.threadId === threadId) this.requests.delete(id);
|
|
46
|
-
this.changed(threadId);
|
|
47
|
-
}
|
|
48
|
-
reset(epoch) {
|
|
49
|
-
const threads = new Set([...this.requests.values()].map(r => r.params.threadId));
|
|
50
|
-
this.requests.clear(); this.epoch = epoch; for (const id of threads) this.changed(id);
|
|
51
|
-
}
|
|
52
|
-
async respond(threadId, { id, epoch, result }, send) {
|
|
53
|
-
const request = this.requests.get(id);
|
|
54
|
-
if (!request || epoch !== this.epoch || request.params.threadId !== threadId) throw new Error('审批已处理或连接已变化,请等待同步');
|
|
55
|
-
if (request.status !== 'pending') throw new Error('决定已提交,正在等待工作站确认,请勿重复提交');
|
|
56
|
-
validate(request, result);
|
|
57
|
-
request.status = 'submitted'; this.changed(threadId);
|
|
58
|
-
try { await send({ id: request.id, result }); }
|
|
59
|
-
catch (error) {
|
|
60
|
-
if (this.requests.get(id) === request) { request.status = 'unknown'; this.changed(threadId); }
|
|
61
|
-
throw new Error('审批发送结果未确认,请检查工作站;不会自动重发。' + error.message);
|
|
62
|
-
}
|
|
63
|
-
return { submitted: true };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return { Store, decisions, validate, command, file, input };
|
|
67
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
(function (root, factory) {
|
|
2
|
-
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
-
else root.CodexTimelineSync = factory();
|
|
4
|
-
})(globalThis, function () {
|
|
5
|
-
const equal = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
6
|
-
function changedFields(previous, current, ignored) {
|
|
7
|
-
const set = {}, append = {}, unset = [];
|
|
8
|
-
for (const key of new Set([...Object.keys(previous || {}), ...Object.keys(current || {})])) {
|
|
9
|
-
if (ignored.has(key) || equal(previous?.[key], current?.[key])) continue;
|
|
10
|
-
if (!Object.hasOwn(current, key)) { unset.push(key); continue; }
|
|
11
|
-
const before = previous?.[key], after = current[key];
|
|
12
|
-
if (typeof before === 'string' && typeof after === 'string' && after.startsWith(before)) append[key] = after.slice(before.length);
|
|
13
|
-
else set[key] = after;
|
|
14
|
-
}
|
|
15
|
-
return { set, append, unset };
|
|
16
|
-
}
|
|
17
|
-
const nonempty = changes => Object.keys(changes.set).length || Object.keys(changes.append).length || changes.unset.length;
|
|
18
|
-
function diffTurn(previous, current) {
|
|
19
|
-
if (!previous || !current || previous.id !== current.id) return null;
|
|
20
|
-
const before = Array.isArray(previous.entries) ? previous.entries : [];
|
|
21
|
-
const after = Array.isArray(current.entries) ? current.entries : [];
|
|
22
|
-
if (after.length < before.length) return null;
|
|
23
|
-
const entries = [];
|
|
24
|
-
for (let index = 0; index < before.length; index++) {
|
|
25
|
-
if (before[index]?.id !== after[index]?.id || before[index]?.type !== after[index]?.type ||
|
|
26
|
-
before[index]?.role !== after[index]?.role || before[index]?.kind !== after[index]?.kind) return null;
|
|
27
|
-
const changes = changedFields(before[index], after[index], new Set(['id', 'type', 'role', 'kind']));
|
|
28
|
-
if (nonempty(changes)) entries.push({ id: after[index].id, index, ...changes });
|
|
29
|
-
}
|
|
30
|
-
const turn = changedFields(previous, current, new Set(['id', 'entries']));
|
|
31
|
-
const appended = after.slice(before.length);
|
|
32
|
-
return { id: current.id, ...turn, entries, appended };
|
|
33
|
-
}
|
|
34
|
-
function applyChanges(target, changes) {
|
|
35
|
-
const result = { ...target, ...(changes.set || {}) };
|
|
36
|
-
for (const key of changes.unset || []) delete result[key];
|
|
37
|
-
for (const [key, suffix] of Object.entries(changes.append || {})) {
|
|
38
|
-
if (typeof result[key] !== 'string' || typeof suffix !== 'string') throw new Error('增量字段类型不匹配');
|
|
39
|
-
result[key] += suffix;
|
|
40
|
-
}
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
function applyTurnPatch(turn, patch) {
|
|
44
|
-
if (!turn || turn.id !== patch?.id) throw new Error('增量轮次不存在');
|
|
45
|
-
const next = applyChanges(turn, patch), entries = (turn.entries || []).map(entry => ({ ...entry }));
|
|
46
|
-
for (const entryPatch of patch.entries || []) {
|
|
47
|
-
const entry = entries[entryPatch.index];
|
|
48
|
-
if (!entry || entry.id !== entryPatch.id) throw new Error('增量条目边界不匹配');
|
|
49
|
-
entries[entryPatch.index] = applyChanges(entry, entryPatch);
|
|
50
|
-
}
|
|
51
|
-
for (const entry of patch.appended || []) {
|
|
52
|
-
if (entries.some(existing => existing.id === entry.id)) throw new Error('增量条目重复');
|
|
53
|
-
entries.push(entry);
|
|
54
|
-
}
|
|
55
|
-
next.entries = entries; return next;
|
|
56
|
-
}
|
|
57
|
-
function acceptSyncUpdate(current, incoming) {
|
|
58
|
-
if (incoming.reset) return { reset: true, next: incoming };
|
|
59
|
-
const valid = current && incoming.id === current.id && incoming.epoch === current.epoch &&
|
|
60
|
-
Number.isSafeInteger(incoming.fromSeq) && incoming.fromSeq >= 1 &&
|
|
61
|
-
Number.isSafeInteger(incoming.toSeq) && incoming.toSeq >= incoming.fromSeq;
|
|
62
|
-
if (!valid || incoming.fromSeq > current.seq + 1) throw new Error('实时更新序号不连续');
|
|
63
|
-
if (incoming.toSeq <= current.seq) return { duplicate: true, next: current };
|
|
64
|
-
return { next: { ...current, seq: incoming.toSeq } };
|
|
65
|
-
}
|
|
66
|
-
return { diffTurn, applyTurnPatch, acceptSyncUpdate };
|
|
67
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Vendored browser dependency
|
|
2
|
-
|
|
3
|
-
- `markdown-it.umd.min.js`: markdown-it 15.0.2 browser UMD build
|
|
4
|
-
- Source: https://github.com/markdown-it/markdown-it
|
|
5
|
-
- License: MIT; see `markdown-it.LICENSE.txt`
|
|
6
|
-
- SHA-256: `635972b985228e8af9f0143647c68616b7a3bb09f6946e7e4a52e43dcf5e7be5`
|
|
7
|
-
- `highlight.min.js`: highlight.js 11.12.0 common browser build
|
|
8
|
-
- `highlight-powershell.min.js`: highlight.js 11.12.0 PowerShell language module
|
|
9
|
-
- Source: https://github.com/highlightjs/highlight.js
|
|
10
|
-
- License: BSD-3-Clause; see `highlight.LICENSE.txt`
|
|
11
|
-
- SHA-256 (`highlight.min.js`): `8ab71eb09c51f501e5e25157d9cff100e46cc29bcbfc744d0b746d451fca7f53`
|
|
12
|
-
- SHA-256 (`highlight-powershell.min.js`): `fc298b3e0db362e531e6d58988b7a78f83da19df6b5d74db75bc961b2bfbfd3d`
|
|
13
|
-
|
|
14
|
-
These files are served locally by the Bridge. They are not fetched from a CDN at runtime.
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/*! `powershell` grammar compiled for Highlight.js 11.12.0 */
|
|
2
|
-
(()=>{var e=(()=>{"use strict";return e=>{const n={$pattern:/-?[A-z\.\-]+\b/,
|
|
3
|
-
keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",
|
|
4
|
-
built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"
|
|
5
|
-
},s={begin:"`[\\s\\S]",relevance:0},i={className:"variable",variants:[{
|
|
6
|
-
begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]
|
|
7
|
-
},a={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],
|
|
8
|
-
contains:[s,i,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},t={
|
|
9
|
-
className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]
|
|
10
|
-
},r=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,
|
|
11
|
-
end:/#>/}],contains:[{className:"doctag",variants:[{
|
|
12
|
-
begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/
|
|
13
|
-
},{
|
|
14
|
-
begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/
|
|
15
|
-
}]}]}),c={className:"class",beginKeywords:"class enum",end:/\s*[{]/,
|
|
16
|
-
excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},l={className:"function",
|
|
17
|
-
begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,
|
|
18
|
-
contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",
|
|
19
|
-
begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,
|
|
20
|
-
className:"params",relevance:0,contains:[i]}]},o={begin:/using\s/,end:/$/,
|
|
21
|
-
returnBegin:!0,contains:[a,t,{className:"keyword",
|
|
22
|
-
begin:/(using|assembly|command|module|namespace|type)/}]},p={
|
|
23
|
-
className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,
|
|
24
|
-
relevance:0,contains:[{className:"keyword",
|
|
25
|
-
begin:"(".concat(n.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,
|
|
26
|
-
relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]
|
|
27
|
-
},g=[p,r,s,e.NUMBER_MODE,a,t,{className:"built_in",variants:[{
|
|
28
|
-
begin:"(Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where)+(-)[\\w\\d]+"
|
|
29
|
-
}]},i,{className:"literal",begin:/\$(null|true|false)\b/},{
|
|
30
|
-
className:"selector-tag",begin:/@\B/,relevance:0}],m={begin:/\[/,end:/\]/,
|
|
31
|
-
excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",g,{
|
|
32
|
-
begin:"(string|char|byte|int|long|bool|decimal|single|double|DateTime|xml|array|hashtable|void)",
|
|
33
|
-
className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,
|
|
34
|
-
relevance:0})};return p.contains.unshift(m),{name:"PowerShell",
|
|
35
|
-
aliases:["pwsh","ps","ps1"],case_insensitive:!0,keywords:n,
|
|
36
|
-
contains:g.concat(c,l,o,{variants:[{className:"operator",
|
|
37
|
-
begin:"(-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor)\\b"
|
|
38
|
-
},{className:"literal",begin:/(-){1,2}[\w\d-]+/,relevance:0}]},m)}}})()
|
|
39
|
-
;hljs.registerLanguage("powershell",e)})();
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
BSD 3-Clause License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2006, Ivan Sagalaev.
|
|
4
|
-
All rights reserved.
|
|
5
|
-
|
|
6
|
-
Redistribution and use in source and binary forms, with or without
|
|
7
|
-
modification, are permitted provided that the following conditions are met:
|
|
8
|
-
|
|
9
|
-
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
-
list of conditions and the following disclaimer.
|
|
11
|
-
|
|
12
|
-
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
-
this list of conditions and the following disclaimer in the documentation
|
|
14
|
-
and/or other materials provided with the distribution.
|
|
15
|
-
|
|
16
|
-
* Neither the name of the copyright holder nor the names of its
|
|
17
|
-
contributors may be used to endorse or promote products derived from
|
|
18
|
-
this software without specific prior written permission.
|
|
19
|
-
|
|
20
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|