@sayknow-cli/coding-agent 0.3.0 → 0.3.2

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 (233) hide show
  1. package/bin/skc.js +4 -0
  2. package/dist/types/cli/mcp-cli.d.ts +25 -0
  3. package/dist/types/cli/notify-cli.d.ts +2 -0
  4. package/dist/types/cli/plugin-cli.d.ts +2 -0
  5. package/dist/types/cli.d.ts +6 -0
  6. package/dist/types/commands/mcp.d.ts +70 -0
  7. package/dist/types/commands/plugin.d.ts +6 -0
  8. package/dist/types/commands/session.d.ts +6 -0
  9. package/dist/types/config/keybindings.d.ts +2 -2
  10. package/dist/types/config/model-profile-activation.d.ts +8 -1
  11. package/dist/types/config/settings-schema.d.ts +39 -2
  12. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  13. package/dist/types/defaults/skc-defaults.d.ts +2 -13
  14. package/dist/types/extensibility/shared-events.d.ts +1 -0
  15. package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
  16. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
  17. package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
  18. package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
  19. package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
  20. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
  21. package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
  22. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
  23. package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
  24. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
  25. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
  26. package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
  27. package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
  28. package/dist/types/lsp/types.d.ts +2 -0
  29. package/dist/types/main.d.ts +2 -0
  30. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  31. package/dist/types/modes/components/model-selector.d.ts +8 -0
  32. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  33. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  34. package/dist/types/notifications/attachment-registry.d.ts +17 -0
  35. package/dist/types/notifications/chat-adapters.d.ts +9 -0
  36. package/dist/types/notifications/config.d.ts +9 -1
  37. package/dist/types/notifications/engine.d.ts +59 -0
  38. package/dist/types/notifications/html-format.d.ts +11 -0
  39. package/dist/types/notifications/index.d.ts +149 -1
  40. package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
  41. package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
  42. package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
  43. package/dist/types/notifications/managed-daemon.d.ts +48 -0
  44. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  45. package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
  46. package/dist/types/notifications/recent-activity.d.ts +35 -0
  47. package/dist/types/notifications/telegram-daemon.d.ts +133 -16
  48. package/dist/types/notifications/telegram-reference.d.ts +3 -1
  49. package/dist/types/notifications/threaded-inbound.d.ts +19 -0
  50. package/dist/types/notifications/threaded-render.d.ts +6 -1
  51. package/dist/types/notifications/topic-registry.d.ts +12 -9
  52. package/dist/types/runtime-mcp/types.d.ts +7 -0
  53. package/dist/types/sdk.d.ts +2 -0
  54. package/dist/types/session/agent-session.d.ts +16 -4
  55. package/dist/types/session/blob-store.d.ts +25 -0
  56. package/dist/types/session/session-manager.d.ts +57 -0
  57. package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
  58. package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
  59. package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
  60. package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
  61. package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
  62. package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
  63. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
  64. package/dist/types/system-prompt.d.ts +2 -0
  65. package/dist/types/task/executor.d.ts +9 -1
  66. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  67. package/dist/types/tools/fetch.d.ts +23 -0
  68. package/dist/types/tools/index.d.ts +4 -1
  69. package/dist/types/tools/telegram-send.d.ts +32 -0
  70. package/dist/types/utils/changelog.d.ts +1 -0
  71. package/dist/types/web/insane/bridge.d.ts +103 -0
  72. package/dist/types/web/insane/url-guard.d.ts +25 -0
  73. package/dist/types/web/scrapers/types.d.ts +5 -0
  74. package/dist/types/web/scrapers/utils.d.ts +7 -1
  75. package/dist/types/web/search/provider.d.ts +18 -1
  76. package/dist/types/web/search/providers/insane.d.ts +53 -0
  77. package/dist/types/web/search/providers/text-citations.d.ts +23 -0
  78. package/dist/types/web/search/types.d.ts +12 -4
  79. package/package.json +14 -10
  80. package/scripts/g004-tmux-smoke.ts +100 -0
  81. package/scripts/g005-daemon-smoke.ts +180 -0
  82. package/scripts/g011-daemon-path-smoke.ts +153 -0
  83. package/scripts/verify-insane-vendor.ts +132 -0
  84. package/src/cli/args.ts +1 -1
  85. package/src/cli/fast-help.ts +30 -2
  86. package/src/cli/mcp-cli.ts +272 -0
  87. package/src/cli/notify-cli.ts +152 -5
  88. package/src/cli/plugin-cli.ts +66 -3
  89. package/src/cli.ts +30 -12
  90. package/src/commands/mcp.ts +117 -0
  91. package/src/commands/plugin.ts +4 -0
  92. package/src/commands/session.ts +18 -0
  93. package/src/commands/team.ts +1 -1
  94. package/src/config/keybindings.ts +2 -2
  95. package/src/config/model-profile-activation.ts +55 -7
  96. package/src/config/settings-schema.ts +30 -1
  97. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  98. package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
  99. package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
  100. package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
  101. package/src/defaults/skc/skills/team/SKILL.md +5 -3
  102. package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
  103. package/src/defaults/skc-defaults.ts +2 -27
  104. package/src/export/html/index.ts +2 -2
  105. package/src/extensibility/extensions/runner.ts +1 -0
  106. package/src/extensibility/shared-events.ts +1 -0
  107. package/src/extensibility/skc-plugins/compiler.ts +351 -0
  108. package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
  109. package/src/extensibility/skc-plugins/index.ts +9 -0
  110. package/src/extensibility/skc-plugins/injection.ts +109 -0
  111. package/src/extensibility/skc-plugins/installer.ts +434 -0
  112. package/src/extensibility/skc-plugins/loader.ts +3 -1
  113. package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
  114. package/src/extensibility/skc-plugins/observability.ts +84 -0
  115. package/src/extensibility/skc-plugins/paths.ts +1 -1
  116. package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
  117. package/src/extensibility/skc-plugins/registry.ts +180 -0
  118. package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
  119. package/src/extensibility/skc-plugins/schema.ts +250 -20
  120. package/src/extensibility/skc-plugins/session-validation.ts +147 -0
  121. package/src/extensibility/skc-plugins/types.ts +199 -3
  122. package/src/extensibility/skc-plugins/validation.ts +80 -0
  123. package/src/extensibility/skills.ts +15 -0
  124. package/src/hooks/skill-state.ts +57 -0
  125. package/src/internal-urls/docs-index.generated.ts +17 -13
  126. package/src/lsp/config.ts +16 -3
  127. package/src/lsp/defaults.json +7 -0
  128. package/src/lsp/types.ts +2 -0
  129. package/src/main.ts +14 -3
  130. package/src/modes/bridge/bridge-mode.ts +11 -0
  131. package/src/modes/components/assistant-message.ts +49 -1
  132. package/src/modes/components/custom-editor.ts +2 -0
  133. package/src/modes/components/footer.ts +2 -3
  134. package/src/modes/components/hook-editor.ts +1 -1
  135. package/src/modes/components/hook-selector.ts +67 -43
  136. package/src/modes/components/model-selector.ts +56 -11
  137. package/src/modes/components/status-line/git-utils.ts +25 -0
  138. package/src/modes/components/status-line.ts +10 -11
  139. package/src/modes/components/welcome.ts +2 -3
  140. package/src/modes/controllers/event-controller.ts +15 -0
  141. package/src/modes/controllers/extension-ui-controller.ts +0 -27
  142. package/src/modes/controllers/selector-controller.ts +53 -11
  143. package/src/modes/interactive-mode.ts +50 -3
  144. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  145. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  146. package/src/modes/theme/defaults/index.ts +2 -0
  147. package/src/modes/utils/context-usage.ts +2 -2
  148. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  149. package/src/notifications/attachment-registry.ts +23 -0
  150. package/src/notifications/chat-adapters.ts +147 -0
  151. package/src/notifications/config.ts +23 -2
  152. package/src/notifications/engine.ts +100 -0
  153. package/src/notifications/html-format.ts +38 -0
  154. package/src/notifications/index.ts +417 -45
  155. package/src/notifications/lifecycle-commands.ts +238 -0
  156. package/src/notifications/lifecycle-control-runtime.ts +405 -0
  157. package/src/notifications/lifecycle-orchestrator.ts +358 -0
  158. package/src/notifications/managed-daemon.ts +163 -0
  159. package/src/notifications/operator-runtime.ts +171 -0
  160. package/src/notifications/rate-limit-pool.ts +19 -0
  161. package/src/notifications/recent-activity.ts +132 -0
  162. package/src/notifications/telegram-daemon.ts +984 -242
  163. package/src/notifications/telegram-reference.ts +25 -7
  164. package/src/notifications/threaded-inbound.ts +60 -4
  165. package/src/notifications/threaded-render.ts +20 -2
  166. package/src/notifications/topic-registry.ts +23 -9
  167. package/src/prompts/agents/executor.md +2 -2
  168. package/src/runtime-mcp/transports/stdio.ts +38 -4
  169. package/src/runtime-mcp/types.ts +7 -0
  170. package/src/sdk.ts +157 -10
  171. package/src/session/agent-session.ts +248 -125
  172. package/src/session/blob-store.ts +196 -8
  173. package/src/session/session-manager.ts +762 -12
  174. package/src/skc-runtime/launch-tmux.ts +85 -22
  175. package/src/skc-runtime/ledger-event-renderer.ts +1 -0
  176. package/src/skc-runtime/psmux-detect.ts +239 -0
  177. package/src/skc-runtime/ralplan-runtime.ts +2 -2
  178. package/src/skc-runtime/team-runtime.ts +56 -23
  179. package/src/skc-runtime/tmux-common.ts +88 -4
  180. package/src/skc-runtime/tmux-sessions.ts +111 -9
  181. package/src/skc-runtime/ultragoal-guard.ts +25 -8
  182. package/src/skc-runtime/ultragoal-runtime.ts +75 -15
  183. package/src/skc-runtime/workflow-manifest.generated.json +29 -0
  184. package/src/skc-runtime/workflow-manifest.ts +7 -2
  185. package/src/slash-commands/builtin-registry.ts +23 -3
  186. package/src/slash-commands/helpers/fast-status-report.ts +13 -3
  187. package/src/slash-commands/helpers/parse.ts +2 -1
  188. package/src/system-prompt.ts +9 -0
  189. package/src/task/executor.ts +31 -7
  190. package/src/task/index.ts +2 -0
  191. package/src/tools/ask.ts +5 -1
  192. package/src/tools/bash.ts +9 -0
  193. package/src/tools/composer-bash-policy.ts +96 -0
  194. package/src/tools/fetch.ts +94 -1
  195. package/src/tools/index.ts +6 -1
  196. package/src/tools/telegram-send.ts +137 -0
  197. package/src/utils/changelog.ts +8 -0
  198. package/src/web/insane/bridge.ts +350 -0
  199. package/src/web/insane/url-guard.ts +159 -0
  200. package/src/web/scrapers/types.ts +143 -45
  201. package/src/web/scrapers/utils.ts +70 -19
  202. package/src/web/search/provider.ts +77 -18
  203. package/src/web/search/providers/anthropic.ts +70 -3
  204. package/src/web/search/providers/codex.ts +1 -119
  205. package/src/web/search/providers/gemini.ts +99 -0
  206. package/src/web/search/providers/insane.ts +551 -0
  207. package/src/web/search/providers/openai-compatible.ts +66 -32
  208. package/src/web/search/providers/text-citations.ts +111 -0
  209. package/src/web/search/types.ts +13 -2
  210. package/vendor/insane-search/LICENSE +21 -0
  211. package/vendor/insane-search/MANIFEST.json +24 -0
  212. package/vendor/insane-search/engine/__init__.py +23 -0
  213. package/vendor/insane-search/engine/__main__.py +128 -0
  214. package/vendor/insane-search/engine/bias_check.py +183 -0
  215. package/vendor/insane-search/engine/executor.py +254 -0
  216. package/vendor/insane-search/engine/fetch_chain.py +725 -0
  217. package/vendor/insane-search/engine/learning.py +175 -0
  218. package/vendor/insane-search/engine/phase0.py +214 -0
  219. package/vendor/insane-search/engine/safety.py +91 -0
  220. package/vendor/insane-search/engine/templates/package.json +11 -0
  221. package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
  222. package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
  223. package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
  224. package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
  225. package/vendor/insane-search/engine/tests/test_u1.py +200 -0
  226. package/vendor/insane-search/engine/tests/test_u4.py +131 -0
  227. package/vendor/insane-search/engine/tests/test_u5.py +163 -0
  228. package/vendor/insane-search/engine/tests/test_u7.py +124 -0
  229. package/vendor/insane-search/engine/transport.py +211 -0
  230. package/vendor/insane-search/engine/url_transforms.py +98 -0
  231. package/vendor/insane-search/engine/validators.py +331 -0
  232. package/vendor/insane-search/engine/waf_detector.py +214 -0
  233. package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
