ai_co_discuss 0.3.0 → 0.3.1
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/backend/schema.sql +339 -0
- package/backend/server.py +22 -0
- package/docs/deployment.md +1 -1
- package/package.json +1 -1
- package/runner/discussion_adapters.mjs +3 -3
- package/runner/discussion_launch.mjs +1 -1
- package/runner/discussion_runner.mjs +1 -1
- package/runner/discussion_service.mjs +57 -3
- package/runner/expert_prompts.mjs +2 -2
- package/runner/free_runner.mjs +12 -0
- package/web/app.js +33 -7
- package/web/modern.css +1 -1
- package/web/modern.js +273 -253
package/backend/schema.sql
CHANGED
|
@@ -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})
|
package/docs/deployment.md
CHANGED
|
@@ -109,7 +109,7 @@ codiscuss health --site https://YOUR_PROJECT_NAME.pages.dev
|
|
|
109
109
|
|
|
110
110
|
### 实时推送(可选)
|
|
111
111
|
|
|
112
|
-
未配置时网页每 12
|
|
112
|
+
未配置时网页每 12 秒检查一次房间是否有变化,有变化才重新读取。需要跨实例实时推送时,先部署通知 Worker,再在私有配置中绑定:
|
|
113
113
|
|
|
114
114
|
```sh
|
|
115
115
|
npx wrangler deploy --config deploy/updates.wrangler.jsonc
|
package/package.json
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 = {
|
package/runner/free_runner.mjs
CHANGED
|
@@ -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.
|