samagotchi 0.53.0 → 0.55.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 +52 -1
- data/README.md +1 -1
- data/bin/chi +3 -2
- data/docs/architecture.md +94 -54
- data/docs/configuration.md +30 -2
- data/docs/context.md +104 -9
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bundles.md +73 -0
- data/docs/internals/context-telemetry.md +27 -6
- data/docs/internals/engine.md +55 -24
- data/docs/internals/tool-guardrails.md +8 -6
- data/docs/plugins.md +2 -1
- data/docs/sessions.md +3 -3
- data/docs/testing.md +23 -3
- data/lib/samagotchi/bridge/turn_accumulator.rb +8 -6
- data/lib/samagotchi/bundle_command.rb +31 -36
- data/lib/samagotchi/bundle_needs.rb +4 -5
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +2 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/child_move.rb +6 -4
- data/lib/samagotchi/config.rb +2 -1
- data/lib/samagotchi/context_command.rb +1 -1
- data/lib/samagotchi/context_providers.rb +11 -14
- data/lib/samagotchi/context_status.rb +10 -18
- data/lib/samagotchi/engine.rb +117 -134
- data/lib/samagotchi/extension_load.rb +102 -0
- data/lib/samagotchi/file_ref.rb +59 -0
- data/lib/samagotchi/guardrail_wiring.rb +16 -16
- data/lib/samagotchi/kernel_loop.rb +5 -3
- data/lib/samagotchi/llm/chat_loop.rb +5 -4
- data/lib/samagotchi/memory_bundle/asset_installer.rb +302 -0
- data/lib/samagotchi/memory_bundle/builder.rb +170 -178
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +90 -0
- data/lib/samagotchi/memory_bundle/installer.rb +252 -488
- data/lib/samagotchi/memory_bundle/listing.rb +6 -6
- data/lib/samagotchi/memory_bundle/manifest.rb +4 -2
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +27 -0
- data/lib/samagotchi/memory_bundle/profile.rb +21 -21
- data/lib/samagotchi/memory_bundle/provenance.rb +38 -40
- data/lib/samagotchi/memory_bundle/shipped_update.rb +24 -26
- data/lib/samagotchi/memory_bundle/status.rb +22 -24
- data/lib/samagotchi/memory_bundle/system_bundle.rb +4 -5
- data/lib/samagotchi/memory_bundle/uninstaller.rb +8 -10
- data/lib/samagotchi/plugin/loader.rb +8 -8
- data/lib/samagotchi/self_report.rb +2 -2
- data/lib/samagotchi/send_command.rb +1 -1
- data/lib/samagotchi/session_continue.rb +275 -0
- data/lib/samagotchi/session_manager.rb +9 -253
- data/lib/samagotchi/session_metrics.rb +3 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -1
- data/lib/samagotchi/terminal_ui.rb +12 -2
- data/lib/samagotchi/tool_activity.rb +84 -14
- data/lib/samagotchi/tool_row_fields.rb +46 -0
- data/lib/samagotchi/tool_runner.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +26 -3
- data/lib/samagotchi/web/capabilities.rb +13 -9
- data/lib/samagotchi/web/editor.rb +38 -0
- data/lib/samagotchi/web/message_parts.rb +5 -10
- data/lib/samagotchi/web/public/activity.js +35 -18
- data/lib/samagotchi/web/public/app.js +240 -1136
- data/lib/samagotchi/web/public/commands.js +236 -0
- data/lib/samagotchi/web/public/composer.js +101 -0
- data/lib/samagotchi/web/public/ctx.js +18 -4
- data/lib/samagotchi/web/public/diff_view.js +30 -4
- data/lib/samagotchi/web/public/index.html +9 -2
- data/lib/samagotchi/web/public/info_bar.js +78 -0
- data/lib/samagotchi/web/public/lists_view.js +725 -0
- data/lib/samagotchi/web/public/llm_context_chip.js +9 -6
- data/lib/samagotchi/web/public/question_card.js +9 -1
- data/lib/samagotchi/web/public/question_cards.js +7 -4
- data/lib/samagotchi/web/public/refs.js +134 -0
- data/lib/samagotchi/web/public/restore.js +81 -0
- data/lib/samagotchi/web/public/session_state.js +52 -0
- data/lib/samagotchi/web/public/stream_handlers.js +324 -0
- data/lib/samagotchi/web/public/thinking_chip.js +6 -7
- data/lib/samagotchi/web/public/timing.js +8 -2
- data/lib/samagotchi/web/public/turn_events.js +17 -258
- data/lib/samagotchi/web/public/turn_html.js +15 -7
- data/lib/samagotchi/web/server.rb +4 -4
- metadata +17 -2
- data/lib/samagotchi/web/public/stable_html.js +0 -18
|
@@ -293,8 +293,8 @@ module Samagotchi
|
|
|
293
293
|
@stderr.puts "Usage: chi bundle uninstall <bundle> [--scope system|project] [--force]"
|
|
294
294
|
return CLI::Exit::USAGE
|
|
295
295
|
end
|
|
296
|
-
|
|
297
|
-
return uninstall_profile(bundle_name, force: force) if Samagotchi::MemoryBundle::Profile.installed_meta?(bundle_name,
|
|
296
|
+
record = Samagotchi::MemoryBundle::Provenance.new(name: bundle_name).record
|
|
297
|
+
return uninstall_profile(bundle_name, force: force) if Samagotchi::MemoryBundle::Profile.installed_meta?(bundle_name, record)
|
|
298
298
|
|
|
299
299
|
uninstaller = Samagotchi::MemoryBundle::Uninstaller.new(name: bundle_name, scope: scope, force: force)
|
|
300
300
|
begin
|
|
@@ -370,7 +370,7 @@ module Samagotchi
|
|
|
370
370
|
@stdout.puts "Bundle '#{name}' not installed."
|
|
371
371
|
return 0
|
|
372
372
|
end
|
|
373
|
-
@stdout.puts "Bundle: #{name} v#{st[:provenance]
|
|
373
|
+
@stdout.puts "Bundle: #{name} v#{st[:provenance].version} scope=#{status_scope(st)} installed=#{st[:provenance].installed_at}"
|
|
374
374
|
if st[:scope_error]
|
|
375
375
|
@stdout.puts "Target: (unknown scope: this chi can't resolve it; upgrade chi or reinstall the bundle)"
|
|
376
376
|
else
|
|
@@ -380,7 +380,7 @@ module Samagotchi
|
|
|
380
380
|
next @stdout.puts(" #{k}: unchecked") if info[:unchecked]
|
|
381
381
|
|
|
382
382
|
mods = []
|
|
383
|
-
mods << "conflict (kept your edits over v#{st[:provenance]
|
|
383
|
+
mods << "conflict (kept your edits over v#{st[:provenance].version}: chi bundle diff #{name} #{k})" if info[:conflict]
|
|
384
384
|
mods << "modified" if info[:modified]
|
|
385
385
|
mods << "missing" if info[:missing]
|
|
386
386
|
mods << "no-index" unless info[:index_present] || info[:overlay]
|
|
@@ -389,20 +389,19 @@ module Samagotchi
|
|
|
389
389
|
@stdout.puts " #{k}: #{label}"
|
|
390
390
|
end
|
|
391
391
|
# Hooks
|
|
392
|
-
hooks = st[:provenance]
|
|
393
|
-
trust = st[:provenance]
|
|
394
|
-
commit = st[:provenance]
|
|
392
|
+
hooks = st[:provenance].hooks
|
|
393
|
+
trust = st[:provenance].trust_level || "experimental"
|
|
394
|
+
commit = st[:provenance].source_commit
|
|
395
395
|
@stdout.puts " trust_level: #{trust}" if hooks.any? || trust
|
|
396
396
|
@stdout.puts " source_commit: #{commit}" if commit
|
|
397
397
|
if hooks.any?
|
|
398
398
|
@stdout.puts " Hooks (#{hooks.size}):"
|
|
399
|
-
hooks.each do |hk,
|
|
400
|
-
|
|
401
|
-
hook_path = File.join(Samagotchi::MemoryBundle::Provenance.new(name: name).hooks_dir, hk.to_s)
|
|
399
|
+
hooks.each do |hk, hook|
|
|
400
|
+
hook_path = File.join(Samagotchi::MemoryBundle::Provenance.new(name: name).hooks_dir, hk)
|
|
402
401
|
exists = File.exist?(hook_path) ? "ok" : "missing"
|
|
403
402
|
@stdout.puts " #{hk}: event=#{hook.event} on_error=#{hook.on_error} priority=#{hook.priority} [#{exists}]"
|
|
404
403
|
end
|
|
405
|
-
@stdout.puts " requires_chi: #{st[:provenance]
|
|
404
|
+
@stdout.puts " requires_chi: #{st[:provenance].requires_chi}" if st[:hooks_requires_failure]
|
|
406
405
|
@stdout.puts " not loaded: #{st[:hooks_requires_failure]}" if st[:hooks_requires_failure]
|
|
407
406
|
end
|
|
408
407
|
if (plugin = st[:plugin])
|
|
@@ -412,28 +411,28 @@ module Samagotchi
|
|
|
412
411
|
@stdout.puts " not loaded: #{plugin[:requires_failure]}" if plugin[:requires_failure]
|
|
413
412
|
end
|
|
414
413
|
st[:needs].each { |need| @stdout.puts " #{Samagotchi::MemoryBundle::Status.need_line(need)}" }
|
|
415
|
-
(st[:provenance]
|
|
416
|
-
state = Samagotchi::MemoryBundle::Provenance.new(name: member
|
|
414
|
+
(st[:provenance].includes || []).each do |member|
|
|
415
|
+
state = Samagotchi::MemoryBundle::Provenance.new(name: member).installed? ? "installed" : "not installed: chi bundle install #{member}"
|
|
417
416
|
@stdout.puts " includes #{member} [#{state}]"
|
|
418
417
|
end
|
|
419
418
|
else
|
|
420
419
|
bundles = Samagotchi::MemoryBundle::Provenance.each_installed.to_a
|
|
421
420
|
@stdout.puts "No installed bundles." if bundles.empty?
|
|
422
|
-
bundles.each do |bname,
|
|
423
|
-
next @stdout.puts(" #{bname} (manifest.json unreadable)") if
|
|
421
|
+
bundles.each do |bname, record|
|
|
422
|
+
next @stdout.puts(" #{bname} (manifest.json unreadable)") if record.error?
|
|
424
423
|
|
|
425
424
|
st = Samagotchi::MemoryBundle::Status.bundle_status(bname)
|
|
426
425
|
mods = st[:files].values.count { |v| v[:modified] || v[:missing] }
|
|
427
|
-
hooks_count =
|
|
426
|
+
hooks_count = st[:provenance].hooks.size
|
|
428
427
|
hook_info = hooks_count > 0 ? " hooks=#{hooks_count}" : ""
|
|
429
428
|
plugin = st[:plugin]
|
|
430
429
|
mods += 1 if plugin && (plugin[:state] != "ok" || plugin[:requires_failure])
|
|
431
430
|
mods += 1 if st[:hooks_requires_failure]
|
|
432
431
|
mods += 1 if st[:scope_error]
|
|
433
432
|
plugin_info = plugin ? " plugin=#{plugin[:file]}" : ""
|
|
434
|
-
includes = st[:provenance]
|
|
433
|
+
includes = st[:provenance].includes
|
|
435
434
|
includes_info = includes ? " includes=#{includes.join(",")}" : ""
|
|
436
|
-
@stdout.puts " #{bname} v#{st[:provenance]
|
|
435
|
+
@stdout.puts " #{bname} v#{st[:provenance].version} scope=#{status_scope(st)} files=#{st[:files].size}#{hook_info}#{plugin_info}#{includes_info} issues=#{mods}"
|
|
437
436
|
end
|
|
438
437
|
end
|
|
439
438
|
0
|
|
@@ -450,19 +449,19 @@ module Samagotchi
|
|
|
450
449
|
@stderr.puts "Usage: chi bundle diff <bundle> [file]"; return CLI::Exit::USAGE
|
|
451
450
|
end
|
|
452
451
|
prov = Samagotchi::MemoryBundle::Provenance.new(name: bname)
|
|
453
|
-
|
|
454
|
-
unless
|
|
452
|
+
record = prov.record
|
|
453
|
+
unless record
|
|
455
454
|
@stderr.puts "Bundle '#{bname}' not installed"; return 1
|
|
456
455
|
end
|
|
457
|
-
scope =
|
|
456
|
+
scope = record.effective_scope
|
|
458
457
|
begin
|
|
459
458
|
target_dir = Samagotchi::MemoryPaths.scope_dir!(scope)
|
|
460
459
|
rescue ArgumentError => e
|
|
461
460
|
@stderr.puts "Bundle '#{bname}': #{e.message}"; return 1
|
|
462
461
|
end
|
|
463
|
-
files =
|
|
464
|
-
hooks =
|
|
465
|
-
plugin_path = prov.plugin_path(
|
|
462
|
+
files = record.files
|
|
463
|
+
hooks = record.hooks
|
|
464
|
+
plugin_path = prov.plugin_path(record)
|
|
466
465
|
plugin_file = plugin_path && File.basename(plugin_path)
|
|
467
466
|
show_plugin = lambda do
|
|
468
467
|
@stdout.puts "=== plugin/#{plugin_file} ==="
|
|
@@ -471,11 +470,11 @@ module Samagotchi
|
|
|
471
470
|
@stdout.puts File.exist?(base) ? File.read(base) : "(no base)"
|
|
472
471
|
@stdout.puts "--- current (on-disk) ---"
|
|
473
472
|
@stdout.puts File.exist?(plugin_path) ? File.read(plugin_path) : "(missing)"
|
|
474
|
-
@stdout.puts "--- metadata: sha256=#{
|
|
473
|
+
@stdout.puts "--- metadata: sha256=#{record.plugin.sha256}#{" requires_chi=#{record.requires_chi}" if record.requires_chi}"
|
|
475
474
|
end
|
|
476
475
|
if file_arg
|
|
477
476
|
# Try file first, then hook
|
|
478
|
-
if
|
|
477
|
+
if record.owns?(file_arg)
|
|
479
478
|
base = prov.base_path(file_arg)
|
|
480
479
|
cur = File.join(target_dir, file_arg)
|
|
481
480
|
@stdout.puts "=== #{file_arg} ==="
|
|
@@ -483,7 +482,7 @@ module Samagotchi
|
|
|
483
482
|
@stdout.puts File.exist?(base) ? File.read(base) : "(no base)"
|
|
484
483
|
@stdout.puts "--- current (on-disk) ---"
|
|
485
484
|
@stdout.puts File.exist?(cur) ? File.read(cur) : "(missing)"
|
|
486
|
-
elsif hooks.key?(file_arg
|
|
485
|
+
elsif hooks.key?(file_arg)
|
|
487
486
|
base = prov.base_path(file_arg)
|
|
488
487
|
cur = File.join(prov.hooks_dir, file_arg)
|
|
489
488
|
@stdout.puts "=== hooks/#{file_arg} ==="
|
|
@@ -492,8 +491,7 @@ module Samagotchi
|
|
|
492
491
|
@stdout.puts "--- current (on-disk) ---"
|
|
493
492
|
@stdout.puts File.exist?(cur) ? File.read(cur) : "(missing)"
|
|
494
493
|
# Show hook metadata
|
|
495
|
-
|
|
496
|
-
@stdout.puts hook_metadata_line(meta) if meta
|
|
494
|
+
@stdout.puts hook_metadata_line(hooks[file_arg])
|
|
497
495
|
elsif plugin_file && [plugin_file, "plugin/#{plugin_file}"].include?(file_arg)
|
|
498
496
|
show_plugin.call
|
|
499
497
|
else
|
|
@@ -502,8 +500,7 @@ module Samagotchi
|
|
|
502
500
|
end
|
|
503
501
|
@stdout.puts ""
|
|
504
502
|
else
|
|
505
|
-
|
|
506
|
-
keys.each do |k|
|
|
503
|
+
files.each_key do |k|
|
|
507
504
|
base = prov.base_path(k)
|
|
508
505
|
cur = File.join(target_dir, k)
|
|
509
506
|
@stdout.puts "=== #{k} ==="
|
|
@@ -514,7 +511,7 @@ module Samagotchi
|
|
|
514
511
|
@stdout.puts ""
|
|
515
512
|
end
|
|
516
513
|
# Hooks diff
|
|
517
|
-
hooks.
|
|
514
|
+
hooks.each do |k, hook|
|
|
518
515
|
base = prov.base_path(k)
|
|
519
516
|
cur = File.join(prov.hooks_dir, k)
|
|
520
517
|
@stdout.puts "=== hooks/#{k} ==="
|
|
@@ -522,8 +519,7 @@ module Samagotchi
|
|
|
522
519
|
@stdout.puts File.exist?(base) ? File.read(base) : "(no base)"
|
|
523
520
|
@stdout.puts "--- current (on-disk) ---"
|
|
524
521
|
@stdout.puts File.exist?(cur) ? File.read(cur) : "(missing)"
|
|
525
|
-
|
|
526
|
-
@stdout.puts hook_metadata_line(meta) if meta
|
|
522
|
+
@stdout.puts hook_metadata_line(hook)
|
|
527
523
|
@stdout.puts ""
|
|
528
524
|
end
|
|
529
525
|
if plugin_file
|
|
@@ -577,8 +573,7 @@ module Samagotchi
|
|
|
577
573
|
"includes=#{here.join(",")}#{" left out=#{gone.join(",")}" unless gone.empty?}"
|
|
578
574
|
end
|
|
579
575
|
|
|
580
|
-
def hook_metadata_line(
|
|
581
|
-
hook = Samagotchi::MemoryBundle::BundleHook.parse(meta)
|
|
576
|
+
def hook_metadata_line(hook)
|
|
582
577
|
"--- metadata: event=#{hook.event} on_error=#{hook.on_error} priority=#{hook.priority} sha256=#{hook.sha256}"
|
|
583
578
|
end
|
|
584
579
|
|
|
@@ -65,19 +65,18 @@ module Samagotchi
|
|
|
65
65
|
# entry name → marker, for the installed bundles of +scope+ with a
|
|
66
66
|
# missing need. A manifest.json or needs: that doesn't parse is skipped.
|
|
67
67
|
def self.entry_markers(scope, path:, bundles_dir:)
|
|
68
|
-
MemoryBundle::Provenance.each_installed(dir: bundles_dir).each_with_object({}) do |(_name,
|
|
69
|
-
next if
|
|
70
|
-
next unless data[:needs].is_a?(Array) && !data[:needs].empty?
|
|
68
|
+
MemoryBundle::Provenance.each_installed(dir: bundles_dir).each_with_object({}) do |(_name, bundle), acc|
|
|
69
|
+
next if bundle.error? || bundle.effective_scope != scope.to_s || !bundle.needs?
|
|
71
70
|
|
|
72
71
|
needs = begin
|
|
73
|
-
MemoryBundle::Manifest.parse_needs(
|
|
72
|
+
MemoryBundle::Manifest.parse_needs(bundle.needs)
|
|
74
73
|
rescue MemoryBundle::Manifest::ValidationError
|
|
75
74
|
next
|
|
76
75
|
end
|
|
77
76
|
marker = marker(missing(needs, path: path))
|
|
78
77
|
next unless marker
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
bundle.files.each_key do |key|
|
|
81
80
|
name = MutedMemories.normalize(key)
|
|
82
81
|
acc[name] ||= marker if name
|
|
83
82
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skills
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.7
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Skills (skill_<name> memories, the steps of a task done together): /skill save, list, show and diff; older versions kept with a short diff line on every update; a nudge when a followed skill's step fails"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:42f535eb1dd616bdf2e027031538c9419767c531e2b52e2baa8f1cc83accaba7
|
|
10
10
|
requires_chi: ">= 0.11.0"
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
# fixing the skill: in a turn that read a skill (memory_read of a skill_*
|
|
26
26
|
# name, or a read of its file), the first failing tool call after it (an
|
|
27
27
|
# execute with "exit: N", N ≠ 0, or with no exit line an Error: line near
|
|
28
|
-
# the top; any tool's "[tool] Error: …"
|
|
28
|
+
# the top; any tool's "[tool] Error: …"; not a grep that found nothing,
|
|
29
|
+
# which core counts ok) steers the model once to find out
|
|
29
30
|
# why and fix the skill, unless a skill read was changed already (by any
|
|
30
31
|
# tool, see above). A call a guardrail or the user denied (its first line
|
|
31
32
|
# "[tool] Error: blocked by guardrail: …", "… denied by guardrail (…): …" or
|
|
@@ -18,7 +18,7 @@ Single registry `Samagotchi::Config` (`Config::ENTRIES` in `lib/samagotchi/confi
|
|
|
18
18
|
|
|
19
19
|
Sections forbid `_`/`-` (`SECTION_RE` `/\A[a-z0-9]+\z/`); leaves keep `snake_case` in YAML (`base_url`) and become kebab in CLI (`base-url`) via registry derivation — no generic string split, registry lookup avoids flat vs nested collision.
|
|
20
20
|
|
|
21
|
-
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/transport/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.shared/keep_empty/max_children` (env and config only), `log.file/disable`, `status.line`, `context.status/window_tokens/chars_per_token/status_thresholds
|
|
21
|
+
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/transport/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.shared/keep_empty/max_children` (env and config only), `log.file/disable`, `status.line`, `context.status/window_tokens/chars_per_token/status_thresholds`, `memory.index_warn_tokens`, `thinking.turn_preamble`, `default.max_tokens`, `max_tool_output_chars`, `retry.max/base_delay/max_delay`, `read.*`, `execute.*`, `web.port/host`, `no_interrupt`, `no_default_input` etc. (`Config::ENTRIES`; `expose` says which of env/config/cli each takes). Precedence is `CLI > ENV > file > default`.
|
|
22
22
|
|
|
23
23
|
Example `config.yml` (new nested form, preferred):
|
|
24
24
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.55.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
7
|
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
8
|
self_map.md: sha256:dfc19266259a9af1e0d769b7e0f67996b740b0c9921fdf3dadad973f6e5c144a
|
|
9
|
-
config_modification_protocol.md: sha256:
|
|
9
|
+
config_modification_protocol.md: sha256:45064ac6b19863e1646d15891d0e9cc7a8f10ab981a0d25d6e13891591538b8a
|
|
10
10
|
memory_guide.md: sha256:15cd3b24eda853cc458371c6c75809a2d4b5352311d075aaa4384926b5221c27
|
|
11
11
|
delegated.md: sha256:aedaa1e756a244904c5560fff0424e06fb468bbdcb0358bccefe321d67cd2614
|
|
@@ -14,6 +14,8 @@ module Samagotchi
|
|
|
14
14
|
# Loaded on first use: session_manager requires terminal_ui, which
|
|
15
15
|
# requires KernelLoop and so the tools (a require cycle otherwise).
|
|
16
16
|
autoload :SessionManager, File.expand_path("session_manager", __dir__)
|
|
17
|
+
# SessionContinue::OpenChild, the rows a plan refuses: loaded on first use too.
|
|
18
|
+
autoload :SessionContinue, File.expand_path("session_continue", __dir__)
|
|
17
19
|
|
|
18
20
|
# A continue moving the previous link's open delegates to the chain's new
|
|
19
21
|
# link (SessionManager.continue_session), instead of archiving them with
|
|
@@ -45,7 +47,7 @@ module Samagotchi
|
|
|
45
47
|
# What a continue does with the previous link's children: +move+ the
|
|
46
48
|
# direct children whose subtree has an open node (they go to the new
|
|
47
49
|
# link, their subtrees with them), +refuse+ the open ones it can't move
|
|
48
|
-
# (
|
|
50
|
+
# (SessionContinue::OpenChild, why: an older chi worker or a chi REPL).
|
|
49
51
|
Plan = Data.define(:move, :refuse)
|
|
50
52
|
# A move under way: from the session, the children moved, and those
|
|
51
53
|
# already sent their note (so a move finished again notes no one twice).
|
|
@@ -55,8 +57,8 @@ module Samagotchi
|
|
|
55
57
|
|
|
56
58
|
module_function
|
|
57
59
|
|
|
58
|
-
# @param open [Array<
|
|
59
|
-
# nodes, at any depth (
|
|
60
|
+
# @param open [Array<SessionContinue::OpenChild>] the previous link's open
|
|
61
|
+
# nodes, at any depth (SessionContinue.open_children)
|
|
60
62
|
# @return [Plan]
|
|
61
63
|
def plan(prev_id, open:, state_dir:)
|
|
62
64
|
open_ids = open.map(&:id)
|
|
@@ -77,7 +79,7 @@ module Samagotchi
|
|
|
77
79
|
|
|
78
80
|
why = unmovable(row[:id], state_dir: state_dir)
|
|
79
81
|
if why
|
|
80
|
-
refuse <<
|
|
82
|
+
refuse << SessionContinue::OpenChild.new(id: row[:id], short_id: row[:id][0, 8], why: why)
|
|
81
83
|
else
|
|
82
84
|
move << row[:id]
|
|
83
85
|
end
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -156,7 +156,6 @@ module Samagotchi
|
|
|
156
156
|
# (MemoryBundle::IndexSize); 0: no note. `memory:` is its own section, not the `memories:` preload list.
|
|
157
157
|
Entry.new(key: "memory.index_warn_tokens", yaml_path: %w[memory index_warn_tokens], type: :integer, default: 2500, expose: %i[env config cli]),
|
|
158
158
|
Entry.new(key: "context.status_thresholds", yaml_path: %w[context status_thresholds], type: :string, default: "20,40,60,80", expose: %i[env config cli]),
|
|
159
|
-
Entry.new(key: "context.status_cadence", yaml_path: %w[context status_cadence], type: :integer, default: 0, expose: %i[env config cli]),
|
|
160
159
|
# Attached context (ContextSources): how often a source's command runs when it names no --every (seconds; less
|
|
161
160
|
# than 30 counts as 30).
|
|
162
161
|
Entry.new(key: "context.every_seconds", yaml_path: %w[context every_seconds], type: :integer, default: 300, expose: %i[env config]),
|
|
@@ -251,6 +250,8 @@ module Samagotchi
|
|
|
251
250
|
Entry.new(key: "web.view", yaml_path: %w[web view], type: :enum, default: "stage", expose: %i[env config cli], enum_values: %w[stage turn]),
|
|
252
251
|
# Quick replies next to Annotate in the page's selection bubble, "|"-separated (a YAML list works too); "" leaves only Annotate.
|
|
253
252
|
Entry.new(key: "web.annotate_presets", yaml_path: %w[web annotate_presets], type: :string, default: "Agreed|Could you please elaborate?", expose: %i[env config cli]),
|
|
253
|
+
# What a file ref (a tool row's file, a diff line) opens on this machine: a preset (vscode, vscode-insiders, cursor, zed, none) or a URL template with {path} and an optional {line} (Web::Editor).
|
|
254
|
+
Entry.new(key: "web.editor", yaml_path: %w[web editor], type: :string, default: "vscode", expose: %i[env config cli]),
|
|
254
255
|
|
|
255
256
|
Entry.new(key: "no_interrupt", yaml_path: %w[no_interrupt], type: :bool, default: false, expose: %i[env config cli]),
|
|
256
257
|
Entry.new(key: "no_default_input", yaml_path: %w[no_default_input], type: :bool, default: false, expose: %i[env config cli]),
|
|
@@ -22,7 +22,7 @@ module Samagotchi
|
|
|
22
22
|
USAGE = <<~TEXT
|
|
23
23
|
Usage: chi context <add|push|ls|show|refresh|rm|mute|unmute> [options] [TARGET]
|
|
24
24
|
add NAME (--cmd CMD | --push) [--every SECONDS] [--why TEXT] [--hint TEXT] TARGET
|
|
25
|
-
add URL [--why TEXT] TARGET
|
|
25
|
+
add URL [--why TEXT] [--hint TEXT] TARGET a URL an installed bundle's provider knows (github-pr: a PR)
|
|
26
26
|
a source: CMD prints its text (plain, or JSON {"text", "summary", "wake", "hint"});
|
|
27
27
|
--push: text comes from chi context push. --every: how often CMD runs
|
|
28
28
|
(seconds, at least 30; default context.every_seconds). --why: why it's attached;
|
|
@@ -84,10 +84,10 @@ module Samagotchi
|
|
|
84
84
|
# @raise [Invalid] the provider makes a name that isn't a source name
|
|
85
85
|
def resolve(url)
|
|
86
86
|
url = url.to_s.strip
|
|
87
|
-
MemoryBundle::Provenance.each_installed do |bundle,
|
|
88
|
-
next if
|
|
87
|
+
MemoryBundle::Provenance.each_installed do |bundle, record|
|
|
88
|
+
next if record.error? || !record.context_providers?
|
|
89
89
|
|
|
90
|
-
providers(bundle,
|
|
90
|
+
providers(bundle, record).each do |provider|
|
|
91
91
|
match = provider.regexp.match(url) or next
|
|
92
92
|
return resolved(bundle, provider, match)
|
|
93
93
|
end
|
|
@@ -97,13 +97,11 @@ module Samagotchi
|
|
|
97
97
|
|
|
98
98
|
# Whether an installed bundle declares a provider (the web's "+ URL").
|
|
99
99
|
def any?
|
|
100
|
-
MemoryBundle::Provenance.each_installed.any?
|
|
101
|
-
!data[:error] && data[:context_providers].is_a?(Array) && !data[:context_providers].empty?
|
|
102
|
-
end
|
|
100
|
+
MemoryBundle::Provenance.each_installed.any? { |_bundle, record| !record.error? && record.context_providers? }
|
|
103
101
|
end
|
|
104
102
|
|
|
105
|
-
def providers(bundle,
|
|
106
|
-
parse_list(
|
|
103
|
+
def providers(bundle, record)
|
|
104
|
+
parse_list(record.context_providers)
|
|
107
105
|
rescue Invalid => e
|
|
108
106
|
Log.warn(:context, "providers_unreadable", bundle: bundle, msg: e.message)
|
|
109
107
|
[]
|
|
@@ -131,13 +129,12 @@ module Samagotchi
|
|
|
131
129
|
return source.cmd unless source.provider
|
|
132
130
|
|
|
133
131
|
provenance = MemoryBundle::Provenance.new(name: source.provider)
|
|
134
|
-
|
|
135
|
-
raise Invalid, "bundle #{source.provider} isn't installed" unless
|
|
132
|
+
record = provenance.record
|
|
133
|
+
raise Invalid, "bundle #{source.provider} isn't installed" unless record
|
|
136
134
|
|
|
137
|
-
scripts
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
recorded = MemoryBundle::Provenance.recorded_sha(meta.is_a?(Hash) ? meta[:sha256] : nil)
|
|
135
|
+
record.scripts.each do |file, sha|
|
|
136
|
+
path = File.join(provenance.scripts_dir, file)
|
|
137
|
+
recorded = MemoryBundle::Provenance.recorded_sha(sha)
|
|
141
138
|
next if File.file?(path) && MemoryBundle::Provenance.file_sha(path) == recorded
|
|
142
139
|
|
|
143
140
|
raise Invalid, "bundle #{source.provider}'s scripts/#{file} differs from the installed one (reinstall the bundle)"
|
|
@@ -12,8 +12,9 @@ module Samagotchi
|
|
|
12
12
|
#
|
|
13
13
|
# - the status line's value (#display), every request and again from the
|
|
14
14
|
# server's final counts after each generation;
|
|
15
|
-
# - a :context_status stream event (#observe's return), on
|
|
16
|
-
#
|
|
15
|
+
# - a :context_status stream event (#observe's return), on every request,
|
|
16
|
+
# so the web and the attached TUI get a fresh estimate each time (the
|
|
17
|
+
# context.status_cadence setting no longer throttles this event);
|
|
17
18
|
# - the model's own short line (#take_guidance), once per rise into a
|
|
18
19
|
# bucket whose guidance asks for a change (the second threshold up).
|
|
19
20
|
#
|
|
@@ -125,7 +126,6 @@ module Samagotchi
|
|
|
125
126
|
parsed = Config.get("context.status_thresholds").to_s.split(",").map { |value| value.strip.to_i }
|
|
126
127
|
.select { |value| value.between?(1, 99) }.uniq.sort
|
|
127
128
|
@thresholds = parsed.empty? ? DEFAULT_THRESHOLDS : parsed
|
|
128
|
-
@cadence = [Config.get("context.status_cadence").to_i, 0].max
|
|
129
129
|
@last_bucket = self.class.last_bucket(conversation)
|
|
130
130
|
@server_usage = nil
|
|
131
131
|
@counted = nil
|
|
@@ -153,16 +153,19 @@ module Samagotchi
|
|
|
153
153
|
# +image_tokens+: the images' estimate (their base64 is not in the chars).
|
|
154
154
|
#
|
|
155
155
|
# @return [Hash, nil] the :context_status event's fields (without
|
|
156
|
-
# :type/:iteration)
|
|
156
|
+
# :type/:iteration) for every request when enabled, else nil (the
|
|
157
|
+
# context.status off). The estimate is returned every request, so the
|
|
158
|
+
# web and the attached TUI get a fresh number on each; the model's own
|
|
159
|
+
# line is still only left on a rise into a guidance bucket (below).
|
|
157
160
|
def observe(prompt_chars, iteration_index:, window:, image_tokens: 0)
|
|
158
161
|
return nil unless @enabled
|
|
159
162
|
|
|
160
163
|
usage = estimate(prompt_chars, server_usage: @server_usage, window: window, image_tokens: image_tokens, counted: @counted)
|
|
161
164
|
bucket = bucket_for(usage[:estimated_pct])
|
|
162
|
-
# The status line's value, every iteration; the
|
|
163
|
-
#
|
|
165
|
+
# The status line's value, every iteration; the event below too, so the
|
|
166
|
+
# consumers get a fresh estimate on every request. Only the model's line
|
|
167
|
+
# is still gated: it is left only on a rise (below).
|
|
164
168
|
@display = { est_pct: usage[:estimated_pct], bucket: bucket }
|
|
165
|
-
emit = emit?(bucket: bucket, iteration_index: iteration_index)
|
|
166
169
|
previous_bucket = @last_bucket
|
|
167
170
|
@last_bucket = bucket
|
|
168
171
|
line = if @forget
|
|
@@ -171,7 +174,6 @@ module Samagotchi
|
|
|
171
174
|
guidance_message(usage: usage, bucket: bucket)
|
|
172
175
|
end
|
|
173
176
|
@guidance = line if line
|
|
174
|
-
return nil unless emit
|
|
175
177
|
|
|
176
178
|
{ status: status_message(usage: usage, bucket: bucket), usage: usage, bucket: bucket, source: usage[:source] }
|
|
177
179
|
end
|
|
@@ -293,16 +295,6 @@ module Samagotchi
|
|
|
293
295
|
rank >= GUIDANCE_FROM_RANK && rank > bucket_rank(previous)
|
|
294
296
|
end
|
|
295
297
|
|
|
296
|
-
def emit?(bucket:, iteration_index:)
|
|
297
|
-
bucket_changed = bucket != if @last_bucket.nil?
|
|
298
|
-
"under#{@thresholds.first}"
|
|
299
|
-
else
|
|
300
|
-
@last_bucket
|
|
301
|
-
end
|
|
302
|
-
cadence_due = @cadence.positive? && ((iteration_index + 1) % @cadence).zero?
|
|
303
|
-
bucket_changed || cadence_due
|
|
304
|
-
end
|
|
305
|
-
|
|
306
298
|
# The forget layer's line for this request, or nil (the tiers and when
|
|
307
299
|
# they come: the class comment).
|
|
308
300
|
def forget_line(usage, bucket, previous, iteration_index)
|