claude-slack-channel-bots 0.6.7 → 0.7.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 +74 -9
- package/package.json +5 -3
- package/skills/install-cscb/SKILL.md +209 -0
- package/src/agent-director-client.ts +42 -67
- package/src/agent-director-errors.ts +6 -6
- package/src/agent-director-startup.ts +59 -132
- package/src/install-check.ts +268 -0
- package/src/install-skill-pointer.ts +123 -0
- package/src/postinstall.ts +11 -9
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ See the sections below for manual configuration details if you prefer not to use
|
|
|
38
38
|
|
|
39
39
|
- [Bun](https://bun.sh) `>= 1.0.21` (agent-director minimum)
|
|
40
40
|
- [Claude Code](https://claude.ai/code) installed and authenticated
|
|
41
|
-
- [`agent-director`](https://github.com/gabemahoney/agent-director) **
|
|
41
|
+
- [`agent-director`](https://github.com/gabemahoney/agent-director) **installed system-wide** as a prerequisite — like `git` or `docker`. CSCB no longer vendors the AD binary. The npm `agent-director` package CSCB depends on is now a thin TypeScript shim that locates the system-installed binary at startup via `resolveSystemBinary()` / `Client.create()` and refuses to start when the binary is missing, too old, or unreachable. The startup gate enforces AD's required version (declared by AD in `dist/version-floor.json`) and reports the required version on mismatch. agent-director itself requires [tmux](https://github.com/tmux/tmux) on the operator's PATH; CSCB no longer probes for it directly.
|
|
42
42
|
- Slack workspace admin access (to create and configure the Slack app)
|
|
43
43
|
- **cozempic** (optional) — Python 3.10+ and `pip install cozempic` — used by JSONL path resolution helpers retained for downstream callers.
|
|
44
44
|
|
|
@@ -52,9 +52,74 @@ See the sections below for manual configuration details if you prefer not to use
|
|
|
52
52
|
| `darwin-x64` (Intel Mac) | **Not supported** by agent-director |
|
|
53
53
|
| Windows | **Not supported** by agent-director |
|
|
54
54
|
|
|
55
|
-
If the host is unsupported, the
|
|
55
|
+
If the host is unsupported, the system-installed `agent-director` itself will refuse to install or run; CSCB's startup gate then exits non-zero with one of the `ad-system-install-*` class labels (see [Startup errors](#startup-errors)) and writes the failure to `~/.claude/channels/slack/startup-errors.log` and stderr. Consult [agent-director's documentation](https://github.com/gabemahoney/agent-director) for the canonical platform support list.
|
|
56
56
|
|
|
57
|
-
> **Note on agent-director versions.** v0.4.1 is a zombie release (the published tarball is missing `dist/` and cannot be imported). v0.4.2 lacks the `MakeTemplateParams.overwrite` field CSCB needs for the boot-time template refresh. v0.5.4 and earlier lack `allow_pending` on `readPane`/`sendKeys`, causing `ErrSpawnNotInteractive` during dev-channels dialog approval on freshly-spawned bots. v0.6.0 shipped a stale TS shim whose `Client` dropped `getPermission`, whose `buildDecide()` dropped `--request-token`, and whose error catalog omitted `ErrInvalidFlags` / `ErrPermissionRequestNotFound` / `ErrAmbiguousRequest` — each silently breaks the disambiguation relay. v0.6.1–0.6.2 still lack the full `permission_requests` plural projection + composite-key disambiguation surface CSCB depends on for concurrent open requests. CSCB
|
|
57
|
+
> **Note on agent-director versions.** v0.4.1 is a zombie release (the published tarball is missing `dist/` and cannot be imported). v0.4.2 lacks the `MakeTemplateParams.overwrite` field CSCB needs for the boot-time template refresh. v0.5.4 and earlier lack `allow_pending` on `readPane`/`sendKeys`, causing `ErrSpawnNotInteractive` during dev-channels dialog approval on freshly-spawned bots. v0.6.0 shipped a stale TS shim whose `Client` dropped `getPermission`, whose `buildDecide()` dropped `--request-token`, and whose error catalog omitted `ErrInvalidFlags` / `ErrPermissionRequestNotFound` / `ErrAmbiguousRequest` — each silently breaks the disambiguation relay. v0.6.1–0.6.2 still lack the full `permission_requests` plural projection + composite-key disambiguation surface CSCB depends on for concurrent open requests. From v0.7.0 onward, AD ships as a thin npm shim around a system-installed binary, and CSCB defers the minimum-AD-version decision to AD itself via `dist/version-floor.json` (AD's library-side `Client.create()` reads it). CSCB's `package.json` caret-pin on `agent-director` governs npm resolution of AD's TypeScript shim only — not the runtime floor, which is owned by the AD release the system binary belongs to.
|
|
58
|
+
|
|
59
|
+
### Checking your agent-director install
|
|
60
|
+
|
|
61
|
+
Before starting the server, you can confirm `agent-director` is installed system-wide and meets the declared minimum with:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bun run install-check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The script calls the same discovery + floor-comparison pipeline the startup gate uses (it reads AD's `dist/version-floor.json`, calls `resolveSystemBinary()`, and compares versions via `semver.gte`). It exits 0 on success with a single-line block naming `agent-director`, the absolute resolved binary path, the detected version, and the floor. On failure it writes one of the canonical class labels to stderr and exits non-zero:
|
|
68
|
+
|
|
69
|
+
- `ad-system-install-not-found` — no agent-director on PATH or at the standard install path. Install AD and retry. The stderr also points at the install-cscb skill for interactive remediation.
|
|
70
|
+
- `ad-system-install-too-old` — AD binary is below the floor. Upgrade AD and retry. The stderr points at the skill.
|
|
71
|
+
- `ad-system-install-unreachable` — AD discovered but the probe could not invoke it (eight `.reason` values exposed verbatim). The stderr points at the skill.
|
|
72
|
+
- `ad-version-floor-unreadable` — `dist/version-floor.json` is missing, malformed, or lacks `min_binary_version`. The remediation is to reinstall `agent-director` from npm; the install-cscb skill cannot fix a corrupt AD package, so this case does NOT append the skill instructions block.
|
|
73
|
+
|
|
74
|
+
The script is purely diagnostic — it never prompts, never runs an install command, never fetches the skill. The startup gate enforces the same floor automatically at server boot via AD's `Client.create()`; `install-check` is for operators who want to confirm their setup ahead of time.
|
|
75
|
+
|
|
76
|
+
### Installing the install-cscb skill
|
|
77
|
+
|
|
78
|
+
If `bun run install-check` (or the startup gate) reports one of the
|
|
79
|
+
`ad-system-install-*` failure classes, you can install the `install-cscb`
|
|
80
|
+
Claude skill for an interactive walkthrough. The skill drives the same
|
|
81
|
+
shared check module but walks you through install/upgrade and a
|
|
82
|
+
per-reason remediation flow for each of the eight
|
|
83
|
+
`ErrSystemInstallUnreachable.reason` values.
|
|
84
|
+
|
|
85
|
+
The skill is NOT auto-installed by `bun install` — fetch it manually
|
|
86
|
+
from CSCB's GitHub repo and place it in your local Claude skills
|
|
87
|
+
folder:
|
|
88
|
+
|
|
89
|
+
1. **Fetch** `SKILL.md` from:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
https://github.com/gabemahoney/claude-slack-channel-bots/blob/main/skills/install-cscb/SKILL.md
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
(The startup gate's `ad-system-install-*` error log line includes
|
|
96
|
+
this URL automatically.)
|
|
97
|
+
|
|
98
|
+
2. **Place** it at:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
~/.claude/skills/install-cscb/SKILL.md
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. **Invoke** the skill from Claude Code:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
/install-cscb
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The skill calls `bun run install-check` on each iteration, surfaces
|
|
111
|
+
`agent-director`'s published install/upgrade command verbatim (no
|
|
112
|
+
CSCB-owned install command — AD's documentation is the source of
|
|
113
|
+
truth), prompts before running, and loops until the check passes or
|
|
114
|
+
you decline. The `ad-version-floor-unreadable` class is handled
|
|
115
|
+
separately: the skill prints reinstall-from-npm guidance and does NOT
|
|
116
|
+
loop on it (the skill cannot fix a corrupt AD npm package).
|
|
117
|
+
|
|
118
|
+
The published CSCB npm tarball includes `skills/install-cscb/SKILL.md`
|
|
119
|
+
under its `files` array, so the skill source is also available via
|
|
120
|
+
`node_modules/claude-slack-channel-bots/skills/install-cscb/SKILL.md`
|
|
121
|
+
after `bun install`. The manual GitHub-fetch step above is for users
|
|
122
|
+
who haven't yet installed CSCB at all.
|
|
58
123
|
|
|
59
124
|
---
|
|
60
125
|
|
|
@@ -433,15 +498,15 @@ CSCB writes fatal startup errors to `~/.claude/channels/slack/startup-errors.log
|
|
|
433
498
|
|
|
434
499
|
Classes you may see:
|
|
435
500
|
|
|
436
|
-
- `ad-
|
|
437
|
-
- `ad-
|
|
501
|
+
- `ad-system-install-not-found` — `Client.create()` could not locate an `agent-director` binary on PATH or at the standard install path. Install agent-director system-wide and retry. The log line appends a manual-skill-install instructions block pointing at `skills/install-cscb/SKILL.md` (URL, target path under `~/.claude/skills/`, and invocation command `/install-cscb`) for the interactive install/upgrade flow.
|
|
502
|
+
- `ad-system-install-too-old` — the system-installed agent-director binary is below the floor declared in `dist/version-floor.json`. The log line names the detected and required versions, and appends the manual-skill-install instructions block.
|
|
503
|
+
- `ad-system-install-unreachable` — agent-director was discovered but the probe could not execute it (e.g. permission bits, broken symlink, runtime crash). The log line surfaces AD's supplied `err.reason` value verbatim (one of `not-executable`, `not-a-regular-file`, `probe-timeout`, `probe-nonzero-exit`, `probe-killed-by-signal`, `unparseable-version`, `spawn-failed`, `other`) and appends the manual-skill-install instructions block.
|
|
438
504
|
- `ad-bun-version-too-old` — agent-director needs Bun `>= 1.0.21`. Upgrade Bun.
|
|
439
|
-
- `ad-cli-not-executable` — the resolved agent-director CLI binary exists but is not executable. Run `chmod +x` on the binary referenced in the log line.
|
|
440
505
|
- `ad-version-probe` — `agent-director` was loaded but the `version()` probe failed (subprocess invocation, platform binary, etc.).
|
|
441
|
-
- `ad-
|
|
442
|
-
- `ad-shim-missing-get-permission` — the installed `agent-director` TS shim's `Client` does not expose `getPermission`. The npm-published package is out of sync with its bundled binary. Reinstall and confirm the resolved version actually ships the method.
|
|
506
|
+
- `ad-shim-missing-get-permission` — the installed `agent-director` TS shim's `Client` does not expose `getPermission`. The npm-published package is out of sync with the system-installed binary. Reinstall a matching `agent-director` version and confirm the resolved package actually ships the method.
|
|
443
507
|
- `ad-shim-catalog-incomplete` — the installed `agent-director` TS error catalog is missing one or more of `ErrInvalidFlags`, `ErrPermissionRequestNotFound`, `ErrAmbiguousRequest`. The log line lists the missing names. Same remediation as `ad-shim-missing-get-permission`.
|
|
444
|
-
- `ad-shim-decide-drops-token` — the installed `agent-director` dist does not include `--request-token` in its bundled JS, meaning `buildDecide()` would resolve permission clicks against the wrong row. Reinstall and confirm `buildDecide` carries the flag.
|
|
508
|
+
- `ad-shim-decide-drops-token` — the installed `agent-director` dist does not include `--request-token` in its bundled JS, meaning `buildDecide()` would resolve permission clicks against the wrong row. Reinstall a matching `agent-director` version and confirm `buildDecide` carries the flag.
|
|
509
|
+
- `ad-version-floor-unreadable` — `node_modules/agent-director/dist/version-floor.json` could not be read, parsed, or is missing `.min_binary_version`. This is a packaging defect — reinstall `agent-director` from npm. Surfaced by `bun run install-check`; the startup gate itself does not emit this label (it relies on `Client.create()`, which fails differently when the AD package is corrupt).
|
|
445
510
|
- `ad-call-timeout` — an agent-director verb call exceeded the configured `callTimeoutMs` (default 30 s). Investigate the subprocess or increase the timeout.
|
|
446
511
|
- `ad-same-user` — `~/.agent-director/state.db` is owned by a different UID than the CSCB process. Reinstall agent-director as the correct user or remove the mismatched file.
|
|
447
512
|
- `ad-same-user-stat` — Non-ENOENT stat error on the state DB (permissions, I/O). Investigate the file before re-launching.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-slack-channel-bots",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Multi-session Slack-to-Claude bridge — run multiple Claude Code bots across Slack channels via Socket Mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"start": "bun install --no-summary && bun src/server.ts",
|
|
23
23
|
"pretest": "bun scripts/fixup-bun-cache.ts && bun scripts/check-no-toplevel-mock-module.ts",
|
|
24
24
|
"check:mocks": "bun scripts/check-no-toplevel-mock-module.ts",
|
|
25
|
+
"install-check": "bun scripts/install-check.ts",
|
|
25
26
|
"test": "bun test",
|
|
26
27
|
"typecheck": "tsc --noEmit"
|
|
27
28
|
},
|
|
@@ -29,11 +30,12 @@
|
|
|
29
30
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
31
|
"@slack/socket-mode": "^2.0.0",
|
|
31
32
|
"@slack/web-api": "^7.0.0",
|
|
32
|
-
"agent-director": "^0.
|
|
33
|
+
"agent-director": "^0.7.0",
|
|
34
|
+
"semver": "^7.6.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/bun": "^1.0.0",
|
|
36
|
-
"semver": "^7.
|
|
38
|
+
"@types/semver": "^7.7.1",
|
|
37
39
|
"typescript": "^5.4.0"
|
|
38
40
|
},
|
|
39
41
|
"license": "MIT",
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: install-cscb
|
|
3
|
+
description: Interactive walkthrough that installs or upgrades the system-installed `agent-director` so claude-slack-channel-bots can boot. Drives Epic 2's bun run install-check and per-reason remediation.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Gabe Mahoney
|
|
6
|
+
license: MIT
|
|
7
|
+
user-invocable: true
|
|
8
|
+
argument-hint: "(no arguments)"
|
|
9
|
+
allowed-tools: [Bash, Read]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /install-cscb
|
|
13
|
+
|
|
14
|
+
Diagnose and fix a broken or missing `agent-director` system install so
|
|
15
|
+
`claude-slack-channel-bots` (CSCB) can boot. This skill is the interactive
|
|
16
|
+
counterpart to the diagnostic `bun run install-check` script — same shared
|
|
17
|
+
check module, but with a guided remediation loop for each failure class.
|
|
18
|
+
|
|
19
|
+
## When to invoke
|
|
20
|
+
|
|
21
|
+
Invoke this skill when CSCB's startup gate emits one of these class labels
|
|
22
|
+
in `~/.claude/channels/slack/startup-errors.log`:
|
|
23
|
+
|
|
24
|
+
- `ad-system-install-not-found`
|
|
25
|
+
- `ad-system-install-too-old`
|
|
26
|
+
- `ad-system-install-unreachable`
|
|
27
|
+
|
|
28
|
+
The gate appends a pointer to this skill on those three classes. Other
|
|
29
|
+
failure classes (`ad-bun-version-too-old`, `ad-shim-*`, `ad-same-user`,
|
|
30
|
+
`ad-version-floor-unreadable`) are NOT remediated by this skill — see
|
|
31
|
+
the README's Startup-errors section for those.
|
|
32
|
+
|
|
33
|
+
## Step 1 — Run the shared check
|
|
34
|
+
|
|
35
|
+
From the CSCB project root, run:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
bun run install-check
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This is the same command you can run directly from the shell. It calls
|
|
42
|
+
the shared check module (`src/install-check.ts`) which is also what this
|
|
43
|
+
skill drives internally on every iteration.
|
|
44
|
+
|
|
45
|
+
Read the output carefully:
|
|
46
|
+
|
|
47
|
+
- **Exit 0 + "OK"**: agent-director is satisfied. Print the resolved
|
|
48
|
+
binary path, detected version, and floor from the success output, then
|
|
49
|
+
exit cleanly. No further action required.
|
|
50
|
+
|
|
51
|
+
- **Exit non-zero**: identify the class label on stderr (one of
|
|
52
|
+
`ad-system-install-not-found`, `ad-system-install-too-old`,
|
|
53
|
+
`ad-system-install-unreachable`, `ad-version-floor-unreadable`) and
|
|
54
|
+
branch on it as documented below.
|
|
55
|
+
|
|
56
|
+
## Step 2 — Identify the user's platform
|
|
57
|
+
|
|
58
|
+
Detect the platform via `uname -sm`. CSCB supports two platforms:
|
|
59
|
+
|
|
60
|
+
- **linux-x64** — `Linux x86_64`
|
|
61
|
+
- **darwin-arm64** — `Darwin arm64` (Apple Silicon Mac)
|
|
62
|
+
|
|
63
|
+
Any other platform is unsupported by `agent-director` itself and the
|
|
64
|
+
skill cannot proceed; surface the platform mismatch to the user and
|
|
65
|
+
exit non-zero.
|
|
66
|
+
|
|
67
|
+
## Step 3 — Branch on the class label
|
|
68
|
+
|
|
69
|
+
### `ad-system-install-not-found` — agent-director missing
|
|
70
|
+
|
|
71
|
+
Surface the install command published by `agent-director` verbatim for
|
|
72
|
+
the detected platform. Do NOT invent or maintain a CSCB-owned command —
|
|
73
|
+
consult agent-director's documentation
|
|
74
|
+
(<https://github.com/gabemahoney/agent-director>) for the canonical
|
|
75
|
+
install procedure. The current installer is `install.sh` from
|
|
76
|
+
agent-director's repo; recommend running it.
|
|
77
|
+
|
|
78
|
+
Prompt the user (using AskUserQuestion or an equivalent) — the literal
|
|
79
|
+
command must be visible:
|
|
80
|
+
|
|
81
|
+
> agent-director is not installed system-wide. Run the AD-published install
|
|
82
|
+
> command? `<command>`
|
|
83
|
+
>
|
|
84
|
+
> - **Yes** — run the command via Bash and continue.
|
|
85
|
+
> - **No** — install manually then continue.
|
|
86
|
+
|
|
87
|
+
- **Yes**: run the command via Bash. Capture stdout/stderr for the user
|
|
88
|
+
to see. Return to Step 1.
|
|
89
|
+
- **No**: instruct the user to install manually. Wait for confirmation.
|
|
90
|
+
Return to Step 1.
|
|
91
|
+
|
|
92
|
+
If the user declines to proceed at any point, exit non-zero with a
|
|
93
|
+
one-line summary: "Aborted by user — agent-director still not installed."
|
|
94
|
+
|
|
95
|
+
### `ad-system-install-too-old` — agent-director below floor
|
|
96
|
+
|
|
97
|
+
Same flow as `ad-system-install-not-found` but with the upgrade command
|
|
98
|
+
instead of the install command. Surface the detected version and the
|
|
99
|
+
required floor from the stderr block so the user understands what they
|
|
100
|
+
are upgrading from and to.
|
|
101
|
+
|
|
102
|
+
Prompt:
|
|
103
|
+
|
|
104
|
+
> agent-director is installed but at version `<detected>`, below the
|
|
105
|
+
> required floor `<required>`. Run the AD-published upgrade command?
|
|
106
|
+
> `<command>`
|
|
107
|
+
>
|
|
108
|
+
> - **Yes** — run the command via Bash and continue.
|
|
109
|
+
> - **No** — upgrade manually then continue.
|
|
110
|
+
|
|
111
|
+
- **Yes**: run via Bash. Return to Step 1.
|
|
112
|
+
- **No**: wait for manual upgrade. Return to Step 1.
|
|
113
|
+
|
|
114
|
+
### `ad-system-install-unreachable` — exhaustive reason switch
|
|
115
|
+
|
|
116
|
+
The stderr block carries an `err.reason` value verbatim. Branch on it
|
|
117
|
+
explicitly — every reason has a named branch, no `default:`-only
|
|
118
|
+
fallthrough.
|
|
119
|
+
|
|
120
|
+
1. **`not-executable`** — the agent-director file exists but lacks the
|
|
121
|
+
executable bit. Show the user the binary path from the stderr block
|
|
122
|
+
and suggest:
|
|
123
|
+
```sh
|
|
124
|
+
chmod +x <binary_path>
|
|
125
|
+
```
|
|
126
|
+
Re-run Step 1.
|
|
127
|
+
|
|
128
|
+
2. **`not-a-regular-file`** — the path resolved by `resolveSystemBinary()`
|
|
129
|
+
is a directory, broken symlink, or other non-file. Recommend
|
|
130
|
+
inspection (`ls -la <binary_path>`) and removal of the bad entry,
|
|
131
|
+
then re-installation via the AD install command. Re-run Step 1.
|
|
132
|
+
|
|
133
|
+
3. **`probe-timeout`** — `agent-director --version` did not return within
|
|
134
|
+
the probe window. Likely the binary is hanging on startup
|
|
135
|
+
(corrupted, mismatched architecture, missing shared library).
|
|
136
|
+
Recommend a manual `<binary_path> --version` invocation to confirm,
|
|
137
|
+
then reinstall via the AD install command. Re-run Step 1.
|
|
138
|
+
|
|
139
|
+
4. **`probe-nonzero-exit`** — `agent-director --version` exited with a
|
|
140
|
+
non-zero code. The stderr block surfaces `exitCode` and any
|
|
141
|
+
`diagnostic` from AD. Show the user the values and recommend a
|
|
142
|
+
manual reproduction (`<binary_path> --version`), then reinstall.
|
|
143
|
+
Re-run Step 1.
|
|
144
|
+
|
|
145
|
+
5. **`probe-killed-by-signal`** — `agent-director --version` was killed
|
|
146
|
+
by a signal (SIGSEGV, SIGBUS, etc.). The stderr block surfaces
|
|
147
|
+
`signal`. The binary is likely corrupted or built for a different
|
|
148
|
+
architecture. Recommend a full reinstall via the AD install command.
|
|
149
|
+
Re-run Step 1.
|
|
150
|
+
|
|
151
|
+
6. **`unparseable-version`** — `agent-director --version` returned but
|
|
152
|
+
the output could not be parsed as semver. The stderr's `diagnostic`
|
|
153
|
+
field carries the raw output. Likely the installed binary is from
|
|
154
|
+
a pre-release line that uses non-semver tags (e.g. `v0.6.3-dev`
|
|
155
|
+
without sentinel handling). Recommend upgrading to a release AD
|
|
156
|
+
version via the AD install command. Re-run Step 1.
|
|
157
|
+
|
|
158
|
+
7. **`spawn-failed`** — the OS rejected the spawn before the subprocess
|
|
159
|
+
ran (ENOENT after stat succeeded, EACCES, EPERM, etc.). The stderr's
|
|
160
|
+
`diagnostic` field carries the underlying OS error. Recommend
|
|
161
|
+
checking filesystem permissions, then reinstalling. Re-run Step 1.
|
|
162
|
+
|
|
163
|
+
8. **`other`** — an unexpected failure mode AD's `resolveSystemBinary()`
|
|
164
|
+
could not classify. Print the raw underlying error message from
|
|
165
|
+
`detail.underlying` (or `diagnostic`) and direct the user to file
|
|
166
|
+
a bug against `agent-director`:
|
|
167
|
+
<https://github.com/gabemahoney/agent-director/issues>
|
|
168
|
+
|
|
169
|
+
This is the only branch that recommends bug-filing rather than a
|
|
170
|
+
local remediation step.
|
|
171
|
+
|
|
172
|
+
After rendering the per-reason remediation, ask the user to confirm
|
|
173
|
+
they have applied it, then return to Step 1.
|
|
174
|
+
|
|
175
|
+
### `ad-version-floor-unreadable` — corrupt agent-director npm package
|
|
176
|
+
|
|
177
|
+
This class is NOT a system-install problem — it indicates the
|
|
178
|
+
`node_modules/agent-director/dist/version-floor.json` file is missing,
|
|
179
|
+
malformed, or lacks `.min_binary_version`. The skill cannot walk the
|
|
180
|
+
user through fixing a corrupt AD npm package.
|
|
181
|
+
|
|
182
|
+
Print the reinstall guidance:
|
|
183
|
+
|
|
184
|
+
> The agent-director npm package appears corrupted. Reinstall it from
|
|
185
|
+
> npm in CSCB's project root:
|
|
186
|
+
>
|
|
187
|
+
> ```sh
|
|
188
|
+
> bun add agent-director@latest
|
|
189
|
+
> ```
|
|
190
|
+
|
|
191
|
+
Then exit. Do NOT loop on this class — the user must manually verify
|
|
192
|
+
the AD package is intact before re-invoking the skill.
|
|
193
|
+
|
|
194
|
+
## Step 4 — Loop or exit
|
|
195
|
+
|
|
196
|
+
After each remediation step, re-run Step 1. The skill keeps looping
|
|
197
|
+
until the check passes (exit cleanly with the success output) or the
|
|
198
|
+
user declines to proceed (exit non-zero with the abort summary).
|
|
199
|
+
|
|
200
|
+
## Notes
|
|
201
|
+
|
|
202
|
+
- This skill does not maintain its own list of agent-director install
|
|
203
|
+
commands. AD's documentation is the source of truth; the skill
|
|
204
|
+
surfaces AD's published command verbatim.
|
|
205
|
+
- This skill does not touch `~/.claude/channels/slack/` or CSCB's
|
|
206
|
+
routing configuration. It only acts on the system-wide
|
|
207
|
+
`agent-director` install.
|
|
208
|
+
- The skill is callable repeatedly; each invocation re-runs Step 1
|
|
209
|
+
from a clean state.
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
* `agent-director` library Client (SR-0.1).
|
|
4
4
|
*
|
|
5
5
|
* Public API:
|
|
6
|
-
* - getClient(): Client —
|
|
6
|
+
* - getClient(): Client — sync cached accessor for the singleton
|
|
7
|
+
* - setClient(client): void — install a pre-built Client (startup gate only)
|
|
7
8
|
* - closeClient(): void — idempotent shutdown for SR-11 Event 11
|
|
8
|
-
* - MIN_AD_VERSION: string — semver string derived from package.json
|
|
9
9
|
* - DEFAULT_STORE_PATH / DEFAULT_TEMPLATE_NAME — paths CSCB pins
|
|
10
10
|
* - resetClientForTests(): void — test-only handle reset
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* Construction: AD 0.7.0+ exposes an async `Client.create()` factory; the
|
|
13
|
+
* subprocess constructor is protected and `new Client(...)` is a compile-time
|
|
14
|
+
* TS error. The SR-5.1 startup gate (src/agent-director-startup.ts) awaits
|
|
15
|
+
* `Client.create({ storePath, createIfMissing: true, logger: console })` and
|
|
16
|
+
* then hands the resolved instance to `setClient()`. Verb call sites pull the
|
|
17
|
+
* installed singleton through `getClient()`; calling `getClient()` before the
|
|
18
|
+
* gate has installed a Client throws an internal bug-marker error (it
|
|
19
|
+
* indicates a caller-site bug, not a runtime condition).
|
|
19
20
|
*
|
|
20
21
|
* Concurrency: AD's Client is internally safe for concurrent verb calls
|
|
21
22
|
* (the subprocess-CLI transport serializes its own dispatch) per SR-0.1,
|
|
@@ -24,9 +25,6 @@
|
|
|
24
25
|
* SPDX-License-Identifier: MIT
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
|
-
import { readFileSync } from 'node:fs'
|
|
28
|
-
import { dirname, join } from 'node:path'
|
|
29
|
-
import { fileURLToPath } from 'node:url'
|
|
30
28
|
import { AgentDirectorError, Client } from 'agent-director'
|
|
31
29
|
import type {
|
|
32
30
|
DecideParams as ADDecideParams,
|
|
@@ -45,40 +43,6 @@ export const DEFAULT_STORE_PATH = '~/.agent-director/state.db'
|
|
|
45
43
|
/** Name of the CSCB-shipped template (SR-3.1). */
|
|
46
44
|
export const DEFAULT_TEMPLATE_NAME = 'slack-channel-bot'
|
|
47
45
|
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the package.json that ships with this module, then extract and
|
|
50
|
-
* normalize `dependencies['agent-director']` into a bare semver string.
|
|
51
|
-
*
|
|
52
|
-
* Throws at module init if the dep is missing or shaped wrong — that's a
|
|
53
|
-
* packaging bug worth failing loudly on, not a runtime condition.
|
|
54
|
-
*/
|
|
55
|
-
function readMinAdVersion(): string {
|
|
56
|
-
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
57
|
-
// src/agent-director-client.ts → package.json lives one level up
|
|
58
|
-
const pkgPath = join(moduleDir, '..', 'package.json')
|
|
59
|
-
const raw = readFileSync(pkgPath, 'utf-8')
|
|
60
|
-
const pkg = JSON.parse(raw) as { dependencies?: Record<string, string> }
|
|
61
|
-
const range = pkg.dependencies?.['agent-director']
|
|
62
|
-
if (typeof range !== 'string' || range.length === 0) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`agent-director-client: package.json dependencies['agent-director'] is missing or empty — cannot derive MIN_AD_VERSION`,
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
// Strip leading semver operators: ^, ~, >=, >, =, v. Anything left should
|
|
68
|
-
// parse as semver; we don't validate further here — the SR-5.1 version gate
|
|
69
|
-
// does the actual comparison.
|
|
70
|
-
const stripped = range.replace(/^[\^~]|^>=|^>|^=/, '').replace(/^v/, '').trim()
|
|
71
|
-
if (stripped.length === 0) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`agent-director-client: dependencies['agent-director']=${range} reduced to empty string after stripping operators`,
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
return stripped
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Minimum agent-director version CSCB requires at runtime (SR-5.3). */
|
|
80
|
-
export const MIN_AD_VERSION: string = readMinAdVersion()
|
|
81
|
-
|
|
82
46
|
// ---------------------------------------------------------------------------
|
|
83
47
|
// Singleton state
|
|
84
48
|
// ---------------------------------------------------------------------------
|
|
@@ -86,20 +50,19 @@ export const MIN_AD_VERSION: string = readMinAdVersion()
|
|
|
86
50
|
let singleton: Client | null = null
|
|
87
51
|
|
|
88
52
|
/**
|
|
89
|
-
* Return the singleton Client
|
|
53
|
+
* Return the singleton Client installed by the SR-5.1 startup gate.
|
|
90
54
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* caller-
|
|
55
|
+
* This is a sync cached accessor; it does NOT construct the Client. The
|
|
56
|
+
* startup gate (`runAgentDirectorStartupGate`) is responsible for awaiting
|
|
57
|
+
* `Client.create(...)` and installing the result via `setClient`. Calling
|
|
58
|
+
* `getClient()` before the gate has run is a caller-site bug and throws
|
|
59
|
+
* loudly — there is no lazy-construction fallback.
|
|
95
60
|
*/
|
|
96
61
|
export function getClient(): Client {
|
|
97
62
|
if (singleton === null) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
logger: console,
|
|
102
|
-
})
|
|
63
|
+
throw new Error(
|
|
64
|
+
'agent-director-client: getClient() called before the startup gate installed a Client — caller-site bug. The startup gate must run first (via runAgentDirectorStartupGate).',
|
|
65
|
+
)
|
|
103
66
|
}
|
|
104
67
|
return singleton
|
|
105
68
|
}
|
|
@@ -128,6 +91,17 @@ export function resetClientForTests(): void {
|
|
|
128
91
|
singleton = null
|
|
129
92
|
}
|
|
130
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Install a pre-built Client into the singleton slot. Called once by the
|
|
96
|
+
* startup gate (src/agent-director-startup.ts) after `await Client.create(opts)`
|
|
97
|
+
* resolves. Production verb call sites never invoke this — they go through
|
|
98
|
+
* `getClient()`. Lexically distinct from `setClientForTests` so the test
|
|
99
|
+
* helper stays grep-discoverable.
|
|
100
|
+
*/
|
|
101
|
+
export function setClient(client: Client): void {
|
|
102
|
+
singleton = client
|
|
103
|
+
}
|
|
104
|
+
|
|
131
105
|
/**
|
|
132
106
|
* Test-only helper: install a pre-built Client (typically a stub) as the
|
|
133
107
|
* singleton. Skip the Client construction path entirely.
|
|
@@ -156,9 +130,9 @@ export interface DecideParamsWithToken extends ADDecideParams {
|
|
|
156
130
|
}
|
|
157
131
|
|
|
158
132
|
/**
|
|
159
|
-
* Always-include-token decide wrapper.
|
|
160
|
-
* `^0.6.0
|
|
161
|
-
* structural cast is retained because, until the lockfile is refreshed to
|
|
133
|
+
* Always-include-token decide wrapper. The `agent-director` package.json pin
|
|
134
|
+
* is at `^0.6.0`+, which carries `request_token` on `DecideParams` natively.
|
|
135
|
+
* The structural cast is retained because, until the lockfile is refreshed to
|
|
162
136
|
* resolve the bumped pin, the imported `DecideParams` type still comes from
|
|
163
137
|
* the locked `0.5.6` package — same lockfile-lag pattern as
|
|
164
138
|
* `GetResultWithPermissionRequests`.
|
|
@@ -201,13 +175,14 @@ export function isErrPermissionRequestNotFound(err: unknown): boolean {
|
|
|
201
175
|
}
|
|
202
176
|
|
|
203
177
|
/**
|
|
204
|
-
* `get-permission` wrapper (SR-7.1).
|
|
205
|
-
* which carries the verb; the SR-6.1 startup gate is the
|
|
206
|
-
* boundary that keeps stale-version installs from reaching this
|
|
207
|
-
* wrapper takes a structural client (rather than
|
|
208
|
-
* because the locked `0.5.6` types do not yet
|
|
209
|
-
* fails loudly at runtime when the verb is
|
|
210
|
-
* the dev/test edge (e.g. stub clients that
|
|
178
|
+
* `get-permission` wrapper (SR-7.1). The `agent-director` package.json pin is
|
|
179
|
+
* at `^0.6.0`+, which carries the verb; the SR-6.1 startup gate is the
|
|
180
|
+
* compatibility boundary that keeps stale-version installs from reaching this
|
|
181
|
+
* path. The wrapper takes a structural client (rather than
|
|
182
|
+
* `Pick<Client, 'getPermission'>`) because the locked `0.5.6` types do not yet
|
|
183
|
+
* expose the method shape, and fails loudly at runtime when the verb is
|
|
184
|
+
* missing as defense-in-depth for the dev/test edge (e.g. stub clients that
|
|
185
|
+
* omit it on purpose).
|
|
211
186
|
*/
|
|
212
187
|
export async function getPermission(
|
|
213
188
|
client: { getPermission?: (params: GetPermissionParams) => Promise<GetPermissionResult> },
|
|
@@ -216,7 +191,7 @@ export async function getPermission(
|
|
|
216
191
|
if (typeof client.getPermission !== 'function') {
|
|
217
192
|
throw new Error(
|
|
218
193
|
'agent-director-client: getPermission verb unavailable — ' +
|
|
219
|
-
'confirm installed agent-director version meets the
|
|
194
|
+
'confirm installed agent-director version meets the package.json pin',
|
|
220
195
|
)
|
|
221
196
|
}
|
|
222
197
|
return client.getPermission(params)
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* keep the subset list in sync with SRD edits.
|
|
8
8
|
*
|
|
9
9
|
* Catalog (SR-0.2):
|
|
10
|
-
* - ErrUnsupportedPlatform (Client constructor / platform gate)
|
|
11
10
|
* - ErrBunVersionTooOld (Client constructor / Bun version gate)
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
11
|
+
* - ErrSystemInstallNotFound (Client.create / resolveSystemBinary — no `agent-director` on PATH or at ~/.agent-director)
|
|
12
|
+
* - ErrSystemInstallTooOld (Client.create / resolveSystemBinary — system binary older than required minimum)
|
|
13
|
+
* - ErrSystemInstallUnreachable (Client.create / resolveSystemBinary — system binary present but not executable or fails --version)
|
|
14
14
|
* - ErrInstanceIdCollision (spawn / SR-1.4 idempotency)
|
|
15
15
|
* - ErrSpawnNotFound (get / status / decide on missing row)
|
|
16
16
|
* - ErrNoSessionId (resume / SR-1.3 fallthrough)
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
export {
|
|
38
38
|
AgentDirectorError,
|
|
39
39
|
ErrClientClosed,
|
|
40
|
-
ErrUnsupportedPlatform,
|
|
41
|
-
ErrPlatformPackageMissing,
|
|
42
40
|
ErrBunVersionTooOld,
|
|
43
|
-
|
|
41
|
+
ErrSystemInstallNotFound,
|
|
42
|
+
ErrSystemInstallTooOld,
|
|
43
|
+
ErrSystemInstallUnreachable,
|
|
44
44
|
ErrCallTimeout,
|
|
45
45
|
ErrInstanceIdCollision,
|
|
46
46
|
ErrSpawnNotFound,
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* time; module-not-found surfaces as a top-level import error caught
|
|
9
9
|
* by callers' try/catch (see runAgentDirectorStartupGate's
|
|
10
10
|
* module-load-error branch below).
|
|
11
|
-
* 2.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* 2. await d.createClient(opts) — typed catches for
|
|
12
|
+
* ErrBunVersionTooOld, ErrSystemInstallNotFound,
|
|
13
|
+
* ErrSystemInstallTooOld, ErrSystemInstallUnreachable; other throws
|
|
14
|
+
* surface verbatim. The factory resolves to a constructed Client
|
|
15
|
+
* (production injects `Client.create`).
|
|
16
16
|
* 3.5. API surface probes (SR-6.1 publish-skew defense) — short-circuit,
|
|
17
17
|
* run in order: probeGetPermission (ad-shim-missing-get-permission) →
|
|
18
18
|
* probeErrorCatalog (ad-shim-catalog-incomplete) → probeDecideArgv
|
|
@@ -36,22 +36,21 @@ import { fileURLToPath } from 'node:url'
|
|
|
36
36
|
import { join } from 'node:path'
|
|
37
37
|
|
|
38
38
|
import {
|
|
39
|
+
Client,
|
|
39
40
|
ErrBunVersionTooOld,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ErrUnsupportedPlatform,
|
|
44
|
-
AgentDirectorError,
|
|
41
|
+
ErrSystemInstallNotFound,
|
|
42
|
+
ErrSystemInstallTooOld,
|
|
43
|
+
ErrSystemInstallUnreachable,
|
|
45
44
|
} from 'agent-director'
|
|
46
|
-
import type {
|
|
45
|
+
import type { ClientOptions } from 'agent-director'
|
|
47
46
|
|
|
48
47
|
import { recordStartupError } from './startup-errors.ts'
|
|
49
48
|
import {
|
|
50
|
-
MIN_AD_VERSION,
|
|
51
49
|
DEFAULT_STORE_PATH,
|
|
52
|
-
|
|
50
|
+
setClient,
|
|
53
51
|
setClientForTests,
|
|
54
52
|
} from './agent-director-client.ts'
|
|
53
|
+
import { renderInstallSkillInstructions } from './install-skill-pointer.ts'
|
|
55
54
|
|
|
56
55
|
// ---------------------------------------------------------------------------
|
|
57
56
|
// Constants
|
|
@@ -111,53 +110,17 @@ function readAdDistSource(): string | Error {
|
|
|
111
110
|
return cachedAdDistSource
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Pure helper: semver-gte for the three-segment versions AD ships
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Compare two `MAJOR.MINOR.PATCH` strings (no pre-release/build suffix
|
|
120
|
-
* handling needed: AD ships canonical three-segment numerics).
|
|
121
|
-
*
|
|
122
|
-
* Returns true when `a` is greater than or equal to `b`. Inputs must parse
|
|
123
|
-
* as integers — any non-numeric segment makes the comparison treat the
|
|
124
|
-
* value as below `b` (i.e. fail closed).
|
|
125
|
-
*
|
|
126
|
-
* Exported for direct testing.
|
|
127
|
-
*/
|
|
128
|
-
export function semverGte(a: string, b: string): boolean {
|
|
129
|
-
const split = (s: string): number[] =>
|
|
130
|
-
s.split('.').map((seg) => {
|
|
131
|
-
const n = parseInt(seg, 10)
|
|
132
|
-
return Number.isFinite(n) ? n : -1
|
|
133
|
-
})
|
|
134
|
-
const av = split(a)
|
|
135
|
-
const bv = split(b)
|
|
136
|
-
const len = Math.max(av.length, bv.length)
|
|
137
|
-
for (let i = 0; i < len; i++) {
|
|
138
|
-
const ai = av[i] ?? 0
|
|
139
|
-
const bi = bv[i] ?? 0
|
|
140
|
-
if (ai > bi) return true
|
|
141
|
-
if (ai < bi) return false
|
|
142
|
-
}
|
|
143
|
-
return true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
113
|
// ---------------------------------------------------------------------------
|
|
147
114
|
// Injectable dependency surface
|
|
148
115
|
// ---------------------------------------------------------------------------
|
|
149
116
|
|
|
150
117
|
export interface StartupGateDeps {
|
|
151
118
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* `() => makeStubClient(...)` or a thrower that simulates the typed
|
|
156
|
-
* Err* constructor failure modes.
|
|
119
|
+
* Async factory that resolves to a constructed Client. Production injects
|
|
120
|
+
* `Client.create`; tests inject `makeStubCreateClient({...})` from
|
|
121
|
+
* tests/test-helpers/agent-director-stub.ts to drive the catch ladder.
|
|
157
122
|
*/
|
|
158
|
-
|
|
159
|
-
/** Hook that returns the client's `version()` result. */
|
|
160
|
-
callVersion: (client: unknown) => Promise<VersionResult>
|
|
123
|
+
createClient: (opts: object) => Promise<unknown>
|
|
161
124
|
/** Hook that returns the client's `close()` for the failure-path cleanup. */
|
|
162
125
|
closeClient: (client: unknown) => void
|
|
163
126
|
/** Stat hook for the SR-5.1 same-user check. */
|
|
@@ -202,11 +165,8 @@ export interface StartupGateDeps {
|
|
|
202
165
|
// ---------------------------------------------------------------------------
|
|
203
166
|
|
|
204
167
|
const prodDeps: StartupGateDeps = {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Cast: production getClient returns the real Client; tests inject a stub
|
|
208
|
-
// with the same structural shape. Either way, .version({}) exists.
|
|
209
|
-
return await (client as { version: (p: object) => Promise<VersionResult> }).version({})
|
|
168
|
+
createClient: async (opts) => {
|
|
169
|
+
return await Client.create(opts as ClientOptions)
|
|
210
170
|
},
|
|
211
171
|
closeClient: (client) => {
|
|
212
172
|
try {
|
|
@@ -304,55 +264,60 @@ export async function runStartupGate(
|
|
|
304
264
|
): Promise<StartupGateOutcome> {
|
|
305
265
|
const d = mergeDeps(deps)
|
|
306
266
|
|
|
307
|
-
// Step 2: construct Client (
|
|
267
|
+
// Step 2: construct Client via async factory (Client.create in prod).
|
|
308
268
|
let client: unknown
|
|
309
269
|
try {
|
|
310
|
-
client = d.
|
|
270
|
+
client = await d.createClient({
|
|
271
|
+
storePath: DEFAULT_STORE_PATH,
|
|
272
|
+
createIfMissing: true,
|
|
273
|
+
logger: console,
|
|
274
|
+
})
|
|
311
275
|
} catch (err) {
|
|
312
|
-
if (err instanceof
|
|
276
|
+
if (err instanceof ErrBunVersionTooOld) {
|
|
313
277
|
return {
|
|
314
278
|
ok: false,
|
|
315
279
|
phase: 'construct',
|
|
316
|
-
classLabel: 'ad-
|
|
280
|
+
classLabel: 'ad-bun-version-too-old',
|
|
317
281
|
message:
|
|
318
|
-
`agent-director
|
|
319
|
-
`
|
|
320
|
-
`Supported: ${SUPPORTED_PLATFORMS.join(', ')}. ` +
|
|
321
|
-
`Detected: ${process.platform}-${process.arch}. ` +
|
|
322
|
-
`Detail: ${err.errDescription}`,
|
|
282
|
+
`agent-director requires Bun >= 1.0.21 but the running runtime is older. ` +
|
|
283
|
+
`Upgrade Bun (https://bun.sh) and retry. Detail: ${err.errDescription}`,
|
|
323
284
|
}
|
|
324
285
|
}
|
|
325
|
-
if (err instanceof
|
|
286
|
+
if (err instanceof ErrSystemInstallNotFound) {
|
|
326
287
|
return {
|
|
327
288
|
ok: false,
|
|
328
289
|
phase: 'construct',
|
|
329
|
-
classLabel: 'ad-
|
|
290
|
+
classLabel: 'ad-system-install-not-found',
|
|
330
291
|
message:
|
|
331
|
-
`agent-director
|
|
332
|
-
`
|
|
333
|
-
`
|
|
334
|
-
|
|
292
|
+
`agent-director system install not found. The startup gate searched ` +
|
|
293
|
+
`the standard install path and PATH but did not locate the agent-director ` +
|
|
294
|
+
`binary. Install agent-director (system-wide) and retry.` +
|
|
295
|
+
renderInstallSkillInstructions(),
|
|
335
296
|
}
|
|
336
297
|
}
|
|
337
|
-
if (err instanceof
|
|
298
|
+
if (err instanceof ErrSystemInstallTooOld) {
|
|
338
299
|
return {
|
|
339
300
|
ok: false,
|
|
340
301
|
phase: 'construct',
|
|
341
|
-
classLabel: 'ad-
|
|
302
|
+
classLabel: 'ad-system-install-too-old',
|
|
342
303
|
message:
|
|
343
|
-
`agent-director
|
|
344
|
-
`
|
|
304
|
+
`agent-director system install is too old. ` +
|
|
305
|
+
`Detected version ${err.actualVersion} is below the required floor ${err.requiredVersion} ` +
|
|
306
|
+
`(binary at ${err.binaryPath}). Upgrade agent-director and retry.` +
|
|
307
|
+
renderInstallSkillInstructions(),
|
|
345
308
|
}
|
|
346
309
|
}
|
|
347
|
-
if (err instanceof
|
|
310
|
+
if (err instanceof ErrSystemInstallUnreachable) {
|
|
348
311
|
return {
|
|
349
312
|
ok: false,
|
|
350
313
|
phase: 'construct',
|
|
351
|
-
classLabel: 'ad-
|
|
314
|
+
classLabel: 'ad-system-install-unreachable',
|
|
352
315
|
message:
|
|
353
|
-
`agent-director
|
|
354
|
-
`
|
|
355
|
-
`
|
|
316
|
+
`agent-director system install is unreachable. ` +
|
|
317
|
+
`Reason: ${err.reason}. ` +
|
|
318
|
+
`Binary at ${err.binaryPath} could not be invoked successfully. ` +
|
|
319
|
+
`Diagnose with the install-cscb skill or re-install agent-director.` +
|
|
320
|
+
renderInstallSkillInstructions(),
|
|
356
321
|
}
|
|
357
322
|
}
|
|
358
323
|
const detail = err instanceof Error ? err.message : String(err)
|
|
@@ -364,54 +329,17 @@ export async function runStartupGate(
|
|
|
364
329
|
}
|
|
365
330
|
}
|
|
366
331
|
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
versionResult = await d.callVersion(client)
|
|
371
|
-
} catch (err) {
|
|
372
|
-
if (err instanceof ErrCallTimeout) {
|
|
373
|
-
d.closeClient(client)
|
|
374
|
-
return {
|
|
375
|
-
ok: false,
|
|
376
|
-
phase: 'version',
|
|
377
|
-
classLabel: 'ad-call-timeout',
|
|
378
|
-
message:
|
|
379
|
-
`agent-director ${err.verb}() timed out after ${err.elapsedMs}ms ` +
|
|
380
|
-
`(configured callTimeoutMs: ${err.timeoutMs}ms). ` +
|
|
381
|
-
`Either the subprocess hung or the timeout is set too low; ` +
|
|
382
|
-
`set ClientOptions.callTimeoutMs higher (default 30000) or investigate the agent-director subprocess.`,
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
d.closeClient(client)
|
|
386
|
-
const detail =
|
|
387
|
-
err instanceof AgentDirectorError
|
|
388
|
-
? `${err.errName}: ${err.errDescription}`
|
|
389
|
-
: err instanceof Error
|
|
390
|
-
? err.message
|
|
391
|
-
: String(err)
|
|
392
|
-
return {
|
|
393
|
-
ok: false,
|
|
394
|
-
phase: 'version',
|
|
395
|
-
classLabel: 'ad-version-probe',
|
|
396
|
-
message: `agent-director version probe failed. Detail: ${detail}`,
|
|
397
|
-
}
|
|
398
|
-
}
|
|
332
|
+
// Construction succeeded: install the live Client into the module-level
|
|
333
|
+
// singleton so subsequent `getClient()` call sites resolve to it.
|
|
334
|
+
setClient(client as Client)
|
|
399
335
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
ok: false,
|
|
405
|
-
phase: 'version',
|
|
406
|
-
classLabel: 'ad-version-stale',
|
|
407
|
-
message:
|
|
408
|
-
`agent-director version ${adVersion} is below the required minimum ${MIN_AD_VERSION}. ` +
|
|
409
|
-
`Run: bun add agent-director@^${MIN_AD_VERSION}`,
|
|
410
|
-
}
|
|
411
|
-
}
|
|
336
|
+
// adVersion is sourced directly from the constructed Client; AD 0.7.0
|
|
337
|
+
// exposes `binaryVersion` as a readonly getter populated during
|
|
338
|
+
// `Client.create()`'s version probe of the resolved system binary.
|
|
339
|
+
const adVersion = (client as { binaryVersion: string }).binaryVersion
|
|
412
340
|
|
|
413
|
-
// Step 3.5: API surface probes.
|
|
414
|
-
//
|
|
341
|
+
// Step 3.5: API surface probes. `Client.create()` confirms the AD binary
|
|
342
|
+
// meets the required floor, but published agent-director npm packages
|
|
415
343
|
// have shipped a stale TS shim that drops methods (getPermission), drops
|
|
416
344
|
// CLI flags (--request-token in buildDecide), and misses err_names in the
|
|
417
345
|
// catalog. Each of those silently breaks CSCB at click-handling time. The
|
|
@@ -426,7 +354,7 @@ export async function runStartupGate(
|
|
|
426
354
|
message:
|
|
427
355
|
`agent-director Client is missing the 'getPermission' method. ` +
|
|
428
356
|
`The installed shim is stale relative to the AD binary (${adVersion}). ` +
|
|
429
|
-
`Run:
|
|
357
|
+
`Run: reinstall a matching 'agent-director' version (confirm the resolved package ships getPermission).`,
|
|
430
358
|
}
|
|
431
359
|
}
|
|
432
360
|
|
|
@@ -442,7 +370,7 @@ export async function runStartupGate(
|
|
|
442
370
|
`${catalogProbe.missing.join(', ')}. ` +
|
|
443
371
|
`Envelopes with these names would surface as the base AgentDirectorError ` +
|
|
444
372
|
`instead of typed subclasses, breaking CSCB's instanceof Err<Name> branches. ` +
|
|
445
|
-
`Run:
|
|
373
|
+
`Run: reinstall a matching 'agent-director' version (confirm the resolved package ships the full catalog).`,
|
|
446
374
|
}
|
|
447
375
|
}
|
|
448
376
|
|
|
@@ -456,7 +384,7 @@ export async function runStartupGate(
|
|
|
456
384
|
message:
|
|
457
385
|
`agent-director shim's decide() does not pass --request-token to the CLI: ${argvProbe.detail} ` +
|
|
458
386
|
`Permission clicks would resolve against the wrong row. ` +
|
|
459
|
-
`Run:
|
|
387
|
+
`Run: reinstall a matching 'agent-director' version (confirm the resolved package's buildDecide includes the flag).`,
|
|
460
388
|
}
|
|
461
389
|
}
|
|
462
390
|
|
|
@@ -533,7 +461,6 @@ export async function runAgentDirectorStartupGate(
|
|
|
533
461
|
// ---------------------------------------------------------------------------
|
|
534
462
|
|
|
535
463
|
export {
|
|
536
|
-
MIN_AD_VERSION,
|
|
537
464
|
DEFAULT_STORE_PATH,
|
|
538
465
|
AD_STATE_DB_PATH as DEFAULT_STATE_DB_PATH,
|
|
539
466
|
SUPPORTED_PLATFORMS,
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-check.ts — SR-5 shared install-check module.
|
|
3
|
+
*
|
|
4
|
+
* Source of truth for "is agent-director system-installed at a version
|
|
5
|
+
* meeting AD's declared floor?" — consumed by:
|
|
6
|
+
* - The `bun run install-check` script (Epic 2).
|
|
7
|
+
* - The install-cscb skill (Epic 3).
|
|
8
|
+
*
|
|
9
|
+
* The startup gate (Epic 1, agent-director-startup.ts) does NOT call this
|
|
10
|
+
* module — AD's own `Client.create()` enforces the same floor against the
|
|
11
|
+
* same `dist/version-floor.json`, so there are two callers that drive the
|
|
12
|
+
* same enforcement path. CSCB never duplicates the floor decision.
|
|
13
|
+
*
|
|
14
|
+
* The module is strictly side-effect-free:
|
|
15
|
+
* - No process.exit.
|
|
16
|
+
* - No disk writes.
|
|
17
|
+
* - No stdout / stderr output.
|
|
18
|
+
*
|
|
19
|
+
* Callers own all presentation, exit code, and skill-instruction-block
|
|
20
|
+
* appending decisions.
|
|
21
|
+
*
|
|
22
|
+
* SPDX-License-Identifier: MIT
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readFileSync } from 'node:fs'
|
|
26
|
+
import { fileURLToPath } from 'node:url'
|
|
27
|
+
import semver from 'semver'
|
|
28
|
+
import {
|
|
29
|
+
DEV_SENTINEL_VERSION,
|
|
30
|
+
ErrSystemInstallNotFound,
|
|
31
|
+
ErrSystemInstallTooOld,
|
|
32
|
+
ErrSystemInstallUnreachable,
|
|
33
|
+
resolveSystemBinary,
|
|
34
|
+
} from 'agent-director'
|
|
35
|
+
import type { UnreachableReason } from 'agent-director'
|
|
36
|
+
|
|
37
|
+
/** Canonical class labels emitted by the check; mirrors the startup gate's. */
|
|
38
|
+
export type InstallCheckClassLabel =
|
|
39
|
+
| 'ad-system-install-not-found'
|
|
40
|
+
| 'ad-system-install-too-old'
|
|
41
|
+
| 'ad-system-install-unreachable'
|
|
42
|
+
| 'ad-version-floor-unreadable'
|
|
43
|
+
|
|
44
|
+
/** Success arm: AD is installed, version satisfies the declared floor. */
|
|
45
|
+
export interface InstallCheckSuccess {
|
|
46
|
+
ok: true
|
|
47
|
+
binaryPath: string
|
|
48
|
+
binaryVersion: string
|
|
49
|
+
floor: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Failure arm: one of the four canonical class labels. */
|
|
53
|
+
export interface InstallCheckFailure {
|
|
54
|
+
ok: false
|
|
55
|
+
classLabel: InstallCheckClassLabel
|
|
56
|
+
message: string
|
|
57
|
+
detail: Record<string, unknown>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type InstallCheckResult = InstallCheckSuccess | InstallCheckFailure
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cached `.min_binary_version` value, or the cached failure-arm result for
|
|
64
|
+
* the `ad-version-floor-unreadable` class so we surface the same shape on
|
|
65
|
+
* every call without re-reading a known-bad file.
|
|
66
|
+
*/
|
|
67
|
+
let cachedFloor: string | InstallCheckFailure | null = null
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Read AD's `dist/version-floor.json` via the subpath export and return the
|
|
71
|
+
* `.min_binary_version` value. Returns a pre-built failure-arm result on
|
|
72
|
+
* any failure mode (missing file, parse error, missing/non-string field).
|
|
73
|
+
*/
|
|
74
|
+
function loadFloor(): string | InstallCheckFailure {
|
|
75
|
+
if (cachedFloor !== null) return cachedFloor
|
|
76
|
+
|
|
77
|
+
let resolved: string
|
|
78
|
+
try {
|
|
79
|
+
resolved = import.meta.resolve('agent-director/dist/version-floor.json')
|
|
80
|
+
} catch (err) {
|
|
81
|
+
cachedFloor = {
|
|
82
|
+
ok: false,
|
|
83
|
+
classLabel: 'ad-version-floor-unreadable',
|
|
84
|
+
message:
|
|
85
|
+
`Could not resolve 'agent-director/dist/version-floor.json' from the installed ` +
|
|
86
|
+
`agent-director package. Reinstall agent-director from npm and retry.`,
|
|
87
|
+
detail: { underlying: (err as Error).message },
|
|
88
|
+
}
|
|
89
|
+
return cachedFloor
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let raw: string
|
|
93
|
+
try {
|
|
94
|
+
raw = readFileSync(fileURLToPath(resolved), 'utf-8')
|
|
95
|
+
} catch (err) {
|
|
96
|
+
cachedFloor = {
|
|
97
|
+
ok: false,
|
|
98
|
+
classLabel: 'ad-version-floor-unreadable',
|
|
99
|
+
message:
|
|
100
|
+
`Could not read agent-director's dist/version-floor.json. ` +
|
|
101
|
+
`Reinstall agent-director from npm and retry.`,
|
|
102
|
+
detail: { underlying: (err as Error).message },
|
|
103
|
+
}
|
|
104
|
+
return cachedFloor
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let parsed: unknown
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(raw)
|
|
110
|
+
} catch (err) {
|
|
111
|
+
cachedFloor = {
|
|
112
|
+
ok: false,
|
|
113
|
+
classLabel: 'ad-version-floor-unreadable',
|
|
114
|
+
message:
|
|
115
|
+
`agent-director's dist/version-floor.json failed to parse as JSON. ` +
|
|
116
|
+
`Reinstall agent-director from npm and retry.`,
|
|
117
|
+
detail: { underlying: (err as Error).message },
|
|
118
|
+
}
|
|
119
|
+
return cachedFloor
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const floor = (parsed as { min_binary_version?: unknown } | null)?.min_binary_version
|
|
123
|
+
if (typeof floor !== 'string' || floor.length === 0) {
|
|
124
|
+
cachedFloor = {
|
|
125
|
+
ok: false,
|
|
126
|
+
classLabel: 'ad-version-floor-unreadable',
|
|
127
|
+
message:
|
|
128
|
+
`agent-director's dist/version-floor.json is missing the required '.min_binary_version' field ` +
|
|
129
|
+
`(or it is not a non-empty string). Reinstall agent-director from npm and retry.`,
|
|
130
|
+
detail: { parsed },
|
|
131
|
+
}
|
|
132
|
+
return cachedFloor
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
cachedFloor = floor
|
|
136
|
+
return floor
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Strip a leading 'v' from a version string, idempotent for inputs that
|
|
141
|
+
* already lack the prefix. AD has historically shipped both forms.
|
|
142
|
+
*/
|
|
143
|
+
function stripLeadingV(version: string): string {
|
|
144
|
+
return version.startsWith('v') ? version.slice(1) : version
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run the full check. See module header for semantics. The result is one of
|
|
149
|
+
* five shapes: success, or one of the four failure class labels.
|
|
150
|
+
*/
|
|
151
|
+
export async function runInstallCheck(): Promise<InstallCheckResult> {
|
|
152
|
+
const floorOrFailure = loadFloor()
|
|
153
|
+
if (typeof floorOrFailure !== 'string') return floorOrFailure
|
|
154
|
+
const floor = floorOrFailure
|
|
155
|
+
|
|
156
|
+
let resolved: { path: string; version: string }
|
|
157
|
+
try {
|
|
158
|
+
resolved = await resolveSystemBinary()
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (err instanceof ErrSystemInstallNotFound) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
classLabel: 'ad-system-install-not-found',
|
|
164
|
+
message:
|
|
165
|
+
`agent-director not found on PATH or at the standard install path. ` +
|
|
166
|
+
`Install agent-director system-wide and retry.`,
|
|
167
|
+
detail: { checkedLocations: err.checkedLocations },
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (err instanceof ErrSystemInstallTooOld) {
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
classLabel: 'ad-system-install-too-old',
|
|
174
|
+
message:
|
|
175
|
+
`agent-director system install at ${err.binaryPath} reports version ${err.actualVersion}, ` +
|
|
176
|
+
`which is below the declared floor ${err.requiredVersion}. Upgrade agent-director and retry.`,
|
|
177
|
+
detail: {
|
|
178
|
+
detected: err.actualVersion,
|
|
179
|
+
required: err.requiredVersion,
|
|
180
|
+
binaryPath: err.binaryPath,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (err instanceof ErrSystemInstallUnreachable) {
|
|
185
|
+
const reason: UnreachableReason = err.reason
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
classLabel: 'ad-system-install-unreachable',
|
|
189
|
+
message:
|
|
190
|
+
`agent-director system install at ${err.binaryPath} is unreachable. ` +
|
|
191
|
+
`Reason: ${reason}. Diagnose with the install-cscb skill or re-install agent-director.`,
|
|
192
|
+
detail: {
|
|
193
|
+
reason,
|
|
194
|
+
binaryPath: err.binaryPath,
|
|
195
|
+
diagnostic: err.diagnostic,
|
|
196
|
+
exitCode: err.exitCode,
|
|
197
|
+
signal: err.signal,
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Non-typed throw — surface verbatim under the unreachable label so
|
|
202
|
+
// callers have one failure-arm shape to dispatch on.
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
classLabel: 'ad-system-install-unreachable',
|
|
206
|
+
message:
|
|
207
|
+
`agent-director system install probe threw an unexpected error: ` +
|
|
208
|
+
`${(err as Error).message ?? String(err)}. Re-install agent-director or file a bug.`,
|
|
209
|
+
detail: { underlying: String(err), reason: 'other' as UnreachableReason },
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const detectedRaw = resolved.version
|
|
214
|
+
|
|
215
|
+
// SR-5: sentinel-equality short-circuit. A detected version exactly equal
|
|
216
|
+
// to DEV_SENTINEL_VERSION passes unconditionally — this is how locally-
|
|
217
|
+
// built dev binaries (without a real semver) pass the check.
|
|
218
|
+
if (detectedRaw === DEV_SENTINEL_VERSION) {
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
binaryPath: resolved.path,
|
|
222
|
+
binaryVersion: detectedRaw,
|
|
223
|
+
floor,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const detected = stripLeadingV(detectedRaw)
|
|
228
|
+
if (!semver.gte(detected, floor)) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
classLabel: 'ad-system-install-too-old',
|
|
232
|
+
message:
|
|
233
|
+
`agent-director system install at ${resolved.path} reports version ${detectedRaw}, ` +
|
|
234
|
+
`which is below the declared floor ${floor}. Upgrade agent-director and retry.`,
|
|
235
|
+
detail: { detected: detectedRaw, required: floor, binaryPath: resolved.path },
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
ok: true,
|
|
241
|
+
binaryPath: resolved.path,
|
|
242
|
+
binaryVersion: detectedRaw,
|
|
243
|
+
floor,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @internal Test-only — reset the cached floor so subsequent calls re-read
|
|
249
|
+
* `dist/version-floor.json`. Used by the install-check tests to exercise
|
|
250
|
+
* both the cache and the per-failure-mode read paths.
|
|
251
|
+
*/
|
|
252
|
+
export function resetCacheForTests(): void {
|
|
253
|
+
cachedFloor = null
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @internal Test-only — pre-populate the floor cache with a synthetic value
|
|
258
|
+
* (or a pre-built failure-arm result) so tests can drive every code path
|
|
259
|
+
* without mocking `node:fs`. Tests must call `resetCacheForTests()` in
|
|
260
|
+
* teardown to leave the module in a clean state for the next test file.
|
|
261
|
+
*
|
|
262
|
+
* Pass a `string` to simulate a successful read of `dist/version-floor.json`
|
|
263
|
+
* with that `.min_binary_version` value. Pass an `InstallCheckFailure` to
|
|
264
|
+
* simulate a malformed-JSON / missing-field / unresolvable-subpath read.
|
|
265
|
+
*/
|
|
266
|
+
export function setFloorForTests(value: string | InstallCheckFailure): void {
|
|
267
|
+
cachedFloor = value
|
|
268
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-skill-pointer.ts — Render the manual-fetch instructions block
|
|
3
|
+
* appended to the gate's three system-install typed-error failure messages
|
|
4
|
+
* (SR-4.5, SR-9.3).
|
|
5
|
+
*
|
|
6
|
+
* The block tells the operator where to fetch `skills/install-cscb/SKILL.md`
|
|
7
|
+
* from CSCB's GitHub repo, where to place it under `~/.claude/skills/`, and
|
|
8
|
+
* how to invoke the skill once installed. The skill itself walks the user
|
|
9
|
+
* through the interactive `agent-director` install/upgrade flow.
|
|
10
|
+
*
|
|
11
|
+
* The append-on-three-branches rule lives in `agent-director-startup.ts` —
|
|
12
|
+
* this module only renders. The block is appended verbatim regardless of
|
|
13
|
+
* whether the skill is already installed; the gate cannot detect that and
|
|
14
|
+
* must not try.
|
|
15
|
+
*
|
|
16
|
+
* Public API:
|
|
17
|
+
* - renderInstallSkillInstructions(): string — production wrapper; reads
|
|
18
|
+
* `package.json` once at module init, normalizes `repository.url`, and
|
|
19
|
+
* renders. Cached for the life of the process.
|
|
20
|
+
* - renderInstallSkillInstructionsFrom(repositoryUrl): string — pure
|
|
21
|
+
* function used by tests to feed synthetic `repository.url` values
|
|
22
|
+
* without `mock.module`. Throws on missing / non-string / unparseable
|
|
23
|
+
* input.
|
|
24
|
+
*
|
|
25
|
+
* SPDX-License-Identifier: MIT
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync } from 'node:fs'
|
|
29
|
+
import { dirname, join } from 'node:path'
|
|
30
|
+
import { fileURLToPath } from 'node:url'
|
|
31
|
+
|
|
32
|
+
/** In-repo path of the install skill — module-local constant, not derived from package.json. */
|
|
33
|
+
const IN_REPO_SKILL_PATH = 'skills/install-cscb/SKILL.md'
|
|
34
|
+
|
|
35
|
+
/** Target path under `~/.claude/skills/` where the user must place SKILL.md. */
|
|
36
|
+
const TARGET_SKILL_PATH = '~/.claude/skills/install-cscb/SKILL.md'
|
|
37
|
+
|
|
38
|
+
/** Literal slash-command the user invokes after installing the skill. */
|
|
39
|
+
const SKILL_INVOCATION_COMMAND = '/install-cscb'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize a `repository.url` value to a plain `https://github.com/<owner>/<repo>`
|
|
43
|
+
* base URL. Strips a leading `git+` prefix and a trailing `.git` suffix.
|
|
44
|
+
*
|
|
45
|
+
* Throws a CSCB-internal packaging-bug Error when the input cannot be normalized
|
|
46
|
+
* to a `https://github.com/...` form — this is a CSCB bug, not a runtime
|
|
47
|
+
* condition, so failing loudly is correct.
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeRepositoryUrl(repositoryUrl: string): string {
|
|
50
|
+
let url = repositoryUrl
|
|
51
|
+
if (url.startsWith('git+')) url = url.slice(4)
|
|
52
|
+
if (url.endsWith('.git')) url = url.slice(0, -4)
|
|
53
|
+
if (!url.startsWith('https://github.com/')) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`install-skill-pointer: repository.url=${repositoryUrl} does not normalize to ` +
|
|
56
|
+
`a https://github.com/<owner>/<repo> base URL — CSCB packaging bug.`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return url
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pure render function. Used by tests with synthetic `repository.url`
|
|
64
|
+
* inputs. The production wrapper below reads `package.json` once and
|
|
65
|
+
* forwards the value here.
|
|
66
|
+
*/
|
|
67
|
+
export function renderInstallSkillInstructionsFrom(repositoryUrl: string): string {
|
|
68
|
+
if (typeof repositoryUrl !== 'string' || repositoryUrl.length === 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`install-skill-pointer: repository.url is missing or not a string — CSCB packaging bug.`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
const base = normalizeRepositoryUrl(repositoryUrl)
|
|
74
|
+
const skillUrl = `${base}/blob/main/${IN_REPO_SKILL_PATH}`
|
|
75
|
+
return [
|
|
76
|
+
'',
|
|
77
|
+
'Interactive remediation is available via the install-cscb skill.',
|
|
78
|
+
`Fetch the skill from: ${skillUrl}`,
|
|
79
|
+
`Place it at: ${TARGET_SKILL_PATH}`,
|
|
80
|
+
`Then run: ${SKILL_INVOCATION_COMMAND}`,
|
|
81
|
+
].join('\n')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read this package's `repository.url` once at module init. Throws at module
|
|
86
|
+
* init when the field is missing / non-string — a CSCB packaging bug.
|
|
87
|
+
*/
|
|
88
|
+
function readRepositoryUrl(): string {
|
|
89
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
90
|
+
const pkgPath = join(moduleDir, '..', 'package.json')
|
|
91
|
+
const raw = readFileSync(pkgPath, 'utf-8')
|
|
92
|
+
const pkg = JSON.parse(raw) as { repository?: { url?: unknown } }
|
|
93
|
+
const url = pkg.repository?.url
|
|
94
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`install-skill-pointer: package.json repository.url is missing or not a string — CSCB packaging bug.`,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
return url
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Cached production-rendered block. Populated lazily on first call. */
|
|
103
|
+
let cachedBlock: string | null = null
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Production wrapper: render the block using this package's `repository.url`.
|
|
107
|
+
* Cached for the life of the process; idempotent.
|
|
108
|
+
*/
|
|
109
|
+
export function renderInstallSkillInstructions(): string {
|
|
110
|
+
if (cachedBlock === null) {
|
|
111
|
+
cachedBlock = renderInstallSkillInstructionsFrom(readRepositoryUrl())
|
|
112
|
+
}
|
|
113
|
+
return cachedBlock
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @internal Test-only — reset the cached block so the production wrapper
|
|
118
|
+
* re-reads `package.json` on the next call. Used by the helper's own tests
|
|
119
|
+
* to exercise the cache + the read path.
|
|
120
|
+
*/
|
|
121
|
+
export function resetCacheForTests(): void {
|
|
122
|
+
cachedBlock = null
|
|
123
|
+
}
|
package/src/postinstall.ts
CHANGED
|
@@ -18,8 +18,8 @@ import { MCP_SERVER_NAME } from './config.ts'
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Read the agent-director dependency range from the shipping package.json.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* One source of truth for the AD version requirement; used in the
|
|
22
|
+
* postinstall-probe failure warning to point operators at the right pin.
|
|
23
23
|
*/
|
|
24
24
|
export function readAdDependencyRange(): string {
|
|
25
25
|
const pkgPath = resolve(dirname(import.meta.filename), '..', 'package.json')
|
|
@@ -149,19 +149,21 @@ export function runPostinstall(options: PostinstallOptions = {}): void {
|
|
|
149
149
|
export async function runAgentDirectorPostinstallProbe(): Promise<void> {
|
|
150
150
|
try {
|
|
151
151
|
const ad = await import('agent-director')
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// caught below.
|
|
155
|
-
const client =
|
|
152
|
+
// AD 0.7.0+ exposes an async `Client.create()` factory; the constructor
|
|
153
|
+
// is protected. Any platform / Bun / subprocess-resolution error fires
|
|
154
|
+
// here (Err* subclasses + system-install errors) and is caught below.
|
|
155
|
+
const client = await ad.Client.create({
|
|
156
156
|
storePath: '~/.agent-director/state.db',
|
|
157
157
|
createIfMissing: true,
|
|
158
158
|
})
|
|
159
159
|
try {
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
// Now that Client.create() has resolved, the binary version is on the
|
|
161
|
+
// Client itself (binaryVersion getter, AD 0.7.0+). Skip the verb-call
|
|
162
|
+
// probe — the factory already enforced the floor.
|
|
163
|
+
const adVersion = client.binaryVersion.replace(/^v/, '')
|
|
162
164
|
console.log(`postinstall: agent-director ${adVersion} OK`)
|
|
163
165
|
} finally {
|
|
164
|
-
try { client.close() } catch { /*
|
|
166
|
+
try { client.close() } catch { /* defensive */ }
|
|
165
167
|
}
|
|
166
168
|
} catch (err) {
|
|
167
169
|
const detail = err instanceof Error ? err.message : String(err)
|