bazaar-compute-node 0.1.19__tar.gz → 0.1.20__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.
Files changed (105) hide show
  1. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/PKG-INFO +1 -1
  2. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/pyproject.toml +1 -1
  3. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/pyproject.toml.orig +1 -1
  4. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/command.py +1 -0
  5. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/wrapper.py +16 -15
  6. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/bcc.py +4 -1
  7. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/channel.py +13 -0
  8. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/channel.py +14 -0
  9. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/models/__init__.py +2 -0
  10. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/models/entities.py +13 -0
  11. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/models/states.py +6 -0
  12. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/turn.py +49 -25
  13. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/LICENSE +0 -0
  14. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/README.md +0 -0
  15. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/__init__.py +0 -0
  16. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/__init__.py +0 -0
  17. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/agent.py +0 -0
  18. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/agent_management.py +0 -0
  19. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/application.py +0 -0
  20. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/attachments.py +0 -0
  21. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/config.py +0 -0
  22. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/registry.py +0 -0
  23. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/reminder_dispatch.py +0 -0
  24. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/system_service.py +0 -0
  25. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/transport.py +0 -0
  26. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  27. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/cli.py +0 -0
  28. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  29. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  30. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  31. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  32. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  33. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  34. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  35. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  36. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
  37. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
  38. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/api.py +0 -0
  39. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/approval.py +0 -0
  40. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
  41. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
  42. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
  43. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/outbound.py +0 -0
  44. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
  45. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
  46. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  47. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  48. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  49. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  50. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/agent_migration.py +0 -0
  51. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  52. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  53. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/migrations.py +0 -0
  54. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  55. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  56. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/reminder_database.py +0 -0
  57. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +0 -0
  58. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +0 -0
  59. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/repository.py +0 -0
  60. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/sqlite/scoped_repository.py +0 -0
  61. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  62. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
  63. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
  64. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  65. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  66. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
  67. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
  68. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  69. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  70. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
  71. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
  72. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  73. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  74. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/__init__.py +0 -0
  75. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/approval.py +0 -0
  76. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/audit.py +0 -0
  77. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/channel.py +0 -0
  78. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/client.py +0 -0
  79. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/command.py +0 -0
  80. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/concurrency.py +0 -0
  81. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/correlation.py +0 -0
  82. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/instruction.py +0 -0
  83. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  84. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  85. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  86. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/observability.py +0 -0
  87. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  88. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
  89. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  90. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
  91. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  92. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  93. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  94. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/orchestration/session.py +0 -0
  95. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/outcomes.py +0 -0
  96. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/paths.py +0 -0
  97. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/reminder.py +0 -0
  98. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  99. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/runtime.py +0 -0
  100. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/storage.py +0 -0
  101. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  102. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  103. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  104. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/i18n/english.py +0 -0
  105. {bazaar_compute_node-0.1.19 → bazaar_compute_node-0.1.20}/src/bazaar_compute_node/i18n/schinese.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.1.19
3
+ Version: 0.1.20
4
4
  Summary: Runtime-agnostic computer node daemon for agents and channels.
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.19"
3
+ version = "0.1.20"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.19"
3
+ version = "0.1.20"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -27,6 +27,7 @@ def serialize_inbound(message: InboundMessage) -> dict[str, object]:
27
27
  if message.sender is None
28
28
  else {"id": message.sender.id, "name": message.sender.name}
29
29
  ),
30
+ "sender_kind": message.sender_kind.value,
30
31
  "message_type": message.message_type,
31
32
  "canonical_target": message.canonical_target,
32
33
  "target_kind": message.target_kind.value,
@@ -32,30 +32,31 @@ def install_bcc_wrapper(bin_dir: Path, *, agent_id: str) -> Path:
32
32
  command_path.write_text(
33
33
  "@echo off\n"
34
34
  f'set "BCN_AGENT_ID={agent_id}"\n'
35
- 'set "PYTHONUTF8=1"\n'
36
35
  'set "PYTHONIOENCODING=utf-8"\n'
36
+ 'set "PYTHONUTF8=1"\n'
37
+ 'set "LANG=C.UTF-8"\n'
38
+ 'set "LC_ALL=C.UTF-8"\n'
39
+ "chcp 65001 >NUL 2>NUL\n"
37
40
  f'"{python_executable}" -m bazaar_compute_node.bcc %*\n',
38
41
  encoding="utf-8",
39
42
  )
