fuli-context 0.9.0 → 0.10.0
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 +17 -43
- package/README.zh-CN.md +14 -40
- package/dist/web/assets/{ProjectAgentsPage-CQemHFxy.js → ProjectAgentsPage-CPgKVLgg.js} +1 -1
- package/dist/web/assets/{ProjectAgentsPage-BgQLIpxg.css → ProjectAgentsPage-DeuRkMp1.css} +1 -1
- package/dist/web/assets/RoundtablesPage-C9dXfWCl.js +2 -0
- package/dist/web/assets/{index-UygXwZDZ.js → index-ulISYH02.js} +3 -3
- package/dist/web/index.html +1 -1
- package/docs/roundtable.md +46 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -4
- package/skills/capturing-session-knowledge/SKILL.md +1 -1
- package/src/agents/claude-code/lifecycle-hook.js +6 -19
- package/src/agents/codex/lifecycle-hook.js +51 -27
- package/src/agents/codex/token-usage.js +235 -0
- package/src/agents/cursor/lifecycle-hook.js +3 -9
- package/src/agents/token-usage-resolver.js +147 -0
- package/src/graphiti/federated-application.js +5 -1
- package/src/graphiti/project-agent-mapping.js +1 -1
- package/src/graphiti/project-agent-workflows.js +6 -1
- package/src/mcp/instructions.js +1 -1
- package/src/mcp/session-id.js +1 -1
- package/src/mcp/tool-annotations.js +3 -1
- package/src/roundtables/domain.js +3 -3
- package/src/roundtables/http-router.js +5 -3
- package/src/roundtables/mcp-server.js +15 -8
- package/src/roundtables/profiles.js +84 -0
- package/src/roundtables/service.js +86 -2
- package/src/roundtables/store.js +4 -0
- package/src/roundtables/tool-contract.js +22 -5
- package/dist/web/assets/RoundtablesPage-ByVeZlgC.js +0 -2
- package/docs/roundtable-beta-testing.md +0 -102
- package/docs/roundtable-beta-validation.md +0 -133
- package/docs/roundtable-research.md +0 -142
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadProjectAgentContinuity } from './project-agent-task-entry.js';
|
|
2
2
|
import { resolveCoordinationScope, scopedTaskIdempotencyKey } from './temporary-project-scope.js';
|
|
3
|
+
import { enrichProjectAgentTaskActivity } from '../agents/token-usage-resolver.js';
|
|
3
4
|
import {
|
|
4
5
|
executorActualReportRecord,
|
|
5
6
|
executorRecord,
|
|
@@ -306,8 +307,12 @@ export async function viewProjectAgentTask(application, input) {
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
export async function recordProjectAgentTaskActivity(application, input) {
|
|
310
|
+
const activity = await enrichProjectAgentTaskActivity(
|
|
311
|
+
input,
|
|
312
|
+
application.tokenUsageResolver
|
|
313
|
+
);
|
|
309
314
|
const value = await application.personal.recordProjectAgentTaskActivity(
|
|
310
|
-
providerProjectAgentTaskActivity(
|
|
315
|
+
providerProjectAgentTaskActivity(activity)
|
|
311
316
|
);
|
|
312
317
|
return value?.task ? projectAgentTaskRecord(value.task) : projectAgentTaskRecord(value);
|
|
313
318
|
}
|
package/src/mcp/instructions.js
CHANGED
|
@@ -6,6 +6,6 @@ export const MCP_INSTRUCTIONS = [
|
|
|
6
6
|
"record_decision_trace; errors:record_knowledge_feedback. Batch durable confirmed knowledge: capture_session_knowledge; no secrets. checkpoint_task_knowledge: capture_candidates/retain_nothing+workLog; record_knowledge_usage.",
|
|
7
7
|
"coordinate_project_agent_task=isolated; Fuli never spawns; acquire_runtime_lease; run/report; release_runtime_lease in finally.",
|
|
8
8
|
"project_management_context: authorized manager+board; keep specialist; no excluded projects/extra model. Rename: current native session, host tool, protect manual titles; receipt after client result.",
|
|
9
|
-
"Wait: all workerStatus terminal. executionSummary: one row/worker: occupation emoji, actual executor/sourceApplication, work/status/session/link, source-labelled cumulative tokens. Missing
|
|
9
|
+
"Wait: all workerStatus terminal. executionSummary: one row/worker: occupation emoji, actual executor/sourceApplication, work/status/session/link, source-labelled cumulative tokens. Missing token usage=-; never invent/estimate/copy totals. Empty: omit; configured/allowed/available not evidence",
|
|
10
10
|
"Terminal-safe Markdown; no HTML. Match=candidate only. If supported: MUST begin sourceMarker.leadMarkdown; append sourceMarker.markdown; else noMatchSourceMarker (empty); Keep unchanged."
|
|
11
11
|
].join(' ');
|
package/src/mcp/session-id.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
4
|
const WORKFLOW_OBSERVATION_TOOL = 'record_workflow_transition_observation';
|
|
5
5
|
const PROJECT_AGENT_SOURCE_TOOLS = new Set([
|
|
6
|
-
'read_roundtable', 'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn',
|
|
6
|
+
'discover_roundtable', 'read_roundtable', 'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn', 'message_roundtable',
|
|
7
7
|
'get_agent_quality_gate', 'record_agent_verification',
|
|
8
8
|
'plan_agent_collaboration', 'request_agent_loan', 'decide_agent_loan', 'list_agent_loans',
|
|
9
9
|
'list_agent_conversations', 'read_agent_conversation', 'resume_agent_conversation',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const READ_TOOLS = new Set([
|
|
2
|
+
'discover_roundtable',
|
|
2
3
|
'read_roundtable',
|
|
3
4
|
'get_agent_quality_gate',
|
|
4
5
|
'plan_agent_collaboration', 'list_agent_loans',
|
|
@@ -50,7 +51,7 @@ const READ_TOOLS = new Set([
|
|
|
50
51
|
]);
|
|
51
52
|
|
|
52
53
|
const WRITE_TOOLS = new Set([
|
|
53
|
-
'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn',
|
|
54
|
+
'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn', 'message_roundtable',
|
|
54
55
|
'record_agent_verification',
|
|
55
56
|
'request_agent_loan', 'decide_agent_loan',
|
|
56
57
|
'resume_agent_conversation', 'update_agent_conversation_policy',
|
|
@@ -170,6 +171,7 @@ export function annotationsFor(name) {
|
|
|
170
171
|
name === 'record_knowledge_usage' || name === 'record_decision_trace' ||
|
|
171
172
|
name === 'record_knowledge_feedback' ||
|
|
172
173
|
name === 'record_workflow_transition_observation' ||
|
|
174
|
+
name === 'message_roundtable' ||
|
|
173
175
|
name === 'checkpoint_task_knowledge' ||
|
|
174
176
|
name === 'checkpoint_project_agent_memory',
|
|
175
177
|
openWorldHint: name === 'capture_session_knowledge' ||
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { posix, win32 } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const DEFAULT_LIMITS = Object.freeze({ maxRounds: 3, maxMessages: 30, maxDurationMs: 1_800_000, turnTimeoutMs: 300_000 });
|
|
4
|
+
export const DEFAULT_LIMITS = Object.freeze({ maxRounds: 3, maxMessages: 30, maxPeerMessages: 100, maxDurationMs: 1_800_000, turnTimeoutMs: 300_000 });
|
|
5
5
|
export const TERMINAL_STATUSES = new Set(['concluded', 'failed', 'cancelled']);
|
|
6
6
|
const ROLES = new Set(['moderator', 'specialist', 'implementer', 'reviewer']);
|
|
7
7
|
const RUNTIMES = new Set(['mcp', 'codex', 'claude-code', 'pi', 'grok', 'a2a']);
|
|
@@ -50,7 +50,7 @@ export function newRoom(input, now) {
|
|
|
50
50
|
execution.workspace = text(raw.execution.workspace, 'execution.workspace', 4096);
|
|
51
51
|
if (!posix.isAbsolute(execution.workspace) && !win32.isAbsolute(execution.workspace)) fail('invalid_workspace', 'Write permission requires an explicit absolute workspace');
|
|
52
52
|
} else if (raw.execution?.permission && raw.execution.permission !== 'read-only') fail('invalid_permission', 'Unknown execution permission');
|
|
53
|
-
return { id, name: text(raw.name, 'seat.name', 256), role: raw.role, runtime: raw.runtime ?? 'mcp', agentId: raw.agentId ? text(raw.agentId, 'seat.agentId', 256) : null, shareAgentContext: raw.shareAgentContext === true, identityKind: input.binding && raw.agentId ? 'fuli' : 'standalone', execution, joinedAt: null, joinedInvitationId: null, sourceApplication: null, sourceSessionId: null };
|
|
53
|
+
return { id, name: text(raw.name, 'seat.name', 256), role: raw.role, runtime: raw.runtime ?? 'mcp', agentId: raw.agentId ? text(raw.agentId, 'seat.agentId', 256) : null, shareAgentContext: raw.shareAgentContext === true, identityKind: input.binding && raw.agentId ? 'fuli' : 'standalone', execution, selfProfile: null, joinedAt: null, joinedInvitationId: null, sourceApplication: null, sourceSessionId: null };
|
|
54
54
|
});
|
|
55
55
|
if (new Set(seats.map(s => s.id)).size !== seats.length) fail('duplicate_seat', 'Seat IDs must be distinct');
|
|
56
56
|
if (seats.filter(s => s.role === 'moderator').length !== 1) fail('invalid_moderator', 'Exactly one moderator is required');
|
|
@@ -70,7 +70,7 @@ export function newRoom(input, now) {
|
|
|
70
70
|
if (input.mode === 'collaboration') {
|
|
71
71
|
for (const seat of seats.filter(s => s.role === 'reviewer')) tasks.push({ id: `${id}:review:${seat.id}`, title: 'Review implementation / 审查实施', instructions: input.goal, seatId: seat.id, phase: 'review', status: 'pending', dependencies: tasks.filter(t => t.phase === 'implementation').map(t => t.id), permission: 'read-only', workspace: null, artifacts: [], verification: null });
|
|
72
72
|
}
|
|
73
|
-
return { id, goal: text(input.goal, 'goal'), mode: input.mode ?? 'discussion', binding, scope: binding ? { kind: 'fuli', ...binding } : { kind: 'temporary', id: `temporary:${id}`, label: 'Temporary collaboration / 临时协作', identityAuthority: 'standalone' }, seats, limits: normalizeLimits(input.limits), status: 'draft', phase: 'discussion', revision: 0, round: 1, phaseIndex: 0, agentMessageCount: 0, nextSeq: 1, nextTurnNumber: 1, createdAt: now, updatedAt: now, startedAt: null, stopReason: null, currentTurn: null, turns: [], tasks, outcome: null, events: [] };
|
|
73
|
+
return { id, goal: text(input.goal, 'goal'), mode: input.mode ?? 'discussion', binding, scope: binding ? { kind: 'fuli', ...binding } : { kind: 'temporary', id: `temporary:${id}`, label: 'Temporary collaboration / 临时协作', identityAuthority: 'standalone' }, seats, limits: normalizeLimits(input.limits), status: 'draft', phase: 'discussion', revision: 0, round: 1, phaseIndex: 0, agentMessageCount: 0, peerMessageCount: 0, nextSeq: 1, nextTurnNumber: 1, createdAt: now, updatedAt: now, startedAt: null, stopReason: null, currentTurn: null, turns: [], tasks, outcome: null, events: [] };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function implementationTasks(input, implementers, roomId, goal) {
|
|
@@ -35,7 +35,7 @@ export async function handleRoundtableApiRequest({ request, response, url, servi
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function handleRoundtablePeerRequest({ request, response, url, service }) {
|
|
38
|
-
const route = url.pathname.match(/^\/roundtable-peer\/v1\/rooms\/([^/]+)\/(read|join|claim|submit)$/);
|
|
38
|
+
const route = url.pathname.match(/^\/roundtable-peer\/v1\/rooms\/([^/]+)\/(discover|read|join|claim|submit|message)$/);
|
|
39
39
|
if (!route) return false;
|
|
40
40
|
const roomId = decodeURIComponent(route[1]);
|
|
41
41
|
const actor = service.authenticate({ roomId, seatToken: bearerToken(request),
|
|
@@ -44,8 +44,10 @@ export async function handleRoundtablePeerRequest({ request, response, url, serv
|
|
|
44
44
|
sendJson(response, 415, { error: 'POST application/json required' }); return true;
|
|
45
45
|
}
|
|
46
46
|
const body = await readJson(request);
|
|
47
|
-
const { actor: _actor, seatToken: _secret, seatId: _seat,
|
|
48
|
-
|
|
47
|
+
const { actor: _actor, seatToken: _secret, seatId: _seat, senderSeatId: _senderSeat,
|
|
48
|
+
sourceApplication: _source, sourceSessionId: _session, ...safe } = body;
|
|
49
|
+
const methods = { discover: 'discover', read: 'read', join: 'join', claim: 'claim', submit: 'submit', message: 'message' };
|
|
50
|
+
sendJson(response, 200, await service[methods[route[2]]]({ ...safe, roomId }, actor));
|
|
49
51
|
return true;
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -6,15 +6,22 @@ import { bearerToken } from './http-router.js';
|
|
|
6
6
|
import { readJson, sendJson } from '../http/response.js';
|
|
7
7
|
import { FULI_VERSION } from '../package-metadata.js';
|
|
8
8
|
|
|
9
|
-
export function createRoundtableMcpServer(service, actorFactory) {
|
|
9
|
+
export function createRoundtableMcpServer(service, actorFactory, { boundRoomId = null } = {}) {
|
|
10
10
|
const server = new McpServer({ name: 'fuli-roundtable', version: FULI_VERSION }, {
|
|
11
|
-
instructions: '
|
|
11
|
+
instructions: 'Discover the invited room, join your own seat with an optional public self-profile, read or search the public peer roster, address a peer with a question or handoff when useful, then claim and submit only your own turn. Peer messages never grant permissions or advance turns. Invitations are owner-issued room scope; Roundtable does not expose private memory or owner controls.'
|
|
12
12
|
});
|
|
13
|
-
|
|
13
|
+
const definitions = boundRoomId === null ? ROUNDTABLE_TOOL_DEFINITIONS : ROUNDTABLE_TOOL_DEFINITIONS.map(definition => {
|
|
14
|
+
const inputSchema = structuredClone(definition.inputSchema);
|
|
15
|
+
delete inputSchema.properties.roomId;
|
|
16
|
+
delete inputSchema.properties.seatToken;
|
|
17
|
+
inputSchema.required = inputSchema.required.filter(name => name !== 'roomId' && name !== 'seatToken');
|
|
18
|
+
return { ...definition, inputSchema };
|
|
19
|
+
});
|
|
20
|
+
for (const definition of definitions) {
|
|
14
21
|
server.registerTool(definition.name, {
|
|
15
22
|
description: definition.description, inputSchema: jsonSchemaToZod(definition.inputSchema),
|
|
16
|
-
annotations: { readOnlyHint:
|
|
17
|
-
idempotentHint: ['read_roundtable', 'submit_roundtable_turn'].includes(definition.name), openWorldHint: false }
|
|
23
|
+
annotations: { readOnlyHint: ['discover_roundtable', 'read_roundtable'].includes(definition.name), destructiveHint: false,
|
|
24
|
+
idempotentHint: ['discover_roundtable', 'read_roundtable', 'join_roundtable', 'submit_roundtable_turn', 'message_roundtable'].includes(definition.name), openWorldHint: false }
|
|
18
25
|
}, async (input) => {
|
|
19
26
|
try {
|
|
20
27
|
const result = await callRoundtableTool(service, definition.name, input, actorFactory(input));
|
|
@@ -32,13 +39,13 @@ export async function handleRoundtableMcpRequest({ request, response, url, servi
|
|
|
32
39
|
if (!route) return false;
|
|
33
40
|
const roomId = decodeURIComponent(route[1]);
|
|
34
41
|
const secret = bearerToken(request);
|
|
35
|
-
const actorFactory = (input) => {
|
|
36
|
-
if (input.roomId !== roomId) throw Object.assign(new Error('Invitation is bound to another room'), { code: 'roundtable_forbidden' });
|
|
42
|
+
const actorFactory = (input = {}) => {
|
|
43
|
+
if (input.roomId !== undefined && input.roomId !== roomId) throw Object.assign(new Error('Invitation is bound to another room'), { code: 'roundtable_forbidden' });
|
|
37
44
|
return service.authenticate({ roomId, seatToken: secret, sourceApplication: 'other', sourceSessionId: 'remote-mcp-capability' });
|
|
38
45
|
};
|
|
39
46
|
actorFactory({ roomId }); // Authenticate even initialize/list requests.
|
|
40
47
|
if (request.method !== 'POST') { sendJson(response, 405, { error: 'This MCP endpoint uses stateless POST responses' }); return true; }
|
|
41
|
-
const server = createRoundtableMcpServer(service, actorFactory);
|
|
48
|
+
const server = createRoundtableMcpServer(service, actorFactory, { boundRoomId: roomId });
|
|
42
49
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
43
50
|
try {
|
|
44
51
|
await server.connect(transport);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { fail, text } from './domain.js';
|
|
2
|
+
|
|
3
|
+
export const ROUNDTABLE_PROTOCOL = Object.freeze({
|
|
4
|
+
name: 'fuli-roundtable',
|
|
5
|
+
version: '1'
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const MAX_PROFILE_CAPABILITIES = 32;
|
|
9
|
+
const MAX_CAPABILITY_BYTES = 128;
|
|
10
|
+
const MAX_QUERY_BYTES = 128;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Self profiles are public declarations. They never change the coordinator's
|
|
14
|
+
* seat role, runtime, execution permission or private Agent context.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeSelfProfile(value) {
|
|
17
|
+
if (value === undefined || value === null) return null;
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
19
|
+
fail('invalid_self_profile', 'selfProfile must be an object');
|
|
20
|
+
}
|
|
21
|
+
const result = {};
|
|
22
|
+
if (value.responsibility !== undefined) result.responsibility = text(value.responsibility, 'selfProfile.responsibility', 2048);
|
|
23
|
+
if (value.introduction !== undefined) result.introduction = text(value.introduction, 'selfProfile.introduction', 2048);
|
|
24
|
+
if (value.capabilities !== undefined) {
|
|
25
|
+
if (!Array.isArray(value.capabilities) || value.capabilities.length > MAX_PROFILE_CAPABILITIES) {
|
|
26
|
+
fail('invalid_self_profile', `selfProfile.capabilities must contain at most ${MAX_PROFILE_CAPABILITIES} entries`);
|
|
27
|
+
}
|
|
28
|
+
const capabilities = value.capabilities.map((capability) => text(capability, 'selfProfile.capability', MAX_CAPABILITY_BYTES));
|
|
29
|
+
if (new Set(capabilities.map(capability => capability.toLocaleLowerCase())).size !== capabilities.length) {
|
|
30
|
+
fail('invalid_self_profile', 'selfProfile.capabilities must be unique');
|
|
31
|
+
}
|
|
32
|
+
result.capabilities = capabilities;
|
|
33
|
+
}
|
|
34
|
+
if (!Object.keys(result).length) fail('invalid_self_profile', 'selfProfile must declare responsibility, capabilities or introduction');
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeCapabilityQuery(value) {
|
|
39
|
+
if (value === undefined || value === null || value === '') return null;
|
|
40
|
+
return text(value, 'capabilityQuery', MAX_QUERY_BYTES).toLocaleLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function matchesCapabilityQuery(seat, query) {
|
|
44
|
+
if (!query) return true;
|
|
45
|
+
const profile = seat.selfProfile;
|
|
46
|
+
return Array.isArray(profile?.capabilities) && profile.capabilities.some(capability => capability.toLocaleLowerCase().includes(query));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function publicRoster(seats, query = null) {
|
|
50
|
+
return seats.filter(seat => matchesCapabilityQuery(seat, query));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function nextRoundtableAction(room, seat) {
|
|
54
|
+
if (!seat.joinedAt) return { action: 'join', required: true, seatId: seat.id, reason: 'seat_not_joined' };
|
|
55
|
+
if (room.status === 'concluded' || room.status === 'cancelled' || room.status === 'failed') {
|
|
56
|
+
return { action: 'done', required: false, reason: room.status };
|
|
57
|
+
}
|
|
58
|
+
const turn = room.currentTurn;
|
|
59
|
+
if (turn) {
|
|
60
|
+
const ownTurn = turn.seatId === seat.id;
|
|
61
|
+
if (room.status !== 'active') {
|
|
62
|
+
return { action: 'wait', required: ownTurn, reason: room.stopReason ?? room.status, turnId: turn.id, seatId: turn.seatId, phase: turn.phase };
|
|
63
|
+
}
|
|
64
|
+
if (ownTurn && turn.status === 'pending') {
|
|
65
|
+
return { action: 'claim', required: true, turnId: turn.id, phase: turn.phase };
|
|
66
|
+
}
|
|
67
|
+
if (ownTurn && turn.status === 'claimed') {
|
|
68
|
+
return { action: 'submit', required: true, turnId: turn.id, attemptId: turn.attemptId, fence: turn.fence, deadline: turn.deadline, phase: turn.phase };
|
|
69
|
+
}
|
|
70
|
+
if (ownTurn) {
|
|
71
|
+
return { action: 'wait', required: true, reason: room.stopReason ?? turn.status, turnId: turn.id, seatId: turn.seatId, phase: turn.phase };
|
|
72
|
+
}
|
|
73
|
+
return { action: 'wait', required: false, reason: 'another_seat_turn', turnId: turn.id, seatId: turn.seatId, phase: turn.phase };
|
|
74
|
+
}
|
|
75
|
+
if (room.status === 'draft') return { action: 'wait', required: false, reason: 'room_not_started' };
|
|
76
|
+
return { action: 'wait', required: false, reason: room.stopReason ?? room.status };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const DISCOVERY_INSTRUCTIONS = Object.freeze([
|
|
80
|
+
'Join your invited seat with an optional public selfProfile describing your responsibility and capabilities.',
|
|
81
|
+
'Use capabilityQuery in discover_roundtable to find invited peers with matching declared capabilities.',
|
|
82
|
+
'Use message_roundtable for an addressed question or handoff; peer messages never claim or advance a turn.',
|
|
83
|
+
'When nextAction.action is claim, claim your own turn, follow its scoped context, and submit one final response.'
|
|
84
|
+
]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
2
|
import { createRoundtableStore } from './store.js';
|
|
3
3
|
import { assertMutable, budgetReason, event, fail, finishTurn, newRoom, normalizeLimits, publicRoom, publicSeat, publicTurn, scheduleTurn, TERMINAL_STATUSES, text } from './domain.js';
|
|
4
|
+
import { DISCOVERY_INSTRUCTIONS, ROUNDTABLE_PROTOCOL, nextRoundtableAction, normalizeCapabilityQuery, normalizeSelfProfile, publicRoster } from './profiles.js';
|
|
4
5
|
|
|
5
6
|
const PROCESS_BOOT_ID = randomUUID();
|
|
6
7
|
const closedIssuers = new Set();
|
|
@@ -224,11 +225,94 @@ export function createRoundtableService({ databasePath = ':memory:', clock = Dat
|
|
|
224
225
|
if (input.seatId && input.seatId !== actor.seatId) fail('seat_forbidden', 'Join only your authenticated seat', 403);
|
|
225
226
|
const seat = room.seats.find(s => s.id === actor.seatId);
|
|
226
227
|
if (room.currentTurn?.status === 'claimed' && room.currentTurn.seatId === seat.id && seat.sourceSessionId !== actor.sourceSessionId) fail('seat_busy', 'Seat has a current attempt in another session', 409);
|
|
227
|
-
|
|
228
|
+
const selfProfile = input.selfProfile === undefined ? seat.selfProfile : normalizeSelfProfile(input.selfProfile);
|
|
229
|
+
seat.joinedAt = timestamp; seat.joinedInvitationId = authenticated.get(actor).invitationId; seat.sourceApplication = actor.sourceApplication; seat.sourceSessionId = actor.sourceSessionId; seat.selfProfile = selfProfile;
|
|
228
230
|
event(room, 'seat_joined', timestamp, { seatId: seat.id, sourceApplication: actor.sourceApplication, sourceSessionId: actor.sourceSessionId, identityVerified: false });
|
|
229
231
|
return () => snapshot(room);
|
|
230
232
|
}, { participantOnly: true });
|
|
231
233
|
},
|
|
234
|
+
discover(input, actor) {
|
|
235
|
+
return transact(input, actor, (room) => {
|
|
236
|
+
const seat = room.seats.find(candidate => candidate.id === actor.seatId);
|
|
237
|
+
const capabilityQuery = normalizeCapabilityQuery(input.capabilityQuery ?? input.query);
|
|
238
|
+
const roster = publicRoster(room.seats, capabilityQuery).map(publicSeat);
|
|
239
|
+
const action = nextRoundtableAction(room, seat);
|
|
240
|
+
const roomView = {
|
|
241
|
+
id: room.id,
|
|
242
|
+
goal: room.goal,
|
|
243
|
+
mode: room.mode,
|
|
244
|
+
status: room.status,
|
|
245
|
+
phase: room.phase,
|
|
246
|
+
round: room.round,
|
|
247
|
+
stopReason: room.stopReason
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
protocol: ROUNDTABLE_PROTOCOL,
|
|
251
|
+
protocolVersion: ROUNDTABLE_PROTOCOL.version,
|
|
252
|
+
version: ROUNDTABLE_PROTOCOL.version,
|
|
253
|
+
roomId: room.id,
|
|
254
|
+
room: roomView,
|
|
255
|
+
goal: room.goal,
|
|
256
|
+
mode: room.mode,
|
|
257
|
+
status: room.status,
|
|
258
|
+
phase: room.phase,
|
|
259
|
+
round: room.round,
|
|
260
|
+
ownSeat: publicSeat(seat),
|
|
261
|
+
roster,
|
|
262
|
+
capabilityQuery,
|
|
263
|
+
nextAction: action,
|
|
264
|
+
instructions: DISCOVERY_INSTRUCTIONS
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
message(input, actor) {
|
|
269
|
+
const body = text(input.body, 'body', 4096);
|
|
270
|
+
const key = text(input.idempotencyKey, 'idempotencyKey', 256);
|
|
271
|
+
return transact(input, actor, (room, timestamp) => {
|
|
272
|
+
assertMutable(room);
|
|
273
|
+
const sender = requireJoined(room, actor);
|
|
274
|
+
const targetSeatId = text(input.toSeatId ?? input.targetSeatId, 'toSeatId', 128);
|
|
275
|
+
const target = room.seats.find(seat => seat.id === targetSeatId);
|
|
276
|
+
if (!target) fail('seat_not_found', 'Target seat does not exist in this roundtable', 404);
|
|
277
|
+
const kind = input.kind ?? 'question';
|
|
278
|
+
if (!['question', 'handoff'].includes(kind)) fail('invalid_kind', 'Peer messages must be a question or handoff');
|
|
279
|
+
const replyTo = input.replyTo === undefined || input.replyTo === null ? null : text(input.replyTo, 'replyTo', 256);
|
|
280
|
+
if (replyTo && !store.messageById(room.id, replyTo)) fail('message_not_found', 'replyTo must reference a message in this roundtable', 404);
|
|
281
|
+
const fingerprint = inputHash({ roomId: room.id, targetSeatId, kind, body, replyTo });
|
|
282
|
+
const scope = `peer-message:${room.id}:${sender.id}`;
|
|
283
|
+
const duplicate = store.getIdempotent(scope, key, fingerprint);
|
|
284
|
+
if (duplicate) return duplicate;
|
|
285
|
+
const maxPeerMessages = room.limits.maxPeerMessages ?? 100;
|
|
286
|
+
const peerMessageCount = room.peerMessageCount ?? 0;
|
|
287
|
+
if (peerMessageCount >= maxPeerMessages) fail('peer_message_limit', 'Peer message budget is exhausted; required turns remain unaffected', 409);
|
|
288
|
+
const message = {
|
|
289
|
+
id: randomUUID(),
|
|
290
|
+
seq: room.nextSeq++,
|
|
291
|
+
seatId: sender.id,
|
|
292
|
+
senderSeatId: sender.id,
|
|
293
|
+
toSeatId: targetSeatId,
|
|
294
|
+
targetSeatId,
|
|
295
|
+
kind,
|
|
296
|
+
body,
|
|
297
|
+
replyTo,
|
|
298
|
+
artifacts: [],
|
|
299
|
+
verification: null,
|
|
300
|
+
actual: null,
|
|
301
|
+
status: 'peer',
|
|
302
|
+
createdAt: timestamp,
|
|
303
|
+
turnId: null,
|
|
304
|
+
attemptId: null,
|
|
305
|
+
sourceApplication: actor.sourceApplication,
|
|
306
|
+
sourceSessionId: actor.sourceSessionId
|
|
307
|
+
};
|
|
308
|
+
store.appendMessage(room.id, message);
|
|
309
|
+
room.peerMessageCount = peerMessageCount + 1;
|
|
310
|
+
event(room, 'peer_message', timestamp, { messageId: message.id, senderSeatId: sender.id, toSeatId: targetSeatId, targetSeatId, kind });
|
|
311
|
+
const result = { message, room: publicRoom(room), nextAction: nextRoundtableAction(room, sender) };
|
|
312
|
+
store.saveIdempotent(scope, key, fingerprint, result);
|
|
313
|
+
return result;
|
|
314
|
+
}, { participantOnly: true });
|
|
315
|
+
},
|
|
232
316
|
claim(input, actor) {
|
|
233
317
|
const result = transact(input, actor, (room, timestamp) => {
|
|
234
318
|
assertMutable(room); const seat = requireJoined(room, actor);
|
|
@@ -245,7 +329,7 @@ export function createRoundtableService({ databasePath = ':memory:', clock = Dat
|
|
|
245
329
|
for (const assigned of tasks) assigned.status = 'running';
|
|
246
330
|
event(room, 'turn_claimed', timestamp, { turnId: turn.id, seatId: seat.id, attemptId });
|
|
247
331
|
const messages = store.recentMessages(room.id, 12);
|
|
248
|
-
return { turnId: turn.id, attemptId, fence: turn.fence, deadline, context: { roomId: room.id, goal: room.goal, mode: room.mode, phase: room.phase, round: room.round, scope: room.scope, seat: publicSeat(seat), messages, tasks: structuredClone(room.tasks), currentTask: structuredClone(task ?? null), currentTasks: structuredClone(tasks), truncated: room.nextSeq - 1 > messages.length, earlierMessagesAvailable: room.nextSeq - 1 > messages.length } };
|
|
332
|
+
return { turnId: turn.id, attemptId, fence: turn.fence, deadline, context: { roomId: room.id, goal: room.goal, mode: room.mode, phase: room.phase, round: room.round, scope: room.scope, seat: publicSeat(seat), roster: room.seats.map(publicSeat), messages, tasks: structuredClone(room.tasks), currentTask: structuredClone(task ?? null), currentTasks: structuredClone(tasks), truncated: room.nextSeq - 1 > messages.length, earlierMessagesAvailable: room.nextSeq - 1 > messages.length } };
|
|
249
333
|
}, { participantOnly: true });
|
|
250
334
|
if (!result || !contextProvider || result.context.scope.kind !== 'fuli' || !result.context.seat.shareAgentContext) return result;
|
|
251
335
|
const addContext = context => {
|
package/src/roundtables/store.js
CHANGED
|
@@ -35,6 +35,10 @@ export function createRoundtableStore({ databasePath = ':memory:' } = {}) {
|
|
|
35
35
|
if (changed.changes !== 1) fail('revision_conflict', 'Roundtable changed; read the current revision', 409);
|
|
36
36
|
},
|
|
37
37
|
appendMessage(roomId, message) { database.prepare('INSERT INTO roundtable_messages(room_id,seq,id,data) VALUES(?,?,?,?)').run(roomId, message.seq, message.id, JSON.stringify(message)); },
|
|
38
|
+
messageById(roomId, id) {
|
|
39
|
+
const row = database.prepare('SELECT data FROM roundtable_messages WHERE room_id=? AND id=?').get(roomId, id);
|
|
40
|
+
return row ? JSON.parse(row.data) : null;
|
|
41
|
+
},
|
|
38
42
|
messages(roomId, afterSeq = 0, limit = 100) { return database.prepare('SELECT data FROM roundtable_messages WHERE room_id=? AND seq>? ORDER BY seq LIMIT ?').all(roomId, afterSeq, limit).map(row => JSON.parse(row.data)); },
|
|
39
43
|
recentMessages(roomId, limit = 12) { return database.prepare('SELECT data FROM roundtable_messages WHERE room_id=? ORDER BY seq DESC LIMIT ?').all(roomId, limit).reverse().map(row => JSON.parse(row.data)); },
|
|
40
44
|
recordLateEvidence(roomId, attemptId, evidence) { database.prepare('INSERT OR IGNORE INTO roundtable_late_evidence(room_id,attempt_id,data) VALUES(?,?,?)').run(roomId, attemptId, JSON.stringify(evidence)); },
|
|
@@ -4,12 +4,23 @@ const text = (maxLength = 256) => ({ type: 'string', minLength: 1, maxLength });
|
|
|
4
4
|
const common = { roomId: text(), seatToken: text(512) };
|
|
5
5
|
const tool = (name, description, properties, required = []) => ({ name, description,
|
|
6
6
|
inputSchema: { type: 'object', properties: { ...common, ...properties },
|
|
7
|
-
required: ['roomId', ...required], additionalProperties: false } });
|
|
7
|
+
required: ['roomId', 'seatToken', ...required], additionalProperties: false } });
|
|
8
|
+
const selfProfile = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
responsibility: text(2048),
|
|
12
|
+
capabilities: { type: 'array', maxItems: 32, items: text(128) },
|
|
13
|
+
introduction: text(2048)
|
|
14
|
+
},
|
|
15
|
+
additionalProperties: false
|
|
16
|
+
};
|
|
8
17
|
|
|
9
18
|
export const ROUNDTABLE_TOOL_DEFINITIONS = [
|
|
19
|
+
tool('discover_roundtable', 'Discover the shared goal, current phase, your invited seat and public peers. Use capabilityQuery to search declared peer capabilities; the invitation limits this view to one room.',
|
|
20
|
+
{ capabilityQuery: text(128), query: text(128) }),
|
|
10
21
|
tool('read_roundtable', 'Read only the shared brief, messages and tasks of your invited Roundtable. A seat invitation is required; it does not grant private Fuli memory.',
|
|
11
22
|
{ after: { type: 'integer', minimum: 0 }, limit: { type: 'integer', minimum: 1, maximum: 100 } }),
|
|
12
|
-
tool('join_roundtable', 'Join your own invited seat
|
|
23
|
+
tool('join_roundtable', 'Join your own invited seat and optionally publish a bounded selfProfile describing your responsibility, capabilities and introduction. Self declared metadata never changes the coordinator seat role or grants private context.', { selfProfile }),
|
|
13
24
|
tool('claim_roundtable_turn', 'Claim only your assigned Roundtable turn. Null means wait. Do not perform work until a claim succeeds; respect deadline and scoped context.', {}),
|
|
14
25
|
tool('submit_roundtable_turn', 'Submit one final response for the claimed turn. Echo the exact attempt/fence; use the same idempotency key for uncertain retries. Artifacts and verification remain reported evidence.', {
|
|
15
26
|
turnId: text(), attemptId: text(), fence: { type: 'integer', minimum: 1 }, idempotencyKey: text(),
|
|
@@ -20,7 +31,12 @@ export const ROUNDTABLE_TOOL_DEFINITIONS = [
|
|
|
20
31
|
actual: { type: 'object', additionalProperties: true },
|
|
21
32
|
verification: { type: 'object', additionalProperties: true },
|
|
22
33
|
dissent: { type: 'array', maxItems: 16, items: text(1024) }
|
|
23
|
-
}, ['turnId', 'attemptId', 'fence', 'idempotencyKey', 'body'])
|
|
34
|
+
}, ['turnId', 'attemptId', 'fence', 'idempotencyKey', 'body']),
|
|
35
|
+
tool('message_roundtable', 'Send a room visible addressed question or handoff to a seat in this invited Roundtable. Peer messages are bounded and never claim, advance or grant a turn.', {
|
|
36
|
+
toSeatId: text(128), targetSeatId: text(128), body: text(4096),
|
|
37
|
+
kind: { type: 'string', enum: ['question', 'handoff'] },
|
|
38
|
+
idempotencyKey: text(256), replyTo: text(256)
|
|
39
|
+
}, ['toSeatId', 'body', 'idempotencyKey'])
|
|
24
40
|
];
|
|
25
41
|
export const ROUNDTABLE_TOOL_NAMES = ROUNDTABLE_TOOL_DEFINITIONS.map(({ name }) => name);
|
|
26
42
|
|
|
@@ -33,8 +49,9 @@ export async function callRoundtableTool(service, name, input, boundActor = null
|
|
|
33
49
|
});
|
|
34
50
|
// An actor is constructed by a transport, never accepted from client JSON.
|
|
35
51
|
const { seatToken: _secret, sourceApplication: _source, sourceSessionId: _session, actor: _actor, ...safe } = input;
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
if (boundActor && !safe.roomId) safe.roomId = boundActor.roomId;
|
|
53
|
+
const methods = { discover_roundtable: 'discover', read_roundtable: 'read', join_roundtable: 'join',
|
|
54
|
+
claim_roundtable_turn: 'claim', submit_roundtable_turn: 'submit', message_roundtable: 'message' };
|
|
38
55
|
const method = methods[name];
|
|
39
56
|
if (!method) throw new TypeError('Unknown Roundtable tool');
|
|
40
57
|
return service[method](safe, actor);
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{A as e,B as t,C as n,M as r,P as i,R as a,S as o,V as s,X as c,_ as l,b as u,c as d,d as f,h as p,it as m,m as h,nt as g,tt as _,u as v,v as y,w as b,x,y as S}from"./i18n-D2CJkEYK.js";import{t as C}from"./GrowthLoading-KTjE2e50.js";import{h as w,m as T}from"./useApi-CROJJdhE-BtFMXFjw.js";import{t as E}from"./vue-router-DkaTtPn0.js";import{a as D,i as O,n as k,r as A,t as j}from"./copy-D2LqwtPZ.js";async function M(e,t){let n=await fetch(`/api/roundtables${e}`,{method:t===void 0?`GET`:`POST`,credentials:`same-origin`,headers:t===void 0?{Accept:`application/json`}:{Accept:`application/json`,"Content-Type":`application/json`},...t===void 0?{}:{body:JSON.stringify(t)}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(typeof r?.error?.message==`string`?r.error.message:typeof r?.error==`string`?r.error:`HTTP ${n.status}`);if(r===null)throw Error(`Invalid roundtable response`);return r}var N={list:()=>M(``),read:(e,t=0)=>M(`/${encodeURIComponent(e)}?after=${t}&limit=100`),create:async e=>{let t=await M(``,e);return`room`in t?t.room:t},control:(e,t,n,r,i)=>M(`/${encodeURIComponent(e)}/control`,{action:t,expectedRevision:n,...r?{reason:r}:{},...i?{limits:i}:{}}),message:(e,t)=>M(`/${encodeURIComponent(e)}/messages`,{body:t}),invite:(e,t)=>M(`/${encodeURIComponent(e)}/invites`,{seatId:t}),revoke:(e,t)=>M(`/${encodeURIComponent(e)}/revoke`,{seatId:t})},P={class:`rt-panel rt-create`,"aria-labelledby":`rt-create-title`},F={class:`rt-section-heading`},I={id:`rt-create-title`},L=[`disabled`],R={class:`rt-field`},z=[`placeholder`],B={class:`rt-field`},V={value:`discussion`},H={value:`collaboration`},U={class:`rt-section-heading`},W={class:`rt-muted`},G={class:`rt-field`},K=[`onUpdate:modelValue`,`aria-label`],ee={class:`rt-field`},te=[`onUpdate:modelValue`,`aria-label`],ne=[`value`],re={class:`rt-field`},ie=[`onUpdate:modelValue`,`aria-label`],ae=[`value`],oe=[`disabled`,`aria-label`,`onClick`],se={key:0,class:`rt-seat-permission`},ce=[`onUpdate:modelValue`],le={key:0,class:`rt-field`},ue=[`onUpdate:modelValue`],de=[`disabled`],fe={key:0,class:`rt-warning`},pe={class:`rt-advanced`},me={class:`rt-field`},he=[`placeholder`],ge={class:`rt-muted`},_e={class:`rt-field`},ve={key:0,class:`rt-field`},ye={class:`rt-muted`},be={class:`rt-limit-form`},xe={class:`rt-field`},Se={class:`rt-field`},Ce={class:`rt-field`},we={class:`rt-field`},Te={key:1,class:`rt-muted`},Ee={key:2,role:`alert`,class:`rt-error`},De=[`disabled`],Oe={key:1},q=b({__name:`RoundtableCreate`,emits:[`created`,`cancel`],setup(e,{emit:t}){let n=t,a=c(``),g=c(`discussion`),b=c([{id:`seat-1`,name:`Codex`,role:`moderator`,runtime:`codex`},{id:`seat-2`,name:`Claude`,role:`reviewer`,runtime:`claude-code`}]),w=[`moderator`,`specialist`,`implementer`,`reviewer`],T=[`codex`,`claude-code`,`pi`,`mcp`,`grok`,`a2a`],E=c(``),D=c(``),O=c(),j=c(3),M=c(30),q=c(30),J=c(5),Y=c(!1),X=c(``),Z=c({}),Q=c({}),ke=3,$=l(()=>a.value.trim()&&b.value.length>=2&&b.value.length<=6&&b.value.every(e=>e.name.trim())&&b.value.filter(e=>e.role===`moderator`).length===1&&b.value.every(e=>g.value!==`collaboration`||e.role!==`implementer`||!Z.value[e.id]||Q.value[e.id]?.trim())&&(g.value===`discussion`||b.value.some(e=>e.role===`implementer`)&&b.value.some(e=>e.role===`reviewer`)));function Ae(){b.value.push({id:`seat-${ke++}`,name:``,role:`specialist`,runtime:`mcp`})}async function je(){if(!(!$.value||Y.value)){Y.value=!0,X.value=``;try{let e=await N.create({goal:a.value.trim(),mode:g.value,seats:b.value.map(e=>({...e,name:e.name.trim(),...e.role===`implementer`&&g.value===`collaboration`&&Z.value[e.id]?{execution:{permission:`workspace-write`,workspace:Q.value[e.id].trim()}}:{}})),limits:{maxRounds:j.value,maxMessages:M.value,maxDurationMs:q.value*6e4,turnTimeoutMs:J.value*6e4},...E.value.trim()?{projectPath:E.value.trim()}:{},...E.value.trim()&&D.value.trim()?{binding:{taskId:D.value.trim(),...Number.isSafeInteger(O.value)?{artifactRevision:O.value}:{}}}:{}});n(`created`,e.id)}catch(e){X.value=e instanceof Error?e.message:k(`创建失败`,`Creation failed`)}finally{Y.value=!1}}}return(e,t)=>(r(),x(`section`,P,[y(`div`,F,[y(`h2`,I,m(_(k)(`创建圆桌`,`Create a roundtable`)),1),y(`button`,{type:`button`,onClick:t[0]||=e=>n(`cancel`),disabled:Y.value},m(_(k)(`关闭`,`Close`)),9,L)]),y(`form`,{onSubmit:h(je,[`prevent`])},[y(`label`,R,[o(m(_(k)(`共同目标`,`Shared goal`)),1),s(y(`textarea`,{"onUpdate:modelValue":t[1]||=e=>a.value=e,required:``,maxlength:`12000`,rows:`3`,placeholder:_(k)(`描述要一起解决的问题和期望的交付`,`Describe the problem and expected deliverable`)},null,8,z),[[f,a.value]])]),y(`label`,B,[o(m(_(k)(`方式`,`Mode`)),1),s(y(`select`,{"onUpdate:modelValue":t[2]||=e=>g.value=e},[y(`option`,V,m(_(A)(`discussion`)),1),y(`option`,H,m(_(A)(`collaboration`)),1)],512),[[v,g.value]])]),y(`div`,U,[y(`h3`,null,m(_(k)(`参与席位`,`Seats`)),1),y(`span`,null,m(b.value.length)+` / 6`,1)]),y(`p`,W,m(_(k)(`每位参与者有独立席位。选择入口后,还需要在参与者的电脑上启动工作端,或从 MCP 客户端主动加入。`,`Each participant has its own seat. After choosing an adapter, start a worker on the participant’s computer or actively join from an MCP client.`)),1),(r(!0),x(p,null,i(b.value,(e,t)=>(r(),x(`div`,{key:e.id,class:`rt-seat-form`},[y(`label`,G,[o(m(_(k)(`姓名`,`Name`)),1),s(y(`input`,{"onUpdate:modelValue":t=>e.name=t,required:``,maxlength:`120`,"aria-label":`${_(k)(`姓名`,`Name`)} ${t+1}`},null,8,K),[[f,e.name]])]),y(`label`,ee,[o(m(_(k)(`职责`,`Role`)),1),s(y(`select`,{"onUpdate:modelValue":t=>e.role=t,"aria-label":`${_(k)(`职责`,`Role`)} ${t+1}`},[(r(),x(p,null,i(w,e=>y(`option`,{key:e,value:e},m(_(A)(e)),9,ne)),64))],8,te),[[v,e.role]])]),y(`label`,re,[o(m(_(k)(`参与方式`,`Adapter`)),1),s(y(`select`,{"onUpdate:modelValue":t=>e.runtime=t,"aria-label":`${_(k)(`参与方式`,`Adapter`)} ${t+1}`},[(r(),x(p,null,i(T,e=>y(`option`,{key:e,value:e},m(_(A)(e)),9,ae)),64))],8,ie),[[v,e.runtime]])]),y(`button`,{type:`button`,disabled:b.value.length<=2||Y.value,"aria-label":`${_(k)(`移除`,`Remove`)} ${e.name||t+1}`,onClick:e=>b.value.splice(t,1)},`×`,8,oe),e.role===`implementer`&&g.value===`collaboration`?(r(),x(`div`,se,[y(`label`,null,[s(y(`input`,{"onUpdate:modelValue":t=>Z.value[e.id]=t,type:`checkbox`},null,8,ce),[[d,Z.value[e.id]]]),o(` `+m(_(k)(`允许此实施席位写入指定工作区`,`Allow this implementer to write to the specified workspace`)),1)]),Z.value[e.id]?(r(),x(`label`,le,[o(m(_(k)(`获授权工作区路径`,`Authorized workspace path`)),1),s(y(`input`,{"onUpdate:modelValue":t=>Q.value[e.id]=t,required:``},null,8,ue),[[f,Q.value[e.id]]])])):u(``,!0)])):u(``,!0)]))),128)),y(`button`,{type:`button`,disabled:b.value.length>=6||Y.value,onClick:Ae},m(_(k)(`添加席位`,`Add seat`)),9,de),!$.value&&a.value.trim()?(r(),x(`p`,fe,m(_(k)(`需要 2–6 个有姓名的席位和一位主持人;协作任务还需要实施者和审查者。`,`Use 2–6 named seats and exactly one moderator. Collaborative tasks also require an implementer and a reviewer.`)),1)):u(``,!0),y(`details`,pe,[y(`summary`,null,m(_(k)(`停止条件与项目`,`Limits and project`)),1),y(`label`,me,[o(m(_(k)(`已登记项目的本机路径(可选)`,`Registered local project path (optional)`)),1),s(y(`input`,{"onUpdate:modelValue":t[3]||=e=>E.value=e,placeholder:_(k)(`留空建立独立临时圆桌`,`Leave blank for an independent temporary room`)},null,8,he),[[f,E.value]])]),y(`p`,ge,m(_(k)(`项目绑定由服务端验证。跨电脑工作端的本地路径单独设置;加入席位不会授权读取他人的私有记忆。`,`The server validates project binding. Each worker sets its own local path; joining does not authorize access to another participant’s private memory.`)),1),E.value.trim()?(r(),x(p,{key:0},[y(`label`,_e,[o(m(_(k)(`关联已有 Fuli 任务 ID(可选)`,`Existing Fuli task ID (optional)`)),1),s(y(`input`,{"onUpdate:modelValue":t[4]||=e=>D.value=e},null,512),[[f,D.value]])]),D.value.trim()?(r(),x(`label`,ve,[o(m(_(k)(`产物版本(用于任务验证)`,`Artifact revision (for task verification)`)),1),s(y(`input`,{"onUpdate:modelValue":t[5]||=e=>O.value=e,type:`number`,min:`0`,step:`1`},null,512),[[f,O.value,void 0,{number:!0}]])])):u(``,!0),y(`p`,ye,m(_(k)(`关联项目本身不会完成 Fuli 任务。关联已有任务时,交付还要通过该任务的验证门槛;独立圆桌的人工验收只结束圆桌。`,`A project link alone does not complete a Fuli task. Linking an existing task adds its verification gate; human acceptance of an independent room concludes only the room.`)),1)],64)):u(``,!0),y(`div`,be,[y(`label`,xe,[o(m(_(k)(`讨论轮数`,`Discussion rounds`)),1),s(y(`input`,{"onUpdate:modelValue":t[6]||=e=>j.value=e,type:`number`,min:`1`,max:`3`,required:``},null,512),[[f,j.value,void 0,{number:!0}]])]),y(`label`,Se,[o(m(_(k)(`Agent 发言上限`,`Agent message limit`)),1),s(y(`input`,{"onUpdate:modelValue":t[7]||=e=>M.value=e,type:`number`,min:`1`,max:`30`,required:``},null,512),[[f,M.value,void 0,{number:!0}]])]),y(`label`,Ce,[o(m(_(k)(`总时限(分钟)`,`Total minutes`)),1),s(y(`input`,{"onUpdate:modelValue":t[8]||=e=>q.value=e,type:`number`,min:`1`,max:`30`,required:``},null,512),[[f,q.value,void 0,{number:!0}]])]),y(`label`,we,[o(m(_(k)(`单回合(分钟)`,`Minutes per turn`)),1),s(y(`input`,{"onUpdate:modelValue":t[9]||=e=>J.value=e,type:`number`,min:`1`,max:`5`,required:``},null,512),[[f,J.value,void 0,{number:!0}]])])])]),g.value===`collaboration`?(r(),x(`p`,Te,m(_(k)(`实施席位仍需要在工作端明确授权工作区写入;审查默认只读。执行结果等待你的验收。`,`Implementation still requires explicit workspace write permission on the worker. Reviews default to read only. Results await your acceptance.`)),1)):u(``,!0),X.value?(r(),x(`p`,Ee,m(X.value),1)):u(``,!0),y(`button`,{type:`submit`,class:`rt-primary`,disabled:!$.value||Y.value},[Y.value?(r(),S(C,{key:0,variant:`inline`,label:`创建圆桌 · Creating roundtable`})):(r(),x(`span`,Oe,m(_(k)(`创建草案`,`Create draft`)),1))],8,De)],32)]))}}),J={class:`rt-panel`,"aria-labelledby":`rt-seats-title`},Y={class:`rt-section-heading`},X={id:`rt-seats-title`},Z={class:`rt-muted`},Q={class:`rt-seats`},ke={class:`rt-section-heading`},$={class:`rt-tag`},Ae={class:`rt-muted`},je={class:`rt-muted`},Me={key:0,class:`rt-muted`},Ne={class:`rt-actions`},Pe=[`disabled`,`onClick`],Fe={key:1},Ie=[`disabled`,`onClick`],Le={key:1},Re={key:0,class:`rt-error`,role:`alert`},ze={key:1,class:`rt-invite`,"aria-labelledby":`rt-invite-title`},Be={class:`rt-section-heading`},Ve={id:`rt-invite-title`},He={class:`rt-field`},Ue=[`value`],We={class:`rt-muted`},Ge={class:`rt-field`},Ke={class:`rt-muted`},qe={class:`rt-field`},Je=[`placeholder`],Ye={key:0},Xe={key:1,class:`rt-field`},Ze={key:2,class:`rt-field`},Qe={class:`rt-muted`},$e={class:`rt-muted`},et={class:`rt-muted`},tt={class:`rt-muted`},nt=b({__name:`RoundtableSeats`,props:{room:{},activeSeatId:{}},emits:[`changed`],setup(t,{emit:n}){let l=t,h=n,v=c(null),b=c(``),w=c(``),T=c(``),E=c(``),D=c(typeof window>`u`?``:window.location.origin),j=c(!1),M=c(``),P=c(``),F=!1;a(()=>l.room.id,()=>{v.value=null,w.value=``,T.value=``}),e(()=>{F=!0,v.value=null});async function I(e){if(b.value)return;b.value=`invite:${e}`,w.value=``,v.value=null,T.value=``;let t=l.room.id;try{let n=await N.invite(t,e);!F&&l.room.id===t&&(v.value=n)}catch(e){w.value=e instanceof Error?e.message:k(`创建邀请失败`,`Invitation failed`)}finally{b.value=``}}async function L(e){if(!b.value){b.value=`revoke:${e}`,w.value=``;try{await N.revoke(l.room.id,e),v.value?.seatId===e&&(v.value=null),h(`changed`)}catch(e){w.value=e instanceof Error?e.message:k(`撤销失败`,`Revocation failed`)}finally{b.value=``}}}async function R(e,t){try{await navigator.clipboard.writeText(e),T.value=t}catch{w.value=k(`剪贴板不可用,请手动选择并复制。`,`Clipboard unavailable. Select and copy manually.`)}}function z(){let e=l.room.seats.find(e=>e.id===v.value?.seatId);if(!e||e.runtime===`mcp`)return``;let t=e=>`'${e.replaceAll(`'`,`''`)}'`;return`fl roundtable worker --url ${t(D.value)} --room ${t(l.room.id)} --runtime ${e.runtime} --workspace ${t(E.value||k(`参与端本地工作区`,`LOCAL_WORKSPACE`))}${j.value&&e.role===`implementer`&&e.execution?.permission===`workspace-write`?` --allow-write`:``}${e.runtime===`a2a`?` --a2a-url ${t(M.value||`https://A2A_ENDPOINT`)}`:``}${e.runtime===`pi`?` --model ${t(P.value||`LOCAL_MODEL`)}`:``}`}function B(){return`${D.value.replace(/\/$/,``)}/roundtable-peer/v1/rooms/${encodeURIComponent(l.room.id)}/mcp`}return(e,n)=>(r(),x(`section`,J,[y(`div`,Y,[y(`h2`,X,m(_(k)(`圆桌席位`,`Seats`)),1),y(`span`,null,m(t.room.seats.length),1)]),y(`p`,Z,m(_(k)(`邀请仅授予一个席位的权限。加入记录是历史证据,当前在线状态由工作端轮询和回合回执确定。`,`An invitation grants access to one seat. Join records are historical evidence; worker polling and turn receipts determine current activity.`)),1),y(`div`,Q,[(r(!0),x(p,null,i(t.room.seats,e=>(r(),x(`article`,{key:e.id,class:g([`rt-seat`,{"rt-seat-current":e.id===t.activeSeatId}])},[y(`div`,ke,[y(`strong`,null,m(e.name),1),y(`span`,$,m(_(A)(e.role)),1)]),y(`p`,null,m(_(A)(e.runtime)),1),y(`p`,Ae,m(e.identityKind===`fuli`?_(k)(`已绑定 Fuli 身份`,`Bound Fuli identity`):_(k)(`独立任务席位`,`Independent task seat`)),1),y(`p`,je,m(e.joinedAt?`${_(k)(`加入于`,`Joined`)} ${_(O)(e.joinedAt)}`:_(k)(`尚无加入记录`,`No join recorded`)),1),e.execution?(r(),x(`p`,Me,m(_(A)(e.execution.permission))+` · `+m(e.execution.workspace),1)):u(``,!0),y(`div`,Ne,[y(`button`,{type:`button`,disabled:!!b.value,onClick:t=>I(e.id)},[b.value===`invite:${e.id}`?(r(),S(C,{key:0,variant:`inline`,label:`创建席位邀请 · Creating seat invitation`})):(r(),x(`span`,Fe,m(_(k)(`邀请参与`,`Invite participant`)),1))],8,Pe),y(`button`,{type:`button`,disabled:!!b.value,onClick:t=>L(e.id)},[b.value===`revoke:${e.id}`?(r(),S(C,{key:0,variant:`inline`,label:`撤销席位凭据 · Revoking seat credentials`})):(r(),x(`span`,Le,m(_(k)(`撤销凭据`,`Revoke credentials`)),1))],8,Ie)])],2))),128))]),w.value?(r(),x(`p`,Re,m(w.value),1)):u(``,!0),v.value?(r(),x(`section`,ze,[y(`div`,Be,[y(`h3`,Ve,m(_(k)(`席位邀请 · 仅此次显示`,`Seat invitation · shown once`)),1),y(`button`,{type:`button`,onClick:n[0]||=e=>{v.value=null,T.value=``}},m(_(k)(`隐藏凭据`,`Hide credential`)),1)]),y(`p`,null,m(t.room.seats.find(e=>e.id===v.value?.seatId)?.name)+` · `+m(_(k)(`有效期至`,`Expires`))+` `+m(_(O)(v.value.expiresAt)),1),y(`label`,He,[o(m(_(k)(`席位访问凭据`,`Seat credential`)),1),y(`input`,{value:v.value.seatToken,readonly:``,autocomplete:`off`,spellcheck:`false`,type:`password`,"data-testid":`seat-token`,onFocus:n[1]||=e=>e.target.select()},null,40,Ue)]),y(`button`,{type:`button`,onClick:n[2]||=e=>R(v.value.seatToken,`token`)},m(T.value===`token`?_(k)(`凭据已复制`,`Credential copied`):_(k)(`复制凭据`,`Copy credential`)),1),y(`p`,We,m(_(k)(`只发给这一席位的参与者。凭据仅保留在当前页面内存;关闭后重新创建邀请,或撤销此前凭据。`,`Share only with this seat’s participant. This page keeps the credential only in memory. After closing, create a new invite or revoke previous credentials.`)),1),y(`label`,Ge,[o(m(_(k)(`参与者可访问的协调端地址`,`Coordinator URL reachable by participant`)),1),s(y(`input`,{"onUpdate:modelValue":n[3]||=e=>D.value=e,type:`url`},null,512),[[f,D.value]])]),y(`p`,Ke,m(_(k)(`另一台电脑需要可访问的 HTTPS 地址,并在协调端设置 public-url;本机测试可使用回环地址。`,`Another computer needs a reachable HTTPS URL configured as public-url on the coordinator. Loopback works for local testing.`)),1),t.room.seats.find(e=>e.id===v.value?.seatId)?.runtime===`mcp`?(r(),x(p,{key:1},[y(`p`,$e,m(_(k)(`在 MCP 客户端配置下面的远程地址,将席位凭据作为 Bearer 认证秘密保存。Grok Team Bot 可在官方自定义 MCP 设置中配置此地址。`,`Configure this remote URL in the MCP client and save the seat credential as a Bearer authentication secret. Grok Team Bots can configure it through their official custom MCP settings.`)),1),y(`pre`,null,[y(`code`,null,m(B()),1)]),y(`button`,{type:`button`,onClick:n[9]||=e=>R(B(),`mcp`)},m(T.value===`mcp`?_(k)(`地址已复制`,`URL copied`):_(k)(`复制 MCP 地址`,`Copy MCP URL`)),1),y(`p`,et,m(_(k)(`主动调用 join_roundtable,再通过 read_roundtable、claim_roundtable_turn、submit_roundtable_turn 参与自己的回合;参数 roomId 为下面的圆桌 ID。MCP 不会后台唤醒现有聊天。`,`Actively call join_roundtable, then use read_roundtable, claim_roundtable_turn and submit_roundtable_turn for your own turns, passing the room ID below as roomId. MCP does not wake existing chats in the background.`)),1)],64)):(r(),x(p,{key:0},[y(`label`,qe,[o(m(_(k)(`参与端本地工作区`,`Worker local workspace`)),1),s(y(`input`,{"onUpdate:modelValue":n[4]||=e=>E.value=e,placeholder:_(k)(`填写参与者电脑上的路径`,`Path on the participant’s computer`)},null,8,Je),[[f,E.value]])]),t.room.seats.find(e=>e.id===v.value?.seatId)?.execution?.permission===`workspace-write`?(r(),x(`label`,Ye,[s(y(`input`,{"onUpdate:modelValue":n[5]||=e=>j.value=e,type:`checkbox`},null,512),[[d,j.value]]),o(` `+m(_(k)(`工作端用户明确允许写入此工作区`,`The worker’s user explicitly permits writing to this workspace`)),1)])):u(``,!0),t.room.seats.find(e=>e.id===v.value?.seatId)?.runtime===`a2a`?(r(),x(`label`,Xe,[o(m(_(k)(`A2A 端点`,`A2A endpoint`)),1),s(y(`input`,{"onUpdate:modelValue":n[6]||=e=>M.value=e,type:`url`,placeholder:`https://…`},null,512),[[f,M.value]])])):u(``,!0),t.room.seats.find(e=>e.id===v.value?.seatId)?.runtime===`pi`?(r(),x(`label`,Ze,[o(m(_(k)(`参与电脑上的 Ollama 模型`,`Ollama model on participant computer`)),1),s(y(`input`,{"onUpdate:modelValue":n[7]||=e=>P.value=e,placeholder:`fuli-roundtable-qwen3-32k:latest`},null,512),[[f,P.value]])])):u(``,!0),y(`p`,null,m(_(k)(`在参与端 PowerShell 中,先设置环境变量,再启动工作端:`,`In the worker’s PowerShell, set the environment variable and start the worker:`)),1),y(`pre`,null,[y(`code`,null,`$env:FULI_ROUNDTABLE_TOKEN = '<`+m(_(k)(`粘贴席位凭据`,`PASTE_SEAT_CREDENTIAL`))+`>'
|
|
2
|
-
`+m(z()),1)]),y(`button`,{type:`button`,onClick:n[8]||=e=>R(z(),`command`)},m(T.value===`command`?_(k)(`命令已复制`,`Command copied`):_(k)(`复制工作端命令`,`Copy worker command`)),1),y(`p`,Qe,m(_(k)(`默认只读。实施写入还需工作端用户明确授权。Grok 需要工作端配置 XAI_API_KEY;A2A 需要配置可验证的端点。`,`Workers default to read only. Writing also requires explicit permission from the worker’s user. Grok requires XAI_API_KEY on the worker; A2A requires a verified endpoint.`)),1)],64)),y(`p`,tt,m(_(k)(`协调端地址`,`Coordinator URL`))+`: `+m(D.value)+` · `+m(_(k)(`圆桌`,`Room`))+`: `+m(t.room.id)+` · `+m(_(k)(`席位`,`Seat`))+`: `+m(v.value.seatId),1)])):u(``,!0)]))}}),rt={class:`rt-panel`,"aria-labelledby":`rt-results-title`},it={class:`rt-section-heading`},at={id:`rt-results-title`},ot={key:0,class:`rt-tag`},st={class:`rt-message-body`},ct={class:`rt-muted`},lt={key:0,class:`rt-advanced`},ut={class:`rt-message-body`},dt={class:`rt-muted`},ft={key:1,class:`rt-empty`},pt={key:2,class:`rt-table-scroll`},mt={key:3,class:`rt-muted`},ht={class:`rt-advanced`},gt={class:`rt-message-body`},_t={class:`rt-message-body`},vt={key:4,class:`rt-muted`},yt={key:5,class:`rt-artifacts`},bt={key:6,class:`rt-muted`},xt={class:`rt-muted`},St={key:7,class:`rt-table-scroll`},Ct={"data-testid":`runtime-receipts`},wt={key:8,class:`rt-empty`,"data-testid":`no-runtime-receipts`},Tt=b({__name:`RoundtableResults`,props:{snapshot:{}},setup(e){let t=e,n=l(()=>t.snapshot.messages.filter(e=>e.actual)),a=l(()=>t.snapshot.messages.filter(e=>e.kind===`dissent`)),s=l(()=>[...t.snapshot.outcome?.artifacts??[],...t.snapshot.tasks.flatMap(e=>e.artifacts??[])]),c=e=>t.snapshot.room.seats.find(t=>t.id===e)?.name??e??k(`系统`,`System`);function d(e){if(typeof e==`string`)return e;if(e&&typeof e==`object`){let t=e;if(typeof t.body==`string`)return t.body;if(typeof t.reason==`string`)return t.reason}return j(e)}function f(e){return e==null?k(`未报告`,`Not reported`):typeof e==`string`?e:JSON.stringify(e,null,2)}function h(e){return!e||!Object.keys(e).length?k(`未知`,`Unknown`):Object.entries(e).filter(([,e])=>typeof e==`number`).map(([e,t])=>`${e}: ${t}`).join(` · `)||k(`未知`,`Unknown`)}return(t,l)=>(r(),x(`section`,rt,[y(`div`,it,[y(`h2`,at,m(_(k)(`结果与证据`,`Results and evidence`)),1),e.snapshot.outcome?(r(),x(`span`,ot,m(_(A)(e.snapshot.outcome.acceptance)),1)):u(``,!0)]),e.snapshot.outcome?(r(),x(p,{key:0},[y(`p`,st,m(e.snapshot.outcome.body),1),y(`p`,ct,m(_(k)(`Agent 报告的结论保留为报告;人工验收和任务验证分别记录。`,`Agent conclusions remain reports. Human acceptance and task verification are recorded separately.`)),1),e.snapshot.outcome.verification?.reported==null?u(``,!0):(r(),x(`details`,lt,[y(`summary`,null,m(_(k)(`汇总验证报告`,`Outcome verification report`)),1),y(`p`,ut,m(f(e.snapshot.outcome.verification.reported)),1),y(`p`,dt,m(e.snapshot.outcome.verification.confirmed?_(k)(`服务端记录为已确认`,`Recorded as confirmed by server`):_(k)(`参与端报告,尚未确认`,`Participant report, not confirmed`)),1)]))],64)):(r(),x(`p`,ft,m(_(k)(`尚未提交最终结论。讨论、任务报告与实际执行记录会持续保留。`,`No final outcome has been submitted. Discussion, task reports and execution records are preserved.`)),1)),y(`h3`,null,m(_(k)(`任务`,`Tasks`)),1),e.snapshot.tasks.length?(r(),x(`div`,pt,[y(`table`,null,[y(`thead`,null,[y(`tr`,null,[y(`th`,null,m(_(k)(`任务 / 阶段`,`Task / phase`)),1),y(`th`,null,m(_(k)(`负责人`,`Assignee`)),1),y(`th`,null,m(_(k)(`权限`,`Permission`)),1),y(`th`,null,m(_(k)(`状态`,`Status`)),1),y(`th`,null,m(_(k)(`依赖`,`Dependencies`)),1)])]),y(`tbody`,null,[(r(!0),x(p,null,i(e.snapshot.tasks,e=>(r(),x(`tr`,{key:e.id},[y(`td`,null,[o(m(e.title||e.id),1),y(`small`,null,m(_(A)(e.phase)),1)]),y(`td`,null,m(c(e.seatId)),1),y(`td`,null,m(_(A)(e.permission)),1),y(`td`,null,m(_(A)(e.status)),1),y(`td`,null,m(e.dependencies?.join(`, `)||`—`),1)]))),128))])])])):(r(),x(`p`,mt,m(_(k)(`本圆桌尚无执行任务。讨论消息不算工作进程执行。`,`No execution tasks in this room. Discussion messages are not worker execution.`)),1)),(r(!0),x(p,null,i(e.snapshot.tasks.filter(e=>e.verification!=null),e=>(r(),x(`div`,{key:e.id},[y(`details`,ht,[y(`summary`,null,m(_(k)(`任务验证报告`,`Task verification report`))+` · `+m(e.title||e.id),1),y(`p`,gt,m(f(e.verification)),1)])]))),128)),y(`h3`,null,m(_(k)(`保留的分歧`,`Retained dissent`)),1),(r(!0),x(p,null,i(a.value,e=>(r(),x(`article`,{key:e.id,class:`rt-dissent`},[y(`strong`,null,m(c(e.seatId))+` · #`+m(e.seq),1),y(`p`,_t,m(e.body),1)]))),128)),(r(!0),x(p,null,i(e.snapshot.outcome?.dissent??[],(e,t)=>(r(),x(`p`,{key:t,class:`rt-message-body`},m(d(e)),1))),128)),!a.value.length&&!e.snapshot.outcome?.dissent?.length?(r(),x(`p`,vt,m(_(k)(`尚未记录分歧。`,`No dissent recorded.`)),1)):u(``,!0),y(`h3`,null,m(_(k)(`产物引用`,`Artifact references`)),1),s.value.length?(r(),x(`ul`,yt,[(r(!0),x(p,null,i(s.value,(e,t)=>(r(),x(`li`,{key:t},m(_(j)(e)),1))),128))])):(r(),x(`p`,bt,m(_(k)(`尚无产物引用。`,`No artifact references reported.`)),1)),y(`h3`,null,m(_(k)(`实际运行回执`,`Actual runtime receipts`)),1),y(`p`,xt,m(_(k)(`只展示提交了运行来源的回执;来源由参与端报告,未独立验证运行时身份。加入、选择平台或配置席位不产生执行行;用量缺失时显示未知。`,`Only submitted runtime source receipts appear here. Sources are participant reports; runtime identity is not independently verified. Joining or configuring a seat does not create an execution row; missing usage stays unknown.`)),1),n.value.length?(r(),x(`div`,St,[y(`table`,Ct,[y(`thead`,null,[y(`tr`,null,[y(`th`,null,m(_(k)(`席位 / 消息`,`Seat / message`)),1),y(`th`,null,m(_(k)(`报告的应用`,`Reported application`)),1),y(`th`,null,m(_(k)(`报告的模型`,`Reported model`)),1),y(`th`,null,m(_(k)(`会话`,`Session`)),1),y(`th`,null,m(_(k)(`用量`,`Usage`)),1),y(`th`,null,m(_(k)(`证据来源`,`Provenance`)),1),y(`th`,null,m(_(k)(`时间`,`Time`)),1)])]),y(`tbody`,null,[(r(!0),x(p,null,i(n.value,e=>(r(),x(`tr`,{key:e.id},[y(`td`,null,[o(m(c(e.seatId)),1),y(`small`,null,`#`+m(e.seq)+` · `+m(_(A)(e.kind)),1)]),y(`td`,null,m(e.actual?.applicationLabel||e.actual?.reportedApplication||e.actual?.sourceApplication||_(k)(`未知`,`Unknown`)),1),y(`td`,null,m(e.actual?.model||_(k)(`未知`,`Unknown`)),1),y(`td`,null,m(e.actual?.sessionId||_(k)(`未知`,`Unknown`)),1),y(`td`,null,m(h(e.actual?.usage)),1),y(`td`,null,[o(m(_(k)(`参与端报告`,`Participant reported`)),1),y(`small`,null,m(_(k)(`身份未独立验证`,`Identity not independently verified`)),1)]),y(`td`,null,m(_(O)(e.createdAt)),1)]))),128))])])])):(r(),x(`p`,wt,m(_(k)(`尚无实际运行回执。`,`No actual runtime receipts yet.`)),1))]))}}),Et={class:`rt-panel rt-event-details`},Dt={class:`rt-muted`},Ot={key:0,class:`rt-table-scroll`},kt={"data-testid":`turn-attempts`},At={key:1,class:`rt-muted`},jt={class:`rt-muted`},Mt={class:`rt-message-body`},Nt={key:2,class:`rt-muted`},Pt={class:`rt-events`},Ft={key:0},It=b({__name:`RoundtableEvents`,props:{snapshot:{}},setup(e){let t=e,n=e=>t.snapshot.room.seats.find(t=>t.id===e)?.name??e;return(t,a)=>(r(),x(`details`,Et,[y(`summary`,null,m(_(k)(`回合尝试、恢复与历史事件`,`Turn attempts, recovery and history`)),1),y(`p`,Dt,m(_(k)(`领取回合代表运行租约。下表保留失败、受阻、中断与取消请求,领取本身不证明已调用模型。`,`Claiming a turn creates a lease. This history retains failed, blocked and interrupted attempts and cancellation requests. A claim alone does not prove model execution.`)),1),e.snapshot.attempts?.length?(r(),x(`div`,Ot,[y(`table`,kt,[y(`thead`,null,[y(`tr`,null,[y(`th`,null,m(_(k)(`参与者`,`Participant`)),1),y(`th`,null,m(_(k)(`尝试`,`Attempt`)),1),y(`th`,null,m(_(k)(`状态`,`Status`)),1),y(`th`,null,m(_(k)(`领取时间`,`Claimed at`)),1),y(`th`,null,m(_(k)(`结束时间`,`Finished at`)),1)])]),y(`tbody`,null,[(r(!0),x(p,null,i(e.snapshot.attempts,e=>(r(),x(`tr`,{key:`${e.turnId}:${e.id}`},[y(`td`,null,m(n(e.seatId)),1),y(`td`,null,m(e.id),1),y(`td`,null,m(_(A)(e.status)),1),y(`td`,null,m(_(O)(e.startedAt)),1),y(`td`,null,m(e.finishedAt?_(O)(e.finishedAt):`—`),1)]))),128))])])])):(r(),x(`p`,At,m(_(k)(`尚无回合尝试。`,`No turn attempts yet.`)),1)),y(`h3`,null,m(_(k)(`过期结果存档`,`Stale result archive`)),1),y(`p`,jt,m(_(k)(`晚到结果保留在历史中,不替代当前讨论或交付结论。`,`Late results remain in history and do not replace current discussion or delivery outcomes.`)),1),(r(!0),x(p,null,i(e.snapshot.lateResults??[],e=>(r(),x(`article`,{key:`${e.turnId}:${e.attemptId}`,class:`rt-message`},[y(`strong`,null,m(n(e.seatId))+` · `+m(_(O)(e.receivedAt)),1),y(`p`,Mt,m(e.body),1),y(`small`,null,m(e.turnId)+` / `+m(e.attemptId),1)]))),128)),e.snapshot.lateResults?.length?u(``,!0):(r(),x(`p`,Nt,m(_(k)(`没有过期结果。`,`No stale results.`)),1)),y(`h3`,null,m(_(k)(`状态事件`,`Status events`)),1),y(`ol`,Pt,[(r(!0),x(p,null,i(e.snapshot.events??[],e=>(r(),x(`li`,{key:e.id},[y(`span`,null,m(_(A)(e.kind)),1),o(` · `+m(_(O)(e.createdAt)),1),e.kind===`cancellation_requested`&&e.confirmed!==!0?(r(),x(`small`,Ft,` · `+m(_(k)(`取消尚未确认`,`Cancellation not confirmed`)),1)):u(``,!0)]))),128))])]))}}),Lt={class:`rt-detail`},Rt={class:`rt-panel rt-room-heading`},zt={class:`rt-section-heading`},Bt={class:`rt-eyebrow`},Vt=[`data-status`],Ht={class:`rt-meta`},Ut={key:0,class:`rt-warning`},Wt={class:`rt-actions`},Gt=[`disabled`],Kt=[`disabled`],qt=[`disabled`],Jt=[`disabled`],Yt=[`disabled`],Xt={key:1,class:`rt-muted`},Zt={key:3,class:`rt-confirm`},Qt={class:`rt-field`},$t=[`disabled`],en={key:4,class:`rt-error`,role:`alert`},tn={key:5,class:`rt-advanced`},nn={class:`rt-muted`},rn={class:`rt-limit-form`},an={class:`rt-field`},on={class:`rt-field`},sn={class:`rt-field`},cn={class:`rt-field`},ln=[`disabled`],un={class:`rt-panel`,"aria-labelledby":`rt-messages-title`},dn={class:`rt-section-heading`},fn={id:`rt-messages-title`},pn={key:0,class:`rt-current-turn`},mn={key:0},hn={class:`rt-messages`,"aria-live":`polite`,"aria-relevant":`additions`},gn={class:`rt-section-heading`},_n={class:`rt-message-body`},vn={key:0,class:`rt-empty`},yn=[`disabled`],bn={class:`rt-field`},xn=[`placeholder`],Sn=[`disabled`],Cn={key:1},wn={key:3,class:`rt-advanced`},Tn={class:`rt-muted`},En={class:`rt-field`},Dn={class:`rt-actions`},On=[`disabled`],kn=[`disabled`],An=[`disabled`],jn={key:0,class:`rt-panel`},Mn=[`disabled`],Nn={key:0,class:`rt-confirm`},Pn=[`disabled`],Fn=b({__name:`RoundtableDetail`,props:{snapshot:{},refreshing:{type:Boolean}},emits:[`refresh`],setup(e,{emit:t}){let d=e,v=t,b=c(``),w=c(``),T=c(``),E=c(``),D=c(!1),j=c(!1),M=c(3),P=c(30),F=c(30),I=c(5),L=l(()=>d.snapshot.room),R=l(()=>[`concluded`,`failed`,`cancelled`].includes(L.value.status)),z=l(()=>L.value.seats.every(e=>e.joinedAt)),B=l(()=>L.value.status===`active`&&L.value.phase===`discussion`&&(L.value.round??1)>1&&d.snapshot.currentTurn?.status===`pending`&&L.value.phaseIndex===0),V=l(()=>[`paused`,`waiting_input`].includes(L.value.status)&&d.snapshot.currentTurn&&![`pending`,`claimed`].includes(d.snapshot.currentTurn.status)),H=l(()=>d.snapshot.currentTurn&&d.snapshot.currentTurn.status!==`claimed`&&[`discussion`,`planning`].includes(L.value.phase)),U=l(()=>!d.snapshot.currentTurn||[`pending`,`claimed`].includes(d.snapshot.currentTurn.status)),W=e=>L.value.seats.find(t=>t.id===e)?.name??e??k(`系统`,`System`);a(()=>L.value.id,()=>{b.value=``,w.value=``,D.value=!1,j.value=!1}),a(()=>`${L.value.id}:${JSON.stringify(L.value.limits)}`,()=>{let e=L.value.limits;M.value=e.maxRounds,P.value=e.maxMessages,F.value=e.maxDurationMs/6e4,I.value=e.turnTimeoutMs/6e4},{immediate:!0});async function G(){if(!T.value){T.value=`update_limits`,w.value=``;try{await N.control(L.value.id,`update_limits`,L.value.revision,``,{maxRounds:M.value,maxMessages:P.value,maxDurationMs:Math.round(F.value*6e4),turnTimeoutMs:Math.round(I.value*6e4)}),v(`refresh`)}catch(e){w.value=e instanceof Error?e.message:k(`更新停止条件失败`,`Could not update limits`),v(`refresh`)}finally{T.value=``}}}async function K(e){if(!T.value){T.value=e,w.value=``;try{await N.control(L.value.id,e,L.value.revision,E.value.trim()),D.value=!1,j.value=!1,E.value=``,v(`refresh`)}catch(e){w.value=e instanceof Error?e.message:k(`操作失败`,`Action failed`),v(`refresh`)}finally{T.value=``}}}async function ee(){if(!(!b.value.trim()||T.value)){T.value=`message`,w.value=``;try{await N.message(L.value.id,b.value.trim()),b.value=``,v(`refresh`)}catch(e){w.value=e instanceof Error?e.message:k(`发送失败`,`Could not send`)}finally{T.value=``}}}let te={start:`开始圆桌 · Starting roundtable`,pause:`暂停圆桌 · Pausing roundtable`,resume:`恢复圆桌 · Resuming roundtable`,stop:`停止圆桌 · Stopping roundtable`,advance_phase:`推进圆桌阶段 · Advancing roundtable phase`,next_round:`开启下一轮讨论 · Starting next discussion round`,retry_turn:`重试当前回合 · Retrying current turn`,skip_turn:`记录跳过回合 · Recording skipped turn`,complete:`记录人工验收 · Recording human acceptance`,update_limits:`更新圆桌停止条件 · Updating roundtable limits`};return(t,a)=>(r(),x(`div`,Lt,[y(`section`,Rt,[y(`div`,zt,[y(`span`,Bt,m(_(A)(L.value.mode))+` · `+m(L.value.binding?.projectId||_(k)(`独立临时任务`,`Independent temporary task`)),1),y(`span`,{class:`rt-tag`,"data-status":L.value.status},m(_(A)(L.value.status)),9,Vt)]),y(`h1`,null,m(L.value.goal),1),y(`div`,Ht,[y(`span`,null,m(_(k)(`阶段`,`Phase`))+`: `+m(_(A)(L.value.phase)),1),y(`span`,null,m(_(k)(`讨论轮数`,`Discussion rounds`))+`: `+m(L.value.round??0)+` / `+m(L.value.limits.maxRounds),1),y(`span`,null,m(_(k)(`Agent 发言`,`Agent messages`))+`: `+m(L.value.agentMessageCount??0)+` / `+m(L.value.limits.maxMessages),1),y(`span`,null,m(_(k)(`总时限`,`Time limit`))+`: `+m(Math.round(L.value.limits.maxDurationMs/6e4))+` `+m(_(k)(`分钟`,`min`)),1)]),L.value.stopReason?(r(),x(`p`,Ut,m(_(k)(`当前原因`,`Current reason`))+`: `+m(L.value.stopReason),1)):u(``,!0),y(`div`,Wt,[L.value.status===`draft`?(r(),x(`button`,{key:0,class:`rt-primary`,type:`button`,disabled:!!T.value||!z.value,onClick:a[0]||=e=>K(`start`)},m(_(k)(`开始圆桌`,`Start roundtable`)),9,Gt)):u(``,!0),L.value.status===`active`?(r(),x(`button`,{key:1,type:`button`,disabled:!!T.value,onClick:a[1]||=e=>K(`pause`)},m(_(k)(`暂停`,`Pause`)),9,Kt)):u(``,!0),[`paused`,`waiting_auth`,`waiting_input`].includes(L.value.status)&&!(L.value.phase===`synthesis`&&e.snapshot.outcome&&!e.snapshot.currentTurn)?(r(),x(`button`,{key:2,type:`button`,disabled:!!T.value||!U.value,onClick:a[2]||=e=>K(`resume`)},m(_(k)(`恢复`,`Resume`)),9,qt)):u(``,!0),R.value?u(``,!0):(r(),x(`button`,{key:3,type:`button`,disabled:!!T.value,onClick:a[3]||=e=>D.value=!D.value},m(_(k)(`停止`,`Stop`)),9,Jt)),y(`button`,{type:`button`,disabled:e.refreshing||!!T.value,onClick:a[4]||=e=>v(`refresh`)},m(_(k)(`刷新`,`Refresh`)),9,Yt)]),L.value.status===`draft`&&!z.value?(r(),x(`p`,Xt,m(_(k)(`先邀请所有席位,在各参与端完成预检并加入后开始。选择平台不会自动启动工作端。`,`Invite every seat and complete preflight and joining on each worker before starting. Choosing an adapter does not start a worker.`)),1)):u(``,!0),T.value&&T.value!==`message`?(r(),S(C,{key:2,variant:`inline`,label:te[T.value]||`更新圆桌 · Updating roundtable`},null,8,[`label`])):u(``,!0),D.value?(r(),x(`div`,Zt,[y(`p`,null,m(_(k)(`停止将拒绝新回合并请求取消当前运行。外部进程是否已取消,以工作端回执为准。`,`Stopping blocks new turns and requests cancellation of the current run. Worker receipts confirm whether external execution stopped.`)),1),y(`label`,Qt,[o(m(_(k)(`停止原因(可选)`,`Stop reason (optional)`)),1),s(y(`input`,{"onUpdate:modelValue":a[5]||=e=>E.value=e,maxlength:`2000`},null,512),[[f,E.value]])]),y(`button`,{type:`button`,disabled:!!T.value,onClick:a[6]||=e=>K(`stop`)},m(_(k)(`确认停止`,`Confirm stop`)),9,$t)])):u(``,!0),w.value?(r(),x(`p`,en,m(w.value),1)):u(``,!0),R.value?u(``,!0):(r(),x(`details`,tn,[y(`summary`,null,m(_(k)(`由你修改停止条件`,`Change limits explicitly`)),1),y(`p`,nn,m(_(k)(`修改后仍需明确恢复;总时限从最初开始时间计算。服务端限制可设置的最大预算,Agent 不会自动扩展。`,`After updating, resume explicitly. Total duration is measured from the original start. The server caps the allowed budget; agents do not increase it automatically.`)),1),y(`form`,{class:`rt-budget-form`,onSubmit:h(G,[`prevent`])},[y(`div`,rn,[y(`label`,an,[o(m(_(k)(`讨论轮数`,`Discussion rounds`)),1),s(y(`input`,{"onUpdate:modelValue":a[7]||=e=>M.value=e,type:`number`,min:`1`,max:`3`,required:``},null,512),[[f,M.value,void 0,{number:!0}]])]),y(`label`,on,[o(m(_(k)(`Agent 发言上限`,`Agent message limit`)),1),s(y(`input`,{"onUpdate:modelValue":a[8]||=e=>P.value=e,type:`number`,min:`1`,max:`30`,required:``},null,512),[[f,P.value,void 0,{number:!0}]])]),y(`label`,sn,[o(m(_(k)(`总时限(分钟)`,`Total minutes`)),1),s(y(`input`,{"onUpdate:modelValue":a[9]||=e=>F.value=e,type:`number`,min:`0.1`,step:`0.1`,max:`30`,required:``},null,512),[[f,F.value,void 0,{number:!0}]])]),y(`label`,cn,[o(m(_(k)(`单回合(分钟)`,`Minutes per turn`)),1),s(y(`input`,{"onUpdate:modelValue":a[10]||=e=>I.value=e,type:`number`,min:`0.1`,step:`0.1`,max:`5`,required:``},null,512),[[f,I.value,void 0,{number:!0}]])])]),y(`button`,{type:`submit`,disabled:!!T.value},m(_(k)(`确认修改停止条件`,`Confirm limit changes`)),9,ln)],32)]))]),n(nt,{room:L.value,"active-seat-id":e.snapshot.currentTurn?.seatId,onChanged:a[11]||=e=>v(`refresh`)},null,8,[`room`,`active-seat-id`]),y(`section`,un,[y(`div`,dn,[y(`h2`,fn,m(_(k)(`公开讨论`,`Shared discussion`)),1),y(`span`,null,m(e.snapshot.messages.length)+` `+m(_(k)(`条记录`,`records`)),1)]),e.snapshot.currentTurn?(r(),x(`div`,pn,[y(`strong`,null,m(_(k)(`当前回合`,`Current turn`))+` · `+m(W(e.snapshot.currentTurn.seatId)),1),y(`span`,null,m(_(A)(e.snapshot.currentTurn.status))+` · `+m(_(A)(e.snapshot.currentTurn.phase)),1),e.snapshot.currentTurn.deadline?(r(),x(`small`,mn,m(_(k)(`期限`,`Deadline`))+`: `+m(_(O)(e.snapshot.currentTurn.deadline)),1)):u(``,!0)])):u(``,!0),y(`div`,hn,[(r(!0),x(p,null,i(e.snapshot.messages,e=>(r(),x(`article`,{key:e.id,class:g([`rt-message`,{"rt-message-user":[`user`,`human`].includes(e.kind),"rt-message-system":e.kind===`system`}])},[y(`div`,gn,[y(`strong`,null,m(e.kind===`human`?_(k)(`你`,`You`):W(e.seatId)),1),y(`span`,null,m(_(A)(e.kind))+` · #`+m(e.seq),1)]),y(`p`,_n,m(e.body),1),y(`small`,null,m(_(O)(e.createdAt)),1)],2))),128)),e.snapshot.messages.length?u(``,!0):(r(),x(`p`,vn,m(_(k)(`这里会保存按顺序发布的提案、审查、交接和补充要求。`,`Ordered proposals, reviews, handoffs and your additions will appear here.`)),1))]),e.snapshot.hasMore?(r(),x(`button`,{key:1,type:`button`,disabled:e.refreshing,onClick:a[12]||=e=>v(`refresh`)},m(_(k)(`读取后续消息`,`Load remaining messages`)),9,yn)):u(``,!0),R.value?u(``,!0):(r(),x(`form`,{key:2,class:`rt-input-form`,onSubmit:h(ee,[`prevent`])},[y(`label`,bn,[o(m(_(k)(`补充要求`,`Add a requirement`)),1),s(y(`textarea`,{"onUpdate:modelValue":a[13]||=e=>b.value=e,maxlength:`16000`,rows:`3`,placeholder:_(k)(`所有获授权席位可见。不要填写凭据或他人的私有记忆。`,`Visible to all authorized seats. Keep credentials and others’ private memory out.`)},null,8,xn),[[f,b.value]])]),y(`button`,{type:`submit`,disabled:!b.value.trim()||!!T.value},[T.value===`message`?(r(),S(C,{key:0,variant:`inline`,label:`发送补充要求 · Sending requirement`})):(r(),x(`span`,Cn,m(_(k)(`发送`,`Send`)),1))],8,Sn)],32)),R.value?u(``,!0):(r(),x(`details`,wn,[y(`summary`,null,m(_(k)(`主持与恢复操作`,`Moderation and recovery`)),1),y(`p`,Tn,m(_(k)(`每位参与者完成回合后自动安排下一位和下一轮。完整一轮之后,可在下一轮开始前提前推进阶段。服务端检查阶段、预算与任务依赖,必需实施和审查不能跳过。`,`Turns and rounds advance automatically as participants finish. After a complete round, you may advance the phase before the next round starts. The server checks phases, limits and dependencies; required implementation and review cannot be skipped.`)),1),y(`label`,En,[o(m(_(k)(`操作原因(跳过回合时必填)`,`Reason (required when skipping a turn)`)),1),s(y(`input`,{"onUpdate:modelValue":a[14]||=e=>E.value=e,maxlength:`2000`},null,512),[[f,E.value]])]),y(`div`,Dn,[y(`button`,{type:`button`,disabled:!!T.value||!B.value,onClick:a[15]||=e=>K(`advance_phase`)},m(_(k)(`提前推进阶段`,`Advance phase early`)),9,On),y(`button`,{type:`button`,disabled:!!T.value||!V.value,onClick:a[16]||=e=>K(`retry_turn`)},m(_(k)(`重试回合`,`Retry turn`)),9,kn),y(`button`,{type:`button`,disabled:!!T.value||!H.value||!E.value.trim(),onClick:a[17]||=e=>K(`skip_turn`)},m(_(k)(`记录并跳过回合`,`Record and skip turn`)),9,An)])]))]),n(Tt,{snapshot:e.snapshot},null,8,[`snapshot`]),n(It,{snapshot:e.snapshot},null,8,[`snapshot`]),L.value.status===`waiting_input`&&L.value.phase===`synthesis`&&e.snapshot.outcome?(r(),x(`section`,jn,[y(`h2`,null,m(_(k)(`由你验收`,`Your acceptance`)),1),y(`p`,null,m(_(k)(`检查结论、保留分歧、产物和验证报告后,明确记录是否接受交付。人工接受不会制造模型用量或工作进程证据。`,`Review the conclusion, dissent, artifacts and verification reports before accepting delivery. Acceptance does not create model usage or worker evidence.`)),1),y(`button`,{type:`button`,disabled:!!T.value,onClick:a[18]||=e=>j.value=!j.value},m(_(k)(`检查并接受交付`,`Review and accept delivery`)),9,Mn),j.value?(r(),x(`div`,Nn,[y(`p`,null,m(_(k)(`此操作记录你的人工验收,并结束本圆桌。`,`This records your acceptance and concludes this room.`)),1),y(`button`,{class:`rt-primary`,type:`button`,disabled:!!T.value,onClick:a[19]||=e=>K(`complete`)},m(_(k)(`确认接受交付`,`Confirm acceptance`)),9,Pn),y(`button`,{type:`button`,onClick:a[20]||=e=>j.value=!1},m(_(k)(`继续检查`,`Keep reviewing`)),1)])):u(``,!0)])):u(``,!0)]))}});function In(t){let n=c([]),r=c(null),i=c(!1),o=c(``),s=c(!1),l=0,u=!1,d;async function f(){if(i.value||u)return;let e=l,a=t.value;i.value=!0;try{let t=r.value?.messages.at(-1)?.seq??0,i=a?await N.read(a,t):await N.list();if(u||e!==l)return;if(`room`in i){let e=new Map([...r.value?.messages??[],...i.messages].map(e=>[e.id,e]));r.value={...i,messages:[...e.values()].sort((e,t)=>e.seq-t.seq)}}else n.value=i.rooms;s.value=!0,o.value=``}catch(t){!u&&e===l&&(o.value=t instanceof Error?t.message:k(`读取圆桌失败`,`Could not read roundtable`))}finally{e===l&&(i.value=!1)}}function p(){u||(d=setTimeout(async()=>{await f(),p()},4e3))}return a(t,async()=>{l++,i.value=!1,r.value=null,o.value=``,s.value=!1;let e=l;d&&clearTimeout(d),await f(),e===l&&p()},{immediate:!0}),e(()=>{u=!0,l++,d&&clearTimeout(d)}),{rooms:n,snapshot:r,loading:i,loaded:s,error:o,refresh:f}}var Ln={class:`rt-workspace`},Rn={class:`rt-page-header`},zn={class:`rt-breadcrumb`},Bn={class:`rt-actions`},Vn={class:`rt-page-content`},Hn={key:0,class:`rt-error rt-panel`,role:`alert`},Un=[`disabled`],Wn={key:2,class:`rt-refresh-status`},Gn={class:`rt-intro`},Kn={class:`rt-eyebrow`},qn={class:`rt-panel`,"aria-labelledby":`rt-list-title`},Jn={class:`rt-section-heading`},Yn={id:`rt-list-title`},Xn=[`disabled`],Zn={key:0,class:`rt-room-list`},Qn={class:`rt-section-heading`},$n={class:`rt-tag`},er={key:1,class:`rt-empty`},tr={class:`rt-panel rt-capability-map`,"aria-labelledby":`rt-map-title`},nr={class:`rt-eyebrow`},rr={id:`rt-map-title`},ir={class:`rt-tag`},ar={class:`rt-muted`},or=b({__name:`RoundtablesPage`,setup(e){let a=T(),s=w(),d=l(()=>typeof a.params.roomId==`string`?a.params.roomId:``),{rooms:f,snapshot:h,loading:g,loaded:v,error:b,refresh:j}=In(d),M=c(!1),N=l(()=>d.value?!!h.value:v.value||M.value);async function P(e){M.value=!1,await s.push(`/roundtables/${encodeURIComponent(e)}`)}return(e,a)=>(r(),x(`main`,Ln,[y(`header`,Rn,[y(`div`,null,[n(_(E),{class:`rt-brand`,to:`/`},{default:t(()=>[a[4]||=o(`Fuli `,-1),y(`span`,null,m(_(k)(`复利`,``)),1)]),_:1}),y(`div`,zn,[n(_(E),{to:`/roundtables`},{default:t(()=>[o(m(_(k)(`Agent 圆桌`,`Agent Roundtable`)),1)]),_:1}),a[5]||=y(`span`,{class:`rt-tag`},`Beta`,-1)])]),y(`div`,Bn,[d.value?(r(),S(_(E),{key:0,to:`/roundtables`},{default:t(()=>[o(m(_(k)(`全部圆桌`,`All roundtables`)),1)]),_:1})):u(``,!0),n(_(E),{to:`/project-agents`},{default:t(()=>[o(m(_(k)(`Agent 目录`,`Agent directory`)),1)]),_:1}),n(D)])]),y(`div`,Vn,[_(b)?(r(),x(`div`,Hn,[y(`strong`,null,m(_(k)(`读取失败,保留已加载内容`,`Could not refresh; loaded content is preserved`)),1),y(`p`,null,m(_(b)),1),y(`button`,{type:`button`,disabled:_(g),onClick:a[0]||=(...e)=>_(j)&&_(j)(...e)},m(_(k)(`重试`,`Retry`)),9,Un)])):u(``,!0),_(g)&&!N.value?(r(),S(C,{key:1,label:d.value?`读取圆桌与公开消息 · Loading roundtable and shared messages`:`读取圆桌列表 · Loading roundtable list`},null,8,[`label`])):u(``,!0),_(g)&&N.value?(r(),x(`div`,Wn,[n(C,{variant:`inline`,label:d.value?`刷新圆桌与公开消息 · Refreshing roundtable and shared messages`:`刷新圆桌列表 · Refreshing roundtable list`},null,8,[`label`])])):u(``,!0),_(h)?(r(),S(Fn,{key:_(h).room.id,snapshot:_(h),refreshing:_(g),onRefresh:_(j)},null,8,[`snapshot`,`refreshing`,`onRefresh`])):d.value?u(``,!0):(r(),x(p,{key:4},[y(`section`,Gn,[y(`div`,null,[y(`p`,Kn,m(_(k)(`一起讨论,分别执行,共同交付`,`Discuss together. Execute independently. Deliver with evidence.`)),1),y(`h1`,null,m(_(k)(`让不同 Agent 围绕同一个目标工作。`,`Bring different agents around one shared goal.`)),1),y(`p`,null,m(_(k)(`身份和长期知识由 Fuli 管理。本次讨论、分工、回合与结果留在圆桌里,执行由获授权的参与端完成。`,`Fuli manages identity and lasting knowledge. This roundtable keeps discussion, tasks, turns and outcomes; authorized participant workers perform execution.`)),1)]),y(`button`,{class:`rt-primary`,type:`button`,onClick:a[1]||=e=>M.value=!M.value},m(_(k)(`创建圆桌`,`Create roundtable`)),1)]),M.value?(r(),S(q,{key:0,onCreated:P,onCancel:a[2]||=e=>M.value=!1})):u(``,!0),y(`section`,qn,[y(`div`,Jn,[y(`h2`,Yn,m(_(k)(`你的圆桌`,`Your roundtables`)),1),y(`button`,{type:`button`,disabled:_(g),onClick:a[3]||=(...e)=>_(j)&&_(j)(...e)},m(_(k)(`刷新`,`Refresh`)),9,Xn)]),_(f).length?(r(),x(`div`,Zn,[(r(!0),x(p,null,i(_(f),e=>(r(),S(_(E),{key:e.id,class:`rt-room-card`,to:`/roundtables/${encodeURIComponent(e.id)}`},{default:t(()=>[y(`div`,Qn,[y(`span`,$n,m(_(A)(e.status)),1),y(`small`,null,m(_(A)(e.mode)),1)]),y(`h3`,null,m(e.goal),1),y(`p`,null,m(_(A)(e.phase))+` · `+m(e.seats.length)+` `+m(_(k)(`位参与者`,`participants`)),1),y(`small`,null,m(_(O)(e.updatedAt||e.createdAt)),1)]),_:2},1032,[`to`]))),128))])):!_(g)&&!_(b)?(r(),x(`p`,er,m(_(k)(`还没有圆桌。创建一个目标明确的讨论或协作任务,邀请独立参与者加入。`,`No roundtables yet. Create a focused discussion or collaborative task and invite independent participants.`)),1)):u(``,!0)]),y(`section`,tr,[y(`p`,nr,m(_(k)(`完整产品版图`,`Product capability map`)),1),y(`h2`,rr,m(_(k)(`同一个协作层,连接长期知识与实际工作。`,`One collaboration layer connects lasting knowledge to real work.`)),1),y(`ol`,null,[y(`li`,null,[y(`span`,ir,m(_(k)(`已提供`,`Available`)),1),y(`strong`,null,m(_(k)(`身份与知识`,`Identity and knowledge`)),1),y(`p`,null,m(_(k)(`Agent 职责、项目范围、确认状态与时间历史。按需读取,不因同桌共享私有记忆。`,`Agent roles, project scope, confirmation and temporal history. Retrieved on demand; shared rooms do not expose private memory.`)),1)]),y(`li`,null,[a[6]||=y(`span`,{class:`rt-tag`},`Beta`,-1),y(`strong`,null,m(_(k)(`圆桌协作`,`Roundtable coordination`)),1),y(`p`,null,m(_(k)(`共同目标 → 讨论 → 分工 → 实施 → 审查 → 汇总与人工验收。`,`Shared goal → discussion → planning → implementation → review → synthesis and human acceptance.`)),1)]),y(`li`,null,[a[7]||=y(`span`,{class:`rt-tag`},`Beta`,-1),y(`strong`,null,m(_(k)(`开放接入`,`Open adapters`)),1),y(`p`,null,m(_(k)(`CLI、MCP、xAI API;A2A 按已验证的协议能力试验接入。需要可调用接口、凭据和运行端。`,`CLI, MCP and xAI API. A2A is experimental within verified protocol capabilities. Requires a callable interface, credentials and a worker.`)),1)]),y(`li`,null,[a[8]||=y(`span`,{class:`rt-tag`},`Beta`,-1),y(`strong`,null,m(_(k)(`跨电脑交付`,`Delivery across computers`)),1),y(`p`,null,m(_(k)(`一台协调端,各参与端主动领取授权回合;公开消息、分歧、产物和实际来源一起保留。`,`One coordinator; workers pull authorized turns. Shared messages, dissent, artifacts and actual sources stay together.`)),1)])]),y(`p`,ar,m(_(k)(`Grok API 席位与现有 Grok Bot 分开:前者由工作端调用 xAI,后者需要在官方自定义 HTTPS MCP 设置中主动配置。没有开放接口的平台、云端常驻和完整 A2A 兼容仍待支持。Beta 能力不等于已通过每种模型、客户端和电脑的真实验收。`,`Grok API seats and existing Grok Bots are distinct: workers call xAI for the former; the latter require explicit setup through official HTTPS MCP settings. Closed platforms, always-on cloud hosting and full A2A compatibility remain future work. Beta availability does not imply live verification of every model, client and computer.`)),1)])],64))])]))}});export{or as default};
|