@zerwiz/ymir 0.1.5 → 0.1.8
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.
- package/.agents/agents/huginn-researcher.md +1 -1
- package/.agents/agents/kvasir-scout.md +1 -1
- package/.agents/agents/sindri-developer.md +1 -1
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/model-bridge.py +74 -4
- package/.agents/config/cron.yaml +2 -0
- package/.agents/config/eindri-dispatch.json +6 -6
- package/.agents/migrations/0003-private-data-separation.sh +11 -1
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/skills/README.md +1 -1
- package/.agents/skills/galdr-ymirsystem/SKILL.md +2 -1
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +73 -10
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +44 -1
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +290 -24
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +3 -1
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +39 -1
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +4 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +7 -2
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +1 -1
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +2 -2
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +16 -11
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +147 -4
- package/.agents/skills/groa-update/SKILL.md +20 -0
- package/.agents/skills/hamr-adapters/SKILL.md +2 -2
- package/.agents/skills/lifecycle/smoke_test.sh +7 -2
- package/.agents/skills/ymir-host/SKILL.md +2 -1
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/AGENTS.md +78 -34
- package/CHANGELOG.md +1362 -1
- package/README.md +1 -1
- package/RULES/02-agents.md +2 -2
- package/RULES/04-hoard.md +32 -0
- package/RULES/06-append-only.md +50 -0
- package/RULES/07-config.md +56 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +2 -0
- package/bin/a2a-mcp.sh +42 -7
- package/bin/agents-config.sh +30 -7
- package/bin/apodex-smoke-test.sh +14 -1
- package/bin/bifrost-bridge.sh +25 -6
- package/bin/bootstrap-macos.sh +6 -1
- package/bin/brokk-send.sh +14 -1
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +39 -7
- package/bin/crash-sense.sh +14 -1
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +156 -0
- package/bin/docs-guard.sh +2 -2
- package/bin/editor-place.sh +33 -15
- package/bin/eindri-acclaim.sh +14 -1
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +14 -1
- package/bin/eindri-start.sh +14 -1
- package/bin/einherjar-spawn.sh +9 -3
- package/bin/eir-doctor.sh +89 -1
- package/bin/electron-lib.sh +34 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +15 -2
- package/bin/github-deploy.sh +14 -1
- package/bin/gjallarhorn-expose.sh +43 -9
- package/bin/gjallarhorn-notify.sh +14 -1
- package/bin/gjallarhorn-purge.sh +14 -1
- package/bin/gjallarhorn-tunnel.sh +16 -3
- package/bin/groa-update.sh +9 -1
- package/bin/hall-snapshot.sh +21 -2
- package/bin/herdr-run.sh +23 -2
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +91 -11
- package/bin/hodd.sh +28 -4
- package/bin/huginn-research-worker.sh +14 -1
- package/bin/local-model-lock.sh +22 -3
- package/bin/mimir-bridge.py +33 -0
- package/bin/mimir-bridge.sh +14 -1
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mjollnir-webhook.sh +14 -1
- package/bin/mjollnir.sh +22 -3
- package/bin/model-hardware.sh +17 -3
- package/bin/model-resolve.sh +14 -1
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-install.sh +18 -5
- package/bin/omarchy-plugins.sh +15 -2
- package/bin/omarchy-sense.sh +14 -1
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/private-guard.sh +20 -0
- package/bin/project-git.sh +18 -4
- package/bin/public-guard.sh +3 -2
- package/bin/sessrumnir-ensure.sh +1 -1
- package/bin/sessrumnir.sh +16 -3
- package/bin/smidja-board.sh +136 -0
- package/bin/smidja-bootstrap.sh +5 -1
- package/bin/smidja-lib.sh +49 -0
- package/bin/syn-asset-pretool-check.sh +14 -1
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/tailscale-sync.sh +15 -2
- package/bin/telegram-bot.sh +14 -1
- package/bin/ui-truth-check.sh +75 -0
- package/bin/valknut-load.sh +42 -4
- package/bin/wedge-notify.sh +14 -1
- package/bin/workspace-rag.sh +14 -1
- package/bin/ymir-install.sh +257 -57
- package/bin/ymir-plan.sh +390 -0
- package/bin/ymir-say.sh +14 -1
- package/bin/ymir-setup-auth.sh +12 -1
- package/bin/ymir-style.sh +108 -0
- package/bin/ymir-validate.sh +64 -11
- package/bin/ymir.js +82 -34
- package/docs/design.md +2 -2
- package/docs/lore.md +5 -1
- package/docs/runbooks/agents.md +1 -1
- package/package.json +13 -3
- package/scripts/electron.sh +74 -15
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +65 -3
- package/.agents/config/agents.yaml +0 -50
- package/.agents/config/tailscale-sync.yaml +0 -27
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/well/episodes.jsonl +0 -367
- package/.agents/memory/well/workspace.jsonl +0 -27
package/bin/changelog-guard.sh
CHANGED
|
@@ -25,6 +25,11 @@ set -u
|
|
|
25
25
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
26
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
27
27
|
CHANGELOG="CHANGELOG.md"
|
|
28
|
+
# A change may tell its story either way: an entry appended to the ledger, or a
|
|
29
|
+
# fragment in CHANGELOG.d/ that the assembler will fold in. The fragment is the
|
|
30
|
+
# preferred form — two branches never touch the same fragment file, so the duty
|
|
31
|
+
# is met without the O(N^2) conflict every branch appending to one file causes.
|
|
32
|
+
FRAGMENT_DIR="CHANGELOG.d"
|
|
28
33
|
ZERO="0000000000000000000000000000000000000000"
|
|
29
34
|
|
|
30
35
|
usage() {
|
|
@@ -32,7 +37,8 @@ usage() {
|
|
|
32
37
|
exit 2
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
# changes_touch_changelog <base> <head> — exit 0 when
|
|
40
|
+
# changes_touch_changelog <base> <head> — exit 0 when the change TELLS ITS STORY,
|
|
41
|
+
# either by appending to CHANGELOG.md or by adding a CHANGELOG.d/ fragment.
|
|
36
42
|
changes_touch_changelog() {
|
|
37
43
|
local base=${1-} head=${2-} f
|
|
38
44
|
[ -n "$head" ] || return 1
|
|
@@ -40,9 +46,16 @@ changes_touch_changelog() {
|
|
|
40
46
|
# No base to compare against (root commit range): look at every commit.
|
|
41
47
|
f="$(git -C "$ROOT" ls-tree -r --name-only "$head" -- "$CHANGELOG" 2>/dev/null)"
|
|
42
48
|
[ -n "$f" ] && return 0
|
|
49
|
+
f="$(git -C "$ROOT" ls-tree -r --name-only "$head" -- "$FRAGMENT_DIR" 2>/dev/null | grep -v '/README.md$' | grep '\.md$')"
|
|
50
|
+
[ -n "$f" ] && return 0
|
|
43
51
|
return 1
|
|
44
52
|
fi
|
|
45
53
|
git -C "$ROOT" diff --name-only "$base" "$head" -- "$CHANGELOG" 2>/dev/null | grep -q . && return 0
|
|
54
|
+
# A fragment ADDED in the range satisfies the duty. A fragment merely deleted
|
|
55
|
+
# (the assembler folding it into the ledger) does not count on its own — but in
|
|
56
|
+
# that same push the ledger changed, which the test above already caught.
|
|
57
|
+
git -C "$ROOT" diff --name-only --diff-filter=A "$base" "$head" -- "$FRAGMENT_DIR" 2>/dev/null \
|
|
58
|
+
| grep -v '/README.md$' | grep -q '\.md$' && return 0
|
|
46
59
|
return 1
|
|
47
60
|
}
|
|
48
61
|
|
|
@@ -90,15 +103,22 @@ verdict() { # <ok|blocked>
|
|
|
90
103
|
cat >&2 <<'EOF'
|
|
91
104
|
changelog-guard: blocked — this push carries no CHANGELOG entry.
|
|
92
105
|
|
|
93
|
-
Every change that ships is a change that was told.
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
Every change that ships is a change that was told. Tell it either way:
|
|
107
|
+
|
|
108
|
+
PREFERRED — add a fragment (no conflict with any other branch):
|
|
109
|
+
CHANGELOG.d/<YYYY-MM-DD>-<slug>.md starting with "## YYYY-MM-DD — title"
|
|
110
|
+
bin/changelog-assemble.sh folds it into CHANGELOG.md
|
|
111
|
+
|
|
112
|
+
OR — append an entry to CHANGELOG.md under the current date
|
|
113
|
+
(append-only — never rewrite an existing entry; RULES/06-append-only.md).
|
|
114
|
+
|
|
115
|
+
Commit it, and push again.
|
|
96
116
|
|
|
97
117
|
Deliberate override, when an entry truly does not belong:
|
|
98
118
|
YMIR_SKIP_CHANGELOG_GUARD=1 git push ...
|
|
99
119
|
EOF
|
|
100
|
-
printf 'error: changelog-guard blocked the push (no CHANGELOG
|
|
101
|
-
printf 'help:
|
|
120
|
+
printf 'error: changelog-guard blocked the push (no CHANGELOG entry or fragment in range)\n' >&2
|
|
121
|
+
printf 'help: add a CHANGELOG.d/ fragment (preferred), or append to CHANGELOG.md; override with YMIR_SKIP_CHANGELOG_GUARD=1\n' >&2
|
|
102
122
|
exit 1
|
|
103
123
|
}
|
|
104
124
|
|
|
@@ -111,12 +131,24 @@ case "${1:-}" in
|
|
|
111
131
|
cat >"$hook" <<EOF
|
|
112
132
|
#!/usr/bin/env bash
|
|
113
133
|
# pre-push hook — the delivery gate. Installed by bin/changelog-guard.sh --install.
|
|
134
|
+
# 0. changelog-assemble — fold CHANGELOG.d/ fragments into CHANGELOG.md, so the
|
|
135
|
+
# ledger is never behind what the fragments already tell. Folding is an
|
|
136
|
+
# append: existing entries are copied verbatim, never reordered.
|
|
114
137
|
# 1. branch-guard — Rule 08: never push a protected branch
|
|
115
|
-
# 2. changelog-guard — every push carries a CHANGELOG entry
|
|
138
|
+
# 2. changelog-guard — every push carries a CHANGELOG entry (or a fragment)
|
|
116
139
|
set -u
|
|
117
140
|
refs="\$(mktemp)"
|
|
118
141
|
trap 'rm -f "\$refs"' EXIT
|
|
119
142
|
cat >"\$refs"
|
|
143
|
+
if [ -x "$SCRIPT_DIR/changelog-assemble.sh" ]; then
|
|
144
|
+
"$SCRIPT_DIR/changelog-assemble.sh" >/dev/null 2>&1 || true
|
|
145
|
+
# If folding changed the ledger, it is now a working-tree change the push does
|
|
146
|
+
# not carry. Say so loudly rather than pushing a ledger the fragments are not in.
|
|
147
|
+
if ! git -C "$ROOT" diff --quiet -- "$CHANGELOG" 2>/dev/null; then
|
|
148
|
+
printf 'changelog-assemble[1]{state}:\n "folded fragments into %s — commit it, then push again"\n' "$CHANGELOG" >&2
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
120
152
|
"$SCRIPT_DIR/branch-guard.sh" <"\$refs" || exit 1
|
|
121
153
|
"$SCRIPT_DIR/changelog-guard.sh" <"\$refs" || exit 1
|
|
122
154
|
exit 0
|
package/bin/crash-sense.sh
CHANGED
|
@@ -20,7 +20,20 @@ set -u
|
|
|
20
20
|
VERSION="1.0.0"
|
|
21
21
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
22
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
25
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
26
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
27
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
28
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
30
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
31
|
+
done
|
|
32
|
+
unset _yr _yc
|
|
33
|
+
fi
|
|
34
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
35
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
36
|
+
STATE_DIR="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
24
37
|
SEEN="$STATE_DIR/crash-seen.json"
|
|
25
38
|
QUIET=0; NOTIFY=1
|
|
26
39
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# design-check.sh — one identity, two renderers: they must agree.
|
|
3
|
+
#
|
|
4
|
+
# The design language is the landing page's cloth (stone · bone · bronze ·
|
|
5
|
+
# blood). It is carried in TWO places, on purpose:
|
|
6
|
+
#
|
|
7
|
+
# midgard/design-system/tokens.css the CSS variables every web surface imports
|
|
8
|
+
# apps/sessrumnir/…/themes/fensalir.json the seven SEEDS the seat derives ~40
|
|
9
|
+
# tokens from (the reference form)
|
|
10
|
+
#
|
|
11
|
+
# Two carriers of one identity drift the moment someone edits one. This is the
|
|
12
|
+
# guard: the same value must appear in both, or the build says so.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bin/design-check.sh # compare, TOON, exit 0/1
|
|
16
|
+
# bin/design-check.sh --fix-hint
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
VERSION="1.0.0"
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
22
|
+
TOKENS="$ROOT/midgard/design-system/tokens.css"
|
|
23
|
+
SEEDS="$ROOT/apps/sessrumnir/src/renderer/src/themes/fensalir.json"
|
|
24
|
+
|
|
25
|
+
case "${1-}" in
|
|
26
|
+
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
|
27
|
+
-h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
30
|
+
[ -r "$TOKENS" ] || { printf 'error: tokens not found: %s\n' "${TOKENS#"$ROOT"/}" >&2; exit 1; }
|
|
31
|
+
# The seat's cloth lives in the Sessrúmnir app repo (the apps split moved it out
|
|
32
|
+
# of the monorepo; `step_apps` clones it). In a fresh clone or a worktree it is
|
|
33
|
+
# absent until that step runs — so an absent seat is a clean SKIP, not a failure.
|
|
34
|
+
# Two carriers can only be compared when both are present; demanding the app's
|
|
35
|
+
# file would fail every worktree for a reason that is not drift.
|
|
36
|
+
if [ ! -r "$SEEDS" ]; then
|
|
37
|
+
printf 'design[1]{state,reason}:\n'
|
|
38
|
+
printf ' "SKIP","the seat'"'"'s cloth is app-provided (%s) — run bin/ymir-install.sh step_apps"\n' "${SEEDS#"$ROOT"/}"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# the pairs that MUST agree: CSS token <-> seed
|
|
43
|
+
python3 - "$TOKENS" "$SEEDS" <<'PY'
|
|
44
|
+
import json, re, sys
|
|
45
|
+
|
|
46
|
+
tokens_path, seeds_path = sys.argv[1], sys.argv[2]
|
|
47
|
+
css = open(tokens_path).read()
|
|
48
|
+
seeds = json.load(open(seeds_path))["seeds"]
|
|
49
|
+
|
|
50
|
+
def token(name):
|
|
51
|
+
m = re.search(r"--%s:\s*([^;]+);" % re.escape(name), css)
|
|
52
|
+
return m.group(1).strip() if m else None
|
|
53
|
+
|
|
54
|
+
PAIRS = [
|
|
55
|
+
("ymir-bg-0", "app", "the ground"),
|
|
56
|
+
("ymir-bg-1", "surface", "the panel"),
|
|
57
|
+
("ymir-text-0", "text", "the bone"),
|
|
58
|
+
("ymir-cyan-1", "accent", "the bronze"),
|
|
59
|
+
("ymir-ok", "success", "the metal proved true"),
|
|
60
|
+
("ymir-warn", "warning", "hot, heed it"),
|
|
61
|
+
("ymir-danger", "error", "the wound"),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
bad = []
|
|
65
|
+
print("design[%d]{css_token,seed,value,agreement}:" % len(PAIRS))
|
|
66
|
+
for tok, seed, label in PAIRS:
|
|
67
|
+
a, b = token(tok), (seeds.get(seed) or "").lower()
|
|
68
|
+
ok = a is not None and a.lower() == b
|
|
69
|
+
if not ok:
|
|
70
|
+
bad.append((tok, seed, a, b))
|
|
71
|
+
print(' "%s","%s","%s","%s"' % (tok, seed, a or "-", "ok" if ok else "MISMATCH (seed %s)" % (b or "-")))
|
|
72
|
+
|
|
73
|
+
if bad:
|
|
74
|
+
print("error: the two carriers of the cloth disagree", file=sys.stderr)
|
|
75
|
+
for tok, seed, a, b in bad:
|
|
76
|
+
print(" --%s = %s ≠ seeds.%s = %s" % (tok, a, seed, b), file=sys.stderr)
|
|
77
|
+
print("help: change the value in midgard/design-system/tokens.css AND fensalir.json, or neither", file=sys.stderr)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
print("design-check: the cloth is one — %d pairs agree" % len(PAIRS))
|
|
80
|
+
PY
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# design-icon.sh — every app wears its own rune.
|
|
3
|
+
#
|
|
4
|
+
# The icon set is runecoded (midgard/design-system/icons.md): an Elder Futhark
|
|
5
|
+
# rune, stroked at the chisel bevel, tinted by the app's HOUSE colour. Not a
|
|
6
|
+
# generic globe, not an emoji, not a screenshot of a UI.
|
|
7
|
+
#
|
|
8
|
+
# bin/design-icon.sh list # the app -> rune -> house mapping
|
|
9
|
+
# bin/design-icon.sh mint [app] # write <app>/public/icon.svg (+ the link hint)
|
|
10
|
+
# bin/design-icon.sh mint --all
|
|
11
|
+
#
|
|
12
|
+
# The tile is stone (--ymir-bg-0) with the rune in the house tint; the SVG is
|
|
13
|
+
# self-contained, so a favicon needs no build step and no raster asset.
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
VERSION="1.0.0"
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
ICONS="$ROOT/midgard/design-system/icons"
|
|
20
|
+
STONE="#0e0c09"
|
|
21
|
+
|
|
22
|
+
case "${1-}" in
|
|
23
|
+
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
|
24
|
+
-h|--help|"") sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
25
|
+
esac
|
|
26
|
+
ACTION="${1:-list}"; shift || true
|
|
27
|
+
|
|
28
|
+
# dir|glyph|tint|says|icon-name|exec|wm-class
|
|
29
|
+
# FIVE UI surfaces, FIVE DIFFERENT glyphs — no app borrows another's rune.
|
|
30
|
+
APPS=(
|
|
31
|
+
"apps/hlidskjalf|ehwaz|#c9973f|the seat — Hlidskjalf, the high seat of the control plane|ymir-hlidskjalf|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf|Ymir · Hlidskjalf"
|
|
32
|
+
"apps/hlidskjalf-mobile|raidho|#c9973f|the road — the seat carried, Hlidskjalf on a phone|ymir-hlidskjalf-mobile|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf-mobile|Ymir · Hlidskjalf Mobile"
|
|
33
|
+
"apps/odrerir|ansuz|#c9973f|the mead — Óðrerir, the Live Hall (Odin's breath, inspiration)|ymir-odrerir|scripts/electron.sh start --view odrerir|ymir-odrerir|Ymir · Óðrerir"
|
|
34
|
+
"apps/sessrumnir|othala|#c9973f|the hall — Sessrúmnir, the seat that holds the cloth|ymir-sessrumnir|bin/sessrumnir.sh start|ymir-sessrumnir|Ymir · Sessrúmnir"
|
|
35
|
+
"apps/smidja-factory/apps/visualizer|kaunan|#f59e0b|the torch — the forge's eye, Smíðja's trace|ymir-smidja|scripts/electron.sh start --view smidja|ymir-smidja|Ymir · Smíðja"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
list() {
|
|
39
|
+
printf 'app_icons[%d]{app,rune,tint,says}:\n' "${#APPS[@]}"
|
|
40
|
+
for row in "${APPS[@]}"; do
|
|
41
|
+
IFS='|' read -r dir glyph tint says icon _exec _klass _name <<<"$row"
|
|
42
|
+
# The surface's name is the one the desktop knows it by (ymir-smidja →
|
|
43
|
+
# smidja), never the directory's leaf — a path is not a name (the naming law).
|
|
44
|
+
surface="${icon#ymir-}"
|
|
45
|
+
printf ' "%s","%s","%s","%s"\n' "$surface" "$glyph" "$tint" "$says"
|
|
46
|
+
done
|
|
47
|
+
printf 'runes[6]{glyph,name,meaning}:\n'
|
|
48
|
+
printf ' "algiz","ᛉ","the Ymir emblem — the platform itself"\n'
|
|
49
|
+
printf ' "ehwaz","ᛖ","horse/journey — the seat and its transit"\n'
|
|
50
|
+
printf ' "ansuz","ᚨ","Odin'"'"'s breath — the forge"\n'
|
|
51
|
+
printf ' "sowilo","ᛊ","sun — light on the tree"\n'
|
|
52
|
+
printf ' "valhalla","ᚹ","the hall"\n'
|
|
53
|
+
printf ' "gjallarhorn","ᚷ","the horn — the tunnel"\n'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
mint() { # <app-dir> <glyph> <tint> <label>
|
|
57
|
+
local dir=$1 glyph=$2 tint=$3 label=$4
|
|
58
|
+
local g="$ICONS/$glyph.svg"
|
|
59
|
+
[ -r "$g" ] || { printf 'error: no glyph %s in midgard/design-system/icons/\nhelp: bin/design-icon.sh list\n' "$glyph" >&2; return 1; }
|
|
60
|
+
local path
|
|
61
|
+
path="$(sed -n 's/.*<path d="\([^"]*\)".*/\1/p' "$g" | head -1)"
|
|
62
|
+
[ -n "$path" ] || { printf 'error: %s carries no <path> to reuse\n' "$glyph" >&2; return 1; }
|
|
63
|
+
local outdir="$ROOT/$dir/public"
|
|
64
|
+
[ -d "$(dirname "$outdir")" ] || { printf ' "%s","SKIP (no such app)"\n' "$dir"; return 0; }
|
|
65
|
+
mkdir -p "$outdir"
|
|
66
|
+
{
|
|
67
|
+
printf '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="%s">\n' "$label"
|
|
68
|
+
printf ' <title>%s</title>\n' "$label"
|
|
69
|
+
printf ' <rect width="32" height="32" rx="7" fill="%s"/>\n' "$STONE"
|
|
70
|
+
printf ' <g transform="translate(4 4) scale(1.0)" fill="none" stroke="%s" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter">\n' "$tint"
|
|
71
|
+
printf ' <path d="%s"/>\n' "$path"
|
|
72
|
+
printf ' </g>\n</svg>\n'
|
|
73
|
+
} >"$outdir/icon.svg"
|
|
74
|
+
printf ' "%s","%s","%s","<link rel=\\"icon\\" href=\\"/icon.svg\\" type=\\"image/svg+xml\\">"\n' \
|
|
75
|
+
"$(basename "$dir")" "$glyph" "${outdir#"$ROOT"/}/icon.svg"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ── install into the user's system ───────────────────────────────────────────
|
|
80
|
+
# An icon that only exists in the checkout is an icon the operator cannot pin.
|
|
81
|
+
# This writes the rune into the user's icon theme and a .desktop entry per app,
|
|
82
|
+
# rendered with THIS machine's root (never a hardcoded home), so every app is
|
|
83
|
+
# dockable and carries the house mark. Idempotent.
|
|
84
|
+
# The DESKTOP the operator actually sees, not whatever a session happened to
|
|
85
|
+
# export: an agent harness may set XDG_DATA_HOME to a sandbox (this one does),
|
|
86
|
+
# and an icon written there is an icon nobody can pin. YMIR_DESKTOP_DATA_HOME
|
|
87
|
+
# exists for a test that genuinely wants a throwaway target.
|
|
88
|
+
data_home="${YMIR_DESKTOP_DATA_HOME:-$HOME/.local/share}"
|
|
89
|
+
icons_dir="$data_home/icons/hicolor/scalable/apps"
|
|
90
|
+
apps_dir="$data_home/applications"
|
|
91
|
+
# app | entry name | Exec | StartupWMClass
|
|
92
|
+
ENTRIES=(
|
|
93
|
+
"hlidskjalf|Ymir · Hlidskjalf|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf"
|
|
94
|
+
"odrerir|Ymir · Óðrerir|scripts/electron.sh start --view odrerir|ymir-odrerir"
|
|
95
|
+
"sessrumnir|Ymir · Sessrúmnir|bin/sessrumnir.sh start|ymir-sessrumnir"
|
|
96
|
+
"visualizer|Ymir · Smíðja|scripts/electron.sh start --view smidja|ymir-smidja"
|
|
97
|
+
"hlidskjalf-mobile|Ymir · Hlidskjalf Mobile|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf-mobile"
|
|
98
|
+
)
|
|
99
|
+
install_all() {
|
|
100
|
+
# A phone app gets an icon for its own bundle, never a desktop entry.
|
|
101
|
+
local EXCLUDE=" hlidskjalf-mobile "
|
|
102
|
+
|
|
103
|
+
mkdir -p "$icons_dir" "$apps_dir" || { printf 'error: cannot write %s / %s\n' "$icons_dir" "$apps_dir" >&2; exit 1; }
|
|
104
|
+
printf 'installed[%d]{app,icon,entry}:\n' "${#APPS[@]}"
|
|
105
|
+
local row dir glyph tint says iconname exec klass src
|
|
106
|
+
for row in "${APPS[@]}"; do
|
|
107
|
+
IFS='|' read -r dir glyph tint says iconname exec klass display <<<"$row"
|
|
108
|
+
case "$EXCLUDE" in *" $(basename "$dir") "*) continue ;; esac
|
|
109
|
+
src="$ROOT/$dir/public/icon.svg"
|
|
110
|
+
if [ ! -f "$src" ]; then
|
|
111
|
+
# mint it first — the icon is defined by this table, so it is never absent
|
|
112
|
+
if [ -r "$ICONS/$glyph.svg" ]; then
|
|
113
|
+
mkdir -p "$(dirname "$src")" 2>/dev/null
|
|
114
|
+
path="$(sed -n 's/.*<path d="\([^"]*\)".*/\1/p' "$ICONS/$glyph.svg" | head -1)"
|
|
115
|
+
{ printf '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="%s">\n' "$says"
|
|
116
|
+
printf ' <title>%s</title>\n <rect width="32" height="32" rx="7" fill="%s"/>\n' "$says" "$STONE"
|
|
117
|
+
printf ' <g transform="translate(4 4)" fill="none" stroke="%s" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter">\n' "$tint"
|
|
118
|
+
printf ' <path d="%s"/>\n </g>\n</svg>\n' "$path"
|
|
119
|
+
} >"$src" 2>/dev/null || true
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
cp -f "$src" "$icons_dir/$iconname.svg" 2>/dev/null || { printf ' "%s","FAILED","-","no icon at %s"\n' "$dir" "${src#"$ROOT"/}"; continue; }
|
|
123
|
+
{
|
|
124
|
+
printf '[Desktop Entry]\nType=Application\nVersion=1.0\n'
|
|
125
|
+
printf 'Name=%s\n' "$display"
|
|
126
|
+
printf 'Comment=%s\n' "$says"
|
|
127
|
+
printf 'Exec=bash %s/%s\n' "$ROOT" "$exec"
|
|
128
|
+
printf 'Icon=%s\n' "$iconname"
|
|
129
|
+
printf 'Terminal=false\nCategories=Development;Utility;\n'
|
|
130
|
+
printf 'StartupWMClass=%s\nStartupNotify=true\n' "$klass"
|
|
131
|
+
} >"$apps_dir/$iconname.desktop"
|
|
132
|
+
printf ' "%s","%s.svg","%s.desktop"\n' "$iconname" "$iconname" "$iconname"
|
|
133
|
+
done
|
|
134
|
+
[ -f "$apps_dir/ymir-smidja.desktop" ] && rm -f "$apps_dir/ymir-smidja.desktop"
|
|
135
|
+
for stale in ymir-hlidskjalf-mobile; do rm -f "$apps_dir/$stale.desktop" "$icons_dir/$stale.svg"; done
|
|
136
|
+
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps_dir" >/dev/null 2>&1 || true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "$ACTION" in
|
|
140
|
+
list) list ;;
|
|
141
|
+
install) install_all ;;
|
|
142
|
+
mint)
|
|
143
|
+
if [ "${1:-}" = "--all" ]; then
|
|
144
|
+
printf 'minted[%d]{app,rune,file,link}:\n' "${#APPS[@]}"
|
|
145
|
+
for row in "${APPS[@]}"; do IFS='|' read -r d g t l _i _e _k _n <<<"$row"; mint "$d" "$g" "$t" "$l"; done
|
|
146
|
+
else
|
|
147
|
+
want="${1:-}"; found=0
|
|
148
|
+
printf 'minted[1]{app,rune,file,link}:\n'
|
|
149
|
+
for row in "${APPS[@]}"; do
|
|
150
|
+
IFS='|' read -r d g t l _i _e _k _n <<<"$row"
|
|
151
|
+
case "$(basename "$d")" in "$want") mint "$d" "$g" "$t" "$l"; found=1 ;; esac
|
|
152
|
+
done
|
|
153
|
+
[ "$found" = 1 ] || { printf 'error: unknown app %s\nhelp: bin/design-icon.sh list\n' "$want" >&2; exit 2; }
|
|
154
|
+
fi ;;
|
|
155
|
+
*) printf 'error: unknown action %s\nhelp: bin/design-icon.sh [list|mint] [app|--all]\n' "$ACTION" >&2; exit 2 ;;
|
|
156
|
+
esac
|
package/bin/docs-guard.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# docs-guard.sh — docs/ is the PUBLIC, user-facing tree. Block operator-private
|
|
3
3
|
# documents (plans, strategy, roadmaps, the masterplan) before they are staged.
|
|
4
|
-
# Those live at $YMIR_HOME/docs/ (Rule 04-hoard.md).
|
|
4
|
+
# Those live at $YMIR_HOME/hodd/docs/ (Rule 04-hoard.md).
|
|
5
5
|
#
|
|
6
6
|
# bin/docs-guard.sh # scan the staged change (used as pre-commit)
|
|
7
7
|
# bin/docs-guard.sh --all # scan every tracked file
|
|
@@ -35,5 +35,5 @@ if [ "${1:-}" = "--all" ]; then
|
|
|
35
35
|
scan_list < <(git -C "$ROOT" ls-files)
|
|
36
36
|
else
|
|
37
37
|
scan_list < <(git -C "$ROOT" diff --cached --name-only --diff-filter=ACM)
|
|
38
|
-
fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/docs/ (Rule 04)\n' >&2; exit 1; }
|
|
38
|
+
fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/hodd/docs/ (Rule 04)\n' >&2; exit 1; }
|
|
39
39
|
exit 0
|
package/bin/editor-place.sh
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# editor-place.sh — the editor
|
|
2
|
+
# editor-place.sh — the editor is UNMANAGED by default: it opens on the
|
|
3
|
+
# desktop you are on, focused and clickable, like any other app.
|
|
3
4
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# is the
|
|
5
|
+
# It CAN pin the editor to its own desktop — never on yours — the same way the
|
|
6
|
+
# Ymir apps do (bin/desktop-place.sh), the mechanism proven on this machine:
|
|
7
|
+
# YMIR_EDITOR_PIN=1 bin/editor-place.sh apply
|
|
8
|
+
# The pin is opt-in because the old always-on behavior made the editor
|
|
9
|
+
# un-focusable (no_focus) and dropped it onto a different desktop, which the
|
|
10
|
+
# Allfather found unusable.
|
|
8
11
|
#
|
|
9
12
|
# Hyprland applies window rules at MAP time, so the window never appears on the
|
|
10
13
|
# working desktop at all. Writing `o.window({ class = ... }, { workspace = ... })`
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
# bin/editor-place.sh --version
|
|
23
26
|
#
|
|
24
27
|
# Env: YMIR_EDITOR_CLASS (default: the resolved editor, else `code`)
|
|
28
|
+
# YMIR_EDITOR_PIN (1 = pin the editor to its own desktop; default unmanaged)
|
|
25
29
|
set -u
|
|
26
30
|
|
|
27
31
|
VERSION="1.0.0"
|
|
@@ -142,12 +146,13 @@ write_rule() { # <desktop>
|
|
|
142
146
|
done
|
|
143
147
|
)"
|
|
144
148
|
fi
|
|
145
|
-
local editorline
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
local editorline=""
|
|
150
|
+
# Unmanaged by default: the editor opens on the active desktop, focused and
|
|
151
|
+
# clickable. Only pin it away (the old `no_focus` behavior) when the operator
|
|
152
|
+
# opts in with YMIR_EDITOR_PIN=1.
|
|
153
|
+
if [ "${YMIR_EDITOR_PIN:-0}" = "1" ]; then
|
|
154
|
+
editorline="$(printf 'o.window({ class = "^%s$" }, { workspace = "%s", no_focus = true })\n' "$cls" "$d")"
|
|
155
|
+
fi
|
|
151
156
|
if [ "$DRY" = 1 ]; then
|
|
152
157
|
printf '%s' "$header"
|
|
153
158
|
[ -n "$applines" ] && printf '%s\n' "$applines"
|
|
@@ -174,8 +179,13 @@ include_rule_file() {
|
|
|
174
179
|
|
|
175
180
|
case "$ACTION" in
|
|
176
181
|
plan)
|
|
177
|
-
cls="$(editor_class)"
|
|
178
|
-
|
|
182
|
+
cls="$(editor_class)"
|
|
183
|
+
if [ "${YMIR_EDITOR_PIN:-0}" = "1" ]; then
|
|
184
|
+
d="$(plan_desktop)"
|
|
185
|
+
printf 'editor-place[1]{class,desktop}:\n "%s",%s\n' "$cls" "$d"
|
|
186
|
+
else
|
|
187
|
+
printf 'editor-place[1]{class,desktop}:\n "%s","unmanaged (YMIR_EDITOR_PIN=1 to pin)"\n' "$cls"
|
|
188
|
+
fi
|
|
179
189
|
;;
|
|
180
190
|
apply)
|
|
181
191
|
have hyprctl || { printf 'editor-place[1]{state}:\n "skipped — no hyprctl (not a Hyprland host)"\n'; exit 0; }
|
|
@@ -190,9 +200,17 @@ case "$ACTION" in
|
|
|
190
200
|
printf 'editor-place[1]{state,desktop,configerrors}:\n "written, but Hyprland reported errors","%s","%s"\n' "$d" "$(printf '%s' "$errs" | tr '\n' ' ')"
|
|
191
201
|
exit 1
|
|
192
202
|
fi
|
|
193
|
-
|
|
203
|
+
if [ "${YMIR_EDITOR_PIN:-0}" = "1" ]; then
|
|
204
|
+
printf 'editor-place[1]{state,class,desktop}:\n "written","%s","%s"\n' "$(editor_class)" "$d"
|
|
205
|
+
else
|
|
206
|
+
printf 'editor-place[1]{state,class}:\n "written (editor unmanaged)","%s"\n' "$(editor_class)"
|
|
207
|
+
fi
|
|
194
208
|
else
|
|
195
|
-
|
|
209
|
+
if [ "${YMIR_EDITOR_PIN:-0}" = "1" ]; then
|
|
210
|
+
printf 'editor-place[1]{state,desktop}:\n "dry-run","%s"\n' "$d"
|
|
211
|
+
else
|
|
212
|
+
printf 'editor-place[1]{state}:\n "dry-run (editor unmanaged)"\n'
|
|
213
|
+
fi
|
|
196
214
|
fi
|
|
197
215
|
;;
|
|
198
216
|
status)
|
package/bin/eindri-acclaim.sh
CHANGED
|
@@ -11,7 +11,20 @@
|
|
|
11
11
|
set -u
|
|
12
12
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
13
|
ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
16
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
17
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
18
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
19
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
21
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
22
|
+
done
|
|
23
|
+
unset _yr _yc
|
|
24
|
+
fi
|
|
25
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
26
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
27
|
+
STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
15
28
|
AGENT="${1:-}"
|
|
16
29
|
WORKTREE="${2:-}"
|
|
17
30
|
[ -n "$AGENT" ] || { echo "usage: eindri-acclaim.sh <agent> [<worktree-root>]" >&2; exit 2; }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# eindri-seat.sh — seat a worker and PROVE it, in one move.
|
|
3
|
+
#
|
|
4
|
+
# The lesson from firstmate (bin/fm-spawn.sh + fm-send.sh + fm-peek.sh): a spawn
|
|
5
|
+
# is not a seat until the brief is delivered AND the agent has been read. Ymir
|
|
6
|
+
# had ported only the launcher, so a seat could be reported while the worker sat
|
|
7
|
+
# idle — "sent but never started". This is the missing half, in our idiom.
|
|
8
|
+
#
|
|
9
|
+
# bin/eindri-seat.sh <name> --role <role> [--model <model>] -- "<brief>"
|
|
10
|
+
#
|
|
11
|
+
# It seats through bin/herdr-run.sh (the house door, so the model->harness rule
|
|
12
|
+
# and the pane bookkeeping stay in one place), waits for the pane to settle,
|
|
13
|
+
# reads it, and reports what the agent ACTUALLY is:
|
|
14
|
+
#
|
|
15
|
+
# state: working — it has begun (exit 0)
|
|
16
|
+
# state: idle — seated but nothing ran (exit 3: a seat, not an errand)
|
|
17
|
+
# state: error — the pane shows a refusal (the 403/1010 class) (exit 1)
|
|
18
|
+
#
|
|
19
|
+
# Nothing is claimed that the pane does not show.
|
|
20
|
+
set -u
|
|
21
|
+
|
|
22
|
+
VERSION="1.0.0"
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
25
|
+
SETTLE="${YMIR_SEAT_SETTLE:-35}"
|
|
26
|
+
|
|
27
|
+
case "${1-}" in
|
|
28
|
+
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
|
29
|
+
-h|--help|"") sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
30
|
+
esac
|
|
31
|
+
|
|
32
|
+
NAME="${1:-}"; shift || true
|
|
33
|
+
[ -n "$NAME" ] || { printf 'error: a name is required\nhelp: bin/eindri-seat.sh <name> --role <role> -- "<brief>"\n' >&2; exit 2; }
|
|
34
|
+
|
|
35
|
+
# hand the rest to the house door, which owns the harness rule and the pane log
|
|
36
|
+
SEAT_OUT="$("$SCRIPT_DIR/herdr-run.sh" eindri "$NAME" "$@" 2>&1)"
|
|
37
|
+
RC=$?
|
|
38
|
+
printf '%s\n' "$SEAT_OUT"
|
|
39
|
+
|
|
40
|
+
# The pane id is the door's own answer; without it there is nothing to read.
|
|
41
|
+
# The door answers with the TAB; the pane must be resolved from it (its row
|
|
42
|
+
# carries the eindri's name in the pane field, which is how I once read a name
|
|
43
|
+
# as a pane and reported an empty tail as an idle agent).
|
|
44
|
+
TAB="$(printf '%s\n' "$SEAT_OUT" | sed -n 's/.*","\(w[0-9]*:t[0-9A-Za-z]*\)","[a-z]*".*/\1/p' | head -1)"
|
|
45
|
+
PANE=""
|
|
46
|
+
if [ -n "$TAB" ] && [ -x "${HERDR_BIN_PATH:-}" ]; then
|
|
47
|
+
PANE="$("${HERDR_BIN_PATH}" pane list 2>/dev/null | python3 -c "
|
|
48
|
+
import json,sys
|
|
49
|
+
try:
|
|
50
|
+
d=json.load(sys.stdin)
|
|
51
|
+
except Exception:
|
|
52
|
+
raise SystemExit
|
|
53
|
+
tab=sys.argv[1] if len(sys.argv)>1 else ''
|
|
54
|
+
for p in d.get('result',{}).get('panes',[]) or []:
|
|
55
|
+
if p.get('tab_id')==tab and p.get('agent'):
|
|
56
|
+
print(p.get('pane_id','')); break
|
|
57
|
+
" "$TAB")"
|
|
58
|
+
fi
|
|
59
|
+
if [ -z "$PANE" ]; then
|
|
60
|
+
# herdr absent or the door refused: report what it said, claim nothing more.
|
|
61
|
+
printf 'eindri[1]{name,state,detail}:\n "%s","unseated","the door did not return a pane"\n' "$NAME"
|
|
62
|
+
exit "$([ "$RC" = 0 ] && echo 3 || echo 1)"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
sleep "$SETTLE"
|
|
66
|
+
TAIL="$("${HERDR_BIN_PATH:-herdr}" pane read "$PANE" 2>/dev/null | tail -25)"
|
|
67
|
+
|
|
68
|
+
STATE="idle"; WHY="seated, but the pane shows no work yet"
|
|
69
|
+
case "$TAIL" in
|
|
70
|
+
*"Error: 403"*|*"error code: 1010"*|*"Access denied"*)
|
|
71
|
+
STATE="blocked"; WHY="the provider refused the client (403/1010 class) — see the pane" ;;
|
|
72
|
+
*working*|*"esc interrupt"*|*"thinking"*|*"✻"*|*"▣"*)
|
|
73
|
+
STATE="working"; WHY="the agent has begun" ;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
printf 'eindri[1]{name,pane,state,detail}:\n "%s","%s","%s","%s"\n' "$NAME" "$PANE" "$STATE" "$WHY"
|
|
77
|
+
printf 'pane_tail[1]{last_lines}:\n "%s"\n' "$(printf '%s' "$TAIL" | tail -3 | tr '\n' '~' | sed 's/"/\\"/g' | cut -c1-200)"
|
|
78
|
+
|
|
79
|
+
case "$STATE" in
|
|
80
|
+
working) exit 0 ;;
|
|
81
|
+
blocked) exit 1 ;;
|
|
82
|
+
*) exit 3 ;;
|
|
83
|
+
esac
|
package/bin/eindri-seen.sh
CHANGED
|
@@ -13,7 +13,20 @@
|
|
|
13
13
|
set -u
|
|
14
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
15
|
ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
18
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
19
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
20
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
21
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
23
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
24
|
+
done
|
|
25
|
+
unset _yr _yc
|
|
26
|
+
fi
|
|
27
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
28
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
29
|
+
STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
17
30
|
AGENT="${1:-}"
|
|
18
31
|
WORKTREE="${2:-}"
|
|
19
32
|
[ -n "$AGENT" ] || { echo "usage: eindri-seen.sh <agent> [<worktree-root>]" >&2; exit 2; }
|
package/bin/eindri-start.sh
CHANGED
|
@@ -15,7 +15,20 @@ set -u
|
|
|
15
15
|
VERSION="1.0.0"
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
17
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
20
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
21
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
22
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
23
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
25
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
26
|
+
done
|
|
27
|
+
unset _yr _yc
|
|
28
|
+
fi
|
|
29
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
30
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
31
|
+
STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
19
32
|
SEATS="$STATE/herdr-seats"
|
|
20
33
|
SESSION="${YMIR_HERDR_SESSION:-brokk}"
|
|
21
34
|
|
package/bin/einherjar-spawn.sh
CHANGED
|
@@ -342,6 +342,12 @@ else
|
|
|
342
342
|
fi
|
|
343
343
|
|
|
344
344
|
# --- isolation resolution ----------------------------------------------------
|
|
345
|
+
# A spawned Eindri is a MODEL-DRIVEN worker: it must reach a model endpoint to
|
|
346
|
+
# think at all. Utgard runs with `--network none`, so a sandboxed agent cannot
|
|
347
|
+
# reach either a cloud model (OpenCode Go) or a local one (llama.cpp) — it dies
|
|
348
|
+
# silently at launch, leaving an empty pane and no status line. Utgard is for
|
|
349
|
+
# untrusted CODE, not for the agent's own brain. So `auto` keeps the worker in
|
|
350
|
+
# its worktree, and `on` is an explicit choice that warns.
|
|
345
351
|
ISOLATION_EFFECTIVE=off
|
|
346
352
|
case "$ISOLATION" in
|
|
347
353
|
on)
|
|
@@ -351,14 +357,14 @@ case "$ISOLATION" in
|
|
|
351
357
|
exit 1
|
|
352
358
|
}
|
|
353
359
|
ISOLATION_EFFECTIVE=on
|
|
360
|
+
echo "warning: Utgard runs with --network none — the worker's model endpoint must be reachable INSIDE the sandbox, or the agent dies silently. Pass --isolation off to run in the worktree." >&2
|
|
354
361
|
;;
|
|
355
362
|
off) ISOLATION_EFFECTIVE=off ;;
|
|
356
363
|
auto)
|
|
364
|
+
ISOLATION_EFFECTIVE=off
|
|
357
365
|
if [ -n "$ENGINE" ] && "$ENGINE" image inspect utgard-runner:latest >/dev/null 2>&1; then
|
|
358
|
-
|
|
359
|
-
echo "isolation: on (Utgard image present; pass --isolation off to run in the worktree)" >&2
|
|
366
|
+
echo "isolation: off (Utgard image present, but a model-driven worker needs its endpoint and Utgard has no network — pass --isolation on only for a sandbox that can reach a model)" >&2
|
|
360
367
|
else
|
|
361
|
-
ISOLATION_EFFECTIVE=off
|
|
362
368
|
echo "isolation: off (no Utgard image; pass --isolation on after building it)" >&2
|
|
363
369
|
fi
|
|
364
370
|
;;
|