multi-agent-platform 0.1.0__py3-none-any.whl

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.
Files changed (144) hide show
  1. cli/__init__.py +0 -0
  2. cli/action_item_escalation.py +177 -0
  3. cli/agent_client.py +554 -0
  4. cli/bridge_state.py +43 -0
  5. cli/commands/__init__.py +13 -0
  6. cli/commands/action.py +142 -0
  7. cli/commands/agent.py +117 -0
  8. cli/commands/audit.py +68 -0
  9. cli/commands/docs.py +179 -0
  10. cli/commands/experiment.py +755 -0
  11. cli/commands/feedback.py +106 -0
  12. cli/commands/notification.py +213 -0
  13. cli/commands/persona.py +63 -0
  14. cli/commands/project.py +87 -0
  15. cli/commands/runtime.py +105 -0
  16. cli/commands/topic.py +361 -0
  17. cli/e2e_collab.py +602 -0
  18. cli/git_checkpoint.py +68 -0
  19. cli/host_worker_types.py +151 -0
  20. cli/main.py +1553 -0
  21. cli/map_command_client.py +497 -0
  22. cli/participant_worker.py +255 -0
  23. cli/reviewer_worker.py +263 -0
  24. cli/runtime/__init__.py +5 -0
  25. cli/runtime/run_lock.py +497 -0
  26. cli/runtime_chat.py +317 -0
  27. cli/session_wake_log.py +235 -0
  28. cli/simple_waker.py +950 -0
  29. cli/table_render.py +113 -0
  30. cli/wake_backend.py +236 -0
  31. cli/worker_cycle_log.py +36 -0
  32. map_client/__init__.py +37 -0
  33. map_client/bootstrap.py +193 -0
  34. map_client/client.py +1045 -0
  35. map_client/config.py +21 -0
  36. map_client/errors.py +283 -0
  37. map_client/exceptions.py +130 -0
  38. map_client/plan_evidence.py +159 -0
  39. map_client/project_config.py +153 -0
  40. map_client/result_template.py +167 -0
  41. map_client/testing.py +27 -0
  42. map_mcp/__init__.py +4 -0
  43. map_mcp/_utils.py +28 -0
  44. map_mcp/auth.py +34 -0
  45. map_mcp/config.py +50 -0
  46. map_mcp/context.py +39 -0
  47. map_mcp/main.py +75 -0
  48. map_mcp/server.py +573 -0
  49. map_mcp/session.py +79 -0
  50. map_sdk/__init__.py +29 -0
  51. map_sdk/evidence.py +68 -0
  52. map_types/__init__.py +203 -0
  53. map_types/enums.py +199 -0
  54. map_types/schemas.py +1351 -0
  55. multi_agent_platform-0.1.0.dist-info/METADATA +298 -0
  56. multi_agent_platform-0.1.0.dist-info/RECORD +144 -0
  57. multi_agent_platform-0.1.0.dist-info/WHEEL +5 -0
  58. multi_agent_platform-0.1.0.dist-info/entry_points.txt +6 -0
  59. multi_agent_platform-0.1.0.dist-info/licenses/LICENSE +21 -0
  60. multi_agent_platform-0.1.0.dist-info/top_level.txt +6 -0
  61. server/__init__.py +0 -0
  62. server/__version__.py +14 -0
  63. server/api/__init__.py +0 -0
  64. server/api/action_items.py +138 -0
  65. server/api/agents.py +412 -0
  66. server/api/audit.py +54 -0
  67. server/api/background_tasks.py +18 -0
  68. server/api/common.py +117 -0
  69. server/api/deps.py +30 -0
  70. server/api/experiments.py +858 -0
  71. server/api/feedback.py +75 -0
  72. server/api/notifications.py +22 -0
  73. server/api/projects.py +209 -0
  74. server/api/router.py +25 -0
  75. server/api/status.py +33 -0
  76. server/api/topics.py +302 -0
  77. server/api/webhooks.py +74 -0
  78. server/auth/__init__.py +8 -0
  79. server/auth/experiment_access.py +66 -0
  80. server/config.py +38 -0
  81. server/db/__init__.py +3 -0
  82. server/db/base.py +5 -0
  83. server/db/deadlock_retry.py +146 -0
  84. server/db/session.py +41 -0
  85. server/domain/__init__.py +3 -0
  86. server/domain/encrypted_types.py +63 -0
  87. server/domain/models.py +713 -0
  88. server/domain/schemas.py +3 -0
  89. server/domain/state_machine.py +79 -0
  90. server/domain/topic_ack_constants.py +9 -0
  91. server/main.py +148 -0
  92. server/scripts/__init__.py +0 -0
  93. server/scripts/migrate_notification_unique.py +231 -0
  94. server/scripts/purge_audit_pollution.py +116 -0
  95. server/services/__init__.py +0 -0
  96. server/services/_lookups.py +26 -0
  97. server/services/acceptance_service.py +90 -0
  98. server/services/action_item_migration_service.py +190 -0
  99. server/services/action_item_service.py +200 -0
  100. server/services/agent_work_service.py +405 -0
  101. server/services/archive_lint_service.py +156 -0
  102. server/services/audit_service.py +457 -0
  103. server/services/auth.py +66 -0
  104. server/services/comment_service.py +173 -0
  105. server/services/errors.py +65 -0
  106. server/services/escalation_resolver.py +248 -0
  107. server/services/evidence_service.py +88 -0
  108. server/services/experiment_capabilities_service.py +277 -0
  109. server/services/inbound_event_service.py +111 -0
  110. server/services/lock_service.py +273 -0
  111. server/services/log_service.py +202 -0
  112. server/services/mention_service.py +730 -0
  113. server/services/notification_service.py +939 -0
  114. server/services/notification_stream.py +138 -0
  115. server/services/permissions.py +147 -0
  116. server/services/persona_activity_service.py +108 -0
  117. server/services/phase_owner_resolver.py +95 -0
  118. server/services/phase_service.py +381 -0
  119. server/services/plan_marker_service.py +235 -0
  120. server/services/plan_service.py +186 -0
  121. server/services/platform_feedback_service.py +114 -0
  122. server/services/project_service.py +534 -0
  123. server/services/project_status_service.py +132 -0
  124. server/services/review_service.py +707 -0
  125. server/services/secret_encryption.py +97 -0
  126. server/services/similarity_service.py +119 -0
  127. server/services/sse_event_schemas.py +17 -0
  128. server/services/status_service.py +68 -0
  129. server/services/template_service.py +134 -0
  130. server/services/text_utils.py +19 -0
  131. server/services/thread_activity.py +180 -0
  132. server/services/todo_persona_filter.py +73 -0
  133. server/services/todo_service.py +604 -0
  134. server/services/topic_ack_service.py +312 -0
  135. server/services/topic_action_item_ops.py +538 -0
  136. server/services/topic_comment_kind.py +14 -0
  137. server/services/topic_comment_service.py +237 -0
  138. server/services/topic_helpers.py +32 -0
  139. server/services/topic_lifecycle_service.py +478 -0
  140. server/services/topic_progress_service.py +40 -0
  141. server/services/topic_resolve_service.py +234 -0
  142. server/services/topic_service.py +102 -0
  143. server/services/topic_work_item_service.py +570 -0
  144. server/services/webhook_service.py +273 -0
