samagotchi 0.131.0 → 0.133.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -1
  3. data/bin/chi +19 -1
  4. data/docs/architecture.md +59 -1
  5. data/docs/cli.md +29 -8
  6. data/docs/configuration.md +115 -16
  7. data/docs/desktop.md +16 -6
  8. data/docs/guardrails.md +10 -4
  9. data/docs/internals/anthropic.md +3 -2
  10. data/docs/internals/engine.md +4 -4
  11. data/docs/internals/prompt-caching.md +26 -6
  12. data/docs/memory.md +16 -2
  13. data/docs/plugins.md +68 -1
  14. data/docs/sessions.md +60 -6
  15. data/docs/testing.md +1 -1
  16. data/lib/samagotchi/bootstrap_command.rb +35 -6
  17. data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
  18. data/lib/samagotchi/bundles/mcp/plugin.rb +10 -3
  19. data/lib/samagotchi/bundles/model-notes/manifest.yml +4 -1
  20. data/lib/samagotchi/bundles/model-notes/model_notes_tone.md +3 -0
  21. data/lib/samagotchi/bundles/model-notes/notes/README.md +14 -0
  22. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  23. data/lib/samagotchi/cache_confirm.rb +18 -0
  24. data/lib/samagotchi/chi_home.rb +311 -0
  25. data/lib/samagotchi/chi_shim.rb +77 -0
  26. data/lib/samagotchi/child_env.rb +5 -3
  27. data/lib/samagotchi/config.rb +2 -0
  28. data/lib/samagotchi/context_fetch.rb +4 -2
  29. data/lib/samagotchi/desktop/macos.rb +100 -18
  30. data/lib/samagotchi/engine.rb +35 -4
  31. data/lib/samagotchi/engine_snapshots.rb +5 -0
  32. data/lib/samagotchi/gem_update.rb +19 -10
  33. data/lib/samagotchi/guardrail_wiring.rb +3 -1
  34. data/lib/samagotchi/guardrails/pair_boundary.rb +21 -2
  35. data/lib/samagotchi/guardrails/pair_command.rb +57 -1
  36. data/lib/samagotchi/guardrails/protected_paths.rb +6 -3
  37. data/lib/samagotchi/guardrails/review_paths.rb +7 -4
  38. data/lib/samagotchi/guardrails/verdict.rb +1 -1
  39. data/lib/samagotchi/home_command.rb +145 -0
  40. data/lib/samagotchi/home_doctor.rb +177 -0
  41. data/lib/samagotchi/home_notice.rb +80 -0
  42. data/lib/samagotchi/installed_versions.rb +5 -3
  43. data/lib/samagotchi/llm_context_command.rb +53 -7
  44. data/lib/samagotchi/llm_context_override.rb +3 -2
  45. data/lib/samagotchi/plugin/api.rb +45 -1
  46. data/lib/samagotchi/plugin/context.rb +34 -1
  47. data/lib/samagotchi/plugin/loader.rb +5 -2
  48. data/lib/samagotchi/plugin/prompt_sections.rb +124 -0
  49. data/lib/samagotchi/project_env.rb +360 -0
  50. data/lib/samagotchi/run_options.rb +1 -0
  51. data/lib/samagotchi/self_report.rb +33 -0
  52. data/lib/samagotchi/session_access/contract.rb +1 -1
  53. data/lib/samagotchi/session_commands.rb +68 -19
  54. data/lib/samagotchi/session_manager.rb +54 -1
  55. data/lib/samagotchi/session_model_settings.rb +5 -2
  56. data/lib/samagotchi/system_prompt.rb +34 -3
  57. data/lib/samagotchi/terminal_ui/attach_launcher.rb +3 -2
  58. data/lib/samagotchi/terminal_ui/formatting.rb +3 -0
  59. data/lib/samagotchi/testing/boundary.rb +3 -1
  60. data/lib/samagotchi/testing/bundle.rb +22 -3
  61. data/lib/samagotchi/testing/host.rb +22 -1
  62. data/lib/samagotchi/testing/recorder.rb +6 -1
  63. data/lib/samagotchi/testing/recording_api.rb +1 -1
  64. data/lib/samagotchi/tools/builtins.rb +13 -2
  65. data/lib/samagotchi/update_command.rb +65 -7
  66. data/lib/samagotchi/version.rb +1 -1
  67. data/lib/samagotchi/web/app.rb +61 -1
  68. data/lib/samagotchi/web/public/app.js +4 -1
  69. data/lib/samagotchi/web/public/chip_popover.js +5 -2
  70. data/lib/samagotchi/web/public/composer.css +11 -1
  71. data/lib/samagotchi/web/public/data.js +8 -4
  72. data/lib/samagotchi/web/public/index.html +52 -10
  73. data/lib/samagotchi/web/public/level_chip.js +22 -38
  74. data/lib/samagotchi/web/public/llm_context_chip.js +30 -73
  75. data/lib/samagotchi/web/public/model_pick.js +26 -0
  76. data/lib/samagotchi/web/public/model_picker.js +3 -1
  77. data/lib/samagotchi/web/public/review_start.js +13 -57
  78. data/lib/samagotchi/web/public/send_turn.js +3 -2
  79. data/lib/samagotchi/web/public/start_page.js +131 -99
  80. data/lib/samagotchi/web/public/start_settings.js +298 -0
  81. data/lib/samagotchi/web/public/start_toolkit.js +157 -0
  82. data/lib/samagotchi/web/public/thinking_chip.js +9 -22
  83. data/lib/samagotchi/web/server.rb +27 -8
  84. data/lib/samagotchi/web/update_check.rb +3 -2
  85. metadata +11 -1
