ai-agentmanager 1.11.1 → 1.13.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/CHANGELOG.md +20 -0
- package/bin/agentkit.js +10 -0
- package/docs/agentkit.md +175 -0
- package/package.json +7 -3
- package/public/app.js +44 -6
- package/public/built.js +332 -0
- package/public/index.html +22 -0
- package/public/style.css +30 -0
- package/src/agentkit/agent.js +171 -0
- package/src/agentkit/approvals.js +97 -0
- package/src/agentkit/cli.js +153 -0
- package/src/agentkit/engines.js +128 -0
- package/src/agentkit/http.js +131 -0
- package/src/agentkit/package.js +114 -0
- package/src/agentkit/report.js +106 -0
- package/src/agentkit/sea-main.js +25 -0
- package/src/agentkit/service.js +118 -0
- package/src/agentkit/spec.js +109 -0
- package/src/agentkit/stdio.js +95 -0
- package/src/agentkit/triggers.js +60 -0
- package/src/agentkit-reports.js +117 -0
- package/src/builder.js +436 -0
- package/src/manager.js +25 -2
- package/src/server.js +74 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.0 (2026-11-01)
|
|
4
|
+
|
|
5
|
+
- **Agents are built in an agent session.** **+ Build an agent** now starts an agent session that works in the new agent's folder (`~/.agentmanager/built/<name>/`) and goes straight to it. The session writes the analysis (`analysis.json`), `agent.yaml`, `instructions.md` and `tests.yaml`, and checks that the agent loads. You keep shaping the agent in that conversation, like any other session (terminal, resume, inbox).
|
|
6
|
+
- The session's page has a panel for its agent: status, tests, **Run tests**, **Start**/**Stop**, its chat, **Details**. Failing tests go back to the session with **Ask this session to fix them**.
|
|
7
|
+
- In the Built tab, each agent links to its session. The details dialog has a box that sends a change request to the session. Agents built before this get **Open a session for it**.
|
|
8
|
+
- Test results and executables built before the agent's latest change are marked as out of date. Deleting a built agent closes its session; the conversation is kept.
|
|
9
|
+
- **Fixes from building a real agent:**
|
|
10
|
+
- A permission prompt is now flagged when newer Claude Code blinks a ⏺ beside the pending tool call. The blinking used to keep the session looking busy, so the prompt never reached the Inbox. This affects every session, not just build sessions.
|
|
11
|
+
- agentkit agents can write files under a folder they're allowed: Claude Code ignores `Write(<path>)` allow rules, so each one now also gets its `Edit(<path>)` rule.
|
|
12
|
+
- The test judge sees the files each test run created or changed, not only the agent's reply, and the Tests list shows them.
|
|
13
|
+
- Build sessions read the agentkit reference without asking, and write the agent's files with their file tools.
|
|
14
|
+
- The build dialog takes an optional name; otherwise the name comes from the task's first words. `POST /api/builder/:name/change` sends a change request to the session from scripts.
|
|
15
|
+
|
|
16
|
+
## 1.12.0 (2026-11-01)
|
|
17
|
+
|
|
18
|
+
- **agentkit (phase 1): agents you build and run.** An agent is a folder with `agent.yaml` (goal, instructions, engine, MCP tools, allow/approve lists, triggers, loop, memory, interfaces); `agentkit run <agent>` runs it continuously. Each input is a run of plan → act → check (acting again on what's left, up to `maxSteps`) → reflect into memory, through an engine's CLI (Claude Code, Codex, Muse Code or an ACP agent), self-contained (only its own MCP servers; none of your personal Claude settings). Triggers: `every`, `cron`, `webhook`, `watch`. Interfaces: a chat page and HTTP API, A2A (agent card and `message/send` / `tasks/get`), ACP on stdio (`agentkit acp`), and an MCP tool (`agentkit mcp`, one tool `ask_<name>`). `agentkit init`, `check`, `run --once`. See [docs/agentkit.md](docs/agentkit.md).
|
|
19
|
+
- **Agent builder:** a **Built** tab with **+ Build an agent**: describe a task or workflow (attachments welcome) and pick an engine; one call designs the agent: an analysis (problem, inputs, outputs, risks), its `agent.yaml` and instructions, suggested MCP tools and 3–5 test cases. Review and edit the spec (checked on save), **Run tests** (each through the real runtime, judged against its expectation), **Start** it on this computer (its chat page and API), **Download** its folder. Built agents live in `~/.agentmanager/built/<name>/`; `/api/builder` for scripts.
|
|
20
|
+
- **Single executables and background service for agents:** `agentkit package <agent> [--platform]` makes one file (macOS arm64/x64, Linux x64/arm64) that runs the agent with no Node or agentkit installed: a Node single executable with the agent's files embedded, built on the official Node binary (downloaded from nodejs.org, checksum-verified, cached). Linux ones build on a Mac. The executable unpacks to `~/.agentkit/<name>/` and takes the same commands (`--once`, `acp`, `mcp`, `check`). `service install|status|uninstall` keeps an agent running in the background (launchd or a systemd user service: started at login, restarted after a crash). In the Built tab: **Build executable** for a platform, with download links.
|
|
21
|
+
- **Approvals and monitoring for agents:** tools on an agent's `approve` list now ask a person instead of being unavailable (Claude Code engine, through `--permission-prompt-tool` and the agent's own approver), on its chat page and in AI Agent Manager's **Inbox**; the first answer decides, silence past `approveTimeout` is a no, and the approve list wins over `allow`. Agents report to AI Agent Manager (runs, cost, results, open approvals; every 30s, at once after a run): the **Built** tab lists them under *Reporting in*. Agents started here report automatically; others connect with `AGENTKIT_REPORT_URL`, `AGENTKIT_REPORT_TOKEN` and, over remote access, `AGENTKIT_REPORT_CERT` (certificate pinning).
|
|
22
|
+
|
|
3
23
|
## 1.11.1 (2026-11-01)
|
|
4
24
|
|
|
5
25
|
- **Copy and paste in terminals on phones:** **Copy** and **Paste** in the terminal's key bar (and in the Terminal's bar). A phone can't select the terminal's text or paste into it directly, so Copy opens its recent output as plain text you can select (or **Copy all**), and Paste reads the clipboard (the phone asks first) or offers a box to paste into, then inserts it as one paste without pressing Enter.
|
package/bin/agentkit.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// agentkit: run an agent described by agent.yaml (docs/agentkit.md).
|
|
3
|
+
// agentkit run <agent> [--once "<input>"] [--port N] triggers, chat page, API and A2A; or one run
|
|
4
|
+
// agentkit acp <agent> ACP on stdio
|
|
5
|
+
// agentkit mcp <agent> MCP on stdio (one tool: ask_<name>)
|
|
6
|
+
// agentkit check <agent> validate agent.yaml
|
|
7
|
+
// agentkit init <folder> a starter agent to edit
|
|
8
|
+
import { main } from '../src/agentkit/cli.js';
|
|
9
|
+
|
|
10
|
+
main(process.argv.slice(2));
|
package/docs/agentkit.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# agentkit: agents you build, download and run
|
|
2
|
+
|
|
3
|
+
Status: the runtime, the builder (AI Agent Manager's **Built** tab), single executables, the background service, approvals and reporting to AI Agent Manager are in.
|
|
4
|
+
|
|
5
|
+
An **agentkit agent** is a folder (or one executable made from it) that does one job continuously: triage new
|
|
6
|
+
support tickets, keep a dependency report current, answer questions about a codebase, run a
|
|
7
|
+
release checklist. It is described by `agent.yaml`, runs its work through an engine's CLI (Claude
|
|
8
|
+
Code, Codex, Muse Code or any ACP agent), uses tools through MCP servers, and can be reached by
|
|
9
|
+
people (a chat page, an HTTP API), by other agents (A2A, MCP) and by editors (ACP).
|
|
10
|
+
|
|
11
|
+
## agent.yaml
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
name: ticket-triage # lowercase, used for files and URLs
|
|
15
|
+
title: Support ticket triage
|
|
16
|
+
description: Reads new tickets, labels and routes them, drafts a first reply.
|
|
17
|
+
goal: |
|
|
18
|
+
Every new ticket is labelled, routed to the right team and has a drafted first reply
|
|
19
|
+
within 10 minutes, without closing or replying on its own.
|
|
20
|
+
instructions: instructions.md # or inline text: how to do the job, step by step
|
|
21
|
+
engine: claude # claude | codex | muse | acp:<name>
|
|
22
|
+
model: sonnet # optional, passed to the engine
|
|
23
|
+
workdir: ./work # where it works (a repo, a folder); default ./work
|
|
24
|
+
|
|
25
|
+
tools: # MCP servers the engine gets
|
|
26
|
+
jira:
|
|
27
|
+
command: npx
|
|
28
|
+
args: [-y, "@acme/jira-mcp"]
|
|
29
|
+
env: { JIRA_TOKEN: "${JIRA_TOKEN}" } # "${VAR}" (quoted) comes from the environment, never stored
|
|
30
|
+
allow: [Read, Grep, "mcp__jira__*"] # tools it may use without asking (engine names);
|
|
31
|
+
# "Edit(./out/**)" lets it write files under ./out only
|
|
32
|
+
approve: ["mcp__jira__transition"] # tools that need a person's yes (asked in chat / Inbox)
|
|
33
|
+
approveTimeout: 30m # no answer by then counts as no
|
|
34
|
+
report: # report to AI Agent Manager (or set AGENTKIT_REPORT_*)
|
|
35
|
+
url: "${AGENTMGR_URL}"
|
|
36
|
+
token: "${AGENTMGR_REPORT_TOKEN}"
|
|
37
|
+
|
|
38
|
+
triggers:
|
|
39
|
+
- every: 10m # interval
|
|
40
|
+
- cron: "0 9 * * 1-5" # schedule (minute hour day month weekday)
|
|
41
|
+
- webhook: /hooks/ticket # POST here starts a run with the body as input
|
|
42
|
+
- watch: ./inbox # a new or changed file starts a run
|
|
43
|
+
|
|
44
|
+
loop:
|
|
45
|
+
plan: true # think and write a plan before acting
|
|
46
|
+
check: true # verify the result against the goal after acting
|
|
47
|
+
maxSteps: 6 # act/check rounds per run before stopping
|
|
48
|
+
budgetUsd: 0.50 # per run (when the engine reports cost)
|
|
49
|
+
|
|
50
|
+
memory: true # keep notes between runs (memory.md)
|
|
51
|
+
|
|
52
|
+
interfaces:
|
|
53
|
+
http: { port: 7700 } # chat page + API
|
|
54
|
+
a2a: true # agent card + tasks over HTTP
|
|
55
|
+
acp: true # `<agent> acp` speaks ACP on stdio
|
|
56
|
+
mcp: true # `<agent> mcp` exposes it as an MCP tool
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Building one in AI Agent Manager
|
|
60
|
+
|
|
61
|
+
**Built → + Build an agent** takes a task or workflow (attachments welcome), the engine it runs on and an optional name. It starts an **agent session** whose folder is the new agent (`~/.agentmanager/built/<name>/`), and opens that session. The session writes the agent and checks that it loads (`agentkit check`):
|
|
62
|
+
|
|
63
|
+
- `analysis.json`: the problem, inputs, outputs, risks and the MCP tools it chose
|
|
64
|
+
- `agent.yaml`
|
|
65
|
+
- `instructions.md`
|
|
66
|
+
- `tests.yaml`: 3 to 8 `{input, expect}` cases
|
|
67
|
+
|
|
68
|
+
It asks you what only you know: accounts, labels, teams. You keep developing the agent in that conversation.
|
|
69
|
+
|
|
70
|
+
The session's page shows a panel for its agent: status and test results, with **Run tests** (each test runs through the real runtime and is judged against `expect`), **Start**/**Stop** (runs it continuously on this computer, with its chat page and API) and **Details**. Failing tests can go straight back to the session. The Built tab lists every built agent with a link to its session. Its details dialog sends change requests to the session and has the spec, executables and downloads. Results and executables from before the latest change are marked out of date.
|
|
71
|
+
|
|
72
|
+
## A run
|
|
73
|
+
|
|
74
|
+
1. **Input** arrives: a trigger fires (with its payload), someone chats, an A2A task or an MCP
|
|
75
|
+
call comes in.
|
|
76
|
+
2. **Plan** (when `loop.plan`): the engine reads the goal, instructions, memory and input and
|
|
77
|
+
writes a short plan.
|
|
78
|
+
3. **Act**: the engine carries out the plan with its tools (MCP), in `workdir`.
|
|
79
|
+
4. **Check** (when `loop.check`): the engine is asked whether the goal is met for this input,
|
|
80
|
+
and what's left. If not and steps remain, it acts again on what's left.
|
|
81
|
+
5. **Reflect** (when `memory`): one or two lessons for next time are appended to memory.
|
|
82
|
+
6. **Result**: the final answer goes back to whoever asked; the run (steps, output, cost,
|
|
83
|
+
duration) is logged in `runs/<id>.jsonl`.
|
|
84
|
+
|
|
85
|
+
The engine keeps one session per conversation (a chat, an A2A context), so follow-ups continue
|
|
86
|
+
it. Runs from triggers start fresh sessions.
|
|
87
|
+
|
|
88
|
+
## Interfaces
|
|
89
|
+
|
|
90
|
+
- **HTTP**: `GET /` chat page; `POST /api/chat {message, conversation?}`; `POST /api/runs {input}`;
|
|
91
|
+
`GET /api/runs`, `GET /api/runs/:id`; `GET /api/health`.
|
|
92
|
+
- **A2A**: agent card at `GET /.well-known/agent.json`; JSON-RPC at `POST /a2a` with
|
|
93
|
+
`message/send` and `tasks/get`.
|
|
94
|
+
- **ACP**: `<agent> acp` runs an ACP agent on stdio (initialize, session/new, session/prompt,
|
|
95
|
+
streamed session/update), so editors (and AI Agent Manager's ACP engine) can drive it.
|
|
96
|
+
- **MCP**: `<agent> mcp` runs an MCP server on stdio with one tool, `ask_<name>`, so other
|
|
97
|
+
agents can delegate to it.
|
|
98
|
+
|
|
99
|
+
## Running
|
|
100
|
+
|
|
101
|
+
`agentkit run ./ticket-triage` (or, packaged, `./ticket-triage`) starts it in the foreground:
|
|
102
|
+
triggers, the HTTP server, the loop. `agentkit run … --once "<input>"` does one run and prints
|
|
103
|
+
the result.
|
|
104
|
+
|
|
105
|
+
In the background: `agentkit service install ./ticket-triage` (packaged: `./ticket-triage service
|
|
106
|
+
install`) registers it with launchd (macOS) or systemd (Linux, a user service): it starts at login,
|
|
107
|
+
restarts after a crash, and writes to `agent.log` in its folder. It runs through your login shell,
|
|
108
|
+
so it finds the engine CLIs and tokens your terminal has. `service status` and `service uninstall`
|
|
109
|
+
do the rest. On Linux, `loginctl enable-linger $USER` keeps it running while you're logged out.
|
|
110
|
+
|
|
111
|
+
## One executable
|
|
112
|
+
|
|
113
|
+
`agentkit package ./ticket-triage [--platform linux-x64] [-o dist]` makes one file that runs the
|
|
114
|
+
agent with no Node or agentkit installed: `ticket-triage-macos-arm64`, `-macos-x64`,
|
|
115
|
+
`-linux-x64` or `-linux-arm64` (about 110 MB; Windows isn't supported yet). It is a Node single
|
|
116
|
+
executable application: the runtime bundled, the agent's files (`agent.yaml`, instructions,
|
|
117
|
+
tests) embedded, injected into the official Node binary for that platform (downloaded once from
|
|
118
|
+
nodejs.org, checked against its published checksums, cached in `~/.agentmanager/node-dist`).
|
|
119
|
+
Linux executables build on any Mac or Linux machine; macOS ones are signed, so they build on a Mac.
|
|
120
|
+
|
|
121
|
+
On its first run, the executable unpacks its files to `~/.agentkit/<name>/` (or `AGENTKIT_HOME`),
|
|
122
|
+
where its memory and runs live too. Edits there stay until a newer build of the agent replaces the
|
|
123
|
+
files it shipped. Everything else works as with the folder: `./ticket-triage` (run),
|
|
124
|
+
`--once "<input>"`, `acp`, `mcp`, `check`, `service install`. Its engine's CLI (Claude Code, Codex
|
|
125
|
+
or Muse Code) must be installed and signed in where it runs. ACP engines aren't packaged yet.
|
|
126
|
+
|
|
127
|
+
In AI Agent Manager, a built agent's dialog has **Build executable** (pick the platform) and a
|
|
128
|
+
download link for each one built. Editing the agent drops the old executables.
|
|
129
|
+
|
|
130
|
+
## Approvals
|
|
131
|
+
|
|
132
|
+
A tool on the `approve` list runs only after a person says yes. The approve list wins over
|
|
133
|
+
`allow`: with `allow: ["mcp__jira__*"]` and `approve: ["mcp__jira__transition"]`, every Jira tool
|
|
134
|
+
runs freely except transitions, which ask.
|
|
135
|
+
|
|
136
|
+
With Claude Code as the engine, the agent passes `--permission-prompt-tool` to point Claude at a
|
|
137
|
+
small MCP server of its own (the approver). Claude asks the approver before any tool it isn't
|
|
138
|
+
allowed outright. The approver hands the question to the running agent over a private local
|
|
139
|
+
address, and the agent asks whoever can answer:
|
|
140
|
+
|
|
141
|
+
- **its chat page**, which shows *Approve …?* with the tool's input and Approve / Deny buttons
|
|
142
|
+
(`GET /api/approvals`, `POST /api/approvals/<id>` with `{"allow": true}`);
|
|
143
|
+
- **AI Agent Manager**, when the agent reports there: the request shows up in the **Inbox** and the
|
|
144
|
+
**Built** tab, and the answer goes back with the agent's next check-in (every 2 seconds while
|
|
145
|
+
something waits).
|
|
146
|
+
|
|
147
|
+
The first answer decides. No answer within `approveTimeout` (default 30m) counts as no, and so does
|
|
148
|
+
having no one to ask (no chat page, not reporting). Tools on neither list are refused without
|
|
149
|
+
asking. Other engines can't ask mid-run, so for them approval tools aren't available at all. Test
|
|
150
|
+
runs in the builder never wait for anyone.
|
|
151
|
+
|
|
152
|
+
## Reporting to AI Agent Manager
|
|
153
|
+
|
|
154
|
+
An agent that reports sends AI Agent Manager who it is (name, host, engine, triggers, chat
|
|
155
|
+
address), each run (status, source, cost, time, the start of its result) and the approvals it's
|
|
156
|
+
waiting on. It checks in every 30 seconds, and right away after a run or a new approval. The
|
|
157
|
+
**Built** tab lists these agents under *Reporting in*: online or last seen, idle or working, runs,
|
|
158
|
+
failures and total cost, recent runs, and approvals to answer. Runs are counted once; the list is
|
|
159
|
+
kept across restarts.
|
|
160
|
+
|
|
161
|
+
Agents started from the Built tab report automatically. For one running elsewhere (a packaged
|
|
162
|
+
executable on a server, a folder on another Mac), **Connect one from elsewhere** shows what to set
|
|
163
|
+
in its environment (or its service):
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
AGENTKIT_REPORT_URL=https://192.168.1.20:4718 # AI Agent Manager's remote-access address
|
|
167
|
+
AGENTKIT_REPORT_TOKEN=… # the report token (made on first use)
|
|
168
|
+
AGENTKIT_REPORT_CERT=AB:CD:… # its certificate's SHA-256 fingerprint
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Remote access uses a self-signed certificate, so the agent trusts exactly that certificate (by
|
|
172
|
+
its fingerprint) and sends nothing until the fingerprint matches. The connection details are shown
|
|
173
|
+
only on the computer AI Agent Manager runs on. The report token lets a program report as an agent
|
|
174
|
+
and receive the decisions made for it, and nothing else in AI Agent Manager; keep it like a
|
|
175
|
+
password (give each computer the same one, or rotate it by deleting `~/.agentmanager/agentkit-reports.json`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-agentmanager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Run background coding agents (Claude Code, Codex) per intent, and keep track of them from a web UI, your phone or the CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"license": "Apache-2.0",
|
|
24
24
|
"type": "module",
|
|
25
25
|
"bin": {
|
|
26
|
-
"agentmgr": "bin/agentmgr.js"
|
|
26
|
+
"agentmgr": "bin/agentmgr.js",
|
|
27
|
+
"agentkit": "bin/agentkit.js"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"bin",
|
|
@@ -51,11 +52,14 @@
|
|
|
51
52
|
"@xterm/xterm": "^6.0.0",
|
|
52
53
|
"commander": "^15.0.0",
|
|
53
54
|
"dompurify": "^3.4.17",
|
|
55
|
+
"esbuild": "^0.25.12",
|
|
54
56
|
"express": "^5.2.1",
|
|
55
57
|
"marked": "^18.0.14",
|
|
56
58
|
"mermaid": "^12.2.0",
|
|
57
59
|
"node-pty": "^1.1.0",
|
|
60
|
+
"postject": "^1.0.0-alpha.6",
|
|
58
61
|
"qrcode": "^1.5.4",
|
|
59
|
-
"ws": "^8.22.0"
|
|
62
|
+
"ws": "^8.22.0",
|
|
63
|
+
"yaml": "^2.9.1"
|
|
60
64
|
}
|
|
61
65
|
}
|
package/public/app.js
CHANGED
|
@@ -7,6 +7,7 @@ import { openStream, post, REMOTE } from '/transport.js';
|
|
|
7
7
|
import { attachBox } from '/attach.js';
|
|
8
8
|
import { initSide } from '/side.js';
|
|
9
9
|
import { openCopy, pasteInto } from '/termclip.js';
|
|
10
|
+
import { initBuilt } from '/built.js';
|
|
10
11
|
import { addTools, download, htmlFile, initExport } from '/export.js';
|
|
11
12
|
import { badgeHtml as scoreBadge, scorecardHtml, scorecardSpeech } from '/scorecard.js';
|
|
12
13
|
import { bindListen, listenHtml, play, speakable, now as speaking, stop as stopSpeaking } from '/speak.js';
|
|
@@ -125,6 +126,15 @@ $('#new-playbook').onclick = () => {
|
|
|
125
126
|
playbooksUi.open({ intent: f.intents.value.trim(), projects: f.project.value.trim() ? [f.project.value.trim()] : [], model: f.model.value.trim(), permissionMode: f.permissionMode.value });
|
|
126
127
|
};
|
|
127
128
|
$('#triggers-open').onclick = () => triggersUi.open();
|
|
129
|
+
let builtReports = []; // agents built with agentkit that report here (runs, approvals)
|
|
130
|
+
const builtUi = initBuilt({
|
|
131
|
+
api, esc, toast, $,
|
|
132
|
+
isOpen: () => sideTab === 'built',
|
|
133
|
+
setCount: (n) => ($('#built-count').textContent = n),
|
|
134
|
+
reports: () => builtReports,
|
|
135
|
+
openSession: (id) => (showTab('agents'), select(id)),
|
|
136
|
+
onPanel: () => agents.get(selectedId)?.builds && renderDetail(),
|
|
137
|
+
});
|
|
128
138
|
const landUi = initLand({ api, esc, toast, agents, $, scoreBadge, select: (id) => (showTab('agents'), select(id)), openReview: (id) => reviewUi.open(id), isOpen: () => sideTab === 'land' });
|
|
129
139
|
|
|
130
140
|
function statusOf(a) {
|
|
@@ -710,7 +720,7 @@ function agentItem(a) {
|
|
|
710
720
|
const idle = !isActive(a);
|
|
711
721
|
return `<li data-id="${a.id}" ${a.kind === 'general' || a.kind === 'assistant' ? '' : 'draggable="true" '}class="agent pr${a.priority ?? 2} ${a.id === selectedId ? 'selected' : ''} ${idle ? 'idle' : ''}">
|
|
712
722
|
<div class="line1">${prioChip(a.priority ?? 2)}${a.results?.outcome === 'blocked' ? '<span class="outcome-mark" title="Blocked">⛔</span>' : a.results?.outcome === 'done' ? '<span class="outcome-mark" title="Done">✅</span>' : ''}<span class="name">${esc(a.name)}</span>${flags(a)}<span class="pill ${st}">${pillText(st)}</span></div>
|
|
713
|
-
<div class="intent" title="${esc(describeTitle(a) ?? '')}">${a.kind === 'general' ? '<span class="tag">general</span> ' : a.kind === 'assistant' ? '<span class="tag assistant">assistant</span> ' : a.startedBy ? '<span class="tag assistant" title="Started by the assistant">🤖 by assistant</span> ' : a.trigger ? `<span class="tag assistant" title="Started from ${a.trigger.source === 'github' ? 'a labeled GitHub issue' : 'the webhook'}${a.trigger.url ? `: ${esc(a.trigger.url)}` : ''}">⚡ ${a.trigger.source === 'github' ? 'GitHub' : 'webhook'}</span> ` : a.playbook ? `<span class="tag assistant" title="Started by the playbook ${esc(a.playbook.name)}">▶ ${esc(a.playbook.name)}</span> ` : ''}${a.engine && a.engine !== 'claude' ? `<span class="tag engine">${esc(ENGINE_LABEL[a.engine] ?? a.engine)}</span> ` : ''}${a.isolation === 'container' ? '<span class="tag engine" title="Runs in its own Docker container: it sees only its own folder">📦 container</span> ' : ''}${esc(describe(a) || (a.kind === 'assistant' ? 'Finds new work and starts agents for it' : 'General session'))}</div>
|
|
723
|
+
<div class="intent" title="${esc(describeTitle(a) ?? '')}">${a.builds ? `<span class="tag assistant" title="Develops the agent ${esc(a.builds.name)} (Built tab)">⚙ builds agent</span> ` : ''}${a.kind === 'general' ? '<span class="tag">general</span> ' : a.kind === 'assistant' ? '<span class="tag assistant">assistant</span> ' : a.startedBy ? '<span class="tag assistant" title="Started by the assistant">🤖 by assistant</span> ' : a.trigger ? `<span class="tag assistant" title="Started from ${a.trigger.source === 'github' ? 'a labeled GitHub issue' : 'the webhook'}${a.trigger.url ? `: ${esc(a.trigger.url)}` : ''}">⚡ ${a.trigger.source === 'github' ? 'GitHub' : 'webhook'}</span> ` : a.playbook ? `<span class="tag assistant" title="Started by the playbook ${esc(a.playbook.name)}">▶ ${esc(a.playbook.name)}</span> ` : ''}${a.engine && a.engine !== 'claude' ? `<span class="tag engine">${esc(ENGINE_LABEL[a.engine] ?? a.engine)}</span> ` : ''}${a.isolation === 'container' ? '<span class="tag engine" title="Runs in its own Docker container: it sees only its own folder">📦 container</span> ' : ''}${esc(describe(a) || (a.kind === 'assistant' ? 'Finds new work and starts agents for it' : 'General session'))}</div>
|
|
714
724
|
${a.status === 'preparing' && a.progress ? `<div class="progress-line">${esc(progressText(a.progress))}</div>` : ''}
|
|
715
725
|
<div class="meta">
|
|
716
726
|
<span class="mono">${a.id}</span>
|
|
@@ -922,6 +932,8 @@ function showTab(tab) {
|
|
|
922
932
|
$('#backlog-view').hidden = tab !== 'backlog';
|
|
923
933
|
$('#inbox-view').hidden = tab !== 'inbox';
|
|
924
934
|
$('#land-view').hidden = tab !== 'land';
|
|
935
|
+
$('#built-view').hidden = tab !== 'built';
|
|
936
|
+
if (tab === 'built') builtUi.load();
|
|
925
937
|
if (tab === 'inbox') loadJudge();
|
|
926
938
|
if (tab === 'land') landUi.load();
|
|
927
939
|
renderBacklog();
|
|
@@ -1001,7 +1013,7 @@ function flags(a) {
|
|
|
1001
1013
|
}
|
|
1002
1014
|
|
|
1003
1015
|
const TRIAGED = { now: 'sent (AI: urgent)', digest: 'held for the digest', skip: 'dropped by the AI judge' };
|
|
1004
|
-
const NEED_ORDER = { prompt: 0, waiting: 1, plan: 1, question: 1, team: 1, review: 2, stuck: 3, conflict: 4 };
|
|
1016
|
+
const NEED_ORDER = { approval: 0, prompt: 0, waiting: 1, plan: 1, question: 1, team: 1, review: 2, stuck: 3, conflict: 4 };
|
|
1005
1017
|
/** What an agent needs from the user. `both`: overlaps on both agents (detail), else only the one that should rebase (inbox). */
|
|
1006
1018
|
function needsFor(a, { both = false } = {}) {
|
|
1007
1019
|
const items = [];
|
|
@@ -1021,23 +1033,34 @@ function teamNeeds() {
|
|
|
1021
1033
|
.filter((t) => ['ready', 'blocked'].includes(t.stage) && agents.has(teamOwner(t)))
|
|
1022
1034
|
.map((t) => ({ a: agents.get(teamOwner(t)), kind: 'team', key: `team:${t.id}`, t }));
|
|
1023
1035
|
}
|
|
1036
|
+
/** Tools that built agents (agentkit) wait to run until someone approves. */
|
|
1037
|
+
function approvalNeeds() {
|
|
1038
|
+
return builtReports.flatMap((r) => r.pending.map((p) => ({ a: { id: r.key, name: r.title, priority: 1 }, kind: 'approval', key: `approval:${r.key}:${p.id}`, r, p })));
|
|
1039
|
+
}
|
|
1024
1040
|
function inboxItems() {
|
|
1025
|
-
return [...[...agents.values()].flatMap((a) => needsFor(a)), ...teamNeeds()]
|
|
1041
|
+
return [...approvalNeeds(), ...[...agents.values()].flatMap((a) => needsFor(a)), ...teamNeeds()]
|
|
1026
1042
|
.sort((x, y) => NEED_ORDER[x.kind] - NEED_ORDER[y.kind] || (x.a.priority ?? 2) - (y.a.priority ?? 2));
|
|
1027
1043
|
}
|
|
1028
1044
|
|
|
1029
|
-
const NEED_LABEL = { team: 'Team', prompt: 'Needs input', waiting: 'Waiting on you', plan: 'Plan review', question: 'Question', review: 'AI review', stuck: 'Stuck?', conflict: 'Overlap' };
|
|
1045
|
+
const NEED_LABEL = { approval: 'Approval', team: 'Team', prompt: 'Needs input', waiting: 'Waiting on you', plan: 'Plan review', question: 'Question', review: 'AI review', stuck: 'Stuck?', conflict: 'Overlap' };
|
|
1030
1046
|
/** Why the AI judge thinks so, as a small tag. */
|
|
1031
1047
|
const aiTag = (confidence, what = 'AI') => `<span class="tag ai" title="Judged by a model from the agent's activity; your reactions tune later calls">🤖 ${what}${confidence != null ? ` ${Math.round(confidence * 100)}%` : ''}</span>`;
|
|
1032
1048
|
function needCard(it, { inDetail = false } = {}) {
|
|
1033
1049
|
const { a } = it;
|
|
1034
1050
|
const who = inDetail
|
|
1035
1051
|
? ''
|
|
1052
|
+
: it.kind === 'approval'
|
|
1053
|
+
? `<a href="#" class="need-agent" data-ib="built-open">⚙ ${esc(a.name)}</a> <span class="hint">${esc(it.r.host)}</span>`
|
|
1036
1054
|
: `<a href="#${a.id}" class="need-agent" data-goto="${a.id}">${prioChip(a.priority ?? 2)}${esc(a.name)}</a>${a.projectInfo?.name ? ` <span class="hint">${esc(a.projectInfo.name)}</span>` : ''}`;
|
|
1037
1055
|
const head = `<div class="need-head"><span class="need-kind ${it.kind}">${NEED_LABEL[it.kind]}</span>${who}</div>`;
|
|
1038
1056
|
const draft = (k, fallback = '') => esc(needDrafts.get(`${it.key}:${k}`) ?? fallback);
|
|
1039
1057
|
let body = '';
|
|
1040
|
-
if (it.kind === '
|
|
1058
|
+
if (it.kind === 'approval') {
|
|
1059
|
+
const p = it.p;
|
|
1060
|
+
body = `<p class="need-title">Wants to run <code>${esc(p.tool)}</code> <span class="hint">asked ${ago(p.at)}</span></p>
|
|
1061
|
+
<pre class="need-input">${esc(JSON.stringify(p.input, null, 2).slice(0, 1500))}</pre>
|
|
1062
|
+
<div class="need-actions"><button class="small primary" data-ib="approve" data-allow="1">Approve</button><button class="small" data-ib="approve">Deny</button>${it.r.chat ? `<a class="button small ghost" href="${esc(it.r.chat)}" target="_blank" rel="noopener">Its chat ↗</a>` : ''}</div>`;
|
|
1063
|
+
} else if (it.kind === 'prompt') {
|
|
1041
1064
|
const p = a.runtime.prompt;
|
|
1042
1065
|
body = `<p class="need-title">${esc(p?.question || a.runtime.attention)}</p>
|
|
1043
1066
|
<div class="need-actions">
|
|
@@ -1141,7 +1164,7 @@ function judgeLine() {
|
|
|
1141
1164
|
|
|
1142
1165
|
function renderInbox() {
|
|
1143
1166
|
const items = inboxItems();
|
|
1144
|
-
const urgent = items.filter((i) => i.kind === 'prompt' || i.kind === 'question' || i.kind === 'waiting').length;
|
|
1167
|
+
const urgent = items.filter((i) => i.kind === 'approval' || i.kind === 'prompt' || i.kind === 'question' || i.kind === 'waiting').length;
|
|
1145
1168
|
$('#inbox-count').textContent = items.length ? String(items.length) : '';
|
|
1146
1169
|
$('#inbox-count').className = `badge ${urgent ? 'attention' : ''}`;
|
|
1147
1170
|
if (sideTab !== 'inbox') return;
|
|
@@ -1175,6 +1198,14 @@ async function onNeed(e) {
|
|
|
1175
1198
|
const id = card.dataset.agent;
|
|
1176
1199
|
const key = card.dataset.need;
|
|
1177
1200
|
switch (el.dataset.ib) {
|
|
1201
|
+
case 'built-open':
|
|
1202
|
+
return showTab('built');
|
|
1203
|
+
case 'approve': {
|
|
1204
|
+
const [, , pid] = key.match(/^approval:(.+):([^:]+)$/);
|
|
1205
|
+
return act(async () => {
|
|
1206
|
+
await api('POST', `/agentkit/agents/${encodeURIComponent(id)}/approvals/${encodeURIComponent(pid)}`, { allow: el.dataset.allow === '1' });
|
|
1207
|
+
}, el.dataset.allow === '1' ? 'Approved; the agent goes on' : 'Declined');
|
|
1208
|
+
}
|
|
1178
1209
|
case 'key':
|
|
1179
1210
|
return act(() => api('POST', `/agents/${id}/prompt-key`, { key: el.dataset.key }), 'Sent');
|
|
1180
1211
|
case 'terminal':
|
|
@@ -1719,6 +1750,7 @@ function renderDetail() {
|
|
|
1719
1750
|
${a.runtime.attention ? `<div class="error-box">${esc(a.runtime.attention)}: the agent is waiting for an answer. Open its terminal to respond.</div>` : ''}
|
|
1720
1751
|
${raceBanner(a)}
|
|
1721
1752
|
${teamBanner(a)}
|
|
1753
|
+
${a.builds ? builtUi.panel(a) : ''}
|
|
1722
1754
|
${a.forkOf ? `<div class="bl-banner">⑂ Forked from <a href="#${a.forkOf.agentId}" data-goto="${a.forkOf.agentId}">${esc(a.forkOf.name)}</a>${a.forkOf.checkpoint ? ` at checkpoint ${a.forkOf.checkpoint}` : ''} ${ago(a.forkOf.at)}</div>` : ''}
|
|
1723
1755
|
${(() => {
|
|
1724
1756
|
const items = needsFor(a, { both: true });
|
|
@@ -3604,6 +3636,8 @@ function handleEvent(msg) {
|
|
|
3604
3636
|
for (const r of msg.races ?? []) races.set(r.id, r);
|
|
3605
3637
|
teams.clear();
|
|
3606
3638
|
for (const t of msg.teams ?? []) teams.set(t.id, t);
|
|
3639
|
+
builtReports = msg.built ?? [];
|
|
3640
|
+
builtUi.render();
|
|
3607
3641
|
msg.agents.forEach(watchDocs);
|
|
3608
3642
|
if (selectedId && !agents.has(selectedId)) selectedId = null;
|
|
3609
3643
|
if (openTermOnLoad && selectedId) {
|
|
@@ -3615,6 +3649,7 @@ function handleEvent(msg) {
|
|
|
3615
3649
|
agents.set(msg.agent.id, msg.agent);
|
|
3616
3650
|
watchDocs(msg.agent);
|
|
3617
3651
|
agentChanged(msg.agent);
|
|
3652
|
+
if (msg.agent.builds) builtUi.sessionChanged(msg.agent);
|
|
3618
3653
|
} else if (msg.t === 'removed') {
|
|
3619
3654
|
agents.delete(msg.id);
|
|
3620
3655
|
if (selectedId === msg.id) select(null);
|
|
@@ -3626,6 +3661,9 @@ function handleEvent(msg) {
|
|
|
3626
3661
|
teams.set(msg.team.id, msg.team);
|
|
3627
3662
|
} else if (msg.t === 'team-removed') {
|
|
3628
3663
|
teams.delete(msg.id);
|
|
3664
|
+
} else if (msg.t === 'built') {
|
|
3665
|
+
builtReports = msg.reports;
|
|
3666
|
+
builtUi.render();
|
|
3629
3667
|
} else if (msg.t === 'backlog') {
|
|
3630
3668
|
backlogState = msg.backlog;
|
|
3631
3669
|
} else if (msg.t === 'projects') {
|