@@ -1,11 +1,14 @@
1
1
  import { isKnownWorkflowState } from "../../skc-runtime/workflow-manifest";
2
2
  import type { CanonicalSkcWorkflowSkill } from "../../skill-state/active-state";
3
+ import { assertMcpInstallPolicy } from "./mcp-policy";
3
4
  import {
4
5
  type LoadedSubskillBinding,
6
+ type NormalizedSkcPluginBundle,
5
7
  SKC_AGENT_SUBSKILL_PHASES,
6
8
  SKC_SUBSKILL_PARENT_AGENTS,
7
9
  SKC_SUBSKILL_PARENT_SKILLS,
8
10
  SkcPluginLoadError,
11
+ type SkcPluginRegistryEntry,
9
12
  type SkcSubskillParentAgent,
10
13
  type SubskillFrontmatter,
11
14
  } from "./types";
@@ -74,3 +77,80 @@ export function buildParentPhaseSet(bindings: readonly LoadedSubskillBinding[]):
74
77
  }
75
78
  return new Set(seen.keys());
76
79
  }
80
+
81
+ /**
82
+ * Hard install-time collision + security validation for a compiled bundle
83
+ * against the effective installed registry (other plugins in the target scope
84
+ * universe). Collisions are hard errors; the registry is the collision
85
+ * authority, never capability first-wins.
86
+ */
87
+ export function validateInstallPlan(
88
+ bundle: NormalizedSkcPluginBundle,
89
+ effectiveEntries: readonly SkcPluginRegistryEntry[],
90
+ ): void {
91
+ const others = effectiveEntries.filter(e => e.name !== bundle.name);
92
+
93
+ const toolNames = new Set<string>();
94
+ const hookKeys = new Set<string>();
95
+ const mcpNames = new Set<string>();
96
+ const appendixIds = new Set<string>();
97
+ const subskillArgs = new Set<string>();
98
+ const parentPhases = new Set<string>();
99
+ for (const e of others) {
100
+ for (const t of e.surfaces.tools) toolNames.add(t.name);
101
+ for (const h of e.surfaces.hooks) hookKeys.add(h.extensionId);
102
+ for (const m of e.surfaces.mcps) mcpNames.add(m.name);
103
+ for (const a of e.surfaces.systemAppendices) appendixIds.add(a.extensionId);
104
+ for (const a of e.surfaces.agentAppendices) appendixIds.add(a.extensionId);
105
+ for (const s of e.surfaces.subskills) {
106
+ subskillArgs.add(`${s.parent}\u0000${s.activationArg}`);
107
+ parentPhases.add(`${s.parent}\u0000${s.phase}`);
108
+ }
109
+ }
110
+
111
+ // Check candidate surfaces against the effective registry AND against each
112
+ // other (intra-bundle duplicates are also hard errors).
113
+ for (const t of bundle.surfaces.tools) {
114
+ if (toolNames.has(t.name)) {
115
+ throw new SkcPluginLoadError("duplicate_tool", `SKC plugin tool name collides: ${t.name}`);
116
+ }
117
+ toolNames.add(t.name);
118
+ }
119
+ for (const h of bundle.surfaces.hooks) {
120
+ if (hookKeys.has(h.extensionId)) {
121
+ throw new SkcPluginLoadError("duplicate_hook", `SKC plugin hook collides: ${h.extensionId}`);
122
+ }
123
+ hookKeys.add(h.extensionId);
124
+ }
125
+ for (const m of bundle.surfaces.mcps) {
126
+ if (mcpNames.has(m.name)) {
127
+ throw new SkcPluginLoadError("duplicate_mcp", `SKC plugin MCP name collides: ${m.name}`);
128
+ }
129
+ mcpNames.add(m.name);
130
+ assertMcpInstallPolicy(m.config, { pluginRoot: bundle.root });
131
+ }
132
+ for (const a of [...bundle.surfaces.systemAppendices, ...bundle.surfaces.agentAppendices]) {
133
+ if (appendixIds.has(a.extensionId)) {
134
+ throw new SkcPluginLoadError("duplicate_appendix", `SKC plugin appendix collides: ${a.extensionId}`);
135
+ }
136
+ appendixIds.add(a.extensionId);
137
+ }
138
+ for (const s of bundle.surfaces.subskills) {
139
+ const argKey = `${s.parent}\u0000${s.activationArg}`;
140
+ const phaseKey = `${s.parent}\u0000${s.phase}`;
141
+ if (subskillArgs.has(argKey)) {
142
+ throw new SkcPluginLoadError(
143
+ "duplicate_arg",
144
+ `SKC plugin subskill activation_arg collides for ${s.parent}: ${s.activationArg}`,
145
+ );
146
+ }
147
+ if (parentPhases.has(phaseKey)) {
148
+ throw new SkcPluginLoadError(
149
+ "duplicate_parent_phase",
150
+ `SKC plugin subskill parent/phase collides: ${s.parent}/${s.phase}`,
151
+ );
152
+ }
153
+ subskillArgs.add(argKey);
154
+ parentPhases.add(phaseKey);
155
+ }
156
+ }
@@ -10,6 +10,7 @@ import type { SkillPromptDetails } from "../session/messages";
10
10
  import { expandTilde } from "../tools/path-utils";
