samagotchi 0.2.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +43 -0
- data/LICENSE +21 -0
- data/README.md +126 -0
- data/bin/chi +1140 -0
- data/docs/architecture.md +299 -0
- data/docs/cli.md +490 -0
- data/docs/configuration.md +494 -0
- data/docs/desktop.md +97 -0
- data/docs/guardrails.md +218 -0
- data/docs/hooks.md +309 -0
- data/docs/internals/background-tasks.md +26 -0
- data/docs/internals/context-telemetry.md +36 -0
- data/docs/internals/gemma4-contract.md +23 -0
- data/docs/internals/tool-guardrails.md +45 -0
- data/docs/memory.md +85 -0
- data/docs/plugins.md +819 -0
- data/docs/releasing.md +135 -0
- data/docs/sessions.md +155 -0
- data/lib/samagotchi/bridge/bounded_queue.rb +70 -0
- data/lib/samagotchi/bridge/card_store.rb +126 -0
- data/lib/samagotchi/bridge/event_id.rb +25 -0
- data/lib/samagotchi/bridge/ring_buffer.rb +63 -0
- data/lib/samagotchi/bridge/sse_writer.rb +248 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +189 -0
- data/lib/samagotchi/bridge.rb +993 -0
- data/lib/samagotchi/bridge_client/event_stream.rb +158 -0
- data/lib/samagotchi/bridge_client/sse_parser.rb +51 -0
- data/lib/samagotchi/bridge_client.rb +330 -0
- data/lib/samagotchi/bundle_needs.rb +97 -0
- data/lib/samagotchi/bundles/btw/manifest.yml +10 -0
- data/lib/samagotchi/bundles/btw/plugin.rb +100 -0
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +82 -0
- data/lib/samagotchi/bundles/guardrails/guardrails.md +14 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +8 -0
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +210 -0
- data/lib/samagotchi/bundles/known-names/known_names.md +3 -0
- data/lib/samagotchi/bundles/known-names/manifest.yml +14 -0
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +10 -0
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +158 -0
- data/lib/samagotchi/bundles/mcp/manifest.yml +11 -0
- data/lib/samagotchi/bundles/mcp/plugin.rb +631 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +149 -0
- data/lib/samagotchi/bundles/system/delegated.md +10 -0
- data/lib/samagotchi/bundles/system/identity.md +7 -0
- data/lib/samagotchi/bundles/system/manifest.yml +11 -0
- data/lib/samagotchi/bundles/system/memory_guide.md +107 -0
- data/lib/samagotchi/bundles/system/self_map.md +55 -0
- data/lib/samagotchi/cancellation_controller.rb +78 -0
- data/lib/samagotchi/client.rb +429 -0
- data/lib/samagotchi/commands/registry.rb +112 -0
- data/lib/samagotchi/config.rb +910 -0
- data/lib/samagotchi/context_note.rb +77 -0
- data/lib/samagotchi/context_quote.rb +21 -0
- data/lib/samagotchi/context_usage.rb +66 -0
- data/lib/samagotchi/context_window.rb +76 -0
- data/lib/samagotchi/debug_log.rb +110 -0
- data/lib/samagotchi/desktop/macos/App.swift +102 -0
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +201 -0
- data/lib/samagotchi/desktop/macos/Hotkey.swift +42 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +42 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +383 -0
- data/lib/samagotchi/desktop/macos.rb +255 -0
- data/lib/samagotchi/desktop.rb +21 -0
- data/lib/samagotchi/desktop_command.rb +143 -0
- data/lib/samagotchi/engine.rb +2807 -0
- data/lib/samagotchi/guardrails/approval.rb +125 -0
- data/lib/samagotchi/guardrails/approvals.rb +177 -0
- data/lib/samagotchi/guardrails/context.rb +71 -0
- data/lib/samagotchi/guardrails/gate.rb +125 -0
- data/lib/samagotchi/guardrails/load_failures.rb +46 -0
- data/lib/samagotchi/guardrails/protected_paths.rb +77 -0
- data/lib/samagotchi/guardrails/rules.rb +199 -0
- data/lib/samagotchi/guardrails/targets.rb +119 -0
- data/lib/samagotchi/guardrails/verdict.rb +134 -0
- data/lib/samagotchi/guardrails.rb +18 -0
- data/lib/samagotchi/hooks/bundle_loader.rb +158 -0
- data/lib/samagotchi/hooks/loader.rb +162 -0
- data/lib/samagotchi/hooks/registry.rb +261 -0
- data/lib/samagotchi/hooks.rb +30 -0
- data/lib/samagotchi/host_registry.rb +315 -0
- data/lib/samagotchi/idle_client.rb +147 -0
- data/lib/samagotchi/idle_recap.rb +549 -0
- data/lib/samagotchi/idle_reminders.rb +101 -0
- data/lib/samagotchi/idle_scheduler.rb +76 -0
- data/lib/samagotchi/image_store.rb +393 -0
- data/lib/samagotchi/installed_gem.rb +38 -0
- data/lib/samagotchi/kernel_loop.rb +1017 -0
- data/lib/samagotchi/launch_mode.rb +34 -0
- data/lib/samagotchi/llm/backend.rb +28 -0
- data/lib/samagotchi/llm/chat_loop.rb +450 -0
- data/lib/samagotchi/llm/errors.rb +329 -0
- data/lib/samagotchi/llm/http.rb +412 -0
- data/lib/samagotchi/llm/model_result.rb +72 -0
- data/lib/samagotchi/llm/native_backend.rb +50 -0
- data/lib/samagotchi/llm/native_tool_normalizer.rb +277 -0
- data/lib/samagotchi/llm/openai_chat.rb +403 -0
- data/lib/samagotchi/llm/usage.rb +79 -0
- data/lib/samagotchi/log.rb +200 -0
- data/lib/samagotchi/log_line.rb +127 -0
- data/lib/samagotchi/log_path.rb +31 -0
- data/lib/samagotchi/log_subscriber.rb +163 -0
- data/lib/samagotchi/memory_bundle/builder.rb +364 -0
- data/lib/samagotchi/memory_bundle/index_updater.rb +123 -0
- data/lib/samagotchi/memory_bundle/installer.rb +528 -0
- data/lib/samagotchi/memory_bundle/listing.rb +72 -0
- data/lib/samagotchi/memory_bundle/manifest.rb +225 -0
- data/lib/samagotchi/memory_bundle/merger.rb +52 -0
- data/lib/samagotchi/memory_bundle/placeholder.rb +37 -0
- data/lib/samagotchi/memory_bundle/provenance.rb +257 -0
- data/lib/samagotchi/memory_bundle/source.rb +153 -0
- data/lib/samagotchi/memory_bundle/status.rb +107 -0
- data/lib/samagotchi/memory_bundle/system_bundle.rb +161 -0
- data/lib/samagotchi/memory_bundle/uninstaller.rb +128 -0
- data/lib/samagotchi/memory_bundle.rb +17 -0
- data/lib/samagotchi/memory_paths.rb +101 -0
- data/lib/samagotchi/model_overlay.rb +53 -0
- data/lib/samagotchi/model_profile.rb +309 -0
- data/lib/samagotchi/muted_memories.rb +66 -0
- data/lib/samagotchi/note_command.rb +163 -0
- data/lib/samagotchi/output_formatter.rb +100 -0
- data/lib/samagotchi/owner_lock.rb +110 -0
- data/lib/samagotchi/pending_input_queue.rb +48 -0
- data/lib/samagotchi/plugin/api.rb +362 -0
- data/lib/samagotchi/plugin/context.rb +193 -0
- data/lib/samagotchi/plugin/loader.rb +126 -0
- data/lib/samagotchi/plugin/service.rb +117 -0
- data/lib/samagotchi/plugin/sessions.rb +150 -0
- data/lib/samagotchi/plugin/side_question.rb +60 -0
- data/lib/samagotchi/plugin/tool_result.rb +24 -0
- data/lib/samagotchi/project_scope.rb +25 -0
- data/lib/samagotchi/prompt.rb +119 -0
- data/lib/samagotchi/prompt_literal_guard.rb +70 -0
- data/lib/samagotchi/recap_store.rb +92 -0
- data/lib/samagotchi/reminder_store.rb +165 -0
- data/lib/samagotchi/self_report.rb +195 -0
- data/lib/samagotchi/send_command.rb +170 -0
- data/lib/samagotchi/served_model.rb +32 -0
- data/lib/samagotchi/session.rb +508 -0
- data/lib/samagotchi/session_commands.rb +527 -0
- data/lib/samagotchi/session_delete_command.rb +105 -0
- data/lib/samagotchi/session_manager.rb +1049 -0
- data/lib/samagotchi/session_metrics.rb +466 -0
- data/lib/samagotchi/session_observer.rb +117 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +118 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +1037 -0
- data/lib/samagotchi/terminal_ui/attached_view.rb +264 -0
- data/lib/samagotchi/terminal_ui/event_renderer.rb +192 -0
- data/lib/samagotchi/terminal_ui/formatting.rb +291 -0
- data/lib/samagotchi/terminal_ui/image_input.rb +36 -0
- data/lib/samagotchi/terminal_ui/input_support.rb +324 -0
- data/lib/samagotchi/terminal_ui/legacy_surface.rb +111 -0
- data/lib/samagotchi/terminal_ui/line_reader.rb +113 -0
- data/lib/samagotchi/terminal_ui/live_region.rb +36 -0
- data/lib/samagotchi/terminal_ui/plain_surface.rb +51 -0
- data/lib/samagotchi/terminal_ui/question_prompt.rb +153 -0
- data/lib/samagotchi/terminal_ui/question_slot.rb +131 -0
- data/lib/samagotchi/terminal_ui/reline_seam.rb +216 -0
- data/lib/samagotchi/terminal_ui/repl_input.rb +138 -0
- data/lib/samagotchi/terminal_ui/screen.rb +316 -0
- data/lib/samagotchi/terminal_ui/surface.rb +47 -0
- data/lib/samagotchi/terminal_ui/thinking_line.rb +101 -0
- data/lib/samagotchi/terminal_ui.rb +1992 -0
- data/lib/samagotchi/thinking_ticker.rb +110 -0
- data/lib/samagotchi/thought_stream_splitter.rb +149 -0
- data/lib/samagotchi/token_usage.rb +88 -0
- data/lib/samagotchi/tool_activity.rb +216 -0
- data/lib/samagotchi/tool_call_parser.rb +637 -0
- data/lib/samagotchi/tool_declarations.rb +561 -0
- data/lib/samagotchi/tool_runner.rb +211 -0
- data/lib/samagotchi/tools/args.rb +259 -0
- data/lib/samagotchi/tools/ask_user_question.rb +152 -0
- data/lib/samagotchi/tools/builtins.rb +122 -0
- data/lib/samagotchi/tools/cancel_reminder.rb +21 -0
- data/lib/samagotchi/tools/delegate.rb +167 -0
- data/lib/samagotchi/tools/delegate_result.rb +53 -0
- data/lib/samagotchi/tools/delegate_wait.rb +153 -0
- data/lib/samagotchi/tools/edit.rb +155 -0
- data/lib/samagotchi/tools/execute.rb +214 -0
- data/lib/samagotchi/tools/list_reminders.rb +20 -0
- data/lib/samagotchi/tools/list_sessions.rb +74 -0
- data/lib/samagotchi/tools/memory.rb +256 -0
- data/lib/samagotchi/tools/output_guardrails.rb +93 -0
- data/lib/samagotchi/tools/peers.rb +18 -0
- data/lib/samagotchi/tools/read.rb +182 -0
- data/lib/samagotchi/tools/register_reminder.rb +53 -0
- data/lib/samagotchi/tools/registry.rb +60 -0
- data/lib/samagotchi/tools/send_note.rb +49 -0
- data/lib/samagotchi/tools/task_create.rb +29 -0
- data/lib/samagotchi/tools/task_get.rb +39 -0
- data/lib/samagotchi/tools/task_list.rb +43 -0
- data/lib/samagotchi/tools/task_runtime.rb +311 -0
- data/lib/samagotchi/tools/task_stop.rb +29 -0
- data/lib/samagotchi/tools/task_wait.rb +104 -0
- data/lib/samagotchi/tools/tool_path.rb +18 -0
- data/lib/samagotchi/tools/web_fetch.rb +163 -0
- data/lib/samagotchi/tools/write.rb +26 -0
- data/lib/samagotchi/turn_flow.rb +242 -0
- data/lib/samagotchi/turn_note.rb +76 -0
- data/lib/samagotchi/turn_tally.rb +101 -0
- data/lib/samagotchi/version.rb +7 -0
- data/lib/samagotchi/vision_context.rb +132 -0
- data/lib/samagotchi/vision_support.rb +109 -0
- data/lib/samagotchi/web/app.rb +1349 -0
- data/lib/samagotchi/web/markdown_renderer.rb +107 -0
- data/lib/samagotchi/web/message_parts.rb +169 -0
- data/lib/samagotchi/web/public/activity.js +100 -0
- data/lib/samagotchi/web/public/annotations.js +67 -0
- data/lib/samagotchi/web/public/app.js +2382 -0
- data/lib/samagotchi/web/public/card.js +74 -0
- data/lib/samagotchi/web/public/chat_view.js +360 -0
- data/lib/samagotchi/web/public/chunk_router.js +25 -0
- data/lib/samagotchi/web/public/command_complete.js +39 -0
- data/lib/samagotchi/web/public/composer_size.js +19 -0
- data/lib/samagotchi/web/public/copy.js +142 -0
- data/lib/samagotchi/web/public/ctx.js +35 -0
- data/lib/samagotchi/web/public/data.js +256 -0
- data/lib/samagotchi/web/public/format.js +232 -0
- data/lib/samagotchi/web/public/hold.js +78 -0
- data/lib/samagotchi/web/public/images.js +77 -0
- data/lib/samagotchi/web/public/index.html +568 -0
- data/lib/samagotchi/web/public/init_row.js +60 -0
- data/lib/samagotchi/web/public/model_pick.js +23 -0
- data/lib/samagotchi/web/public/question_card.js +100 -0
- data/lib/samagotchi/web/public/route.js +17 -0
- data/lib/samagotchi/web/public/scope.js +36 -0
- data/lib/samagotchi/web/public/scroll.js +24 -0
- data/lib/samagotchi/web/public/sentences.js +88 -0
- data/lib/samagotchi/web/public/sessions_list.js +60 -0
- data/lib/samagotchi/web/public/strip.js +25 -0
- data/lib/samagotchi/web/public/tally.js +37 -0
- data/lib/samagotchi/web/public/thinking_ticker.js +79 -0
- data/lib/samagotchi/web/public/timing.js +185 -0
- data/lib/samagotchi/web/public/turn_events.js +209 -0
- data/lib/samagotchi/web/public/turn_model.js +204 -0
- data/lib/samagotchi/web/public/turn_view.js +587 -0
- data/lib/samagotchi/web/server.rb +183 -0
- data/lib/samagotchi/web/session_hub.rb +329 -0
- data/lib/samagotchi/web/session_summary.rb +85 -0
- data/lib/samagotchi/worker.rb +635 -0
- data/lib/samagotchi/worker_idle_exit.rb +87 -0
- data/lib/samagotchi.rb +12 -0
- metadata +374 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "tools/args"
|
|
5
|
+
|
|
6
|
+
module Samagotchi
|
|
7
|
+
# Tool-declaration constants, protocol constants, and guidance text.
|
|
8
|
+
#
|
|
9
|
+
# Placed in a dedicated module so that both Engine and any future UI can
|
|
10
|
+
# reference declarations without instantiating an Engine.
|
|
11
|
+
module ToolDeclarations
|
|
12
|
+
# ── Tool schemas (declared once) ──────────────────────────────────────────
|
|
13
|
+
#
|
|
14
|
+
# Every tool's name, description and JSON Schema parameters. The Gemma 4
|
|
15
|
+
# and Qwen 3.6 prompt declarations and the chat path's tools: are all
|
|
16
|
+
# rendered from this table, in this order.
|
|
17
|
+
|
|
18
|
+
TOOL_SCHEMAS = [
|
|
19
|
+
{
|
|
20
|
+
name: "execute",
|
|
21
|
+
description: "Run any shell command in a working directory (defaults to the project root) and see stdout, stderr, and exit code. Large output may be truncated to a head+tail preview with metadata.",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
command: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "The shell command to run"
|
|
28
|
+
},
|
|
29
|
+
cwd: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Optional working directory to run in (defaults to the project root). Relative paths are resolved against the project root."
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ["command"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "read",
|
|
39
|
+
description: "Read a file from disk. Large files may be truncated to a head+tail preview with metadata. Optionally pass start_line and end_line (1-based, inclusive) to read only a specific line range. An image file (png, jpeg, gif, webp) comes back as the picture itself: read it to see it.",
|
|
40
|
+
parameters: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
path: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Path to the file"
|
|
46
|
+
},
|
|
47
|
+
start_line: {
|
|
48
|
+
type: "integer",
|
|
49
|
+
description: "Optional start line (1-based, inclusive). Must be provided with end_line."
|
|
50
|
+
},
|
|
51
|
+
end_line: {
|
|
52
|
+
type: "integer",
|
|
53
|
+
description: "Optional end line (1-based, inclusive). Must be provided with start_line."
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
required: ["path"]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "write",
|
|
61
|
+
description: "Write content to a file (parent directories are created automatically)",
|
|
62
|
+
parameters: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
path: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Destination file path"
|
|
68
|
+
},
|
|
69
|
+
content: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Content to write to the file"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ["path", "content"]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "edit",
|
|
79
|
+
description: "Edit an existing file. Mode 1 (default): replace an exact old_text block with new_text, where old_text must appear exactly once. Mode 2 (range): when start_line and end_line are provided, replace that whole line range with new_text.",
|
|
80
|
+
parameters: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
path: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "File path"
|
|
86
|
+
},
|
|
87
|
+
old_text: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "Exact text to replace (required in exact-match mode)"
|
|
90
|
+
},
|
|
91
|
+
new_text: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Replacement text"
|
|
94
|
+
},
|
|
95
|
+
start_line: {
|
|
96
|
+
type: "integer",
|
|
97
|
+
description: "Optional start line (1-based, inclusive) for range mode. Must be provided with end_line."
|
|
98
|
+
},
|
|
99
|
+
end_line: {
|
|
100
|
+
type: "integer",
|
|
101
|
+
description: "Optional end line (1-based, inclusive) for range mode. Must be provided with start_line."
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
required: ["path", "new_text"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "memory_read",
|
|
109
|
+
description: "Read a memory entry from scoped memories. Scope is optional: if omitted, read falls back from project to system. Leave name blank to read indexes.",
|
|
110
|
+
parameters: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
name: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Memory entry name without .md extension; leave blank for indexes"
|
|
116
|
+
},
|
|
117
|
+
scope: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "Optional scope: project or system"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "memory_write",
|
|
126
|
+
description: "Write or update a memory entry in scoped memories. Scope is required: project or system. Use the `name` parameter for the entry name (use `name`, NOT `path` — the file tools use `path`); each scope's index.md is auto-maintained (one managed line per entry); use name \"index\" to write the index file verbatim. If the user asks to save guidance for the current model only, pass current_model_only: true to save a model-specific overlay.",
|
|
127
|
+
parameters: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
name: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Memory entry name without .md extension"
|
|
133
|
+
},
|
|
134
|
+
content: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Markdown content to write"
|
|
137
|
+
},
|
|
138
|
+
scope: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Scope to write into: project or system"
|
|
141
|
+
},
|
|
142
|
+
description: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "Optional short description appended to the managed index line"
|
|
145
|
+
},
|
|
146
|
+
current_model_only: {
|
|
147
|
+
type: "boolean",
|
|
148
|
+
description: "Set true to save this entry as a model-specific overlay for the current model only (<name>.<model>.md); it is auto-appended when the entry is read under that model and never listed in the index."
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
required: ["name", "content", "scope"]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "task_create",
|
|
156
|
+
description: "Start a background task for a long-running shell command. Returns task id and output path for later inspection.",
|
|
157
|
+
parameters: {
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {
|
|
160
|
+
command: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Shell command to run in the background"
|
|
163
|
+
},
|
|
164
|
+
cwd: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Optional working directory (defaults to project root)"
|
|
167
|
+
},
|
|
168
|
+
env: {
|
|
169
|
+
type: "string",
|
|
170
|
+
description: "Optional JSON object of environment overrides; use this for PATH or tool-specific variables"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
required: ["command"]
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "task_get",
|
|
178
|
+
description: "Get full metadata for a task by id. Use read on output_path to inspect command output.",
|
|
179
|
+
parameters: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
id: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Task id returned by task_create"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
required: ["id"]
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "task_list",
|
|
192
|
+
description: "List all background tasks in the current workspace with current status.",
|
|
193
|
+
parameters: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "task_stop",
|
|
200
|
+
description: "Stop a running background task by id.",
|
|
201
|
+
parameters: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
id: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Task id to stop"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
required: ["id"]
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "task_wait",
|
|
214
|
+
description: "Wait for a background task to finish. Polls every 0.5s until completion, a log pattern matches, or timeout. Timed-out waits include a bounded output tail.",
|
|
215
|
+
parameters: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
task_id: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "Task id returned by task_create"
|
|
221
|
+
},
|
|
222
|
+
timeout: {
|
|
223
|
+
type: "integer",
|
|
224
|
+
description: "Maximum seconds to wait (default: 600)"
|
|
225
|
+
},
|
|
226
|
+
tail_lines: {
|
|
227
|
+
type: "integer",
|
|
228
|
+
description: "Log lines to return when timing out or matching a pattern (default: 10, max: 100)"
|
|
229
|
+
},
|
|
230
|
+
done_pattern: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Optional regular expression that returns early when it matches the recent log output"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
required: ["task_id"]
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "web_fetch",
|
|
240
|
+
description: "Fetch the content of a URL (HTML or text) and return cleaned text. Handles HTML by stripping scripts/styles and extracting visible text. Returns error messages for invalid URLs or HTTP errors.",
|
|
241
|
+
parameters: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
url: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "The URL to fetch"
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required: ["url"]
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "register_reminder",
|
|
254
|
+
description: "Register a periodic reminder. The harness injects a [SYSTEM:] message into your next idle turn when the reminder is due.",
|
|
255
|
+
parameters: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
name: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Short identifier, e.g. 'api_health'"
|
|
261
|
+
},
|
|
262
|
+
description: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "What should happen when this reminder fires"
|
|
265
|
+
},
|
|
266
|
+
interval_minutes: {
|
|
267
|
+
type: "integer",
|
|
268
|
+
description: "How often to remind (1-1440 minutes, i.e. up to 1 day)"
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
required: ["name", "description", "interval_minutes"]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "cancel_reminder",
|
|
276
|
+
description: "Cancel a previously registered reminder so it stops firing.",
|
|
277
|
+
parameters: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
name: {
|
|
281
|
+
type: "string",
|
|
282
|
+
description: "The reminder identifier to cancel"
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
required: ["name"]
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "list_reminders",
|
|
290
|
+
description: "List all active registered reminders.",
|
|
291
|
+
parameters: {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "list_sessions",
|
|
298
|
+
description: "List the other chi sessions of this project (newest first, up to 20): id, whether a worker runs it, its folder and last prompt; other projects: cwd \"/\". Use it to find the session send_note should go to.",
|
|
299
|
+
parameters: {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties: {
|
|
302
|
+
cwd: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: "Only sessions in this folder or below it, in any project (optional)"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "send_note",
|
|
311
|
+
description: "Send a context note to another chi session: background information it sees on its next turn, attributed to this session. It does not start a turn there or ask it to do anything.",
|
|
312
|
+
parameters: {
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
session: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "The target session's id or a unique prefix of it (from list_sessions)"
|
|
318
|
+
},
|
|
319
|
+
text: {
|
|
320
|
+
type: "string",
|
|
321
|
+
description: "The note: what the other session should know (up to 16 KiB)"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
required: ["session", "text"]
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "delegate",
|
|
329
|
+
description: "Hand a task to a child chi session that runs in parallel in this folder and returns only its final reply (its trace stays out of this context). The child is a normal session: it shows in the lists as a child of this one and the user can attach to it. With session, send a follow-up to one of this session's children instead. Children keep running if this turn is canceled; delegate_result waits for them later. A child stays until stopped: once its work is done, stop it with execute `chi sessions stop ID` (a follow-up with session wakes it again).",
|
|
330
|
+
parameters: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
task: {
|
|
334
|
+
type: "string",
|
|
335
|
+
description: "The task, as the child's first message: self-contained, with what to report back"
|
|
336
|
+
},
|
|
337
|
+
model: {
|
|
338
|
+
type: "string",
|
|
339
|
+
description: "The child's model (a name or alias; default: this session's)"
|
|
340
|
+
},
|
|
341
|
+
session: {
|
|
342
|
+
type: "string",
|
|
343
|
+
description: "A child's id or prefix: send the task there as a follow-up instead of starting a new child"
|
|
344
|
+
},
|
|
345
|
+
wait: {
|
|
346
|
+
type: "boolean",
|
|
347
|
+
description: "Wait for the reply (default true); false returns at once so several children can run side by side"
|
|
348
|
+
},
|
|
349
|
+
timeout: {
|
|
350
|
+
type: "integer",
|
|
351
|
+
description: "Maximum seconds to wait (default: 600); a timed-out child keeps running"
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
required: ["task"]
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "delegate_result",
|
|
359
|
+
description: "Wait for a delegated child session's next reply: the child named, or this session's newest running child. Returns early if the child waits for an approval or a question the user must answer.",
|
|
360
|
+
parameters: {
|
|
361
|
+
type: "object",
|
|
362
|
+
properties: {
|
|
363
|
+
session: {
|
|
364
|
+
type: "string",
|
|
365
|
+
description: "The child's id or prefix (default: the newest running child)"
|
|
366
|
+
},
|
|
367
|
+
timeout: {
|
|
368
|
+
type: "integer",
|
|
369
|
+
description: "Maximum seconds to wait (default: 600)"
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: "ask_user_question",
|
|
376
|
+
description: "Ask the user a structured qualification question. Supports single/multi selection plus optional freeform/Other input. Prefer this over plain numbered lists when you need a clear choice. The harness renders it natively and returns {selected, freeform}.",
|
|
377
|
+
parameters: {
|
|
378
|
+
type: "object",
|
|
379
|
+
properties: {
|
|
380
|
+
question: {
|
|
381
|
+
type: "string",
|
|
382
|
+
description: "The question to ask the user"
|
|
383
|
+
},
|
|
384
|
+
options: {
|
|
385
|
+
type: "array",
|
|
386
|
+
description: "2-8 answer options as strings (labels)",
|
|
387
|
+
items: { type: "string" }
|
|
388
|
+
},
|
|
389
|
+
header: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Optional short header/title"
|
|
392
|
+
},
|
|
393
|
+
multi_select: {
|
|
394
|
+
type: "boolean",
|
|
395
|
+
description: "Allow selecting multiple options (comma-separated in TUI). Default false."
|
|
396
|
+
},
|
|
397
|
+
allow_freeform: {
|
|
398
|
+
type: "boolean",
|
|
399
|
+
description: "Allow freeform/Other text alongside selection. Default false."
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
required: ["question", "options"]
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
].freeze
|
|
406
|
+
|
|
407
|
+
# Where Gemma's declaration text says something the schema doesn't: extra
|
|
408
|
+
# description text, and explicit required:false on optional parameters.
|
|
409
|
+
GEMMA_PARAM_OVERRIDES = {
|
|
410
|
+
"edit" => { new_text: { description: "Replacement text (required)" } },
|
|
411
|
+
"task_wait" => {
|
|
412
|
+
timeout: { required: false },
|
|
413
|
+
tail_lines: { required: false },
|
|
414
|
+
done_pattern: { required: false }
|
|
415
|
+
},
|
|
416
|
+
"delegate" => {
|
|
417
|
+
model: { required: false },
|
|
418
|
+
session: { required: false },
|
|
419
|
+
wait: { required: false },
|
|
420
|
+
timeout: { required: false }
|
|
421
|
+
},
|
|
422
|
+
"delegate_result" => {
|
|
423
|
+
session: { required: false },
|
|
424
|
+
timeout: { required: false }
|
|
425
|
+
},
|
|
426
|
+
"ask_user_question" => {
|
|
427
|
+
options: { description: "2-8 answer options as strings (labels). Single/multi selection via multi_select flag." }
|
|
428
|
+
}
|
|
429
|
+
}.freeze
|
|
430
|
+
|
|
431
|
+
# What only the chat path's JSON Schemas carry (F3): enums the prompt
|
|
432
|
+
# text states in words. Adding them to TOOL_SCHEMAS would change the
|
|
433
|
+
# Qwen prompt, which renders the table as JSON.
|
|
434
|
+
CHAT_PARAM_OVERRIDES = {
|
|
435
|
+
"memory_read" => { scope: { enum: %w[project system] } },
|
|
436
|
+
"memory_write" => { scope: { enum: %w[project system] } }
|
|
437
|
+
}.freeze
|
|
438
|
+
|
|
439
|
+
GEMMA_QUOTE = '<|"|>'
|
|
440
|
+
|
|
441
|
+
module_function
|
|
442
|
+
|
|
443
|
+
# Gemma 4 <|tool>declaration:NAME{…}<tool|> blocks for every tool, one per line group.
|
|
444
|
+
# @param schemas [Array<Hash>] a Tools::Registry's schemas
|
|
445
|
+
def gemma_declarations(schemas = TOOL_SCHEMAS)
|
|
446
|
+
schemas.map { |schema| gemma_declaration(schema) }.join("\n")
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def gemma_declaration(schema)
|
|
450
|
+
q = GEMMA_QUOTE
|
|
451
|
+
required = Array(schema[:parameters][:required])
|
|
452
|
+
overrides = GEMMA_PARAM_OVERRIDES.fetch(schema[:name], {})
|
|
453
|
+
lines = schema[:parameters][:properties].map do |name, param|
|
|
454
|
+
override = overrides.fetch(name, {})
|
|
455
|
+
description = override.fetch(:description, param[:description])
|
|
456
|
+
line = " #{name}:{type:#{q}#{param[:type]}#{q}, description:#{q}#{description}#{q}"
|
|
457
|
+
req = override.fetch(:required, required.include?(name.to_s) ? true : nil)
|
|
458
|
+
line += ", required:#{req}" unless req.nil?
|
|
459
|
+
"#{line}}"
|
|
460
|
+
end
|
|
461
|
+
params = lines.empty? ? " parameters:{}" : " parameters:{\n#{lines.join(",\n")}\n }"
|
|
462
|
+
"<|tool>declaration:#{schema[:name]}{\n description:#{q}#{schema[:description]}#{q},\n#{params}\n}<tool|>"
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# The schemas for the chat path's tools: array: +schemas+ with
|
|
466
|
+
# CHAT_PARAM_OVERRIDES merged in and each tool's parameters closed
|
|
467
|
+
# (additionalProperties: false, unless a plugin's schema says), so a
|
|
468
|
+
# strict provider rejects made-up parameters instead of the tool
|
|
469
|
+
# ignoring them. A plugin's schema goes as it is, nesting and all.
|
|
470
|
+
def chat_schemas(schemas = TOOL_SCHEMAS)
|
|
471
|
+
schemas.map do |schema|
|
|
472
|
+
overrides = CHAT_PARAM_OVERRIDES.fetch(schema[:name], {})
|
|
473
|
+
properties = schema[:parameters][:properties].to_h do |name, param|
|
|
474
|
+
[name, param.merge(overrides.fetch(name, {}))]
|
|
475
|
+
end
|
|
476
|
+
parameters = schema[:parameters].merge(properties: properties)
|
|
477
|
+
parameters = parameters.merge(additionalProperties: false) unless parameters.key?(:additionalProperties)
|
|
478
|
+
schema.merge(parameters: parameters)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# The schemas the native (Gemma, Qwen) prompts declare: the built-ins
|
|
483
|
+
# as they are, and each plugin tool's flattened (.flat_schema).
|
|
484
|
+
# @param registry [Tools::Registry]
|
|
485
|
+
def native_schemas(registry)
|
|
486
|
+
registry.entries.map { |entry| entry.core? ? entry.schema : flat_schema(entry.schema) }
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# A plugin tool's schema in the shape the built-ins have, which is all
|
|
490
|
+
# the native declarations render: each parameter a type and a
|
|
491
|
+
# description. What doesn't fit goes into the description in words: an
|
|
492
|
+
# enum's values, an object's fields, a list's item type. Nested schemas
|
|
493
|
+
# and additionalProperties are dropped; the call's args are still
|
|
494
|
+
# typed by the full schema (Tools::Args).
|
|
495
|
+
def flat_schema(schema)
|
|
496
|
+
parameters = schema[:parameters] || {}
|
|
497
|
+
properties = (parameters[:properties] || {}).to_h { |name, param| [name.to_sym, flat_param(param)] }
|
|
498
|
+
{ name: schema[:name], description: schema[:description].to_s,
|
|
499
|
+
parameters: { type: "object", properties: properties, required: Array(parameters[:required]).map(&:to_s) } }
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def flat_param(param)
|
|
503
|
+
param = {} unless param.is_a?(Hash)
|
|
504
|
+
type = Tools::Args.type_of(param) || "string"
|
|
505
|
+
description = param[:description].to_s.strip
|
|
506
|
+
notes = []
|
|
507
|
+
notes << "One of: #{param[:enum].map { |value| JSON.generate(value) }.join(", ")}." if param[:enum].is_a?(Array)
|
|
508
|
+
case type
|
|
509
|
+
when "object"
|
|
510
|
+
fields = param[:properties].is_a?(Hash) ? param[:properties] : {}
|
|
511
|
+
notes << "A JSON object with #{fields.map { |name, field| "#{name} (#{field_type(field)})" }.join(", ")}." unless fields.empty?
|
|
512
|
+
when "array"
|
|
513
|
+
items = param[:items]
|
|
514
|
+
notes << "A list of #{field_type(items)} values." if items.is_a?(Hash)
|
|
515
|
+
end
|
|
516
|
+
# "Extra fields." then the words: a description without an end mark
|
|
517
|
+
# would run into them.
|
|
518
|
+
description += "." unless description.empty? || notes.empty? || description.match?(/[.!?:;]\z/)
|
|
519
|
+
{ type: type, description: [description, *notes].reject(&:empty?).join(" ") }
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def field_type(field)
|
|
523
|
+
return "any" unless field.is_a?(Hash)
|
|
524
|
+
|
|
525
|
+
type = Tools::Args.type_of(field) || "any"
|
|
526
|
+
field[:enum].is_a?(Array) ? "#{type}: #{field[:enum].map { |value| JSON.generate(value) }.join("|")}" : type
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Qwen 3.6 <tools> block: the schemas as pretty-printed JSON.
|
|
530
|
+
def qwen_declarations(schemas = TOOL_SCHEMAS)
|
|
531
|
+
"<tools>\n#{JSON.pretty_generate(schemas)}\n</tools>"
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
TOOL_CALL_HINT = 'To call a tool, emit: <|tool_call>call:NAME{param:<|"|>value<|"|>}<tool_call|>. CRITICAL: check the tool declaration for the exact parameter names and required fields!'
|
|
535
|
+
QWEN_TOOL_CALL_HINT = <<~HINT
|
|
536
|
+
To call a tool, emit XML in this exact shape:
|
|
537
|
+
<tool_call>
|
|
538
|
+
<function=NAME>
|
|
539
|
+
<parameter=KEY>
|
|
540
|
+
VALUE
|
|
541
|
+
</parameter>
|
|
542
|
+
</function>
|
|
543
|
+
</tool_call>
|
|
544
|
+
|
|
545
|
+
Required parameters must be present and use exact names from the tool declaration.
|
|
546
|
+
You may include optional natural-language reasoning before the <tool_call> block, but never after it.
|
|
547
|
+
If you call a function, end your response at </tool_call> with no suffix.
|
|
548
|
+
For write and memory_write, preserve content bytes exactly as provided by the user (no reformatting, no markdown normalization, no heading level changes).
|
|
549
|
+
HINT
|
|
550
|
+
RG_GUIDANCE = "For fast repository/text search, prefer `rg` (ripgrep) over `grep` when exploring files or text."
|
|
551
|
+
SMALL_CONTEXT_PROTOCOL = <<~PROTOCOL
|
|
552
|
+
Small-context retrieval protocol:
|
|
553
|
+
Default to targeted context before full-file reads.
|
|
554
|
+
Retrieval order:
|
|
555
|
+
1. If the user provides file:line (for example, src/app.rb:130), inspect that location first.
|
|
556
|
+
2. Use execute with rg/nl/sed to find the smallest relevant snippet.
|
|
557
|
+
3. Read a full file only when targeted snippet extraction is insufficient.
|
|
558
|
+
Avoid broad reads early in debugging; gather just enough context to decide the next step.
|
|
559
|
+
PROTOCOL
|
|
560
|
+
end
|
|
561
|
+
end
|