pulse-coding-agent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. pulse/__init__.py +5 -0
  2. pulse/__main__.py +4 -0
  3. pulse/agent.py +270 -0
  4. pulse/agent_manager.py +335 -0
  5. pulse/audit.py +70 -0
  6. pulse/auth.py +670 -0
  7. pulse/ci/github_client.py +66 -0
  8. pulse/ci/runner.py +28 -0
  9. pulse/cli.py +1075 -0
  10. pulse/cli_ui.py +977 -0
  11. pulse/config.py +167 -0
  12. pulse/context.py +960 -0
  13. pulse/conversations/__init__.py +8 -0
  14. pulse/conversations/manager.py +312 -0
  15. pulse/core/agent.py +188 -0
  16. pulse/core/planner.py +105 -0
  17. pulse/core/protocols.py +37 -0
  18. pulse/edits.py +65 -0
  19. pulse/episodic.py +93 -0
  20. pulse/eval/__init__.py +8 -0
  21. pulse/eval/trajectory_logger.py +91 -0
  22. pulse/eval/verifier.py +133 -0
  23. pulse/execution/__init__.py +5 -0
  24. pulse/execution/remote_task.py +76 -0
  25. pulse/git.py +162 -0
  26. pulse/interactive.py +234 -0
  27. pulse/mcp/__init__.py +4 -0
  28. pulse/mcp/client.py +215 -0
  29. pulse/mcp/local_tools.py +105 -0
  30. pulse/memory.py +212 -0
  31. pulse/mutations.py +283 -0
  32. pulse/orchestration/__init__.py +3 -0
  33. pulse/orchestration/orchestrator.py +162 -0
  34. pulse/patch.py +129 -0
  35. pulse/planner/__init__.py +3 -0
  36. pulse/planner/dag_planner.py +85 -0
  37. pulse/planner/execution_loop.py +159 -0
  38. pulse/production.py +235 -0
  39. pulse/provider.py +59 -0
  40. pulse/provider_keys.py +278 -0
  41. pulse/providers/__init__.py +26 -0
  42. pulse/providers/anthropic.py +65 -0
  43. pulse/providers/base.py +251 -0
  44. pulse/providers/deepseek.py +10 -0
  45. pulse/providers/failover.py +32 -0
  46. pulse/providers/gemini.py +66 -0
  47. pulse/providers/groq.py +10 -0
  48. pulse/providers/manager.py +262 -0
  49. pulse/providers/openai.py +40 -0
  50. pulse/providers/openrouter.py +20 -0
  51. pulse/py.typed +1 -0
  52. pulse/reasoning.py +570 -0
  53. pulse/refactor/__init__.py +3 -0
  54. pulse/refactor/impact_analyzer.py +44 -0
  55. pulse/repository.py +209 -0
  56. pulse/rpc.py +249 -0
  57. pulse/rule_synthesizer.py +54 -0
  58. pulse/runtime.py +217 -0
  59. pulse/safety/__init__.py +3 -0
  60. pulse/safety/safety_manager.py +97 -0
  61. pulse/sandbox/SECURITY.md +57 -0
  62. pulse/sandbox/__init__.py +57 -0
  63. pulse/sandbox/api.py +594 -0
  64. pulse/sandbox/audit.py +153 -0
  65. pulse/sandbox/backend/__init__.py +7 -0
  66. pulse/sandbox/backend/base.py +72 -0
  67. pulse/sandbox/backend/docker.py +498 -0
  68. pulse/sandbox/backend/host.py +140 -0
  69. pulse/sandbox/backend/remote.py +224 -0
  70. pulse/sandbox/errors.py +106 -0
  71. pulse/sandbox/filesystem.py +476 -0
  72. pulse/sandbox/git_safe.py +50 -0
  73. pulse/sandbox/lifecycle.py +88 -0
  74. pulse/sandbox/network.py +205 -0
  75. pulse/sandbox/path_validator.py +280 -0
  76. pulse/sandbox/policy.py +209 -0
  77. pulse/sandbox/process.py +331 -0
  78. pulse/sandbox/project.py +158 -0
  79. pulse/sandbox/python_safe.py +62 -0
  80. pulse/sandbox/remote/__init__.py +1 -0
  81. pulse/sandbox/remote/client.py +389 -0
  82. pulse/sandbox/remote/models.py +167 -0
  83. pulse/sandbox/remote/protocol.py +65 -0
  84. pulse/sandbox/remote/server.py +984 -0
  85. pulse/sandbox/remote/worker.py +175 -0
  86. pulse/sandbox/resources.py +236 -0
  87. pulse/sandbox/secrets.py +241 -0
  88. pulse/session_manager.py +365 -0
  89. pulse/software_engineer.py +189 -0
  90. pulse/storage.py +140 -0
  91. pulse/streaming.py +385 -0
  92. pulse/subprocesses.py +79 -0
  93. pulse/task_manager.py +2005 -0
  94. pulse/telemetry/__init__.py +25 -0
  95. pulse/telemetry/cost_tracker.py +95 -0
  96. pulse/telemetry/logger.py +110 -0
  97. pulse/tool_policy.py +197 -0
  98. pulse/tool_registry.py +163 -0
  99. pulse/tools.py +372 -0
  100. pulse/verification.py +118 -0
  101. pulse_coding_agent-0.1.0.dist-info/METADATA +211 -0
  102. pulse_coding_agent-0.1.0.dist-info/RECORD +104 -0
  103. pulse_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  104. pulse_coding_agent-0.1.0.dist-info/entry_points.txt +4 -0
