daemora 1.0.10 → 1.0.11
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 +37 -15
- package/SOUL.md +23 -4
- package/daemora-ui/dist/assets/index-BkPHvKYt.css +1 -0
- package/daemora-ui/dist/assets/index-ZiuOJUu0.js +92 -0
- package/daemora-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/planning.md +168 -0
- package/src/agents/systemPrompt.js +2 -1
- package/src/cli.js +124 -4
- package/src/index.js +6 -2
- package/src/safety/CommandGuard.js +22 -1
- package/src/setup/theme.js +1 -0
- package/src/setup/wizard.js +220 -26
- package/src/tenants/TenantManager.js +37 -0
- package/src/tools/_paths.js +39 -0
- package/src/tools/applyPatch.js +6 -0
- package/src/tools/browserAutomation.js +18 -6
- package/src/tools/createDocument.js +4 -0
- package/src/tools/executeCommand.js +4 -2
- package/src/tools/generateImage.js +7 -3
- package/src/tools/replyWithFile.js +4 -0
- package/src/tools/screenCapture.js +6 -1
- package/src/tools/sendFile.js +4 -0
- package/src/tools/sshTool.js +2 -2
- package/src/tools/textToSpeech.js +21 -12
- package/src/tools/transcribeAudio.js +15 -4
- package/daemora-ui/dist/assets/index-D7W1-PNQ.js +0 -92
- package/daemora-ui/dist/assets/index-DzMLJeoL.css +0 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<meta name="theme-color" content="#0a0a0f" />
|
|
8
8
|
<meta name="description" content="Daemora — Self-hosted AI agent platform" />
|
|
9
9
|
<title>Daemora</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-ZiuOJUu0.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BkPHvKYt.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daemora",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "A powerful open-source AI agent that runs on your machine. Connects to any AI model, any MCP server, any channel. Fully autonomous - plans, codes, tests, browses, emails, and manages your tools without asking permission.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planning
|
|
3
|
+
description: Task planning for any complex work — coding, research, communication, automation. Decide when to plan vs just do, break into steps, get user confirmation before executing.
|
|
4
|
+
triggers: plan, planning, design, architect, approach, strategy, implement, big task, complex task, multi-step, think through, break down, figure out, how should, what approach, steps, workflow
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Planning — Think Before You Act
|
|
8
|
+
|
|
9
|
+
## When to Plan vs Just Do It
|
|
10
|
+
|
|
11
|
+
**Plan first** when ANY of these apply:
|
|
12
|
+
- **Multiple steps required** — the task needs 3+ distinct actions to complete.
|
|
13
|
+
- **Multiple valid approaches** — the task can be solved several ways. Pick the right one first.
|
|
14
|
+
- **Unclear scope** — you need to explore or research before understanding the full extent of work.
|
|
15
|
+
- **User preferences matter** — the outcome could go multiple reasonable directions.
|
|
16
|
+
- **High stakes** — mistakes are costly to undo (emails sent, files restructured, data transformed).
|
|
17
|
+
- **Multi-agent work** — the task needs parallel or sequential agent coordination.
|
|
18
|
+
- **New feature or system change** — adding meaningful new functionality or modifying existing behavior.
|
|
19
|
+
- **Multi-file code changes** — the task will touch 3+ files. Map out which files and what changes.
|
|
20
|
+
- **Architectural decisions** — choosing between patterns, libraries, data models, or technologies.
|
|
21
|
+
|
|
22
|
+
**Skip planning** — do it directly:
|
|
23
|
+
- Single-action tasks (send one email, fetch one page, fix a typo).
|
|
24
|
+
- Tasks where the user gave very specific, detailed instructions.
|
|
25
|
+
- Quick lookups, simple questions, casual conversation.
|
|
26
|
+
- Few-line code fixes with obvious solutions.
|
|
27
|
+
|
|
28
|
+
**When in doubt → plan.** The cost of planning is low. The cost of rework is high.
|
|
29
|
+
|
|
30
|
+
## Planning Workflow
|
|
31
|
+
|
|
32
|
+
1. **Explore** — gather context. Read files, search the web, check memory, review conversation history. Understand the current state before deciding what to change.
|
|
33
|
+
2. **Identify the approach** — what needs to happen, in what order, using what tools/agents. Consider alternatives and pick the best one.
|
|
34
|
+
3. **Break into steps** — ordered list of concrete actions. Each step = one verifiable outcome. Keep it short — a list of actions, not an essay.
|
|
35
|
+
4. **Present the plan to the user** — before executing, tell the user what you're about to do and ask for confirmation. This prevents wasted effort and ensures alignment. Format: numbered list of concrete actions, not vague descriptions.
|
|
36
|
+
5. **Execute on confirmation** — work through each step. Verify after each one.
|
|
37
|
+
|
|
38
|
+
## User Confirmation
|
|
39
|
+
|
|
40
|
+
**Always confirm before executing a complex plan.** Present the plan clearly and ask:
|
|
41
|
+
- "Here's my plan — want me to go ahead?"
|
|
42
|
+
- List the concrete steps so the user can see what will happen.
|
|
43
|
+
- If the user adjusts, update the plan and confirm again.
|
|
44
|
+
- Only skip confirmation for simple tasks that don't need planning.
|
|
45
|
+
|
|
46
|
+
This is non-negotiable for complex work. The user should always know what's about to happen before it happens.
|
|
47
|
+
|
|
48
|
+
## Exploration by Task Type
|
|
49
|
+
|
|
50
|
+
**Code tasks:**
|
|
51
|
+
- Find files — `glob("src/**/*.ts")`, `searchFiles("*.controller.*")` to map the structure.
|
|
52
|
+
- Find patterns — `searchContent("export function", "src/")`, `grep("interface.*Props")` to see conventions.
|
|
53
|
+
- Read key files — entry points, related components, tests, configs.
|
|
54
|
+
- Check dependencies — what libraries, APIs, patterns are established.
|
|
55
|
+
|
|
56
|
+
**Research tasks:**
|
|
57
|
+
- Web search for current information on the topic.
|
|
58
|
+
- Fetch and read actual pages — don't stop at summaries.
|
|
59
|
+
- Check memory for previous findings on the same topic.
|
|
60
|
+
- Cross-reference multiple sources for anything important.
|
|
61
|
+
|
|
62
|
+
**Communication tasks:**
|
|
63
|
+
- Review conversation history for context and tone.
|
|
64
|
+
- Check memory for user preferences (writing style, contacts, templates).
|
|
65
|
+
- Identify all recipients, attachments, and follow-up actions needed.
|
|
66
|
+
|
|
67
|
+
**Automation / workflow tasks:**
|
|
68
|
+
- Map the full workflow end-to-end before automating any step.
|
|
69
|
+
- Identify dependencies between steps.
|
|
70
|
+
- Check what tools, APIs, and MCP servers are available.
|
|
71
|
+
|
|
72
|
+
3-5 targeted explorations is usually enough. Get the lay of the land, then plan.
|
|
73
|
+
|
|
74
|
+
## What a Good Plan Looks Like
|
|
75
|
+
|
|
76
|
+
A plan is a short ordered list of concrete actions:
|
|
77
|
+
|
|
78
|
+
**Coding example:**
|
|
79
|
+
```
|
|
80
|
+
1. Add FooService class in src/services/foo.ts — handles X with methods Y, Z
|
|
81
|
+
2. Update src/routes/api.ts — add GET /api/foo endpoint, wire to FooService
|
|
82
|
+
3. Add tests in tests/foo.test.ts — cover happy path + error cases
|
|
83
|
+
4. Run build + tests, fix any failures
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Research example:**
|
|
87
|
+
```
|
|
88
|
+
1. Search web for latest pricing on X, Y, Z services
|
|
89
|
+
2. Fetch each provider's pricing page, extract plan details
|
|
90
|
+
3. Compare features and costs in a structured table
|
|
91
|
+
4. Save findings to memory for future reference
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Workflow example:**
|
|
95
|
+
```
|
|
96
|
+
1. Fetch all invoices from email (last 30 days)
|
|
97
|
+
2. Extract amounts, dates, vendors from each
|
|
98
|
+
3. Create summary spreadsheet in workspace
|
|
99
|
+
4. Send summary to user via email
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
NOT:
|
|
103
|
+
```
|
|
104
|
+
First, I'll think about what to do. Then I'll consider the options.
|
|
105
|
+
After that, I'll figure out the best approach. Finally, I'll do everything.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Each step should be specific enough to hand to someone with zero context.
|
|
109
|
+
|
|
110
|
+
## Planning Multi-Agent Work
|
|
111
|
+
|
|
112
|
+
Any task that spawns agents — `spawnAgent`, `parallelAgents`, `useMCP` — is multi-agent work. MCP tasks are spawned agent tasks. Plan them the same way.
|
|
113
|
+
|
|
114
|
+
### Agent Isolation — Non-Negotiable
|
|
115
|
+
- Each agent operates in its own context. No shared memory, no shared state, no implicit communication.
|
|
116
|
+
- Agents must NOT touch files, paths, or resources owned by another agent. Define boundaries upfront.
|
|
117
|
+
- If two agents need the same data, pass it explicitly via `sharedContext` or workspace files. Never assume one agent can read another's output unless the plan says so.
|
|
118
|
+
- MCP agents (`useMCP`) get ONLY that server's tools. They cannot access files, shell, or other MCP servers. Plan accordingly — don't expect an MCP agent to do something outside its server's scope.
|
|
119
|
+
|
|
120
|
+
### Contract-Based Planning
|
|
121
|
+
Before spawning any agents, define the contract:
|
|
122
|
+
1. **Inputs** — what each agent receives. Exact data, file paths, specs. Paste the actual content into the brief — don't reference it.
|
|
123
|
+
2. **Outputs** — what each agent produces. File paths, data shapes, expected format.
|
|
124
|
+
3. **Boundaries** — what each agent is NOT allowed to touch. Files, directories, APIs outside its scope.
|
|
125
|
+
4. **Dependencies** — does agent B need output from agent A? Yes → sequential. No → parallel.
|
|
126
|
+
5. **Profiles** — coder for code, researcher for research, writer for docs, analyst for data.
|
|
127
|
+
|
|
128
|
+
### MCP as Spawned Agents
|
|
129
|
+
- `useMCP(serverName, taskDescription)` spawns a specialist agent with ONLY that MCP server's tools.
|
|
130
|
+
- The specialist has ZERO context beyond the task description you write. Include everything: what to do, all details, full content, background context.
|
|
131
|
+
- Plan MCP calls like any other agent spawn — define inputs, expected outputs, and what happens with the result.
|
|
132
|
+
- Multiple MCP calls to different servers can run in parallel if they don't depend on each other.
|
|
133
|
+
|
|
134
|
+
### Preventing Cross-Agent Impact
|
|
135
|
+
- Never let two agents write to the same file. Split by file or by section with clear ownership.
|
|
136
|
+
- Never let an agent modify global state (env vars, configs, databases) without the plan explicitly calling for it.
|
|
137
|
+
- Use workspace directories (`data/workspaces/{id}/`) as artifact stores. Each agent writes to its own path within the workspace.
|
|
138
|
+
- After all agents finish, the parent synthesizes results. Agents never directly consume each other's output during execution.
|
|
139
|
+
|
|
140
|
+
Load `readFile("skills/orchestration.md")` for full multi-agent coordination patterns, error recovery, and structured return conventions.
|
|
141
|
+
|
|
142
|
+
## Tracking Complex Work
|
|
143
|
+
|
|
144
|
+
For multi-step work, use `projectTracker("createProject")` to persist the plan:
|
|
145
|
+
- Mark tasks `in_progress` before starting, `done` with notes when finished.
|
|
146
|
+
- If interrupted → `projectTracker("listProjects")` to find and resume.
|
|
147
|
+
- Workspace files survive crashes — work is never lost.
|
|
148
|
+
|
|
149
|
+
## Re-Assessment
|
|
150
|
+
|
|
151
|
+
If you're 3+ steps into execution and:
|
|
152
|
+
- The approach isn't working as expected
|
|
153
|
+
- You discovered something that changes the requirements
|
|
154
|
+
- The scope is larger than initially estimated
|
|
155
|
+
|
|
156
|
+
**Stop. Re-read the original request. Re-plan from current state.** Don't push through a broken plan. If the new plan differs significantly, confirm with the user again.
|
|
157
|
+
|
|
158
|
+
## Planning Checklist
|
|
159
|
+
|
|
160
|
+
Before executing, verify:
|
|
161
|
+
- [ ] Gathered enough context (files, web, memory, conversation)
|
|
162
|
+
- [ ] Identified the best approach from available options
|
|
163
|
+
- [ ] Steps are ordered (dependencies resolved)
|
|
164
|
+
- [ ] Each step has a clear, verifiable outcome
|
|
165
|
+
- [ ] User has confirmed the plan (for complex work)
|
|
166
|
+
- [ ] Edge cases and potential failures considered
|
|
167
|
+
- [ ] Multi-agent tasks: contracts defined, boundaries set, no shared-file conflicts
|
|
168
|
+
- [ ] MCP tasks: task descriptions are self-contained, expected outputs specified
|
|
@@ -162,7 +162,7 @@ You MUST respond with a JSON object matching this exact schema on every turn:
|
|
|
162
162
|
- Task complete and verified → concise outcome in 1-3 sentences. finalResponse = true.
|
|
163
163
|
|
|
164
164
|
## Task execution rules
|
|
165
|
-
1.
|
|
165
|
+
1. **Decide: plan or just do it.** Simple task (single action, clear instructions, quick fix) → start immediately with a tool call. Complex task (3+ steps, multiple approaches, unclear scope, high stakes, multi-agent, new feature, multi-file changes) → load the planning skill first (\`readFile("skills/planning.md")\`), gather context, break into steps, **present the plan to the user and get confirmation before executing**. When in doubt → plan. The cost of planning is low; the cost of rework is high.
|
|
166
166
|
2. Chain multiple tool calls. After each result: need more? Call another. Done? Verify first, then finalize.
|
|
167
167
|
3. After writing/editing any file, read it back to verify.
|
|
168
168
|
4. After code changes, run build/tests. Fix failures until clean.
|
|
@@ -170,6 +170,7 @@ You MUST respond with a JSON object matching this exact schema on every turn:
|
|
|
170
170
|
6. Never give up. Never ask the user to do it manually. Never report a problem without attempting to solve it.
|
|
171
171
|
7. Never claim you did something without actually calling the tool.
|
|
172
172
|
8. Never set finalResponse=true while errors or failures exist.
|
|
173
|
+
9. If 3+ steps into execution and something doesn't add up → stop, re-read the request, re-plan from current state.
|
|
173
174
|
|
|
174
175
|
## Understanding user intent
|
|
175
176
|
- Read the full request carefully. Identify exactly what the user wants done.
|
package/src/cli.js
CHANGED
|
@@ -871,10 +871,18 @@ function handleSandbox(action, args) {
|
|
|
871
871
|
console.error(`\n ${S.cross} Usage: daemora sandbox add ${t.dim("<absolute-path>")}\n`);
|
|
872
872
|
process.exit(1);
|
|
873
873
|
}
|
|
874
|
-
if (!newPath.startsWith("/") && !newPath.match(/^[A-Za-z]
|
|
874
|
+
if (!newPath.startsWith("/") && !newPath.match(/^[A-Za-z]:[\\\/]/)) {
|
|
875
875
|
console.error(`\n ${S.cross} Path must be absolute (start with / or C:\\)\n`);
|
|
876
876
|
process.exit(1);
|
|
877
877
|
}
|
|
878
|
+
if (/[\x00-\x1f]/.test(newPath) || newPath.includes("\0")) {
|
|
879
|
+
console.error(`\n ${S.cross} Path must not contain control characters or null bytes.\n`);
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
if (/(^|[\\/])\.\.([\\/]|$)/.test(newPath)) {
|
|
883
|
+
console.error(`\n ${S.cross} Path must not contain ".." traversal.\n`);
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
878
886
|
const updated = [...new Set([...allowedPaths, newPath])];
|
|
879
887
|
writeEnvKey("ALLOWED_PATHS", updated.join(","));
|
|
880
888
|
console.log(`\n${header} ${S.check} ${t.bold(newPath)} added to allowed paths.`);
|
|
@@ -909,6 +917,10 @@ function handleSandbox(action, args) {
|
|
|
909
917
|
console.error(`\n ${S.cross} Usage: daemora sandbox block ${t.dim("<absolute-path>")}\n`);
|
|
910
918
|
process.exit(1);
|
|
911
919
|
}
|
|
920
|
+
if (/[\x00-\x1f]/.test(blockPath) || /(^|[\\/])\.\.([\\/]|$)/.test(blockPath)) {
|
|
921
|
+
console.error(`\n ${S.cross} Invalid path — no control characters or ".." traversal allowed.\n`);
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
912
924
|
const updated = [...new Set([...blockedPaths, blockPath])];
|
|
913
925
|
writeEnvKey("BLOCKED_PATHS", updated.join(","));
|
|
914
926
|
console.log(`\n${header} ${S.check} ${t.bold(blockPath)} added to blocked paths.\n`);
|
|
@@ -972,15 +984,24 @@ async function handleTenant(action, args) {
|
|
|
972
984
|
const port = process.env.PORT || "8081";
|
|
973
985
|
const base = `http://localhost:${port}`;
|
|
974
986
|
|
|
987
|
+
// Read auth token for API calls
|
|
988
|
+
const tokenPath = join(config.dataDir, "auth-token");
|
|
989
|
+
const authToken = existsSync(tokenPath) ? readFileSync(tokenPath, "utf-8").trim() : "";
|
|
990
|
+
|
|
975
991
|
async function apiCall(method, path, body) {
|
|
976
992
|
const { default: http } = await import("http");
|
|
993
|
+
// Ensure path starts with /api/
|
|
994
|
+
const apiPath = path.startsWith("/api/") ? path : `/api${path}`;
|
|
977
995
|
return new Promise((resolve, reject) => {
|
|
978
996
|
const opts = {
|
|
979
997
|
hostname: "localhost",
|
|
980
998
|
port: parseInt(port),
|
|
981
|
-
path,
|
|
999
|
+
path: apiPath,
|
|
982
1000
|
method,
|
|
983
|
-
headers: {
|
|
1001
|
+
headers: {
|
|
1002
|
+
"Content-Type": "application/json",
|
|
1003
|
+
...(authToken ? { "Authorization": `Bearer ${authToken}` } : {}),
|
|
1004
|
+
},
|
|
984
1005
|
};
|
|
985
1006
|
const req = http.request(opts, (res) => {
|
|
986
1007
|
let data = "";
|
|
@@ -1285,9 +1306,100 @@ async function handleTenant(action, args) {
|
|
|
1285
1306
|
break;
|
|
1286
1307
|
}
|
|
1287
1308
|
|
|
1309
|
+
case "workspace": {
|
|
1310
|
+
// daemora tenant workspace <tenantId> — show paths
|
|
1311
|
+
// daemora tenant workspace <tenantId> add <path> — add to allowedPaths
|
|
1312
|
+
// daemora tenant workspace <tenantId> remove <path> — remove from allowedPaths
|
|
1313
|
+
// daemora tenant workspace <tenantId> block <path> — add to blockedPaths
|
|
1314
|
+
// daemora tenant workspace <tenantId> unblock <path> — remove from blockedPaths
|
|
1315
|
+
const [tenantId, wsAction, wsPath] = args;
|
|
1316
|
+
if (!tenantId) {
|
|
1317
|
+
console.error(`\n ${S.cross} Usage: daemora tenant workspace ${t.dim("<tenantId> [add|remove|block|unblock] [path]")}\n`);
|
|
1318
|
+
process.exit(1);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const { default: tm } = await import("./tenants/TenantManager.js");
|
|
1322
|
+
const tenant = tm.get(tenantId);
|
|
1323
|
+
if (!tenant) {
|
|
1324
|
+
console.error(`\n ${S.cross} Tenant "${tenantId}" not found.\n`);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (!wsAction) {
|
|
1329
|
+
// Show current workspace paths
|
|
1330
|
+
console.log(header);
|
|
1331
|
+
console.log(` ${S.bar} Tenant ${t.bold(tenantId)}`);
|
|
1332
|
+
console.log(` ${S.bar} Workspace ${t.dim(tm.getWorkspace(tenantId))}`);
|
|
1333
|
+
const allowed = tenant.allowedPaths || [];
|
|
1334
|
+
const blocked = tenant.blockedPaths || [];
|
|
1335
|
+
if (allowed.length > 0) {
|
|
1336
|
+
console.log(` ${S.bar} Allowed paths:`);
|
|
1337
|
+
for (const p of allowed) console.log(` ${S.bar} ${S.check} ${p}`);
|
|
1338
|
+
} else {
|
|
1339
|
+
console.log(` ${S.bar} Allowed paths ${t.muted("(none — uses global or workspace default)")}`);
|
|
1340
|
+
}
|
|
1341
|
+
if (blocked.length > 0) {
|
|
1342
|
+
console.log(` ${S.bar} Blocked paths:`);
|
|
1343
|
+
for (const p of blocked) console.log(` ${S.bar} ${S.cross} ${p}`);
|
|
1344
|
+
} else {
|
|
1345
|
+
console.log(` ${S.bar} Blocked paths ${t.muted("(none)")}`);
|
|
1346
|
+
}
|
|
1347
|
+
console.log("");
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Validate path is absolute
|
|
1352
|
+
if (["add", "remove", "block", "unblock"].includes(wsAction)) {
|
|
1353
|
+
if (!wsPath) {
|
|
1354
|
+
console.error(`\n ${S.cross} Usage: daemora tenant workspace ${tenantId} ${wsAction} ${t.dim("<absolute-path>")}\n`);
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
}
|
|
1357
|
+
if (!wsPath.startsWith("/") && !/^[A-Za-z]:\\/.test(wsPath)) {
|
|
1358
|
+
console.error(`\n ${S.cross} Path must be absolute (start with / or C:\\)\n`);
|
|
1359
|
+
process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
try {
|
|
1364
|
+
if (wsAction === "add") {
|
|
1365
|
+
const updated = [...new Set([...(tenant.allowedPaths || []), wsPath])];
|
|
1366
|
+
tm.set(tenantId, { allowedPaths: updated });
|
|
1367
|
+
console.log(`\n${header} ${S.check} ${t.bold(wsPath)} added to allowedPaths for ${t.bold(tenantId)}\n`);
|
|
1368
|
+
} else if (wsAction === "remove") {
|
|
1369
|
+
const updated = (tenant.allowedPaths || []).filter(p => p !== wsPath);
|
|
1370
|
+
if (updated.length === (tenant.allowedPaths || []).length) {
|
|
1371
|
+
console.error(`\n ${S.cross} "${wsPath}" not found in allowedPaths.\n`);
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
tm.set(tenantId, { allowedPaths: updated });
|
|
1375
|
+
console.log(`\n${header} ${S.check} ${t.bold(wsPath)} removed from allowedPaths for ${t.bold(tenantId)}\n`);
|
|
1376
|
+
} else if (wsAction === "block") {
|
|
1377
|
+
const updated = [...new Set([...(tenant.blockedPaths || []), wsPath])];
|
|
1378
|
+
tm.set(tenantId, { blockedPaths: updated });
|
|
1379
|
+
console.log(`\n${header} ${S.check} ${t.bold(wsPath)} added to blockedPaths for ${t.bold(tenantId)}\n`);
|
|
1380
|
+
} else if (wsAction === "unblock") {
|
|
1381
|
+
const updated = (tenant.blockedPaths || []).filter(p => p !== wsPath);
|
|
1382
|
+
if (updated.length === (tenant.blockedPaths || []).length) {
|
|
1383
|
+
console.error(`\n ${S.cross} "${wsPath}" not found in blockedPaths.\n`);
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
}
|
|
1386
|
+
tm.set(tenantId, { blockedPaths: updated });
|
|
1387
|
+
console.log(`\n${header} ${S.check} ${t.bold(wsPath)} removed from blockedPaths for ${t.bold(tenantId)}\n`);
|
|
1388
|
+
} else {
|
|
1389
|
+
console.error(`\n ${S.cross} Unknown workspace action: ${wsAction}`);
|
|
1390
|
+
console.log(` ${t.muted("Usage:")} daemora tenant workspace ${t.dim("<id> [add|remove|block|unblock] <path>")}\n`);
|
|
1391
|
+
process.exit(1);
|
|
1392
|
+
}
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
console.error(`\n ${S.cross} ${err.message}\n`);
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1288
1400
|
default:
|
|
1289
1401
|
console.error(`\n ${S.cross} Unknown tenant command: ${action || "(none)"}`);
|
|
1290
|
-
console.log(` ${t.muted("Usage:")} daemora tenant ${t.dim("[list|show|set|plan|suspend|unsuspend|reset|delete|apikey|channel]")}\n`);
|
|
1402
|
+
console.log(` ${t.muted("Usage:")} daemora tenant ${t.dim("[list|show|set|plan|suspend|unsuspend|reset|delete|apikey|channel|workspace]")}\n`);
|
|
1291
1403
|
process.exit(1);
|
|
1292
1404
|
}
|
|
1293
1405
|
}
|
|
@@ -2193,6 +2305,11 @@ ${line}
|
|
|
2193
2305
|
${t.cmd("tenant channel unset")} ${t.dim("<id> <key>")} Remove a channel credential
|
|
2194
2306
|
${t.cmd("tenant channel list")} ${t.dim("<id>")} List stored channel credential keys
|
|
2195
2307
|
${t.muted(" channel keys: email email_password resend_api_key resend_from")}
|
|
2308
|
+
${t.cmd("tenant workspace")} ${t.dim("<id>")} Show workspace + allowed/blocked paths
|
|
2309
|
+
${t.cmd("tenant workspace")} ${t.dim("<id> add <path>")} Add path to tenant's allowedPaths
|
|
2310
|
+
${t.cmd("tenant workspace")} ${t.dim("<id> remove <path>")} Remove from allowedPaths
|
|
2311
|
+
${t.cmd("tenant workspace")} ${t.dim("<id> block <path>")} Add to blockedPaths
|
|
2312
|
+
${t.cmd("tenant workspace")} ${t.dim("<id> unblock <path>")} Remove from blockedPaths
|
|
2196
2313
|
|
|
2197
2314
|
${t.cmd("channels")} List all 19 supported channels + setup status
|
|
2198
2315
|
${t.cmd("models")} List all model providers + task-type routing
|
|
@@ -2247,6 +2364,9 @@ ${line}
|
|
|
2247
2364
|
${t.dim("$")} daemora tenant channel set telegram:123 email_password xxxx-xxxx-xxxx-xxxx
|
|
2248
2365
|
${t.dim("$")} daemora tenant channel list telegram:123
|
|
2249
2366
|
${t.dim("$")} daemora tenant channel unset telegram:123 email_password
|
|
2367
|
+
${t.dim("$")} daemora tenant workspace telegram:123
|
|
2368
|
+
${t.dim("$")} daemora tenant workspace telegram:123 add /home/user/projects
|
|
2369
|
+
${t.dim("$")} daemora tenant workspace telegram:123 block /home/user/private
|
|
2250
2370
|
${t.dim("$")} daemora doctor
|
|
2251
2371
|
`);
|
|
2252
2372
|
}
|
package/src/index.js
CHANGED
|
@@ -784,8 +784,12 @@ app.get("/api/tenants/:id", (req, res) => {
|
|
|
784
784
|
|
|
785
785
|
app.patch("/api/tenants/:id", (req, res) => {
|
|
786
786
|
const id = decodeURIComponent(req.params.id);
|
|
787
|
-
|
|
788
|
-
|
|
787
|
+
try {
|
|
788
|
+
const updated = tenantManager.set(id, req.body);
|
|
789
|
+
res.json(updated);
|
|
790
|
+
} catch (err) {
|
|
791
|
+
res.status(400).json({ error: err.message });
|
|
792
|
+
}
|
|
789
793
|
});
|
|
790
794
|
|
|
791
795
|
app.post("/api/tenants/:id/suspend", (req, res) => {
|
|
@@ -92,7 +92,28 @@ const BLOCKED_COMMANDS = [
|
|
|
92
92
|
pattern: /\b(?:cat|less|more|head|tail)\b[^;|&\n]*(?:id_rsa|id_ed25519|id_ecdsa|\.pem|\.key)\b/i,
|
|
93
93
|
reason: "Reading private key files via shell is blocked.",
|
|
94
94
|
},
|
|
95
|
-
// ── 6.
|
|
95
|
+
// ── 6. Daemora CLI privilege escalation ──────────────────────────────────
|
|
96
|
+
// Agent must NEVER run daemora/aegis CLI commands — they can modify tenant
|
|
97
|
+
// config, workspace paths, API keys, sandbox rules, etc.
|
|
98
|
+
{
|
|
99
|
+
pattern: /(?:^|[;&|`]\s*)(?:daemora|aegis)\b/,
|
|
100
|
+
reason: "Running daemora/aegis CLI commands from within the agent is blocked (privilege escalation).",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
pattern: /\bnpx\s+(?:daemora|aegis)\b/,
|
|
104
|
+
reason: "Running daemora/aegis via npx is blocked (privilege escalation).",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
// Block: node src/cli.js, node ./src/cli.js, bash -c "daemora ..."
|
|
108
|
+
pattern: /\bnode\b[^;|&\n]*(?:cli\.js|bin\/daemora|bin\/aegis)\b/,
|
|
109
|
+
reason: "Running daemora/aegis CLI via node is blocked (privilege escalation).",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
pattern: /\bbash\b[^;|&\n]*-c\s+['"][^'"]*(?:daemora|aegis)\b/,
|
|
113
|
+
reason: "Running daemora/aegis via bash -c is blocked (privilege escalation).",
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// ── 7. Agent config files (may contain plaintext MCP API keys) ────────────
|
|
96
117
|
{
|
|
97
118
|
// config/mcp.json contains GITHUB_TOKEN, Bearer tokens, etc. in plaintext
|
|
98
119
|
pattern: /\b(?:cat|less|more|head|tail|bat|jq|python|node)\b[^;|&\n]*config[\/\\]mcp\.json/i,
|
package/src/setup/theme.js
CHANGED