meerkat-taskpanel 0.1.0 → 0.1.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -1
- package/docs/install.md +15 -6
- package/docs/scheduling.md +17 -8
- package/install.sh +3 -3
- package/package.json +1 -1
- package/scripts/install/lib/taskd-guard.mjs +31 -5
- package/scripts/install/templates/taskd/systemd/meerkat-taskpanel-taskd.service +1 -1
- package/src/foundation/constants.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The `taskd` keepalive guard now writes its launchd/systemd units under the install root
|
|
15
|
+
(`<home>/.meerkat-taskpanel/scheduling/`) instead of a stray `<home>/taskpanel`
|
|
16
|
+
directory beside it.
|
|
17
|
+
- The guard is no longer loaded when a board is already answering `/health` at the runtime
|
|
18
|
+
pointer's address. A second `taskd` on that port only restarted into `EADDRINUSE`; the
|
|
19
|
+
install now leaves the running board alone and reports what it found.
|
|
20
|
+
|
|
10
21
|
## [0.1.0] - 2026-10-27
|
|
11
22
|
|
|
12
23
|
First public release.
|
|
@@ -117,5 +128,6 @@ First public release.
|
|
|
117
128
|
- `issue get` shows the card title.
|
|
118
129
|
- The subscriber unit points at the event hub instead of a direct stream.
|
|
119
130
|
|
|
120
|
-
[
|
|
131
|
+
[0.1.1]: https://github.com/tanyu216/meerkat-taskpanel/releases/tag/v0.1.1
|
|
132
|
+
[Unreleased]: https://github.com/tanyu216/meerkat-taskpanel/compare/v0.1.1...HEAD
|
|
121
133
|
[0.1.0]: https://github.com/tanyu216/meerkat-taskpanel/releases/tag/v0.1.0
|
package/docs/install.md
CHANGED
|
@@ -344,12 +344,12 @@ a daemon for you (the [Scheduling units](#scheduling-units-claude-codex-pi) abov
|
|
|
344
344
|
applies **only to the `taskd` service guard**: the `meerkat-loop` supervisor's units are
|
|
345
345
|
still written and left for you to load, and OpenClaw still gets none.
|
|
346
346
|
|
|
347
|
-
**Where it lands** —
|
|
347
|
+
**Where it lands** — under the install root, deliberately *not* the loop units'
|
|
348
348
|
`<agent-dir>/scheduling/`, so re-installing an agent kind's loop units never touches it:
|
|
349
349
|
|
|
350
350
|
```text
|
|
351
|
-
<prefix
|
|
352
|
-
<prefix
|
|
351
|
+
<prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
352
|
+
<prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service
|
|
353
353
|
```
|
|
354
354
|
|
|
355
355
|
`<prefix>` is `--prefix` (default `$HOME`). There is **one guard per machine** — and
|
|
@@ -362,10 +362,10 @@ separate flag:
|
|
|
362
362
|
|
|
363
363
|
```bash
|
|
364
364
|
# macOS
|
|
365
|
-
launchctl bootstrap gui/$UID <prefix
|
|
365
|
+
launchctl bootstrap gui/$UID <prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
366
366
|
# Linux (user unit)
|
|
367
367
|
mkdir -p ~/.config/systemd/user
|
|
368
|
-
cp <prefix
|
|
368
|
+
cp <prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service ~/.config/systemd/user/
|
|
369
369
|
systemctl --user enable --now meerkat-taskpanel-taskd.service
|
|
370
370
|
```
|
|
371
371
|
|
|
@@ -383,6 +383,15 @@ and names the log to read:
|
|
|
383
383
|
taskd guard: loaded (com.meerkat-taskpanel.taskd, pid …); /health did not answer within 10000ms — read <dataDir>/logs/taskd.log
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
+
A board that is **already serving** — the runtime pointer's address answers `/health`
|
|
387
|
+
before the guard is loaded — is left alone. Loading a second `taskd` on that port is not a
|
|
388
|
+
keepalive; it is an `EADDRINUSE` restart loop. The units are still written, nothing is
|
|
389
|
+
loaded, and the install says:
|
|
390
|
+
|
|
391
|
+
```text
|
|
392
|
+
taskd guard: not loaded — a board is already serving http://127.0.0.1:9527 (pid …); stop it and re-run to install the guard
|
|
393
|
+
```
|
|
394
|
+
|
|
386
395
|
Neither line fails the install. The unit names the **absolute** node that ran the install,
|
|
387
396
|
so a node that lives only under nvm or Homebrew still starts the server — see
|
|
388
397
|
[`scheduling.md § The taskd guard (keepalive)`](scheduling.md#the-taskd-guard-keepalive).
|
|
@@ -428,7 +437,7 @@ plain `kill` is not enough — use the supervisor's own stop:
|
|
|
428
437
|
```bash
|
|
429
438
|
# macOS
|
|
430
439
|
launchctl bootout gui/$UID/com.meerkat-taskpanel.taskd
|
|
431
|
-
rm <prefix
|
|
440
|
+
rm <prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
432
441
|
# Linux
|
|
433
442
|
systemctl --user disable --now meerkat-taskpanel-taskd.service
|
|
434
443
|
rm ~/.config/systemd/user/meerkat-taskpanel-taskd.service
|
package/docs/scheduling.md
CHANGED
|
@@ -844,12 +844,12 @@ write lock (R1: one `taskd` per data directory).
|
|
|
844
844
|
#### Where the unit lands (ownership separation)
|
|
845
845
|
|
|
846
846
|
```text
|
|
847
|
-
<prefix
|
|
848
|
-
<prefix
|
|
847
|
+
<prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
848
|
+
<prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service
|
|
849
849
|
```
|
|
850
850
|
|
|
851
|
-
|
|
852
|
-
deliberately **not** `<agent-dir>/scheduling/` where the loop's `ping`/`continue` units
|
|
851
|
+
The install root (`<prefix>/.meerkat-taskpanel/`; `<prefix>` is `install.sh --prefix`,
|
|
852
|
+
default `$HOME`), deliberately **not** `<agent-dir>/scheduling/` where the loop's `ping`/`continue` units
|
|
853
853
|
live ([Scheduling units](install.md#scheduling-units-claude-codex-pi)). Re-installing a
|
|
854
854
|
agent kind's loop units never touches this directory, and the two are never confused for one
|
|
855
855
|
another.
|
|
@@ -980,7 +980,7 @@ nothing.
|
|
|
980
980
|
# ~/.config/systemd/user/ and enables it immediately.
|
|
981
981
|
#
|
|
982
982
|
# mkdir -p ~/.config/systemd/user
|
|
983
|
-
# cp <prefix
|
|
983
|
+
# cp <prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service ~/.config/systemd/user/
|
|
984
984
|
# systemctl --user enable --now meerkat-taskpanel-taskd.service
|
|
985
985
|
#
|
|
986
986
|
# ExecStart names the ABSOLUTE node that rendered this unit — never
|
|
@@ -1039,11 +1039,11 @@ user switch**. `install.sh` writes the unit and then loads/enables it:
|
|
|
1039
1039
|
|
|
1040
1040
|
```bash
|
|
1041
1041
|
# macOS
|
|
1042
|
-
launchctl bootstrap gui/$UID <prefix
|
|
1042
|
+
launchctl bootstrap gui/$UID <prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
1043
1043
|
# (older systems: launchctl load -w <plist>)
|
|
1044
1044
|
# Linux (user unit)
|
|
1045
1045
|
mkdir -p ~/.config/systemd/user
|
|
1046
|
-
cp <prefix
|
|
1046
|
+
cp <prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service ~/.config/systemd/user/
|
|
1047
1047
|
systemctl --user enable --now meerkat-taskpanel-taskd.service
|
|
1048
1048
|
```
|
|
1049
1049
|
|
|
@@ -1078,6 +1078,15 @@ taskd guard: loaded (com.meerkat-taskpanel.taskd, pid 17388); /health did not an
|
|
|
1078
1078
|
Neither line fails the install (the agent-kind skills are installed either way), but only the
|
|
1079
1079
|
first one claims the guard works.
|
|
1080
1080
|
|
|
1081
|
+
A board that is **already answering** at the runtime pointer's address is a third case, and
|
|
1082
|
+
it is not loaded over: a second `taskd` on the same port is an `EADDRINUSE` restart loop, not
|
|
1083
|
+
a keepalive. The units are written, the load is skipped, and the report names the running
|
|
1084
|
+
board:
|
|
1085
|
+
|
|
1086
|
+
```text
|
|
1087
|
+
taskd guard: not loaded — a board is already serving http://127.0.0.1:9527 (pid …); stop it and re-run to install the guard
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1081
1090
|
A **second board** is where the install has to be careful, because the guard is one per
|
|
1082
1091
|
machine ([One guard per machine](#one-guard-per-machine)): on Linux the file systemd reads
|
|
1083
1092
|
`~/.config/systemd/user/meerkat-taskpanel-taskd.service`, and that file *is* the machine's
|
|
@@ -1112,7 +1121,7 @@ To stop and remove the guard:
|
|
|
1112
1121
|
```bash
|
|
1113
1122
|
# macOS
|
|
1114
1123
|
launchctl bootout gui/$UID/com.meerkat-taskpanel.taskd
|
|
1115
|
-
rm <prefix
|
|
1124
|
+
rm <prefix>/.meerkat-taskpanel/scheduling/launchd/com.meerkat-taskpanel.taskd.plist
|
|
1116
1125
|
# Linux
|
|
1117
1126
|
systemctl --user disable --now meerkat-taskpanel-taskd.service
|
|
1118
1127
|
rm ~/.config/systemd/user/meerkat-taskpanel-taskd.service
|
package/install.sh
CHANGED
|
@@ -206,7 +206,7 @@ Environment (set for each agent-kind installer):
|
|
|
206
206
|
(internal — for tests, not a user option)
|
|
207
207
|
|
|
208
208
|
The taskd keepalive guard is written and loaded on every install (one unit per
|
|
209
|
-
machine, at <prefix
|
|
209
|
+
machine, at <prefix>/.meerkat-taskpanel/scheduling/) — see docs/scheduling.md. It has no
|
|
210
210
|
user flag and no prompt; the bypasses are --no-automation, --dry-run, and the
|
|
211
211
|
test-only MEERKAT_TASKPANEL_SKIP_TASKD_SERVICE above.
|
|
212
212
|
EOF
|
|
@@ -219,8 +219,8 @@ die() {
|
|
|
219
219
|
|
|
220
220
|
# install_taskd_guard — write the `taskd` keepalive unit and load it (docs/scheduling.md).
|
|
221
221
|
#
|
|
222
|
-
# One unit per **machine**, not per agent kind: it is written once here,
|
|
223
|
-
# `<prefix
|
|
222
|
+
# One unit per **machine**, not per agent kind: it is written once here, under the install
|
|
223
|
+
# root `<prefix>/.meerkat-taskpanel/scheduling/`, and never touched by an agent kind's own
|
|
224
224
|
# scheduling install. It is also the one unit this installer *loads* rather than
|
|
225
225
|
# only printing a command for — a deliberate, narrow exception to "write the unit,
|
|
226
226
|
# never start a daemon for you". The loop supervisor's units keep that rule.
|
package/package.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* The `taskd` OS-level keepalive unit — "the guard" (`docs/scheduling.md`).
|
|
4
4
|
*
|
|
5
5
|
* `install.sh` calls this once per install, because the guard is **one per
|
|
6
|
-
* machine**, not one per agent kind: it writes both unit templates
|
|
7
|
-
* `<prefix
|
|
6
|
+
* machine**, not one per agent kind: it writes both unit templates under the install root
|
|
7
|
+
* `<prefix>/.meerkat-taskpanel/scheduling/` (deliberately *not* the loop units'
|
|
8
8
|
* `<agent-dir>/scheduling/`, so re-installing an agent kind never touches it) and then
|
|
9
9
|
* loads the one for this platform. It is the single **explicit exception** to
|
|
10
10
|
* the installer's rule that it writes a unit and prints the load command but
|
|
@@ -50,6 +50,12 @@
|
|
|
50
50
|
* exactly the readiness test the CLI applies. A unit that is loaded but not
|
|
51
51
|
* serving reports `loaded … /health did not answer` instead, with the log to read.
|
|
52
52
|
*
|
|
53
|
+
* **A board that is already running is not loaded over.** Before loading, the address the
|
|
54
|
+
* runtime pointer names is knocked once. A `/health` answer means a second `taskd` would
|
|
55
|
+
* only collide on that port (`EADDRINUSE`, a keepalive restart loop), so the units are
|
|
56
|
+
* written and the load is skipped, with the running board named. The same rule applies to
|
|
57
|
+
* the guard's own board lazily started by a CLI call: stop it and re-run to hand it over.
|
|
58
|
+
*
|
|
53
59
|
* **The guard is one per machine, so a second install may have to refuse**
|
|
54
60
|
* (`docs/scheduling.md § One guard per machine`). Because the label and the unit name
|
|
55
61
|
* carry no board segment, the machine has exactly one guard job, and on Linux the file
|
|
@@ -175,8 +181,8 @@ export function guardPaths(options = {}) {
|
|
|
175
181
|
nodeDir: dirname(node),
|
|
176
182
|
serverEntry: join(root, "src", "server", "main.mjs"),
|
|
177
183
|
logFile: join(dataDir, LOGS_DIRNAME, LOG_FILENAME),
|
|
178
|
-
launchdUnit: join(prefix, "taskpanel", "scheduling", "launchd", `${TASKD_LABEL}.plist`),
|
|
179
|
-
systemdUnit: join(prefix, "taskpanel", "scheduling", "systemd", TASKD_UNIT),
|
|
184
|
+
launchdUnit: join(prefix, ".meerkat-taskpanel", "scheduling", "launchd", `${TASKD_LABEL}.plist`),
|
|
185
|
+
systemdUnit: join(prefix, ".meerkat-taskpanel", "scheduling", "systemd", TASKD_UNIT),
|
|
180
186
|
pointerFile: resolveRuntimePointerPath({ env }),
|
|
181
187
|
};
|
|
182
188
|
}
|
|
@@ -531,6 +537,26 @@ export async function installGuard(options = {}) {
|
|
|
531
537
|
const already = runStep(plan.loadedProbe, run);
|
|
532
538
|
if (already.ok) report.pid = waitForPid(plan, run, pidWaitMs);
|
|
533
539
|
|
|
540
|
+
// A board that is **already answering** owns the address this unit would bind. Loading
|
|
541
|
+
// the guard then does not create a keepalive — it starts a second server on an occupied
|
|
542
|
+
// port, which is an EADDRINUSE restart loop, not a board. The runtime pointer is the
|
|
543
|
+
// running board's own statement of its address, and `/health` is the same readiness test
|
|
544
|
+
// the CLI trusts, so one knock settles it. The unit files stay written (the operator can
|
|
545
|
+
// load them after stopping the board); nothing is loaded and nothing is claimed.
|
|
546
|
+
if (!already.ok) {
|
|
547
|
+
const running = (options.readPointerFn ?? readPointer)({ path: paths.pointerFile });
|
|
548
|
+
const runningUrl = running !== null && typeof running.url === "string" ? running.url : null;
|
|
549
|
+
if (runningUrl !== null && (await (options.healthProbe ?? probeHealth)(runningUrl))) {
|
|
550
|
+
report.state = "running";
|
|
551
|
+
report.url = runningUrl;
|
|
552
|
+
report.reason = `a board is already serving ${runningUrl}`;
|
|
553
|
+
log(
|
|
554
|
+
`taskd guard: not loaded — a board is already serving ${runningUrl}${running.pid ? ` (pid ${running.pid})` : ""}; stop it and re-run to install the guard`,
|
|
555
|
+
);
|
|
556
|
+
return report;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
534
560
|
// The board this run would install, read back from the file that would be copied —
|
|
535
561
|
// what actually lands, not a re-derivation of it.
|
|
536
562
|
const mine = unitBoard(readFileSync(kind === "launchd" ? paths.launchdUnit : paths.systemdUnit, "utf8"));
|
|
@@ -805,7 +831,7 @@ function waitForPid(plan, run, budgetMs) {
|
|
|
805
831
|
const USAGE = `Usage: node scripts/install/lib/taskd-guard.mjs install --prefix <dir> [--root <tree>] [--data-dir <dir>] [--force]
|
|
806
832
|
|
|
807
833
|
Writes the taskd keepalive unit (launchd + systemd) under
|
|
808
|
-
<dir
|
|
834
|
+
<dir>/.meerkat-taskpanel/scheduling/, then loads this platform's one, waits for its
|
|
809
835
|
board to answer /health, and reports what happened.
|
|
810
836
|
--root names the program tree the unit runs (default: this checkout);
|
|
811
837
|
--data-dir pins TASKD_DATA_DIR in the unit (used by --target server).
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# ~/.config/systemd/user/ and enables it immediately.
|
|
11
11
|
#
|
|
12
12
|
# mkdir -p ~/.config/systemd/user
|
|
13
|
-
# cp <prefix
|
|
13
|
+
# cp <prefix>/.meerkat-taskpanel/scheduling/systemd/meerkat-taskpanel-taskd.service ~/.config/systemd/user/
|
|
14
14
|
# systemctl --user enable --now meerkat-taskpanel-taskd.service
|
|
15
15
|
#
|
|
16
16
|
# ExecStart names the ABSOLUTE node that rendered this unit — never
|