samagotchi 0.66.0 → 0.68.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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -1
  3. data/docs/architecture.md +55 -30
  4. data/docs/cli.md +3 -1
  5. data/docs/configuration.md +50 -9
  6. data/docs/context.md +35 -1
  7. data/docs/desktop.md +90 -22
  8. data/docs/guardrails.md +8 -5
  9. data/docs/hooks.md +2 -2
  10. data/docs/internals/anthropic.md +156 -0
  11. data/docs/internals/background-tasks.md +1 -1
  12. data/docs/internals/bundles.md +2 -0
  13. data/docs/internals/engine.md +129 -38
  14. data/docs/internals/gemma4-contract.md +1 -1
  15. data/docs/internals/llm-context-bench.md +5 -4
  16. data/docs/internals/prompt-caching.md +26 -11
  17. data/docs/plugins.md +126 -23
  18. data/docs/sessions.md +6 -6
  19. data/docs/sub-agent.md +4 -1
  20. data/docs/testing.md +18 -4
  21. data/lib/samagotchi/bootstrap/probe.rb +4 -3
  22. data/lib/samagotchi/bootstrap_command.rb +13 -3
  23. data/lib/samagotchi/bridge/ring_buffer.rb +0 -5
  24. data/lib/samagotchi/bridge.rb +6 -3
  25. data/lib/samagotchi/bundles/claude-peer/manifest.yml +7 -6
  26. data/lib/samagotchi/bundles/claude-peer/plugin.rb +34 -313
  27. data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +8 -3
  28. data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +14 -6
  29. data/lib/samagotchi/bundles/codex-peer/manifest.yml +7 -6
  30. data/lib/samagotchi/bundles/codex-peer/plugin.rb +37 -312
  31. data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +8 -3
  32. data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +14 -6
  33. data/lib/samagotchi/bundles/mcp/manifest.yml +5 -5
  34. data/lib/samagotchi/bundles/mcp/plugin.rb +82 -294
  35. data/lib/samagotchi/bundles/system/config_modification_protocol.md +7 -3
  36. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  37. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  38. data/lib/samagotchi/config.rb +15 -7
  39. data/lib/samagotchi/context_absorber.rb +66 -13
  40. data/lib/samagotchi/desktop/macos/AgentTerminal.swift +260 -0
  41. data/lib/samagotchi/desktop/macos/App.swift +3 -3
  42. data/lib/samagotchi/desktop/macos/ChiRunner.swift +5 -2
  43. data/lib/samagotchi/desktop/macos/Images.swift +2 -2
  44. data/lib/samagotchi/desktop/macos/Kitty.swift +33 -197
  45. data/lib/samagotchi/desktop/macos/Panel.swift +118 -82
  46. data/lib/samagotchi/desktop/macos/ProcessRunner.swift +1 -1
  47. data/lib/samagotchi/desktop/macos/Tmux.swift +239 -0
  48. data/lib/samagotchi/desktop/macos.rb +84 -13
  49. data/lib/samagotchi/desktop_command.rb +14 -3
  50. data/lib/samagotchi/effective_model.rb +283 -0
  51. data/lib/samagotchi/engine.rb +307 -549
  52. data/lib/samagotchi/engine_peers.rb +3 -0
  53. data/lib/samagotchi/guardrail_wiring.rb +1 -1
  54. data/lib/samagotchi/guardrails/approval.rb +1 -1
  55. data/lib/samagotchi/guardrails/approvals.rb +1 -1
  56. data/lib/samagotchi/guardrails/rules.rb +1 -1
  57. data/lib/samagotchi/guardrails/targets.rb +1 -1
  58. data/lib/samagotchi/hook_routes.rb +151 -0
  59. data/lib/samagotchi/hooks/registry.rb +2 -2
  60. data/lib/samagotchi/host_keys.rb +147 -0
  61. data/lib/samagotchi/level_setting_command.rb +99 -0
  62. data/lib/samagotchi/llm/adapter_common.rb +138 -0
  63. data/lib/samagotchi/llm/anthropic_messages.rb +28 -105
  64. data/lib/samagotchi/llm/api_key.rb +23 -7
  65. data/lib/samagotchi/llm/chat_loop.rb +10 -6
  66. data/lib/samagotchi/llm/openai_chat.rb +9 -92
  67. data/lib/samagotchi/llm/refusal_memo.rb +29 -0
  68. data/lib/samagotchi/llm_context_override.rb +1 -0
  69. data/lib/samagotchi/llm_context_strategy.rb +5 -10
  70. data/lib/samagotchi/peer_sources.rb +58 -0
  71. data/lib/samagotchi/plugin/api.rb +64 -155
  72. data/lib/samagotchi/plugin/loader.rb +4 -2
  73. data/lib/samagotchi/plugin/peer.rb +394 -0
  74. data/lib/samagotchi/plugin/tool_spec.rb +166 -0
  75. data/lib/samagotchi/plugin_tasks.rb +2 -2
  76. data/lib/samagotchi/reasoning_history_command.rb +29 -90
  77. data/lib/samagotchi/row_refs.rb +1 -1
  78. data/lib/samagotchi/self_report.rb +5 -3
  79. data/lib/samagotchi/send_command.rb +21 -11
  80. data/lib/samagotchi/session.rb +22 -8
  81. data/lib/samagotchi/session_continue.rb +58 -15
  82. data/lib/samagotchi/session_inbox.rb +0 -13
  83. data/lib/samagotchi/session_setting.rb +86 -0
  84. data/lib/samagotchi/thinking_command.rb +24 -76
  85. data/lib/samagotchi/thinking_notices.rb +169 -0
  86. data/lib/samagotchi/tool_call_parser.rb +1 -1
  87. data/lib/samagotchi/toolkit_binding.rb +138 -0
  88. data/lib/samagotchi/tools/context_read.rb +14 -0
  89. data/lib/samagotchi/tools/execute.rb +12 -12
  90. data/lib/samagotchi/tools/registry.rb +1 -1
  91. data/lib/samagotchi/tools/task_create.rb +6 -6
  92. data/lib/samagotchi/tools/task_get.rb +13 -13
  93. data/lib/samagotchi/tools/task_list.rb +6 -6
  94. data/lib/samagotchi/tools/task_record.rb +52 -0
  95. data/lib/samagotchi/tools/task_runtime.rb +55 -91
  96. data/lib/samagotchi/tools/task_stop.rb +6 -6
  97. data/lib/samagotchi/tools/task_wait.rb +12 -13
  98. data/lib/samagotchi/tools/tool_index.rb +454 -0
  99. data/lib/samagotchi/update_command.rb +9 -6
  100. data/lib/samagotchi/version.rb +1 -1
  101. data/lib/samagotchi/web/app.rb +71 -93
  102. data/lib/samagotchi/web/capabilities.rb +1 -1
  103. data/lib/samagotchi/web/public/activity.js +2 -2
  104. data/lib/samagotchi/web/public/annotate_actions.js +1 -1
  105. data/lib/samagotchi/web/public/app.js +103 -539
  106. data/lib/samagotchi/web/public/bubbles.js +291 -0
  107. data/lib/samagotchi/web/public/cards_place.js +184 -0
  108. data/lib/samagotchi/web/public/chains.js +1 -1
  109. data/lib/samagotchi/web/public/chip_popover.js +111 -0
  110. data/lib/samagotchi/web/public/command_list.js +12 -10
  111. data/lib/samagotchi/web/public/commands.js +2 -2
  112. data/lib/samagotchi/web/public/composer_size.js +2 -2
  113. data/lib/samagotchi/web/public/copy.js +1 -1
  114. data/lib/samagotchi/web/public/ctx.js +1 -1
  115. data/lib/samagotchi/web/public/delivery_line.js +4 -4
  116. data/lib/samagotchi/web/public/families_view.js +1 -1
  117. data/lib/samagotchi/web/public/format.js +11 -7
  118. data/lib/samagotchi/web/public/images.js +25 -2
  119. data/lib/samagotchi/web/public/index.html +15 -7
  120. data/lib/samagotchi/web/public/info_bar.js +3 -3
  121. data/lib/samagotchi/web/public/info_bar_model.js +110 -0
  122. data/lib/samagotchi/web/public/init_row.js +21 -10
  123. data/lib/samagotchi/web/public/level_chip.js +29 -80
  124. data/lib/samagotchi/web/public/lists_view.js +64 -9
  125. data/lib/samagotchi/web/public/llm_context_chip.js +38 -107
  126. data/lib/samagotchi/web/public/model_picker.js +26 -22
  127. data/lib/samagotchi/web/public/notify.js +6 -4
  128. data/lib/samagotchi/web/public/question_card.js +1 -1
  129. data/lib/samagotchi/web/public/ref_scan.js +2 -8
  130. data/lib/samagotchi/web/public/refs.js +13 -0
  131. data/lib/samagotchi/web/public/session_state.js +14 -0
  132. data/lib/samagotchi/web/public/session_title.js +2 -3
  133. data/lib/samagotchi/web/public/sessions_list.js +0 -8
  134. data/lib/samagotchi/web/public/stage_frame.js +179 -0
  135. data/lib/samagotchi/web/public/stage_model.js +35 -2
  136. data/lib/samagotchi/web/public/stage_slots.js +130 -0
  137. data/lib/samagotchi/web/public/stage_view.js +56 -335
  138. data/lib/samagotchi/web/public/stage_watch.js +65 -0
  139. data/lib/samagotchi/web/public/strip.js +3 -3
  140. data/lib/samagotchi/web/public/task_stop.js +3 -2
  141. data/lib/samagotchi/web/public/turn_events.js +1 -1
  142. data/lib/samagotchi/web/public/turn_html.js +2 -2
  143. data/lib/samagotchi/web/public/turn_model.js +2 -2
  144. data/lib/samagotchi/web/public/turn_view.js +1 -1
  145. data/lib/samagotchi/web/public/vendor/directive-helpers.js +8 -0
  146. data/lib/samagotchi/web/public/vendor/directives/repeat.js +9 -0
  147. data/lib/samagotchi/web/server.rb +0 -4
  148. data/lib/samagotchi/worker.rb +22 -24
  149. data/lib/samagotchi/worker_inbound.rb +36 -4
  150. data/lib/samagotchi/worker_wakes.rb +67 -7
  151. metadata +27 -1
