@agentproto/runtime-profile-standard 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/files/.claude/agents/reviewer.md +31 -0
- package/files/.claude/commands/ap-swarm.md +56 -0
- package/files/.claude/examples/swarm-local.md +84 -0
- package/files/.claude/hooks/session-start.mjs +53 -0
- package/files/.claude/hooks/user-prompt-submit.mjs +124 -0
- package/files/.claude/settings.json +25 -0
- package/package.json +67 -0
- package/profile.json +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 agentproto contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @agentproto/runtime-profile-standard
|
|
2
|
+
|
|
3
|
+
Reference file-mode MultiAgentRuntime profile for the `agentproto`
|
|
4
|
+
CLI. Installs a small Claude Code scaffolding into the current
|
|
5
|
+
repository so `agentproto run-swarm` works out of the box.
|
|
6
|
+
|
|
7
|
+
**Status:** alpha.
|
|
8
|
+
|
|
9
|
+
## What it drops in
|
|
10
|
+
|
|
11
|
+
| Path | Strategy |
|
|
12
|
+
| ------------------------------------------ | --------------- |
|
|
13
|
+
| `.claude/agents/reviewer.md` | overwrite |
|
|
14
|
+
| `.claude/hooks/session-start.mjs` | overwrite (+x) |
|
|
15
|
+
| `.claude/hooks/user-prompt-submit.mjs` | overwrite (+x) |
|
|
16
|
+
| `.claude/commands/ap-swarm.md` | overwrite |
|
|
17
|
+
| `.claude/examples/swarm-local.md` | overwrite |
|
|
18
|
+
| `.claude/settings.json` | deep-merge |
|
|
19
|
+
|
|
20
|
+
The install handler records what landed in
|
|
21
|
+
`~/.agentproto/profiles/standard.json`, so re-runs are idempotent and
|
|
22
|
+
user edits aren't clobbered without `--force`.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
agentproto install runtime-profile/standard
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
After install, follow `/ap-swarm` inside a Claude Code session, or
|
|
31
|
+
copy `.claude/examples/swarm-local.md` to `.runtime/multi-agent.yaml`
|
|
32
|
+
and run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
agentproto run-swarm --manifest .runtime/multi-agent.yaml --verbose
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Scope
|
|
39
|
+
|
|
40
|
+
This profile is file-mode only: a local append-only markdown journal
|
|
41
|
+
at `.runtime/conversation.md`. Transport-bridge profiles (Slack,
|
|
42
|
+
hosted chat servers, MCP-bridged threads, …) ship as separate
|
|
43
|
+
`@<vendor>/runtime-profile-*` packages that install the same way and
|
|
44
|
+
register their own adapters with the CLI's plugin registry.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentproto/runtime-profile-standard
|
|
3
|
+
*
|
|
4
|
+
* Exports the profile manifest and the absolute path to the files
|
|
5
|
+
* tree, so the cli install handler can resolve and copy without
|
|
6
|
+
* filesystem guessing.
|
|
7
|
+
*/
|
|
8
|
+
declare const FILES_DIR: string;
|
|
9
|
+
declare const PROFILE_PATH: string;
|
|
10
|
+
type RuntimeProfileFile = {
|
|
11
|
+
readonly src: string;
|
|
12
|
+
readonly dest: string;
|
|
13
|
+
readonly strategy: "overwrite" | "preserve" | "merge-json-deep" | "append";
|
|
14
|
+
readonly executable?: boolean;
|
|
15
|
+
};
|
|
16
|
+
type RuntimeProfileManifest = {
|
|
17
|
+
readonly schema: "agentproto/runtime-profile/v1";
|
|
18
|
+
readonly slug: string;
|
|
19
|
+
readonly version: string;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly files: readonly RuntimeProfileFile[];
|
|
23
|
+
};
|
|
24
|
+
declare function loadProfileManifest(): Promise<RuntimeProfileManifest>;
|
|
25
|
+
|
|
26
|
+
export { FILES_DIR, PROFILE_PATH, type RuntimeProfileFile, type RuntimeProfileManifest, loadProfileManifest };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @agentproto/runtime-profile-standard v0.1.0-alpha
|
|
7
|
+
* Reference MultiAgentRuntime profile — files installed via
|
|
8
|
+
* `agentproto install runtime-profile/standard`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
var pkgRoot = resolve(here, "..");
|
|
13
|
+
var FILES_DIR = resolve(pkgRoot, "files");
|
|
14
|
+
var PROFILE_PATH = resolve(pkgRoot, "profile.json");
|
|
15
|
+
async function loadProfileManifest() {
|
|
16
|
+
const raw = await readFile(PROFILE_PATH, "utf8");
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { FILES_DIR, PROFILE_PATH, loadProfileManifest };
|
|
21
|
+
//# sourceMappingURL=index.mjs.map
|
|
22
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;AAYA,IAAM,IAAA,GAAO,OAAA,CAAQ,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC,CAAA;AAGnD,IAAM,OAAA,GAAU,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAA;AAE3B,IAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,EAAS,OAAO;AAC1C,IAAM,YAAA,GAAe,OAAA,CAAQ,OAAA,EAAS,cAAc;AAkB3D,eAAsB,mBAAA,GAAuD;AAC3E,EAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,YAAA,EAAc,MAAM,CAAA;AAC/C,EAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AACvB","file":"index.mjs","sourcesContent":["/**\n * @agentproto/runtime-profile-standard\n *\n * Exports the profile manifest and the absolute path to the files\n * tree, so the cli install handler can resolve and copy without\n * filesystem guessing.\n */\n\nimport { readFile } from \"node:fs/promises\"\nimport { dirname, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nconst here = dirname(fileURLToPath(import.meta.url))\n// Source layout: <pkg>/dist/index.mjs at build time; the files/\n// dir + profile.json live at <pkg>/files/ and <pkg>/profile.json.\nconst pkgRoot = resolve(here, \"..\")\n\nexport const FILES_DIR = resolve(pkgRoot, \"files\")\nexport const PROFILE_PATH = resolve(pkgRoot, \"profile.json\")\n\nexport type RuntimeProfileFile = {\n readonly src: string\n readonly dest: string\n readonly strategy: \"overwrite\" | \"preserve\" | \"merge-json-deep\" | \"append\"\n readonly executable?: boolean\n}\n\nexport type RuntimeProfileManifest = {\n readonly schema: \"agentproto/runtime-profile/v1\"\n readonly slug: string\n readonly version: string\n readonly name: string\n readonly description: string\n readonly files: readonly RuntimeProfileFile[]\n}\n\nexport async function loadProfileManifest(): Promise<RuntimeProfileManifest> {\n const raw = await readFile(PROFILE_PATH, \"utf8\")\n return JSON.parse(raw) as RuntimeProfileManifest\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Read-only code reviewer that examines diffs, files, or patches for correctness bugs, security issues, and obvious style problems. Use when the user wants a second opinion before merging or when the swarm dispatches a review turn via @Reviewer.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are Reviewer, a focused code review participant.
|
|
9
|
+
|
|
10
|
+
Your job: read the code that has been changed and report concrete, high-confidence findings. Nothing else.
|
|
11
|
+
|
|
12
|
+
What to look for, in order of priority:
|
|
13
|
+
|
|
14
|
+
1. **Correctness bugs** — off-by-one errors, null/undefined dereferences, missing await on promises, incorrect conditionals, broken control flow.
|
|
15
|
+
2. **Security issues** — injection vectors (SQL/command/HTML), unvalidated user input crossing trust boundaries, secrets in logs or error messages, missing auth checks.
|
|
16
|
+
3. **API contract drift** — public function signatures changed without updating call sites, schema changes that break consumers.
|
|
17
|
+
4. **Resource leaks** — file handles, connections, or processes that aren't cleaned up on the error path.
|
|
18
|
+
5. **Obvious style issues only** — naming that misleads, dead code, commented-out blocks. Skip subjective preferences.
|
|
19
|
+
|
|
20
|
+
Format your reply:
|
|
21
|
+
|
|
22
|
+
- Start with a one-line verdict: `LGTM`, `Minor issues`, or `Blocking concerns`.
|
|
23
|
+
- Then bullet each finding with `file:line — <issue>`.
|
|
24
|
+
- If LGTM, you can stop after the verdict.
|
|
25
|
+
|
|
26
|
+
Hard rules:
|
|
27
|
+
|
|
28
|
+
- Read-only. Never edit files. Never propose patches in code blocks unless explicitly asked.
|
|
29
|
+
- One reply per turn — concise. Don't write essays.
|
|
30
|
+
- If the diff is empty or you can't see what changed, say so plainly and ask what to look at. Don't guess.
|
|
31
|
+
- Don't acknowledge the trigger ("Thanks for the ping!"). Get to the verdict.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run a MultiAgentRuntime swarm in file mode — local journal at .runtime/conversation.md, no network
|
|
3
|
+
argument-hint: [--manifest=<path>] [--once] [--verbose]
|
|
4
|
+
allowed-tools: Read, Write, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are starting a local-mode MultiAgentRuntime swarm using `@agentproto/agent-runtime` via the `pnpm agentproto run-swarm` verb.
|
|
8
|
+
|
|
9
|
+
## Quick path
|
|
10
|
+
|
|
11
|
+
1. If `.runtime/multi-agent.yaml` does not exist, copy the template:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
mkdir -p .runtime && cp .claude/examples/swarm-local.md .runtime/multi-agent.yaml
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then read it back and tell the user what's wired (participants, substrate kind, state dir).
|
|
18
|
+
|
|
19
|
+
2. Build the agent-runtime + cli packages if they haven't been built yet:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm --filter @agentproto/agent-runtime build && pnpm --filter @agentproto/cli build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You only need this on first run or after pulling changes — `tsup --watch` (from `pnpm dev`) keeps it fresh.
|
|
26
|
+
|
|
27
|
+
3. Start the swarm. Pass through any user-supplied args (`--once`, `--interval`, `--verbose`). Default:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm agentproto run-swarm --manifest .runtime/multi-agent.yaml --verbose
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This is a long-running loop. Suggest the user open a separate terminal for it, OR run with `--once` to do a single dispatch cycle and exit.
|
|
34
|
+
|
|
35
|
+
4. Seeding the journal: tell the user that participant turns only fire on `@Mention` of a known participant. Show them how to add a seed turn manually:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cat <<'EOF' >> .runtime/conversation.md
|
|
39
|
+
=== TURN id=t_seed participant=user ts=$(date -u +%FT%TZ) ===
|
|
40
|
+
@Reviewer please look at <file or topic>
|
|
41
|
+
EOF
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or just edit `.runtime/conversation.md` directly in the editor.
|
|
45
|
+
|
|
46
|
+
## Notes
|
|
47
|
+
|
|
48
|
+
- The journal at `.runtime/conversation.md` is the source of truth for file mode. It's gitignored. Resetting = deleting the file.
|
|
49
|
+
- Per-participant scratch state lives at `.runtime/state/<participantId>.json`. Also gitignored.
|
|
50
|
+
- Other substrates (chat servers, MCP bridges, …) ship as separate plugin packages. Register them via `--plugin <module-id>` on `run-swarm`, or list them under `plugins[]` in `~/.agentproto/config.json`.
|
|
51
|
+
|
|
52
|
+
## Errors to watch
|
|
53
|
+
|
|
54
|
+
- "unknown substrate kind '<kind>'. Registered kinds: […]" → the manifest's `substrate.kind` has no matching factory. Either fix the kind, or install the plugin that provides it and pass `--plugin <module-id>`.
|
|
55
|
+
- "manifest file not found" → check the path; the verb resolves it from the current cwd.
|
|
56
|
+
- Spawned `claude` returning non-zero → the participant agent's command failed. Check stderr in the swarm process output.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema: agentruntimes/v1
|
|
3
|
+
kind: MultiAgentRuntime
|
|
4
|
+
id: local-swarm
|
|
5
|
+
participants:
|
|
6
|
+
- id: reviewer
|
|
7
|
+
executor: agent-cli
|
|
8
|
+
displayName: Reviewer
|
|
9
|
+
role: ../../.claude/agents/reviewer.md
|
|
10
|
+
substrate:
|
|
11
|
+
kind: file
|
|
12
|
+
path: ./conversation.md
|
|
13
|
+
dispatcher:
|
|
14
|
+
kind: mention
|
|
15
|
+
state:
|
|
16
|
+
kind: fs
|
|
17
|
+
dir: ./state
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Local swarm — file substrate
|
|
21
|
+
|
|
22
|
+
One participant (Reviewer) coordinated through an append-only markdown
|
|
23
|
+
journal at `.runtime/conversation.md`. No network, no remote services —
|
|
24
|
+
useful as a starting template you copy and extend with your own
|
|
25
|
+
participants.
|
|
26
|
+
|
|
27
|
+
## How to run
|
|
28
|
+
|
|
29
|
+
1. Copy this file to `.runtime/multi-agent.yaml` (the `.runtime/` dir is
|
|
30
|
+
gitignored, so manifests there are per-developer):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
mkdir -p .runtime && cp .claude/examples/swarm-local.md .runtime/multi-agent.yaml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Start the swarm in one terminal:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
agentproto run-swarm --manifest .runtime/multi-agent.yaml --verbose
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
3. In another terminal, seed the journal with a turn that mentions one
|
|
43
|
+
of the participants:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cat <<'EOF' >> .runtime/conversation.md
|
|
47
|
+
=== TURN id=t_seed participant=user ts=2026-05-23T10:00:00Z ===
|
|
48
|
+
@Reviewer please take a look at <file or topic>
|
|
49
|
+
EOF
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. The swarm process polls every 2s. Within one cycle it will detect the
|
|
53
|
+
@Reviewer mention, spawn `claude --print --output-format=json` with
|
|
54
|
+
Reviewer's role + the recent transcript, and append the reply as a
|
|
55
|
+
new turn in the journal.
|
|
56
|
+
|
|
57
|
+
## Adding participants
|
|
58
|
+
|
|
59
|
+
Each participant under `participants:` needs an `id`, an `executor`
|
|
60
|
+
kind, a `displayName` (used for `@Name` detection), and an optional
|
|
61
|
+
`role` (inline string or relative path to a markdown file).
|
|
62
|
+
|
|
63
|
+
The reference `agent-cli` executor spawns `claude` by default; the
|
|
64
|
+
`role` content is fed to it on stdin along with the recent transcript.
|
|
65
|
+
Drop in additional `.claude/agents/*.md` files and reference them
|
|
66
|
+
under `role:` to add specialists.
|
|
67
|
+
|
|
68
|
+
## Other substrates
|
|
69
|
+
|
|
70
|
+
`substrate.kind` resolves through the agentproto CLI's adapter
|
|
71
|
+
registry. Built-in: `file`. Third-party substrates (chat servers,
|
|
72
|
+
MCP bridges, …) register themselves when loaded — pass
|
|
73
|
+
`--plugin <module-id>` to `run-swarm`, or list them under `plugins[]`
|
|
74
|
+
in `~/.agentproto/config.json`.
|
|
75
|
+
|
|
76
|
+
## Path conventions
|
|
77
|
+
|
|
78
|
+
Paths in this manifest resolve relative to the manifest file itself.
|
|
79
|
+
- `role: ../../.claude/agents/reviewer.md` works when the manifest is at
|
|
80
|
+
`.runtime/multi-agent.yaml`.
|
|
81
|
+
- `substrate.path: ./conversation.md` resolves to `.runtime/conversation.md`.
|
|
82
|
+
- `state.dir: ./state` resolves to `.runtime/state/`.
|
|
83
|
+
|
|
84
|
+
If you put the manifest somewhere else, adjust the relative paths.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SessionStart hook — when a Claude Code session opens in this repo,
|
|
4
|
+
* if a MultiAgentRuntime journal exists at `.runtime/conversation.md`,
|
|
5
|
+
* inject the last few turns as `additionalContext` so Claude picks up
|
|
6
|
+
* mid-swarm without losing thread.
|
|
7
|
+
*
|
|
8
|
+
* No-ops if the journal file is absent.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile, stat } from "node:fs/promises"
|
|
12
|
+
import { resolve } from "node:path"
|
|
13
|
+
|
|
14
|
+
const JOURNAL_PATH = resolve(process.cwd(), ".runtime/conversation.md")
|
|
15
|
+
const TAIL_BYTES = 4000
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
// Drain stdin (event payload) but ignore — we don't need its fields.
|
|
19
|
+
// SessionStart doesn't carry conversation-specific data.
|
|
20
|
+
for await (const _chunk of process.stdin) {
|
|
21
|
+
// discard
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await stat(JOURNAL_PATH)
|
|
26
|
+
} catch {
|
|
27
|
+
return // no journal, nothing to inject
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const raw = await readFile(JOURNAL_PATH, "utf8")
|
|
31
|
+
const tail = raw.length > TAIL_BYTES ? raw.slice(-TAIL_BYTES) : raw
|
|
32
|
+
if (!tail.trim()) return
|
|
33
|
+
|
|
34
|
+
const additionalContext = [
|
|
35
|
+
"## Multi-Agent Runtime journal — recent turns",
|
|
36
|
+
"",
|
|
37
|
+
"A `pnpm agentproto run-swarm` swarm may be running against `.runtime/conversation.md` in this repo. Below is the tail of the journal so this Claude session has thread continuity.",
|
|
38
|
+
"",
|
|
39
|
+
"```",
|
|
40
|
+
tail.trimEnd(),
|
|
41
|
+
"```",
|
|
42
|
+
"",
|
|
43
|
+
"If the user asks about swarm activity, refer to this journal. If they want to seed a new turn, they can either edit the file directly or use `/ap-swarm` to learn the seeding command.",
|
|
44
|
+
].join("\n")
|
|
45
|
+
|
|
46
|
+
process.stdout.write(JSON.stringify({ additionalContext }))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch((err) => {
|
|
50
|
+
// Never crash the session on a hook failure — log and continue.
|
|
51
|
+
process.stderr.write(`session-start hook: ${err?.message ?? String(err)}\n`)
|
|
52
|
+
process.exit(0)
|
|
53
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UserPromptSubmit hook — when the user submits a prompt, scan for
|
|
4
|
+
* @-mentions of any participant declared in `.runtime/multi-agent.yaml`.
|
|
5
|
+
* If a known participant is mentioned, inject a routing hint via
|
|
6
|
+
* `additionalContext` so Claude knows the swarm may pick this up.
|
|
7
|
+
*
|
|
8
|
+
* The hook does NOT itself route to the swarm. The swarm is a separate
|
|
9
|
+
* process polling the journal. This hook just makes the Claude session
|
|
10
|
+
* aware of the mention so it can mirror it into the journal if the user
|
|
11
|
+
* is in file-mode.
|
|
12
|
+
*
|
|
13
|
+
* The mention parser below is INLINED AT BUILD TIME from
|
|
14
|
+
* `@agentproto/agent-runtime/util/mention-parser`. Edit the parser
|
|
15
|
+
* there, not here — the next build regenerates this hook with the
|
|
16
|
+
* latest source.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFile, stat } from "node:fs/promises"
|
|
20
|
+
import { resolve } from "node:path"
|
|
21
|
+
|
|
22
|
+
const MANIFEST_PATH = resolve(process.cwd(), ".runtime/multi-agent.yaml")
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
// Read event JSON from stdin.
|
|
26
|
+
const chunks = []
|
|
27
|
+
for await (const chunk of process.stdin) chunks.push(chunk)
|
|
28
|
+
let event = {}
|
|
29
|
+
try {
|
|
30
|
+
event = JSON.parse(Buffer.concat(chunks).toString("utf8"))
|
|
31
|
+
} catch {
|
|
32
|
+
return // malformed event — bail silently
|
|
33
|
+
}
|
|
34
|
+
const prompt = typeof event?.prompt === "string" ? event.prompt : ""
|
|
35
|
+
if (!prompt) return
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await stat(MANIFEST_PATH)
|
|
39
|
+
} catch {
|
|
40
|
+
return // no active swarm manifest
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let participants = []
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(MANIFEST_PATH, "utf8")
|
|
46
|
+
participants = extractParticipants(raw)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
process.stderr.write(`user-prompt-submit hook: manifest parse failed — ${err?.message ?? err}\n`)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mentioned = participants.filter((p) => textContainsMention(prompt, p.name))
|
|
53
|
+
if (mentioned.length === 0) return
|
|
54
|
+
|
|
55
|
+
const list = mentioned.map((p) => `- \`${p.name}\` (id: ${p.id})`).join("\n")
|
|
56
|
+
const additionalContext = [
|
|
57
|
+
"## Multi-Agent Runtime — mention detected in prompt",
|
|
58
|
+
"",
|
|
59
|
+
`The user's prompt mentions ${mentioned.length} participant${mentioned.length === 1 ? "" : "s"} declared in \`.runtime/multi-agent.yaml\`:`,
|
|
60
|
+
"",
|
|
61
|
+
list,
|
|
62
|
+
"",
|
|
63
|
+
"If a `pnpm agentproto run-swarm` process is polling the journal, it will pick this up on its next cycle. If the user wants the mention to flow to those participants, mirror the prompt into `.runtime/conversation.md` as a `user` turn (see `/ap-swarm` for the seed format). Otherwise treat the mention as in-session context only.",
|
|
64
|
+
].join("\n")
|
|
65
|
+
|
|
66
|
+
process.stdout.write(JSON.stringify({ additionalContext }))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── BEGIN inlined from @agentproto/agent-runtime/util/mention-parser ──
|
|
70
|
+
// ── DO NOT EDIT — regenerated by scripts/build-hooks.mjs ──
|
|
71
|
+
/** @param {string} text @param {string} name @returns {boolean} */
|
|
72
|
+
function textContainsMention(text, name) {
|
|
73
|
+
if (text.includes(`@${name}`)) return true
|
|
74
|
+
if (name.includes(" ")) {
|
|
75
|
+
const firstName = name.split(" ")[0]?.trim()
|
|
76
|
+
if (!firstName) return false
|
|
77
|
+
const regex = new RegExp(`@${firstName}(?!\\w)`, "i")
|
|
78
|
+
return regex.test(text)
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
// ── END inlined ──
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Minimal frontmatter parser for the participants list. The hook script can't
|
|
86
|
+
* resolve gray-matter at hook-execution time (.claude/ has no node_modules),
|
|
87
|
+
* and the manifest format is constrained enough that a hand-rolled extractor
|
|
88
|
+
* is fine. If the manifest grows complex, switch to `pnpm exec node ...`
|
|
89
|
+
* so module resolution finds the workspace install.
|
|
90
|
+
*
|
|
91
|
+
* Returns [{ id, name }] from a participants list shaped like:
|
|
92
|
+
*
|
|
93
|
+
* participants:
|
|
94
|
+
* - id: reviewer
|
|
95
|
+
* displayName: Reviewer
|
|
96
|
+
* - id: migration-writer
|
|
97
|
+
* displayName: MigrationWriter
|
|
98
|
+
*/
|
|
99
|
+
function extractParticipants(raw) {
|
|
100
|
+
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw)
|
|
101
|
+
if (!fmMatch) return []
|
|
102
|
+
const fm = fmMatch[1]
|
|
103
|
+
|
|
104
|
+
const listMatch = /^participants:\s*\n([\s\S]*?)(?=^[a-zA-Z][a-zA-Z0-9_-]*:|\Z)/m.exec(fm)
|
|
105
|
+
if (!listMatch) return []
|
|
106
|
+
const block = listMatch[1]
|
|
107
|
+
|
|
108
|
+
const out = []
|
|
109
|
+
const entries = block.split(/\n(?=\s*-\s)/)
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
const idMatch = /\bid:\s*([^\n#]+)/.exec(entry)
|
|
112
|
+
const nameMatch = /\bdisplayName:\s*([^\n#]+)/.exec(entry)
|
|
113
|
+
if (!idMatch) continue
|
|
114
|
+
const id = idMatch[1].trim()
|
|
115
|
+
const name = (nameMatch?.[1] ?? id).trim()
|
|
116
|
+
if (id) out.push({ id, name })
|
|
117
|
+
}
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main().catch((err) => {
|
|
122
|
+
process.stderr.write(`user-prompt-submit hook: ${err?.message ?? String(err)}\n`)
|
|
123
|
+
process.exit(0)
|
|
124
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": ".claude/hooks/session-start.mjs"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": ".claude/hooks/user-prompt-submit.mjs"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentproto/runtime-profile-standard",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "@agentproto/runtime-profile-standard — reference file-mode MultiAgentRuntime profile. Drops a Claude Code reviewer sub-agent, session-start + user-prompt-submit hooks, an `/ap-swarm` slash command, and an example local swarm manifest into a repo so `agentproto run-swarm` works out of the box. Installed via `agentproto install runtime-profile/standard`.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentproto",
|
|
7
|
+
"agent-runtime",
|
|
8
|
+
"runtime-profile",
|
|
9
|
+
"claude-code"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://agentproto.sh",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/agentproto/ts",
|
|
15
|
+
"directory": "packages/runtime-profile-standard"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/agentproto/ts/issues"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.mjs",
|
|
23
|
+
"module": "dist/index.mjs",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"default": "./dist/index.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./profile.json": "./profile.json",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"files",
|
|
37
|
+
"profile.json",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.6.2",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vitest": "^3.2.4",
|
|
50
|
+
"@agentproto/tooling": "0.1.0-alpha.0",
|
|
51
|
+
"@agentproto/agent-runtime": "0.1.0-alpha.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.9.0"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"predev": "node scripts/build-hooks.mjs",
|
|
58
|
+
"dev": "tsup --watch",
|
|
59
|
+
"prebuild": "node scripts/build-hooks.mjs",
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"clean": "rm -rf dist files/.claude/hooks/user-prompt-submit.mjs",
|
|
62
|
+
"check-types": "tsc --noEmit",
|
|
63
|
+
"test": "vitest run --passWithNoTests",
|
|
64
|
+
"test:watch": "vitest",
|
|
65
|
+
"build:hooks": "node scripts/build-hooks.mjs"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/profile.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "agentproto/runtime-profile/v1",
|
|
3
|
+
"slug": "runtime-profile/standard",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"name": "Standard MultiAgentRuntime profile",
|
|
6
|
+
"description": "Reference file-mode setup: an example reviewer sub-agent, session-start + user-prompt-submit Claude Code hooks, and an `/ap-swarm` slash command. Composes with the @agentproto/agent-runtime kernel and the @agentproto/cli `run-swarm` verb. Transport-bridge profiles (Slack, MCP servers, etc.) ship as separate `@<vendor>/runtime-profile-*` packages.",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "src": ".claude/agents/reviewer.md", "dest": ".claude/agents/reviewer.md", "strategy": "overwrite" },
|
|
9
|
+
{ "src": ".claude/hooks/session-start.mjs", "dest": ".claude/hooks/session-start.mjs", "strategy": "overwrite", "executable": true },
|
|
10
|
+
{ "src": ".claude/hooks/user-prompt-submit.mjs", "dest": ".claude/hooks/user-prompt-submit.mjs", "strategy": "overwrite", "executable": true },
|
|
11
|
+
{ "src": ".claude/commands/ap-swarm.md", "dest": ".claude/commands/ap-swarm.md", "strategy": "overwrite" },
|
|
12
|
+
{ "src": ".claude/examples/swarm-local.md", "dest": ".claude/examples/swarm-local.md", "strategy": "overwrite" },
|
|
13
|
+
{ "src": ".claude/settings.json", "dest": ".claude/settings.json", "strategy": "merge-json-deep" }
|
|
14
|
+
]
|
|
15
|
+
}
|