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.
- cli/__init__.py +0 -0
- cli/action_item_escalation.py +177 -0
- cli/agent_client.py +554 -0
- cli/bridge_state.py +43 -0
- cli/commands/__init__.py +13 -0
- cli/commands/action.py +142 -0
- cli/commands/agent.py +117 -0
- cli/commands/audit.py +68 -0
- cli/commands/docs.py +179 -0
- cli/commands/experiment.py +755 -0
- cli/commands/feedback.py +106 -0
- cli/commands/notification.py +213 -0
- cli/commands/persona.py +63 -0
- cli/commands/project.py +87 -0
- cli/commands/runtime.py +105 -0
- cli/commands/topic.py +361 -0
- cli/e2e_collab.py +602 -0
- cli/git_checkpoint.py +68 -0
- cli/host_worker_types.py +151 -0
- cli/main.py +1553 -0
- cli/map_command_client.py +497 -0
- cli/participant_worker.py +255 -0
- cli/reviewer_worker.py +263 -0
- cli/runtime/__init__.py +5 -0
- cli/runtime/run_lock.py +497 -0
- cli/runtime_chat.py +317 -0
- cli/session_wake_log.py +235 -0
- cli/simple_waker.py +950 -0
- cli/table_render.py +113 -0
- cli/wake_backend.py +236 -0
- cli/worker_cycle_log.py +36 -0
- map_client/__init__.py +37 -0
- map_client/bootstrap.py +193 -0
- map_client/client.py +1045 -0
- map_client/config.py +21 -0
- map_client/errors.py +283 -0
- map_client/exceptions.py +130 -0
- map_client/plan_evidence.py +159 -0
- map_client/project_config.py +153 -0
- map_client/result_template.py +167 -0
- map_client/testing.py +27 -0
- map_mcp/__init__.py +4 -0
- map_mcp/_utils.py +28 -0
- map_mcp/auth.py +34 -0
- map_mcp/config.py +50 -0
- map_mcp/context.py +39 -0
- map_mcp/main.py +75 -0
- map_mcp/server.py +573 -0
- map_mcp/session.py +79 -0
- map_sdk/__init__.py +29 -0
- map_sdk/evidence.py +68 -0
- map_types/__init__.py +203 -0
- map_types/enums.py +199 -0
- map_types/schemas.py +1351 -0
- multi_agent_platform-0.1.0.dist-info/METADATA +298 -0
- multi_agent_platform-0.1.0.dist-info/RECORD +144 -0
- multi_agent_platform-0.1.0.dist-info/WHEEL +5 -0
- multi_agent_platform-0.1.0.dist-info/entry_points.txt +6 -0
- multi_agent_platform-0.1.0.dist-info/licenses/LICENSE +21 -0
- multi_agent_platform-0.1.0.dist-info/top_level.txt +6 -0
- server/__init__.py +0 -0
- server/__version__.py +14 -0
- server/api/__init__.py +0 -0
- server/api/action_items.py +138 -0
- server/api/agents.py +412 -0
- server/api/audit.py +54 -0
- server/api/background_tasks.py +18 -0
- server/api/common.py +117 -0
- server/api/deps.py +30 -0
- server/api/experiments.py +858 -0
- server/api/feedback.py +75 -0
- server/api/notifications.py +22 -0
- server/api/projects.py +209 -0
- server/api/router.py +25 -0
- server/api/status.py +33 -0
- server/api/topics.py +302 -0
- server/api/webhooks.py +74 -0
- server/auth/__init__.py +8 -0
- server/auth/experiment_access.py +66 -0
- server/config.py +38 -0
- server/db/__init__.py +3 -0
- server/db/base.py +5 -0
- server/db/deadlock_retry.py +146 -0
- server/db/session.py +41 -0
- server/domain/__init__.py +3 -0
- server/domain/encrypted_types.py +63 -0
- server/domain/models.py +713 -0
- server/domain/schemas.py +3 -0
- server/domain/state_machine.py +79 -0
- server/domain/topic_ack_constants.py +9 -0
- server/main.py +148 -0
- server/scripts/__init__.py +0 -0
- server/scripts/migrate_notification_unique.py +231 -0
- server/scripts/purge_audit_pollution.py +116 -0
- server/services/__init__.py +0 -0
- server/services/_lookups.py +26 -0
- server/services/acceptance_service.py +90 -0
- server/services/action_item_migration_service.py +190 -0
- server/services/action_item_service.py +200 -0
- server/services/agent_work_service.py +405 -0
- server/services/archive_lint_service.py +156 -0
- server/services/audit_service.py +457 -0
- server/services/auth.py +66 -0
- server/services/comment_service.py +173 -0
- server/services/errors.py +65 -0
- server/services/escalation_resolver.py +248 -0
- server/services/evidence_service.py +88 -0
- server/services/experiment_capabilities_service.py +277 -0
- server/services/inbound_event_service.py +111 -0
- server/services/lock_service.py +273 -0
- server/services/log_service.py +202 -0
- server/services/mention_service.py +730 -0
- server/services/notification_service.py +939 -0
- server/services/notification_stream.py +138 -0
- server/services/permissions.py +147 -0
- server/services/persona_activity_service.py +108 -0
- server/services/phase_owner_resolver.py +95 -0
- server/services/phase_service.py +381 -0
- server/services/plan_marker_service.py +235 -0
- server/services/plan_service.py +186 -0
- server/services/platform_feedback_service.py +114 -0
- server/services/project_service.py +534 -0
- server/services/project_status_service.py +132 -0
- server/services/review_service.py +707 -0
- server/services/secret_encryption.py +97 -0
- server/services/similarity_service.py +119 -0
- server/services/sse_event_schemas.py +17 -0
- server/services/status_service.py +68 -0
- server/services/template_service.py +134 -0
- server/services/text_utils.py +19 -0
- server/services/thread_activity.py +180 -0
- server/services/todo_persona_filter.py +73 -0
- server/services/todo_service.py +604 -0
- server/services/topic_ack_service.py +312 -0
- server/services/topic_action_item_ops.py +538 -0
- server/services/topic_comment_kind.py +14 -0
- server/services/topic_comment_service.py +237 -0
- server/services/topic_helpers.py +32 -0
- server/services/topic_lifecycle_service.py +478 -0
- server/services/topic_progress_service.py +40 -0
- server/services/topic_resolve_service.py +234 -0
- server/services/topic_service.py +102 -0
- server/services/topic_work_item_service.py +570 -0
- server/services/webhook_service.py +273 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Topic comment CRUD and tree/seq helpers.
|
|
2
|
+
|
|
3
|
+
拆分自 ``topic_service.py``(P1 #1)以缓解 1100+ 行单文件可读性问题。
|
|
4
|
+
本模块只负责 topic 评论的写入与读取:
|
|
5
|
+
- ``create_topic_comment``:写评论 + bump ``topic.updated_at`` + 触发
|
|
6
|
+
round-summary 检测 + mention 处理。
|
|
7
|
+
- ``list_topic_comments`` / ``topic_comment_read``:序列化(含 author_name、
|
|
8
|
+
``comment_seq`` 兜底、可选 tree)。
|
|
9
|
+
- ``_build_comment_tree`` / ``_ensure_comment_seq_values`` /
|
|
10
|
+
``_next_topic_comment_seq``:tree 组装与 seq 分配(兼容历史无 seq 行)。
|
|
11
|
+
|
|
12
|
+
``_get_topic`` 与 ``_agent_names_by_ids`` 是从 ``topic_service`` 复制的极短
|
|
13
|
+
helper(避免新模块依赖 topic_service 的私有 API 引入循环);这两个函数
|
|
14
|
+
保持单行为,未来若 topic_service 公开了等价 API 可统一。
|
|
15
|
+
|
|
16
|
+
向后兼容:``topic_service`` 仍 re-export 本模块的公开函数,调用方
|
|
17
|
+
``from server.services.topic_service import create_topic_comment`` 无需修改。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import uuid
|
|
23
|
+
from datetime import UTC, datetime
|
|
24
|
+
|
|
25
|
+
from map_types.enums import AgentRole, TopicCommentKind
|
|
26
|
+
from sqlalchemy import func, select
|
|
27
|
+
from sqlalchemy.orm import Session
|
|
28
|
+
|
|
29
|
+
from server.domain.models import Agent, Topic, TopicComment
|
|
30
|
+
from server.domain.schemas import (
|
|
31
|
+
TopicCommentCreate,
|
|
32
|
+
TopicCommentRead,
|
|
33
|
+
TopicCommentTreeNode,
|
|
34
|
+
)
|
|
35
|
+
from server.services import mention_service, topic_ack_service
|
|
36
|
+
from server.services.errors import NotFoundError
|
|
37
|
+
from server.services.thread_activity import topic_comment_order_clauses
|
|
38
|
+
from server.services.topic_comment_kind import resolve_topic_comment_kind
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_topic(db: Session, topic_id: uuid.UUID) -> Topic:
|
|
42
|
+
topic = db.get(Topic, topic_id)
|
|
43
|
+
if topic is None or topic.deleted_at is not None:
|
|
44
|
+
raise NotFoundError("Topic not found")
|
|
45
|
+
return topic
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _agent_names_by_ids(db: Session, agent_ids: set[uuid.UUID]) -> dict[uuid.UUID, str]:
|
|
49
|
+
if not agent_ids:
|
|
50
|
+
return {}
|
|
51
|
+
rows = db.execute(
|
|
52
|
+
select(Agent.id, Agent.name).where(Agent.id.in_(agent_ids))
|
|
53
|
+
)
|
|
54
|
+
return {agent_id: name for agent_id, name in rows}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _comment_kind(comment: TopicComment) -> TopicCommentKind:
|
|
58
|
+
return comment.kind or TopicCommentKind.user
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_comment_tree(
|
|
62
|
+
comments: list[TopicComment],
|
|
63
|
+
author_names: dict[uuid.UUID, str],
|
|
64
|
+
) -> list[TopicCommentTreeNode]:
|
|
65
|
+
_ensure_comment_seq_values(comments)
|
|
66
|
+
nodes: dict[uuid.UUID, TopicCommentTreeNode] = {}
|
|
67
|
+
for comment in comments:
|
|
68
|
+
nodes[comment.id] = TopicCommentTreeNode(
|
|
69
|
+
id=comment.id,
|
|
70
|
+
topic_id=comment.topic_id,
|
|
71
|
+
author_agent_id=comment.author_agent_id,
|
|
72
|
+
author_name=author_names.get(comment.author_agent_id),
|
|
73
|
+
parent_comment_id=comment.parent_comment_id,
|
|
74
|
+
body=comment.body,
|
|
75
|
+
kind=_comment_kind(comment),
|
|
76
|
+
comment_seq=comment.comment_seq,
|
|
77
|
+
created_at=comment.created_at,
|
|
78
|
+
children=[],
|
|
79
|
+
)
|
|
80
|
+
roots: list[TopicCommentTreeNode] = []
|
|
81
|
+
for comment in comments:
|
|
82
|
+
node = nodes[comment.id]
|
|
83
|
+
if comment.parent_comment_id and comment.parent_comment_id in nodes:
|
|
84
|
+
nodes[comment.parent_comment_id].children.append(node)
|
|
85
|
+
else:
|
|
86
|
+
roots.append(node)
|
|
87
|
+
return roots
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _ensure_comment_seq_values(comments: list[TopicComment]) -> None:
|
|
91
|
+
if all(comment.comment_seq is not None for comment in comments):
|
|
92
|
+
return
|
|
93
|
+
by_topic: dict[uuid.UUID, list[TopicComment]] = {}
|
|
94
|
+
for comment in comments:
|
|
95
|
+
by_topic.setdefault(comment.topic_id, []).append(comment)
|
|
96
|
+
for topic_comments in by_topic.values():
|
|
97
|
+
ordered = sorted(
|
|
98
|
+
topic_comments,
|
|
99
|
+
key=lambda comment: (
|
|
100
|
+
comment.created_at,
|
|
101
|
+
comment.comment_seq if comment.comment_seq is not None else 0,
|
|
102
|
+
comment.id,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
for index, comment in enumerate(ordered, start=1):
|
|
106
|
+
if comment.comment_seq is None:
|
|
107
|
+
comment.comment_seq = index
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _next_topic_comment_seq(db: Session, topic_id: uuid.UUID) -> int:
|
|
111
|
+
current = db.scalar(
|
|
112
|
+
select(func.coalesce(func.max(TopicComment.comment_seq), 0)).where(
|
|
113
|
+
TopicComment.topic_id == topic_id
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
return int(current or 0) + 1
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def create_topic_comment(
|
|
120
|
+
db: Session,
|
|
121
|
+
topic_id: uuid.UUID,
|
|
122
|
+
author: Agent,
|
|
123
|
+
payload: TopicCommentCreate,
|
|
124
|
+
*,
|
|
125
|
+
commit: bool = True,
|
|
126
|
+
) -> tuple[TopicComment, list[str]]:
|
|
127
|
+
topic = _get_topic(db, topic_id)
|
|
128
|
+
if payload.parent_id is not None:
|
|
129
|
+
parent = db.scalar(
|
|
130
|
+
select(TopicComment).where(
|
|
131
|
+
TopicComment.id == payload.parent_id, TopicComment.topic_id == topic_id
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
if parent is None:
|
|
135
|
+
raise NotFoundError("Parent comment not found")
|
|
136
|
+
comment = TopicComment(
|
|
137
|
+
topic_id=topic_id,
|
|
138
|
+
author_agent_id=author.id,
|
|
139
|
+
parent_comment_id=payload.parent_id,
|
|
140
|
+
body=payload.body,
|
|
141
|
+
kind=resolve_topic_comment_kind(payload.body),
|
|
142
|
+
comment_seq=_next_topic_comment_seq(db, topic_id),
|
|
143
|
+
)
|
|
144
|
+
db.add(comment)
|
|
145
|
+
# Bump topic.updated_at so any prior host-side dismiss on this topic
|
|
146
|
+
# is automatically un-dismissed — there's new activity worth seeing.
|
|
147
|
+
topic.updated_at = datetime.now(UTC)
|
|
148
|
+
if (
|
|
149
|
+
author.id == topic.creator_agent_id
|
|
150
|
+
and payload.parent_id is None
|
|
151
|
+
and topic_ack_service.is_round_summary_comment(payload.body)
|
|
152
|
+
):
|
|
153
|
+
topic_ack_service.mark_round_ack_pending(topic)
|
|
154
|
+
db.flush()
|
|
155
|
+
db.refresh(comment)
|
|
156
|
+
|
|
157
|
+
unresolved = mention_service.process_topic_comment_mentions(
|
|
158
|
+
db, comment=comment, author=author, topic=topic, commit=False
|
|
159
|
+
)
|
|
160
|
+
mention_service.auto_dismiss_mentions_after_comment(
|
|
161
|
+
db,
|
|
162
|
+
new_comment_author=author,
|
|
163
|
+
experiment_id=None,
|
|
164
|
+
topic_id=topic_id,
|
|
165
|
+
new_comment_id=comment.id,
|
|
166
|
+
comment_body=payload.body,
|
|
167
|
+
commit=False,
|
|
168
|
+
)
|
|
169
|
+
if commit:
|
|
170
|
+
db.commit()
|
|
171
|
+
else:
|
|
172
|
+
db.flush()
|
|
173
|
+
return comment, unresolved
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def topic_comment_read(
|
|
177
|
+
db: Session, comment: TopicComment, *, unresolved_mentions: list[str] | None = None
|
|
178
|
+
) -> TopicCommentRead:
|
|
179
|
+
author_names = _agent_names_by_ids(db, {comment.author_agent_id})
|
|
180
|
+
_ensure_comment_seq_values([comment])
|
|
181
|
+
return TopicCommentRead(
|
|
182
|
+
id=comment.id,
|
|
183
|
+
topic_id=comment.topic_id,
|
|
184
|
+
author_agent_id=comment.author_agent_id,
|
|
185
|
+
author_name=author_names.get(comment.author_agent_id),
|
|
186
|
+
parent_comment_id=comment.parent_comment_id,
|
|
187
|
+
body=comment.body,
|
|
188
|
+
kind=_comment_kind(comment),
|
|
189
|
+
comment_seq=comment.comment_seq,
|
|
190
|
+
created_at=comment.created_at,
|
|
191
|
+
unresolved_mentions=list(unresolved_mentions or ()),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def list_topic_comments(
|
|
196
|
+
db: Session,
|
|
197
|
+
topic_id: uuid.UUID,
|
|
198
|
+
*,
|
|
199
|
+
tree: bool = False,
|
|
200
|
+
limit: int = 100,
|
|
201
|
+
) -> list[TopicCommentRead] | list[TopicCommentTreeNode]:
|
|
202
|
+
_get_topic(db, topic_id)
|
|
203
|
+
stmt = (
|
|
204
|
+
select(TopicComment)
|
|
205
|
+
.where(TopicComment.topic_id == topic_id)
|
|
206
|
+
.order_by(*topic_comment_order_clauses())
|
|
207
|
+
.limit(max(1, min(limit, 500)))
|
|
208
|
+
)
|
|
209
|
+
comments = list(db.scalars(stmt))
|
|
210
|
+
_ensure_comment_seq_values(comments)
|
|
211
|
+
author_names = _agent_names_by_ids(db, {comment.author_agent_id for comment in comments})
|
|
212
|
+
if tree:
|
|
213
|
+
return _build_comment_tree(comments, author_names)
|
|
214
|
+
return [
|
|
215
|
+
TopicCommentRead(
|
|
216
|
+
id=comment.id,
|
|
217
|
+
topic_id=comment.topic_id,
|
|
218
|
+
author_agent_id=comment.author_agent_id,
|
|
219
|
+
author_name=author_names.get(comment.author_agent_id),
|
|
220
|
+
parent_comment_id=comment.parent_comment_id,
|
|
221
|
+
body=comment.body,
|
|
222
|
+
kind=_comment_kind(comment),
|
|
223
|
+
comment_seq=comment.comment_seq,
|
|
224
|
+
created_at=comment.created_at,
|
|
225
|
+
)
|
|
226
|
+
for comment in comments
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Suppress unused-import warning for AgentRole — re-exported for backwards
|
|
231
|
+
# compatibility with callers that imported it from topic_service.
|
|
232
|
+
__all__ = [
|
|
233
|
+
"AgentRole",
|
|
234
|
+
"create_topic_comment",
|
|
235
|
+
"list_topic_comments",
|
|
236
|
+
"topic_comment_read",
|
|
237
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Shared topic lookup helpers.
|
|
2
|
+
|
|
3
|
+
Split out so lifecycle / resolve / action-item modules can share
|
|
4
|
+
``_get_topic`` and ``_agent_names_by_ids`` without circular imports.
|
|
5
|
+
``topic_service`` re-exports both for existing private callers
|
|
6
|
+
(e.g. ``topic_work_item_service``).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
from sqlalchemy import select
|
|
13
|
+
from sqlalchemy.orm import Session
|
|
14
|
+
|
|
15
|
+
from server.domain.models import Agent, Topic
|
|
16
|
+
from server.services.errors import NotFoundError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_topic(db: Session, topic_id: uuid.UUID) -> Topic:
|
|
20
|
+
topic = db.get(Topic, topic_id)
|
|
21
|
+
if topic is None or topic.deleted_at is not None:
|
|
22
|
+
raise NotFoundError("Topic not found")
|
|
23
|
+
return topic
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _agent_names_by_ids(db: Session, agent_ids: set[uuid.UUID]) -> dict[uuid.UUID, str]:
|
|
27
|
+
if not agent_ids:
|
|
28
|
+
return {}
|
|
29
|
+
return {
|
|
30
|
+
agent.id: agent.name
|
|
31
|
+
for agent in db.scalars(select(Agent).where(Agent.id.in_(agent_ids)))
|
|
32
|
+
}
|