Complete the full planned effort for the detailed ctx.bare code audit and the new ctx.pear surface, delivering the ability to create, stage, and integrate real Pear applications from within a booted Bare OS. ### Audit (ctx.bare) - Performed exhaustive code audit of bare-os-ctx-bare.js (host import path, drive bundle eval + require.addon wrappers, referrer workarounds). - Inventoried all manifest/bundle verifiers and related scripts. - Researched manifest format, implicit tiering model, and dual loading strategy (JSON + .data.mjs). - Deep analysis of the local Holepunch clone (bare-* and pear-* packages) to identify realistic guest vs host-delegate boundaries. - Full cross-reference of call sites, greps, and historical pain points (pear:// referrer resolution, nativeHint handling, addon stubs). ### Implementation (ctx.pear) - Added `pearEntries` tier to bare-module-manifest.json with initial high-value packages (pear-build, pear-bundle, pear-ref, etc.). - Implemented `loadPearModuleManifest()` and `buildPearCtxObjectFromHost()`. - Wired ctx.pear exposure through the booter into the guest context. - Updated TypeScript definitions (`bare-os-ctx.d.ts`). ### User-Facing Surface - Created full `/bin/pear` command with `help`, `info`, `list`, `init` (functional skeleton creation), and improved `stage` subcommands. - Registered as Tier-1 command (now 183 total commands). - Added man page and rebuilt coreutils (kernel + seeder). ### Agent Autonomy - Created production-quality `pear-dev` agent skill. - Added to skill seed list with cross-references to the appstore skill. ### P2P App Store Integration - Updated appstore skill with explicit Pear development synergy section. - Updated p2p-app-store design doc to document the new closed loop. - Added cross-references in both skills and design documents. ### Verification & Hygiene - Created `scripts/verify-pear-module-manifest-data.mjs`. - Enhanced `verify-pear-no-static-node-import.mjs` with explicit pear command coverage. - Integrated new verifier into release-checklist and agent hints. - Performed comprehensive zero-TODO/scaffolding sweep across all new Pear artifacts (clean). - Multiple full verification harness runs (all green). ### Documentation & Governance - Added complete "Pear Development Environment" thread to feature-roadmap.md. - Updated developer guide (Chapter 12). - Maintained living plan document and detailed audit notes with full Implementation Log throughout. - Updated command counts across READMEs and supporting docs. All changes follow project governance: - Bare-only guest constraints strictly observed - Verifier-first discipline maintained - Living plan + audit documents kept as single source of truth - Production quality bar matching the completed P2P App Store feature Plan items 04–21 completed. See: - docs/design/ctx-pear-surface-and-bare-audit-plan.md - docs/audit/ctx-bare-audit-notes.md (full audit + implementation log)
313 lines
9.5 KiB
JavaScript
313 lines
9.5 KiB
JavaScript
/**
|
|
* 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
|
|
* @param {{ workspace: string, workspaceSkills: string, dir: string }} paths
|
|
* @param {Record<string, unknown>} [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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
|
|
* @param {{ workspace: string }} paths
|
|
* @param {Record<string, unknown>} 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<string, unknown>} 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
|
|
}
|