@sayknow-cli/coding-agent 0.3.12 → 0.3.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 (248) hide show
  1. package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
  2. package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
  3. package/dist/types/cli/classify-install-target.d.ts +0 -10
  4. package/dist/types/cli/daemon-cli.d.ts +2 -0
  5. package/dist/types/cli/initial-message.d.ts +1 -1
  6. package/dist/types/cli/malloc-env-guard.d.ts +65 -0
  7. package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
  8. package/dist/types/cli/notify-cli.d.ts +3 -1
  9. package/dist/types/cli/session-picker.d.ts +4 -3
  10. package/dist/types/cli/update-cli.d.ts +22 -3
  11. package/dist/types/cli.d.ts +5 -0
  12. package/dist/types/commands/daemon.d.ts +6 -2
  13. package/dist/types/commands/launch.d.ts +1 -0
  14. package/dist/types/commands/notify.d.ts +6 -0
  15. package/dist/types/commands/web-search.d.ts +1 -1
  16. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
  17. package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
  18. package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
  19. package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
  20. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  21. package/dist/types/commit/analysis/summary.d.ts +1 -1
  22. package/dist/types/commit/changelog/generate.d.ts +3 -3
  23. package/dist/types/commit/changelog/index.d.ts +2 -2
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  26. package/dist/types/commit/shared-llm.d.ts +9 -9
  27. package/dist/types/config/keybindings.d.ts +134 -129
  28. package/dist/types/config/model-profile-activation.d.ts +1 -0
  29. package/dist/types/config/model-registry.d.ts +35 -35
  30. package/dist/types/config/models-config-schema.d.ts +111 -111
  31. package/dist/types/config/settings-schema.d.ts +56 -16
  32. package/dist/types/config/settings.d.ts +2 -0
  33. package/dist/types/coordinator/contract.d.ts +1 -1
  34. package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
  35. package/dist/types/coordinator-mcp/policy.d.ts +3 -0
  36. package/dist/types/coordinator-mcp/server.d.ts +79 -6
  37. package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
  38. package/dist/types/daemon/control-types.d.ts +10 -0
  39. package/dist/types/daemon/operator-contract.d.ts +47 -0
  40. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
  41. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
  42. package/dist/types/discovery/helpers.d.ts +1 -1
  43. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  44. package/dist/types/edit/modes/patch.d.ts +2 -2
  45. package/dist/types/extensibility/extensions/types.d.ts +3 -1
  46. package/dist/types/goals/tools/goal-tool.d.ts +6 -6
  47. package/dist/types/i18n/messages/en.d.ts +8 -8
  48. package/dist/types/internal-urls/local-protocol.d.ts +13 -9
  49. package/dist/types/lsp/index.d.ts +9 -9
  50. package/dist/types/lsp/types.d.ts +9 -9
  51. package/dist/types/main.d.ts +66 -2
  52. package/dist/types/modes/acp/acp-agent.d.ts +19 -2
  53. package/dist/types/modes/acp/acp-mode.d.ts +12 -0
  54. package/dist/types/modes/components/assistant-message.d.ts +1 -1
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  56. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  57. package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
  58. package/dist/types/modes/components/pet-selector.d.ts +11 -0
  59. package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
  60. package/dist/types/modes/components/session-selector.d.ts +22 -9
  61. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  62. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  63. package/dist/types/modes/components/status-line.d.ts +0 -10
  64. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  65. package/dist/types/modes/components/user-message.d.ts +2 -1
  66. package/dist/types/modes/controllers/event-controller.d.ts +4 -0
  67. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  68. package/dist/types/modes/interactive-mode.d.ts +24 -2
  69. package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
  70. package/dist/types/modes/print-mode.d.ts +3 -4
  71. package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
  72. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
  73. package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
  74. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
  75. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
  76. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
  77. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
  78. package/dist/types/modes/theme/defaults/index.d.ts +568 -568
  79. package/dist/types/modes/types.d.ts +28 -2
  80. package/dist/types/modes/utils/abort-message.d.ts +1 -1
  81. package/dist/types/modes/utils/context-usage.d.ts +4 -19
  82. package/dist/types/modes/utils/irc-message.d.ts +38 -0
  83. package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
  84. package/dist/types/notifications/config.d.ts +34 -0
  85. package/dist/types/notifications/daemon-paths.d.ts +7 -0
  86. package/dist/types/notifications/index.d.ts +27 -0
  87. package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
  88. package/dist/types/notifications/notification-service.d.ts +144 -0
  89. package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
  90. package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
  91. package/dist/types/notifications/telegram-daemon.d.ts +130 -3
  92. package/dist/types/notifications/telegram-reference.d.ts +19 -2
  93. package/dist/types/notifications/topic-registry.d.ts +24 -4
  94. package/dist/types/registry/agent-registry.d.ts +2 -0
  95. package/dist/types/rlm/index.d.ts +1 -1
  96. package/dist/types/sdk.d.ts +2 -0
  97. package/dist/types/secrets/obfuscator.d.ts +1 -1
  98. package/dist/types/session/agent-session.d.ts +40 -3
  99. package/dist/types/session/cache-economics.d.ts +79 -0
  100. package/dist/types/session/context-estimation.d.ts +31 -0
  101. package/dist/types/session/provider-safety-stop.d.ts +5 -0
  102. package/dist/types/session/session-manager.d.ts +124 -4
  103. package/dist/types/session/session-storage.d.ts +155 -13
  104. package/dist/types/setup/credential-auto-import.d.ts +2 -2
  105. package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
  106. package/dist/types/skc-runtime/state-schema.d.ts +19 -19
  107. package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
  108. package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
  109. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
  110. package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
  111. package/dist/types/task/agents.d.ts +2 -2
  112. package/dist/types/task/executor.d.ts +3 -0
  113. package/dist/types/task/receipt.d.ts +3 -1
  114. package/dist/types/task/types.d.ts +35 -27
  115. package/dist/types/tools/ask-answer-registry.d.ts +5 -0
  116. package/dist/types/tools/bash.d.ts +1 -1
  117. package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
  118. package/dist/types/tools/browser.d.ts +28 -28
  119. package/dist/types/tools/computer.d.ts +20 -20
  120. package/dist/types/tools/cron.d.ts +2 -2
  121. package/dist/types/tools/debug.d.ts +40 -40
  122. package/dist/types/tools/gh.d.ts +10 -10
  123. package/dist/types/tools/image-gen.d.ts +2 -2
  124. package/dist/types/tools/index.d.ts +58 -6
  125. package/dist/types/tools/monitor.d.ts +6 -6
  126. package/dist/types/tools/skill-discovery.d.ts +4 -4
  127. package/dist/types/tools/subagent.d.ts +12 -12
  128. package/dist/types/tools/telegram-send.d.ts +7 -0
  129. package/dist/types/tools/todo-write.d.ts +8 -8
  130. package/dist/types/utils/external-editor.d.ts +2 -0
  131. package/dist/types/web/insane/bridge.d.ts +9 -9
  132. package/dist/types/web/search/index.d.ts +4 -4
  133. package/package.json +8 -8
  134. package/scripts/g004-tmux-smoke.ts +196 -36
  135. package/src/cli/daemon-cli.ts +19 -29
  136. package/src/cli/malloc-env-guard.test.ts +104 -0
  137. package/src/cli/malloc-env-guard.ts +149 -0
  138. package/src/cli/notify-cli.ts +78 -14
  139. package/src/cli/session-picker.ts +23 -40
  140. package/src/cli/update-cli.ts +56 -37
  141. package/src/cli.ts +51 -10
  142. package/src/commands/daemon.ts +19 -5
  143. package/src/commands/harness.ts +515 -47
  144. package/src/commands/launch.ts +11 -3
  145. package/src/commands/notify.ts +5 -1
  146. package/src/commands/session.ts +6 -1
  147. package/src/config/keybindings.ts +5 -0
  148. package/src/config/model-profile-activation.ts +1 -1
  149. package/src/config/model-profiles.ts +14 -14
  150. package/src/config/settings-schema.ts +41 -1
  151. package/src/config/settings.ts +96 -45
  152. package/src/coordinator/contract.ts +1 -0
  153. package/src/coordinator-mcp/model-preset.ts +100 -0
  154. package/src/coordinator-mcp/policy.ts +30 -0
  155. package/src/coordinator-mcp/server.ts +2958 -430
  156. package/src/coordinator-mcp/session-reaper.ts +129 -0
  157. package/src/daemon/control-types.ts +11 -0
  158. package/src/daemon/operator-contract.ts +121 -0
  159. package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  160. package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  161. package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  162. package/src/extensibility/extensions/runner.ts +1 -1
  163. package/src/extensibility/extensions/types.ts +3 -1
  164. package/src/internal-urls/docs-index.generated.ts +10 -9
  165. package/src/internal-urls/local-protocol.ts +29 -10
  166. package/src/main.ts +301 -80
  167. package/src/modes/acp/acp-agent.ts +730 -100
  168. package/src/modes/acp/acp-mode.ts +29 -2
  169. package/src/modes/components/assistant-message.ts +32 -3
  170. package/src/modes/components/bash-execution.ts +43 -13
  171. package/src/modes/components/custom-editor.ts +5 -0
  172. package/src/modes/components/footer.ts +3 -3
  173. package/src/modes/components/hook-selector.ts +12 -0
  174. package/src/modes/components/irc-sidebar.ts +206 -0
  175. package/src/modes/components/pet-selector.ts +55 -0
  176. package/src/modes/components/sayknow-pet-widget.ts +360 -0
  177. package/src/modes/components/session-selector.ts +222 -221
  178. package/src/modes/components/settings-selector.ts +10 -0
  179. package/src/modes/components/status-line/segments.ts +14 -7
  180. package/src/modes/components/status-line/types.ts +1 -1
  181. package/src/modes/components/status-line.ts +4 -171
  182. package/src/modes/components/tool-execution.ts +47 -6
  183. package/src/modes/components/user-message.ts +26 -9
  184. package/src/modes/controllers/command-controller.ts +28 -2
  185. package/src/modes/controllers/event-controller.ts +159 -36
  186. package/src/modes/controllers/extension-ui-controller.ts +37 -18
  187. package/src/modes/controllers/input-controller.ts +8 -2
  188. package/src/modes/controllers/selector-controller.ts +62 -15
  189. package/src/modes/interactive-mode.ts +199 -7
  190. package/src/modes/irc-observation-ledger.ts +127 -0
  191. package/src/modes/print-mode.ts +228 -92
  192. package/src/modes/rpc/rpc-client.ts +58 -1
  193. package/src/modes/rpc/rpc-mode.ts +286 -85
  194. package/src/modes/rpc/rpc-types.ts +15 -1
  195. package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
  196. package/src/modes/shared/agent-wire/command-validation.ts +10 -0
  197. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  198. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  199. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  200. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  201. package/src/modes/types.ts +34 -2
  202. package/src/modes/utils/context-usage.ts +56 -92
  203. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  204. package/src/modes/utils/irc-message.ts +138 -0
  205. package/src/modes/utils/ui-helpers.ts +216 -50
  206. package/src/notifications/config.ts +44 -0
  207. package/src/notifications/daemon-paths.ts +8 -0
  208. package/src/notifications/index.ts +535 -75
  209. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  210. package/src/notifications/notification-service.ts +710 -0
  211. package/src/notifications/rate-limit-pool.ts +46 -12
  212. package/src/notifications/telegram-daemon-control.ts +27 -2
  213. package/src/notifications/telegram-daemon.ts +635 -94
  214. package/src/notifications/telegram-reference.ts +73 -13
  215. package/src/notifications/topic-registry.ts +86 -9
  216. package/src/prompts/system/irc-peer-roster.md +1 -0
  217. package/src/prompts/system/subagent-system-prompt.md +1 -0
  218. package/src/prompts/system/system-prompt.md +1 -0
  219. package/src/prompts/tools/irc.md +1 -1
  220. package/src/registry/agent-registry.ts +3 -0
  221. package/src/sdk.ts +52 -1
  222. package/src/secrets/obfuscator.ts +4 -2
  223. package/src/session/agent-session.ts +670 -217
  224. package/src/session/cache-economics.ts +275 -0
  225. package/src/session/context-estimation.ts +91 -0
  226. package/src/session/provider-safety-stop.ts +9 -0
  227. package/src/session/session-dump-format.ts +6 -0
  228. package/src/session/session-manager.ts +809 -39
  229. package/src/session/session-storage.ts +582 -38
  230. package/src/skc-runtime/session-state-sidecar.ts +756 -135
  231. package/src/skc-runtime/tmux-common.ts +26 -7
  232. package/src/skc-runtime/tmux-gc.ts +52 -9
  233. package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
  234. package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
  235. package/src/skc-runtime/tmux-sessions.ts +871 -68
  236. package/src/slash-commands/builtin-registry.ts +153 -9
  237. package/src/slash-commands/helpers/context-report.ts +26 -4
  238. package/src/task/executor.ts +12 -3
  239. package/src/task/index.ts +8 -0
  240. package/src/task/receipt.ts +7 -2
  241. package/src/task/types.ts +26 -0
  242. package/src/tools/ask-answer-registry.ts +25 -0
  243. package/src/tools/browser/tab-supervisor.ts +63 -6
  244. package/src/tools/browser.ts +8 -3
  245. package/src/tools/index.ts +81 -6
  246. package/src/tools/sqlite-reader.ts +8 -4
  247. package/src/tui/output-block.ts +18 -6
  248. package/src/utils/external-editor.ts +6 -1
