samagotchi 0.129.0 → 0.130.1
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 +77 -1
- data/docs/architecture.md +1 -1
- data/docs/cli.md +29 -11
- data/docs/configuration.md +7 -2
- data/docs/desktop.md +28 -9
- data/docs/guardrails.md +60 -16
- data/docs/internals/background-tasks.md +12 -0
- data/docs/internals/engine.md +4 -3
- data/docs/internals/participants.md +25 -6
- data/docs/internals/remote-sessions.md +5 -4
- data/docs/sessions.md +5 -3
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos.rb +99 -19
- data/lib/samagotchi/desktop_command.rb +7 -2
- data/lib/samagotchi/engine.rb +20 -4
- data/lib/samagotchi/engine_peers.rb +3 -1
- data/lib/samagotchi/guardrails/chi_dirs.rb +64 -0
- data/lib/samagotchi/guardrails/child_boundary.rb +5 -5
- data/lib/samagotchi/guardrails/outside.rb +22 -20
- data/lib/samagotchi/guardrails/pair_boundary.rb +14 -3
- data/lib/samagotchi/guardrails/pair_command.rb +70 -10
- data/lib/samagotchi/guardrails/parent_approvals.rb +8 -11
- data/lib/samagotchi/guardrails/path_resolve.rb +231 -0
- data/lib/samagotchi/guardrails/protected_paths.rb +17 -116
- data/lib/samagotchi/guardrails/review_paths.rb +13 -11
- data/lib/samagotchi/guardrails/rm_targets.rb +5 -5
- data/lib/samagotchi/guardrails/rules.rb +23 -5
- data/lib/samagotchi/guardrails/scratch_writes.rb +3 -2
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +17 -12
- data/lib/samagotchi/guardrails/shell_paths.rb +18 -29
- data/lib/samagotchi/guardrails/targets.rb +24 -13
- data/lib/samagotchi/remote_command.rb +13 -3
- data/lib/samagotchi/remotes.rb +3 -2
- data/lib/samagotchi/review_start.rb +3 -2
- data/lib/samagotchi/session.rb +49 -4
- data/lib/samagotchi/session_inbox.rb +17 -0
- data/lib/samagotchi/system_one/self_protect.rb +34 -19
- data/lib/samagotchi/terminal_ui.rb +2 -0
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tools/builtins.rb +10 -1
- data/lib/samagotchi/tools/execute.rb +5 -2
- data/lib/samagotchi/tools/peers.rb +4 -0
- data/lib/samagotchi/tools/task_runtime.rb +4 -2
- data/lib/samagotchi/tools/task_wait.rb +26 -1
- data/lib/samagotchi/turn_state.rb +23 -4
- data/lib/samagotchi/update_command.rb +9 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +69 -25
- data/lib/samagotchi/web/participant_review.rb +9 -2
- data/lib/samagotchi/web/proxy_gate.rb +14 -6
- data/lib/samagotchi/web/server.rb +29 -4
- data/lib/samagotchi/web/token.rb +20 -1
- data/lib/samagotchi/worker.rb +16 -1
- metadata +3 -1
|
@@ -118,11 +118,15 @@ module Samagotchi
|
|
|
118
118
|
puts "chi web already runs on port #{port} (pid #{found["pid"]}): #{url}"
|
|
119
119
|
other_start = start_given && other_start_line(found["start_dir"], start_dir)
|
|
120
120
|
puts other_start if other_start
|
|
121
|
-
puts running_lan_lines(found["lan"], port) if found["lan"]
|
|
122
121
|
running_public = public_url || Running.live_public_url(probe: ->(at) { probe(host, at) })
|
|
122
|
+
# On the LAN this machine's page needs the token too: the link
|
|
123
|
+
# printed and opened here carries an owner's one-time code.
|
|
124
|
+
here = found["lan"] && !running_public && Token.read(Token.path) ? local_owner_link(url) : nil
|
|
125
|
+
puts local_lines(here) if here
|
|
126
|
+
puts running_lan_lines(found["lan"], port) if found["lan"]
|
|
123
127
|
puts running_public_lines(running_public) if running_public
|
|
124
128
|
puts lan_off_line(setting) if new_token && !found["lan"]
|
|
125
|
-
open_url(url) if open_browser
|
|
129
|
+
open_url(here ? local_owner_link(url) : url) if open_browser
|
|
126
130
|
0
|
|
127
131
|
when :other
|
|
128
132
|
warn in_use_message(port)
|
|
@@ -388,6 +392,10 @@ module Samagotchi
|
|
|
388
392
|
on_bound&.call
|
|
389
393
|
record_running(running_path, port, public_url)
|
|
390
394
|
puts "Chi Web on #{url} (public: #{File.expand_path("public", __dir__)})"
|
|
395
|
+
# On the LAN (no web.public_url) this machine's page needs the
|
|
396
|
+
# token too: a one-time owner's code in the link instead.
|
|
397
|
+
local = lan && !public_url
|
|
398
|
+
puts local_lines(local_owner_link(url, codes: owner_codes(state_dir))) if local
|
|
391
399
|
puts lan_lines(ip: lan.ip, port: port, token: Token.read(token_path), others: lan.others, public: lan.public) if lan
|
|
392
400
|
if public_url
|
|
393
401
|
puts public_lines(public_url, token: Token.read(token_path), codes: owner_codes(state_dir), notes: public_notes)
|
|
@@ -395,8 +403,12 @@ module Samagotchi
|
|
|
395
403
|
puts "Press Ctrl-C to stop."
|
|
396
404
|
$stdout.flush # a log file isn't line-buffered
|
|
397
405
|
if open_browser
|
|
398
|
-
# With web.public_url the page opens through the proxy
|
|
399
|
-
|
|
406
|
+
# With web.public_url the page opens through the proxy; on the
|
|
407
|
+
# LAN with a one-time owner's code (#code=, its own).
|
|
408
|
+
open_target = if public_url then public_url.link(owner_codes(state_dir).issue(kind: :owner, ttl: LinkCodes::OWNER_TTL))
|
|
409
|
+
elsif local then local_owner_link(url, codes: owner_codes(state_dir))
|
|
410
|
+
else url
|
|
411
|
+
end
|
|
400
412
|
Thread.new do
|
|
401
413
|
sleep 0.8
|
|
402
414
|
open_url(open_target)
|
|
@@ -518,6 +530,19 @@ module Samagotchi
|
|
|
518
530
|
lines
|
|
519
531
|
end
|
|
520
532
|
|
|
533
|
+
# This machine's page on the LAN (web.host lan, no web.public_url),
|
|
534
|
+
# whose loopback needs the token too: +url+ with an owner's one-time
|
|
535
|
+
# code in its fragment (token_fragment.js trades it for the cookie on
|
|
536
|
+
# 127.0.0.1), never the token. One code per link.
|
|
537
|
+
def self.local_owner_link(url, codes: owner_codes)
|
|
538
|
+
"#{url}#code=#{codes.issue(kind: :owner, ttl: LinkCodes::OWNER_TTL)}"
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def self.local_lines(link)
|
|
542
|
+
["Local: #{link} ← opens chi web as you once, within #{LinkCodes::OWNER_TTL / 60} minutes " \
|
|
543
|
+
"(chi web again prints a new one; the LAN link works here too)"]
|
|
544
|
+
end
|
|
545
|
+
|
|
521
546
|
# The owner's one-time link codes, next to the grants.
|
|
522
547
|
def self.owner_codes(state_dir = nil) = LinkCodes.for_grants(Grants.path(state_dir: state_dir))
|
|
523
548
|
|
data/lib/samagotchi/web/token.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "openssl"
|
|
4
5
|
require "securerandom"
|
|
5
6
|
require_relative "../atomic_file"
|
|
6
7
|
|
|
@@ -14,11 +15,15 @@ module Samagotchi
|
|
|
14
15
|
# can read, $XDG_STATE_HOME/samagotchi/web-token (0600). It is made on
|
|
15
16
|
# the first LAN start and kept, so a phone's bookmark survives
|
|
16
17
|
# restarts; `chi web --new-token` replaces it. A loopback-only chi web
|
|
17
|
-
# never reads it.
|
|
18
|
+
# never reads it. The owner's cookie for this machine's 127.0.0.1
|
|
19
|
+
# holds #loopback_cookie, never the token: a server on any other local
|
|
20
|
+
# port is sent that cookie too.
|
|
18
21
|
module Token
|
|
19
22
|
FILE = "web-token"
|
|
20
23
|
# 32 random bytes: 43 URL-safe characters.
|
|
21
24
|
BYTES = 32
|
|
25
|
+
LOOPBACK_PREFIX = "chl_"
|
|
26
|
+
LOOPBACK_LABEL = "chi web loopback cookie v1"
|
|
22
27
|
|
|
23
28
|
module_function
|
|
24
29
|
|
|
@@ -49,6 +54,20 @@ module Samagotchi
|
|
|
49
54
|
SecureRandom.urlsafe_base64(BYTES)
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
# The owner's 127.0.0.1 cookie on the LAN: an HMAC of a label keyed
|
|
58
|
+
# by +token+, so a leaked one gives nothing about the token, and
|
|
59
|
+
# rotating the token ends it. The gate takes it only as that cookie,
|
|
60
|
+
# from an unproxied loopback peer (App#loopback_cookie_peer?).
|
|
61
|
+
# @return [String, nil] "chl_" and 43 URL-safe characters; nil
|
|
62
|
+
# without a token
|
|
63
|
+
def loopback_cookie(token)
|
|
64
|
+
return nil if token.nil? || token.empty?
|
|
65
|
+
|
|
66
|
+
# URL-safe base64 without padding (pack: base64 isn't a default gem).
|
|
67
|
+
digest = OpenSSL::HMAC.digest("SHA256", token, LOOPBACK_LABEL)
|
|
68
|
+
LOOPBACK_PREFIX + [digest].pack("m0").tr("+/", "-_").delete("=")
|
|
69
|
+
end
|
|
70
|
+
|
|
52
71
|
# Written whole under a temporary name, then renamed over +path+: a
|
|
53
72
|
# reader never sees half a token. The file is 0600 from the start,
|
|
54
73
|
# its folder 0700 when this makes it.
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -251,6 +251,13 @@ module Samagotchi
|
|
|
251
251
|
ChildMove.clear_starting(@session_id, state_dir: @state_dir)
|
|
252
252
|
ChildMove.adopt(@session_id, state_dir: @state_dir)
|
|
253
253
|
@session = Session.load(@session_id, state_dir: @state_dir)
|
|
254
|
+
# A newer chi saved it: this worker keeps the fields it doesn't know
|
|
255
|
+
# but runs the session without them (docs/sessions.md).
|
|
256
|
+
if @session.newer_writer?
|
|
257
|
+
unknown = @session.extra_fields.keys.join(",")
|
|
258
|
+
Log.warn(:worker, "newer_writer", sid: @session_id, version: VERSION, written_by: @session.written_by,
|
|
259
|
+
unknown_fields: (unknown unless unknown.empty?))
|
|
260
|
+
end
|
|
254
261
|
drop_dead_question
|
|
255
262
|
# The running turn, on disk at its iteration boundaries.
|
|
256
263
|
@checkpoint = TurnCheckpoint.new(session: @session, state_dir: @state_dir)
|
|
@@ -1245,7 +1252,8 @@ module Samagotchi
|
|
|
1245
1252
|
# status, its rings and every save from here on say so.
|
|
1246
1253
|
@session.parent_id = Session.parent_override(@session_id, state_dir: @state_dir) || @session.parent_id
|
|
1247
1254
|
save_session
|
|
1248
|
-
result = @engine.run_turn(@session, prompt, pending_input: pending_input_drain,
|
|
1255
|
+
result = @engine.run_turn(@session, prompt, pending_input: pending_input_drain, input_waiting: method(:input_waiting?),
|
|
1256
|
+
**turn_args)
|
|
1249
1257
|
rescue StandardError => e
|
|
1250
1258
|
error = e
|
|
1251
1259
|
# Still running: no turn of the Engine's ended (it never began).
|
|
@@ -1468,6 +1476,13 @@ module Samagotchi
|
|
|
1468
1476
|
end
|
|
1469
1477
|
end
|
|
1470
1478
|
|
|
1479
|
+
# Whether a person's message waits that the next boundary merges
|
|
1480
|
+
# (SessionInbox.steer_waiting?): a running task_wait ends on it. Never
|
|
1481
|
+
# in a participant's turn, whose drain merges none.
|
|
1482
|
+
def input_waiting?
|
|
1483
|
+
!@participant_turn && SessionInbox.steer_waiting?(@session_dir)
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1471
1486
|
# A participant's author fields (Engine#author_fields), {} for the owner.
|
|
1472
1487
|
def turn_author_fields(origin)
|
|
1473
1488
|
origin.participant? ? @engine.author_fields(origin.author, session: @session) : {}
|
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.
|
|
4
|
+
version: 0.130.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -342,6 +342,7 @@ files:
|
|
|
342
342
|
- lib/samagotchi/guardrails.rb
|
|
343
343
|
- lib/samagotchi/guardrails/approval.rb
|
|
344
344
|
- lib/samagotchi/guardrails/approvals.rb
|
|
345
|
+
- lib/samagotchi/guardrails/chi_dirs.rb
|
|
345
346
|
- lib/samagotchi/guardrails/child_boundary.rb
|
|
346
347
|
- lib/samagotchi/guardrails/context.rb
|
|
347
348
|
- lib/samagotchi/guardrails/data_readers.rb
|
|
@@ -354,6 +355,7 @@ files:
|
|
|
354
355
|
- lib/samagotchi/guardrails/parent_approvals.rb
|
|
355
356
|
- lib/samagotchi/guardrails/parent_levels.rb
|
|
356
357
|
- lib/samagotchi/guardrails/parent_policy.rb
|
|
358
|
+
- lib/samagotchi/guardrails/path_resolve.rb
|
|
357
359
|
- lib/samagotchi/guardrails/protected_paths.rb
|
|
358
360
|
- lib/samagotchi/guardrails/read_only_shell.rb
|
|
359
361
|
- lib/samagotchi/guardrails/review_boundary.rb
|