@sayknow-cli/coding-agent 0.3.1 → 0.3.4

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 (216) 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/plugin-cli.d.ts +2 -0
  4. package/dist/types/cli.d.ts +6 -0
  5. package/dist/types/commands/mcp.d.ts +70 -0
  6. package/dist/types/commands/plugin.d.ts +6 -0
  7. package/dist/types/commands/session.d.ts +6 -0
  8. package/dist/types/config/keybindings.d.ts +2 -2
  9. package/dist/types/config/model-profile-activation.d.ts +8 -1
  10. package/dist/types/config/model-profiles.d.ts +2 -2
  11. package/dist/types/config/model-registry.d.ts +3 -3
  12. package/dist/types/config/models-config-schema.d.ts +0 -5
  13. package/dist/types/config/settings-schema.d.ts +79 -68
  14. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  15. package/dist/types/export/html/template.generated.d.ts +1 -1
  16. package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
  17. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
  18. package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
  19. package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
  20. package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
  21. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
  22. package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
  23. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
  24. package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
  25. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
  26. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
  27. package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
  28. package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
  29. package/dist/types/i18n/messages/en.d.ts +0 -1
  30. package/dist/types/main.d.ts +2 -0
  31. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  32. package/dist/types/modes/components/model-selector.d.ts +8 -0
  33. package/dist/types/modes/components/settings-defs.d.ts +0 -1
  34. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  35. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  36. package/dist/types/modes/theme/theme.d.ts +1 -1
  37. package/dist/types/notifications/html-format.d.ts +11 -0
  38. package/dist/types/notifications/index.d.ts +149 -1
  39. package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
  40. package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
  41. package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
  42. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  43. package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
  44. package/dist/types/notifications/recent-activity.d.ts +35 -0
  45. package/dist/types/notifications/telegram-daemon.d.ts +114 -16
  46. package/dist/types/notifications/telegram-reference.d.ts +3 -1
  47. package/dist/types/notifications/topic-registry.d.ts +12 -9
  48. package/dist/types/runtime-mcp/types.d.ts +7 -0
  49. package/dist/types/sdk.d.ts +2 -0
  50. package/dist/types/session/agent-session.d.ts +14 -4
  51. package/dist/types/session/blob-store.d.ts +25 -0
  52. package/dist/types/session/session-manager.d.ts +57 -0
  53. package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
  54. package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
  55. package/dist/types/skc-runtime/state-renderer.d.ts +5 -0
  56. package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
  57. package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
  58. package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
  59. package/dist/types/skc-runtime/ultragoal-guard.d.ts +37 -1
  60. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +80 -0
  61. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
  62. package/dist/types/system-prompt.d.ts +2 -0
  63. package/dist/types/task/executor.d.ts +9 -1
  64. package/dist/types/tools/browser/tab-supervisor.d.ts +31 -0
  65. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  66. package/dist/types/tools/computer-gc.d.ts +23 -0
  67. package/dist/types/tools/cron.d.ts +36 -61
  68. package/dist/types/tools/index.d.ts +3 -2
  69. package/dist/types/tools/resource-gc.d.ts +54 -0
  70. package/dist/types/utils/changelog.d.ts +1 -0
  71. package/dist/types/web/insane/url-guard.d.ts +6 -3
  72. package/dist/types/web/scrapers/types.d.ts +5 -0
  73. package/dist/types/web/scrapers/utils.d.ts +7 -1
  74. package/dist/types/web/search/index.d.ts +1 -0
  75. package/dist/types/web/search/providers/utils.d.ts +11 -4
  76. package/package.json +11 -9
  77. package/scripts/g004-tmux-smoke.ts +100 -0
  78. package/scripts/g005-daemon-smoke.ts +180 -0
  79. package/scripts/g011-daemon-path-smoke.ts +153 -0
  80. package/src/cli/args.ts +0 -1
  81. package/src/cli/fast-help.ts +29 -2
  82. package/src/cli/mcp-cli.ts +272 -0
  83. package/src/cli/plugin-cli.ts +66 -3
  84. package/src/cli/web-search-cli.ts +5 -0
  85. package/src/cli.ts +30 -12
  86. package/src/commands/mcp.ts +117 -0
  87. package/src/commands/plugin.ts +4 -0
  88. package/src/commands/session.ts +18 -0
  89. package/src/config/keybindings.ts +2 -2
  90. package/src/config/model-profile-activation.ts +62 -8
  91. package/src/config/model-profiles.ts +3 -4
  92. package/src/config/model-registry.ts +3 -6
  93. package/src/config/models-config-schema.ts +1 -1
  94. package/src/config/settings-schema.ts +88 -103
  95. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  96. package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
  97. package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
  98. package/src/defaults/skc/skills/team/SKILL.md +5 -3
  99. package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
  100. package/src/export/html/index.ts +2 -2
  101. package/src/export/html/template.generated.ts +1 -1
  102. package/src/export/html/template.js +0 -12
  103. package/src/extensibility/extensions/runner.ts +1 -0
  104. package/src/extensibility/skc-plugins/compiler.ts +351 -0
  105. package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
  106. package/src/extensibility/skc-plugins/index.ts +9 -0
  107. package/src/extensibility/skc-plugins/injection.ts +109 -0
  108. package/src/extensibility/skc-plugins/installer.ts +434 -0
  109. package/src/extensibility/skc-plugins/loader.ts +3 -1
  110. package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
  111. package/src/extensibility/skc-plugins/observability.ts +84 -0
  112. package/src/extensibility/skc-plugins/paths.ts +1 -1
  113. package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
  114. package/src/extensibility/skc-plugins/registry.ts +180 -0
  115. package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
  116. package/src/extensibility/skc-plugins/schema.ts +250 -20
  117. package/src/extensibility/skc-plugins/session-validation.ts +147 -0
  118. package/src/extensibility/skc-plugins/types.ts +199 -3
  119. package/src/extensibility/skc-plugins/validation.ts +80 -0
  120. package/src/extensibility/skills.ts +15 -0
  121. package/src/goals/tools/goal-tool.ts +14 -1
  122. package/src/hooks/skill-state.ts +57 -0
  123. package/src/i18n/messages/de.ts +0 -1
  124. package/src/i18n/messages/en.ts +0 -1
  125. package/src/i18n/messages/es.ts +0 -1
  126. package/src/i18n/messages/fr.ts +0 -1
  127. package/src/i18n/messages/ja.ts +0 -1
  128. package/src/i18n/messages/ko.ts +0 -1
  129. package/src/i18n/messages/zh.ts +0 -1
  130. package/src/internal-urls/docs-index.generated.ts +14 -11
  131. package/src/main.ts +14 -3
  132. package/src/modes/bridge/bridge-mode.ts +11 -0
  133. package/src/modes/components/assistant-message.ts +49 -1
  134. package/src/modes/components/custom-editor.ts +2 -0
  135. package/src/modes/components/footer.ts +2 -3
  136. package/src/modes/components/hook-editor.ts +1 -1
  137. package/src/modes/components/hook-selector.ts +67 -43
  138. package/src/modes/components/model-selector.ts +65 -12
  139. package/src/modes/components/settings-defs.ts +1 -2
  140. package/src/modes/components/settings-selector.ts +2 -7
  141. package/src/modes/components/status-line/git-utils.ts +25 -0
  142. package/src/modes/components/status-line.ts +10 -11
  143. package/src/modes/components/welcome.ts +2 -3
  144. package/src/modes/controllers/extension-ui-controller.ts +0 -27
  145. package/src/modes/controllers/selector-controller.ts +59 -11
  146. package/src/modes/interactive-mode.ts +4 -1
  147. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  148. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  149. package/src/modes/theme/defaults/index.ts +2 -0
  150. package/src/modes/theme/theme.ts +0 -4
  151. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  152. package/src/notifications/html-format.ts +38 -0
  153. package/src/notifications/index.ts +242 -12
  154. package/src/notifications/lifecycle-commands.ts +238 -0
  155. package/src/notifications/lifecycle-control-runtime.ts +405 -0
  156. package/src/notifications/lifecycle-orchestrator.ts +358 -0
  157. package/src/notifications/operator-runtime.ts +171 -0
  158. package/src/notifications/rate-limit-pool.ts +19 -0
  159. package/src/notifications/recent-activity.ts +132 -0
  160. package/src/notifications/telegram-daemon.ts +778 -257
  161. package/src/notifications/telegram-reference.ts +25 -7
  162. package/src/notifications/topic-registry.ts +23 -9
  163. package/src/prompts/agents/executor.md +2 -2
  164. package/src/prompts/system/system-prompt.md +2 -2
  165. package/src/prompts/tools/cron.md +5 -3
  166. package/src/prompts/tools/read.md +1 -1
  167. package/src/runtime-mcp/transports/stdio.ts +38 -4
  168. package/src/runtime-mcp/types.ts +7 -0
  169. package/src/sdk.ts +162 -10
  170. package/src/session/agent-session.ts +210 -74
  171. package/src/session/blob-store.ts +196 -8
  172. package/src/session/session-manager.ts +762 -12
  173. package/src/skc-runtime/launch-tmux.ts +68 -19
  174. package/src/skc-runtime/psmux-detect.ts +239 -0
  175. package/src/skc-runtime/state-renderer.ts +13 -0
  176. package/src/skc-runtime/team-runtime.ts +56 -23
  177. package/src/skc-runtime/tmux-common.ts +88 -4
  178. package/src/skc-runtime/tmux-sessions.ts +111 -9
  179. package/src/skc-runtime/ultragoal-guard.ts +192 -8
  180. package/src/skc-runtime/ultragoal-runtime.ts +296 -16
  181. package/src/slash-commands/builtin-registry.ts +23 -3
  182. package/src/slash-commands/helpers/fast-status-report.ts +13 -3
  183. package/src/slash-commands/helpers/parse.ts +2 -1
  184. package/src/system-prompt.ts +9 -0
  185. package/src/task/executor.ts +31 -7
  186. package/src/task/index.ts +2 -0
  187. package/src/tools/ask.ts +5 -1
  188. package/src/tools/bash.ts +9 -0
  189. package/src/tools/browser/tab-supervisor.ts +86 -2
  190. package/src/tools/composer-bash-policy.ts +96 -0
  191. package/src/tools/computer-gc.ts +66 -0
  192. package/src/tools/computer.ts +2 -0
  193. package/src/tools/cron.ts +75 -112
  194. package/src/tools/fetch.ts +18 -2
  195. package/src/tools/index.ts +5 -9
  196. package/src/tools/read.ts +25 -55
  197. package/src/tools/renderers.ts +0 -2
  198. package/src/tools/resource-gc.ts +291 -0
  199. package/src/tools/ultragoal-ask-guard.ts +7 -1
  200. package/src/utils/changelog.ts +8 -0
  201. package/src/web/insane/url-guard.ts +18 -14
  202. package/src/web/scrapers/types.ts +143 -45
  203. package/src/web/scrapers/utils.ts +70 -19
  204. package/src/web/search/index.ts +1 -0
  205. package/src/web/search/providers/utils.ts +22 -5
  206. package/vendor/insane-search/MANIFEST.json +3 -1
  207. package/vendor/insane-search/engine/__init__.py +14 -0
  208. package/vendor/insane-search/engine/content_safety.py +151 -0
  209. package/vendor/insane-search/engine/fetch_chain.py +32 -0
  210. package/vendor/insane-search/engine/tests/test_u8.py +216 -0
  211. package/dist/types/tools/inspect-image-renderer.d.ts +0 -26
  212. package/dist/types/tools/inspect-image.d.ts +0 -31
  213. package/src/prompts/tools/inspect-image-system.md +0 -20
  214. package/src/prompts/tools/inspect-image.md +0 -32
  215. package/src/tools/inspect-image-renderer.ts +0 -103
  216. package/src/tools/inspect-image.ts +0 -172
