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.
- pulse/__init__.py +5 -0
- pulse/__main__.py +4 -0
- pulse/agent.py +270 -0
- pulse/agent_manager.py +335 -0
- pulse/audit.py +70 -0
- pulse/auth.py +670 -0
- pulse/ci/github_client.py +66 -0
- pulse/ci/runner.py +28 -0
- pulse/cli.py +1075 -0
- pulse/cli_ui.py +977 -0
- pulse/config.py +167 -0
- pulse/context.py +960 -0
- pulse/conversations/__init__.py +8 -0
- pulse/conversations/manager.py +312 -0
- pulse/core/agent.py +188 -0
- pulse/core/planner.py +105 -0
- pulse/core/protocols.py +37 -0
- pulse/edits.py +65 -0
- pulse/episodic.py +93 -0
- pulse/eval/__init__.py +8 -0
- pulse/eval/trajectory_logger.py +91 -0
- pulse/eval/verifier.py +133 -0
- pulse/execution/__init__.py +5 -0
- pulse/execution/remote_task.py +76 -0
- pulse/git.py +162 -0
- pulse/interactive.py +234 -0
- pulse/mcp/__init__.py +4 -0
- pulse/mcp/client.py +215 -0
- pulse/mcp/local_tools.py +105 -0
- pulse/memory.py +212 -0
- pulse/mutations.py +283 -0
- pulse/orchestration/__init__.py +3 -0
- pulse/orchestration/orchestrator.py +162 -0
- pulse/patch.py +129 -0
- pulse/planner/__init__.py +3 -0
- pulse/planner/dag_planner.py +85 -0
- pulse/planner/execution_loop.py +159 -0
- pulse/production.py +235 -0
- pulse/provider.py +59 -0
- pulse/provider_keys.py +278 -0
- pulse/providers/__init__.py +26 -0
- pulse/providers/anthropic.py +65 -0
- pulse/providers/base.py +251 -0
- pulse/providers/deepseek.py +10 -0
- pulse/providers/failover.py +32 -0
- pulse/providers/gemini.py +66 -0
- pulse/providers/groq.py +10 -0
- pulse/providers/manager.py +262 -0
- pulse/providers/openai.py +40 -0
- pulse/providers/openrouter.py +20 -0
- pulse/py.typed +1 -0
- pulse/reasoning.py +570 -0
- pulse/refactor/__init__.py +3 -0
- pulse/refactor/impact_analyzer.py +44 -0
- pulse/repository.py +209 -0
- pulse/rpc.py +249 -0
- pulse/rule_synthesizer.py +54 -0
- pulse/runtime.py +217 -0
- pulse/safety/__init__.py +3 -0
- pulse/safety/safety_manager.py +97 -0
- pulse/sandbox/SECURITY.md +57 -0
- pulse/sandbox/__init__.py +57 -0
- pulse/sandbox/api.py +594 -0
- pulse/sandbox/audit.py +153 -0
- pulse/sandbox/backend/__init__.py +7 -0
- pulse/sandbox/backend/base.py +72 -0
- pulse/sandbox/backend/docker.py +498 -0
- pulse/sandbox/backend/host.py +140 -0
- pulse/sandbox/backend/remote.py +224 -0
- pulse/sandbox/errors.py +106 -0
- pulse/sandbox/filesystem.py +476 -0
- pulse/sandbox/git_safe.py +50 -0
- pulse/sandbox/lifecycle.py +88 -0
- pulse/sandbox/network.py +205 -0
- pulse/sandbox/path_validator.py +280 -0
- pulse/sandbox/policy.py +209 -0
- pulse/sandbox/process.py +331 -0
- pulse/sandbox/project.py +158 -0
- pulse/sandbox/python_safe.py +62 -0
- pulse/sandbox/remote/__init__.py +1 -0
- pulse/sandbox/remote/client.py +389 -0
- pulse/sandbox/remote/models.py +167 -0
- pulse/sandbox/remote/protocol.py +65 -0
- pulse/sandbox/remote/server.py +984 -0
- pulse/sandbox/remote/worker.py +175 -0
- pulse/sandbox/resources.py +236 -0
- pulse/sandbox/secrets.py +241 -0
- pulse/session_manager.py +365 -0
- pulse/software_engineer.py +189 -0
- pulse/storage.py +140 -0
- pulse/streaming.py +385 -0
- pulse/subprocesses.py +79 -0
- pulse/task_manager.py +2005 -0
- pulse/telemetry/__init__.py +25 -0
- pulse/telemetry/cost_tracker.py +95 -0
- pulse/telemetry/logger.py +110 -0
- pulse/tool_policy.py +197 -0
- pulse/tool_registry.py +163 -0
- pulse/tools.py +372 -0
- pulse/verification.py +118 -0
- pulse_coding_agent-0.1.0.dist-info/METADATA +211 -0
- pulse_coding_agent-0.1.0.dist-info/RECORD +104 -0
- pulse_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- 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"))
|