11
11
  import type { LoadedSubskillActivation } from "./skc-plugins";
12
12
  import { buildSubskillInjection } from "./skc-plugins/injection";
13
+ import { renderSkillAdvertisement } from "./skc-plugins/runtime-adapters";
13
14
  export interface Skill {
14
15
  name: string;
15
16
  description: string;
@@ -415,6 +416,20 @@ export async function buildSkillPromptMessage(
415
416
  } else if (context.subskillActivation) {
416
417
  details.subskillActivation = context.subskillActivation;
417
418
  }
419
+ // Tier-1 advertisement: metadata-only list of installed sub-skills bound to
420
+ // this parent skill, so the agent can choose one contextually.
421
+ if (context.cwd) {
422
+ try {
423
+ const advert = await renderSkillAdvertisement({
424
+ cwd: context.cwd,
425
+ skillName: skill.name,
426
+ phase: context.currentPhase,
427
+ });
428
+ if (advert) message += `\n\n${advert}`;
429
+ } catch {
430
+ // Advertisement is best-effort; never block skill prompt construction.
431
+ }
432
+ }
418
433
  }
419
434
  return {
420
435
  message,
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { logger } from "@sayknow-cli/utils";
4
4
  import type { SkillDiscoverySettings } from "../config/skill-settings-defaults";
5
+ import { detectDeepInterviewPlaintextAskLeak } from "../deep-interview/plaintext-gate-guard";
5
6
  import { buildSessionContext, loadEntriesFromFile, type SessionEntry } from "../session/session-manager";
6
7
  import { activeSnapshotPath, modeStatePath as sessionModeStatePath } from "../skc-runtime/session-layout";
7
8
  import { resolveSkcSessionForRead } from "../skc-runtime/session-resolution";
@@ -675,6 +676,52 @@ async function readCurrentGoalObjectiveFromSessionFile(sessionFile: string | und
675
676
  return typeof objective === "string" && objective.trim().length > 0 ? objective.trim() : null;
676
677
  }
677
678
 
679
+ async function readLatestAssistantTextFromSessionFile(sessionFile: string | undefined): Promise<string | null> {
680
+ const trimmed = sessionFile?.trim();
681
+ if (!trimmed) return null;
682
+ let entries: SessionEntry[];
683
+ try {
684
+ entries = (await loadEntriesFromFile(trimmed)).filter((entry): entry is SessionEntry => entry.type !== "session");
685
+ } catch {
686
+ return null;
687
+ }
688
+ if (entries.length === 0) return null;
689
+ const context = buildSessionContext(entries);
690
+ for (let index = context.messages.length - 1; index >= 0; index--) {
691
+ const message = context.messages[index];
692
+ if (message?.role !== "assistant") continue;
693
+ const text = message.content
694
+ .filter(block => block.type === "text")
695
+ .map(block => block.text)
696
+ .join("");
697
+ const trimmedText = text.trim();
698
+ return trimmedText.length > 0 ? trimmedText : null;
699
+ }
700
+ return null;
701
+ }
702
+
703
+ async function shouldRescueDeepInterviewPlaintextAskLeak(
704
+ skill: SkcWorkflowSkill,
705
+ state: ModeState | null,
706
+ cwd: string,
707
+ sessionFile: string | undefined,
708
+ ): Promise<boolean> {
709
+ if (skill !== "deep-interview") return false;
710
+ if (state?.active !== true) return false;
711
+ const phase = String(state.current_phase ?? "")
712
+ .trim()
713
+ .toLowerCase();
714
+ if (DEEP_INTERVIEW_ABORT_PHASES.has(phase)) return false;
715
+ if (await deepInterviewSpecCrystallized(state, cwd)) return false;
716
+ const latestAssistantText = await readLatestAssistantTextFromSessionFile(sessionFile);
717
+ if (!latestAssistantText) return false;
718
+ return detectDeepInterviewPlaintextAskLeak(latestAssistantText) !== null;
719
+ }
720
+
721
+ function buildDeepInterviewPlaintextAskLeakMessage(statePath: string): string {
722
+ return `SKC deep-interview emitted a Deep Interview question/options block as plain text (${statePath}). It must not wait for a prose answer. Continue immediately by calling the ask tool with the Restate gate question and options: Yes, crystallize; Adjust wording; Missing scope; plus free text/custom input.`;
723
+ }
724
+
678
725
  export async function buildActiveUltragoalPromptContext(input: UserPromptSubmitStateInput): Promise<string | null> {
679
726
  const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
680
727
  const visibleModeState = await readVisibleModeState(input.cwd, "ultragoal", resolvedSessionId, input.stateDir);
@@ -756,6 +803,16 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
756
803
  systemMessage: recoveryMessage,
757
804
  };
758
805
  }
806
+ if (await shouldRescueDeepInterviewPlaintextAskLeak(entry.skill, modeState, input.cwd, input.sessionFile)) {
807
+ const statePath = modeStatePath(input.cwd, entry.skill, resolvedSessionId);
808
+ const rescueMessage = buildDeepInterviewPlaintextAskLeakMessage(statePath);
809
+ return {
810
+ decision: "block",
811
+ reason: rescueMessage,
812
+ stopReason: "skc_skill_deep_interview_plaintext_ask_leak",
813
+ systemMessage: rescueMessage,
814
+ };
815
+ }
759
816
  if (modeStateReleasesStop(modeState, handoffRequired)) {
760
817
  // A mode-state that claims it releases the Stop block must agree with
761
818
  // authoritative durable state. If a stale/incoherent mode-state would