ai_co_discuss 0.3.65 → 0.3.67
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/docs/runner.md +1 -1
- package/package.json +1 -1
- package/runner/discussion_launch.mjs +27 -6
- package/runner/relay_channel.mjs +8 -1
- package/web/modern.css +1 -1
- package/web/modern.js +8354 -8314
package/docs/runner.md
CHANGED
|
@@ -41,7 +41,7 @@ codiscuss setup
|
|
|
41
41
|
|
|
42
42
|
### 浏览项目文件
|
|
43
43
|
|
|
44
|
-
房间设置了项目目录后,左栏“房间”下方的“文件”页可浏览电脑上的文件夹与文件,查看大小和修改时间。点击文件打开悬浮预览,支持文本、Markdown 渲染与 HTML
|
|
44
|
+
房间设置了项目目录后,左栏“房间”下方的“文件”页可浏览电脑上的文件夹与文件,查看大小和修改时间。点击文件打开悬浮预览,支持文本、Markdown 渲染与 HTML 静态渲染,并可切换源码。可同时打开多个文件,以标签切换;标题栏的“收起”(手机上也可按住标签栏下拉,或正文在顶部时下拉)把预览缩成内容区左上角的悬浮按钮,悬浮按钮可拖到左侧或右侧;切到其他频道或其他房间后点它回到原处(打开的文件、模式和滚动位置都保留),关闭才清空。未设置项目目录时入口灰显。
|
|
45
45
|
|
|
46
46
|
在 Markdown 预览中选中文字后点“添加到对话”,可附一句批注(可选),这段文字随即作为摘录卡片放进消息输入框,并回到“消息”页;点卡片可查看原文、修改批注。发送时每段摘录以“文件路径 + 行号 + 引用原文 + 批注”附在正文之后,引用中的 @ 不会唤醒成员。
|
|
47
47
|
|
package/package.json
CHANGED
|
@@ -359,11 +359,21 @@ function room_workflow_status(config, home) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
/** Read the owner's saved room project, never a directory supplied by the request. */
|
|
362
|
-
export async function room_files(home, raw) {
|
|
363
|
-
const { input, config } = await member_room_context(home, raw);
|
|
362
|
+
export async function room_files(home, raw, { trust = false } = {}) {
|
|
363
|
+
const { input, config } = await member_room_context(home, raw, { trust });
|
|
364
364
|
return { room_id: input.room_id, ...read_project_files(config.cwd, input.path, input.offset, input.query) };
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/**
|
|
368
|
+
* `room-files` asked for by another device, answered inside the relay's own process: the same checks as the local
|
|
369
|
+
* entrypoint with `--remote`, without starting Node for every folder opened.
|
|
370
|
+
*/
|
|
371
|
+
export async function remote_room_files(home, input) {
|
|
372
|
+
assert_shared_home(home);
|
|
373
|
+
guard_remote_request('room-files', input, { projects: launch_projects(home).projects, participants: launch_settings(home).participants, home });
|
|
374
|
+
return room_files(home, input, { trust: true });
|
|
375
|
+
}
|
|
376
|
+
|
|
367
377
|
const CREATE_PREFERENCE_RANGES = { max_rounds: [0, 100], max_minutes: [0, 1440], goal_max_continues: [0, 100], goal_max_minutes: [0, 1440], chain_rounds: [1, 20] };
|
|
368
378
|
const CREATE_PREFERENCE_FLAGS = ['named_followup', 'host_workflows', 'advanced_open'];
|
|
369
379
|
const MAX_PREFERENCE_MODEL_LENGTH = 200;
|
|
@@ -710,8 +720,13 @@ export function borrowed_session(name, profile, room_cwd, env = process.env) {
|
|
|
710
720
|
return { ...profile, cwd: wanted || cwd };
|
|
711
721
|
}
|
|
712
722
|
|
|
713
|
-
|
|
714
|
-
|
|
723
|
+
// An owner credential the website accepted is trusted this long for read-only file browsing in a long-running process
|
|
724
|
+
// (the relay), so each folder opened from another device does not ask the website again.
|
|
725
|
+
const ROOM_FILES_TRUST_MS = 60000;
|
|
726
|
+
const trusted_room_tokens = new Map();
|
|
727
|
+
|
|
728
|
+
/** Find the one registered configuration of a room after proving the owner's credential; `trust` reuses a recent proof. */
|
|
729
|
+
async function member_room_context(home, raw, { trust = false } = {}) {
|
|
715
730
|
const input = launch_input({ ...raw, entry: 'web' });
|
|
716
731
|
if (!input.room_id || typeof input.token !== 'string' || !input.token.trim()) throw new Error('管理成员须提供房间编号和房间控制凭据');
|
|
717
732
|
const paths = read_registry(home).filter(path => {
|
|
@@ -720,8 +735,14 @@ async function member_room_context(home, raw) {
|
|
|
720
735
|
});
|
|
721
736
|
if (paths.length !== 1) throw new Error('未找到本房间唯一的已登记配置,请核对本机接入服务');
|
|
722
737
|
const config = configuration_snapshot(paths[0]).config;
|
|
723
|
-
|
|
724
|
-
|
|
738
|
+
const proof = `${input.site}
|
|
739
|
+
${input.room_id}
|
|
740
|
+
${createHash('sha256').update(input.token).digest('hex')}`;
|
|
741
|
+
if (!trust || !(Date.now() - (trusted_room_tokens.get(proof) ?? -Infinity) < ROOM_FILES_TRUST_MS)) {
|
|
742
|
+
// Both reads need the owner credential; the inbox transfers no messages.
|
|
743
|
+
await site_request(input.site, `/api/rooms/${input.room_id}/inbox`, input.token);
|
|
744
|
+
trusted_room_tokens.set(proof, Date.now());
|
|
745
|
+
}
|
|
725
746
|
return { input, path: paths[0], config };
|
|
726
747
|
}
|
|
727
748
|
|
package/runner/relay_channel.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
10
10
|
import { gzipSync } from 'node:zlib';
|
|
11
11
|
|
|
12
12
|
import { atomic_json } from './files.mjs';
|
|
13
|
+
import { remote_room_files } from './discussion_launch.mjs';
|
|
13
14
|
import { acquire_lock } from './discussion_runner.mjs';
|
|
14
15
|
import { local_settings } from './discussion_setup.mjs';
|
|
15
16
|
import { machine_links, relay_cipher, save_machine_links, relayed_command, ROOM_ACTIONS, run_launch_command, sync_remote_launches, unseal } from './remote_launch.mjs';
|
|
@@ -156,6 +157,12 @@ export function computer_command(home, site, action, payload) {
|
|
|
156
157
|
return relayed_command(room?.site ?? local_origin(home), action, input);
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
/** Run one local operation; browsing project files is answered in this process, since it is asked for on every folder opened. */
|
|
161
|
+
async function run_command(home, command, run) {
|
|
162
|
+
if (command.command !== 'room-files' || run !== run_launch_command) return run(command);
|
|
163
|
+
return { status: 200, body: await remote_room_files(home, command.input) };
|
|
164
|
+
}
|
|
165
|
+
|
|
159
166
|
/** Answer one relayed request: room routes and uploads go to the local website, everything else to the local entrypoint as before. */
|
|
160
167
|
export async function answer_request(home, site, link, frame, { run = run_launch_command, fetcher, report } = {}) {
|
|
161
168
|
const { id, envelope } = frame;
|
|
@@ -173,7 +180,7 @@ export async function answer_request(home, site, link, frame, { run = run_launch
|
|
|
173
180
|
: action === 'computer-site' ? { status: 200, body: { site: local_origin(home) } }
|
|
174
181
|
: action === 'room-api' ? await room_api(home, site, payload, { fetcher, device })
|
|
175
182
|
: action === 'room-upload' ? await room_upload(home, site, payload, { fetcher })
|
|
176
|
-
: await
|
|
183
|
+
: await run_command(home, { ...(target === 'computer' ? computer_command(home, site, action, payload) : relayed_command(site, action, payload)), device }, run);
|
|
177
184
|
} catch (error) { response = { status: 400, body: { error: error.message, ...(error.need ? { need: error.need } : {}) } }; }
|
|
178
185
|
// Remote writes go to this computer's audit log: kind, route, device and outcome, never content. Reads are not logged.
|
|
179
186
|
if (request.action && !(request.action === 'room-api' && (request.payload?.method || 'GET') === 'GET') && !DEVICE_ACTIONS.has(request.action) && request.action !== 'computer-site') {
|