@geminixiang/mikan 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,39 @@ change in a minor release.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.0.2] - 2026-10-08
15
+
16
+ ### Changed
17
+
18
+ - The system prompt lists the folders already in the office's `scratch/` and asks the agent
19
+ to reuse an existing clone, with `git fetch` or `git worktree add`, before cloning again.
20
+ A new thread previously could not tell a repository was already there and cloned another copy.
21
+
22
+ ### Fixed
23
+
24
+ - A command left running in a container sandbox by a crashed or killed daemon is ended
25
+ when the daemon starts again, so a resumed or repeated run does not race the old copy.
26
+
27
+ ## [1.0.1] - 2026-10-07
28
+
29
+ ### Changed
30
+
31
+ - `mikan onboard` asks for a sandbox image instead of a sandbox mode: the published
32
+ `mikan-sandbox` image (recommended), `debian:trixie-slim`, or any other image. It writes
33
+ `~/.mikan/ecosystem.config.cjs` with that image and prints the `docker pull` and
34
+ `pm2 start` commands, so the PM2 file no longer has to be downloaded. An existing
35
+ ecosystem file is kept. `host` and `container:<name>` still work through `--sandbox` but
36
+ are no longer offered.
37
+ - The README and the quick start and deployment guides, in all four languages, follow the
38
+ onboard flow: install, `mikan onboard`, `docker pull`, and `pm2 start` with the generated
39
+ ecosystem file. The docs site has a new logo, favicon, hero image, and footer.
40
+
41
+ ### Fixed
42
+
43
+ - Stopping a run, or a command timing out, now ends the command inside the container
44
+ sandbox. Before, only the host `docker exec` client was killed and the command kept
45
+ running in the container. Images without `setsid` keep the old behavior.
46
+
14
47
  ## [1.0.0] - 2026-10-06
15
48
 
16
49
  1.0.0 is the first stable release. It replaces the 0.5.3 runtime, which ran on
package/README.md CHANGED
@@ -1,13 +1,17 @@
1
1
  <p align="center">
2
- <img src="src/content/docs/assets/mikan-office-hero.png" alt="mikan office architecture — conversation-scoped workspaces and sandbox execution" width="100%">
2
+ <img src="src/content/docs/assets/mikan-hero.png" alt="mikan — your team's AI coding agent, in chat" width="100%">
3
3
  </p>
4
4
 
