samagotchi 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +198 -1
- data/README.md +56 -4
- data/bin/chi +118 -50
- data/docs/cli.md +184 -9
- data/docs/configuration.md +333 -47
- data/docs/desktop.md +45 -4
- data/docs/guardrails.md +11 -0
- data/docs/hooks.md +208 -5
- data/docs/plugins.md +68 -2
- data/docs/releasing.md +23 -13
- data/docs/sessions.md +45 -17
- data/lib/samagotchi/answer_display.rb +95 -0
- data/lib/samagotchi/archive_store.rb +90 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +342 -0
- data/lib/samagotchi/bootstrap/probe.rb +262 -0
- data/lib/samagotchi/bootstrap_command.rb +347 -0
- data/lib/samagotchi/bridge/pending_card.rb +89 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +15 -3
- data/lib/samagotchi/bridge.rb +13 -1
- data/lib/samagotchi/bridge_client.rb +6 -2
- data/lib/samagotchi/bundles/check-in/manifest.yml +10 -0
- data/lib/samagotchi/bundles/check-in/plugin.rb +244 -0
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +531 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +14 -0
- data/lib/samagotchi/bundles/source-links/source_links.md +5 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +10 -6
- data/lib/samagotchi/bundles/system/delegated.md +6 -7
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/self_map.md +8 -2
- data/lib/samagotchi/client.rb +81 -19
- data/lib/samagotchi/commands/registry.rb +8 -0
- data/lib/samagotchi/config.rb +252 -48
- data/lib/samagotchi/desktop/macos/App.swift +12 -8
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +17 -9
- data/lib/samagotchi/desktop/macos/Images.swift +113 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +180 -25
- data/lib/samagotchi/desktop/macos.rb +59 -8
- data/lib/samagotchi/desktop_command.rb +6 -3
- data/lib/samagotchi/edit_preview.rb +82 -0
- data/lib/samagotchi/empty_answer_retry.rb +43 -0
- data/lib/samagotchi/engine.rb +434 -140
- data/lib/samagotchi/gem_update.rb +89 -0
- data/lib/samagotchi/guardrails/approval.rb +35 -4
- data/lib/samagotchi/guardrails/load_failures.rb +9 -3
- data/lib/samagotchi/guardrails/scratch_writes.rb +40 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/hooks/registry.rb +24 -5
- data/lib/samagotchi/host_registry.rb +9 -12
- data/lib/samagotchi/idle_client.rb +24 -15
- data/lib/samagotchi/idle_recap.rb +5 -1
- data/lib/samagotchi/idle_reminders.rb +2 -2
- data/lib/samagotchi/image_store.rb +10 -6
- data/lib/samagotchi/kernel_loop.rb +73 -94
- data/lib/samagotchi/live_versions.rb +59 -0
- data/lib/samagotchi/llm/api_key.rb +41 -0
- data/lib/samagotchi/llm/chat_loop.rb +132 -29
- data/lib/samagotchi/llm/errors.rb +41 -9
- data/lib/samagotchi/llm/http.rb +57 -17
- data/lib/samagotchi/llm/openai_chat.rb +17 -30
- data/lib/samagotchi/log_subscriber.rb +18 -3
- data/lib/samagotchi/memory_bundle/installer.rb +65 -63
- data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
- data/lib/samagotchi/memory_bundle/status.rb +4 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
- data/lib/samagotchi/model_profile.rb +24 -1
- data/lib/samagotchi/plugin/context.rb +22 -1
- data/lib/samagotchi/plugin/sessions.rb +3 -1
- data/lib/samagotchi/prompt.rb +4 -2
- data/lib/samagotchi/reminder_store.rb +1 -9
- data/lib/samagotchi/reply_wait.rb +126 -0
- data/lib/samagotchi/sampling_settings.rb +58 -0
- data/lib/samagotchi/self_report.rb +18 -3
- data/lib/samagotchi/send_command.rb +252 -11
- data/lib/samagotchi/session.rb +52 -11
- data/lib/samagotchi/session_archive_command.rb +107 -0
- data/lib/samagotchi/session_commands.rb +46 -7
- data/lib/samagotchi/session_manager.rb +115 -25
- data/lib/samagotchi/session_metrics.rb +222 -106
- data/lib/samagotchi/steer.rb +72 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +57 -28
- data/lib/samagotchi/terminal_ui/event_renderer.rb +21 -11
- data/lib/samagotchi/terminal_ui/formatting.rb +40 -8
- data/lib/samagotchi/terminal_ui/input_support.rb +7 -19
- data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
- data/lib/samagotchi/terminal_ui.rb +134 -247
- data/lib/samagotchi/text_diff.rb +181 -0
- data/lib/samagotchi/thinking.rb +115 -0
- data/lib/samagotchi/tool_activity.rb +3 -1
- data/lib/samagotchi/tool_runner.rb +34 -1
- data/lib/samagotchi/tools/ask_user_question.rb +41 -33
- data/lib/samagotchi/tools/builtins.rb +15 -4
- data/lib/samagotchi/tools/delegate_wait.rb +26 -69
- data/lib/samagotchi/tools/edit.rb +23 -9
- data/lib/samagotchi/tools/execute.rb +52 -14
- data/lib/samagotchi/tools/task_runtime.rb +19 -0
- data/lib/samagotchi/tools/task_wait.rb +27 -3
- data/lib/samagotchi/tools/write.rb +4 -0
- data/lib/samagotchi/turn_flow.rb +12 -2
- data/lib/samagotchi/turn_note.rb +60 -6
- data/lib/samagotchi/update_command.rb +308 -0
- data/lib/samagotchi/update_hint.rb +59 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +7 -9
- data/lib/samagotchi/web/app.rb +91 -7
- data/lib/samagotchi/web/message_parts.rb +8 -3
- data/lib/samagotchi/web/public/activity.js +13 -1
- data/lib/samagotchi/web/public/annotate_presets.js +26 -0
- data/lib/samagotchi/web/public/annotations.js +13 -0
- data/lib/samagotchi/web/public/app.js +472 -111
- data/lib/samagotchi/web/public/card.js +5 -3
- data/lib/samagotchi/web/public/chat_view.js +13 -1
- data/lib/samagotchi/web/public/copy.js +20 -4
- data/lib/samagotchi/web/public/ctx.js +15 -0
- data/lib/samagotchi/web/public/data.js +23 -6
- data/lib/samagotchi/web/public/diff_view.js +58 -0
- data/lib/samagotchi/web/public/format.js +9 -0
- data/lib/samagotchi/web/public/index.html +60 -3
- data/lib/samagotchi/web/public/notify.js +175 -0
- data/lib/samagotchi/web/public/question_card.js +5 -2
- data/lib/samagotchi/web/public/sessions_list.js +7 -0
- data/lib/samagotchi/web/public/timing.js +39 -14
- data/lib/samagotchi/web/public/turn_events.js +75 -5
- data/lib/samagotchi/web/public/turn_view.js +49 -8
- data/lib/samagotchi/web/server.rb +8 -4
- data/lib/samagotchi/web/session_hub.rb +2 -1
- data/lib/samagotchi/web/session_summary.rb +24 -1
- data/lib/samagotchi/worker.rb +16 -4
- metadata +31 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "text_diff"
|
|
4
|
+
require_relative "tools/edit"
|
|
5
|
+
require_relative "tools/tool_path"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
# What chi's own edit/write tools change, as a TextDiff. Used twice:
|
|
9
|
+
#
|
|
10
|
+
# - EditPreview.for(call): a dry run before the call runs (the approval
|
|
11
|
+
# card). It reads the file once and never writes.
|
|
12
|
+
# - EditPreview.snapshot + change: the file before and after the call ran
|
|
13
|
+
# (the tool row), so the row shows what really happened.
|
|
14
|
+
#
|
|
15
|
+
# A diff is {text:, added:, removed:, truncated:, new_file:}; a preview can
|
|
16
|
+
# also be {error: "old text not found in …"} (the call would fail) or
|
|
17
|
+
# {skipped: "binary file" | "file over 1 MB"}.
|
|
18
|
+
module EditPreview
|
|
19
|
+
TOOLS = %w[edit write].freeze
|
|
20
|
+
MAX_FILE_BYTES = 1024 * 1024
|
|
21
|
+
SNIFF_BYTES = 8 * 1024
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def tool?(name) = TOOLS.include?(name.to_s)
|
|
26
|
+
|
|
27
|
+
# @return [Hash, nil] nil for a tool that isn't edit/write
|
|
28
|
+
def for(call)
|
|
29
|
+
return nil unless tool?(call[:name])
|
|
30
|
+
|
|
31
|
+
path = Tools::ToolPath.normalize(call[:path])
|
|
32
|
+
before = snapshot(path)
|
|
33
|
+
return before if before.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
if call[:name].to_s == "edit"
|
|
36
|
+
result = Tools::Edit.apply(call[:content], path: path, start_line: call[:start_line],
|
|
37
|
+
end_line: call[:end_line], read: ->(_) { before })
|
|
38
|
+
return { error: result.delete_prefix("Error: ") } if result.is_a?(String)
|
|
39
|
+
|
|
40
|
+
after = result.first
|
|
41
|
+
else
|
|
42
|
+
after = call[:content]
|
|
43
|
+
return { error: "missing content" } unless after.is_a?(String)
|
|
44
|
+
|
|
45
|
+
skip = skip_reason(after)
|
|
46
|
+
return { skipped: skip } if skip
|
|
47
|
+
end
|
|
48
|
+
# Unlike change, a no-op still gets a (0/0) diff: nil means "not edit/write".
|
|
49
|
+
TextDiff.unified(before.to_s, after).merge(new_file: before.nil?)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The file as the tools read it: nil when it doesn't exist, the text, or
|
|
53
|
+
# {skipped:} / {error:} when it can't be diffed.
|
|
54
|
+
def snapshot(path)
|
|
55
|
+
return nil unless File.file?(path)
|
|
56
|
+
return { skipped: "file over 1 MB" } if File.size(path) > MAX_FILE_BYTES
|
|
57
|
+
|
|
58
|
+
text = File.read(path)
|
|
59
|
+
skip = skip_reason(text)
|
|
60
|
+
skip ? { skipped: skip } : text
|
|
61
|
+
rescue SystemCallError, IOError => e
|
|
62
|
+
{ error: e.message }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param before [String, nil] nil for a file that didn't exist
|
|
66
|
+
# @return [Hash, nil] nil when there's nothing to show (same text, or
|
|
67
|
+
# the after side isn't a readable text file)
|
|
68
|
+
def change(before, after)
|
|
69
|
+
return nil unless after.is_a?(String)
|
|
70
|
+
return nil unless before.nil? || before.is_a?(String)
|
|
71
|
+
return nil if before == after
|
|
72
|
+
|
|
73
|
+
TextDiff.unified(before.to_s, after).merge(new_file: before.nil?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def skip_reason(text)
|
|
77
|
+
return "file over 1 MB" if text.bytesize > MAX_FILE_BYTES
|
|
78
|
+
|
|
79
|
+
"binary file" if text.byteslice(0, SNIFF_BYTES).include?("\0")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "config"
|
|
4
|
+
require_relative "turn_note"
|
|
5
|
+
|
|
6
|
+
module Samagotchi
|
|
7
|
+
# Asking the model again, in the same turn, when a generation ends with no
|
|
8
|
+
# visible text and no tool calls (thinking only, or nothing): a hidden
|
|
9
|
+
# nudge (TurnNote.empty_retry), then another generation. Both loops use
|
|
10
|
+
# it; the Engine's TurnNote.empty is what's left when the retries run out.
|
|
11
|
+
module EmptyAnswerRetry
|
|
12
|
+
MAX = 3
|
|
13
|
+
# Greedy decoding on a near-identical prompt would likely loop again:
|
|
14
|
+
# the retry runs at this temperature unless one is configured.
|
|
15
|
+
TEMPERATURE = 0.6
|
|
16
|
+
# A generation cut at the provider's output cap (finish_reason length)
|
|
17
|
+
# is retried unless the context is this full.
|
|
18
|
+
CONTEXT_FULL_RATIO = 0.9
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# retry.empty_answer, 0..MAX.
|
|
23
|
+
def limit
|
|
24
|
+
Config.get("retry.empty_answer").to_i.clamp(0, MAX)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The request options of a retry generation: the turn's sampling, with
|
|
30
|
+
# TEMPERATURE when it sets none (a configured nil keeps "don't send").
|
|
31
|
+
def sampling(base)
|
|
32
|
+
base = base || {}
|
|
33
|
+
base.key?(:temperature) ? base : base.merge(temperature: TEMPERATURE)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The prompt filled the window (a length stop is then no loop to break).
|
|
37
|
+
def context_full?(used_tokens, window_tokens)
|
|
38
|
+
return false unless used_tokens.to_i.positive? && window_tokens.to_i.positive?
|
|
39
|
+
|
|
40
|
+
used_tokens.to_f / window_tokens >= CONTEXT_FULL_RATIO
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|