@@ -0,0 +1,157 @@
1
+ // The start page's 🧰 list: the toolkit the next new session starts on
2
+ // (GET /api/models' toolkits: {default, suggest, kits: [{name,
3
+ // description?}]}), a choice for that create only (start_page.js keeps it;
4
+ // never remembered). Auto, the everyday path, sends nothing: with
5
+ // toolkit.suggest.mode on, the first message may get a suggested kit, else
6
+ // the default. A kit picked is sent (the session is "chosen" and gets no
7
+ // suggestion), the default too while Auto is offered; without Auto the
8
+ // default's row is Auto's and sends nothing. A pick closes the list.
9
+ //
10
+ // Beside 🧰 on the row an override chip names the kit ("tools: web");
11
+ // on a phone 🧰 carries a dot instead (toolkitLabel()).
12
+
13
+ import { createChipPopover } from "./chip_popover.js";
14
+ import { html, nothing, render } from "./vendor/lit-html.js";
15
+
16
+ export const AUTO = "auto";
17
+ export const LIST_LABEL = "Toolkit";
18
+ export const NOTE = "Change it later with /toolkit.";
19
+ // The built-ins carry no description (one would make them System 1
20
+ // candidates): the page words them.
21
+ const BUILTIN_WORDS = { full: "every tool", lean: "core tools; find_tools reaches the rest" };
22
+
23
+ // Whether the first message may get a suggested kit (the Auto row).
24
+ export const offersAuto = (toolkits) => toolkits?.suggest === "on";
25
+
26
+ // The list's rows, each {value (AUTO or a kit's name), label, detail,
27
+ // title, pressed}. +choice+: the kit picked, null for none (Auto; the
28
+ // default's row without Auto).
29
+ export function toolkitRows(toolkits, choice) {
30
+ if (!toolkits) return [];
31
+ const auto = offersAuto(toolkits);
32
+ const fallback = toolkits.default;
33
+ const rows = auto ? [{
34
+ value: AUTO, label: "Auto", detail: `may be picked on the first message, else ${fallback}`,
35
+ title: `A described kit may be suggested on the first message; else the session starts on ${fallback}`,
36
+ }] : [];
37
+ for (const kit of toolkits.kits || []) {
38
+ const own = kit.description || BUILTIN_WORDS[kit.name] || "";
39
+ const isDefault = kit.name === fallback;
40
+ rows.push({
41
+ value: kit.name, label: kit.name,
42
+ detail: isDefault ? [own, "default"].filter(Boolean).join(" · ") : own,
43
+ title: isDefault && auto ? `${kit.name}, picked: the session starts on it and gets no suggestion`
44
+ : kit.description || null,
45
+ });
46
+ }
47
+ const pressed = choice || (auto ? AUTO : fallback);
48
+ return rows.map((r) => ({ ...r, pressed: r.value === pressed }));
49
+ }
50
+
51
+ // What a row's pick sends: a kit's name, or null (Auto; the default's row
52
+ // without Auto).
53
+ export function pickValue(toolkits, value) {
54
+ if (value === AUTO) return null;
55
+ if (!offersAuto(toolkits) && value === toolkits?.default) return null;
56
+ return value;
57
+ }
58
+
59
+ // The row's override chip for +choice+, {text, title}, or null.
60
+ export function toolkitOverride(choice) {
61
+ if (!choice) return null;
62
+ return { text: `tools: ${choice}`, title: `Toolkit: ${choice} for the new session (no suggestion). Click to change it.` };
63
+ }
64
+
65
+ // 🧰's state: whether a kit is picked (its dot on a phone) and its tooltip
66
+ // and accessible name.
67
+ export function toolkitLabel(choice) {
68
+ return { set: !!choice, label: choice ? `${LIST_LABEL}: ${choice}` : LIST_LABEL };
69
+ }
70
+
71
+ // +state+: {toolkits, choice, review: review files set (with Auto checked
72
+ // a note says a review gets no suggestion), error: Start's refusal}.
73
+ export function listTemplate({ toolkits, choice, review = false, error = null }) {
74
+ const rows = toolkitRows(toolkits, choice);
75
+ return html`<div class="ctx-pop-head"><strong>Toolkit · new session</strong
76
+ ><button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div
77
+ ><div class="st-rows" role="group" aria-label="Toolkit">${rows.map((r) => html`<button type="button" class="st-row"
78
+ data-act=${`kit:${r.value}`} aria-pressed=${r.pressed ? "true" : "false"} title=${r.title || nothing}
79
+ ><span class="st-check" aria-hidden="true">${r.pressed ? "✓" : ""}</span><span class="st-name">${r.label}</span
80
+ ><span class="st-detail">${r.detail}</span></button>`)}</div
81
+ >${review && !choice && offersAuto(toolkits) ? html`<div class="ctx-pop-meta">A review session gets no suggestion: it starts on ${
82
+ toolkits.default} unless you pick a kit.</div>` : nothing
83
+ }<div class="ctx-pop-error" role="alert" ?hidden=${!error}>${error || ""}</div
84
+ ><div class="ctx-pop-meta">${NOTE}</div>`;
85
+ }
86
+
87
+ // The list on chip_popover.js' shell, a bottom sheet on a phone. +el+:
88
+ // where 🧰 is; +button+: 🧰 (the list opens at it; focus goes back to it);
89
+ // +openers+: the selector of every element that opens it (🧰 and the
90
+ // override chip). Hooks: +toolkits()+ the payload's toolkits (null: nothing
91
+ // opens), +choice()+, +choose(next)+, +review()+ whether review files are
92
+ // set.
93
+ // @return {open, toggle, close, refresh, refuse(message), isOpen}
94
+ export function createStartToolkit({ el, button, openers = `#${button.id}`, toolkits, choice, choose,
95
+ review = () => false, doc = document, win = window }) {
96
+ // Start's refusal of the picked kit (400 toolkit_refused), until a pick.
97
+ let error = null;
98
+
99
+ function draw() {
100
+ render(listTemplate({ toolkits: toolkits(), choice: choice(), review: review(), error }), shell.pop);
101
+ shell.replace();
102
+ }
103
+
104
+ const rowsOf = () => [...shell.pop.querySelectorAll(".st-row")];
105
+ // Focus on the checked row (the list sits at the end of <body>).
106
+ function focusInside() {
107
+ (shell.pop.querySelector(".st-row[aria-pressed=true]") || rowsOf()[0])?.focus();
108
+ }
109
+
110
+ const shell = createChipPopover({
111
+ id: "startToolkitPopover", className: "ctx-popover start-toolkit", width: 380, el, anchor: `#${button.id}`, openers,
112
+ summary: () => toolkits(), sheet: true, label: LIST_LABEL, doc, win,
113
+ show: () => draw(),
114
+ onOpen: focusInside,
115
+ onClose: () => { error = null; },
116
+ onAct: (name) => {
117
+ const [what, value] = name.split(/:(.*)/s);
118
+ if (what !== "kit") return;
119
+ error = null;
120
+ choose(pickValue(toolkits(), value));
121
+ shell.close({ refocus: true });
122
+ },
123
+ });
124
+ // ↑/↓ between the rows (Enter is the button's own click).
125
+ shell.pop.addEventListener("keydown", (e) => {
126
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
127
+ const rows = rowsOf();
128
+ const at = rows.indexOf(doc.activeElement);
129
+ if (at < 0) return;
130
+ e.preventDefault();
131
+ rows[Math.max(0, Math.min(rows.length - 1, at + (e.key === "ArrowDown" ? 1 : -1)))].focus();
132
+ });
133
+
134
+ function open() {
135
+ if (shell.isOpen()) {
136
+ draw();
137
+ focusInside();
138
+ } else {
139
+ shell.open(button);
140
+ }
141
+ }
142
+
143
+ return {
144
+ open,
145
+ // The override chip's click: opens the list, or closes it.
146
+ toggle: () => (shell.isOpen() ? shell.close() : open()),
147
+ close: () => shell.close(),
148
+ isOpen: () => shell.isOpen(),
149
+ refresh: () => { if (shell.isOpen()) draw(); },
150
+ // Start refused the picked kit (400 toolkit_refused): said in the list,
151
+ // opened again.
152
+ refuse: (message) => {
153
+ error = message;
154
+ open();
155
+ },
156
+ };
157
+ }
@@ -6,12 +6,9 @@
6
6
  // {level, source, own}; the worker's snapshot, a command_ran, or the
