ai_co_discuss 0.3.0 → 0.3.2

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.
@@ -778,3 +778,342 @@ INSERT OR IGNORE INTO member_manual_sources(member_id,source)
778
778
  SELECT id,'web' FROM room_members WHERE access='online';
779
779
  UPDATE room_members SET access='manual' WHERE access='online';
780
780
  UPDATE room_modes SET mode='free' WHERE mode='brainstorm';
781
+
782
+ -- Change revisions, so idle rooms are not read on every poll. Every write a reader could show or act on gives its room the next
783
+ -- value of one site-wide sequence: a room's revision tells a runner or page that this room changed, and the largest revision
784
+ -- (read through the index) tells a page that anything on the site changed. The '' row stands for directory-only changes.
785
+ -- Triggers update or insert without conflicts, so an outer INSERT OR IGNORE cannot override them into skipping the bump.
786
+ CREATE TABLE IF NOT EXISTS room_revisions (
787
+ room_id TEXT PRIMARY KEY,
788
+ revision INTEGER NOT NULL
789
+ );
790
+ CREATE INDEX IF NOT EXISTS room_revisions_order ON room_revisions(revision);
791
+ DROP TRIGGER IF EXISTS revision_message_insert;
792
+ CREATE TRIGGER revision_message_insert AFTER INSERT ON messages
793
+ BEGIN
794
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
795
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
796
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
797
+ END;
798
+ DROP TRIGGER IF EXISTS revision_message_update;
799
+ CREATE TRIGGER revision_message_update AFTER UPDATE ON messages
800
+ BEGIN
801
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
802
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
803
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
804
+ END;
805
+ DROP TRIGGER IF EXISTS revision_message_withdrawal;
806
+ CREATE TRIGGER revision_message_withdrawal AFTER INSERT ON message_withdrawals
807
+ BEGIN
808
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
809
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM messages WHERE id=NEW.message_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
810
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id));
811
+ END;
812
+ DROP TRIGGER IF EXISTS revision_member_insert;
813
+ CREATE TRIGGER revision_member_insert AFTER INSERT ON room_members
814
+ BEGIN
815
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
816
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
817
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
818
+ END;
819
+ DROP TRIGGER IF EXISTS revision_member_update;
820
+ CREATE TRIGGER revision_member_update AFTER UPDATE ON room_members
821
+ BEGIN
822
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
823
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
824
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
825
+ END;
826
+ DROP TRIGGER IF EXISTS revision_registration_insert;
827
+ CREATE TRIGGER revision_registration_insert AFTER INSERT ON member_registrations
828
+ BEGIN
829
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
830
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
831
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
832
+ END;
833
+ DROP TRIGGER IF EXISTS revision_registration_update;
834
+ CREATE TRIGGER revision_registration_update AFTER UPDATE ON member_registrations
835
+ BEGIN
836
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
837
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
838
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
839
+ END;
840
+ DROP TRIGGER IF EXISTS revision_session_report_insert;
841
+ CREATE TRIGGER revision_session_report_insert AFTER INSERT ON member_sessions
842
+ BEGIN
843
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
844
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
845
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
846
+ END;
847
+ DROP TRIGGER IF EXISTS revision_session_report_update;
848
+ CREATE TRIGGER revision_session_report_update AFTER UPDATE ON member_sessions
849
+ WHEN OLD.session_id IS NOT NEW.session_id OR OLD.client IS NOT NEW.client
850
+ BEGIN
851
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
852
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
853
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
854
+ END;
855
+ DROP TRIGGER IF EXISTS revision_request_insert;
856
+ CREATE TRIGGER revision_request_insert AFTER INSERT ON member_requests
857
+ BEGIN
858
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
859
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
860
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
861
+ END;
862
+ DROP TRIGGER IF EXISTS revision_request_status;
863
+ CREATE TRIGGER revision_request_status AFTER UPDATE OF status ON member_requests
864
+ WHEN OLD.status IS NOT NEW.status
865
+ BEGIN
866
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
867
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
868
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
869
+ END;
870
+ DROP TRIGGER IF EXISTS revision_execution_insert;
871
+ CREATE TRIGGER revision_execution_insert AFTER INSERT ON member_executions
872
+ BEGIN
873
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
874
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
875
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
876
+ END;
877
+ DROP TRIGGER IF EXISTS revision_execution_status;
878
+ CREATE TRIGGER revision_execution_status AFTER UPDATE OF status ON member_executions
879
+ WHEN OLD.status IS NOT NEW.status
880
+ BEGIN
881
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
882
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
883
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
884
+ END;
885
+ DROP TRIGGER IF EXISTS revision_retry_insert;
886
+ CREATE TRIGGER revision_retry_insert AFTER INSERT ON execution_retries
887
+ BEGIN
888
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
889
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
890
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
891
+ END;
892
+ DROP TRIGGER IF EXISTS revision_retry_update;
893
+ CREATE TRIGGER revision_retry_update AFTER UPDATE ON execution_retries
894
+ BEGIN
895
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
896
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
897
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
898
+ END;
899
+ DROP TRIGGER IF EXISTS revision_discussion_insert;
900
+ CREATE TRIGGER revision_discussion_insert AFTER INSERT ON discussion_sessions
901
+ BEGIN
902
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
903
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
904
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
905
+ END;
906
+ DROP TRIGGER IF EXISTS revision_discussion_status;
907
+ CREATE TRIGGER revision_discussion_status AFTER UPDATE OF status ON discussion_sessions
908
+ WHEN OLD.status IS NOT NEW.status
909
+ BEGIN
910
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
911
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
912
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
913
+ END;
914
+ DROP TRIGGER IF EXISTS revision_plan_insert;
915
+ CREATE TRIGGER revision_plan_insert AFTER INSERT ON session_requests
916
+ BEGIN
917
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
918
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
919
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
920
+ END;
921
+ DROP TRIGGER IF EXISTS revision_plan_status;
922
+ CREATE TRIGGER revision_plan_status AFTER UPDATE OF status ON session_requests
923
+ WHEN OLD.status IS NOT NEW.status
924
+ BEGIN
925
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
926
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
927
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
928
+ END;
929
+ DROP TRIGGER IF EXISTS revision_session_phase;
930
+ CREATE TRIGGER revision_session_phase AFTER UPDATE OF phase ON session_state
931
+ WHEN OLD.phase IS NOT NEW.phase
932
+ BEGIN
933
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
934
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
935
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
936
+ END;
937
+ DROP TRIGGER IF EXISTS revision_session_limits;
938
+ CREATE TRIGGER revision_session_limits AFTER UPDATE ON session_limits
939
+ BEGIN
940
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
941
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
942
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
943
+ END;
944
+ DROP TRIGGER IF EXISTS revision_topic_update;
945
+ CREATE TRIGGER revision_topic_update AFTER UPDATE ON topic_settings
946
+ BEGIN
947
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
948
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
949
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
950
+ END;
951
+ DROP TRIGGER IF EXISTS revision_brief_insert;
952
+ CREATE TRIGGER revision_brief_insert AFTER INSERT ON room_briefs
953
+ BEGIN
954
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
955
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
956
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
957
+ END;
958
+ DROP TRIGGER IF EXISTS revision_brief_update;
959
+ CREATE TRIGGER revision_brief_update AFTER UPDATE ON room_briefs
960
+ BEGIN
961
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
962
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
963
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
964
+ END;
965
+ DROP TRIGGER IF EXISTS revision_user_request_insert;
966
+ CREATE TRIGGER revision_user_request_insert AFTER INSERT ON room_user_requests
967
+ BEGIN
968
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
969
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
970
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
971
+ END;
972
+ DROP TRIGGER IF EXISTS revision_user_request_update;
973
+ CREATE TRIGGER revision_user_request_update AFTER UPDATE ON room_user_requests
974
+ BEGIN
975
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
976
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
977
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
978
+ END;
979
+ DROP TRIGGER IF EXISTS revision_decision_insert;
980
+ CREATE TRIGGER revision_decision_insert AFTER INSERT ON scheduler_decisions
981
+ BEGIN
982
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
983
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
984
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
985
+ END;
986
+ DROP TRIGGER IF EXISTS revision_recommendation_insert;
987
+ CREATE TRIGGER revision_recommendation_insert AFTER INSERT ON member_recommendations
988
+ BEGIN
989
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
990
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
991
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
992
+ END;
993
+ DROP TRIGGER IF EXISTS revision_recommendation_status;
994
+ CREATE TRIGGER revision_recommendation_status AFTER UPDATE OF status ON member_recommendations
995
+ WHEN OLD.status IS NOT NEW.status
996
+ BEGIN
997
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
998
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
999
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1000
+ END;
1001
+ DROP TRIGGER IF EXISTS revision_description_insert;
1002
+ CREATE TRIGGER revision_description_insert AFTER INSERT ON member_descriptions
1003
+ BEGIN
1004
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1005
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1006
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1007
+ END;
1008
+ DROP TRIGGER IF EXISTS revision_description_update;
1009
+ CREATE TRIGGER revision_description_update AFTER UPDATE ON member_descriptions
1010
+ BEGIN
1011
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1012
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1013
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1014
+ END;
1015
+ DROP TRIGGER IF EXISTS revision_label_insert;
1016
+ CREATE TRIGGER revision_label_insert AFTER INSERT ON member_labels
1017
+ BEGIN
1018
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1019
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1020
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1021
+ END;
1022
+ DROP TRIGGER IF EXISTS revision_label_update;
1023
+ CREATE TRIGGER revision_label_update AFTER UPDATE ON member_labels
1024
+ BEGIN
1025
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1026
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1027
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1028
+ END;
1029
+ DROP TRIGGER IF EXISTS revision_order_insert;
1030
+ CREATE TRIGGER revision_order_insert AFTER INSERT ON speaking_orders
1031
+ BEGIN
1032
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1033
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1034
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1035
+ END;
1036
+ DROP TRIGGER IF EXISTS revision_order_update;
1037
+ CREATE TRIGGER revision_order_update AFTER UPDATE ON speaking_orders
1038
+ BEGIN
1039
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1040
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1041
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1042
+ END;
1043
+ DROP TRIGGER IF EXISTS revision_free_settings_insert;
1044
+ CREATE TRIGGER revision_free_settings_insert AFTER INSERT ON free_settings
1045
+ BEGIN
1046
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1047
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1048
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1049
+ END;
1050
+ DROP TRIGGER IF EXISTS revision_free_settings_update;
1051
+ CREATE TRIGGER revision_free_settings_update AFTER UPDATE ON free_settings
1052
+ BEGIN
1053
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1054
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1055
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1056
+ END;
1057
+ DROP TRIGGER IF EXISTS revision_room_insert;
1058
+ CREATE TRIGGER revision_room_insert AFTER INSERT ON rooms
1059
+ BEGIN
1060
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1061
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1062
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1063
+ END;
1064
+ DROP TRIGGER IF EXISTS revision_room_update;
1065
+ CREATE TRIGGER revision_room_update AFTER UPDATE ON rooms
1066
+ BEGIN
1067
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1068
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1069
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1070
+ END;
1071
+ DROP TRIGGER IF EXISTS revision_room_delete;
1072
+ CREATE TRIGGER revision_room_delete AFTER DELETE ON rooms
1073
+ BEGIN
1074
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1075
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1076
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1077
+ END;
1078
+ DROP TRIGGER IF EXISTS revision_directory_update;
1079
+ CREATE TRIGGER revision_directory_update AFTER UPDATE ON room_directory
1080
+ BEGIN
1081
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1082
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1083
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1084
+ END;
1085
+ DROP TRIGGER IF EXISTS revision_assignment_update;
1086
+ CREATE TRIGGER revision_assignment_update AFTER UPDATE ON room_folder_assignments
1087
+ BEGIN
1088
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1089
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1090
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1091
+ END;
1092
+ DROP TRIGGER IF EXISTS revision_folder_insert;
1093
+ CREATE TRIGGER revision_folder_insert AFTER INSERT ON topic_folders
1094
+ BEGIN
1095
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1096
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1097
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1098
+ END;
1099
+ DROP TRIGGER IF EXISTS revision_folder_update;
1100
+ CREATE TRIGGER revision_folder_update AFTER UPDATE ON topic_folders
1101
+ BEGIN
1102
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1103
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1104
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1105
+ END;
1106
+ DROP TRIGGER IF EXISTS revision_folder_delete;
1107
+ CREATE TRIGGER revision_folder_delete AFTER DELETE ON topic_folders
1108
+ BEGIN
1109
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1110
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1111
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1112
+ END;
1113
+ DROP TRIGGER IF EXISTS revision_directory_settings;
1114
+ CREATE TRIGGER revision_directory_settings AFTER UPDATE ON directory_settings
1115
+ BEGIN
1116
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1117
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1118
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1119
+ END;
package/backend/server.py CHANGED
@@ -51,6 +51,8 @@ MAX_IMPORT_BYTES = 32 * 1024 * 1024
51
51
  MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024
