Skip to content

Commit a29f45b

Browse files
authored
Prepare 1.3.2 source for release validation (#86)
Fix the source architecture ratchet without raising budgets, make the follow-up authorization fixture deterministic, and complete the 1.3.2 changelog before platform acceptance.\n\nSigned-off-by: Joseph Yaksich <294273268+gitcommit90@users.noreply.github.com>
1 parent f56b12b commit a29f45b

8 files changed

Lines changed: 27 additions & 22 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- Board follow-up controls remain available in compact thread layouts.
3636
- Small-screen thread headers keep Back and Close navigation clear by shrinking
3737
or hiding usage metadata before it can crowd the navigation controls.
38+
- Scheduled work stays visible while its wake is running, shows that the agent is
39+
checking now, and continues toward the requested outcome after intermediate
40+
jobs succeed or fail.
41+
- Linux installation and OCI startup repair missing, empty, or historical
42+
newline-terminated Podman netavark selectors atomically while preserving
43+
intentional custom backend selections.
3844

3945
## [1.3.1] - 2026-08-26
4046

‎config/module-budgets.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"legacy": {
88
"lines": {
99
"src/client/app.ts": 3672,
10-
"src/client/channel.ts": 1205,
10+
"src/client/channel.ts": 1202,
1111
"src/client/cowork.ts": 879,
1212
"src/client/routing.ts": 864,
1313
"src/client/settings.ts": 812,

‎public/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@
3434
</head>
3535
<body class="h-screen w-screen overflow-hidden antialiased">
3636
<div id="app" class="h-full w-full"></div>
37-
<script type="module" src="/bundle.js?v=9b4dc4047f62"></script>
37+
<script type="module" src="/bundle.js?v=56c74917b6b0"></script>
3838
</body>
3939
</html>

‎src/client/app.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2871,7 +2871,6 @@ function residentFollowupName(): string {
28712871
function threadFollowupBanner(): HTMLElement | null {
28722872
const followup = S.threadFollowup;
28732873
if (!followup?.due_at || !["pending", "running"].includes(followup.status)) return null;
2874-
const running = followup.status === "running";
28752874
const intent = String(followup.check_hint || followup.reason || "").trim();
28762875
return h("div", {
28772876
class: "mx-3 mb-2 rounded-lg border border-accent/30 bg-accent-soft px-3 py-2 sm:mx-4",
@@ -2881,9 +2880,9 @@ function threadFollowupBanner(): HTMLElement | null {
28812880
},
28822881
h("div", { class: "flex min-w-0 items-center gap-2 text-xs" },
28832882
h("span", { class: "shrink-0 text-accent" }, icon("helm", 15)),
2884-
h("span", { class: "min-w-0 flex-1 text-fg" }, h("strong", {}, `@${residentFollowupName()}`), running ? " is checking now" : " will check back in ",
2885-
running ? null : h("span", { class: "font-mono tabular-nums text-accent", dataset: { threadFollowupCountdown: "" } }, formatThreadFollowupCountdown(Number(followup.due_at)))),
2886-
h("span", { class: "hidden shrink-0 font-mono text-[9px] uppercase tracking-[0.12em] text-faint sm:inline" }, running ? "working" : "scheduled")),
2883+
h("span", { class: "min-w-0 flex-1 text-fg" }, h("strong", {}, `@${residentFollowupName()}`), followup.status === "running" ? " is checking now" : " will check back in ",
2884+
followup.status === "running" ? null : h("span", { class: "font-mono tabular-nums text-accent", dataset: { threadFollowupCountdown: "" } }, formatThreadFollowupCountdown(Number(followup.due_at)))),
2885+
h("span", { class: "hidden shrink-0 font-mono text-[9px] uppercase tracking-[0.12em] text-faint sm:inline" }, followup.status === "running" ? "working" : "scheduled")),
28872886
intent ? h("p", { class: "mt-1 line-clamp-2 pl-[23px] text-[11px] leading-4 text-muted" }, intent) : null);
28882887
}
28892888
function paintThreadFollowup(): void {

‎src/client/channel.ts‎

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { NOTIFICATION_SOUNDS, channelNotificationPreference, previewNotification
77
import { channelTextingSettings, skipperCallSettings, workflowModelSettings } from "./workflows.ts";
88
import { authenticatedAssetSrc } from "./avatar-assets.ts";
99
import { bindResidentFileUploads } from "./file-uploads.ts";
10+
import { formatBoardFollowupCountdown } from "./thread-formatters.ts";
1011
export type ChannelView = "chat" | "texts" | "board" | "workflows" | "threads" | "cowork" | "notes" | "files" | "terminal" | "memory" | "activity" | "settings";
1112
type RenderRefreshOptions = { preserveExisting?: boolean; isCurrent?: () => boolean; onPaint?: () => void };
1213
function refreshIsCurrent(options: RenderRefreshOptions): boolean {
@@ -78,22 +79,12 @@ function statusPath(status: string, updatedAt: number): HTMLElement {
7879
return h("div", { class: "flex flex-wrap items-center gap-1.5" }, ...parts);
7980
}
8081
/** Real countdown from durable followup.due_at (ms epoch). Updates in place once/sec. */
81-
function formatCountdown(dueAt: number, nowMs = Date.now()): string {
82-
const remaining = Math.max(0, Math.floor((dueAt - nowMs) / 1000));
83-
if (remaining <= 0) return "due now";
84-
const h = Math.floor(remaining / 3600);
85-
const m = Math.floor((remaining % 3600) / 60);
86-
const s = remaining % 60;
87-
if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m ${String(s).padStart(2, "0")}s`;
88-
if (m > 0) return `${m}m ${String(s).padStart(2, "0")}s`;
89-
return `${s}s`;
90-
}
9182
function followupCountdownEl(dueAt: number): HTMLElement {
9283
const el = h("span", {
9384
class: "board-countdown font-mono text-[11px] tabular-nums tracking-wide text-accent",
9485
dataset: { dueAt: String(dueAt) },
9586
title: `Wakes at ${new Date(dueAt).toLocaleString()}`,
96-
}, formatCountdown(dueAt)) as HTMLElement;
87+
}, formatBoardFollowupCountdown(dueAt)) as HTMLElement;
9788
return el;
9889
}
9990
function followupMeta(thread: ThreadState, opts?: { onBumped?: () => void; onCancelled?: () => void }): HTMLElement | null {
@@ -173,7 +164,7 @@ function startBoardCountdownTicker(root: HTMLElement): void {
173164
for (const node of root.querySelectorAll<HTMLElement>(".board-countdown[data-due-at]")) {
174165
const due = Number(node.dataset.dueAt || 0);
175166
if (!due) continue;
176-
node.textContent = formatCountdown(due, nowMs);
167+
node.textContent = formatBoardFollowupCountdown(due, nowMs);
177168
node.classList.toggle("board-countdown-due", due <= nowMs);
178169
}
179170
};

‎src/client/thread-formatters.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,8 @@ export function formatThreadFollowupCountdown(dueAt: number, nowMs = Date.now())
119119
if (minutes) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
120120
return `${seconds}s`;
121121
}
122+
123+
export function formatBoardFollowupCountdown(dueAt: number, nowMs = Date.now()): string {
124+
const value = formatThreadFollowupCountdown(dueAt, nowMs);
125+
return value === "now" ? "due now" : value;
126+
}

‎test/followup-authorization.mjs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const providerServer = createServer(async (req, res) => {
3131
const body = JSON.parse(raw || "{}");
3232
providerRequests.push(body);
3333
const serialized = JSON.stringify(body.messages || []);
34+
const wakeMessage = [...(body.messages || [])].reverse().find((message) => /scheduled-followup-wake/i.test(String(message.content || "")));
35+
const wake = String(wakeMessage?.content || "");
3436
const toolNames = (body.tools || []).map((tool) => tool.function?.name);
3537
const toolResults = (body.messages || []).filter((message) => message.role === "tool");
3638
const lastTool = toolResults.at(-1);
@@ -45,19 +47,19 @@ const providerServer = createServer(async (req, res) => {
4547
return answer(res, "No action.");
4648
}
4749

48-
if (/authorized-complete/i.test(serialized)) {
50+
if (/authorized-complete/i.test(wake)) {
4951
if (lastTool?.name !== "run_command") return toolCall(res, "run_command", { command: "inspect-authorized-status" });
5052
return answer(res, "Completed — the background task is confirmed complete.");
5153
}
52-
if (/running-complete/i.test(serialized)) {
54+
if (/running-complete/i.test(wake)) {
5355
if (lastTool?.name !== "run_command") return toolCall(res, "run_command", { command: "inspect-completed-status" });
5456
return answer(res, "Completed — the retried task is confirmed complete.");
5557
}
56-
if (/running-first/i.test(serialized)) {
58+
if (/running-first/i.test(wake)) {
5759
if (lastTool?.name !== "run_command") return toolCall(res, "run_command", { command: "inspect-running-status" });
5860
return toolCall(res, "schedule_followup", { delay_seconds: 30, reason: "running-complete", check_hint: "inspect background status", observed_state: "confirmed_running" });
5961
}
60-
if (/unknown-no-capability/i.test(serialized)) {
62+
if (/unknown-no-capability/i.test(wake)) {
6163
if (!toolNames.includes("run_command")) return answer(res, "Blocked — task state is unknown because host run_command is unavailable for this wake.");
6264
if (lastTool?.name !== "run_command") return toolCall(res, "run_command", { command: "must-not-run-unauthorized" });
6365
if (lastTool.name === "run_command" && toolNames.includes("schedule_followup")) return toolCall(res, "schedule_followup", { user_update: { completed: "Initial setup is complete.", observed_state: "The process state was directly inspected.", wait_reason: "The running process needs more time.", next_check: "Inspect the process and resulting output." }, delay_seconds: 30, reason: "unknown-no-capability", observed_state: "confirmed_running" });

‎test/phase6-modules.mjs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import test from "node:test";
66
import { actionSummary, completedToolAnswer, toolActionStatus } from "../src/server/bot-output.ts";
77
import { body, clearRateLimit, jbody, json, rateLimited, SECURITY_HEADERS } from "../src/server/http.ts";
88
import {
9+
formatBoardFollowupCountdown,
910
formatRoughTokens,
1011
formatThreadFollowupCountdown,
1112
parseToolBody,
@@ -92,6 +93,7 @@ test("thread formatter extraction preserves progress, usage, and countdown edge
9293
assert.equal(threadUsageLabel({ input_tokens: 1_240, output_tokens: 340, cached_input_tokens: 900, model_calls: 3 }), "Spent 1.2k input (900 cached) · 340 output · 3 calls");
9394
const now = 1_000_000;
9495
assert.equal(formatThreadFollowupCountdown(now - 1, now), "now");
96+
assert.equal(formatBoardFollowupCountdown(now - 1, now), "due now");
9597
assert.equal(formatThreadFollowupCountdown(now + 9_000, now), "9s");
9698
assert.equal(formatThreadFollowupCountdown(now + 65_000, now), "1m 05s");
9799
assert.equal(formatThreadFollowupCountdown(now + 3_665_000, now), "1h 01m 05s");

0 commit comments

Comments
 (0)