deskbuddy 0.2.0-beta.13 → 0.2.0-beta.15

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.
Files changed (49) hide show
  1. package/dist/cli.js +5342 -1566
  2. package/dist/deskbuddy-dsh.mjs +706 -0
  3. package/dist/gate.cjs +24 -14
  4. package/dist/hook.cjs +404 -11
  5. package/dist/ocTranslate.mjs +106 -0
  6. package/dist/opencode.mjs +100 -69
  7. package/hook/deskbuddy-dsh.d.mts +24 -0
  8. package/hook/deskbuddy-dsh.mjs +706 -0
  9. package/hook/gate.cjs +24 -14
  10. package/hook/hook-debug.log +11 -0
  11. package/hook/hook.cjs +404 -11
  12. package/hook/ocTranslate.d.mts +7 -0
  13. package/hook/ocTranslate.mjs +106 -0
  14. package/hook/opencode.d.mts +4 -1
  15. package/hook/opencode.mjs +100 -69
  16. package/package.json +3 -2
  17. package/vendor/node-pty/LICENSE +69 -0
  18. package/vendor/node-pty/lib/conpty_console_list_agent.js +16 -0
  19. package/vendor/node-pty/lib/eventEmitter2.js +47 -0
  20. package/vendor/node-pty/lib/index.js +52 -0
  21. package/vendor/node-pty/lib/interfaces.js +7 -0
  22. package/vendor/node-pty/lib/shared/conout.js +11 -0
  23. package/vendor/node-pty/lib/terminal.js +190 -0
  24. package/vendor/node-pty/lib/types.js +7 -0
  25. package/vendor/node-pty/lib/unixTerminal.js +346 -0
  26. package/vendor/node-pty/lib/utils.js +39 -0
  27. package/vendor/node-pty/lib/windowsConoutConnection.js +125 -0
  28. package/vendor/node-pty/lib/windowsPtyAgent.js +320 -0
  29. package/vendor/node-pty/lib/windowsTerminal.js +199 -0
  30. package/vendor/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  31. package/vendor/node-pty/package.json +7 -0
  32. package/vendor/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  33. package/vendor/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  34. package/vendor/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  35. package/vendor/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  36. package/vendor/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  37. package/vendor/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  38. package/vendor/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  39. package/vendor/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  40. package/vendor/node-pty/prebuilds/win32-arm64/pty.node +0 -0
  41. package/vendor/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
  42. package/vendor/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
  43. package/vendor/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  44. package/vendor/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  45. package/vendor/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  46. package/vendor/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  47. package/vendor/node-pty/prebuilds/win32-x64/pty.node +0 -0
  48. package/vendor/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
  49. package/vendor/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