52
52
  MAX_MESSAGE_ATTACHMENTS = 10
53
53
  MAX_FILENAME_LENGTH = 255
54
+ # A revision check names at most this many rooms; a runner or page asks only for the rooms it follows.
55
+ MAX_REVISION_ROOMS = 100
54
56
  LOCAL_LAUNCH_TIMEOUT = 60
55
57
  # Ordering and filing a channel only rearrange the shared directory, like folder edits, so they need no room credential.
56
58
  DIRECTORY_OPERATIONS = ("move", "folder")
@@ -804,6 +806,24 @@ def submission_value(value):
804
806
  return identifier
805
807
 
806
808
 
809
+ def room_revisions(connection, params):
810
+ """Return the site revision and each named room's revision, so an idle room is read in full only after it changed.
811
+
812
+ Args:
813
+ connection: Database connection.
814
+ params: Query parameters; ``rooms`` is a comma-separated list of room ids.
815
+
816
+ Returns:
817
+ ``{"site": int, "rooms": {room_id: int}}``; a room that does not exist is left out.
818
+ """
819
+ ids = [item for item in str(params.get("rooms", "")).split(",") if item][:MAX_REVISION_ROOMS]
820
+ site = connection.execute("SELECT COALESCE(MAX(revision),0) AS revision FROM room_revisions").fetchone()["revision"]
821
+ marks = ",".join("?" * len(ids))
822
+ rows = connection.execute(f"""SELECT rooms.id,COALESCE(room_revisions.revision,0) AS revision FROM rooms
823
+ LEFT JOIN room_revisions ON room_revisions.room_id=rooms.id WHERE rooms.id IN ({marks})""", ids).fetchall() if ids else []
824
+ return {"site": site, "rooms": {row["id"]: row["revision"] for row in rows}}
825
+
826
+
807
827
  def list_rooms(connection, params=None):