@@ -9,8 +9,9 @@
9
9
  * until the RuntimeOwner (M3+) lands.
10
10
  */
11
11
  import { execFileSync } from "node:child_process";
12
- import { randomBytes } from "node:crypto";
12
+ import { randomBytes, randomUUID } from "node:crypto";
13
13
  import { existsSync, readFileSync } from "node:fs";
14
+ import * as fs from "node:fs/promises";
14
15
  import * as path from "node:path";
15
16
  import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
16
17
  import { classifyRecovery } from "../harness-control-plane/classifier";
@@ -46,10 +47,79 @@ import {
46
47
  type SessionHandle,
47
48
  type SessionState,
48
49
  } from "../harness-control-plane/types";
49
- import { resolveSkcTmuxCommand, sanitizeTmuxToken } from "../skc-runtime/tmux-common";
50
+ import { SPAWN_PROVENANCE_ENV } from "../notifications/config";
51
+ import {
52
+ SKC_TMUX_OWNER_GENERATION_ENV,
53
+ SKC_TMUX_OWNER_SERVER_KEY_ENV,
54
+ SKC_TMUX_OWNER_STATE_DIR_ENV,
55
+ } from "../skc-runtime/session-state-sidecar";
56
+ import { resolveSkcTmuxBinary, resolveSkcTmuxCommand, sanitizeTmuxToken } from "../skc-runtime/tmux-common";
57
+ import {
58
+ captureOwnerGenerationBaselineSync,
59
+ classifyCgroup,
60
+ isExactScopedBootstrapSuccessReceipt,
61
+ isOwnerGenerationBaselineCurrentSync,
62
+ type OwnerGenerationBaseline,
63
+ type OwnerIsolationProbe,
64
+ observeOwnerTerminal,
65
+ ownerProcessStartTime,
66
+ planTmuxOwnerIsolation,
67
+ replaceOwnerGenerationSync,
68
+ type TmuxServerProof,
69
+ } from "../skc-runtime/tmux-owner-isolation";
70
+
71
+ const PRIVATE_OWNER_CONTROL_FIELDS = new Set([
72
+ "socket_key",
73
+ "socketKey",
74
+ "tmux_socket_key",
75
+ "tmuxSocketKey",
76
+ "tmux_owner_socket_key",
77
+ "tmuxOwnerSocketKey",
78
+ "owner_generation",
79
+ "ownerGeneration",
80
+ "state_dir",
81
+ "stateDir",
82
+ "owner_state_dir",
83
+ "ownerStateDir",
84
+ "owner_server_key",
85
+ "ownerServerKey",
86
+ "owner_server_pid",
87
+ "ownerServerPid",
88
+ "owner_server_start_time",
89
+ "ownerServerStartTime",
90
+ "tmux_owner_generation",
91
+ "tmuxOwnerGeneration",
92
+ "tmux_owner_state_dir",
93
+ "tmuxOwnerStateDir",
94
+ "tmux_owner_server_key",
95
+ "tmuxOwnerServerKey",
96
+ "tmux_owner_server_pid",
97
+ "tmuxOwnerServerPid",
98
+ "tmux_owner_server_start_time",
99
+ "tmuxOwnerServerStartTime",
100
+ "socket_path",
101
+ "socketPath",
102
+ "endpoint",
103
+ "owner_terminal",
104
+ "ownerTerminal",
105
+ "generation",
106
+ "server_key",
107
+ "intent_id",
108
+ "dedupe_key",
109
+ ]);
110
+
111
+ function publicHarnessResponse(value: unknown): unknown {
112
+ if (Array.isArray(value)) return value.map(publicHarnessResponse);
113
+ if (!value || typeof value !== "object") return value;
114
+ return Object.fromEntries(
115
+ Object.entries(value as Record<string, unknown>)
116
+ .filter(([key]) => !PRIVATE_OWNER_CONTROL_FIELDS.has(key))
117
+ .map(([key, item]) => [key, publicHarnessResponse(item)]),
118
+ );
119
+ }
50
120
 
