@biaoo/tiangong-wiki 0.2.2 → 0.2.3

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.
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { Codex } from "@openai/codex-sdk";
3
3
  import { readWorkflowResult } from "./workflow-result.js";
4
+ import { resolveAgentSettings } from "./paths.js";
4
5
  import { readTextFileSync, writeTextFileSync } from "../utils/fs.js";
5
6
  import { AppError } from "../utils/errors.js";
6
7
  export const CODEX_WORKFLOW_VERSION = "2026-04-07";
@@ -85,16 +86,10 @@ async function runThread(thread, input) {
85
86
  continue;
86
87
  }
87
88
  if (event.type === "turn.failed") {
88
- throw new AppError("Codex workflow turn failed", "runtime", {
89
- cause: event.error.message,
90
- threadId: activeThreadId,
91
- });
89
+ throw classifyWorkflowRuntimeError("Codex workflow turn failed", event.error.message, activeThreadId);
92
90
  }
93
91
  if (event.type === "error") {
94
- throw new AppError("Codex workflow stream failed", "runtime", {
95
- cause: event.message,
96
- threadId: activeThreadId,
97
- });
92
+ throw classifyWorkflowRuntimeError("Codex workflow stream failed", event.message, activeThreadId);
98
93
  }
99
94
  }
100
95
  }
@@ -103,10 +98,7 @@ async function runThread(thread, input) {
103
98
  throw error;
104
99
  }
105
100
  const message = error instanceof Error ? error.message : String(error);
106
- throw new AppError("Codex workflow turn failed", "runtime", {
107
- cause: message,
108
- threadId: activeThreadId,
109
- });
101
+ throw classifyWorkflowRuntimeError("Codex workflow turn failed", message, activeThreadId);
110
102
  }
