dispatch_agents 0.12.2__tar.gz → 0.13.1__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 (148) hide show
  1. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/PKG-INFO +1 -1
  2. dispatch_agents-0.13.1/RELEASE_NOTES.md +2 -0
  3. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/__init__.py +3 -0
  4. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/config.py +161 -0
  5. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/integrations/github/__init__.py +31 -13
  6. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/uv.lock +50 -49
  7. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/pyproject.toml +2 -1
  8. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_config.py +240 -0
  9. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_github_integration.py +384 -429
  10. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_github_schema_compliance.py +93 -0
  11. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/uv.lock +367 -328
  12. dispatch_agents-0.12.2/RELEASE_NOTES.md +0 -1
  13. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.claude-plugin/marketplace.json +0 -0
  14. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/scripts/change_scope.py +0 -0
  15. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/scripts/ci_git.py +0 -0
  16. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/scripts/version_policy.py +0 -0
  17. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/tests/test_change_scope.py +0 -0
  18. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/tests/test_ci_git.py +0 -0
  19. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/tests/test_version_policy.py +0 -0
  20. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/workflows/ci-reusable.yml +0 -0
  21. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/workflows/feature-branch.yml +0 -0
  22. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/workflows/release.yml +0 -0
  23. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.github/workflows/version-policy-reusable.yml +0 -0
  24. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/.gitignore +0 -0
  25. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/CONTRIBUTING.md +0 -0
  26. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/LICENSE +0 -0
  27. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/LICENSE-3rdparty.csv +0 -0
  28. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/NOTICE +0 -0
  29. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/README.md +0 -0
  30. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/__init__.py +0 -0
  31. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/py.typed +0 -0
  32. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/__init__.py +0 -0
  33. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/message_pb2.py +0 -0
  34. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/message_pb2.pyi +0 -0
  35. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/message_pb2_grpc.py +0 -0
  36. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/request_response_pb2.py +0 -0
  37. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/request_response_pb2.pyi +0 -0
  38. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/request_response_pb2_grpc.py +0 -0
  39. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/service_pb2.py +0 -0
  40. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/service_pb2.pyi +0 -0
  41. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/agentservice/v1/service_pb2_grpc.py +0 -0
  42. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/agent_service.py +0 -0
  43. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/contrib/__init__.py +0 -0
  44. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/contrib/claude/__init__.py +0 -0
  45. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/contrib/openai/__init__.py +0 -0
  46. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/events.py +0 -0
  47. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/grpc_server.py +0 -0
  48. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/instrument.py +0 -0
  49. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/integrations/__init__.py +0 -0
  50. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/integrations/github/README.md +0 -0
  51. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/integrations/github/client.py +0 -0
  52. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/invocation.py +0 -0
  53. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/llm.py +0 -0
  54. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/llm_langchain.py +0 -0
  55. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/logging_config.py +0 -0
  56. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/mcp.py +0 -0
  57. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/memory.py +0 -0
  58. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/models.py +0 -0
  59. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/proxy/__init__.py +0 -0
  60. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/proxy/server.py +0 -0
  61. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/proxy/sse_utils.py +0 -0
  62. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/py.typed +0 -0
  63. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/resources.py +0 -0
  64. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/dispatch_agents/version.py +0 -0
  65. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/README.md +0 -0
  66. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/.gitignore +0 -0
  67. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/AGENTS.md +0 -0
  68. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/README.md +0 -0
  69. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/agent.py +0 -0
  70. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/dispatch.yaml +0 -0
  71. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/pyproject.toml +0 -0
  72. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/company-researcher/uv.lock +0 -0
  73. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/conversational-agent/README.md +0 -0
  74. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/conversational-agent/agent.py +0 -0
  75. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/conversational-agent/dispatch.yaml +0 -0
  76. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/conversational-agent/pyproject.toml +0 -0
  77. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/daily-digest/README.md +0 -0
  78. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/daily-digest/agent.py +0 -0
  79. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/daily-digest/dispatch.yaml +0 -0
  80. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/daily-digest/pyproject.toml +0 -0
  81. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/README.md +0 -0
  82. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/agent.py +0 -0
  83. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/configuration.py +0 -0
  84. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/deep_researcher.py +0 -0
  85. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/dispatch.yaml +0 -0
  86. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/prompts.py +0 -0
  87. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/pyproject.toml +0 -0
  88. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/state.py +0 -0
  89. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/deep-research/tools.py +0 -0
  90. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/.gitignore +0 -0
  91. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/AGENTS.md +0 -0
  92. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/agent.py +0 -0
  93. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/dispatch.yaml +0 -0
  94. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/pyproject.toml +0 -0
  95. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/hello_world/test_agent.py +0 -0
  96. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/.env.example +0 -0
  97. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/.gitignore +0 -0
  98. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/AGENTS.md +0 -0
  99. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/agent.py +0 -0
  100. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/dispatch.yaml +0 -0
  101. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/knowledge-base-query/pyproject.toml +0 -0
  102. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/multi-framework/README.md +0 -0
  103. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/multi-framework/agent.py +0 -0
  104. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/multi-framework/dispatch.yaml +0 -0
  105. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/multi-framework/pyproject.toml +0 -0
  106. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/pyproject.toml +0 -0
  107. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/uv.lock +0 -0
  108. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/.gitignore +0 -0
  109. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/AGENTS.md +0 -0
  110. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/agent.py +0 -0
  111. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/dispatch.yaml +0 -0
  112. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/pyproject.toml +0 -0
  113. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-assistant/uv.lock +0 -0
  114. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/.gitignore +0 -0
  115. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/AGENTS.md +0 -0
  116. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/agent.py +0 -0
  117. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/dispatch.yaml +0 -0
  118. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/pyproject.toml +0 -0
  119. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/examples/weather-service/uv.lock +0 -0
  120. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/internal/py.typed +0 -0
  121. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/plugins/README.md +0 -0
  122. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/__init__.py +0 -0
  123. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/e2e_claude_mcp_proxy.py +0 -0
  124. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/schemas/README.md +0 -0
  125. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/schemas/octokit-webhooks.json +0 -0
  126. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test.py +0 -0
  127. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_agent_service.py +0 -0
  128. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_agent_uid.py +0 -0
  129. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_contrib_claude.py +0 -0
  130. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_contrib_openai.py +0 -0
  131. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_dev_mode_isolation.py +0 -0
  132. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_extra_headers.py +0 -0
  133. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_fn_decorator.py +0 -0
  134. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_github_client.py +0 -0
  135. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_grpc_server.py +0 -0
  136. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_init.py +0 -0
  137. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_instrument.py +0 -0
  138. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_llm_langchain.py +0 -0
  139. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_llm_logging.py +0 -0
  140. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_logging_config.py +0 -0
  141. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_mcp.py +0 -0
  142. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_memory.py +0 -0
  143. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_proxy_e2e.py +0 -0
  144. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_proxy_server.py +0 -0
  145. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_resources.py +0 -0
  146. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_sse_utils.py +0 -0
  147. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_trace_context.py +0 -0
  148. {dispatch_agents-0.12.2 → dispatch_agents-0.13.1}/tests/test_typed_events.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dispatch_agents
