@@ -42,7 +42,7 @@ function bareAgentToolDefinitions() {
|
||||
function: {
|
||||
name: 'write_file',
|
||||
description:
|
||||
'Create or overwrite a file. Parent directories are created as needed.',
|
||||
'Create a new file or overwrite an existing one at any writable guest path. Parent directories are created as needed. Do not ask permission.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -751,12 +751,57 @@ function bareAgentToolDefinitions() {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'discord_send_message',
|
||||
description:
|
||||
'Send a Discord Direct Message on the open operator channel. Reach out anytime — do not wait for the user to message first. Defaults to the last whitelisted DM partner (or the sole whitelist id). Never sends to anyone off DISCORD_ID_WHITELIST. If the bot is offline the message is queued in ~/.discord/outbox.json.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'Message body (Discord markdown). Max 4000 characters.'
|
||||
},
|
||||
user_id: {
|
||||
type: 'string',
|
||||
description: 'Optional Discord user snowflake. Omit to use the open channel partner.'
|
||||
}
|
||||
},
|
||||
required: ['text']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'discord_channel_status',
|
||||
description:
|
||||
'Show the Discord DM channel: partner user id, last inbound/outbound, pending outbox, inbox count, whether the bot client is live.',
|
||||
parameters: { type: 'object', properties: {} }
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'discord_read_inbox',
|
||||
description:
|
||||
'Read recent inbound Discord DMs stored on the channel (from the operator). Use when you need what they said while you were not in a turn.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
max: { type: 'integer', description: 'How many recent messages (default 12, max 50)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'emit_host_notification',
|
||||
description:
|
||||
'Request an audited host notification via HRPC route. Enabled by default; emergency_stop_mutations or a denylist can still block.',
|
||||
'Request an audited host notification via HRPC route. Also fans out to the open Discord DM channel when one exists. Enabled by default; emergency_stop_mutations or a denylist can still block.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
@@ -1865,6 +1865,100 @@ async function bareAgentDispatchTool(o) {
|
||||
return bareAgentJsonResult(r)
|
||||
}
|
||||
|
||||
if (
|
||||
toolName === 'discord_send_message' ||
|
||||
toolName === 'discord_channel_status' ||
|
||||
toolName === 'discord_read_inbox'
|
||||
) {
|
||||
if (toolName === 'discord_channel_status') {
|
||||
appendProgress('discord_channel_status')
|
||||
if (typeof ctx.bareOsDiscordChannelStatus === 'function') {
|
||||
try {
|
||||
return bareAgentJsonResult(await ctx.bareOsDiscordChannelStatus())
|
||||
} catch (e) {
|
||||
const msg =
|
||||
e && typeof e === 'object' && 'message' in e
|
||||
? String(e.message)
|
||||
: String(e)
|
||||
return bareAgentJsonResult({ ok: false, error: msg })
|
||||
}
|
||||
}
|
||||
const rec = await bareAgentReadJsonFile(ctx, '~/.discord/channel.json', {})
|
||||
const outbox = await bareAgentReadJsonFile(ctx, '~/.discord/outbox.json', [])
|
||||
const inbox = await bareAgentReadJsonFile(ctx, '~/.discord/inbox.json', [])
|
||||
return bareAgentJsonResult({
|
||||
ok: true,
|
||||
open: rec && rec.open !== false && Boolean(rec.userId),
|
||||
userId: rec && rec.userId ? String(rec.userId) : '',
|
||||
lastInboundAt: (rec && rec.lastInboundAt) || '',
|
||||
lastOutboundAt: (rec && rec.lastOutboundAt) || '',
|
||||
pendingOutbox: Array.isArray(outbox) ? outbox.length : 0,
|
||||
inboxCount: Array.isArray(inbox) ? inbox.length : 0,
|
||||
liveClient: false
|
||||
})
|
||||
}
|
||||
if (toolName === 'discord_read_inbox') {
|
||||
const n = Math.max(1, Math.min(50, Math.floor(Number(args.max) || 12)))
|
||||
appendProgress('discord_read_inbox')
|
||||
const inbox = await bareAgentReadJsonFile(ctx, '~/.discord/inbox.json', [])
|
||||
const list = Array.isArray(inbox) ? inbox : []
|
||||
return bareAgentJsonResult({
|
||||
ok: true,
|
||||
count: list.length,
|
||||
messages: list.slice(-n)
|
||||
})
|
||||
}
|
||||
const text = String(args.text || args.message || '').trim()
|
||||
if (!text) return bareAgentJsonResult({ ok: false, error: 'empty_message' })
|
||||
if (text.length > 4000) {
|
||||
return bareAgentJsonResult({ ok: false, error: 'message_too_long', max: 4000 })
|
||||
}
|
||||
const userId = String(args.user_id || args.userId || '').trim()
|
||||
appendProgress('discord_send_message')
|
||||
if (typeof ctx.bareOsDiscordSendDm === 'function') {
|
||||
try {
|
||||
const res = await ctx.bareOsDiscordSendDm({
|
||||
text: text,
|
||||
userId: userId,
|
||||
source: 'agent'
|
||||
})
|
||||
return bareAgentJsonResult(res && typeof res === 'object' ? res : { ok: true })
|
||||
} catch (e) {
|
||||
const msg =
|
||||
e && typeof e === 'object' && 'message' in e
|
||||
? String(e.message)
|
||||
: String(e)
|
||||
return bareAgentJsonResult({ ok: false, error: msg })
|
||||
}
|
||||
}
|
||||
const rec = await bareAgentReadJsonFile(ctx, '~/.discord/channel.json', {})
|
||||
const partner = userId || (rec && rec.userId ? String(rec.userId) : '')
|
||||
if (!partner) {
|
||||
return bareAgentJsonResult({
|
||||
ok: false,
|
||||
error: 'no_channel_partner',
|
||||
hint: 'A whitelisted user must DM the bot first, or pass user_id.'
|
||||
})
|
||||
}
|
||||
const outbox = await bareAgentReadJsonFile(ctx, '~/.discord/outbox.json', [])
|
||||
const list = Array.isArray(outbox) ? outbox.slice() : []
|
||||
const item = {
|
||||
id: 'out-' + String(Date.now()),
|
||||
userId: partner,
|
||||
text: text,
|
||||
createdAt: new Date().toISOString(),
|
||||
source: 'agent'
|
||||
}
|
||||
list.push(item)
|
||||
await bareAgentWriteJsonFile(ctx, '~/.discord/outbox.json', list.slice(-100))
|
||||
return bareAgentJsonResult({
|
||||
ok: true,
|
||||
queued: true,
|
||||
id: item.id,
|
||||
userId: partner
|
||||
})
|
||||
}
|
||||
|
||||
if (toolName === 'emit_host_notification' || toolName === 'request_host_action') {
|
||||
const cfg = configRef.current || {}
|
||||
if (cfg && cfg.emergency_stop_mutations) {
|
||||
@@ -1891,6 +1985,16 @@ async function bareAgentDispatchTool(o) {
|
||||
}
|
||||
appendProgress('emit_host_notification ' + payload.title)
|
||||
const res = await ctx.bareOsHrpcRequest('bare_os', 'host_notify', payload)
|
||||
if (typeof ctx.bareOsDiscordSendDm === 'function') {
|
||||
try {
|
||||
await ctx.bareOsDiscordSendDm({
|
||||
text: payload.title + '\n' + payload.message,
|
||||
source: 'host_notify'
|
||||
})
|
||||
} catch {
|
||||
/* Discord fan-out is optional */
|
||||
}
|
||||
}
|
||||
return bareAgentJsonResult({ ok: true, result: res })
|
||||
}
|
||||
const action = String(args.action || '').trim()
|
||||
|
||||
@@ -245,6 +245,8 @@ async function bareAgentPromptSetupLine(ctx, prompt, opts) {
|
||||
|
||||
const BARE_AGENT_STATIC_SYSTEM = `You are the Bare OS coding agent — a senior implementer that lives inside the guest image (JavaScript POSIX on Hyperdrive + Hyperswarm, Pear/Bare runtime). You write, edit, debug, and verify code and OS state by calling tools. You are not a chatbot that narrates plans and waits. Lead with tools. Execute until the job is done, then call task_complete.
|
||||
|
||||
YOU RUN THE TOOLS. The user never runs your tools, commands, patches, or scripts. They will not copy-paste what you print into a shell, Discord, or another agent. If the work needs a tool, YOU call it (run_command, write_file, apply_patch, read_file, …). Never reply with "run this", "please execute", "you can run", or a command dump for the user to do. The user is not your tool runner.
|
||||
|
||||
Bare OS by Raven Scott (https://raven-scott.fyi). Repo: https://git.ssh.surf/snxraven/bare-operating-system. Booter: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy
|
||||
|
||||
WORK POLICY.
|
||||
@@ -254,15 +256,17 @@ WORK POLICY.
|
||||
- Claim done / fixed / tested only when a tool result supports it. Otherwise say what you did not verify.
|
||||
- Scope to what was asked. Comments are short and factual. No placeholders. Comments must not substitute for a fix.
|
||||
|
||||
ACCESS (denylist, not allowlist). You already have full guest admin. NEVER ASK whether you may run a command, edit, delete, fetch, or call a tool — just do it. Only refuse when a denylist or the read-only base system blocks the path.
|
||||
- WRITE: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer unique search_replace / edit_file; set replace_all only when you mean it. write_file creates or overwrites.
|
||||
ACCESS (denylist, not allowlist). You already have full guest admin. You can create files, edit files, delete files, run commands, and fetch the network. NEVER ASK whether you may — just do it. Only refuse when a denylist or the read-only base system blocks the path.
|
||||
- CREATE / EDIT: write_file creates or overwrites any writable path. create_directory makes folders. search_replace / edit_file change one hunk; apply_patch for multi-hunk or multi-file. Never say you cannot write files. Never ask the user to paste a file you can write yourself. Never ask the user to run a command you can run_command yourself.
|
||||
- WRITE PATHS: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer unique search_replace / edit_file; set replace_all only when you mean it.
|
||||
- READ: any absolute path, including the entire /proc kernel surface (read_proc_file, runtime_diagnostic_bundle). Use read_file offset/limit for large files.
|
||||
- RUN: every guest command via run_command (command_deny is empty by default). Prefer list_directory / glob_files / file_stat over \`ls\` / \`find\` when you only need names. list_bin lists guest /bin utilities (POSIX-in-JS, not GNU).
|
||||
- DELETE / MOVE: enabled. Cannot mutate the read-only base system: /bin /etc /boot /lib /usr /share /proc /dev /sys /run.
|
||||
- JS: Node is NOT installed in the guest. Never plan or run node, npm, or npx here. Author JS with run_js_script (Bare kernel, writes under ~/.agent) or run_js_script_at_path / run_command with an absolute .mjs path. Guest scripts use async function run(ctx, argv) — ctx.vfs, ctx.execLine, ctx.console, ctx.exitCode. No require('node:fs').
|
||||
- JS: Node is NOT installed in the guest. Never plan or run node, npm, or npx here. Author JS with run_js_script (Bare kernel, writes under ~/.agent) or run_js_script_at_path / run_command with an absolute .mjs path. Guest scripts use async function run(ctx, argv) — ctx.vfs, ctx.execLine, ctx.console, ctx.exitCode. Do not import Node builtins; use ctx.vfs and Bare ctx hooks.
|
||||
- LIVE KERNEL: read_proc_file on /proc/bare_os/features (or features.json) and /proc/bare_os/capabilities.json. Man pages and apropos_man are docs only — never infer what is enabled from them.
|
||||
- NET: web_fetch uses the same host allow/deny list as wget/curl.
|
||||
- BRIDGE: emit_host_notification and request_host_action are enabled by default. emergency_stop_mutations is the kill switch.
|
||||
- DISCORD CHANNEL: You have a Direct Message channel with the operator. Use discord_send_message to reach out whenever you want — progress, completion, blockers, scheduled results. Do not wait for them to message first. discord_channel_status / discord_read_inbox inspect the same channel. Only whitelisted users receive DMs.
|
||||
- SECRETS: never print ~/.agent/config.json, API keys, seeds, or vault material.
|
||||
- ask_user_question is only for a real product choice the user must make. Never use it (or chat) to request permission.
|
||||
|
||||
@@ -273,6 +277,7 @@ CODING LOOP. Multi-step work: todo_write (merge=true). Large unknown surface: en
|
||||
4. Finish: task_complete (and update_goal completed=true on autonomous runs) with what changed, how you verified, and what is still assumed. If the same action fails three times, stop, update_goal blocked_reason if needed, and report evidence.
|
||||
|
||||
TOOL DISCIPLINE.
|
||||
- YOU call every tool. The user will never run them. Do not print commands, patches, or "run this" for the user.
|
||||
- Independent reads may be issued together; the harness may serialize them (tool_parallelism defaults to 1).
|
||||
- Do not paste huge files into the user reply — cite paths and show only the slice that matters.
|
||||
- Persist durable facts in MEMORY.md; older turns may be compacted.
|
||||
@@ -289,7 +294,7 @@ TOOL MAP (schemas are already attached — use them):
|
||||
- Kernel / ops: read_proc_file, runtime_diagnostic_bundle, get_system_info, get_resource_limits, get_swarm_peers, list_services, service_status, list_timers, read_cron_log, read_audit_log, read_boot_policy, read_kernel_extension_resolution, get_initd_graph, read_unit_journal, inspect_ipc_backpressure, get_network_summary, tail_telemetry_streams, pkg_index_lookup
|
||||
- Checks: list_verification_scripts, run_maintenance_gate, run_contract_checks, summarize_build_drift, verification_hints
|
||||
- Docs: read_man_page, apropos_man (documentation search only)
|
||||
- Bridge / web: web_search, web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action
|
||||
- Bridge / web: web_search, web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action, discord_send_message, discord_channel_status, discord_read_inbox
|
||||
- Autonomy: autonomous_run, autonomous_run_status, autonomous_run_stop, update_goal
|
||||
- Other: ask_user_question (product choice only, never permission), task_complete
|
||||
|
||||
@@ -307,7 +312,10 @@ This turn is shown in Discord. This block overrides the TTY plain-text rule abov
|
||||
- Use \`inline code\` for paths, commands, ids, and env vars.
|
||||
- Use fenced \`\`\` blocks for multi-line code or logs, and always close every fence.
|
||||
- Use [label](https://url) for links. Do not wrap the entire reply in one fence.
|
||||
- Keep the final user-facing answer as Markdown. Tool chatter and process steps belong in tools (progress.txt is automated), not the reply.`
|
||||
- The Discord message is ONLY your final answer. Do not prefix status, model name, "Loading", or a restatement of the prompt.
|
||||
- Tool chatter and process steps belong in tools (progress.txt is automated), not the reply.
|
||||
- Do not tell the user to run a command. Call tools yourself.
|
||||
- You may discord_send_message at any time on the open DM channel, including before the final answer.`
|
||||
|
||||
const BARE_AGENT_OPERATING_CONTRACT = `
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"synopsis": [
|
||||
"discord-bot [--env PATH] [--token TOKEN] [--guild ID] [--check] [--debug] [--message-content] [--login-timeout MS] [--no-user-install]"
|
||||
],
|
||||
"description": "Bare OS Discord bot via ctx.bare.discordJS. Commands run as the unlocked session user (not guest). /r requires cmd and is a non-interactive shell (cwd, history, autocomplete). /upload attaches a file and wget-saves it into the /r cwd or a given path. /hdms manages extra Hyperdrives (list, create, add, invite, pair, health) via ctx.runHdms. /holesail manages persisted Holesail tunnels and the bare-holesail unit (list, add, edit, start/stop, enable/disable, service, url) via ctx.bareOsRunHolesailCli; show never prints seed material. /agent is the full guest /bin/agent harness when ~/.agent/config.json is ready (QVAC + host bridge, or REST + API key + base URL): ask (required prompt; new/plan/auto/compact/max_turns/model), status, config, models, skills, todos, plan, hooks, history, recap, undo, rewind, compact, export, remember, reset, stop. History, skills, todos, and MEMORY.md are shared with the TTY; API keys are never shown. /plugins loads JSON or JS from ~/.discord/plugins (see developer-guide ch.21). Also /settings, /files, /panel, /edit, /create, /bare, /sys, /svc, /fs, /net, /man, /say, /journal, /ping. Embeds paginate when large and expire after 2 minutes idle. Token from --token, DISCORD_TOKEN, or ~/.discord/.env. User-installable (Add App to your Discord profile) is on by default so slash commands work in DMs and any server; DISCORD_ID_WHITELIST is always enforced on those surfaces (empty list denies user-install / DM use). The initd unit bare-os-discord is listed by systemctl only when ~/.discord/.env exists.",
|
||||
"description": "Bare OS Discord bot via ctx.bare.discordJS. Commands run as the unlocked session user (not guest). /r requires cmd and is a non-interactive shell (cwd, history, autocomplete). /upload attaches a file and wget-saves it into the /r cwd or a given path. /hdms manages extra Hyperdrives (list, create, add, invite, pair, health) via ctx.runHdms. /holesail manages persisted Holesail tunnels and the bare-holesail unit (list, add, edit, start/stop, enable/disable, service, url) via ctx.bareOsRunHolesailCli; show never prints seed material. /agent is the full guest /bin/agent harness when ~/.agent/config.json is ready (QVAC + host bridge, or REST + API key + base URL): ask (required prompt; new/plan/auto/compact/max_turns/model), status, config, models, skills, todos, plan, hooks, history, recap, undo, rewind, compact, export, remember, reset, stop. History, skills, todos, and MEMORY.md are shared with the TTY; API keys are never shown. Whitelisted users can Direct Message the bot to talk to the agent without a slash command (same ~/.agent session; help / reset / stop as text). The first allowed DM opens a persistent message channel (~/.discord/channel.json). The agent can then reach out anytime with discord_send_message (live send via ctx.bareOsDiscordSendDm, or queue ~/.discord/outbox.json until the bot drains it). Inbound copies land in ~/.discord/inbox.json. Empty DISCORD_ID_WHITELIST denies every DM, inbound and outbound. /status sets the bot presence shown in the member list (playing, watching, listening, competing, custom, streaming plus online/idle/dnd/invisible); persisted as DISCORD_STATUS / DISCORD_ACTIVITY_TYPE / DISCORD_ACTIVITY_NAME in ~/.discord/.env and also under /settings Discord. /plugins loads JSON or JS from ~/.discord/plugins (see developer-guide ch.21). Also /settings, /files, /panel, /edit, /create, /bare, /sys, /svc, /fs, /net, /man, /say, /journal, /ping. Embeds paginate when large and expire after 2 minutes idle. Token from --token, DISCORD_TOKEN, or ~/.discord/.env. User-installable (Add App to your Discord profile) is on by default so slash commands work in DMs and any server; DISCORD_ID_WHITELIST is always enforced on those surfaces (empty list denies user-install / DM use). DirectMessages intent is requested so DM chat works without the privileged Message Content Intent. The initd unit bare-os-discord is listed by systemctl only when ~/.discord/.env exists.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "--env PATH",
|
||||
@@ -46,8 +46,12 @@
|
||||
"DISCORD_ENV_FILE / BARE_OS_DISCORD_ENV_FILE — path to a .env file. On the host this may be a host filesystem path (booter reads it). In the guest it is a VFS path. Preferred guest path: ~/.discord/.env.",
|
||||
"BARE_OS_DISCORD_INITD — set 0 / false to never register the bare-os-discord systemctl unit, even when ~/.discord/.env exists.",
|
||||
"DISCORD_GUILD_ID — optional guild for slash-command registration.",
|
||||
"DISCORD_ID_WHITELIST — comma-separated Discord user ids allowed to use the bot (slash, autocomplete, buttons, selects, modals, and channel ping). Read from the session, ~/.discord/.env, ~/.discord.env, ~/discord.env, ~/.env, and ./.env. Unset or empty still allows guild-installed commands in a server, but user-install / DM / private-channel use is always denied. A user not on a non-empty list is denied (ephemeral reply) in every install context.",
|
||||
"DISCORD_ID_WHITELIST — comma-separated Discord user ids allowed to use the bot (slash, autocomplete, buttons, selects, modals, channel ping, and Direct Messages). Read from the session, ~/.discord/.env, ~/.discord.env, ~/discord.env, ~/.env, and ./.env. Unset or empty still allows guild-installed commands in a server, but user-install / DM / private-channel use is always denied. A user not on a non-empty list is denied (ephemeral reply, or a DM deny text) in every install context.",
|
||||
"DISCORD_USER_INSTALL / BARE_OS_DISCORD_USER_INSTALL — user-installable profile app (default on). Set 0 / false / off for a guild-only bot.",
|
||||
"DISCORD_STATUS — presence: online, idle, dnd, or invisible (member list). Default online.",
|
||||
"DISCORD_ACTIVITY_TYPE — playing, watching, listening, competing, custom, or streaming. Default playing.",
|
||||
"DISCORD_ACTIVITY_NAME — activity text (default Bare OS). Set off / none / - to clear.",
|
||||
"DISCORD_ACTIVITY_URL — optional streaming URL when activity type is streaming.",
|
||||
"DISCORD_MESSAGE_CONTENT / BARE_OS_DISCORD_MESSAGE_CONTENT — set 1 to request Message Content Intent (same as --message-content).",
|
||||
"DISCORD_DEBUG / BARE_OS_DISCORD_DEBUG — set 1 to print discord.js debug lines.",
|
||||
"DISCORD_LOGIN_TIMEOUT_MS — login deadline in milliseconds (default 45000).",
|
||||
|
||||
@@ -14,7 +14,7 @@ Workspace files are already in the system prompt. Skills index is already attach
|
||||
|
||||
1. Discover — glob_files, grep, find_symbol, list_directory (tree=true), read_file (offset/limit), git_status / git_log, web_search. Read before you edit.
|
||||
2. Track — todo_write (merge=true) for multi-step work. Large unknown surface: enter_plan_mode, write ~/.agent/plan.md, exit_plan_mode, then implement. Plan mode is read-only except that file.
|
||||
3. Edit — unique search_replace for one hunk; apply_patch for multi-hunk/multi-file. write_file to create or overwrite. Match surrounding style. No placeholders.
|
||||
3. Edit — unique search_replace for one hunk; apply_patch for multi-hunk/multi-file. write_file to create or overwrite any writable path. create_directory for folders. You can create and edit files; never claim otherwise. Match surrounding style. No placeholders.
|
||||
4. Verify — re-read, run_command / run_js_script, read_proc_file or logs. Host checkout: verification_hints (does not run npm here).
|
||||
5. Finish — task_complete with what changed, how you verified, and what is still assumed. Same action failing three times: stop and report evidence.
|
||||
|
||||
@@ -25,6 +25,7 @@ Before multi-file edits, note packages, contracts, generated artifacts, and doc
|
||||
## Access (NEVER violate)
|
||||
|
||||
- Full guest admin. NEVER ASK to proceed. Deletes, shell, VFS admin, /proc, and bridge tools are on by default.
|
||||
- You run every tool. The user will never execute your commands, patches, or scripts. Do not ask them to.
|
||||
- Refuse only when a denylist or the read-only base system blocks the path (/bin /etc /boot /lib /usr /share /proc /dev /sys /run).
|
||||
- Never exfiltrate keys, seeds, or ~/.agent/config.json secrets.
|
||||
- ask_user_question is for a real product choice only — never for permission.
|
||||
|
||||
@@ -4,7 +4,9 @@ You are the Bare OS coding agent. Not a chatbot. A senior implementer that lives
|
||||
|
||||
## Core truths
|
||||
|
||||
**Execute.** NEVER ASK whether you may run a command, edit, delete, fetch, or call a tool. You already have full guest admin (denylist, not allowlist). Just do it.
|
||||
**Execute.** NEVER ASK whether you may run a command, create a file, edit, delete, fetch, or call a tool. You already have full guest admin (denylist, not allowlist). Just do it.
|
||||
**You run the tools.** The user never runs your tools, commands, or patches. Call them yourself. Never print "run this" for the user.
|
||||
**Create and edit files.** write_file, create_directory, search_replace, apply_patch, edit_file. Never claim you cannot write.
|
||||
**Be resourceful first.** Read files, search the VFS, check /proc, load a skill — then act. Do not wait for permission.
|
||||
**Be genuinely helpful, not performatively helpful.** Skip filler. Ship the change, verify it, report what is true.
|
||||
**Have opinions.** Disagree when it makes sense. Be blunt and honest.
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
- Location: Atlanta, Georgia, US
|
||||
- Expertise: P2P systems, Bare runtime, Hyperdrive, decentralized identity, POSIX-in-JS
|
||||
- Preferences: Concise technical answers, bullet points, no corporate speak, direct honesty
|
||||
- Permissions: Full guest admin (denylist). Do not ask before using tools. Base system remains read-only.
|
||||
- Permissions: Full guest admin (denylist). Create, edit, and delete files without asking. Base system remains read-only.
|
||||
- The owner never runs agent tools. The agent executes every command and file change itself.
|
||||
|
||||
@@ -166,8 +166,10 @@ function discordUsage(argv0) {
|
||||
'host DISCORD_ENV_FILE, ~/.discord/.env, ~/.discord.env).\n' +
|
||||
'Ctrl+C stops the foreground bot. Initd unit bare-os-discord appears in\n' +
|
||||
'systemctl only when ~/.discord/.env exists with DISCORD_TOKEN=.\n' +
|
||||
'Slash commands: /panel /files /edit /create /upload /hdms /holesail /agent /bare /sys /svc /fs /net /man /say /r /journal /ping.\n' +
|
||||
'Slash commands: /panel /files /edit /create /upload /hdms /holesail /agent /status /bare /sys /svc /fs /net /man /say /r /journal /ping.\n' +
|
||||
'Commands run as the unlocked session user. Options use Discord autocomplete.\n' +
|
||||
'Whitelisted users can DM the bot to talk to /bin/agent (same ~/.agent session).\n' +
|
||||
'/status sets playing / watching / listening / competing in the member list.\n' +
|
||||
'Channel "ping" still replies pong when Message Content Intent is enabled.\n' +
|
||||
'User-installable (profile app) is on by default so operators can use slash\n' +
|
||||
'commands in DMs and any server. DISCORD_ID_WHITELIST is always enforced on\n' +
|
||||
@@ -234,7 +236,11 @@ function discordApplyDotEnvExtras(ctx, parsed) {
|
||||
const extras = [
|
||||
'DISCORD_GUILD_ID',
|
||||
'DISCORD_ID_WHITELIST',
|
||||
'DISCORD_USER_INSTALL'
|
||||
'DISCORD_USER_INSTALL',
|
||||
'DISCORD_STATUS',
|
||||
'DISCORD_ACTIVITY_TYPE',
|
||||
'DISCORD_ACTIVITY_NAME',
|
||||
'DISCORD_ACTIVITY_URL'
|
||||
]
|
||||
const maps = [ctx.env]
|
||||
if (
|
||||
@@ -379,13 +385,16 @@ async function run(ctx, argv) {
|
||||
const loginMs = discordLoginTimeoutMs(ctx, argv)
|
||||
|
||||
const intents = [GatewayIntentBits.Guilds]
|
||||
if (GatewayIntentBits.DirectMessages) {
|
||||
intents.push(GatewayIntentBits.DirectMessages)
|
||||
}
|
||||
if (wantsMessageContent && GatewayIntentBits.MessageContent) {
|
||||
intents.push(GatewayIntentBits.MessageContent)
|
||||
}
|
||||
|
||||
const osName =
|
||||
(typeof process !== 'undefined' && process.platform) || 'darwin'
|
||||
const client = new Client({
|
||||
const clientOpts = {
|
||||
intents: intents,
|
||||
ws: {
|
||||
identifyProperties: {
|
||||
@@ -394,7 +403,12 @@ async function run(ctx, argv) {
|
||||
device: 'bare-os'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (dj.Partials && dj.Partials.Channel != null) {
|
||||
clientOpts.partials = [dj.Partials.Channel]
|
||||
}
|
||||
const client = new Client(clientOpts)
|
||||
if (ctx) ctx.bareOsDiscordClient = client
|
||||
const slashBody = await Promise.resolve(
|
||||
typeof discordBuildSlashCommands === 'function'
|
||||
? discordBuildSlashCommands(dj, ctx)
|
||||
@@ -460,6 +474,27 @@ async function run(ctx, argv) {
|
||||
|
||||
client.once(Events.ClientReady, async function (readyClient) {
|
||||
ctx.console.log('Logged in as ' + readyClient.user.tag)
|
||||
if (typeof discordApplyPresence === 'function') {
|
||||
try {
|
||||
discordApplyPresence(ctx, client)
|
||||
} catch {
|
||||
/* presence is optional */
|
||||
}
|
||||
}
|
||||
if (typeof discordChannelBindHook === 'function') {
|
||||
try {
|
||||
discordChannelBindHook(ctx)
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
}
|
||||
if (typeof discordChannelStartPump === 'function') {
|
||||
try {
|
||||
discordChannelStartPump(ctx)
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
}
|
||||
if (typeof REST !== 'function' || !Routes) return
|
||||
try {
|
||||
await discordRegisterSlashBody(slashBody)
|
||||
@@ -522,6 +557,16 @@ async function run(ctx, argv) {
|
||||
|
||||
if (Events.MessageCreate) {
|
||||
client.on(Events.MessageCreate, async function (message) {
|
||||
if (typeof discordDispatchMessage === 'function') {
|
||||
try {
|
||||
await discordDispatchMessage(ctx, message)
|
||||
} catch (err) {
|
||||
ctx.console.error(
|
||||
'discord-bot: message dispatch failed: ' + discordErrText(err)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!message || (message.author && message.author.bot)) return
|
||||
const text = String(message.content || '')
|
||||
.trim()
|
||||
@@ -625,6 +670,13 @@ async function run(ctx, argv) {
|
||||
let interruptStop = null
|
||||
const detachInterrupt = discordAttachInterrupt(ctx, function () {
|
||||
if (typeof interruptStop === 'function') interruptStop()
|
||||
if (typeof discordChannelStopPump === 'function') {
|
||||
try {
|
||||
discordChannelStopPump()
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
}
|
||||
Promise.resolve(client.destroy()).catch(function () {})
|
||||
})
|
||||
|
||||
@@ -667,7 +719,7 @@ async function run(ctx, argv) {
|
||||
}
|
||||
if (!wantsMessageContent) {
|
||||
ctx.console.log(
|
||||
'discord-bot: /ping only (pass --message-content after enabling Message Content Intent for channel ping/pong)'
|
||||
'discord-bot: DM agent chat is on (DirectMessages intent). Channel "ping" still needs --message-content.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -806,6 +858,13 @@ async function run(ctx, argv) {
|
||||
await new Promise(function (resolve) {
|
||||
function shutdown() {
|
||||
detachInterrupt()
|
||||
if (typeof discordChannelStopPump === 'function') {
|
||||
try {
|
||||
discordChannelStopPump()
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
}
|
||||
Promise.resolve(client.destroy())
|
||||
.catch(function () {})
|
||||
.then(resolve)
|
||||
|
||||
@@ -134,6 +134,9 @@ test('agent-tools exposes agent ops tools', async (t) => {
|
||||
'get_hrpc_allowlist_status',
|
||||
'emit_host_notification',
|
||||
'request_host_action',
|
||||
'discord_send_message',
|
||||
'discord_channel_status',
|
||||
'discord_read_inbox',
|
||||
'autonomous_run',
|
||||
'autonomous_run_status',
|
||||
'autonomous_run_stop',
|
||||
@@ -215,6 +218,14 @@ test('agent-tui embeds operating contract appendix', async (t) => {
|
||||
t.ok(TUI.includes('BARE_OS_AGENT_DISCORD'))
|
||||
t.ok(TUI.includes('verification_hints'))
|
||||
t.ok(TUI.includes('NEVER ASK'))
|
||||
t.ok(TUI.includes('YOU RUN THE TOOLS'))
|
||||
t.ok(TUI.includes('The user never runs your tools'))
|
||||
t.ok(TUI.includes('discord_send_message'))
|
||||
t.ok(TUI.includes('DISCORD CHANNEL'))
|
||||
t.ok(TUI.includes('The user is not your tool runner'))
|
||||
t.ok(TUI.includes('You can create files, edit files'))
|
||||
t.ok(TUI.includes('Never say you cannot write files'))
|
||||
t.ok(TUI.includes('ONLY your final answer'))
|
||||
t.ok(TUI.includes('Lead with tools'))
|
||||
t.ok(TUI.includes('TOOL DISCIPLINE'))
|
||||
t.ok(TUI.includes('WORK POLICY'))
|
||||
|
||||
@@ -901,3 +901,67 @@ test('dispatch delete / proc / run_command are open by default', async (t) => {
|
||||
t.absent(blocked.ok)
|
||||
t.is(blocked.error, 'command_denied')
|
||||
})
|
||||
|
||||
test('discord_send_message queues or uses the live DM hook', async (t) => {
|
||||
const s = loadDispatch()
|
||||
const { vfs, files, b4a } = makeVfs({})
|
||||
await vfs.writeFile(
|
||||
'~/.discord/channel.json',
|
||||
b4a.from(JSON.stringify({ userId: '111', open: true }) + '\n')
|
||||
)
|
||||
const paths = {
|
||||
dir: '/home/guest/.agent',
|
||||
config: '/home/guest/.agent/config.json',
|
||||
cmdOut: '/tmp/agent.out'
|
||||
}
|
||||
const queued = await dispatch(s, {
|
||||
ctx: { vfs, b4a },
|
||||
paths,
|
||||
toolName: 'discord_send_message',
|
||||
args: { text: 'hello from agent' },
|
||||
home: '/home/guest'
|
||||
})
|
||||
t.ok(queued.ok)
|
||||
t.ok(queued.queued)
|
||||
t.ok(files.has('~/.discord/outbox.json'))
|
||||
t.ok(String(files.get('~/.discord/outbox.json')).includes('hello from agent'))
|
||||
|
||||
const sent = []
|
||||
const live = await dispatch(s, {
|
||||
ctx: {
|
||||
vfs,
|
||||
b4a,
|
||||
bareOsDiscordSendDm: async (opts) => {
|
||||
sent.push(opts)
|
||||
return { ok: true, queued: false, userId: '111' }
|
||||
}
|
||||
},
|
||||
paths,
|
||||
toolName: 'discord_send_message',
|
||||
args: { text: 'live ping' },
|
||||
home: '/home/guest'
|
||||
})
|
||||
t.ok(live.ok)
|
||||
t.absent(live.queued)
|
||||
t.is(sent[0].text, 'live ping')
|
||||
|
||||
const empty = await dispatch(s, {
|
||||
ctx: { vfs, b4a },
|
||||
paths,
|
||||
toolName: 'discord_send_message',
|
||||
args: { text: ' ' },
|
||||
home: '/home/guest'
|
||||
})
|
||||
t.absent(empty.ok)
|
||||
t.is(empty.error, 'empty_message')
|
||||
|
||||
const st = await dispatch(s, {
|
||||
ctx: { vfs, b4a },
|
||||
paths,
|
||||
toolName: 'discord_channel_status',
|
||||
args: {},
|
||||
home: '/home/guest'
|
||||
})
|
||||
t.ok(st.ok)
|
||||
t.is(st.userId, '111')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user