111
103
  if (!activeThreadId && thread.id) {
112
104
  activeThreadId = thread.id;
@@ -118,6 +110,10 @@ async function runThread(thread, input) {
118
110
  return activeThreadId;
119
111
  }
120
112
  export class CodexSdkWorkflowRunner {
113
+ options;
114
+ constructor(options = {}) {
115
+ this.options = options;
116
+ }
121
117
  // The SDK can only continue a thread by sending a new input, so queue retries
122
118
  // must not automatically resume real workflow threads inline.
123
119
  async startWorkflow(input) {
@@ -127,7 +123,7 @@ export class CodexSdkWorkflowRunner {
127
123
  modelReasoningEffort: "low",
128
124
  workingDirectory: input.workspaceRoot,
129
125
  skipGitRepoCheck: true,
130
- sandboxMode: "workspace-write",
126
+ sandboxMode: this.options.sandboxMode ?? "danger-full-access",
131
127
  networkAccessEnabled: true,
132
128
  approvalPolicy: "never",
133
129
  webSearchMode: "disabled",
@@ -143,7 +139,7 @@ export class CodexSdkWorkflowRunner {
143
139
  modelReasoningEffort: "low",
144
140
  workingDirectory: input.workspaceRoot,
145
141
  skipGitRepoCheck: true,
146
- sandboxMode: "workspace-write",
142
+ sandboxMode: this.options.sandboxMode ?? "danger-full-access",
147
143
  networkAccessEnabled: true,
148
144
  approvalPolicy: "never",
149
145
  webSearchMode: "disabled",
@@ -229,5 +225,31 @@ export function createDefaultWorkflowRunner(env = process.env) {
229
225
  const delayMs = Number.parseInt(env.WIKI_TEST_FAKE_WORKFLOW_DELAY_MS ?? "0", 10) || 0;
230
226
  return createSkipOnlyTestWorkflowRunner({ delayMs, mode: "delay-skip" });
231
227
  }
232
- return new CodexSdkWorkflowRunner();
228
+ return new CodexSdkWorkflowRunner({
229
+ sandboxMode: resolveAgentSettings(env).sandboxMode,
230
+ });
231
+ }
232
+ function isSandboxStartupFailure(message) {
233
+ const normalized = message.toLowerCase();
234
+ return (normalized.includes("bwrap") ||
235
+ normalized.includes("bubblewrap") ||
236
+ normalized.includes("uid map") ||
237
+ normalized.includes("uid_map") ||
238
+ normalized.includes("gid map") ||
239
+ normalized.includes("gid_map") ||
240
+ normalized.includes("unshare") ||
241
+ normalized.includes("operation not permitted"));
242
+ }
243
+ function classifyWorkflowRuntimeError(baseMessage, cause, threadId) {
244
+ if (isSandboxStartupFailure(cause)) {
245
+ return new AppError("Codex workflow sandbox failed to initialize", "runtime", {
246
+ cause,
247
+ threadId,
248
+ phase: "sandbox",
249
+ });
250
+ }
251
+ return new AppError(baseMessage, "runtime", {
252
+ cause,
253
+ threadId,
254
+ });
233
255
  }
@@ -6,7 +6,7 @@ import { DEFAULT_WIKI_ENV_FILE, getCliEnvironmentInfo, parseEnvFile, serializeEn
6
6
  import { resolveTemplateFilePath, loadConfig } from "./config.js";
7
7
  import { EmbeddingClient } from "./embedding.js";
8
8
  import { writeGlobalConfig } from "./global-config.js";
9
- import { parseVaultHashMode, resolveAgentSettings } from "./paths.js";
9
+ import { parseVaultHashMode, parseWikiAgentSandboxMode, resolveAgentSettings } from "./paths.js";
10
10
  import { loadSynologyConfigFromEnv, normalizeSynologyRemotePath, withSynologyClient } from "./synology.js";
11
11
  import { ensureWikiSkillInstall, formatParserSkills, inspectSkillInstall, installParserSkill, OPTIONAL_PARSER_SKILLS, parseParserSkillSelection, parseParserSkills, resolveWorkspaceRootFromWikiPath, resolveWorkspaceSkillPath, resolveWorkspaceSkillPaths, } from "./workspace-skills.js";
12
12
  import { scaffoldWorkspaceAssets } from "./workspace-bootstrap.js";
@@ -36,11 +36,16 @@ const MANAGED_ENV_KEYS = new Set([
36
36
  "WIKI_AGENT_API_KEY",
37
37
  "WIKI_AGENT_MODEL",
38
38
  "WIKI_AGENT_BATCH_SIZE",
39
+ "WIKI_AGENT_SANDBOX_MODE",
39
40
  "WIKI_PARSER_SKILLS",
40
41
  ]);
41
42
  function writeSection(output, title) {
42
43
  output.write(`\n${title}\n`);
43
44
  }
45
+ function writeWarning(output, message) {
46
+ const isTty = "isTTY" in output && output.isTTY;
47
+ output.write(isTty ? `\x1b[31m${message}\x1b[0m\n` : `${message}\n`);
48
+ }
44
49
  function resolvePackageRoot(packageRoot) {
45
50
  return packageRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
46
51
  }
@@ -73,6 +78,14 @@ function safeVaultHashMode(rawValue, defaultValue) {
73
78
  return defaultValue;
74
79
  }
75
80
  }
81
+ function safeAgentSandboxMode(rawValue) {
82
+ try {
83
+ return parseWikiAgentSandboxMode(rawValue);
84
+ }
85
+ catch {
86
+ return "danger-full-access";
87
+ }
88
+ }
76
89
  function safeBooleanFlag(rawValue, defaultValue) {
77
90
  if (rawValue === undefined || rawValue.trim().length === 0) {
78
91
  return defaultValue;
@@ -381,6 +394,7 @@ function getPathDefaults(env, cwd) {
381
394
  agentApiKey: env.WIKI_AGENT_API_KEY ?? null,
382
395
  agentModel: env.WIKI_AGENT_MODEL ?? null,
383
396
  agentBatchSize: env.WIKI_AGENT_BATCH_SIZE ?? "5",
397
+ agentSandboxMode: safeAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE),
384
398
  parserSkills: parseParserSkills(env.WIKI_PARSER_SKILLS, { strict: false }),
385
399
  };
386
400
  }
@@ -447,14 +461,32 @@ async function collectAgentSettings(driver, ctx, defaults) {
447
461
  agentApiKey: null,
448
462
  agentModel: null,
449
463
  agentBatchSize: null,
464
+ agentSandboxMode: null,
450
465
  };
451
466
  }
467
+ writeWarning(ctx.output, "Warning: danger-full-access grants full access to the runtime workspace.");
452
468
  return {
453
469
  agentEnabled: true,
454
470
  agentBaseUrl: await promptText(driver, "WIKI_AGENT_BASE_URL", defaults.agentBaseUrl ?? "https://api.openai.com/v1", { validator: (value) => validateUrl(value, "WIKI_AGENT_BASE_URL") }),
455
471
  agentApiKey: await promptPassword(driver, "WIKI_AGENT_API_KEY", defaults.agentApiKey ?? "", { required: true }),
456
472
  agentModel: await promptText(driver, "WIKI_AGENT_MODEL", defaults.agentModel ?? "", { required: true }),
457
473
  agentBatchSize: await promptText(driver, "WIKI_AGENT_BATCH_SIZE", defaults.agentBatchSize ?? "5", { validator: (value) => validateNonNegativeInteger(value, "WIKI_AGENT_BATCH_SIZE") }),
474
+ agentSandboxMode: await driver.select({
475
+ message: "WIKI_AGENT_SANDBOX_MODE",
476
+ defaultValue: defaults.agentSandboxMode ?? "danger-full-access",
477
+ choices: [
478
+ {
479
+ value: "danger-full-access",
480
+ label: "danger-full-access",
481
+ description: "Full access to the runtime workspace. Default.",
482
+ },
483
+ {
484
+ value: "workspace-write",
485
+ label: "workspace-write",
486
+ description: "Use Codex workspace-write sandbox when the host supports it.",
487
+ },
488
+ ],
489
+ }),
458
490
  };
459
491
  }
460
492
  async function collectSynologySettings(driver, ctx, defaults) {
@@ -532,6 +564,7 @@ function buildSetupSummary(values) {
532
564
  lines.push(` WIKI_AGENT_BASE_URL: ${values.agentBaseUrl}`);
533
565
  lines.push(` WIKI_AGENT_MODEL: ${values.agentModel}`);
534
566
  lines.push(` WIKI_AGENT_BATCH_SIZE: ${values.agentBatchSize}`);
567
+ lines.push(` WIKI_AGENT_SANDBOX_MODE: ${values.agentSandboxMode}`);
535
568
  }
536
569
  if (values.vaultSource === "synology") {
537
570
  lines.push(` SYNOLOGY_BASE_URL: ${values.synologyBaseUrl}`);
@@ -569,6 +602,7 @@ function writeSetupEnvFile(values) {
569
602
  ["WIKI_AGENT_API_KEY", values.agentEnabled ? values.agentApiKey : null],
570
603
  ["WIKI_AGENT_MODEL", values.agentEnabled ? values.agentModel : null],
571
604
  ["WIKI_AGENT_BATCH_SIZE", values.agentEnabled ? values.agentBatchSize : null],
605
+ ["WIKI_AGENT_SANDBOX_MODE", values.agentEnabled ? values.agentSandboxMode : null],
572
606
  ["WIKI_PARSER_SKILLS", formatParserSkills(values.parserSkills)],
573
607
  ];
574
608
  const body = [
@@ -89,12 +89,20 @@ export function parseWikiAgentBackend(raw) {
89
89
  }
90
90
  throw new AppError(`WIKI_AGENT_BACKEND must be "codex-workflow", got ${raw}`, "config");
91
91
  }
92
+ export function parseWikiAgentSandboxMode(raw) {
93
+ const value = (raw ?? "danger-full-access").trim().toLowerCase();
94
+ if (value === "danger-full-access" || value === "workspace-write") {
95
+ return value;
96
+ }
97
+ throw new AppError(`WIKI_AGENT_SANDBOX_MODE must be "danger-full-access" or "workspace-write", got ${raw}`, "config");
98
+ }
92
99
  export function resolveAgentSettings(env = process.env, options = {}) {
93
100
  const enabled = parseBooleanFlag("WIKI_AGENT_ENABLED", env.WIKI_AGENT_ENABLED, false);
94
101
  const baseUrl = normalizeOptionalUrl(env.WIKI_AGENT_BASE_URL);
95
102
  const apiKey = env.WIKI_AGENT_API_KEY?.trim() || null;
96
103
  const model = env.WIKI_AGENT_MODEL?.trim() || null;
97
104
  const batchSize = parseNonNegativeInteger(env.WIKI_AGENT_BATCH_SIZE, 5, "WIKI_AGENT_BATCH_SIZE");
105
+ const sandboxMode = parseWikiAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE);
98
106
  const workflowTimeoutSeconds = parsePositiveInteger(env.WIKI_WORKFLOW_TIMEOUT, 600, "WIKI_WORKFLOW_TIMEOUT");
99
107
  const missing = [];
100
108
  if (enabled) {
@@ -114,6 +122,7 @@ export function resolveAgentSettings(env = process.env, options = {}) {
114
122
  apiKey,
115
123
  model,
116
124
  batchSize,
125
+ sandboxMode,
117
126
  workflowTimeoutSeconds,
118
127
  configured: enabled && missing.length === 0,
119
128
  missing,
@@ -148,6 +148,11 @@ export function readWorkflowResult(resultPath) {
148
148
  fail(`Workflow result not found: ${resultPath}`);
149
149
  }
150
150
  const rawText = readTextFileSync(resultPath);
151
+ if (!rawText.trim()) {
152
+ fail("Workflow result is empty", {
153
+ resultPath,
154
+ });
155
+ }
151
156
  let parsed;
152
157
  try {
153
158
  parsed = JSON.parse(rawText);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biaoo/tiangong-wiki",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Local-first wiki index and query engine for Markdown knowledge pages (Tiangong Wiki).",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -73,8 +73,11 @@ The agent uses [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk) to p
73
73
  | `WIKI_AGENT_API_KEY` | If enabled | API key for the LLM provider |
74
74
  | `WIKI_AGENT_MODEL` | No | Model name (e.g. `gpt-5.4`, `Qwen/Qwen3.5-397B-A17B-GPTQ-Int4`) |
75
75
  | `WIKI_AGENT_BATCH_SIZE` | No | Max concurrent vault items per batch (default: `5`) |
76
+ | `WIKI_AGENT_SANDBOX_MODE` | No | Codex sandbox mode: `danger-full-access` (default) or `workspace-write` |
76
77
  | `WIKI_PARSER_SKILLS` | No | Comma-separated parser skill list (e.g. `pdf,docx,pptx,xlsx`) |
77
78
 
79
+ `tiangong-wiki setup` now prompts for `WIKI_AGENT_SANDBOX_MODE` when automatic vault processing is enabled. The default is `danger-full-access`, and the setup wizard highlights that this mode grants full runtime access.
80
+
78
81
  ---
79
82
 
80
83
  ## Common Issues
@@ -102,6 +105,10 @@ Always run `tiangong-wiki lint --path <page-id> --format json` after mutations.
102
105
 
103
106
  Parser skills must be installed under `<workspace-root>/.agents/skills/`. Run `tiangong-wiki skill` to inspect installed skills. Use `tiangong-wiki skill update --all` to update.
104
107
 
108
+ ### Codex workflow sandbox fails to initialize
109
+
110
+ If the agent workflow fails with `bwrap`, `unshare`, `uid_map`, or similar sandbox startup errors, switch `WIKI_AGENT_SANDBOX_MODE` to `danger-full-access`. Use `workspace-write` only when you explicitly want that sandbox mode and know the host supports it.
111
+
105
112
  ---
106
113
 
107
114
  ## LLM Provider Setup