portable-agent-layer 0.76.1 → 0.77.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.
- package/README.md +1 -1
- package/assets/skills/consulting-report/tools/generate-pdf.ts +2 -2
- package/package.json +1 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/setup-auto-update.ts +45 -0
- package/src/hooks/AutoUpdate.ts +16 -0
- package/src/hooks/LoadContext.ts +7 -0
- package/src/hooks/handlers/agenda.ts +1 -1
- package/src/hooks/handlers/update-check.ts +15 -16
- package/src/hooks/lib/auto-update.ts +184 -0
- package/src/hooks/lib/goal-links.ts +2 -2
- package/src/hooks/lib/security.ts +1 -1
- package/src/hooks/lib/settings.ts +2 -0
- package/src/tools/control-room/server.ts +26 -0
- package/src/tools/control-room/ui/dist/assets/{index-BoQjFpzV.js → index-ZQVmA4BJ.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/lib/write.ts +9 -1
- package/src/tools/control-room/ui/screens/settings.tsx +103 -1
- package/src/tools/control-room/writes.ts +11 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
rel="stylesheet"
|
|
11
11
|
href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600&family=Barlow+Condensed:wght@400;500;600&display=swap"
|
|
12
12
|
/>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-ZQVmA4BJ.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-Dncp2bYg.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The page's
|
|
2
|
+
* The page's write calls. Each POSTs one named field and hands back the
|
|
3
3
|
* server's own error text, so a refusal reads the same wherever it surfaces.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -41,3 +41,11 @@ export function setSnooze(project: string, id: number, days: number) {
|
|
|
41
41
|
export function setPrefs(update: Record<string, unknown>) {
|
|
42
42
|
return post("/api/prefs", update);
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
export function setAutoUpdate(enabled: boolean) {
|
|
46
|
+
return post("/api/update", { enabled });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function runUpdateNow() {
|
|
50
|
+
return post("/api/update/run", {});
|
|
51
|
+
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
AutoUpdateLedger,
|
|
4
|
+
AutoUpdateStatus,
|
|
5
|
+
} from "../../../../hooks/lib/auto-update";
|
|
2
6
|
import type { ControlRoomPrefs } from "../../prefs";
|
|
3
7
|
import type { ServerStatus } from "../../server";
|
|
4
8
|
import { Button } from "../components/button";
|
|
5
9
|
import { Input } from "../components/input";
|
|
6
10
|
import { Label } from "../components/label";
|
|
7
11
|
import { Switch } from "../components/switch";
|
|
12
|
+
import { clock } from "../format";
|
|
8
13
|
import { Empty, Panel, Pending } from "../frame";
|
|
9
14
|
import { useLoaded } from "../lib/api";
|
|
10
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
runUpdateNow,
|
|
17
|
+
setAutoUpdate,
|
|
18
|
+
setInstallSettings,
|
|
19
|
+
setPrefs as writePrefs,
|
|
20
|
+
} from "../lib/write";
|
|
11
21
|
|
|
12
22
|
interface InstallSettings {
|
|
13
23
|
actor: string;
|
|
@@ -82,6 +92,96 @@ function ThisInstall({ initial }: { initial: InstallSettings }) {
|
|
|
82
92
|
);
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
function latest(last: AutoUpdateLedger): string {
|
|
96
|
+
return last.finishedAt ?? last.attemptedAt ?? last.skippedAt ?? "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function lastRunLine(last: AutoUpdateLedger | null): string {
|
|
100
|
+
if (!last) return "never run";
|
|
101
|
+
const at = latest(last);
|
|
102
|
+
const when = at ? ` · ${clock(at)}` : "";
|
|
103
|
+
if (at && at === last.skippedAt) return `waited — ${last.skipped}${when}`;
|
|
104
|
+
if (last.finishedAt) {
|
|
105
|
+
return last.ok
|
|
106
|
+
? `updated ${last.from} → ${last.to}${when}`
|
|
107
|
+
: `failed — ${last.error ?? "unknown error"}${when}`;
|
|
108
|
+
}
|
|
109
|
+
return `running since ${clock(last.attemptedAt ?? "")}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function versionLine(status: AutoUpdateStatus): string {
|
|
113
|
+
if (status.available && status.latest) return `${status.current} → ${status.latest}`;
|
|
114
|
+
return `${status.current} · up to date`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function Updates({ initial }: { initial: AutoUpdateStatus }) {
|
|
118
|
+
const [status, setStatus] = useState(initial);
|
|
119
|
+
const [error, setError] = useState<string | null>(null);
|
|
120
|
+
const [running, setRunning] = useState(false);
|
|
121
|
+
|
|
122
|
+
const refresh = () => {
|
|
123
|
+
void fetch("/api/update")
|
|
124
|
+
.then((res) => res.json() as Promise<AutoUpdateStatus>)
|
|
125
|
+
.then(setStatus)
|
|
126
|
+
.catch(() => setError("could not read update status"));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const toggle = (enabled: boolean) => {
|
|
130
|
+
setStatus({ ...status, enabled });
|
|
131
|
+
void setAutoUpdate(enabled).then((failure) => {
|
|
132
|
+
setError(failure);
|
|
133
|
+
if (failure) setStatus({ ...status, enabled: !enabled });
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const now = () => {
|
|
138
|
+
setRunning(true);
|
|
139
|
+
void runUpdateNow().then((failure) => {
|
|
140
|
+
setError(failure);
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
setRunning(false);
|
|
143
|
+
refresh();
|
|
144
|
+
}, 4000);
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Panel title="Updates">
|
|
150
|
+
<div className="flex flex-col gap-4">
|
|
151
|
+
<div className="flex items-center justify-between gap-3 text-[12.5px]">
|
|
152
|
+
<span>update once a day, at session start</span>
|
|
153
|
+
<Switch
|
|
154
|
+
aria-label="update once a day"
|
|
155
|
+
checked={status.enabled}
|
|
156
|
+
onCheckedChange={toggle}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
<dl className="m-0 grid grid-cols-[110px_1fr] gap-y-1.5 text-[12.5px]">
|
|
160
|
+
<dt className="text-neutral-600">version</dt>
|
|
161
|
+
<dd className="m-0 tabular-nums">{versionLine(status)}</dd>
|
|
162
|
+
<dt className="text-neutral-600">last run</dt>
|
|
163
|
+
<dd className="m-0">{lastRunLine(status.last)}</dd>
|
|
164
|
+
</dl>
|
|
165
|
+
{error && (
|
|
166
|
+
<p className="border-l-2 border-alarm bg-alarm/10 px-3 py-2 text-[12px] text-alarm">
|
|
167
|
+
{error}
|
|
168
|
+
</p>
|
|
169
|
+
)}
|
|
170
|
+
<div className="flex items-center gap-3">
|
|
171
|
+
<Button variant="primary" disabled={running} onClick={now}>
|
|
172
|
+
{running ? "Updating…" : "Update now"}
|
|
173
|
+
</Button>
|
|
174
|
+
{status.mode === "repo" && (
|
|
175
|
+
<span className="text-[11px] text-neutral-600">
|
|
176
|
+
waits if this clone has uncommitted changes
|
|
177
|
+
</span>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</Panel>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
85
185
|
const ATTENTION_LABELS: Record<string, string> = {
|
|
86
186
|
refusals: "A hook blocked a call, or you denied one",
|
|
87
187
|
waiting: "A handoff left a question for you",
|
|
@@ -172,12 +272,14 @@ export function Settings() {
|
|
|
172
272
|
const settings = useLoaded<InstallSettings>("/api/settings");
|
|
173
273
|
const prefs = useLoaded<ControlRoomPrefs>("/api/prefs");
|
|
174
274
|
const status = useLoaded<ServerStatus>("/api/status");
|
|
275
|
+
const updates = useLoaded<AutoUpdateStatus>("/api/update");
|
|
175
276
|
|
|
176
277
|
if (settings.state !== "ready") return <Pending value={settings} />;
|
|
177
278
|
return (
|
|
178
279
|
<div className="grid items-start gap-6 md:grid-cols-2">
|
|
179
280
|
<ThisInstall initial={settings.data} />
|
|
180
281
|
{prefs.state === "ready" && <Ranking initial={prefs.data} />}
|
|
282
|
+
{updates.state === "ready" && <Updates initial={updates.data} />}
|
|
181
283
|
<Panel title="Where it runs">
|
|
182
284
|
{status.state === "ready" ? (
|
|
183
285
|
<dl className="m-0 grid grid-cols-[110px_1fr] gap-y-1.5 text-[12.5px]">
|
|
@@ -96,6 +96,17 @@ export function writeInstallSettings(update: Partial<InstallSettings>): WriteOut
|
|
|
96
96
|
return { ok: true, changed: true };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Flipping the switch here is itself a decision, so `decided` closes the
|
|
101
|
+
* one-time install question too — nobody is asked about a setting they just set.
|
|
102
|
+
*/
|
|
103
|
+
export function setAutoUpdate(enabled: boolean): WriteOutcome {
|
|
104
|
+
const data = rawSettings();
|
|
105
|
+
writeSettings({ ...data, autoUpdate: { ...data.autoUpdate, enabled, decided: true } });
|
|
106
|
+
reload();
|
|
107
|
+
return { ok: true, changed: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
99
110
|
function isTimezone(value: string): boolean {
|
|
100
111
|
try {
|
|
101
112
|
new Intl.DateTimeFormat("en-US", { timeZone: value });
|