feat(dna): provider+model agent DNA; kei primary; smoke 4/5 + RULE 0.14 hang fix (#46)
Mirror of keigit e4980f6a. Multi-LLM DNA + Recombobulating async fix.
This commit is contained in:
parent
ef7e695227
commit
a6a540a45f
7 changed files with 335 additions and 93 deletions
|
|
@ -45,6 +45,12 @@ fn write_frontmatter(m: &Manifest, out: &mut String) {
|
|||
out.push_str(&format!("description: {}\n", desc.trim()));
|
||||
out.push_str(&format!("tools: {}\n", m.tools.join(", ")));
|
||||
out.push_str(&format!("model: {}\n", m.model));
|
||||
// v0.39: optional provider for DNA-resolved kei agent dispatch.
|
||||
if let Some(prov) = &m.provider {
|
||||
if !prov.is_empty() {
|
||||
out.push_str(&format!("provider: {}\n", prov));
|
||||
}
|
||||
}
|
||||
out.push_str("---\n\n");
|
||||
out.push_str(&format!(
|
||||
"<!-- GENERATED by _assembler (Rust) from _manifests/{}.toml — DO NOT EDIT. Edit the manifest. -->\n\n",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ pub struct Manifest {
|
|||
pub description: String,
|
||||
pub tools: Vec<String>,
|
||||
pub model: String,
|
||||
/// v0.39 (multi-CLI): optional LLM provider this agent prefers when invoked
|
||||
/// via `kei agent <name>`. Values: claude / grok / agy / copilot / kimi /
|
||||
/// codex. Empty / missing → DNA resolver falls back to ~/.claude/config/
|
||||
/// primary.toml, then to claude. Affects `kei run-via` / `kei agent`
|
||||
/// dispatch; does NOT change Claude Code's in-session model.
|
||||
#[serde(default)]
|
||||
pub provider: Option<String>,
|
||||
pub role: String,
|
||||
pub blocks: Vec<String>,
|
||||
/// v0.16 (phase 5): agent substrate role. When present, assembler loads
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ homepage = "https://github.com/github/copilot-cli"
|
|||
|
||||
[backend.kimi]
|
||||
bin = "kimi"
|
||||
prompt_flag = "stdin"
|
||||
notes = "Moonshot Kimi CLI — primarily TUI/ACP; non-interactive via stdin"
|
||||
prompt_flag = "tui-only"
|
||||
notes = "Moonshot Kimi CLI — TUI-ONLY (smoke 2026-05-26). Headless requires ACP client; launcher saves prompt to tmpfile + opens TUI for paste."
|
||||
homepage = "https://moonshotai.github.io/kimi-cli/"
|
||||
|
||||
[backend.codex]
|
||||
|
|
|
|||
26
bin/kei
26
bin/kei
|
|
@ -10,10 +10,12 @@
|
|||
# kei --status # status only, don't launch claude
|
||||
# kei message ... # inter-session mailbox (send/inbox/list) — see kei-message.sh
|
||||
# kei configure # re-pick stack profile + opt-in hook packs
|
||||
# kei run-via <backend> <agent> "<task>"
|
||||
# # invoke a KeiSeiKit agent via an external LLM CLI
|
||||
# kei agent <name> "<task>" # invoke agent, backend from DNA → primary
|
||||
# kei agent --on=<backend> <name> "<task>" # override backend
|
||||
# kei run-via <backend> <name> "<task>" # invoke agent on explicit backend
|
||||
# # backends: claude grok agy copilot kimi codex
|
||||
# # `kei run-via list` shows install status
|
||||
# # `kei run-via list` shows install status + agents
|
||||
# kei primary [<backend>] # get/set primary LLM provider (DNA fallback)
|
||||
# kei [args...] # splash → claude args... (forwarded verbatim)
|
||||
#
|
||||
# The splash shows: substrate version, agent count, last sleep run,
|
||||
|
|
@ -24,8 +26,12 @@
|
|||
set -e
|
||||
|
||||
# --- subcommand dispatch (before splash) ---------------------------------
|
||||
# `kei message ...` → mailbox CLI; `kei configure` → hook/stack re-picker;
|
||||
# `kei run-via ...` → invoke agent through external LLM CLI; rest = launch.
|
||||
# `kei message ...` → mailbox CLI
|
||||
# `kei configure` → hook/stack re-picker
|
||||
# `kei agent ...` → DNA-resolved agent (manifest provider → primary → claude)
|
||||
# `kei run-via ...` → explicit-backend agent invocation
|
||||
# `kei primary ...` → get/set primary LLM provider
|
||||
# rest = splash + launch claude (legacy primary).
|
||||
case "${1:-}" in
|
||||
message|msg|m)
|
||||
shift
|
||||
|
|
@ -35,10 +41,18 @@ case "${1:-}" in
|
|||
shift
|
||||
exec "$HOME/.claude/scripts/kei-configure.sh" "$@"
|
||||
;;
|
||||
run-via|via|run|agent-via)
|
||||
agent)
|
||||
shift
|
||||
exec "$HOME/.claude/scripts/kei-agent-cli.sh" "$@"
|
||||
;;
|
||||
run-via|via|agent-via)
|
||||
shift
|
||||
exec "$HOME/.claude/scripts/kei-agent-cli.sh" "$@"
|
||||
;;
|
||||
primary)
|
||||
shift
|
||||
exec "$HOME/.claude/scripts/kei-agent-cli.sh" primary "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- args ----------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,53 +1,99 @@
|
|||
# Multi-CLI agent invocation
|
||||
|
||||
> *Cross-LLM agent execution. Same agent definition, different backend.*
|
||||
> *Same DNA, swap the brain. KeiSeiKit is no longer Claude-Code-only.*
|
||||
|
||||
KeiSeiKit agents are markdown files. Any LLM CLI that takes a prompt can
|
||||
host them — `kei run-via` is the launcher that bridges them.
|
||||
|
||||
## Backends
|
||||
|
||||
Registered in `_primitives/cli-backends.toml` (SSoT). Installed locally
|
||||
via your own subscription / package manager:
|
||||
|
||||
| Backend | CLI binary | Non-interactive flag | Native `--agent` | Notes |
|
||||
|----------|-----------|----------------------|------------------|-------|
|
||||
| claude | `claude` | `-p` | yes | Claude Code (Anthropic) |
|
||||
| grok | `grok` | `--print` | yes | xAI Grok Build TUI |
|
||||
| agy | `agy` | `--print` | no | Google Antigravity (alias: `antigravity`) |
|
||||
| copilot | `copilot` | `--prompt` | no | GitHub Copilot CLI (`@github/copilot`) |
|
||||
| kimi | `kimi` | stdin | no | Moonshot Kimi (primarily TUI/ACP) |
|
||||
| codex | `codex` | `-p` | no | OpenAI Codex (register-only) |
|
||||
|
||||
Run `kei run-via list` to see which are installed on the current machine
|
||||
and to list available agent names.
|
||||
|
||||
## Usage
|
||||
host them. Three call shapes:
|
||||
|
||||
```bash
|
||||
# Invoke the 'critic' agent through Grok with a task:
|
||||
kei run-via grok critic "review src/auth.rs for variant analysis"
|
||||
kei agent <name> "<task>" # DNA-resolved (manifest → primary → claude)
|
||||
kei agent --on=<backend> <name> "<task>" # override DNA
|
||||
kei run-via <backend> <name> "<task>" # explicit backend (no DNA lookup)
|
||||
```
|
||||
|
||||
# Same agent, different backend:
|
||||
kei run-via agy critic "review src/auth.rs"
|
||||
kei run-via copilot critic "review src/auth.rs"
|
||||
kei run-via claude critic "review src/auth.rs"
|
||||
## Backends — smoke-tested 2026-05-26
|
||||
|
||||
# Point at an arbitrary agent .md (not in ~/.claude/agents/):
|
||||
kei run-via grok --file=/tmp/my-agent.md "do the thing"
|
||||
| Backend | CLI | Flag | Smoke | Notes |
|
||||
|----------|-----------|--------------|-------|-------|
|
||||
| claude | `claude` | `-p` | ✅ | Claude Code, native `--agent` flag |
|
||||
| grok | `grok` | `--print` | ✅ | xAI Grok Build TUI, native `--agent` flag |
|
||||
| agy | `agy` | `--print` | ✅ | Google Antigravity (Gemini models). Alias: `antigravity` |
|
||||
| copilot | `copilot` | `--prompt` | ✅ | GitHub Copilot CLI (`@github/copilot`) |
|
||||
| kimi | `kimi` | TUI-only | ⚠ | No print mode — launcher saves prompt to tmpfile + opens TUI for paste. `kimi acp` JSON-RPC integration is future work. |
|
||||
| codex | `codex` | `-p` | — | OpenAI Codex (register-only; not installed locally) |
|
||||
|
||||
# Backend's native --agent flag (grok/claude only):
|
||||
KEI_NATIVE_AGENT=1 kei run-via grok critic "review src/auth.rs"
|
||||
Run `kei run-via list` to see installed backends, current primary, and agent names.
|
||||
|
||||
## DNA — agent prefers a provider
|
||||
|
||||
Add `provider` to the agent manifest:
|
||||
|
||||
```toml
|
||||
# _manifests/my-agent.toml
|
||||
name = "my-agent"
|
||||
provider = "grok" # preferred backend; optional
|
||||
model = "grok-2" # advisory; informs choice but not yet sent through
|
||||
```
|
||||
|
||||
The assembler emits it into frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-agent
|
||||
provider: grok
|
||||
---
|
||||
```
|
||||
|
||||
Resolution order (each falls through if previous returns nothing):
|
||||
1. `--on=<backend>` flag on the command line
|
||||
2. `provider:` field in agent manifest
|
||||
3. `~/.claude/config/primary.toml` (set via `kei primary <backend>`)
|
||||
4. Default: `claude`
|
||||
|
||||
## Primary — your default LLM
|
||||
|
||||
```bash
|
||||
kei primary # show current primary (and fallback)
|
||||
kei primary grok # set default to Grok
|
||||
kei primary claude # back to Claude Code
|
||||
```
|
||||
|
||||
`kei primary` writes `~/.claude/config/primary.toml`. Any agent without
|
||||
its own `provider:` field will resolve to this. This is the lever to
|
||||
"swap out Claude Code as the primary shell" — set primary to grok, and
|
||||
every `kei agent <name>` runs on Grok.
|
||||
|
||||
## Usage examples
|
||||
|
||||
```bash
|
||||
# DNA mode (manifest's provider, or primary, or claude):
|
||||
kei agent critic "review src/auth.rs"
|
||||
|
||||
# Override DNA — try the same agent on a different model for a second opinion:
|
||||
kei agent --on=grok critic "review src/auth.rs"
|
||||
kei agent --on=agy critic "review src/auth.rs"
|
||||
kei agent --on=copilot critic "review src/auth.rs"
|
||||
|
||||
# Explicit backend, no DNA lookup (legacy):
|
||||
kei run-via grok critic "review src/auth.rs"
|
||||
|
||||
# Point at an arbitrary agent file:
|
||||
kei agent --on=grok --file=/tmp/my-agent.md "do the thing"
|
||||
|
||||
# Native --agent flag (grok/claude only):
|
||||
KEI_NATIVE_AGENT=1 kei agent critic "review src/auth.rs"
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. Reads `~/.claude/agents/<agent-name>.md` (assembler-generated prompt).
|
||||
2. Strips YAML frontmatter.
|
||||
3. Composes with task as: `<agent prompt>\n\n---\n\nTASK FOR THIS RUN:\n<task>`.
|
||||
4. Execs the backend's non-interactive CLI with the composed prompt.
|
||||
1. Resolves backend from DNA (see above).
|
||||
2. Reads `~/.claude/agents/<agent-name>.md` (assembler-generated prompt).
|
||||
3. Strips YAML frontmatter.
|
||||
4. Composes with task: `<agent prompt>\n\n---\n\nTASK FOR THIS RUN:\n<task>`.
|
||||
5. Execs the backend's non-interactive CLI with the composed prompt.
|
||||
|
||||
No agent file is modified. No new tokens are issued. Subscription
|
||||
No agent file is modified. No new tokens are issued — subscription
|
||||
authentication is whatever each CLI uses (its own login / config dir).
|
||||
|
||||
## When to use each
|
||||
|
|
@ -62,18 +108,40 @@ strengths; the substrate is agnostic about which you pick. Pick by:
|
|||
- **Independent second opinion** — same agent, different model, see if
|
||||
conclusions diverge.
|
||||
|
||||
## Rule enforcement caveat (READ THIS)
|
||||
|
||||
KeiSeiKit hooks (`numeric-claims-guard`, `citation-verify`, `no-github-push`,
|
||||
`safety-guard`, `push-to-main`, etc.) are **Claude Code-side**:
|
||||
`PreToolUse:Bash` / `:Edit` / `:Write` events that fire inside Claude Code's
|
||||
process. They do **not** propagate to grok / agy / copilot / kimi.
|
||||
|
||||
That means:
|
||||
- **Prompt-level rules** (the agent's instructions inside the `.md`) DO
|
||||
carry through — the agent reads Constructor Pattern, Evidence Grading,
|
||||
No Hallucination, etc. as part of its system prompt on any backend.
|
||||
- **Tool-level enforcement** (hard-deny on `git push github.com`,
|
||||
citation guard, etc.) only applies on the **claude** backend. Other
|
||||
backends' tool surfaces are governed by THEIR own hooks/policies.
|
||||
|
||||
If you need true rule-enforcement on a non-claude backend, the path is
|
||||
the **MCP server** (`_primitives/_rust/kei-mcp/`): registers KeiSeiKit
|
||||
primitives as MCP tools that the other CLI invokes. Tool-side policies
|
||||
travel with the MCP wrapper, not with the CLI.
|
||||
|
||||
## Adding a new backend
|
||||
|
||||
1. Add a `[backend.<name>]` table to `_primitives/cli-backends.toml`.
|
||||
2. Add a case arm in `scripts/kei-agent-cli.sh` `backend_bin()` and
|
||||
`backend_invoke()` for the new CLI's print-flag.
|
||||
3. Add a row to the table above.
|
||||
3. Add a row to the smoke-test table above (state PASS/FAIL/PARTIAL).
|
||||
|
||||
## What it is NOT
|
||||
|
||||
- Not a router — picks no backend for you; you ask, it dispatches.
|
||||
- Not a router — picks no backend for you; you (or DNA) ask, it dispatches.
|
||||
- Not a federation — each backend runs independently with its own
|
||||
context; there is no cross-backend state.
|
||||
- Not a rule-enforcement layer — hooks only fire on the claude backend
|
||||
(see caveat above). For non-claude rule enforcement use MCP server.
|
||||
- Not a wrapper around the backend's tool surface — what the CLI can
|
||||
do (Bash, file edits, MCP, etc.) is determined by that CLI, not
|
||||
KeiSeiKit. The substrate only ships the prompt.
|
||||
|
|
|
|||
|
|
@ -36,36 +36,67 @@ if [ -n "$transcript" ] && [ -f "$transcript" ]; then
|
|||
cp -f "$transcript" "$dest" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Best-effort ingest — advisory only; never blocks the session from ending.
|
||||
# RECURRENCE FIX 2026-05-26: 18MB+ transcripts caused 4-minute "Recombobulating…"
|
||||
# hangs at session end. The three heavy ops below now run async-detached:
|
||||
# hook returns immediately, ingest / scan / sync grind in background.
|
||||
# Raw JSONL is already saved sync (line 36) — no data loss; only the
|
||||
# index/embedding step is deferred. kei-memory ingest is idempotent on
|
||||
# session_id so partial runs are safe.
|
||||
|
||||
bg_log="${HOME}/.claude/memory/traces/session-end.bg.log"
|
||||
mkdir -p "$(dirname "$bg_log")" 2>/dev/null || true
|
||||
|
||||
# Portable timeout (macOS has no `timeout` / `gtimeout` by default).
|
||||
# Fallback: perl alarm. Final fallback: no timeout (rely on detach).
|
||||
kei_with_timeout() {
|
||||
secs="$1"; shift
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$secs" "$@"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
gtimeout "$secs" "$@"
|
||||
elif command -v perl >/dev/null 2>&1; then
|
||||
perl -e 'alarm shift @ARGV; exec @ARGV' "$secs" "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Best-effort ingest — async-detached.
|
||||
if command -v kei-memory >/dev/null 2>&1 && [ -f "$dest" ]; then
|
||||
kei-memory ingest \
|
||||
--session-id "$session_id" \
|
||||
--transcript "$dest" \
|
||||
>/dev/null 2>&1 || true
|
||||
(
|
||||
kei_with_timeout 90 kei-memory ingest \
|
||||
--session-id "$session_id" \
|
||||
--transcript "$dest" \
|
||||
>>"$bg_log" 2>&1 \
|
||||
|| printf '[%s] kei-memory ingest timeout/fail for %s\n' \
|
||||
"$(date +%H:%M:%S)" "$session_id" >>"$bg_log"
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
disown 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Wave 25 — frustration-matrix scan: regex+firmware classifier produces a
|
||||
# JSONL of per-line affect hits per session, much smaller than the full
|
||||
# transcript. Cloud REM agent reads the affect file instead of 80MB JSONL.
|
||||
# Silent no-op when the primitive is absent.
|
||||
# Wave 25 — frustration-matrix scan.
|
||||
if command -v frustration-matrix >/dev/null 2>&1; then
|
||||
affect_dir="${HOME}/.claude/memory/affect"
|
||||
mkdir -p "$affect_dir" 2>/dev/null || true
|
||||
affect_out="${affect_dir}/${session_id}.jsonl"
|
||||
frustration-matrix scan \
|
||||
--root "$traces_dir" \
|
||||
--since 1d \
|
||||
--format jsonl \
|
||||
--output "$affect_out" \
|
||||
>/dev/null 2>&1 || true
|
||||
(
|
||||
kei_with_timeout 60 frustration-matrix scan \
|
||||
--root "$traces_dir" \
|
||||
--since 1d \
|
||||
--format jsonl \
|
||||
--output "$affect_out" \
|
||||
>>"$bg_log" 2>&1 || true
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
disown 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# v0.11 sleep-sync (RULE 0.15) — push traces to the user's memory-repo so a
|
||||
# cloud agent can consolidate them overnight. Silent no-op when the primitive
|
||||
# is absent or the user hasn't opted in via /sleep-setup.
|
||||
# v0.11 sleep-sync (RULE 0.15) — push traces to memory-repo.
|
||||
sleep_sync="${HOME}/.claude/agents/_primitives/kei-sleep-sync.sh"
|
||||
if [ -x "$sleep_sync" ]; then
|
||||
"$sleep_sync" >/dev/null 2>&1 || true
|
||||
(
|
||||
kei_with_timeout 120 "$sleep_sync" >>"$bg_log" 2>&1 || true
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
disown 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,33 +1,42 @@
|
|||
#!/usr/bin/env bash
|
||||
# kei-agent-cli — invoke a KeiSeiKit agent via an external LLM CLI backend.
|
||||
#
|
||||
# Usage:
|
||||
# kei run-via <backend> <agent-name> "<task>" # by agent name
|
||||
# kei run-via <backend> --file=<path> "<task>" # by agent file path
|
||||
# kei run-via list # show backends + status
|
||||
# Two entry points (both route through this script):
|
||||
#
|
||||
# kei run-via <backend> <agent> "<task>" # explicit backend
|
||||
# kei agent <agent> "<task>" # backend resolved from DNA:
|
||||
# # 1. --on=<backend> flag
|
||||
# # 2. agent manifest's `provider`
|
||||
# # 3. ~/.claude/config/primary.toml
|
||||
# # 4. fallback: claude
|
||||
#
|
||||
# Other forms:
|
||||
# kei run-via list # show backends + agents
|
||||
# kei agent --on=<backend> <agent> "<task>" # override DNA backend
|
||||
# kei primary # print current primary
|
||||
# kei primary <backend> # set primary provider
|
||||
# kei run-via --help
|
||||
#
|
||||
# Backends (SSoT: _primitives/cli-backends.toml, fallback table below):
|
||||
# claude Claude Code (claude -p)
|
||||
# grok xAI Grok (grok --print, native --agent supported)
|
||||
# agy Antigravity (agy --print) — alias: antigravity
|
||||
# copilot GitHub Copilot (copilot --prompt)
|
||||
# kimi Moonshot Kimi (stdin, TUI primary)
|
||||
# codex OpenAI Codex (codex -p) — register-only if not installed
|
||||
# Backends (SSoT: _primitives/cli-backends.toml):
|
||||
# claude grok agy copilot kimi codex
|
||||
#
|
||||
# Reads agent prompt from ~/.claude/agents/<agent-name>.md (assembler output).
|
||||
# Reads assembled prompt from ~/.claude/agents/<agent-name>.md.
|
||||
# Strips YAML frontmatter, composes with task, execs the CLI.
|
||||
#
|
||||
# Env overrides:
|
||||
# KEI_AGENTS_DIR agent .md dir (default: ~/.claude/agents)
|
||||
# KEI_MANIFESTS_DIR manifest .toml dir (default: ~/.claude/_manifests)
|
||||
# KEI_PRIMARY override primary backend (beats config file)
|
||||
# KEI_NATIVE_AGENT=1 prefer backend's native --agent flag (grok/claude)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
KEI_AGENTS_DIR="${KEI_AGENTS_DIR:-$HOME/.claude/agents}"
|
||||
KEI_MANIFESTS_DIR="${KEI_MANIFESTS_DIR:-$HOME/.claude/_manifests}"
|
||||
KEI_PRIMARY_CFG="${KEI_PRIMARY_CFG:-$HOME/.claude/config/primary.toml}"
|
||||
KEI_NATIVE_AGENT="${KEI_NATIVE_AGENT:-0}"
|
||||
|
||||
usage() { sed -n '2,20p' "$0" | sed 's|^# \{0,1\}||'; }
|
||||
usage() { sed -n '2,32p' "$0" | sed 's|^# \{0,1\}||'; }
|
||||
|
||||
# ---- backend table (SSoT mirror; kept in sync with cli-backends.toml) -----
|
||||
backend_bin() {
|
||||
|
|
@ -46,9 +55,49 @@ backend_supports_native_agent() {
|
|||
case "$1" in claude|grok) return 0 ;; *) return 1 ;; esac
|
||||
}
|
||||
|
||||
# Invoke backend with composed prompt as argument or stdin per backend.
|
||||
# ---- DNA resolver: agent → preferred backend --------------------------------
|
||||
# Reads `provider = "..."` line from the manifest TOML if present.
|
||||
manifest_provider() {
|
||||
local agent="$1" tomlf="$KEI_MANIFESTS_DIR/$1.toml"
|
||||
[ -f "$tomlf" ] || return 1
|
||||
awk -F'=' '
|
||||
/^provider[[:space:]]*=/ {
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
|
||||
gsub(/^"|"$/, "", $2)
|
||||
print $2; exit
|
||||
}
|
||||
' "$tomlf"
|
||||
}
|
||||
|
||||
# Reads primary from config file (or KEI_PRIMARY env override).
|
||||
config_primary() {
|
||||
if [ -n "${KEI_PRIMARY:-}" ]; then
|
||||
printf '%s\n' "$KEI_PRIMARY"; return 0
|
||||
fi
|
||||
[ -f "$KEI_PRIMARY_CFG" ] || return 1
|
||||
awk -F'=' '
|
||||
/^provider[[:space:]]*=/ {
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
|
||||
gsub(/^"|"$/, "", $2)
|
||||
print $2; exit
|
||||
}
|
||||
' "$KEI_PRIMARY_CFG"
|
||||
}
|
||||
|
||||
# Resolution order: explicit --on= → manifest provider → primary → claude.
|
||||
resolve_backend() {
|
||||
local agent="$1" explicit="${2:-}" out=""
|
||||
if [ -n "$explicit" ]; then printf '%s\n' "$explicit"; return 0; fi
|
||||
out=$(manifest_provider "$agent" 2>/dev/null) || true
|
||||
if [ -n "$out" ]; then printf '%s\n' "$out"; return 0; fi
|
||||
out=$(config_primary 2>/dev/null) || true
|
||||
if [ -n "$out" ]; then printf '%s\n' "$out"; return 0; fi
|
||||
printf 'claude\n'
|
||||
}
|
||||
|
||||
# ---- backend invocation ---------------------------------------------------
|
||||
backend_invoke() {
|
||||
local backend="$1" prompt="$2" bin agent_name="${3:-}"
|
||||
local backend="$1" prompt="$2" agent_name="${3:-}" bin
|
||||
bin=$(backend_bin "$backend") || {
|
||||
printf '[kei-agent-cli] unknown backend: %s\n' "$backend" >&2
|
||||
return 2
|
||||
|
|
@ -70,9 +119,20 @@ backend_invoke() {
|
|||
claude) exec "$bin" -p "$prompt" ;;
|
||||
grok|agy|antigravity) exec "$bin" --print "$prompt" ;;
|
||||
copilot) exec "$bin" --prompt "$prompt" ;;
|
||||
kimi) # Kimi non-interactive surface is limited;
|
||||
# stdin works against TUI default mode.
|
||||
printf '%s\n' "$prompt" | exec "$bin" ;;
|
||||
kimi)
|
||||
# Kimi has NO one-shot print mode (smoke-tested 2026-05-26): bare `kimi`
|
||||
# opens an interactive TUI that ignores piped stdin and exits with "Bye!".
|
||||
# For headless invocation we'd need an ACP client (`kimi acp` is a JSON-RPC
|
||||
# stdio server). Until KeiSeiKit ships that client, dump the composed
|
||||
# prompt to a tmpfile and open the TUI so the user can paste it in.
|
||||
tmp=$(mktemp -t kei-agent-kimi.XXXX.md)
|
||||
printf '%s\n' "$prompt" > "$tmp"
|
||||
printf '[kei-agent-cli] kimi non-interactive is unsupported (TUI only).\n' >&2
|
||||
printf '[kei-agent-cli] composed prompt saved: %s\n' "$tmp" >&2
|
||||
printf '[kei-agent-cli] copy-paste it into Kimi after the TUI opens.\n' >&2
|
||||
printf '[kei-agent-cli] (or pipe via `kimi acp` if you have an ACP client.)\n' >&2
|
||||
exec "$bin"
|
||||
;;
|
||||
codex) exec "$bin" -p "$prompt" ;;
|
||||
esac
|
||||
}
|
||||
|
|
@ -80,7 +140,6 @@ backend_invoke() {
|
|||
# ---- agent loader -------------------------------------------------------
|
||||
load_agent() {
|
||||
local name="$1" path
|
||||
# explicit path via --file=
|
||||
case "$name" in
|
||||
--file=*) path="${name#--file=}" ;;
|
||||
/*|./*|*/*) path="$name" ;;
|
||||
|
|
@ -96,16 +155,35 @@ load_agent() {
|
|||
fi
|
||||
return 1
|
||||
fi
|
||||
# strip YAML frontmatter (---\n...\n---) if present
|
||||
awk '
|
||||
BEGIN { in_fm=0; past_fm=0 }
|
||||
BEGIN { in_fm=0 }
|
||||
NR==1 && /^---$/ { in_fm=1; next }
|
||||
in_fm && /^---$/ { in_fm=0; past_fm=1; next }
|
||||
in_fm && /^---$/ { in_fm=0; next }
|
||||
in_fm { next }
|
||||
{ print }
|
||||
' "$path"
|
||||
}
|
||||
|
||||
# ---- primary subcommand ------------------------------------------------
|
||||
handle_primary() {
|
||||
local arg="${1:-}"
|
||||
if [ -z "$arg" ]; then
|
||||
cur=$(config_primary 2>/dev/null || true)
|
||||
printf 'primary provider: %s\n' "${cur:-claude (default fallback)}"
|
||||
[ -f "$KEI_PRIMARY_CFG" ] && printf 'config: %s\n' "$KEI_PRIMARY_CFG"
|
||||
return 0
|
||||
fi
|
||||
backend_bin "$arg" >/dev/null || {
|
||||
printf '[kei-primary] unknown backend: %s\n' "$arg" >&2
|
||||
printf 'valid: claude grok agy copilot kimi codex\n' >&2
|
||||
return 2
|
||||
}
|
||||
mkdir -p "$(dirname "$KEI_PRIMARY_CFG")"
|
||||
printf '# kei primary — written %s\nprovider = "%s"\n' \
|
||||
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$arg" > "$KEI_PRIMARY_CFG"
|
||||
printf 'primary provider set: %s → %s\n' "$arg" "$KEI_PRIMARY_CFG"
|
||||
}
|
||||
|
||||
# ---- subcommands --------------------------------------------------------
|
||||
case "${1:-}" in
|
||||
""|-h|--help|help) usage; exit 0 ;;
|
||||
|
|
@ -119,6 +197,8 @@ case "${1:-}" in
|
|||
printf ' %-10s ✗ (not on PATH)\n' "$b"
|
||||
fi
|
||||
done
|
||||
cur=$(config_primary 2>/dev/null || true)
|
||||
printf '\nprimary: %s\n' "${cur:-claude (default)}"
|
||||
printf '\nAgents (%s):\n' "$KEI_AGENTS_DIR"
|
||||
if [ -d "$KEI_AGENTS_DIR" ]; then
|
||||
find "$KEI_AGENTS_DIR" -maxdepth 1 -name '*.md' -not -name '_*' 2>/dev/null \
|
||||
|
|
@ -128,26 +208,62 @@ case "${1:-}" in
|
|||
fi
|
||||
exit 0
|
||||
;;
|
||||
primary)
|
||||
shift
|
||||
handle_primary "${1:-}"
|
||||
exit $?
|
||||
;;
|
||||
agent)
|
||||
# Direct-invocation passthrough: `kei-agent-cli.sh agent <name> "task"`
|
||||
# behaves identically to `kei-agent-cli.sh <name> "task"` (DNA mode).
|
||||
# Lets users call either form without surprise.
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
|
||||
# ---- main ---------------------------------------------------------------
|
||||
if [ $# -lt 3 ]; then
|
||||
# ---- main: DNA mode (no leading backend) OR explicit run-via ------------
|
||||
# Detect call shape:
|
||||
# "$1" is a known backend → run-via flow (kei run-via <backend> <agent> "task")
|
||||
# "$1" starts with --on= → DNA flow with override
|
||||
# "$1" is anything else → DNA flow (kei agent <agent> "task")
|
||||
|
||||
EXPLICIT_BACKEND=""
|
||||
case "${1:-}" in
|
||||
--on=*)
|
||||
EXPLICIT_BACKEND="${1#--on=}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
if [ $# -ge 1 ] && backend_bin "$1" >/dev/null 2>&1; then
|
||||
EXPLICIT_BACKEND="$1"
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
BACKEND="$1"; AGENT_REF="$2"; shift 2
|
||||
AGENT_REF="$1"; shift
|
||||
TASK="$*"
|
||||
|
||||
AGENT_NAME=$(basename "${AGENT_REF#--file=}")
|
||||
AGENT_NAME="${AGENT_NAME%.md}"
|
||||
|
||||
BACKEND=$(resolve_backend "$AGENT_NAME" "$EXPLICIT_BACKEND")
|
||||
|
||||
if ! AGENT_PROMPT=$(load_agent "$AGENT_REF"); then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Compose: agent system + task delimiter.
|
||||
COMPOSED=$(printf '%s\n\n---\n\nTASK FOR THIS RUN:\n%s\n' "$AGENT_PROMPT" "$TASK")
|
||||
|
||||
# Derive a clean agent name for KEI_NATIVE_AGENT path.
|
||||
AGENT_NAME=$(basename "${AGENT_REF#--file=}")
|
||||
AGENT_NAME="${AGENT_NAME%.md}"
|
||||
printf '[kei-agent-cli] agent=%s backend=%s (via %s)\n' \
|
||||
"$AGENT_NAME" "$BACKEND" \
|
||||
"$([ -n "$EXPLICIT_BACKEND" ] && echo explicit \
|
||||
|| ([ -n "$(manifest_provider "$AGENT_NAME" 2>/dev/null)" ] && echo manifest \
|
||||
|| ([ -n "$(config_primary 2>/dev/null)" ] && echo primary || echo default)))" >&2
|
||||
|
||||
backend_invoke "$BACKEND" "$COMPOSED" "$AGENT_NAME"
|
||||
|
|
|
|||
Loading…
Reference in a new issue