40
43
  (bin_dir / "bcc.ps1").write_text(
44
+ "$ErrorActionPreference = 'Stop'\n"
41
45
  f"$env:BCN_AGENT_ID = '{agent_id}'\n"
42
- "$env:PYTHONUTF8 = '1'\n"
46
+ "$utf8NoBom = [System.Text.UTF8Encoding]::new($false)\n"
47
+ "[Console]::OutputEncoding = $utf8NoBom\n"
48
+ "$OutputEncoding = $utf8NoBom\n"
43
49
  "$env:PYTHONIOENCODING = 'utf-8'\n"
50
+ "$env:PYTHONUTF8 = '1'\n"
51
+ "$env:LANG = 'C.UTF-8'\n"
52
+ "$env:LC_ALL = 'C.UTF-8'\n"
44
53
  f'$python = "{python_executable}"\n'
45
- "$previousOutputEncoding = $OutputEncoding\n"
46
- "$exitCode = 1\n"
47
- "try {\n"
48
- " $OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n"
49
- " if ($MyInvocation.ExpectingInput) {\n"
50
- " @($Input) | & $python -m bazaar_compute_node.bcc @args\n"
51
- " } else {\n"
52
- " & $python -m bazaar_compute_node.bcc @args\n"
53
- " }\n"
54
- " $exitCode = $LASTEXITCODE\n"
55
- "} finally {\n"
56
- " $OutputEncoding = $previousOutputEncoding\n"
54
+ "if ($MyInvocation.ExpectingInput) {\n"
55
+ " $input | & $python -m bazaar_compute_node.bcc @args\n"
56
+ "} else {\n"
57
+ " & $python -m bazaar_compute_node.bcc @args\n"
57
58
  "}\n"
58
- "exit $exitCode\n",
59
+ "exit $LASTEXITCODE\n",
59
60
  encoding="utf-8",
60
61
  )
61
62
  except BaseException:
@@ -480,6 +480,9 @@ def _message_header_fields(
480
480
  ) -> tuple[str, str, str, str, str | None, str]:
481
481
  target = _require_text(message, "canonical_target")
482
482
  message_id = _require_text(message, "message_id")
483
+ sender_kind = _require_text(message, "sender_kind")
484
+ if sender_kind not in {"human", "agent", "unknown"}:
485
+ _invalid_response("command response contains an invalid sender_kind")
483
486
  sender_value = message.get("sender")
484
487
  sender: str | None = None
485
488
  if sender_value is not None:
@@ -493,7 +496,7 @@ def _message_header_fields(
493
496
  target,
494
497
  message_id,
495
498
  _format_message_timestamp(message),
496
- _require_text(message, "message_type"),
499
+ sender_kind,
497
500
  sender,
498
501
  _require_text(message, "body", allow_empty=True),
499
502
  )
@@ -27,6 +27,7 @@ from ...core.models import (
27
27
  RuntimeEvent,
28
28
  RuntimeEventState,
29
29
  SenderIdentity,
30
+ SenderKind,
30
31
  StreamEvent,
31
32
  )
32
33
  from ...core.outcomes import ProviderCallResult, ProviderCallStatus
@@ -481,6 +482,18 @@ class LarkChannel(IChannel):
481
482
  tenant_key=tenant_key,
482
483
  )
483
484
  metadata = dict(provider_payload_metadata or {})
485
+ sender_type = (
486
+ _provider_text(sender_payload.get("sender_type"))
487
+ if isinstance(sender_payload, Mapping)
488
+ else None
489
+ )
490
+ if sender_type == "user":
491
+ sender_kind = SenderKind.HUMAN
492
+ elif sender_type in {"app", "bot", "system"}:
493
+ sender_kind = SenderKind.AGENT
494
+ else:
495
+ sender_kind = SenderKind.UNKNOWN
496
+ metadata.setdefault("sender_kind", sender_kind.value)
484
497
  if projection.content_error:
485
498
  metadata["content_parse_failed"] = True
