Files
bare-operating-system/packages/bare-os-coreutils/lib/agent-workspace.js
T
Raven Scott 014c70ad09 feat: introduce ctx.pear surface and /bin/pear for in-OS Pear development
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)
2026-05-26 17:54:06 -04:00

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
}