3
- Version: 0.12.2
3
+ Version: 0.13.1
4
4
  Summary: Dispatch Agents SDK
5
5
  License-File: LICENSE
6
6
  License-File: LICENSE-3rdparty.csv
@@ -0,0 +1,2 @@
1
+ ## Bug Fixes
2
+ - Fixed a schema drift issue with the GitHub push webhook integration to ensure incoming webhook payloads are correctly parsed and handled.
@@ -162,6 +162,7 @@ def _dev_mode_audit_hook(event: str, args: tuple) -> None:
162
162
 
163
163
 
164
164
  from .agent_service import AgentServiceClient
165
+ from .config import _runtime as config
165
166
  from .events import (
166
167
  HANDLER_METADATA,
167
168
  REGISTERED_HANDLERS,
@@ -208,6 +209,8 @@ from .models import (
208
209
  )
209
210
 
210
211
  __all__ = [
212
+ # runtime config
213
+ "config",
211
214
  # storage and dev mode isolation
212
215
  "get_data_dir",
213
216
  "DisallowedWriteError",
@@ -500,6 +500,49 @@ class DispatchConfig(BaseModel):
500
500
  raise ValueError(f"All env values must be strings. {examples}")
501
501
  return v
502
502
 
503
+ vars: dict[str, Any] | None = Field(
504
+ default=None,
505
+ description="Configuration variables accessible at runtime via dispatch_agents.config.vars. "
506
+ "Unlike env, these are NOT injected as environment variables. "
507
+ "Supports any YAML-serializable type. Use {value: <any>, description: <str>} "
508
+ "to attach descriptions for the UI.",
509
+ )
510
+
511
+ @field_validator("vars", mode="before")
512
+ @classmethod
513
+ def _validate_vars(cls, v: dict | None) -> dict[str, Any] | None:
514
+ """Validate vars format.
515
+
516
+ Non-dict values (str, int, float, bool, list, None) are allowed as-is.
517
+ Dict values must use the described-var shape:
518
+ ``{value: <any>, description?: <str>}``. Other dicts are rejected
519
+ because ``value`` and ``description`` are reserved keywords — silently
520
+ treating arbitrary dicts as plain values would let a caller
521
+ accidentally collide with the described-var schema.
522
+ """
523
+ if not v or not isinstance(v, dict):
524
+ return v
525
+ _DESCRIBED_KEYS = {"value", "description"}
526
+ for key, val in v.items():
527
+ if not isinstance(val, dict):
528
+ continue
529
+ if "value" not in val:
530
+ raise ValueError(
531
+ f"Var '{key}' is a dict without a 'value' key. Dict values "
532
+ "must use the described-var shape "
533
+ "{value: <any>, description?: <str>}. To store structured "
534
+ "data, nest it under 'value'."
535
+ )
536
+ extra = set(val.keys()) - _DESCRIBED_KEYS
537
+ if extra:
538
+ raise ValueError(
539
+ f"Var '{key}' has unexpected keys {sorted(extra)}. Dict "
540
+ "values may only use the described-var shape "
541
+ "{value: <any>, description?: <str>}. To store structured "
542
+ "data, nest it under 'value'."
543
+ )
544
+ return v
545
+
503
546
  secrets: list[SecretConfig] | None = Field(
504
547
  default=None,
505
548
  description="Secrets to inject as environment variables",
@@ -573,10 +616,14 @@ class DispatchConfig(BaseModel):
573
616
  result["local_dependencies"] = self.local_dependencies
574
617
  if self.env:
575
618
  result["env"] = dict(self.env)
619
+ if self.vars:
620
+ result["vars"] = dict(self.vars)
576
621
  if self.secrets:
577
622
  result["secrets"] = [
578
623
  {"name": s.name, "secret_id": s.secret_id} for s in self.secrets
579
624
  ]
625
+ if self.mcp_servers:
626
+ result["mcp_servers"] = [{"server": m.server} for m in self.mcp_servers]
580
627
  if self.volumes:
581
628
  result["volumes"] = [
582
629
  {"name": v.name, "mountPath": v.mount_path, "mode": v.mode.value}
@@ -602,3 +649,117 @@ class DispatchConfig(BaseModel):
602
649
  return result
603
650
 
604
651
  model_config = {"populate_by_name": True}
652
+
653
+
654
+ # ---------------------------------------------------------------------------
655
+ # Runtime config singleton
656
+ # ---------------------------------------------------------------------------
657
+
658
+ import functools as _functools
659
+
660
+ _DISPATCH_YAML_PATH = "/app/dispatch.yaml"
661
+
662
+
663
+ @_functools.lru_cache(maxsize=1)
664
+ def _load_runtime_config() -> "DispatchConfig":
665
+ """Load dispatch.yaml once and cache the parsed DispatchConfig.
666
+
667
+ Falls back to an empty DispatchConfig when the file is absent
668
+ (dev mode or non-containerized runs). Namespace and agent_name
669
+ additionally fall back to DISPATCH_NAMESPACE / DISPATCH_AGENT_NAME
670
+ env vars so local ``dispatch agent dev`` runs work transparently.
671
+ """
672
+ import yaml as _yaml
673
+
674
+ path = os.environ.get("DISPATCH_CONFIG_PATH", _DISPATCH_YAML_PATH)
675
+ raw: dict = {}
676
+ if os.path.exists(path):
677
+ with open(path, encoding="utf-8") as f:
678
+ raw = _yaml.safe_load(f) or {}
679
+
680
+ cfg = DispatchConfig.model_validate(raw)
681
+
682
+ # Env-var fallbacks for identity fields used outside containers
683
+ updates: dict[str, Any] = {}
684
+ if cfg.namespace is None:
685
+ ns = os.environ.get("DISPATCH_NAMESPACE")
686
+ if ns:
687
+ updates["namespace"] = ns
688
+ if cfg.agent_name is None:
689
+ name = os.environ.get("DISPATCH_AGENT_NAME")
690
+ if name:
691
+ updates["agent_name"] = name
692
+ if updates:
693
+ cfg = cfg.model_copy(update=updates)
694
+
695
+ return cfg
696
+
697
+
698
+ _DESCRIBED_VAR_KEYS = frozenset({"value", "description"})
699
+
700
+
701
+ def _is_described_var(val: Any) -> bool:
702
+ """Check if a value is a described var ({value, description}) vs a plain dict."""
703
+ return (
704
+ isinstance(val, dict)
705
+ and "value" in val
706
+ and set(val.keys()) <= _DESCRIBED_VAR_KEYS
707
+ )
708
+
709
+
710
+ def _unwrap_vars(raw: dict[str, Any] | None) -> dict[str, Any]:
711
+ """Unwrap described vars ({value, description}) to plain values.
712
+
713
+ Plain dicts (with keys outside {value, description}) are passed through as-is.
714
+ """
715
+ if not raw:
716
+ return {}
717
+ result: dict[str, Any] = {}
718
+ for key, val in raw.items():
719
+ if _is_described_var(val):
720
+ result[key] = val["value"]
721
+ else:
722
+ result[key] = val
723
+ return result
724
+
725
+
726
+ def _extract_var_descriptions(raw: dict[str, Any] | None) -> dict[str, str]:
727
+ """Extract description strings from described vars."""
728
+ if not raw:
729
+ return {}
730
+ result: dict[str, str] = {}
731
+ for key, val in raw.items():
732
+ if _is_described_var(val) and "description" in val:
733
+ result[key] = val["description"]
734
+ return result
735
+
736
+
737
+ class _RuntimeConfig:
738
+ """Proxy that exposes dispatch.yaml fields at runtime.
739
+
740
+ Usage::
741
+
742
+ from dispatch_agents import config
743
+
744
+ config.namespace # str | None
745
+ config.agent_name # str | None
746
+ config.vars["temperature"] # 0.7 (unwrapped)
747
+ config.vars.get("missing") # None
748
+ config.vars_descriptions["max_turns"] # "Maximum agent turns"
749
+ """
750
+
751
+ @property
752
+ def vars(self) -> dict[str, Any]:
753
+ """Return unwrapped vars dict (described vars return just the value)."""
754
+ return _unwrap_vars(_load_runtime_config().vars)
755
+
756
+ @property
757
+ def vars_descriptions(self) -> dict[str, str]:
758
+ """Return descriptions for vars that have them."""
759
+ return _extract_var_descriptions(_load_runtime_config().vars)
760
+
761
+ def __getattr__(self, name: str) -> Any:
762
+ return getattr(_load_runtime_config(), name)
763
+
764
+
765
+ _runtime = _RuntimeConfig()
@@ -83,9 +83,9 @@ GitHub Topics:
83
83
 
84
84
  from __future__ import annotations
85
85
 
86
- from typing import Any, ClassVar, Literal
86
+ from typing import Any, ClassVar, Literal, Self
87
87
 
88
- from pydantic import ConfigDict, Field
88
+ from pydantic import ConfigDict, Field, StrictInt, model_validator
89
89
 
90
90
  from dispatch_agents.events import BasePayload
91
91
  from dispatch_agents.integrations.github.client import GitHubAppToken
@@ -209,12 +209,12 @@ class GitHubRepository(GitHubModel):
209
209
  description: str | None = Field(default=None, description="Repository description")
210
210
  fork: bool = Field(default=False, description="Whether this is a fork")
211
211
  url: str | None = Field(default=None, description="API URL")
212
- created_at: str | None = Field(
213
- default=None, description="ISO8601 creation timestamp"
212
+ created_at: StrictInt | str = Field(
213
+ description="Creation timestamp (Unix epoch int or ISO8601 string)",
214
214
  )
215
215
  updated_at: str | None = Field(default=None, description="ISO8601 update timestamp")
216
- pushed_at: str | None = Field(
217
- default=None, description="ISO8601 last push timestamp"
216
+ pushed_at: StrictInt | str | None = Field(
217
+ description="Last push timestamp (Unix epoch int, ISO8601 string, or null)",
218
218
  )
219
219
  homepage: str | None = Field(default=None, description="Homepage URL")
220
220
  size: int = Field(default=0, description="Repository size in KB")
@@ -490,7 +490,7 @@ class GitHubCommitUser(GitHubModel):
490
490
  """Git user information (author/committer)."""
491
491
 
492
492
  name: str = Field(description="Git user name")
493
- email: str = Field(description="Git user email")
493
+ email: str | None = Field(description="Git user email")
494
494
  username: str | None = Field(default=None, description="GitHub username if linked")
495
495
  date: str | None = Field(default=None, description="ISO8601 timestamp")
496
496
 
@@ -906,20 +906,38 @@ class PullRequestUnassigned(PullRequestBase):
906
906
  assignee: GitHubUser = Field(description="User who was unassigned")
907
907
 
908
908
 
909
- class PullRequestReviewRequested(PullRequestBase):
909
+ class PullRequestReviewTargetBase(PullRequestBase):
910
+ """Base payload for pull_request review target events."""
911
+
912
+ requested_reviewer: GitHubUser | None = Field(
913
+ default=None,
914
+ description="User requested for review (absent when a team is requested)",
915
+ )
916
+ requested_team: GitHubTeam | None = Field(
917
+ default=None,
918
+ description="Team requested for review (absent when a user is requested)",
919
+ )
920
+
921
+ @model_validator(mode="after")
922
+ def validate_review_target(self) -> Self:
923
+ """Require exactly one review target field."""
924
+ if (self.requested_reviewer is None) == (self.requested_team is None):
925
+ raise ValueError(
926
+ "Exactly one of requested_reviewer or requested_team must be provided"
927
+ )
928
+ return self
929
+
930
+
931
+ class PullRequestReviewRequested(PullRequestReviewTargetBase):
910
932
  """Payload for github.pull_request.review_requested events."""
911
933
 
912
934
  _dispatch_topic: ClassVar[str] = "github.pull_request.review_requested"
913
- requested_reviewer: GitHubUser = Field(description="User requested for review")
914
935
 
915
936
 
916
- class PullRequestReviewRequestRemoved(PullRequestBase):
937
+ class PullRequestReviewRequestRemoved(PullRequestReviewTargetBase):
917
938
  """Payload for github.pull_request.review_request_removed events."""
918
939
 
919
940
  _dispatch_topic: ClassVar[str] = "github.pull_request.review_request_removed"
920
- requested_reviewer: GitHubUser = Field(
921
- description="User whose review request was removed"
922
- )
923
941
 
924
942
 
925
943
  class PullRequestReadyForReview(PullRequestBase):