7
7
  // server's own reading of the session file). A config on level_chip.js.
8
8
  //
9
- // The start page has one too (kind "new session"): it shows the picked
10
- // model's level (GET /api/models' thinking) and keeps a choice for the
11
- // next create only (POST /api/sessions' thinking), never remembered:
12
- // nothing is sent unless the user sets one.
9
+ // The start page's new session sets its level in the ⚙ panel instead
10
+ // (start_settings.js, over LEVELS).
13
11
 
14
- import { SESSION } from "./llm_context_chip.js";
15
12
  import * as level from "./level_chip.js";
16
13
  import { html, nothing } from "./vendor/lit-html.js";
17
14
 
@@ -26,25 +23,16 @@ export const CONFIG = Object.freeze({
26
23
  formClass: "think-form",
27
24
  popoverClass: "think-popover",
28
25
  nowClass: "think-now",
29
- when: (fresh) => (fresh ? "For the new session's first turn on; not remembered for the next one."
30
- : "From the next turn's start. On Gemma the whole prompt is read again; a hosted API reads the whole conversation again."),
26
+ when: () => "From the next turn's start. On Gemma the whole prompt is read again; a hosted API reads the whole conversation again.",
31
27
  });
32
28
 
33
29
  export const { nowText } = level;
34
30
 