808
828
  """Return rooms, optionally locating a single persisted creation request."""
809
829
  params = params or {}
@@ -2342,6 +2362,8 @@ class DiscussionHandler(BaseHTTPRequestHandler):
2342
2362
  return self.respond(list_feedback_cases(connection, int(params.get("after", 0))))
2343
2363
  if parts[1:2] in (["machines"], ["remote-launches"]):
2344
2364
  return self.handle_remote_launch_api(connection, parts, method)
2365
+ if parts == ["api", "revisions"] and method == "GET":
2366
+ return self.respond(room_revisions(connection, params))
2345
2367
  if parts == ["api", "rooms"]:
2346
2368
  if method == "GET":
2347
2369
  return self.respond({**list_rooms(connection, params), "participants": PARTICIPANTS})
@@ -109,7 +109,7 @@ codiscuss health --site https://YOUR_PROJECT_NAME.pages.dev
109
109
 
110
110
  ### 实时推送(可选)
111
111
 
112
- 未配置时网页每 12 秒轮询。需要跨实例实时推送时,先部署通知 Worker,再在私有配置中绑定:
112
+ 未配置时网页每 12 秒检查一次房间是否有变化,有变化才重新读取。需要跨实例实时推送时,先部署通知 Worker,再在私有配置中绑定:
113
113
 