@@ -26,8 +26,8 @@ require_relative "../host_registry"
26
26
  require_relative "../llm_context_override"
27
27
  require_relative "../model_catalog"
28
28
  require_relative "../model_profile"
29
+ require_relative "../session_setting"
29
30
  require_relative "../thinking"
30
- require_relative "../reasoning_history"
31
31
  require_relative "../sampling_settings"
32
32
  require_relative "../served_model"
33
33
  require_relative "../project_scope"
@@ -557,52 +557,6 @@ module Samagotchi
557
557
  nil
558
558
  end
559
559
 
560
- # A session's LLM context strategy, apply rule and budget, each with
561
- # where it came from (LLMContextStrategy::Explained#summary), as its
562
- # worker would resolve them; nil when they can't be.
563
- # (LLMContextStrategy.for_session, shared with the ctx % the session
564
- # lists count.)
565
- def llm_context_for(session)
566
- LLMContextStrategy.for_session(session, registry: host_registry)
567
- end
568
-
569
- # A session's toolkit as its file names it ({name:, label:}), nil on
570
- # full: without a worker nothing resolves it (a bundle's kits load in
571
- # the worker), so the tools it leaves out aren't known here.
572
- def toolkit_for(session)
573
- toolkit = session.toolkit
574
- toolkit && !toolkit.full? ? { name: toolkit.name, label: toolkit.label } : nil
575
- end
576
-
577
- # A session's thinking level with where it came from and its own
578
- # (SettingExplained#summary), as its worker would resolve it; nil
579
- # when it can't be. Config and the registry only: no network.
580
- def thinking_for(session)
581
- registry = host_registry
582
- name = session.model_name.to_s.strip.empty? ? ModelProfile.required_model_name(nil) : session.model_name
583
- target = registry.resolve(name)
584
- names = registry.lookup_names(session.model_typed || name, resolved: name, target: target)
585
- Thinking.explain(target, names: names, session: session.thinking).summary
586
- rescue StandardError
587
- nil
588
- end
589
-
590
- # A session's reasoning_history level with where it came from and its
591
- # own (SettingExplained#summary), as its worker would resolve it; nil
592
- # on a native host (it ignores the level) or when it can't be. Config
593
- # and the registry only: no network.
594
- def reasoning_history_for(session)
595
- registry = host_registry
596
- name = session.model_name.to_s.strip.empty? ? ModelProfile.required_model_name(nil) : session.model_name
597
- target = registry.resolve(name)
598
- return nil unless target.entry&.chat?
599
-
600
- names = registry.lookup_names(session.model_typed || name, resolved: name, target: target)
601
- ReasoningHistory.explain(target, names: names, session: session.reasoning_history).summary
602
- rescue StandardError
603
- nil
604
- end
605
-
606
560
  # Built again when config.yml's hosts: changed (compared as read: the
