@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.
- package/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/config/model-profiles.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +0 -5
- package/dist/types/config/settings-schema.d.ts +79 -68
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
- package/dist/types/i18n/messages/en.d.ts +0 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +0 -1
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/state-renderer.d.ts +5 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
- package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +37 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +80 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +31 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/computer-gc.d.ts +23 -0
- package/dist/types/tools/cron.d.ts +36 -61
- package/dist/types/tools/index.d.ts +3 -2
- package/dist/types/tools/resource-gc.d.ts +54 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +11 -4
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +180 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/args.ts +0 -1
- package/src/cli/fast-help.ts +29 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli/web-search-cli.ts +5 -0
- package/src/cli.ts +30 -12
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +62 -8
- package/src/config/model-profiles.ts +3 -4
- package/src/config/model-registry.ts +3 -6
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +88 -103
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -12
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/skc-plugins/compiler.ts +351 -0
- package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/skc-plugins/index.ts +9 -0
- package/src/extensibility/skc-plugins/injection.ts +109 -0
- package/src/extensibility/skc-plugins/installer.ts +434 -0
- package/src/extensibility/skc-plugins/loader.ts +3 -1
- package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/skc-plugins/observability.ts +84 -0
- package/src/extensibility/skc-plugins/paths.ts +1 -1
- package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/skc-plugins/registry.ts +180 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/skc-plugins/schema.ts +250 -20
- package/src/extensibility/skc-plugins/session-validation.ts +147 -0
- package/src/extensibility/skc-plugins/types.ts +199 -3
- package/src/extensibility/skc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/goals/tools/goal-tool.ts +14 -1
- package/src/hooks/skill-state.ts +57 -0
- package/src/i18n/messages/de.ts +0 -1
- package/src/i18n/messages/en.ts +0 -1
- package/src/i18n/messages/es.ts +0 -1
- package/src/i18n/messages/fr.ts +0 -1
- package/src/i18n/messages/ja.ts +0 -1
- package/src/i18n/messages/ko.ts +0 -1
- package/src/i18n/messages/zh.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -11
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/assistant-message.ts +49 -1
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +65 -12
- package/src/modes/components/settings-defs.ts +1 -2
- package/src/modes/components/settings-selector.ts +2 -7
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +59 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +0 -4
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +238 -0
- package/src/notifications/lifecycle-control-runtime.ts +405 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/prompts/system/system-prompt.md +2 -2
- package/src/prompts/tools/cron.md +5 -3
- package/src/prompts/tools/read.md +1 -1
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +162 -10
- package/src/session/agent-session.ts +210 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +68 -19
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/state-renderer.ts +13 -0
- package/src/skc-runtime/team-runtime.ts +56 -23
- package/src/skc-runtime/tmux-common.ts +88 -4
- package/src/skc-runtime/tmux-sessions.ts +111 -9
- package/src/skc-runtime/ultragoal-guard.ts +192 -8
- package/src/skc-runtime/ultragoal-runtime.ts +296 -16
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/browser/tab-supervisor.ts +86 -2
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/computer-gc.ts +66 -0
- package/src/tools/computer.ts +2 -0
- package/src/tools/cron.ts +75 -112
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +5 -9
- package/src/tools/read.ts +25 -55
- package/src/tools/renderers.ts +0 -2
- package/src/tools/resource-gc.ts +291 -0
- package/src/tools/ultragoal-ask-guard.ts +7 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/index.ts +1 -0
- package/src/web/search/providers/utils.ts +22 -5
- package/vendor/insane-search/MANIFEST.json +3 -1
- package/vendor/insane-search/engine/__init__.py +14 -0
- package/vendor/insane-search/engine/content_safety.py +151 -0
- package/vendor/insane-search/engine/fetch_chain.py +32 -0
- package/vendor/insane-search/engine/tests/test_u8.py +216 -0
- package/dist/types/tools/inspect-image-renderer.d.ts +0 -26
- package/dist/types/tools/inspect-image.d.ts +0 -31
- package/src/prompts/tools/inspect-image-system.md +0 -20
- package/src/prompts/tools/inspect-image.md +0 -32
- package/src/tools/inspect-image-renderer.ts +0 -103
- package/src/tools/inspect-image.ts +0 -172
package/src/commands/session.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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:
|
|
27
|
-
agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default"
|
|
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,
|
|
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"
|
|
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"
|
|
85
|
+
export const MODEL_ROLE_IDS: ModelRole[] = ["default"];
|
|
87
86
|
|
|
88
|
-
export type SkcModelAssignmentTargetId = "default" | "
|
|
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", "
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
}
|
|
@@ -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
|
|
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
|
|
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":
|
|
826
|
+
"maxRounds": 100,
|
|
826
827
|
"softWarningRounds": 10,
|
|
827
828
|
"minRoundsBeforeExit": 3,
|
|
828
829
|
"enableChallengeAgents": true,
|