5
- # @geminixiang/mikan
5
+ # <img src="src/content/docs/assets/mikan-logo.png" alt="" width="44" align="top"> @geminixiang/mikan
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@geminixiang/mikan.svg)](https://www.npmjs.com/package/@geminixiang/mikan)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
- Self-hosted AI coding agent for organizations: every Slack channel and DM gets its own sandboxed workspace, credentials, and memory. Also speaks Telegram, Discord, and GitHub.
10
+ Self-hosted AI coding agent that works in your team's chat.
11
+
12
+ - **One office per conversation** — every Slack channel and DM gets its own workspace, sandbox, credentials, and memory; each thread runs as its own session.
13
+ - **Where your team already talks** — Slack, Telegram, Discord, and GitHub (experimental).
14
+ - **Real tools, contained** — in `image:` mode, commands run in a per-office Docker sandbox with credentials from that office's vault.
11
15
 
12
16
  Upgrading from 0.5.3? Follow [Upgrading from 0.5.3](src/content/docs/deployment.mdx#upgrading-from-053); `mikan migrate` converts the state in place.
13
17
 
@@ -21,8 +25,8 @@ mikan keeps the chat record, agent session, and execution runtime separate:
21
25
 
22
26
  - **Conversation office** is the unit everything else hangs off: one conversation's working area plus its own sandbox runtime. An office is identified by its platform and raw conversation id, and its directory is named by an office key (`v1-<platform>-<readable-id>-<hash>`), so two platforms can never collide on the same raw id.
23
27
  - **Chat / conversation data** is the platform-facing record: `log.jsonl`, attachments, and conversation files.
24
- - **Session orchestration** turns platform events into agent runs, handles top-level/thread scopes, and persists structured context in host-only session files under the State dir.
25
- - **mikan agent harness** (`src/harness/`, built on pi-agent-core and pi-ai) owns run preparation, authorized prompts/tools, response presentation, and native Pi session integration. Pi handles the model/tool loop, persistence, compaction, retries, and cancellation.
28
+ - **Session orchestration** turns platform events into agent runs, handles top-level/thread scopes, and persists agent sessions in one host-only SQLite `sessions.db` per office under the State dir.
29
+ - **mikan agent harness** (`src/harness/`, built on pi-durable and pi-ai) owns run preparation, authorized prompts/tools, response presentation, and native Pi session integration. Pi handles the model/tool loop, persistence, compaction, retries, and cancellation.
26
30
  - **Sandbox runtime** is where tool commands execute: host, or a Docker container or image.
27
31
  - **Vault** provides runtime credentials as env vars and mounted secret files.
28
32
 
@@ -31,7 +35,7 @@ mikan keeps the chat record, agent session, and execution runtime separate:
31
35
  - **Multi-platform** — Slack, Telegram, Discord, and GitHub (experimental) adapters
32
36
  - **Concurrent conversations** — Slack threads, Discord replies/threads, and Telegram reply chains run as independent sessions
33
37
  - **Conversation offices** — one office directory and one sandbox runtime per conversation, with public/private visibility derived from the platform conversation type
34
- - **Sandbox execution** — host, shared container, or per-conversation managed container
38
+ - **Sandbox execution** — one managed Docker container per conversation office
35
39
  - **Credential vaults** — `/login` stores credentials under `~/.mikan` and injects env into sandbox runs
36
40
  - **Web session viewer** — read-only web view of the current session via `session` / `/session`
37
41
  - **Persistent memory** — workspace-level and per-office `MEMORY.md`
@@ -39,72 +43,22 @@ mikan keeps the chat record, agent session, and execution runtime separate:
39
43
  - **Events** — the agent schedules one-shot or recurring runs with its `event` tool
40
44
  - **Multi-provider** — any provider/model supported by `pi-ai`
41
45
 
42
- ## Requirements
43
-
44
- - Node.js >= 24.15.0
45
-
46
- ## Installation
47
-
48
- ```bash
49
- npm install -g @geminixiang/mikan
50
- ```
51
-
52
- Or from source:
53
-
54
- ```bash
55
- npm install && npm run build
56
- ```
57
-
58
46
  ## Quick Start
59
47
 
60
- Run mikan under [PM2](https://pm2.keymetrics.io/) so it stays up, restarts after failures, and starts on boot:
48
+ You need Node.js 24.15 or later and Docker.
61
49
 
62
50
  ```bash
63
51
  npm i -g @geminixiang/mikan pm2
64
-
65
- # One-time setup: platform tokens, LLM provider, and sandbox mode,
66
- # written to ~/.mikan/settings.json and ~/.mikan/mikan.env (0600)
67
- mikan onboard
68
-
69
- # Pull the sandbox image the default deployment runs tools in
52
+ mikan onboard # pick a chat platform, a model, and a sandbox image
70
53
  docker pull ghcr.io/geminixiang/mikan-sandbox:latest
71
-
72
- # Grab the maintained ecosystem file (supervision only)
73
- curl -O https://raw.githubusercontent.com/geminixiang/mikan/main/deploy/pm2/ecosystem.config.cjs
74
-
75
- pm2 start ecosystem.config.cjs
76
- pm2 save
77
- pm2 startup # run the printed command to enable boot autostart
54
+ pm2 start ~/.mikan/ecosystem.config.cjs && pm2 save
78
55
  ```
79
56
 
80
- Each file has one job: `settings.json` holds behavior (model, sandbox limits, reply modes — the Admin surface), `~/.mikan/mikan.env` holds secrets and platform tokens, and `ecosystem.config.cjs` holds process supervision only. mikan keeps all of them in `~/.mikan`. In `ecosystem.config.cjs`, `args` holds the sandbox mode and an optional working directory (`mikan --help` documents the flags):
57
+ That's it: `@mention` the bot in a channel or send it a DM. `mikan onboard` prints the exact `docker pull` line for the image you picked; the recommended image is about 900 MB, so the pull takes a few minutes the first time. Run `pm2 startup` once if mikan should start on boot.
81
58
 
82
- ```js
83
- args: "--sandbox=image:ghcr.io/geminixiang/mikan-sandbox:latest",
84
- ```
59
+ Everything lives in `~/.mikan`: `settings.json` for behavior (model, sandbox limits, reply modes), `mikan.env` for secrets and platform tokens, and `ecosystem.config.cjs` for process supervision. To add another platform later, put its tokens in `mikan.env` and run `pm2 reload ~/.mikan/ecosystem.config.cjs` so pm2 rereads the file; `mikan env` lists every variable mikan reads.
85
60
 
86
- To add a platform later, set its tokens in `~/.mikan/mikan.env`; you can run multiple platforms at once. `mikan env` prints the full inventory and what is currently set:
87
-
88
- ```bash
89
- SLACK_APP_TOKEN=xapp-...
90
- SLACK_BOT_TOKEN=xoxb-...
91
- TELEGRAM_BOT_TOKEN=123456:ABC-...
92
- DISCORD_BOT_TOKEN=MTI...
93
- GITHUB_AGENT_TOKEN=github_pat_...
94
- GITHUB_WEBHOOK_SECRET=...
95
- ```
96
-
97
- GitHub also needs the repositories it answers in, as `"github": { "repos": ["acme/*"] }` in `~/.mikan/settings.json`.
98
-
99
- Tail logs with `pm2 logs mikan`; upgrade with `npm i -g @geminixiang/mikan`, check `mikan migrate --sandbox=<mode> --dry-run` for pending State migrations, then `pm2 reload mikan`. See [the deployment guide](src/content/docs/deployment.mdx) for sandbox images, graceful shutdown, and the health endpoint.
100
-
101
- For a one-off foreground run, the same CLI works directly:
102
-
103
- ```bash
104
- mikan [--sandbox=<mode>] [<working-directory>]
105
- ```
106
-
107
- The working directory is optional: it defaults to `~/.mikan/workspace` and is created on first run.
61
+ Upgrade with `npm i -g @geminixiang/mikan`, check `mikan migrate --sandbox=image:<image> --dry-run` for pending State migrations, then `pm2 reload mikan`. See [the deployment guide](src/content/docs/deployment.mdx) for graceful shutdown and the health endpoint.
108
62
 
109
63
  ## Platforms
110
64
 
@@ -117,15 +71,11 @@ Slack threads, Discord replies/threads, and Telegram reply chains are mapped to
117
71
 
118
72
  ## Sandbox
119
73
 
120
- | Mode | Description |
121
- | ------------------ | ----------------------------------------------------------------------------- |
122
- | `host` (default) | Run on host; no vault env injection |
123
- | `container:<name>` | Run in an existing shared container; everyone sharing it shares its one vault |
124
- | `image:<image>` | Auto-provision one Docker container and one vault per conversation office |
74
+ Use `image:<image>`: mikan starts one Docker container and one credential vault per conversation office, and enforces office visibility. `mikan onboard` sets it up.
125
75
 
126
- Each office is **public** or **private**, following the Slack conversation type: public channels are public — every other office can read them (read-only, under `/workspace/public/`) and they may write the shared `MEMORY.md` and `skills/`. Private channels, DMs, group DMs, and externally shared channels are private — visible only to themselves, reading shared knowledge and public offices without writing back. Unknown conversation kinds are private. The admin portal or `/pi-sandbox visibility private` can narrow a public channel; nothing can widen beyond Slack. Visibility governs data access only; execution isolation is unaffected.
76
+ `host` (run directly on the machine) and `container:<name>` (one shared, existing container) still work but are not recommended: they give the agent the whole machine or one shared filesystem, skip `/login` credential injection, and do not enforce office visibility.
127
77
 
128
- Only `image:*` enforces visibility. `host` and `container:*` share one filesystem and are trusted deployments; a private office there is served with a logged warning.
78
+ Each office is **public** or **private**, following the Slack conversation type: public channels are public — every other office can read them (read-only, under `/workspace/public/`) and they may write the shared `MEMORY.md` and `skills/`. Private channels, DMs, group DMs, and externally shared channels are private — visible only to themselves, reading shared knowledge and public offices without writing back. Unknown conversation kinds are private. The admin portal or `/pi-sandbox visibility private` can narrow a public channel; nothing can widen beyond Slack. Visibility governs data access only; execution isolation is unaffected.
129
79
 
130
80
  For routing, mounts, vault behavior, and managed container details, see [src/content/docs/sandbox.mdx](src/content/docs/sandbox.mdx).
131
81
 
@@ -144,55 +94,9 @@ For routing, mounts, vault behavior, and managed container details, see [src/con
144
94
 
145
95
  `session` is the only command accepted without a leading slash. See [src/content/docs/commands.mdx](src/content/docs/commands.mdx) for the full command reference and web session viewer setup.
146
96
 
147
- ## Configuration
148
-
149
- mikan reads global settings from `~/.mikan/settings.json`; host-only per-conversation overrides live at `~/.mikan/conversations/<office-key>/settings.json`. Legacy workspace settings are migrated once, then ignored.
150
-
151
- ```json
152
- {
153
- "llm": {
154
- "provider": "anthropic",
155
- "model": "claude-sonnet-4-6",
156
- "thinkingLevel": "off"
157
- }
158
- }
159
- ```
160
-
161
- See [src/content/docs/configuration.md](src/content/docs/configuration.md) for all fields.
162
-
163
- ## Data layout
164
-
165
- ```text
166
- ~/.mikan/
167
- ├── settings.json
168
- ├── mikan.env
169
- ├── models.json
170
- ├── migrations.json
171
- ├── office-registry.json
172
- ├── conversations/
173
- │ └── <office-key>/
174
- │ ├── settings.json
175
- │ ├── sessions.db
176
- │ └── events/
177
- └── vaults/
178
-
179
- <working-directory>/
180
- ├── MEMORY.md
181
- ├── skills/
182
- ├── agents/
183
- └── <office-key>/
184
- ├── MEMORY.md
185
- ├── SYSTEM.md
186
- ├── log.jsonl
187
- ├── attachments/
188
- ├── scratch/
189
- └── skills/
190
- ```
191
-
192
- Office directories are named by office key (`v1-<platform>-<readable-id>-<hash>`) and are not reversible to a raw platform id, so `office-registry.json` records each office's `(platform, conversationId)`. `mikan office list` prints the registered offices. `mikan migrate` upgrades a 0.5.3 install to this layout; see [State migrations](src/content/docs/deployment.mdx).
193
-
194
97
  ## More docs
195
98
 
99
+ - [Configuration](src/content/docs/configuration.md)
196
100
  - [Events](src/content/docs/events.md)
197
101
  - [Skills](src/content/docs/skills.md)
198
102
  - [Deployment](src/content/docs/deployment.mdx)
@@ -203,6 +107,7 @@ Office directories are named by office key (`v1-<platform>-<readable-id>-<hash>`
203
107
  ## Development
204
108
 
205
109
  ```bash
110
+ npm install && npm run build
206
111
  npm run dev
207
112
  npm test
208
113
  npm run lint
@@ -1 +1 @@
1
- {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA+H5C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAUhG;AAUD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,SAAS,EACb,KAAK,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CAAC,MAAM,CAAC,CAiDjB;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzE"}
1
+ {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAkI5C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAUhG;AAUD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,SAAS,EACb,KAAK,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CAAC,MAAM,CAAC,CA2DjB;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzE"}
@@ -5,6 +5,7 @@ import { ENV_MANIFEST, envSetReport, readEnv } from "../env-manifest.js";
5
5
  import { createGlobalSettingsFile } from "../settings/index.js";
6
6
  import { recordAllMigrations } from "../migrations/index.js";
7
7
  import { atomicWritePrivateFile } from "../file-guards.js";
8
+ import { MINIMAL_SANDBOX_IMAGE, PM2_ECOSYSTEM_FILENAME, PUBLISHED_SANDBOX_IMAGE, renderPm2Ecosystem, } from "./pm2-ecosystem.js";
8
9
  class OnboardAborted extends Error {
9
10
  constructor() {
10
11
  super("Onboarding aborted.");
@@ -92,13 +93,18 @@ async function askLlm(io, env) {
92
93
  }, null, 2);
93
94
  return { llm: { provider, model: ids[0] }, modelsJson };
94
95
  }
95
- async function askSandbox(io) {
96
- io.print("\nStep 3/3 — sandbox (where agent commands run)");
96
+ async function askSandboxImage(io) {
97
+ io.print("\nStep 3/3 — sandbox image (one Docker container per conversation; needs Docker)");
97
98
  const index = await askChoice(io, [
98
- "host — run directly on this machine",
99
- "image — one Docker container per conversation (needs Docker)",
100
- ], "Sandbox");
101
- return index === 1 ? "image:ghcr.io/geminixiang/mikan-sandbox:latest" : undefined;
99
+ `${PUBLISHED_SANDBOX_IMAGE} (recommended: git, gh, node, python, curl)`,
100
+ `${MINIMAL_SANDBOX_IMAGE} (minimal: shell only, no git or package installs)`,
101
+ "Another image from Docker Hub or a registry…",
102
+ ], "Sandbox image");
103
+ if (index === 0)
104
+ return PUBLISHED_SANDBOX_IMAGE;
105
+ if (index === 1)
106
+ return MINIMAL_SANDBOX_IMAGE;
107
+ return askRequired(io, " Image (e.g. registry/name:tag): ");
102
108
  }
103
109
  export function renderEnvFile(existing, vars) {
104
110
  const pending = new Map(Object.entries(vars));
@@ -132,13 +138,13 @@ export async function runOnboardWizard(stateDir, io, paths) {
132
138
  const env = {};
133
139
  const { githubRepos } = await askAdapter(io, env);
134
140
  const { llm, modelsJson } = await askLlm(io, env);
135
- const sandboxArg = await askSandbox(io);
141
+ const image = await askSandboxImage(io);
136
142
  const modelsPath = join(stateDir, "models.json");
137
143
  if (modelsJson && existsSync(modelsPath)) {
138
144
  io.print(`models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`);
139
145
  return 1;
140
146
  }
141
- io.print(`Review settings\nProvider: ${llm.provider}\nModel: ${llm.model}\nSandbox: ${sandboxArg ?? "host"}\nSettings: ${settingsPath}\nCredentials: ${paths?.envFilePath ?? join(stateDir, "mikan.env")}${modelsJson ? `\nModels: ${modelsPath}` : ""}\nSecrets are hidden.`);
147
+ io.print(`Review settings\nProvider: ${llm.provider}\nModel: ${llm.model}\nSandbox: image:${image}\nSettings: ${settingsPath}\nCredentials: ${paths?.envFilePath ?? join(stateDir, "mikan.env")}${modelsJson ? `\nModels: ${modelsPath}` : ""}\nSecrets are hidden.`);
142
148
  if (!(await io.confirm("Save these settings?"))) {
143
149
  io.print("Onboarding cancelled; nothing was written.");
144
150
  return 1;
@@ -155,10 +161,18 @@ export async function runOnboardWizard(stateDir, io, paths) {
155
161
  atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));
156
162
  io.print(`Wrote ${envFilePath} (0600)`);
157
163
  io.print(`\n${envSetReport((name) => env[name] ?? readEnv(name))}`);
158
- const sandboxFlag = sandboxArg ? ` --sandbox ${sandboxArg}` : "";
164
+ const ecosystemPath = join(stateDir, PM2_ECOSYSTEM_FILENAME);
165
+ if (existsSync(ecosystemPath)) {
166
+ io.print(`Kept existing ${ecosystemPath}`);
167
+ }
168
+ else {
169
+ atomicWritePrivateFile(ecosystemPath, renderPm2Ecosystem(image));
170
+ io.print(`Wrote ${ecosystemPath}`);
171
+ }
159
172
  io.print("Next steps:");
160
- io.print(` pm2: pm2 start ecosystem.config.cjs (loads ${envFilePath}; see deploy/pm2/)`);
161
- io.print(` direct: set -a; source ${envFilePath}; set +a; mikan${sandboxFlag}`);
173
+ io.print(` 1. docker pull ${image}`);
174
+ io.print(` 2. pm2 start ${ecosystemPath} && pm2 save`);
175
+ io.print(` Foreground instead: set -a; source ${envFilePath}; set +a; mikan --sandbox=image:${image}`);
162
176
  return 0;
163
177
  }
164
178
  export async function runOnboardCommand(stateDir) {
@@ -1 +1 @@
1
- {"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as prompts from \"@clack/prompts\";\nimport { ENV_MANIFEST, envSetReport, readEnv } from \"../env-manifest.js\";\nimport { createGlobalSettingsFile } from \"../settings/index.js\";\nimport { recordAllMigrations } from \"../migrations/index.js\";\nimport type { OnboardLlmChoice } from \"../types.js\";\nimport { atomicWritePrivateFile } from \"../file-guards.js\";\nimport type { OnboardIo } from \"./types.js\";\n\nclass OnboardAborted extends Error {\n constructor() {\n super(\"Onboarding aborted.\");\n }\n}\n\nasync function answer<T>(result: Promise<T>): Promise<Exclude<T, symbol>> {\n const value = await result;\n if (prompts.isCancel(value)) throw new OnboardAborted();\n return value as Exclude<T, symbol>;\n}\n\nfunction terminalIo(): OnboardIo {\n return {\n ask: (message) => answer(prompts.text({ message: message.trim() })),\n askSecret: (message) => answer(prompts.password({ message: message.trim() })),\n select: (message, labels) =>\n answer(\n prompts.select({\n message,\n options: labels.map((label, value) => ({ label, value })),\n }),\n ),\n confirm: (message) => answer(prompts.confirm({ message, initialValue: true })),\n print: (line) => prompts.log.info(line.trim()),\n close: () => {},\n };\n}\n\nasync function askRequired(io: OnboardIo, query: string, secret = false): Promise<string> {\n for (;;) {\n const value = (secret ? await io.askSecret(query) : await io.ask(query)).trim();\n if (value) return value;\n io.print(\" (required)\");\n }\n}\n\nasync function askChoice(io: OnboardIo, labels: string[], prompt: string): Promise<number> {\n return io.select(prompt, labels);\n}\n\nasync function askAdapter(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ githubRepos?: string[] }> {\n const platforms = ENV_MANIFEST.filter((group) => group.kind === \"platform\");\n io.print(\"\\nStep 1/3 — chat adapter\");\n const index = await askChoice(\n io,\n platforms.map((g) => g.title),\n \"Platform\",\n );\n const group = platforms[index]!;\n for (const spec of group.vars.filter((v) => v.required)) {\n env[spec.name] = await askRequired(io, ` ${spec.name} (${spec.doc}): `, spec.secret);\n }\n if (group.key !== \"github\") return {};\n const repos = await askRequired(io, \" Repositories (owner/repo or owner/*, comma-separated): \");\n return {\n githubRepos: repos\n .split(\",\")\n .map((repo) => repo.trim())\n .filter(Boolean),\n };\n}\n\nasync function askLlm(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ llm: OnboardLlmChoice; modelsJson?: string }> {\n io.print(\"\\nStep 2/3 — LLM provider\");\n const index = await askChoice(\n io,\n [\n \"Anthropic (ANTHROPIC_API_KEY)\",\n \"OpenAI (OPENAI_API_KEY)\",\n \"Custom OpenAI-compatible endpoint (vLLM, proxy, …) — writes models.json\",\n ],\n \"Provider\",\n );\n if (index === 0) {\n env.ANTHROPIC_API_KEY = await askRequired(io, \" ANTHROPIC_API_KEY: \", true);\n return { llm: { provider: \"anthropic\", model: \"claude-sonnet-4-6\" } };\n }\n if (index === 1) {\n env.OPENAI_API_KEY = await askRequired(io, \" OPENAI_API_KEY: \", true);\n return { llm: { provider: \"openai\", model: \"gpt-5.2\" } };\n }\n const provider = (await io.ask(\" Provider name [custom]: \")).trim() || \"custom\";\n const baseUrl = await askRequired(io, \" Base URL (e.g. http://host:8080/v1): \");\n const apiKey = (await io.askSecret(\" API key (empty if none): \")).trim();\n const ids = (await askRequired(io, \" Model ids (comma-separated): \"))\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const modelsJson = JSON.stringify(\n {\n providers: {\n [provider]: {\n api: \"openai-completions\",\n baseUrl,\n apiKey: apiKey ? apiKey : undefined,\n models: ids.map((id) => ({ id })),\n },\n },\n },\n null,\n 2,\n );\n return { llm: { provider, model: ids[0]! }, modelsJson };\n}\n\nasync function askSandbox(io: OnboardIo): Promise<string | undefined> {\n io.print(\"\\nStep 3/3 — sandbox (where agent commands run)\");\n const index = await askChoice(\n io,\n [\n \"host — run directly on this machine\",\n \"image — one Docker container per conversation (needs Docker)\",\n ],\n \"Sandbox\",\n );\n return index === 1 ? \"image:ghcr.io/geminixiang/mikan-sandbox:latest\" : undefined;\n}\n\nexport function renderEnvFile(existing: string | undefined, vars: Record<string, string>): string {\n const pending = new Map(Object.entries(vars));\n const lines: string[] = [];\n for (const line of existing ? existing.split(\"\\n\") : []) {\n const rendered = rewriteEnvLine(line, pending);\n if (rendered.trim() || lines.length > 0) lines.push(rendered);\n }\n while (lines.at(-1) === \"\") lines.pop();\n for (const [key, value] of pending) lines.push(`${key}=${value}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction rewriteEnvLine(line: string, pending: Map<string, string>): string {\n const key = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line)?.[1];\n if (key === undefined || !pending.has(key)) return line;\n const value = pending.get(key);\n pending.delete(key);\n return `${key}=${value}`;\n}\n\nexport async function runOnboardWizard(\n stateDir: string,\n io: OnboardIo,\n paths?: { envFilePath?: string },\n): Promise<number> {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n io.print(`Global settings already exists at ${settingsPath}.`);\n io.print(\"Remove it first to re-run onboarding, or edit it directly.\");\n return 1;\n }\n\n const env: Record<string, string> = {};\n const { githubRepos } = await askAdapter(io, env);\n const { llm, modelsJson } = await askLlm(io, env);\n const sandboxArg = await askSandbox(io);\n\n const modelsPath = join(stateDir, \"models.json\");\n if (modelsJson && existsSync(modelsPath)) {\n io.print(\n `models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`,\n );\n return 1;\n }\n io.print(\n `Review settings\\nProvider: ${llm.provider}\\nModel: ${llm.model}\\nSandbox: ${sandboxArg ?? \"host\"}\\nSettings: ${settingsPath}\\nCredentials: ${paths?.envFilePath ?? join(stateDir, \"mikan.env\")}${modelsJson ? `\\nModels: ${modelsPath}` : \"\"}\\nSecrets are hidden.`,\n );\n if (!(await io.confirm(\"Save these settings?\"))) {\n io.print(\"Onboarding cancelled; nothing was written.\");\n return 1;\n }\n\n createGlobalSettingsFile(stateDir, { llm, githubRepos });\n recordAllMigrations(stateDir);\n io.print(`\\nWrote ${settingsPath}`);\n\n if (modelsJson) {\n atomicWritePrivateFile(modelsPath, `${modelsJson}\\n`);\n io.print(`Wrote ${modelsPath}`);\n }\n\n const envFilePath = paths?.envFilePath ?? join(stateDir, \"mikan.env\");\n const existing = existsSync(envFilePath) ? readFileSync(envFilePath, \"utf-8\") : undefined;\n atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));\n io.print(`Wrote ${envFilePath} (0600)`);\n\n io.print(`\\n${envSetReport((name) => env[name] ?? readEnv(name))}`);\n\n const sandboxFlag = sandboxArg ? ` --sandbox ${sandboxArg}` : \"\";\n io.print(\"Next steps:\");\n io.print(` pm2: pm2 start ecosystem.config.cjs (loads ${envFilePath}; see deploy/pm2/)`);\n io.print(` direct: set -a; source ${envFilePath}; set +a; mikan${sandboxFlag}`);\n return 0;\n}\n\nexport async function runOnboardCommand(stateDir: string): Promise<number> {\n if (!process.stdin.isTTY) {\n const settingsPath = createGlobalSettingsFile(stateDir);\n recordAllMigrations(stateDir);\n console.log(`Created global settings at ${settingsPath}`);\n console.log(\n `Review the file, then start mikan (workspace defaults to ${join(stateDir, \"workspace\")}).`,\n );\n return 0;\n }\n prompts.intro(\"Mikan setup · arrows to choose · Esc / Ctrl+C to cancel\");\n const io = terminalIo();\n try {\n const code = await runOnboardWizard(stateDir, io);\n prompts.outro(code === 0 ? \"Setup complete\" : \"Setup not saved\");\n return code;\n } catch (err) {\n if (err instanceof OnboardAborted) {\n prompts.cancel(\"Onboarding aborted; nothing was written.\");\n return 1;\n }\n throw err;\n } finally {\n io.close();\n }\n}\n"]}
1
+ {"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as prompts from \"@clack/prompts\";\nimport { ENV_MANIFEST, envSetReport, readEnv } from \"../env-manifest.js\";\nimport { createGlobalSettingsFile } from \"../settings/index.js\";\nimport { recordAllMigrations } from \"../migrations/index.js\";\nimport type { OnboardLlmChoice } from \"../types.js\";\nimport { atomicWritePrivateFile } from \"../file-guards.js\";\nimport {\n MINIMAL_SANDBOX_IMAGE,\n PM2_ECOSYSTEM_FILENAME,\n PUBLISHED_SANDBOX_IMAGE,\n renderPm2Ecosystem,\n} from \"./pm2-ecosystem.js\";\nimport type { OnboardIo } from \"./types.js\";\n\nclass OnboardAborted extends Error {\n constructor() {\n super(\"Onboarding aborted.\");\n }\n}\n\nasync function answer<T>(result: Promise<T>): Promise<Exclude<T, symbol>> {\n const value = await result;\n if (prompts.isCancel(value)) throw new OnboardAborted();\n return value as Exclude<T, symbol>;\n}\n\nfunction terminalIo(): OnboardIo {\n return {\n ask: (message) => answer(prompts.text({ message: message.trim() })),\n askSecret: (message) => answer(prompts.password({ message: message.trim() })),\n select: (message, labels) =>\n answer(\n prompts.select({\n message,\n options: labels.map((label, value) => ({ label, value })),\n }),\n ),\n confirm: (message) => answer(prompts.confirm({ message, initialValue: true })),\n print: (line) => prompts.log.info(line.trim()),\n close: () => {},\n };\n}\n\nasync function askRequired(io: OnboardIo, query: string, secret = false): Promise<string> {\n for (;;) {\n const value = (secret ? await io.askSecret(query) : await io.ask(query)).trim();\n if (value) return value;\n io.print(\" (required)\");\n }\n}\n\nasync function askChoice(io: OnboardIo, labels: string[], prompt: string): Promise<number> {\n return io.select(prompt, labels);\n}\n\nasync function askAdapter(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ githubRepos?: string[] }> {\n const platforms = ENV_MANIFEST.filter((group) => group.kind === \"platform\");\n io.print(\"\\nStep 1/3 — chat adapter\");\n const index = await askChoice(\n io,\n platforms.map((g) => g.title),\n \"Platform\",\n );\n const group = platforms[index]!;\n for (const spec of group.vars.filter((v) => v.required)) {\n env[spec.name] = await askRequired(io, ` ${spec.name} (${spec.doc}): `, spec.secret);\n }\n if (group.key !== \"github\") return {};\n const repos = await askRequired(io, \" Repositories (owner/repo or owner/*, comma-separated): \");\n return {\n githubRepos: repos\n .split(\",\")\n .map((repo) => repo.trim())\n .filter(Boolean),\n };\n}\n\nasync function askLlm(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ llm: OnboardLlmChoice; modelsJson?: string }> {\n io.print(\"\\nStep 2/3 — LLM provider\");\n const index = await askChoice(\n io,\n [\n \"Anthropic (ANTHROPIC_API_KEY)\",\n \"OpenAI (OPENAI_API_KEY)\",\n \"Custom OpenAI-compatible endpoint (vLLM, proxy, …) — writes models.json\",\n ],\n \"Provider\",\n );\n if (index === 0) {\n env.ANTHROPIC_API_KEY = await askRequired(io, \" ANTHROPIC_API_KEY: \", true);\n return { llm: { provider: \"anthropic\", model: \"claude-sonnet-4-6\" } };\n }\n if (index === 1) {\n env.OPENAI_API_KEY = await askRequired(io, \" OPENAI_API_KEY: \", true);\n return { llm: { provider: \"openai\", model: \"gpt-5.2\" } };\n }\n const provider = (await io.ask(\" Provider name [custom]: \")).trim() || \"custom\";\n const baseUrl = await askRequired(io, \" Base URL (e.g. http://host:8080/v1): \");\n const apiKey = (await io.askSecret(\" API key (empty if none): \")).trim();\n const ids = (await askRequired(io, \" Model ids (comma-separated): \"))\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const modelsJson = JSON.stringify(\n {\n providers: {\n [provider]: {\n api: \"openai-completions\",\n baseUrl,\n apiKey: apiKey ? apiKey : undefined,\n models: ids.map((id) => ({ id })),\n },\n },\n },\n null,\n 2,\n );\n return { llm: { provider, model: ids[0]! }, modelsJson };\n}\n\nasync function askSandboxImage(io: OnboardIo): Promise<string> {\n io.print(\"\\nStep 3/3 — sandbox image (one Docker container per conversation; needs Docker)\");\n const index = await askChoice(\n io,\n [\n `${PUBLISHED_SANDBOX_IMAGE} (recommended: git, gh, node, python, curl)`,\n `${MINIMAL_SANDBOX_IMAGE} (minimal: shell only, no git or package installs)`,\n \"Another image from Docker Hub or a registry…\",\n ],\n \"Sandbox image\",\n );\n if (index === 0) return PUBLISHED_SANDBOX_IMAGE;\n if (index === 1) return MINIMAL_SANDBOX_IMAGE;\n return askRequired(io, \" Image (e.g. registry/name:tag): \");\n}\n\nexport function renderEnvFile(existing: string | undefined, vars: Record<string, string>): string {\n const pending = new Map(Object.entries(vars));\n const lines: string[] = [];\n for (const line of existing ? existing.split(\"\\n\") : []) {\n const rendered = rewriteEnvLine(line, pending);\n if (rendered.trim() || lines.length > 0) lines.push(rendered);\n }\n while (lines.at(-1) === \"\") lines.pop();\n for (const [key, value] of pending) lines.push(`${key}=${value}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction rewriteEnvLine(line: string, pending: Map<string, string>): string {\n const key = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line)?.[1];\n if (key === undefined || !pending.has(key)) return line;\n const value = pending.get(key);\n pending.delete(key);\n return `${key}=${value}`;\n}\n\nexport async function runOnboardWizard(\n stateDir: string,\n io: OnboardIo,\n paths?: { envFilePath?: string },\n): Promise<number> {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n io.print(`Global settings already exists at ${settingsPath}.`);\n io.print(\"Remove it first to re-run onboarding, or edit it directly.\");\n return 1;\n }\n\n const env: Record<string, string> = {};\n const { githubRepos } = await askAdapter(io, env);\n const { llm, modelsJson } = await askLlm(io, env);\n const image = await askSandboxImage(io);\n\n const modelsPath = join(stateDir, \"models.json\");\n if (modelsJson && existsSync(modelsPath)) {\n io.print(\n `models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`,\n );\n return 1;\n }\n io.print(\n `Review settings\\nProvider: ${llm.provider}\\nModel: ${llm.model}\\nSandbox: image:${image}\\nSettings: ${settingsPath}\\nCredentials: ${paths?.envFilePath ?? join(stateDir, \"mikan.env\")}${modelsJson ? `\\nModels: ${modelsPath}` : \"\"}\\nSecrets are hidden.`,\n );\n if (!(await io.confirm(\"Save these settings?\"))) {\n io.print(\"Onboarding cancelled; nothing was written.\");\n return 1;\n }\n\n createGlobalSettingsFile(stateDir, { llm, githubRepos });\n recordAllMigrations(stateDir);\n io.print(`\\nWrote ${settingsPath}`);\n\n if (modelsJson) {\n atomicWritePrivateFile(modelsPath, `${modelsJson}\\n`);\n io.print(`Wrote ${modelsPath}`);\n }\n\n const envFilePath = paths?.envFilePath ?? join(stateDir, \"mikan.env\");\n const existing = existsSync(envFilePath) ? readFileSync(envFilePath, \"utf-8\") : undefined;\n atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));\n io.print(`Wrote ${envFilePath} (0600)`);\n\n io.print(`\\n${envSetReport((name) => env[name] ?? readEnv(name))}`);\n\n const ecosystemPath = join(stateDir, PM2_ECOSYSTEM_FILENAME);\n if (existsSync(ecosystemPath)) {\n io.print(`Kept existing ${ecosystemPath}`);\n } else {\n atomicWritePrivateFile(ecosystemPath, renderPm2Ecosystem(image));\n io.print(`Wrote ${ecosystemPath}`);\n }\n\n io.print(\"Next steps:\");\n io.print(` 1. docker pull ${image}`);\n io.print(` 2. pm2 start ${ecosystemPath} && pm2 save`);\n io.print(\n ` Foreground instead: set -a; source ${envFilePath}; set +a; mikan --sandbox=image:${image}`,\n );\n return 0;\n}\n\nexport async function runOnboardCommand(stateDir: string): Promise<number> {\n if (!process.stdin.isTTY) {\n const settingsPath = createGlobalSettingsFile(stateDir);\n recordAllMigrations(stateDir);\n console.log(`Created global settings at ${settingsPath}`);\n console.log(\n `Review the file, then start mikan (workspace defaults to ${join(stateDir, \"workspace\")}).`,\n );\n return 0;\n }\n prompts.intro(\"Mikan setup · arrows to choose · Esc / Ctrl+C to cancel\");\n const io = terminalIo();\n try {\n const code = await runOnboardWizard(stateDir, io);\n prompts.outro(code === 0 ? \"Setup complete\" : \"Setup not saved\");\n return code;\n } catch (err) {\n if (err instanceof OnboardAborted) {\n prompts.cancel(\"Onboarding aborted; nothing was written.\");\n return 1;\n }\n throw err;\n } finally {\n io.close();\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export declare const PUBLISHED_SANDBOX_IMAGE = "ghcr.io/geminixiang/mikan-sandbox:latest";
2
+ export declare const MINIMAL_SANDBOX_IMAGE = "debian:trixie-slim";
3
+ export declare const PM2_ECOSYSTEM_FILENAME = "ecosystem.config.cjs";
4
+ export declare function renderPm2Ecosystem(image: string): string;
5
+ //# sourceMappingURL=pm2-ecosystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2-ecosystem.d.ts","sourceRoot":"","sources":["../../src/cli/pm2-ecosystem.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,6CAA6C,CAAC;AAElF,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqDxD"}
@@ -0,0 +1,58 @@
1
+ export const PUBLISHED_SANDBOX_IMAGE = "ghcr.io/geminixiang/mikan-sandbox:latest";
2
+ export const MINIMAL_SANDBOX_IMAGE = "debian:trixie-slim";
3
+ export const PM2_ECOSYSTEM_FILENAME = "ecosystem.config.cjs";
4
+ export function renderPm2Ecosystem(image) {
5
+ return `const fs = require("node:fs");
6
+ const os = require("node:os");
7
+ const path = require("node:path");
8
+
9
+ function loadEnvFile(file) {
10
+ let text;
11
+ try {
12
+ text = fs.readFileSync(file, "utf-8");
13
+ } catch {
14
+ return {};
15
+ }
16
+ const env = {};
17
+ for (const line of text.split("\\n")) {
18
+ const trimmed = line.trim();
19
+ if (!trimmed || trimmed.startsWith("#")) continue;
20
+ const eq = trimmed.indexOf("=");
21
+ if (eq <= 0) continue;
22
+ const key = trimmed.slice(0, eq).trim();
23
+ let value = trimmed.slice(eq + 1).trim();
24
+ if (
25
+ (value.startsWith('"') && value.endsWith('"')) ||
26
+ (value.startsWith("'") && value.endsWith("'"))
27
+ ) {
28
+ value = value.slice(1, -1);
29
+ }
30
+ env[key] = value;
31
+ }
32
+ return env;
33
+ }
34
+
35
+ module.exports = {
36
+ apps: [
37
+ {
38
+ name: "mikan",
39
+ script: "mikan",
40
+
41
+ args: ${JSON.stringify(`--sandbox=image:${image}`)},
42
+
43
+ env: loadEnvFile(path.join(os.homedir(), ".mikan", "mikan.env")),
44
+
45
+ kill_timeout: 360000,
46
+
47
+ autorestart: true,
48
+ max_restarts: 10,
49
+ restart_delay: 2000,
50
+
51
+ time: true,
52
+ merge_logs: true,
53
+ },
54
+ ],
55
+ };
56
+ `;
57
+ }
58
+ //# sourceMappingURL=pm2-ecosystem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2-ecosystem.js","sourceRoot":"","sources":["../../src/cli/pm2-ecosystem.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,0CAA0C,CAAC;AAElF,MAAM,CAAC,MAAM,qBAAqB,GAAG,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoCK,IAAI,CAAC,SAAS,CAAC,mBAAmB,KAAK,EAAE,CAAC;;;;;;;;;;;;;;;CAevD,CAAC;AACF,CAAC","sourcesContent":["export const PUBLISHED_SANDBOX_IMAGE = \"ghcr.io/geminixiang/mikan-sandbox:latest\";\n\nexport const MINIMAL_SANDBOX_IMAGE = \"debian:trixie-slim\";\n\nexport const PM2_ECOSYSTEM_FILENAME = \"ecosystem.config.cjs\";\n\nexport function renderPm2Ecosystem(image: string): string {\n return `const fs = require(\"node:fs\");\nconst os = require(\"node:os\");\nconst path = require(\"node:path\");\n\nfunction loadEnvFile(file) {\n let text;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n return {};\n }\n const env = {};\n for (const line of text.split(\"\\\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq <= 0) continue;\n const key = trimmed.slice(0, eq).trim();\n let value = trimmed.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n env[key] = value;\n }\n return env;\n}\n\nmodule.exports = {\n apps: [\n {\n name: \"mikan\",\n script: \"mikan\",\n\n args: ${JSON.stringify(`--sandbox=image:${image}`)},\n\n env: loadEnvFile(path.join(os.homedir(), \".mikan\", \"mikan.env\")),\n\n kill_timeout: 360000,\n\n autorestart: true,\n max_restarts: 10,\n restart_delay: 2000,\n\n time: true,\n merge_logs: true,\n },\n ],\n};\n`;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { Static, TSchema } from "typebox";
2
+ import type { Dirent } from "node:fs";
2
3
  type JsonFailureKind = "syntax" | "shape" | "field";
3
4
  type MalformedJsonMessage = (detail: string, kind: JsonFailureKind) => string;
4
5
  export declare function ensureDirExists(dir: string): void;
@@ -6,6 +7,7 @@ export declare function ensurePrivateDirExists(dir: string): void;
6
7
  export declare function readTextFileNoFollowIfExists(path: string): string | undefined;
7
8
  export interface PinnedDirectory {
8
9
  pathOf(name: string): string;
10
+ entries(): Dirent[];
9
11
  close(): void;
10
12
  }
11
13
  export declare function pinDirectoryNoFollow(dir: string): PinnedDirectory;
@@ -1 +1 @@
1
- {"version":3,"file":"file-guards.d.ts","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAmB/C,KAAK,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpD,KAAK,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,KAAK,MAAM,CAAC;AAI9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAExD;AAmBD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa7E;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAiBjE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAUtE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,oBAAoB,GACrC,CAAC,GAAG,SAAS,CAGf;AAED,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,OAAO,EAC1D,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,oBAAoB,GACrC,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAGvB;AAWD,wBAAgB,cAAc,CAAC,CAAC,EAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,oBAAoB,GACrC,CAAC,CAMH;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EACpD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,oBAAoB,GACrC,MAAM,CAAC,CAAC,CAAC,CAUX;AAID,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA6BhF;AAQD,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,IAAI,CAWN"}
1
+ {"version":3,"file":"file-guards.d.ts","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAc/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAOtC,KAAK,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpD,KAAK,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,KAAK,MAAM,CAAC;AAI9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAExD;AAmBD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa7E;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,IAAI,MAAM,EAAE,CAAC;IACpB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAkBjE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAUtE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,oBAAoB,GACrC,CAAC,GAAG,SAAS,CAGf;AAED,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,OAAO,EAC1D,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,oBAAoB,GACrC,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAGvB;AAWD,wBAAgB,cAAc,CAAC,CAAC,EAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,oBAAoB,GACrC,CAAC,CAMH;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EACpD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,oBAAoB,GACrC,MAAM,CAAC,CAAC,CAAC,CAUX;AAID,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA6BhF;AAQD,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,IAAI,CAWN"}
@@ -1,5 +1,5 @@
1
1
  import { Value } from "typebox/value";
2
- import { closeSync, constants as fsConstants, fstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
2
+ import { closeSync, constants as fsConstants, fstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeSync, } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import * as log from "./log.js";
5
5
  import { randomBytes } from "node:crypto";
@@ -62,6 +62,7 @@ export function pinDirectoryNoFollow(dir) {
62
62
  }
63
63
  return join(base, name);
64
64
  },
65
+ entries: () => readdirSync(base, { withFileTypes: true }),
65
66
  close: () => closeSync(fd),
66
67
  };
67
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-guards.js","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Static, TSchema } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n closeSync,\n constants as fsConstants,\n fstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport * as log from \"./log.js\";\nimport { randomBytes } from \"node:crypto\";\nimport { basename, dirname, join } from \"node:path\";\nimport { errorMessage } from \"./unknown-values.js\";\n\ntype JsonFailureKind = \"syntax\" | \"shape\" | \"field\";\n\ntype MalformedJsonMessage = (detail: string, kind: JsonFailureKind) => string;\n\nconst UNEXPECTED_JSON_SHAPE = \"unexpected JSON shape\";\n\nexport function ensureDirExists(dir: string): void {\n mkdirSync(dir, { recursive: true });\n}\n\nexport function ensurePrivateDirExists(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n}\n\nfunction openRegularFileNoFollow(path: string, flags: number): number {\n let fd: number;\n try {\n fd = openSync(path, flags | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK, 0o644);\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ELOOP\") {\n throw new Error(`Refusing to follow a symbolic link: ${path}`, { cause: err });\n }\n throw err;\n }\n if (!fstatSync(fd).isFile()) {\n closeSync(fd);\n throw new Error(`Expected a regular file: ${path}`);\n }\n return fd;\n}\n\nexport function readTextFileNoFollowIfExists(path: string): string | undefined {\n let fd: number;\n try {\n fd = openRegularFileNoFollow(path, fsConstants.O_RDONLY);\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ENOENT\") return undefined;\n throw err;\n }\n try {\n return readFileSync(fd, \"utf-8\");\n } finally {\n closeSync(fd);\n }\n}\n\nexport interface PinnedDirectory {\n pathOf(name: string): string;\n close(): void;\n}\n\nexport function pinDirectoryNoFollow(dir: string): PinnedDirectory {\n let fd: number;\n try {\n fd = openSync(dir, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);\n } catch (err) {\n throw new Error(`Expected a directory that is not a symbolic link: ${dir}`, { cause: err });\n }\n const base = process.platform === \"linux\" ? `/proc/self/fd/${fd}` : dir;\n return {\n pathOf(name) {\n if (basename(name) !== name || name === \".\" || name === \"..\") {\n throw new Error(`Expected a plain file name: ${name}`);\n }\n return join(base, name);\n },\n close: () => closeSync(fd),\n };\n}\n\nexport function appendFileNoFollow(path: string, content: string): void {\n const fd = openRegularFileNoFollow(\n path,\n fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT,\n );\n try {\n writeSync(fd, content);\n } finally {\n closeSync(fd);\n }\n}\n\nexport function readTextFileIfExists(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n}\n\nexport function readJsonFileIfExists<T>(\n path: string,\n validate: (value: unknown) => value is T,\n malformedMessage: MalformedJsonMessage,\n): T | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonValue(raw, validate, malformedMessage);\n}\n\nexport function readJsonSchemaFileIfExists<T extends TSchema>(\n path: string,\n schema: T,\n malformedMessage: MalformedJsonMessage,\n): Static<T> | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonSchemaValue(raw, schema, malformedMessage);\n}\n\nfunction parseJson(raw: string, malformedMessage: MalformedJsonMessage): unknown {\n try {\n return JSON.parse(raw);\n } catch (err) {\n const detail = errorMessage(err);\n throw new Error(malformedMessage(detail, \"syntax\"), { cause: err });\n }\n}\n\nexport function parseJsonValue<T>(\n raw: string,\n validate: (value: unknown) => value is T,\n malformedMessage: MalformedJsonMessage,\n): T {\n const parsed = parseJson(raw, malformedMessage);\n if (!validate(parsed)) {\n throw new Error(malformedMessage(UNEXPECTED_JSON_SHAPE, \"shape\"));\n }\n return parsed;\n}\n\nexport function parseJsonSchemaValue<T extends TSchema>(\n raw: string,\n schema: T,\n malformedMessage: MalformedJsonMessage,\n): Static<T> {\n const parsed = parseJson(raw, malformedMessage);\n if (!Value.Check(schema, parsed)) {\n const firstError = Value.Errors(schema, parsed)[0];\n if (!firstError || firstError.instancePath === \"\" || firstError.instancePath === \"/\") {\n throw new Error(malformedMessage(UNEXPECTED_JSON_SHAPE, \"shape\"));\n }\n throw new Error(malformedMessage(`${firstError.instancePath}: ${firstError.message}`, \"field\"));\n }\n return parsed;\n}\n\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {}\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {}\n throw err;\n }\n}\n\nfunction isPathInside(child: string, parent: string): boolean {\n const parentPath = resolve(parent);\n const childPath = resolve(child);\n return childPath === parentPath || childPath.startsWith(parentPath + \"/\");\n}\n\nexport function assertStateDirOutsideWorkspace(\n stateDir: string,\n workingDir: string,\n sandboxType: string,\n): void {\n if (!isPathInside(stateDir, workingDir)) return;\n const message =\n `The state directory (${stateDir}) must not be inside the working directory (${workingDir}): ` +\n `sandbox containers mount the working directory, and a mounted state dir ` +\n `would expose settings, office records, vaults, and credentials to sandboxed code.`;\n if (sandboxType === \"host\") {\n log.logWarning(\"Insecure state dir location\", message);\n return;\n }\n throw new Error(message);\n}\n"]}
1
+ {"version":3,"file":"file-guards.js","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Static, TSchema } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n closeSync,\n constants as fsConstants,\n fstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport type { Dirent } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport * as log from \"./log.js\";\nimport { randomBytes } from \"node:crypto\";\nimport { basename, dirname, join } from \"node:path\";\nimport { errorMessage } from \"./unknown-values.js\";\n\ntype JsonFailureKind = \"syntax\" | \"shape\" | \"field\";\n\ntype MalformedJsonMessage = (detail: string, kind: JsonFailureKind) => string;\n\nconst UNEXPECTED_JSON_SHAPE = \"unexpected JSON shape\";\n\nexport function ensureDirExists(dir: string): void {\n mkdirSync(dir, { recursive: true });\n}\n\nexport function ensurePrivateDirExists(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n}\n\nfunction openRegularFileNoFollow(path: string, flags: number): number {\n let fd: number;\n try {\n fd = openSync(path, flags | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK, 0o644);\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ELOOP\") {\n throw new Error(`Refusing to follow a symbolic link: ${path}`, { cause: err });\n }\n throw err;\n }\n if (!fstatSync(fd).isFile()) {\n closeSync(fd);\n throw new Error(`Expected a regular file: ${path}`);\n }\n return fd;\n}\n\nexport function readTextFileNoFollowIfExists(path: string): string | undefined {\n let fd: number;\n try {\n fd = openRegularFileNoFollow(path, fsConstants.O_RDONLY);\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ENOENT\") return undefined;\n throw err;\n }\n try {\n return readFileSync(fd, \"utf-8\");\n } finally {\n closeSync(fd);\n }\n}\n\nexport interface PinnedDirectory {\n pathOf(name: string): string;\n entries(): Dirent[];\n close(): void;\n}\n\nexport function pinDirectoryNoFollow(dir: string): PinnedDirectory {\n let fd: number;\n try {\n fd = openSync(dir, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);\n } catch (err) {\n throw new Error(`Expected a directory that is not a symbolic link: ${dir}`, { cause: err });\n }\n const base = process.platform === \"linux\" ? `/proc/self/fd/${fd}` : dir;\n return {\n pathOf(name) {\n if (basename(name) !== name || name === \".\" || name === \"..\") {\n throw new Error(`Expected a plain file name: ${name}`);\n }\n return join(base, name);\n },\n entries: () => readdirSync(base, { withFileTypes: true }),\n close: () => closeSync(fd),\n };\n}\n\nexport function appendFileNoFollow(path: string, content: string): void {\n const fd = openRegularFileNoFollow(\n path,\n fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT,\n );\n try {\n writeSync(fd, content);\n } finally {\n closeSync(fd);\n }\n}\n\nexport function readTextFileIfExists(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch (err) {\n if (err instanceof Error && \"code\" in err && err.code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n}\n\nexport function readJsonFileIfExists<T>(\n path: string,\n validate: (value: unknown) => value is T,\n malformedMessage: MalformedJsonMessage,\n): T | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonValue(raw, validate, malformedMessage);\n}\n\nexport function readJsonSchemaFileIfExists<T extends TSchema>(\n path: string,\n schema: T,\n malformedMessage: MalformedJsonMessage,\n): Static<T> | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonSchemaValue(raw, schema, malformedMessage);\n}\n\nfunction parseJson(raw: string, malformedMessage: MalformedJsonMessage): unknown {\n try {\n return JSON.parse(raw);\n } catch (err) {\n const detail = errorMessage(err);\n throw new Error(malformedMessage(detail, \"syntax\"), { cause: err });\n }\n}\n\nexport function parseJsonValue<T>(\n raw: string,\n validate: (value: unknown) => value is T,\n malformedMessage: MalformedJsonMessage,\n): T {\n const parsed = parseJson(raw, malformedMessage);\n if (!validate(parsed)) {\n throw new Error(malformedMessage(UNEXPECTED_JSON_SHAPE, \"shape\"));\n }\n return parsed;\n}\n\nexport function parseJsonSchemaValue<T extends TSchema>(\n raw: string,\n schema: T,\n malformedMessage: MalformedJsonMessage,\n): Static<T> {\n const parsed = parseJson(raw, malformedMessage);\n if (!Value.Check(schema, parsed)) {\n const firstError = Value.Errors(schema, parsed)[0];\n if (!firstError || firstError.instancePath === \"\" || firstError.instancePath === \"/\") {\n throw new Error(malformedMessage(UNEXPECTED_JSON_SHAPE, \"shape\"));\n }\n throw new Error(malformedMessage(`${firstError.instancePath}: ${firstError.message}`, \"field\"));\n }\n return parsed;\n}\n\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {}\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {}\n throw err;\n }\n}\n\nfunction isPathInside(child: string, parent: string): boolean {\n const parentPath = resolve(parent);\n const childPath = resolve(child);\n return childPath === parentPath || childPath.startsWith(parentPath + \"/\");\n}\n\nexport function assertStateDirOutsideWorkspace(\n stateDir: string,\n workingDir: string,\n sandboxType: string,\n): void {\n if (!isPathInside(stateDir, workingDir)) return;\n const message =\n `The state directory (${stateDir}) must not be inside the working directory (${workingDir}): ` +\n `sandbox containers mount the working directory, and a mounted state dir ` +\n `would expose settings, office records, vaults, and credentials to sandboxed code.`;\n if (sandboxType === \"host\") {\n log.logWarning(\"Insecure state dir location\", message);\n return;\n }\n throw new Error(message);\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  import type { ImageContent } from "@earendil-works/pi-ai";
2
2
  import type { ConversationMessage } from "../types.js";
3
3
  import type { RuntimePathContext } from "../sandbox/types.js";
4
- import type { WorkspaceProjection } from "../office/types.js";
5
- import type { BuildSystemPromptOptions } from "./types.js";
4
+ import type { WorkspaceProjection, Office } from "../office/types.js";
5
+ import type { BuildSystemPromptOptions, ScratchListing } from "./types.js";
6
+ export declare function listScratchFolders(office: Office): ScratchListing;
6
7
  export declare function buildPromptPayload(message: ConversationMessage, workspacePath: string, pathContext?: RuntimePathContext, readAttachment?: (runtimePath: string) => Promise<string>): Promise<{
7
8
  userMessage: string;
8
9
  imageAttachments: ImageContent[];
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/harness/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,mBAAmB,EAAU,MAAM,oBAAoB,CAAC;AAEtE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAuD3D,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,EAChC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACxD,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAAC,CAmBD;AAiBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAQjE;AAwBD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GAC7D,MAAM,GAAG,SAAS,CAOpB;AA+LD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAGzE;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAQrE;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAQR"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/harness/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,KAAK,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAiD3E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CA0BjE;AAkBD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,EAChC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACxD,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAAC,CAmBD;AAiBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAQjE;AAwBD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GAC7D,MAAM,GAAG,SAAS,CAOpB;AAiMD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAGzE;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAQrE;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAQR"}