@@ -0,0 +1,106 @@
1
+ // opencode 系事件 → cc-hook 同构输入的翻译(spec 2026-10-03 §3.2「共用翻译」)。
2
+ // opencode 插件(opencode.mjs,跑在 opencode 自带的 bun 里)与 daemon 的 DumateWatch(百度搭子 = 改名的 opencode)共用这一份:
3
+ // 插件 import 同目录的本文件(随 hook.cjs 一起同步到 ~/.deskbuddy/),daemon 经 src/core/ocTranslate.ts 打包进去。
4
+ // 🔴 零依赖纯函数:不能 import 任何东西(插件那边同目录只有我们自己的文件)
5
+
6
+ /** opencode 工具 → cc-hook 同构的 tool_name / tool_input(daemon 的 classify 按 Claude Code 的工具名判断) */
7
+ export function toCcTool(tool, args) {
8
+ const a = args && typeof args === 'object' ? args : {}
9
+ const s = (v) => (typeof v === 'string' ? v : '')
10
+ switch (tool) {
11
+ case 'bash':
12
+ return { tool_name: 'Bash', tool_input: { command: s(a.command) } }
13
+ case 'read':
14
+ return { tool_name: 'Read', tool_input: { file_path: s(a.filePath) } }
15
+ case 'edit':
16
+ return { tool_name: 'Edit', tool_input: { file_path: s(a.filePath) } }
17
+ case 'write':
18
+ return { tool_name: 'Write', tool_input: { file_path: s(a.filePath) } }
19
+ case 'glob':
20
+ return { tool_name: 'Glob', tool_input: { pattern: s(a.pattern), path: s(a.path) } }
21
+ case 'grep':
22
+ return { tool_name: 'Grep', tool_input: { pattern: s(a.pattern), path: s(a.path) } }
23
+ case 'list':
24
+ return { tool_name: 'LS', tool_input: { path: s(a.path) } }
25
+ case 'webfetch':
26
+ return { tool_name: 'WebFetch', tool_input: { url: s(a.url) } }
27
+ case 'task':
28
+ return { tool_name: 'Task', tool_input: {} }
29
+ case 'todowrite':
30
+ case 'todoread':
31
+ return { tool_name: 'TodoWrite', tool_input: {} }
32
+ case 'patch':
33
+ case 'apply_patch':
34
+ return { tool_name: 'apply_patch', tool_input: { patch: s(a.patchText) || s(a.patch) || s(a.input) } }
35
+ default:
36
+ // 不认识的工具(MCP 等)原名原参数交出去:classify 对不认识的一律推设备
37
+ return { tool_name: String(tool || ''), tool_input: a }
38
+ }
39
+ }
40
+
41
+ /** permission.asked → PermissionRequest 的 tool_name / tool_input,给设备卡片生成摘要 */
42
+ export function askToCc(props) {
43
+ const md = (props && props.metadata) || {}
44
+ if (typeof md.command === 'string' && md.command) return { tool_name: 'Bash', tool_input: { command: md.command } }
45
+ const file = md.filepath || md.filePath
46
+ if (typeof file === 'string' && file) return { tool_name: 'Edit', tool_input: { file_path: file } }
47
+ const patterns = Array.isArray(props && props.patterns) ? props.patterns.join(' ') : ''
48
+ return { tool_name: String((props && props.permission) || 'permission'), tool_input: { path: patterns } }
49
+ }
50
+
51
+ /**
52
+ * 授权请求里 opencode 自己给的「以后都允许」模式(permission.asked 的 always,如 bash 的 `npm *`、edit 的 `*`)
53
+ * → 一条 session 级 addRules 建议(与 Claude Code 的 permission_suggestions 同形),daemon 据此在设备上给「允许并记住」。
54
+ * 设备选了记住,gate 输出带 updatedPermissions,插件回 always(opencode 在本次运行期间记住这些模式)。没有 always 返回 undefined
55
+ */
56
+ export function alwaysSuggestions(props) {
57
+ const perm = String((props && props.permission) || '').trim()
58
+ const always = props && Array.isArray(props.always) ? props.always.filter((x) => typeof x === 'string' && x.trim() !== '') : []
59
+ if (!perm || always.length === 0) return undefined
60
+ return [{ type: 'addRules', rules: always.map((x) => ({ toolName: perm, ruleContent: `${perm} ${x.trim()}` })), behavior: 'allow', destination: 'session' }]
61
+ }
62
+
63
+ /**
64
+ * 这次提问来自哪个工具:question.asked 只带 tool.callID,工具名要从 tool.execute.before 记下的调用里查。
65
+ * 🔴 只推 question 工具本身的提问:plan_exit 也走 question.asked(「计划完成,是否切到 build」Yes/No),
66
+ * 推到设备 = 盲批计划,违背 ExitPlanMode 不推的原则(设备答选择题 spec §3.7)。认不出来源的一律不推
67
+ */
68
+ export function isPlainQuestion(props, callTool) {
69
+ if (callTool !== 'question') return false
70
+ const qs = props && Array.isArray(props.questions) ? props.questions : []
71
+ // 兜底:plan_exit 的固定题面 / header
72
+ return !qs.some((q) => q && (q.header === 'Build Agent' || (typeof q.question === 'string' && /^Plan at .* is complete\./.test(q.question))))
73
+ }
74
+
75
+ /**
76
+ * question.asked(opencode 的 question 工具 / plan_exit 等向用户提问)→ AskUserQuestion 形状的 tool_input,
77
+ * 统一走 daemon 的选择题链路(设备答选择题 spec §3.4)。`multiple` → multiSelect;`custom:false` 原样带上(不许自己填)
78
+ */
79
+ export function questionToCc(props) {
80
+ const qs = props && Array.isArray(props.questions) ? props.questions : null
81
+ if (!qs || qs.length === 0) return null
82
+ return {
83
+ tool_name: 'AskUserQuestion',
84
+ tool_input: {
85
+ questions: qs.map((q) => ({
86
+ question: q && q.question,
87
+ header: (q && q.header) || '',
88
+ multiSelect: !!(q && q.multiple),
89
+ options: q && Array.isArray(q.options) ? q.options.map((o) => ({ label: o && o.label, description: (o && o.description) || '' })) : [],
90
+ ...(q && q.custom === false ? { custom: false } : {}),
91
+ })),
92
+ },
93
+ }
94
+ }
95
+
96
+ /** opencode 起标题前的占位名(1.18.32 isDefaultTitle:`New session - ` / `Child session - ` + ISO 时间),不当标题 */
97
+ const OC_DEFAULT_TITLE_RE = /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
98
+
99
+ /** session.updated 的 info → 记下自动标题。子会话(subagent,带 parentID)、占位名不记 */
100
+ export function rememberTitle(titles, info) {
101
+ if (!info || typeof info.id !== 'string' || !info.id || info.parentID) return
102
+ const t = typeof info.title === 'string' ? info.title.trim() : ''
103
+ if (!t || OC_DEFAULT_TITLE_RE.test(t)) return
104
+ titles.set(info.id, t.slice(0, 100))
105
+ }
106
+
package/dist/opencode.mjs CHANGED
@@ -21,8 +21,13 @@
21
21
  // 🔴 窗口模式反过来:任何失败都不表态,授权框照常由人处理。
