delegation-layer 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/README.md +221 -0
- package/install.sh +77 -0
- package/package.json +27 -0
- package/scripts/install-agent-integration.mjs +122 -0
- package/scripts/install-cli.mjs +86 -0
- package/skills/agent-integration/SKILL.md +182 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Delegation Layer
|
|
2
|
+
|
|
3
|
+
Delegation Layer runs one bounded turn of a supported AI command line tool as a
|
|
4
|
+
durable, supervised local task. It records the request before submission,
|
|
5
|
+
checks the executable and its capabilities at runtime, and publishes a
|
|
6
|
+
validated result that can be collected later.
|
|
7
|
+
|
|
8
|
+
> **Preview release**
|
|
9
|
+
>
|
|
10
|
+
> The project publishes checksum-verified CLI releases and an npm package with
|
|
11
|
+
> installers for the CLI and integration skill. The provider CLI and Pueue
|
|
12
|
+
> remain host prerequisites.
|
|
13
|
+
|
|
14
|
+
## What it does
|
|
15
|
+
|
|
16
|
+
The `delegate` command accepts a finite brief, a canonical workspace, a
|
|
17
|
+
permission mode, and a wall clock budget. It prepares a provider-specific
|
|
18
|
+
launch profile, submits exactly one provider turn through Pueue, and stores
|
|
19
|
+
immutable task records and output descriptors under a private state root.
|
|
20
|
+
|
|
21
|
+
The companion `delegate-run` executable is started by the supervisor. It
|
|
22
|
+
reconstructs the recorded profile, verifies that admission and launch still
|
|
23
|
+
refer to the same executable, policy, workspace, and request, then captures and
|
|
24
|
+
seals the provider result. A later `collect` call can recover a committed
|
|
25
|
+
outcome without launching the provider again.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Go 1.27.1 is needed only for source builds and the Homebrew formula.
|
|
30
|
+
- Pueue 4.0.4 and a running `pueued` daemon using a private configuration.
|
|
31
|
+
- A signed-in installation of one or more supported provider CLIs: `agy`,
|
|
32
|
+
`codex`, `claude`, `pi`, or `opencode`.
|
|
33
|
+
- A Darwin or Linux host on amd64 or arm64 for the supplied build targets.
|
|
34
|
+
Provider availability is profile-specific; see the provider guide for native
|
|
35
|
+
platform prerequisites.
|
|
36
|
+
- Node.js 18 or newer is needed for the npm and pnpm installers; Bun is needed
|
|
37
|
+
for the Bun installer.
|
|
38
|
+
|
|
39
|
+
The provider executable must be available to the process that dispatches the
|
|
40
|
+
task. Delegation Layer runs the provider's version and help commands before
|
|
41
|
+
admission and accepts any reported version that is valid and advertises every
|
|
42
|
+
flag required by the adapter. It does not pin a provider release or predeclared
|
|
43
|
+
binary hash. A valid version here means nonempty trimmed UTF-8 output without control
|
|
44
|
+
characters; it is not required to match a semantic-version pattern. The
|
|
45
|
+
observed version is diagnostic evidence, while capability flags, executable
|
|
46
|
+
identity, and provider behavior decide compatibility. No web release lookup or
|
|
47
|
+
semver allowlist is used.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
### Install the CLI
|
|
52
|
+
|
|
53
|
+
The shell installer downloads the latest published release, verifies its
|
|
54
|
+
checksum, and installs both `delegate` and `delegate-run` in
|
|
55
|
+
`$HOME/.local/bin`:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
curl -fsSL https://raw.githubusercontent.com/hishamkaram/delegation-layer/main/install.sh | sh
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Set `DELEGATION_LAYER_INSTALL_DIR` if you want to install somewhere other than
|
|
62
|
+
`$HOME/.local/bin`.
|
|
63
|
+
|
|
64
|
+
Homebrew installs the same two executables:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
brew install hishamkaram/tap/delegation-layer
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The npm package also provides a checksum-verified CLI installer through npm,
|
|
71
|
+
pnpm, and Bun:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npx --yes delegation-layer install-cli
|
|
75
|
+
pnpm dlx delegation-layer install-cli
|
|
76
|
+
bunx --bun delegation-layer install-cli
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For a source build, install both Go commands from the latest version:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
go install github.com/hishamkaram/delegation-layer/cmd/delegate@latest
|
|
83
|
+
go install github.com/hishamkaram/delegation-layer/cmd/delegate-run@latest
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Ensure the directory used by the installer or Go is on `PATH`.
|
|
87
|
+
|
|
88
|
+
Confirm the installation:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
delegate --help
|
|
92
|
+
delegate providers --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Install the agent integration skill
|
|
96
|
+
|
|
97
|
+
The provider-agnostic skill is separate from the CLI installation. Hermes can
|
|
98
|
+
install the current skill directly:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
hermes skills install \
|
|
102
|
+
https://raw.githubusercontent.com/hishamkaram/delegation-layer/main/skills/agent-integration/SKILL.md
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Any harness with a writable skill directory can use the dependency-free npm,
|
|
106
|
+
pnpm, or Bun installer:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npx --yes delegation-layer \
|
|
110
|
+
--target "$HOME/.hermes/skills/agent-integration"
|
|
111
|
+
pnpm dlx delegation-layer \
|
|
112
|
+
--target "$HOME/.hermes/skills/agent-integration"
|
|
113
|
+
bunx --bun delegation-layer \
|
|
114
|
+
--target "$HOME/.hermes/skills/agent-integration"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The default package command copies only `SKILL.md` into the target directory.
|
|
118
|
+
The package's explicit `install-cli` command installs the CLI separately; it
|
|
119
|
+
does not change the skill or configure Pueue or a provider CLI. Release tags
|
|
120
|
+
and `DELEGATION_LAYER_VERSION` remain available when a deployment needs a
|
|
121
|
+
reproducible version.
|
|
122
|
+
|
|
123
|
+
## Quick start
|
|
124
|
+
|
|
125
|
+
Create a brief and a workspace outside the state root. Use an absolute path to
|
|
126
|
+
the Pueue configuration that controls the private daemon:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
mkdir -p "$HOME/delegation-workspace"
|
|
130
|
+
printf '%s\n' 'Inspect the repository and summarize the current build status.' > brief.txt
|
|
131
|
+
|
|
132
|
+
delegate --pueue-config /absolute/path/to/pueue.yml dispatch \
|
|
133
|
+
--provider codex:exec \
|
|
134
|
+
--brief "$PWD/brief.txt" \
|
|
135
|
+
--cwd "$HOME/delegation-workspace" \
|
|
136
|
+
--permission read-only \
|
|
137
|
+
--budget 30m \
|
|
138
|
+
--json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The response contains a task ID and separate admission, liveness, and
|
|
142
|
+
publication fields. Observe it with `status`, wait for a terminal publication,
|
|
143
|
+
then read the validated result with `collect`:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
delegate status TASK_ID --json
|
|
147
|
+
delegate collect TASK_ID --watch 5s --json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use the exact task ID printed by `dispatch`. A continuation is a new task that
|
|
151
|
+
names its predecessor with `--resume-task`; it never silently reuses the most
|
|
152
|
+
recent conversation.
|
|
153
|
+
|
|
154
|
+
## Providers
|
|
155
|
+
|
|
156
|
+
| Profile | Modes | Options |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| `antigravity:print` | `workspace-write` | `continuation`, `native-timeout` |
|
|
159
|
+
| `codex:exec` | `read-only`, `workspace-write` | `continuation` |
|
|
160
|
+
| `claude:print` | `read-only`, `workspace-write` | `continuation` |
|
|
161
|
+
| `pi:json` | `read-only` | `continuation`, `model`, `effort` |
|
|
162
|
+
| `opencode:run` | `read-only`, `workspace-write` | `continuation`, `model`, `effort` |
|
|
163
|
+
|
|
164
|
+
Run `delegate providers --json` for the compiled capability catalog. See
|
|
165
|
+
[providers](docs/providers.md) for launch behavior, policy boundaries, and
|
|
166
|
+
runtime compatibility checks.
|
|
167
|
+
Use `delegate capabilities --provider PROFILE --json` for one provider's
|
|
168
|
+
provider-neutral contract. Its catalog response is descriptive; dispatch is
|
|
169
|
+
where the supervised runtime probe establishes host compatibility.
|
|
170
|
+
|
|
171
|
+
When native logins are available, `make acceptance-native` runs the Pi and
|
|
172
|
+
OpenCode live gates through a private supervisor. A pass exits `0`; an
|
|
173
|
+
unavailable executable, supervisor, or login emits a sanitized `BLOCKED`
|
|
174
|
+
receipt and exits `2`, which the aggregate target treats as neutral. Real
|
|
175
|
+
behavior and evidence failures still fail the target.
|
|
176
|
+
|
|
177
|
+
The catalog uses public request option names. The public `effort` option maps
|
|
178
|
+
to Pi's native `--thinking` flag and OpenCode's native `--variant` flag.
|
|
179
|
+
|
|
180
|
+
## Safety model
|
|
181
|
+
|
|
182
|
+
- Every task has one immutable request and one provider turn.
|
|
183
|
+
- State, workspace, provider runtime, and temporary directories are canonical
|
|
184
|
+
and kept disjoint where the selected policy requires it.
|
|
185
|
+
- The supervisor owns process lifetime; the runner owns capture and sealing.
|
|
186
|
+
- Admission and launch re-check executable identity, effective policy, and
|
|
187
|
+
session identity.
|
|
188
|
+
- Briefs and control responses are bounded. Sealed payloads and raw streams are
|
|
189
|
+
described by validated paths, sizes, and SHA-256 digests.
|
|
190
|
+
- Provider authentication remains native to the provider CLI. Delegation Layer
|
|
191
|
+
does not copy credentials into task state or rewrite account settings.
|
|
192
|
+
|
|
193
|
+
Read [security](docs/security.md) for the full boundary and its operational
|
|
194
|
+
limitations.
|
|
195
|
+
|
|
196
|
+
## Documentation
|
|
197
|
+
|
|
198
|
+
- [Getting started](docs/getting-started.md) — install, configure, and run a
|
|
199
|
+
first task.
|
|
200
|
+
- [CLI reference](docs/cli-reference.md) — commands, flags, responses, and
|
|
201
|
+
exit codes.
|
|
202
|
+
- [Integration API](docs/api.md) — JSON responses, state semantics, and
|
|
203
|
+
idempotent client behavior.
|
|
204
|
+
- [Provider guide](docs/providers.md) — supported profiles and compatibility
|
|
205
|
+
behavior.
|
|
206
|
+
- [Architecture](docs/architecture.md) — task lifecycle and ownership.
|
|
207
|
+
- [System components](diagrams/components.html) — current Archify component map.
|
|
208
|
+
- [Task lifecycle](diagrams/lead-interface.html) — current Archify sequence view.
|
|
209
|
+
- [Security](docs/security.md) — isolation, policy, and result integrity.
|
|
210
|
+
- [Troubleshooting](docs/troubleshooting.md) — common setup and runtime
|
|
211
|
+
failures.
|
|
212
|
+
- [Contributing](docs/contributing.md) — development setup and adapter
|
|
213
|
+
extension guidance.
|
|
214
|
+
- [Agent integration skill](skills/agent-integration/SKILL.md) — provider-neutral
|
|
215
|
+
JSON and evidence handling guidance for agents.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
No public license has been declared yet. Treat the source and release artifacts
|
|
220
|
+
as proprietary and do not redistribute them without the project owner's
|
|
221
|
+
permission.
|
package/install.sh
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
REPOSITORY="hishamkaram/delegation-layer"
|
|
5
|
+
REQUESTED_VERSION="${DELEGATION_LAYER_VERSION:-latest}"
|
|
6
|
+
INSTALL_DIR="${DELEGATION_LAYER_INSTALL_DIR:-$HOME/.local/bin}"
|
|
7
|
+
|
|
8
|
+
fail() {
|
|
9
|
+
printf 'delegation-layer: %s\n' "$1" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for command_name in awk curl grep install mktemp tar uname; do
|
|
14
|
+
command -v "$command_name" >/dev/null 2>&1 || fail "required command not found: $command_name"
|
|
15
|
+
done
|
|
16
|
+
|
|
17
|
+
if [ "$REQUESTED_VERSION" = "latest" ]; then
|
|
18
|
+
latest_url="https://github.com/${REPOSITORY}/releases/latest"
|
|
19
|
+
resolved_url="$(curl -fsSL -o /dev/null -w '%{url_effective}' "$latest_url")" || fail "could not resolve the latest release"
|
|
20
|
+
VERSION="${resolved_url##*/}"
|
|
21
|
+
else
|
|
22
|
+
VERSION="$REQUESTED_VERSION"
|
|
23
|
+
case "$VERSION" in
|
|
24
|
+
v*) ;;
|
|
25
|
+
*) VERSION="v${VERSION}" ;;
|
|
26
|
+
esac
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
printf '%s\n' "$VERSION" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' || \
|
|
30
|
+
fail "invalid version: $VERSION"
|
|
31
|
+
|
|
32
|
+
case "$(uname -s)" in
|
|
33
|
+
Linux) OS=Linux ;;
|
|
34
|
+
Darwin) OS=Darwin ;;
|
|
35
|
+
*) fail "unsupported operating system: $(uname -s)" ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
case "$(uname -m)" in
|
|
39
|
+
x86_64|amd64) ARCH=amd64 ;;
|
|
40
|
+
arm64|aarch64) ARCH=arm64 ;;
|
|
41
|
+
*) fail "unsupported architecture: $(uname -m)" ;;
|
|
42
|
+
esac
|
|
43
|
+
|
|
44
|
+
ARCHIVE="delegation-layer_${VERSION#v}_${OS}_${ARCH}.tar.gz"
|
|
45
|
+
RELEASE_URL="https://github.com/${REPOSITORY}/releases/download/${VERSION}"
|
|
46
|
+
TEMP_DIR="$(mktemp -d)"
|
|
47
|
+
trap 'rm -rf "$TEMP_DIR"' EXIT
|
|
48
|
+
|
|
49
|
+
curl -fsSL --retry 3 -o "$TEMP_DIR/$ARCHIVE" "$RELEASE_URL/$ARCHIVE" || \
|
|
50
|
+
fail "could not download $ARCHIVE"
|
|
51
|
+
curl -fsSL --retry 3 -o "$TEMP_DIR/checksums.txt" "$RELEASE_URL/checksums.txt" || \
|
|
52
|
+
fail "could not download checksums.txt"
|
|
53
|
+
|
|
54
|
+
CHECKSUM_LINE="$(awk -v archive="$ARCHIVE" '$2 == archive { print; exit }' "$TEMP_DIR/checksums.txt")"
|
|
55
|
+
[ -n "$CHECKSUM_LINE" ] || fail "checksums.txt does not contain $ARCHIVE"
|
|
56
|
+
|
|
57
|
+
cd "$TEMP_DIR"
|
|
58
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
59
|
+
printf '%s\n' "$CHECKSUM_LINE" | sha256sum -c - || fail "checksum verification failed"
|
|
60
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
61
|
+
printf '%s\n' "$CHECKSUM_LINE" | shasum -a 256 -c - || fail "checksum verification failed"
|
|
62
|
+
else
|
|
63
|
+
fail "sha256sum or shasum is required for checksum verification"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
tar -xzf "$ARCHIVE" -C "$TEMP_DIR" || fail "could not extract $ARCHIVE"
|
|
67
|
+
mkdir -p "$INSTALL_DIR"
|
|
68
|
+
for binary in delegate delegate-run; do
|
|
69
|
+
[ -f "$TEMP_DIR/$binary" ] || fail "release archive does not contain $binary"
|
|
70
|
+
install -m 0755 "$TEMP_DIR/$binary" "$INSTALL_DIR/$binary"
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
printf 'Installed delegate and delegate-run %s in %s\n' "$VERSION" "$INSTALL_DIR"
|
|
74
|
+
case ":${PATH:-}:" in
|
|
75
|
+
*:"$INSTALL_DIR":*) ;;
|
|
76
|
+
*) printf 'Add %s to PATH if it is not already there.\n' "$INSTALL_DIR" ;;
|
|
77
|
+
esac
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "delegation-layer",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Delegation Layer CLI and provider-agnostic agent skill installers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"delegation-layer": "scripts/install-agent-integration.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"install.sh",
|
|
11
|
+
"skills/agent-integration/SKILL.md",
|
|
12
|
+
"scripts/install-agent-integration.mjs",
|
|
13
|
+
"scripts/install-cli.mjs"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/hishamkaram/delegation-layer.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/hishamkaram/delegation-layer",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/hishamkaram/delegation-layer/issues"
|
|
25
|
+
},
|
|
26
|
+
"license": "UNLICENSED"
|
|
27
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { copyFile, lstat, mkdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const sourcePath = join(packageRoot, "skills", "agent-integration", "SKILL.md");
|
|
9
|
+
|
|
10
|
+
function usage(message) {
|
|
11
|
+
if (message) {
|
|
12
|
+
console.error(`Error: ${message}`);
|
|
13
|
+
}
|
|
14
|
+
console.error(
|
|
15
|
+
"Usage:\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]",
|
|
16
|
+
);
|
|
17
|
+
process.exitCode = 2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseArguments(argumentsList) {
|
|
21
|
+
let target;
|
|
22
|
+
let force = false;
|
|
23
|
+
|
|
24
|
+
for (let index = 0; index < argumentsList.length; index += 1) {
|
|
25
|
+
const argument = argumentsList[index];
|
|
26
|
+
if (argument === "--force") {
|
|
27
|
+
force = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (argument === "--target") {
|
|
31
|
+
target = argumentsList[index + 1];
|
|
32
|
+
index += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (argument === "--help" || argument === "-h") {
|
|
36
|
+
console.log(
|
|
37
|
+
"Usage:\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]",
|
|
38
|
+
);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
usage(`unknown argument ${argument}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!target) {
|
|
46
|
+
usage("--target is required");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!isAbsolute(target)) {
|
|
50
|
+
usage("--target must be an absolute path");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return { force, target: resolve(target) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function existingPath(path) {
|
|
57
|
+
try {
|
|
58
|
+
return await lstat(path);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error.code === "ENOENT") {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function install({ force, target }) {
|
|
68
|
+
const targetStat = await existingPath(target);
|
|
69
|
+
if (targetStat?.isSymbolicLink()) {
|
|
70
|
+
throw new Error(`target directory must not be a symlink: ${target}`);
|
|
71
|
+
}
|
|
72
|
+
if (targetStat && !targetStat.isDirectory()) {
|
|
73
|
+
throw new Error(`target must be a directory: ${target}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await mkdir(target, { recursive: true });
|
|
77
|
+
|
|
78
|
+
const destination = join(target, "SKILL.md");
|
|
79
|
+
const destinationStat = await existingPath(destination);
|
|
80
|
+
if (destinationStat?.isSymbolicLink()) {
|
|
81
|
+
throw new Error(`existing SKILL.md must not be a symlink: ${destination}`);
|
|
82
|
+
}
|
|
83
|
+
if (destinationStat && !destinationStat.isFile()) {
|
|
84
|
+
throw new Error(`existing SKILL.md is not a regular file: ${destination}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (destinationStat && !force) {
|
|
88
|
+
const [source, existing] = await Promise.all([
|
|
89
|
+
readFile(sourcePath),
|
|
90
|
+
readFile(destination),
|
|
91
|
+
]);
|
|
92
|
+
if (source.equals(existing)) {
|
|
93
|
+
console.log(`Skill already installed: ${destination}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`SKILL.md already exists; use --force to replace it: ${destination}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await copyFile(sourcePath, destination);
|
|
100
|
+
console.log(`Installed agent integration skill: ${destination}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const argumentsList = process.argv.slice(2);
|
|
104
|
+
if (argumentsList[0] === "install-cli") {
|
|
105
|
+
try {
|
|
106
|
+
const { main } = await import("./install-cli.mjs");
|
|
107
|
+
await main(argumentsList.slice(1));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`Error: ${error.message}`);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
const options = parseArguments(argumentsList);
|
|
114
|
+
if (options) {
|
|
115
|
+
try {
|
|
116
|
+
await install(options);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`Error: ${error.message}`);
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const installerPath = join(packageRoot, "install.sh");
|
|
9
|
+
|
|
10
|
+
function usage(message) {
|
|
11
|
+
if (message) {
|
|
12
|
+
console.error(`Error: ${message}`);
|
|
13
|
+
}
|
|
14
|
+
console.error(
|
|
15
|
+
"Usage: delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseArguments(argumentsList) {
|
|
20
|
+
let version;
|
|
21
|
+
let installDir;
|
|
22
|
+
|
|
23
|
+
for (let index = 0; index < argumentsList.length; index += 1) {
|
|
24
|
+
const argument = argumentsList[index];
|
|
25
|
+
if (argument === "--version") {
|
|
26
|
+
version = argumentsList[index + 1];
|
|
27
|
+
index += 1;
|
|
28
|
+
if (!version) {
|
|
29
|
+
throw new Error("--version requires a value");
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (argument === "--install-dir") {
|
|
34
|
+
installDir = argumentsList[index + 1];
|
|
35
|
+
index += 1;
|
|
36
|
+
if (!installDir) {
|
|
37
|
+
throw new Error("--install-dir requires a value");
|
|
38
|
+
}
|
|
39
|
+
if (!isAbsolute(installDir)) {
|
|
40
|
+
throw new Error("--install-dir must be an absolute path");
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (argument === "--help" || argument === "-h") {
|
|
45
|
+
usage();
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`unknown argument ${argument}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (process.platform === "win32") {
|
|
52
|
+
throw new Error("the release installer supports Linux and macOS only");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { installDir: installDir ? resolve(installDir) : undefined, version };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function main(argumentsList) {
|
|
59
|
+
const options = parseArguments(argumentsList);
|
|
60
|
+
if (!options) {
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const environment = { ...process.env };
|
|
65
|
+
if (options.version) {
|
|
66
|
+
environment.DELEGATION_LAYER_VERSION = options.version;
|
|
67
|
+
}
|
|
68
|
+
if (options.installDir) {
|
|
69
|
+
environment.DELEGATION_LAYER_INSTALL_DIR = options.installDir;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new Promise((resolvePromise, reject) => {
|
|
73
|
+
const child = spawn("sh", [installerPath], {
|
|
74
|
+
env: environment,
|
|
75
|
+
stdio: "inherit",
|
|
76
|
+
});
|
|
77
|
+
child.on("error", reject);
|
|
78
|
+
child.on("exit", (code, signal) => {
|
|
79
|
+
if (code === 0) {
|
|
80
|
+
resolvePromise();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
reject(new Error(`CLI installer exited with ${signal ?? `status ${code}`}`));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-integration
|
|
3
|
+
description: Use Delegation Layer from a provider-agnostic agent workflow, including capability discovery, bounded dispatch, terminal collection, and live authentication handling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent integration
|
|
7
|
+
|
|
8
|
+
This skill is self-contained as an operating guide. It gives an agent enough
|
|
9
|
+
information to prepare a safe run, choose a provider from observed
|
|
10
|
+
capabilities, and collect a terminal result when the host exposes the
|
|
11
|
+
Delegation Layer CLI. It applies to every provider profile and does not encode
|
|
12
|
+
provider names, release versions, or native CLI syntax.
|
|
13
|
+
|
|
14
|
+
## Preconditions
|
|
15
|
+
|
|
16
|
+
The host is responsible for installing and configuring the CLI and supervisor.
|
|
17
|
+
This skill must not install, build, upgrade, or repair either one. It may check
|
|
18
|
+
whether `delegate` is available, use a configured private supervisor
|
|
19
|
+
configuration and socket, and create separate task and state directories that
|
|
20
|
+
it is authorized to use.
|
|
21
|
+
Authentication is a separate live gate; do not invent a provider-specific
|
|
22
|
+
login check or classify a missing login as static incompatibility.
|
|
23
|
+
|
|
24
|
+
Keep the supervisor configuration, Delegation Layer state root, and task
|
|
25
|
+
workspace as separate canonical paths. Do not put credentials in a brief, task
|
|
26
|
+
option, environment value recorded by the task, or agent-visible output.
|
|
27
|
+
|
|
28
|
+
## Start safely
|
|
29
|
+
|
|
30
|
+
First check that the already-installed CLI is available, then discover compiled
|
|
31
|
+
profiles:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
command -v delegate
|
|
35
|
+
delegate help
|
|
36
|
+
delegate providers --json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If `command -v delegate` fails, stop and report that the host prerequisite is
|
|
40
|
+
missing. Do not install or build the CLI from this skill.
|
|
41
|
+
|
|
42
|
+
`providers --json` is static discovery. It lists provider IDs, supported
|
|
43
|
+
permission modes and options, and the help arguments and flags required by the
|
|
44
|
+
compiled adapters. It does not inspect the host, authenticate a provider, or
|
|
45
|
+
prove that a provider is launchable.
|
|
46
|
+
|
|
47
|
+
Choose a listed `PROFILE`, then inspect its provider-neutral contract:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
delegate capabilities --provider PROFILE --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This command is side-effect-free. It does not create state, contact Pueue, or
|
|
54
|
+
launch a provider. A successful catalog response has
|
|
55
|
+
`status: "unknown"`, `verification: "catalog"`, and
|
|
56
|
+
`live_acceptance.status: "not_run"`. Unknown here means that dispatch has not
|
|
57
|
+
run the host probe yet; it is neither a compatibility failure nor live proof.
|
|
58
|
+
|
|
59
|
+
Choose a listed `PROFILE`, ensure its provider executable is available to the
|
|
60
|
+
dispatch environment, and prepare a finite brief and two separate absolute
|
|
61
|
+
directories. Then dispatch one bounded task:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
mkdir -p "${HOME}/delegation-workspace" "${HOME}/delegation-state"
|
|
65
|
+
printf '%s\n' 'Inspect the workspace and return a short summary.' > "${HOME}/delegation-brief.txt"
|
|
66
|
+
|
|
67
|
+
delegate --root "${HOME}/delegation-state" \
|
|
68
|
+
--pueue-config /absolute/path/to/private-pueue.yml \
|
|
69
|
+
dispatch \
|
|
70
|
+
--provider PROFILE \
|
|
71
|
+
--brief "${HOME}/delegation-brief.txt" \
|
|
72
|
+
--cwd "${HOME}/delegation-workspace" \
|
|
73
|
+
--permission read-only \
|
|
74
|
+
--budget 30m \
|
|
75
|
+
--json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use the host's configured private supervisor path (or its
|
|
79
|
+
`DELEGATE_PUEUE_CONFIG` equivalent); the placeholder above is not a path to
|
|
80
|
+
create. If the supervisor or provider executable is unavailable, stop on the
|
|
81
|
+
CLI's structured error. For live acceptance, an unavailable authentication
|
|
82
|
+
prerequisite is `blocked`; preserve that evidence and never relabel it as
|
|
83
|
+
incompatible or passed.
|
|
84
|
+
|
|
85
|
+
Save the returned `task_id`. Admission records one immutable request and at
|
|
86
|
+
most one provider launch attempt; the command does not wait for completion.
|
|
87
|
+
Observe and collect with that exact ID:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
delegate --root "${HOME}/delegation-state" status TASK_ID --json
|
|
91
|
+
delegate --root "${HOME}/delegation-state" collect TASK_ID --watch 5s --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Repeat `collect` while the task is pending. Collection is observational: it
|
|
95
|
+
may wait for the bounded watch interval and clean up a validated reservation,
|
|
96
|
+
but it never launches, retries, or resumes provider work. A continuation is a
|
|
97
|
+
new task with an exact predecessor ID when the selected profile advertises
|
|
98
|
+
continuation support.
|
|
99
|
+
|
|
100
|
+
## Read the JSON contract
|
|
101
|
+
|
|
102
|
+
The capability response has this stable shape. The two arrays are
|
|
103
|
+
profile-specific and can be non-empty; empty arrays below only keep the example
|
|
104
|
+
provider-agnostic:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"schema_version": 1,
|
|
109
|
+
"command": "capabilities",
|
|
110
|
+
"capability": {
|
|
111
|
+
"contract": "runtime-capability-v1",
|
|
112
|
+
"provider": "PROFILE",
|
|
113
|
+
"status": "unknown",
|
|
114
|
+
"verification": "catalog",
|
|
115
|
+
"help_args": [],
|
|
116
|
+
"required_flags": [],
|
|
117
|
+
"reason_code": "runtime_probe_not_run",
|
|
118
|
+
"live_acceptance": {
|
|
119
|
+
"status": "not_run",
|
|
120
|
+
"authentication": "unknown",
|
|
121
|
+
"reason_code": "runtime_probe_not_run"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Interpret the fields as follows:
|
|
128
|
+
|
|
129
|
+
- `status=unknown` with `verification=catalog` is a declaration from the
|
|
130
|
+
compiled catalog.
|
|
131
|
+
- `status=ready` with `verification=runtime` appears in a dispatch response
|
|
132
|
+
only after the supervised probe observed the required behavior and exact
|
|
133
|
+
executable identity for that admission boundary.
|
|
134
|
+
- `status=unsupported` is a machine-readable refusal. Use `reason_code` and
|
|
135
|
+
the bounded error to explain it; do not try another profile without a new
|
|
136
|
+
explicit selection.
|
|
137
|
+
- `version` is diagnostic observation. Accept any valid nonempty trimmed UTF-8
|
|
138
|
+
version text. Never use a release allowlist, semantic-version comparison, or
|
|
139
|
+
web lookup as a compatibility decision. Required flags, executable identity,
|
|
140
|
+
and provider behavior decide compatibility. An unusually large version may
|
|
141
|
+
be omitted from the bounded projection.
|
|
142
|
+
- `live_acceptance` is separate from compatibility. `not_run` does not claim a
|
|
143
|
+
live test; `passed` requires authenticated provider evidence; `blocked` means
|
|
144
|
+
authentication or another prerequisite was unavailable and is neutral. A
|
|
145
|
+
blocked result must never be reported as a pass or as incompatible behavior.
|
|
146
|
+
|
|
147
|
+
Dispatch JSON contains the same `capability` projection plus separate task
|
|
148
|
+
`admission`, `liveness`, and `publication` fields. Read those fields
|
|
149
|
+
independently. Trust a terminal `outcome` only after it has been returned by
|
|
150
|
+
the validated collection path and its payload and sealed evidence descriptors
|
|
151
|
+
match the task records.
|
|
152
|
+
|
|
153
|
+
## Integration acceptance contract
|
|
154
|
+
|
|
155
|
+
Apply these criteria to every provider and every agent integration:
|
|
156
|
+
|
|
157
|
+
- required behavior is verified at the runtime boundary;
|
|
158
|
+
- arbitrary valid provider version text remains acceptable;
|
|
159
|
+
- missing flags, executable identity drift, malformed facts, and timeouts fail
|
|
160
|
+
closed;
|
|
161
|
+
- authentication is represented separately from compatibility;
|
|
162
|
+
- missing authentication is `blocked`, never a passing skip;
|
|
163
|
+
- admission is at most once and collection is observational;
|
|
164
|
+
- terminal results come from durable validated evidence; and
|
|
165
|
+
- JSON output is bounded, deterministic for observational commands, versioned,
|
|
166
|
+
and free of credentials.
|
|
167
|
+
|
|
168
|
+
Do not expose credentials, tokens, private raw provider output, or unrestricted
|
|
169
|
+
diagnostics in prompts, tool results, receipts, or task metadata. After an
|
|
170
|
+
uncertain admission or lost response, inspect the original task with
|
|
171
|
+
`status`/`collect`; do not relaunch it. Stop and report the durable state when
|
|
172
|
+
the task cannot be resolved from validated evidence.
|
|
173
|
+
|
|
174
|
+
## Completion record
|
|
175
|
+
|
|
176
|
+
An integration is complete when it can provide:
|
|
177
|
+
|
|
178
|
+
- the selected profile and original task ID;
|
|
179
|
+
- the bounded capability and dispatch JSON responses;
|
|
180
|
+
- the terminal committed or rejected outcome and validated evidence
|
|
181
|
+
descriptors; and
|
|
182
|
+
- a sanitized live-acceptance receipt marked `passed`, `failed`, or `blocked`.
|