607
561
  # YAML read is mtime-cached), so the model picker lists a host added
608
562
  # while chi web runs. An injected registry (specs) stays.
@@ -719,9 +673,13 @@ module Samagotchi
719
673
 
720
674
  # POST /api/sessions with continues: <id, prefix or last:id>: the next
721
675
  # link of that session's chain (SessionManager.continue_session), in its
722
- # folder, on its model and with its own setup (SessionSetup: llm_context,
723
- # thinking and reasoning_history levels), so dir, model, llm_context and
724
- # thinking are refused beside it (a create takes no reasoning_history).
676
+ # folder and with its own setup (SessionSetup: llm_context, thinking
677
+ # and reasoning_history levels), so dir, llm_context and thinking are
678
+ # refused beside it (a create takes no reasoning_history). It runs on
679
+ # model (or model_name) when given, else on the previous link's: one
680
+ # whose host is gone falls back to the default, and the 201's
681
+ # model_warning (null when none) says so. A model it can't start on is
682
+ # 400 invalid_model, nothing archived.
725
683
  # 409 continued (with next_id, the link that continues it already: the
726
684
  # page opens that one), open_children (ids), folder_gone, busy, scratch
727
685
  # or owned_by_tui.
@@ -730,14 +688,16 @@ module Samagotchi
730
688
  unless given.is_a?(String) && !given.strip.empty?