114
114
  ```sh
115
115
  npx wrangler deploy --config deploy/updates.wrangler.jsonc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -256,7 +256,7 @@ function agy_project_grants(config, task, resources, user_home) {
256
256
  throw new Error('agy 草稿目录不能位于只读项目、运行目录、技能或 .gemini 配置目录中,未交付讨论任务');
257
257
  }
258
258
  const write_rule = directory => `write_file(${resolve(directory).replaceAll('\\', '/')})`;
259
- // 完全放开 also runs commands outside agy's sandbox and allows MCP tools; run data stays protected at every level.
259
+ // 全放开 also runs commands outside agy's sandbox and allows MCP tools; run data stays protected at every level.
260
260
  const full = config.project_access === 'full';
261
261
  return { allow: [agy_rule(), write_rule(task.draft_directory),
262
262
  ...(config.project_access === 'read' ? [] : [...projects.map(write_rule), 'command(*)']), ...(full ? ['unsandboxed_command(*)'] : []),
@@ -315,7 +315,7 @@ export function remove_agy_projects(directory, user_home = homedir()) {
315
315
  */
316
316
  export function claude_permissions(config, task) {
317
317
  // A headless run cannot ask for approval: 自动 and a taken-over manual member use auto mode, as the original window
318
- // does, and 完全放开 skips every check; the website commands and drafts stay pre-approved either way.
318
+ // does, and 全放开 skips every check; the website commands and drafts stay pre-approved either way.
319
319
  if (['own', 'auto', 'full'].includes(config.project_access)) {
320
320
  const commands = TASK_SUBCOMMANDS.flatMap(command => [`Bash(codiscuss ${command} *)`, `PowerShell(codiscuss ${command} *)`]);
321
321
  return ['--permission-mode', config.project_access === 'full' ? 'bypassPermissions' : 'auto', '--add-dir', task.directory, task.draft_directory, task.skills_directory, '--allowedTools',
@@ -640,7 +640,7 @@ async function claude_delivery(config, binding, task, spawn_process = spawn) {
640
640
 
641
641
  /** Build a per-invocation profile without inheriting a user's custom profile of the same name. */
642
642
  export function codex_permissions(config, task, platform = process.platform) {
643
- // 完全放开 runs without approval or sandbox, so there is no profile to build or check.
643
+ // 全放开 runs without approval or sandbox, so there is no profile to build or check.
644
644
  if (config.project_access === 'full') return { name: '', args: ['--dangerously-bypass-approvals-and-sandbox'] };
645
645
  const name = `codiscuss-${randomUUID()}`;
646
646
  // A taken-over manual member writes like 可写: its own config may sandbox read-only and then could not save its answer draft.
@@ -643,7 +643,7 @@ export async function room_member_access(home, raw) {
643
643
  const profile = config.participants?.[name];
644
644
  if (!profile) throw new Error(`${name} 不由本机唤醒,项目权限由它自己的会话决定`);
645
645
  if (profile.project_access === 'own') throw new Error(`${name} 是手动接入的成员,权限沿用它自己会话的软件设置`);
646
- if (!access_levels(profile.adapter).includes(project_access)) throw new Error('agy 无人值守不支持自动档,请选只读、可写或完全放开');
646
+ if (!access_levels(profile.adapter).includes(project_access)) throw new Error('agy 无人值守不支持自动档,请选只读、可写或全放开');
647
647
  if (!ACCESS_ADAPTERS.includes(profile.adapter)) throw new Error(profile.adapter === 'api' ? 'API 成员没有本机文件工具,项目固定只读' : `${name} 的项目权限在 App 内设置`);
648
648
  if (profile.adapter === 'antigravity_cli' && profile.session_id) throw new Error(`${name} 续接的是它自己的 agy 对话,项目权限沿用该对话所在项目,请在 agy 中设置`);
649
649
  if (project_access === 'write' && !(profile.cwd || config.cwd)) throw new Error('本房间没有指定项目目录,可写没有作用;请在新建房间时选择项目目录');
@@ -66,7 +66,7 @@ export function validate_config(input, base_directory) {
66
66
  participant.project_access = participant.project_access || config.project_access || 'read';
67
67
  // `own` is set only when the service takes over a manual member: it keeps its own session's permissions.
68
68
  if (![...PROJECT_ACCESS, 'own'].includes(participant.project_access)) throw new Error('project_access 只能为 read、write、auto 或 full');
69
- if (participant.project_access !== 'own' && !access_levels(participant.adapter).includes(participant.project_access)) throw new Error('agy 无人值守不支持自动档,请选只读、可写或完全放开');
69
+ if (participant.project_access !== 'own' && !access_levels(participant.adapter).includes(participant.project_access)) throw new Error('agy 无人值守不支持自动档,请选只读、可写或全放开');
70
70
  if (participant.cwd && !statSync(participant.cwd).isDirectory()) throw new Error('cwd 必须是已有本机目录');
71
71
  if (!ADAPTER_NAMES.includes(participant.adapter)) throw new Error(`适配器只能为 ${ADAPTER_NAMES.join('/')}`);
72
72
  if (participant.adapter === 'antigravity' && !participant.ipc_directory) throw new Error('缺少 Antigravity Sidecar IPC 目录');
@@ -15,6 +15,10 @@ import { FreeRoomRunner } from './free_runner.mjs';
15
15
  const POLL_MS = 2000;
16
16
  // A hosted site is polled less often than a local one: every request spends its database quota.
17
17
  const REMOTE_POLL_MS = 10_000;
18
+ // A hosted site is asked this often which rooms changed; an idle room is read in full only then, or at the safety interval.
19
+ const REVISION_POLL_MS = 3000;
20
+ const IDLE_REMOTE_POLL_MS = 5 * 60_000;
21
+ const REVISION_TIMEOUT_MS = 10_000;
18
22
  const UPDATE_RETRY_MS = 5000;
19
23
  const LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]']);
20
24
  const REMOTE_SYNC_MS = 3000;
@@ -229,7 +233,8 @@ class RoomWorker {
229
233
  }
230
234
  /** A manual Claude, Codex or agy expert that has registered and whose CLI reported its session is handed to the service, which resumes that session from the next turn on. */
231
235
  async adopt_manual_members() {
232
- const members = (await api(this.config, '')).free?.members || [];
236
+ // The runner has just read the room; reading it again here would double every poll.
237
+ const members = (this.runner.room ?? await api(this.config, '')).free?.members || [];
233
238
  for (const member of members) {
234
239
  const key = `${member.name}:${member.reported_session}`;
235
240
  // A restored room keeps its Claude Code members manual so their original window stays the only writer.
@@ -256,7 +261,9 @@ class RoomWorker {
256
261
  const result = await this.runner.tick();
257
262
  await this.adopt_manual_members();
258
263
  Object.assign(this.state, { status: TICK_STATES[result], error: '' });
264
+ this.idle = this.runner.idle?.() === true;
259
265
  } catch (error) {
266
+ this.idle = false;
260
267
  if (await this.deleted(error)) { this.removed = true; return; }
261
268
  if (error.message !== this.state.error) this.log(`${this.state.room || this.path}: ${error.message}`);
262
269
  Object.assign(this.state, { status: 'error', error: error.message });
@@ -296,7 +303,9 @@ export class DiscussionService {
296
303
  this.log = log;
297
304
  this.workers = new Map();
298
305
  // How often each room is checked without a push: a local site costs nothing, a hosted one spends its database quota.
299
- this.intervals = { local: POLL_MS, remote: REMOTE_POLL_MS };
306
+ this.intervals = { local: POLL_MS, remote: REMOTE_POLL_MS, revisions: REVISION_POLL_MS, idle: IDLE_REMOTE_POLL_MS };
307
+ // Per hosted site: when its revisions were last asked, whether a check is in flight, and whether the site offers them.
308
+ this.revisions = new Map();
300
309
  // The resident service turns this on so a site's update stream checks its rooms at once; tests leave it off.
301
310
  this.push = false;
302
311
  this.streams = new Map();
@@ -317,6 +326,8 @@ export class DiscussionService {
317
326
  if (!worker.busy && (wait || Date.now() >= (worker.next_at ?? 0))) this.start(path, worker);
318
327
  }
319
328
  if (this.push) for (const site of new Set([...this.workers.values()].map(worker => worker.state.site).filter(Boolean))) this.watch(site);
329
+ const checks = this.check_revisions();
330
+ if (wait) await checks;
320
331
  if (wait) await Promise.all([...this.workers.values()].map(worker => worker.busy));
321
332
  this.write_status();
322
333
  }
@@ -329,11 +340,54 @@ export class DiscussionService {
329
340
  .finally(() => {
330
341
  // Counted from the end of the tick, once the room's site is known; a slow site does not poll back to back.
331
342
  const local = !worker.state.site || LOOPBACK.has(new URL(worker.state.site).hostname);
332
- worker.next_at = Date.now() + (local ? this.intervals.local : this.intervals.remote);
343
+ // An idle hosted room waits for its revision to change; a site without revisions keeps the regular interval.
344
+ const watched = worker.idle && this.revisions.get(worker.state.site)?.supported !== false;
345
+ worker.next_at = Date.now() + (local ? this.intervals.local : watched ? this.intervals.idle : this.intervals.remote);
333
346
  worker.busy = null;
334
347
  if (worker.woken && this.workers.get(path) === worker) { worker.woken = false; this.start(path, worker); }
335
348
  });
336
349
  }
350
+ /**
351
+ * Ask each hosted site which of its rooms changed since the last check and tick those at once; one small read per site
352
+ * replaces a full read of every idle room. A room missing from the answer was deleted, and its next tick removes it.
353
+ * A site that does not answer (404 without revisions, or an error) leaves its rooms on the regular interval and is asked
354
+ * again after the safety interval.
355
+ */
356
+ async check_revisions() {
357
+ const sites = new Map();
358
+ for (const [path, worker] of this.workers) {
359
+ const site = worker.state.site;
360
+ if (site && worker.state.room && !LOOPBACK.has(new URL(site).hostname)) sites.set(site, [...(sites.get(site) || []), [path, worker]]);
361
+ }
362
+ const checks = [];
363
+ for (const [site, entries] of sites) {
364
+ const watch = this.revisions.get(site) ?? { at: 0, busy: false, supported: true };
365
+ this.revisions.set(site, watch);
366
+ if (this.closed || watch.busy || Date.now() - watch.at < (watch.supported ? this.intervals.revisions : this.intervals.idle)) continue;
367
+ Object.assign(watch, { at: Date.now(), busy: true });
368
+ const rooms = entries.map(([, worker]) => encodeURIComponent(worker.state.room)).join(',');
369
+ checks.push(fetch(`${site}/api/revisions?rooms=${rooms}`, { redirect: 'error', signal: AbortSignal.timeout(REVISION_TIMEOUT_MS) })
370
+ .then(async response => {
371
+ // Without an answer (no such route, or the site failing) the rooms fall back to the regular interval at once,
372
+ // instead of waiting out the safety interval for a revision that may not come.
373
+ if (!response.ok) {
374
+ if (watch.supported) for (const [, worker] of entries) worker.next_at = 0;
375
+ watch.supported = false;
376
+ return;
377
+ }
378
+ watch.supported = true;
379
+ const revisions = (await response.json()).rooms || {};
380
+ for (const [path, worker] of entries) {
381
+ const revision = revisions[worker.state.room] ?? null;
382
+ if (worker.revision !== revision) { worker.revision = revision; this.start(path, worker); }
383
+ }
384
+ })
385
+ // A failed check is retried at the next interval; idle rooms still get their safety tick.
386
+ .catch(() => {})
387
+ .finally(() => { watch.busy = false; }));
388
+ }
389
+ await Promise.all(checks);
390
+ }
337
391
  /** Check every room of a site now, as its website reported a change. */
338
392
  wake(site) {
339
393
  for (const [path, worker] of this.workers) if (worker.state.site === site) this.start(path, worker);
@@ -45,11 +45,11 @@ export function with_expert_prompt(prompt, profile) {
45
45
 
46
46
  /**
47
47
  * Project access of a member woken on this computer: 只读, 可写 (project files and any command), 自动 (the software's own
48
- * automatic approval) and 完全放开 (no approval or sandbox). A manual member the service took over is `own`.
48
+ * automatic approval) and 全放开 (no approval or sandbox). A manual member the service took over is `own`.
49
49
  */
50
50
  export const PROJECT_ACCESS = ['read', 'write', 'auto', 'full'];
51
51
 
52
- const ACCESS_LABELS = { read: '只读', write: '可写', auto: '自动', full: '完全放开' };
52
+ const ACCESS_LABELS = { read: '只读', write: '可写', auto: '自动', full: '全放开' };
53
53
 
54
54
  /** What each level does in each software, measured headless (Claude Code 2.1.312, Codex CLI 0.163, agy 1.4.10). */
55
55
  const ACCESS_HINTS = {
@@ -221,6 +221,7 @@ export class FreeRoomRunner {
221
221
  if (!inbox.archived) this.arrange_groups(inbox, room);
222
222
  this.room_title = room.title;
223
223
  this.room = room;
224
+ this.inbox = inbox;
224
225
  const active = new Map(inbox.executions.map(item => [item.id, item]));
225
226
  const present = new Set(inbox.members.map(member => member.name));
226
227
  // A member sent away or removed no longer appears; its live work was already voided on the website.
@@ -238,6 +239,17 @@ export class FreeRoomRunner {
238
239
  && !member.pending.some(request => this.routing.has(request.id))) await this.start(member, row);
239
240
  }
240
241
  }
242
+ /**
243
+ * Whether the last tick left nothing in progress: no live delivery or process, no open request for a member this
244
+ * runner delivers to, no pending scheduling judgement and no running discussion. An idle room changes only through its
245
+ * website, so the service may wait for the website to report a change before ticking it again.
246
+ */
247
+ idle() {
248
+ const inbox = this.inbox;
249
+ if (!inbox || this.routing.size || [...this.members.values()].some(children => children.size)) return false;
250
+ if (this.room?.session?.status === 'active' || inbox.executions.length || inbox.members.some(member => member.pending.length)) return false;
251
+ return !this.store.db.prepare('SELECT status FROM free_deliveries').all().some(row => LIVE.includes(row.status));
252
+ }
241
253
  /**
242
254
  * Smart scheduling: for each new unnamed message routed to the host, ask the provider whether another woken
243
255
  * member suits it better and apply a confident pick. The host waits for the answer (or its failure) before starting.