@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
@@ -4,13 +4,13 @@ import type { AgentProgress, AgentSource } from "../task/types";
4
4
  import type { ToolSession } from "./index";
5
5
  declare const subagentSchema: z.ZodObject<{
6
6
  action: z.ZodEnum<{
7
- await: "await";
8
- cancel: "cancel";
9
- inspect: "inspect";
10
- list: "list";
11
- pause: "pause";
12
7
  resume: "resume";
8
+ pause: "pause";
13
9
  steer: "steer";
10
+ list: "list";
11
+ cancel: "cancel";
12
+ inspect: "inspect";
13
+ await: "await";
14
14
  }>;
15
15
  ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
16
16
  id: z.ZodOptional<z.ZodString>;
@@ -19,9 +19,9 @@ declare const subagentSchema: z.ZodObject<{
19
19
  timeout_ms: z.ZodOptional<z.ZodNumber>;
20
20
  limit: z.ZodOptional<z.ZodNumber>;
21
21
  verbosity: z.ZodOptional<z.ZodEnum<{
22
+ receipt: "receipt";
22
23
  full: "full";
23
24
  preview: "preview";
24
- receipt: "receipt";
25
25
  }>>;
26
26
  }, z.core.$strip>;
27
27
  type SubagentParams = z.infer<typeof subagentSchema>;
@@ -68,13 +68,13 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
68
68
  readonly description: string;
69
69
  readonly parameters: z.ZodObject<{
70
70
  action: z.ZodEnum<{
71
- await: "await";
72
- cancel: "cancel";
73
- inspect: "inspect";
74
- list: "list";
75
- pause: "pause";
76
71
  resume: "resume";
72
+ pause: "pause";
77
73
  steer: "steer";
74
+ list: "list";
75
+ cancel: "cancel";
76
+ inspect: "inspect";
77
+ await: "await";
78
78
  }>;
79
79
  ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
80
  id: z.ZodOptional<z.ZodString>;
@@ -83,9 +83,9 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
83
83
  timeout_ms: z.ZodOptional<z.ZodNumber>;
84
84
  limit: z.ZodOptional<z.ZodNumber>;
85
85
  verbosity: z.ZodOptional<z.ZodEnum<{
86
+ receipt: "receipt";
86
87
  full: "full";
87
88
  preview: "preview";
88
- receipt: "receipt";
89
89
  }>>;
90
90
  }, z.core.$strip>;
91
91
  readonly strict = true;
@@ -26,6 +26,13 @@ export declare class TelegramSendTool implements AgentTool<typeof telegramSendSc
26
26
  readonly strict = true;
27
27
  constructor(session: ToolSession);
28
28
  static createIf(session: ToolSession): TelegramSendTool | null;
29
+ /**
30
+ * Resolve `requested` against the workspace root and confine it via realpath:
31
+ * blocks absolute paths outside the project, `..` traversal, and symlinks that
32
+ * escape the root. Returns the resolved real path of a regular file, or an
33
+ * error message. This is the egress safety boundary — the model can only send
34
+ * files that genuinely live inside the session workspace.
35
+ */
29
36
  private resolveContainedFile;
30
37
  execute(_toolCallId: string, params: TelegramSendParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<TelegramSendDetails>, _context?: AgentToolContext): Promise<AgentToolResult<TelegramSendDetails>>;
31
38
  }
