create-ekka-desktop-app 0.4.1 → 0.4.3
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 +5 -5
- package/bin/cli.js +2 -2
- package/package.json +1 -1
- package/template/package.json +2 -2
- package/template/src-tauri/Cargo.toml +55 -4
- package/template/src-tauri/crates/ekka-desktop-core/Cargo.toml +1 -4
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/Cargo.toml +67 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/TECH_DEBT.md +111 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/dispatch.rs +87 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/debug_bundle.rs +520 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/mod.rs +8 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/node_exec.rs +97 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/prompt_run.rs +2121 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/lib.rs +17 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/main.rs +1181 -0
- package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/types.rs +840 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/Cargo.toml +27 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/TECH_DEBT.md +55 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/compression.rs +56 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/error.rs +23 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/fs_store.rs +255 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/hash.rs +27 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/lib.rs +42 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/retrieve.rs +52 -0
- package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/types.rs +44 -0
- package/template/src-tauri/crates/vendor/core/ekka-crypto/Cargo.toml +23 -0
- package/template/src-tauri/crates/vendor/core/ekka-crypto/src/lib.rs +287 -0
- package/template/src-tauri/crates/vendor/core/ekka-encrypted-db/Cargo.toml +14 -0
- package/template/src-tauri/crates/vendor/core/ekka-encrypted-db/src/lib.rs +222 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/Cargo.toml +29 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/context.rs +116 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/error.rs +140 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/grants.rs +304 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/home.rs +308 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/lib.rs +76 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/llm_result.rs +1034 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/paths.rs +473 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/prompt_run_payload.rs +772 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/retention.rs +330 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/traits.rs +175 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/audit_impl.rs +154 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/bundles_impl.rs +300 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/cache.rs +308 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/files_impl.rs +432 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/manager.rs +189 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/mod.rs +371 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/path_safety.rs +235 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/secrets_impl.rs +311 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/status_impl.rs +67 -0
- package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/types.rs +361 -0
- package/template/src-tauri/crates/vendor/core/ekka-secure-storage/Cargo.toml +18 -0
- package/template/src-tauri/crates/vendor/core/ekka-secure-storage/src/lib.rs +388 -0
- package/template/src-tauri/crates/vendor/core/ekka-vault-seal/Cargo.toml +44 -0
- package/template/src-tauri/crates/vendor/core/ekka-vault-seal/src/lib.rs +617 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-module-catalog/Cargo.toml +39 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-module-catalog/src/lib.rs +1560 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-module-runner/Cargo.toml +43 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-module-runner/src/lib.rs +1286 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-modules/Cargo.toml +15 -0
- package/template/src-tauri/crates/vendor/framework/ekka-node-modules/src/lib.rs +309 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/Cargo.toml +55 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/dispatch.rs +68 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/artifact_capture.rs +570 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/mod.rs +5 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/node_exec.rs +74 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/prompt_run.rs +423 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/lib.rs +663 -0
- package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/types.rs +296 -0
- package/template/src-tauri/crates/vendor/framework/ekka-sdk-core/Cargo.toml +56 -0
- package/template/src-tauri/crates/vendor/framework/ekka-sdk-core/src/lib.rs +71 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-actions/Cargo.toml +39 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-actions/src/lib.rs +738 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-agent/Cargo.toml +41 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-agent/src/lib.rs +1060 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-git/Cargo.toml +43 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-git/src/lib.rs +3762 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-github/Cargo.toml +33 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-github/src/lib.rs +954 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/Cargo.toml +43 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/src/lib.rs +3753 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/src/persist.rs +1113 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-llm/Cargo.toml +35 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-llm/src/lib.rs +670 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-vault/Cargo.toml +30 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-vault/src/lib.rs +379 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/Cargo.toml +46 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/src/lib.rs +1501 -0
- package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/src/persist.rs +1009 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/Cargo.toml +30 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/epoch.rs +221 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/lib.rs +375 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/marker.rs +278 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/wipe.rs +311 -0
- package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/work_home.rs +518 -0
- package/template/src-tauri/crates/vendor/security/ekka-path-guard/Cargo.toml +24 -0
- package/template/src-tauri/crates/vendor/security/ekka-path-guard/src/grant_store.rs +609 -0
- package/template/src-tauri/crates/vendor/security/ekka-path-guard/src/lib.rs +1247 -0
- package/template/src-tauri/crates/vendor/security/ekka-vault/Cargo.toml +19 -0
- package/template/src-tauri/crates/vendor/security/ekka-vault/src/lib.rs +423 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Scaffold a new EKKA desktop app with one command. Zero config, batteries include
|
|
|
8
8
|
npx create-ekka-desktop-app my-app
|
|
9
9
|
cd my-app
|
|
10
10
|
npm install
|
|
11
|
-
npm run
|
|
11
|
+
npm run ekka:dev
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
That's it. You now have a native desktop app running.
|
|
@@ -34,14 +34,14 @@ my-app/
|
|
|
34
34
|
npm start
|
|
35
35
|
|
|
36
36
|
# Desktop window (native)
|
|
37
|
-
npm run
|
|
37
|
+
npm run ekka:dev
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Build
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
# Create distributable .app
|
|
44
|
-
npm run
|
|
44
|
+
npm run ekka:build
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Output: `src-tauri/target/release/bundle/macos/<AppName>.app`
|
|
@@ -110,8 +110,8 @@ When you're ready for production:
|
|
|
110
110
|
| Command | Description |
|
|
111
111
|
|---------|-------------|
|
|
112
112
|
| `npm start` | Start dev server (web) |
|
|
113
|
-
| `npm run
|
|
114
|
-
| `npm run
|
|
113
|
+
| `npm run ekka:dev` | Start dev server (desktop) |
|
|
114
|
+
| `npm run ekka:build` | Build distributable app |
|
|
115
115
|
| `npm run lint` | Run ESLint |
|
|
116
116
|
| `npm run build` | Build frontend only |
|
|
117
117
|
|
package/bin/cli.js
CHANGED
|
@@ -240,13 +240,13 @@ To get started:
|
|
|
240
240
|
|
|
241
241
|
cd ${projectName}
|
|
242
242
|
npm install
|
|
243
|
-
npm run
|
|
243
|
+
npm run ekka:dev
|
|
244
244
|
|
|
245
245
|
Configuration:
|
|
246
246
|
Edit app.config.json to change app identity or engine URL.
|
|
247
247
|
|
|
248
248
|
Build:
|
|
249
|
-
npm run
|
|
249
|
+
npm run ekka:build
|
|
250
250
|
`);
|
|
251
251
|
}
|
|
252
252
|
|
package/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"test:watch": "vitest",
|
|
14
14
|
"preview": "vite preview",
|
|
15
15
|
"tauri": "tauri",
|
|
16
|
-
"
|
|
16
|
+
"ekka:dev": "tauri dev",
|
|
17
17
|
"prebuild:check": "node -e \"const fs=require('fs');const p=s=>{try{return fs.readFileSync(s,'utf8')}catch{return''}};const e=['.env.local','.env','src-tauri/.env.local','src-tauri/.env'].map(p).join('\\n');if(!/^EKKA_ENGINE_URL=.+/m.test(e)&&!process.env.EKKA_ENGINE_URL){console.error('\\n❌ EKKA_ENGINE_URL not set. Add to .env.local before building:\\n\\n echo \\'EKKA_ENGINE_URL=https://api.ekka.ai\\' >> .env.local\\n');process.exit(1)}console.log('✓ EKKA_ENGINE_URL configured')\"",
|
|
18
|
-
"
|
|
18
|
+
"ekka:build": "npm run prebuild:check && tauri build"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@tauri-apps/api": "^2.0.0",
|
|
@@ -5,8 +5,59 @@ description = "EKKA Desktop Application"
|
|
|
5
5
|
authors = ["EKKA"]
|
|
6
6
|
edition = "2021"
|
|
7
7
|
|
|
8
|
-
# Prevent inheriting parent workspace
|
|
9
8
|
[workspace]
|
|
9
|
+
members = [
|
|
10
|
+
"crates/ekka-desktop-core",
|
|
11
|
+
"crates/vendor/core/*",
|
|
12
|
+
"crates/vendor/security/*",
|
|
13
|
+
"crates/vendor/framework/*",
|
|
14
|
+
"crates/vendor/modules/*",
|
|
15
|
+
"crates/vendor/apps/*",
|
|
16
|
+
]
|
|
17
|
+
resolver = "2"
|
|
18
|
+
|
|
19
|
+
[workspace.package]
|
|
20
|
+
version = "0.1.0"
|
|
21
|
+
edition = "2021"
|
|
22
|
+
authors = ["EKKA"]
|
|
23
|
+
license = "LicenseRef-Proprietary"
|
|
24
|
+
repository = "https://github.com/ekka-ai/ekka-execution-node-sdk-rust"
|
|
25
|
+
description = "EKKA Execution Node SDK"
|
|
26
|
+
|
|
27
|
+
[workspace.dependencies]
|
|
28
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
29
|
+
serde_json = "1.0"
|
|
30
|
+
thiserror = "1.0"
|
|
31
|
+
anyhow = "1.0"
|
|
32
|
+
zeroize = { version = "1.7", features = ["derive"] }
|
|
33
|
+
sha2 = "0.10"
|
|
34
|
+
hex = "0.4"
|
|
35
|
+
base64 = "0.22"
|
|
36
|
+
aes-gcm = "0.10"
|
|
37
|
+
pbkdf2 = "0.12"
|
|
38
|
+
hmac = "0.12"
|
|
39
|
+
rand = "0.8"
|
|
40
|
+
keyring = "3.6"
|
|
41
|
+
rusqlite = { version = "0.31", features = ["bundled-sqlcipher"] }
|
|
42
|
+
dirs = "5.0"
|
|
43
|
+
uuid = { version = "1.0", features = ["v4", "serde"] }
|
|
44
|
+
chrono = { version = "0.4", features = ["serde"] }
|
|
45
|
+
tracing = "0.1"
|
|
46
|
+
tokio = { version = "1", features = ["full"] }
|
|
47
|
+
async-trait = "0.1"
|
|
48
|
+
hostname = "0.4"
|
|
49
|
+
|
|
50
|
+
[workspace.lints.rust]
|
|
51
|
+
warnings = "deny"
|
|
52
|
+
unused = "deny"
|
|
53
|
+
dead_code = "deny"
|
|
54
|
+
unused_variables = "deny"
|
|
55
|
+
unused_imports = "deny"
|
|
56
|
+
unused_mut = "deny"
|
|
57
|
+
|
|
58
|
+
[workspace.lints.clippy]
|
|
59
|
+
all = "deny"
|
|
60
|
+
pedantic = "warn"
|
|
10
61
|
|
|
11
62
|
[build-dependencies]
|
|
12
63
|
tauri-build = { version = "2", features = [] }
|
|
@@ -19,9 +70,9 @@ tauri-plugin-updater = "2"
|
|
|
19
70
|
serde = { version = "1", features = ["derive"] }
|
|
20
71
|
serde_json = "1"
|
|
21
72
|
chrono = { version = "0.4", features = ["serde"] }
|
|
22
|
-
ekka-sdk-core = { path = "
|
|
23
|
-
ekka-runner-core = { path = "
|
|
24
|
-
ekka-runner-local = { path = "
|
|
73
|
+
ekka-sdk-core = { path = "./crates/vendor/framework/ekka-sdk-core" }
|
|
74
|
+
ekka-runner-core = { path = "./crates/vendor/framework/ekka-runner-core" }
|
|
75
|
+
ekka-runner-local = { path = "./crates/vendor/apps/ekka-runner-local" }
|
|
25
76
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
|
26
77
|
http = "1"
|
|
27
78
|
uuid = { version = "1.0", features = ["v4"] }
|
|
@@ -4,9 +4,6 @@ version = "0.1.0"
|
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "EKKA Desktop Core - Security-critical logic process (JSON-RPC over stdio)"
|
|
6
6
|
|
|
7
|
-
# Prevent inheriting parent workspace
|
|
8
|
-
[workspace]
|
|
9
|
-
|
|
10
7
|
[build-dependencies]
|
|
11
8
|
serde_json = "1"
|
|
12
9
|
|
|
@@ -24,7 +21,7 @@ hex = "0.4"
|
|
|
24
21
|
anyhow = "1.0"
|
|
25
22
|
|
|
26
23
|
# SDK dependencies (only the primitives we need)
|
|
27
|
-
ekka-sdk-core = { path = "
|
|
24
|
+
ekka-sdk-core = { path = "../vendor/framework/ekka-sdk-core" }
|
|
28
25
|
|
|
29
26
|
[dev-dependencies]
|
|
30
27
|
tempfile = "3"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ekka-runner-local"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Local runner for EKKA jobs - thin wrapper around ekka-runner-core"
|
|
6
|
+
|
|
7
|
+
[[bin]]
|
|
8
|
+
name = "ekka-runner-local"
|
|
9
|
+
path = "src/main.rs"
|
|
10
|
+
|
|
11
|
+
[lib]
|
|
12
|
+
name = "ekka_runner_local"
|
|
13
|
+
path = "src/lib.rs"
|
|
14
|
+
|
|
15
|
+
[dependencies]
|
|
16
|
+
# Runner core library
|
|
17
|
+
ekka-runner-core = { path = "../../framework/ekka-runner-core" }
|
|
18
|
+
|
|
19
|
+
# Async runtime
|
|
20
|
+
tokio = { version = "1", features = ["full"] }
|
|
21
|
+
|
|
22
|
+
# Logging
|
|
23
|
+
tracing = "0.1"
|
|
24
|
+
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
|
25
|
+
|
|
26
|
+
# Re-use job types from jobs module (for node mode - DEPRECATED)
|
|
27
|
+
ekka-node-module-jobs = { path = "../../modules/ekka-node-module-jobs" }
|
|
28
|
+
|
|
29
|
+
# JSON
|
|
30
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
31
|
+
serde_json = "1.0"
|
|
32
|
+
|
|
33
|
+
# HTTP (for node mode - DEPRECATED)
|
|
34
|
+
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
|
35
|
+
|
|
36
|
+
# UUID
|
|
37
|
+
uuid = { version = "1.0", features = ["v4"] }
|
|
38
|
+
|
|
39
|
+
# Crypto
|
|
40
|
+
sha2 = "0.10"
|
|
41
|
+
hex = "0.4"
|
|
42
|
+
|
|
43
|
+
# Time
|
|
44
|
+
chrono = "0.4"
|
|
45
|
+
|
|
46
|
+
# Regex (for node mode executors)
|
|
47
|
+
regex = "1.10"
|
|
48
|
+
|
|
49
|
+
# Path authorization (for node mode executors)
|
|
50
|
+
ekka-path-guard = { path = "../../security/ekka-path-guard" }
|
|
51
|
+
|
|
52
|
+
# Vault sealing (staging -> encrypted vault)
|
|
53
|
+
ekka-vault-seal = { path = "../../core/ekka-vault-seal" }
|
|
54
|
+
|
|
55
|
+
# Crypto (for key derivation)
|
|
56
|
+
ekka-crypto = { path = "../../core/ekka-crypto" }
|
|
57
|
+
|
|
58
|
+
# LLM result types (for ArtifactRef)
|
|
59
|
+
ekka-ops = { path = "../../core/ekka-ops" }
|
|
60
|
+
|
|
61
|
+
# Home directory fallback
|
|
62
|
+
dirs = "5.0"
|
|
63
|
+
|
|
64
|
+
[dev-dependencies]
|
|
65
|
+
|
|
66
|
+
[lints]
|
|
67
|
+
workspace = true
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Technical Debt - ekka-runner-local
|
|
2
|
+
|
|
3
|
+
## TD-TIMEOUT-001: Make LLM timeout configurable per prompt/workflow
|
|
4
|
+
|
|
5
|
+
**Status:** Open
|
|
6
|
+
**Created:** 2026-01-30
|
|
7
|
+
**Priority:** Medium
|
|
8
|
+
|
|
9
|
+
### Current State
|
|
10
|
+
|
|
11
|
+
LLM execution timeout is a single global default (`LLM_TIMEOUT_SECS_DEFAULT = 1200`, i.e., 20 minutes).
|
|
12
|
+
Can be overridden via `EKKA_LLM_TIMEOUT_SECS` environment variable for testing.
|
|
13
|
+
|
|
14
|
+
### Problem
|
|
15
|
+
|
|
16
|
+
Different prompts/workflows have vastly different execution times:
|
|
17
|
+
- Simple classification: 5-30 seconds
|
|
18
|
+
- Document generation (docgen): 2-15 minutes
|
|
19
|
+
- Complex analysis: 10-30+ minutes
|
|
20
|
+
|
|
21
|
+
A single global timeout either:
|
|
22
|
+
- Times out legitimate long-running tasks (too short)
|
|
23
|
+
- Delays failure detection for stuck tasks (too long)
|
|
24
|
+
|
|
25
|
+
### Proposed Solution
|
|
26
|
+
|
|
27
|
+
1. Add `timeout_secs` field to `PromptRunTaskPayloadV1`:
|
|
28
|
+
```rust
|
|
29
|
+
#[serde(default)]
|
|
30
|
+
pub timeout_secs: Option<u64>,
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Engine workflow definition specifies expected timeout per prompt/step.
|
|
34
|
+
|
|
35
|
+
3. Runner uses `payload.timeout_secs.unwrap_or(LLM_TIMEOUT_SECS_DEFAULT)`.
|
|
36
|
+
|
|
37
|
+
4. Enforce safe maximum cap (e.g., 3600 seconds / 1 hour) to prevent runaway tasks.
|
|
38
|
+
|
|
39
|
+
5. Log both configured and effective timeout in `prompt_run.llm.started` for observability.
|
|
40
|
+
|
|
41
|
+
### Considerations
|
|
42
|
+
|
|
43
|
+
- Backward compatibility: absent field uses default
|
|
44
|
+
- Security: cap maximum to prevent DoS
|
|
45
|
+
- Observability: log configured vs effective timeout
|
|
46
|
+
- Engine schema: add `timeout_secs` to prompt_run task input schema
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## TD-TOOLS-001: Make tool allowlist/disallowlist prompt/workflow-driven
|
|
51
|
+
|
|
52
|
+
**Status:** Open
|
|
53
|
+
**Created:** 2026-01-30
|
|
54
|
+
**Priority:** High
|
|
55
|
+
|
|
56
|
+
### Current State
|
|
57
|
+
|
|
58
|
+
Claude CLI tool configuration uses a global default:
|
|
59
|
+
- `--allowedTools default` (broad toolset for many prompt types)
|
|
60
|
+
- `--disallowedTools Bash,WebFetch,WebSearch` (blocks risky tools)
|
|
61
|
+
|
|
62
|
+
This one-size-fits-all approach is applied to all prompts regardless of their needs.
|
|
63
|
+
|
|
64
|
+
### Problem
|
|
65
|
+
|
|
66
|
+
Different prompts/workflows need different tool profiles:
|
|
67
|
+
- **docgen**: needs Write, Read, Glob, Grep (file operations only)
|
|
68
|
+
- **compare**: needs Read, Glob, Grep (read-only analysis)
|
|
69
|
+
- **plan**: needs Read, Glob, Grep, EnterPlanMode (planning only)
|
|
70
|
+
- **execute**: may need broader tool access with approval
|
|
71
|
+
|
|
72
|
+
A single global toolset either:
|
|
73
|
+
- Over-permits tools for simple tasks (security risk)
|
|
74
|
+
- Under-permits tools for complex tasks (functionality blocked)
|
|
75
|
+
|
|
76
|
+
### Proposed Solution
|
|
77
|
+
|
|
78
|
+
1. Add `tool_profile` or `allowed_tools`/`disallowed_tools` fields to `PromptRunTaskPayloadV1`:
|
|
79
|
+
```rust
|
|
80
|
+
#[serde(default)]
|
|
81
|
+
pub tool_profile: Option<String>, // e.g., "docgen", "readonly", "full"
|
|
82
|
+
|
|
83
|
+
#[serde(default)]
|
|
84
|
+
pub allowed_tools: Option<Vec<String>>,
|
|
85
|
+
|
|
86
|
+
#[serde(default)]
|
|
87
|
+
pub disallowed_tools: Option<Vec<String>>,
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
2. Define tool profiles in engine/prompt registry:
|
|
91
|
+
- `readonly`: Read, Glob, Grep only
|
|
92
|
+
- `docgen`: Read, Write, Edit, Glob, Grep
|
|
93
|
+
- `planning`: Read, Glob, Grep, EnterPlanMode, ExitPlanMode
|
|
94
|
+
- `full`: default toolset (current behavior)
|
|
95
|
+
|
|
96
|
+
3. Runner applies profile or explicit lists, with safe defaults.
|
|
97
|
+
|
|
98
|
+
4. Enforce a "never allowed" blocklist regardless of profile:
|
|
99
|
+
- Bash (arbitrary command execution)
|
|
100
|
+
- WebFetch/WebSearch (network access without approval)
|
|
101
|
+
|
|
102
|
+
5. Log effective tool configuration in `prompt_run.llm.started`.
|
|
103
|
+
|
|
104
|
+
### Considerations
|
|
105
|
+
|
|
106
|
+
- Backward compatibility: absent fields use current global defaults
|
|
107
|
+
- Security: always enforce blocklist even if profile says "full"
|
|
108
|
+
- AskUserQuestion behavior: revisit whether to enable when `--permission-mode dontAsk` is set
|
|
109
|
+
(currently allowed but user cannot respond, may cause hangs or confusing behavior)
|
|
110
|
+
- Engine schema: add tool configuration to prompt_run task input schema
|
|
111
|
+
- Capability integration: consider tying tool profiles to capability grants
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//! Task dispatch for ekka-runner-local
|
|
2
|
+
//!
|
|
3
|
+
//! Routes tasks to the appropriate executor based on task_subtype.
|
|
4
|
+
//! This is the single point where new executors should be added.
|
|
5
|
+
|
|
6
|
+
use reqwest::Client;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
use tracing::warn;
|
|
9
|
+
|
|
10
|
+
use crate::executors;
|
|
11
|
+
use crate::types::{EngineContext, TaskExecutionContext};
|
|
12
|
+
|
|
13
|
+
/// Dispatch a task to the appropriate executor based on task_subtype.
|
|
14
|
+
///
|
|
15
|
+
/// # Arguments
|
|
16
|
+
/// * `task_subtype` - The task subtype (e.g., "node_exec", "prompt_run")
|
|
17
|
+
/// * `client` - HTTP client for making requests
|
|
18
|
+
/// * `node_url` - Base URL of the local node (for node_exec)
|
|
19
|
+
/// * `session_id` - Session ID for node authentication (for node_exec)
|
|
20
|
+
/// * `engine_ctx` - Engine context for prompt_run (URL, internal key, tenant/workspace)
|
|
21
|
+
/// * `ctx` - Task execution context with input_json
|
|
22
|
+
/// * `heartbeat_fn` - Optional heartbeat callback for long-running tasks
|
|
23
|
+
///
|
|
24
|
+
/// # Returns
|
|
25
|
+
/// * `Ok(serde_json::Value)` - The output from task execution
|
|
26
|
+
/// * `Err(String)` - Error message if execution failed
|
|
27
|
+
pub async fn dispatch_task(
|
|
28
|
+
task_subtype: Option<&str>,
|
|
29
|
+
client: &Client,
|
|
30
|
+
node_url: &str,
|
|
31
|
+
session_id: &str,
|
|
32
|
+
engine_ctx: Option<&EngineContext>,
|
|
33
|
+
ctx: &TaskExecutionContext,
|
|
34
|
+
heartbeat_fn: Option<Arc<dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), String>> + Send>> + Send + Sync>>,
|
|
35
|
+
) -> Result<serde_json::Value, String> {
|
|
36
|
+
match task_subtype {
|
|
37
|
+
Some("node_exec") => {
|
|
38
|
+
executors::node_exec::execute(client, node_url, session_id, ctx).await
|
|
39
|
+
}
|
|
40
|
+
Some("prompt_run") => {
|
|
41
|
+
// prompt_run requires engine context
|
|
42
|
+
let engine_ctx = engine_ctx.ok_or_else(|| {
|
|
43
|
+
"Engine context required for prompt_run executor".to_string()
|
|
44
|
+
})?;
|
|
45
|
+
executors::prompt_run::execute(client, engine_ctx, ctx, heartbeat_fn).await
|
|
46
|
+
}
|
|
47
|
+
_ => {
|
|
48
|
+
// Unknown subtype
|
|
49
|
+
warn!(
|
|
50
|
+
op = "engine_runner.task.unsupported",
|
|
51
|
+
task_id = %ctx.task_id_short,
|
|
52
|
+
task_subtype = ?task_subtype,
|
|
53
|
+
"Unsupported task subtype for engine runner"
|
|
54
|
+
);
|
|
55
|
+
Err("Unsupported task subtype".to_string())
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Determine error code and retryability from an execution error.
|
|
61
|
+
///
|
|
62
|
+
/// This centralizes error classification for all executors.
|
|
63
|
+
pub fn classify_error(error: &str) -> (&'static str, bool) {
|
|
64
|
+
if error.contains("CAPABILITY_DENIED") {
|
|
65
|
+
("CAPABILITY_DENIED", false)
|
|
66
|
+
} else if error.contains("Unknown capability_code") {
|
|
67
|
+
("INVALID_CAPABILITY", false)
|
|
68
|
+
} else if error.contains("Unsupported task subtype") {
|
|
69
|
+
("UNSUPPORTED_TASK", false)
|
|
70
|
+
// prompt_run specific errors (non-retryable)
|
|
71
|
+
} else if error.contains("INVALID_SCHEMA_VERSION")
|
|
72
|
+
|| error.contains("INVALID_PROMPT_IDENTITY")
|
|
73
|
+
|| error.contains("SECRETS_IN_PAYLOAD")
|
|
74
|
+
|| error.contains("PROMPT_HASH_MISMATCH")
|
|
75
|
+
|| error.contains("MISSING_VARIABLE")
|
|
76
|
+
|| error.contains("INVALID_VARIABLE_TYPE")
|
|
77
|
+
|| error.contains("PROMPT_NOT_FOUND")
|
|
78
|
+
|| error.contains("PROMPT_NOT_AUTHORIZED")
|
|
79
|
+
{
|
|
80
|
+
("PROMPT_RUN_ERROR", false)
|
|
81
|
+
// prompt_run retryable errors
|
|
82
|
+
} else if error.contains("LLM_TIMEOUT") || error.contains("PROMPT_FETCH_FAILED") {
|
|
83
|
+
("PROMPT_RUN_ERROR", true)
|
|
84
|
+
} else {
|
|
85
|
+
("RUNNER_ERROR", true)
|
|
86
|
+
}
|
|
87
|
+
}
|