ai_co_discuss 0.2.99 → 0.3.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/.agents/skills/discuss-host/SKILL.md +3 -3
- package/.agents/skills/discuss-host/references/discussion.md +16 -16
- package/.agents/skills/discuss-host/references/launch.md +1 -3
- package/.agents/skills/discuss-host/references/manual.md +14 -77
- package/.agents/skills/discuss-participant/SKILL.md +3 -3
- package/.agents/skills/discuss-participant/references/discussion.md +29 -27
- package/.agents/skills/discuss-participant/references/free.md +2 -2
- package/.agents/skills/discuss-participant/references/manual.md +8 -10
- package/.agents/skills/discuss-participant/references/writing.md +9 -9
- package/.github/SECURITY.md +1 -1
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/backend/free_room.py +360 -68
- package/backend/room_import.py +75 -15
- package/backend/runtime.py +81 -169
- package/backend/schema.sql +136 -244
- package/backend/server.py +602 -166
- package/backend/session_room.py +919 -0
- package/docs/architecture.md +3 -2
- package/docs/en/architecture.md +4 -3
- package/docs/en/runner.md +3 -1
- package/docs/en/workflow.md +9 -6
- package/docs/runner.md +3 -1
- package/docs/workflow.md +11 -8
- package/package.json +2 -2
- package/runner/api_participant.mjs +47 -44
- package/runner/discuss.py +77 -58
- package/runner/discussion_adapters.mjs +6 -3
- package/runner/discussion_launch.mjs +98 -70
- package/runner/discussion_runner.mjs +38 -496
- package/runner/discussion_service.mjs +21 -74
- package/runner/discussion_sidecar.mjs +4 -13
- package/runner/expert_prompts.mjs +29 -21
- package/runner/free_runner.mjs +84 -19
- package/runner/session_driver.mjs +175 -0
- package/web/app.js +193 -128
- package/web/backup.js +1 -1
- package/web/modern.css +1 -1
- package/web/modern.js +5482 -6646
package/backend/schema.sql
CHANGED
|
@@ -188,7 +188,7 @@ BEGIN
|
|
|
188
188
|
SELECT RAISE(ABORT, 'floor_turn_required');
|
|
189
189
|
END;
|
|
190
190
|
|
|
191
|
-
--
|
|
191
|
+
-- Legacy room-scoped limits remain for read-only history; new discussions use session-scoped limits below.
|
|
192
192
|
CREATE TABLE IF NOT EXISTS discussion_limits (
|
|
193
193
|
room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
|
|
194
194
|
max_rounds INTEGER NOT NULL DEFAULT 0,
|
|
@@ -207,20 +207,10 @@ CREATE TRIGGER limits_room_created AFTER INSERT ON rooms
|
|
|
207
207
|
BEGIN
|
|
208
208
|
INSERT INTO discussion_limits(room_id,execution_minutes) VALUES (NEW.id,0);
|
|
209
209
|
END;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
END;
|
|
215
|
-
CREATE TRIGGER IF NOT EXISTS limits_phase_started AFTER UPDATE OF phase ON topic_settings
|
|
216
|
-
WHEN NEW.phase IN ('initial','discussing') AND NEW.phase<>OLD.phase
|
|
217
|
-
BEGIN
|
|
218
|
-
UPDATE discussion_limits SET started_at=CAST(strftime('%s','now') AS INTEGER) WHERE room_id=NEW.room_id AND started_at=0;
|
|
219
|
-
END;
|
|
220
|
-
CREATE TRIGGER IF NOT EXISTS limits_turn_started AFTER INSERT ON discussion_turns
|
|
221
|
-
BEGIN
|
|
222
|
-
UPDATE discussion_limits SET started_at=CAST(strftime('%s','now') AS INTEGER) WHERE room_id=NEW.room_id AND started_at=0;
|
|
223
|
-
END;
|
|
210
|
+
DROP TRIGGER IF EXISTS limits_topic_started;
|
|
211
|
+
DROP TRIGGER IF EXISTS limits_phase_started;
|
|
212
|
+
DROP TRIGGER IF EXISTS limits_turn_started;
|
|
213
|
+
-- Legacy event-based orchestration tables remain for historical reads; the request-based session is authoritative.
|
|
224
214
|
CREATE TABLE IF NOT EXISTS discussion_runtime (
|
|
225
215
|
room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
|
|
226
216
|
mode TEXT NOT NULL CHECK(mode IN ('manual','automatic')),
|
|
@@ -275,202 +265,34 @@ INSERT INTO message_timings(message_id,elapsed_seconds)
|
|
|
275
265
|
WHERE m.source IN ('cli','api') AND e.role='expert' AND e.status='completed'
|
|
276
266
|
AND e.started_at>0 AND t.dispatched_at>0 AND t.finished_at>0
|
|
277
267
|
ON CONFLICT(message_id) DO NOTHING;
|
|
278
|
-
|
|
279
|
-
WHEN OLD.status<>NEW.status
|
|
280
|
-
BEGIN
|
|
281
|
-
-- Avoid OR IGNORE: an outer statement's conflict policy would override it.
|
|
282
|
-
INSERT INTO event_timings(event_id) SELECT NEW.id
|
|
283
|
-
WHERE NOT EXISTS(SELECT 1 FROM event_timings WHERE event_id=NEW.id);
|
|
284
|
-
UPDATE event_timings SET dispatched_at=CAST(strftime('%s','now') AS INTEGER)
|
|
285
|
-
WHERE event_id=NEW.id AND NEW.status='dispatched';
|
|
286
|
-
UPDATE event_timings SET finished_at=CAST(strftime('%s','now') AS INTEGER)
|
|
287
|
-
WHERE event_id=NEW.id AND NEW.status IN ('completed','cancelled','failed');
|
|
288
|
-
END;
|
|
268
|
+
DROP TRIGGER IF EXISTS record_event_timing;
|
|
289
269
|
-- A failed assertion rolls back the whole D1 batch, including concurrent writes.
|
|
290
270
|
CREATE TABLE IF NOT EXISTS runtime_assertions (
|
|
291
271
|
id TEXT PRIMARY KEY, allowed INTEGER NOT NULL CHECK(allowed=1)
|
|
292
272
|
);
|
|
293
|
-
|
|
294
|
-
WHEN OLD.generation<>NEW.generation OR OLD.status<>NEW.status
|
|
295
|
-
BEGIN
|
|
296
|
-
UPDATE discussion_events SET status='cancelled' WHERE room_id=NEW.room_id
|
|
297
|
-
AND status IN ('pending','dispatched','started')
|
|
298
|
-
AND (generation<>NEW.generation OR NEW.status<>'running');
|
|
299
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role,turn_id)
|
|
300
|
-
SELECT NEW.room_id,NEW.run_id,NEW.generation,'start:'||NEW.generation,
|
|
301
|
-
COALESCE(turns.expert,settings.host),CASE WHEN turns.id IS NULL THEN 'host' ELSE 'expert' END,turns.id
|
|
302
|
-
FROM topic_settings AS settings LEFT JOIN discussion_turns AS turns
|
|
303
|
-
ON turns.room_id=settings.room_id AND turns.status IN ('pending','notified')
|
|
304
|
-
WHERE settings.room_id=NEW.room_id AND NEW.status='running';
|
|
305
|
-
END;
|
|
273
|
+
DROP TRIGGER IF EXISTS runtime_generation;
|
|
306
274
|
DROP TRIGGER IF EXISTS runtime_turn_created;
|
|
307
|
-
|
|
308
|
-
WHEN NEW.status<>'queued'
|
|
309
|
-
BEGIN
|
|
310
|
-
UPDATE discussion_events SET status='completed',result='turn:'||NEW.id
|
|
311
|
-
WHERE room_id=NEW.room_id AND role='host' AND status IN ('pending','dispatched','started');
|
|
312
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role,turn_id)
|
|
313
|
-
SELECT NEW.room_id,run_id,generation,'turn:'||NEW.id,NEW.expert,'expert',NEW.id
|
|
314
|
-
FROM discussion_runtime WHERE room_id=NEW.room_id AND status='running';
|
|
315
|
-
END;
|
|
316
|
-
-- Round plans: queued turns wait in id order. held_since marks the first unprocessed user action
|
|
317
|
-
-- (a message id, or the next id for a topic edit); while set, answers return to the host instead of relaying.
|
|
275
|
+
-- Retired relay state is preserved for old databases and historical reads only.
|
|
318
276
|
CREATE TABLE IF NOT EXISTS turn_relay (
|
|
319
277
|
room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
|
|
320
278
|
held_since INTEGER NOT NULL DEFAULT 0
|
|
321
279
|
);
|
|
322
|
-
-- Both triggers below clear the hold only when no user message arrived after the running host execution began; the host cannot have read it.
|
|
323
280
|
DROP TRIGGER IF EXISTS relay_turn_promoted;
|
|
324
|
-
CREATE TRIGGER relay_turn_promoted AFTER UPDATE OF status ON discussion_turns
|
|
325
|
-
WHEN OLD.status='queued' AND NEW.status='pending'
|
|
326
|
-
BEGIN
|
|
327
|
-
UPDATE turn_relay SET held_since=0 WHERE room_id=NEW.room_id
|
|
328
|
-
AND NOT EXISTS(SELECT 1 FROM messages m JOIN discussion_events e ON e.room_id=m.room_id AND e.role='host' AND e.status='started'
|
|
329
|
-
WHERE m.room_id=NEW.room_id AND m.author='用户' AND CAST(strftime('%s',m.created_at) AS INTEGER)>e.started_at);
|
|
330
|
-
UPDATE discussion_events SET status='completed',result='turn:'||NEW.id
|
|
331
|
-
WHERE room_id=NEW.room_id AND role='host' AND status IN ('pending','dispatched','started');
|
|
332
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role,turn_id)
|
|
333
|
-
SELECT NEW.room_id,run_id,generation,'turn:'||NEW.id,NEW.expert,'expert',NEW.id
|
|
334
|
-
FROM discussion_runtime WHERE room_id=NEW.room_id AND status='running';
|
|
335
|
-
END;
|
|
336
|
-
-- A new arrangement is the host's response: it replaces any older plan and clears the hold.
|
|
337
281
|
DROP TRIGGER IF EXISTS relay_plan_replaced;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
UPDATE turn_relay SET held_since=0 WHERE room_id=NEW.room_id
|
|
343
|
-
AND NOT EXISTS(SELECT 1 FROM messages m JOIN discussion_events e ON e.room_id=m.room_id AND e.role='host' AND e.status='started'
|
|
344
|
-
WHERE m.room_id=NEW.room_id AND m.author='用户' AND CAST(strftime('%s',m.created_at) AS INTEGER)>e.started_at);
|
|
345
|
-
END;
|
|
346
|
-
CREATE TRIGGER IF NOT EXISTS relay_hold_user_message AFTER INSERT ON message_details
|
|
347
|
-
WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id AND author='用户')
|
|
348
|
-
BEGIN
|
|
349
|
-
INSERT INTO turn_relay(room_id) SELECT room_id FROM messages WHERE id=NEW.message_id
|
|
350
|
-
AND NOT EXISTS(SELECT 1 FROM turn_relay WHERE turn_relay.room_id=messages.room_id);
|
|
351
|
-
UPDATE turn_relay SET held_since=NEW.message_id
|
|
352
|
-
WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id) AND held_since=0;
|
|
353
|
-
END;
|
|
354
|
-
CREATE TRIGGER IF NOT EXISTS relay_hold_goal AFTER UPDATE OF goal ON rooms
|
|
355
|
-
WHEN NEW.goal<>OLD.goal
|
|
356
|
-
BEGIN
|
|
357
|
-
INSERT INTO turn_relay(room_id) SELECT NEW.id WHERE NOT EXISTS(SELECT 1 FROM turn_relay WHERE room_id=NEW.id);
|
|
358
|
-
UPDATE turn_relay SET held_since=(SELECT COALESCE(MAX(id),0)+1 FROM messages WHERE room_id=NEW.id)
|
|
359
|
-
WHERE room_id=NEW.id AND held_since=0;
|
|
360
|
-
END;
|
|
361
|
-
CREATE TRIGGER IF NOT EXISTS relay_hold_topic AFTER UPDATE OF background,boundaries,focus ON topic_settings
|
|
362
|
-
WHEN NEW.background<>OLD.background OR NEW.boundaries<>OLD.boundaries OR NEW.focus<>OLD.focus
|
|
363
|
-
BEGIN
|
|
364
|
-
INSERT INTO turn_relay(room_id) SELECT NEW.room_id WHERE NOT EXISTS(SELECT 1 FROM turn_relay WHERE room_id=NEW.room_id);
|
|
365
|
-
UPDATE turn_relay SET held_since=(SELECT COALESCE(MAX(id),0)+1 FROM messages WHERE room_id=NEW.room_id)
|
|
366
|
-
WHERE room_id=NEW.room_id AND held_since=0;
|
|
367
|
-
END;
|
|
368
|
-
-- Concluding, replacing the host or removing/changing an expert voids plans that have not started.
|
|
369
|
-
-- Adding experts preserves the current plan; the invitation transaction appends their opening turns.
|
|
370
|
-
CREATE TRIGGER IF NOT EXISTS relay_plan_concluded AFTER UPDATE OF phase ON topic_settings
|
|
371
|
-
WHEN NEW.phase='concluded'
|
|
372
|
-
BEGIN
|
|
373
|
-
UPDATE discussion_turns SET status='skipped' WHERE room_id=NEW.room_id AND status='queued';
|
|
374
|
-
END;
|
|
282
|
+
DROP TRIGGER IF EXISTS relay_hold_user_message;
|
|
283
|
+
DROP TRIGGER IF EXISTS relay_hold_goal;
|
|
284
|
+
DROP TRIGGER IF EXISTS relay_hold_topic;
|
|
285
|
+
DROP TRIGGER IF EXISTS relay_plan_concluded;
|
|
375
286
|
DROP TRIGGER IF EXISTS relay_plan_participants;
|
|
376
|
-
CREATE TRIGGER relay_plan_participants AFTER UPDATE OF host,experts ON topic_settings
|
|
377
|
-
WHEN NEW.host<>OLD.host OR EXISTS (
|
|
378
|
-
SELECT 1 FROM json_each(OLD.experts) AS previous
|
|
379
|
-
WHERE NOT EXISTS (SELECT 1 FROM json_each(NEW.experts) AS current
|
|
380
|
-
WHERE current.key=previous.key AND current.value=previous.value)
|
|
381
|
-
)
|
|
382
|
-
BEGIN
|
|
383
|
-
UPDATE discussion_turns SET status='skipped' WHERE room_id=NEW.room_id AND status='queued';
|
|
384
|
-
END;
|
|
385
287
|
DROP TRIGGER IF EXISTS runtime_turn_answered;
|
|
386
|
-
-- An answer relays to the next queued turn unless the user has acted since the host last arranged.
|
|
387
|
-
CREATE TRIGGER runtime_turn_answered AFTER UPDATE OF status ON discussion_turns
|
|
388
|
-
WHEN NEW.status='answered' AND OLD.status<>'answered'
|
|
389
|
-
BEGIN
|
|
390
|
-
UPDATE discussion_events SET status='completed',result='message:'||NEW.answer_id
|
|
391
|
-
WHERE room_id=NEW.room_id AND turn_id=NEW.id AND status IN ('pending','dispatched','started');
|
|
392
|
-
UPDATE discussion_turns SET status='pending'
|
|
393
|
-
WHERE id=(SELECT MIN(id) FROM discussion_turns WHERE room_id=NEW.room_id AND status='queued')
|
|
394
|
-
AND NOT EXISTS(SELECT 1 FROM turn_relay WHERE room_id=NEW.room_id AND held_since>0)
|
|
395
|
-
AND NOT EXISTS(SELECT 1 FROM topic_settings WHERE room_id=NEW.room_id AND phase IN ('paused','concluded'));
|
|
396
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
397
|
-
SELECT NEW.room_id,run_id,generation,'answer:'||NEW.id,host,'host'
|
|
398
|
-
FROM discussion_runtime JOIN topic_settings USING(room_id)
|
|
399
|
-
WHERE room_id=NEW.room_id AND status='running'
|
|
400
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_turns WHERE room_id=NEW.room_id AND status IN ('pending','notified'));
|
|
401
|
-
END;
|
|
402
288
|
DROP TRIGGER IF EXISTS runtime_topic_stop;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
UPDATE discussion_events SET status='completed',result='concluded' WHERE room_id=NEW.room_id
|
|
407
|
-
AND role='host' AND status='started' AND NEW.phase='concluded';
|
|
408
|
-
UPDATE discussion_runtime SET status=NEW.phase,generation=generation+1,revision=revision+1,
|
|
409
|
-
resume_phase=CASE WHEN OLD.phase IN ('initial','discussing') THEN OLD.phase ELSE resume_phase END,
|
|
410
|
-
updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE room_id=NEW.room_id;
|
|
411
|
-
END;
|
|
412
|
-
CREATE TRIGGER IF NOT EXISTS runtime_user_message AFTER INSERT ON message_details
|
|
413
|
-
WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id AND author='用户')
|
|
414
|
-
BEGIN
|
|
415
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
416
|
-
SELECT messages.room_id,run_id,generation,'user:'||NEW.message_id,host,'host'
|
|
417
|
-
FROM messages JOIN discussion_runtime ON discussion_runtime.room_id=messages.room_id
|
|
418
|
-
JOIN topic_settings ON topic_settings.room_id=messages.room_id
|
|
419
|
-
WHERE messages.id=NEW.message_id AND status='running';
|
|
420
|
-
END;
|
|
421
|
-
CREATE TRIGGER IF NOT EXISTS runtime_turn_cancelled AFTER UPDATE OF status ON discussion_turns
|
|
422
|
-
WHEN NEW.status='cancelled' AND OLD.status<>'cancelled'
|
|
423
|
-
BEGIN
|
|
424
|
-
UPDATE discussion_events SET status='cancelled' WHERE room_id=NEW.room_id AND turn_id=NEW.id
|
|
425
|
-
AND status IN ('pending','dispatched','started');
|
|
426
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
427
|
-
SELECT NEW.room_id,run_id,generation,'cancel:'||NEW.id,host,'host'
|
|
428
|
-
FROM discussion_runtime JOIN topic_settings USING(room_id)
|
|
429
|
-
WHERE room_id=NEW.room_id AND status='running'
|
|
430
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_events WHERE room_id=NEW.room_id AND role='host' AND status='started');
|
|
431
|
-
END;
|
|
432
|
-
CREATE TRIGGER IF NOT EXISTS runtime_archive AFTER UPDATE OF archived ON room_directory
|
|
433
|
-
WHEN NEW.archived=1 AND OLD.archived<>NEW.archived
|
|
434
|
-
BEGIN
|
|
435
|
-
UPDATE discussion_runtime SET status='stopped',generation=generation+1,revision=revision+1
|
|
436
|
-
WHERE room_id=NEW.room_id;
|
|
437
|
-
END;
|
|
289
|
+
DROP TRIGGER IF EXISTS runtime_user_message;
|
|
290
|
+
DROP TRIGGER IF EXISTS runtime_turn_cancelled;
|
|
291
|
+
DROP TRIGGER IF EXISTS runtime_archive;
|
|
438
292
|
DROP TRIGGER IF EXISTS runtime_participants_changed;
|
|
439
|
-
-- A new host needs a new run. Experts joining or leaving keep the run going; the host is woken to re-plan.
|
|
440
|
-
CREATE TRIGGER runtime_participants_changed AFTER UPDATE OF host ON topic_settings
|
|
441
|
-
WHEN NEW.host<>OLD.host
|
|
442
|
-
BEGIN
|
|
443
|
-
UPDATE discussion_runtime SET status='paused',generation=generation+1,revision=revision+1
|
|
444
|
-
WHERE room_id=NEW.room_id AND status='running';
|
|
445
|
-
END;
|
|
446
293
|
DROP TRIGGER IF EXISTS runtime_experts_changed;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
BEGIN
|
|
450
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
451
|
-
SELECT NEW.room_id,run_id,generation,'members:'||COALESCE((SELECT MAX(id) FROM discussion_events),0),NEW.host,'host'
|
|
452
|
-
FROM discussion_runtime WHERE room_id=NEW.room_id AND status='running'
|
|
453
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_events WHERE room_id=NEW.room_id AND role='host' AND status IN ('pending','dispatched','started'))
|
|
454
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_turns WHERE room_id=NEW.room_id AND status IN ('pending','notified'));
|
|
455
|
-
END;
|
|
456
|
-
CREATE TRIGGER IF NOT EXISTS runtime_goal_changed AFTER UPDATE OF goal ON rooms
|
|
457
|
-
WHEN NEW.goal<>OLD.goal
|
|
458
|
-
BEGIN
|
|
459
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
460
|
-
SELECT NEW.id,run_id,generation,'goal:'||COALESCE((SELECT MAX(id) FROM discussion_events),0),host,'host'
|
|
461
|
-
FROM discussion_runtime JOIN topic_settings USING(room_id)
|
|
462
|
-
WHERE room_id=NEW.id AND status='running'
|
|
463
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_events WHERE room_id=NEW.id AND role='host' AND status='started');
|
|
464
|
-
END;
|
|
465
|
-
CREATE TRIGGER IF NOT EXISTS runtime_constraints_changed AFTER UPDATE OF background,boundaries ON topic_settings
|
|
466
|
-
WHEN NEW.background<>OLD.background OR NEW.boundaries<>OLD.boundaries
|
|
467
|
-
BEGIN
|
|
468
|
-
INSERT OR IGNORE INTO discussion_events(room_id,run_id,generation,cause,participant,role)
|
|
469
|
-
SELECT NEW.room_id,run_id,generation,'constraints:'||COALESCE((SELECT MAX(id) FROM discussion_events),0),NEW.host,'host'
|
|
470
|
-
FROM discussion_runtime WHERE room_id=NEW.room_id AND status='running'
|
|
471
|
-
AND NOT EXISTS(SELECT 1 FROM discussion_events WHERE room_id=NEW.room_id AND role='host' AND status='started');
|
|
472
|
-
END;
|
|
473
|
-
-- Paused, preparing or concluded time is idle: it is excluded from elapsed time and total-duration deadlines.
|
|
294
|
+
DROP TRIGGER IF EXISTS runtime_goal_changed;
|
|
295
|
+
DROP TRIGGER IF EXISTS runtime_constraints_changed;
|
|
474
296
|
CREATE TABLE IF NOT EXISTS discussion_pauses (
|
|
475
297
|
room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
|
|
476
298
|
paused_at INTEGER NOT NULL DEFAULT 0,
|
|
@@ -481,19 +303,8 @@ INSERT INTO discussion_pauses(room_id,paused_at)
|
|
|
481
303
|
FROM discussion_runtime WHERE discussion_runtime.room_id=topic_settings.room_id AND status<>'running'),
|
|
482
304
|
CAST(strftime('%s','now') AS INTEGER)) ELSE 0 END
|
|
483
305
|
FROM topic_settings WHERE NOT EXISTS(SELECT 1 FROM discussion_pauses WHERE discussion_pauses.room_id=topic_settings.room_id);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
BEGIN
|
|
487
|
-
INSERT INTO discussion_pauses(room_id) SELECT NEW.room_id
|
|
488
|
-
WHERE NOT EXISTS(SELECT 1 FROM discussion_pauses WHERE room_id=NEW.room_id);
|
|
489
|
-
UPDATE discussion_pauses SET paused_at=CAST(strftime('%s','now') AS INTEGER) WHERE room_id=NEW.room_id AND paused_at=0;
|
|
490
|
-
END;
|
|
491
|
-
CREATE TRIGGER IF NOT EXISTS pause_clock_resumed AFTER UPDATE OF phase ON topic_settings
|
|
492
|
-
WHEN NEW.phase IN ('initial','discussing') AND OLD.phase NOT IN ('initial','discussing')
|
|
493
|
-
BEGIN
|
|
494
|
-
UPDATE discussion_pauses SET paused_seconds=paused_seconds+MAX(0,CAST(strftime('%s','now') AS INTEGER)-paused_at),paused_at=0
|
|
495
|
-
WHERE room_id=NEW.room_id AND paused_at>0;
|
|
496
|
-
END;
|
|
306
|
+
DROP TRIGGER IF EXISTS pause_clock_stopped;
|
|
307
|
+
DROP TRIGGER IF EXISTS pause_clock_resumed;
|
|
497
308
|
-- Both room kinds register members; free rooms additionally persist requests and executions.
|
|
498
309
|
CREATE TABLE IF NOT EXISTS room_members (
|
|
499
310
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -524,6 +335,11 @@ CREATE TABLE IF NOT EXISTS member_registrations (
|
|
|
524
335
|
token_hash TEXT NOT NULL,
|
|
525
336
|
registered_at TEXT NOT NULL
|
|
526
337
|
);
|
|
338
|
+
-- Web-relay members use the manual access path while retaining owner-only proxy semantics.
|
|
339
|
+
CREATE TABLE IF NOT EXISTS member_manual_sources (
|
|
340
|
+
member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
|
|
341
|
+
source TEXT NOT NULL CHECK(source='web')
|
|
342
|
+
);
|
|
527
343
|
CREATE TABLE IF NOT EXISTS member_executions (
|
|
528
344
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
529
345
|
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
@@ -574,6 +390,16 @@ CREATE TABLE IF NOT EXISTS execution_denials (
|
|
|
574
390
|
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
575
391
|
denials TEXT NOT NULL
|
|
576
392
|
);
|
|
393
|
+
-- A manual member's session refused a delivery: the runner keeps the execution and delivers again at retry_at
|
|
394
|
+
-- (0 while delivering). The owner may ask for it now or stop it, and the runner then fails it. Not part of backups.
|
|
395
|
+
CREATE TABLE IF NOT EXISTS execution_retries (
|
|
396
|
+
execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
|
|
397
|
+
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
398
|
+
attempt INTEGER NOT NULL,
|
|
399
|
+
retry_at INTEGER NOT NULL DEFAULT 0,
|
|
400
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
401
|
+
stopped INTEGER NOT NULL DEFAULT 0
|
|
402
|
+
);
|
|
577
403
|
-- Phases the runner saw a member's software go through in one execution (a free execution or a discussion event), as
|
|
578
404
|
-- millisecond offsets from its delivery; numbered per execution, and a resent segment keeps the later end. Not part of backups.
|
|
579
405
|
CREATE TABLE IF NOT EXISTS execution_segments (
|
|
@@ -781,8 +607,8 @@ CREATE TABLE IF NOT EXISTS room_links (
|
|
|
781
607
|
created_at TEXT NOT NULL
|
|
782
608
|
);
|
|
783
609
|
|
|
784
|
-
-- A discussion is a switch on a room, not a room type: one thin
|
|
785
|
-
--
|
|
610
|
+
-- A discussion is a switch on a room, not a room type: one thin record per started discussion.
|
|
611
|
+
-- Session status is written by the application; legacy topic phase remains for old history only.
|
|
786
612
|
CREATE TABLE IF NOT EXISTS discussion_sessions (
|
|
787
613
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
788
614
|
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
@@ -802,31 +628,9 @@ FROM rooms JOIN topic_settings ON topic_settings.room_id=rooms.id
|
|
|
802
628
|
WHERE NOT EXISTS (SELECT 1 FROM room_modes WHERE room_id=rooms.id AND mode='free')
|
|
803
629
|
AND NOT EXISTS (SELECT 1 FROM discussion_sessions WHERE room_id=rooms.id);
|
|
804
630
|
DROP TRIGGER IF EXISTS session_for_new_discussion;
|
|
805
|
-
CREATE TRIGGER session_for_new_discussion AFTER INSERT ON room_modes
|
|
806
|
-
WHEN NEW.mode='brainstorm' AND NOT EXISTS (SELECT 1 FROM discussion_sessions WHERE room_id=NEW.room_id)
|
|
807
|
-
BEGIN
|
|
808
|
-
INSERT INTO discussion_sessions (room_id,goal,status,created_at)
|
|
809
|
-
SELECT rooms.id,rooms.goal,
|
|
810
|
-
COALESCE((SELECT CASE WHEN phase='concluded' THEN 'concluded' WHEN phase IN ('preparing','paused') THEN 'paused' ELSE 'active' END
|
|
811
|
-
FROM topic_settings WHERE room_id=rooms.id),'paused'),rooms.created_at
|
|
812
|
-
FROM rooms WHERE rooms.id=NEW.room_id;
|
|
813
|
-
END;
|
|
814
631
|
DROP TRIGGER IF EXISTS session_follows_phase_insert;
|
|
815
|
-
CREATE TRIGGER session_follows_phase_insert AFTER INSERT ON topic_settings
|
|
816
|
-
BEGIN
|
|
817
|
-
UPDATE discussion_sessions SET status=CASE WHEN NEW.phase='concluded' THEN 'concluded' WHEN NEW.phase IN ('preparing','paused') THEN 'paused' ELSE 'active' END
|
|
818
|
-
WHERE id=(SELECT MAX(id) FROM discussion_sessions WHERE room_id=NEW.room_id);
|
|
819
|
-
END;
|
|
820
632
|
DROP TRIGGER IF EXISTS session_follows_phase;
|
|
821
|
-
|
|
822
|
-
BEGIN
|
|
823
|
-
UPDATE discussion_sessions SET status=CASE WHEN NEW.phase='concluded' THEN 'concluded' WHEN NEW.phase IN ('preparing','paused') THEN 'paused' ELSE 'active' END,
|
|
824
|
-
ended_message_id=CASE WHEN NEW.phase='concluded' THEN COALESCE((SELECT MAX(id) FROM messages WHERE room_id=NEW.room_id),0) ELSE 0 END
|
|
825
|
-
WHERE id=(SELECT MAX(id) FROM discussion_sessions WHERE room_id=NEW.room_id);
|
|
826
|
-
END;
|
|
827
|
-
-- What a discussion concluded with: the room keeps one shared state and brief that the next discussion overwrites, so
|
|
828
|
-
-- ending a discussion copies them, with why it ended, onto its session; a state or brief last written before it started
|
|
829
|
-
-- belongs to an earlier discussion and is left out. Reopening and ending again replaces the copy.
|
|
633
|
+
-- Historical conclusion snapshots are written by the application and remain readable across later discussions.
|
|
830
634
|
CREATE TABLE IF NOT EXISTS discussion_conclusions (
|
|
831
635
|
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
832
636
|
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
@@ -839,17 +643,6 @@ CREATE TABLE IF NOT EXISTS discussion_conclusions (
|
|
|
839
643
|
concluded_at TEXT NOT NULL
|
|
840
644
|
);
|
|
841
645
|
DROP TRIGGER IF EXISTS conclusion_snapshot;
|
|
842
|
-
CREATE TRIGGER conclusion_snapshot AFTER UPDATE OF phase ON topic_settings
|
|
843
|
-
WHEN NEW.phase='concluded' AND OLD.phase<>'concluded'
|
|
844
|
-
BEGIN
|
|
845
|
-
INSERT OR REPLACE INTO discussion_conclusions (session_id,room_id,stop_reason,max_rounds,max_minutes,brief,state,state_until,concluded_at)
|
|
846
|
-
SELECT s.id,s.room_id,COALESCE(l.stop_reason,''),COALESCE(l.max_rounds,0),COALESCE(l.max_minutes,0),CASE WHEN b.until>s.started_message_id THEN b.text ELSE '' END,
|
|
847
|
-
CASE WHEN r.state_until>s.started_message_id THEN r.state_text ELSE '' END,CASE WHEN r.state_until>s.started_message_id THEN r.state_until ELSE 0 END,
|
|
848
|
-
strftime('%Y-%m-%dT%H:%M:%SZ','now')
|
|
849
|
-
FROM discussion_sessions AS s JOIN rooms AS r ON r.id=s.room_id
|
|
850
|
-
LEFT JOIN discussion_limits AS l ON l.room_id=s.room_id LEFT JOIN room_briefs AS b ON b.room_id=s.room_id
|
|
851
|
-
WHERE s.id=(SELECT MAX(id) FROM discussion_sessions WHERE room_id=NEW.room_id);
|
|
852
|
-
END;
|
|
853
646
|
-- Discussions that ended before snapshots: only the latest still matches the room's state, so only it gets one. Safe to repeat.
|
|
854
647
|
INSERT OR IGNORE INTO discussion_conclusions (session_id,room_id,stop_reason,max_rounds,max_minutes,brief,state,state_until,concluded_at)
|
|
855
648
|
SELECT s.id,s.room_id,COALESCE(l.stop_reason,''),COALESCE(l.max_rounds,0),COALESCE(l.max_minutes,0),CASE WHEN b.until>s.started_message_id THEN b.text ELSE '' END,
|
|
@@ -862,6 +655,99 @@ WHERE s.status='concluded' AND s.id=(SELECT MAX(id) FROM discussion_sessions WHE
|
|
|
862
655
|
UPDATE topic_settings SET phase='discussing' WHERE phase='reviewing';
|
|
863
656
|
UPDATE discussion_runtime SET resume_phase='discussing' WHERE resume_phase='reviewing';
|
|
864
657
|
|
|
658
|
+
-- Request-based sessions use session-scoped settings. The room-scoped tables above remain read-only legacy history.
|
|
659
|
+
CREATE TABLE IF NOT EXISTS session_start_submissions (
|
|
660
|
+
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
661
|
+
submission_id TEXT NOT NULL,
|
|
662
|
+
session_id INTEGER NOT NULL UNIQUE REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
663
|
+
request_json TEXT NOT NULL,
|
|
664
|
+
PRIMARY KEY (room_id,submission_id)
|
|
665
|
+
);
|
|
666
|
+
CREATE TABLE IF NOT EXISTS session_state (
|
|
667
|
+
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
668
|
+
phase TEXT NOT NULL CHECK(phase IN ('initial','discussing')),
|
|
669
|
+
revision INTEGER NOT NULL DEFAULT 0,
|
|
670
|
+
read_until INTEGER NOT NULL DEFAULT 0,
|
|
671
|
+
held_since INTEGER NOT NULL DEFAULT 0
|
|
672
|
+
);
|
|
673
|
+
CREATE TABLE IF NOT EXISTS session_limits (
|
|
674
|
+
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
675
|
+
max_rounds INTEGER NOT NULL DEFAULT 0,
|
|
676
|
+
max_minutes INTEGER NOT NULL DEFAULT 0,
|
|
677
|
+
execution_minutes INTEGER NOT NULL DEFAULT 0,
|
|
678
|
+
started_at INTEGER NOT NULL DEFAULT 0,
|
|
679
|
+
stop_reason TEXT NOT NULL DEFAULT '',
|
|
680
|
+
revision INTEGER NOT NULL DEFAULT 0
|
|
681
|
+
);
|
|
682
|
+
CREATE TABLE IF NOT EXISTS session_round_budgets (
|
|
683
|
+
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
684
|
+
base INTEGER NOT NULL DEFAULT 0,
|
|
685
|
+
cap INTEGER NOT NULL DEFAULT 0,
|
|
686
|
+
extra INTEGER NOT NULL DEFAULT 0
|
|
687
|
+
);
|
|
688
|
+
CREATE TABLE IF NOT EXISTS session_pauses (
|
|
689
|
+
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
690
|
+
paused_at INTEGER NOT NULL DEFAULT 0,
|
|
691
|
+
paused_seconds INTEGER NOT NULL DEFAULT 0
|
|
692
|
+
);
|
|
693
|
+
CREATE TABLE IF NOT EXISTS session_options (
|
|
694
|
+
session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
695
|
+
named_followup INTEGER NOT NULL DEFAULT 0 CHECK(named_followup IN (0,1))
|
|
696
|
+
);
|
|
697
|
+
CREATE TABLE IF NOT EXISTS session_requests (
|
|
698
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
699
|
+
session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
700
|
+
round INTEGER NOT NULL DEFAULT 0,
|
|
701
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
702
|
+
member_id INTEGER NOT NULL REFERENCES room_members(id),
|
|
703
|
+
purpose TEXT NOT NULL CHECK(purpose IN ('discuss','discuss_host')),
|
|
704
|
+
question TEXT NOT NULL DEFAULT '',
|
|
705
|
+
status TEXT NOT NULL DEFAULT 'queued'
|
|
706
|
+
CHECK(status IN ('queued','open','answered','failed','skipped','cancelled')),
|
|
707
|
+
member_request_id INTEGER UNIQUE REFERENCES member_requests(id),
|
|
708
|
+
submission_id TEXT NOT NULL,
|
|
709
|
+
read_until INTEGER DEFAULT NULL,
|
|
710
|
+
created_at TEXT NOT NULL,
|
|
711
|
+
UNIQUE(session_id,submission_id)
|
|
712
|
+
);
|
|
713
|
+
CREATE INDEX IF NOT EXISTS session_requests_queue ON session_requests(session_id,status,round,position,id);
|
|
714
|
+
CREATE UNIQUE INDEX IF NOT EXISTS one_open_session_request ON session_requests(session_id)
|
|
715
|
+
WHERE status='open' AND purpose='discuss';
|
|
716
|
+
CREATE TABLE IF NOT EXISTS task_message_sources (
|
|
717
|
+
message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
|
|
718
|
+
session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
|
|
719
|
+
session_request_id INTEGER NOT NULL UNIQUE REFERENCES session_requests(id) ON DELETE CASCADE,
|
|
720
|
+
cause_kind TEXT NOT NULL CHECK(cause_kind IN ('session_started','scheduled_speaker','speaker_answered','round_complete','user_message','member_change','request_failed')),
|
|
721
|
+
cause_message_id INTEGER REFERENCES messages(id),
|
|
722
|
+
cause_request_id INTEGER REFERENCES member_requests(id)
|
|
723
|
+
);
|
|
724
|
+
CREATE INDEX IF NOT EXISTS task_message_sources_session ON task_message_sources(session_id,message_id);
|
|
725
|
+
|
|
726
|
+
-- Stop old engine sessions once during upgrade. Their original phase and rows remain readable, while new sessions can open.
|
|
727
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
728
|
+
name TEXT PRIMARY KEY,
|
|
729
|
+
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
730
|
+
);
|
|
731
|
+
INSERT OR IGNORE INTO discussion_conclusions (session_id,room_id,stop_reason,max_rounds,max_minutes,brief,state,state_until,concluded_at)
|
|
732
|
+
SELECT s.id,s.room_id,'engine_upgrade',COALESCE(l.max_rounds,0),COALESCE(l.max_minutes,0),
|
|
733
|
+
CASE WHEN b.until>s.started_message_id THEN b.text ELSE '' END,
|
|
734
|
+
CASE WHEN r.state_until>s.started_message_id THEN r.state_text ELSE '' END,
|
|
735
|
+
CASE WHEN r.state_until>s.started_message_id THEN r.state_until ELSE 0 END,
|
|
736
|
+
strftime('%Y-%m-%dT%H:%M:%SZ','now')
|
|
737
|
+
FROM discussion_sessions AS s JOIN rooms AS r ON r.id=s.room_id
|
|
738
|
+
LEFT JOIN discussion_limits AS l ON l.room_id=s.room_id LEFT JOIN room_briefs AS b ON b.room_id=s.room_id
|
|
739
|
+
WHERE s.status IN ('active','paused')
|
|
740
|
+
AND NOT EXISTS (SELECT 1 FROM schema_migrations WHERE name='discussion_session_requests_v1');
|
|
741
|
+
UPDATE discussion_runtime SET status='stopped',generation=generation+1,updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now')
|
|
742
|
+
WHERE status IN ('ready','running','paused')
|
|
743
|
+
AND room_id IN (SELECT room_id FROM discussion_sessions WHERE status IN ('active','paused'))
|
|
744
|
+
AND NOT EXISTS (SELECT 1 FROM schema_migrations WHERE name='discussion_session_requests_v1');
|
|
745
|
+
UPDATE discussion_sessions SET status='concluded',
|
|
746
|
+
ended_message_id=COALESCE((SELECT MAX(id) FROM messages WHERE messages.room_id=discussion_sessions.room_id),0)
|
|
747
|
+
WHERE status IN ('active','paused')
|
|
748
|
+
AND NOT EXISTS (SELECT 1 FROM schema_migrations WHERE name='discussion_session_requests_v1');
|
|
749
|
+
INSERT OR IGNORE INTO schema_migrations(name) VALUES ('discussion_session_requests_v1');
|
|
750
|
+
|
|
865
751
|
-- A computer running the local service, paired by the site administrator. Only project names and ids and the
|
|
866
752
|
-- configured client of each platform are published; directories and credentials stay on that computer.
|
|
867
753
|
CREATE TABLE IF NOT EXISTS local_machines (
|
|
@@ -886,3 +772,9 @@ CREATE TABLE IF NOT EXISTS remote_launches (
|
|
|
886
772
|
updated_at INTEGER NOT NULL
|
|
887
773
|
);
|
|
888
774
|
CREATE INDEX IF NOT EXISTS remote_launches_machine ON remote_launches(machine_id, status);
|
|
775
|
+
|
|
776
|
+
-- Normalize legacy online relay members and retired brainstorm rooms for the shared free-room model.
|
|
777
|
+
INSERT OR IGNORE INTO member_manual_sources(member_id,source)
|
|
778
|
+
SELECT id,'web' FROM room_members WHERE access='online';
|
|
779
|
+
UPDATE room_members SET access='manual' WHERE access='online';
|
|
780
|
+
UPDATE room_modes SET mode='free' WHERE mode='brainstorm';
|