@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.
Files changed (117) hide show
  1. package/README.md +353 -0
  2. package/npm/bin/kagent-serve.js +6 -0
  3. package/npm/bin/kagent.js +6 -0
  4. package/npm/lib/App.js +524 -0
  5. package/npm/lib/app-state.js +224 -0
  6. package/npm/lib/approval-choice.js +25 -0
  7. package/npm/lib/commands.js +59 -0
  8. package/npm/lib/editor.js +188 -0
  9. package/npm/lib/ink-runner.js +41 -0
  10. package/npm/lib/kagent-home.js +39 -0
  11. package/npm/lib/launcher.js +221 -0
  12. package/npm/lib/protocol.js +33 -0
  13. package/npm/lib/provider-setup.js +139 -0
  14. package/npm/lib/python-runner.js +892 -0
  15. package/npm/lib/runtime-client.js +390 -0
  16. package/npm/lib/terminal-input.js +127 -0
  17. package/npm/lib/terminal-text.js +19 -0
  18. package/npm/lib/terminal-width.js +14 -0
  19. package/npm/lib/transcript.js +227 -0
  20. package/npm/lib/ui-components.js +247 -0
  21. package/npm/lib/update-manager.js +334 -0
  22. package/package.json +39 -0
  23. package/pyproject.toml +55 -0
  24. package/src/kagent/__init__.py +90 -0
  25. package/src/kagent/cli/__init__.py +5 -0
  26. package/src/kagent/cli/__main__.py +6 -0
  27. package/src/kagent/cli/commands.py +112 -0
  28. package/src/kagent/cli/conversation.py +127 -0
  29. package/src/kagent/cli/interactive.py +841 -0
  30. package/src/kagent/cli/main.py +685 -0
  31. package/src/kagent/cli/memory.py +460 -0
  32. package/src/kagent/cli/pending_approval.py +169 -0
  33. package/src/kagent/cli/provider.py +27 -0
  34. package/src/kagent/cli/session_commands.py +401 -0
  35. package/src/kagent/cli/stdio_runtime.py +784 -0
  36. package/src/kagent/cli/trace.py +67 -0
  37. package/src/kagent/cli/ui.py +931 -0
  38. package/src/kagent/core/__init__.py +0 -0
  39. package/src/kagent/core/agent.py +296 -0
  40. package/src/kagent/core/faults.py +11 -0
  41. package/src/kagent/core/invariants.py +47 -0
  42. package/src/kagent/core/normalization.py +81 -0
  43. package/src/kagent/core/planning.py +48 -0
  44. package/src/kagent/core/state.py +73 -0
  45. package/src/kagent/core/summary.py +64 -0
  46. package/src/kagent/core/tools.py +196 -0
  47. package/src/kagent/core/trace.py +57 -0
  48. package/src/kagent/eval/__init__.py +11 -0
  49. package/src/kagent/eval/cases.py +229 -0
  50. package/src/kagent/eval/evaluator.py +216 -0
  51. package/src/kagent/integrations/__init__.py +3 -0
  52. package/src/kagent/integrations/audit.py +95 -0
  53. package/src/kagent/integrations/backends.py +131 -0
  54. package/src/kagent/integrations/memory.py +301 -0
  55. package/src/kagent/ops/__init__.py +0 -0
  56. package/src/kagent/ops/batch.py +156 -0
  57. package/src/kagent/ops/doctor.py +255 -0
  58. package/src/kagent/ops/metrics.py +214 -0
  59. package/src/kagent/ops/release_evidence.py +877 -0
  60. package/src/kagent/ops/release_manifest.py +142 -0
  61. package/src/kagent/ops/trace_replay.py +285 -0
  62. package/src/kagent/providers/__init__.py +7 -0
  63. package/src/kagent/providers/embeddings.py +187 -0
  64. package/src/kagent/providers/llm.py +770 -0
  65. package/src/kagent/py.typed +1 -0
  66. package/src/kagent/runtime/__init__.py +28 -0
  67. package/src/kagent/runtime/action_graph.py +543 -0
  68. package/src/kagent/runtime/agent.py +2089 -0
  69. package/src/kagent/runtime/approval.py +64 -0
  70. package/src/kagent/runtime/cancellation.py +64 -0
  71. package/src/kagent/runtime/checkpoint_state.py +146 -0
  72. package/src/kagent/runtime/checkpoint_storage.py +270 -0
  73. package/src/kagent/runtime/context.py +65 -0
  74. package/src/kagent/runtime/file_transaction.py +195 -0
  75. package/src/kagent/runtime/hooks.py +74 -0
  76. package/src/kagent/runtime/metadata.py +116 -0
  77. package/src/kagent/runtime/patch_checkpoints.py +385 -0
  78. package/src/kagent/runtime/policy.py +50 -0
  79. package/src/kagent/runtime/presentation.py +205 -0
  80. package/src/kagent/runtime/redaction.py +130 -0
  81. package/src/kagent/runtime/sandbox.py +331 -0
  82. package/src/kagent/runtime/skills.py +66 -0
  83. package/src/kagent/runtime/steering.py +56 -0
  84. package/src/kagent/runtime/steps.py +255 -0
  85. package/src/kagent/runtime/task_state.py +40 -0
  86. package/src/kagent/runtime/tools.py +3532 -0
  87. package/src/kagent/runtime/types.py +240 -0
  88. package/src/kagent/runtime/workspace.py +597 -0
  89. package/src/kagent/service/__init__.py +26 -0
  90. package/src/kagent/service/__main__.py +6 -0
  91. package/src/kagent/service/active_runs.py +178 -0
  92. package/src/kagent/service/cli.py +737 -0
  93. package/src/kagent/service/contract.py +2571 -0
  94. package/src/kagent/service/errors.py +71 -0
  95. package/src/kagent/service/idempotency.py +584 -0
  96. package/src/kagent/service/router.py +884 -0
  97. package/src/kagent/service/run.py +150 -0
  98. package/src/kagent/service/runtime.py +1915 -0
  99. package/src/kagent/service/runtime_approval.py +20 -0
  100. package/src/kagent/service/runtime_cancel.py +192 -0
  101. package/src/kagent/service/runtime_lifecycle.py +229 -0
  102. package/src/kagent/service/runtime_metadata.py +21 -0
  103. package/src/kagent/service/runtime_policy.py +115 -0
  104. package/src/kagent/service/runtime_recovery.py +573 -0
  105. package/src/kagent/service/runtime_resume.py +382 -0
  106. package/src/kagent/service/runtime_resume_claim.py +116 -0
  107. package/src/kagent/service/runtime_run.py +350 -0
  108. package/src/kagent/service/runtime_status.py +2007 -0
  109. package/src/kagent/service/safety.py +139 -0
  110. package/src/kagent/service/server.py +114 -0
  111. package/src/kagent/service/status.py +233 -0
  112. package/src/kagent/service/trace_store.py +322 -0
  113. package/src/kagent/service/transport.py +24 -0
  114. package/src/kagent/utils/__init__.py +0 -0
  115. package/src/kagent/utils/config_validation.py +21 -0
  116. package/src/kagent/utils/json_output.py +23 -0
  117. 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
+ }