51
121
  function writeJson(value: unknown): void {
52
- process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
122
+ process.stdout.write(`${JSON.stringify(publicHarnessResponse(value), null, 2)}\n`);
53
123
  }
54
124
 
55
125
  function nowIso(): string {
@@ -473,6 +543,7 @@ interface OwnerSpawnResult {
473
543
  live: boolean;
474
544
  runtime: "tmux" | "detached" | "manual";
475
545
  tmuxSessionName: string | null;
546
+ socketKey: string | null;
476
547
  fallbackReason: string | null;
477
548
  blockerReason: string | null;
478
549
  }
@@ -481,6 +552,113 @@ function shellQuote(value: string): string {
481
552
  return `'${value.replaceAll("'", "'\\''")}'`;
482
553
  }
483
554
 
555
+ function isBoundedNoServerDiagnostic(stderr: Uint8Array): boolean {
556
+ const diagnostic = new TextDecoder().decode(stderr);
557
+ return (
558
+ diagnostic.length > 0 &&
559
+ diagnostic.length <= 512 &&
560
+ /^(?:no server running on |failed to connect to server|error connecting to )/i.test(diagnostic.trim())
561
+ );
562
+ }
563
+
564
+ function sameServerIdentity(left: TmuxServerProof, right: TmuxServerProof): boolean {
565
+ return (
566
+ left.pid === right.pid &&
567
+ left.startTime === right.startTime &&
568
+ left.cgroup?.classification === right.cgroup?.classification &&
569
+ left.cgroup?.scope === right.cgroup?.scope &&
570
+ left.cgroup?.diagnostic === right.cgroup?.diagnostic
571
+ );
572
+ }
573
+
574
+ function isSafeServerProof(proof: TmuxServerProof): boolean {
575
+ return (
576
+ proof.state === "safe" &&
577
+ typeof proof.pid === "number" &&
578
+ Number.isSafeInteger(proof.pid) &&
579
+ proof.pid > 0 &&
580
+ typeof proof.startTime === "string" &&
581
+ proof.startTime.length > 0 &&
582
+ (proof.cgroup?.classification === "safe" ||
583
+ (process.platform !== "linux" && proof.cgroup?.classification === "not_applicable"))
584
+ );
585
+ }
586
+
587
+ function exactNativeTmuxSessionId(stdout: Uint8Array): string | null {
588
+ const value = new TextDecoder().decode(stdout);
589
+ const line = value.endsWith("\n") ? value.slice(0, -1) : value;
590
+ return /^\$\d+$/.test(line) ? line : null;
591
+ }
592
+
593
+ interface ScopedBootstrapReceipt {
594
+ nativeSessionId: string;
595
+ serverPid: number;
596
+ serverStartTime: string;
597
+ sessionName: string;
598
+ }
599
+
600
+ function scopedBootstrapReceipt(stdout: Uint8Array): ScopedBootstrapReceipt | null {
601
+ const value = new TextDecoder().decode(stdout);
602
+ if (!isExactScopedBootstrapSuccessReceipt(value)) return null;
603
+ try {
604
+ const receipt = JSON.parse(value) as {
605
+ native_session_id: unknown;
606
+ server_pid: unknown;
607
+ server_start_time: unknown;
608
+ session_name: unknown;
609
+ };
610
+ if (
611
+ typeof receipt.native_session_id !== "string" ||
612
+ !/^\$\d+$/.test(receipt.native_session_id) ||
613
+ typeof receipt.server_pid !== "number" ||
614
+ !Number.isSafeInteger(receipt.server_pid) ||
615
+ receipt.server_pid <= 0 ||
616
+ typeof receipt.server_start_time !== "string" ||
617
+ !receipt.server_start_time ||
618
+ typeof receipt.session_name !== "string" ||
619
+ !receipt.session_name
620
+ )
621
+ return null;
622
+ return {
623
+ nativeSessionId: receipt.native_session_id,
624
+ serverPid: receipt.server_pid,
625
+ serverStartTime: receipt.server_start_time,
626
+ sessionName: receipt.session_name,
627
+ };
628
+ } catch {
629
+ return null;
630
+ }
631
+ }
632
+
633
+ function portableProcessStartTime(pid: number): string | null {
634
+ if (process.platform === "linux") return null;
635
+ const configured = process.env.SKC_HARNESS_PROCESS_START_COMMAND;
636
+ let command: string[] = ["ps", "-o", "lstart=", "-p"];
637
+ if (configured) {
638
+ try {
639
+ const parsed = JSON.parse(configured) as unknown;
640
+ if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some(value => typeof value !== "string" || !value))
641
+ return null;
642
+ command = parsed;
643
+ } catch {
644
+ return null;
645
+ }
646
+ }
647
+ const result = Bun.spawnSync([...command, String(pid)], { stdout: "pipe", stderr: "ignore" });
648
+ if (result.exitCode !== 0) return null;
649
+ const value = result.stdout.toString();
650
+ const line = value.endsWith("\n") ? value.slice(0, -1) : value;
651
+ if (
652
+ !line ||
653
+ line.includes("\n") ||
654
+ line.includes("\r") ||
655
+ Buffer.byteLength(line) > 128 ||
656
+ !/^[\x20-\x7e]+$/.test(line)
657
+ )
658
+ return null;
659
+ return `portable:${line}`;
660
+ }
661
+
484
662
  function deterministicHarnessTmuxSessionName(sessionId: string): string {
485
663
  return `sayknow_cli_harness_${sanitizeTmuxToken(sessionId)}`;
486
664
  }
