Skip to content

Commit 1e41c58

Browse files
author
VoardWalker-Code
committed
fix: task execution pipeline — 5 bugs causing tasks to start but never complete
Bug 1 (CRITICAL): Session ID mismatch — executeTask() generated its own sessionId while callers subscribed events on a different session.id. Events never reached subscribers. Fix: executeTask now accepts an optional sessionId from callers; both task-routes.js and slash-interceptor.js pass session.id. Bug 2 (CRITICAL): Task History always showed 'No entity loaded' because task-ui.js checked activeConfig.entityId which never exists. Fix: use window.getActiveEntityId() instead. Bug 3 (MAJOR): No Frontman/SSE bridge for slash command or API-initiated tasks — only task-pipeline-bridge wired Frontman. Fix: expose taskFrontman from chat-pipeline.js, add to server ctx, wire into slash-interceptor.js and task-routes.js so SSE task_milestone/task_complete events reach clients. Bug 6 (MODERATE): Hollow entity context — both slash-interceptor and task-routes built stub entity objects with empty workspacePath. Fix: load entity.json from disk via entityPaths to get real name and workspacePath. Files changed: - server/brain/tasks/task-executor.js — accept external sessionId param - server/routes/slash-interceptor.js — entity profile loading, session ID pass-through, Frontman wiring - server/routes/task-routes.js — entity profile loading, Frontman wiring - server/services/chat-pipeline.js — expose taskFrontman in return value - server/server.js — add taskFrontman getter to ctx - client/js/apps/optional/task-ui.js — fix entity ID resolution Tests: 2012/2012 (0 fail)
1 parent c3a0602 commit 1e41c58

8 files changed

Lines changed: 99 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Built with MA (Memory Architect v1).
66
## [Unreleased]
77

