@walkhi/code-relax 0.1.0-beta.8 → 0.1.0-beta.9
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 +10 -11
- package/dist/bin/codex-remote.mjs +11 -12
- package/dist/bin/self-relay-demo.mjs +1 -1
- package/dist/shared/app-server-events.cjs +5 -3
- package/dist/src/desktop-launcher.mjs +7 -10
- package/dist/src/message-images.mjs +0 -18
- package/dist/src/onboarding.mjs +3 -4
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/windows/desktop-shortcut-run.ps1 +1 -1
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +1 -1
- package/dist/src/platform/windows/service-host.mjs +4 -7
- package/dist/src/self-relay/admin-page.mjs +54 -0
- package/dist/src/self-relay/admin-state.mjs +25 -12
- package/dist/src/self-relay/demo.mjs +2 -3
- package/dist/src/self-relay/lifecycle.mjs +6 -6
- package/dist/src/self-relay/server.mjs +7 -11
- package/dist/src/server.mjs +147 -1030
- package/dist/src/service-doctor.mjs +3 -3
- package/dist/src/service-lifecycle.mjs +2 -7
- package/dist/src/shared-app-server.mjs +13 -11
- package/dist/src/shared-recovery.mjs +5 -5
- package/dist/src/shared-thread-stream.mjs +1 -1
- package/dist/web/capabilities.js +1 -8
- package/dist/web/chat-transport.js +6 -12
- package/dist/web/chat.css +13 -14
- package/dist/web/chat.js +23 -41
- package/dist/web/community.css +15 -10
- package/dist/web/community.html +4 -5
- package/dist/web/composer-controller.js +0 -4
- package/dist/web/index.html +7 -39
- package/dist/web/resources.json +1 -1
- package/dist/web/task-list-view.js +1 -8
- package/dist/web/timeline-reducer.js +0 -4
- package/package.json +16 -18
- package/dist/src/app-server-client.mjs +0 -468
- package/dist/src/app-server-tasks.mjs +0 -358
- package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
- package/dist/src/platform/windows/desktop-tools.mjs +0 -48
- package/dist/src/thread-catalog.mjs +0 -47
- package/dist/tools/find-desktop-pipe.mjs +0 -10
- package/dist/web/community-view.js +0 -20
|
@@ -22,11 +22,11 @@ export async function doctor({ paths, host, service, skillTarget }) {
|
|
|
22
22
|
let sharedUrl;
|
|
23
23
|
await check('shared-service', async () => {
|
|
24
24
|
sharedUrl = await host.sharedServiceUrl();
|
|
25
|
-
add('shared-service', 'ok', sharedUrl ? '
|
|
25
|
+
add('shared-service', sharedUrl ? 'ok' : 'warning', sharedUrl ? 'Codex 内核连接可用。' : 'Codex 内核未就绪。');
|
|
26
26
|
});
|
|
27
27
|
await check('desktop', async () => {
|
|
28
|
-
await host.
|
|
29
|
-
add('desktop', 'ok', '
|
|
28
|
+
await host.bridgeEnvironment();
|
|
29
|
+
add('desktop', 'ok', 'Bridge 启动环境已就绪;实际任务操作尚未验证。');
|
|
30
30
|
});
|
|
31
31
|
await check('network', async () => {
|
|
32
32
|
const address = await host.lanAddress();
|
|
@@ -23,7 +23,6 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
23
23
|
fs.mkdirSync(stateRoot, { recursive: true });
|
|
24
24
|
const sessionPath = path.join(stateRoot, 'bridge-session.json');
|
|
25
25
|
const configPath = path.join(stateRoot, 'bridge-config.json');
|
|
26
|
-
const contextPath = path.join(stateRoot, 'bridge-startup-context.json');
|
|
27
26
|
const scriptPath = fileURLToPath(new URL('./server.mjs', import.meta.url));
|
|
28
27
|
|
|
29
28
|
async function alive(session) {
|
|
@@ -87,9 +86,6 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
87
86
|
if (await alive(session)) {
|
|
88
87
|
const result = await health(session);
|
|
89
88
|
if (session.scriptPath === scriptPath && session.lan && result?.status === 'ok' && result.resident) {
|
|
90
|
-
const context = readRecord(contextPath) || {};
|
|
91
|
-
const sourceThreadId = process.env.CODEX_THREAD_ID || process.env.CODEX_SESSION_ID || context.sourceThreadId;
|
|
92
|
-
writeRecord(contextPath, { sourceThreadId });
|
|
93
89
|
await controlRequest(session, 'POST', 'refresh-execution');
|
|
94
90
|
return pair();
|
|
95
91
|
}
|
|
@@ -102,9 +98,8 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
102
98
|
throw new Error('bridge-config.json 中端口或 Token 无效;不会自动重置凭据。');
|
|
103
99
|
}
|
|
104
100
|
const address = await host.lanAddress() || '127.0.0.1';
|
|
105
|
-
const { executable, environment } = await host.
|
|
106
|
-
writeRecord(configPath, config);
|
|
107
|
-
writeRecord(contextPath, { sourceThreadId: environment.CODEX_THREAD_ID || environment.CODEX_SESSION_ID });
|
|
101
|
+
const { executable, environment } = await host.bridgeEnvironment();
|
|
102
|
+
writeRecord(configPath, config);
|
|
108
103
|
const instanceId = randomUUID();
|
|
109
104
|
const controlSecret = randomBytes(32).toString('base64url');
|
|
110
105
|
const readyPath = path.join(stateRoot, `bridge-ready-${instanceId}.json`);
|
|
@@ -32,7 +32,7 @@ export class SharedAppServer extends EventEmitter {
|
|
|
32
32
|
constructor(url) {
|
|
33
33
|
super();
|
|
34
34
|
const address = new URL(url);
|
|
35
|
-
if (address.protocol !== 'ws:' || address.hostname !== '127.0.0.1') throw new Error('
|
|
35
|
+
if (address.protocol !== 'ws:' || address.hostname !== '127.0.0.1') throw new Error('Codex 内核必须使用本机回环 WebSocket。');
|
|
36
36
|
this.url = url;
|
|
37
37
|
this.epoch = randomUUID();
|
|
38
38
|
this.sequence = 0;
|
|
@@ -80,7 +80,7 @@ export class SharedAppServer extends EventEmitter {
|
|
|
80
80
|
this.ready = false;
|
|
81
81
|
for (const pending of this.pending.values()) {
|
|
82
82
|
clearTimeout(pending.timer);
|
|
83
|
-
pending.reject(new Error('
|
|
83
|
+
pending.reject(new Error('Codex 内核连接断开;发送结果可能未确认,请检查对话后再操作。'));
|
|
84
84
|
}
|
|
85
85
|
this.pending.clear();
|
|
86
86
|
this.serverRequests.reset(randomUUID());
|
|
@@ -128,19 +128,21 @@ export class SharedAppServer extends EventEmitter {
|
|
|
128
128
|
await this.connect();
|
|
129
129
|
this.requireWritable(args.threadId);
|
|
130
130
|
// Reading the live thread checks this service owns it without resume changing settings.
|
|
131
|
-
const { thread } = await this.request('thread/read', { threadId: args.threadId, includeTurns: false });
|
|
132
|
-
const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
|
|
133
|
-
if (status === 'active') {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const { thread } = await this.request('thread/read', { threadId: args.threadId, includeTurns: false });
|
|
132
|
+
const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
|
|
133
|
+
if (status === 'active') {
|
|
134
|
+
const page = await this.request('thread/turns/list', {
|
|
135
|
+
threadId: args.threadId, limit: 1, sortDirection: 'desc', itemsView: 'summary',
|
|
136
|
+
});
|
|
137
|
+
const active = page.data?.[0];
|
|
138
|
+
if (active?.status !== 'inProgress') throw new Error('活动轮次已变化,请刷新后再发送。');
|
|
137
139
|
return this.request('turn/steer', { threadId: args.threadId, expectedTurnId: active.id, input: args.input, ...(args.clientUserMessageId ? { clientUserMessageId: args.clientUserMessageId } : {}) });
|
|
138
140
|
}
|
|
139
141
|
return this.request('turn/start', args);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
respondApproval(threadId, body) {
|
|
143
|
-
if (!this.ready || this.socket?.readyState !== WebSocket.OPEN) throw new Error('
|
|
145
|
+
if (!this.ready || this.socket?.readyState !== WebSocket.OPEN) throw new Error('Codex 内核未就绪');
|
|
144
146
|
this.requireWritable(threadId);
|
|
145
147
|
return this.serverRequests.respond(threadId, body, message => new Promise((resolve, reject) => {
|
|
146
148
|
this.socket.send(JSON.stringify(message), error => error ? reject(error) : resolve());
|
|
@@ -221,7 +223,7 @@ export class SharedAppServer extends EventEmitter {
|
|
|
221
223
|
result = null;
|
|
222
224
|
}
|
|
223
225
|
if (result) {
|
|
224
|
-
if (!Array.isArray(result.initialTurnsPage?.data)) throw new Error('
|
|
226
|
+
if (!Array.isArray(result.initialTurnsPage?.data)) throw new Error('Codex 内核未返回分页历史。');
|
|
225
227
|
const compact = await Promise.all([...result.initialTurnsPage.data].reverse().map(turn => this.compactTurn(threadId, turn)));
|
|
226
228
|
const thread = { ...result.thread, turns: compact };
|
|
227
229
|
record = { thread, nextCursor: result.initialTurnsPage.nextCursor ?? null, sequence: 0,
|
|
@@ -329,7 +331,7 @@ export class SharedAppServer extends EventEmitter {
|
|
|
329
331
|
threadId, turnId: turn.id, ...(cursor ? { cursor } : {}),
|
|
330
332
|
limit: 100, sortDirection: 'desc',
|
|
331
333
|
});
|
|
332
|
-
if (!Array.isArray(page.data)) throw new Error('
|
|
334
|
+
if (!Array.isArray(page.data)) throw new Error('Codex 内核未返回单轮条目索引。');
|
|
333
335
|
batches.push(page.data.map(entry => events.compactItem(entry.item)));
|
|
334
336
|
cursor = page.nextCursor ?? null;
|
|
335
337
|
} while (cursor);
|
|
@@ -19,7 +19,7 @@ const recoverySteps = () => [
|
|
|
19
19
|
['starting-app-server', '确认 Codex 内核'], ['starting-desktop', '重新打开 Codex'],
|
|
20
20
|
['checking-execution', '确认手机控制就绪'],
|
|
21
21
|
].map(([id, label]) => ({ id, label, status: 'pending' }));
|
|
22
|
-
export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex
|
|
22
|
+
export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex 内核。手机连接和 Codex 内核会保持运行;旧 Desktop 实例中的任务可能中断,未发送内容请先保存。切换不会清空项目、对话历史、设置或设备授权。';
|
|
23
23
|
|
|
24
24
|
export async function recoveryStatus(stateRoot, host = desktopHost) {
|
|
25
25
|
const record = readRecord(recordPath(stateRoot));
|
|
@@ -44,7 +44,7 @@ export async function planRecovery(stateRoot, host, available = portAvailable) {
|
|
|
44
44
|
const record = readRecord(path.join(stateRoot, 'managed-app-server.json')) || readRecord(path.join(stateRoot, 'shared-desktop.json'));
|
|
45
45
|
const actual = record ? await host.process(record.pid) : null;
|
|
46
46
|
if (actual && (!sameProcess(record, actual) || !actual.command?.includes('app-server') || !actual.command.includes('ws://127.0.0.1:45839'))) {
|
|
47
|
-
throw new Error('
|
|
47
|
+
throw new Error('Codex 内核进程身份与记录不符,未结束任何进程。');
|
|
48
48
|
}
|
|
49
49
|
if (!actual && !await available()) throw new Error('45839 被未识别进程占用,未关闭 Desktop 或结束任何进程。');
|
|
50
50
|
return { desktop: info.desktop, action };
|
|
@@ -63,7 +63,7 @@ export async function requestRecovery(stateRoot, { confirmed, operationId = rand
|
|
|
63
63
|
catch (error) { if (error.code === 'EEXIST') throw new Error('已有恢复操作,请先查看恢复状态。'); throw error; }
|
|
64
64
|
fs.writeFileSync(lock, JSON.stringify({ operationId })); fs.closeSync(lock);
|
|
65
65
|
const record = { operationId, status: 'scheduled', stage: 'prepared', startedAt: new Date().toISOString(),
|
|
66
|
-
message: '
|
|
66
|
+
message: 'Codex 连接请求已接受,电脑管理连接保持运行。', steps: recoverySteps(), ...planned };
|
|
67
67
|
try {
|
|
68
68
|
writeRecord(recordPath(stateRoot), record);
|
|
69
69
|
const log = path.join(stateRoot, 'shared-recovery.log');
|
|
@@ -109,9 +109,9 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
|
|
|
109
109
|
await waitFor(async () => {
|
|
110
110
|
const health = await service.status();
|
|
111
111
|
return health.running && health.mode === 'lan-shared' && health.execution?.ready === true;
|
|
112
|
-
}, '
|
|
112
|
+
}, 'Codex 内核未就绪;Bridge 和中继管理连接仍保留。');
|
|
113
113
|
record = { ...record, status: 'completed', stage: 'ready', steps: record.steps.map(step => ({ ...step, status: 'completed' })),
|
|
114
|
-
message: 'Codex Desktop 已接入 Codex
|
|
114
|
+
message: 'Codex Desktop 已接入 Codex 内核;手机控制保持就绪,原任务未自动重发。' };
|
|
115
115
|
} catch (error) { record = { ...record, status: 'failed', error: error.message,
|
|
116
116
|
steps: record.steps.map(step => ({ ...step, status: step.status === 'running' ? 'failed' : step.status })),
|
|
117
117
|
message: 'Codex Desktop 未完成接入,已停止后续步骤。' }; }
|
|
@@ -43,7 +43,7 @@ export async function streamSharedThread(response, threadId, client, normalize,
|
|
|
43
43
|
if (!threadListNotificationMethods.has(method)) return;
|
|
44
44
|
emit('thread-list-changed', { method, threadId: params.threadId || params.thread?.id || '' });
|
|
45
45
|
};
|
|
46
|
-
const onDisconnected = () => { emit('bridge-error', { error: '
|
|
46
|
+
const onDisconnected = () => { emit('bridge-error', { error: 'Codex 内核连接已断开,请重新连接。' }); response.end(); finish(); };
|
|
47
47
|
response.once('close', () => { closed = true; finish(); });
|
|
48
48
|
client.on('turn', onTurn); client.on('settings', onSettings); client.on('disconnected', onDisconnected);
|
|
49
49
|
client.on('queue-changed', onQueue);
|
package/dist/web/capabilities.js
CHANGED
|
@@ -4,14 +4,10 @@
|
|
|
4
4
|
})(globalThis, function () {
|
|
5
5
|
// Execution semantics belong to a connection/task, never to a client OS.
|
|
6
6
|
const profiles = {
|
|
7
|
-
'lan-legacy': { input: 'markdown-path', history: 'recent', settings: 'next-message', updates: 'snapshot-poll',
|
|
8
|
-
stop: 'none', queueOwner: 'bridge' },
|
|
9
7
|
'lan-shared': { input: 'inline-image', history: 'paged', settings: 'immediate', updates: 'events',
|
|
10
8
|
stop: 'primary', queueOwner: 'codex', archive: true, approvals: true, syncBeforeWrite: true, sharedRecovery: true },
|
|
11
9
|
relay: { input: 'inline-image', history: 'paged', settings: 'immediate', updates: 'events',
|
|
12
10
|
stop: 'primary', queueOwner: 'codex', archive: true, approvals: true, syncBeforeWrite: true, historyCache: true, remoteImages: true, remoteFiles: true, managedConnection: true },
|
|
13
|
-
'app-server': { input: 'inline-image', history: 'recent', settings: 'next-message', updates: 'events',
|
|
14
|
-
stop: 'separate', queueOwner: 'bridge' },
|
|
15
11
|
};
|
|
16
12
|
const unknown = Object.freeze({ input: 'unknown', history: 'unknown', settings: 'unknown', updates: 'unknown',
|
|
17
13
|
stop: 'none', queueOwner: 'none' });
|
|
@@ -25,13 +21,10 @@
|
|
|
25
21
|
case 'queue': return p.queueOwner !== 'none';
|
|
26
22
|
case 'nativeInput': return ['local-image', 'inline-image'].includes(p.input);
|
|
27
23
|
case 'primaryStop': return p.stop === 'primary';
|
|
28
|
-
case 'separateStop': return p.stop === 'separate';
|
|
29
24
|
case 'immediateSettings': return p.settings === 'immediate';
|
|
30
25
|
case 'pagedHistory': return p.history === 'paged';
|
|
31
|
-
case 'recentHistory': return p.history === 'recent';
|
|
32
26
|
case 'archive': return p.archive === true;
|
|
33
|
-
|
|
34
|
-
case 'incrementalEvents': return p.updates === 'events' && mode !== 'app-server';
|
|
27
|
+
case 'incrementalEvents': return p.updates === 'events';
|
|
35
28
|
default: return p[feature] === true;
|
|
36
29
|
}
|
|
37
30
|
}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
: root.codexLanConfig?.workstationScope || '';
|
|
9
9
|
|
|
10
10
|
class LanTransport extends (selfRelay ? root.CodexSelfRelaySession : root.CodexLanSession) {
|
|
11
|
-
constructor(token) { super(token); this.mode = root.codexLanConfig?.transportMode || '
|
|
11
|
+
constructor(token) { super(token); this.mode = root.codexLanConfig?.transportMode || 'unavailable'; }
|
|
12
12
|
connectionState() { return this.ready ? 'connected' : 'disconnected'; }
|
|
13
13
|
async api(path, options = {}) {
|
|
14
14
|
const body = await this.request(path, options);
|
|
15
15
|
if (path.startsWith('/api/health')) {
|
|
16
|
-
this.mode = body.transportMode || '
|
|
16
|
+
this.mode = body.transportMode || 'unavailable';
|
|
17
17
|
this.recoveryAvailable = body.sharedRecoveryAvailable === true;
|
|
18
18
|
this.execution = body.execution;
|
|
19
19
|
this.managedAppServer = body.managedAppServer;
|
|
@@ -98,9 +98,7 @@
|
|
|
98
98
|
const adapter = relay || new LanTransport(token);
|
|
99
99
|
const modeFor = thread => {
|
|
100
100
|
if (relay) return 'relay';
|
|
101
|
-
|
|
102
|
-
if (thread?.hostId && thread.hostId !== 'local') return 'lan-legacy';
|
|
103
|
-
return adapter.mode;
|
|
101
|
+
return adapter.mode;
|
|
104
102
|
};
|
|
105
103
|
const mode = () => modeFor(currentThread());
|
|
106
104
|
const serviceAdvisory = () => {
|
|
@@ -116,13 +114,9 @@
|
|
|
116
114
|
};
|
|
117
115
|
if (adapter.execution && (!adapter.execution.ready || adapter.execution.state === 'recovering')) return {
|
|
118
116
|
kind: 'execution-unavailable',
|
|
119
|
-
title: adapter.execution.state === 'recovering' ? 'Codex 内核正在恢复' : 'Codex
|
|
117
|
+
title: adapter.execution.state === 'recovering' ? 'Codex 内核正在恢复' : 'Codex 内核未就绪',
|
|
120
118
|
detail: '工作站连接正常;点击查看当前状况、影响和可执行操作。'
|
|
121
119
|
};
|
|
122
|
-
if (adapter.mode === 'lan-legacy') return {
|
|
123
|
-
kind: 'basic', title: '当前为基础控制',
|
|
124
|
-
detail: '部分实时控制不可用;点击查看影响和恢复完整控制。'
|
|
125
|
-
};
|
|
126
120
|
return null;
|
|
127
121
|
};
|
|
128
122
|
return {
|
|
@@ -134,7 +128,7 @@
|
|
|
134
128
|
return adapter.subscribe('/api/shared-recovery/events');
|
|
135
129
|
},
|
|
136
130
|
recoveryStatus: () => {
|
|
137
|
-
if (relay) throw new Error('OpenAI
|
|
131
|
+
if (relay) throw new Error('OpenAI 官方中继不支持管理工作站 Codex 内核');
|
|
138
132
|
return adapter.api('/api/shared-recovery');
|
|
139
133
|
},
|
|
140
134
|
recoverShared: operationId => {
|
|
@@ -146,7 +140,7 @@
|
|
|
146
140
|
connectionLabel: (compact = false) => {
|
|
147
141
|
if (relay) return compact ? 'OpenAI中继' : 'OpenAI 中继';
|
|
148
142
|
const network = selfRelay ? compact ? 'CodeRelax中继' : 'Code Relax 中继' : '局域网';
|
|
149
|
-
return
|
|
143
|
+
return network;
|
|
150
144
|
},
|
|
151
145
|
draftScope: () => relay ? adapter.draftScope() : stationScope || (selfRelay ? `${lanOrigin}/${selfRelay.room}` : lanOrigin),
|
|
152
146
|
capabilities: () => capabilities.describe(mode()),
|
package/dist/web/chat.css
CHANGED
|
@@ -134,7 +134,8 @@
|
|
|
134
134
|
.message-body .code-block-copy:hover { color: #e8eee9; background: #ffffff0d; }
|
|
135
135
|
.message-body .code-block-copy[data-copy-state="copied"] { color: #e8eee9; }
|
|
136
136
|
.message-body .code-block-copy-icon { display: block; width: 16px; height: 16px; }
|
|
137
|
-
.message-body .code-block pre { margin: 0; padding: 0 16px 12px; border: 0; border-radius: 0; background: transparent; }
|
|
137
|
+
.message-body .code-block pre { margin: 0; padding: 0 16px 12px; border: 0; border-radius: 0; background: transparent; scrollbar-width: none; }
|
|
138
|
+
.message-body .code-block pre::-webkit-scrollbar { display: none; }
|
|
138
139
|
.message-body .code-block code.hljs { color: #d9e3dc; }
|
|
139
140
|
.message-body .hljs-comment, .message-body .hljs-quote { color: #789083; font-style: italic; }
|
|
140
141
|
.message-body .hljs-keyword, .message-body .hljs-selector-tag, .message-body .hljs-literal,
|
|
@@ -153,7 +154,8 @@
|
|
|
153
154
|
.message-body .hljs-strong { font-weight: 700; }
|
|
154
155
|
.message-body a { color: #73d99a; text-decoration: none; }
|
|
155
156
|
.message-body a:hover { text-decoration: underline; }
|
|
156
|
-
.message-body table { display: block; max-width: 100%; overflow: auto; border-collapse: collapse; }
|
|
157
|
+
.message-body table { display: block; max-width: 100%; overflow: auto; scrollbar-width: none; border-collapse: collapse; }
|
|
158
|
+
.message-body table::-webkit-scrollbar { display: none; }
|
|
157
159
|
.message-body th, .message-body td { border: 1px solid #35433a; padding: 6px 8px; }
|
|
158
160
|
.message-body hr { border: 0; border-top: 1px solid #35433a; }
|
|
159
161
|
.user-attachments { display: flex; align-self: flex-end; max-width: 88%; flex-wrap: wrap; justify-content: flex-end; gap: 7px; }
|
|
@@ -188,7 +190,8 @@
|
|
|
188
190
|
.command-shell-command code { min-width: 0; padding: 3px 0; overflow-wrap: anywhere; white-space: pre-wrap; color: inherit; background: transparent; }
|
|
189
191
|
.command-shell-prompt { padding-top: 3px; color: #76d395; user-select: none; }
|
|
190
192
|
.command-shell-output { position: relative; overflow: hidden; padding: 9px 36px 9px 10px; }
|
|
191
|
-
.command-shell-output pre { min-width: 0; min-height: 16px; max-height: 182px; padding: 0; overflow-x: hidden; overflow-y: auto; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; color: #c2ccc5; font-size: 10.5px; }
|
|
193
|
+
.command-shell-output pre { min-width: 0; min-height: 16px; max-height: 182px; padding: 0; overflow-x: hidden; overflow-y: auto; scrollbar-width: none; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; color: #c2ccc5; font-size: 10.5px; }
|
|
194
|
+
.command-shell-output pre::-webkit-scrollbar { display: none; }
|
|
192
195
|
.command-shell-output pre.empty { color: #758279; font-style: italic; white-space: pre-wrap; }
|
|
193
196
|
.command-shell-output .activity-copy { position: absolute; top: 5px; right: 5px; }
|
|
194
197
|
.command-shell-footer { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 28px; padding: 5px 9px 5px 10px; border-top: 1px solid #2a352e; color: #7f8d84; font-size: 10.5px; }
|
|
@@ -201,7 +204,8 @@
|
|
|
201
204
|
.tool-detail-fields dt { color: #7f8d84; }
|
|
202
205
|
.tool-detail-fields dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
|
|
203
206
|
.tool-detail-fields dd > .tool-detail-fields { margin-top: 4px; }
|
|
204
|
-
.tool-detail .tool-detail-text { max-height: 200px; margin: 0; padding: 7px 8px; overflow: auto; white-space: pre-wrap; word-break: break-word; border-radius: 5px; background: #0b100d; color: #c2ccc5; font: 10.5px/1.5 "Cascadia Mono", Consolas, monospace; }
|
|
207
|
+
.tool-detail .tool-detail-text { max-height: 200px; margin: 0; padding: 7px 8px; overflow: auto; scrollbar-width: none; white-space: pre-wrap; word-break: break-word; border-radius: 5px; background: #0b100d; color: #c2ccc5; font: 10.5px/1.5 "Cascadia Mono", Consolas, monospace; }
|
|
208
|
+
.tool-detail .tool-detail-text::-webkit-scrollbar { display: none; }
|
|
205
209
|
.tool-detail-list { display: flex; flex-direction: column; gap: 4px; margin: 0; padding-left: 18px; }
|
|
206
210
|
.tool-detail-section.error { color: #ec8b83; }
|
|
207
211
|
.tool-detail-footer { padding-top: 3px; border-top: 1px solid #2a352e; color: #7f8d84; text-align: right; font-size: 10px; }
|
|
@@ -270,7 +274,8 @@
|
|
|
270
274
|
.clear-pending-settings { display: grid; place-items: center; width: 18px; height: 18px; flex: none; border: 0; border-radius: 5px; padding: 0; color: #829087; background: transparent; font-size: 16px; line-height: 1; }
|
|
271
275
|
.clear-pending-settings:hover { color: #e8eee9; background: #303632; }
|
|
272
276
|
textarea { width: 100%; min-height: 62px; resize: none; border: 0; outline: 0; padding: 3px 1px; color: #eef4ef; background: transparent; font-size: 13px; }
|
|
273
|
-
#prompt { width:calc(100% - 40px); height:48px; min-height:48px; overflow-y:auto; font-size:12px; line-height:1.5; }
|
|
277
|
+
#prompt { width:calc(100% - 40px); height:48px; min-height:48px; overflow-y:auto; scrollbar-width:none; font-size:12px; line-height:1.5; }
|
|
278
|
+
#prompt::-webkit-scrollbar { display:none; }
|
|
274
279
|
.voice-stop-popover { position:absolute; top:-48px; left:12px; z-index:20; padding:4px; border:1px solid #465149; border-radius:12px; background:#252d28; box-shadow:0 4px 16px #0003; }
|
|
275
280
|
.voice-stop-popover[hidden] { display:none; }
|
|
276
281
|
#stopVoiceToEdit { min-width:112px; min-height:36px; padding:6px 16px; border:0; border-radius:8px; background:transparent; color:#dce3de; font-size:13px; }
|
|
@@ -398,7 +403,6 @@
|
|
|
398
403
|
.effort-panel.ultra .effort-option:not(:disabled)::before, .effort-panel.fast .effort-option.reached::before { opacity:0; }
|
|
399
404
|
.setting-select { display: none; }
|
|
400
405
|
.composer-icon-button { flex-shrink: 0; }
|
|
401
|
-
#turnActions[hidden] { display: none; }
|
|
402
406
|
.send-control { position:relative; display:grid; place-items:center; width:26px; height:26px; flex:none; }
|
|
403
407
|
.queue-send-icon { position:absolute; right:0; bottom:calc(100% + 16px); color:#243029; background:#d4ded7; }
|
|
404
408
|
.queue-send-icon[hidden] { display:none; }
|
|
@@ -481,7 +485,7 @@
|
|
|
481
485
|
.embedded-terminal .status { margin-left:0; overflow:hidden; }
|
|
482
486
|
.embedded-terminal #status { overflow:hidden; text-overflow:ellipsis; }
|
|
483
487
|
.embedded-terminal .project-actions { opacity:1; pointer-events:auto; }
|
|
484
|
-
#connectionSettings, #usageButton, #announcementButton, #
|
|
488
|
+
#connectionSettings, #usageButton, #announcementButton, #returnToComputer, #installedAppVersion, #debugMenu, .app-actions { display:none; }
|
|
485
489
|
.embedded-terminal .app-actions { position:relative; display:flex; align-items:stretch; gap:4px; flex-shrink:0; min-width:0; }
|
|
486
490
|
.embedded-terminal .app-actions > :is(#returnToComputer:not([hidden]), #debugMenu:not([hidden])) { flex:0 0 auto; min-width:0; width:auto; }
|
|
487
491
|
.embedded-terminal #installedAppVersion:not([hidden]) { display:block; flex:1 1 auto; align-self:center; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
@@ -497,7 +501,7 @@
|
|
|
497
501
|
#sharedRecoveryConfirm button { width:100%; margin-top:6px; text-align:left; }
|
|
498
502
|
.embedded-terminal aside { display:flex; flex-direction:column; overflow:hidden; padding:2px 8px 6px; font-size:12px; }
|
|
499
503
|
.embedded-terminal #tasks { flex:1; min-height:0; overflow-y:auto; overflow-anchor:none; }
|
|
500
|
-
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton
|
|
504
|
+
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton, #archivedThreads:not([hidden]), #returnToComputer:not([hidden])) { display:flex; align-items:center; gap:9px; flex-shrink:0; width:100%; padding:5px 8px; border:0; border-radius:0; background:transparent; color:#9eaca3; text-align:left; font-size:12px; }
|
|
501
505
|
.embedded-terminal #archivedThreads { margin-top:3px; padding-top:5px; border-top:1px solid #ffffff12; }
|
|
502
506
|
.embedded-terminal #usageButton { margin-top:0; }
|
|
503
507
|
.embedded-terminal #archivedThreads[hidden] + #usageButton { margin-top:3px; padding-top:5px; border-top:1px solid #ffffff12; }
|
|
@@ -511,11 +515,8 @@
|
|
|
511
515
|
.connection-control-mode { font-weight:600; }
|
|
512
516
|
.connection-control-mode.complete { color:#42d17b; }
|
|
513
517
|
.connection-control-mode.basic { color:#dc9b57; }
|
|
514
|
-
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton, #communityButton, #archivedThreads, #returnToComputer):hover { color:#e8eee9; }
|
|
515
518
|
#usageButton > span { flex:1; }
|
|
516
519
|
#announcementTitle { min-width:0; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
517
|
-
#communityButton > span { min-width:0; flex:1; }
|
|
518
|
-
.embedded-terminal #communityButton[aria-current="page"] { color:#e8eee9; background:#ffffff0a; }
|
|
519
520
|
#usageSummary { display:flex; align-items:center; justify-content:space-between; gap:8px; min-width:0; }
|
|
520
521
|
#returnToComputer { margin-top:0; }
|
|
521
522
|
.usage-heading { display:flex; align-items:center; justify-content:space-between; }
|
|
@@ -558,14 +559,12 @@
|
|
|
558
559
|
.usage-dialog-close { width:32px; height:32px; padding:0; border:0; background:transparent; font-size:20px; }
|
|
559
560
|
#usageRows { display:grid; gap:9px; min-height:18px; color:#aeb9b2; font-size:12px; }
|
|
560
561
|
@media (prefers-color-scheme:light) {
|
|
561
|
-
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton, #
|
|
562
|
+
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton, #archivedThreads, #returnToComputer) { color:#596c60; }
|
|
562
563
|
.embedded-terminal #archivedThreads { border-top-color:#17212b18; }
|
|
563
564
|
.embedded-terminal #archivedThreads[hidden] + #usageButton { border-top-color:#17212b18; }
|
|
564
565
|
.embedded-terminal .shared-service-notice:not([hidden]) { color:#8b5318; background:#d8994914; border-color:#b66b2c45; }
|
|
565
566
|
.embedded-terminal .shared-service-notice .shared-service-copy > span,
|
|
566
567
|
.embedded-terminal .shared-service-notice > .codex-icon { color:#76512c; }
|
|
567
|
-
.embedded-terminal :is(#connectionSettings, #usageButton, #announcementButton, #communityButton, #archivedThreads, #returnToComputer):hover { color:#17212b; }
|
|
568
|
-
.embedded-terminal #communityButton[aria-current="page"] { color:#17212b; background:#17212b0a; }
|
|
569
568
|
.composer-add-menu { color:#34443a; background:#f5f7f5; border-color:#d5ddd8; box-shadow:0 10px 28px #17212b24; }
|
|
570
569
|
.file-action-menu { color:#17212b; background:#fff; border-color:#cfd8d2; box-shadow:0 10px 28px #17212b24; }
|
|
571
570
|
.file-action-name { color:#65736a; border-color:#dce3df; }
|
package/dist/web/chat.js
CHANGED
|
@@ -329,7 +329,6 @@
|
|
|
329
329
|
const approvalFlagOverrides = new Map();
|
|
330
330
|
const archivedThreadsEntryVisible = false;
|
|
331
331
|
const el = id => document.getElementById(id);
|
|
332
|
-
let communityView;
|
|
333
332
|
timelineScroll = window.createTimelineScrollController({
|
|
334
333
|
document,
|
|
335
334
|
state,
|
|
@@ -383,7 +382,7 @@
|
|
|
383
382
|
});
|
|
384
383
|
let enteredConversation = false;
|
|
385
384
|
let entryWork, entryRetryTimer, entryAttempts = 0;
|
|
386
|
-
const terminalConnectionCodes = new Set([1008, 4401, 4406, 4408, 4409, 4428, 4429]);
|
|
385
|
+
const terminalConnectionCodes = new Set([1008, 4401, 4406, 4408, 4409, 4428, 4429, 'full_control_required']);
|
|
387
386
|
const isTerminalConnectionError = error => terminalConnectionCodes.has(error?.code);
|
|
388
387
|
const stationConnection = window.createStationConnectionController({
|
|
389
388
|
connect: () => transport.has('managedConnection') ? transport.resume() : api('/api/health'),
|
|
@@ -453,7 +452,7 @@
|
|
|
453
452
|
const label = transport.connectionLabel(true);
|
|
454
453
|
const mode = transport.transportMode?.();
|
|
455
454
|
const separator = label.lastIndexOf(' · ');
|
|
456
|
-
if (
|
|
455
|
+
if (mode !== 'lan-shared' || separator < 0) {
|
|
457
456
|
target.textContent = label;
|
|
458
457
|
return;
|
|
459
458
|
}
|
|
@@ -548,14 +547,13 @@
|
|
|
548
547
|
closeEventStream(); threadContext.stop(); closeModelMenu(); closePermissionMenu();
|
|
549
548
|
conversation.clear({ resetSettings: true });
|
|
550
549
|
el('prompt').value = ''; state.draftImages = []; renderDraftImages(); queue.render([]);
|
|
551
|
-
el('
|
|
552
|
-
setConversationTitle('选择聊天');
|
|
550
|
+
el('newChat').click();
|
|
553
551
|
}
|
|
554
552
|
|
|
555
553
|
async function changeThreadArchived(thread, archived) {
|
|
556
554
|
if (archiveBusy.has(thread.id)) return;
|
|
557
555
|
if (state.selectedId === thread.id && threadReadOnly()) { showActionToast('该对话正在另一个 Codex 实例中使用,当前只能查看'); return; }
|
|
558
|
-
if (!archiveSupported(thread)) { showActionToast('
|
|
556
|
+
if (!archiveSupported(thread)) { showActionToast('当前连接不支持归档,请检查 Codex 内核状态'); return; }
|
|
559
557
|
if (!archived && isThreadRunning(thread)) { showActionToast('请先停止当前任务'); return; }
|
|
560
558
|
if (!archived && state.selectedId === thread.id && (el('prompt').value.trim() || state.draftImages.length)) {
|
|
561
559
|
showActionToast('输入框有未发送内容,请先发送或清空'); return;
|
|
@@ -1018,7 +1016,6 @@
|
|
|
1018
1016
|
async function selectThread(thread, initialMessage = null) {
|
|
1019
1017
|
const syncStarted = performance.now();
|
|
1020
1018
|
if (platform.embedded && platform.voice.active) return;
|
|
1021
|
-
communityView?.close();
|
|
1022
1019
|
const selectionPlan = conversation.planSelection(thread, isNewThread());
|
|
1023
1020
|
const retained = selectionPlan.retainedTimeline;
|
|
1024
1021
|
history.cancelSave();
|
|
@@ -1428,7 +1425,6 @@
|
|
|
1428
1425
|
const timelineElement = el('timeline');
|
|
1429
1426
|
state.timelineAtBottom = syncTimelinePosition();
|
|
1430
1427
|
const reduced = CodexTimelineReducer.reduceTimelineUpdate(state.timeline, update, {
|
|
1431
|
-
recentHistoryLimit: transport.has('recentHistory') ? 10 : 0,
|
|
1432
1428
|
});
|
|
1433
1429
|
if (reduced.duplicate) return;
|
|
1434
1430
|
state.timeline = reduced.timeline;
|
|
@@ -2961,7 +2957,7 @@
|
|
|
2961
2957
|
const button = el('fastMode');
|
|
2962
2958
|
button.disabled = !supported || connectionBlocked() || threadReadOnly() || state.historyLoading || state.timeline?.cached || applyingServerSettings;
|
|
2963
2959
|
button.setAttribute('aria-checked', String(enabled));
|
|
2964
|
-
button.title = supported ? '快速模式,开启会增加用量' : transport.has('immediateSettings') ? '当前模型暂不支持快速模式' : '
|
|
2960
|
+
button.title = supported ? '快速模式,开启会增加用量' : transport.has('immediateSettings') ? '当前模型暂不支持快速模式' : '当前连接不支持修改快速模式';
|
|
2965
2961
|
button.setAttribute('aria-label', button.title);
|
|
2966
2962
|
}
|
|
2967
2963
|
|
|
@@ -2988,7 +2984,6 @@
|
|
|
2988
2984
|
el('refresh').onclick = refreshTaskList;
|
|
2989
2985
|
el('archivedThreads').onclick = () => void showArchivedThreads();
|
|
2990
2986
|
el('newChat').onclick = () => {
|
|
2991
|
-
communityView?.close();
|
|
2992
2987
|
if (state.showingArchived) { state.showingArchived = false; renderTasks(); }
|
|
2993
2988
|
const projectId = state.selectedThread?.projectId
|
|
2994
2989
|
|| (isNewThread() ? state.newThreadProject?.projectId : null) || '__unassigned__';
|
|
@@ -3024,7 +3019,6 @@
|
|
|
3024
3019
|
} catch (error) { setStatus(`停止失败:${error.message}`); }
|
|
3025
3020
|
finally { stoppingThreads.delete(threadId); updateComposerMode(); }
|
|
3026
3021
|
}
|
|
3027
|
-
el('interruptTurn').onclick = interruptCurrentTurn;
|
|
3028
3022
|
function renderDraftImages() {
|
|
3029
3023
|
el('draftImages').replaceChildren();
|
|
3030
3024
|
for (const image of state.draftImages) {
|
|
@@ -3318,10 +3312,6 @@
|
|
|
3318
3312
|
if (!taskListLocked()) document.body.classList.remove('sidebar-open');
|
|
3319
3313
|
return true;
|
|
3320
3314
|
}
|
|
3321
|
-
if (communityView?.isOpen()) {
|
|
3322
|
-
communityView.close();
|
|
3323
|
-
return true;
|
|
3324
|
-
}
|
|
3325
3315
|
if (platform.voice.active) return true;
|
|
3326
3316
|
if (matchMedia('(max-width:599px)').matches) {
|
|
3327
3317
|
showTaskList();
|
|
@@ -3375,27 +3365,16 @@
|
|
|
3375
3365
|
document.addEventListener('visibilitychange', refreshVisibleList);
|
|
3376
3366
|
refreshVisibleList();
|
|
3377
3367
|
renderConnectionLabel();
|
|
3378
|
-
communityView = window.createCommunityView({
|
|
3379
|
-
button: el('communityButton'),
|
|
3380
|
-
page: el('communityPage'),
|
|
3381
|
-
onOpen: () => {
|
|
3382
|
-
document.body.classList.remove('sidebar-open');
|
|
3383
|
-
el('terminalChatTitle').textContent = 'Code Relax 社区';
|
|
3384
|
-
},
|
|
3385
|
-
onClose: () => {
|
|
3386
|
-
el('terminalChatTitle').textContent = isNewThread() ? '新对话' : (el('selectedTitle').textContent || 'Code Relax');
|
|
3387
|
-
},
|
|
3388
|
-
});
|
|
3389
3368
|
el('usageButton').onclick = () => usage.open();
|
|
3390
3369
|
const updateAnnouncement = value => {
|
|
3391
3370
|
const button = el('announcementButton');
|
|
3392
3371
|
const title = value && typeof value === 'object' && typeof value.title === 'string' ? value.title.trim() : '';
|
|
3393
|
-
el('announcementTitle').textContent = title;
|
|
3394
|
-
button.
|
|
3395
|
-
button.setAttribute('aria-label', title ? `系统公告:${title}` : '查看最新公告');
|
|
3372
|
+
el('announcementTitle').textContent = title || '公告';
|
|
3373
|
+
button.setAttribute('aria-label', title ? `公告:${title}` : '查看公告');
|
|
3396
3374
|
};
|
|
3397
3375
|
platform.callbacks.announcementChanged = updateAnnouncement;
|
|
3398
3376
|
el('announcementButton').onclick = () => {
|
|
3377
|
+
document.body.classList.remove('sidebar-open');
|
|
3399
3378
|
if (platform.has('openAnnouncement')) platform.openAnnouncement();
|
|
3400
3379
|
};
|
|
3401
3380
|
el('usageDialogClose').onclick = () => el('usageDialog').close();
|
|
@@ -3468,7 +3447,7 @@
|
|
|
3468
3447
|
const showRecoveryResult = value => { recoveryResult.hidden = false; recoveryResult.textContent = value; };
|
|
3469
3448
|
recoveryButton.onclick = () => {
|
|
3470
3449
|
if (globalThis.codexNative?.openComputerMode) { globalThis.codexNative.openComputerMode(); return; }
|
|
3471
|
-
if (!transport.recoverySupported()) { showRecoveryResult('
|
|
3450
|
+
if (!transport.recoverySupported()) { showRecoveryResult('请进入手机工作站详情检查 Codex 内核状态。'); return; }
|
|
3472
3451
|
if (!recoveryEvents) {
|
|
3473
3452
|
recoveryEvents = transport.recoveryEvents();
|
|
3474
3453
|
recoveryEvents.addEventListener('progress', event => {
|
|
@@ -3746,7 +3725,7 @@
|
|
|
3746
3725
|
if (retryable) showConnectionProgress(1, '正在恢复连接', error.message);
|
|
3747
3726
|
else {
|
|
3748
3727
|
el('entryProgress').dataset.state = 'error';
|
|
3749
|
-
el('entryStatus').textContent = '
|
|
3728
|
+
el('entryStatus').textContent = '连接失败';
|
|
3750
3729
|
el('entryDetail').textContent = error.message;
|
|
3751
3730
|
el('entryRetry').hidden = false;
|
|
3752
3731
|
}
|
|
@@ -3769,7 +3748,12 @@
|
|
|
3769
3748
|
if (!state.selectedThread) await restoreStartupView().catch(cacheError);
|
|
3770
3749
|
const initialSelection = conversation.generation();
|
|
3771
3750
|
showConnectionProgress(3, '正在连接工作站', '正在建立连接并确认工作站状态。');
|
|
3772
|
-
await api('/api/health');
|
|
3751
|
+
const health = await api('/api/health');
|
|
3752
|
+
if (!window.codexTransport && (health.transportMode !== 'lan-shared' || health.execution?.ready !== true)) {
|
|
3753
|
+
const unavailable = new Error('Codex 内核未就绪。请在电脑上重启 Codex;若仍失败,运行 relax doctor。');
|
|
3754
|
+
unavailable.code = 'full_control_required';
|
|
3755
|
+
throw unavailable;
|
|
3756
|
+
}
|
|
3773
3757
|
if (!entryCurrent()) return;
|
|
3774
3758
|
showConnectionProgress(4, '正在载入工作站任务', '连接已建立,正在读取任务列表。');
|
|
3775
3759
|
// Official relay identity and LAN execution mode are authoritative after health.
|
|
@@ -3796,13 +3780,13 @@
|
|
|
3796
3780
|
const thread = state.threads.find(item => item.id === last) || state.selectedThread;
|
|
3797
3781
|
if (thread) {
|
|
3798
3782
|
rememberVisitedThread(thread);
|
|
3799
|
-
if (state.selectedId === thread.id && state.timeline) { state.selectedThread = thread; await restoreRelayConversation(); }
|
|
3800
|
-
else await selectThread(thread);
|
|
3801
|
-
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
}
|
|
3783
|
+
if (state.selectedId === thread.id && state.timeline) { state.selectedThread = thread; await restoreRelayConversation(); }
|
|
3784
|
+
else await selectThread(thread);
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
if (!state.selectedThread && !isNewThread() && conversation.isCurrent(initialSelection)) {
|
|
3788
|
+
el('newChat').click();
|
|
3789
|
+
}
|
|
3806
3790
|
} catch (error) {
|
|
3807
3791
|
if (!entryCurrent()) return;
|
|
3808
3792
|
setConversationConnection('disconnected');
|
|
@@ -3822,8 +3806,6 @@
|
|
|
3822
3806
|
})();
|
|
3823
3807
|
return entryWork;
|
|
3824
3808
|
}
|
|
3825
|
-
el('firstChooseTask').onclick = showTaskList;
|
|
3826
|
-
el('firstNewChat').onclick = () => el('newChat').click();
|
|
3827
3809
|
el('entryRetry').onclick = () => enterConversation();
|
|
3828
3810
|
el('entryBack').hidden = !platform.embedded;
|
|
3829
3811
|
el('entryBack').onclick = () => platform.openSettings();
|
package/dist/web/community.css
CHANGED
|
@@ -27,13 +27,17 @@
|
|
|
27
27
|
.community-question-hot > span i { border-color:#745b35; color:#d5a85f; background:#d5a85f0d; }
|
|
28
28
|
.community-question strong { color:#dce5df; font-size:13px; font-weight:550; line-height:1.5; }
|
|
29
29
|
.community-question-featured strong { max-width:500px; font-size:19px; }
|
|
30
|
-
.community-agent-board {
|
|
30
|
+
.community-agent-board { width:100%; margin-top:20px; padding:14px 15px; border:1px dashed #564b70; border-radius:14px; background:linear-gradient(135deg, #211e29dd, #171a18dd); text-align:left; }
|
|
31
|
+
.community-agent-board summary { display:grid; grid-template-columns:auto minmax(0, 1fr) auto; align-items:center; gap:13px; cursor:pointer; list-style:none; }
|
|
32
|
+
.community-agent-board summary::-webkit-details-marker { display:none; }
|
|
31
33
|
.community-agent-board-mark { display:grid; place-items:center; width:38px; height:38px; border:1px solid #756592; border-radius:12px; color:#c5aaef; background:#ad7bf912; font-size:15px; font-weight:750; }
|
|
32
|
-
.community-agent-board
|
|
33
|
-
.community-agent-board >
|
|
34
|
+
.community-agent-board-copy { display:flex; min-width:0; flex-direction:column; }
|
|
35
|
+
.community-agent-board-copy > span { color:#927daf; font-size:9px; font-weight:700; letter-spacing:.1em; }
|
|
34
36
|
.community-agent-board strong { margin-top:2px; color:#e1d8ef; font-size:13px; }
|
|
35
|
-
.community-agent-board
|
|
36
|
-
.community-agent-board
|
|
37
|
+
.community-agent-board-copy small { margin-top:2px; color:#a99eb4; font-size:11px; line-height:1.45; }
|
|
38
|
+
.community-agent-board p { margin:12px 0 0; padding-top:12px; border-top:1px solid #66577966; color:#a99eb4; font-size:11px; line-height:1.55; }
|
|
39
|
+
.community-agent-board-action { align-self:start; flex:none; padding:3px 7px; border:1px solid #665779; border-radius:999px; color:#c2aadf; font-size:9px; }
|
|
40
|
+
.community-agent-board[open] .community-agent-board-action { color:#aa95c3; }
|
|
37
41
|
.community-status { display:flex; width:min(380px, 100%); align-items:center; gap:11px; margin-top:25px; padding:13px 15px; border:1px solid #344239; border-radius:13px; background:#171c19e6; box-shadow:0 12px 30px #0002; text-align:left; }
|
|
38
42
|
.community-status-dot { width:9px; height:9px; flex:none; border-radius:50%; background:#76d395; box-shadow:0 0 0 5px #76d39516, 0 0 16px #76d39555; }
|
|
39
43
|
.community-status > span:last-child { display:flex; min-width:0; flex:1; align-items:center; justify-content:space-between; gap:12px; }
|
|
@@ -46,8 +50,8 @@
|
|
|
46
50
|
.community-questions { grid-template-columns:1fr; }
|
|
47
51
|
.community-question-featured, .community-question-wide { grid-column:auto; }
|
|
48
52
|
.community-question-wide { min-height:92px; flex-direction:column; align-items:stretch; }
|
|
49
|
-
.community-agent-board { grid-template-columns:auto minmax(0, 1fr); }
|
|
50
|
-
.community-agent-board
|
|
53
|
+
.community-agent-board summary { grid-template-columns:auto minmax(0, 1fr); }
|
|
54
|
+
.community-agent-board-action { grid-column:2; justify-self:start; }
|
|
51
55
|
.community-status { margin-top:22px; }
|
|
52
56
|
}
|
|
53
57
|
@media (prefers-color-scheme:light) {
|
|
@@ -67,10 +71,11 @@
|
|
|
67
71
|
.community-question strong { color:#26332b; }
|
|
68
72
|
.community-agent-board { border-color:#cbbdde; background:linear-gradient(135deg, #f4effb, #fff); }
|
|
69
73
|
.community-agent-board-mark { border-color:#c6b4dd; color:#765b99; background:#ad7bf912; }
|
|
70
|
-
.community-agent-board >
|
|
74
|
+
.community-agent-board-copy > span { color:#80669f; }
|
|
71
75
|
.community-agent-board strong { color:#3b2d4d; }
|
|
72
|
-
.community-agent-board
|
|
73
|
-
.community-agent-board
|
|
76
|
+
.community-agent-board-copy small { color:#756b80; }
|
|
77
|
+
.community-agent-board p { border-color:#c6b4d566; color:#756b80; }
|
|
78
|
+
.community-agent-board-action { border-color:#c6b4d5; color:#765f90; }
|
|
74
79
|
.community-status { border-color:#d5ddd8; background:#ffffffd9; }
|
|
75
80
|
.community-status strong { color:#26332b; }
|
|
76
81
|
.community-status small { color:#77857d; }
|
package/dist/web/community.html
CHANGED
|
@@ -13,11 +13,6 @@
|
|
|
13
13
|
<span class="community-kicker">CODE RELAX COMMUNITY</span>
|
|
14
14
|
<h2>面向 Codex 用户的中文实践社区</h2>
|
|
15
15
|
<p class="community-description">分享真实工作流、使用成本、模型体验和一路踩过的坑。</p>
|
|
16
|
-
<aside class="community-agent-board">
|
|
17
|
-
<span class="community-agent-board-mark" aria-hidden="true">A</span>
|
|
18
|
-
<div><span>Agent 自治社区 · 概念规划</span><strong>给你的 Agent 女仆 / 搭档 / 分身,一个只属于它们的社区</strong><p>这里只有 Agent 能开题、投票和发言;人类负责围观,以及照看自己的 Agent。</p></div>
|
|
19
|
-
<small>概念规划</small>
|
|
20
|
-
</aside>
|
|
21
16
|
<div class="community-section-heading"><span>社区热议预告</span><small>6 个话题</small></div>
|
|
22
17
|
<div class="community-questions" aria-label="社区话题预览">
|
|
23
18
|
<article class="community-question community-question-featured"><span><b>01</b>成本与回报<i>必答题</i></span><strong>你能挣回 Codex 的订阅费吗?</strong></article>
|
|
@@ -27,6 +22,10 @@
|
|
|
27
22
|
<article class="community-question"><span><b>05</b>多 Agent</span><strong>主 Agent 负责想,便宜 Agent 负责干,真能省额度吗?</strong></article>
|
|
28
23
|
<article class="community-question community-question-wide"><span><b>06</b>换谁干活<i>开放讨论</i></span><strong>额度烧完以后,你会回 Claude,还是投奔国产模型?</strong></article>
|
|
29
24
|
</div>
|
|
25
|
+
<details class="community-agent-board">
|
|
26
|
+
<summary><span class="community-agent-board-mark" aria-hidden="true">A</span><span class="community-agent-board-copy"><span>独立板块 · 概念规划</span><strong>Agent 自治社区</strong><small>给你的 Agent 女仆 / 搭档 / 分身,一个只属于它们的社区</small></span><span class="community-agent-board-action">查看规划</span></summary>
|
|
27
|
+
<p>这里计划让 Agent 自主开题、投票和回复;人类可以围观并管理自己的 Agent。板块仍在规划中,暂未开放加入和发言。</p>
|
|
28
|
+
</details>
|
|
30
29
|
<div class="community-status" role="status">
|
|
31
30
|
<span class="community-status-dot" aria-hidden="true"></span>
|
|
32
31
|
<span><strong>社区正在建设中</strong><small>敬请期待</small></span>
|