/** * Agent Markdown workspace at ~/.agent/workspace — soul files + optional daily memory. * Defaults ship on the system image at /share/agent-workspace/ and are seeded * into the personal drive when SOUL.md is missing (portable across peers via Hyperdrive). */ /** @type {readonly string[]} Canonical Markdown load order */ var BARE_AGENT_WORKSPACE_FILES = Object.freeze([ 'SOUL.md', 'AGENTS.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'MEMORY.md', 'BOOTSTRAP.md', 'HEARTBEAT.md', 'PROMPT.md' ]) /** System-drive templates (kernel share) */ var BARE_AGENT_WORKSPACE_SHARE = '/share/agent-workspace' /** Relative paths under workspace/ and share root for skill templates */ var BARE_AGENT_SKILL_SEED_REL = Object.freeze([ 'skills/.gitkeep', 'skills/p2p-os-status/SKILL.md', 'skills/bare-os-kernel-proc/SKILL.md', 'skills/bare-os-super-developer/SKILL.md', 'skills/agent-ops/SKILL.md', 'skills/xai-compat/SKILL.md', 'skills/holesail/SKILL.md', 'skills/hdms/SKILL.md', 'skills/bareos-code-change/SKILL.md', 'skills/hyperdrive-replication/SKILL.md', 'skills/protomux-channel/SKILL.md', 'skills/ctx-api-change/SKILL.md', 'skills/proc-node-change/SKILL.md', 'skills/seed-rpc-change/SKILL.md', 'skills/coreutils-command-change/SKILL.md', 'skills/shell-grammar-change/SKILL.md', 'skills/docs-contract-update/SKILL.md', 'skills/kernel-program-extension/SKILL.md', 'skills/appstore/SKILL.md', 'skills/pear-dev/SKILL.md', 'skills/pear-runtime-debug/SKILL.md', 'skills/holepunch-local-mirror/SKILL.md' ]) /** * @param {Record} ctx * @param {Uint8Array} buf */ function bareAgentWorkspaceDecode(ctx, buf) { if (!buf || !buf.length) return '' if ( typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function' ) return ctx.b4a.toString(buf) return String(new TextDecoder().decode(buf)) } /** * @returns {string} UTC YYYY-MM-DD */ function bareAgentWorkspaceUtcYmd() { const d = new Date() const y = d.getUTCFullYear() const m = d.getUTCMonth() + 1 const day = d.getUTCDate() const pad = (n) => (n < 10 ? '0' : '') + n return y + '-' + pad(m) + '-' + pad(day) } /** * Seed ~/.agent/workspace from /share/agent-workspace when SOUL.md is absent. * @param {Record} ctx * @param {{ dir: string, workspace: string, workspaceMemory: string, workspaceSkills: string }} paths */ async function bareAgentEnsureWorkspace(ctx, paths) { const vfs = ctx.vfs if (!vfs || typeof vfs.mkdir !== 'function' || typeof vfs.readFile !== 'function') return if (typeof vfs.writeFile !== 'function') return try { const b = await vfs.readFile(paths.workspace + '/SOUL.md') if (b && b.length) return } catch { /* missing — seed */ } try { await vfs.mkdir(paths.workspace, { recursive: true }) await vfs.mkdir(paths.workspaceMemory, { recursive: true }) } catch { return } const share = BARE_AGENT_WORKSPACE_SHARE for (const name of BARE_AGENT_WORKSPACE_FILES) { try { const buf = await vfs.readFile(share + '/' + name) await vfs.writeFile(paths.workspace + '/' + name, buf) } catch { /* template missing on image — skip */ } } try { const gk = await vfs.readFile(share + '/memory/.gitkeep') await vfs.writeFile(paths.workspaceMemory + '/.gitkeep', gk) } catch { /* optional */ } /** @type {[string, string][]} */ const stubs = [ ['loader.stub.js', paths.dir + '/loader.js'], ['index.stub.js', paths.dir + '/index.js'], ['skill-loader.stub.js', paths.dir + '/skill-loader.js'], ['README-agent.md', paths.dir + '/README-agent.md'] ] for (const [srcName, dest] of stubs) { try { const buf = await vfs.readFile(share + '/' + srcName) await vfs.writeFile(dest, buf) } catch { /* optional */ } } } /** * Ensure workspace/skills templates and ~/.agent/skill-loader.js exist (idempotent; for upgrades). * @param {Record} ctx * @param {{ workspace: string, workspaceSkills: string, dir: string }} paths * @param {Record} [config] */ async function bareAgentEnsureSkillTemplates(ctx, paths, config) { const vfs = ctx.vfs if ( !vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function' ) return const provider = config && typeof config === 'object' ? String(config.provider || '').trim().toLowerCase() : '' try { await vfs.mkdir(paths.workspaceSkills, { recursive: true }) } catch { return } const share = BARE_AGENT_WORKSPACE_SHARE for (const rel of BARE_AGENT_SKILL_SEED_REL) { if (rel === 'skills/xai-compat/SKILL.md' && provider !== 'xai') { if (typeof vfs.unlink === 'function') { try { await vfs.unlink(paths.workspace + '/' + rel) } catch { /* ignore */ } } continue } const dest = paths.workspace + '/' + rel try { const b = await vfs.readFile(dest) if (b && b.length) continue } catch { /* missing — copy */ } try { const buf = await vfs.readFile(share + '/' + rel) const parent = dest.replace(/\/[^/]+$/, '') await vfs.mkdir(parent, { recursive: true }) await vfs.writeFile(dest, buf) } catch { /* template missing on image */ } } try { await vfs.readFile(paths.dir + '/skill-loader.js') } catch { try { const buf = await vfs.readFile(share + '/skill-loader.stub.js') await vfs.writeFile(paths.dir + '/skill-loader.js', buf) } catch { /* optional */ } } } /** * @param {Record} ctx * @param {string} text */ function bareAgentWorkspaceEncode(ctx, text) { const s = String(text) if ( typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function' ) return ctx.b4a.from(s) return new TextEncoder().encode(s) } /** * @param {Record} config */ function bareAgentIdentityMarkdownForConfig(config) { const label = String(config.agent_label || '').trim() || 'BareAgent' const owner = String(config.owner_name || '').trim() const role = owner ? 'Decentralized OS intelligence for **' + owner + '** · upstream [bare-operating-system](https://git.ssh.surf/snxraven/bare-operating-system).' : 'Decentralized OS Intelligence for snxraven\'s bare-operating-system' return ( '# IDENTITY.md\n\n' + '**Name:** ' + label + '\n' + '**Role:** ' + role + '\n' + '**Emoji:** 🦾\n' + '**Version:** 0.1\n' ) } /** * @param {Record} config */ function bareAgentUserMarkdownForConfig(config) { const owner = String(config.owner_name || '').trim() const opLine = owner ? '- **Operator (this Hyperdrive):** ' + owner + '\n' : '- **Operator:** (set `owner_name` via `agent --config` or `edit_agent_config`)\n' return ( '# USER.md - About the Owner\n\n' + opLine + '- **Upstream maintainer (repo):** snxraven\n' + '- **Location:** Atlanta, Georgia, US\n' + '- **Expertise:** P2P systems, Bare runtime, Hyperdrive, decentralized identity, POSIX-in-JS\n' + '- **Preferences:** Concise technical answers, bullet points, no corporate speak, direct honesty\n' + '- **Permissions:** Full access to system drive and personal Hyperdrive within tool policy\n' ) } /** * Rewrite IDENTITY.md / USER.md from ~/.agent/config.json (owner_name, agent_label). * Call after seeding workspace or when those keys change. * @param {Record} ctx * @param {{ workspace: string }} paths * @param {Record} config */ async function bareAgentSyncWorkspaceFromConfig(ctx, paths, config) { const vfs = ctx.vfs if (!vfs || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function') return const label = String(config.agent_label || '').trim() const owner = String(config.owner_name || '').trim() if (!label && !owner) return try { await vfs.mkdir(paths.workspace, { recursive: true }) } catch { return } try { const idMd = bareAgentIdentityMarkdownForConfig(config) await vfs.writeFile( paths.workspace + '/IDENTITY.md', bareAgentWorkspaceEncode(ctx, idMd) ) const userMd = bareAgentUserMarkdownForConfig(config) await vfs.writeFile(paths.workspace + '/USER.md', bareAgentWorkspaceEncode(ctx, userMd)) } catch { /* ignore — best-effort */ } } /** * Build concatenated system prompt block (Markdown) from workspace files. * @param {Record} ctx * @param {{ workspace: string, workspaceMemory: string }} paths * @param {number} [maxChars] */ async function bareAgentLoadWorkspacePrompt(ctx, paths, maxChars) { const vfs = ctx.vfs if (!vfs || typeof vfs.readFile !== 'function') return '' const cap = Math.min(Math.max(Number(maxChars) || 24000, 4000), 64000) let out = '# Agent workspace (~/.agent/workspace)\n\n' for (const name of BARE_AGENT_WORKSPACE_FILES) { try { const buf = await vfs.readFile(paths.workspace + '/' + name) const t = bareAgentWorkspaceDecode(ctx, buf).trim() out += '=== ' + name + ' ===\n' + (t || '(empty)') + '\n\n' } catch { out += '=== ' + name + ' ===\n(File not found)\n\n' } } const day = bareAgentWorkspaceUtcYmd() try { const buf = await vfs.readFile(paths.workspaceMemory + '/' + day + '.md') const t = bareAgentWorkspaceDecode(ctx, buf).trim() if (t) out += '=== memory/' + day + '.md ===\n' + t + '\n\n' } catch { /* no daily log */ } if (out.length > cap) out = out.slice(0, cap) + '\n… truncated\n' return out }