bazaar-compute-node 0.1.23__tar.gz → 0.1.25__tar.gz
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.
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/PKG-INFO +1 -1
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/pyproject.toml +1 -1
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/pyproject.toml.orig +1 -1
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/command.py +228 -39
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/system_service.py +42 -8
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/bcc.py +34 -152
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/outbound.py +5 -1
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/codec.py +26 -114
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/migrations.py +2 -0
- bazaar_compute_node-0.1.25/src/bazaar_compute_node/contrib/sqlite/outbound_draft_migration.py +112 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/repository.py +59 -20
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/scoped_repository.py +32 -4
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/command.py +43 -2
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/instruction.py +5 -1
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/__init__.py +0 -2
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/entities.py +7 -58
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/states.py +0 -20
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/command.py +151 -208
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/storage.py +12 -4
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/LICENSE +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/README.md +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/agent.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/agent_management.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/application.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/attachments.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/config.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/registry.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/resource_dispatch.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/transport.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/wrapper.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/cli.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/api.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/approval.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/channel.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/agent_migration.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/handoff_codec.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/handoff_migration.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/handoff_repository.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/inbox_migration.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/migration.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/sqlite/storage.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/channel.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/approval.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/audit.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/channel.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/client.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/concurrency.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/correlation.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/handoff.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/inbox.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/lifecycle.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/handoff.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/reminder.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/observability.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/handoff_command.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/session.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/orchestration/turn.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/outcomes.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/paths.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/reminder.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/runtime.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/core/timerwheel.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/i18n/__init__.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/i18n/catalog.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/i18n/english.py +0 -0
- {bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/i18n/schinese.py +0 -0
{bazaar_compute_node-0.1.23 → bazaar_compute_node-0.1.25}/src/bazaar_compute_node/app/command.py
RENAMED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
from collections.abc import Awaitable, Callable, Mapping
|
|
5
6
|
from datetime import UTC, datetime
|
|
6
7
|
from time import time_ns
|
|
7
8
|
from typing import Annotated, Literal, cast
|
|
8
9
|
|
|
9
|
-
from pydantic import
|
|
10
|
+
from pydantic import (
|
|
11
|
+
BaseModel,
|
|
12
|
+
ConfigDict,
|
|
13
|
+
Field,
|
|
14
|
+
StrictBool,
|
|
15
|
+
StrictInt,
|
|
16
|
+
StrictStr,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
10
19
|
|
|
11
20
|
from ..core.command import (
|
|
12
21
|
ICommandService,
|
|
13
22
|
InboxListResult,
|
|
23
|
+
MessageSendFreshnessHold,
|
|
14
24
|
MessageSendHandoffRequired,
|
|
15
25
|
SessionNotFoundError,
|
|
16
26
|
)
|
|
17
27
|
from ..core.lifecycle import TimeoutBudget
|
|
18
|
-
from ..core.models import InboundMessage, InboxTargetSummary,
|
|
28
|
+
from ..core.models import InboundMessage, InboxTargetSummary, OutboundDeliveryState
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
def serialize_inbound(message: InboundMessage) -> dict[str, object]:
|
|
@@ -56,39 +66,6 @@ def serialize_inbound(message: InboundMessage) -> dict[str, object]:
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
|
|
59
|
-
def serialize_outbound(message: OutboundMessage) -> dict[str, object]:
|
|
60
|
-
return {
|
|
61
|
-
"outbound_message_id": message.outbound_message_id,
|
|
62
|
-
"command_id": message.command_id,
|
|
63
|
-
"session_id": message.session_id,
|
|
64
|
-
"channel_session_id": message.channel_session_id,
|
|
65
|
-
"target": message.target,
|
|
66
|
-
"reply_to_message_id": message.reply_to_message_id,
|
|
67
|
-
"body": message.body,
|
|
68
|
-
"attachments": [
|
|
69
|
-
{
|
|
70
|
-
"name": attachment.name,
|
|
71
|
-
"relative_path": attachment.relative_path,
|
|
72
|
-
"media_type": attachment.media_type,
|
|
73
|
-
"size_bytes": attachment.size_bytes,
|
|
74
|
-
"sha256": attachment.sha256,
|
|
75
|
-
}
|
|
76
|
-
for attachment in message.attachments
|
|
77
|
-
],
|
|
78
|
-
"state": message.state.value,
|
|
79
|
-
"fresh_check_state": message.fresh_check_state.value,
|
|
80
|
-
"created_at_ms": message.created_at_ms,
|
|
81
|
-
"snapshot_seq": message.snapshot_seq,
|
|
82
|
-
"current_inbound_seq": message.current_inbound_seq,
|
|
83
|
-
"provider_attempted_at_ms": message.provider_attempted_at_ms,
|
|
84
|
-
"completed_at_ms": message.completed_at_ms,
|
|
85
|
-
"draft_saved_at_ms": message.draft_saved_at_ms,
|
|
86
|
-
"error_kind": message.error_kind,
|
|
87
|
-
"error_message": message.error_message,
|
|
88
|
-
"next_action": message.next_action,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
69
|
def serialize_inbox_target(summary: InboxTargetSummary) -> dict[str, object]:
|
|
93
70
|
sender = summary.latest_sender
|
|
94
71
|
latest_time_ms = (
|
|
@@ -172,6 +149,7 @@ class _MessageSendRequest(_CommandRequest):
|
|
|
172
149
|
command_id: NonEmptyText
|
|
173
150
|
attachment_paths: list[NonEmptyText] = Field(default_factory=list)
|
|
174
151
|
reply_to_message_id: NonEmptyText | None = None
|
|
152
|
+
send_draft: StrictBool = False
|
|
175
153
|
created_at_ms: NonNegativeInt = Field(
|
|
176
154
|
default_factory=lambda: time_ns() // 1_000_000
|
|
177
155
|
)
|
|
@@ -480,17 +458,57 @@ class CommandDispatcher:
|
|
|
480
458
|
created_at_ms=request.created_at_ms,
|
|
481
459
|
attachment_paths=tuple(request.attachment_paths),
|
|
482
460
|
reply_to_message_id=request.reply_to_message_id,
|
|
461
|
+
send_draft=request.send_draft,
|
|
483
462
|
)
|
|
463
|
+
if isinstance(result, MessageSendFreshnessHold):
|
|
464
|
+
return {
|
|
465
|
+
"ok": True,
|
|
466
|
+
"result": {"text": format_freshness_hold(result)},
|
|
467
|
+
}
|
|
484
468
|
if isinstance(result, MessageSendHandoffRequired):
|
|
485
469
|
return {
|
|
486
470
|
"ok": True,
|
|
487
|
-
"result": {
|
|
488
|
-
"handoff_required": {"target": result.target},
|
|
489
|
-
},
|
|
471
|
+
"result": {"text": format_cross_session_hold(result.target)},
|
|
490
472
|
}
|
|
473
|
+
if result.state is OutboundDeliveryState.SENT:
|
|
474
|
+
text = (
|
|
475
|
+
f"Message sent to {result.target}. "
|
|
476
|
+
f"Message ID: {result.outbound_message_id}"
|
|
477
|
+
)
|
|
478
|
+
elif result.state is OutboundDeliveryState.QUEUED:
|
|
479
|
+
text = (
|
|
480
|
+
f"Message queued to {result.target}. "
|
|
481
|
+
f"Message ID: {result.outbound_message_id}"
|
|
482
|
+
)
|
|
483
|
+
elif result.state is OutboundDeliveryState.PARTIAL:
|
|
484
|
+
raise CommandDispatchError(
|
|
485
|
+
"SEND_PARTIAL",
|
|
486
|
+
result.error_message
|
|
487
|
+
or "Message delivery was only partially confirmed.",
|
|
488
|
+
next_action=(
|
|
489
|
+
"Do not retry the complete message automatically; reconcile "
|
|
490
|
+
"confirmed delivery first."
|
|
491
|
+
),
|
|
492
|
+
)
|
|
493
|
+
elif result.state is OutboundDeliveryState.UNKNOWN:
|
|
494
|
+
raise CommandDispatchError(
|
|
495
|
+
"SEND_UNKNOWN",
|
|
496
|
+
result.error_message or "Message delivery outcome is unknown.",
|
|
497
|
+
next_action="Reconcile channel delivery before retrying.",
|
|
498
|
+
)
|
|
499
|
+
elif result.state is OutboundDeliveryState.FAILED:
|
|
500
|
+
raise CommandDispatchError(
|
|
501
|
+
"SEND_FAILED",
|
|
502
|
+
result.error_message or "Message delivery failed.",
|
|
503
|
+
next_action="Fix the provider error before retrying.",
|
|
504
|
+
)
|
|
505
|
+
else:
|
|
506
|
+
raise AssertionError(
|
|
507
|
+
f"message send returned unsupported state: {result.state.value}"
|
|
508
|
+
)
|
|
491
509
|
return {
|
|
492
510
|
"ok": True,
|
|
493
|
-
"result": {"
|
|
511
|
+
"result": {"text": text},
|
|
494
512
|
}
|
|
495
513
|
|
|
496
514
|
if resource == "thread" and command == "unfollow":
|
|
@@ -507,3 +525,174 @@ def format_message_time(timestamp_ms: int) -> str:
|
|
|
507
525
|
.astimezone()
|
|
508
526
|
.strftime("%Y-%m-%d %H:%M:%S")
|
|
509
527
|
)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _message_timestamp(message: Mapping[str, object]) -> int:
|
|
531
|
+
timestamp = message["provider_time_ms"]
|
|
532
|
+
if timestamp is None:
|
|
533
|
+
timestamp = message["received_at_ms"]
|
|
534
|
+
return cast(int, timestamp)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _message_header_fields(
|
|
538
|
+
message: Mapping[str, object],
|
|
539
|
+
) -> tuple[str, str, str, str, str | None, str]:
|
|
540
|
+
target = cast(str, message["canonical_target"])
|
|
541
|
+
message_id = cast(str, message["message_id"])
|
|
542
|
+
sender_kind = cast(str, message["sender_kind"])
|
|
543
|
+
sender_value = message["sender"]
|
|
544
|
+
sender: str | None = None
|
|
545
|
+
if sender_value is not None:
|
|
546
|
+
sender_mapping = cast(Mapping[str, object], sender_value)
|
|
547
|
+
sender_id = cast(str | None, sender_mapping.get("id"))
|
|
548
|
+
sender_name = cast(str | None, sender_mapping.get("name"))
|
|
549
|
+
sender = f"@{sender_id}({sender_name})" if sender_name else f"@{sender_id}"
|
|
550
|
+
return (
|
|
551
|
+
target,
|
|
552
|
+
message_id,
|
|
553
|
+
format_message_time(_message_timestamp(message)),
|
|
554
|
+
sender_kind,
|
|
555
|
+
sender,
|
|
556
|
+
cast(str, message["body"]),
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _attachment_suffix(message: Mapping[str, object]) -> str:
|
|
561
|
+
attachments = cast(list[Mapping[str, object]], message["attachments"])
|
|
562
|
+
if not attachments:
|
|
563
|
+
return ""
|
|
564
|
+
rendered: list[str] = []
|
|
565
|
+
for attachment in attachments:
|
|
566
|
+
name = cast(str, attachment["name"])
|
|
567
|
+
attachment_id = cast(str, attachment["attachment_id"])
|
|
568
|
+
state = cast(str, attachment["state"])
|
|
569
|
+
if state == "ready":
|
|
570
|
+
path = cast(str, attachment["relative_path"])
|
|
571
|
+
rendered.append(f"{name} (id:{attachment_id}, path:{path})")
|
|
572
|
+
else:
|
|
573
|
+
error = cast(str, attachment["error"])
|
|
574
|
+
rendered.append(f"{name} (id:{attachment_id}, state:failed, error:{error})")
|
|
575
|
+
label = "attachment" if len(rendered) == 1 else "attachments"
|
|
576
|
+
return f" [{len(rendered)} {label}: {', '.join(rendered)}]"
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def format_check_message(message: Mapping[str, object]) -> str:
|
|
580
|
+
target, message_id, timestamp, message_type, sender, body = _message_header_fields(
|
|
581
|
+
message
|
|
582
|
+
)
|
|
583
|
+
line = (
|
|
584
|
+
f"[target={target} msg={message_id} time={timestamp} "
|
|
585
|
+
f"type={message_type} mentioned={str(message['mentions_agent']).lower()}"
|
|
586
|
+
)
|
|
587
|
+
reply_to_message_id = message["reply_to_message_id"]
|
|
588
|
+
if reply_to_message_id is not None:
|
|
589
|
+
line += f" reply_to={cast(str, reply_to_message_id)}"
|
|
590
|
+
line += "] "
|
|
591
|
+
if sender is not None:
|
|
592
|
+
line += f"{sender} "
|
|
593
|
+
return line + body + _attachment_suffix(message)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def format_read_message(
|
|
597
|
+
message: Mapping[str, object],
|
|
598
|
+
*,
|
|
599
|
+
index: int,
|
|
600
|
+
count: int,
|
|
601
|
+
) -> str:
|
|
602
|
+
target, message_id, timestamp, message_type, sender, body = _message_header_fields(
|
|
603
|
+
message
|
|
604
|
+
)
|
|
605
|
+
fields = [
|
|
606
|
+
f"seq={cast(int, message['seq'])}",
|
|
607
|
+
f"msg={message_id}",
|
|
608
|
+
f"time={timestamp}",
|
|
609
|
+
f"type={message_type}",
|
|
610
|
+
f"replyTarget={target}",
|
|
611
|
+
f"mentioned={str(message['mentions_agent']).lower()}",
|
|
612
|
+
]
|
|
613
|
+
reply_to_message_id = message["reply_to_message_id"]
|
|
614
|
+
if reply_to_message_id is not None:
|
|
615
|
+
fields.append(f"replyTo={cast(str, reply_to_message_id)}")
|
|
616
|
+
line = f"[{index}/{count} {' '.join(fields)}] "
|
|
617
|
+
if sender is not None:
|
|
618
|
+
line += f"{sender} "
|
|
619
|
+
return line + body + _attachment_suffix(message)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def format_freshness_hold(result: MessageSendFreshnessHold) -> str:
|
|
623
|
+
messages = [serialize_inbound(message) for message in result.messages]
|
|
624
|
+
referenced_messages = [
|
|
625
|
+
serialize_inbound(message) for message in result.referenced_messages
|
|
626
|
+
]
|
|
627
|
+
shown = len(messages)
|
|
628
|
+
total = result.newer_message_total
|
|
629
|
+
message_label = "message" if total == 1 else "messages"
|
|
630
|
+
if messages:
|
|
631
|
+
bounds = f"{messages[0]['seq']}-{messages[-1]['seq']}"
|
|
632
|
+
else:
|
|
633
|
+
bounds = "none-none"
|
|
634
|
+
older_bound = (
|
|
635
|
+
"Older unreviewed messages are omitted."
|
|
636
|
+
if total > shown
|
|
637
|
+
else "No older unreviewed messages."
|
|
638
|
+
)
|
|
639
|
+
newer_bound = "No newer unreviewed messages."
|
|
640
|
+
lines = [
|
|
641
|
+
f"Unreviewed synced context for this target: {total} {message_label}.",
|
|
642
|
+
(
|
|
643
|
+
"Your message has been saved as a draft. Review this target's "
|
|
644
|
+
"synced context before sending."
|
|
645
|
+
),
|
|
646
|
+
"",
|
|
647
|
+
(
|
|
648
|
+
f"Read window: {shown} returned, seq {bounds}, oldest to newest. "
|
|
649
|
+
f"{older_bound} {newer_bound}"
|
|
650
|
+
),
|
|
651
|
+
"",
|
|
652
|
+
]
|
|
653
|
+
if referenced_messages:
|
|
654
|
+
lines.append(f"Referenced messages: {len(referenced_messages)}")
|
|
655
|
+
lines.extend(
|
|
656
|
+
format_read_message(message, index=index, count=len(referenced_messages))
|
|
657
|
+
for index, message in enumerate(referenced_messages, start=1)
|
|
658
|
+
)
|
|
659
|
+
lines.append("Window messages:")
|
|
660
|
+
lines.extend(
|
|
661
|
+
format_read_message(message, index=index, count=shown)
|
|
662
|
+
for index, message in enumerate(messages, start=1)
|
|
663
|
+
)
|
|
664
|
+
lines.extend(
|
|
665
|
+
(
|
|
666
|
+
"",
|
|
667
|
+
f"End of window: {shown}/{total} shown.",
|
|
668
|
+
"",
|
|
669
|
+
"To update the draft, send revised content normally:",
|
|
670
|
+
f" bcc message send --target {json.dumps(result.target)} <<'BCCMSG'",
|
|
671
|
+
" revised message",
|
|
672
|
+
" BCCMSG",
|
|
673
|
+
"To send the current draft unchanged:",
|
|
674
|
+
f" bcc message send --send-draft --target {json.dumps(result.target)}",
|
|
675
|
+
"You can also choose not to send anything.",
|
|
676
|
+
)
|
|
677
|
+
)
|
|
678
|
+
return "\n".join(lines)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def format_cross_session_hold(target: str) -> str:
|
|
682
|
+
quoted_target = json.dumps(target)
|
|
683
|
+
return "\n".join(
|
|
684
|
+
(
|
|
685
|
+
"Your message was not sent because the target belongs to another conversation.",
|
|
686
|
+
"",
|
|
687
|
+
(
|
|
688
|
+
"To continue this work in the target conversation, send a "
|
|
689
|
+
"self-contained handoff:"
|
|
690
|
+
),
|
|
691
|
+
f" bcc handoff send --target {quoted_target} <<'BCCMSG'",
|
|
692
|
+
" enough context to understand the background, goal, and next action",
|
|
693
|
+
" BCCMSG",
|
|
694
|
+
"This creates a handoff notice that wakes you in that conversation.",
|
|
695
|
+
"",
|
|
696
|
+
"You can also choose not to send anything.",
|
|
697
|
+
)
|
|
698
|
+
)
|
|
@@ -271,6 +271,29 @@ exit $exitCode
|
|
|
271
271
|
"""
|
|
272
272
|
|
|
273
273
|
|
|
274
|
+
def _vbs_literal(value: Path | str) -> str:
|
|
275
|
+
return '"' + str(value).replace('"', '""') + '"'
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _render_windows_launcher(wrapper_path: Path) -> str:
|
|
279
|
+
command = _vbs_literal(
|
|
280
|
+
"powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden "
|
|
281
|
+
f'-ExecutionPolicy Bypass -File "{wrapper_path}"'
|
|
282
|
+
)
|
|
283
|
+
return f"""' {MANAGED_MARKER}
|
|
284
|
+
Option Explicit
|
|
285
|
+
|
|
286
|
+
Dim shell
|
|
287
|
+
Dim command
|
|
288
|
+
Dim exitCode
|
|
289
|
+
|
|
290
|
+
Set shell = CreateObject("WScript.Shell")
|
|
291
|
+
command = {command}
|
|
292
|
+
exitCode = shell.Run(command, 0, True)
|
|
293
|
+
WScript.Quit exitCode
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
|
|
274
297
|
def _add_task_element(
|
|
275
298
|
parent: ElementTree.Element,
|
|
276
299
|
name: str,
|
|
@@ -284,7 +307,7 @@ def _add_task_element(
|
|
|
284
307
|
|
|
285
308
|
def _render_windows_task(
|
|
286
309
|
context: SystemServiceContext,
|
|
287
|
-
|
|
310
|
+
launcher_path: Path,
|
|
288
311
|
) -> bytes:
|
|
289
312
|
ElementTree.register_namespace("", TASK_NAMESPACE)
|
|
290
313
|
task = ElementTree.Element(
|
|
@@ -323,11 +346,11 @@ def _render_windows_task(
|
|
|
323
346
|
{"Context": "Author"},
|
|
324
347
|
)
|
|
325
348
|
action = _add_task_element(actions, "Exec")
|
|
326
|
-
_add_task_element(action, "Command", "
|
|
349
|
+
_add_task_element(action, "Command", "wscript.exe")
|
|
327
350
|
_add_task_element(
|
|
328
351
|
action,
|
|
329
352
|
"Arguments",
|
|
330
|
-
f'
|
|
353
|
+
f'//B //Nologo "{launcher_path}"',
|
|
331
354
|
)
|
|
332
355
|
_add_task_element(action, "WorkingDirectory", str(context.data_dir))
|
|
333
356
|
return ElementTree.tostring(task, encoding="utf-16", xml_declaration=True)
|
|
@@ -423,10 +446,11 @@ def _launchd_paths() -> tuple[Path, Path]:
|
|
|
423
446
|
)
|
|
424
447
|
|
|
425
448
|
|
|
426
|
-
def _windows_paths(context: SystemServiceContext) -> tuple[Path, Path]:
|
|
449
|
+
def _windows_paths(context: SystemServiceContext) -> tuple[Path, Path, Path]:
|
|
427
450
|
return (
|
|
428
451
|
context.data_dir / "bcn-system-service.xml",
|
|
429
452
|
context.data_dir / "bcn-system-service.ps1",
|
|
453
|
+
context.data_dir / "bcn-system-service.vbs",
|
|
430
454
|
)
|
|
431
455
|
|
|
432
456
|
|
|
@@ -462,16 +486,21 @@ def _install_macos(context: SystemServiceContext) -> None:
|
|
|
462
486
|
|
|
463
487
|
|
|
464
488
|
def _install_windows(context: SystemServiceContext) -> None:
|
|
465
|
-
xml_path, wrapper_path = _windows_paths(context)
|
|
489
|
+
xml_path, wrapper_path, launcher_path = _windows_paths(context)
|
|
466
490
|
context.data_dir.mkdir(parents=True, exist_ok=True)
|
|
467
491
|
_write_managed_file(
|
|
468
492
|
wrapper_path,
|
|
469
493
|
_render_windows_wrapper(context),
|
|
470
494
|
mode=0o600,
|
|
471
495
|
)
|
|
496
|
+
_write_managed_file(
|
|
497
|
+
launcher_path,
|
|
498
|
+
_render_windows_launcher(wrapper_path),
|
|
499
|
+
mode=0o600,
|
|
500
|
+
)
|
|
472
501
|
_write_managed_file(
|
|
473
502
|
xml_path,
|
|
474
|
-
_render_windows_task(context,
|
|
503
|
+
_render_windows_task(context, launcher_path),
|
|
475
504
|
mode=0o600,
|
|
476
505
|
)
|
|
477
506
|
_run_native_command(
|
|
@@ -686,15 +715,20 @@ def _uninstall_macos() -> None:
|
|
|
686
715
|
|
|
687
716
|
|
|
688
717
|
def _uninstall_windows(context: SystemServiceContext) -> None:
|
|
689
|
-
xml_path, wrapper_path = _windows_paths(context)
|
|
718
|
+
xml_path, wrapper_path, launcher_path = _windows_paths(context)
|
|
690
719
|
_stop_windows(context)
|
|
691
720
|
_run_native_command(
|
|
692
721
|
["schtasks", "/Delete", "/TN", WINDOWS_TASK_NAME, "/F"],
|
|
693
722
|
check=False,
|
|
694
723
|
)
|
|
695
724
|
_remove_managed_file(xml_path)
|
|
725
|
+
_remove_managed_file(launcher_path)
|
|
696
726
|
_remove_managed_file(wrapper_path)
|
|
697
|
-
if
|
|
727
|
+
if (
|
|
728
|
+
not xml_path.exists()
|
|
729
|
+
and not launcher_path.exists()
|
|
730
|
+
and not wrapper_path.exists()
|
|
731
|
+
):
|
|
698
732
|
print("system service uninstalled platform=windows", flush=True)
|
|
699
733
|
|
|
700
734
|
|