731
689
  return error_response(400, "invalid_continues", "continues is a session id")
732
690
  end
733
- if (fields = %w[dir model model_name llm_context thinking].select { |key| body.key?(key) }).any?
691
+ if (fields = %w[dir llm_context thinking].select { |key| body.key?(key) }).any?
734
692
  return error_response(400, "invalid_continues",
735
- "continues takes the previous session's folder, model, llm_context and thinking; " \
693
+ "continues takes the previous session's folder, llm_context and thinking; " \
736
694
  "leave out #{fields.join(", ")}")
737
695
  end
738
696
 
739
697
  preview = idle ? body["preview"].to_s.strip : ""
740
- session, error = continue_from(given, prompt: idle ? nil : prompt.to_s, title: preview.empty? ? nil : preview)
698
+ model = (body["model"] || body["model_name"]).to_s.strip
699
+ session, error = continue_from(given, prompt: idle ? nil : prompt.to_s, title: preview.empty? ? nil : preview,
700
+ model: model.empty? ? nil : model)
741
701
  return error if error
742
702
 
743
703
  port = await_bridge_port(session.id)
@@ -745,14 +705,15 @@ module Samagotchi
745
705
  # The moved delegates' cards fold under the new link now.
746
706
  [session.continues, session.id, *moved].each { |sid| @hub&.touch(sid) }
747
707
  record_history(body, prompt) if !idle && PromptHistory.shell_line?(prompt.to_s.strip)
748
- json_response(201, session_to_json(session).merge(bridge_port: port, moved: moved))
708
+ json_response(201, session_to_json(session).merge(bridge_port: port, moved: moved,
709
+ model_warning: session.model_warning))
749
710
  end
750
711
 
751
712
  # SessionManager.continue_session, its refusals as responses: [session,
752
713
  # nil] or [nil, the response]. Only its own errors: what fails after
753
714
  # the new link started is not a refusal of it.
754
- def continue_from(given, prompt:, title:)
755
- [@manager.continue_session(given, prompt: prompt, title: title, state_dir: @state_dir), nil]
715
+ def continue_from(given, prompt:, title:, model:)
716
+ [@manager.continue_session(given, prompt: prompt, title: title, model: model, state_dir: @state_dir), nil]
756
717
  rescue SessionContinue::Refused => e
757
718
  extra = e.reason == :continued ? { next_id: e.ids.first } : { ids: e.ids }
758
719
  [nil, json_response(409, { error: e.reason.to_s, detail: e.message, **extra })]
@@ -821,46 +782,15 @@ module Samagotchi
821
782
  live = bridge_get_json(id, "snapshot")
822
783
  turn_snapshot = live && live["snapshot"]
823
784
  snapshot = live && live["session_state_snapshot"]
824
- last_event_seq = snapshot ? snapshot["event_seq"] : bridge_event_seq(id)
785
+ last_event_seq = live_or_saved(snapshot, "event_seq") { bridge_event_seq(id) }
825
786
  current_turn = turn_snapshot && turn_snapshot["current_turn"]
826
787
  owner = session_owner(id)