486
499
  return InboundMessage(
@@ -24,6 +24,7 @@ from ...core.models import (
24
24
  RuntimeEvent,
25
25
  RuntimeEventState,
26
26
  SenderIdentity,
27
+ SenderKind,
27
28
  )
28
29
  from ...core.outcomes import ProviderCallResult, ProviderCallStatus
29
30
  from ...core.runtime import RuntimeStreamItem
@@ -613,6 +614,7 @@ class TelegramChannel(IChannel):
613
614
  "telegram_chat_id": chat_id,
614
615
  "telegram_message_thread_id": topic_id,
615
616
  "telegram_chat_type": chat_type,
617
+ "sender_kind": self._sender_kind(message).value,
616
618
  "historical": historical,
617
619
  "activation_reason": activation_reason,
618
620
  "rich_message": content.rich_message,
@@ -702,6 +704,7 @@ class TelegramChannel(IChannel):
702
704
  "telegram_chat_id": identity.chat_id,
703
705
  "telegram_message_thread_id": identity.topic_id,
704
706
  "telegram_chat_type": chat_type,
707
+ "sender_kind": self._sender_kind(reply).value,
705
708
  "historical": historical,
706
709
  "activation_reason": "none",
707
710
  "quoted_backfill": True,
@@ -797,6 +800,17 @@ class TelegramChannel(IChannel):
797
800
  )
798
801
  return None
799
802
 
803
+ @staticmethod
804
+ def _sender_kind(message: Mapping[str, object]) -> SenderKind:
805
+ provider_sender = message.get("from")
806
+ if isinstance(provider_sender, Mapping):
807
+ is_bot = provider_sender.get("is_bot")
808
+ if is_bot is True:
809
+ return SenderKind.AGENT
810
+ if is_bot is False:
811
+ return SenderKind.HUMAN
812
+ return SenderKind.UNKNOWN
813
+
800
814
  @staticmethod
