chore(dist): compile PRs #60/#61 into soul.c
- PR #60: inject operator home dir into system prompt (#30) Adds OPERATOR IDENTITY section so the LLM correctly resolves 'my files/notes/desktop' to the actual running user's $HOME. Prevents identity confusion between imprint author and operator. - PR #61: plan-mode endpoint POST /api/chat {mode:'plan'} (#27) Adds handle_chat_plan — returns {steps:[{id,title,detail}]} JSON. Wired into all three /api/chat route handlers. Grounds the plan via engram_compile (same as agentic path) for context awareness. dist changes: - soul.c: both PRs compiled in; build_system_prompt updated to 2-param signature (ctx, chat_mode); handle_chat_plan added - chat.c/routes.c/chat.elh: individual module outputs updated - elp-c-decls.h: remove stale 1-param build_system_prompt decl, add handle_chat_plan declaration - soul.elh.c: new soul header declarations file (from PR #60) Compile verified: cc -O2 -DHAVE_CURL soul.c el_runtime.c -lcurl Binary: 805K arm64, smoke test passes (port in use = expected).
This commit is contained in:
@@ -608,6 +608,22 @@ fn json_safe(s: String) -> String {
|
||||
// Issue #8 fix: engram_block at END of system prompt for strongest recency bias.
|
||||
// Issue #10 fix: STABLE IDENTITY vs RETRIEVED MEMORY section labels.
|
||||
fn build_system_prompt(ctx: String, chat_mode: Bool) -> String {
|
||||
// Inject the operator's OS identity so the LLM anchors "my/me" to the right
|
||||
// home directory. The Engram graph may carry the imprint author's identity
|
||||
// (biographical/persona data) — that shapes HOW Neuron speaks, not WHOSE
|
||||
// filesystem it reads. The operator is whoever is running this daemon process.
|
||||
let op_home: String = env("HOME")
|
||||
let op_user: String = env("USER")
|
||||
let op_display: String = if str_eq(op_user, "") { "the current user" } else { op_user }
|
||||
let operator_section: String = "OPERATOR IDENTITY\n\n"
|
||||
+ "You are running on " + op_display + "'s machine. Their home directory is " + op_home + ".\n\n"
|
||||
+ "When they say \"my files\", \"my notes\", \"my downloads\", \"my desktop\", or any possessive "
|
||||
+ "referring to their filesystem, always resolve those paths under " + op_home + " — never under "
|
||||
+ "a different user's home directory. This is a hard rule.\n\n"
|
||||
+ "The memory graph may include identity context from a different person (the imprint who shaped your personality and values). "
|
||||
+ "That context governs how you think and speak — it does not tell you whose machine you are on. "
|
||||
+ "The person speaking to you right now is " + op_display + " at " + op_home + ".\n\n"
|
||||
|
||||
let identity: String = state_get("soul_identity")
|
||||
let current_date: String = time_format(time_now(), "%A, %B %d, %Y")
|
||||
let date_line: String = "\n\nCurrent date: " + current_date
|
||||
@@ -673,7 +689,7 @@ fn build_system_prompt(ctx: String, chat_mode: Bool) -> String {
|
||||
safety_addendum
|
||||
}
|
||||
|
||||
return identity + date_line + voice_rules + security_rules + capability_rules + identity_block + affective_boot_block + engram_block + safety_block
|
||||
return identity + operator_section + date_line + voice_rules + security_rules + capability_rules + identity_block + affective_boot_block + engram_block + safety_block
|
||||
}
|
||||
|
||||
fn hist_append(hist: String, role: String, content: String) -> String {
|
||||
@@ -1573,6 +1589,55 @@ fn next_bridge_id() -> String {
|
||||
return "br-" + uid
|
||||
}
|
||||
|
||||
fn handle_chat_plan(body: String) -> String {
|
||||
let message: String = json_get(body, "message")
|
||||
if str_eq(message, "") {
|
||||
return "{\"error\":\"message required\",\"plan\":null}"
|
||||
}
|
||||
|
||||
let req_model: String = json_get(body, "model")
|
||||
let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model }
|
||||
|
||||
let op_home: String = env("HOME")
|
||||
let op_user: String = env("USER")
|
||||
let op_display: String = if str_eq(op_user, "") { "the current user" } else { op_user }
|
||||
|
||||
// Compile context — same intent-seeding as agentic path so the plan is grounded.
|
||||
let ctx: String = engram_compile(message)
|
||||
let ctx_block: String = if str_eq(ctx, "") { "" } else { "\n\n[CONTEXT]\n" + ctx }
|
||||
|
||||
let plan_system: String = "You are in PLAN MODE. Your job is to produce a concise step-by-step plan for the request below — WITHOUT executing it.\n\nReturn ONLY a JSON object. No markdown. No preamble. No explanation. Just the JSON:\n{\"steps\":[{\"id\":\"s1\",\"title\":\"<2-6 word title>\",\"detail\":\"<one concrete sentence>\"},{\"id\":\"s2\",...}]}\n\nPlan rules:\n- 3-7 steps (more only when genuinely needed for a complex multi-file task)\n- Each step is one atomic, independently verifiable action\n- title: 2-6 words, imperative (e.g. \"Read config file\", \"Write updated handler\")\n- detail: exactly one sentence describing what happens\n- No tool calls. No execution. No side effects. The user approves before anything runs.\n\nOperator: " + op_display + " at " + op_home + ctx_block
|
||||
|
||||
let raw: String = llm_call_system(model, plan_system, message)
|
||||
|
||||
let is_error: Bool = str_starts_with(raw, "{\"error\"")
|
||||
if is_error {
|
||||
return "{\"error\":\"plan generation failed\",\"plan\":null,\"detail\":" + raw + "}"
|
||||
}
|
||||
|
||||
// Extract the JSON object from the response (LLM sometimes wraps in markdown).
|
||||
let brace_start: Int = str_index_of(raw, "{")
|
||||
// Scan backwards to find the last closing brace (str_last_index_of not available).
|
||||
let brace_end: Int = -1
|
||||
let scan_i: Int = str_len(raw) - 1
|
||||
while scan_i >= 0 {
|
||||
let ch: String = str_slice(raw, scan_i, scan_i + 1)
|
||||
let brace_end = if str_eq(ch, "}") && brace_end < 0 { scan_i } else { brace_end }
|
||||
let scan_i = if brace_end >= 0 { -1 } else { scan_i - 1 }
|
||||
}
|
||||
let plan_json: String = if brace_start >= 0 {
|
||||
if brace_end > brace_start {
|
||||
str_slice(raw, brace_start, brace_end + 1)
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
|
||||
return "{\"plan\":" + plan_json + ",\"model\":\"" + json_safe(model) + "\"}"
|
||||
}
|
||||
|
||||
fn handle_chat_agentic(body: String) -> String {
|
||||
let message: String = json_get(body, "message")
|
||||
if str_eq(message, "") {
|
||||
|
||||
Reference in New Issue
Block a user