samagotchi 0.3.0 → 0.5.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 +162 -1
- data/README.md +29 -2
- data/bin/chi +60 -69
- data/docs/cli.md +211 -77
- data/docs/configuration.md +118 -21
- data/docs/desktop.md +39 -4
- data/docs/guardrails.md +11 -0
- data/docs/hooks.md +89 -7
- data/docs/memory.md +40 -0
- data/docs/plugins.md +50 -0
- data/docs/releasing.md +15 -12
- data/docs/sessions.md +20 -18
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -2
- data/lib/samagotchi/bridge/sse_writer.rb +0 -3
- data/lib/samagotchi/bridge/turn_accumulator.rb +2 -0
- data/lib/samagotchi/bridge.rb +20 -12
- data/lib/samagotchi/bundles/skills/manifest.yml +10 -0
- data/lib/samagotchi/bundles/skills/plugin.rb +419 -0
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +178 -5
- data/lib/samagotchi/bundles/source-links/manifest.yml +3 -3
- data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +9 -10
- data/lib/samagotchi/bundles/system/delegated.md +6 -7
- data/lib/samagotchi/bundles/system/identity.md +5 -0
- data/lib/samagotchi/bundles/system/manifest.yml +6 -6
- data/lib/samagotchi/bundles/system/memory_guide.md +26 -0
- data/lib/samagotchi/bundles/system/self_map.md +2 -1
- data/lib/samagotchi/client.rb +25 -26
- data/lib/samagotchi/commands/registry.rb +8 -0
- data/lib/samagotchi/config.rb +97 -113
- data/lib/samagotchi/desktop/macos/App.swift +12 -8
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +4 -2
- data/lib/samagotchi/desktop/macos/Images.swift +113 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +112 -9
- data/lib/samagotchi/desktop/macos.rb +59 -8
- data/lib/samagotchi/desktop_command.rb +6 -3
- data/lib/samagotchi/edit_preview.rb +82 -0
- data/lib/samagotchi/engine.rb +236 -443
- data/lib/samagotchi/gem_update.rb +89 -0
- data/lib/samagotchi/guardrails/approval.rb +26 -4
- data/lib/samagotchi/guardrails/load_failures.rb +9 -3
- data/lib/samagotchi/host_registry.rb +8 -12
- data/lib/samagotchi/idle_client.rb +24 -15
- data/lib/samagotchi/idle_reminders.rb +2 -2
- data/lib/samagotchi/image_store.rb +10 -6
- data/lib/samagotchi/kernel_loop.rb +59 -123
- data/lib/samagotchi/live_versions.rb +65 -0
- data/lib/samagotchi/llm/api_key.rb +41 -0
- data/lib/samagotchi/llm/chat_loop.rb +77 -13
- data/lib/samagotchi/llm/errors.rb +38 -7
- data/lib/samagotchi/llm/http.rb +19 -22
- data/lib/samagotchi/llm/openai_chat.rb +22 -26
- data/lib/samagotchi/memory_bundle/installer.rb +65 -63
- data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
- data/lib/samagotchi/memory_bundle/status.rb +4 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
- data/lib/samagotchi/model_profile.rb +27 -10
- data/lib/samagotchi/note_command.rb +2 -1
- data/lib/samagotchi/prompt.rb +4 -2
- data/lib/samagotchi/reminder_store.rb +1 -9
- data/lib/samagotchi/reply_wait.rb +48 -4
- data/lib/samagotchi/self_report.rb +37 -5
- data/lib/samagotchi/send_command.rb +190 -17
- data/lib/samagotchi/session.rb +4 -2
- data/lib/samagotchi/session_commands.rb +38 -8
- data/lib/samagotchi/session_manager.rb +19 -53
- data/lib/samagotchi/system_prompt.rb +403 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +5 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +141 -108
- data/lib/samagotchi/terminal_ui/attached_view.rb +27 -12
- data/lib/samagotchi/terminal_ui/event_renderer.rb +29 -8
- data/lib/samagotchi/terminal_ui/formatting.rb +41 -22
- data/lib/samagotchi/terminal_ui/input_support.rb +7 -23
- data/lib/samagotchi/terminal_ui/plain_surface.rb +13 -7
- data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
- data/lib/samagotchi/terminal_ui/status_row.rb +81 -0
- data/lib/samagotchi/terminal_ui/surface.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +142 -923
- data/lib/samagotchi/text_diff.rb +181 -0
- data/lib/samagotchi/thinking.rb +126 -0
- data/lib/samagotchi/tool_activity.rb +52 -2
- data/lib/samagotchi/tool_runner.rb +37 -1
- data/lib/samagotchi/tools/ask_user_question.rb +41 -33
- data/lib/samagotchi/tools/edit.rb +23 -9
- data/lib/samagotchi/tools/execute.rb +3 -3
- data/lib/samagotchi/tools/output_guardrails.rb +8 -7
- data/lib/samagotchi/tools/read.rb +4 -4
- data/lib/samagotchi/tools/write.rb +4 -0
- data/lib/samagotchi/turn_flow.rb +12 -2
- data/lib/samagotchi/update_command.rb +309 -0
- data/lib/samagotchi/update_hint.rb +59 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +6 -4
- data/lib/samagotchi/web/app.rb +173 -38
- data/lib/samagotchi/web/lan.rb +99 -0
- data/lib/samagotchi/web/message_parts.rb +19 -10
- data/lib/samagotchi/web/public/activity.js +10 -0
- data/lib/samagotchi/web/public/app.js +135 -78
- data/lib/samagotchi/web/public/chat_view.js +8 -1
- data/lib/samagotchi/web/public/data.js +2 -0
- data/lib/samagotchi/web/public/diff_view.js +58 -0
- data/lib/samagotchi/web/public/index.html +185 -18
- data/lib/samagotchi/web/public/model_pick.js +136 -0
- data/lib/samagotchi/web/public/model_picker.js +224 -0
- data/lib/samagotchi/web/public/notify.js +10 -0
- data/lib/samagotchi/web/public/question_card.js +3 -1
- data/lib/samagotchi/web/public/stage_model.js +110 -0
- data/lib/samagotchi/web/public/stage_view.js +580 -0
- data/lib/samagotchi/web/public/timing.js +6 -2
- data/lib/samagotchi/web/public/turn_events.js +38 -10
- data/lib/samagotchi/web/public/turn_model.js +11 -3
- data/lib/samagotchi/web/public/turn_view.js +76 -20
- data/lib/samagotchi/web/qr.rb +40 -0
- data/lib/samagotchi/web/server.rb +101 -11
- data/lib/samagotchi/web/token.rb +97 -0
- data/lib/samagotchi/worker.rb +5 -4
- metadata +38 -3
- data/lib/samagotchi/terminal_ui/legacy_surface.rb +0 -111
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
// summaries (GET /api/events). The page shows an OS notification and a
|
|
3
3
|
// title badge for what it returns, while the tab is not in front.
|
|
4
4
|
|
|
5
|
+
// The bell's state. OS notifications need a secure context: on chi web
|
|
6
|
+
// over http at a LAN address the API is missing or always denied, so the
|
|
7
|
+
// bell is hidden there (the title badge still counts).
|
|
8
|
+
// @return "on" | "off" | "denied" | "unsupported"
|
|
9
|
+
export function notifyState({ hasNotification, secureContext, permission, wanted }) {
|
|
10
|
+
if (!hasNotification || !secureContext) return "unsupported";
|
|
11
|
+
if (permission === "denied") return "denied";
|
|
12
|
+
return wanted && permission === "granted" ? "on" : "off";
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
// A finished turn shorter than this was watched: no "done".
|
|
6
16
|
export const DONE_MIN_SECONDS = 10;
|
|
7
17
|
|
|
@@ -8,7 +8,8 @@ export function isApproval(pq) {
|
|
|
8
8
|
return !!pq && pq.kind === "approval";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// @return {null | {tool, what, isCommand, where, why}}
|
|
11
|
+
// @return {null | {tool, what, isCommand, where, why, preview?}}; preview is
|
|
12
|
+
// an edit/write's dry-run diff (diff_view.js draws it)
|
|
12
13
|
export function approvalView(pq) {
|
|
13
14
|
if (!isApproval(pq)) return null;
|
|
14
15
|
const a = pq.approval || {};
|
|
@@ -31,6 +32,7 @@ export function approvalView(pq) {
|
|
|
31
32
|
isCommand: !!command,
|
|
32
33
|
where,
|
|
33
34
|
why: `${reason} (${who})`,
|
|
35
|
+
...(a.preview && typeof a.preview === "object" ? { preview: a.preview } : {}),
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The stage view's pure parts (web.view: stage): what the live slots show
|
|
2
|
+
// for a turn (turn_model.js's), when a finished turn hands off into the
|
|
3
|
+
// history, and in what order its pieces go there. No DOM, so `node --test`
|
|
4
|
+
// covers the rules; stage_view.js draws them.
|
|
5
|
+
import { toolName } from "./activity.js";
|
|
6
|
+
|
|
7
|
+
const READ_LIKE = new Set(["read", "memory_read", "web_fetch", "task_get", "task_list", "list_sessions"]);
|
|
8
|
+
const EDITS = new Set(["edit", "write", "memory_write"]);
|
|
9
|
+
const EXECS = new Set(["execute", "task_create", "task_wait", "task_stop", "delegate", "delegate_result"]);
|
|
10
|
+
const FILE_TOOLS = new Set(["read", "edit", "write"]);
|
|
11
|
+
const TRAIL = 3;
|
|
12
|
+
// The hand-off waits this long after the stage was last in use.
|
|
13
|
+
export const QUIET_MS = 1500;
|
|
14
|
+
// A touch, wheel or scroll counts as use this long (phones have no hover).
|
|
15
|
+
export const TOUCH_MS = 4000;
|
|
16
|
+
|
|
17
|
+
// A tool's tick colour: read-like, edit, exec or other.
|
|
18
|
+
export function toolKind(name) {
|
|
19
|
+
if (READ_LIKE.has(name)) return "read";
|
|
20
|
+
if (EDITS.has(name)) return "edit";
|
|
21
|
+
if (EXECS.has(name)) return "exec";
|
|
22
|
+
return "other";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rowTitle(row) {
|
|
26
|
+
return row.title || row.params || "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// While the first step has no thinking and no tool rows the turn is plain:
|
|
30
|
+
// its text streams in the stage's answer slot, as the turn view's
|
|
31
|
+
// chrome-less block streams like a bubble.
|
|
32
|
+
export function isPlain(turn) {
|
|
33
|
+
if (!turn || turn.ended || turn.gens.length > 1) return false;
|
|
34
|
+
const [gen] = turn.gens;
|
|
35
|
+
return !gen || (!gen.thinking && gen.tools.length === 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const END_PHASES = { completed: "answered", canceled: "canceled" };
|
|
39
|
+
|
|
40
|
+
// What the live slots show for +turn+:
|
|
41
|
+
// phase the status row's word;
|
|
42
|
+
// step the step count (at the end as the block summary counts:
|
|
43
|
+
// an answer-only generation is no step);
|
|
44
|
+
// headline the newest narration sentence (+narration+, the view's sentence
|
|
45
|
+
// feed), else the newest thinking one (+thinking+, its ticker),
|
|
46
|
+
// else "…";
|
|
47
|
+
// tool the running call ({name, title, kind}) or null;
|
|
48
|
+
// trail the last three finished calls, newest last (a file's title
|
|
49
|
+
// without its dir);
|
|
50
|
+
// ticks one per call: {kind, status, step (the gen's index)}.
|
|
51
|
+
// +pending+: a card waits for the user; +ended+: the turn's end kind.
|
|
52
|
+
export function liveSlots(turn, { narration = "", thinking = "", pending = false, ended = null } = {}) {
|
|
53
|
+
const gens = turn?.gens || [];
|
|
54
|
+
const rows = gens.flatMap((gen, step) => gen.tools.map((row) => ({ row, step })));
|
|
55
|
+
const running = [...rows].reverse().find(({ row }) => row.status === "running")?.row || null;
|
|
56
|
+
const current = gens[gens.length - 1];
|
|
57
|
+
let phase;
|
|
58
|
+
if (ended) phase = END_PHASES[ended] || "failed";
|
|
59
|
+
else if (pending) phase = "waiting for you";
|
|
60
|
+
else if (running) phase = "running a tool";
|
|
61
|
+
else if (current?.text && isPlain(turn)) phase = "writing the answer";
|
|
62
|
+
else if (current?.text && !current.textDone) phase = "writing";
|
|
63
|
+
else phase = "thinking";
|
|
64
|
+
let headline = { text: "…", thinking: false };
|
|
65
|
+
if (narration) headline = { text: narration, thinking: false };
|
|
66
|
+
else if (thinking) headline = { text: thinking, thinking: true };
|
|
67
|
+
const trail = rows.filter(({ row }) => row.status !== "running").slice(-TRAIL).map(({ row }) => {
|
|
68
|
+
const title = rowTitle(row);
|
|
69
|
+
return { name: toolName(row), title: FILE_TOOLS.has(row.tool) ? title.split("/").pop() : title, status: row.status };
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
phase,
|
|
73
|
+
step: ended ? gens.filter((g) => !g.answer || g.thinking || g.tools.length).length : gens.length,
|
|
74
|
+
headline,
|
|
75
|
+
tool: running ? { name: toolName(running), title: rowTitle(running), kind: toolKind(running.tool) } : null,
|
|
76
|
+
trail,
|
|
77
|
+
ticks: rows.map(({ row, step }) => ({ kind: toolKind(row.tool), status: row.status, step })),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// In use: the pointer over the stage, focus or a selection in it, or a
|
|
82
|
+
// touch/wheel/scroll in it within TOUCH_MS.
|
|
83
|
+
export function inUseFrom({ pointerIn, lastTouchMs, focusInside, selectionInside, nowMs }) {
|
|
84
|
+
return !!(pointerIn || focusInside || selectionInside || (lastTouchMs && nowMs - lastTouchMs < TOUCH_MS));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// One tick of the hand-off watcher: due once the turn has ended, nothing
|
|
88
|
+
// is pending (a card, the turn-end read) and the stage has not been in use
|
|
89
|
+
// for QUIET_MS. Returns the next quiet clock with it.
|
|
90
|
+
export function handOffDue({ ended, pending, inUse, quietSinceMs, nowMs }) {
|
|
91
|
+
if (!ended || pending || inUse) return { due: false, quietSinceMs: null };
|
|
92
|
+
const since = quietSinceMs ?? nowMs;
|
|
93
|
+
return { due: nowMs - since >= QUIET_MS, quietSinceMs: since };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A handed-off turn's nodes in the order the turn view leaves them: the
|
|
97
|
+
// prompt bubble (none for a reminder turn), the block, the extras in
|
|
98
|
+
// arrival order, the answer's pieces, the timing line, the end line, the
|
|
99
|
+
// kept cards.
|
|
100
|
+
export function handOffOrder({ prompt = null, block = null, extras = [], answer = [], timing = null, end = null, kept = [] }) {
|
|
101
|
+
return [prompt, block, ...extras, ...answer, timing, end, ...kept].filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Where a row app.js would append goes: the stage's extras while a turn is
|
|
105
|
+
// live (and not handing off), else the history; the recap always the
|
|
106
|
+
// history (it is idle-only).
|
|
107
|
+
export function placeFor({ live, handingOff, kind }) {
|
|
108
|
+
if (kind === "recap") return "history";
|
|
109
|
+
return live && !handingOff ? "stage" : "history";
|
|
110
|
+
}
|