function shellQuote(value) { return "'" + String(value).replace(/'/g, "'\\''") + "'"; } function serviceCommands(action) { var groups = action.scopes || [{ scope: "system", units: action.units || [] }, { scope: "user", units: action.units || [] }]; var commands = []; groups.forEach(function (group) { if (!group.units.length) return; var prefix = ["systemctl"].concat(group.scope === "user" ? ["--user"] : []); commands.push(prefix.concat(["--no-pager", "--full", "status", "--"], group.units)); }); return commands; } function eventCommand(action) { if (!/^[0-9a-f]{32}$/.test(action.bootId || "") || !/^[\x21-\x7e]{1,512}$/.test(action.cursor || "") || !isFinite(Date.parse(action.timestamp))) return null; return ["journalctl", "--boot=" + action.bootId, "--cursor=" + action.cursor, "--lines=+1", "--no-pager"]; } function eventContextCommand(action) { var time = Date.parse(action.timestamp); if (!/^[0-9a-f]{32}$/.test(action.bootId || "") || !isFinite(time)) return null; // PIDs can be reused. Pin the boot and a narrow time interval instead. return ["journalctl", "--boot=" + action.bootId, "--since=" + new Date(time - 60000).toISOString(), "--until=" + new Date(time + 60000).toISOString(), "--lines=100", "--no-pager"]; } function terminalCommands(commands) { return ["wezterm", "start", "--", "sh", "-c", commands.map(function (argv) { return argv.map(shellQuote).join(" "); }).join("; ") + '; exec "$SHELL"']; } function actionPlan(action) { var a = action || {}; var kind = String(a.kind || ""); if (kind === "btm") return { ok: true, kind: kind, argv: ["wezterm", "start", "--", "btm"] }; if (kind === "systemctl-status") return { ok: true, kind: kind, argv: terminalCommands(serviceCommands(a)) }; if (kind === "journal-event") { var command = eventCommand(a); if (command) return { ok: true, kind: kind, argv: terminalCommands([command, eventContextCommand(a)]) }; } if (kind === "open-mount") return { ok: true, kind: kind, argv: ["wezterm", "start", "--cwd", String(a.target || "/")] }; if (kind === "update") return { ok: true, kind: kind, argv: ["wezterm", "start", "--", "sh", "-c", 'sudo pacman -Syu; exec "$SHELL"'] }; return { ok: false, reason: "unknown-action", kind: kind }; } function issueAction(issues, id) { var issue = (issues || []).filter(function (entry) { return entry.id === String(id); })[0]; if (!issue || !issue.action) return { ok: false, reason: "no-action", id: String(id) }; var plan = actionPlan(issue.action); if (!plan.ok) return plan; return { ok: true, id: String(id), kind: plan.kind, argv: plan.argv }; } function agentAllowed(issue) { return !!issue && issue.id !== "updates" && issue.id !== "battery-health"; } function agentAction(issues, id, context, template) { var issue = (issues || []).filter(function (entry) { return entry.id === String(id); })[0]; if (!issue) return { ok: false, reason: "issue-gone", id: String(id) }; if (!agentAllowed(issue)) return { ok: false, reason: "agent-not-applicable", id: String(id) }; var ctx = context || {}; if (!ctx.home || String(ctx.home)[0] !== "/") return { ok: false, reason: "home-unavailable", id: String(id) }; var stateHome = ctx.stateHome && String(ctx.stateHome)[0] === "/" ? String(ctx.stateHome) : String(ctx.home).replace(/\/+$/, "") + "/.local/state"; var workingDirectory = stateHome.replace(/\/+$/, "") + "/nuguland/repairs"; var parts = String(template || "").split("{{context}}"); if (parts.length !== 2) return { ok: false, reason: "prompt-invalid", id: String(id) }; var snapshot = { observedAt: ctx.observedAt, machine: ctx.identity || {}, issue: { id: issue.id, type: issue.context ? issue.context.source : String(issue.id).split(":")[0], label: issue.label, detail: issue.detail, severity: issue.severity, historical: !!issue.context, measurements: issue.measurements || null, context: issue.context || null, occurrences: issue.context ? (issue.events || [issue.context]).length : null, events: issue.events || (issue.context ? [issue.context] : []) }, serviceScopes: issue.action && issue.action.scopes || [] }; // One literal substitution: injected metadata is never re-expanded. var prompt = parts[0] + JSON.stringify(snapshot, null, 2) + parts[1]; return { ok: true, id: String(id), kind: "agent", argv: ["wezterm", "start", "--cwd", workingDirectory, "--", "pi", "--name", "arrrgh: " + issue.label, "--", prompt] }; } function issueHeading(issue) { var metadata = issue.context && issue.context.metadata; if (metadata) return metadata.comm || metadata.executable || metadata.target || issue.label; if (issue.action && issue.action.kind === "systemctl-status") return issue.action.units.join(", "); return issue.label; } function issueDescription(issue) { var metadata = issue.context && issue.context.metadata; if (metadata) { var label = issue.events ? issue.events.length + (issue.events.length === 1 ? " crash" : " crashes") : issue.label; var signal = (metadata.signal || "").toLowerCase(); if (issue.events && issue.events.length > 1 && signal) signal = "latest " + signal; return [label, signal, metadata.pid ? "pid " + metadata.pid : ""].filter(Boolean).join(" · "); } if (issue.action && issue.action.kind === "systemctl-status") return issue.label + " · " + (issue.action.scopes || []).filter(function (group) { return group.units.length; }).map(function (group) { return group.scope; }).join(" / "); return issue.detail; } function eventTime(timestamp, now) { var event = new Date(timestamp); var current = new Date(now); if (!isFinite(event.getTime()) || !isFinite(current.getTime())) return ""; var minutes = Math.max(0, Math.floor((current - event) / 60000)); var ago = minutes < 1 ? "just now" : minutes < 60 ? minutes + " min ago" : Math.floor(minutes / 60) + " h ago"; var yesterday = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 1); var day = event.toDateString() === current.toDateString() ? "today" : event.toDateString() === yesterday.toDateString() ? "yesterday" : event.getDate() + "/" + (event.getMonth() + 1); function pad(n) { return n < 10 ? "0" + n : String(n); } return ago + " · " + day + ", " + pad(event.getHours()) + ":" + pad(event.getMinutes()); } if (typeof module !== 'undefined' && module.exports) { module.exports = { actionPlan: actionPlan, issueAction: issueAction, agentAction: agentAction, agentAllowed: agentAllowed, issueHeading: issueHeading, issueDescription: issueDescription, eventTime: eventTime }; }