gobby 0.2.5__py3-none-any.whl → 0.2.7__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.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +395 -0
- gobby/agents/runner.py +8 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +385 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/install.py +4 -4
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +15 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +8 -8
- gobby/cli/installers/shared.py +175 -13
- gobby/cli/sessions.py +1 -1
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +12 -5
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +69 -91
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +9 -41
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +188 -2
- gobby/hooks/hook_manager.py +50 -4
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/skill_manager.py +130 -0
- gobby/hooks/webhooks.py +1 -1
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +98 -35
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +56 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -8
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/agents.py +31 -731
- gobby/mcp_proxy/tools/clones.py +518 -0
- gobby/mcp_proxy/tools/memory.py +3 -26
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
- gobby/mcp_proxy/tools/skills/__init__.py +616 -0
- gobby/mcp_proxy/tools/spawn_agent.py +417 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +0 -338
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +73 -285
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +24 -12
- gobby/servers/routes/admin.py +294 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +1 -1
- gobby/servers/routes/mcp/tools.py +48 -1317
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +2 -0
- gobby/sessions/transcripts/claude.py +79 -10
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +286 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +162 -201
- gobby/storage/sessions.py +116 -7
- gobby/storage/skills.py +782 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +57 -7
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +40 -5
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +46 -35
- gobby/tools/summarizer.py +91 -10
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1135
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +93 -1
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +269 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
- gobby/workflows/engine.py +13 -2
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/loader.py +19 -6
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +154 -0
- gobby/workflows/safe_evaluator.py +183 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +111 -1
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1292
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/session_messages.py +0 -1056
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
gobby/servers/websocket.py
CHANGED
|
@@ -44,7 +44,7 @@ class WebSocketConfig:
|
|
|
44
44
|
"""Configuration for WebSocket server."""
|
|
45
45
|
|
|
46
46
|
host: str = "localhost"
|
|
47
|
-
port: int =
|
|
47
|
+
port: int = 60888
|
|
48
48
|
ping_interval: int = 30 # seconds
|
|
49
49
|
ping_timeout: int = 10 # seconds
|
|
50
50
|
max_message_size: int = 2 * 1024 * 1024 # 2MB
|
|
@@ -64,7 +64,7 @@ class WebSocketServer:
|
|
|
64
64
|
|
|
65
65
|
Example:
|
|
66
66
|
```python
|
|
67
|
-
config = WebSocketConfig(host="0.0.0.0", port=
|
|
67
|
+
config = WebSocketConfig(host="0.0.0.0", port=60888)
|
|
68
68
|
|
|
69
69
|
async with WebSocketServer(config, mcp_manager) as server:
|
|
70
70
|
await server.serve_forever()
|
gobby/sessions/analyzer.py
CHANGED
|
@@ -32,6 +32,8 @@ class HandoffContext:
|
|
|
32
32
|
key_decisions: list[str] | None = None
|
|
33
33
|
active_worktree: dict[str, Any] | None = None
|
|
34
34
|
"""Worktree context if session is operating in a worktree."""
|
|
35
|
+
active_skills: list[str] = field(default_factory=list)
|
|
36
|
+
"""List of skill names that were active/injected during the session."""
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class TranscriptAnalyzer:
|
gobby/sessions/lifecycle.py
CHANGED
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
import os
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
-
from gobby.config.
|
|
14
|
+
from gobby.config.sessions import SessionLifecycleConfig
|
|
15
15
|
from gobby.sessions.transcripts.claude import ClaudeTranscriptParser
|
|
16
16
|
from gobby.sessions.transcripts.codex import CodexTranscriptParser
|
|
17
17
|
from gobby.sessions.transcripts.gemini import GeminiTranscriptParser
|
gobby/sessions/processor.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
|
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from gobby.servers.websocket import WebSocketServer
|
|
15
|
+
from gobby.storage.sessions import LocalSessionManager
|
|
15
16
|
|
|
16
17
|
from gobby.sessions.transcripts import get_parser
|
|
17
18
|
from gobby.sessions.transcripts.base import TranscriptParser
|
|
@@ -36,11 +37,13 @@ class SessionMessageProcessor:
|
|
|
36
37
|
db: DatabaseProtocol,
|
|
37
38
|
poll_interval: float = 2.0,
|
|
38
39
|
websocket_server: "WebSocketServer | None" = None,
|
|
40
|
+
session_manager: "LocalSessionManager | None" = None,
|
|
39
41
|
):
|
|
40
42
|
self.db = db
|
|
41
43
|
self.message_manager = LocalSessionMessageManager(db)
|
|
42
44
|
self.poll_interval = poll_interval
|
|
43
45
|
self.websocket_server: WebSocketServer | None = websocket_server
|
|
46
|
+
self.session_manager: LocalSessionManager | None = session_manager
|
|
44
47
|
|
|
45
48
|
# Track active sessions: session_id -> transcript_path
|
|
46
49
|
self._active_sessions: dict[str, str] = {}
|
|
@@ -196,6 +199,13 @@ class SessionMessageProcessor:
|
|
|
196
199
|
# Store messages
|
|
197
200
|
await self.message_manager.store_messages(session_id, parsed_messages)
|
|
198
201
|
|
|
202
|
+
# Extract and store model from parsed messages (if present)
|
|
203
|
+
if self.session_manager:
|
|
204
|
+
for msg in parsed_messages:
|
|
205
|
+
if msg.model:
|
|
206
|
+
self.session_manager.update_model(session_id, msg.model)
|
|
207
|
+
break # Only need the first model found
|
|
208
|
+
|
|
199
209
|
# Broadcast new messages
|
|
200
210
|
if self.websocket_server:
|
|
201
211
|
for msg in parsed_messages:
|
|
@@ -123,7 +123,11 @@ class ClaudeTranscriptParser:
|
|
|
123
123
|
|
|
124
124
|
# If no /clear found, just take the last max_turns
|
|
125
125
|
if most_recent_clear_idx is None:
|
|
126
|
-
|
|
126
|
+
result = turns[-max_turns:] if len(turns) > max_turns else turns
|
|
127
|
+
result, removed = self._validate_tool_pairing(result)
|
|
128
|
+
if removed:
|
|
129
|
+
self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
|
|
130
|
+
return result
|
|
127
131
|
|
|
128
132
|
# Start after this /clear (which is the last in any cluster since we scanned backwards)
|
|
129
133
|
start_idx = most_recent_clear_idx + 1
|
|
@@ -159,7 +163,11 @@ class ClaudeTranscriptParser:
|
|
|
159
163
|
start_idx = max(start_idx, boundary_idx + 1)
|
|
160
164
|
break
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
result = turns[start_idx:end_idx]
|
|
167
|
+
result, removed = self._validate_tool_pairing(result)
|
|
168
|
+
if removed:
|
|
169
|
+
self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
|
|
170
|
+
return result
|
|
163
171
|
|
|
164
172
|
# Segment is > max_turns, so we need to limit it
|
|
165
173
|
# Take the last max_turns from the segment
|
|
@@ -184,7 +192,11 @@ class ClaudeTranscriptParser:
|
|
|
184
192
|
start_idx = boundary_idx + 1
|
|
185
193
|
break
|
|
186
194
|
|
|
187
|
-
|
|
195
|
+
result = turns[start_idx:end_idx]
|
|
196
|
+
result, removed = self._validate_tool_pairing(result)
|
|
197
|
+
if removed:
|
|
198
|
+
self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
|
|
199
|
+
return result
|
|
188
200
|
|
|
189
201
|
def is_session_boundary(self, turn: dict[str, Any]) -> bool:
|
|
190
202
|
"""
|
|
@@ -212,6 +224,51 @@ class ClaudeTranscriptParser:
|
|
|
212
224
|
# Check for /clear command marker
|
|
213
225
|
return "<command-name>/clear</command-name>" in str(content)
|
|
214
226
|
|
|
227
|
+
def _validate_tool_pairing(
|
|
228
|
+
self, turns: list[dict[str, Any]]
|
|
229
|
+
) -> tuple[list[dict[str, Any]], list[str]]:
|
|
230
|
+
"""Remove orphaned tool_results that reference missing tool_use blocks.
|
|
231
|
+
|
|
232
|
+
This prevents Claude API validation errors when truncation cuts between
|
|
233
|
+
a tool_use and its corresponding tool_result.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
turns: List of transcript turns to validate
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Tuple of (cleaned turns, list of removed tool_use_ids)
|
|
240
|
+
"""
|
|
241
|
+
# Collect valid tool_use_ids from assistant messages
|
|
242
|
+
valid_ids: set[str] = set()
|
|
243
|
+
for turn in turns:
|
|
244
|
+
content = turn.get("message", {}).get("content", [])
|
|
245
|
+
if isinstance(content, list):
|
|
246
|
+
for block in content:
|
|
247
|
+
if isinstance(block, dict) and block.get("type") == "tool_use":
|
|
248
|
+
if tid := block.get("id"):
|
|
249
|
+
valid_ids.add(tid)
|
|
250
|
+
|
|
251
|
+
# Filter orphaned tool_results from user messages
|
|
252
|
+
cleaned: list[dict[str, Any]] = []
|
|
253
|
+
removed: list[str] = []
|
|
254
|
+
for turn in turns:
|
|
255
|
+
msg = turn.get("message", {})
|
|
256
|
+
content = msg.get("content", [])
|
|
257
|
+
if isinstance(content, list):
|
|
258
|
+
new_content: list[Any] = []
|
|
259
|
+
for block in content:
|
|
260
|
+
if isinstance(block, dict) and block.get("type") == "tool_result":
|
|
261
|
+
tid = block.get("tool_use_id")
|
|
262
|
+
if tid and tid not in valid_ids:
|
|
263
|
+
removed.append(tid)
|
|
264
|
+
continue
|
|
265
|
+
new_content.append(block)
|
|
266
|
+
if new_content != content:
|
|
267
|
+
turn = {**turn, "message": {**msg, "content": new_content}}
|
|
268
|
+
cleaned.append(turn)
|
|
269
|
+
|
|
270
|
+
return cleaned, removed
|
|
271
|
+
|
|
215
272
|
def parse_line(self, line: str, index: int) -> ParsedMessage | None:
|
|
216
273
|
"""
|
|
217
274
|
Parse a single line from the transcript JSONL.
|
|
@@ -247,6 +304,7 @@ class ClaudeTranscriptParser:
|
|
|
247
304
|
tool_name = None
|
|
248
305
|
tool_input = None
|
|
249
306
|
tool_result = None
|
|
307
|
+
tool_use_id = None
|
|
250
308
|
|
|
251
309
|
if msg_type == "user":
|
|
252
310
|
role = "user"
|
|
@@ -274,8 +332,7 @@ class ClaudeTranscriptParser:
|
|
|
274
332
|
content_type = "tool_use"
|
|
275
333
|
tool_name = block.get("name")
|
|
276
334
|
tool_input = block.get("input")
|
|
277
|
-
|
|
278
|
-
# but for now we append nothing to text content
|
|
335
|
+
tool_use_id = block.get("id")
|
|
279
336
|
|
|
280
337
|
elif block_type == "tool_result":
|
|
281
338
|
content_type = "tool_result"
|
|
@@ -291,12 +348,15 @@ class ClaudeTranscriptParser:
|
|
|
291
348
|
content_type = "tool_result"
|
|
292
349
|
tool_name = data.get("tool_name")
|
|
293
350
|
tool_result = data.get("result")
|
|
351
|
+
tool_use_id = data.get("tool_use_id")
|
|
294
352
|
content = str(tool_result)
|
|
295
353
|
|
|
296
354
|
else:
|
|
297
355
|
# Skip unknown message types (e.g., 'progress', 'error' internal events)
|
|
298
356
|
return None
|
|
299
357
|
|
|
358
|
+
usage, model = self._extract_usage(data)
|
|
359
|
+
|
|
300
360
|
return ParsedMessage(
|
|
301
361
|
index=index,
|
|
302
362
|
role=role,
|
|
@@ -307,11 +367,20 @@ class ClaudeTranscriptParser:
|
|
|
307
367
|
tool_result=tool_result,
|
|
308
368
|
timestamp=timestamp,
|
|
309
369
|
raw_json=data,
|
|
310
|
-
usage=
|
|
370
|
+
usage=usage,
|
|
371
|
+
tool_use_id=tool_use_id,
|
|
372
|
+
model=model,
|
|
311
373
|
)
|
|
312
374
|
|
|
313
|
-
def _extract_usage(self, data: dict[str, Any]) -> TokenUsage | None:
|
|
314
|
-
"""Extract token usage from message data.
|
|
375
|
+
def _extract_usage(self, data: dict[str, Any]) -> tuple[TokenUsage | None, str | None]:
|
|
376
|
+
"""Extract token usage and model from message data.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Tuple of (TokenUsage | None, model string | None)
|
|
380
|
+
"""
|
|
381
|
+
# Extract model from message object
|
|
382
|
+
model = data.get("message", {}).get("model")
|
|
383
|
+
|
|
315
384
|
# Check for top-level usage field (some formats)
|
|
316
385
|
usage_data = data.get("usage")
|
|
317
386
|
|
|
@@ -320,7 +389,7 @@ class ClaudeTranscriptParser:
|
|
|
320
389
|
usage_data = data.get("message", {}).get("usage")
|
|
321
390
|
|
|
322
391
|
if not usage_data:
|
|
323
|
-
return None
|
|
392
|
+
return None, model
|
|
324
393
|
|
|
325
394
|
# Use explicit presence checks to handle 0 correctly
|
|
326
395
|
input_tokens = (
|
|
@@ -354,7 +423,7 @@ class ClaudeTranscriptParser:
|
|
|
354
423
|
cache_creation_tokens=cache_creation_tokens,
|
|
355
424
|
cache_read_tokens=cache_read_tokens,
|
|
356
425
|
total_cost_usd=total_cost_usd,
|
|
357
|
-
)
|
|
426
|
+
), model
|
|
358
427
|
|
|
359
428
|
def parse_lines(self, lines: list[str], start_index: int = 0) -> list[ParsedMessage]:
|
|
360
429
|
"""
|
gobby/skills/__init__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Skills module for Agent Skills spec compliant skill management.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- YAML frontmatter parsing for SKILL.md files
|
|
5
|
+
- Validation against Agent Skills specification
|
|
6
|
+
- Search integration (TF-IDF + optional embeddings via UnifiedSearcher)
|
|
7
|
+
- Skill loading from filesystem, GitHub, and ZIP archives
|
|
8
|
+
- Skill updates from source
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Embedding utilities are now in gobby.search
|
|
12
|
+
from gobby.search import (
|
|
13
|
+
generate_embedding,
|
|
14
|
+
generate_embeddings,
|
|
15
|
+
is_embedding_available,
|
|
16
|
+
)
|
|
17
|
+
from gobby.skills.loader import (
|
|
18
|
+
GitHubRef,
|
|
19
|
+
SkillLoader,
|
|
20
|
+
SkillLoadError,
|
|
21
|
+
clone_skill_repo,
|
|
22
|
+
extract_zip,
|
|
23
|
+
parse_github_url,
|
|
24
|
+
)
|
|
25
|
+
from gobby.skills.manager import SkillManager
|
|
26
|
+
from gobby.skills.parser import (
|
|
27
|
+
ParsedSkill,
|
|
28
|
+
SkillParseError,
|
|
29
|
+
parse_frontmatter,
|
|
30
|
+
parse_skill_file,
|
|
31
|
+
parse_skill_text,
|
|
32
|
+
)
|
|
33
|
+
from gobby.skills.search import (
|
|
34
|
+
SearchFilters,
|
|
35
|
+
SkillSearch,
|
|
36
|
+
SkillSearchResult,
|
|
37
|
+
)
|
|
38
|
+
from gobby.skills.updater import (
|
|
39
|
+
SkillUpdateError,
|
|
40
|
+
SkillUpdater,
|
|
41
|
+
SkillUpdateResult,
|
|
42
|
+
)
|
|
43
|
+
from gobby.skills.validator import (
|
|
44
|
+
SkillValidator,
|
|
45
|
+
ValidationResult,
|
|
46
|
+
validate_skill_category,
|
|
47
|
+
validate_skill_compatibility,
|
|
48
|
+
validate_skill_description,
|
|
49
|
+
validate_skill_name,
|
|
50
|
+
validate_skill_tags,
|
|
51
|
+
validate_skill_version,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Embeddings (from gobby.search)
|
|
56
|
+
"generate_embedding",
|
|
57
|
+
"generate_embeddings",
|
|
58
|
+
"is_embedding_available",
|
|
59
|
+
# Loader
|
|
60
|
+
"GitHubRef",
|
|
61
|
+
"SkillLoadError",
|
|
62
|
+
"SkillLoader",
|
|
63
|
+
"clone_skill_repo",
|
|
64
|
+
"extract_zip",
|
|
65
|
+
"parse_github_url",
|
|
66
|
+
# Manager
|
|
67
|
+
"SkillManager",
|
|
68
|
+
# Updater
|
|
69
|
+
"SkillUpdateError",
|
|
70
|
+
"SkillUpdateResult",
|
|
71
|
+
"SkillUpdater",
|
|
72
|
+
# Parser
|
|
73
|
+
"ParsedSkill",
|
|
74
|
+
"SkillParseError",
|
|
75
|
+
"parse_frontmatter",
|
|
76
|
+
"parse_skill_file",
|
|
77
|
+
"parse_skill_text",
|
|
78
|
+
# Search
|
|
79
|
+
"SearchFilters",
|
|
80
|
+
"SkillSearch",
|
|
81
|
+
"SkillSearchResult",
|
|
82
|
+
# Validator
|
|
83
|
+
"SkillValidator",
|
|
84
|
+
"ValidationResult",
|
|
85
|
+
"validate_skill_category",
|
|
86
|
+
"validate_skill_compatibility",
|
|
87
|
+
"validate_skill_description",
|
|
88
|
+
"validate_skill_name",
|
|
89
|
+
"validate_skill_tags",
|
|
90
|
+
"validate_skill_version",
|
|
91
|
+
]
|