@@ -627,7 +805,14 @@ export default class Harness extends Command {
627
805
  // Optional rpc command override (tests / non-default hosts); defaults to `skc --mode rpc`.
628
806
  const override = process.env.SKC_HARNESS_RPC_COMMAND;
629
807
  const command = override ? (JSON.parse(override) as string[]) : undefined;
630
- const rpc = new SayknowCliRpc({ sessionDir, command });
808
+ // Mark the RPC owner as a SKC-spawned child (canonical provenance marker)
809
+ // so `notifications.sessionScope = "primary"` suppresses its notification
810
+ // endpoint/topic; default `all` preserves historical auto-on behavior.
811
+ const rpc = new SayknowCliRpc({
812
+ sessionDir,
813
+ command,
814
+ env: { ...process.env, [SPAWN_PROVENANCE_ENV]: sessionId },
815
+ });
631
816
  const owner = new RuntimeOwner({ root, sessionId, rpc });
632
817
  const info = await owner.start();
633
818
  writeJson({ ok: true, owner: info });
@@ -671,57 +856,354 @@ export default class Harness extends Command {
671
856
  return false;
672
857
  }
673
858
 
674
- #startTmuxResidentOwner(
859
+ async #harnessOwnerIsolationProbe(tmuxCommand: string): Promise<OwnerIsolationProbe> {
860
+ return {
861
+ readCallerCgroup: async () =>
862
+ process.env.SKC_HARNESS_TEST_CALLER_CGROUP ??
863
+ (await fs.readFile("/proc/self/cgroup", "utf8").catch(() => null)),
864
+ probeServer: async (socketKey, tmuxControlArgv): Promise<TmuxServerProof> => {
865
+ if (!socketKey || socketKey.length > 128) return { state: "unverifiable" };
866
+ const controlArgv = tmuxControlArgv ?? [tmuxCommand, "-L", socketKey];
867
+ if (controlArgv.length < 3 || controlArgv[0] !== tmuxCommand || !controlArgv.includes("-L")) {
868
+ return { state: "unverifiable" };
869
+ }
870
+ const result = Bun.spawnSync([...controlArgv, "display-message", "-p", "#{pid}"], {
871
+ stdout: "pipe",
872
+ stderr: "pipe",
873
+ });
874
+ if (result.exitCode !== 0) {
875
+ return isBoundedNoServerDiagnostic(result.stderr) ? { state: "absent" } : { state: "unverifiable" };
876
+ }
877
+ const pid = Number(result.stdout.toString().trim());
878
+ if (!Number.isSafeInteger(pid) || pid <= 0) return { state: "unverifiable" };
879
+ const cgroupText =
880
+ process.env.SKC_HARNESS_TEST_SERVER_CGROUP ??
881
+ (process.platform === "linux"
882
+ ? await fs.readFile(`/proc/${pid}/cgroup`, "utf8").catch(() => null)
883
+ : null);
884
+ const testStartTime = process.env.SKC_HARNESS_TEST_SERVER_START_TIME;
885
+ const stat =
886
+ testStartTime || process.platform !== "linux"
887
+ ? null
888
+ : await fs.readFile(`/proc/${pid}/stat`, "utf8").catch(() => null);
889
+ const cgroup = classifyCgroup({ platform: process.platform, cgroupText });
890
+ const startTime =
891
+ testStartTime ??
892
+ (process.platform === "linux"
893
+ ? ownerProcessStartTime(process.platform, stat)
894
+ : portableProcessStartTime(pid));
895
+ if (!startTime) return { state: "unverifiable", pid, cgroup };
896
+ return {
897
+ state:
898
+ cgroup.classification === "safe" || cgroup.classification === "not_applicable"
899
+ ? "safe"
900
+ : cgroup.classification === "unsafe_service"
901
+ ? "unsafe"
902
+ : "unverifiable",
903
+ pid,
904
+ startTime,
905
+ cgroup,
906
+ };
907
+ },
908
+ };
909
+ }
910
+
911
+ async #nativeSessionBoundToName(
912
+ tmuxCommand: string,
913
+ socketKey: string,
914
+ nativeSessionId: string,
915
+ sessionName: string,
916
+ ): Promise<boolean> {
917
+ const result = Bun.spawnSync(
918
+ [
919
+ tmuxCommand,
920
+ "-L",
921
+ socketKey,
922
+ "display-message",
923
+ "-p",
924
+ "-t",
925
+ nativeSessionId,
926
+ "#{session_id}\t#{session_name}",
927
+ ],
928
+ { stdout: "pipe", stderr: "ignore" },
929
+ );
930
+ if (result.exitCode !== 0) return false;
931
+ const value = result.stdout.toString();
932
+ const line = value.endsWith("\n") ? value.slice(0, -1) : value;
933
+ return line === `${nativeSessionId}\t${sessionName}`;
934
+ }
935
+
936
+ async #cleanupTmuxAttempt(
937
+ tmuxCommand: string,
938
+ socketKey: string,
939
+ nativeSessionId: string | null,
940
+ sessionName: string,
941
+ proof: TmuxServerProof | null,
942
+ probeServer: OwnerIsolationProbe["probeServer"],
943
+ ): Promise<void> {
944
+ if (!nativeSessionId || !proof || !isSafeServerProof(proof)) throw new Error("tmux-owner-cleanup_uncertain");
945
+ if (!(await this.#nativeSessionBoundToName(tmuxCommand, socketKey, nativeSessionId, sessionName)))
946
+ throw new Error("tmux-owner-cleanup_uncertain");
947
+ const current = await probeServer(socketKey, [tmuxCommand, "-L", socketKey]);
948
+ if (!sameServerIdentity(proof, current)) throw new Error("tmux-owner-cleanup_uncertain");
949
+ const predicate = `#{&&:#{==:#{pid},${proof.pid}},#{&&:#{==:#{session_id},${nativeSessionId}},#{==:#{session_name},${sessionName}}}}`;
950
+ const killed = Bun.spawnSync(
951
+ [
952
+ tmuxCommand,
953
+ "-L",
954
+ socketKey,
955
+ "if-shell",
956
+ "-t",
957
+ nativeSessionId,
958
+ "-F",
959
+ predicate,
960
+ `kill-session -t '${nativeSessionId}' ; display-message -p __skc_harness_cleanup_ok__`,
961
+ "display-message -p __skc_harness_cleanup_refused__",
962
+ ],
963
+ { stdout: "pipe", stderr: "pipe" },
964
+ );
965
+ if (killed.exitCode !== 0 || killed.stdout.toString().trim() !== "__skc_harness_cleanup_ok__")
966
+ throw new Error("tmux-owner-cleanup_uncertain");
967
+ }
968
+
969
+ async #startTmuxResidentOwner(
675
970
  root: string,
676
971
  sessionId: string,
677
972
  cwd: string,
678
- ): { started: boolean; sessionName: string; reason: string | null } {
973
+ ): Promise<{
974
+ started: boolean;
975
+ sessionName: string;
976
+ socketKey: string | null;
977
+ reason: string | null;
978
+ cleanup?: () => Promise<void>;
979
+ }> {
679
980
  const tmuxCommand = resolveSkcTmuxCommand();
680
- if (Bun.which(tmuxCommand) === null) {
981
+ const sessionName = deterministicHarnessTmuxSessionName(sessionId);
982
+ if (Bun.which(tmuxCommand) === null)
983
+ return { started: false, sessionName, socketKey: null, reason: "tmux-unavailable" };
984
+ if (resolveSkcTmuxBinary({ env: process.env }).isPsmux)
681
985
  return {
682
986
  started: false,
683
- sessionName: deterministicHarnessTmuxSessionName(sessionId),
684
- reason: "tmux-unavailable",
987
+ sessionName,
988
+ socketKey: null,
989
+ reason: "tmux-owner-native_session_identity_unavailable",
685
990
  };
991
+ const socketKey = `skc-owner-${randomBytes(24).toString("hex")}`;
992
+ const ownerStateDir = root;
993
+ let baseline: OwnerGenerationBaseline;
994
+ try {
995
+ baseline = captureOwnerGenerationBaselineSync(ownerStateDir, sessionId);
996
+ } catch {
997
+ return { started: false, sessionName, socketKey, reason: "tmux-owner-generation_unverifiable" };
686
998
  }
687
- const sessionName = deterministicHarnessTmuxSessionName(sessionId);
688
- const envAssignments = [`SKC_HARNESS_STATE_ROOT=${shellQuote(root)}`];
689
- if (process.env[RECEIPT_SPOOL_DIR_ENV]) {
999
+ if (process.platform !== "linux" && !portableProcessStartTime(process.pid))
1000
+ return { started: false, sessionName, socketKey, reason: "tmux-owner-generation_unverifiable" };
1001
+ await fs.mkdir(path.join(ownerStateDir, sessionId, "owner-lifecycle"), { recursive: true, mode: 0o700 });
1002
+ const ownerGeneration = randomUUID();
1003
+ const envAssignments = [
1004
+ `SKC_HARNESS_STATE_ROOT=${shellQuote(root)}`,
1005
+ `${SKC_TMUX_OWNER_GENERATION_ENV}=${shellQuote(ownerGeneration)}`,
1006
+ `${SKC_TMUX_OWNER_STATE_DIR_ENV}=${shellQuote(ownerStateDir)}`,
1007
+ `${SKC_TMUX_OWNER_SERVER_KEY_ENV}=${shellQuote(socketKey)}`,
1008
+ ];
1009
+ if (process.env[RECEIPT_SPOOL_DIR_ENV])
690
1010
  envAssignments.push(`${RECEIPT_SPOOL_DIR_ENV}=${shellQuote(process.env[RECEIPT_SPOOL_DIR_ENV])}`);
691
- }
692
- if (process.env.SKC_HARNESS_RPC_COMMAND) {
1011
+ if (process.env.SKC_HARNESS_RPC_COMMAND)
693
1012
  envAssignments.push(`SKC_HARNESS_RPC_COMMAND=${shellQuote(process.env.SKC_HARNESS_RPC_COMMAND)}`);
694
- }
695
- if (process.env.SKC_HARNESS_TEST_NODE_MODULES) {
1013
+ if (process.env.SKC_HARNESS_TEST_NODE_MODULES)
696
1014
  envAssignments.push(`SKC_HARNESS_TEST_NODE_MODULES=${shellQuote(process.env.SKC_HARNESS_TEST_NODE_MODULES)}`);
697
- }
698
- const ownerCommand = this.#buildOwnerCommand(sessionId).map(shellQuote).join(" ");
699
- const shellCommand = `exec env ${envAssignments.join(" ")} ${ownerCommand}`;
700
- const created = Bun.spawnSync([tmuxCommand, "new-session", "-d", "-s", sessionName, "-c", cwd, shellCommand], {
1015
+ const shellCommand = `exec env ${envAssignments.join(" ")} ${this.#buildOwnerCommand(sessionId).map(shellQuote).join(" ")}`;
1016
+ const probe = await this.#harnessOwnerIsolationProbe(tmuxCommand);
1017
+ const probeServer = probe.probeServer;
1018
+ probe.probeServer = async (requestedSocketKey, controlArgv) =>
1019
+ requestedSocketKey === socketKey ? probeServer(requestedSocketKey, controlArgv) : { state: "unverifiable" };
1020
+ const tmuxArgv = [
1021
+ tmuxCommand,
1022
+ "-L",
1023
+ socketKey,
1024
+ "new-session",
1025
+ "-d",
1026
+ "-s",
1027
+ sessionName,
1028
+ "-P",
1029
+ "-F",
1030
+ "#{session_id}",
1031
+ "-c",
1032
+ cwd,
1033
+ shellCommand,
1034
+ ];
1035
+ const plan = await planTmuxOwnerIsolation(
1036
+ {
1037
+ schema_version: 1,
1038
+ op: "plan",
1039
+ platform: process.platform,
1040
+ session_id: sessionId,
1041
+ owner_generation: ownerGeneration,
1042
+ baseline,
1043
+ cwd,
1044
+ state_dir: ownerStateDir,
1045
+ socket_key: socketKey,
1046
+ tmux_argv: tmuxArgv,
1047
+ },
1048
+ probe,
1049
+ );
1050
+ if (!plan.ok) return { started: false, sessionName, socketKey, reason: `tmux-owner-${plan.code}` };
1051
+ if (!isOwnerGenerationBaselineCurrentSync(ownerStateDir, sessionId, baseline))
1052
+ return { started: false, sessionName, socketKey, reason: "tmux-owner-generation_stale" };
1053
+ let nativeSessionId: string | null = null;
1054
+ let cleanupProof: TmuxServerProof | null = null;
1055
+ const fail = async (
1056
+ reason: string,
1057
+ ): Promise<{ started: false; sessionName: string; socketKey: string; reason: string }> => {
1058
+ try {
1059
+ await this.#cleanupTmuxAttempt(
1060
+ tmuxCommand,
1061
+ socketKey,
1062
+ nativeSessionId,
1063
+ sessionName,
1064
+ cleanupProof,
1065
+ probeServer,
1066
+ );
1067
+ return { started: false, sessionName, socketKey, reason };
1068
+ } catch {
1069
+ return { started: false, sessionName, socketKey, reason: `${reason}:tmux-owner-cleanup_uncertain` };
1070
+ }
1071
+ };
1072
+ const created = Bun.spawnSync(plan.execution.argv, {
701
1073
  stdout: "pipe",
702
1074
  stderr: "pipe",
703
1075
  env: process.env,
1076
+ ...(plan.execution.mode === "scoped"
1077
+ ? { stdin: new TextEncoder().encode(`${plan.execution.stdin_line}\n`) }
1078
+ : {}),
704
1079
  });
705
- if (created.exitCode === 0) return { started: true, sessionName, reason: null };
706
- const stderr = created.stderr.toString().trim();
707
- return { started: false, sessionName, reason: stderr || "tmux-start-failed" };
1080
+ const scopedReceipt = plan.execution.mode === "scoped" ? scopedBootstrapReceipt(created.stdout) : null;
1081
+ nativeSessionId =
1082
+ scopedReceipt?.nativeSessionId ??
1083
+ (plan.execution.mode === "direct" ? exactNativeTmuxSessionId(created.stdout) : null);
1084
+ if (created.exitCode !== 0)
1085
+ return fail(
1086
+ plan.execution.mode === "scoped"
1087
+ ? "tmux-owner-scope_bootstrap_failed"
1088
+ : "tmux-owner-direct_creation_failed",
1089
+ );
1090
+ if (!nativeSessionId)
1091
+ return fail(
1092
+ plan.execution.mode === "scoped"
1093
+ ? "tmux-owner-scope_bootstrap_failed"
1094
+ : "tmux-owner-native_session_identity_unavailable",
1095
+ );
1096
+ const postSpawnServer = await probeServer(socketKey, [tmuxCommand, "-L", socketKey]);
1097
+ cleanupProof = postSpawnServer;
1098
+ if (postSpawnServer.state === "unsafe") return fail("tmux-owner-server_unsafe");
1099
+ if (!isSafeServerProof(postSpawnServer)) return fail("tmux-owner-server_unverifiable");
1100
+ if (
1101
+ scopedReceipt &&
1102
+ (scopedReceipt.sessionName !== sessionName ||
1103
+ scopedReceipt.serverPid !== postSpawnServer.pid ||
1104
+ scopedReceipt.serverStartTime !== postSpawnServer.startTime)
1105
+ )
1106
+ return fail("tmux-owner-receipt_server_mismatch");
1107
+ if (!(await this.#nativeSessionBoundToName(tmuxCommand, socketKey, nativeSessionId, sessionName)))
1108
+ return fail("tmux-owner-native_session_identity_unproven");
1109
+ const boundServer = await probeServer(socketKey, [tmuxCommand, "-L", socketKey]);
1110
+ if (!sameServerIdentity(postSpawnServer, boundServer)) return fail("tmux-owner-server_race");
1111
+ cleanupProof = boundServer;
1112
+ if (
1113
+ plan.execution.mode === "direct" &&
1114
+ !plan.execution.server_absent_before &&
1115
+ (boundServer.pid !== plan.execution.server_pid || boundServer.startTime !== plan.execution.server_start_time)
1116
+ ) {
1117
+ cleanupProof = null;
1118
+ return fail("tmux-owner-server_race");
1119
+ }
1120
+ if (!isOwnerGenerationBaselineCurrentSync(ownerStateDir, sessionId, baseline))
1121
+ return fail("tmux-owner-generation_stale");
1122
+ try {
1123
+ replaceOwnerGenerationSync(ownerStateDir, sessionId, ownerGeneration, baseline);
1124
+ } catch {
1125
+ return fail("tmux-owner-generation_stale");
1126
+ }
1127
+ return {
1128
+ started: true,
1129
+ sessionName,
1130
+ socketKey,
1131
+ reason: null,
1132
+ cleanup: async () => {
1133
+ await this.#cleanupTmuxAttempt(
1134
+ tmuxCommand,
1135
+ socketKey,
1136
+ nativeSessionId,
1137
+ sessionName,
1138
+ cleanupProof,
1139
+ probeServer,
1140
+ );
1141
+ await observeOwnerTerminal({
1142
+ schema_version: 1,
1143
+ op: "observe_terminal",
1144
+ session_id: sessionId,
1145
+ owner_generation: ownerGeneration,
1146
+ state_dir: ownerStateDir,
1147
+ socket_key: socketKey,
1148
+ observer: "sidecar",
1149
+ observed_at: new Date().toISOString(),
1150
+ signal: "UNKNOWN",
1151
+ exit_code: null,
1152
+ exit_kind: "owner_lost",
1153
+ reason: "endpoint_unroutable",
1154
+ });
1155
+ },
1156
+ };
708
1157
  }
709
1158
 
710
- /** Spawn the owner daemon. Prefer a tmux-resident owner, then explicitly fall back to detached. */
1159
+ /** Spawn the owner daemon. Tmux isolation failures and unroutable starts block; unavailable tmux may fall back. */
711
1160
  async #spawnDetachedOwner(root: string, sessionId: string, cwd: string): Promise<OwnerSpawnResult> {
712
- const tmux = this.#startTmuxResidentOwner(root, sessionId, cwd);
1161
+ const tmux = await this.#startTmuxResidentOwner(root, sessionId, cwd);
1162
+ if (!tmux.started && tmux.reason?.startsWith("tmux-owner-")) {
1163
+ return {
1164
+ live: false,
1165
+ runtime: "manual",
1166
+ tmuxSessionName: null,
1167
+ socketKey: tmux.socketKey,
1168
+ fallbackReason: tmux.reason,
1169
+ blockerReason: "tmux-owner-isolation-failed",
1170
+ };
1171
+ }
713
1172
  if (tmux.started && (await this.#waitForOwner(root, sessionId))) {
714
1173
  return {
715
1174
  live: true,
716
1175
  runtime: "tmux",
717
1176
  tmuxSessionName: tmux.sessionName,
1177
+ socketKey: tmux.socketKey,
718
1178
  fallbackReason: null,
719
1179
  blockerReason: null,
720
1180
  };
721
1181
  }
722
- const fallbackReason = tmux.started
723
- ? "tmux new-session exited 0 but owner endpoint did not become routable"
724
- : tmux.reason;
1182
+ if (tmux.started) {
1183
+ try {
1184
+ if (!tmux.cleanup) throw new Error("tmux-owner-cleanup_uncertain");
1185
+ await tmux.cleanup();
1186
+ } catch {
1187
+ return {
1188
+ live: false,
1189
+ runtime: "manual",
1190
+ tmuxSessionName: tmux.sessionName,
1191
+ socketKey: tmux.socketKey,
1192
+ fallbackReason:
1193
+ "tmux new-session owner endpoint not routable; exact cleanup or reconciliation uncertain",
1194
+ blockerReason: "tmux-owner-endpoint-cleanup-uncertain",
1195
+ };
1196
+ }
1197
+ return {
1198
+ live: false,
1199
+ runtime: "manual",
1200
+ tmuxSessionName: null,
1201
+ socketKey: tmux.socketKey,
1202
+ fallbackReason: "tmux new-session exited 0 but owner endpoint did not become routable; owner cleaned",
1203
+ blockerReason: "tmux-owner-endpoint-not-routable",
1204
+ };
1205
+ }
1206
+ const fallbackReason = tmux.reason;
725
1207
  const cmd = this.#buildOwnerCommand(sessionId);
726
1208
  const child = Bun.spawn(cmd, {
727
1209
  cwd,
@@ -745,6 +1227,7 @@ export default class Harness extends Command {
745
1227
  live,
746
1228
  runtime: "detached",
747
1229
  tmuxSessionName: null,
1230
+ socketKey: null,
748
1231
  fallbackReason,
749
1232
  blockerReason: live ? null : "detached-owner-not-live",
750
1233
  };
@@ -815,12 +1298,14 @@ export default class Harness extends Command {
815
1298
  let ownerRuntime: OwnerSpawnResult["runtime"] = "manual";
816
1299
  let ownerFallbackReason: string | null = null;
817
1300
  let ownerBlockerReason: string | null = null;
1301
+ let ownerSocketKey: string | null = null;
818
1302
  if (input.detach === true) {
819
1303
  const ownerSpawn = await this.#spawnDetachedOwner(root, sessionId, workspace);
820
1304
  ownerLive = ownerSpawn.live;
821
1305
  ownerRuntime = ownerSpawn.runtime;
822
1306
  ownerFallbackReason = ownerSpawn.fallbackReason;
823
1307
  ownerBlockerReason = ownerSpawn.blockerReason;
1308
+ ownerSocketKey = ownerSpawn.socketKey;
824
1309
  handle.viewportHandle = {
825
1310
  kind: "event-monitor",
826
1311
  tmuxSessionName: ownerSpawn.tmuxSessionName,
@@ -842,25 +1327,7 @@ export default class Harness extends Command {
842
1327
  await writeSessionState(root, state);
843
1328
  }
844
1329
  }
845
- if (ownerBlockerReason) {
846
- const resolved = await resolveOwner(root, sessionId);
847
- if (resolved.live && resolved.socketPath) {
848
- ownerLive = true;
849
- ownerBlockerReason = null;
850
- handle.processHandle = {
851
- kind: "runtime-owner",
852
- ownerId: resolved.lease?.ownerId ?? null,
853
- pid: resolved.lease?.pid ?? null,
854
- };
855
- handle.ownerHandle = {
856
- leasePath,
857
- endpoint: resolved.socketPath,
858
- heartbeatAt: resolved.lease?.heartbeatAt ?? null,
859
- };
860
- state.handle = handle;
861
- await writeSessionState(root, state);
862
- }
863
- }
1330
+ // A live endpoint never proves a failed tmux launch safe: preserve the isolation/provenance blocker.
864
1331
  if (ownerBlockerReason) {
865
1332
  state.lifecycle = "blocked";
866
1333
  state.blockers = [...state.blockers, ownerBlockerReason];
@@ -876,6 +1343,7 @@ export default class Harness extends Command {
876
1343
  handle,
877
1344
  ownerRuntime,
878
1345
  preflight,
1346
+ ...(ownerSocketKey ? { tmuxOwnerSocketKey: ownerSocketKey } : {}),
879
1347
  ...(ownerFallbackReason ? { ownerFallbackReason } : {}),
880
1348
  ...(ownerBlockerReason ? { reason: ownerBlockerReason } : {}),
881
1349
  },