@@ -0,0 +1,234 @@
1
+ """Topic resolve + decision read helpers.
2
+
3
+ 拆分自 ``topic_service.py``。本模块负责:
4
+ - ``resolve_topic``(决策 upsert + action_item 二次约束)
5
+ - ``topic_decision_read`` / ``_load_decision`` / ``list_project_decisions``
6
+
7
+ Action-item 序列化委托 ``topic_action_item_ops``;
8
+ ``topic_service`` re-exports 保持 import 兼容。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+ from datetime import UTC, datetime
14
+ from typing import Any
15
+
16
+ from map_types.enums import AgentRole, TopicActionItemStatus
17
+ from sqlalchemy import select
18
+ from sqlalchemy.orm import Session, joinedload
19
+
20
+ from server.domain.models import Agent, Experiment, Topic, TopicActionItem, TopicDecision
21
+ from server.domain.schemas import TopicDecisionRead, TopicResolve
22
+ from server.services import audit_service
23
+ from server.services._lookups import get_project
24
+ from server.services.errors import NotFoundError
25
+ from server.services.topic_action_item_ops import (
26
+ _action_item_read,
27
+ _linked_experiment_phases_batch,
28
+ _suggest_linked_experiments_batch,
29
+ )
30
+ from server.services.topic_helpers import _get_topic
31
+
32
+
33
+ def topic_decision_read(db: Session, decision: TopicDecision) -> TopicDecisionRead:
34
+ author_name = None
35
+ author = getattr(decision, "author", None)
36
+ if author is None:
37
+ author = db.get(Agent, decision.author_agent_id)
38
+ if author is not None:
39
+ author_name = author.name
40
+
41
+ topic_title = None
42
+ topic = getattr(decision, "topic", None)
43
+ if topic is None:
44
+ topic = db.get(Topic, decision.topic_id)
45
+ if topic is not None:
46
+ topic_title = topic.title
47
+
48
+ suggested_map = _suggest_linked_experiments_batch(db, list(decision.action_items))
49
+ phase_map = _linked_experiment_phases_batch(db, list(decision.action_items))
50
+ return TopicDecisionRead(
51
+ id=decision.id,
52
+ project_id=decision.project_id,
53
+ topic_id=decision.topic_id,
54
+ topic_title=topic_title,
55
+ author_agent_id=decision.author_agent_id,
56
+ author_name=author_name,
57
+ decision=decision.decision,
58
+ rationale=decision.rationale,
59
+ rejected_options=decision.rejected_options,
60
+ open_questions=decision.open_questions,
61
+ no_decision_reason=decision.no_decision_reason,
62
+ action_items=[
63
+ _action_item_read(
64
+ db,
65
+ item,
66
+ suggested=suggested_map.get(item.id),
67
+ linked_experiment_phase=phase_map.get(item.id),
68
+ linked_experiment_phase_provided=True,
69
+ )
70
+ for item in decision.action_items
71
+ ],
72
+ created_at=decision.created_at,
73
+ updated_at=decision.updated_at,
74
+ )
75
+
76
+
77
+ def _load_decision(db: Session, topic_id: uuid.UUID) -> TopicDecision | None:
78
+ stmt = (
79
+ select(TopicDecision)
80
+ .where(TopicDecision.topic_id == topic_id)
81
+ .options(
82
+ joinedload(TopicDecision.author),
83
+ joinedload(TopicDecision.topic),
84
+ joinedload(TopicDecision.action_items).joinedload(TopicActionItem.owner),
85
+ )
86
+ )
87
+ return db.execute(stmt).unique().scalar_one_or_none()
88
+ def resolve_topic(
89
+ db: Session,
90
+ topic_id: uuid.UUID,
91
+ author: Agent,
92
+ payload: TopicResolve,
93
+ ) -> TopicDecision:
94
+ topic = _get_topic(db, topic_id)
95
+ decision = db.scalar(select(TopicDecision).where(TopicDecision.topic_id == topic_id))
96
+ if decision is None:
97
+ decision = TopicDecision(
98
+ project_id=topic.project_id,
99
+ topic_id=topic.id,
100
+ author_agent_id=author.id,
101
+ )
102
+ db.add(decision)
103
+ db.flush()
104
+ else:
105
+ decision.author_agent_id = author.id
106
+
107
+ decision.decision = payload.decision
108
+ decision.rationale = payload.rationale
109
+ decision.rejected_options = payload.rejected_options
110
+ decision.open_questions = payload.open_questions
111
+ decision.no_decision_reason = payload.no_decision_reason
112
+
113
+ # --- 二次约束: 旧项不在新 payload 中 → 等价显式 done (A1 resolve idempotency) -
114
+ # Up until v0.x the resolver did ``db.execute(delete(TopicActionItem))`` and
115
+ # re-inserted from payload, which made every re-resolve a destructive
116
+ # delete-and-rebuild and made ``status=done`` impossible to persist. The new
117
+ # rule is: items not present in the new payload (by id) are auto-closed as
118
+ # ``done``; items present are upserted by id (fields updated, status
119
+ # preserved); items with no id are inserted as open.
120
+ existing = list(
121
+ db.scalars(select(TopicActionItem).where(TopicActionItem.decision_id == decision.id))
122
+ )
123
+ existing_by_id = {item.id: item for item in existing}
124
+ new_payload_ids: set[uuid.UUID] = {
125
+ item.id for item in payload.action_items if item.id is not None
126
+ }
127
+
128
+ audit_entries: list[dict[str, Any]] = []
129
+ for old_item in existing:
130
+ if old_item.id in new_payload_ids:
131
+ continue
132
+ # 旧项不在新 payload:若仍为 open,等价显式 done(写 audit;已 closed 项保持原状)。
133
+ if old_item.status == TopicActionItemStatus.open:
134
+ prev_status = old_item.status
135
+ old_item.status = TopicActionItemStatus.done
136
+ old_item.updated_at = datetime.now(UTC)
137
+ audit_entries.append(
138
+ {
139
+ "action": "action_item.completed",
140
+ "target_id": old_item.id,
141
+ "payload": {
142
+ "action_item_id": str(old_item.id),
143
+ "prev_status": prev_status.value,
144
+ "new_status": old_item.status.value,
145
+ "triggered_by": "topic_resolve",
146
+ },
147
+ }
148
+ )
149
+
150
+ for item_payload in payload.action_items:
151
+ if item_payload.owner_agent_id is not None:
152
+ owner = db.get(Agent, item_payload.owner_agent_id)
153
+ if owner is None or (owner.project_id != topic.project_id and owner.role != AgentRole.admin):
154
+ raise NotFoundError("Action item owner agent not found")
155
+ if item_payload.linked_experiment_id is not None:
156
+ experiment = db.get(Experiment, item_payload.linked_experiment_id)
157
+ if experiment is None or experiment.deleted_at is not None or experiment.project_id != topic.project_id:
158
+ raise NotFoundError("Linked experiment not found")
159
+ if item_payload.id is not None and item_payload.id in existing_by_id:
160
+ old_item = existing_by_id[item_payload.id]
161
+ old_item.title = item_payload.title
162
+ old_item.description = item_payload.description
163
+ old_item.owner_agent_id = item_payload.owner_agent_id
164
+ old_item.due_at = item_payload.due_at
165
+ old_item.linked_experiment_id = item_payload.linked_experiment_id
166
+ old_item.topic_id = topic.id
167
+ if item_payload.category is not None:
168
+ old_item.category = item_payload.category.value
169
+ else:
170
+ # Stamp first_open_at at creation so the waker's T+24h / T+72h
171
+ # escalation timer starts immediately on resolve. Plan §2: open
172
+ # transitions are the moment the timer anchors to. Existing open
173
+ # items are backfilled by alembic migration 027.
174
+ db.add(
175
+ TopicActionItem(
176
+ decision_id=decision.id,
177
+ project_id=topic.project_id,
178
+ topic_id=topic.id,
179
+ title=item_payload.title,
180
+ description=item_payload.description,
181
+ owner_agent_id=item_payload.owner_agent_id,
182
+ status=TopicActionItemStatus.open,
183
+ due_at=item_payload.due_at,
184
+ linked_experiment_id=item_payload.linked_experiment_id,
185
+ category=item_payload.category.value if item_payload.category else None,
186
+ first_open_at=datetime.now(UTC),
187
+ )
188
+ )
189
+
190
+ # Audit 与状态变更同事务:audit_entries 收集的「旧项等价 done」事件用
191
+ # log_no_commit 累积,与上面的 decision / action_item 变更在单次 commit 内
192
+ # 一起落库。commit 失败则全部回滚——不再出现「decision 已存但 audit 缺失」。
193
+ for entry in audit_entries:
194
+ audit_service.log_no_commit(
195
+ db,
196
+ action=entry["action"],
197
+ target_type="topic_action_item",
198
+ target_id=entry["target_id"],
199
+ agent_id=author.id,
200
+ project_id=topic.project_id,
201
+ summary="resolve 二次约束关闭 action_item",
202
+ payload=entry["payload"],
203
+ )
204
+ db.commit()
205
+
206
+ loaded = _load_decision(db, topic.id)
207
+ if loaded is None:
208
+ raise NotFoundError("Topic decision not found")
209
+ return loaded
210
+
211
+
212
+ def list_project_decisions(
213
+ db: Session,
214
+ project_id: uuid.UUID,
215
+ *,
216
+ limit: int = 20,
217
+ ) -> list[TopicDecisionRead]:
218
+ get_project(db, project_id)
219
+ rows = list(
220
+ db.scalars(
221
+ select(TopicDecision)
222
+ .where(TopicDecision.project_id == project_id)
223
+ .options(
224
+ joinedload(TopicDecision.author),
225
+ joinedload(TopicDecision.topic),
226
+ joinedload(TopicDecision.action_items).joinedload(TopicActionItem.owner),
227
+ )
228
+ .order_by(TopicDecision.updated_at.desc())
229
+ .limit(max(1, min(limit, 100)))
230
+ )
231
+ .unique()
232
+ )
233
+ return [topic_decision_read(db, row) for row in rows]
234
+
@@ -0,0 +1,102 @@
1
+ """Topic domain service facade.
2
+
3
+ Business logic lives in:
4
+ - ``topic_lifecycle_service`` — CRUD / summaries / rounds
5
+ - ``topic_resolve_service`` — resolve + decisions
6
+ - ``topic_action_item_ops`` — action-item mutations + reads
7
+ - ``topic_comment_service`` — comments (earlier split)
8
+ - ``topic_helpers`` — shared ``_get_topic`` / ``_agent_names_by_ids``
9
+
10
+ This module re-exports the public (and historically private) API so
11
+ existing ``from server.services.topic_service import ...`` call sites
12
+ and ``topic_service.<name>`` attribute access keep working.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from server.services import topic_comment_service
17
+ from server.services.topic_action_item_ops import (
18
+ _action_item_read,
19
+ _complete_action_item_no_commit,
20
+ _ensure_action_item_accessor,
21
+ _linked_experiment_phases_batch,
22
+ _suggest_linked_experiment,
23
+ _suggest_linked_experiments_batch,
24
+ cancel_action_item,
25
+ complete_action_item,
26
+ deliver_action_item,
27
+ deliver_action_item_no_commit,
28
+ link_action_item,
29
+ list_action_items,
30
+ mark_stale_action_item,
31
+ mark_wake_sent_action_item,
32
+ )
33
+ from server.services.topic_helpers import _agent_names_by_ids, _get_topic
34
+ from server.services.topic_lifecycle_service import (
35
+ advance_topic_round,
36
+ create_topic,
37
+ dismiss_topic,
38
+ get_topic_detail,
39
+ list_topics,
40
+ mark_topic_read,
41
+ record_participant_round_ack,
42
+ set_topic_status,
43
+ soft_delete_topic,
44
+ topic_summaries_for_topics,
45
+ topic_summary,
46
+ update_topic,
47
+ )
48
+ from server.services.topic_resolve_service import (
49
+ _load_decision,
50
+ list_project_decisions,
51
+ resolve_topic,
52
+ topic_decision_read,
53
+ )
54
+
55
+ # Comment CRUD / tree / seq — re-export for backward compatibility.
56
+ _build_comment_tree = topic_comment_service._build_comment_tree
57
+ _ensure_comment_seq_values = topic_comment_service._ensure_comment_seq_values
58
+ _next_topic_comment_seq = topic_comment_service._next_topic_comment_seq
59
+ create_topic_comment = topic_comment_service.create_topic_comment
60
+ topic_comment_read = topic_comment_service.topic_comment_read
61
+ list_topic_comments = topic_comment_service.list_topic_comments
62
+
63
+ __all__ = [
64
+ "_agent_names_by_ids",
65
+ "_get_topic",
66
+ "_action_item_read",
67
+ "_complete_action_item_no_commit",
68
+ "_ensure_action_item_accessor",
69
+ "_linked_experiment_phases_batch",
70
+ "_load_decision",
71
+ "_suggest_linked_experiment",
72
+ "_suggest_linked_experiments_batch",
73
+ "_build_comment_tree",
74
+ "_ensure_comment_seq_values",
75
+ "_next_topic_comment_seq",
76
+ "advance_topic_round",
77
+ "cancel_action_item",
78
+ "complete_action_item",
79
+ "create_topic",
80
+ "create_topic_comment",
81
+ "deliver_action_item",
82
+ "deliver_action_item_no_commit",
83
+ "dismiss_topic",
84
+ "get_topic_detail",
85
+ "link_action_item",
86
+ "list_action_items",
87
+ "list_project_decisions",
88
+ "list_topic_comments",
89
+ "list_topics",
90
+ "mark_stale_action_item",
91
+ "mark_topic_read",
92
+ "mark_wake_sent_action_item",
93
+ "record_participant_round_ack",
94
+ "resolve_topic",
95
+ "set_topic_status",
96
+ "soft_delete_topic",
97
+ "topic_comment_read",
98
+ "topic_decision_read",
99
+ "topic_summaries_for_topics",
100
+ "topic_summary",
101
+ "update_topic",
102
+ ]