827
- # The file's question only while a worker owns the session: one a
828
- # dead worker saved can't be answered (its turn is gone), nor a chi
829
- # REPL's (it shares nothing with the web).
830
- # A question between turns (the step-limit one) is the snapshot's own.
831
- pending = if turn_snapshot
832
- current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
833
- elsif owner&.worker?
834
- session.pending_question
835
- end
836
- # A /model in the worker changes it before the file catches up.
837
- session.model_name = snapshot["model_name"] if snapshot && !snapshot["model_name"].to_s.empty?
838
- session_json = session_to_json(session, status: displayed_status(session, snapshot, owner: owner), owner: owner)
788
+ pending = shown_pending_question(session, turn_snapshot, owner)
839
789
  # What the worker's server said it served for that model (after a turn);
840
790
  # without a worker, what the session's analytics.json last saved.
841
791
  persisted = snapshot ? nil : read_analytics(id)
842
- if snapshot
843
- session_json = session_json.merge(served_model: snapshot["served_model"], served_model_for: snapshot["served_model_for"],
844
- served_expected_by: snapshot["served_expected_by"])
845
- # The model notes its prompt carries now (a /model there changes them).
846
- session_json = session_json.merge(prompt_notes: snapshot["prompt_notes"]) if snapshot["prompt_notes"].is_a?(Array)
847
- else
848
- session_json = session_json.merge(saved_served_model(session, persisted))
849
- end
850
- # The LLM context strategy the next turn runs under (the info bar's
851
- # chip): the worker's, else worked out here from the session file.
852
- session_json = session_json.merge(llm_context: (snapshot && snapshot["llm_context"]) || llm_context_for(session))
853
- # The thinking level the next turn runs at (the think chip), the same way.
854
- session_json = session_json.merge(thinking: (snapshot && snapshot["thinking"]) || thinking_for(session))
855
- # Its reasoning history (the reasoning chip): the worker's (nil on a
856
- # native host), else the server's reading (an older worker, none).
857
- history = snapshot&.key?("reasoning_history") ? snapshot["reasoning_history"] : reasoning_history_for(session)
858
- session_json = session_json.merge(reasoning_history: history)
859
- # Its toolkit when it isn't full (the tools chip): the worker's, which
860
- # resolved it, else the name saved in the session file.
861
- toolkit = snapshot&.key?("toolkit") ? snapshot["toolkit"] : toolkit_for(session)
862
- session_json = session_json.merge(toolkit: toolkit)
863
- raw_messages = turn_snapshot ? turn_snapshot["messages"] : session.messages
792
+ session_json = shown_session_json(session, snapshot, owner, persisted)
793
+ raw_messages = live_or_saved(turn_snapshot, "messages") { session.messages }
864
794
  timing = timing_payload(id, live_metrics: snapshot && snapshot["metrics"], persisted: persisted)