35
- // The new session's choice (a level, or null) laid over the model's summary:
36
- // what the chip shows. The model's own summary without a choice.
37
- export function withChoice(summary, choice) {
38
- if (!summary || !choice) return summary;
39
- return { ...summary, level: choice, source: SESSION, own: choice };
40
- }
41
-
42
- // The chip: its text, tooltip and whether the session (or the new session)
43
- // set its own; null without a summary (an older worker). The default with
44
- // nothing set anywhere shows too ("think default"): the chip is where the
45
- // level is set.
46
- export function chipModel(summary, opts) {
47
- return level.chipModel(CONFIG, summary, opts);
31
+ // The chip: its text, tooltip and whether the session set its own; null
32
+ // without a summary (an older worker). The default with nothing set
33
+ // anywhere shows too ("think default"): the chip is where the level is set.
34
+ export function chipModel(summary) {
35
+ return level.chipModel(CONFIG, summary);
48
36
  }
49
37
 
50
38
  // The session bar's chip (a lit-html template; info_bar.js), nothing
@@ -66,8 +54,7 @@ export function formTemplate(summary, opts) {
66
54
  return level.formTemplate(CONFIG, summary, opts);
67
55
  }
68
56
 
69
- // The chip's popover (level_chip.js createLevelControl); the start page's
70
- // passes kind NEW_SESSION, choice and choose.
57
+ // The chip's popover (level_chip.js createLevelControl).
71
58
  export function createThinkingControl({ id = "thinkingPopover", ...opts }) {
72
59
  return level.createLevelControl({ cfg: CONFIG, id, ...opts });
73
60
  }
@@ -22,6 +22,7 @@ require_relative "update_check"
22
22
  require_relative "token"
23
23
  require_relative "tunnel"
24
24
  require_relative "../config"
25
+ require_relative "../chi_home"
25
26
  require_relative "../child_env"
26
27
  require_relative "../installed_gem"
27
28
  require_relative "../paths"
@@ -219,25 +220,43 @@ module Samagotchi
219
220
  # No line before the exec: one on a hung-up terminal raises EIO, and
220
221
  # the restart would end in a dead chi web; the log says it.
221
222
  def self.reexec(argv, provider: nil, plan: nil, exec: nil)
222
- program = reexec_program
223
- raise ArgumentError, "RubyGems chi wrapper not found" if program.to_s.empty?
223
+ command = reexec_command(argv)
224
+ raise ArgumentError, "RubyGems chi wrapper not found" unless command
224
225
 
225
226
  env = ChildEnv.unbundled.merge(
226
227
  RESTARTED_ENV => "1",
227
228
  ADOPT_PIDS_ENV => SessionManager.live_spawn_pids.join(",")
228
229
  )
229
- Samagotchi::Log.info(:web, "restart", program: program)
230
+ Samagotchi::Log.info(:web, "restart", program: command[1] == "-e" ? "home:#{command[0]}" : command[1])
230
231
  runner = exec || ->(environment, *args) { Kernel.exec(environment, *args) }
231
- runner.call(env, RbConfig.ruby, program, *argv)
232
+ runner.call(env, *command)
232
233
  rescue SystemCallError, ArgumentError => e
233
234
  tunnel_down(provider, plan) if provider && plan
234
235
  warn "Error: couldn't restart chi web: #{e.message}"
235
236
  1
236
237
  end
237
238
 
238
- # What a restart execs: the installed gem's wrapper (it activates the
239
- # newest installed chi), this bin/chi in a checkout; nil for a gem
240
- # installed without one (--bindir).
239
+ # A restart's argv: with a chi home, its boot (ChiHome::CHI_BOOT: the
240
+ # newest chi in the home; a bare wrapper would activate the newest in
241
+ # whatever GEM_* the env has, off the home); else this Ruby and
242
+ # #reexec_program. A home whose newest chi is older than this one is
243
+ # passed over (logged): the restart argv has this chi's flags, which
244
+ # an older one may not know. nil when there is nothing to exec.
245
+ # @return [Array<String>, nil]
246
+ def self.reexec_command(argv, home: ChiHome.current)
247
+ if home && home.newest_version && home.newest_version < Gem::Version.new(Samagotchi::VERSION)
248
+ Samagotchi::Log.warn(:web, "restart_home_older", home: home.newest_version.to_s, version: Samagotchi::VERSION)
249
+ home = nil
250
+ end
251
+ return home.boot_argv(run: ChiHome::CHI_BOOT) + ["--", *argv] if home
252
+
253
+ program = reexec_program
254
+ program.to_s.empty? ? nil : [RbConfig.ruby, program, *argv]
255
+ end
256
+
257
+ # What a restart execs without a chi home: the installed gem's wrapper
258
+ # (it activates the newest installed chi), this bin/chi in a checkout;
259
+ # nil for a gem installed without one (--bindir).
241
260
  def self.reexec_program
242
261
  gem_spec = InstalledGem.spec
243
262
  gem_spec ? InstalledGem.wrapper(gem_spec) : $PROGRAM_NAME
@@ -354,7 +373,7 @@ module Samagotchi
354
373
  app.restart_web = lambda do
355
374
  restart_mutex.synchronize do
356
375
  next false if restart_requested
357
- next :no_wrapper if reexec_program.to_s.empty?
376
+ next :no_wrapper unless reexec_command([])
358
377
 
359
378
  restart_requested = true
360
379
  restart_thread = Thread.new do
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "json"
5
5
  require "time"
6
6
  require_relative "../atomic_file"
7
+ require_relative "../chi_home"
7
8
  require_relative "../config"
8
9
  require_relative "../gem_update"
9
10
  require_relative "../installed_gem"
@@ -27,7 +28,7 @@ module Samagotchi
27
28
  min_age_hours: Config.get("update.min_age_hours"),
28
29
  fetcher: -> { GemUpdate.new.releases }, clock: -> { Time.now },
29
30
  state_file: File.join(Paths.state_dir, "update_check.json"),
30
- gem_spec: InstalledGem.spec, bundler: nil, gem_dir: Gem.dir,
31
+ gem_spec: InstalledGem.spec, bundler: nil, gem_dir: ChiHome.gem_dir(gem_spec: gem_spec),
31
32
  installed_versions: InstalledVersions.new)