@@ -2,6 +2,7 @@ import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
2
2
  import {
3
3
  attachSkcTmuxSession,
4
4
  createSkcTmuxSession,
5
+ forceCloseSkcTmuxSession,
5
6
  listSkcTmuxSessions,
6
7
  removeSkcTmuxSession,
7
8
  statusSkcTmuxSession,
@@ -60,6 +61,12 @@ export default class Session extends Command {
60
61
 
61
62
  static flags = {
62
63
  json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
64
+ "session-id": Flags.string({
65
+ description: "Expected @skc-session-id tag for force-close (defense-in-depth match)",
66
+ }),
67
+ "state-file": Flags.string({
68
+ description: "Expected @skc-session-state-file tag for force-close (defense-in-depth match)",
69
+ }),
63
70
  };
64
71
 
65
72
  static examples = [
@@ -68,6 +75,7 @@ export default class Session extends Command {
68
75
  "skc session status <session>",
69
76
  "skc session attach <session>",
70
77
  "skc session remove <session>",
78
+ "skc session force-close <session> --session-id <id>",
71
79
  ];
72
80
 
73
81
  async run(): Promise<void> {
@@ -136,6 +144,16 @@ export default class Session extends Command {
136
144
  return;
137
145
  }
138
146
 
147
+ if (action === "force-close" || action === "force-remove") {
148
+ const closed = forceCloseSkcTmuxSession(sessionName, process.env, flags["session-id"], flags["state-file"]);
149
+ if (json) {
150
+ writeJson({ ok: true, session: sessionJson(closed) });
151
+ return;
152
+ }
153
+ writeText([`force-closed: ${closed.name}`]);
154
+ return;
155
+ }
156
+
139
157
  if (action === "attach") {
140
158
  attachSkcTmuxSession(sessionName);
141
159
  return;
@@ -117,8 +117,8 @@ export const KEYBINDINGS = {
117
117
  description: "Open external editor",
118
118
  },
119
119
  "app.message.followUp": {
120
- defaultKeys: "ctrl+enter",
121
- description: "Send follow-up message",
120
+ defaultKeys: [],
121
+ description: "Send follow-up message (no default; Ctrl+Enter inserts a newline)",
122
122
  },
123
123
  "app.message.queue": {
124
124
  defaultKeys: "alt+enter",
@@ -7,8 +7,14 @@ import {
7
7
  formatAvailableProfileNames,
8
8
  resolveProfileBindings,
9
9
  } from "./model-profiles";
10
- import { isAuthenticated, type ModelRegistry, type SkcModelAssignmentTargetId } from "./model-registry";
11
- import { resolveModelRoleValue } from "./model-resolver";
10
+ import {
11
+ isAuthenticated,
12
+ kNoAuth,
13
+ type ModelRegistry,
14
+ SKC_MODEL_ASSIGNMENT_TARGETS,
15
+ type SkcModelAssignmentTargetId,
16
+ } from "./model-registry";
17
+ import { formatModelSelectorValue, resolveModelRoleValue } from "./model-resolver";
12
18
  import type { Settings } from "./settings";
13
19
 
14
20
  const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
@@ -60,6 +66,48 @@ export interface PreparedModelProfileActivation {
60
66
  */
61
67
  previousSessionDefaultModel: string | undefined;
62
68
  }
69
+ export interface MaterializeModelProfileAssignmentOptions {
70
+ session: Pick<
71
+ ModelProfileActivationSession,
72
+ "model" | "thinkingLevel" | "setActiveModelProfile" | "getActiveModelProfile"
73
+ >;
74
+ settings: Pick<Settings, "clearOverride" | "get" | "override" | "set">;
75
+ role: SkcModelAssignmentTargetId;
76
+ selector: string;
77
+ }
78
+
79
+ export function materializeActiveModelProfileAssignment(options: MaterializeModelProfileAssignmentOptions): boolean {
80
+ const activeProfile = options.session.getActiveModelProfile?.() ?? options.settings.get("modelProfile.default");
81
+ if (!activeProfile) return false;
82
+
83
+ const nextModelRoles = { ...options.settings.get("modelRoles") };
84
+ const nextAgentModelOverrides = { ...options.settings.get("task.agentModelOverrides") };
85
+ const target = SKC_MODEL_ASSIGNMENT_TARGETS[options.role];
86
+
87
+ if (options.role === "default") {
88
+ nextModelRoles.default = options.selector;
89
+ } else if (!nextModelRoles.default && options.session.model) {
90
+ nextModelRoles.default = formatModelSelectorValue(
91
+ `${options.session.model.provider}/${options.session.model.id}`,
92
+ options.session.thinkingLevel,
93
+ );
94
+ }
95
+
96
+ if (target.settingsPath === "modelRoles") {
97
+ nextModelRoles[options.role] = options.selector;
98
+ } else {
99
+ nextAgentModelOverrides[options.role] = options.selector;
100
+ }
101
+
102
+ options.settings.set("modelRoles", nextModelRoles);
103
+ options.settings.set("task.agentModelOverrides", nextAgentModelOverrides);
104
+ options.settings.set("modelProfile.default", undefined);
105
+ options.settings.clearOverride("modelProfile.default");
106
+ options.settings.override("modelRoles", nextModelRoles);
107
+ options.settings.override("task.agentModelOverrides", nextAgentModelOverrides);
108
+ options.session.setActiveModelProfile?.(undefined);
109
+ return true;
110
+ }
63
111
 
64
112
  export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
65
113
  return `Model profile "${profileName}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
@@ -145,7 +193,12 @@ export async function prepareModelProfileActivation(
145
193
  const authenticatedProviders: string[] = [];
146
194
  for (const provider of allProviders) {
147
195
  const apiKey = await options.modelRegistry.getApiKeyForProvider(provider, options.session.sessionId);
148
- if (!isAuthenticated(apiKey)) {
196
+ // kNoAuth ("N/A") is the sentinel for keyless/no-auth providers (local LLMs,
197
+ // `--auth none` custom providers): usable WITHOUT a key. isAuthenticated()
198
+ // rejects it, so gate on usability ("a credential resolved OR keyless")
199
+ // rather than "has a real key" — otherwise activating a keyless profile
200
+ // throws a spurious "requires credentials" error and the model never applies.
201
+ if (!isAuthenticated(apiKey) && apiKey !== kNoAuth) {
149
202
  missingProviders.push(provider);
150
203
  } else {
151
204
  authenticatedProviders.push(provider);
@@ -255,20 +308,19 @@ export async function applyPreparedModelProfileActivation(
255
308
  modelChanged = true;
256
309
  }
257
310
  if (Object.keys(prepared.modelRoles).length > 0) {
258
- prepared.settings.override("modelRoles", {
259
- ...prepared.settings.get("modelRoles"),
260
- ...prepared.modelRoles,
261
- });
311
+ prepared.settings.override("modelRoles", { ...previousModelRoles, ...prepared.modelRoles });
262
312
  modelRolesChanged = true;
263
313
  }
264
314
  if (Object.keys(prepared.agentModelOverrides).length > 0) {
265
315
  prepared.settings.override("task.agentModelOverrides", {
266
- ...prepared.settings.get("task.agentModelOverrides"),
316
+ ...previousAgentModelOverrides,
267
317
  ...prepared.agentModelOverrides,
268
318
  });
269
319
  overridesChanged = true;
270
320
  }
271
321
  if (options.persistDefault) {
322
+ prepared.settings.set("modelRoles", {});
323
+ prepared.settings.set("task.agentModelOverrides", {});
272
324
  prepared.settings.set("modelProfile.default", prepared.profileName);
273
325
  defaultChanged = true;
274
326
  await prepared.settings.flush();
@@ -277,6 +329,8 @@ export async function applyPreparedModelProfileActivation(
277
329
  } catch (error) {
278
330
  if (defaultChanged) {
279
331
  prepared.settings.set("modelProfile.default", previousPersistedDefault);
332
+ prepared.settings.set("modelRoles", previousModelRoles);
333
+ prepared.settings.set("task.agentModelOverrides", previousAgentModelOverrides);
280
334
  }
281
335
  if (modelRolesChanged) {
282
336
  prepared.settings.override("modelRoles", previousModelRoles);
@@ -23,8 +23,8 @@ export interface ModelProfileDefinition {
23
23
 
24
24
  export interface ResolvedProfileBinding {
25
25
  defaultSelector?: string;
26
- modelRoles: Partial<Record<"vision", string>>;
27
- agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default" | "vision">, string>>;
26
+ modelRoles: Record<string, string>;
27
+ agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default">, string>>;
28
28
  }
29
29
 
30
30
  function parseModelSelectorProvider(selector: string): string | undefined {
@@ -383,9 +383,8 @@ export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map
383
383
  }
384
384
 
385
385
  export function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding {
386
- const { default: defaultSelector, vision, executor, architect, planner, critic } = definition.modelMapping;
386
+ const { default: defaultSelector, executor, architect, planner, critic } = definition.modelMapping;
387
387
  const modelRoles: ResolvedProfileBinding["modelRoles"] = {};
388
- if (vision !== undefined) modelRoles.vision = vision;
389
388
  const agentModelOverrides: ResolvedProfileBinding["agentModelOverrides"] = {};
390
389
  if (executor !== undefined) agentModelOverrides.executor = executor;
391
390
  if (architect !== undefined) agentModelOverrides.architect = architect;
@@ -70,7 +70,7 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
70
70
  return Boolean(apiKey) && apiKey !== kNoAuth;
71
71
  }
72
72
 
73
- export type ModelRole = "default" | "vision";
73
+ export type ModelRole = "default";
74
74
 
75
75
  export interface ModelRoleInfo {
76
76
  tag?: string;
@@ -80,12 +80,11 @@ export interface ModelRoleInfo {
80
80
 
81
81
  export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
82
82
  default: { tag: "DEFAULT", name: "Default", color: "success" },
83
- vision: { tag: "VISION", name: "Vision", color: "accent" },
84
83
  };
85
84
 
86
- export const MODEL_ROLE_IDS: ModelRole[] = ["default", "vision"];
85
+ export const MODEL_ROLE_IDS: ModelRole[] = ["default"];
87
86
 
88
- export type SkcModelAssignmentTargetId = "default" | "vision" | "executor" | "architect" | "planner" | "critic";
87
+ export type SkcModelAssignmentTargetId = "default" | "executor" | "architect" | "planner" | "critic";
89
88
 
90
89
  export interface SkcModelAssignmentTargetInfo extends ModelRoleInfo {
91
90
  id: SkcModelAssignmentTargetId;
@@ -94,7 +93,6 @@ export interface SkcModelAssignmentTargetInfo extends ModelRoleInfo {
94
93
 
95
94
  export const SKC_MODEL_ASSIGNMENT_TARGET_IDS: SkcModelAssignmentTargetId[] = [
96
95
  "default",
97
- "vision",
98
96
  "executor",
99
97
  "architect",
100
98
  "planner",
@@ -103,7 +101,6 @@ export const SKC_MODEL_ASSIGNMENT_TARGET_IDS: SkcModelAssignmentTargetId[] = [
103
101
 
104
102
  export const SKC_MODEL_ASSIGNMENT_TARGETS: Record<SkcModelAssignmentTargetId, SkcModelAssignmentTargetInfo> = {
105
103
  default: { id: "default", tag: "DEFAULT", name: "Default", color: "success", settingsPath: "modelRoles" },
106
- vision: { id: "vision", tag: "VISION", name: "Vision", color: "accent", settingsPath: "modelRoles" },
107
104
  executor: {
108
105
  id: "executor",
109
106
  tag: "EXECUTOR",
@@ -81,7 +81,7 @@ const ModelBindingsSchema = z.object({
81
81
  modelRoles: z.record(z.string(), z.string().min(1)).optional(),
82
82
  agentModelOverrides: z.record(z.string(), z.string().min(1)).optional(),
83
83
  });
84
- export const ProfileRoleSchema = z.enum(["default", "vision", "executor", "architect", "planner", "critic"]);
84
+ export const ProfileRoleSchema = z.enum(["default", "executor", "architect", "planner", "critic"]);
85
85
 
86
86
  function isValidProfileModelSelector(value: string): boolean {
87
87
  if (value.includes(",")) return false;
@@ -31,7 +31,6 @@ export type SettingTab =
31
31
  | "appearance"
32
32
  | "model"
33
33
  | "interaction"
34
- | "notifications"
35
34
  | "context"
36
35
  | "memory"
37
36
  | "editing"
@@ -47,7 +46,6 @@ export const SETTING_TABS: SettingTab[] = [
47
46
  "appearance",
48
47
  "model",
49
48
  "interaction",
50
- "notifications",
51
49
  "context",
52
50
  "memory",
53
51
  "editing",
@@ -61,7 +59,6 @@ export const TAB_METADATA: Record<SettingTab, { label: string; icon: `tab.${stri
61
59
  appearance: { label: "Appearance", icon: "tab.appearance" },
62
60
  model: { label: "Model", icon: "tab.model" },
63
61
  interaction: { label: "Interaction", icon: "tab.interaction" },
64
- notifications: { label: "Notifications", icon: "tab.notifications" },
65
62
  context: { label: "Context", icon: "tab.context" },
66
63
  memory: { label: "Memory", icon: "tab.memory" },
67
64
  editing: { label: "Editing", icon: "tab.editing" },
@@ -133,14 +130,11 @@ interface UiString extends UiBase {
133
130
  * - Omitted → renders as a free text input.
134
131
  */
135
132
  options?: ReadonlyArray<SubmenuOption> | "runtime";
136
- /** When true, the value is masked in the settings list display but edited in full. */
137
- secret?: boolean;
138
133
  }
139
134
 
140
135
  /** Wide ui shape exposed to consumers that walk the schema generically. */
141
136
  export type AnyUiMetadata = UiBase & {
142
137
  options?: ReadonlyArray<SubmenuOption> | "runtime";
143
- secret?: boolean;
144
138
  };
145
139
 
146
140
  interface BooleanDef {
@@ -262,97 +256,18 @@ export const SETTINGS_SCHEMA = {
262
256
  "auth.broker.token": { type: "string", default: undefined },
263
257
 
264
258
  // Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
265
- "notifications.enabled": {
266
- type: "boolean",
267
- default: false,
268
- ui: {
269
- tab: "notifications",
270
- label: "Enabled",
271
- description:
272
- "Master switch for notifications. Requires at least one adapter (Telegram/Discord/Slack) configured with a token and a chat/channel to actually deliver.",
273
- },
274
- },
275
- "notifications.telegram.botToken": {
276
- type: "string",
277
- default: undefined,
278
- ui: {
279
- tab: "notifications",
280
- label: "Telegram bot token",
281
- description: "Telegram BotFather HTTP API token. Stored locally; shown masked.",
282
- secret: true,
283
- },
284
- },
285
- "notifications.telegram.chatId": {
286
- type: "string",
287
- default: undefined,
288
- ui: {
289
- tab: "notifications",
290
- label: "Telegram chat ID",
291
- description:
292
- "Target chat. For end-to-end per-session delivery this must be a forum-enabled supergroup the bot can manage; a private chat id is only enough for setup/status.",
293
- },
294
- },
295
- "notifications.discord.botToken": {
296
- type: "string",
297
- default: undefined,
298
- ui: {
299
- tab: "notifications",
300
- label: "Discord bot token",
301
- description: "Discord bot token for the notifications adapter. Stored locally; shown masked.",
302
- secret: true,
303
- },
304
- },
305
- "notifications.discord.channelId": {
306
- type: "string",
307
- default: undefined,
308
- ui: {
309
- tab: "notifications",
310
- label: "Discord channel ID",
311
- description: "Discord channel that receives notifications and routes replies back.",
312
- },
313
- },
314
- "notifications.slack.botToken": {
315
- type: "string",
316
- default: undefined,
317
- ui: {
318
- tab: "notifications",
319
- label: "Slack bot token",
320
- description: "Slack bot token for the notifications adapter. Stored locally; shown masked.",
321
- secret: true,
322
- },
323
- },
324
- "notifications.slack.channelId": {
325
- type: "string",
326
- default: undefined,
327
- ui: {
328
- tab: "notifications",
329
- label: "Slack channel ID",
330
- description: "Slack channel that receives notifications and routes replies back.",
331
- },
332
- },
333
- "notifications.redact": {
334
- type: "boolean",
335
- default: false,
336
- ui: {
337
- tab: "notifications",
338
- label: "Redact",
339
- description:
340
- "Strip idle summaries and streamed content before remote delivery. Ask prompts are always sent so they remain answerable.",
341
- },
342
- },
259
+ "notifications.enabled": { type: "boolean", default: false },
260
+ "notifications.telegram.botToken": { type: "string", default: undefined },
261
+ "notifications.telegram.chatId": { type: "string", default: undefined },
262
+ "notifications.discord.botToken": { type: "string", default: undefined },
263
+ "notifications.discord.channelId": { type: "string", default: undefined },
264
+ "notifications.slack.botToken": { type: "string", default: undefined },
265
+ "notifications.slack.channelId": { type: "string", default: undefined },
266
+ "notifications.redact": { type: "boolean", default: false },
343
267
  "notifications.verbosity": {
344
268
  type: "string",
345
269
  default: "lean",
346
270
  validate: (value: string) => value === "lean" || value === "verbose",
347
- ui: {
348
- tab: "notifications",
349
- label: "Verbosity",
350
- description: "How much session context to stream to Telegram.",
351
- options: [
352
- { value: "lean", label: "Lean" },
353
- { value: "verbose", label: "Verbose" },
354
- ],
355
- },
356
271
  },
357
272
  "notifications.daemon.idleTimeoutMs": {
358
273
  type: "number",
@@ -2178,16 +2093,6 @@ export const SETTINGS_SCHEMA = {
2178
2093
  },
2179
2094
  },
2180
2095
 
2181
- "inspect_image.enabled": {
2182
- type: "boolean",
2183
- default: false,
2184
- ui: {
2185
- tab: "tools",
2186
- label: "Inspect Image",
2187
- description: "Enable the inspect_image tool, delegating image understanding to a vision-capable model",
2188
- },
2189
- },
2190
-
2191
2096
  "checkpoint.enabled": {
2192
2097
  type: "boolean",
2193
2098
  default: false,
@@ -2285,6 +2190,17 @@ export const SETTINGS_SCHEMA = {
2285
2190
  },
2286
2191
  },
2287
2192
 
2193
+ "web_search.timeout": {
2194
+ type: "number",
2195
+ default: 300,
2196
+ validate: (value: number) => Number.isFinite(value) && value > 0,
2197
+ ui: {
2198
+ tab: "tools",
2199
+ label: "Web Search Timeout",
2200
+ description: "Hard timeout in seconds for a single web search request (default 300)",
2201
+ },
2202
+ },
2203
+
2288
2204
  "browser.enabled": {
2289
2205
  type: "boolean",
2290
2206
  default: true,
@@ -2315,6 +2231,46 @@ export const SETTINGS_SCHEMA = {
2315
2231
  },
2316
2232
  },
2317
2233
 
2234
+ "browser.gc.enabled": {
2235
+ type: "boolean",
2236
+ default: true,
2237
+ ui: {
2238
+ tab: "tools",
2239
+ label: "Browser Tab GC",
2240
+ description: "Automatically reclaim idle, unheld SKC-managed headless/spawned browser tabs.",
2241
+ },
2242
+ },
2243
+ "browser.gc.idleMs": {
2244
+ type: "number",
2245
+ default: 300_000,
2246
+ validate: (value: number) => Number.isFinite(value) && value >= 0,
2247
+ ui: {
2248
+ tab: "tools",
2249
+ label: "Browser Tab GC Idle (ms)",
2250
+ description: "Evict headless/spawned tabs idle longer than this many milliseconds.",
2251
+ },
2252
+ },
2253
+ "browser.gc.rssLimitMb": {
2254
+ type: "number",
2255
+ default: 1536,
2256
+ validate: (value: number) => Number.isFinite(value) && value > 0,
2257
+ ui: {
2258
+ tab: "tools",
2259
+ label: "Browser Tab GC RSS Limit (MB)",
2260
+ description: "Parent-process RSS (MB) above which idle tabs are opportunistically evicted LRU.",
2261
+ },
2262
+ },
2263
+ "resourceGc.sweepIntervalMs": {
2264
+ type: "number",
2265
+ default: 30_000,
2266
+ validate: (value: number) => Number.isFinite(value) && value > 0,
2267
+ ui: {
2268
+ tab: "tools",
2269
+ label: "Resource GC Sweep Interval (ms)",
2270
+ description: "How often the resource GC sweeps browser tabs and stale screenshot directories.",
2271
+ },
2272
+ },
2273
+
2318
2274
  "computer.enabled": {
2319
2275
  type: "boolean",
2320
2276
  default: false,
@@ -2374,6 +2330,35 @@ export const SETTINGS_SCHEMA = {
2374
2330
  description: "Persist audit records for enabled computer-use actions.",
2375
2331
  },
2376
2332
  },
2333
+ "computer.screenshotGc.enabled": {
2334
+ type: "boolean",
2335
+ default: true,
2336
+ ui: {
2337
+ tab: "tools",
2338
+ label: "Computer Screenshot GC",
2339
+ description: "Delete stale computer-use screenshot fallback directories on disk.",
2340
+ },
2341
+ },
2342
+ "computer.screenshotGc.staleMs": {
2343
+ type: "number",
2344
+ default: 43_200_000,
2345
+ validate: (value: number) => Number.isFinite(value) && value >= 0,
2346
+ ui: {
2347
+ tab: "tools",
2348
+ label: "Computer Screenshot GC Stale Age (ms)",
2349
+ description: "Remove screenshot fallback directories whose mtime is older than this many milliseconds.",
2350
+ },
2351
+ },
2352
+ "computer.screenshotGc.scanIntervalMs": {
2353
+ type: "number",
2354
+ default: 1_800_000,
2355
+ validate: (value: number) => Number.isFinite(value) && value > 0,
2356
+ ui: {
2357
+ tab: "tools",
2358
+ label: "Computer Screenshot GC Scan Interval (ms)",
2359
+ description: "Minimum interval between os.tmpdir scans for stale screenshot directories.",
2360
+ },
2361
+ },
2377
2362
 
2378
2363
  // Tool execution
2379
2364
  "tools.intentTracing": {
@@ -0,0 +1,94 @@
1
+ export type DeepInterviewPlaintextAskLeakOption = "Yes, crystallize" | "Adjust wording" | "Missing scope";
2
+
3
+ export type DeepInterviewPlaintextAskLeakResult = {
4
+ kind: "deep_interview_plaintext_ask_leak";
5
+ matchedOptions: DeepInterviewPlaintextAskLeakOption[];
6
+ signals: {
7
+ optionsHeading: true;
8
+ restateIntent: true;
9
+ deepInterviewContext: boolean;
10
+ };
11
+ };
12
+
13
+ const CANONICAL_OPTIONS: DeepInterviewPlaintextAskLeakOption[] = [
14
+ "Yes, crystallize",
15
+ "Adjust wording",
16
+ "Missing scope",
17
+ ];
18
+
19
+ const RESTATE_INTENT_PATTERNS: RegExp[] = [
20
+ /\brestate\b/i,
21
+ /\bconfirmation\b/i,
22
+ /\bconfirm(?:ation|ed|ing)?\b/i,
23
+ /\bcrystall?ize\b/i,
24
+ /\bdoes\s+this\s+(?:capture|match|reflect|summari[sz]e)\b/i,
25
+ /\bbefore\s+(?:i|we)\s+(?:write|finali[sz]e|crystall?ize)\b/i,
26
+ /\bread\s+only\s+this\s+line\b/i,
27
+ /\bturn\s+this\s+into\s+(?:the\s+)?(?:spec|final)\b/i,
28
+ /재진술/,
29
+ /다시\s*말/,
30
+ /확인/,
31
+ /확정/,
32
+ /정리/,
33
+ /요약/,
34
+ /맞(?:습니까|나요|는지)/,
35
+ ];
36
+
37
+ const DEEP_INTERVIEW_CONTEXT_PATTERNS: RegExp[] = [
38
+ /\bdeep[ -]?interview\b/i,
39
+ /\bsocratic\b/i,
40
+ /\binterview\s+round\b/i,
41
+ /\bround\s+\d+\b/i,
42
+ /\bambiguity\b/i,
43
+ /\btopology\s+confirmation\b/i,
44
+ /딥\s*인터뷰/,
45
+ /심층\s*인터뷰/,
46
+ /인터뷰\s*(?:라운드|진행|확인)/,
47
+ ];
48
+
49
+ const FINAL_ARTIFACT_PATTERNS: RegExp[] = [
50
+ /^\s*#{1,3}\s*(?:(?:final|approved|completed|완료|최종)\s+)?(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/im,
51
+ /^\s*#{1,3}\s*(?:deep[ -]?interview\s+)?(?:final|approved|completed|완료|최종)\s+(?:spec(?:ification)?|transcript)\b/im,
52
+ /\b(?:final|approved|completed)\s+(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/i,
53
+ /\binterview\s+transcript\b/i,
54
+ /\.skc\/specs\//i,
55
+ /최종\s*(?:명세|스펙|기록)/,
56
+ ];
57
+
58
+ function hasOptionsHeading(text: string): boolean {
59
+ return /(?:^|\n)\s*options\s*:/i.test(text);
60
+ }
61
+
62
+ function hasPattern(text: string, patterns: RegExp[]): boolean {
63
+ return patterns.some(pattern => pattern.test(text));
64
+ }
65
+
66
+ function matchedCanonicalOptions(text: string): DeepInterviewPlaintextAskLeakOption[] {
67
+ const folded = text.toLocaleLowerCase("en-US");
68
+ return CANONICAL_OPTIONS.filter(option => folded.includes(option.toLocaleLowerCase("en-US")));
69
+ }
70
+
71
+ function looksLikeFinalArtifact(text: string): boolean {
72
+ return hasPattern(text, FINAL_ARTIFACT_PATTERNS);
73
+ }
74
+
75
+ export function detectDeepInterviewPlaintextAskLeak(text: string): DeepInterviewPlaintextAskLeakResult | null {
76
+ const trimmed = text.trim();
77
+ if (trimmed.length === 0) return null;
78
+ if (looksLikeFinalArtifact(trimmed)) return null;
79
+ if (!hasOptionsHeading(trimmed)) return null;
80
+
81
+ const matchedOptions = matchedCanonicalOptions(trimmed);
82
+ if (matchedOptions.length < 2) return null;
83
+ if (!hasPattern(trimmed, RESTATE_INTENT_PATTERNS)) return null;
84
+
85
+ return {
86
+ kind: "deep_interview_plaintext_ask_leak",
87
+ matchedOptions,
88
+ signals: {
89
+ optionsHeading: true,
90
+ restateIntent: true,
91
+ deepInterviewContext: hasPattern(trimmed, DEEP_INTERVIEW_CONTEXT_PATTERNS),
92
+ },
93
+ };
94
+ }
@@ -17,7 +17,7 @@
17
17
  "linter": {
18
18
  "enabled": true,
19
19
  "rules": {
20
- "recommended": true
20
+ "preset": "recommended"
21
21
  }
22
22
  },
23
23
  "javascript": {
@@ -292,7 +292,7 @@ Auto-research must never add a public skill entrypoint, never be slash-command/d
292
292
 
293
293
  ### Step 2b: Ask the Question
294
294
 
295
- Use the `ask` tool with the generated question. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
295
+ Use the `ask` tool with the generated question. When a question has options, you MUST call `ask` and must not print `Question:`/`Options:` blocks as assistant prose. If you already printed a question/options block as prose, your next action is to call `ask` with the same question/options, not to wait for a typed answer. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
296
296
 
297
297
  ```
298
298
  Round {n} | Component: {target_component_name} | Targeting: {weakest_dimension} | Why now: {one_sentence_targeting_rationale} | Ambiguity: {score}%
@@ -458,7 +458,7 @@ Also recompute and persist `ambiguity_milestone` each round (detect band transit
458
458
 
459
459
  - **Round 3+**: Allow early exit if user says "enough", "let's go", "build it"
460
460
  - **Round 10**: Show soft warning: "We're at 10 rounds. Current ambiguity: {score}%. Continue or proceed with current clarity?"
461
- - **Round 20**: Hard cap: "Maximum interview rounds reached. Proceeding with current clarity level ({score}%)."
461
+ - **Round 100**: Hard cap: "Maximum interview rounds reached. Proceeding with current clarity level ({score}%)."
462
462
 
463
463
  ## Phase 3: Lateral Review Panel (milestone-triggered)
464
464
 
@@ -495,7 +495,7 @@ When ambiguity ≤ threshold (or hard cap / early exit):
495
495
 
496
496
  **4a. Closure / Acceptance Guard.** Even when ambiguity ≤ threshold, do not treat the math as completion. Run an independent readiness audit from the full main-session perspective (including explore findings, established facts, and triggers the scorer may not have fully weighed). Confirm every active topology component has goal/constraint/criteria coverage, no unresolved or disputed trigger remains on a path that matters, and no low-confidence auto-answer is standing in for user-confirmed truth above the clarity cap. If a material gap exists, explicitly override the gate to the user — "The math says ready, but I am not accepting it yet because {gap}" — and ask the single highest-impact follow-up, returning to Phase 2. Record any override in `state.closure_overrides`.
497
497
 
498
- **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
498
+ **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. Because this gate has options, it MUST go through `ask`: do not print the Restate question and options as assistant prose with `Question:`/`Options:` labels. If the Restate gate was already printed that way, immediately call `ask` with the same question/options before accepting or waiting for any answer. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
499
499
 
500
500
  1. **Generate the specification** using opus model with the prompt-safe transcript. If the full interview transcript or initial context is too large, include the summary plus all concrete decisions, acceptance criteria, unresolved gaps, and ontology snapshots; never overflow the prompt with raw oversized context.
501
501
  - Apply `language.instruction` when present so user-facing prose in the spec preserves the session language; keep code identifiers, file paths, commands, JSON/settings keys, and quoted source text unchanged.
@@ -690,6 +690,7 @@ Skipping any stage is possible but reduces quality assurance:
690
690
 
691
691
  <Tool_Usage>
692
692
  - Use the `ask` tool for each interview question — provides clickable UI with contextual options
693
+ - For any option-bearing question, call `ask`; never print `Question:`/`Options:` blocks as assistant prose. If such a block was already printed, call `ask` with the same question/options as the very next action instead of waiting for a typed/prose answer
693
694
  - Preserve the SKC `ask` tool path for native interaction; do not introduce parallel structured-question transport into this skill
694
695
  - Use `read/search/find exploration or a bounded read-only planner/architect subagent` for brownfield codebase exploration (run BEFORE asking user about codebase)
695
696
  - Use opus model (temperature 0.1) for ambiguity scoring — consistency is critical
@@ -698,7 +699,7 @@ Skipping any stage is possible but reduces quality assurance:
698
699
  - Use the SKC workflow CLI to save the final spec at `.skc/_session-{sessionid}/specs/deep-interview-{slug}.md` exactly; do not use `write`, `edit`, or `ast_edit` directly on `.skc/` paths without force override.
699
700
  - Use public SKC workflow entrypoints to bridge to ralplan, ultragoal, or team only after explicit execution approval — never implement directly. Implementation handoff defaults to ultragoal; reserve team for when tmux-based interactive worker parallelization is genuinely required.
700
701
  - The lateral-review panel spawns read-only persona subagents (Task tool) in parallel with independent context; it is an assist layer, never an executor and never the completion authority
701
- - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each
702
+ - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each; if any of these gates has options, the assistant must call `ask` and must not print `Question:`/`Options:` blocks as assistant prose
702
703
  - Use internal fragment auto-modes only at their documented hooks: `auto-research-greenfield.md` between Step 2a and 2b for greenfield `research: true` questions, `auto-answer-uncertain.md` as Step 2b′ after `ask` resolves and before scoring, and `lateral-review-panel.md` for the Phase 3 panel personas at ambiguity-milestone transitions and before synthesizing agent-supplied answers.
703
704
  - Fragment auto-modes are loaded on demand as `kind: "skill-fragment"`; they are not public workflow skills, not slash-command/discoverable, and not `skill://` registrations.
704
705
  </Tool_Usage>
@@ -787,7 +788,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
787
788
  </Examples>
788
789
 
789
790
  <Escalation_And_Stop_Conditions>
790
- - **Hard cap at 20 rounds**: Proceed with whatever clarity exists, noting the risk
791
+ - **Hard cap at 100 rounds**: Proceed with whatever clarity exists, noting the risk
791
792
  - **Soft warning at 10 rounds**: Offer to continue or proceed
792
793
  - **Early exit (round 3+)**: Allow with warning if ambiguity > threshold
793
794
  - **User says "stop", "cancel", "abort"**: Stop immediately, save state for resume
@@ -822,7 +823,7 @@ Optional settings in `.skc/settings.json`:
822
823
  "skc": {
823
824
  "deepInterview": {
824
825
  "ambiguityThreshold": <resolvedThreshold>,
825
- "maxRounds": 20,
826
+ "maxRounds": 100,
826
827
  "softWarningRounds": 10,
827
828
  "minRoundsBeforeExit": 3,
828
829
  "enableChallengeAgents": true,