Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/ardumine_hagent.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
37 KiB
JSON

{"repo": "ardumine/hagent", "files": {"Agent/AgentImplement.cs": {"69318a38b4ea4feee28f5edd009b14f03c072610": {"t": 1785745802.3753653, "n": 15186, "h": "4bdc207642d17c49", "c": "using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing HCore.Modules.Base;\n\nnamespace HCore.Packages.Agent;\n\n/// <summary>\n/// An interactive agent app. Launched from the shell as <c>agent</c> (optionally\n/// with an initial prompt), it takes over the terminal and runs a chat REPL:\n/// you type, it thinks and uses tools (VFS + shell), it answers, and the\n/// conversation history persists across turns so you can reply to its questions.\n/// Type <c>/exit</c> to quit and return to the shell.\n/// </summary>\npublic class AgentImplement : BaseImplement, IAgent, IOneshotCommand\n{\n private const string ApiKeyPath = \"/etc/agent/apikey\";\n private const string ConfigPath = \"/etc/agent/config.json\";\n private const int MaxSteps = 12;\n\n private string[] _args = [];\n\n public void SetArguments(string[] args) => _args = args;\n\n public void Run()\n {\n string apiKey;\n try\n {\n apiKey = LoadApiKey();\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"agent: {ex.Message}\");\n return;\n }\n\n var (model, baseUrl) = LoadConfig();\n var client = new DeepseekClient(apiKey, model, baseUrl);\n var tools = new AgentTools(Vfs, Host, ShellPath());\n var store = new SessionStore(Vfs);\n\n // Parse launch args: `agent [-s|--session <name>] [initial prompt...]`.\n var (sessionArg, promptArg) = ParseLaunchArgs(_args);\n\n var messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n string sessionName;\n if (sessionArg is not null && store.Exists(sessionArg))\n {\n sessionName = SessionStore.Sanitize(sessionArg);\n var restored = store.Load(sessionName);\n if (restored is not null)\n {\n foreach (var m in restored) messages.Add(m?.DeepClone());\n Console.WriteLine($\"(restored session '{sessionName}' — {restored.Count} messages)\");\n }\n }\n else\n {\n sessionName = sessionArg is not null ? SessionStore.Sanitize(sessionArg) : SessionStore.NewName();\n }\n\n Console.WriteLine($\"HCore Agent ({model}). Session: {sessionName}. Type your message.\");\n Console.WriteLine(\"Commands: /exit, /reset, /model <name>, /sessions, /save [name], /load <name>, /new [name], /help.\");\n Logger.I($\"agent: interactive session started, model={model}, session={sessionName}\");\n\n // Optional initial prompt passed on the command line: `agent do something`.\n var pending = string.IsNullOrWhiteSpace(promptArg) ? null : promptArg;\n\n while (true)\n {\n // Honor our own kill signal: if this instance was reaped (e.g. someone\n // ran `kill` on us), the kernel cancelled StopToken — end the session\n // instead of lingering as a zombie on the shell's thread.\n if (StopToken.IsCancellationRequested)\n {\n Console.WriteLine(\"agent: this instance was killed — ending session.\");\n break;\n }\n\n string? line;\n if (!string.IsNullOrEmpty(pending))\n {\n line = pending;\n pending = null;\n Console.WriteLine($\"you> {line}\");\n }\n else\n {\n Console.Write(\"you> \");\n line = Console.ReadLine();\n }\n\n if (line is null) break; // EOF / Ctrl-D\n line = line.Trim();\n if (line.Length == 0) continue;\n\n if (line is \"/exit\" or \"/quit\") break;\n if (line == \"/help\")\n {\n Console.WriteLine(\n \"Commands: /exit quit · /reset clear conversation · /model <name> switch model ·\\n\" +\n \" /sessions list saved · /save [name] save now · /load <name> restore ·\\n\" +\n \" /new [name] start fresh session · /help this help.\");\n continue;\n }\n if (line == \"/reset\")\n {\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n Console.WriteLine(\"(conversation reset)\");\n continue;\n }\n if (line == \"/sessions\")\n {\n var names = store.List();\n if (names.Count == 0) Console.WriteLine(\"(no saved sessions)\");\n else foreach (var n in names)\n Console.WriteLine($\" {(n == sessionName ? \"*\" : \" \")} {n} ({store.Count(n)} messages)\");\n continue;\n }\n if (line == \"/save\" || line.StartsWith(\"/save \"))\n {\n var given = line.Length > 5 ? line[5..].Trim() : \"\";\n if (given.Length > 0) sessionName = SessionStore.Sanitize(given);\n store.Save(sessionName, model, messages);\n Console.WriteLine($\"(saved session '{sessionName}')\");\n continue;\n }\n if (line == \"/load\" || line.StartsWith(\"/load \"))\n {\n var given = line.Length > 5 ? line[5..].Trim() : \"\";\n if (given.Length == 0) { Console.WriteLine(\"usage: /load <name>\"); continue; }\n var restored = store.Load(given);\n if (restored is null) { Console.WriteLine($\"(no session '{given}')\"); continue; }\n sessionName = SessionStore.Sanitize(given);\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n foreach (var m in restored) messages.Add(m?.DeepClone());\n Console.WriteLine($\"(loaded session '{sessionName}' — {restored.Count} messages)\");\n continue;\n }\n if (line == \"/new\" || line.StartsWith(\"/new \"))\n {\n var given = line.Length > 4 ? line[4..].Trim() : \"\";\n sessionName = given.Length > 0 ? SessionStore.Sanitize(given) : SessionStore.NewName();\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n Console.WriteLine($\"(new session '{sessionName}')\");\n continue;\n }\n if (line == \"/model\" || line.StartsWith(\"/model \"))\n {\n var requested = line.Length > 6 ? line[6..].Trim() : \"\";\n if (requested.Length == 0) { Console.WriteLine($\"model: {model}\"); continue; }\n model = requested;\n SaveModel(model, baseUrl);\n client = new DeepseekClient(apiKey, model, baseUrl);\n Console.WriteLine($\"(model set to {model})\");\n continue;\n }\n\n // Hot-reload config each turn so edits to /etc/agent/config.json\n // (by you, or by the agent itself via a tool) take effect live.\n var (curModel, curBase) = LoadConfig();\n if (curModel != model || curBase != baseUrl)\n {\n model = curModel;\n baseUrl = curBase;\n client = new DeepseekClient(apiKey, model, baseUrl);\n Console.WriteLine($\"(config reloaded: model={model})\");\n }\n\n messages.Add(Msg(\"user\", line));\n if (!RunTurn(client, tools, messages))\n break; // fatal API error already reported\n\n // Auto-persist the conversation so it survives crashes / kills.\n try { store.Save(sessionName, model, messages); }\n catch (Exception ex) { Logger.W($\"agent: session save failed: {ex.Message}\"); }\n }\n\n Console.WriteLine($\"agent: session '{sessionName}' ended.\");\n Logger.I($\"agent: interactive session '{sessionName}' ended.\");\n }\n\n /// <summary>\n /// Drive one user turn: call the model, execute any tool calls, loop until it\n /// produces a plain-text answer (or the step budget runs out). Returns false\n /// only on a fatal API error (caller should stop the session).\n /// </summary>\n private bool RunTurn(DeepseekClient client, AgentTools tools, JsonArray messages)\n {\n for (var step = 0; step < MaxSteps; step++)\n {\n JsonNode message;\n try\n {\n message = client.Chat(messages, AgentTools.Schema);\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"agent: request failed: {ex.Message}\");\n Logger.E($\"agent request failed: {ex.Message}\");\n return false;\n }\n\n messages.Add(message.DeepClone());\n\n var toolCalls = message[\"tool_calls\"] as JsonArray;\n if (toolCalls is { Count: > 0 })\n {\n foreach (var call in toolCalls)\n {\n if (call is null) continue;\n var id = call[\"id\"]?.GetValue<string>() ?? \"\";\n var fn = call[\"function\"];\n var name = fn?[\"name\"]?.GetValue<string>() ?? \"\";\n var argsRaw = fn?[\"arguments\"]?.GetValue<string>() ?? \"{}\";\n\n var args = ParseArgs(argsRaw);\n Console.WriteLine($\" [tool] {name}({Compact(argsRaw)})\");\n var result = tools.Invoke(name, args);\n Logger.I($\"agent tool {name} -> {result.Length} chars\");\n\n messages.Add(new JsonObject\n {\n [\"role\"] = \"tool\",\n [\"tool_call_id\"] = id,\n [\"content\"] = result,\n });\n }\n continue;\n }\n\n var content = message[\"content\"]?.GetValue<string>() ?? \"\";\n Console.WriteLine($\"agent> {content.Trim()}\");\n return true;\n }\n\n Console.WriteLine($\"agent: stopped after {MaxSteps} steps without a final answer.\");\n return true;\n }\n\n private string ShellPath()\n {\n var name = InstanceName;\n var slash = name.LastIndexOf('/');\n return slash > 0 ? name[..slash] : \"init/console\";\n }\n\n private string LoadApiKey()\n {\n if (Vfs.Exists(ApiKeyPath))\n {\n var key = Vfs.ReadAllText(ApiKeyPath).Trim();\n if (key.Length > 0) return key;\n }\n\n var env = Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\");\n if (!string.IsNullOrWhiteSpace(env)) return env.Trim();\n\n throw new InvalidOperationException(\n $\"no API key. Put it in {ApiKeyPath} or set DEEPSEEK_API_KEY.\");\n }\n\n private (string model, string baseUrl) LoadConfig()\n {\n var model = \"deepseek-chat\";\n var baseUrl = \"https://api.deepseek.com\";\n try\n {\n if (Vfs.Exists(ConfigPath))\n {\n var doc = JsonNode.Parse(Vfs.ReadAllText(ConfigPath));\n model = doc?[\"model\"]?.GetValue<string>() ?? model;\n baseUrl = doc?[\"baseUrl\"]?.GetValue<string>() ?? baseUrl;\n }\n }\n catch { /* fall back to defaults */ }\n return (model, baseUrl);\n }\n\n private void SaveModel(string model, string baseUrl)\n {\n try\n {\n var cfg = new JsonObject { [\"model\"] = model, [\"baseUrl\"] = baseUrl };\n Vfs.WriteAllText(ConfigPath, cfg.ToJsonString());\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"(warning: could not persist config: {ex.Message})\");\n }\n }\n\n private string SystemPrompt()\n {\n return\n \"You are an autonomous agent running inside HCore, a microkernel with a \" +\n \"virtual filesystem (VFS). You help the user by reasoning and using tools.\\n\" +\n \"\\n\" +\n \"Environment:\\n\" +\n $\"- Your instance name: {InstanceName}\\n\" +\n $\"- Current working directory: {Vfs.WorkingDirectory}\\n\" +\n \"- The VFS layout: /packs (installed packages), /proc (running module \" +\n \"instances, read-only), /etc (config + services), /home, /data.\\n\" +\n \"\\n\" +\n \"Tools: vfs_list(path), vfs_read(path), vfs_write(path, content, append?), \" +\n \"shell(command), pkg_build(name, source). Use vfs_* for direct file access; use \" +\n \"shell to run HShell command lines and interact with modules (ls, cat, spawn, run, \" +\n \"kill, service, hpm, ...). Use pkg_build to CREATE, COMPILE and HOT-LOAD a brand-new \" +\n \"HCore package from C# source on the fly, then 'spawn'/'run' it via shell — a full \" +\n \"write→compile→(read errors)→fix→run→debug loop, all without a reboot. \" +\n \"All paths are VFS paths. Use tools to inspect or act on the system \" +\n \"before answering. When you have the answer, reply in plain text with no tool \" +\n \"call. Be concise.\\n\" +\n \"\\n\" +\n \"This is an ongoing, multi-turn conversation with the user at a terminal. \" +\n \"If a request is ambiguous or you need more information, ask the user a \" +\n \"clarifying question in plain text (no tool call) and wait for their reply.\\n\" +\n \"\\n\" +\n \"About yourself: you run as a one-shot interactive command spawned by the \" +\n \"shell (instance under init/console), NOT as a registered service. So \" +\n $\"'service stop {InstanceName}' or 'service start agent' will NOT affect this \" +\n \"session — there is no 'agent' service. Your model/endpoint come from \" +\n \"/etc/agent/config.json and are hot-reloaded every turn: if you (or the user) \" +\n \"change 'model' there, it takes effect on the next message automatically — no \" +\n \"restart needed. The user can also switch models with the /model command.\";\n }\n\n private static JsonObject Msg(string role, string content)\n => new() { [\"role\"] = role, [\"content\"] = content };\n\n /// <summary>\n /// Split argv (argv[0] == command name) into an optional session name\n /// (<c>-s</c>/<c>--session &lt;name&gt;</c>) and the remaining initial prompt.\n /// </summary>\n private static (string? session, string? prompt) ParseLaunchArgs(string[] args)\n {\n string? session = null;\n var rest = new List<string>();\n for (var i = 1; i < args.Length; i++)\n {\n if ((args[i] is \"-s\" or \"--session\") && i + 1 < args.Length)\n {\n session = args[++i];\n }\n else\n {\n rest.Add(args[i]);\n }\n }\n var prompt = rest.Count > 0 ? string.Join(' ', rest).Trim() : null;\n return (session, prompt);\n }\n\n private static JsonElement ParseArgs(string raw)\n {\n try\n {\n return JsonDocument.Parse(string.IsNullOrWhiteSpace(raw) ? \"{}\" : raw).RootElement.Clone();\n }\n catch\n {\n return JsonDocument.Parse(\"{}\").RootElement.Clone();\n }\n }\n\n private static string Compact(string s)\n {\n s = s.Replace(\"\\n\", \" \").Replace(\"\\r\", \" \");\n return s.Length > 120 ? s[..120] + \"...\" : s;\n }\n}\n"}, "4dc73b9e7b24ff410e37dce28a9538b5d64450d5": {"t": 1785756662.139189, "n": 15186, "h": "4bdc207642d17c49", "c": "using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing HCore.Modules.Base;\n\nnamespace HCore.Packages.Agent;\n\n/// <summary>\n/// An interactive agent app. Launched from the shell as <c>agent</c> (optionally\n/// with an initial prompt), it takes over the terminal and runs a chat REPL:\n/// you type, it thinks and uses tools (VFS + shell), it answers, and the\n/// conversation history persists across turns so you can reply to its questions.\n/// Type <c>/exit</c> to quit and return to the shell.\n/// </summary>\npublic class AgentImplement : BaseImplement, IAgent, IOneshotCommand\n{\n private const string ApiKeyPath = \"/etc/agent/apikey\";\n private const string ConfigPath = \"/etc/agent/config.json\";\n private const int MaxSteps = 12;\n\n private string[] _args = [];\n\n public void SetArguments(string[] args) => _args = args;\n\n public void Run()\n {\n string apiKey;\n try\n {\n apiKey = LoadApiKey();\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"agent: {ex.Message}\");\n return;\n }\n\n var (model, baseUrl) = LoadConfig();\n var client = new DeepseekClient(apiKey, model, baseUrl);\n var tools = new AgentTools(Vfs, Host, ShellPath());\n var store = new SessionStore(Vfs);\n\n // Parse launch args: `agent [-s|--session <name>] [initial prompt...]`.\n var (sessionArg, promptArg) = ParseLaunchArgs(_args);\n\n var messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n string sessionName;\n if (sessionArg is not null && store.Exists(sessionArg))\n {\n sessionName = SessionStore.Sanitize(sessionArg);\n var restored = store.Load(sessionName);\n if (restored is not null)\n {\n foreach (var m in restored) messages.Add(m?.DeepClone());\n Console.WriteLine($\"(restored session '{sessionName}' — {restored.Count} messages)\");\n }\n }\n else\n {\n sessionName = sessionArg is not null ? SessionStore.Sanitize(sessionArg) : SessionStore.NewName();\n }\n\n Console.WriteLine($\"HCore Agent ({model}). Session: {sessionName}. Type your message.\");\n Console.WriteLine(\"Commands: /exit, /reset, /model <name>, /sessions, /save [name], /load <name>, /new [name], /help.\");\n Logger.I($\"agent: interactive session started, model={model}, session={sessionName}\");\n\n // Optional initial prompt passed on the command line: `agent do something`.\n var pending = string.IsNullOrWhiteSpace(promptArg) ? null : promptArg;\n\n while (true)\n {\n // Honor our own kill signal: if this instance was reaped (e.g. someone\n // ran `kill` on us), the kernel cancelled StopToken — end the session\n // instead of lingering as a zombie on the shell's thread.\n if (StopToken.IsCancellationRequested)\n {\n Console.WriteLine(\"agent: this instance was killed — ending session.\");\n break;\n }\n\n string? line;\n if (!string.IsNullOrEmpty(pending))\n {\n line = pending;\n pending = null;\n Console.WriteLine($\"you> {line}\");\n }\n else\n {\n Console.Write(\"you> \");\n line = Console.ReadLine();\n }\n\n if (line is null) break; // EOF / Ctrl-D\n line = line.Trim();\n if (line.Length == 0) continue;\n\n if (line is \"/exit\" or \"/quit\") break;\n if (line == \"/help\")\n {\n Console.WriteLine(\n \"Commands: /exit quit · /reset clear conversation · /model <name> switch model ·\\n\" +\n \" /sessions list saved · /save [name] save now · /load <name> restore ·\\n\" +\n \" /new [name] start fresh session · /help this help.\");\n continue;\n }\n if (line == \"/reset\")\n {\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n Console.WriteLine(\"(conversation reset)\");\n continue;\n }\n if (line == \"/sessions\")\n {\n var names = store.List();\n if (names.Count == 0) Console.WriteLine(\"(no saved sessions)\");\n else foreach (var n in names)\n Console.WriteLine($\" {(n == sessionName ? \"*\" : \" \")} {n} ({store.Count(n)} messages)\");\n continue;\n }\n if (line == \"/save\" || line.StartsWith(\"/save \"))\n {\n var given = line.Length > 5 ? line[5..].Trim() : \"\";\n if (given.Length > 0) sessionName = SessionStore.Sanitize(given);\n store.Save(sessionName, model, messages);\n Console.WriteLine($\"(saved session '{sessionName}')\");\n continue;\n }\n if (line == \"/load\" || line.StartsWith(\"/load \"))\n {\n var given = line.Length > 5 ? line[5..].Trim() : \"\";\n if (given.Length == 0) { Console.WriteLine(\"usage: /load <name>\"); continue; }\n var restored = store.Load(given);\n if (restored is null) { Console.WriteLine($\"(no session '{given}')\"); continue; }\n sessionName = SessionStore.Sanitize(given);\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n foreach (var m in restored) messages.Add(m?.DeepClone());\n Console.WriteLine($\"(loaded session '{sessionName}' — {restored.Count} messages)\");\n continue;\n }\n if (line == \"/new\" || line.StartsWith(\"/new \"))\n {\n var given = line.Length > 4 ? line[4..].Trim() : \"\";\n sessionName = given.Length > 0 ? SessionStore.Sanitize(given) : SessionStore.NewName();\n messages = new JsonArray { Msg(\"system\", SystemPrompt()) };\n Console.WriteLine($\"(new session '{sessionName}')\");\n continue;\n }\n if (line == \"/model\" || line.StartsWith(\"/model \"))\n {\n var requested = line.Length > 6 ? line[6..].Trim() : \"\";\n if (requested.Length == 0) { Console.WriteLine($\"model: {model}\"); continue; }\n model = requested;\n SaveModel(model, baseUrl);\n client = new DeepseekClient(apiKey, model, baseUrl);\n Console.WriteLine($\"(model set to {model})\");\n continue;\n }\n\n // Hot-reload config each turn so edits to /etc/agent/config.json\n // (by you, or by the agent itself via a tool) take effect live.\n var (curModel, curBase) = LoadConfig();\n if (curModel != model || curBase != baseUrl)\n {\n model = curModel;\n baseUrl = curBase;\n client = new DeepseekClient(apiKey, model, baseUrl);\n Console.WriteLine($\"(config reloaded: model={model})\");\n }\n\n messages.Add(Msg(\"user\", line));\n if (!RunTurn(client, tools, messages))\n break; // fatal API error already reported\n\n // Auto-persist the conversation so it survives crashes / kills.\n try { store.Save(sessionName, model, messages); }\n catch (Exception ex) { Logger.W($\"agent: session save failed: {ex.Message}\"); }\n }\n\n Console.WriteLine($\"agent: session '{sessionName}' ended.\");\n Logger.I($\"agent: interactive session '{sessionName}' ended.\");\n }\n\n /// <summary>\n /// Drive one user turn: call the model, execute any tool calls, loop until it\n /// produces a plain-text answer (or the step budget runs out). Returns false\n /// only on a fatal API error (caller should stop the session).\n /// </summary>\n private bool RunTurn(DeepseekClient client, AgentTools tools, JsonArray messages)\n {\n for (var step = 0; step < MaxSteps; step++)\n {\n JsonNode message;\n try\n {\n message = client.Chat(messages, AgentTools.Schema);\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"agent: request failed: {ex.Message}\");\n Logger.E($\"agent request failed: {ex.Message}\");\n return false;\n }\n\n messages.Add(message.DeepClone());\n\n var toolCalls = message[\"tool_calls\"] as JsonArray;\n if (toolCalls is { Count: > 0 })\n {\n foreach (var call in toolCalls)\n {\n if (call is null) continue;\n var id = call[\"id\"]?.GetValue<string>() ?? \"\";\n var fn = call[\"function\"];\n var name = fn?[\"name\"]?.GetValue<string>() ?? \"\";\n var argsRaw = fn?[\"arguments\"]?.GetValue<string>() ?? \"{}\";\n\n var args = ParseArgs(argsRaw);\n Console.WriteLine($\" [tool] {name}({Compact(argsRaw)})\");\n var result = tools.Invoke(name, args);\n Logger.I($\"agent tool {name} -> {result.Length} chars\");\n\n messages.Add(new JsonObject\n {\n [\"role\"] = \"tool\",\n [\"tool_call_id\"] = id,\n [\"content\"] = result,\n });\n }\n continue;\n }\n\n var content = message[\"content\"]?.GetValue<string>() ?? \"\";\n Console.WriteLine($\"agent> {content.Trim()}\");\n return true;\n }\n\n Console.WriteLine($\"agent: stopped after {MaxSteps} steps without a final answer.\");\n return true;\n }\n\n private string ShellPath()\n {\n var name = InstanceName;\n var slash = name.LastIndexOf('/');\n return slash > 0 ? name[..slash] : \"init/console\";\n }\n\n private string LoadApiKey()\n {\n if (Vfs.Exists(ApiKeyPath))\n {\n var key = Vfs.ReadAllText(ApiKeyPath).Trim();\n if (key.Length > 0) return key;\n }\n\n var env = Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\");\n if (!string.IsNullOrWhiteSpace(env)) return env.Trim();\n\n throw new InvalidOperationException(\n $\"no API key. Put it in {ApiKeyPath} or set DEEPSEEK_API_KEY.\");\n }\n\n private (string model, string baseUrl) LoadConfig()\n {\n var model = \"deepseek-chat\";\n var baseUrl = \"https://api.deepseek.com\";\n try\n {\n if (Vfs.Exists(ConfigPath))\n {\n var doc = JsonNode.Parse(Vfs.ReadAllText(ConfigPath));\n model = doc?[\"model\"]?.GetValue<string>() ?? model;\n baseUrl = doc?[\"baseUrl\"]?.GetValue<string>() ?? baseUrl;\n }\n }\n catch { /* fall back to defaults */ }\n return (model, baseUrl);\n }\n\n private void SaveModel(string model, string baseUrl)\n {\n try\n {\n var cfg = new JsonObject { [\"model\"] = model, [\"baseUrl\"] = baseUrl };\n Vfs.WriteAllText(ConfigPath, cfg.ToJsonString());\n }\n catch (Exception ex)\n {\n Console.WriteLine($\"(warning: could not persist config: {ex.Message})\");\n }\n }\n\n private string SystemPrompt()\n {\n return\n \"You are an autonomous agent running inside HCore, a microkernel with a \" +\n \"virtual filesystem (VFS). You help the user by reasoning and using tools.\\n\" +\n \"\\n\" +\n \"Environment:\\n\" +\n $\"- Your instance name: {InstanceName}\\n\" +\n $\"- Current working directory: {Vfs.WorkingDirectory}\\n\" +\n \"- The VFS layout: /packs (installed packages), /proc (running module \" +\n \"instances, read-only), /etc (config + services), /home, /data.\\n\" +\n \"\\n\" +\n \"Tools: vfs_list(path), vfs_read(path), vfs_write(path, content, append?), \" +\n \"shell(command), pkg_build(name, source). Use vfs_* for direct file access; use \" +\n \"shell to run HShell command lines and interact with modules (ls, cat, spawn, run, \" +\n \"kill, service, hpm, ...). Use pkg_build to CREATE, COMPILE and HOT-LOAD a brand-new \" +\n \"HCore package from C# source on the fly, then 'spawn'/'run' it via shell — a full \" +\n \"write→compile→(read errors)→fix→run→debug loop, all without a reboot. \" +\n \"All paths are VFS paths. Use tools to inspect or act on the system \" +\n \"before answering. When you have the answer, reply in plain text with no tool \" +\n \"call. Be concise.\\n\" +\n \"\\n\" +\n \"This is an ongoing, multi-turn conversation with the user at a terminal. \" +\n \"If a request is ambiguous or you need more information, ask the user a \" +\n \"clarifying question in plain text (no tool call) and wait for their reply.\\n\" +\n \"\\n\" +\n \"About yourself: you run as a one-shot interactive command spawned by the \" +\n \"shell (instance under init/console), NOT as a registered service. So \" +\n $\"'service stop {InstanceName}' or 'service start agent' will NOT affect this \" +\n \"session — there is no 'agent' service. Your model/endpoint come from \" +\n \"/etc/agent/config.json and are hot-reloaded every turn: if you (or the user) \" +\n \"change 'model' there, it takes effect on the next message automatically — no \" +\n \"restart needed. The user can also switch models with the /model command.\";\n }\n\n private static JsonObject Msg(string role, string content)\n => new() { [\"role\"] = role, [\"content\"] = content };\n\n /// <summary>\n /// Split argv (argv[0] == command name) into an optional session name\n /// (<c>-s</c>/<c>--session &lt;name&gt;</c>) and the remaining initial prompt.\n /// </summary>\n private static (string? session, string? prompt) ParseLaunchArgs(string[] args)\n {\n string? session = null;\n var rest = new List<string>();\n for (var i = 1; i < args.Length; i++)\n {\n if ((args[i] is \"-s\" or \"--session\") && i + 1 < args.Length)\n {\n session = args[++i];\n }\n else\n {\n rest.Add(args[i]);\n }\n }\n var prompt = rest.Count > 0 ? string.Join(' ', rest).Trim() : null;\n return (session, prompt);\n }\n\n private static JsonElement ParseArgs(string raw)\n {\n try\n {\n return JsonDocument.Parse(string.IsNullOrWhiteSpace(raw) ? \"{}\" : raw).RootElement.Clone();\n }\n catch\n {\n return JsonDocument.Parse(\"{}\").RootElement.Clone();\n }\n }\n\n private static string Compact(string s)\n {\n s = s.Replace(\"\\n\", \" \").Replace(\"\\r\", \" \");\n return s.Length > 120 ? s[..120] + \"...\" : s;\n }\n}\n"}}, "Agent/DeepseekClient.cs": {"69318a38b4ea4feee28f5edd009b14f03c072610": {"t": 1786068391.50368, "n": 2186, "h": "20d646560d7106c8", "c": "using System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\n\nnamespace HCore.Packages.Agent;\n\n/// <summary>\n/// Minimal client for the Deepseek OpenAI-compatible Chat Completions API.\n/// Endpoint: https://api.deepseek.com/chat/completions (OpenAI wire format).\n/// </summary>\npublic sealed class DeepseekClient\n{\n private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromMinutes(2) };\n\n private readonly string _apiKey;\n private readonly string _model;\n private readonly string _endpoint;\n\n public DeepseekClient(string apiKey, string model = \"deepseek-chat\",\n string baseUrl = \"https://api.deepseek.com\")\n {\n _apiKey = apiKey;\n _model = model;\n _endpoint = baseUrl.TrimEnd('/') + \"/chat/completions\";\n }\n\n /// <summary>\n /// Send the conversation + tool schema, return the assistant message node\n /// (contains \"content\" and/or \"tool_calls\").\n /// </summary>\n public JsonNode Chat(JsonArray messages, JsonElement tools)\n {\n var body = new JsonObject\n {\n [\"model\"] = _model,\n [\"messages\"] = messages.DeepClone(),\n [\"tools\"] = JsonNode.Parse(tools.GetRawText()),\n [\"tool_choice\"] = \"auto\",\n [\"temperature\"] = 0.2,\n };\n\n using var req = new HttpRequestMessage(HttpMethod.Post, _endpoint);\n req.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n req.Content = new StringContent(body.ToJsonString(), Encoding.UTF8, \"application/json\");\n\n using var resp = Http.Send(req);\n var payload = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();\n\n if (!resp.IsSuccessStatusCode)\n throw new InvalidOperationException($\"Deepseek API {(int)resp.StatusCode}: {payload}\");\n\n var root = JsonNode.Parse(payload)\n ?? throw new InvalidOperationException(\"Empty response from Deepseek API.\");\n\n var message = root[\"choices\"]?[0]?[\"message\"]\n ?? throw new InvalidOperationException($\"Malformed response: {payload}\");\n\n return message.DeepClone();\n }\n}\n"}}, "README.md": {"69318a38b4ea4feee28f5edd009b14f03c072610": {"t": 1786068580.1678717, "n": 3154, "h": "248e75e55cf79733", "c": "# hagent\n\nAn interactive LLM agent for [HCore](https://github.com/Ardumine/hcore) — a chat REPL you launch from the shell that can reason, use tools, read/write the HCore VFS, drive modules, and even **create, compile, and hot-load new HCore packages on the fly**. Uses the Deepseek OpenAI-compatible API.\n\n## Layout\n\nThis is a standalone HCore package repo, cloned **alongside** the kernel:\n\n```\nardumine/\n hcore/ # the kernel (https://github.com/Ardumine/hcore)\n hagent/ # this repo\n```\n\nThe project consumes the ABI (`HCore.Modules.Base`) as a **git submodule** (pinned to a\nreleased tag) and its PostBuild step deploys the built pack into `../hcore/FS/packs/`, so\na kernel clone must sit alongside for deploy/run.\n\n## Build\n\n```bash\n# from a workspace containing both hcore/ and hagent/\ngit clone --recurse-submodules https://github.com/Ardumine/hcore.git\ngit clone --recurse-submodules https://github.com/Ardumine/hagent.git\n\ncd hagent\ndotnet build # compiles + deploys to ../hcore/FS/packs/HCore.Packages.Agent\n```\n\n## Configure\n\nThe agent needs a Deepseek API key. Provide it either way:\n\n- VFS file `/etc/agent/apikey` (i.e. `hcore/FS/etc/agent/apikey`), or\n- the `DEEPSEEK_API_KEY` environment variable.\n\nOptional `/etc/agent/config.json` selects the model/endpoint (defaults shown):\n\n```json\n{ \"model\": \"deepseek-chat\", \"baseUrl\": \"https://api.deepseek.com\" }\n```\n\n> Do not commit your API key. `config.json` intentionally contains no secret.\n\n## Run\n\nBoot the kernel, then launch the agent from the HCore shell:\n\n```\n$ dotnet run --project ../hcore/HCore.Main\n...\n/ $ agent\nHCore Agent (deepseek-chat). Session: session-... Type your message.\nyou> what packages are installed?\nyou> /exit\n```\n\nLaunch options: `agent [-s|--session <name>] [initial prompt...]`.\n\n### REPL commands\n\n| Command | Description |\n|---|---|\n| `/exit`, `/quit` | End the session (returns to the shell) |\n| `/reset` | Clear the current conversation |\n| `/model [name]` | Show or switch the model (persists to config) |\n| `/sessions` | List saved sessions |\n| `/save [name]` | Save the conversation now (optionally rename) |\n| `/load <name>` | Restore a saved session |\n| `/new [name]` | Start a fresh session |\n| `/help` | Show help |\n\nConversations auto-save after each turn to `/home/agent/sessions/<name>.json`.\n\n## Tools available to the agent\n\n| Tool | Purpose |\n|---|---|\n| `vfs_list` / `vfs_read` / `vfs_write` | Browse and edit the HCore VFS |\n| `shell` | Run HShell command lines (`ls`, `cat`, `spawn`, `run`, `kill`, `service`, `hpm`, ...) |\n| `pkg_build` | Author C# source → compile with `dotnet` → hot-load into the running kernel → spawn/run. Full write → compile → fix → run loop, no reboot. |\n\n`pkg_build` relies on the kernel's `@forge` service (VFS↔host path bridge + runtime module registration), available in recent `hcore`.\n\n## Examples\n\nSee [docs/EXAMPLES.md](docs/EXAMPLES.md) for ready-to-paste prompts — from\ninspecting the system to having the agent build and run an HTTP server module on\nthe fly — plus tips for prompting reliably (notably: the `shell` tool runs\nHShell, **not** bash).\n\n## License\n\nSee the HCore project.\n"}}}}