tylor-mcp 1.0.0 β 1.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/README.md +165 -146
- package/bin/tylor.js +37 -2
- package/package.json +1 -1
- package/server/config.py +8 -13
- package/server/server.log +1 -0
- package/server/storage/dynamo.py +33 -0
- package/server/storage/json_store.py +49 -2
- package/server/tests/test_install.py +18 -4
- package/server/tests/test_ui_server.py +57 -19
- package/server/tests/test_ui_shader_background.py +14 -17
- package/server/tests/test_ui_story_6_3.py +64 -71
- package/server/tools/agents.py +278 -10
- package/server/tools/executor.py +38 -1
- package/server/tools/harness.py +119 -14
- package/server/tools/help.py +21 -1
- package/server/tools/hooks.py +19 -6
- package/server/tools/registry.py +50 -3
- package/server/tools/security.py +162 -0
- package/server/tools/skill_installer.py +34 -1
- package/server/tools/summarizer.py +3 -1
- package/server/tools/tests/test_agents.py +17 -4
- package/server/tools/tests/test_executor.py +39 -0
- package/server/tools/tests/test_help_agent101.py +20 -13
- package/server/tools/tests/test_registry_client.py +26 -12
- package/server/tools/tests/test_skill_installer.py +19 -0
- package/server/tools/tests/test_spawn_agent_harness.py +225 -0
- package/server/tools/tests/test_thread_command_skills.py +6 -0
- package/server/tools/tests/test_tier1_schema.py +17 -3
- package/server/tools/tylor.py +11 -10
- package/server/ui_server.py +160 -11
- package/server/validate.py +29 -15
- package/skills/ecc-data/SKILL.md +32 -0
- package/skills/ecc-diagrams/SKILL.md +31 -0
- package/skills/ecc-pipeline/SKILL.md +31 -0
- package/skills/ecc-presentation/SKILL.md +31 -0
- package/skills/ecc-web/SKILL.md +31 -0
- package/skills/help-agent101/SKILL.md +1 -0
- package/skills/open-threads-ui/SKILL.md +33 -0
- package/ui/index.html +760 -889
package/README.md
CHANGED
|
@@ -1,146 +1,165 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<img src="assets/tylor_logo.png" alt="Tylor Logo" width="150">
|
|
3
|
-
<h1>Tylor</h1>
|
|
4
|
-
<p><strong>The Tailor to Your Threads</strong></p>
|
|
5
|
-
<p><em>Give Claude Code persistent memory, laser-focused context, and an autonomous team of specialists.</em></p>
|
|
6
|
-
|
|
7
|
-
[](https://opensource.org/licenses/MIT)
|
|
8
|
-
[](#)
|
|
9
|
-
[](#)
|
|
10
|
-
[](#)
|
|
11
|
-
[](#)
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
Tylor transforms your Claude Code experience from a single-shot terminal interaction into a **persistent, intelligent workspace**.
|
|
17
|
-
|
|
18
|
-
Every time you open Claude Code, you normally start from zero. Tylor fixes that. It organizes your work into **threads**βisolated, named workspaces that survive restarts and reboots. It remembers every decision, every line of code, and every discussion, so you never have to repeat yourself.
|
|
19
|
-
|
|
20
|
-
**No database. No cloud account. No configuration. Just install and go.**
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## π¨ How It Works
|
|
25
|
-
|
|
26
|
-
<div align="center">
|
|
27
|
-
<img src="assets/tylor_threads_concept.png" alt="Tylor Threads Architecture" width="800">
|
|
28
|
-
<p><em>Tylor weaves parallel, persistent memory threads and orchestrates specialist sub-agents.</em></p>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## β¨ Features
|
|
34
|
-
|
|
35
|
-
### π§ Persistent Memory
|
|
36
|
-
Tylor completely eliminates the "context reset." Shut down your computer, close your terminal, and come back a week laterβClaude will pick up exactly where you left off.
|
|
37
|
-
|
|
38
|
-
### ποΈ Context Isolation (Threads)
|
|
39
|
-
Work in parallel without context bleed. Discuss frontend components in a `Frontend` thread and database schemas in a `Backend` thread. By isolating context, token usage stays low, and Claude's focus stays incredibly sharp.
|
|
40
|
-
|
|
41
|
-
### π€ Intelligent Orchestration
|
|
42
|
-
You don't need to micromanage. Claude acts as the orchestrator. If you ask it to review architecture, it will dynamically load its `cto` persona. If you ask it to write a PRD, it natively invokes the `bmad` skill framework to get the job done.
|
|
43
|
-
|
|
44
|
-
### π Infinite Extensibility (Lazy-Loading)
|
|
45
|
-
Tylor is built on a production-hardened ADK-pattern harness. You can register hundreds of domain-specific ECC skills (like `ecc/web`, `ecc/data`) via the `/add-skill` command. Tylor **lazy-loads** only the tools required for the current prompt, giving you massive capability scaling without ever blowing up Claude's token context window.
|
|
46
|
-
|
|
47
|
-
### ποΈ Autonomous AFK Sandboxing
|
|
48
|
-
Declare a sandbox for your thread and let Claude work autonomously. Assign large, complex tasks and let Claude execute them while you step away from the keyboard.
|
|
49
|
-
|
|
50
|
-
### π Visual Dashboard
|
|
51
|
-
Monitor your entire workspace through a beautiful, locally hosted web UI. Track active threads, review past conversations, and watch autonomous agent progress in real-time.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## π Installation
|
|
56
|
-
|
|
57
|
-
Tylor installs seamlessly into your Claude Code, Claude Desktop, GitHub Copilot, Antigravity, or VSCode Claude extension environment. Requires Python 3.8+.
|
|
58
|
-
|
|
59
|
-
### β‘ Option 1: The One-Line Installer (Recommended)
|
|
60
|
-
|
|
61
|
-
If you have Node.js installed, you can configure Tylor instantly across all your clients without manually cloning the repository. Simply run:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
npx tylor-mcp
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### π» Option 2: Manual Git Clone
|
|
68
|
-
|
|
69
|
-
**macOS / Linux / WSL:**
|
|
70
|
-
```bash
|
|
71
|
-
git clone https://github.com/GunjanGrunge/tylor ~/.claude/plugins/GunjanGrunge/tylor
|
|
72
|
-
python3 ~/.claude/plugins/GunjanGrunge/tylor/install.py
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**Windows:**
|
|
76
|
-
```powershell
|
|
77
|
-
git clone https://github.com/GunjanGrunge/tylor %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor
|
|
78
|
-
python %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor\install.py
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Step 3: Verify
|
|
82
|
-
|
|
83
|
-
1. Restart your Claude, GitHub Copilot, or Antigravity client completely (close the terminal/app and reopen it).
|
|
84
|
-
2. Type `/help-agent101` in your prompt (or use Copilot Chat / `/mcp show`).
|
|
85
|
-
3. If you see the capability index, Tylor is fully operational!
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## πΉοΈ Quick Start
|
|
90
|
-
|
|
91
|
-
Creating your first persistent workflow is incredibly simple:
|
|
92
|
-
|
|
93
|
-
```text
|
|
94
|
-
/new-thread Authentication β Create a persistent workspace
|
|
95
|
-
/run we need to implement JWT based authentication
|
|
96
|
-
|
|
97
|
-
/new-thread Dashboard UI β Create an isolated UI thread
|
|
98
|
-
/run build a react dashboard with a sidebar
|
|
99
|
-
|
|
100
|
-
/switch-thread Authentication β Instantly switch context back to Auth
|
|
101
|
-
/run add refresh token logic
|
|
102
|
-
|
|
103
|
-
/list-threads β View your workspace status
|
|
104
|
-
/open-threads-ui β Launch the visual dashboard
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## π οΈ Command Reference
|
|
110
|
-
|
|
111
|
-
Tylor exposes a suite of powerful commands directly within Claude:
|
|
112
|
-
|
|
113
|
-
| Command | Description |
|
|
114
|
-
|---|---|
|
|
115
|
-
| `/new-thread <name>` | Create a named thread and seamlessly switch future work into it. |
|
|
116
|
-
| `/switch-thread <name>` | Switch context to an existing thread (fuzzy matching supported). |
|
|
117
|
-
| `/list-threads` | Show all available threads alongside their status and activity. |
|
|
118
|
-
| `/kill-thread <name>` | Close a thread and dispatch asynchronous summarization. |
|
|
119
|
-
| `/recall` | Search through the deep semantic memory of your active thread. |
|
|
120
|
-
| `/add-skill` | Install a new skill package dynamically. |
|
|
121
|
-
| `/open-threads-ui` | Open the live, local thread visualizer UI in your browser. |
|
|
122
|
-
| `/set-sandbox <path>` | Declare specific filesystem roots for secure, autonomous execution. |
|
|
123
|
-
| `/afk-status` | Get real-time progress reports on current autonomous background tasks. |
|
|
124
|
-
|
|
125
|
-
> **Pro Tip:** You can also use shorthand aliases like `CT <name>` to create a thread or `SwThread <name>` to switch.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
##
|
|
130
|
-
|
|
131
|
-
Tylor
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="assets/tylor_logo.png" alt="Tylor Logo" width="150">
|
|
3
|
+
<h1>Tylor</h1>
|
|
4
|
+
<p><strong>The Tailor to Your Threads</strong></p>
|
|
5
|
+
<p><em>Give Claude Code persistent memory, laser-focused context, and an autonomous team of specialists.</em></p>
|
|
6
|
+
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](#)
|
|
9
|
+
[](#)
|
|
10
|
+
[](#)
|
|
11
|
+
[](#)
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Tylor transforms your Claude Code experience from a single-shot terminal interaction into a **persistent, intelligent workspace**.
|
|
17
|
+
|
|
18
|
+
Every time you open Claude Code, you normally start from zero. Tylor fixes that. It organizes your work into **threads**βisolated, named workspaces that survive restarts and reboots. It remembers every decision, every line of code, and every discussion, so you never have to repeat yourself.
|
|
19
|
+
|
|
20
|
+
**No database. No cloud account. No configuration. Just install and go.**
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π¨ How It Works
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
<img src="assets/tylor_threads_concept.png" alt="Tylor Threads Architecture" width="800">
|
|
28
|
+
<p><em>Tylor weaves parallel, persistent memory threads and orchestrates specialist sub-agents.</em></p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## β¨ Features
|
|
34
|
+
|
|
35
|
+
### π§ Persistent Memory
|
|
36
|
+
Tylor completely eliminates the "context reset." Shut down your computer, close your terminal, and come back a week laterβClaude will pick up exactly where you left off.
|
|
37
|
+
|
|
38
|
+
### ποΈ Context Isolation (Threads)
|
|
39
|
+
Work in parallel without context bleed. Discuss frontend components in a `Frontend` thread and database schemas in a `Backend` thread. By isolating context, token usage stays low, and Claude's focus stays incredibly sharp.
|
|
40
|
+
|
|
41
|
+
### π€ Intelligent Orchestration
|
|
42
|
+
You don't need to micromanage. Claude acts as the orchestrator. If you ask it to review architecture, it will dynamically load its `cto` persona. If you ask it to write a PRD, it natively invokes the `bmad` skill framework to get the job done.
|
|
43
|
+
|
|
44
|
+
### π Infinite Extensibility (Lazy-Loading)
|
|
45
|
+
Tylor is built on a production-hardened ADK-pattern harness. You can register hundreds of domain-specific ECC skills (like `ecc/web`, `ecc/data`) via the `/add-skill` command. Tylor **lazy-loads** only the tools required for the current prompt, giving you massive capability scaling without ever blowing up Claude's token context window.
|
|
46
|
+
|
|
47
|
+
### ποΈ Autonomous AFK Sandboxing
|
|
48
|
+
Declare a sandbox for your thread and let Claude work autonomously. Assign large, complex tasks and let Claude execute them while you step away from the keyboard.
|
|
49
|
+
|
|
50
|
+
### π Visual Dashboard
|
|
51
|
+
Monitor your entire workspace through a beautiful, locally hosted web UI. Track active threads, review past conversations, and watch autonomous agent progress in real-time.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## π Installation
|
|
56
|
+
|
|
57
|
+
Tylor installs seamlessly into your Claude Code, Claude Desktop, GitHub Copilot, Antigravity, or VSCode Claude extension environment. Requires Python 3.8+.
|
|
58
|
+
|
|
59
|
+
### β‘ Option 1: The One-Line Installer (Recommended)
|
|
60
|
+
|
|
61
|
+
If you have Node.js installed, you can configure Tylor instantly across all your clients without manually cloning the repository. Simply run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx tylor-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### π» Option 2: Manual Git Clone
|
|
68
|
+
|
|
69
|
+
**macOS / Linux / WSL:**
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/GunjanGrunge/tylor ~/.claude/plugins/GunjanGrunge/tylor
|
|
72
|
+
python3 ~/.claude/plugins/GunjanGrunge/tylor/install.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Windows:**
|
|
76
|
+
```powershell
|
|
77
|
+
git clone https://github.com/GunjanGrunge/tylor %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor
|
|
78
|
+
python %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor\install.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 3: Verify
|
|
82
|
+
|
|
83
|
+
1. Restart your Claude, GitHub Copilot, or Antigravity client completely (close the terminal/app and reopen it).
|
|
84
|
+
2. Type `/help-agent101` in your prompt (or use Copilot Chat / `/mcp show`).
|
|
85
|
+
3. If you see the capability index, Tylor is fully operational!
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## πΉοΈ Quick Start
|
|
90
|
+
|
|
91
|
+
Creating your first persistent workflow is incredibly simple:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
/new-thread Authentication β Create a persistent workspace
|
|
95
|
+
/run we need to implement JWT based authentication
|
|
96
|
+
|
|
97
|
+
/new-thread Dashboard UI β Create an isolated UI thread
|
|
98
|
+
/run build a react dashboard with a sidebar
|
|
99
|
+
|
|
100
|
+
/switch-thread Authentication β Instantly switch context back to Auth
|
|
101
|
+
/run add refresh token logic
|
|
102
|
+
|
|
103
|
+
/list-threads β View your workspace status
|
|
104
|
+
/open-threads-ui β Launch the visual dashboard
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## π οΈ Command Reference
|
|
110
|
+
|
|
111
|
+
Tylor exposes a suite of powerful commands directly within Claude:
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `/new-thread <name>` | Create a named thread and seamlessly switch future work into it. |
|
|
116
|
+
| `/switch-thread <name>` | Switch context to an existing thread (fuzzy matching supported). |
|
|
117
|
+
| `/list-threads` | Show all available threads alongside their status and activity. |
|
|
118
|
+
| `/kill-thread <name>` | Close a thread and dispatch asynchronous summarization. |
|
|
119
|
+
| `/recall` | Search through the deep semantic memory of your active thread. |
|
|
120
|
+
| `/add-skill` | Install a new skill package dynamically. |
|
|
121
|
+
| `/open-threads-ui` | Open the live, local thread visualizer UI in your browser. |
|
|
122
|
+
| `/set-sandbox <path>` | Declare specific filesystem roots for secure, autonomous execution. |
|
|
123
|
+
| `/afk-status` | Get real-time progress reports on current autonomous background tasks. |
|
|
124
|
+
|
|
125
|
+
> **Pro Tip:** You can also use shorthand aliases like `CT <name>` to create a thread or `SwThread <name>` to switch.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## π Bumblebee Security Gate
|
|
130
|
+
|
|
131
|
+
Tylor now includes a default, plugin-wide security gate powered by Bumblebee. When a risky command is detectedβespecially package installs, extension installs, skill/package additions, or MCP config changesβTylor will initiate a read-only Bumblebee scan before the command runs.
|
|
132
|
+
|
|
133
|
+
- Enabled by default for any command pattern that looks like `pip install`, `npm install`, editor/extension installs, or skill/config setup.
|
|
134
|
+
- If Bumblebee is missing, Tylor will flag the command and surface clear guidance instead of executing it blindly.
|
|
135
|
+
- If Bumblebee detects risk, execution is blocked and the user sees actionable alternatives.
|
|
136
|
+
|
|
137
|
+
Suggested responses from the gate include:
|
|
138
|
+
|
|
139
|
+
- Install Bumblebee or set `BUMBLEBEE_PATH` if the CLI is not found.
|
|
140
|
+
- Run `bumblebee scan --json` manually before retrying.
|
|
141
|
+
- Disable the gate temporarily with `BUMBLEBEE_ENABLED=false` only if you understand the risk.
|
|
142
|
+
- Review package metadata, MCP config changes, and AI tool integrations before proceeding.
|
|
143
|
+
|
|
144
|
+
This layer applies across the plugin, regardless of which thread or persona is active.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## π Sub-Agents & Personas
|
|
149
|
+
|
|
150
|
+
Tylor comes pre-equipped with specialist sub-agents. Claude will **automatically invoke** these personas based on the nature of your queryβno manual intervention required.
|
|
151
|
+
|
|
152
|
+
* **`cto`**: System architecture, tradeoffs, platform strategy, and engineering standards.
|
|
153
|
+
* **`code_agent`**: Senior software engineer laser-focused on shipping robust code and tests.
|
|
154
|
+
* **`analyst`**: Market research, data synthesis, and technical decision support.
|
|
155
|
+
* **`ceo`**: Product strategy, roadmap prioritization, and stakeholder framing.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## π License
|
|
160
|
+
|
|
161
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
162
|
+
|
|
163
|
+
<div align="center">
|
|
164
|
+
<p><em>Tylor β Tailoring the future of AI development.</em></p>
|
|
165
|
+
</div>
|
package/bin/tylor.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { spawnSync } = require('child_process');
|
|
2
|
+
const { spawnSync, execSync } = require('child_process');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const fs = require('fs');
|
|
4
6
|
|
|
7
|
+
// ββ Update check ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
8
|
+
function checkForUpdate() {
|
|
9
|
+
try {
|
|
10
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
11
|
+
const localPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
12
|
+
const localVersion = localPkg.version;
|
|
13
|
+
const pkgName = localPkg.name;
|
|
14
|
+
|
|
15
|
+
const url = `https://registry.npmjs.org/${pkgName}/latest`;
|
|
16
|
+
const req = https.get(url, { timeout: 3000 }, (res) => {
|
|
17
|
+
let data = '';
|
|
18
|
+
res.on('data', chunk => data += chunk);
|
|
19
|
+
res.on('end', () => {
|
|
20
|
+
try {
|
|
21
|
+
const latest = JSON.parse(data).version;
|
|
22
|
+
if (latest && latest !== localVersion) {
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(` β οΈ Update available: ${localVersion} β ${latest}`);
|
|
25
|
+
console.log(` Run: npm update -g ${pkgName} (or npx ${pkgName}@latest)`);
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
28
|
+
} catch (_) { /* silent */ }
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
req.on('error', () => { /* silent β offline or registry down */ });
|
|
32
|
+
req.on('timeout', () => { req.destroy(); });
|
|
33
|
+
} catch (_) { /* silent */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
checkForUpdate();
|
|
37
|
+
|
|
38
|
+
// ββ Installer βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
5
39
|
const installPy = path.join(__dirname, '..', 'install.py');
|
|
6
40
|
const args = process.argv.slice(2);
|
|
7
41
|
|
|
@@ -13,7 +47,7 @@ let result = spawnSync('python', [installPy, ...args], { stdio: 'inherit' });
|
|
|
13
47
|
// Fallback to `python3` if `python` fails (standard on macOS/Linux)
|
|
14
48
|
if (result.error || result.status !== 0) {
|
|
15
49
|
result = spawnSync('python3', [installPy, ...args], { stdio: 'inherit' });
|
|
16
|
-
|
|
50
|
+
|
|
17
51
|
if (result.error) {
|
|
18
52
|
console.error("β Failed to launch the Tylor installer. Please ensure Python 3.8+ is installed on your system.");
|
|
19
53
|
process.exit(1);
|
|
@@ -21,3 +55,4 @@ if (result.error || result.status !== 0) {
|
|
|
21
55
|
}
|
|
22
56
|
|
|
23
57
|
process.exit(result.status);
|
|
58
|
+
|
package/package.json
CHANGED
package/server/config.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
server/config.py β Agent101 server configuration.
|
|
3
|
-
Reads
|
|
3
|
+
Reads local storage settings and optional cloud provider settings.
|
|
4
4
|
Resolution order: env var β ~/.agent101/config.json β .env file β defaults.
|
|
5
|
-
|
|
5
|
+
Missing cloud settings are normal in local-first mode; startup never crashes.
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
import json
|
|
@@ -57,6 +57,7 @@ def _load() -> dict:
|
|
|
57
57
|
cfg = {
|
|
58
58
|
"aws_profile": _get("AWS_PROFILE"),
|
|
59
59
|
"aws_access_key_id": _get("AWS_ACCESS_KEY_ID"),
|
|
60
|
+
"storage_mode": _get("AGENT101_STORAGE_MODE", default="local"),
|
|
60
61
|
"bedrock_region": _get("BEDROCK_REGION", default="us-east-1"),
|
|
61
62
|
"bedrock_opus_model": _get(
|
|
62
63
|
"BEDROCK_OPUS_MODEL",
|
|
@@ -69,19 +70,13 @@ def _load() -> dict:
|
|
|
69
70
|
"s3_bucket": _get("S3_BUCKET"),
|
|
70
71
|
"opensearch_host": _get("OPENSEARCH_HOST"),
|
|
71
72
|
"opensearch_port": _get("OPENSEARCH_PORT", default="9200"),
|
|
73
|
+
"bumblebee_enabled": str(_get("BUMBLEBEE_ENABLED", default="true")).strip().lower() in {"1", "true", "yes", "on"},
|
|
74
|
+
"bumblebee_path": _get("BUMBLEBEE_PATH"),
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"ANTHROPIC_PLATFORM_AWS_API_KEY/ANTHROPIC_AWS_API_KEY not set β "
|
|
78
|
-
"token overflow fallback to Claude Platform on AWS is disabled"
|
|
79
|
-
)
|
|
80
|
-
if not cfg["opensearch_host"]:
|
|
81
|
-
logger.warning(
|
|
82
|
-
"OPENSEARCH_HOST not set β "
|
|
83
|
-
"semantic memory recall (recall_memory) will be unavailable until configured"
|
|
84
|
-
)
|
|
77
|
+
if cfg["storage_mode"] not in {"local", "aws"}:
|
|
78
|
+
logger.warning("Unknown AGENT101_STORAGE_MODE=%s; falling back to local mode", cfg["storage_mode"])
|
|
79
|
+
cfg["storage_mode"] = "local"
|
|
85
80
|
|
|
86
81
|
return cfg
|
|
87
82
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zsh: command not found: python
|
package/server/storage/dynamo.py
CHANGED
|
@@ -187,6 +187,29 @@ class DynamoClient:
|
|
|
187
187
|
attributes["Task"] = task
|
|
188
188
|
return self.put_item(sk=sk, attributes=attributes)
|
|
189
189
|
|
|
190
|
+
def put_agent_event(
|
|
191
|
+
self,
|
|
192
|
+
thread_id: str,
|
|
193
|
+
agent_id: str,
|
|
194
|
+
event_type: str,
|
|
195
|
+
content: str,
|
|
196
|
+
persona: str | None = None,
|
|
197
|
+
) -> dict:
|
|
198
|
+
"""Persist a streamed sub-agent event for live UI replay."""
|
|
199
|
+
self._validate_agent_id(agent_id)
|
|
200
|
+
sk = f"THREAD#{thread_id}#AGENT#{agent_id}#EVENT#{_unique_event_suffix()}"
|
|
201
|
+
self._assert_thread_isolation(thread_id, sk)
|
|
202
|
+
attributes = {
|
|
203
|
+
"ThreadId": thread_id,
|
|
204
|
+
"AgentId": agent_id,
|
|
205
|
+
"Type": "agent_event",
|
|
206
|
+
"EventType": event_type,
|
|
207
|
+
"Content": content,
|
|
208
|
+
}
|
|
209
|
+
if persona:
|
|
210
|
+
attributes["Persona"] = persona
|
|
211
|
+
return self.put_item(sk=sk, attributes=attributes)
|
|
212
|
+
|
|
190
213
|
def put_agent_handoff(
|
|
191
214
|
self,
|
|
192
215
|
thread_id: str,
|
|
@@ -230,6 +253,16 @@ class DynamoClient:
|
|
|
230
253
|
items = self.query_thread(thread_id, f"THREAD#{thread_id}#AGENT#")
|
|
231
254
|
return [item for item in items if item.get("SK", "").endswith("#STATE")]
|
|
232
255
|
|
|
256
|
+
def query_agent_events(self, thread_id: str, agent_id: str | None = None) -> list:
|
|
257
|
+
"""Return streamed sub-agent events for one thread, optionally one agent."""
|
|
258
|
+
prefix = f"THREAD#{thread_id}#AGENT#"
|
|
259
|
+
if agent_id:
|
|
260
|
+
self._validate_agent_id(agent_id)
|
|
261
|
+
prefix = f"{prefix}{agent_id}#EVENT#"
|
|
262
|
+
items = self.query_thread(thread_id, prefix)
|
|
263
|
+
events = [item for item in items if "#EVENT#" in item.get("SK", "")]
|
|
264
|
+
return sorted(events, key=lambda item: item.get("SK", ""))
|
|
265
|
+
|
|
233
266
|
def get_thread_meta(self, thread_id: str) -> dict | None:
|
|
234
267
|
"""Return thread META item for the given thread_id."""
|
|
235
268
|
return self.get_item(thread_id, f"THREAD#{thread_id}#META")
|
|
@@ -104,7 +104,7 @@ class JsonStore:
|
|
|
104
104
|
"name": name, "status": "active",
|
|
105
105
|
"created_at": now, "updated_at": now,
|
|
106
106
|
"messages": [], "summary": None,
|
|
107
|
-
"sandbox_roots": [], "agent_states": {}, "agent_outputs": [],
|
|
107
|
+
"sandbox_roots": [], "agent_states": {}, "agent_outputs": [], "agent_events": [],
|
|
108
108
|
}
|
|
109
109
|
data["threads"].append(thread)
|
|
110
110
|
self._save(data)
|
|
@@ -159,6 +159,10 @@ class JsonStore:
|
|
|
159
159
|
"updated_at": now,
|
|
160
160
|
**({"project": item["Project"]} if item.get("Project") else {}),
|
|
161
161
|
})
|
|
162
|
+
if "sandbox_roots" in item:
|
|
163
|
+
existing["sandbox_roots"] = list(item.get("sandbox_roots", []))
|
|
164
|
+
if "afk_session" in item:
|
|
165
|
+
existing["afk_session"] = item.get("afk_session")
|
|
162
166
|
if "Summary" in item:
|
|
163
167
|
existing["summary"] = item["Summary"]
|
|
164
168
|
self._save(data)
|
|
@@ -170,7 +174,7 @@ class JsonStore:
|
|
|
170
174
|
"project": item.get("Project", ""),
|
|
171
175
|
"created_at": item.get("CreatedAt", now),
|
|
172
176
|
"updated_at": now, "messages": [], "summary": None,
|
|
173
|
-
"sandbox_roots": [], "agent_states": {}, "agent_outputs": [],
|
|
177
|
+
"sandbox_roots": [], "agent_states": {}, "agent_outputs": [], "agent_events": [],
|
|
174
178
|
}
|
|
175
179
|
data["threads"].append(thread)
|
|
176
180
|
self._save(data)
|
|
@@ -249,9 +253,13 @@ class JsonStore:
|
|
|
249
253
|
"CreatedAt": t.get("created_at", ""),
|
|
250
254
|
"UpdatedAt": t.get("updated_at", ""),
|
|
251
255
|
"Project": t.get("project", ""),
|
|
256
|
+
"sandbox_roots": list(t.get("sandbox_roots", [])),
|
|
257
|
+
**({"afk_session": t["afk_session"]} if "afk_session" in t else {}),
|
|
252
258
|
}]
|
|
253
259
|
for msg in t.get("messages", []):
|
|
254
260
|
items.append(msg)
|
|
261
|
+
for event in t.get("agent_events", []):
|
|
262
|
+
items.append(event)
|
|
255
263
|
return items
|
|
256
264
|
|
|
257
265
|
def query_thread(self, thread_id: str, sk_prefix: str) -> list:
|
|
@@ -274,6 +282,8 @@ class JsonStore:
|
|
|
274
282
|
"MessageCount": len(t.get("messages", [])),
|
|
275
283
|
"CreatedAt": t.get("created_at", ""),
|
|
276
284
|
"UpdatedAt": t.get("updated_at", ""),
|
|
285
|
+
"sandbox_roots": list(t.get("sandbox_roots", [])),
|
|
286
|
+
**({"afk_session": t["afk_session"]} if "afk_session" in t else {}),
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
def get_current_thread_marker(self) -> dict | None:
|
|
@@ -332,6 +342,33 @@ class JsonStore:
|
|
|
332
342
|
self._save(data)
|
|
333
343
|
return item
|
|
334
344
|
|
|
345
|
+
def put_agent_event(
|
|
346
|
+
self,
|
|
347
|
+
thread_id: str,
|
|
348
|
+
agent_id: str,
|
|
349
|
+
event_type: str,
|
|
350
|
+
content: str,
|
|
351
|
+
persona: str | None = None,
|
|
352
|
+
) -> dict:
|
|
353
|
+
data = self._load()
|
|
354
|
+
t = self._require(data, thread_id)
|
|
355
|
+
now = _now_iso()
|
|
356
|
+
item = {
|
|
357
|
+
"SK": f"THREAD#{thread_id}#AGENT#{agent_id}#EVENT#{now}#{uuid.uuid4().hex}",
|
|
358
|
+
"ThreadId": thread_id,
|
|
359
|
+
"AgentId": agent_id,
|
|
360
|
+
"Type": "agent_event",
|
|
361
|
+
"EventType": event_type,
|
|
362
|
+
"Content": content,
|
|
363
|
+
"CreatedAt": now,
|
|
364
|
+
}
|
|
365
|
+
if persona:
|
|
366
|
+
item["Persona"] = persona
|
|
367
|
+
t.setdefault("agent_events", []).append(item)
|
|
368
|
+
t["updated_at"] = now
|
|
369
|
+
self._save(data)
|
|
370
|
+
return item
|
|
371
|
+
|
|
335
372
|
def put_agent_handoff(self, thread_id: str, agent_id: str, handoff_state: dict) -> dict:
|
|
336
373
|
data = self._load()
|
|
337
374
|
t = self._require(data, thread_id)
|
|
@@ -357,3 +394,13 @@ class JsonStore:
|
|
|
357
394
|
if not t:
|
|
358
395
|
return []
|
|
359
396
|
return [{"AgentId": aid, **s} for aid, s in t.get("agent_states", {}).items()]
|
|
397
|
+
|
|
398
|
+
def query_agent_events(self, thread_id: str, agent_id: str | None = None) -> list:
|
|
399
|
+
data = self._load()
|
|
400
|
+
t = self._find(data, thread_id)
|
|
401
|
+
if not t:
|
|
402
|
+
return []
|
|
403
|
+
events = list(t.get("agent_events", []))
|
|
404
|
+
if agent_id:
|
|
405
|
+
events = [e for e in events if e.get("AgentId") == agent_id]
|
|
406
|
+
return sorted(events, key=lambda e: e.get("SK", ""))
|
|
@@ -288,8 +288,6 @@ def test_platform_key_present_via_env():
|
|
|
288
288
|
# --- AC8: run_all exits 0 even when all AWS checks fail ---
|
|
289
289
|
|
|
290
290
|
def test_run_all_is_advisory(tmp_path, capsys):
|
|
291
|
-
from botocore.exceptions import NoCredentialsError
|
|
292
|
-
|
|
293
291
|
with (
|
|
294
292
|
patch("server.validate.check_dynamodb", return_value=(False, " β DynamoDB β no creds")),
|
|
295
293
|
patch("server.validate.check_s3", return_value=(False, " β S3 β no creds")),
|
|
@@ -299,8 +297,24 @@ def test_run_all_is_advisory(tmp_path, capsys):
|
|
|
299
297
|
):
|
|
300
298
|
error_count = run_all(str(tmp_path))
|
|
301
299
|
|
|
302
|
-
assert error_count ==
|
|
303
|
-
#
|
|
300
|
+
assert error_count == 0
|
|
301
|
+
# Local JSON storage is the default; AWS failures are irrelevant until opted in.
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_run_all_counts_aws_failures_when_explicitly_enabled(tmp_path, monkeypatch):
|
|
305
|
+
monkeypatch.setenv("AGENT101_ENABLE_AWS", "1")
|
|
306
|
+
validate_module = run_all.__module__
|
|
307
|
+
|
|
308
|
+
with (
|
|
309
|
+
patch(f"{validate_module}.check_dynamodb", return_value=(False, " β DynamoDB β no creds")),
|
|
310
|
+
patch(f"{validate_module}.check_s3", return_value=(False, " β S3 β no creds")),
|
|
311
|
+
patch(f"{validate_module}.check_bedrock", return_value=(False, " β Bedrock β no creds")),
|
|
312
|
+
patch(f"{validate_module}.check_opensearch", return_value=(True, " β OpenSearch β skipped")),
|
|
313
|
+
patch(f"{validate_module}.check_platform_key", return_value=(True, " β key not set")),
|
|
314
|
+
):
|
|
315
|
+
error_count = run_all(str(tmp_path))
|
|
316
|
+
|
|
317
|
+
assert error_count == 3
|
|
304
318
|
|
|
305
319
|
|
|
306
320
|
# ---------------------------------------------------------------------------
|