22
22
 
23
23
  import { spawn } from 'node:child_process'
24
+ import { appendFileSync } from 'node:fs'
24
25
  import { dirname, join } from 'node:path'
25
26
  import { fileURLToPath } from 'node:url'
27
+ import { alwaysSuggestions, askToCc, isPlainQuestion, questionToCc, rememberTitle, toCcTool } from './ocTranslate.mjs'
28
+
29
+ // 翻译函数搬到了 ocTranslate.mjs(与 DumateWatch 共用),这里转出去保持原来的导出
30
+ export { alwaysSuggestions, askToCc, isPlainQuestion, questionToCc, rememberTitle, toCcTool }
26
31
 
27
32
  const HERE = dirname(fileURLToPath(import.meta.url))
28
33
  const HOOK = join(HERE, 'hook.cjs')
@@ -62,63 +67,6 @@ export function isHeadless(argv = process.argv) {
62
67
  return (end >= 0 ? args.slice(0, end) : args).includes('run')
63
68
  }
64
69
 
65
- /** opencode 工具 → cc-hook 同构的 tool_name / tool_input(daemon 的 classify 按 Claude Code 的工具名判断) */
66
- export function toCcTool(tool, args) {
67
- const a = args && typeof args === 'object' ? args : {}
68
- const s = (v) => (typeof v === 'string' ? v : '')
69
- switch (tool) {
70
- case 'bash':
71
- return { tool_name: 'Bash', tool_input: { command: s(a.command) } }
72
- case 'read':
73
- return { tool_name: 'Read', tool_input: { file_path: s(a.filePath) } }
74
- case 'edit':
75
- return { tool_name: 'Edit', tool_input: { file_path: s(a.filePath) } }
76
- case 'write':
77
- return { tool_name: 'Write', tool_input: { file_path: s(a.filePath) } }
78
- case 'glob':
79
- return { tool_name: 'Glob', tool_input: { pattern: s(a.pattern), path: s(a.path) } }
80
- case 'grep':
81
- return { tool_name: 'Grep', tool_input: { pattern: s(a.pattern), path: s(a.path) } }
82
- case 'list':
83
- return { tool_name: 'LS', tool_input: { path: s(a.path) } }
84
- case 'webfetch':
85
- return { tool_name: 'WebFetch', tool_input: { url: s(a.url) } }
86
- case 'task':
87
- return { tool_name: 'Task', tool_input: {} }
88
- case 'todowrite':
89
- case 'todoread':
90
- return { tool_name: 'TodoWrite', tool_input: {} }
91
- case 'patch':
92
- case 'apply_patch':
93
- return { tool_name: 'apply_patch', tool_input: { patch: s(a.patchText) || s(a.patch) || s(a.input) } }
94
- default:
95
- // 不认识的工具(MCP 等)原名原参数交出去:classify 对不认识的一律推设备
96
- return { tool_name: String(tool || ''), tool_input: a }
97
- }
98
- }
99
-
100
- /** permission.asked → PermissionRequest 的 tool_name / tool_input,给设备卡片生成摘要 */
101
- export function askToCc(props) {
102
- const md = (props && props.metadata) || {}
103
- if (typeof md.command === 'string' && md.command) return { tool_name: 'Bash', tool_input: { command: md.command } }
104
- const file = md.filepath || md.filePath
105
- if (typeof file === 'string' && file) return { tool_name: 'Edit', tool_input: { file_path: file } }
106
- const patterns = Array.isArray(props && props.patterns) ? props.patterns.join(' ') : ''
107
- return { tool_name: String((props && props.permission) || 'permission'), tool_input: { path: patterns } }
108
- }
109
-
110
- /**
111
- * 授权请求里 opencode 自己给的「以后都允许」模式(permission.asked 的 always,如 bash 的 `npm *`、edit 的 `*`)
112
- * → 一条 session 级 addRules 建议(与 Claude Code 的 permission_suggestions 同形),daemon 据此在设备上给「允许并记住」。
113
- * 设备选了记住,gate 输出带 updatedPermissions,插件回 always(opencode 在本次运行期间记住这些模式)。没有 always 返回 undefined
114
- */
115
- export function alwaysSuggestions(props) {
116
- const perm = String((props && props.permission) || '').trim()
117
- const always = props && Array.isArray(props.always) ? props.always.filter((x) => typeof x === 'string' && x.trim() !== '') : []
118
- if (!perm || always.length === 0) return undefined
119
- return [{ type: 'addRules', rules: always.map((x) => ({ toolName: perm, ruleContent: `${perm} ${x.trim()}` })), behavior: 'allow', destination: 'session' }]
120
- }
121
-
122
70
  /**
123
71
  * 远程模式:关卡改由 tool.execute.before 把,放开权限。🔴 必须原地改(整对象替换不生效,O8)。
124
72
  * - OC_GATED_KEYS 是 daemon 压成 deny 的,放成 allow(用户原来在这些键上写的值已被覆盖,找不回来 —— 每一步都要过设备)
@@ -184,17 +132,6 @@ export function injectAppended(output, raw) {
184
132
  return o && typeof o.systemMessage === 'string' ? o.systemMessage : ''
185
133
  }
186
134
 
187
- /** opencode 起标题前的占位名(1.18.32 isDefaultTitle:`New session - ` / `Child session - ` + ISO 时间),不当标题 */
188
- const OC_DEFAULT_TITLE_RE = /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
189
-
190
- /** session.updated 的 info → 记下自动标题。子会话(subagent,带 parentID)、占位名不记 */
191
- export function rememberTitle(titles, info) {
192
- if (!info || typeof info.id !== 'string' || !info.id || info.parentID) return
193
- const t = typeof info.title === 'string' ? info.title.trim() : ''
194
- if (!t || OC_DEFAULT_TITLE_RE.test(t)) return
195
- titles.set(info.id, t.slice(0, 100))
196
- }
197
-
198
135
  /** 活着的子进程;opencode 退出时一并结束,不留孤儿 gate 挂着设备卡片(评审) */
