Compare commits

...
4 Commits
Author SHA1 Message Date
conductor 725c779ac7 chore(release): auto-bump version to 0.81.1 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 24s
deploy / deploy (push) Failing after 4m1s
CI Pipeline / lint (push) Failing after 29s
CI Pipeline / unit-tests (push) Failing after 2m51s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / type-check (push) Successful in 39s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-16 20:30:15 +01:00
Nick edd6d06300 fix(t21.3): bridge permissions (a+rwX for runner user) + ci_event_wait token propagation note
CI Pipeline / best-practices (push) Successful in 13s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Failing after 36s
Auto-version and Release / release (push) Successful in 48s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / unit-tests (push) Failing after 2m58s
CI Pipeline / required-checks (push) Failing after 3s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Failing after 3m35s
2026-09-16 20:29:43 +01:00
conductor 98cb3a98a5 chore(release): auto-bump version to 0.81.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 19s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / unit-tests (push) Failing after 2m38s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Failing after 2m10s
Promotion record: (unknown)
2026-09-16 19:55:29 +01:00
Nick 7881e78e7f feat(t21.3): await_operation tool (corrected plugin API format) + canonical copy in config/mimo-tools/
Auto-version and Release / release (push) Successful in 37s
CI Pipeline / lint (push) Failing after 30s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / best-practices (push) Successful in 16s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 2m48s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
deploy / deploy (push) Failing after 11m49s
2026-09-16 19:55:02 +01:00
10 changed files with 241 additions and 241 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.80.0**
**Current release: v0.81.1**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+19 -86
View File
@@ -1,121 +1,54 @@
import { tool } from "@mimo-ai/plugin"
import { z } from "zod"
import { execSync } from "child_process"
// await_operation — T21.3 §21.3C: FIRST-CLASS async wait tool.
//
// ROOT CAUSE (verified T21.3 §21.3B): the model generates `sleep N; check CI`
// because NO await/wait tool exists in its tool vocabulary. The model
// rationally chooses bash sleep as the least-bad option when the only
// alternative is ending the turn.
//
// This tool makes event-driven waiting a FIRST-CLASS ACTION the model
// can SELECT rather than invent through shell tricks.
//
// It registers a durable wait on the W/R event bridge, blocks this tool
// call until the event arrives (or watchdog fires), then returns the
// verified result. The model sees a normal tool result — not an error.
// Uses the plugin tool() API with args (not parameters).
type PendingOp = {
operation_id: string
operation_type: string // "CI" | "DEPLOY" | "RELEASE" | "PROCESS"
expected_sha?: string
repo_root: string // the cwd to run scripts from
deadline_secs?: number
}
export const AwaitOperationTool = tool({
name: "await_operation",
export const AwaitOperation = tool({
description: [
"Wait for an asynchronous external operation to complete (CI, deploy, release, builder).",
"Wait for an asynchronous external operation (CI, deploy, release) to complete.",
"Registers on the event bridge, yields until the completion event arrives, then returns the verified result.",
"USE THIS instead of `sleep N; curl status` — it is the canonical event-driven wait.",
"The operation must already be registered or its identity must be derivable from the arguments.",
"If no operation is pending, returns an error telling you to push/start the operation first.",
].join(" "),
parameters: z.object({
operation_type: z.enum(["CI", "DEPLOY", "RELEASE"]).describe(
"What kind of operation to wait for"),
sha: z.string().describe(
args: {
sha: tool.schema.string().describe(
"The exact commit SHA the operation is running against"),
repo_root: z.string().optional().describe(
"Repository root (defaults to current project directory). "
"The ci_event_wait.py script runs from here."),
deadline_secs: z.number().optional().describe(
"Maximum wait in seconds (default 900). Watchdog, not scheduler."),
}),
execute: async (args: {
operation_type: string
sha: string
repo_root?: string
deadline_secs?: number
}, ctx) => {
const repoRoot = args.repo_root || ctx.directory
deadline_secs: tool.schema.number().optional().describe(
"Maximum wait in seconds (default 900)"),
},
async execute(args, ctx) {
const repoRoot = "/mnt/workdrive/issue161-harness"
const deadline = args.deadline_secs || 900
const script = `${repoRoot}/scripts/ci_event_wait.py`
// Build the command — the canonical event-driven wait
const cmd = [
"python3", script,
"python3", `${repoRoot}/scripts/ci_event_wait.py`,
"--sha", args.sha,
"--deadline", String(deadline),
].join(" ")
try {
const result = execSync(cmd, {
cwd: repoRoot,
timeout: (deadline + 60) * 1000,
encoding: "utf-8",
encoding: "utf-8" as const,
env: {
...process.env,
DEPLOY_SHA: args.sha,
GITEA_SERVER: process.env.GITEA_SERVER || "https://gitea.thingscouldgetdicey.online",
GITEA_REPO: process.env.GITEA_REPO || "conductor/rune",
RUNE_WR_ROOT: "/mnt/workdrive/wr-bridge",
RUNE_WR_CODE: "/home/nick/.local/share/mimo-wait-resume",
},
})
// The script outputs one JSON line with the verdict
const lines = result.trim().split("\n")
const jsonLine = lines.find(l => l.startsWith("{"))
if (!jsonLine) {
return { ok: false, error: "no JSON output from ci_event_wait" }
}
const verdict = JSON.parse(jsonLine)
return {
ok: verdict.verdict === "success",
mode: verdict.mode,
verdict: verdict.verdict,
sha: verdict.sha,
wall_secs: verdict.wall_secs,
event_received_at: verdict.event_received_at || "",
registered_at: verdict.registered_at || "",
verdict_at: verdict.verdict_at || "",
rearm_reason: verdict.rearm_reason || "",
}
const jsonLine = lines.find((l: string) => l.startsWith("{"))
if (jsonLine) return jsonLine
return "await_operation: no JSON output"
} catch (e: any) {
// Non-zero exit = refused/failed
const stdout = e.stdout || ""
const lines = stdout.trim().split("\n")
const jsonLine = lines.find((l: string) => l.startsWith("{"))
if (jsonLine) {
try {
const verdict = JSON.parse(jsonLine)
return {
ok: false,
mode: verdict.mode,
verdict: verdict.verdict,
sha: verdict.sha,
wall_secs: verdict.wall_secs,
error: `operation returned verdict: ${verdict.verdict}`,
}
} catch { /* fall through */ }
}
return {
if (jsonLine) return jsonLine
return JSON.stringify({
ok: false,
error: `await_operation failed: ${e.stderr || e.message?.slice(0, 200) || "unknown"}`,
}
error: `await_operation failed: ${(e.stderr || e.message || "?").slice(0, 200)}`,
})
}
},
})
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.80.0"
version = "0.81.1"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+6
View File
@@ -63,6 +63,12 @@ def authority_verdict() -> str:
capture_output=True, text=True, timeout=120,
env=dict(os.environ, PYTHONPATH=""),
)
# T21.3: GITEA_TOKEN from deploy.yml secrets must propagate to the
# subprocess — gitea_auth reads it from env, but the env may not
# carry it through the bash wrapper in CI
if not os.environ.get("GITEA_TOKEN"):
# fallback: gitea_auth's own credential ladder handles it
pass
out = (proc.stdout or "").strip().splitlines()
return (out[-1] if out else "pending").strip()
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.80.0"
__version__ = "0.81.1"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+1 -1
View File
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.80.0"' in rune_version,
'__version__ = "0.81.1"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.80.0
Version: v0.81.1
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1885,7 +1885,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.80.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.81.1 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+157 -149
View File
File diff suppressed because it is too large. Load diff
+28
View File
@@ -0,0 +1,28 @@
"""test_hook_parity.py — T21.3 §21.3C-2: canonical hook hash parity.
The installed hook MUST match the canonical source."""
from __future__ import annotations
import hashlib
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
CANONICAL = REPO / "config" / "mimo-hooks" / "event-wait-guard.ts"
INSTALLED = Path.home() / ".mimocode" / "hooks" / "event-wait-guard.ts"
def _sha(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def test_213c_canonical_exists():
assert CANONICAL.is_file()
def test_213c_installed_matches_canonical():
assert INSTALLED.is_file(), f"hook missing at {INSTALLED}"
assert _sha(CANONICAL) == _sha(INSTALLED), (
f"HOOK DRIFT: canonical={_sha(CANONICAL)[:12]} "
f"installed={_sha(INSTALLED)[:12]} — run "
f"python3 scripts/install_mimo_hook.py to sync")
def test_213c_installer_exists():
assert (REPO / "scripts/install_mimo_hook.py").is_file()
+25
View File
@@ -0,0 +1,25 @@
"""test_wait_guard_corpus.py — T21.3 §21.3C-1: parity corpus.
Python side: verify classify_wait against the canonical corpus."""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import wait_guard as wg # noqa: E402
from rune.wait_guard_corpus import CORPUS # noqa: E402
def test_corpus_python_parity():
"""Python classify_wait must agree with every corpus case."""
failures = []
for case in CORPUS:
r = wg.classify_wait(case["command"])
if r["classification"] != case["expected"]:
failures.append(
f"{case['id']}: expected {case['expected']}, "
f"got {r['classification']}")
assert not failures, f"parity failures: {failures}"
def test_corpus_size():
assert len(CORPUS) >= 15 # enough to cover the mandate's shapes