samagotchi 0.148.0 → 0.150.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 +62 -1
- data/README.md +4 -2
- data/docs/architecture.md +3 -3
- data/docs/bundles.md +38 -1
- data/docs/cli.md +76 -24
- data/docs/configuration.md +8 -6
- data/docs/guardrails.md +2 -1
- data/docs/hooks.md +2 -1
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bridge.md +8 -0
- data/docs/internals/bundles.md +42 -14
- data/docs/internals/objectives.md +269 -0
- data/docs/internals/web-frontend.md +4 -1
- data/docs/internals/web-push.md +2 -2
- data/docs/internals/working-set.md +5 -2
- data/docs/memory.md +47 -9
- data/docs/plugins.md +10 -2
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +45 -2
- data/docs/system-one.md +17 -1
- data/docs/testing.md +5 -0
- data/lib/samagotchi/atomic_file.rb +3 -0
- data/lib/samagotchi/bootstrap_command.rb +5 -2
- data/lib/samagotchi/bridge/card_store.rb +52 -7
- data/lib/samagotchi/bridge.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +91 -68
- data/lib/samagotchi/bundler_env.rb +24 -0
- data/lib/samagotchi/bundles/coordinator/manifest.yml +6 -4
- data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +9 -0
- data/lib/samagotchi/bundles/decision-card/manifest.yml +13 -0
- data/lib/samagotchi/bundles/decision-card/plugin.rb +280 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +2 -1
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +3 -1
- data/lib/samagotchi/bundles/known-names/manifest.yml +3 -2
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +2 -2
- data/lib/samagotchi/bundles/source-links/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/chi_home.rb +4 -2
- data/lib/samagotchi/chi_shim.rb +2 -1
- data/lib/samagotchi/child_env.rb +10 -3
- data/lib/samagotchi/config.rb +7 -1
- data/lib/samagotchi/context_note.rb +17 -5
- data/lib/samagotchi/engine.rb +113 -6
- data/lib/samagotchi/engine_peers.rb +11 -0
- data/lib/samagotchi/git_env.rb +43 -0
- data/lib/samagotchi/guardrails/context.rb +2 -0
- data/lib/samagotchi/guardrails/pair_boundary.rb +3 -2
- data/lib/samagotchi/guardrails/targets.rb +4 -1
- data/lib/samagotchi/llm/chat_loop.rb +1 -1
- data/lib/samagotchi/memory_bundle/asset_installer.rb +16 -2
- data/lib/samagotchi/memory_bundle/file_standing.rb +167 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +62 -1
- data/lib/samagotchi/memory_bundle/installer.rb +161 -52
- data/lib/samagotchi/memory_bundle/listing.rb +4 -3
- data/lib/samagotchi/memory_bundle/merger.rb +209 -8
- data/lib/samagotchi/memory_bundle/placeholder.rb +2 -1
- data/lib/samagotchi/memory_bundle/profile.rb +7 -3
- data/lib/samagotchi/memory_bundle/provenance.rb +178 -20
- data/lib/samagotchi/memory_bundle/remerge.rb +167 -0
- data/lib/samagotchi/memory_bundle/shipped_history.rb +79 -0
- data/lib/samagotchi/memory_bundle/shipped_update.rb +89 -24
- data/lib/samagotchi/memory_bundle/source.rb +3 -3
- data/lib/samagotchi/memory_bundle/status.rb +21 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +98 -33
- data/lib/samagotchi/memory_bundle/text_merge.rb +399 -0
- data/lib/samagotchi/memory_bundle/trash.rb +58 -2
- data/lib/samagotchi/memory_bundle/uninstaller.rb +26 -6
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/note_delivery.rb +6 -2
- data/lib/samagotchi/objectives.rb +395 -0
- data/lib/samagotchi/objectives_audit.rb +115 -0
- data/lib/samagotchi/objectives_check.rb +365 -0
- data/lib/samagotchi/objectives_command.rb +192 -0
- data/lib/samagotchi/plugin/api.rb +2 -1
- data/lib/samagotchi/plugin/context.rb +21 -1
- data/lib/samagotchi/project_env.rb +3 -2
- data/lib/samagotchi/self_report.rb +2 -0
- data/lib/samagotchi/session_commands.rb +15 -0
- data/lib/samagotchi/session_continue.rb +3 -0
- data/lib/samagotchi/session_inbox.rb +14 -6
- data/lib/samagotchi/session_manager.rb +1 -1
- data/lib/samagotchi/steer.rb +24 -4
- data/lib/samagotchi/testing/boundary.rb +2 -0
- data/lib/samagotchi/testing/host.rb +1 -0
- data/lib/samagotchi/testing/minitest.rb +3 -0
- data/lib/samagotchi/testing/rspec.rb +3 -0
- data/lib/samagotchi/testing/sandbox.rb +3 -1
- data/lib/samagotchi/tool_activity.rb +17 -0
- data/lib/samagotchi/tool_declarations.rb +34 -0
- data/lib/samagotchi/tools/builtin_calls.rb +2 -1
- data/lib/samagotchi/tools/builtins.rb +4 -1
- data/lib/samagotchi/tools/delegate.rb +2 -1
- data/lib/samagotchi/tools/objectives_tool.rb +75 -0
- data/lib/samagotchi/tools/peers.rb +3 -0
- data/lib/samagotchi/tools/task_runtime.rb +13 -10
- data/lib/samagotchi/turn_flow.rb +3 -1
- data/lib/samagotchi/update_command.rb +76 -28
- data/lib/samagotchi/update_hint.rb +4 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/auto_update.rb +8 -8
- data/lib/samagotchi/web/auto_update_record.rb +6 -4
- data/lib/samagotchi/web/message_display.rb +9 -0
- data/lib/samagotchi/web/participant_view.rb +2 -2
- data/lib/samagotchi/web/public/app.js +15 -3
- data/lib/samagotchi/web/public/block_open.js +13 -0
- data/lib/samagotchi/web/public/bubbles.js +4 -2
- data/lib/samagotchi/web/public/cards_place.js +30 -9
- data/lib/samagotchi/web/public/commands.js +4 -1
- data/lib/samagotchi/web/public/details_motion.js +2 -1
- data/lib/samagotchi/web/public/index.html +20 -14
- data/lib/samagotchi/web/public/objectives.css +80 -0
- data/lib/samagotchi/web/public/objectives_entry.js +22 -0
- data/lib/samagotchi/web/public/objectives_model.js +100 -0
- data/lib/samagotchi/web/public/objectives_moves.js +145 -0
- data/lib/samagotchi/web/public/objectives_strip.js +242 -0
- data/lib/samagotchi/web/public/objectives_wiring.js +62 -0
- data/lib/samagotchi/web/public/stage_model.js +5 -6
- data/lib/samagotchi/web/public/stage_view.js +8 -7
- data/lib/samagotchi/web/public/stream_handlers.js +6 -1
- data/lib/samagotchi/web/public/turn_block.js +34 -19
- data/lib/samagotchi/web/public/turn_html.js +4 -1
- data/lib/samagotchi/web/public/turn_model.js +1 -1
- data/lib/samagotchi/web/public/update_sheet.js +8 -3
- data/lib/samagotchi/web/public/working_set_view.js +10 -7
- data/lib/samagotchi/web/push/notifier.rb +7 -9
- data/lib/samagotchi/web/session_hub.rb +2 -1
- data/lib/samagotchi/web/session_show.rb +12 -1
- data/lib/samagotchi/web/update_ops.rb +2 -1
- data/lib/samagotchi/worker.rb +3 -0
- data/lib/samagotchi/worker_inbound.rb +16 -8
- metadata +22 -1
|
@@ -9,6 +9,7 @@ require_relative "desktop"
|
|
|
9
9
|
require_relative "gem_update"
|
|
10
10
|
require_relative "installed_gem"
|
|
11
11
|
require_relative "live_versions"
|
|
12
|
+
require_relative "memory_bundle/file_standing"
|
|
12
13
|
require_relative "memory_bundle/shipped_update"
|
|
13
14
|
require_relative "memory_bundle/system_bundle"
|
|
14
15
|
require_relative "session"
|
|
@@ -20,8 +21,8 @@ require "json"
|
|
|
20
21
|
module Samagotchi
|
|
21
22
|
# `chi update`: brings an installed chi up to date in one go: the system
|
|
22
23
|
# bundle, the shipped bundles the user installed, and the macOS helper.
|
|
23
|
-
# Unedited things update; edited ones
|
|
24
|
-
# reported (hooks, plugin, rules, which didn't load once edited). Live
|
|
24
|
+
# Unedited things update; edited ones merge or stay behind (.md) or are replaced
|
|
25
|
+
# and reported (hooks, plugin, rules, which didn't load once edited). Live
|
|
25
26
|
# workers and a running chi web are reported, never restarted. The bundles
|
|
26
27
|
# chi didn't ship are only checked, under the chi that will run them
|
|
27
28
|
# (BundleCommand::InstalledCheck): what it finds is rows that fail nothing.
|
|
@@ -34,10 +35,15 @@ module Samagotchi
|
|
|
34
35
|
Updates this chi's install: the gem (from rubygems.org), then the
|
|
35
36
|
system bundle, the bundles chi ships that you installed (chi bundle
|
|
36
37
|
list), and the desktop helper (macOS; rebuilt only when its sources
|
|
37
|
-
changed).
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
a
|
|
38
|
+
changed). A memory file you edited takes the update in a text merge
|
|
39
|
+
(the old file goes to the bundle trash), or stays behind as it is when
|
|
40
|
+
no merge is possible, and the table says which and why; each run
|
|
41
|
+
retries a file left behind, though one an older chi marked, with a base
|
|
42
|
+
no installed chi shipped, merges only if the bundle takes out or rewords
|
|
43
|
+
no line it still holds. Nothing is
|
|
44
|
+
installed that isn't already, and old gem versions stay. The bundles chi doesn't ship are
|
|
45
|
+
never changed: the new chi checks them and adds a row per warning or
|
|
46
|
+
error (these don't fail the update).
|
|
41
47
|
--dry-run show what would change; change nothing (with a newer
|
|
42
48
|
gem to install, bundles chi doesn't ship aren't checked:
|
|
43
49
|
that chi would)
|
|
@@ -76,8 +82,11 @@ module Samagotchi
|
|
|
76
82
|
|
|
77
83
|
# A table row. info rows (live workers, chi web) say what runs; they
|
|
78
84
|
# never fail. level ("warn", "error"; else nil) marks a finding on a bundle
|
|
79
|
-
# chi didn't ship: it reports, changes nothing and fails nothing.
|
|
80
|
-
|
|
85
|
+
# chi didn't ship: it reports, changes nothing and fails nothing. kept: the
|
|
86
|
+
# memory files that stay behind; merged: the ones that took the update;
|
|
87
|
+
# trash_copy: where their old copies went, in full (the table's short: --json
|
|
88
|
+
# has the path); earlier: the files of the earlier merges the row names.
|
|
89
|
+
Row = Struct.new(:component, :from, :to, :status, :failed, :kept, :merged, :earlier, :trash_copy, :level, keyword_init: true)
|
|
81
90
|
|
|
82
91
|
# What a row says after a change, and in a dry run.
|
|
83
92
|
VERBS = { update: ["updated", "would update"], install: ["installed", "would install"],
|
|
@@ -95,6 +104,7 @@ module Samagotchi
|
|
|
95
104
|
gem_update: nil, exec: nil, bundler: nil, home: ChiHome, env: ENV)
|
|
96
105
|
@home = home
|
|
97
106
|
@in_home = env.delete(IN_HOME_ENV) == "1"
|
|
107
|
+
@unattended = env[ChildEnv::UNATTENDED_UPDATE] == "1"
|
|
98
108
|
# Built after the home is known: its install env goes into the home.
|
|
99
109
|
@gem_update_given = gem_update
|
|
100
110
|
@exec = exec || ->(argv) { Kernel.exec(*argv) }
|
|
@@ -147,7 +157,7 @@ module Samagotchi
|
|
|
147
157
|
gem = gem_row(options)
|
|
148
158
|
rows = [gem, system_bundle_row, *bundle_rows(options), *external_rows(options), desktop_row(options), *live_rows].compact
|
|
149
159
|
if options[:json]
|
|
150
|
-
@stdout.puts(JSON.generate(rows.map { |row| row.to_h.merge(failed: !!row.failed, kept: row.kept || []) }))
|
|
160
|
+
@stdout.puts(JSON.generate(rows.map { |row| row.to_h.merge(failed: !!row.failed, kept: row.kept || [], merged: row.merged || [], earlier: row.earlier || []) }))
|
|
151
161
|
else
|
|
152
162
|
print_table(rows)
|
|
153
163
|
footers(options, rows).each { |line| @stdout.puts(line) }
|
|
@@ -279,17 +289,24 @@ module Samagotchi
|
|
|
279
289
|
end
|
|
280
290
|
|
|
281
291
|
def system_bundle_row
|
|
282
|
-
|
|
292
|
+
system = MemoryBundle::SystemBundle
|
|
293
|
+
earlier = system.merge_notes
|
|
294
|
+
result = system.sync(dry_run: @dry_run)
|
|
295
|
+
# These rows have no place for the notes, which wait for one that has.
|
|
296
|
+
earlier = [] if %i[installed skipped failed].include?(result.status)
|
|
283
297
|
row = Row.new(component: "system bundle", from: result.from, to: result.to)
|
|
298
|
+
notes = file_notes(row, system::BUNDLE_NAME, result.to, result.merged, result.behind, result.trash_copy, earlier: earlier)
|
|
284
299
|
case result.status
|
|
285
300
|
when :installed then done(row, :install)
|
|
286
|
-
when :updated then done(row, :update,
|
|
287
|
-
when :restored then done(row, :restore).tap { |r| r.to = nil }
|
|
288
|
-
when :up_to_date then
|
|
289
|
-
when :newer_installed then skipped(row, "newer than this chi's #{result.to}: left").tap { |r| r.to = nil }
|
|
301
|
+
when :updated then done(row, :update, notes.empty? ? nil : notes.join("; "))
|
|
302
|
+
when :restored then done(row, :restore, notes.empty? ? nil : notes.join("; ")).tap { |r| r.to = nil }
|
|
303
|
+
when :merged, :up_to_date then same_version(row, notes + result.warnings, changed: result.merged.any?)
|
|
304
|
+
when :newer_installed then skipped(row, ["newer than this chi's #{result.to}: left", *notes].join("; ")).tap { |r| r.to = nil }
|
|
290
305
|
when :skipped then skipped(row, result.error)
|
|
291
306
|
else failed(row, result.error)
|
|
292
307
|
end
|
|
308
|
+
system.clear_merge_notes(earlier, files: result.merged) if read_by_someone?
|
|
309
|
+
row
|
|
293
310
|
end
|
|
294
311
|
|
|
295
312
|
def bundle_rows(options)
|
|
@@ -320,14 +337,50 @@ module Samagotchi
|
|
|
320
337
|
|
|
321
338
|
def bundle_row(update)
|
|
322
339
|
row = Row.new(component: update.name, from: update.from, to: update.to)
|
|
340
|
+
earlier = update.status == :failed ? [] : Array(update.earlier)
|
|
341
|
+
notes = file_notes(row, update.name, update.to || update.from, update.merged, update.behind, update.trash_copy, earlier: earlier)
|
|
323
342
|
case update.status
|
|
324
343
|
when :would_update, :updated
|
|
325
|
-
notes = [adds(update.adds),
|
|
344
|
+
notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own].compact
|
|
326
345
|
done(row, :update, notes.empty? ? nil : notes.join("; "))
|
|
327
|
-
when :up_to_date then
|
|
328
|
-
when :skipped then skipped(row, update.note)
|
|
329
|
-
else failed(row, update.note)
|
|
346
|
+
when :would_merge, :merged, :up_to_date then same_version(row, [*notes, update.note].compact, changed: Array(update.merged).any?)
|
|
347
|
+
when :skipped then skipped(row, [update.note, *notes].join("; "))
|
|
348
|
+
else failed(row, [update.note, *notes].compact.join("; "))
|
|
330
349
|
end
|
|
350
|
+
# A failed row keeps them: the run didn't finish, and the next says what stands.
|
|
351
|
+
MemoryBundle::Provenance.new(name: update.name).clear_merge_notes(earlier, files: row.merged) if read_by_someone? && update.status != :failed
|
|
352
|
+
row
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# What one bundle's memory files did, in FileStanding's words; the row keeps
|
|
356
|
+
# the file names and the trash copy's full line for --json. merged is what
|
|
357
|
+
# this run merged, earlier the files of the notes it shows.
|
|
358
|
+
# @return [Array<String>] the row's clauses
|
|
359
|
+
def file_notes(row, bundle, version, merged, behind, trash_copy, earlier: [])
|
|
360
|
+
merged = Array(merged)
|
|
361
|
+
behind = Array(behind)
|
|
362
|
+
row.merged = merged
|
|
363
|
+
row.earlier = earlier.map(&:file)
|
|
364
|
+
row.kept = behind.map(&:file)
|
|
365
|
+
@unsure_behind ||= behind.any?(&:unsure)
|
|
366
|
+
row.trash_copy = trash_copy
|
|
367
|
+
notes = MemoryBundle::FileStanding.clauses(bundle, version, merged: merged, behind: behind, earlier: earlier, dry_run: @dry_run)
|
|
368
|
+
trash_copy || earlier.any?(&:trash) ? [*notes, MemoryBundle::FileStanding::IN_THE_TRASH] : notes
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Whether a row printed now ends the notes for what it printed, this run's
|
|
372
|
+
# merges too. A dry run changes nothing, and an automatic update's rows may be
|
|
373
|
+
# read by no one (ChildEnv::UNATTENDED_UPDATE).
|
|
374
|
+
def read_by_someone? = !@dry_run && !@unattended
|
|
375
|
+
|
|
376
|
+
# A row at the same version: up to date only when nothing merged and nothing
|
|
377
|
+
# stays behind. No to, and no verb of its own: the clauses say it all.
|
|
378
|
+
def same_version(row, notes, changed:)
|
|
379
|
+
return up_to_date(row, notes.join("; ")) unless changed || row.kept.any?
|
|
380
|
+
|
|
381
|
+
row.to = nil
|
|
382
|
+
row.status = notes.join("; ")
|
|
383
|
+
row
|
|
331
384
|
end
|
|
332
385
|
|
|
333
386
|
def desktop_row(options)
|
|
@@ -420,6 +473,9 @@ module Samagotchi
|
|
|
420
473
|
return "(dry run: nothing was changed)" if @dry_run
|
|
421
474
|
return "some parts failed (see above)" if rows.any?(&:failed)
|
|
422
475
|
|
|
476
|
+
behind = rows.sum { |row| Array(row.kept).size }
|
|
477
|
+
return "#{MemoryBundle::FileStanding.stay_behind(behind, unsure: @unsure_behind)} (see above)" if behind.positive?
|
|
478
|
+
|
|
423
479
|
# A finding on a bundle chi didn't ship repeats until its bundle changes; it isn't a change.
|
|
424
480
|
changes = rows.reject(&:level)
|
|
425
481
|
changes.all? { |r| r.status.start_with?("up to date", "skipped", "not installed", "couldn't check") } ? "everything is up to date" : "done"
|
|
@@ -434,9 +490,9 @@ module Samagotchi
|
|
|
434
490
|
row
|
|
435
491
|
end
|
|
436
492
|
|
|
437
|
-
def up_to_date(row)
|
|
493
|
+
def up_to_date(row, note = nil)
|
|
438
494
|
row.to = nil
|
|
439
|
-
row.status = "up to date"
|
|
495
|
+
row.status = note.to_s.empty? ? "up to date" : "up to date (#{note})"
|
|
440
496
|
row
|
|
441
497
|
end
|
|
442
498
|
|
|
@@ -456,14 +512,6 @@ module Samagotchi
|
|
|
456
512
|
members.nil? || members.empty? ? nil : "+ #{members.join(", ")}"
|
|
457
513
|
end
|
|
458
514
|
|
|
459
|
-
def kept(row, files, bundle)
|
|
460
|
-
row.kept = files.dup
|
|
461
|
-
return nil if row.kept.empty?
|
|
462
|
-
|
|
463
|
-
verb = @dry_run ? "would keep" : "kept"
|
|
464
|
-
"#{verb} your edits in #{row.kept.join(", ")}: chi bundle diff #{bundle} #{row.kept.first}"
|
|
465
|
-
end
|
|
466
|
-
|
|
467
515
|
def replaced(files)
|
|
468
516
|
return nil if files.empty?
|
|
469
517
|
|
|
@@ -43,7 +43,10 @@ module Samagotchi
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def message(version, shipped_dir:, helper:)
|
|
46
|
-
|
|
46
|
+
# A retry that would merge is an update too; one that only records a reason isn't.
|
|
47
|
+
bundles = MemoryBundle::ShippedUpdate.plan(shipped_dir: shipped_dir).count do |r|
|
|
48
|
+
r.status == :would_update || (r.status == :would_merge && r.merged.any?)
|
|
49
|
+
end
|
|
47
50
|
parts = []
|
|
48
51
|
parts << (bundles == 1 ? "1 bundle" : "#{bundles} bundles") if bundles.positive?
|
|
49
52
|
parts << "the desktop helper" if helper&.stale?
|
data/lib/samagotchi/version.rb
CHANGED
|
@@ -197,7 +197,7 @@ module Samagotchi
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# A newer chi this didn't install, settled: a run of its own from here.
|
|
200
|
-
# The terminal's table already told what `chi update`
|
|
200
|
+
# The terminal's table already told what `chi update` merged or left behind.
|
|
201
201
|
def adopt_installed
|
|
202
202
|
return unless @restart == "idle" && @seen
|
|
203
203
|
|
|
@@ -215,10 +215,10 @@ module Samagotchi
|
|
|
215
215
|
return Log.info(:web, "auto_update_deferred", version: version, why: result) if result.is_a?(Symbol)
|
|
216
216
|
|
|
217
217
|
rows = result.rows
|
|
218
|
-
record = AutoUpdateRecord.new(version: version, from: VERSION, at: @clock.call.iso8601, kept:
|
|
219
|
-
phase: "installed")
|
|
218
|
+
record = AutoUpdateRecord.new(version: version, from: VERSION, at: @clock.call.iso8601, kept: files_in(rows, "kept"),
|
|
219
|
+
merged: files_in(rows, "merged"), phase: "installed")
|
|
220
220
|
if result.done?
|
|
221
|
-
Log.info(:web, "auto_update_installed", version: version, kept: record.kept.size)
|
|
221
|
+
Log.info(:web, "auto_update_installed", version: version, kept: record.kept.size, merged: record.merged.size)
|
|
222
222
|
installed(record)
|
|
223
223
|
else
|
|
224
224
|
failed(record, rows)
|
|
@@ -232,7 +232,7 @@ module Samagotchi
|
|
|
232
232
|
return carry_on(save(record)) unless @restart == "never"
|
|
233
233
|
|
|
234
234
|
save(record.with(phase: "done", pushed: true))
|
|
235
|
-
@hub.publish_notice(kind: "installed", version: record.version, kept: record.kept.size)
|
|
235
|
+
@hub.publish_notice(kind: "installed", version: record.version, kept: record.kept.size, merged: record.merged.size)
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
# The next step of a run on record.
|
|
@@ -328,7 +328,7 @@ module Samagotchi
|
|
|
328
328
|
record = restart_workers(record) || record
|
|
329
329
|
Log.info(:web, "auto_update_done", version: record.version, restarted: record.restarted, left: record.left)
|
|
330
330
|
save(record.with(phase: record.left_to_try?(@clock.call, RETRY_FOR) ? RETRYING : "done", pushed: true))
|
|
331
|
-
@hub.publish_notice(kind: "updated", version: record.version, kept: record.kept.size,
|
|
331
|
+
@hub.publish_notice(kind: "updated", version: record.version, kept: record.kept.size, merged: record.merged.size,
|
|
332
332
|
restarted: record.restarted, left: record.left)
|
|
333
333
|
end
|
|
334
334
|
|
|
@@ -366,8 +366,8 @@ module Samagotchi
|
|
|
366
366
|
installed: gem_installed)
|
|
367
367
|
end
|
|
368
368
|
|
|
369
|
-
def
|
|
370
|
-
rows.flat_map { |row| Array(row[
|
|
369
|
+
def files_in(rows, field)
|
|
370
|
+
rows.flat_map { |row| Array(row[field]).map(&:to_s) }.uniq
|
|
371
371
|
end
|
|
372
372
|
end
|
|
373
373
|
end
|
|
@@ -13,8 +13,10 @@ module Samagotchi
|
|
|
13
13
|
# +phase+: installed, workers, web (AutoUpdate::OPEN_PHASES), retrying,
|
|
14
14
|
# done or failed. +restarted+ and +left+ count the stale workers (`workers` in
|
|
15
15
|
# the file); +at+ is when the install ended, an ISO 8601 string.
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
# +kept+ and +merged+ name the edited memory files that stayed behind and
|
|
17
|
+
# that took the update (chi update --json's).
|
|
18
|
+
AutoUpdateRecord = Data.define(:version, :from, :at, :kept, :merged, :phase, :restarted, :left, :detail, :pushed) do
|
|
19
|
+
def initialize(version:, phase:, from: nil, at: nil, kept: [], merged: [], restarted: 0, left: 0, detail: nil, pushed: false)
|
|
18
20
|
super
|
|
19
21
|
end
|
|
20
22
|
|
|
@@ -30,14 +32,14 @@ module Samagotchi
|
|
|
30
32
|
|
|
31
33
|
workers = hash["workers"].is_a?(Hash) ? hash["workers"] : {}
|
|
32
34
|
new(version: version, phase: phase, from: hash["from"], at: hash["at"],
|
|
33
|
-
kept: Array(hash["kept"]).grep(String), restarted: workers["restarted"].to_i, left: workers["left"].to_i,
|
|
35
|
+
kept: Array(hash["kept"]).grep(String), merged: Array(hash["merged"]).grep(String), restarted: workers["restarted"].to_i, left: workers["left"].to_i,
|
|
34
36
|
detail: hash["detail"], pushed: hash["pushed"] == true)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
# What goes in the file, for .from_h to read back.
|
|
38
40
|
# @return [Hash{String => Object}]
|
|
39
41
|
def to_store
|
|
40
|
-
{ "version" => version, "from" => from, "at" => at, "kept" => kept, "phase" => phase,
|
|
42
|
+
{ "version" => version, "from" => from, "at" => at, "kept" => kept, "merged" => merged, "phase" => phase,
|
|
41
43
|
"workers" => { "restarted" => restarted, "left" => left }, "detail" => detail,
|
|
42
44
|
"pushed" => pushed ? true : nil }.compact
|
|
43
45
|
end
|
|
@@ -7,6 +7,7 @@ require_relative "../context_note"
|
|
|
7
7
|
require_relative "../image_store"
|
|
8
8
|
require_relative "../message_parts"
|
|
9
9
|
require_relative "../message_time"
|
|
10
|
+
require_relative "../objectives"
|
|
10
11
|
require_relative "../output_formatter"
|
|
11
12
|
require_relative "../recap_store"
|
|
12
13
|
require_relative "../ref"
|
|
@@ -95,11 +96,19 @@ module Samagotchi
|
|
|
95
96
|
list = Array(msgs)
|
|
96
97
|
# The outputs' ✂ marks (LLM context edits), by id, for the parts.
|
|
97
98
|
marks = parts ? MessageParts.edit_marks(list) : {}
|
|
99
|
+
# The list before each objectives note, for what its entry says changed.
|
|
100
|
+
objectives = nil
|
|
98
101
|
list.each_with_index do |m, index|
|
|
99
102
|
role = (m[:role] || m["role"]).to_s
|
|
100
103
|
content = (m[:content] || m["content"]).to_s
|
|
101
104
|
if Samagotchi::ContextNote.note?(m)
|
|
102
105
|
note = { role: "note", content: Samagotchi::ContextNote.text_of(m), label: Samagotchi::ContextNote.label_of(m), **time_of(m) }
|
|
106
|
+
payload = Samagotchi::ContextNote.fetch(m, :objectives)
|
|
107
|
+
entry = payload && Samagotchi::Objectives.entry(payload, before: objectives)
|
|
108
|
+
if entry
|
|
109
|
+
note[:objectives] = entry
|
|
110
|
+
objectives = Samagotchi::Objectives.from_payload(payload)
|
|
111
|
+
end
|
|
103
112
|
# A context wake turn's note starts that turn (Steer.turn_prompt?):
|
|
104
113
|
# the page opens the turn with it and pairs it with its record.
|
|
105
114
|
if Samagotchi::Steer.turn_prompt?(m)
|
|
@@ -82,10 +82,10 @@ module Samagotchi
|
|
|
82
82
|
after_generation after_tool_call after_turn answer_display before_generation before_tool_call before_turn
|
|
83
83
|
card command_output command_queued command_ran context_added context_status empty_answer_retry
|
|
84
84
|
generation_cancelled generation_completed generation_progress generation_started guardrail_warning hook_notice
|
|
85
|
-
llm_context_edited merged_input participants_changed pending_input_merged plugin_init_finished
|
|
85
|
+
llm_context_edited merged_input objectives_updated participants_changed pending_input_merged plugin_init_finished
|
|
86
86
|
plugin_init_started plugin_init_wait prompt_restored question question_relay reasoning_history_refused
|
|
87
87
|
recap_ready reminder_injected sampling_refused session_end session_start side_usage steer_cut thinking_dropped
|
|
88
|
-
thinking_refused tool_dispatch_completed tool_dispatch_started used_memories_updated working_set_updated
|
|
88
|
+
thinking_refused tool_dispatch_completed tool_dispatch_started turn_rolled_back used_memories_updated working_set_updated
|
|
89
89
|
].freeze
|
|
90
90
|
|
|
91
91
|
# A merged input line's saved sources a participant sees: a person's
|
|
@@ -54,6 +54,7 @@ import { createBubbles, hintEl, outputBubbleTemplate } from "./bubbles.js";
|
|
|
54
54
|
import { createCardsPlace } from "./cards_place.js";
|
|
55
55
|
import { createStageView } from "./stage_view.js";
|
|
56
56
|
import { createWorkingSet, groupItems } from "./working_set_view.js";
|
|
57
|
+
import { createObjectives } from "./objectives_wiring.js";
|
|
57
58
|
import { turnGroups } from "./timing.js";
|
|
58
59
|
import { createGapClock, lastActivity } from "./time_gaps.js";
|
|
59
60
|
import { createMarkdownPlace } from "./markdown_place.js";
|
|
@@ -421,6 +422,10 @@ const deliveryControl = createDeliveryControl({
|
|
|
421
422
|
// This tab's id in the session's events: tells its own prompts from the
|
|
422
423
|
// ones other clients (an attached TUI, another tab, reminders) sent.
|
|
423
424
|
const clientId = newClientId();
|
|
425
|
+
const objectives = createObjectives({
|
|
426
|
+
el: $("#objectivesStrip"), historyEl, session: () => store.selected, running: () => liveTurn.running, holding: () => turnDom.holds(), commands: () => store.current.commands,
|
|
427
|
+
clientId, sendCommand, sendTurn, follow: () => followWokenWorker(), toast: (...args) => showToast(...args),
|
|
428
|
+
});
|
|
424
429
|
// The question and continue cards in the history (question_cards.js).
|
|
425
430
|
const questionCards = createQuestionCards({
|
|
426
431
|
sessionId: () => store.selected,
|
|
@@ -1017,6 +1022,7 @@ function forgetSession({ keepVerify = false } = {}) {
|
|
|
1017
1022
|
});
|
|
1018
1023
|
commandList.close();
|
|
1019
1024
|
resetTurnView();
|
|
1025
|
+
objectives.reset();
|
|
1020
1026
|
seedInitTasks(initTasks, []);
|
|
1021
1027
|
renderInitRow();
|
|
1022
1028
|
setMarkdownWarning(null);
|
|
@@ -1178,11 +1184,13 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1178
1184
|
if (data.messages && data.messages.length) renderHistory(data.messages);
|
|
1179
1185
|
else renderHistory(data.history || []);
|
|
1180
1186
|
seedWorkingSet(data);
|
|
1187
|
+
objectives.seed(data.objectives);
|
|
1181
1188
|
restoreCommandBubbles(historyEl, kept);
|
|
1182
1189
|
// A failed turn's prompt went back to the user: shown after the
|
|
1183
1190
|
// conversation, marked failed, as it was live (prompt_restored).
|
|
1184
|
-
|
|
1185
|
-
|
|
1191
|
+
const failedPrompt = data.failed_turn ? makeUserBubble(data.failed_turn.prompt, { state: "failed" }) : null;
|
|
1192
|
+
if (failedPrompt) {
|
|
1193
|
+
failedPrompt.dataset.failedTurn = "1";
|
|
1186
1194
|
addEndBubble(failedTurnText(data.failed_turn));
|
|
1187
1195
|
}
|
|
1188
1196
|
seedSeenFromDom();
|
|
@@ -1196,7 +1204,8 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1196
1204
|
// A live worker's last cards and between-turns notices, where they
|
|
1197
1205
|
// arrived; the running turn's go into its step once it is drawn.
|
|
1198
1206
|
const snapshotCards = splitSnapshotCards(data.cards, pageCapabilities);
|
|
1199
|
-
|
|
1207
|
+
// Above a failed turn's prompt: it came after them, and it is no turn.
|
|
1208
|
+
placeSnapshotCards(snapshotCards.placed, { before: failedPrompt });
|
|
1200
1209
|
// The recap saved with the session (also a stopped one's), else a live
|
|
1201
1210
|
// worker's from before saved_recap existed. After the cards: the last
|
|
1202
1211
|
// turn's check-in notice goes to the end, and the recap closes the page.
|
|
@@ -1761,6 +1770,8 @@ const streamHandlers = createStreamHandlers({
|
|
|
1761
1770
|
// cards_place.js
|
|
1762
1771
|
upsertCard, refreshCardBodies,
|
|
1763
1772
|
syncModelPick: startPage.commandRan,
|
|
1773
|
+
objectivesUpdated: (data) => objectives.update(data),
|
|
1774
|
+
cardActionFailed: cards.actionFailed,
|
|
1764
1775
|
});
|
|
1765
1776
|
|
|
1766
1777
|
async function refreshTimingFromSession() {
|
|
@@ -2204,6 +2215,7 @@ store.on("settingsPanel", () => settingsControl.refresh());
|
|
|
2204
2215
|
store.on("workingSetStrategy", () => workingSet?.strategyChanged());
|
|
2205
2216
|
store.on("featureGates", () => featureGates.check(store.current));
|
|
2206
2217
|
store.on("composer", updateComposerMode);
|
|
2218
|
+
store.on("composer", () => objectives.redraw());
|
|
2207
2219
|
// The answers' Revise buttons follow liveTurn.running too (none on the
|
|
2208
2220
|
// running turn's answer), which touches the composer.
|
|
2209
2221
|
store.on("composer", () => reviseStacks.decorate());
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Whether a finished turn's steps block stays open: a completed turn's closes
|
|
2
|
+
// to its summary line, whatever its steps said (opened, it shows the outline).
|
|
3
|
+
// A canceled or failed turn with an outline stays open: its last narrated step
|
|
4
|
+
// is the only text the reader gets. A "gone" end (the worker left) counts as
|
|
5
|
+
// completed: its partial answer bubble carries the text, and a reload, which
|
|
6
|
+
// can't tell it from one, draws the same. The live end, the stage's hand-off
|
|
7
|
+
// and a reload all ask here.
|
|
8
|
+
//
|
|
9
|
+
// A module of its own: static JS is cached at unversioned URLs, and a new
|
|
10
|
+
// export on an old module would blank the page.
|
|
11
|
+
export function openAfterEnd(kind, outlined) {
|
|
12
|
+
return !!outlined && (kind === "canceled" || kind === "failed");
|
|
13
|
+
}
|
|
@@ -26,8 +26,10 @@ export function removeQueuedBubble(bubble) {
|
|
|
26
26
|
if (slot && slot.childElementCount === 0) slot.remove();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// A prompt that started a turn: not a line steered into a running one
|
|
30
|
-
|
|
29
|
+
// A prompt that started a turn: not a line steered into a running one, nor a
|
|
30
|
+
// failed turn's prompt, drawn again after the history (it went back to its
|
|
31
|
+
// sender: the session and the turn count have no such turn).
|
|
32
|
+
export const TURN_PROMPT = ".bubble.user:not(.steered):not(.failed)";
|
|
31
33
|
|
|
32
34
|
// The history's turn prompts, oldest first (what turns_since counts).
|
|
33
35
|
export function turnPromptBubbles(historyEl) {
|
|
@@ -46,8 +46,10 @@ export function turnAnswerEl(turnEls) {
|
|
|
46
46
|
// (bubbles.js createBubbles)
|
|
47
47
|
// @param followWokenWorker () follows the worker a card's command woke
|
|
48
48
|
// @param api {getSession, sendCommand} (data.js)
|
|
49
|
-
// @return {cardElement(id), upsertCard(card, {before}), placeSnapshotCards(entries),
|
|
49
|
+
// @return {cardElement(id), upsertCard(card, {before}), placeSnapshotCards(entries, {before}),
|
|
50
50
|
// refreshCardBodies(), onCardAction(event)}
|
|
51
|
+
const HELD_UNTIL_ANSWERED = /^\/objectives --card /;
|
|
52
|
+
|
|
51
53
|
export function createCardsPlace({
|
|
52
54
|
historyEl, turnDom, findInTurnRoots, selected, clientId, questionCards, bubbles,
|
|
53
55
|
followWokenWorker, api = { getSession, sendCommand },
|
|
@@ -61,9 +63,12 @@ export function createCardsPlace({
|
|
|
61
63
|
// running turn's card as a row of its step; else before +before+ or at the
|
|
62
64
|
// end of the history). A running turn's card with actions asks the user
|
|
63
65
|
// something (check-in's Nudge / Keep going / Stop): it goes under the turn's
|
|
64
|
-
// block, not into a step that collapses when the next one starts.
|
|
66
|
+
// block, not into a step that collapses when the next one starts. A turn
|
|
67
|
+
// still sliding away arrives first: its list of pieces holds this card's
|
|
68
|
+
// element, and an update may swap that (drawCard, a notice to a card).
|
|
65
69
|
function upsertCard(card, { before = null } = {}) {
|
|
66
70
|
if (!card || !card.id) return;
|
|
71
|
+
turnDom.settleFold?.();
|
|
67
72
|
const existing = cardElement(card.id);
|
|
68
73
|
if (existing) {
|
|
69
74
|
const drawn = drawCard(existing, card);
|
|
@@ -79,11 +84,13 @@ export function createCardsPlace({
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
// A snapshot's cards and notices, each above the first turn after it; a
|
|
82
|
-
// turn's own notice where it showed live (placeTurnNotice).
|
|
83
|
-
|
|
87
|
+
// turn's own notice where it showed live (placeTurnNotice). +end+: what
|
|
88
|
+
// closes the history below them (a failed turn's prompt), for those with
|
|
89
|
+
// no turn after them.
|
|
90
|
+
function placeSnapshotCards(entries, { before: end = null } = {}) {
|
|
84
91
|
const users = turnPromptBubbles(historyEl);
|
|
85
92
|
for (const entry of entries) {
|
|
86
|
-
const before = cardPlace(users, entry);
|
|
93
|
+
const before = cardPlace(users, entry) || end;
|
|
87
94
|
if (isCard(entry)) upsertCard(entry, { before });
|
|
88
95
|
else if (entry.type === "question") placeResolvedQuestion(entry, before);
|
|
89
96
|
else if (entry.type === "guardrail_warning") bubbles.showGuardrailWarning(entry.message, entry.label || "guardrails", { before });
|
|
@@ -162,7 +169,18 @@ export function createCardsPlace({
|
|
|
162
169
|
}, 150);
|
|
163
170
|
}
|
|
164
171
|
|
|
165
|
-
//
|
|
172
|
+
// Its command line's card is back in play: the worker refused the action.
|
|
173
|
+
function actionFailed(line) {
|
|
174
|
+
for (const button of findInTurnRoots(".plugin-card .card-action")) {
|
|
175
|
+
if (button.dataset.command === line) {
|
|
176
|
+
button.closest(".plugin-card").querySelectorAll(".card-action").forEach((b) => { b.disabled = false; });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// A card's action button (a click anywhere in the turn roots). A proposal
|
|
182
|
+
// card's buttons stay off until its folded card replaces them or the
|
|
183
|
+
// action fails (actionFailed): a second tap has nothing to do.
|
|
166
184
|
function onCardAction(e) {
|
|
167
185
|
const button = e.target.closest(".plugin-card .card-action");
|
|
168
186
|
const id = selected();
|
|
@@ -170,7 +188,9 @@ export function createCardsPlace({
|
|
|
170
188
|
const card = button.closest(".plugin-card");
|
|
171
189
|
const err = card.querySelector(".card-error");
|
|
172
190
|
err.classList.add("hidden");
|
|
173
|
-
|
|
191
|
+
const held = HELD_UNTIL_ANSWERED.test(button.dataset.command);
|
|
192
|
+
const off = held ? [...card.querySelectorAll(".card-action")] : [button];
|
|
193
|
+
off.forEach((b) => { b.disabled = true; });
|
|
174
194
|
api.sendCommand(id, button.dataset.command, { clientId, card: true })
|
|
175
195
|
.then(() => {
|
|
176
196
|
// The command woke the worker: follow its stream if none is open
|
|
@@ -180,9 +200,10 @@ export function createCardsPlace({
|
|
|
180
200
|
.catch((error) => {
|
|
181
201
|
err.textContent = error.message;
|
|
182
202
|
err.classList.remove("hidden");
|
|
203
|
+
off.forEach((b) => { b.disabled = false; });
|
|
183
204
|
})
|
|
184
|
-
.finally(() => { button.disabled = false; });
|
|
205
|
+
.finally(() => { if (!held) button.disabled = false; });
|
|
185
206
|
}
|
|
186
207
|
|
|
187
|
-
return { cardElement, upsertCard, placeSnapshotCards, refreshCardBodies, onCardAction };
|
|
208
|
+
return { cardElement, upsertCard, placeSnapshotCards, refreshCardBodies, onCardAction, actionFailed };
|
|
188
209
|
}
|
|
@@ -163,6 +163,8 @@ export function continueLine(choice, reason = "") {
|
|
|
163
163
|
const QUEUED_TEXT = "queued: runs after this turn";
|
|
164
164
|
const APPLY_TEXT = "Apply (restarts the cache)";
|
|
165
165
|
|
|
166
|
+
const OBJECTIVES_LINE = /^\/objectives\b/;
|
|
167
|
+
|
|
166
168
|
export function commandView(event, myId) {
|
|
167
169
|
const own = isOwn(event.client_id, myId);
|
|
168
170
|
const waiting = event.waits === "turn_end";
|
|
@@ -181,7 +183,8 @@ export function commandView(event, myId) {
|
|
|
181
183
|
commandId: event.command_id || null,
|
|
182
184
|
confirm: typeof event.confirm?.line === "string" ? event.confirm.line : null,
|
|
183
185
|
// A card's action (Nudge…): no bubble unless it says something back.
|
|
184
|
-
|
|
186
|
+
// A tap's /objectives edit: the strip and the entry are its answer.
|
|
187
|
+
hidden: event.card === true && event.status !== "error" && (!event.output || OBJECTIVES_LINE.test(event.line || "")),
|
|
185
188
|
};
|
|
186
189
|
}
|
|
187
190
|
|
|
@@ -56,7 +56,8 @@ export function installDetailsMotion({ root, selector, motion = canAnimate }) {
|
|
|
56
56
|
const summary = e.target.closest?.("summary");
|
|
57
57
|
const details = summary?.parentElement;
|
|
58
58
|
if (!details || !details.matches?.(selector) || summary.parentElement !== details) return;
|
|
59
|
-
|
|
59
|
+
// A live part of the summary (a steps block's footprint opens its own view).
|
|
60
|
+
if (e.target.closest("a, button, input, label, .ws-foot") !== null && e.target !== summary) return;
|
|
60
61
|
if (!motion(details)) return;
|
|
61
62
|
e.preventDefault();
|
|
62
63
|
if (busy.has(details)) return;
|