@openlucaskaka/kagent 0.1.7
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.
- package/README.md +353 -0
- package/npm/bin/kagent-serve.js +6 -0
- package/npm/bin/kagent.js +6 -0
- package/npm/lib/App.js +524 -0
- package/npm/lib/app-state.js +224 -0
- package/npm/lib/approval-choice.js +25 -0
- package/npm/lib/commands.js +59 -0
- package/npm/lib/editor.js +188 -0
- package/npm/lib/ink-runner.js +41 -0
- package/npm/lib/kagent-home.js +39 -0
- package/npm/lib/launcher.js +221 -0
- package/npm/lib/protocol.js +33 -0
- package/npm/lib/provider-setup.js +139 -0
- package/npm/lib/python-runner.js +892 -0
- package/npm/lib/runtime-client.js +390 -0
- package/npm/lib/terminal-input.js +127 -0
- package/npm/lib/terminal-text.js +19 -0
- package/npm/lib/terminal-width.js +14 -0
- package/npm/lib/transcript.js +227 -0
- package/npm/lib/ui-components.js +247 -0
- package/npm/lib/update-manager.js +334 -0
- package/package.json +39 -0
- package/pyproject.toml +55 -0
- package/src/kagent/__init__.py +90 -0
- package/src/kagent/cli/__init__.py +5 -0
- package/src/kagent/cli/__main__.py +6 -0
- package/src/kagent/cli/commands.py +112 -0
- package/src/kagent/cli/conversation.py +127 -0
- package/src/kagent/cli/interactive.py +841 -0
- package/src/kagent/cli/main.py +685 -0
- package/src/kagent/cli/memory.py +460 -0
- package/src/kagent/cli/pending_approval.py +169 -0
- package/src/kagent/cli/provider.py +27 -0
- package/src/kagent/cli/session_commands.py +401 -0
- package/src/kagent/cli/stdio_runtime.py +784 -0
- package/src/kagent/cli/trace.py +67 -0
- package/src/kagent/cli/ui.py +931 -0
- package/src/kagent/core/__init__.py +0 -0
- package/src/kagent/core/agent.py +296 -0
- package/src/kagent/core/faults.py +11 -0
- package/src/kagent/core/invariants.py +47 -0
- package/src/kagent/core/normalization.py +81 -0
- package/src/kagent/core/planning.py +48 -0
- package/src/kagent/core/state.py +73 -0
- package/src/kagent/core/summary.py +64 -0
- package/src/kagent/core/tools.py +196 -0
- package/src/kagent/core/trace.py +57 -0
- package/src/kagent/eval/__init__.py +11 -0
- package/src/kagent/eval/cases.py +229 -0
- package/src/kagent/eval/evaluator.py +216 -0
- package/src/kagent/integrations/__init__.py +3 -0
- package/src/kagent/integrations/audit.py +95 -0
- package/src/kagent/integrations/backends.py +131 -0
- package/src/kagent/integrations/memory.py +301 -0
- package/src/kagent/ops/__init__.py +0 -0
- package/src/kagent/ops/batch.py +156 -0
- package/src/kagent/ops/doctor.py +255 -0
- package/src/kagent/ops/metrics.py +214 -0
- package/src/kagent/ops/release_evidence.py +877 -0
- package/src/kagent/ops/release_manifest.py +142 -0
- package/src/kagent/ops/trace_replay.py +285 -0
- package/src/kagent/providers/__init__.py +7 -0
- package/src/kagent/providers/embeddings.py +187 -0
- package/src/kagent/providers/llm.py +770 -0
- package/src/kagent/py.typed +1 -0
- package/src/kagent/runtime/__init__.py +28 -0
- package/src/kagent/runtime/action_graph.py +543 -0
- package/src/kagent/runtime/agent.py +2089 -0
- package/src/kagent/runtime/approval.py +64 -0
- package/src/kagent/runtime/cancellation.py +64 -0
- package/src/kagent/runtime/checkpoint_state.py +146 -0
- package/src/kagent/runtime/checkpoint_storage.py +270 -0
- package/src/kagent/runtime/context.py +65 -0
- package/src/kagent/runtime/file_transaction.py +195 -0
- package/src/kagent/runtime/hooks.py +74 -0
- package/src/kagent/runtime/metadata.py +116 -0
- package/src/kagent/runtime/patch_checkpoints.py +385 -0
- package/src/kagent/runtime/policy.py +50 -0
- package/src/kagent/runtime/presentation.py +205 -0
- package/src/kagent/runtime/redaction.py +130 -0
- package/src/kagent/runtime/sandbox.py +331 -0
- package/src/kagent/runtime/skills.py +66 -0
- package/src/kagent/runtime/steering.py +56 -0
- package/src/kagent/runtime/steps.py +255 -0
- package/src/kagent/runtime/task_state.py +40 -0
- package/src/kagent/runtime/tools.py +3532 -0
- package/src/kagent/runtime/types.py +240 -0
- package/src/kagent/runtime/workspace.py +597 -0
- package/src/kagent/service/__init__.py +26 -0
- package/src/kagent/service/__main__.py +6 -0
- package/src/kagent/service/active_runs.py +178 -0
- package/src/kagent/service/cli.py +737 -0
- package/src/kagent/service/contract.py +2571 -0
- package/src/kagent/service/errors.py +71 -0
- package/src/kagent/service/idempotency.py +584 -0
- package/src/kagent/service/router.py +884 -0
- package/src/kagent/service/run.py +150 -0
- package/src/kagent/service/runtime.py +1915 -0
- package/src/kagent/service/runtime_approval.py +20 -0
- package/src/kagent/service/runtime_cancel.py +192 -0
- package/src/kagent/service/runtime_lifecycle.py +229 -0
- package/src/kagent/service/runtime_metadata.py +21 -0
- package/src/kagent/service/runtime_policy.py +115 -0
- package/src/kagent/service/runtime_recovery.py +573 -0
- package/src/kagent/service/runtime_resume.py +382 -0
- package/src/kagent/service/runtime_resume_claim.py +116 -0
- package/src/kagent/service/runtime_run.py +350 -0
- package/src/kagent/service/runtime_status.py +2007 -0
- package/src/kagent/service/safety.py +139 -0
- package/src/kagent/service/server.py +114 -0
- package/src/kagent/service/status.py +233 -0
- package/src/kagent/service/trace_store.py +322 -0
- package/src/kagent/service/transport.py +24 -0
- package/src/kagent/utils/__init__.py +0 -0
- package/src/kagent/utils/config_validation.py +21 -0
- package/src/kagent/utils/json_output.py +23 -0
- package/src/kagent/utils/paths.py +473 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from kagent.runtime.redaction import redact_runtime_text
|
|
7
|
+
|
|
8
|
+
RuntimeStep = Dict[str, str]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def derive_runtime_steps(payload: Dict[str, Any]) -> List[RuntimeStep]:
|
|
12
|
+
"""Project runtime trace data into a user-facing step view.
|
|
13
|
+
|
|
14
|
+
This is intentionally derived from the existing trace payload. It is not
|
|
15
|
+
execution state and must not drive policy, retries, resume, or persistence.
|
|
16
|
+
"""
|
|
17
|
+
actions = _trace_actions(payload)
|
|
18
|
+
observations_by_action_id = _observations_by_action_id(payload.get("observations"))
|
|
19
|
+
pending = payload.get("pending_approval")
|
|
20
|
+
pending_action_id = (
|
|
21
|
+
str(pending.get("id", "")).strip() if isinstance(pending, dict) else ""
|
|
22
|
+
)
|
|
23
|
+
steps: List[RuntimeStep] = []
|
|
24
|
+
for index, action in enumerate(actions, start=1):
|
|
25
|
+
action_id = str(action.get("id", "")).strip()
|
|
26
|
+
observation = observations_by_action_id.get(action_id, {})
|
|
27
|
+
state = _step_state(
|
|
28
|
+
observation.get("status"),
|
|
29
|
+
pending=bool(action_id and action_id == pending_action_id),
|
|
30
|
+
)
|
|
31
|
+
title = _step_title(action, observation, state=state)
|
|
32
|
+
detail = _step_detail(action, observation, state=state)
|
|
33
|
+
step: RuntimeStep = {
|
|
34
|
+
"index": str(index),
|
|
35
|
+
"state": state,
|
|
36
|
+
"title": redact_runtime_text(title),
|
|
37
|
+
}
|
|
38
|
+
if detail:
|
|
39
|
+
step["detail"] = redact_runtime_text(detail)
|
|
40
|
+
steps.append(step)
|
|
41
|
+
if steps:
|
|
42
|
+
return steps
|
|
43
|
+
return _planner_failure_steps(payload)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _trace_actions(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
47
|
+
actions = _planned_actions(payload.get("plans"))
|
|
48
|
+
if actions:
|
|
49
|
+
return actions
|
|
50
|
+
return _latest_actions(payload)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _planned_actions(value: Any) -> List[Dict[str, Any]]:
|
|
54
|
+
if not isinstance(value, list):
|
|
55
|
+
return []
|
|
56
|
+
actions: List[Dict[str, Any]] = []
|
|
57
|
+
action_indexes_by_id = {}
|
|
58
|
+
for plan in value:
|
|
59
|
+
if not isinstance(plan, dict):
|
|
60
|
+
continue
|
|
61
|
+
for action in _actions_from_plan(plan):
|
|
62
|
+
action_id = str(action.get("id", "")).strip()
|
|
63
|
+
if action_id and action_id in action_indexes_by_id:
|
|
64
|
+
actions[action_indexes_by_id[action_id]] = action
|
|
65
|
+
continue
|
|
66
|
+
if action_id:
|
|
67
|
+
action_indexes_by_id[action_id] = len(actions)
|
|
68
|
+
actions.append(action)
|
|
69
|
+
return actions
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _latest_actions(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
73
|
+
plan = payload.get("plan")
|
|
74
|
+
if not isinstance(plan, dict):
|
|
75
|
+
return []
|
|
76
|
+
return _actions_from_plan(plan)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _actions_from_plan(plan: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
80
|
+
actions = plan.get("actions")
|
|
81
|
+
if not isinstance(actions, list):
|
|
82
|
+
return []
|
|
83
|
+
return [
|
|
84
|
+
action
|
|
85
|
+
for action in actions
|
|
86
|
+
if isinstance(action, dict) and str(action.get("tool", "")).strip() != "note"
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _observations_by_action_id(value: Any) -> Dict[str, Dict[str, Any]]:
|
|
91
|
+
if not isinstance(value, list):
|
|
92
|
+
return {}
|
|
93
|
+
observations: Dict[str, Dict[str, Any]] = {}
|
|
94
|
+
for item in value:
|
|
95
|
+
if not isinstance(item, dict):
|
|
96
|
+
continue
|
|
97
|
+
action_id = str(item.get("action_id", "")).strip()
|
|
98
|
+
if action_id:
|
|
99
|
+
observations[action_id] = item
|
|
100
|
+
return observations
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _step_state(status: Any, *, pending: bool) -> str:
|
|
104
|
+
if pending or str(status).strip() == "requires_approval":
|
|
105
|
+
return "waiting_approval"
|
|
106
|
+
if str(status).strip() in {"ok", "done"}:
|
|
107
|
+
return "done"
|
|
108
|
+
if str(status).strip() == "failed":
|
|
109
|
+
return "failed"
|
|
110
|
+
return "pending"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _step_title(
|
|
114
|
+
action: Dict[str, Any],
|
|
115
|
+
observation: Dict[str, Any],
|
|
116
|
+
*,
|
|
117
|
+
state: str,
|
|
118
|
+
) -> str:
|
|
119
|
+
tool = str(action.get("tool", "")).strip()
|
|
120
|
+
output = observation.get("output")
|
|
121
|
+
if state == "done" and isinstance(output, dict):
|
|
122
|
+
output_title = _title_from_tool_payload(tool, output, completed=True)
|
|
123
|
+
if output_title:
|
|
124
|
+
return output_title
|
|
125
|
+
action_input = action.get("input")
|
|
126
|
+
if isinstance(action_input, dict):
|
|
127
|
+
input_title = _title_from_tool_payload(tool, action_input, completed=False)
|
|
128
|
+
if input_title:
|
|
129
|
+
return input_title
|
|
130
|
+
reason = _one_line(action.get("reason"))
|
|
131
|
+
return reason or "Complete action"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _step_detail(
|
|
135
|
+
action: Dict[str, Any],
|
|
136
|
+
observation: Dict[str, Any],
|
|
137
|
+
*,
|
|
138
|
+
state: str,
|
|
139
|
+
) -> str:
|
|
140
|
+
if state == "failed":
|
|
141
|
+
return _one_line(observation.get("error")) or _one_line(
|
|
142
|
+
observation.get("error_code")
|
|
143
|
+
)
|
|
144
|
+
if state == "waiting_approval":
|
|
145
|
+
return _one_line(action.get("reason"))
|
|
146
|
+
return ""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _title_from_tool_payload(
|
|
150
|
+
tool: str,
|
|
151
|
+
payload: Dict[str, Any],
|
|
152
|
+
*,
|
|
153
|
+
completed: bool,
|
|
154
|
+
) -> str:
|
|
155
|
+
prefix = {
|
|
156
|
+
"apply_patch": "Updated files" if completed else "Update files",
|
|
157
|
+
"artifact": "Created" if completed else "Create",
|
|
158
|
+
"http_request": "Fetched" if completed else "Fetch",
|
|
159
|
+
"list_files": "Listed files" if completed else "List files",
|
|
160
|
+
"open_app": "Opened" if completed else "Open",
|
|
161
|
+
"open_url": "Opened" if completed else "Open",
|
|
162
|
+
"read_file": "Read" if completed else "Read",
|
|
163
|
+
"revert_patch": "Restored files" if completed else "Restore files",
|
|
164
|
+
"workspace_restore": "Restored" if completed else "Restore",
|
|
165
|
+
"shell_command": "Ran command" if completed else "Run command",
|
|
166
|
+
}.get(tool, "")
|
|
167
|
+
if not prefix:
|
|
168
|
+
return ""
|
|
169
|
+
target = _tool_target(tool, payload)
|
|
170
|
+
return " ".join(part for part in [prefix, target] if part)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _tool_target(tool: str, payload: Dict[str, Any]) -> str:
|
|
174
|
+
if tool in {"open_url", "http_request"}:
|
|
175
|
+
return _short_value(payload.get("url", ""))
|
|
176
|
+
if tool == "open_app":
|
|
177
|
+
return _short_value(payload.get("application", ""))
|
|
178
|
+
if tool == "read_file":
|
|
179
|
+
return _short_value(payload.get("path", ""))
|
|
180
|
+
if tool == "revert_patch":
|
|
181
|
+
return _changed_files_label(payload.get("paths"))
|
|
182
|
+
if tool == "list_files":
|
|
183
|
+
return _short_value(payload.get("root", ""))
|
|
184
|
+
if tool == "shell_command":
|
|
185
|
+
return _short_value(payload.get("command", ""))
|
|
186
|
+
if tool == "artifact":
|
|
187
|
+
return _short_value(payload.get("title", ""))
|
|
188
|
+
if tool == "workspace_restore":
|
|
189
|
+
return _short_value(payload.get("path", ""))
|
|
190
|
+
if tool == "apply_patch":
|
|
191
|
+
return _changed_files_label(payload.get("changed_files"))
|
|
192
|
+
return ""
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _changed_files_label(value: Any) -> str:
|
|
196
|
+
if not isinstance(value, list) or not value:
|
|
197
|
+
return ""
|
|
198
|
+
paths = []
|
|
199
|
+
for item in value[:3]:
|
|
200
|
+
if isinstance(item, dict):
|
|
201
|
+
path = str(item.get("path", "")).strip()
|
|
202
|
+
if path:
|
|
203
|
+
paths.append(path)
|
|
204
|
+
else:
|
|
205
|
+
text = _short_value(item)
|
|
206
|
+
if text:
|
|
207
|
+
paths.append(text)
|
|
208
|
+
if len(value) > 3:
|
|
209
|
+
paths.append(f"+{len(value) - 3} more")
|
|
210
|
+
return ", ".join(paths)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _planner_failure_steps(payload: Dict[str, Any]) -> List[RuntimeStep]:
|
|
214
|
+
observations = payload.get("observations")
|
|
215
|
+
if not isinstance(observations, list):
|
|
216
|
+
return []
|
|
217
|
+
for observation in reversed(observations):
|
|
218
|
+
if not isinstance(observation, dict):
|
|
219
|
+
continue
|
|
220
|
+
if (
|
|
221
|
+
str(observation.get("tool", "")).strip() == "planner"
|
|
222
|
+
and str(observation.get("status", "")).strip() == "failed"
|
|
223
|
+
):
|
|
224
|
+
detail = _one_line(observation.get("error")) or _one_line(
|
|
225
|
+
observation.get("error_code")
|
|
226
|
+
)
|
|
227
|
+
step: RuntimeStep = {
|
|
228
|
+
"index": "1",
|
|
229
|
+
"state": "failed",
|
|
230
|
+
"title": "Plan request",
|
|
231
|
+
}
|
|
232
|
+
if detail:
|
|
233
|
+
step["detail"] = redact_runtime_text(detail)
|
|
234
|
+
return [step]
|
|
235
|
+
return []
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _one_line(value: Any) -> str:
|
|
239
|
+
text = str(value or "").strip()
|
|
240
|
+
return " ".join(text.split())
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _short_value(value: Any) -> str:
|
|
244
|
+
if isinstance(value, str):
|
|
245
|
+
text = value
|
|
246
|
+
elif isinstance(value, bool):
|
|
247
|
+
text = json.dumps(value)
|
|
248
|
+
elif isinstance(value, (int, float)):
|
|
249
|
+
text = str(value)
|
|
250
|
+
else:
|
|
251
|
+
text = json.dumps(value, ensure_ascii=False, sort_keys=True)
|
|
252
|
+
text = " ".join(text.strip().split())
|
|
253
|
+
if len(text) > 96:
|
|
254
|
+
return text[:93] + "..."
|
|
255
|
+
return text
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
TASK_STATES = ("pending", "in_progress", "blocked", "done", "failed", "cancelled")
|
|
6
|
+
TASK_EVENTS = ("start", "block", "resume", "complete", "fail", "cancel", "reopen")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TaskStateMachine:
|
|
10
|
+
_TRANSITIONS = {
|
|
11
|
+
("pending", "start"): "in_progress",
|
|
12
|
+
("pending", "fail"): "failed",
|
|
13
|
+
("pending", "cancel"): "cancelled",
|
|
14
|
+
("in_progress", "block"): "blocked",
|
|
15
|
+
("in_progress", "complete"): "done",
|
|
16
|
+
("in_progress", "fail"): "failed",
|
|
17
|
+
("in_progress", "cancel"): "cancelled",
|
|
18
|
+
("blocked", "resume"): "in_progress",
|
|
19
|
+
("blocked", "fail"): "failed",
|
|
20
|
+
("blocked", "cancel"): "cancelled",
|
|
21
|
+
("done", "reopen"): "in_progress",
|
|
22
|
+
("failed", "reopen"): "pending",
|
|
23
|
+
("cancelled", "reopen"): "pending",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def transition(self, state: str, event: str) -> Dict[str, str]:
|
|
27
|
+
normalized_state = str(state).strip()
|
|
28
|
+
normalized_event = str(event).strip()
|
|
29
|
+
if normalized_state not in TASK_STATES:
|
|
30
|
+
raise ValueError("unknown task state")
|
|
31
|
+
if normalized_event not in TASK_EVENTS:
|
|
32
|
+
raise ValueError("unknown task event")
|
|
33
|
+
next_state = self._TRANSITIONS.get((normalized_state, normalized_event))
|
|
34
|
+
if next_state is None:
|
|
35
|
+
raise ValueError("invalid task transition")
|
|
36
|
+
return {
|
|
37
|
+
"previous_state": normalized_state,
|
|
38
|
+
"event": normalized_event,
|
|
39
|
+
"state": next_state,
|
|
40
|
+
}
|