copilot-hub 0.1.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 +215 -0
- package/apps/agent-engine/.env.example +41 -0
- package/apps/agent-engine/LICENSE +21 -0
- package/apps/agent-engine/README.md +57 -0
- package/apps/agent-engine/bot-registry.example.json +28 -0
- package/apps/agent-engine/capabilities/example/index.js +3 -0
- package/apps/agent-engine/capabilities/example/manifest.json +14 -0
- package/apps/agent-engine/dist/agent-worker.js +241 -0
- package/apps/agent-engine/dist/config.js +225 -0
- package/apps/agent-engine/dist/index.js +352 -0
- package/apps/agent-engine/dist/test/project-fingerprint.test.js +40 -0
- package/apps/agent-engine/dist/test/thread-id.test.js +12 -0
- package/apps/agent-engine/package.json +28 -0
- package/apps/control-plane/.env.example +25 -0
- package/apps/control-plane/README.md +35 -0
- package/apps/control-plane/bot-registry.example.json +40 -0
- package/apps/control-plane/capabilities/example/index.js +3 -0
- package/apps/control-plane/capabilities/example/manifest.json +14 -0
- package/apps/control-plane/dist/agent-worker.js +243 -0
- package/apps/control-plane/dist/channels/channel-factory.js +21 -0
- package/apps/control-plane/dist/channels/hub-ops-commands.js +752 -0
- package/apps/control-plane/dist/channels/telegram-channel.js +743 -0
- package/apps/control-plane/dist/channels/whatsapp-channel.js +35 -0
- package/apps/control-plane/dist/config.js +230 -0
- package/apps/control-plane/dist/copilot-hub.js +138 -0
- package/apps/control-plane/dist/index.js +349 -0
- package/apps/control-plane/dist/kernel/admin-contract.js +51 -0
- package/apps/control-plane/dist/test/project-fingerprint.test.js +40 -0
- package/apps/control-plane/dist/test/thread-id.test.js +12 -0
- package/apps/control-plane/package.json +27 -0
- package/package.json +89 -0
- package/packages/contracts/README.md +10 -0
- package/packages/contracts/dist/control-plane.d.ts +24 -0
- package/packages/contracts/dist/control-plane.js +37 -0
- package/packages/contracts/dist/control-plane.js.map +1 -0
- package/packages/contracts/dist/index.d.ts +1 -0
- package/packages/contracts/dist/index.js +2 -0
- package/packages/contracts/dist/index.js.map +1 -0
- package/packages/contracts/package.json +27 -0
- package/packages/core/README.md +33 -0
- package/packages/core/dist/agent-supervisor.d.ts +39 -0
- package/packages/core/dist/agent-supervisor.js +552 -0
- package/packages/core/dist/agent-supervisor.js.map +1 -0
- package/packages/core/dist/bot-manager.d.ts +66 -0
- package/packages/core/dist/bot-manager.js +333 -0
- package/packages/core/dist/bot-manager.js.map +1 -0
- package/packages/core/dist/bot-registry.d.ts +60 -0
- package/packages/core/dist/bot-registry.js +381 -0
- package/packages/core/dist/bot-registry.js.map +1 -0
- package/packages/core/dist/bot-runtime.d.ts +135 -0
- package/packages/core/dist/bot-runtime.js +349 -0
- package/packages/core/dist/bot-runtime.js.map +1 -0
- package/packages/core/dist/bridge-service.d.ts +39 -0
- package/packages/core/dist/bridge-service.js +272 -0
- package/packages/core/dist/bridge-service.js.map +1 -0
- package/packages/core/dist/capability-manager.d.ts +18 -0
- package/packages/core/dist/capability-manager.js +335 -0
- package/packages/core/dist/capability-manager.js.map +1 -0
- package/packages/core/dist/capability-scaffold.d.ts +26 -0
- package/packages/core/dist/capability-scaffold.js +118 -0
- package/packages/core/dist/capability-scaffold.js.map +1 -0
- package/packages/core/dist/channel-factory.d.ts +6 -0
- package/packages/core/dist/channel-factory.js +22 -0
- package/packages/core/dist/channel-factory.js.map +1 -0
- package/packages/core/dist/codex-app-client.d.ts +56 -0
- package/packages/core/dist/codex-app-client.js +762 -0
- package/packages/core/dist/codex-app-client.js.map +1 -0
- package/packages/core/dist/codex-provider.d.ts +31 -0
- package/packages/core/dist/codex-provider.js +64 -0
- package/packages/core/dist/codex-provider.js.map +1 -0
- package/packages/core/dist/control-permission.d.ts +19 -0
- package/packages/core/dist/control-permission.js +106 -0
- package/packages/core/dist/control-permission.js.map +1 -0
- package/packages/core/dist/control-plane-actions.d.ts +1 -0
- package/packages/core/dist/control-plane-actions.js +2 -0
- package/packages/core/dist/control-plane-actions.js.map +1 -0
- package/packages/core/dist/example-capability.d.ts +17 -0
- package/packages/core/dist/example-capability.js +22 -0
- package/packages/core/dist/example-capability.js.map +1 -0
- package/packages/core/dist/extension-contract.d.ts +22 -0
- package/packages/core/dist/extension-contract.js +28 -0
- package/packages/core/dist/extension-contract.js.map +1 -0
- package/packages/core/dist/index.d.ts +26 -0
- package/packages/core/dist/index.js +27 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/instance-lock.d.ts +9 -0
- package/packages/core/dist/instance-lock.js +74 -0
- package/packages/core/dist/instance-lock.js.map +1 -0
- package/packages/core/dist/kernel-control-plane.d.ts +16 -0
- package/packages/core/dist/kernel-control-plane.js +500 -0
- package/packages/core/dist/kernel-control-plane.js.map +1 -0
- package/packages/core/dist/kernel-version.d.ts +1 -0
- package/packages/core/dist/kernel-version.js +2 -0
- package/packages/core/dist/kernel-version.js.map +1 -0
- package/packages/core/dist/project-fingerprint.d.ts +11 -0
- package/packages/core/dist/project-fingerprint.js +33 -0
- package/packages/core/dist/project-fingerprint.js.map +1 -0
- package/packages/core/dist/provider-factory.d.ts +7 -0
- package/packages/core/dist/provider-factory.js +21 -0
- package/packages/core/dist/provider-factory.js.map +1 -0
- package/packages/core/dist/secret-store.d.ts +18 -0
- package/packages/core/dist/secret-store.js +110 -0
- package/packages/core/dist/secret-store.js.map +1 -0
- package/packages/core/dist/state-store.d.ts +50 -0
- package/packages/core/dist/state-store.js +324 -0
- package/packages/core/dist/state-store.js.map +1 -0
- package/packages/core/dist/telegram-channel.d.ts +27 -0
- package/packages/core/dist/telegram-channel.js +951 -0
- package/packages/core/dist/telegram-channel.js.map +1 -0
- package/packages/core/dist/thread-id.d.ts +1 -0
- package/packages/core/dist/thread-id.js +12 -0
- package/packages/core/dist/thread-id.js.map +1 -0
- package/packages/core/dist/whatsapp-channel.d.ts +26 -0
- package/packages/core/dist/whatsapp-channel.js +36 -0
- package/packages/core/dist/whatsapp-channel.js.map +1 -0
- package/packages/core/dist/workspace-paths.d.ts +5 -0
- package/packages/core/dist/workspace-paths.js +77 -0
- package/packages/core/dist/workspace-paths.js.map +1 -0
- package/packages/core/dist/workspace-policy.d.ts +30 -0
- package/packages/core/dist/workspace-policy.js +104 -0
- package/packages/core/dist/workspace-policy.js.map +1 -0
- package/packages/core/package.json +126 -0
- package/scripts/cli.mjs +537 -0
- package/scripts/configure.mjs +254 -0
- package/scripts/ensure-shared-build.mjs +96 -0
- package/scripts/run-node-tests.mjs +52 -0
- package/scripts/supervisor.mjs +332 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 openminedev
|
|
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,215 @@
|
|
|
1
|
+
# Copilot Hub
|
|
2
|
+
|
|
3
|
+
Copilot Hub is a 2-plane monorepo for building and operating Telegram AI agents.
|
|
4
|
+
|
|
5
|
+
## Planes
|
|
6
|
+
|
|
7
|
+
- `apps/control-plane`: single Telegram hub chat for operations commands and LLM development tasks.
|
|
8
|
+
- `apps/agent-engine`: execution plane (workers, channels, sessions, capabilities).
|
|
9
|
+
|
|
10
|
+
Shared packages:
|
|
11
|
+
|
|
12
|
+
- `packages/contracts`
|
|
13
|
+
- `packages/core`
|
|
14
|
+
- `packages/capabilities`
|
|
15
|
+
|
|
16
|
+
## Hub chat model
|
|
17
|
+
|
|
18
|
+
The same Telegram chat handles both operations and development:
|
|
19
|
+
|
|
20
|
+
- commands: `/help`, `/health`, `/bots`, `/create_agent`, `/cancel`
|
|
21
|
+
- normal message: handled by the LLM assistant
|
|
22
|
+
|
|
23
|
+
## Workspace isolation
|
|
24
|
+
|
|
25
|
+
Each runtime agent has its own dedicated `workspaceRoot`.
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
CA["Chat A"] --> A["Agent A"] --> WA["~/Desktop/copilot_workspaces/Agent_A"]
|
|
30
|
+
CB["Chat B"] --> B["Agent B"] --> WB["~/Desktop/copilot_workspaces/Agent_B"]
|
|
31
|
+
CC["Chat C"] --> C["Agent C"] --> WC["~/Desktop/copilot_workspaces/Agent_C"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Why this matters:
|
|
35
|
+
|
|
36
|
+
- multiple chats can run at the same time without mixing files
|
|
37
|
+
- multiple projects can run in parallel on the same machine
|
|
38
|
+
- one agent change stays inside that agent workspace
|
|
39
|
+
|
|
40
|
+
## Control-plane role
|
|
41
|
+
|
|
42
|
+
- `control chat` is a simple maintenance chat: fix issues and add new capabilities.
|
|
43
|
+
- `runtime chats` are user-facing chats: execute tasks and deliver results.
|
|
44
|
+
- users stay in runtime chats for normal work and use control chat only when an agent needs a fix or upgrade.
|
|
45
|
+
|
|
46
|
+
## Example flow
|
|
47
|
+
|
|
48
|
+
Scenario: user asks a runtime chat (Agent A) to create a website from an image.
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart TD
|
|
52
|
+
U1["User -> Runtime chat (Agent A): Create a website from this image"] --> C{"Can Agent A read images?"}
|
|
53
|
+
C -- Yes --> O1["Agent A builds the website"]
|
|
54
|
+
C -- No --> M["Agent A says: I cannot read images yet"]
|
|
55
|
+
|
|
56
|
+
M --> U3["User -> Control chat (control-plane): Fix this for Agent A"]
|
|
57
|
+
U3 --> I["Control chat adds image_reader to Agent A workspace"]
|
|
58
|
+
I --> T{"Works correctly?"}
|
|
59
|
+
T -- No --> U3
|
|
60
|
+
T -- Yes --> R["Agent A reloads capabilities"]
|
|
61
|
+
|
|
62
|
+
R --> U4["User -> Runtime chat (Agent A): Continue the website task"]
|
|
63
|
+
U4 --> O1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Install from npm
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g copilot-hub@latest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
copilot-hub configure
|
|
76
|
+
copilot-hub start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick start from source
|
|
80
|
+
|
|
81
|
+
1. Install dependencies:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. Configure the required hub token:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run configure
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
3. Start services:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run start
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`start` checks required tokens and prompts only if values are missing.
|
|
100
|
+
|
|
101
|
+
## Telegram setup
|
|
102
|
+
|
|
103
|
+
### 1) Create the control-plane (hub) bot token
|
|
104
|
+
|
|
105
|
+
1. Open Telegram and search for `@BotFather`.
|
|
106
|
+
2. Send `/start`.
|
|
107
|
+
3. Send `/newbot`.
|
|
108
|
+
4. Enter the bot display name you want.
|
|
109
|
+
5. Enter a unique username ending with `bot` (example: `my_copilot_hub_bot`).
|
|
110
|
+
6. BotFather returns a token like `123456789:AA...`.
|
|
111
|
+
|
|
112
|
+
Now run:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm run configure
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
When prompted, paste this token.
|
|
119
|
+
|
|
120
|
+
### 2) Start Copilot Hub
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm run start
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Then open your hub bot in Telegram and send `/start`.
|
|
127
|
+
|
|
128
|
+
Hub commands:
|
|
129
|
+
|
|
130
|
+
- `/help`
|
|
131
|
+
- `/health`
|
|
132
|
+
- `/bots`
|
|
133
|
+
- `/create_agent`
|
|
134
|
+
- `/cancel`
|
|
135
|
+
|
|
136
|
+
### 3) Create runtime agent bot(s)
|
|
137
|
+
|
|
138
|
+
You need one Telegram bot token per runtime agent.
|
|
139
|
+
|
|
140
|
+
1. Go back to `@BotFather`.
|
|
141
|
+
2. Run `/newbot` again.
|
|
142
|
+
3. Create a new bot for the runtime agent.
|
|
143
|
+
4. Copy the new token.
|
|
144
|
+
5. In the hub chat, run `/create_agent` and follow the wizard:
|
|
145
|
+
- Step 1: send the runtime agent token
|
|
146
|
+
- Step 2: send agent id (or `default`)
|
|
147
|
+
- Step 3: reply `YES`
|
|
148
|
+
|
|
149
|
+
After creation, use `/bots` in the hub chat to manage policy, reset context, or delete an agent.
|
|
150
|
+
A streamlined default profile is applied, and actions start from that agent workspace folder.
|
|
151
|
+
|
|
152
|
+
### 4) Token safety
|
|
153
|
+
|
|
154
|
+
- Never commit real bot tokens.
|
|
155
|
+
- If a token is leaked, regenerate it in `@BotFather` using `/revoke`.
|
|
156
|
+
- Keep local runtime files (`data/`, `logs/`) private.
|
|
157
|
+
|
|
158
|
+
## Startup troubleshooting
|
|
159
|
+
|
|
160
|
+
- If `npm run start` fails, first read the error and follow the suggested action.
|
|
161
|
+
- `npm run start` now auto-detects Codex from VS Code (Windows) and can install Codex CLI automatically if missing.
|
|
162
|
+
- For Codex login issues, run `codex login` (or the configured `CODEX_BIN`) and retry `npm run start`.
|
|
163
|
+
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex` or set `CODEX_BIN` in `.env`.
|
|
164
|
+
- If you are still stuck, ask your favorite LLM with the exact error output.
|
|
165
|
+
|
|
166
|
+
## Commands
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm run start
|
|
170
|
+
npm run stop
|
|
171
|
+
npm run restart
|
|
172
|
+
npm run status
|
|
173
|
+
npm run logs
|
|
174
|
+
npm run configure
|
|
175
|
+
npm run test
|
|
176
|
+
npm run lint
|
|
177
|
+
npm run format:check
|
|
178
|
+
npm run check:apps
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## npm release (CI)
|
|
182
|
+
|
|
183
|
+
Publishing is automated from GitHub Actions on tags (`v*`).
|
|
184
|
+
|
|
185
|
+
Release flow:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm version patch
|
|
189
|
+
git push origin main --follow-tags
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The release workflow validates build/test/lint/format, checks that the tag version matches `package.json`, then publishes to npm.
|
|
193
|
+
|
|
194
|
+
Authentication options:
|
|
195
|
+
|
|
196
|
+
- Recommended: npm Trusted Publishing (OIDC/provenance)
|
|
197
|
+
- Fallback: set `NPM_TOKEN` in GitHub repository secrets
|
|
198
|
+
|
|
199
|
+
## Workspace policy
|
|
200
|
+
|
|
201
|
+
- Default workspace root: `~/Desktop/copilot_workspaces` when not explicitly set.
|
|
202
|
+
- `WORKSPACE_STRICT_MODE=true` enforces allowed roots.
|
|
203
|
+
- `WORKSPACE_ALLOWED_ROOTS` adds extra allowed roots.
|
|
204
|
+
- Workspaces inside kernel directories are rejected.
|
|
205
|
+
|
|
206
|
+
## Runtime files
|
|
207
|
+
|
|
208
|
+
- PIDs: `.copilot-hub/pids/`
|
|
209
|
+
- Logs: `logs/`
|
|
210
|
+
|
|
211
|
+
## Security
|
|
212
|
+
|
|
213
|
+
- Never commit real tokens.
|
|
214
|
+
- Keep `.env` and runtime data local.
|
|
215
|
+
- Rotate leaked tokens immediately.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Runtime state files
|
|
2
|
+
BOT_DATA_DIR=./data
|
|
3
|
+
BOT_REGISTRY_FILE=./data/bot-registry.json
|
|
4
|
+
SECRET_STORE_FILE=./data/secrets.json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Worker defaults
|
|
8
|
+
# If empty, runtime uses Desktop/copilot_workspaces by default
|
|
9
|
+
# (Windows: %USERPROFILE%/Desktop/copilot_workspaces)
|
|
10
|
+
DEFAULT_WORKSPACE_ROOT=
|
|
11
|
+
PROJECTS_BASE_DIR=
|
|
12
|
+
WORKSPACE_STRICT_MODE=true
|
|
13
|
+
WORKSPACE_ALLOWED_ROOTS=
|
|
14
|
+
DEFAULT_PROVIDER_KIND=codex
|
|
15
|
+
CODEX_HOME_DIR=
|
|
16
|
+
CODEX_SANDBOX=danger-full-access
|
|
17
|
+
CODEX_APPROVAL_POLICY=never
|
|
18
|
+
TURN_ACTIVITY_TIMEOUT_MS=3600000
|
|
19
|
+
MAX_THREAD_MESSAGES=200
|
|
20
|
+
|
|
21
|
+
AGENT_HEARTBEAT_ENABLED=true
|
|
22
|
+
AGENT_HEARTBEAT_INTERVAL_MS=5000
|
|
23
|
+
AGENT_HEARTBEAT_TIMEOUT_MS=4000
|
|
24
|
+
|
|
25
|
+
THREAD_MODE=single
|
|
26
|
+
SHARED_THREAD_ID=shared-main
|
|
27
|
+
TELEGRAM_ALLOWED_CHAT_IDS=
|
|
28
|
+
|
|
29
|
+
INSTANCE_LOCK_ENABLED=true
|
|
30
|
+
INSTANCE_LOCK_FILE=./data/runtime.lock
|
|
31
|
+
|
|
32
|
+
WEB_HOST=127.0.0.1
|
|
33
|
+
WEB_PORT=8787
|
|
34
|
+
WEB_PUBLIC_BASE_URL=http://localhost:8787
|
|
35
|
+
WEB_PORT_AUTO_INCREMENT=true
|
|
36
|
+
WEB_PORT_SEARCH_MAX=30
|
|
37
|
+
|
|
38
|
+
# Optional worker token environment variables used in bot-registry.json
|
|
39
|
+
TELEGRAM_TOKEN_AGENT_1=
|
|
40
|
+
|
|
41
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 amine
|
|
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.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Agent Engine
|
|
2
|
+
|
|
3
|
+
`agent-engine` is the execution plane for worker agents, Telegram channels, capabilities, projects, and runtime policy.
|
|
4
|
+
|
|
5
|
+
In Copilot Hub:
|
|
6
|
+
- `apps/control-plane`: single Telegram hub chat (operations + LLM development)
|
|
7
|
+
- `apps/agent-engine`: runtime execution plane
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install
|
|
13
|
+
# Windows
|
|
14
|
+
copy .env.example .env
|
|
15
|
+
# macOS/Linux
|
|
16
|
+
cp .env.example .env
|
|
17
|
+
npm run setup
|
|
18
|
+
npm run start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Operator entry point
|
|
22
|
+
|
|
23
|
+
Use `apps/control-plane` as the main operator chat.
|
|
24
|
+
|
|
25
|
+
## Workspace policy
|
|
26
|
+
|
|
27
|
+
- If `DEFAULT_WORKSPACE_ROOT` is empty, default root is `~/Desktop/copilot_workspaces`.
|
|
28
|
+
- `WORKSPACE_STRICT_MODE=true` enforces allowed roots.
|
|
29
|
+
- `WORKSPACE_ALLOWED_ROOTS` lets you append extra allowed roots.
|
|
30
|
+
- Agent workspaces must stay outside the kernel directory.
|
|
31
|
+
|
|
32
|
+
## Runtime API
|
|
33
|
+
|
|
34
|
+
Core:
|
|
35
|
+
- `GET /api/health`
|
|
36
|
+
- `GET /api/bots`
|
|
37
|
+
- `POST /api/bots/create`
|
|
38
|
+
- `POST /api/bots/:botId/delete`
|
|
39
|
+
- `POST /api/bots/:botId/policy`
|
|
40
|
+
- `POST /api/bots/:botId/reset`
|
|
41
|
+
|
|
42
|
+
Projects:
|
|
43
|
+
- `GET /api/projects`
|
|
44
|
+
- `POST /api/projects/create`
|
|
45
|
+
- `POST /api/bots/:botId/project`
|
|
46
|
+
|
|
47
|
+
Capabilities and approvals:
|
|
48
|
+
- `GET /api/bots/:botId/capabilities`
|
|
49
|
+
- `POST /api/bots/:botId/capabilities/reload`
|
|
50
|
+
- `POST /api/bots/:botId/capabilities/scaffold`
|
|
51
|
+
- `GET /api/bots/:botId/approvals`
|
|
52
|
+
- `POST /api/bots/:botId/approvals/:approvalId`
|
|
53
|
+
|
|
54
|
+
## Security
|
|
55
|
+
|
|
56
|
+
- Never commit `.env` or `data/`.
|
|
57
|
+
- Rotate exposed tokens.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"agents": [
|
|
4
|
+
{
|
|
5
|
+
"id": "Agent_1",
|
|
6
|
+
"name": "Agent 1",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"autoStart": true,
|
|
9
|
+
"threadMode": "single",
|
|
10
|
+
"sharedThreadId": "shared-Agent_1",
|
|
11
|
+
"provider": {
|
|
12
|
+
"kind": "codex",
|
|
13
|
+
"options": {
|
|
14
|
+
"sandboxMode": "danger-full-access",
|
|
15
|
+
"approvalPolicy": "never"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"channels": [
|
|
19
|
+
{
|
|
20
|
+
"kind": "telegram",
|
|
21
|
+
"id": "telegram_Agent_1",
|
|
22
|
+
"tokenEnv": "TELEGRAM_TOKEN_AGENT_1"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"capabilities": []
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "example-capability",
|
|
3
|
+
"name": "Example Capability",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"entry": "./index.js",
|
|
6
|
+
"minKernelVersion": "1.0.0",
|
|
7
|
+
"timeoutMs": 3000,
|
|
8
|
+
"hooks": [
|
|
9
|
+
"onRuntimeStart",
|
|
10
|
+
"onTurnStart",
|
|
11
|
+
"onTurnResult"
|
|
12
|
+
],
|
|
13
|
+
"permissions": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { BotRuntime } from "@copilot-hub/core/bot-runtime";
|
|
3
|
+
const rawBotConfig = String(process.env.AGENT_BOT_CONFIG_JSON ?? "").trim();
|
|
4
|
+
const rawProviderDefaults = String(process.env.AGENT_PROVIDER_DEFAULTS_JSON ?? "").trim();
|
|
5
|
+
const turnActivityTimeoutMs = Number.parseInt(String(process.env.AGENT_TURN_ACTIVITY_TIMEOUT_MS ?? "3600000"), 10);
|
|
6
|
+
const maxMessages = Number.parseInt(String(process.env.AGENT_MAX_MESSAGES ?? "200"), 10);
|
|
7
|
+
const initialWebPublicBaseUrl = String(process.env.AGENT_WEB_PUBLIC_BASE_URL ?? "http://127.0.0.1:8787").trim();
|
|
8
|
+
const kernelRequestTimeoutMs = Number.parseInt(String(process.env.AGENT_KERNEL_REQUEST_TIMEOUT_MS ?? "20000"), 10);
|
|
9
|
+
if (!rawBotConfig) {
|
|
10
|
+
throw new Error("AGENT_BOT_CONFIG_JSON is required.");
|
|
11
|
+
}
|
|
12
|
+
const botConfig = JSON.parse(rawBotConfig);
|
|
13
|
+
const providerDefaults = rawProviderDefaults ? JSON.parse(rawProviderDefaults) : {};
|
|
14
|
+
let nextKernelRequestId = 1;
|
|
15
|
+
const pendingKernelRequests = new Map();
|
|
16
|
+
const runtime = new BotRuntime({
|
|
17
|
+
botConfig,
|
|
18
|
+
providerDefaults,
|
|
19
|
+
turnActivityTimeoutMs,
|
|
20
|
+
maxMessages,
|
|
21
|
+
kernelControl: {
|
|
22
|
+
request: (payload) => requestKernelAction(payload),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
runtime.setWebPublicBaseUrl(initialWebPublicBaseUrl);
|
|
26
|
+
process.on("message", (message) => {
|
|
27
|
+
void handleInboundMessage(message);
|
|
28
|
+
});
|
|
29
|
+
process.on("disconnect", () => {
|
|
30
|
+
void gracefulShutdown(0);
|
|
31
|
+
});
|
|
32
|
+
process.on("SIGINT", () => {
|
|
33
|
+
void gracefulShutdown(0);
|
|
34
|
+
});
|
|
35
|
+
process.on("SIGTERM", () => {
|
|
36
|
+
void gracefulShutdown(0);
|
|
37
|
+
});
|
|
38
|
+
process.on("uncaughtException", (error) => {
|
|
39
|
+
console.error(`Worker uncaught exception: ${sanitizeError(error)}`);
|
|
40
|
+
void gracefulShutdown(1);
|
|
41
|
+
});
|
|
42
|
+
process.on("unhandledRejection", (reason) => {
|
|
43
|
+
console.error(`Worker unhandled rejection: ${sanitizeError(reason)}`);
|
|
44
|
+
void gracefulShutdown(1);
|
|
45
|
+
});
|
|
46
|
+
sendEvent("workerReady", {
|
|
47
|
+
runtimeId: runtime.id,
|
|
48
|
+
name: runtime.name,
|
|
49
|
+
});
|
|
50
|
+
async function handleInboundMessage(message) {
|
|
51
|
+
const type = String(message?.type ?? "request").trim();
|
|
52
|
+
if (type === "kernelResponse") {
|
|
53
|
+
handleKernelResponse(message);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (type !== "request") {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await handleWorkerRequest(message);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const requestId = message?.requestId ?? null;
|
|
64
|
+
if (requestId !== null && requestId !== undefined) {
|
|
65
|
+
sendResponse({
|
|
66
|
+
requestId,
|
|
67
|
+
ok: false,
|
|
68
|
+
error: sanitizeError(error),
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.error(`Worker inbound message failed: ${sanitizeError(error)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function handleWorkerRequest(message) {
|
|
76
|
+
const requestId = message?.requestId;
|
|
77
|
+
const action = String(message?.action ?? "").trim();
|
|
78
|
+
const payload = message?.payload ?? {};
|
|
79
|
+
if (!requestId) {
|
|
80
|
+
throw new Error("requestId is required.");
|
|
81
|
+
}
|
|
82
|
+
if (!action) {
|
|
83
|
+
throw new Error("action is required.");
|
|
84
|
+
}
|
|
85
|
+
let result;
|
|
86
|
+
switch (action) {
|
|
87
|
+
case "getStatus": {
|
|
88
|
+
result = runtime.getStatus();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "startChannels": {
|
|
92
|
+
result = await runtime.startChannels();
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "stopChannels": {
|
|
96
|
+
result = await runtime.stopChannels();
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "resetWebThread": {
|
|
100
|
+
result = await runtime.resetWebThread();
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case "listPendingApprovals": {
|
|
104
|
+
const threadId = payload?.threadId ? String(payload.threadId) : undefined;
|
|
105
|
+
result = await runtime.listPendingApprovals(threadId);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "resolvePendingApproval": {
|
|
109
|
+
result = await runtime.resolvePendingApproval({
|
|
110
|
+
threadId: String(payload?.threadId ?? ""),
|
|
111
|
+
approvalId: String(payload?.approvalId ?? ""),
|
|
112
|
+
decision: String(payload?.decision ?? ""),
|
|
113
|
+
});
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "reloadCapabilities": {
|
|
117
|
+
const capabilityDefinitions = Array.isArray(payload?.capabilityDefinitions)
|
|
118
|
+
? payload.capabilityDefinitions
|
|
119
|
+
: null;
|
|
120
|
+
result = await runtime.reloadCapabilities(capabilityDefinitions);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "setProjectRoot": {
|
|
124
|
+
const projectRoot = String(payload?.projectRoot ?? "").trim();
|
|
125
|
+
if (!projectRoot) {
|
|
126
|
+
throw new Error("projectRoot is required.");
|
|
127
|
+
}
|
|
128
|
+
await runtime.setProjectRoot(projectRoot);
|
|
129
|
+
result = runtime.getStatus();
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "setWebPublicBaseUrl": {
|
|
133
|
+
const value = String(payload?.webPublicBaseUrl ?? "").trim();
|
|
134
|
+
if (!value) {
|
|
135
|
+
throw new Error("webPublicBaseUrl is required.");
|
|
136
|
+
}
|
|
137
|
+
runtime.setWebPublicBaseUrl(value);
|
|
138
|
+
result = runtime.getStatus();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "shutdown": {
|
|
142
|
+
await runtime.shutdown();
|
|
143
|
+
result = { ok: true };
|
|
144
|
+
sendResponse({ requestId, ok: true, result });
|
|
145
|
+
process.exit(0);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
default: {
|
|
149
|
+
throw new Error(`Unsupported worker action '${action}'.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
sendResponse({ requestId, ok: true, result });
|
|
153
|
+
}
|
|
154
|
+
async function gracefulShutdown(exitCode) {
|
|
155
|
+
try {
|
|
156
|
+
await runtime.shutdown();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Ignore shutdown errors during process termination.
|
|
160
|
+
}
|
|
161
|
+
for (const pending of pendingKernelRequests.values()) {
|
|
162
|
+
clearTimeout(pending.timer);
|
|
163
|
+
pending.reject(new Error("Worker process is shutting down."));
|
|
164
|
+
}
|
|
165
|
+
pendingKernelRequests.clear();
|
|
166
|
+
process.exit(exitCode);
|
|
167
|
+
}
|
|
168
|
+
function sendResponse(value) {
|
|
169
|
+
if (!process.send) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
process.send({
|
|
173
|
+
type: "response",
|
|
174
|
+
...value,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function sendEvent(event, payload) {
|
|
178
|
+
if (!process.send) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
process.send({
|
|
182
|
+
type: "event",
|
|
183
|
+
event,
|
|
184
|
+
payload,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function requestKernelAction({ action, payload, context }) {
|
|
188
|
+
if (!process.send) {
|
|
189
|
+
return Promise.reject(new Error("Kernel IPC is unavailable."));
|
|
190
|
+
}
|
|
191
|
+
const requestId = `kreq_${Date.now()}_${nextKernelRequestId++}`;
|
|
192
|
+
const timeoutMs = Number.isFinite(kernelRequestTimeoutMs) && kernelRequestTimeoutMs >= 1000
|
|
193
|
+
? kernelRequestTimeoutMs
|
|
194
|
+
: 20000;
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const timer = setTimeout(() => {
|
|
197
|
+
pendingKernelRequests.delete(requestId);
|
|
198
|
+
reject(new Error(`Kernel request '${String(action ?? "")}' timed out after ${timeoutMs}ms.`));
|
|
199
|
+
}, timeoutMs);
|
|
200
|
+
pendingKernelRequests.set(requestId, {
|
|
201
|
+
resolve,
|
|
202
|
+
reject,
|
|
203
|
+
timer,
|
|
204
|
+
});
|
|
205
|
+
try {
|
|
206
|
+
process.send({
|
|
207
|
+
type: "kernelRequest",
|
|
208
|
+
requestId,
|
|
209
|
+
action,
|
|
210
|
+
payload,
|
|
211
|
+
context,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
pendingKernelRequests.delete(requestId);
|
|
217
|
+
reject(error);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function handleKernelResponse(message) {
|
|
222
|
+
const requestId = String(message?.requestId ?? "").trim();
|
|
223
|
+
if (!requestId) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const pending = pendingKernelRequests.get(requestId);
|
|
227
|
+
if (!pending) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
pendingKernelRequests.delete(requestId);
|
|
231
|
+
clearTimeout(pending.timer);
|
|
232
|
+
if (message?.ok) {
|
|
233
|
+
pending.resolve(message.result);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
pending.reject(new Error(String(message?.error ?? "Unknown kernel response error.")));
|
|
237
|
+
}
|
|
238
|
+
function sanitizeError(error) {
|
|
239
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
240
|
+
return raw.split(/\r?\n/).slice(0, 12).join("\n");
|
|
241
|
+
}
|