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/config.py ADDED
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ _MAX_ENV_FILE_BYTES = 1_048_576
11
+ _MAX_CONFIG_FILE_BYTES = 1_048_576
12
+ _ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
13
+
14
+
15
+ def _workspace_path(workspace: Path, value: object, label: str) -> Path:
16
+ candidate = (workspace / str(value)).resolve()
17
+ try:
18
+ candidate.relative_to(workspace.resolve())
19
+ except ValueError as error:
20
+ raise ValueError(f"{label} must remain inside the workspace.") from error
21
+ return candidate
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ModelConfig:
26
+ provider: str
27
+ name: str
28
+ temperature: float
29
+ max_tokens: int = 8192
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class SandboxConfig:
34
+ workspace_root: Path
35
+ require_permission_for_reads: bool = True
36
+ require_permission_for_project_actions: bool = True
37
+ allow_writes: bool = False
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class LoggingConfig:
42
+ action_log: Path
43
+ telemetry_log: Path | None = None
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class AgentConfig:
48
+ agent_name: str
49
+ mode: str
50
+ model: ModelConfig
51
+ sandbox: SandboxConfig
52
+ logging: LoggingConfig
53
+
54
+
55
+ def load_agent_config(workspace: Path) -> AgentConfig:
56
+ raw = _read_json(workspace / "agent.config.json")
57
+ orig_env_provider = os.environ.get("AGENT_PROVIDER")
58
+ orig_env_model = os.environ.get("AGENT_MODEL")
59
+
60
+ env = load_env_file(workspace / ".env")
61
+
62
+ model_raw = raw.get("model", {})
63
+ sandbox_raw = raw.get("sandbox", {})
64
+ logging_raw = raw.get("logging", {})
65
+
66
+ from pulse.providers.manager import ProviderManager
67
+
68
+ pm = ProviderManager(workspace)
69
+ saved_provider, saved_model = pm.get_active_selection()
70
+
71
+ if orig_env_provider and orig_env_model:
72
+ provider = orig_env_provider
73
+ model_name = orig_env_model
74
+ elif (workspace / ".agent" / "provider.json").exists():
75
+ provider = saved_provider
76
+ model_name = saved_model
77
+ else:
78
+ provider = (
79
+ orig_env_provider
80
+ or os.environ.get("AGENT_PROVIDER")
81
+ or model_raw.get("provider")
82
+ or saved_provider
83
+ )
84
+ model_name = (
85
+ orig_env_model
86
+ or os.environ.get("AGENT_MODEL")
87
+ or model_raw.get("name")
88
+ or saved_model
89
+ )
90
+
91
+ max_tokens = int(
92
+ os.environ.get("AGENT_MAX_TOKENS")
93
+ or env.get("AGENT_MAX_TOKENS")
94
+ or model_raw.get("maxTokens", 8192)
95
+ )
96
+ if max_tokens <= 0:
97
+ raise ValueError("Model maxTokens must be a positive integer.")
98
+
99
+ return AgentConfig(
100
+ agent_name=raw.get("agentName", "Kiran"),
101
+ mode=raw.get("mode", "single-model"),
102
+ model=ModelConfig(
103
+ provider=provider,
104
+ name=model_name,
105
+ temperature=float(model_raw.get("temperature", 0.2)),
106
+ max_tokens=max_tokens,
107
+ ),
108
+ sandbox=SandboxConfig(
109
+ workspace_root=_workspace_path(
110
+ workspace, sandbox_raw.get("workspaceRoot", "."), "sandbox.workspaceRoot"
111
+ ),
112
+ require_permission_for_reads=bool(
113
+ sandbox_raw.get("requirePermissionForReads", True)
114
+ ),
115
+ require_permission_for_project_actions=bool(
116
+ sandbox_raw.get("requirePermissionForProjectActions", True)
117
+ ),
118
+ allow_writes=bool(sandbox_raw.get("allowWrites", False)),
119
+ ),
120
+ logging=LoggingConfig(
121
+ action_log=_workspace_path(
122
+ workspace,
123
+ logging_raw.get("actionLog", ".agent/logs/actions.jsonl"),
124
+ "logging.actionLog",
125
+ ),
126
+ telemetry_log=_workspace_path(
127
+ workspace,
128
+ logging_raw.get("telemetryLog", ".agent/logs/telemetry.jsonl"),
129
+ "logging.telemetryLog",
130
+ ),
131
+ ),
132
+ )
133
+
134
+
135
+ def load_env_file(path: Path) -> dict[str, str]:
136
+ if not path.exists():
137
+ return {}
138
+ if path.is_symlink():
139
+ raise ValueError("Refusing to load a symbolic-link .env file.")
140
+ if path.stat().st_size > _MAX_ENV_FILE_BYTES:
141
+ raise ValueError("The .env file exceeds the 1 MiB safety limit.")
142
+
143
+ values: dict[str, str] = {}
144
+ for line in path.read_text(encoding="utf-8").splitlines():
145
+ stripped = line.strip()
146
+ if not stripped or stripped.startswith("#") or "=" not in stripped:
147
+ continue
148
+
149
+ key, value = stripped.split("=", 1)
150
+ k = key.strip()
151
+ if not _ENV_NAME.fullmatch(k):
152
+ continue
153
+ v = value.strip().strip("\"'")
154
+ values[k] = v
155
+
156
+ return values
157
+
158
+
159
+ def _read_json(path: Path) -> dict[str, Any]:
160
+ if not path.exists():
161
+ return {}
162
+ if path.is_symlink():
163
+ raise ValueError(f"Refusing to load symbolic-link configuration: {path.name}")
164
+ if path.stat().st_size > _MAX_CONFIG_FILE_BYTES:
165
+ raise ValueError(f"Configuration file exceeds the 1 MiB safety limit: {path.name}")
166
+
167
+ return json.loads(path.read_text(encoding="utf-8"))