32
33
  @hub = hub
33
34
  @enabled = enabled
@@ -97,7 +98,7 @@ module Samagotchi
97
98
  # one answer), or nil: a source checkout, a Bundler run, a gem dir it
98
99
  # can't write.
99
100
  # @return [String, nil]
100
- def self.blocked_reason(gem_spec: InstalledGem.spec, bundler: nil, gem_dir: Gem.dir)
101
+ def self.blocked_reason(gem_spec: InstalledGem.spec, bundler: nil, gem_dir: ChiHome.gem_dir(gem_spec: gem_spec))
101
102
  bundler = defined?(::Bundler) || !ENV["BUNDLE_GEMFILE"].to_s.empty? if bundler.nil?
102
103
  return "checkout" unless gem_spec
103
104
  return "bundler" if bundler
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samagotchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.131.0
4
+ version: 0.133.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -256,6 +256,7 @@ files:
256
256
  - lib/samagotchi/bundles/mcp/plugin.rb
257
257
  - lib/samagotchi/bundles/model-notes/manifest.yml
258
258
  - lib/samagotchi/bundles/model-notes/model_notes_deepseek.md
259
+ - lib/samagotchi/bundles/model-notes/model_notes_tone.md
259
260
  - lib/samagotchi/bundles/model-notes/notes/README.md