88
### Added
9+
- Server-Side Slash Command Interceptor: slash commands now work from ALL clients (entity chat, NekoCore OS chat, failsafe console) — new `project/server/routes/slash-interceptor.js` intercepts `/command` messages at the server level before they reach the LLM pipeline; supported commands: `/task <description>` (run generic task), `/project <description>` (run multi-phase project), `/skill <description>` (run skill-based task), `/websearch <query>` (web research), `/list` (task history), `/listactive` (running tasks), `/stop` (cancel current session); wired into `/api/chat` (entity chat) and `/api/nekocore/chat` (NekoCore OS chat) with fire-and-forget async execution for long-running tasks; registered in CORE_REGISTRY (301 entries); eliminates the need for separate client-side slash parsers — single server-side implementation serves all frontends.
910
- Self-Repair Skill: teaches NekoCore how to diagnose and fix her own system — new `project/skills/self-repair/SKILL.md` provides complete self-healing knowledge: health scanner usage (`node scripts/health-scan.js` with `--json` and `--fix-list` modes via `cmd_run` tool), fixer generator usage (`node scripts/generate-fixer.js` to produce `neko_fixer.py`), all fixer modes (`--repair` for missing/empty files, `--force` for full DNA restore, `--verify` for hash check, `--list` for inventory), failsafe console location (`/failsafe.html`), CORE_REGISTRY as source of truth (300 entries), step-by-step diagnosis workflow (scan → categorize → repair strategy → verify), UI-broken recovery flow (direct user to failsafe), headless server recovery (SSH → fixer → start server → Telegram/failsafe chat), key file locations table, complete "fix yourself" sequence, and safety guardrails (what NOT to do); closes the self-healing loop: `neko_fixer.py --repair` → `failsafe.html` → "Neko, fix yourself" → she runs the health scanner and repairs what's broken.
1011
- Command Execution Tool (`cmd_run`): sandboxed shell execution for entities — new `project/server/integrations/cmd-executor.js` provides a secure command executor with a 20+ command whitelist (cargo, rustc, rustfmt, python, python3, pip, pip3, node, npm, npx, gcc, g++, make, cmake, go, git, cat, head, tail, wc, ls, dir, find, grep, type) each with restricted subcommand lists; BLOCKED_PATTERNS reject shell metacharacters (;&|`$), directory traversal (../), redirects, destructive commands (rm/del/format/shutdown/kill), and shell escape commands (curl/wget/powershell/cmd.exe/bash); `parseCommand()` tokenizer validates against whitelist + subcommands + blocked patterns; `execCommand()` uses `child_process.spawn` with `shell: false`, workspace jail (cwd locked to entity workspace), timeout enforcement (default 60s, max 300s), output truncation (16KB), and CI env vars (CI=true, NO_COLOR=1); wired through full pipeline: `chat-pipeline.js` → `task-pipeline-bridge.js` → `task-runner.js` → `workspace-tools.js`; added to CODE and PROJECT task types; `formatToolResults` outputs STDOUT/STDERR blocks with exit code and timeout indicator; 70 guard tests in `cmd-run-guards.test.js` covering whitelist validation, security rejection (17 attack vectors), exec validation, dispatch, task-type integration, skill structure, pipeline wiring, and CORE_REGISTRY coverage; enables entities to compile Rust, run Python scripts, execute Node.js, build C/C++, and use Git — all within a sandboxed workspace.
1112
- Rust Skill: comprehensive Rust programming skill for entities — new `project/skills/rust/SKILL.md` (~300 lines) provides cargo project lifecycle (build/run/test/check/clippy/fmt/init/new/add), file + cmd_run tool syntax, critical rules (complete files, read-before-edit, use cargo, read compiler errors), Rust patterns (structs, enums, traits, Result/?, iterators, collections), Cargo.toml patterns, common crates table (15 entries: serde, tokio, clap, reqwest, anyhow, thiserror, rand, regex, chrono, log, tracing, axum, sqlx, rayon, uuid), debugging workflow with error code table (E0382/E0308/E0502/E0433/E0599), testing patterns (unit + integration + doc tests), project scaffolding templates (CLI, library, web API), and explicit "what NOT to do" section.

WORKLOG.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ Emergency exception log:
4444

4545
---
4646

47-
## Stop/Resume Snapshot — 2026-03-21 (Self-Repair Skill COMPLETE)
47+
## Stop/Resume Snapshot — 2026-03-21 (Server-Side Slash Interceptor COMPLETE)
4848

4949
- **Current phase:** `Phase 4 — Feature work`
5050
- **Current slice:** `None — awaiting next plan`
51-
- **Last completed work:** `Self-repair skill (skills/self-repair/SKILL.mdteaches Neko how to diagnose, scan, and fix her own system); CORE_REGISTRY now 300 entries; 14 new guard tests; 2012/2012 (0 fail)`
51+
- **Last completed work:** `Server-side slash command interceptor (server/routes/slash-interceptor.js)intercepts /command messages before LLM pipeline so slash commands work from all clients (entity chat, NekoCore OS, failsafe); wired into /api/chat and /api/nekocore/chat; CORE_REGISTRY now 301 entries; 2012/2012 (0 fail)`
5252
- **In-progress item:** `none`
5353
- **Blocking issue:** `none`
5454
- **Next action on resume:** `Pick up next plan or user request`
@@ -76,6 +76,22 @@ Boundary markers: [BOUNDARY_OK]
7676

7777
---
7878

79+
## Session Ledger — 2026-03-21 (Server-Side Slash Interceptor)
80+
81+
Status: `Complete`
82+
83+
- **Purpose:** Make slash commands work from all clients (entity chat, NekoCore OS chat, failsafe console) by intercepting `/command` messages at the server level before the LLM pipeline.
84+
- Created `server/routes/slash-interceptor.js` (~210 lines) — server-side slash command detector and dispatcher: regex parser for `/command arg` syntax, handles `/task`, `/project`, `/skill`, `/websearch` (fire-and-forget async via task system), `/list` (task history), `/listactive` (running tasks), `/stop` (cancel active session); returns `{ handled, response }` so callers skip LLM when a slash command is detected
85+
- Wired into `server/routes/chat-routes.js` (`/api/chat`) — intercept call before LLM pipeline using `ctx.getActiveEntityId()` for entity resolution
86+
- Wired into `server/routes/nekocore-routes.js` (`/api/nekocore/chat`) — intercept call with hardcoded `'nekocore'` entity ID
87+
- Registered in CORE_REGISTRY — registry now 301 entries
88+
- Tests: 2012/2012 full suite (0 fail)
89+
- Pushed to `origin staging` (commit c3a0602)
90+
91+
Boundary markers: [BOUNDARY_OK]
92+
93+
---
94+
7995
## Session Ledger — 2026-03-21 (BIOS Completeness + Failsafe Console)
8096

8197
Status: `Complete`

project/client/js/apps/optional/task-ui.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,11 @@ function _taskUIPushTelemetry(eventName, detail) {
162162

163163
// ─── Task History Panel ───────────────────────────────────────────────────────
164164
async function openTaskHistory() {
165-
// Resolve entityId from global config
165+
// Resolve entityId from the global entity manager
166166
let entityId = null;
167167
try {
168-
if (typeof activeConfig !== 'undefined' && activeConfig && activeConfig.entityId) {
169-
entityId = activeConfig.entityId;
168+
if (typeof window.getActiveEntityId === 'function') {
169+
entityId = window.getActiveEntityId();
170170
}
171171
} catch (_) {}
172172

project/server/brain/tasks/task-executor.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const _pendingInputs = new Map();
2525
* - allTools {Object} — all available tools { toolName: handler }
2626
* - taskArchiveId {string?} — archive ID for step writes (optional)
2727
* - archiveWriter {Object?} — task-archive-writer instance (optional)
28+
* - sessionId {string?} — external session ID (callers pass their own to keep event subscriptions aligned)
2829
* - _runTaskFn {Function?} — override for testing; defaults to task-runner.runTask
2930
* @returns {Promise<Object>} { sessionId, steps, finalOutput, taskType, entityId, completedAt }
3031
*/
@@ -39,6 +40,7 @@ async function executeTask(config) {
3940
allTools = {},
4041
taskArchiveId = null,
4142
archiveWriter = null,
43+
sessionId: externalSessionId = null,
4244
_runTaskFn = null
4345
} = config;
4446

@@ -50,10 +52,12 @@ async function executeTask(config) {
5052
throw new Error('executeTask: callLLM must be a function');
5153
}
5254

53-
// Generate unique session ID
54-
const sessionId = typeof crypto.randomUUID === 'function'
55-
? crypto.randomUUID()
56-
: `task-${Date.now()}-${Math.random().toString(36).slice(2)}`;
55+
// Use caller-supplied session ID when available, otherwise generate one
56+
const sessionId = externalSessionId || (
57+
typeof crypto.randomUUID === 'function'
58+
? crypto.randomUUID()
59+
: `task-${Date.now()}-${Math.random().toString(36).slice(2)}`
60+
);
5761

5862
// Get module config for this task type
5963
const moduleConfig = taskModuleRegistry.getModule(taskType);

project/server/routes/slash-interceptor.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ const projectStore = require('../brain/tasks/task-project-store');
2222
const archiveWriter = require('../brain/tasks/task-archive-writer');
2323
const workspaceTools = require('../brain/workspace-tools');
2424

25+
function _loadEntityProfile(entityId) {
26+
const fs = require('fs');
27+
const path = require('path');
28+
try {
29+
const entityPaths = require('../entityPaths');
30+
const entityFile = path.join(entityPaths.getEntityRoot(entityId), 'entity.json');
31+
if (fs.existsSync(entityFile)) {
32+
return JSON.parse(fs.readFileSync(entityFile, 'utf8'));
33+
}
34+
} catch (_) {}
35+
return {};
36+
}
37+
2538
function _readAllSessionsForHistory() {
2639
const fs = require('fs');
2740
const path = require('path');
@@ -162,10 +175,21 @@ async function _runTask(userMessage, entityId, ctx, taskType, skill) {
162175
sharedContext: { taskContext: context }
163176
}, {});
164177

178+
// Resolve entity profile from disk for workspace path and identity
179+
const entityProfile = _loadEntityProfile(entityId);
180+
165181
const runConfig = {
182+
sessionId: session.id,
166183
taskType: classification.taskType,
167184
userMessage,
168-
entity: { id: entityId, name: 'Entity', persona: null, mood: null, relationship: null, workspacePath: '' },
185+
entity: {
186+
id: entityId,
187+
name: entityProfile.name || 'Entity',
188+
persona: entityProfile.personality_traits || null,
189+
mood: null,
190+
relationship: null,
191+
workspacePath: entityProfile.workspacePath || ''
192+
},
169193
contextSnippets: context.snippets || [],
170194
callLLM: ctx.callLLMWithRuntime,
171195
runtime: {},
@@ -175,6 +199,17 @@ async function _runTask(userMessage, entityId, ctx, taskType, skill) {
175199
...(skill ? { skill } : {})
176200
};
177201

202+
// Wire Frontman for SSE feedback if available on ctx
203+
if (ctx.taskFrontman && typeof ctx.taskFrontman.startSession === 'function') {
204+
ctx.taskFrontman.startSession({
205+
sessionId: session.id,
206+
entityId,
207+
entity: runConfig.entity,
208+
relationshipSignal: 'neutral',
209+
runtime: {}
210+
});
211+
}
212+
178213
// Subscribe to task events for session tracking
179214
const handler = (event) => {
180215
if (!event || !event.type) return;

project/server/routes/task-routes.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ function createTaskRoutes(ctx) {
2424
return (body && body.entityId) || (ctx.getActiveEntityId ? ctx.getActiveEntityId() : null);
2525
}
2626

27+
function _loadEntityProfile(entityId) {
28+
const fs = require('fs');
29+
const path = require('path');
30+
try {
31+
const entityPaths = require('../entityPaths');
32+
const entityFile = path.join(entityPaths.getEntityRoot(entityId), 'entity.json');
33+
if (fs.existsSync(entityFile)) {
34+
return JSON.parse(fs.readFileSync(entityFile, 'utf8'));
35+
}
36+
} catch (_) {}
37+
return {};
38+
}
39+
2740
function _readAllSessionsForHistory() {
2841
const fs = require('fs');
2942
const path = require('path');
@@ -157,16 +170,20 @@ function createTaskRoutes(ctx) {
157170
}
158171
}, _sessionOpts());
159172

173+
// Resolve entity profile from disk for workspace path and identity
174+
const entityProfile = _loadEntityProfile(entityId);
175+
160176
const runConfig = {
177+
sessionId: session.id,
161178
taskType: classification.taskType,
162179
userMessage,
163180
entity: {
164181
id: entityId,
165-
name: body.entityName || 'Entity',
166-
persona: body.persona || null,
182+
name: body.entityName || entityProfile.name || 'Entity',
183+
persona: body.persona || entityProfile.persona || null,
167184
mood: body.mood || null,
168185
relationship: body.relationship || null,
169-
workspacePath: body.workspacePath || ''
186+
workspacePath: body.workspacePath || entityProfile.workspacePath || ''
170187
},
171188
contextSnippets: context.snippets || [],
172189
callLLM: ctx.callLLMWithRuntime,
@@ -181,6 +198,17 @@ function createTaskRoutes(ctx) {
181198

182199
const unsubscribe = _attachEventSync(session.id);
183200

201+
// Wire Frontman for SSE feedback if available on ctx
202+
if (ctx.taskFrontman && typeof ctx.taskFrontman.startSession === 'function') {
203+
ctx.taskFrontman.startSession({
204+
sessionId: session.id,
205+
entityId,
206+
entity: runConfig.entity,
207+
relationshipSignal: 'neutral',
208+
runtime: body.runtime || {}
209+
});
210+
}
211+
184212
const asyncMode = body.async === true;
185213
if (asyncMode) {
186214
setImmediate(async () => {

project/server/server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ const ctx = {
690690
gracefulShutdown(src) { return gracefulShutdown(src); },
691691
webFetch,
692692
authService,
693+
get taskFrontman() { return chatPipeline.taskFrontman; },
693694
};
694695

695696
const lifecycle = createRuntimeLifecycle({

project/server/services/chat-pipeline.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ function createChatPipeline(deps) {
11201120
return { finalResponse, innerDialog: null, memoryConnections: [] };
11211121
}
11221122

1123-
return { processChatMessage, processSingleLlmChatMessage, processPendingSkillApproval };
1123+
return { processChatMessage, processSingleLlmChatMessage, processPendingSkillApproval, taskFrontman };
11241124
}
11251125

11261126
module.exports = createChatPipeline;

0 commit comments

Comments
 (0)