typeclaw 0.29.0 → 0.30.0

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/src/run/index.ts CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  } from '@/cron'
43
43
  import { CLI_VERSION } from '@/init/cli-version'
44
44
  import { createMcpManager } from '@/mcp'
45
+ import { runStartupMigrations } from '@/migrations'
45
46
  import { loadPlugins, type LoadPluginsResult, pluginCronJobs, type PluginRegistry, summarizeLoaded } from '@/plugin'
46
47
  import { createPluginLogger } from '@/plugin/context'
47
48
  import type { CronHandlerContext } from '@/plugin/types'
@@ -194,6 +195,12 @@ export async function startAgent({
194
195
  materializedSkills: null,
195
196
  })
196
197
 
198
+ // Graduate any pre-0.20.0 on-disk shapes (v1 secrets.json, legacy auth.json)
199
+ // to the current v2 envelope before anything reads secrets — otherwise the
200
+ // v2-only parser rejects the file and hydrate below sees no channels. Runs
201
+ // exactly once per folder; a folder already at v2 is a no-op.
202
+ runStartupMigrations(cwd)
203
+
197
204
  // Channel adapters read `process.env[TOKEN_ENV]` (see channels/manager.ts).
198
205
  // Hydrate fills any unset env var from secrets.json#channels via env-wins:
199
206
  // values already in process.env (from `docker --env-file .env`) are kept
@@ -329,6 +336,8 @@ export async function startAgent({
329
336
  ...(subagentOptions?.spawnedByRole !== undefined ? { spawnedByRole: subagentOptions.spawnedByRole } : {}),
330
337
  ...(subagentOptions?.spawnedByOrigin !== undefined ? { spawnedByOrigin: subagentOptions.spawnedByOrigin } : {}),
331
338
  }
339
+ const allowBackgroundFromSubagent =
340
+ entry.pluginSubagent.canBackgroundSpawnSubagents === true && entry.pluginSubagent.canSpawnSubagents === true
332
341
  const created = await createSessionWithDispose({
333
342
  systemPromptOverride: entry.pluginSubagent.systemPrompt,
334
343
  sessionManager,
@@ -359,6 +368,7 @@ export async function startAgent({
359
368
  liveSubagentRegistry,
360
369
  subagentRegistry: snap.subagents,
361
370
  createSessionForSubagent,
371
+ allowBackgroundFromSubagent,
362
372
  }
363
373
  : {}),
364
374
  ...(entry.pluginSubagent.profile !== undefined ? { profile: entry.pluginSubagent.profile } : {}),
@@ -380,6 +390,9 @@ export async function startAgent({
380
390
  agentDir: cwd,
381
391
  origin,
382
392
  getTranscriptPath: () => sessionManager.getSessionFile(),
393
+ ...(allowBackgroundFromSubagent
394
+ ? { backgroundDrain: { stream, sessionId, liveRegistry: liveSubagentRegistry } }
395
+ : {}),
383
396
  }
384
397
  }
385
398
  return defaultCreateSessionForSubagent(subagent, subagentOptions)
@@ -33,3 +33,15 @@ async function probe(bwrap: string): Promise<boolean> {
33
33
  export function _resetBwrapAvailabilityCacheForTests(): void {
34
34
  availabilityCache.clear()
35
35
  }
36
+
37
+ // The bun binary this process runs as (process.execPath). build.ts re-exposes
38
+ // it at /proc/self/exe over the masked /proc so sandboxed package runners can
39
+ // self-locate. This is correct ONLY in the bun-centric container: the base
40
+ // image (oven/bun:1-slim) ships no real node — `node` is a bun symlink and
41
+ // bunx/npx/pnpx all resolve to bun (Bun's fake-node model), so every runtime
42
+ // reading /proc/self/exe IS bun. A real node binary would self-locate to the
43
+ // wrong ELF here; if node is ever added to the image this must resolve the
44
+ // actual interpreter instead.
45
+ export function resolveProcSelfExe(): string {
46
+ return process.execPath
47
+ }
@@ -103,6 +103,18 @@ function buildArgv(command: string, policy: SandboxPolicy): string[] {
103
103
  // (leaks the outer container's /proc/N/environ — including
104
104
  // FIREWORKS_API_KEY — into the sandbox). See sandbox.mdx.
105
105
  argv.push('--tmpfs', '/proc')
106
+
107
+ // Re-expose ONLY the bun ELF at /proc/self/exe so sandboxed package runners
108
+ // can self-locate; /proc/N/environ stays masked by the tmpfs above. The
109
+ // caller passes bun's path (see resolveProcSelfExe): in this bun-centric
110
+ // container bunx/npx/pnpx all resolve to bun, so bun IS the runtime reading
111
+ // /proc/self/exe. --symlink (not --ro-bind /proc/self/exe): /proc/self at
112
+ // setup time is bwrap's pid, so a bind would capture bwrap's own binary.
113
+ // Must come AFTER --tmpfs /proc (last-op-wins) or the tmpfs erases it.
114
+ if (policy.procSelfExe !== undefined) {
115
+ argv.push('--ro-bind', policy.procSelfExe, policy.procSelfExe)
116
+ argv.push('--symlink', policy.procSelfExe, '/proc/self/exe')
117
+ }
106
118
  }
107
119
 
108
120
  for (const mount of policy.mounts ?? []) {
@@ -1,5 +1,5 @@
1
1
  export { buildSandboxedCommand, type SandboxedCommand } from './build'
2
- export { ensureBwrapAvailable, _resetBwrapAvailabilityCacheForTests } from './availability'
2
+ export { ensureBwrapAvailable, resolveProcSelfExe, _resetBwrapAvailabilityCacheForTests } from './availability'
3
3
  export { resolveHiddenPaths, type HiddenPaths } from './hidden-paths'
4
4
  export {
5
5
  resolveProtectedZones,
@@ -60,6 +60,14 @@ export type SandboxProtectedPolicy = {
60
60
  export type SandboxPolicy = {
61
61
  bwrapPath?: string
62
62
  cwd?: string
63
+ // Concrete host interpreter ELF (the running bun binary) re-exposed at
64
+ // /proc/self/exe over the --tmpfs /proc mask. JS runtimes self-locate via
65
+ // /proc/self/exe; under the empty tmpfs /proc that read fails and bunx panics
66
+ // in createFakeTemporaryNodeExecutable. A direct --ro-bind of /proc/self/exe
67
+ // is wrong: at bwrap setup time /proc/self is bwrap's pid, so it captures the
68
+ // bwrap binary, not the child runtime. The caller resolves this path (I/O);
69
+ // the builder stays pure.
70
+ procSelfExe?: string
63
71
  mounts?: SandboxMount[]
64
72
  masks?: SandboxMaskPolicy
65
73
  writable?: SandboxWritablePolicy