865
795
  json_response(200, {
866
796
  session: session_json,
@@ -879,7 +809,7 @@ module Samagotchi
879
809
  plugin_warning: turn_snapshot && turn_snapshot["plugin_warning"],
880
810
  # Plugins' slow setup still running (chi.init): the page's init row.
881
811
  init_tasks: turn_snapshot ? Array(turn_snapshot["init_tasks"]) : [],
882
- cards: cards_for_display(turn_snapshot ? turn_snapshot["cards"] : saved_cards(id)),
812
+ cards: cards_for_display(live_or_saved(turn_snapshot, "cards") { saved_cards(id) }),
883
813
  # The composer's / autocomplete; the built-ins until a worker
884
814
  # names its plugins' too.
885
815
  commands: turn_snapshot&.fetch("commands", nil) || SessionCommands.builtin_registry.listing,
@@ -896,6 +826,54 @@ module Samagotchi
896
826
  error_response(404, "not_found", e.message)
897
827
  end
898
828
 
829
+ # A live worker's +key+ as its reading (+live+: a snapshot, a tail)
830
+ # says it, nil included; without a worker (+live+ nil) the block's,
831
+ # the server's own reading of the saved session. A nil value from a
832
+ # worker is not a missing one (contrast SessionSetting#snapshot_value's
833
+ # :or).
834
+ def live_or_saved(live, key)
835
+ live ? live[key] : yield
836
+ end
837
+
838
+ # The file's question only while a worker owns the session: one a
839
+ # dead worker saved can't be answered (its turn is gone), nor a chi
840
+ # REPL's (it shares nothing with the web).
841
+ # A question between turns (the step-limit one) is the snapshot's own.
842
+ def shown_pending_question(session, turn_snapshot, owner)
843
+ if turn_snapshot
844
+ current_turn = turn_snapshot["current_turn"]
845
+ current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
846
+ elsif owner&.worker?
847
+ session.pending_question
848
+ end
849
+ end
850
+
851
+ # The full show's session part: the file's, with what a live worker's
852
+ # state +snapshot+ knows first (its model, status, served model,
853
+ # prompt notes and chips); without one, the served model from
854
+ # +persisted+ (analytics.json). Sets a worker's model on +session+:
855
+ # a /model in the worker changes it before the file catches up.
856
+ def shown_session_json(session, snapshot, owner, persisted)
857
+ session.model_name = snapshot["model_name"] if snapshot && !snapshot["model_name"].to_s.empty?
858
+ session_json = session_to_json(session, status: displayed_status(session, snapshot, owner: owner), owner: owner)
859
+ if snapshot
860
+ session_json = session_json.merge(served_model: snapshot["served_model"], served_model_for: snapshot["served_model_for"],
861
+ served_expected_by: snapshot["served_expected_by"])
862
+ # The model notes its prompt carries now (a /model there changes them).
863
+ session_json = session_json.merge(prompt_notes: snapshot["prompt_notes"]) if snapshot["prompt_notes"].is_a?(Array)
864
+ else
865
+ session_json = session_json.merge(saved_served_model(session, persisted))
866
+ end
867
+ # The per-session settings' chips (the info bar's llm ctx, think,
868
+ # reasoning and tools): the worker's, else worked out here from the
869
+ # session file (SessionSetting#snapshot_value, #saved_summary).
870
+ SessionSetting::ALL.each do |setting|
871
+ chip = setting.snapshot_value(snapshot) { setting.saved_summary(session, registry: host_registry) }
872
+ session_json = session_json.merge(setting.key => chip)
873
+ end
874
+ session_json
875
+ end
876
+
899
877
  # ?cards=1: the page's re-read when a card arrives, for its rendered
900
878
  # body (the stream carries the card's plain text only). The worker's
901
879
  # cards from its light GET tail, else the saved ones; the session file
@@ -904,7 +882,7 @@ module Samagotchi
904
882
  return error_response(404, "not_found", "Session not found: #{id}") unless session_exists?(id)
905
883
 
906
884
  tail = live_tail(id)
907
- json_response(200, { cards: cards_for_display(tail ? tail["cards"] : saved_cards(id)) })
885
+ json_response(200, { cards: cards_for_display(live_or_saved(tail, "cards") { saved_cards(id) }) })
908
886
  end
909
887
 
910
888
  # ?tail=1: the page's re-read at the end of a turn wants the final
@@ -3,7 +3,7 @@
3
3
  module Samagotchi
4
4
  module Web
5
5
  # What this web page renders that a notice may stand in for
6
- # (Engine#hook_notify's fallback_for:), and what this viewer can do. The
6
+ # (HookRoutes#notify's fallback_for:), and what this viewer can do. The
7
7
  # session and tail JSON carry it as +capabilities+; the page leaves out
8
8
  # a notice whose fallback_for names a capability that is true here.
9
9
  # +display+: the answer's display is rendered as markdown, so its links
@@ -6,7 +6,7 @@
6
6
  // state from :tool_call_started / :tool_call_completed events. Keeping the
7
7
  // state pure makes it unit-testable under `node --test` like data.js/format.js.
8
8
 
9
- export const ROW_STATUS = Object.freeze({
9
+ const ROW_STATUS = Object.freeze({
10
10
  RUNNING: "running",
11
11
  OK: "ok",
12
12
  ERROR: "error",
@@ -161,7 +161,7 @@ function callDuration(row, event, now) {
161
161
 
162
162
  // A completed call's row status from the server's: error, stopped,
163
163
  // blocked, else ok.
164
- export function completedStatus(status) {
164
+ function completedStatus(status) {
165
165
  if (status === "error") return ROW_STATUS.ERROR;
166
166
  if (status === "stopped") return ROW_STATUS.STOPPED;
167
167
  if (status === "blocked") return ROW_STATUS.BLOCKED;
@@ -15,7 +15,7 @@
15
15
  import { quoteBlock } from "./annotations.js";
16
16
 
17
17
  // How many plugin pills the bar shows; the rest go behind "⋯".
18
- export const INLINE_ACTIONS = 3;
18
+ const INLINE_ACTIONS = 3;
19
19
 
20
20
  // The session's commands' annotations, flattened in listing order: {label,
21
21
  // line, template, send, command, anytime, source}. anytime: the command