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.
Files changed (99) hide show
  1. package/README.md +5 -5
  2. package/bin/cli.js +2 -2
  3. package/package.json +1 -1
  4. package/template/package.json +2 -2
  5. package/template/src-tauri/Cargo.toml +55 -4
  6. package/template/src-tauri/crates/ekka-desktop-core/Cargo.toml +1 -4
  7. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/Cargo.toml +67 -0
  8. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/TECH_DEBT.md +111 -0
  9. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/dispatch.rs +87 -0
  10. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/debug_bundle.rs +520 -0
  11. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/mod.rs +8 -0
  12. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/node_exec.rs +97 -0
  13. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/executors/prompt_run.rs +2121 -0
  14. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/lib.rs +17 -0
  15. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/main.rs +1181 -0
  16. package/template/src-tauri/crates/vendor/apps/ekka-runner-local/src/types.rs +840 -0
  17. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/Cargo.toml +27 -0
  18. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/TECH_DEBT.md +55 -0
  19. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/compression.rs +56 -0
  20. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/error.rs +23 -0
  21. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/fs_store.rs +255 -0
  22. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/hash.rs +27 -0
  23. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/lib.rs +42 -0
  24. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/retrieve.rs +52 -0
  25. package/template/src-tauri/crates/vendor/core/ekka-artifact-store/src/types.rs +44 -0
  26. package/template/src-tauri/crates/vendor/core/ekka-crypto/Cargo.toml +23 -0
  27. package/template/src-tauri/crates/vendor/core/ekka-crypto/src/lib.rs +287 -0
  28. package/template/src-tauri/crates/vendor/core/ekka-encrypted-db/Cargo.toml +14 -0
  29. package/template/src-tauri/crates/vendor/core/ekka-encrypted-db/src/lib.rs +222 -0
  30. package/template/src-tauri/crates/vendor/core/ekka-ops/Cargo.toml +29 -0
  31. package/template/src-tauri/crates/vendor/core/ekka-ops/src/context.rs +116 -0
  32. package/template/src-tauri/crates/vendor/core/ekka-ops/src/error.rs +140 -0
  33. package/template/src-tauri/crates/vendor/core/ekka-ops/src/grants.rs +304 -0
  34. package/template/src-tauri/crates/vendor/core/ekka-ops/src/home.rs +308 -0
  35. package/template/src-tauri/crates/vendor/core/ekka-ops/src/lib.rs +76 -0
  36. package/template/src-tauri/crates/vendor/core/ekka-ops/src/llm_result.rs +1034 -0
  37. package/template/src-tauri/crates/vendor/core/ekka-ops/src/paths.rs +473 -0
  38. package/template/src-tauri/crates/vendor/core/ekka-ops/src/prompt_run_payload.rs +772 -0
  39. package/template/src-tauri/crates/vendor/core/ekka-ops/src/retention.rs +330 -0
  40. package/template/src-tauri/crates/vendor/core/ekka-ops/src/traits.rs +175 -0
  41. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/audit_impl.rs +154 -0
  42. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/bundles_impl.rs +300 -0
  43. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/cache.rs +308 -0
  44. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/files_impl.rs +432 -0
  45. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/manager.rs +189 -0
  46. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/mod.rs +371 -0
  47. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/path_safety.rs +235 -0
  48. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/secrets_impl.rs +311 -0
  49. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/status_impl.rs +67 -0
  50. package/template/src-tauri/crates/vendor/core/ekka-ops/src/vault/types.rs +361 -0
  51. package/template/src-tauri/crates/vendor/core/ekka-secure-storage/Cargo.toml +18 -0
  52. package/template/src-tauri/crates/vendor/core/ekka-secure-storage/src/lib.rs +388 -0
  53. package/template/src-tauri/crates/vendor/core/ekka-vault-seal/Cargo.toml +44 -0
  54. package/template/src-tauri/crates/vendor/core/ekka-vault-seal/src/lib.rs +617 -0
  55. package/template/src-tauri/crates/vendor/framework/ekka-node-module-catalog/Cargo.toml +39 -0
  56. package/template/src-tauri/crates/vendor/framework/ekka-node-module-catalog/src/lib.rs +1560 -0
  57. package/template/src-tauri/crates/vendor/framework/ekka-node-module-runner/Cargo.toml +43 -0
  58. package/template/src-tauri/crates/vendor/framework/ekka-node-module-runner/src/lib.rs +1286 -0
  59. package/template/src-tauri/crates/vendor/framework/ekka-node-modules/Cargo.toml +15 -0
  60. package/template/src-tauri/crates/vendor/framework/ekka-node-modules/src/lib.rs +309 -0
  61. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/Cargo.toml +55 -0
  62. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/dispatch.rs +68 -0
  63. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/artifact_capture.rs +570 -0
  64. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/mod.rs +5 -0
  65. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/node_exec.rs +74 -0
  66. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/executors/prompt_run.rs +423 -0
  67. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/lib.rs +663 -0
  68. package/template/src-tauri/crates/vendor/framework/ekka-runner-core/src/types.rs +296 -0
  69. package/template/src-tauri/crates/vendor/framework/ekka-sdk-core/Cargo.toml +56 -0
  70. package/template/src-tauri/crates/vendor/framework/ekka-sdk-core/src/lib.rs +71 -0
  71. package/template/src-tauri/crates/vendor/modules/ekka-node-module-actions/Cargo.toml +39 -0
  72. package/template/src-tauri/crates/vendor/modules/ekka-node-module-actions/src/lib.rs +738 -0
  73. package/template/src-tauri/crates/vendor/modules/ekka-node-module-agent/Cargo.toml +41 -0
  74. package/template/src-tauri/crates/vendor/modules/ekka-node-module-agent/src/lib.rs +1060 -0
  75. package/template/src-tauri/crates/vendor/modules/ekka-node-module-git/Cargo.toml +43 -0
  76. package/template/src-tauri/crates/vendor/modules/ekka-node-module-git/src/lib.rs +3762 -0
  77. package/template/src-tauri/crates/vendor/modules/ekka-node-module-github/Cargo.toml +33 -0
  78. package/template/src-tauri/crates/vendor/modules/ekka-node-module-github/src/lib.rs +954 -0
  79. package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/Cargo.toml +43 -0
  80. package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/src/lib.rs +3753 -0
  81. package/template/src-tauri/crates/vendor/modules/ekka-node-module-jobs/src/persist.rs +1113 -0
  82. package/template/src-tauri/crates/vendor/modules/ekka-node-module-llm/Cargo.toml +35 -0
  83. package/template/src-tauri/crates/vendor/modules/ekka-node-module-llm/src/lib.rs +670 -0
  84. package/template/src-tauri/crates/vendor/modules/ekka-node-module-vault/Cargo.toml +30 -0
  85. package/template/src-tauri/crates/vendor/modules/ekka-node-module-vault/src/lib.rs +379 -0
  86. package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/Cargo.toml +46 -0
  87. package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/src/lib.rs +1501 -0
  88. package/template/src-tauri/crates/vendor/modules/ekka-node-module-workspaces/src/persist.rs +1009 -0
  89. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/Cargo.toml +30 -0
  90. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/epoch.rs +221 -0
  91. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/lib.rs +375 -0
  92. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/marker.rs +278 -0
  93. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/wipe.rs +311 -0
  94. package/template/src-tauri/crates/vendor/security/ekka-home-bootstrap/src/work_home.rs +518 -0
  95. package/template/src-tauri/crates/vendor/security/ekka-path-guard/Cargo.toml +24 -0
  96. package/template/src-tauri/crates/vendor/security/ekka-path-guard/src/grant_store.rs +609 -0
  97. package/template/src-tauri/crates/vendor/security/ekka-path-guard/src/lib.rs +1247 -0
  98. package/template/src-tauri/crates/vendor/security/ekka-vault/Cargo.toml +19 -0
  99. 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 tauri:dev
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 tauri:dev
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 tauri:build
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 tauri:dev` | Start dev server (desktop) |
114
- | `npm run tauri:build` | Build distributable app |
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 tauri:dev
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 tauri:build
249
+ npm run ekka:build
250
250
  `);
251
251
  }
252
252
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ekka-desktop-app",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Create an EKKA desktop app with built-in demo backend. No setup required.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,9 +13,9 @@
13
13
  "test:watch": "vitest",
14
14
  "preview": "vite preview",
15
15
  "tauri": "tauri",
16
- "tauri:dev": "tauri dev",
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
- "tauri:build": "npm run prebuild:check && tauri build"
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 = "../../ekka-execution-node-sdk-rust/crates/framework/ekka-sdk-core" }
23
- ekka-runner-core = { path = "../../ekka-execution-node-sdk-rust/crates/framework/ekka-runner-core" }
24
- ekka-runner-local = { path = "../../ekka-execution-node-sdk-rust/crates/apps/ekka-runner-local" }
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 = "../../../../ekka-execution-node-sdk-rust/crates/framework/ekka-sdk-core" }
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
+ }