samagotchi 0.76.0 → 0.77.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 +26 -1
- data/docs/internals/engine.md +1 -1
- data/docs/plugins.md +42 -0
- data/lib/samagotchi/answer_command.rb +5 -5
- data/lib/samagotchi/bridge.rb +3 -3
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +12 -10
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_reports.rb +1 -1
- data/lib/samagotchi/engine.rb +21 -12
- data/lib/samagotchi/guardrails/parent_approvals.rb +4 -4
- data/lib/samagotchi/guardrails/parent_continue.rb +2 -2
- data/lib/samagotchi/memory_bundle/builder.rb +2 -0
- data/lib/samagotchi/message_parts.rb +272 -0
- data/lib/samagotchi/parent_report.rb +30 -22
- data/lib/samagotchi/pending_question.rb +49 -0
- data/lib/samagotchi/question_desk.rb +39 -32
- data/lib/samagotchi/relay.rb +8 -9
- data/lib/samagotchi/relay_verifier.rb +2 -2
- data/lib/samagotchi/relay_watcher.rb +2 -2
- data/lib/samagotchi/reply_wait.rb +4 -4
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/session.rb +22 -14
- data/lib/samagotchi/session_commands.rb +4 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +3 -2
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -2
- data/lib/samagotchi/terminal_ui/formatting.rb +3 -106
- data/lib/samagotchi/terminal_ui/model_segments_text.rb +118 -0
- data/lib/samagotchi/terminal_ui.rb +5 -3
- data/lib/samagotchi/tool_output_cut.rb +1 -1
- data/lib/samagotchi/tool_response.rb +13 -13
- data/lib/samagotchi/tool_row_fields.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +16 -9
- data/lib/samagotchi/tools/delegate_relay.rb +7 -7
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/web_fetch.rb +3 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -2
- data/lib/samagotchi/web/public/app.js +17 -55
- data/lib/samagotchi/web/public/ctx.js +12 -1
- data/lib/samagotchi/web/public/index.html +2 -1
- data/lib/samagotchi/web/public/markdown_place.js +53 -0
- data/lib/samagotchi/web/public/stream_handlers.js +7 -3
- data/lib/samagotchi/web/public/toasts.js +69 -0
- metadata +6 -2
- data/lib/samagotchi/web/message_parts.rb +0 -274
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 433cf999c039d529cdf878223e798ed00b23efc0a35393c48111c0b1f7b597a7
|
|
4
|
+
data.tar.gz: eb13e65818fc0b2efaa04d28315d9693856ff77861d5643d668702fe80cdeca1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58012cc677a1b2c14e358dabed798856f88a8a74f59dff1606f08dca297ae78d5f89d69c392b2254523308996a67a86ff828205e07a7eac7ea0b7adf99961d79
|
|
7
|
+
data.tar.gz: 7332bff82c9060f9ad9a77063891ced098572ce28861fbe2a65ab9486076478d40f1731fec13f41ec9dc4a15fc60e14ac023b40255c0356993bfe22969817986
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,30 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.77.0] - 2026-10-17
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- A session's worker starts about 80 ms faster: it loads the HTML parser (Nokogiri) only when `web_fetch` first reads
|
|
16
|
+
an HTML page.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Web: toasts stack (up to three) instead of replacing one another, so a later notice no longer hides a sticky error
|
|
21
|
+
such as "Can't send: …"; each one has its own timer and ✕.
|
|
22
|
+
- After `/model`, a session's card drops the old model's context % right away (the new window's %, or none while it
|
|
23
|
+
is unknown), in every tab and after a reload, instead of keeping it until the next turn ends.
|
|
24
|
+
- Web: the context chip and its tooltip update as soon as a generation starts, not at its first chunk.
|
|
25
|
+
- Web: a model ref longer than 40 characters is cut in the middle in the ctx tooltip's per-model rows, so it no
|
|
26
|
+
longer stretches the tooltip.
|
|
27
|
+
- REPL (`--no-shared`): the status row drops the old context % after `/model` when the new window is unknown.
|
|
28
|
+
- source-links: a URL in the terminal answer no longer takes in trailing punctuation (`.`, `,`, `)` …) or an unmatched
|
|
29
|
+
`]`, the same as in the web.
|
|
30
|
+
- `chi bundle build` skips a symlinked memory (`notes.md is a symlink (not included)`) instead of copying the file it
|
|
31
|
+
links to into the bundle.
|
|
32
|
+
|
|
33
|
+
Update with `chi update` (the system bundle 0.77.0, `source-links` 0.5.1), then restart `chi web`.
|
|
34
|
+
|
|
11
35
|
## [0.76.0] - 2026-10-17
|
|
12
36
|
|
|
13
37
|
### Security
|
|
@@ -3221,7 +3245,8 @@ and long-lived sessions.
|
|
|
3221
3245
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3222
3246
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3223
3247
|
|
|
3224
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3248
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.77.0...HEAD
|
|
3249
|
+
[0.77.0]: https://github.com/dm1try/samagotchi/compare/v0.76.0...v0.77.0
|
|
3225
3250
|
[0.76.0]: https://github.com/dm1try/samagotchi/compare/v0.75.0...v0.76.0
|
|
3226
3251
|
[0.75.0]: https://github.com/dm1try/samagotchi/compare/v0.74.0...v0.75.0
|
|
3227
3252
|
[0.74.0]: https://github.com/dm1try/samagotchi/compare/v0.73.0...v0.74.0
|
data/docs/internals/engine.md
CHANGED
|
@@ -290,7 +290,7 @@ One iteration with tool calls:
|
|
|
290
290
|
replace it; known-names corrects a misspelled name this way, and the model gets one line saying what ran).
|
|
291
291
|
- Emits `:tool_call_started` {iteration, call_count, call_index, tool, call, params, label (plugin tools),
|
|
292
292
|
title, called_as, via, view, ref, refs}. The row fields (title, called_as, via, view, ref) come from `ToolRowFields.for`, which the
|
|
293
|
-
reload (`
|
|
293
|
+
reload (`MessageParts#tool_part`, after `BuiltinCalls.unwrap_saved`) calls too. `ToolRowFields::ACTIVITY_KEYS` (title, called_as, via) travel
|
|
294
294
|
inside `activity` on the completed event; `EVENT_KEYS` (view, ref, refs) go top-level on both events.
|
|
295
295
|
`Bridge::TurnAccumulator` (snapshot and replay) and the web (`activity.js` `ACTIVITY_FIELDS` /
|
|
296
296
|
`EVENT_FIELDS`) copy them by these lists, so a new row field is added there. `ref` is `FileRef.for` (a
|
data/docs/plugins.md
CHANGED
|
@@ -990,6 +990,48 @@ context_providers:
|
|
|
990
990
|
the PATH may be another one, such as macOS's 2.6).
|
|
991
991
|
- The command follows [the contract](context.md#the-command-contract).
|
|
992
992
|
|
|
993
|
+
## Stable for bundles
|
|
994
|
+
|
|
995
|
+
Bundles normally use only `chi.*` and `ctx`; the constants and helpers below
|
|
996
|
+
are the rest of the surface they may use. A change to any of them gets a
|
|
997
|
+
CHANGELOG line and bumps the shipped bundles that use it. Anything else under
|
|
998
|
+
`Samagotchi::` is internal.
|
|
999
|
+
|
|
1000
|
+
**Stable**
|
|
1001
|
+
|
|
1002
|
+
1. `Samagotchi::Plugin::Api` / `chi.*` as documented above, including
|
|
1003
|
+
`Samagotchi::Plugin::ToolResult`, `Samagotchi::Plugin::ModelError` (and its
|
|
1004
|
+
subclass `Samagotchi::Plugin::ModelCancelled`),
|
|
1005
|
+
`Samagotchi::Plugin::Service::Stopped`, `Samagotchi::Plugin::Sessions::Error`
|
|
1006
|
+
and `Samagotchi::Plugin::AttachedContext::Error`.
|
|
1007
|
+
2. `Samagotchi::Plugin::Peer` as a base class. A subclass defines `post`,
|
|
1008
|
+
`check_hint!`, `paired_lines`, `transport_line`, `peer_dir` and
|
|
1009
|
+
`setup_paragraph` (optionally `skill_substitutions`), and sets `NAME`,
|
|
1010
|
+
`APP`, `COMMAND`, `USAGE`, `OUTBOX` and `SCRIPTS_DIRS`. These constants are
|
|
1011
|
+
read as `self.class::X`. See the [claude-peer](#the-claude-peer-bundle) and
|
|
1012
|
+
[codex-peer](#the-codex-peer-bundle) bundles.
|
|
1013
|
+
3. `Samagotchi::Tools::ToolIndex::Tool` and `Samagotchi::Tools::ToolIndex::Group`
|
|
1014
|
+
values, `Samagotchi::Tools::ToolIndex.schema_note` and
|
|
1015
|
+
`Samagotchi::Tools::ToolIndex.spec_tokens` (used by the mcp bundle).
|
|
1016
|
+
4. `Samagotchi::Tools::Execute::NAME`, `TIMEOUT_PREFIX`, `HANDED_OVER` and
|
|
1017
|
+
`Samagotchi::Tools::Execute.timeout_seconds`.
|
|
1018
|
+
5. `Samagotchi::Tools::TaskRuntime::TASKS_DIR` and `EXIT_CODE_FILENAME`.
|
|
1019
|
+
6. `Samagotchi::AtomicFile.write`, `Samagotchi::Log.warn` /
|
|
1020
|
+
`Samagotchi::Log.debug`, `Samagotchi::Config.get` and
|
|
1021
|
+
`Samagotchi::VERSION`.
|
|
1022
|
+
|
|
1023
|
+
**Used by shipped bundles today, but internal** (may change without notice; a
|
|
1024
|
+
bundle that needs one should ask for an API instead):
|
|
1025
|
+
|
|
1026
|
+
- `Samagotchi::Tools::MemoryRead.memories_dir`, `Samagotchi::MemoryPaths.scope_dir`,
|
|
1027
|
+
`Samagotchi::ConfigFile.global_path` and
|
|
1028
|
+
`Samagotchi::Config.validate_yaml_sections`.
|
|
1029
|
+
- `Samagotchi::Ref` / `Samagotchi::RefAction` constants and
|
|
1030
|
+
`Samagotchi::TokenUsage::CHARS_PER_TOKEN`.
|
|
1031
|
+
- `Samagotchi::ProcessGroup.stop`, `Samagotchi::Guardrails::ShellLex` and
|
|
1032
|
+
`Samagotchi::Guardrails::GitInfo`.
|
|
1033
|
+
- `Samagotchi::Tools::Execute.const_get` reflection.
|
|
1034
|
+
|
|
993
1035
|
## The btw bundle
|
|
994
1036
|
|
|
995
1037
|
`chi bundle install btw` (or the `dev` profile) installs the bundle shipped with chi. It is written
|
|
@@ -117,7 +117,7 @@ module Samagotchi
|
|
|
117
117
|
# turn queued behind it; nil (an older worker): the next reply.
|
|
118
118
|
turn = ReplyWait.own_turn(session, session_dir: Session.session_dir(id, state_dir: @state_dir))
|
|
119
119
|
|
|
120
|
-
if pending && pending
|
|
120
|
+
if pending && pending.id.to_s == qid
|
|
121
121
|
return failure(not_dismissable(pending)) if options[:dismiss] && ParentReport.continue?(pending)
|
|
122
122
|
|
|
123
123
|
selected = selection(pending, options) or return @selection_status
|
|
@@ -140,7 +140,7 @@ module Samagotchi
|
|
|
140
140
|
# exact label wins over a number.
|
|
141
141
|
# @return [Array<String>, nil] nil after a usage error
|
|
142
142
|
def selection(pending, options)
|
|
143
|
-
offered = Array(pending
|
|
143
|
+
offered = Array(pending.options).map(&:to_s)
|
|
144
144
|
labels = options[:options].map do |given|
|
|
145
145
|
next given if offered.include?(given)
|
|
146
146
|
|
|
@@ -151,7 +151,7 @@ module Samagotchi
|
|
|
151
151
|
@selection_status = usage_failure("no option #{given}; the options: #{listed}")
|
|
152
152
|
return nil
|
|
153
153
|
end
|
|
154
|
-
if labels.uniq.size > 1 && !pending
|
|
154
|
+
if labels.uniq.size > 1 && !pending.multi_select
|
|
155
155
|
@selection_status = usage_failure("one option only: the question is single-select")
|
|
156
156
|
return nil
|
|
157
157
|
end
|
|
@@ -163,7 +163,7 @@ module Samagotchi
|
|
|
163
163
|
# Deny, text alone or a dismissal always passes: each one denies. The
|
|
164
164
|
# worker checks it again with its own config.
|
|
165
165
|
def approval_refusal(pending, selected, _id)
|
|
166
|
-
offered = Array(pending
|
|
166
|
+
offered = Array(pending.options).map(&:to_s)
|
|
167
167
|
indices = selected.map { |label| offered.index(label) }
|
|
168
168
|
reason = Guardrails::ParentApprovals.refusal(pending, indices, setting: Guardrails::ParentApprovals.setting)
|
|
169
169
|
reason && Guardrails::ParentApprovals.message(reason)
|
|
@@ -177,7 +177,7 @@ module Samagotchi
|
|
|
177
177
|
|
|
178
178
|
# A step-limit question can't be left unanswered: the turn waits on it.
|
|
179
179
|
def not_dismissable(pending)
|
|
180
|
-
"the step-limit question can't be dismissed: answer #{Array(pending
|
|
180
|
+
"the step-limit question can't be dismissed: answer #{Array(pending.options).join(" or ")} " \
|
|
181
181
|
"(--option Stop --text WHY stops it)"
|
|
182
182
|
end
|
|
183
183
|
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -260,7 +260,7 @@ module Samagotchi
|
|
|
260
260
|
recap: @accumulator.recap,
|
|
261
261
|
saved_recap: @engine.saved_recap,
|
|
262
262
|
continue_offer: @accumulator.continue_offer,
|
|
263
|
-
pending_question: @engine.pending_question,
|
|
263
|
+
pending_question: @engine.pending_question&.to_h,
|
|
264
264
|
guardrail_warning: @engine.guardrail_warning,
|
|
265
265
|
plugin_warning: @engine.plugin_warning,
|
|
266
266
|
init_tasks: @engine.init_tasks,
|
|
@@ -1178,8 +1178,8 @@ module Samagotchi
|
|
|
1178
1178
|
# still that relay's.
|
|
1179
1179
|
def relay_closed(qid, relay_id, reason)
|
|
1180
1180
|
pending = @engine.pending_question
|
|
1181
|
-
relay = pending
|
|
1182
|
-
unless pending && pending
|
|
1181
|
+
relay = pending&.relayed_to
|
|
1182
|
+
unless pending && pending.id.to_s == qid && relay && (relay[:relay_id] || relay["relay_id"]).to_s == relay_id
|
|
1183
1183
|
return [{}, 409, { error: "question_not_pending", detail: "no question #{qid} relayed by #{relay_id}" }]
|
|
1184
1184
|
end
|
|
1185
1185
|
return [{}, 409, { error: "question_not_pending", detail: "no pending question #{qid}" }] unless @engine.annotate_question(qid, relayed_to: nil, reason: reason)
|
|
@@ -76,6 +76,7 @@ class SourceLinks
|
|
|
76
76
|
# A bare URL (scheme-anchored, so it can't start inside a markdown label),
|
|
77
77
|
# and a markdown link (label in group 1, target in group 2).
|
|
78
78
|
URL_SPAN = %r{[a-z][a-z0-9+.-]*://\S+}i
|
|
79
|
+
TRAILING_URL_PUNCTUATION = /[.,;:!?'"]+\z/
|
|
79
80
|
MARKDOWN_LINK = /\[([^\]]*)\]\(([^)]*)\)/
|
|
80
81
|
# A fenced code block's opening line (up to 3 spaces, then ``` or ~~~).
|
|
81
82
|
FENCE_OPEN = /\A {0,3}(`{3,}|~{3,})/
|
|
@@ -328,35 +329,36 @@ class SourceLinks
|
|
|
328
329
|
|
|
329
330
|
# The [start, end) character ranges of the answer that are a bare URL
|
|
330
331
|
# (scheme-anchored, `[a-z][a-z0-9+.\-]*://\S+`). A ref inside one is not
|
|
331
|
-
# linked again.
|
|
332
|
-
#
|
|
333
|
-
# `…/Foo_(bar)` keeps it.
|
|
332
|
+
# linked again. Trailing punctuation and a trailing `]` without a `[` are
|
|
333
|
+
# excluded; balanced parentheses and brackets stay part of the URL.
|
|
334
334
|
def bare_url_spans(text)
|
|
335
335
|
spans = []
|
|
336
336
|
text.scan(URL_SPAN) do
|
|
337
337
|
match = Regexp.last_match
|
|
338
|
-
finish = trim_url_end(match[0], match.begin(0)
|
|
338
|
+
finish = trim_url_end(match[0], match.begin(0))
|
|
339
339
|
spans << [match.begin(0), finish] if finish > match.begin(0)
|
|
340
340
|
end
|
|
341
341
|
spans
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
-
# The end offset of a URL match after trimming
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
# The end offset of a URL match after trimming it like notice_links.js.
|
|
345
|
+
def trim_url_end(url, start)
|
|
346
|
+
trimmed = url.sub(TRAILING_URL_PUNCTUATION, "")
|
|
347
347
|
depth = 0
|
|
348
|
-
|
|
348
|
+
trimmed.each_char.with_index do |char, index|
|
|
349
349
|
if char == "("
|
|
350
350
|
depth += 1
|
|
351
351
|
elsif char == ")"
|
|
352
352
|
if depth.zero?
|
|
353
|
-
|
|
353
|
+
trimmed = trimmed[0...index]
|
|
354
354
|
break
|
|
355
355
|
end
|
|
356
356
|
depth -= 1
|
|
357
357
|
end
|
|
358
358
|
end
|
|
359
|
-
|
|
359
|
+
trimmed = trimmed.sub(/\]+\z/, "") if trimmed.end_with?("]") && !trimmed.include?("[")
|
|
360
|
+
trimmed = trimmed.sub(TRAILING_URL_PUNCTUATION, "")
|
|
361
|
+
start + trimmed.length
|
|
360
362
|
end
|
|
361
363
|
|
|
362
364
|
# A markdown link's label and target ranges, with the target text. A ref in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: source-links
|
|
3
|
-
version: 0.5.
|
|
3
|
+
version: 0.5.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: Links source refs (JIRA tickets, GitHub issues, …) in the model's answer — inline links in the web, and a one-line note after the turn (note false turns it off); sources are configured patterns
|
|
6
6
|
trust_level: reviewed
|
|
@@ -9,7 +9,7 @@ files:
|
|
|
9
9
|
source_links.md: sha256:ace01a0f7c308a0ae54ece714c31c0e6d7a49c372d98111030e6d268320bed3a
|
|
10
10
|
hooks:
|
|
11
11
|
source_links.rb:
|
|
12
|
-
sha256: sha256:
|
|
12
|
+
sha256: sha256:c41249ec0bbb708ec7827e1d0f66689dfbbc415c1da5fe33f5d3a8ec8e5eaa2f
|
|
13
13
|
event: after_turn
|
|
14
14
|
on_error: log
|
|
15
15
|
priority: 90
|
|
@@ -254,7 +254,7 @@ module Samagotchi
|
|
|
254
254
|
def reportable?(wait)
|
|
255
255
|
case wait.status
|
|
256
256
|
when :done, :no_reply, :error then true
|
|
257
|
-
when :waiting_for_answer then [nil, "", "continue"].include?(wait.question&.
|
|
257
|
+
when :waiting_for_answer then [nil, "", "continue"].include?(wait.question&.kind&.to_s)
|
|
258
258
|
else false
|
|
259
259
|
end
|
|
260
260
|
end
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -242,10 +242,7 @@ module Samagotchi
|
|
|
242
242
|
wire_kernel(kernel, muted_memories)
|
|
243
243
|
Log.debug(:model, "backend", provider: backend.provider) if Log.level?(:debug)
|
|
244
244
|
@resume_session = session_id ? Session.load(session_id) : nil
|
|
245
|
-
@prompt_builder =
|
|
246
|
-
thinking: -> { turn_thinking }, model: -> { model_identity },
|
|
247
|
-
model_notes: -> { model_notes }, memories: memories,
|
|
248
|
-
muted_memory_names: @muted_memory_names, appended_files: appended_prompt_files)
|
|
245
|
+
@prompt_builder = build_system_prompt(memories, appended_prompt_files)
|
|
249
246
|
@session = nil
|
|
250
247
|
@session_observer = SessionObserver.new
|
|
251
248
|
@metrics = SessionMetrics.new
|
|
@@ -268,10 +265,7 @@ module Samagotchi
|
|
|
268
265
|
# One shared poller for the whole idle layer (reminders + optional
|
|
269
266
|
# recap). Both jobs read the activity seam above; the scheduler owns
|
|
270
267
|
# the single background thread and isolates per-job failures.
|
|
271
|
-
@idle_scheduler =
|
|
272
|
-
engine: self,
|
|
273
|
-
jobs: [@reminders, @recap].compact
|
|
274
|
-
)
|
|
268
|
+
@idle_scheduler = build_idle_scheduler
|
|
275
269
|
subscribe_session_observers
|
|
276
270
|
end
|
|
277
271
|
|
|
@@ -388,6 +382,21 @@ module Samagotchi
|
|
|
388
382
|
end
|
|
389
383
|
private :build_plugin_tasks
|
|
390
384
|
|
|
385
|
+
# The system prompt's live sources and this session's fixed inputs.
|
|
386
|
+
def build_system_prompt(memories, appended_prompt_files)
|
|
387
|
+
SystemPrompt.new(profile: -> { profile }, tools: -> { @tools }, session: -> { @session },
|
|
388
|
+
thinking: -> { turn_thinking }, model: -> { model_identity },
|
|
389
|
+
model_notes: -> { model_notes }, memories: memories,
|
|
390
|
+
muted_memory_names: @muted_memory_names, appended_files: appended_prompt_files)
|
|
391
|
+
end
|
|
392
|
+
private :build_system_prompt
|
|
393
|
+
|
|
394
|
+
# The idle layer's shared scheduler (see #initialize for when).
|
|
395
|
+
def build_idle_scheduler
|
|
396
|
+
IdleScheduler.new(engine: self, jobs: [@reminders, @recap].compact)
|
|
397
|
+
end
|
|
398
|
+
private :build_idle_scheduler
|
|
399
|
+
|
|
391
400
|
# A monotonically-increasing clock (wall clock can jump backwards; the idle
|
|
392
401
|
# detector must never treat a jump as "activity"). Injectable for specs.
|
|
393
402
|
def monotonic_now
|
|
@@ -1218,7 +1227,7 @@ module Samagotchi
|
|
|
1218
1227
|
# :last_prompt [String, nil] the last user prompt (empty string if none)
|
|
1219
1228
|
# :event_seq [Integer] @session_observer.event_count
|
|
1220
1229
|
# :metrics [Hash] @metrics.snapshot (per-session analytics)
|
|
1221
|
-
# :pending_question [Hash, nil]
|
|
1230
|
+
# :pending_question [Hash, nil] the pending question (PendingQuestion#to_h)
|
|
1222
1231
|
# :used_memory_names [Array<String>] deduped memory names active this session
|
|
1223
1232
|
# :prompt_notes [Array<Hash>] the model notes the prompt carried
|
|
1224
1233
|
# ({name:, scope:, chars:, digest:}; #prompt_notes)
|
|
@@ -1244,7 +1253,7 @@ module Samagotchi
|
|
|
1244
1253
|
last_prompt: @session&.last_prompt,
|
|
1245
1254
|
event_seq: @session_observer&.event_count,
|
|
1246
1255
|
metrics: metrics,
|
|
1247
|
-
pending_question: @question_desk.pending,
|
|
1256
|
+
pending_question: @question_desk.pending&.to_h,
|
|
1248
1257
|
used_memory_names: @used_memories.names,
|
|
1249
1258
|
preloaded_memory_names: preloaded_memory_names,
|
|
1250
1259
|
muted_memory_names: @muted_memory_names.dup,
|
|
@@ -1554,7 +1563,7 @@ module Samagotchi
|
|
|
1554
1563
|
|
|
1555
1564
|
# ── Ask-user-question (structured qualification) ──────────────────────────
|
|
1556
1565
|
|
|
1557
|
-
# @return [
|
|
1566
|
+
# @return [PendingQuestion, nil] the question pending now
|
|
1558
1567
|
def pending_question
|
|
1559
1568
|
@question_desk.pending
|
|
1560
1569
|
end
|
|
@@ -1580,7 +1589,7 @@ module Samagotchi
|
|
|
1580
1589
|
|
|
1581
1590
|
# Publish a standing question that blocks nobody (QuestionDesk#post: a
|
|
1582
1591
|
# worker's step-limit question).
|
|
1583
|
-
# @return [
|
|
1592
|
+
# @return [PendingQuestion, nil] the pending question, nil while another is open
|
|
1584
1593
|
def post_question(fields, on_answer:, on_superseded_close: nil)
|
|
1585
1594
|
@question_desk.post(fields, on_answer: on_answer, on_superseded_close: on_superseded_close)
|
|
1586
1595
|
end
|
|
@@ -93,7 +93,7 @@ module Samagotchi
|
|
|
93
93
|
# guardrail rules or approvals: by its rule, its paths or its command
|
|
94
94
|
# (the dirs as this process's environment and config.yml name them).
|
|
95
95
|
def protected?(pending)
|
|
96
|
-
facts =
|
|
96
|
+
facts = pending.approval
|
|
97
97
|
return false unless facts.is_a?(Hash)
|
|
98
98
|
return true if PROTECTED_RULES.include?(fetch(facts, :rule).to_s)
|
|
99
99
|
return true if fetch(facts, :source).to_s == ProtectedPaths::SOURCE
|
|
@@ -137,13 +137,13 @@ module Samagotchi
|
|
|
137
137
|
|
|
138
138
|
# An approval: its kind says so, or it carries approval facts.
|
|
139
139
|
def approval?(pending)
|
|
140
|
-
|
|
140
|
+
pending.kind.to_s == Approval::KIND || !pending.approval.nil?
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
# The offered scopes, in option order, or nil when they are missing,
|
|
144
144
|
# empty, not strings, or leave no option after them for Deny.
|
|
145
145
|
def scopes(pending)
|
|
146
|
-
facts =
|
|
146
|
+
facts = pending.approval
|
|
147
147
|
scopes = facts.is_a?(Hash) ? fetch(facts, :scopes) : nil
|
|
148
148
|
return nil unless scopes.is_a?(Array) && !scopes.empty? && scopes.all?(String)
|
|
149
149
|
return nil unless scopes.size < options(pending).size
|
|
@@ -160,7 +160,7 @@ module Samagotchi
|
|
|
160
160
|
index == options.size - 1 && options[index].to_s == Approval::DENY
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
-
def options(pending) = Array(
|
|
163
|
+
def options(pending) = Array(pending.options)
|
|
164
164
|
|
|
165
165
|
def fetch(hash, key)
|
|
166
166
|
return nil unless hash.is_a?(Hash)
|
|
@@ -24,12 +24,12 @@ module Samagotchi
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Why a parent may not give this answer, or nil.
|
|
27
|
-
# @param pending [
|
|
27
|
+
# @param pending [PendingQuestion] the pending question
|
|
28
28
|
# @param selected [Array<String>] the selected labels
|
|
29
29
|
# @return [Symbol, nil] :stop_only
|
|
30
30
|
def refusal(pending, selected, allowed: allowed?)
|
|
31
31
|
return nil if allowed
|
|
32
|
-
return nil unless
|
|
32
|
+
return nil unless pending.kind.to_s == KIND
|
|
33
33
|
|
|
34
34
|
Array(selected).map(&:to_s).include?(CONTINUE) ? :stop_only : nil
|
|
35
35
|
end
|
|
@@ -110,6 +110,8 @@ module Samagotchi
|
|
|
110
110
|
def select_memories(target_dir, name)
|
|
111
111
|
candidates = Dir.glob(File.join(target_dir, "*.md")).sort
|
|
112
112
|
.reject { |p| File.basename(p) == "index.md" || File.basename(p).start_with?(".") }
|
|
113
|
+
symlinks, candidates = candidates.partition { |path| File.symlink?(path) }
|
|
114
|
+
@warnings.concat(symlinks.map { |path| "#{File.basename(path)} is a symlink (not included)" })
|
|
113
115
|
if candidates.empty?
|
|
114
116
|
raise BuildError, "No memories to build in #{@scope} scope (#{target_dir}): no .md files found"
|
|
115
117
|
end
|