{"_id":"@aituber-onair/agent","name":"@aituber-onair/agent","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@aituber-onair/agent","version":"0.0.1","description":"Embeddable runtime for giving AI characters jobs inside JavaScript and TypeScript products","type":"module","main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/cjs/index.d.ts","exports":{".":{"types":"./dist/cjs/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"},"./chat":{"types":"./dist/cjs/chat.d.ts","import":"./dist/esm/chat.js","require":"./dist/cjs/chat.js"},"./codex-app-server":{"types":"./dist/cjs/codex-app-server.d.ts","import":"./dist/esm/codex-app-server.js","require":"./dist/cjs/codex-app-server.js"},"./package.json":"./package.json"},"sideEffects":false,"scripts":{"clean":"rm -rf dist","build":"npm run clean && npm run build:esm && npm run build:cjs","build:esm":"tsc --project tsconfig.esm.json","build:cjs":"tsc --project tsconfig.cjs.json && node -e \"require('node:fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }))\"","typecheck":"tsc --noEmit --project tsconfig.test.json","test":"npm run typecheck && npm run build && vitest run && node test/dist-smoke.mjs && node test/package-smoke.mjs","test:watch":"vitest","test:coverage":"vitest run --coverage","fmt":"biome format . --write","fmt:check":"biome format .","lint":"biome lint .","prepublishOnly":"npm run build"},"keywords":["aituber","agent","agent-runtime","ai-character","live-stream"],"author":{"name":"shinshin86","url":"https://github.com/shinshin86"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/shinshin86/aituber-onair.git","directory":"packages/agent"},"publishConfig":{"access":"public"},"peerDependencies":{"@aituber-onair/chat":"^0.52.0"},"peerDependenciesMeta":{"@aituber-onair/chat":{"optional":true}},"devDependencies":{"@aituber-onair/chat":"^0.52.0","@biomejs/biome":"1.9.4","@types/node":"^18.15.0","@vitest/coverage-v8":"^1.3.1","esbuild":"^0.21.3","typescript":"^5.0.0","vitest":"^1.3.1"},"browserslist":["defaults","not IE 11"],"_id":"@aituber-onair/agent@0.0.1","gitHead":"20265277881ec86156c9cd3932da5014afda50b7","bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"homepage":"https://github.com/shinshin86/aituber-onair#readme","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-+XXzMwsgd9J5XS15DzN0zH4csfkT3w5XvwexQ0CDafo4/GpdTHqveL0PBWywKaDbgT+lqMg79UTs01kqNYVquw==","shasum":"799345a4f6cf73ebfbd144e31f3390b4d329b193","tarball":"https://registry.npmjs.org/@aituber-onair/agent/-/agent-0.0.1.tgz","fileCount":213,"unpackedSize":981161,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCID+NBzKGr41gbjmT1WxC8x9EIsfZMPViyCgB/KAkLaBtAiEAmSbSWBn2IDlk4srO6kwtUS+q7Nr/1j6xk1cRmLdF8Hw="}]},"_npmUser":{"name":"shinshin86","email":"shinshin86npm@gmail.com"},"directories":{},"maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/agent_0.0.1_1787904499193_0.090031405625697"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T08:08:18.939Z","0.0.1":"2026-08-28T08:08:19.349Z","modified":"2026-08-28T08:08:19.620Z"},"maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"description":"Embeddable runtime for giving AI characters jobs inside JavaScript and TypeScript products","homepage":"https://github.com/shinshin86/aituber-onair#readme","keywords":["aituber","agent","agent-runtime","ai-character","live-stream"],"repository":{"type":"git","url":"git+https://github.com/shinshin86/aituber-onair.git","directory":"packages/agent"},"author":{"name":"shinshin86","url":"https://github.com/shinshin86"},"bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"license":"MIT","readme":"# @aituber-onair/agent\n\n![@aituber-onair/agent logo](https://raw.githubusercontent.com/shinshin86/aituber-onair/main/packages/agent/images/aituber-onair-agent.png)\n\n[English README](README.md) | [日本語版 README](README.ja.md)\n\nAn embeddable runtime for giving an AI character a job inside a JavaScript or\nTypeScript product.\n\n> This package is an alpha release. Its public API may change before a stable\n> release.\n\n```bash\nnpm install @aituber-onair/agent @aituber-onair/chat\n```\n\n`@aituber-onair/chat` is an optional peer dependency needed only for the\n`@aituber-onair/agent/chat` entry point. The Codex app-server entry point needs\na locally installed Codex CLI; see\n[Codex app-server integration](#codex-app-server-integration).\n\n## What this package is\n\n`@aituber-onair/chat` lets an application communicate with language models.\n`@aituber-onair/agent` turns an AI character into a managed member\nof a product: a character that understands its assignment, organizes its work,\nuses approved capabilities, and asks a human for help when necessary.\n\nThe host application provides:\n\n- a natural-language brief describing the character and its assignment;\n- the tools, services, credentials, and workspace the character may use;\n- rules for operations that must be denied or approved; and\n- product events that start or resume the character's work.\n\nWithin those limits, the character can choose how to organize its notes,\nprocedures, database, and long-term working state. The package does not force\napplications to use fixed schemas for job titles, responsibilities, task\nqueues, or character memory.\n\nThe host application always owns the Agent's lifecycle and authority. The\ncharacter cannot grant itself new tools, credentials, network access, or\nwritable locations.\n\n## How it differs from personal AI assistants\n\n[OpenClaw](https://docs.openclaw.ai/) and\n[Hermes Agent](https://hermes-agent.nousresearch.com/docs/) are primarily\ncomplete runtimes for an assistant that works for its user.\n`@aituber-onair/agent` is designed for a different situation: a developer\nalready has a product and wants an AI character to work inside it.\n\n| | Personal AI assistant | `@aituber-onair/agent` |\n| --- | --- | --- |\n| Works for | An individual user | A product or service |\n| Delivered as | An agent application, service, or gateway | An npm package embedded in an application |\n| Identity | The user's assistant | A character owned by the product |\n| Lifecycle | Managed mainly by the agent runtime | Managed by the host application |\n| Integration | General messaging, tools, and automation | Product events and AITuber OnAir packages |\n\nThis package is not intended to replace OpenClaw or Hermes Agent. Choose a\npersonal AI assistant when the assistant itself is the product. Choose this\npackage when an existing JavaScript or TypeScript product needs its own managed\nAI character.\n\n## Use cases\n\n### AI staff for live-stream monitoring and operations\n\nThe same character can appear on a live stream and also work\nprivately as staff that monitors and supports the stream.\n\nA host application can:\n\n1. receive comments from YouTube, Twitch, WebSocket, or another source;\n2. analyze safety, priority, topics, questions, and repetition with\n   `@aituber-onair/comment-intelligence`;\n3. give analysis accepted by the host, together with the stream state, to a\n   private operations Session;\n4. let the character organize monitoring notes and operating procedures;\n5. notify an operator when attention or human judgment is required; and\n6. create a structured post-stream report for a dashboard or notification UI.\n\nThe dashboard, platform connections, and notification delivery remain the\nresponsibility of the host application.\n\nThe `stream-operations-staff` example runs Miko against a real Codex app-server.\nIts Node server preprocesses fixed comments with `comment-intelligence`, sends\nonly text-free structured observations to Codex, validates Codex-generated\ncards and reports, and streams Agent Events to the existing React dashboard.\n\nThe `channel-strategy-staff` example uses `createChatServiceBackend()` and five\nread-only domain Tools to compare fixed YouTube and Twitch channel history. It\nkeeps platform metrics separate, validates every cited evidence ID against\nTool results from the current Turn, and attaches a structured strategy\nArtifact through host hooks.\n\n### A resident character inside a product\n\nExamples include:\n\n- a game character that manages a community area;\n- a character in a creator tool that organizes production work;\n- an in-product guide that learns the product's operating context; and\n- a brand character that handles routine requests and asks a human to resolve\n  exceptions.\n\n### A workspace character\n\nA Node.js application can connect the same character to a\nrestricted workspace backend such as Codex app-server. The character may build\nits own way of working inside that workspace, while sandbox, writable-root, and\napproval rules remain under host control.\n\nPublic input such as viewer comments must never become workspace instructions.\nOnly structured information selected or accepted by the host after analysis may\nenter a privileged workspace Session.\n\n## Core model\n\n- **Brief:** A natural-language description of the character's identity, role,\n  goals, values, responsibilities, and boundaries. It remains owned by the host\n  application.\n- **Available capabilities:** The tools, storage, services, network access, and\n  writable locations granted by the host. The character may choose from them\n  but cannot expand them.\n- **Workspace and memory:** The character may choose files, a database, an\n  external memory service, or another suitable representation. The package does\n  not require one memory format.\n- **Session:** A conversation or task context with its own audience, input trust\n  level, and available tools. Public and privileged work use separate Sessions.\n- **Human involvement:** The character may ask a human when its evidence or\n  authority is insufficient. Separately, the runtime pauses operations that\n  require mandatory approval.\n\n## Responsibilities\n\nThe Agent package handles:\n\n- Agent and Session lifecycle;\n- delivery of the character brief to each backend;\n- Session-specific tool visibility;\n- tool validation, execution, policy, and approval flow;\n- interruption, timeout, and cleanup; and\n- structured events and artifacts for the host application.\n\nThe host application remains responsible for:\n\n- YouTube, Twitch, and other platform connections;\n- dashboards and notification delivery;\n- scheduling and wake-up events;\n- credentials, storage limits, encryption, backup, and deletion; and\n- the final decision about external or destructive operations.\n\n## Connect to @aituber-onair/chat\n\nUse both packages together and create the ChatService through a factory. The\nfactory runs once for each Agent Session and receives only the Tool definitions\nvisible to that Session.\n\n```ts\nimport { ChatServiceFactory } from '@aituber-onair/chat';\nimport { createAgent } from '@aituber-onair/agent';\nimport { createChatServiceBackend } from '@aituber-onair/agent/chat';\n\nexport function createStreamStaff(apiKey: string) {\n  const backend = createChatServiceBackend({\n    provider: 'openai',\n    createChatService: ({ tools }) =>\n      ChatServiceFactory.createChatService('openai', {\n        apiKey,\n        tools,\n      }),\n  });\n\n  return createAgent({\n    id: 'stream-staff-miko',\n    brief: 'You are Miko, AI staff responsible for stream operations.',\n    backend,\n    tools: [analyzeComments],\n    policy: {\n      defaultDecision: 'deny',\n      requireApproval: { tools: ['comments.analyze'] },\n    },\n  });\n}\n```\n\nStart separate Sessions for public conversation and private operations. The\nbrief becomes one system message. Each Turn adds the host instruction, context,\nand conversational input as separate messages, so viewer text is never copied\ninto the system message.\n\n```ts\nconst publicSession = await agent.startSession({\n  purpose: 'Respond to public comments',\n  audience: 'public',\n  inputTrust: 'untrusted',\n  allowedTools: ['comments.analyze'],\n});\n\nconst result = await publicSession.run(\n  {\n    instruction: 'Respond only when a reply is useful.',\n    input: {\n      kind: 'viewer-comment',\n      data: { text: viewerComment },\n    },\n  },\n  {\n    onApprovalRequest: async (request, { signal }) =>\n      (await showApprovalDialog(request, { signal })) ? 'allow-once' : 'deny',\n  }\n);\n```\n\nBuilt-in Chat provider names use `ChatServiceFactory` capability metadata as a\nfallback. Supply `backendCapabilities` explicitly for a custom provider.\nProviders without Tool support receive an empty Tool list; for example, the\ncurrent `codex-sdk` Chat provider is text-only and returns completed text rather\nthan streaming deltas.\n\nThe backend keeps conversation and Tool history inside each Session and limits\none Turn to six provider Tool rounds by default. Set `maxToolRounds` to another\npositive integer when needed. `AbortSignal` and Agent timeouts stop the Agent\nTurn and ignore late results. The generic `ChatService` interface does not\nguarantee that an already-running provider request is cancelled at the network\ntransport layer. For the same reason, `backendCapabilities` derived from\nbuilt-in provider metadata declare `interruption: false` and\n`sessionResume: false`: cancel ChatService backend Turns with `AbortSignal` or\ntimeouts, and use a backend that declares `sessionResume`, such as the Codex\napp-server backend, when `agent.resumeSession(...)` is required.\n\n## Tool execution rules\n\n- `allowedTools` controls which Tool definitions a Session exposes to its\n  backend. Tool execution is still denied by default unless the host supplies a\n  policy that allows it or requests approval.\n- Tool input schemas support `type`, `properties`, `required`, `items`, `enum`,\n  `description`, and boolean `additionalProperties`. Unsupported keywords are\n  rejected when the Agent is created instead of being silently ignored.\n- With `session.run(...)`, answer approvals through\n  `options.onApprovalRequest`. With `session.runStream(...)`, either use the\n  same callback or call `session.resolveApproval(requestId, decision)` while\n  consuming events. The approval timer starts when `approval.requested` is\n  emitted, not when a stream consumer reads it, and waits up to\n  `limits.approvalTimeoutMs` (default 30 seconds). Timeout, abort, and Session\n  close deny the request. A callback that throws or returns an invalid decision\n  also denies the request and records its error on `approval.resolved` without\n  turning the callback bug into the Turn's failure reason. Raise the limit in\n  `createAgent` when a human operator answers approvals.\n- `limits.maxToolCallsPerTurn` (default 8) bounds runtime Tool executions per\n  Turn.\n- `sensitiveFields` accepts dot-separated object paths. Matching input values\n  are redacted in Tool and approval events, while the original validated values\n  are copied into the immutable snapshot passed to the host handler. Approval\n  and execution therefore use the same input values.\n- Tool success, handler failure, timeout, and Turn cancellation remain distinct\n  results. A host approval denial never runs the handler. A timeout aborts the\n  handler's signal and fails the Turn; JavaScript cannot forcibly stop a\n  handler that ignores that signal, so side-effecting handlers must cooperate\n  with cancellation and use `toolCallId` as an idempotency key where needed.\n\n## Bootstrapping a character workspace\n\n`agent.bootstrap()` gives a character one bounded, private Turn to inspect its\nassignment and prepare its own operating state. The Agent may choose files,\ntables, indexes, notes, or another representation through the Tools and backend\nworkspace that the host has granted. Agent core does not define their layout.\n\n```ts\nimport {\n  createAgent,\n  defineAgentTool,\n  type AgentWorkspaceMetadataStore,\n} from '@aituber-onair/agent';\n\nconst workspaceMetadata = {\n  load: (agentId) => appDatabase.agentWorkspaces.get(agentId),\n  save: async (metadata, expectedRevision) => {\n    const saved = await appDatabase.agentWorkspaces.compareAndSet(\n      metadata.agentId,\n      expectedRevision,\n      metadata\n    );\n    if (!saved) throw new Error('Workspace metadata changed concurrently.');\n  },\n} satisfies AgentWorkspaceMetadataStore;\n\nconst agent = createAgent({\n  id: 'stream-staff-miko',\n  brief: 'You are Miko, AI staff responsible for stream operations.',\n  backend,\n  tools: [workspaceRead, workspaceWrite],\n  capabilityCatalog: [\n    {\n      id: 'workspace.local',\n      kind: 'workspace',\n      description: 'A workspace limited to this character',\n      requiredTools: ['workspace.read', 'workspace.write'],\n      limits: [{ name: 'maxBytes', value: 1_000_000, unit: 'bytes' }],\n    },\n  ],\n  policy,\n});\n\nconst bootstrap = await agent.bootstrap({\n  workspace: workspaceMetadata,\n  version: 'stream-operations-v1',\n  allowedTools: ['workspace.read', 'workspace.write'],\n  allowedCapabilities: ['workspace.local'],\n  context: {\n    trust: 'trusted',\n    data: { product: 'stream-dashboard' },\n  },\n});\n```\n\nThe metadata store contains only host-owned lifecycle state: `fresh`,\n`bootstrapping`, `ready`, `degraded`, or `failed`. A successful `version` is not\nrun again; it resumes the existing state. A failed attempt can resume the\nprevious backend Session and any partial workspace state. Bump `version` when the brief or required operating\nstate changes.\n\n`save` must compare `expectedRevision` and update the record atomically. A\nstale writer must reject instead of overwriting a newer bootstrap operation.\n\nCapability descriptors are discovery metadata, not permission grants. A\ncapability is shown only when all of its `requiredTools` are visible, and every\nTool call still passes through the runtime policy and approval path described\nabove. Numeric capability limits describe the host's envelope; the Tool handler\nor backend that owns the resource must enforce limits such as workspace bytes.\nEach bootstrap attempt is limited to one Turn. `limits.timeoutMs` bounds that\nTurn (default 60 seconds), and the runtime also limits Tool calls and retry\nattempts. Metadata storage and\nbackend Session start/close are host-owned operations; their implementations\nmust apply appropriate timeouts and cancellation. Bootstrap accepts product\ncontext only with an explicit `trust: 'trusted'` host assertion. Do not mark raw\nviewer input as trusted or inject the entire workspace.\n\nAsking a human is an ordinary host Tool rather than a fixed escalation schema:\n\n```ts\nconst askOperator = defineAgentTool({\n  id: 'human.ask',\n  definition: {\n    name: 'human_ask',\n    description: 'Add a question to the operator review inbox',\n    parameters: {\n      type: 'object',\n      properties: { question: { type: 'string' } },\n      required: ['question'],\n      additionalProperties: false,\n    },\n  },\n  risk: 'write',\n  execute: ({ question }: { question: string }) =>\n    operatorInbox.add({ question }),\n});\n```\n\nThe host may allow this local review request while still requiring a hard\nruntime approval for external or destructive Tools.\n\n## Position in AITuber OnAir\n\n```mermaid\nflowchart LR\n    Host[\"Host application\"] --> Agent[\"@aituber-onair/agent\"]\n    Host --> Events[\"Product events\"]\n    Events --> Agent\n    Agent --> Backend[\"Chat / Codex app-server\"]\n    Agent --> Workspace[\"Restricted workspace\"]\n    Agent --> CI[\"comment-intelligence\"]\n    Agent --> Manneri[\"manneri\"]\n    Agent --> Kizuna[\"kizuna\"]\n    Agent --> Core[\"core adapter\"]\n    Core --> Voice[\"voice\"]\n    Core --> Avatar[\"Avatar / UI\"]\n```\n\nThe existing AITuber OnAir packages remain independently usable. Agent combines\nthem through tools, context, hooks, and events rather than moving their\ndomain logic into one large package.\n\n## Codex app-server integration\n\nUse the Node.js-only entry point when a character needs to inspect or work in a\nrestricted local workspace through Codex. The backend launches the locally\ninstalled Codex CLI over JSONL stdio and uses that CLI's existing\nauthentication. After signing in with `codex login`, this can use the ChatGPT\nplan access supported by Codex without passing an OpenAI API key to Agent.\n\nAny Codex environment at or above the minimum version can be used; installing\none exact CLI version is not required. The required app-server schema elements\nwere confirmed in Codex CLI `0.136.0`, while this integration was verified\nagainst `0.145.0`. The minimum records a schema check, not a live connection\ntest. The backend errors only below the minimum or when a method it needs is\nunavailable in the installed CLI.\n\n```bash\nnpm install --global @openai/codex\ncodex login\n```\n\n```ts\nimport { createAgent } from '@aituber-onair/agent';\nimport { createCodexAppServerBackend } from '@aituber-onair/agent/codex-app-server';\n\nconst backend = createCodexAppServerBackend({\n  // PATH lookup is never implicit. Alternatively, provide an absolute codexPath.\n  allowPathLookup: true,\n  workingDirectory: '/absolute/path/to/character-workspace',\n  sandbox: 'read-only',\n  approvalPolicy: 'on-request',\n});\n\nconst agent = createAgent({\n  id: 'stream-operations-staff',\n  brief:\n    'You are AI staff responsible for monitoring stream operations. Inspect available state, report anomalies, and escalate decisions that require the operator.',\n  backend,\n});\n\nconst session = await agent.startSession({\n  purpose: 'Review the latest stream report',\n  audience: 'owner',\n  inputTrust: 'trusted',\n});\n\ntry {\n  for await (const event of session.runStream({\n    instruction: 'Inspect the workspace and summarize issues that need attention.',\n  })) {\n    if (event.type === 'approval.requested') {\n      // Replace this with an operator decision in a real application.\n      await session.resolveApproval(event.request.id, 'deny');\n    }\n    if (event.type === 'message.completed') console.log(event.text);\n  }\n} finally {\n  await session.close();\n  await agent.close();\n}\n```\n\nHosts that require the verified version can set\n`compatibility: { onMismatch: 'reject' }`. To use a specific CLI version\nwithout changing the global installation, pass its absolute path as\n`codexPath` instead of enabling PATH lookup.\n\n`read-only` and `on-request` are also the defaults. A host decision of\n`allow-once` maps to Codex `accept`; `deny` maps to `decline`; interruption,\ntimeout, and shutdown map to `cancel`. The backend never grants Codex\n`acceptForSession`, because that would widen permission beyond one host\ndecision.\n\nThe Agent brief is applied as Codex developer instructions for new and resumed\nThreads. On the first Turn after a cold resume, the backend also includes one\nhost-controlled brief reminder to mitigate the current resume behavior tracked\nin [openai/codex#19045](https://github.com/openai/codex/issues/19045). Persist\n`session.backendSessionId` in host-owned state and pass it to\n`agent.resumeSession(...)` when resuming.\n\nThe entry point intentionally supports only the verified stable protocol\nsubset:\n\n- local stdio transport on Node.js; no remote WebSocket transport\n- Thread start/resume and Turn start/interrupt; Turn steering exists on the\n  Codex backend Session type but is not yet exposed through `AgentSession`\n- account and model reads (`backend.readAccount()`, `backend.listModels()`),\n  streamed messages, safe artifacts, and command/file approval requests\n- no experimental API, `thread/shellCommand`, raw Codex\n  configuration/authentication access, or dynamic Tools\n- no Agent domain Tools in Codex Sessions; use the ChatService backend for\n  host-executed domain integrations\n\nSee the official\n[Codex App Server documentation](https://developers.openai.com/codex/app-server)\nfor the underlying protocol.\n\n## Observing progress\n\n`session.run(...)` resolves with the final result, and\n`session.runStream(...)` yields the same execution as typed events. The\n`AgentEvent` union contains:\n\n| Event | Meaning |\n| --- | --- |\n| `session.started` / `session.resumed` / `session.closed` | Session lifecycle |\n| `turn.started` | A Turn began |\n| `message.delta` / `message.completed` | Streaming text and the final message |\n| `tool.requested` / `tool.started` / `tool.completed` / `tool.failed` | Tool call lifecycle |\n| `approval.requested` / `approval.resolved` | Approval flow; resolve with `session.resolveApproval(...)` |\n| `artifact.created` | A structured `AgentArtifact` was produced |\n| `turn.completed` / `turn.interrupted` / `turn.failed` | Exactly one of these ends every Turn |\n\nRuntime failures are typed error classes exported from the base entry point,\nsuch as `AgentPolicyDeniedError`, `AgentApprovalTimeoutError`,\n`AgentCapabilityError`, `AgentToolValidationError`, and\n`AgentBackendCompatibilityError`.\n\n## State management\n\n| State | Managed by |\n| --- | --- |\n| Character identity and assignment brief | Host application |\n| Character-created notes, procedures, and database | Host-managed workspace; the character organizes the content |\n| Current conversation and task state | Agent Session and backend |\n| Viewer safety history | `comment-intelligence` |\n| Viewer relationships and points | `kizuna` or a host-selected service |\n| Approvals and external-operation audit | Host application |\n\n## Safety principles\n\n- Treat viewer comments and other public input as untrusted data.\n- Keep untrusted data separate from host instructions and the character brief.\n- Treat analysis output as trusted only after the host validates and accepts it.\n- Expose only the minimum tools required by each Session.\n- Never let character-created memory, skills, or configuration expand\n  permissions.\n- Require host policy and approval for writes, external sends, and destructive\n  operations.\n- Treat tool results, not model claims, as evidence that an action succeeded.\n- Keep API keys, tokens, and authentication files out of events and logs.\n- Keep privileged Node.js backends separate from browser entry points.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-2bd38c9d9baed13637cfd0b54579cde0"}