801
815
  def _text_projection(
802
816
  message: Mapping[str, object],
@@ -28,6 +28,7 @@ from .states import (
28
28
  ReminderState,
29
29
  RuntimeEventState,
30
30
  RuntimeTurnState,
31
+ SenderKind,
31
32
  SessionRuntimeObservation,
32
33
  SessionRuntimeObservationSource,
33
34
  SessionRuntimeSignal,
@@ -67,6 +68,7 @@ __all__ = [
67
68
  "RuntimeTurn",
68
69
  "RuntimeTurnState",
69
70
  "SenderIdentity",
71
+ "SenderKind",
70
72
  "SessionRuntimeObservation",
71
73
  "SessionRuntimeObservationSource",
72
74
  "SessionRuntimeSignal",
@@ -16,6 +16,7 @@ from .states import (
16
16
  OutboundDeliveryState,
17
17
  RuntimeEventState,
18
18
  RuntimeTurnState,
19
+ SenderKind,
19
20
  StreamEventKind,
20
21
  ensure_transition,
21
22
  )
@@ -306,6 +307,18 @@ class InboundMessage:
306
307
  if self.provider_time_ms is not None:
307
308
  _validate_non_negative(self.provider_time_ms, "provider_time_ms")
308
309
 
310
+ @property
311
+ def sender_kind(self) -> SenderKind:
312
+ value = self.metadata.get("sender_kind", SenderKind.UNKNOWN.value)
313
+ if not isinstance(value, str):
314
+ raise TypeError("metadata sender_kind must be a string")
315
+ try:
316
+ return SenderKind(value)
317
+ except ValueError as error:
318
+ raise ValueError(
319
+ "metadata sender_kind must be human, agent, or unknown"
320
+ ) from error
321
+
309
322
 
310
323
  @dataclass(frozen=True, slots=True)
311
324
  class OutboundMessage:
@@ -276,6 +276,12 @@ class ApprovalDecision(StrEnum):
276
276
  REJECTED = "rejected"
277
277
 
278
278
 
279
+ class SenderKind(StrEnum):
280
+ HUMAN = "human"
281
+ AGENT = "agent"
282
+ UNKNOWN = "unknown"
283
+
284
+
279
285
  class ReminderState(StrEnum):
280
286
  SCHEDULED = "scheduled"
281
287
  FIRED = "fired"
@@ -12,6 +12,7 @@ from ..concurrency import ISessionConcurrency
12
12
  from ..correlation import CorrelationContext
13
13
  from ..lifecycle import TimeoutBudget
14
14
  from ..models import (
15
+ ApprovalDecision,
15
16
  ApprovalRequest,
16
17
  ApprovalResult,
17
18
  BcnSession,
@@ -22,6 +23,7 @@ from ..models import (
22
23
  RuntimeSession,
23
24
  RuntimeTurn,
24
25
  RuntimeTurnState,
26
+ SenderKind,
25
27
  SessionRuntimeObservation,
26
28
  SessionRuntimeObservationSource,
27
29
  SessionRuntimeSignal,
@@ -179,38 +181,58 @@ class SessionTurnCoordinator:
179
181
  request_id=request_id,
180
182
  inbound_seq=message.seq,
181
183
  )
184
+ sender_kind = message.sender_kind
182
185
  await self._audit.append(
183
186
  event_name="approval.requested",
184
187
  state=RuntimeEventState.STARTED,
185
188
  correlation=approval_correlation,
186
- metadata={"action": request.action},
189
+ metadata={
190
+ "action": request.action,
191
+ "sender_kind": sender_kind.value,
192
+ },
187
193
  )
188
- try:
189
- channel_request = ChannelApprovalRequest(
190
- approval=request,
191
- target_kind=context.channel_session.target_kind,
192
- provider_thread_id=context.channel_session.provider_thread_id,
193
- provider_reply_to_message_id=message.provider_message_id,
194
- provider_sender_id=(
195
- message.sender.id if message.sender is not None else None
196
- ),
194
+ if sender_kind is not SenderKind.HUMAN:
195
+ rejection_reason = (
196
+ "agent_cannot_approve"
197
+ if sender_kind is SenderKind.AGENT
198
+ else "unknown_sender_cannot_approve"
197
199
  )
198
- result = await self._channel.request_approval(
199
- channel_request,
200
- timeout=timeout,
200
+ result = ApprovalResult(
201
+ request_id=request_id,
202
+ decision=ApprovalDecision.REJECTED,
203
+ decided_at_ms=self._clock(),
204
+ reason=rejection_reason,
201
205
  )
202
- if result.request_id != request_id:
203
- raise ValueError("channel approval result correlation mismatch")
204
- except Exception as error:
205
- await self._audit.append(
206
- event_name="approval.failed",
207
- state=RuntimeEventState.FAILED,
208
- correlation=approval_correlation,
209
- error_kind=ErrorKind.PROVIDER_FAILED,
210
- error_message=f"approval failed: {type(error).__name__}",
211
- metadata={"action": request.action},
212
- )
213
- raise
206
+ else:
207
+ try:
208
+ channel_request = ChannelApprovalRequest(
209
+ approval=request,
210
+ target_kind=context.channel_session.target_kind,
211
+ provider_thread_id=context.channel_session.provider_thread_id,
212
+ provider_reply_to_message_id=message.provider_message_id,
213
+ provider_sender_id=(
214
+ message.sender.id if message.sender is not None else None
215
+ ),
216
+ )
217
+ result = await self._channel.request_approval(
218
+ channel_request,
219
+ timeout=timeout,
220
+ )
221
+ if result.request_id != request_id:
222
+ raise ValueError("channel approval result correlation mismatch")
223
+ except Exception as error:
224
+ await self._audit.append(
225
+ event_name="approval.failed",
226
+ state=RuntimeEventState.FAILED,
227
+ correlation=approval_correlation,
228
+ error_kind=ErrorKind.PROVIDER_FAILED,
229
+ error_message=f"approval failed: {type(error).__name__}",
230
+ metadata={
231
+ "action": request.action,
232
+ "sender_kind": sender_kind.value,
233
+ },
234
+ )
235
+ raise
214
236
  await self._audit.append(
215
237
  event_name="approval.decided",
216
238
  state=RuntimeEventState.COMPLETED,
@@ -218,6 +240,8 @@ class SessionTurnCoordinator:
218
240
  metadata={
219
241
  "action": request.action,
220
242
  "decision": result.decision.value,
243
+ "sender_kind": sender_kind.value,
244
+ **({"reason": result.reason} if result.reason is not None else {}),
221
245
  },
222
246
  )
223
247
  return result