260
261
  - lib/samagotchi/bundles/ruby/manifest.yml
261
262
  - lib/samagotchi/bundles/ruby/plugin.rb
@@ -276,7 +277,9 @@ files:
276
277
  - lib/samagotchi/bundles/work-cap/plugin.rb
277
278
  - lib/samagotchi/cache_confirm.rb
278
279
  - lib/samagotchi/cancellation_controller.rb
280
+ - lib/samagotchi/chi_home.rb
279
281
  - lib/samagotchi/chi_invocation.rb
282
+ - lib/samagotchi/chi_shim.rb
280
283
  - lib/samagotchi/child_env.rb
281
284
  - lib/samagotchi/child_move.rb
282
285
  - lib/samagotchi/child_reports.rb
@@ -371,6 +374,9 @@ files:
371
374
  - lib/samagotchi/guardrails/verdict.rb
372
375
  - lib/samagotchi/guardrails_command.rb
373
376
  - lib/samagotchi/hold_stop.rb
377
+ - lib/samagotchi/home_command.rb
378
+ - lib/samagotchi/home_doctor.rb
379
+ - lib/samagotchi/home_notice.rb
374
380
  - lib/samagotchi/hook_question.rb
375
381
  - lib/samagotchi/hook_routes.rb
376
382
  - lib/samagotchi/hooks.rb
@@ -497,6 +503,7 @@ files:
497
503
  - lib/samagotchi/plugin/http_transport.rb
498
504
  - lib/samagotchi/plugin/loader.rb
499
505
  - lib/samagotchi/plugin/peer.rb
506
+ - lib/samagotchi/plugin/prompt_sections.rb
500
507
  - lib/samagotchi/plugin/service.rb
501
508
  - lib/samagotchi/plugin/sessions.rb
502
509
  - lib/samagotchi/plugin/side_question.rb
@@ -505,6 +512,7 @@ files:
505
512
  - lib/samagotchi/plugin_session_state.rb
506
513
  - lib/samagotchi/plugin_tasks.rb
507
514
  - lib/samagotchi/process_group.rb
515
+ - lib/samagotchi/project_env.rb
508
516
  - lib/samagotchi/project_scope.rb
509
517
  - lib/samagotchi/prompt.rb
510
518
  - lib/samagotchi/prompt_files.rb
@@ -858,6 +866,8 @@ files:
858
866
  - lib/samagotchi/web/public/stage_view.js
859
867
  - lib/samagotchi/web/public/stage_watch.js
860
868
  - lib/samagotchi/web/public/start_page.js
869
+ - lib/samagotchi/web/public/start_settings.js
870
+ - lib/samagotchi/web/public/start_toolkit.js
861
871
  - lib/samagotchi/web/public/stop_sheet.js
862
872
  - lib/samagotchi/web/public/storage.js
863
873
  - lib/samagotchi/web/public/stream_handlers.js