pulse/runtime.py ADDED
@@ -0,0 +1,217 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from pulse.agent import ProjectAgent
7
+ from pulse.agent_manager import (
8
+ AgentManager,
9
+ DocumentationAgent,
10
+ GitAgent,
11
+ PlannerAgent,
12
+ ReviewerAgent,
13
+ SoftwareEngineerAgent,
14
+ TesterAgent,
15
+ )
16
+ from pulse.audit import AuditLog
17
+ from pulse.auth import AuthenticationManager
18
+ from pulse.config import AgentConfig, load_agent_config
19
+ from pulse.context import ContextManager
20
+ from pulse.core.planner import RequestPlanner
21
+ from pulse.edits import EditWorkflow
22
+ from pulse.git import GitIntelligence
23
+ from pulse.memory import LongTermMemory
24
+ from pulse.mutations import MutationTracker
25
+ from pulse.provider import ModelProvider
26
+ from pulse.providers.manager import ProviderManager
27
+ from pulse.reasoning import ReasoningEngine
28
+ from pulse.repository import RepositoryIndex
29
+ from pulse.sandbox import ProjectSandbox
30
+ from pulse.session_manager import SessionManager
31
+ from pulse.software_engineer import AutonomousSoftwareEngineer
32
+ from pulse.streaming import StreamingExecutionEngine
33
+ from pulse.task_manager import TaskManager
34
+ from pulse.telemetry import TelemetryLogger
35
+ from pulse.tool_policy import ToolPolicyEngine
36
+ from pulse.tool_registry import ToolInvocation, ToolRegistry
37
+ from pulse.tools import (
38
+ DoctorTool,
39
+ EditTool,
40
+ GitTool,
41
+ IndexTool,
42
+ MemoryTool,
43
+ MutationsTool,
44
+ RollbackTool,
45
+ SearchTool,
46
+ SessionTool,
47
+ StatusTool,
48
+ SymbolsTool,
49
+ TaskTool,
50
+ VerifyTool,
51
+ )
52
+ from pulse.verification import VerificationEngine
53
+
54
+
55
+ @dataclass(slots=True)
56
+ class AgentRuntime:
57
+ workspace: Path
58
+ config: AgentConfig
59
+ audit: AuditLog
60
+ mutations: MutationTracker
61
+ sandbox: ProjectSandbox
62
+ provider: ModelProvider
63
+ agent: ProjectAgent
64
+ edits: EditWorkflow
65
+ tools: ToolRegistry
66
+ repository: RepositoryIndex
67
+ verification: VerificationEngine
68
+ git: GitIntelligence
69
+ memory: LongTermMemory
70
+ manager: AgentManager
71
+ task_manager: TaskManager
72
+ session_manager: SessionManager
73
+ context_manager: ContextManager
74
+ reasoning_engine: ReasoningEngine
75
+ planner: RequestPlanner
76
+ streaming_engine: StreamingExecutionEngine
77
+ software_engineer: AutonomousSoftwareEngineer
78
+ auth: AuthenticationManager
79
+ telemetry: TelemetryLogger
80
+
81
+
82
+ def build_runtime(workspace: Path, config: AgentConfig | None = None) -> AgentRuntime:
83
+ resolved_workspace = workspace.resolve()
84
+ resolved_config = config or load_agent_config(resolved_workspace)
85
+ audit = AuditLog(resolved_config.logging.action_log)
86
+ telemetry = TelemetryLogger(resolved_config.logging.telemetry_log)
87
+ mutations = MutationTracker(resolved_workspace)
88
+ sandbox = ProjectSandbox(resolved_config.sandbox, audit, mutations)
89
+
90
+ provider_manager = ProviderManager(resolved_workspace)
91
+ provider = provider_manager.create_provider(
92
+ resolved_config.model, resolved_workspace / ".env"
93
+ )
94
+ audit.add_secret(getattr(provider, "api_key", None))
95
+ telemetry.add_secret(getattr(provider, "api_key", None))
96
+
97
+ edits = EditWorkflow(sandbox)
98
+ repository = RepositoryIndex(resolved_workspace)
99
+ verification = VerificationEngine(resolved_workspace)
100
+ git = GitIntelligence(resolved_workspace)
101
+ memory = LongTermMemory(
102
+ resolved_workspace,
103
+ secrets=[provider.api_key] if getattr(provider, "api_key", None) else None,
104
+ )
105
+ task_manager = TaskManager(resolved_workspace, memory=memory, telemetry=telemetry)
106
+ session_manager = SessionManager(
107
+ resolved_workspace, task_manager=task_manager, telemetry=telemetry
108
+ )
109
+
110
+ async def check_permission(invocation: ToolInvocation, tool: object) -> bool:
111
+ return sandbox.request_project_action(
112
+ "run tool", getattr(tool, "name", "tool"), "This action changes the project."
113
+ )
114
+
115
+ tool_capabilities = frozenset(
116
+ {
117
+ "status", "doctor", "mutations", "edit", "rollback", "git", "memory",
118
+ "index", "search", "symbols", "verify", "task", "tasks", "resume", "cancel",
119
+ "session", "sessions", "resume-session",
120
+ }
121
+ )
122
+ tools = ToolRegistry(
123
+ [
124
+ StatusTool(resolved_config, provider), DoctorTool(resolved_workspace, resolved_config, provider),
125
+ MutationsTool(mutations), EditTool(edits, git), RollbackTool(edits), GitTool(git), MemoryTool(memory),
126
+ ],
127
+ permission_checker=check_permission,
128
+ telemetry=telemetry,
129
+ policy_engine=ToolPolicyEngine(
130
+ workspace=resolved_workspace,
131
+ allowed_capabilities=tool_capabilities,
132
+ audit_log=audit,
133
+ ),
134
+ )
135
+ tools.register(IndexTool(repository))
136
+ tools.register(SearchTool(repository))
137
+ tools.register(SymbolsTool(repository))
138
+ tools.register(VerifyTool(verification))
139
+ tools.register(TaskTool(task_manager))
140
+ tools.register(SessionTool(session_manager))
141
+
142
+ manager = AgentManager(
143
+ task_manager=task_manager,
144
+ agents=[
145
+ PlannerAgent(provider, tools),
146
+ SoftwareEngineerAgent(provider, tools),
147
+ ReviewerAgent(provider, tools),
148
+ TesterAgent(provider, tools),
149
+ DocumentationAgent(provider, tools),
150
+ GitAgent(provider, tools),
151
+ ],
152
+ )
153
+ agent = ProjectAgent(
154
+ resolved_config.agent_name,
155
+ sandbox,
156
+ provider,
157
+ audit,
158
+ tools,
159
+ repository,
160
+ memory,
161
+ manager,
162
+ )
163
+
164
+ context_manager = ContextManager(
165
+ repository=repository, memory=memory, git=git, workspace=resolved_workspace
166
+ )
167
+ reasoning_engine = ReasoningEngine(
168
+ provider=provider, context_manager=context_manager, tool_registry=tools
169
+ )
170
+ planner = RequestPlanner()
171
+ streaming_engine = StreamingExecutionEngine(
172
+ provider=provider,
173
+ tool_registry=tools,
174
+ reasoning_engine=reasoning_engine,
175
+ task_manager=task_manager,
176
+ telemetry=telemetry,
177
+ verification_engine=verification,
178
+ )
179
+ software_engineer = AutonomousSoftwareEngineer(
180
+ reasoning_engine=reasoning_engine,
181
+ planner=planner,
182
+ task_manager=task_manager,
183
+ session_manager=session_manager,
184
+ streaming_engine=streaming_engine,
185
+ verification_engine=verification,
186
+ context_manager=context_manager,
187
+ memory=memory,
188
+ repository=repository,
189
+ tool_registry=tools,
190
+ )
191
+ auth = AuthenticationManager(resolved_workspace)
192
+
193
+ return AgentRuntime(
194
+ workspace=resolved_workspace,
195
+ config=resolved_config,
196
+ audit=audit,
197
+ mutations=mutations,
198
+ sandbox=sandbox,
199
+ provider=provider,
200
+ agent=agent,
201
+ edits=edits,
202
+ tools=tools,
203
+ repository=repository,
204
+ verification=verification,
205
+ git=git,
206
+ memory=memory,
207
+ manager=manager,
208
+ task_manager=task_manager,
209
+ session_manager=session_manager,
210
+ context_manager=context_manager,
211
+ reasoning_engine=reasoning_engine,
212
+ planner=planner,
213
+ streaming_engine=streaming_engine,
214
+ software_engineer=software_engineer,
215
+ auth=auth,
216
+ telemetry=telemetry,
217
+ )
@@ -0,0 +1,3 @@
1
+ from pulse.safety.safety_manager import RiskLevel, SafetyManager
2
+
3
+ __all__ = ["RiskLevel", "SafetyManager"]
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Awaitable, Callable
5
+ from enum import Enum
6
+
7
+ from pulse.audit import AuditLog
8
+
9
+
10
+ class RiskLevel(Enum):
11
+ LOW = "LOW" # Read-only operations
12
+ MEDIUM = "MEDIUM" # File edits and test executions
13
+ HIGH = "HIGH" # System execution, shell commands, or file deletion
14
+
15
+
16
+ class SafetyManager:
17
+ """Assesses action risk levels, enforces user confirmation for HIGH risk actions, and records audit logs."""
18
+
19
+ def __init__(
20
+ self,
21
+ audit_log: AuditLog | None = None,
22
+ confirmation_callback: Callable[[str, RiskLevel], bool | Awaitable[bool]] | None = None,
23
+ ) -> None:
24
+ self.audit_log = audit_log
25
+ self.confirmation_callback = confirmation_callback
26
+
27
+ def assess_risk(self, action: str, target: str = "") -> RiskLevel:
28
+ action_lower = action.lower()
29
+ target_lower = target.lower()
30
+
31
+ # HIGH risk: system command execution, shell access, deletion
32
+ high_keywords = {
33
+ "execute",
34
+ "system",
35
+ "shell",
36
+ "delete",
37
+ "remove",
38
+ "destroy",
39
+ "drop",
40
+ "unlink",
41
+ "bash",
42
+ "cmd",
43
+ "terminal",
44
+ "eval",
45
+ }
46
+ if any(kw in action_lower for kw in high_keywords) or "delete" in target_lower or "remove" in target_lower:
47
+ return RiskLevel.HIGH
48
+
49
+ # MEDIUM risk: edits, code modifications, writing files, running test suites
50
+ medium_keywords = {
51
+ "edit",
52
+ "modify",
53
+ "write",
54
+ "update",
55
+ "patch",
56
+ "test",
57
+ "pytest",
58
+ "mutate",
59
+ "create",
60
+ }
61
+ if any(kw in action_lower for kw in medium_keywords):
62
+ return RiskLevel.MEDIUM
63
+
64
+ return RiskLevel.LOW
65
+
66
+ async def authorize(self, action: str, target: str = "", detail: str = "") -> bool:
67
+ risk = self.assess_risk(action, target)
68
+ authorized = True
69
+
70
+ if risk is RiskLevel.HIGH:
71
+ if self.confirmation_callback is not None:
72
+ res = self.confirmation_callback(action, risk)
73
+ if asyncio.iscoroutine(res) or hasattr(res, "__await__"):
74
+ authorized = bool(await res)
75
+ else:
76
+ authorized = bool(res)
77
+ else:
78
+ authorized = False
79
+
80
+ if self.audit_log:
81
+ status = "APPROVED" if authorized else "REJECTED"
82
+ self.audit_log.record(
83
+ action=f"safety-{risk.value.lower()}-{status.lower()}",
84
+ file=target or ".",
85
+ detail=f"Action: {action} | Risk: {risk.value} | Authorized: {authorized} | {detail}".strip(" |"),
86
+ )
87
+
88
+ return authorized
89
+
90
+ def log_audit(self, action: str, target: str, detail: str, risk: RiskLevel | None = None) -> None:
91
+ if self.audit_log:
92
+ risk_label = risk.value if risk else "UNKNOWN"
93
+ self.audit_log.record(
94
+ action=action,
95
+ file=target or ".",
96
+ detail=f"[{risk_label}] {detail}",
97
+ )
@@ -0,0 +1,57 @@
1
+ # Pulse Secure Sandbox — Security Architecture
2
+
3
+ ## Isolation Models
4
+
5
+ The Pulse Sandbox provides multiple layers of defense to execute untrusted code safely.
6
+ We categorize security controls into Prevention, Detection, and Redaction.
7
+
8
+ ### Prevention (Isolation)
9
+ - **Tool Authorization**: Model-produced tool arguments are validated against
10
+ typed schemas before execution. `ToolPolicyEngine` evaluates a per-runtime
11
+ capability allowlist, workspace scope, risk level, and approval requirement.
12
+ Unknown capabilities, malformed arguments, and paths outside the configured
13
+ workspace are rejected before a tool receives them.
14
+ - **Containerization**: `DockerBackend` enforces process isolation, filesystem isolation, and network isolation using namespaces and cgroups.
15
+ - **Remote Execution**: `RemoteSandboxBackend` enables untrusted code to run on a dedicated remote host via the `pulse-remote` worker daemon. This physical/network separation prevents any host escape from affecting the local development machine, with communication secured via authenticated WSS.
16
+ - **Secret Injection**: Secrets are explicitly injected into the container environment via temporary `--env-file` structures stored outside the workspace. This prevents argv leakage (e.g. `ps aux`) and ensures robust lifecycle cleanup.
17
+ - **Copy-on-Write Filesystem (CoW)**: Changes are strictly staged via CoW transactions. If explicitly authorized secrets are detected in staged files, the commit is outright rejected by the security engine to prevent secret persistence in snapshots.
18
+ - **Path Validation**: TOCTOU-safe path verification prevents directory traversal and symlink escapes.
19
+
20
+ ### Detection (Auditing)
21
+ - **Structured Audit Logging**: All security boundary interactions (reads, writes, network connections, execution) are logged securely in `audit.jsonl` with their resolved `isolation_level`.
22
+ - **Policy Decision Logging**: Every central tool-policy decision records the
23
+ subject, capability, risk, and reason. Raw tool argument values are excluded
24
+ so audit records cannot become a second secret store.
25
+
26
+ ### Redaction (Scrubber)
27
+ - **SecretScrubber**: In-memory regex and exact-value matching redaction engine scrub sensitive keys from standard output, errors, and audit logs. All regex patterns are optimized to prevent ReDoS.
28
+
29
+ ## Known Limitations & Unsupported Scopes
30
+
31
+ - **Host Execution Fallback**: Running `HostBackend` is intrinsically unsafe for untrusted code execution. By default, the sandbox requires an explicit opt-in (`unsafe_host_execution=True`) which is audited and warned against.
32
+ - **Process Group Termination Escapes**: On POSIX, a child process running directly on the host can escape `killpg()` termination by calling `setsid()`. This underscores the mandatory requirement for container-level isolation boundaries.
33
+ - **Scoped Secret Access**: Providing per-network-request secret injection (Scoped Secrets) is **currently unsupported**. Given that network isolation and environment variable capabilities are decoupled, attempting to implement scoped secrets without strong OS/ebpf-level enforcement can lead to a false sense of security. Secrets injected into a container are accessible to any process within that container.
34
+
35
+ ## Threat Model and Incident Response
36
+
37
+ Pulse treats provider output, tool arguments, downloaded artifacts, and remote
38
+ worker messages as untrusted. The intended deployment boundary is a dedicated
39
+ container or remote worker; host execution is development-only and explicitly
40
+ unsafe. The system is designed to contain path traversal, shell injection,
41
+ archive attacks, unrestricted network egress, credential leakage, stale remote
42
+ results, and cross-tenant remote execution access.
43
+
44
+ If a remote token, client certificate, or provider credential may be exposed:
45
+
46
+ 1. Revoke the affected credential at its issuer and replace the configured
47
+ `PULSE_REMOTE_TOKEN` or TLS material on both worker and clients.
48
+ 2. Restart remote workers to terminate active sessions; inspect the durable
49
+ execution store and `audit.jsonl` for the affected tenant and execution IDs.
50
+ 3. Quarantine ambiguous tasks through recovery rather than retrying them with a
51
+ new external ID, then rotate any secrets mounted into the affected worker.
52
+ 4. Preserve redacted audit logs and the worker version/configuration for
53
+ incident review. Do not copy raw execution output into tickets.
54
+
55
+ Remote workers must use `wss://` with mTLS outside loopback, have a dedicated
56
+ tenant-isolated workspace volume, enforce a finite execution retention period,
57
+ and expose health checks only on an authenticated operations network.
@@ -0,0 +1,57 @@
1
+ """Secure sandbox subsystem public exports.
2
+
3
+ The package keeps these exports lazy so importing a narrow helper such as
4
+ ``pulse.sandbox.secrets`` does not initialize the full sandbox stack.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from importlib import import_module
10
+ from typing import Any
11
+
12
+ _EXPORTS = {
13
+ "ActionType": "pulse.sandbox.policy",
14
+ "CoWFilesystem": "pulse.sandbox.filesystem",
15
+ "CoWTransaction": "pulse.sandbox.filesystem",
16
+ "ContainerBackend": "pulse.sandbox.backend",
17
+ "DockerBackend": "pulse.sandbox.backend",
18
+ "ExecutionMetrics": "pulse.sandbox.resources",
19
+ "HostBackend": "pulse.sandbox.backend",
20
+ "PathValidationError": "pulse.sandbox.path_validator",
21
+ "PathValidator": "pulse.sandbox.path_validator",
22
+ "PolicyDecision": "pulse.sandbox.policy",
23
+ "PolicyRule": "pulse.sandbox.policy",
24
+ "ProcessManager": "pulse.sandbox.process",
25
+ "ProcessResult": "pulse.sandbox.process",
26
+ "ProjectSandbox": "pulse.sandbox.project",
27
+ "ResourceController": "pulse.sandbox.resources",
28
+ "ResourceLimitExceeded": "pulse.sandbox.resources",
29
+ "ResourceLimiter": "pulse.sandbox.resources",
30
+ "ResourceLimits": "pulse.sandbox.resources",
31
+ "ResourceMonitor": "pulse.sandbox.resources",
32
+ "ResourcePolicy": "pulse.sandbox.resources",
33
+ "SafeGit": "pulse.sandbox.git_safe",
34
+ "SafePython": "pulse.sandbox.python_safe",
35
+ "Sandbox": "pulse.sandbox.api",
36
+ "SandboxConcurrentModificationError": "pulse.sandbox.errors",
37
+ "SandboxPolicy": "pulse.sandbox.policy",
38
+ "SandboxResourceError": "pulse.sandbox.errors",
39
+ "SandboxSecurityError": "pulse.sandbox.errors",
40
+ "SandboxSession": "pulse.sandbox.api",
41
+ "SandboxUnavailableError": "pulse.sandbox.errors",
42
+ "SecretScrubber": "pulse.sandbox.secrets",
43
+ "StructuredAuditEntry": "pulse.sandbox.audit",
44
+ "StructuredAuditLogger": "pulse.sandbox.audit",
45
+ "TimeoutExceeded": "pulse.sandbox.resources",
46
+ }
47
+
48
+ __all__ = tuple(sorted(_EXPORTS))
49
+
50
+
51
+ def __getattr__(name: str) -> Any:
52
+ if name not in _EXPORTS:
53
+ raise AttributeError(name)
54
+ module = import_module(_EXPORTS[name])
55
+ value = getattr(module, name)
56
+ globals()[name] = value
57
+ return value