@@ -28,13 +28,13 @@ export interface TodoWriteToolDetails {
28
28
  declare const todoWriteSchema: z.ZodObject<{
29
29
  ops: z.ZodArray<z.ZodObject<{
30
30
  op: z.ZodEnum<{
31
- append: "append";
32
- done: "done";
33
31
  drop: "drop";
34
32
  init: "init";
35
- note: "note";
36
- rm: "rm";
37
33
  start: "start";
34
+ done: "done";
35
+ rm: "rm";
36
+ append: "append";
37
+ note: "note";
38
38
  }>;
39
39
  list: z.ZodOptional<z.ZodArray<z.ZodObject<{
40
40
  phase: z.ZodString;
@@ -70,13 +70,13 @@ export declare class TodoWriteTool implements AgentTool<typeof todoWriteSchema,
70
70
  readonly parameters: z.ZodObject<{
71
71
  ops: z.ZodArray<z.ZodObject<{
72
72
  op: z.ZodEnum<{
73
- append: "append";
74
- done: "done";
75
73
  drop: "drop";
76
74
  init: "init";
77
- note: "note";
78
- rm: "rm";
79
75
  start: "start";
76
+ done: "done";
77
+ rm: "rm";
78
+ append: "append";
79
+ note: "note";
80
80
  }>;
81
81
  list: z.ZodOptional<z.ZodArray<z.ZodObject<{
82
82
  phase: z.ZodString;
@@ -8,6 +8,8 @@ export interface OpenInEditorOptions {
8
8
  /** Keep the file's trailing newline instead of trimming it from the returned text. */
9
9
  trimTrailingNewline?: boolean;
10
10
  }
11
+ /** Remove one platform-standard trailing line terminator from editor content. */
12
+ export declare function trimEditorTrailingNewline(text: string): string;
11
13
  /**
12
14
  * Opens `content` in the user's external editor and returns the edited text.
13
15
  * Returns `null` if the editor exits with a non-zero code.
@@ -17,18 +17,18 @@ export declare const INSANE_VENDOR_DIR: string;
17
17
  /** Stable note prefixes — tests assert on these without depending on full stderr. */
18
18
  export declare const INSANE_NOTES: {
19
19
  readonly guardBlocked: (reason: string) => string;
20
- readonly vendorMissing: `insane fallback unavailable: vendor engine missing at packages/coding-agent/vendor/insane-search`;
21
- readonly noPython: `insane fallback unavailable: python3 not found; install python3 and curl_cffi, then retry with web.insaneFallback=true`;
22
- readonly noCurlCffi: `insane fallback unavailable: python3 cannot import curl_cffi; install curl_cffi for Phase 0-2`;
23
- readonly noBrowser: `insane fallback unavailable: node/playwright/stealth dependencies missing for Phase 3; install dependencies under packages/coding-agent/vendor/insane-search/engine/templates`;
20
+ readonly vendorMissing: "insane fallback unavailable: vendor engine missing at packages/coding-agent/vendor/insane-search";
21
+ readonly noPython: "insane fallback unavailable: python3 not found; install python3 and curl_cffi, then retry with web.insaneFallback=true";
22
+ readonly noCurlCffi: "insane fallback unavailable: python3 cannot import curl_cffi; install curl_cffi for Phase 0-2";
23
+ readonly noBrowser: "insane fallback unavailable: node/playwright/stealth dependencies missing for Phase 3; install dependencies under packages/coding-agent/vendor/insane-search/engine/templates";
24
24
  readonly timeout: (seconds: number) => string;
25
- readonly invalidJson: `insane fallback failed: engine returned invalid JSON`;
26
- readonly authRequired: `insane fallback stopped: authentication required`;
25
+ readonly invalidJson: "insane fallback failed: engine returned invalid JSON";
26
+ readonly authRequired: "insane fallback stopped: authentication required";
27
27
  readonly verdict: (verdict: string) => string;
28
28
  readonly untried: (routes: string) => string;
29
- readonly mustBrowserMcp: `insane fallback requires browser MCP/manual phase: must_invoke_playwright_mcp=true`;
30
- readonly concurrency: `insane fallback skipped: max concurrent engine attempts reached`;
31
- readonly emptyContent: `insane fallback failed: engine reported ok but returned no content`;
29
+ readonly mustBrowserMcp: "insane fallback requires browser MCP/manual phase: must_invoke_playwright_mcp=true";
30
+ readonly concurrency: "insane fallback skipped: max concurrent engine attempts reached";
31
+ readonly emptyContent: "insane fallback failed: engine reported ok but returned no content";
32
32
  };
33
33
  /** Raw JSON envelope produced by `python3 -m engine --json`. */
34
34
  export interface InsaneFetchResultRaw {
@@ -16,8 +16,8 @@ export declare const webSearchSchema: z.ZodObject<{
16
16
  query: z.ZodString;
17
17
  recency: z.ZodOptional<z.ZodEnum<{
18
18
  day: "day";
19
- month: "month";
20
19
  week: "week";
20
+ month: "month";
21
21
  year: "year";
22
22
  }>>;
23
23
  limit: z.ZodOptional<z.ZodNumber>;
@@ -25,9 +25,9 @@ export declare const webSearchSchema: z.ZodObject<{
25
25
  temperature: z.ZodOptional<z.ZodNumber>;
26
26
  num_search_results: z.ZodOptional<z.ZodNumber>;
27
27
  xai_search_mode: z.ZodOptional<z.ZodEnum<{
28
+ x: "x";
28
29
  web: "web";
29
30
  web_and_x: "web_and_x";
30
- x: "x";
31
31
  }>>;
32
32
  allowed_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
33
  excluded_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -79,8 +79,8 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
79
79
  query: z.ZodString;
80
80
  recency: z.ZodOptional<z.ZodEnum<{
81
81
  day: "day";
82
- month: "month";
83
82
  week: "week";
83
+ month: "month";
84
84
  year: "year";
85
85
  }>>;
86
86
  limit: z.ZodOptional<z.ZodNumber>;
@@ -88,9 +88,9 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
88
88
  temperature: z.ZodOptional<z.ZodNumber>;
89
89
  num_search_results: z.ZodOptional<z.ZodNumber>;
90
90
  xai_search_mode: z.ZodOptional<z.ZodEnum<{
91
+ x: "x";
91
92
  web: "web";
92
93
  web_and_x: "web_and_x";
93
- x: "x";
94
94
  }>>;
95
95
  allowed_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
96
96
  excluded_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/coding-agent",
4
- "version": "0.3.12",
4
+ "version": "0.3.15",
5
5
  "description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -49,15 +49,15 @@
49
49
  "verify:insane-vendor": "bun scripts/verify-insane-vendor.ts"
50
50
  },
51
51
  "dependencies": {
52
- "@agentclientprotocol/sdk": "0.21.0",
52
+ "@agentclientprotocol/sdk": "1.2.1",
53
53
  "@babel/parser": "^7.29.3",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@sayknow-cli/stats": "0.3.12",
56
- "@sayknow-cli/agent-core": "0.3.12",
57
- "@sayknow-cli/ai": "0.3.12",
58
- "@sayknow-cli/natives": "0.3.12",
59
- "@sayknow-cli/tui": "0.3.12",
60
- "@sayknow-cli/utils": "0.3.12",
55
+ "@sayknow-cli/stats": "0.3.15",
56
+ "@sayknow-cli/agent-core": "0.3.15",
57
+ "@sayknow-cli/ai": "0.3.15",
58
+ "@sayknow-cli/natives": "0.3.15",
59
+ "@sayknow-cli/tui": "0.3.15",
60
+ "@sayknow-cli/utils": "0.3.15",
61
61
  "@puppeteer/browsers": "^2.13.0",
62
62
  "@types/turndown": "5.0.6",
63
63
  "@xterm/headless": "^6.0.0",
@@ -1,100 +1,260 @@
1
- // G004 real-tmux smoke: exercises forceCloseSkcTmuxSession against LIVE tmux
2
- // sessions (tmux 3.6a). Proves the wrapper hard-kills SKC-managed live panes
3
- // (where remove refuses), refuses non-SKC sessions, and enforces session-id
4
- // matching. Produces durable evidence; not part of the unit suite.
1
+ // G004 real-tmux smoke: exercises forceCloseSkcTmuxSession refusal boundaries
2
+ // against live tmux sessions. Generation-bound successful TERM/verdict/cleanup is
3
+ // covered by the issue evidence harness; this smoke proves incomplete and non-SKC
4
+ // live owners are never hard-killed.
5
5
  import assert from "node:assert";
6
+ import { randomUUID } from "node:crypto";
7
+ import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import path from "node:path";
6
10
 
7
11
  import {
8
12
  buildSkcTmuxExactOptionTarget,
13
+ buildSkcTmuxExactSessionTarget,
9
14
  buildSkcTmuxProfileCommands,
10
15
  resolveSkcTmuxCommand,
11
16
  } from "../src/skc-runtime/tmux-common";
12
17
  import { forceCloseSkcTmuxSession, removeSkcTmuxSession, statusSkcTmuxSession } from "../src/skc-runtime/tmux-sessions";
13
18
 
14
- const tmux = resolveSkcTmuxCommand(process.env);
19
+ const runId = randomUUID().slice(0, 8);
20
+ const suffix = `${process.pid}-${runId}`;
21
+ const privateTmpdir = mkdtempSync(path.join(tmpdir(), "g4-"));
22
+ chmodSync(privateTmpdir, 0o700);
23
+ const socket = `g4-${runId}`;
24
+ const wrapper = path.join(privateTmpdir, "tmux-private.sh");
25
+ const scopeUnit = `skc-g004-${suffix}.scope`;
26
+ const tmuxBootstrapEnv: NodeJS.ProcessEnv = {
27
+ ...process.env,
28
+ TMUX: "",
29
+ TMUX_PANE: "",
30
+ TMUX_TMPDIR: privateTmpdir,
31
+ };
32
+ const tmuxBinary = resolveSkcTmuxCommand(tmuxBootstrapEnv);
33
+ const privateEnv: NodeJS.ProcessEnv = {
34
+ ...tmuxBootstrapEnv,
35
+ SKC_TMUX_COMMAND: wrapper,
36
+ };
37
+ let scopeRunner: Bun.Subprocess<"ignore", "ignore", "pipe"> | null = null;
15
38
 
16
- function sh(args: string[]): { code: number; err: string } {
17
- const r = Bun.spawnSync([tmux, ...args], { stdout: "pipe", stderr: "pipe" });
18
- return { code: r.exitCode, err: r.stderr.toString().trim() };
39
+ function shellQuote(value: string): string {
40
+ return `'${value.replace(/'/g, `'\\''`)}'`;
41
+ }
42
+
43
+ function bounded(value: string): string {
44
+ return value.trim().slice(0, 512) || "no diagnostic";
45
+ }
46
+
47
+ function sh(args: string[]): { code: number; out: string; err: string } {
48
+ try {
49
+ const r = Bun.spawnSync([wrapper, ...args], { env: privateEnv, stdout: "pipe", stderr: "pipe" });
50
+ return { code: r.exitCode, out: r.stdout.toString().trim(), err: r.stderr.toString().trim() };
51
+ } catch (error) {
52
+ return { code: -1, out: "", err: bounded(String(error)) };
53
+ }
19
54
  }
20
55
 
21
56
  function makeRawSession(name: string): void {
22
57
  const r = sh(["new-session", "-d", "-s", name, "sleep 600"]);
23
- if (r.code !== 0) throw new Error(`failed to create tmux session ${name}: ${r.err}`);
58
+ if (r.code !== 0) throw new Error(`failed to create private tmux session ${name}: ${bounded(r.err)}`);
24
59
  }
25
60
 
26
61
  function tagAsSkc(name: string, sessionId?: string): void {
27
- const target = buildSkcTmuxExactOptionTarget(name);
28
- for (const cmd of buildSkcTmuxProfileCommands(target, process.env, { sessionId })) {
62
+ const target = buildSkcTmuxExactOptionTarget(name, { env: privateEnv });
63
+ for (const cmd of buildSkcTmuxProfileCommands(target, privateEnv, { sessionId })) {
29
64
  const r = sh(cmd.args);
30
- if (r.code !== 0) throw new Error(`failed to tag ${name} (${cmd.description}): ${r.err}`);
65
+ if (r.code !== 0) throw new Error(`failed to tag ${name} (${cmd.description}): ${bounded(r.err)}`);
31
66
  }
32
67
  }
33
68
 
34
69
  function exists(name: string): boolean {
35
- return sh(["has-session", "-t", `=${name}`]).code === 0;
70
+ return sh(["has-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]).code === 0;
71
+ }
72
+
73
+ function isPrivateSessionAbsent(name: string): boolean {
74
+ const result = sh(["has-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]);
75
+ return result.code !== 0 && /(?:no server running|can't find session|no sessions)/i.test(result.err);
36
76
  }
37
77
 
38
- function killQuiet(name: string): void {
39
- sh(["kill-session", "-t", `=${name}`]);
78
+ function privateServerPid(session: string): number | null {
79
+ const result = sh([
80
+ "display-message",
81
+ "-p",
82
+ "-t",
83
+ buildSkcTmuxExactOptionTarget(session, { env: privateEnv }),
84
+ "#{pid}",
85
+ ]);
86
+ const pid = Number.parseInt(result.out, 10);
87
+ return result.code === 0 && Number.isSafeInteger(pid) && pid > 0 ? pid : null;
88
+ }
89
+
90
+ function scopeIsActive(): boolean {
91
+ return (
92
+ Bun.spawnSync(["systemctl", "--user", "is-active", "--quiet", scopeUnit], { stdout: "ignore", stderr: "ignore" })
93
+ .exitCode === 0
94
+ );
95
+ }
96
+
97
+ function serverIsProvenInScope(session: string): boolean {
98
+ const pid = privateServerPid(session);
99
+ if (pid == null) return false;
100
+ const controlGroup = Bun.spawnSync(
101
+ ["systemctl", "--user", "show", scopeUnit, "--property=ControlGroup", "--value"],
102
+ { stdout: "pipe", stderr: "ignore" },
103
+ );
104
+ const group = controlGroup.stdout.toString().trim();
105
+ if (controlGroup.exitCode !== 0 || !group.startsWith("/")) return false;
106
+ try {
107
+ return readFileSync(`/proc/${pid}/cgroup`, "utf8")
108
+ .split("\n")
109
+ .some(line => line.endsWith(group));
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ async function makeFirstPrivateSession(name: string): Promise<void> {
116
+ if (process.platform !== "linux") {
117
+ makeRawSession(name);
118
+ return;
119
+ }
120
+ try {
121
+ scopeRunner = Bun.spawn(
122
+ [
123
+ "systemd-run",
124
+ "--user",
125
+ "--quiet",
126
+ "--collect",
127
+ `--setenv=TMUX_TMPDIR=${privateTmpdir}`,
128
+ "--scope",
129
+ `--unit=${scopeUnit}`,
130
+ wrapper,
131
+ "new-session",
132
+ "-d",
133
+ "-s",
134
+ name,
135
+ "sleep 600",
136
+ ],
137
+ { env: privateEnv, stdout: "ignore", stderr: "pipe" },
138
+ );
139
+ } catch (error) {
140
+ throw new Error(`failed to provision private user scope ${scopeUnit}: ${bounded(String(error))}`);
141
+ }
142
+ for (let attempt = 0; attempt < 50; attempt += 1) {
143
+ if (scopeIsActive() && exists(name) && serverIsProvenInScope(name)) return;
144
+ await Bun.sleep(50);
145
+ }
146
+ throw new Error(`failed to provision a proven private user scope ${scopeUnit}`);
147
+ }
148
+
149
+ async function cleanupOwnedResources(names: string[]): Promise<void> {
150
+ const failures: string[] = [];
151
+ for (const name of names) {
152
+ const result = sh(["kill-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]);
153
+ if (result.code !== 0 && !isPrivateSessionAbsent(name)) failures.push(`session ${name}: ${bounded(result.err)}`);
154
+ }
155
+ const server = sh(["kill-server"]);
156
+ if (server.code !== 0) {
157
+ const probe = sh(["list-sessions"]);
158
+ if (!/(?:no server running|failed to connect to server|error connecting to)/i.test(probe.err))
159
+ failures.push(`server: ${bounded(server.err)}`);
160
+ }
161
+ if (process.platform === "linux") {
162
+ const stop = Bun.spawnSync(["systemctl", "--user", "stop", scopeUnit], { stdout: "pipe", stderr: "pipe" });
163
+ const state = Bun.spawnSync(
164
+ ["systemctl", "--user", "show", scopeUnit, "--property=ActiveState", "--property=LoadState", "--value"],
165
+ { stdout: "pipe", stderr: "pipe" },
166
+ );
167
+ const states = state.stdout.toString().trim().split("\n");
168
+ const scopeAbsent = state.exitCode === 0 && (states.includes("inactive") || states.includes("not-found"));
169
+ if (stop.exitCode !== 0 && !scopeAbsent) failures.push(`scope stop: ${bounded(stop.stderr.toString())}`);
170
+ if (!scopeAbsent)
171
+ failures.push(`scope remains present: ${bounded(state.stderr.toString() || state.stdout.toString())}`);
172
+ if (scopeRunner != null) {
173
+ const exited = await Promise.race([scopeRunner.exited.then(() => true), Bun.sleep(3_000).then(() => false)]);
174
+ if (!exited) {
175
+ try {
176
+ scopeRunner.kill();
177
+ } catch (error) {
178
+ failures.push(`scope runner: ${bounded(String(error))}`);
179
+ }
180
+ const killed = await Promise.race([
181
+ scopeRunner.exited.then(() => true),
182
+ Bun.sleep(1_000).then(() => false),
183
+ ]);
184
+ if (!killed) failures.push("scope runner did not exit after scope cleanup");
185
+ }
186
+ }
187
+ }
188
+ try {
189
+ rmSync(privateTmpdir, { recursive: true });
190
+ } catch (error) {
191
+ if (existsSync(privateTmpdir)) failures.push(`temporary directory: ${bounded(String(error))}`);
192
+ }
193
+ if (existsSync(privateTmpdir)) failures.push("temporary directory remains present");
194
+ if (failures.length > 0) throw new Error(`g004 cleanup incomplete: ${failures.join("; ")}`);
40
195
  }
41
196
 
42
- const suffix = `${process.pid}-${Date.now()}`;
43
197
  const live = `skc_g004live_${suffix}`;
44
198
  const raw = `g004raw_${suffix}`;
45
199
  const mism = `skc_g004mism_${suffix}`;
46
- const cleanup: string[] = [live, raw, mism];
200
+ const cleanup = [live, raw, mism];
47
201
 
48
202
  try {
49
- // 1. SKC-managed LIVE session: remove refuses, force-close hard-kills.
50
- makeRawSession(live);
203
+ writeFileSync(wrapper, `#!/bin/sh\nexec ${shellQuote(tmuxBinary)} -L ${shellQuote(socket)} "$@"\n`, { mode: 0o700 });
204
+ chmodSync(wrapper, 0o700);
205
+
206
+ // 1. Incompletely tagged LIVE session: remove refuses and force-close fails closed.
207
+ await makeFirstPrivateSession(live);
51
208
  tagAsSkc(live, "sess-g004");
52
- const status = statusSkcTmuxSession(live);
209
+ const status = statusSkcTmuxSession(live, privateEnv);
53
210
  assert.equal(status.profile, "1", "session must be recognized as SKC-managed");
54
211
  assert.ok(status.panePids.length > 0, "session must have a live pane (sleep)");
55
- console.log(`[g004] live SKC session up: ${live} panePids=${status.panePids.length}`);
212
+ process.stdout.write(`[g004] incomplete SKC session up: ${live} panePids=${status.panePids.length}\n`);
56
213
 
57
214
  let removeRefused = false;
58
215
  try {
59
- removeSkcTmuxSession(live);
216
+ removeSkcTmuxSession(live, privateEnv);
60
217
  } catch (e) {
61
218
  removeRefused = /skc_tmux_session_live/.test(String(e));
62
219
  }
63
220
  assert.ok(removeRefused, "removeSkcTmuxSession must REFUSE a live pane");
64
- console.log("[g004] remove refused live session (expected)");
65
221
 
66
- const closed = forceCloseSkcTmuxSession(live, process.env, "sess-g004");
67
- assert.equal(closed.name, live);
68
- assert.ok(!exists(live), "force-close must hard-kill the live SKC session");
69
- console.log("[g004] force-close hard-killed the live SKC session (id-matched)");
222
+ let ownerUnverifiable = false;
223
+ try {
224
+ await forceCloseSkcTmuxSession(live, privateEnv, "sess-g004");
225
+ } catch (e) {
226
+ ownerUnverifiable = /skc_tmux_owner_unverifiable/.test(String(e));
227
+ }
228
+ assert.ok(ownerUnverifiable, "force-close must refuse incomplete owner provenance");
229
+ assert.ok(exists(live), "incompletely tagged session must be left untouched");
230
+ process.stdout.write("[g004] force-close refused incomplete owner provenance (expected)\n");
70
231
 
71
232
  // 2. Non-SKC (untagged) session: force-close must refuse.
72
233
  makeRawSession(raw);
73
234
  let notManaged = false;
74
235
  try {
75
- forceCloseSkcTmuxSession(raw, process.env);
236
+ await forceCloseSkcTmuxSession(raw, privateEnv);
76
237
  } catch (e) {
77
- notManaged = /skc_tmux_session_(not_managed|not_found)/.test(String(e));
238
+ notManaged = /skc_tmux_session_(not_managed|not_found|untagged)/.test(String(e));
78
239
  }
79
240
  assert.ok(notManaged, "force-close must refuse a non-SKC tmux session");
80
241
  assert.ok(exists(raw), "non-SKC session must be left untouched");
81
- killQuiet(raw);
82
- console.log("[g004] force-close refused + preserved non-SKC session (expected)");
242
+ process.stdout.write("[g004] force-close refused + preserved non-SKC session (expected)\n");
83
243
 
84
244
  // 3. SKC session with a MISMATCHED expected session id: must refuse.
85
245
  makeRawSession(mism);
86
246
  tagAsSkc(mism, "sess-real");
87
247
  let idMismatch = false;
88
248
  try {
89
- forceCloseSkcTmuxSession(mism, process.env, "sess-WRONG");
249
+ await forceCloseSkcTmuxSession(mism, privateEnv, "sess-WRONG");
90
250
  } catch (e) {
91
251
  idMismatch = /skc_tmux_session_id_mismatch/.test(String(e));
92
252
  }
93
253
  assert.ok(idMismatch, "force-close must refuse on session-id mismatch");
94
254
  assert.ok(exists(mism), "mismatched session must be left untouched");
95
- console.log("[g004] force-close refused on session-id mismatch (expected)");
96
-
97
- console.log("[g004] PASS: forceCloseSkcTmuxSession verified against live tmux");
255
+ process.stdout.write("[g004] force-close refused on session-id mismatch (expected)\n");
98
256
  } finally {
99
- for (const name of cleanup) killQuiet(name);
257
+ await cleanupOwnedResources(cleanup);
100
258
  }
259
+
260
+ process.stdout.write("[g004] PASS: forceCloseSkcTmuxSession refusal boundaries verified against live tmux\n");
@@ -12,8 +12,14 @@ import type {
12
12
  DaemonKind,
13
13
  DaemonOperationOptions,
14
14
  DaemonOperationResult,
15
- DaemonStatus,
16
15
  } from "../daemon/control-types";
16
+ import {
17
+ DAEMON_ACTION_TOKENS,
18
+ DAEMON_EXIT,
19
+ formatDaemonResult,
20
+ formatDaemonStatus,
21
+ resolveDaemonAction,
22
+ } from "../daemon/operator-contract";
17
23
 
18
24
  export type DaemonCliAction = "list" | "status" | "stop" | "reload";
19
25
 
@@ -26,6 +32,8 @@ export interface DaemonCommandArgs {
26
32
  gracefulTimeoutMs?: number;
27
33
  killTimeoutMs?: number;
28
34
  spawnIfStopped?: boolean;
35
+ /** Show runtime detail and the full roots list in human output. */
36
+ verbose?: boolean;
29
37
  }
30
38
 
31
39
  export interface DaemonCommandDeps {
@@ -33,18 +41,20 @@ export interface DaemonCommandDeps {
33
41
  controllers?: BuiltInDaemonController[];
34
42
  }
35
43
 
36
- const KNOWN_ACTIONS: DaemonCliAction[] = ["list", "status", "stop", "reload"];
37
44
  const KNOWN_KINDS: DaemonKind[] = ["telegram"];
38
45
 
39
46
  export function parseDaemonArgs(argv: string[]): DaemonCommandArgs | undefined {
40
47
  if (argv.length === 0 || argv[0] !== "daemon") return undefined;
41
48
  const rest = argv.slice(1);
42
- const action = (KNOWN_ACTIONS as string[]).includes(rest[0] ?? "") ? (rest[0] as DaemonCliAction) : "status";
43
- const positional = (KNOWN_ACTIONS as string[]).includes(rest[0] ?? "") ? rest.slice(1) : rest;
49
+ const resolved = resolveDaemonAction(rest[0]);
50
+ const isActionToken = (DAEMON_ACTION_TOKENS as readonly string[]).includes(rest[0] ?? "");
51
+ const action = (resolved ?? "status") as DaemonCliAction;
52
+ const positional = isActionToken ? rest.slice(1) : rest;
44
53
  const kinds: DaemonKind[] = [];
45
54
  let all = false;
46
55
  let json = false;
47
56
  let force = false;
57
+ let verbose = false;
48
58
  let gracefulTimeoutMs: number | undefined;
49
59
  let killTimeoutMs: number | undefined;
50
60
  let spawnIfStopped: boolean | undefined;
@@ -53,34 +63,14 @@ export function parseDaemonArgs(argv: string[]): DaemonCommandArgs | undefined {
53
63
  if (arg === "--all") all = true;
54
64
  else if (arg === "--json") json = true;
55
65
  else if (arg === "--force") force = true;
66
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
56
67
  else if (arg === "--spawn-if-stopped") spawnIfStopped = true;
57
68
  else if (arg === "--graceful-timeout-ms") gracefulTimeoutMs = Number.parseInt(positional[++i], 10);
58
69
  else if (arg === "--kill-timeout-ms") killTimeoutMs = Number.parseInt(positional[++i], 10);
59
70
  else if (!arg.startsWith("--") && (KNOWN_KINDS as string[]).includes(arg)) kinds.push(arg as DaemonKind);
60
71
  }
61
- return { action, kinds, all, json, force, gracefulTimeoutMs, killTimeoutMs, spawnIfStopped };
72
+ return { action, kinds, all, json, force, verbose, gracefulTimeoutMs, killTimeoutMs, spawnIfStopped };
62
73
  }
63
-
64
- function formatStatus(status: DaemonStatus): string {
65
- const parts = [
66
- `${status.kind}: ${status.health}`,
67
- status.configured ? undefined : "(not configured)",
68
- status.pid !== undefined ? `pid=${status.pid}` : undefined,
69
- status.ownerId ? `owner=${status.ownerId}` : undefined,
70
- status.rootCount !== undefined ? `roots=${status.rootCount}` : undefined,
71
- `mode=${status.runtime.mode}`,
72
- ].filter(Boolean);
73
- let line = parts.join(" ");
74
- if (status.runtime.warning) line += `\n warning: ${status.runtime.warning}`;
75
- return line;
76
- }
77
-
78
- function formatResult(result: DaemonOperationResult): string {
79
- const head = `${result.kind} ${result.action}: ${result.ok ? "ok" : "failed"} — ${result.message}`;
80
- const warnings = result.warnings.map(w => `\n warning: ${w}`).join("");
81
- return head + warnings;
82
- }
83
-
84
74
  export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonCommandDeps = {}): Promise<void> {
85
75
  const unknownKinds = cmd.kinds.filter(kind => !(KNOWN_KINDS as string[]).includes(kind));
86
76
  if (unknownKinds.length > 0) {
@@ -98,7 +88,7 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
98
88
  if (cmd.json) {
99
89
  process.stdout.write(`${JSON.stringify(statuses, null, 2)}\n`);
100
90
  } else {
101
- process.stdout.write(`${statuses.map(formatStatus).join("\n")}\n`);
91
+ process.stdout.write(`${statuses.map(s => formatDaemonStatus(s, { verbose: cmd.verbose })).join("\n")}\n`);
102
92
  }
103
93
  return;
104
94
  }
@@ -116,7 +106,7 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
116
106
  if (cmd.json) {
117
107
  process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
118
108
  } else {
119
- process.stdout.write(`${results.map(formatResult).join("\n")}\n`);
109
+ process.stdout.write(`${results.map(formatDaemonResult).join("\n")}\n`);
120
110
  }
121
- if (results.some(r => !r.ok)) process.exitCode = 1;
111
+ if (results.some(r => !r.ok)) process.exitCode = DAEMON_EXIT.failure;
122
112
  }