199
136
  const LIVE = new Set()
200
137
  process.on('exit', () => {
@@ -238,7 +175,18 @@ function startChild(node, script, args, input, killMs = 0) {
238
175
 
239
176
  const runChild = (node, script, args, input, killMs) => startChild(node, script, args, input, killMs).done
240
177
 
178
+ /**
179
+ * 跑在百度搭子(DuMate,内核是改名的 opencode:dumate-opencode)里。那边的会话由 daemon 的 DumateWatch 直连 HTTP 上报和推卡,
180
+ * 插件再报一遍就是 tool=opencode 的第二份会话 + 双卡(spec 2026-10-03 §3.2「防双卡」)
181
+ */
182
+ export function isDumateHost(env = process.env, execPath = process.execPath) {
183
+ return !!(env && env.DUMATE_INAPP_KEY) || /dumate-opencode/i.test(String(execPath || ''))
184
+ }
185
+
241
186
  export function make(opts = {}) {
187
+ if (isDumateHost(opts.env, opts.execPath)) return async function DeskBuddyOff() {
188
+ return {}
189
+ }
242
190
  return async function DeskBuddy(ctx) {
243
191
  const node = process.env.DESKBUDDY_NODE || opts.node || 'node'
244
192
  const client = ctx && ctx.client
@@ -325,6 +273,82 @@ export function make(opts = {}) {
325
273
  if (typeof out.systemMessage === 'string' && out.systemMessage) await toast(out.systemMessage)
326
274
  }
327
275
 
276
+ // ---- 选择题(question.asked):推设备作答,答案经 /question/{id}/reply 回填;设备跳过 → reject ----
277
+ const pendingQ = new Map() // requestID → gate 子进程
278
+ const debugLog = (line) => {
279
+ try {
280
+ appendFileSync(join(HERE, 'hook-debug.log'), `${new Date().toISOString()} [opencode] ${line}\n`, 'utf8')
281
+ } catch (_) {
282
+ /* 记不下不影响 */
283
+ }
284
+ }
285
+ /** 返回是否成功(hey-api 客户端不抛错,失败在返回值的 error / response.status 里) */
286
+ const postRaw = async (url, path, body) => {
287
+ // 插件拿到的是 v1 SDK 客户端,没有 question 的方法:用它底下的 HTTP 客户端直接打(路径参数由客户端填)
288
+ const http = client && (client._client || client.client)
289
+ if (!http || typeof http.post !== 'function') {
290
+ debugLog(`回填选择题失败:客户端没有 post(${url})`)
291
+ return false
292
+ }
293
+ try {
294
+ const r = await http.post({ url, path, ...(body ? { body, headers: { 'Content-Type': 'application/json' } } : {}) })
295
+ const status = r && r.response && typeof r.response.status === 'number' ? r.response.status : 0
296
+ if ((r && r.error) || status >= 400) {
297
+ debugLog(`回填选择题失败:${url} status=${status || '?'}`)
298
+ return false
299
+ }
300
+ return true
301
+ } catch (e) {
302
+ debugLog(`回填选择题出错:${url} ${(e && e.message) || e}`)
303
+ return false
304
+ }
305
+ }
306
+ const onQuestion = async (p) => {
307
+ const { id, sessionID } = p || {}
308
+ if (!id || !sessionID || runId || headless || !client) return
309
+ const call = p.tool && p.tool.callID ? calls.get(p.tool.callID) : null
310
+ if (!isPlainQuestion(p, call && call.tool)) return
311
+ const cc = questionToCc(p)
312
+ if (!cc) return
313
+ const input = { hook_event_name: 'PermissionRequest', session_id: sessionID, cwd, ...cc }
314
+ const { child, done } = startChild(node, GATE, ['--window', '--tool', TOOL], input)
315
+ if (child) pendingQ.set(id, child)
316
+ const r = await done
317
+ pendingQ.delete(id)
318
+ if (r.code !== 0 || !r.out) return
319
+ let out
320
+ try {
321
+ out = JSON.parse(r.out)
322
+ } catch (_) {
323
+ return
324
+ }
325
+ const d = out && out.hookSpecificOutput && out.hookSpecificOutput.decision
326
+ if (!d) return
327
+ let ok
328
+ if (d.behavior === 'allow' && Array.isArray(out.deskbuddyAnswers)) {
329
+ ok = await postRaw('/question/{requestID}/reply', { requestID: id }, { answers: out.deskbuddyAnswers })
330
+ } else if (d.behavior === 'deny') {
331
+ ok = await postRaw('/question/{requestID}/reject', { requestID: id })
332
+ } else return
333
+ if (!ok) {
334
+ await toast('设备答案未能回填,请在窗口作答')
335
+ return
336
+ }
337
+ if (typeof out.systemMessage === 'string' && out.systemMessage) await toast(out.systemMessage)
338
+ }
339
+ const onQuestionDone = (p) => {
340
+ // 人先在窗口里答了 / 拒了:结束 gate 子进程,daemon 随之撤掉设备上的卡片
341
+ const child = p && pendingQ.get(p.requestID)
342
+ if (child) {
343
+ pendingQ.delete(p.requestID)
344
+ try {
345
+ child.kill('SIGTERM')
346
+ } catch (_) {
347
+ /* 已经退了 */
348
+ }
349
+ }
350
+ }
351
+
328
352
  const onReplied = (p) => {
329
353
  // 人先在窗口里答了:结束 gate 子进程,daemon 随之撤掉设备上的卡片
330
354
  const child = p && pending.get(p.requestID)
@@ -362,7 +386,7 @@ export function make(opts = {}) {
362
386
 
363
387
  'tool.execute.before': async (input, output) => {
364
388
  const cc = toCcTool(input && input.tool, output && output.args)
365
- if (input && input.callID) calls.set(input.callID, { cc, at: Date.now() })
389
+ if (input && input.callID) calls.set(input.callID, { cc, tool: input && input.tool, at: Date.now() })
366
390
  report('PreToolUse', input && input.sessionID, cc)
367
391
  if (!runId) return
368
392
  // 远程关卡:等设备。任何失败都拒(见顶部)
@@ -398,6 +422,13 @@ export function make(opts = {}) {
398
422
  case 'permission.replied':
399
423
  onReplied(p)
400
424
  break
425
+ case 'question.asked':
426
+ onQuestion(p).catch(() => {})
427
+ break
428
+ case 'question.replied':
429
+ case 'question.rejected':
430
+ onQuestionDone(p)
431
+ break
401
432
  case 'session.idle':
402
433
  report('Stop', p.sessionID)
403
434
  break
@@ -0,0 +1,24 @@
1
+ // hook/deskbuddy-dsh.mjs 的类型声明(只给测试用;插件本体是零依赖 JS,跑在 dsh 进程里)
2
+ type Cc = { tool_name: string; tool_input: Record<string, unknown> }
3
+ export const TOOL: string
4
+ export const SUPPORTED_VERSION_RE: RegExp
5
+ export function toCcTool(tool: unknown, args: unknown): Cc
6
+ export function parseArgs(raw: unknown): Record<string, unknown>
7
+ export function questionsToCc(questions: unknown): { tool_name: string; tool_input: { questions: Array<Record<string, unknown>> } } | null
8
+ export function answersFromLists(questions: unknown, lists: unknown): { answers: Array<{ id: string; selected: string[]; custom?: string }> } | null
9
+ export function skippedAnswers(questions: unknown): { answers: Array<{ id: string; selected: string[] }> } | null
10
+ export function profileName(ctxProfile: unknown, argv?: string[]): string
11
+ export function isWindowProfile(name: unknown): boolean
12
+ export function gatingAllowed(version: string): boolean
13
+ export function dshVersion(argv?: string[]): string
14
+ export function parseGateOut(r: unknown): { decision: 'allow' | 'deny'; answers: string[][] | null } | null
15
+ export function race<T>(next: () => Promise<T> | T, device: Promise<T | null>, cancelDevice: () => void, abortNative: () => void): Promise<T>
16
+ export function spliceSignal(req: { signal?: AbortSignal }, ac: AbortController): boolean
17
+ export const name: string
18
+ export function apply(ctx: unknown, config?: { node?: string }): void
19
+ export function runIdFromEnv(env?: Record<string, string | undefined>): string
20
+ export function mailboxHome(env?: Record<string, string | undefined>): string
21
+ export function runGateAllowed(r: unknown): boolean
22
+ export function rootSession(parents: Map<string, string>, sid: string): string
23
+ export const STALE_LETTER_MS: number
24
+ export const LETTER_RE: RegExp