@sayknow-cli/coding-agent 0.2.7 → 0.3.1
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/CHANGELOG.md +20 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +25 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +136 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +69 -0
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +295 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +77 -0
- package/dist/types/notifications/threaded-render.d.ts +71 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +41 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/state-writer.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +14 -0
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +19 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +22 -0
- package/dist/types/web/search/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +10 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +421 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -3
- package/src/commands/daemon.ts +47 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +143 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/skc/rules/ponytail.md +68 -0
- package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/ultragoal/SKILL.md +16 -0
- package/src/edit/modes/replace.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/hashline/hash.ts +1 -1
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +30 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/settings-selector.ts +7 -2
- package/src/modes/controllers/event-controller.ts +35 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +52 -4
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/theme/theme.ts +4 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +128 -0
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +842 -0
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1591 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +136 -0
- package/src/notifications/threaded-render.ts +173 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/rlm/index.ts +19 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +195 -54
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skc-runtime/deep-interview-recorder.ts +2 -2
- package/src/skc-runtime/launch-tmux.ts +27 -5
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- package/src/skc-runtime/state-runtime.ts +18 -10
- package/src/skc-runtime/state-writer.ts +8 -8
- package/src/skc-runtime/tmux-common.ts +8 -0
- package/src/skc-runtime/tmux-sessions.ts +8 -1
- package/src/skc-runtime/ultragoal-guard.ts +57 -2
- package/src/skc-runtime/ultragoal-runtime.ts +105 -19
- package/src/skc-runtime/workflow-manifest.generated.json +56 -2
- package/src/skc-runtime/workflow-manifest.ts +18 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +77 -6
- package/src/tools/fetch.ts +78 -1
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +22 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/src/tools/telegram-send.ts +137 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +155 -0
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
|
@@ -23,7 +23,8 @@ export interface ModelProfileDefinition {
|
|
|
23
23
|
|
|
24
24
|
export interface ResolvedProfileBinding {
|
|
25
25
|
defaultSelector?: string;
|
|
26
|
-
|
|
26
|
+
modelRoles: Partial<Record<"vision", string>>;
|
|
27
|
+
agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default" | "vision">, string>>;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function parseModelSelectorProvider(selector: string): string | undefined {
|
|
@@ -56,7 +57,7 @@ export function aggregateModelProfileRequiredProviders(
|
|
|
56
57
|
const profile = (
|
|
57
58
|
name: string,
|
|
58
59
|
requiredProviders: string[],
|
|
59
|
-
modelMapping: Record<ModelProfileRole, string
|
|
60
|
+
modelMapping: Partial<Record<ModelProfileRole, string>>,
|
|
60
61
|
alternativeProviderGroups?: readonly (readonly string[])[],
|
|
61
62
|
): ModelProfileDefinition => ({
|
|
62
63
|
name,
|
|
@@ -382,13 +383,15 @@ export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map
|
|
|
382
383
|
}
|
|
383
384
|
|
|
384
385
|
export function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding {
|
|
385
|
-
const { default: defaultSelector, executor, architect, planner, critic } = definition.modelMapping;
|
|
386
|
+
const { default: defaultSelector, vision, executor, architect, planner, critic } = definition.modelMapping;
|
|
387
|
+
const modelRoles: ResolvedProfileBinding["modelRoles"] = {};
|
|
388
|
+
if (vision !== undefined) modelRoles.vision = vision;
|
|
386
389
|
const agentModelOverrides: ResolvedProfileBinding["agentModelOverrides"] = {};
|
|
387
390
|
if (executor !== undefined) agentModelOverrides.executor = executor;
|
|
388
391
|
if (architect !== undefined) agentModelOverrides.architect = architect;
|
|
389
392
|
if (planner !== undefined) agentModelOverrides.planner = planner;
|
|
390
393
|
if (critic !== undefined) agentModelOverrides.critic = critic;
|
|
391
|
-
return { defaultSelector, agentModelOverrides };
|
|
394
|
+
return { defaultSelector, modelRoles, agentModelOverrides };
|
|
392
395
|
}
|
|
393
396
|
|
|
394
397
|
export function formatAvailableProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string {
|
|
@@ -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" | "vision";
|
|
74
74
|
|
|
75
75
|
export interface ModelRoleInfo {
|
|
76
76
|
tag?: string;
|
|
@@ -80,11 +80,12 @@ 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" },
|
|
83
84
|
};
|
|
84
85
|
|
|
85
|
-
export const MODEL_ROLE_IDS: ModelRole[] = ["default"];
|
|
86
|
+
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "vision"];
|
|
86
87
|
|
|
87
|
-
export type SkcModelAssignmentTargetId = "default" | "executor" | "architect" | "planner" | "critic";
|
|
88
|
+
export type SkcModelAssignmentTargetId = "default" | "vision" | "executor" | "architect" | "planner" | "critic";
|
|
88
89
|
|
|
89
90
|
export interface SkcModelAssignmentTargetInfo extends ModelRoleInfo {
|
|
90
91
|
id: SkcModelAssignmentTargetId;
|
|
@@ -93,6 +94,7 @@ export interface SkcModelAssignmentTargetInfo extends ModelRoleInfo {
|
|
|
93
94
|
|
|
94
95
|
export const SKC_MODEL_ASSIGNMENT_TARGET_IDS: SkcModelAssignmentTargetId[] = [
|
|
95
96
|
"default",
|
|
97
|
+
"vision",
|
|
96
98
|
"executor",
|
|
97
99
|
"architect",
|
|
98
100
|
"planner",
|
|
@@ -101,6 +103,7 @@ export const SKC_MODEL_ASSIGNMENT_TARGET_IDS: SkcModelAssignmentTargetId[] = [
|
|
|
101
103
|
|
|
102
104
|
export const SKC_MODEL_ASSIGNMENT_TARGETS: Record<SkcModelAssignmentTargetId, SkcModelAssignmentTargetInfo> = {
|
|
103
105
|
default: { id: "default", tag: "DEFAULT", name: "Default", color: "success", settingsPath: "modelRoles" },
|
|
106
|
+
vision: { id: "vision", tag: "VISION", name: "Vision", color: "accent", settingsPath: "modelRoles" },
|
|
104
107
|
executor: {
|
|
105
108
|
id: "executor",
|
|
106
109
|
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", "executor", "architect", "planner", "critic"]);
|
|
84
|
+
export const ProfileRoleSchema = z.enum(["default", "vision", "executor", "architect", "planner", "critic"]);
|
|
85
85
|
|
|
86
86
|
function isValidProfileModelSelector(value: string): boolean {
|
|
87
87
|
if (value.includes(",")) return false;
|
|
@@ -31,6 +31,7 @@ export type SettingTab =
|
|
|
31
31
|
| "appearance"
|
|
32
32
|
| "model"
|
|
33
33
|
| "interaction"
|
|
34
|
+
| "notifications"
|
|
34
35
|
| "context"
|
|
35
36
|
| "memory"
|
|
36
37
|
| "editing"
|
|
@@ -46,6 +47,7 @@ export const SETTING_TABS: SettingTab[] = [
|
|
|
46
47
|
"appearance",
|
|
47
48
|
"model",
|
|
48
49
|
"interaction",
|
|
50
|
+
"notifications",
|
|
49
51
|
"context",
|
|
50
52
|
"memory",
|
|
51
53
|
"editing",
|
|
@@ -59,6 +61,7 @@ export const TAB_METADATA: Record<SettingTab, { label: string; icon: `tab.${stri
|
|
|
59
61
|
appearance: { label: "Appearance", icon: "tab.appearance" },
|
|
60
62
|
model: { label: "Model", icon: "tab.model" },
|
|
61
63
|
interaction: { label: "Interaction", icon: "tab.interaction" },
|
|
64
|
+
notifications: { label: "Notifications", icon: "tab.notifications" },
|
|
62
65
|
context: { label: "Context", icon: "tab.context" },
|
|
63
66
|
memory: { label: "Memory", icon: "tab.memory" },
|
|
64
67
|
editing: { label: "Editing", icon: "tab.editing" },
|
|
@@ -130,11 +133,14 @@ interface UiString extends UiBase {
|
|
|
130
133
|
* - Omitted → renders as a free text input.
|
|
131
134
|
*/
|
|
132
135
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
136
|
+
/** When true, the value is masked in the settings list display but edited in full. */
|
|
137
|
+
secret?: boolean;
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
/** Wide ui shape exposed to consumers that walk the schema generically. */
|
|
136
141
|
export type AnyUiMetadata = UiBase & {
|
|
137
142
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
143
|
+
secret?: boolean;
|
|
138
144
|
};
|
|
139
145
|
|
|
140
146
|
interface BooleanDef {
|
|
@@ -255,6 +261,105 @@ export const SETTINGS_SCHEMA = {
|
|
|
255
261
|
"auth.broker.url": { type: "string", default: undefined },
|
|
256
262
|
"auth.broker.token": { type: "string", default: undefined },
|
|
257
263
|
|
|
264
|
+
// 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
|
+
},
|
|
343
|
+
"notifications.verbosity": {
|
|
344
|
+
type: "string",
|
|
345
|
+
default: "lean",
|
|
346
|
+
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
|
+
},
|
|
357
|
+
"notifications.daemon.idleTimeoutMs": {
|
|
358
|
+
type: "number",
|
|
359
|
+
default: 60000,
|
|
360
|
+
validate: (value: number) => Number.isFinite(value) && value > 0,
|
|
361
|
+
},
|
|
362
|
+
|
|
258
363
|
autoResume: {
|
|
259
364
|
type: "boolean",
|
|
260
365
|
default: false,
|
|
@@ -2110,6 +2215,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
2110
2215
|
ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
|
|
2111
2216
|
},
|
|
2112
2217
|
|
|
2218
|
+
"web.insaneFallback": {
|
|
2219
|
+
type: "boolean",
|
|
2220
|
+
default: false,
|
|
2221
|
+
ui: {
|
|
2222
|
+
tab: "tools",
|
|
2223
|
+
label: "Insane Search Fallback",
|
|
2224
|
+
description:
|
|
2225
|
+
"Opt in to the vendored insane-search escalation for blocked public URL reads (403/WAF/JS-gated). Off by default. Requires preinstalled python3 + curl_cffi (and node + playwright/stealth for the browser phase); changes network posture by enabling TLS/browser impersonation for public pages.",
|
|
2226
|
+
},
|
|
2227
|
+
},
|
|
2228
|
+
|
|
2113
2229
|
"github.enabled": {
|
|
2114
2230
|
type: "boolean",
|
|
2115
2231
|
default: false,
|
|
@@ -2737,6 +2853,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
2737
2853
|
values: [
|
|
2738
2854
|
"auto",
|
|
2739
2855
|
"duckduckgo",
|
|
2856
|
+
"insane",
|
|
2740
2857
|
"exa",
|
|
2741
2858
|
"brave",
|
|
2742
2859
|
"jina",
|
|
@@ -2769,6 +2886,11 @@ export const SETTINGS_SCHEMA = {
|
|
|
2769
2886
|
label: "DuckDuckGo",
|
|
2770
2887
|
description: "Keyless default — no API key or OAuth required",
|
|
2771
2888
|
},
|
|
2889
|
+
{
|
|
2890
|
+
value: "insane",
|
|
2891
|
+
label: "Insane",
|
|
2892
|
+
description: "Keyless safe public-route fallback inspired by upstream insane-search",
|
|
2893
|
+
},
|
|
2772
2894
|
{ value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set" },
|
|
2773
2895
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
2774
2896
|
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
@@ -3209,6 +3331,26 @@ export interface ShellMinimizerSettings {
|
|
|
3209
3331
|
maxCaptureBytes: number;
|
|
3210
3332
|
}
|
|
3211
3333
|
|
|
3334
|
+
export interface NotificationsSettings {
|
|
3335
|
+
enabled: boolean;
|
|
3336
|
+
telegram: {
|
|
3337
|
+
botToken: string | undefined;
|
|
3338
|
+
chatId: string | undefined;
|
|
3339
|
+
};
|
|
3340
|
+
discord: {
|
|
3341
|
+
botToken: string | undefined;
|
|
3342
|
+
channelId: string | undefined;
|
|
3343
|
+
};
|
|
3344
|
+
slack: {
|
|
3345
|
+
botToken: string | undefined;
|
|
3346
|
+
channelId: string | undefined;
|
|
3347
|
+
};
|
|
3348
|
+
redact: boolean;
|
|
3349
|
+
daemon: {
|
|
3350
|
+
idleTimeoutMs: number;
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3212
3354
|
/** Map group prefix -> typed settings interface */
|
|
3213
3355
|
export interface GroupTypeMap {
|
|
3214
3356
|
compaction: CompactionSettings;
|
|
@@ -3227,6 +3369,7 @@ export interface GroupTypeMap {
|
|
|
3227
3369
|
modelTags: ModelTagsSettings;
|
|
3228
3370
|
cycleOrder: string[];
|
|
3229
3371
|
shellMinimizer: ShellMinimizerSettings;
|
|
3372
|
+
notifications: NotificationsSettings;
|
|
3230
3373
|
}
|
|
3231
3374
|
|
|
3232
3375
|
export type GroupPrefix = keyof GroupTypeMap;
|
|
@@ -17,6 +17,9 @@ export const COORDINATOR_MCP_TOOL_NAMES = [
|
|
|
17
17
|
"skc_coordinator_read_turn",
|
|
18
18
|
"skc_coordinator_await_turn",
|
|
19
19
|
"skc_coordinator_report_status",
|
|
20
|
+
"skc_delegate_plan",
|
|
21
|
+
"skc_delegate_execute",
|
|
22
|
+
"skc_delegate_team",
|
|
20
23
|
] as const;
|
|
21
24
|
|
|
22
25
|
export type CoordinatorToolName = (typeof COORDINATOR_MCP_TOOL_NAMES)[number];
|
|
@@ -184,7 +184,8 @@ type CoordinatorEventKind =
|
|
|
184
184
|
| "question.answered"
|
|
185
185
|
| "report.written"
|
|
186
186
|
| "tmux.delivery_succeeded"
|
|
187
|
-
| "tmux.delivery_failed"
|
|
187
|
+
| "tmux.delivery_failed"
|
|
188
|
+
| "delegation.started";
|
|
188
189
|
|
|
189
190
|
interface CoordinatorEvent {
|
|
190
191
|
schema_version: 1;
|
|
@@ -411,9 +412,116 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
411
412
|
},
|
|
412
413
|
};
|
|
413
414
|
}
|
|
415
|
+
const delegateWorkflow = workflowForDelegateTool(name);
|
|
416
|
+
if (delegateWorkflow) {
|
|
417
|
+
return {
|
|
418
|
+
name,
|
|
419
|
+
description: delegateToolDescription(delegateWorkflow),
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: "object",
|
|
422
|
+
properties: {
|
|
423
|
+
cwd,
|
|
424
|
+
task: {
|
|
425
|
+
type: "string",
|
|
426
|
+
description: "Delegated task or objective to run through the selected SKC workflow.",
|
|
427
|
+
},
|
|
428
|
+
prompt: { type: "string", description: "Alias for task; accepted when task is absent." },
|
|
429
|
+
allow_mutation: allowMutation,
|
|
430
|
+
session_id: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description:
|
|
433
|
+
"Optional existing SKC coordinator bridge session id to reuse; omitted starts a fresh session.",
|
|
434
|
+
},
|
|
435
|
+
queue: {
|
|
436
|
+
type: "boolean",
|
|
437
|
+
description: "When reusing a session with an active turn, queue instead of failing.",
|
|
438
|
+
},
|
|
439
|
+
force: {
|
|
440
|
+
type: "boolean",
|
|
441
|
+
description: "When reusing a session with an active turn, supersede it before sending.",
|
|
442
|
+
},
|
|
443
|
+
model: {
|
|
444
|
+
type: "string",
|
|
445
|
+
description: "Optional model hint passed in prompt metadata; no provider default is implied.",
|
|
446
|
+
},
|
|
447
|
+
await_completion: { type: "boolean", description: "If true, poll the turn until terminal or timeout." },
|
|
448
|
+
timeout_ms: {
|
|
449
|
+
type: "number",
|
|
450
|
+
description: "Bounded await timeout; same cap semantics as skc_coordinator_await_turn.",
|
|
451
|
+
},
|
|
452
|
+
poll_interval_ms: { type: "number", description: "Bounded await polling interval." },
|
|
453
|
+
lines: { type: "number", description: "Bounded advisory tail lines returned with await/read payloads." },
|
|
454
|
+
},
|
|
455
|
+
required: ["cwd", "allow_mutation"],
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
414
459
|
return { name, description: "List known scoped SKC coordinator bridge sessions.", inputSchema: common };
|
|
415
460
|
}
|
|
416
461
|
|
|
462
|
+
type DelegateWorkflow = "plan" | "execute" | "team";
|
|
463
|
+
|
|
464
|
+
function workflowForDelegateTool(name: string): DelegateWorkflow | null {
|
|
465
|
+
switch (name) {
|
|
466
|
+
case "skc_delegate_plan":
|
|
467
|
+
return "plan";
|
|
468
|
+
case "skc_delegate_execute":
|
|
469
|
+
return "execute";
|
|
470
|
+
case "skc_delegate_team":
|
|
471
|
+
return "team";
|
|
472
|
+
default:
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function workflowSkill(workflow: DelegateWorkflow): "ralplan" | "ultragoal" | "team" {
|
|
478
|
+
switch (workflow) {
|
|
479
|
+
case "plan":
|
|
480
|
+
return "ralplan";
|
|
481
|
+
case "execute":
|
|
482
|
+
return "ultragoal";
|
|
483
|
+
case "team":
|
|
484
|
+
return "team";
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function delegateToolDescription(workflow: DelegateWorkflow): string {
|
|
489
|
+
switch (workflow) {
|
|
490
|
+
case "plan":
|
|
491
|
+
return "Delegate consensus planning to SKC: start a session and run /skill:ralplan to completion, returning durable turn status and artifact references.";
|
|
492
|
+
case "execute":
|
|
493
|
+
return "Delegate execution to SKC: start a session and run /skill:ultragoal to completion, returning durable turn status and artifact references.";
|
|
494
|
+
case "team":
|
|
495
|
+
return "Delegate parallel team execution to SKC: start a session and run /skill:team to completion, returning durable turn status and artifact references.";
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function workflowPrompt(
|
|
500
|
+
workflow: DelegateWorkflow,
|
|
501
|
+
toolName: string,
|
|
502
|
+
canonicalCwd: string,
|
|
503
|
+
task: string,
|
|
504
|
+
options: { mutationRequested: boolean; model?: string | null },
|
|
505
|
+
): string {
|
|
506
|
+
const skill = workflowSkill(workflow);
|
|
507
|
+
const model = options.model && options.model.trim().length > 0 ? options.model.trim() : "none";
|
|
508
|
+
const mutationIntent = options.mutationRequested ? "mutation requested" : "read-only";
|
|
509
|
+
return [
|
|
510
|
+
`/skill:${skill}`,
|
|
511
|
+
"",
|
|
512
|
+
`Delegated by coordinator MCP tool: ${toolName}`,
|
|
513
|
+
`Workflow: ${workflow}`,
|
|
514
|
+
`CWD: ${canonicalCwd}`,
|
|
515
|
+
`Mutation intent: ${mutationIntent}; coordinator startup policy remains authoritative.`,
|
|
516
|
+
`Optional model hint: ${model}`,
|
|
517
|
+
"",
|
|
518
|
+
"Task:",
|
|
519
|
+
task,
|
|
520
|
+
"",
|
|
521
|
+
"Return durable status and artifact references through SKC runtime/coordinator state. Do not expose host-facing tmux controls.",
|
|
522
|
+
].join("\n");
|
|
523
|
+
}
|
|
524
|
+
|
|
417
525
|
function normalizeSession(session: Record<string, unknown>): Record<string, unknown> {
|
|
418
526
|
return {
|
|
419
527
|
session_id: session.sessionId ?? session.session_id ?? session.name ?? "unknown",
|
|
@@ -424,6 +532,14 @@ function normalizeSession(session: Record<string, unknown>): Record<string, unkn
|
|
|
424
532
|
};
|
|
425
533
|
}
|
|
426
534
|
|
|
535
|
+
async function canonicalizePath(value: string): Promise<string> {
|
|
536
|
+
try {
|
|
537
|
+
return await fs.realpath(value);
|
|
538
|
+
} catch {
|
|
539
|
+
return path.resolve(value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
427
543
|
async function ensureDir(dir: string): Promise<void> {
|
|
428
544
|
await fs.mkdir(dir, { recursive: true });
|
|
429
545
|
}
|
|
@@ -1587,6 +1703,159 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1587
1703
|
transport: { mcp: "long_poll", push_subscriptions: false },
|
|
1588
1704
|
};
|
|
1589
1705
|
}
|
|
1706
|
+
const delegateWorkflow = workflowForDelegateTool(name);
|
|
1707
|
+
if (delegateWorkflow) {
|
|
1708
|
+
requireCoordinatorMutation(config, "sessions", args);
|
|
1709
|
+
const canonicalCwd = await assertCoordinatorWorkdir(config, args.cwd);
|
|
1710
|
+
const hasTask = typeof args.task === "string" && args.task.trim().length > 0;
|
|
1711
|
+
const hasPrompt = typeof args.prompt === "string" && args.prompt.trim().length > 0;
|
|
1712
|
+
const task = hasTask ? String(args.task) : hasPrompt ? String(args.prompt) : null;
|
|
1713
|
+
if (!task) return { ok: false, reason: "task_required" };
|
|
1714
|
+
const promptAliasIgnored = hasTask && hasPrompt;
|
|
1715
|
+
const mutationRequested = args.allow_mutation === true;
|
|
1716
|
+
const taggedPrompt = workflowPrompt(delegateWorkflow, name, canonicalCwd, task, {
|
|
1717
|
+
mutationRequested,
|
|
1718
|
+
model: typeof args.model === "string" ? args.model : null,
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
let session: Record<string, unknown>;
|
|
1722
|
+
let reusedSession = false;
|
|
1723
|
+
if (args.session_id != null) {
|
|
1724
|
+
const sessionId = safeExternalId("session", args.session_id);
|
|
1725
|
+
const existing = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
1726
|
+
if (!existing) return { ok: false, reason: "unknown_session", session_id: sessionId };
|
|
1727
|
+
const storedCwd = typeof existing.cwd === "string" ? existing.cwd : null;
|
|
1728
|
+
const canonicalStored = storedCwd ? await canonicalizePath(storedCwd) : null;
|
|
1729
|
+
const canonicalRequested = await canonicalizePath(canonicalCwd);
|
|
1730
|
+
if (!canonicalStored || canonicalStored !== canonicalRequested) {
|
|
1731
|
+
return { ok: false, reason: "session_cwd_mismatch", session_id: sessionId };
|
|
1732
|
+
}
|
|
1733
|
+
session = existing;
|
|
1734
|
+
reusedSession = true;
|
|
1735
|
+
} else {
|
|
1736
|
+
const input = {
|
|
1737
|
+
cwd: canonicalCwd,
|
|
1738
|
+
prompt: undefined,
|
|
1739
|
+
namespace: config.namespace,
|
|
1740
|
+
worktree: true as const,
|
|
1741
|
+
};
|
|
1742
|
+
const started = services.startSession
|
|
1743
|
+
? await services.startSession(input)
|
|
1744
|
+
: await startTmuxSession(config, input, namespaceDir, commandRunner);
|
|
1745
|
+
const startedRecord = asRecord(started);
|
|
1746
|
+
if (!startedRecord) throw new Error("coordinator_session_command_required");
|
|
1747
|
+
session = normalizeSession(startedRecord);
|
|
1748
|
+
await writeJsonFile(sessionFile(session.session_id), session);
|
|
1749
|
+
await appendCoordinatorEvent(namespaceDir, {
|
|
1750
|
+
kind: "session.started",
|
|
1751
|
+
sessionId: String(session.session_id),
|
|
1752
|
+
summary: `Session ${String(session.session_id)} started by coordinator delegate`,
|
|
1753
|
+
payloadRef: path.relative(namespaceDir, sessionFile(session.session_id)),
|
|
1754
|
+
metadata: { delegate: true, workflow: delegateWorkflow },
|
|
1755
|
+
});
|
|
1756
|
+
const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
|
|
1757
|
+
await writeSessionState(namespaceDir, String(session.session_id), "ready_for_input", {
|
|
1758
|
+
live,
|
|
1759
|
+
reason: null,
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const sessionId = String(session.session_id);
|
|
1764
|
+
const activeTurn = reusedSession ? await readActiveTurn(namespaceDir, sessionId) : null;
|
|
1765
|
+
if (activeTurn && args.force !== true && args.queue !== true) {
|
|
1766
|
+
return {
|
|
1767
|
+
ok: false,
|
|
1768
|
+
reason: "active_turn_exists",
|
|
1769
|
+
session_id: sessionId,
|
|
1770
|
+
active_turn_id: activeTurn.turn_id,
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
if (activeTurn && args.force === true) {
|
|
1774
|
+
const timestamp = new Date().toISOString();
|
|
1775
|
+
const superseded = {
|
|
1776
|
+
...activeTurn,
|
|
1777
|
+
status: "superseded" as const,
|
|
1778
|
+
updated_at: timestamp,
|
|
1779
|
+
completed_at: timestamp,
|
|
1780
|
+
};
|
|
1781
|
+
await writeTurnRecord(namespaceDir, superseded);
|
|
1782
|
+
await clearActiveTurn(namespaceDir, superseded);
|
|
1783
|
+
}
|
|
1784
|
+
const shouldQueue = args.queue === true && args.force !== true && !!activeTurn;
|
|
1785
|
+
const turn = shouldQueue
|
|
1786
|
+
? makeTurnRecord(config, sessionId, taggedPrompt, "queued")
|
|
1787
|
+
: await activateTurn(session, makeTurnRecord(config, sessionId, taggedPrompt, "active"));
|
|
1788
|
+
if (shouldQueue) await writeTurnRecord(namespaceDir, turn);
|
|
1789
|
+
await appendCoordinatorEvent(namespaceDir, {
|
|
1790
|
+
kind: "delegation.started",
|
|
1791
|
+
sessionId,
|
|
1792
|
+
turnId: turn.turn_id,
|
|
1793
|
+
summary: `Delegated ${delegateWorkflow} via ${name} on session ${sessionId}`,
|
|
1794
|
+
metadata: {
|
|
1795
|
+
workflow: delegateWorkflow,
|
|
1796
|
+
tool_name: name,
|
|
1797
|
+
reused_session: reusedSession,
|
|
1798
|
+
queued: shouldQueue,
|
|
1799
|
+
allow_mutation: args.allow_mutation === true,
|
|
1800
|
+
},
|
|
1801
|
+
});
|
|
1802
|
+
const sessionState = await readSessionState(namespaceDir, sessionId);
|
|
1803
|
+
const base: Record<string, unknown> = {
|
|
1804
|
+
ok: true,
|
|
1805
|
+
workflow: delegateWorkflow,
|
|
1806
|
+
tool_name: name,
|
|
1807
|
+
session_id: sessionId,
|
|
1808
|
+
turn_id: turn.turn_id,
|
|
1809
|
+
active_turn_id: shouldQueue ? activeTurn?.turn_id : turn.turn_id,
|
|
1810
|
+
status: turn.status,
|
|
1811
|
+
queued: turn.delivery.queued,
|
|
1812
|
+
delivered: turn.delivery.delivered,
|
|
1813
|
+
delivery: turn.delivery,
|
|
1814
|
+
session,
|
|
1815
|
+
session_state: sessionState,
|
|
1816
|
+
turn,
|
|
1817
|
+
awaited: false,
|
|
1818
|
+
artifacts: [],
|
|
1819
|
+
};
|
|
1820
|
+
if (promptAliasIgnored) base.prompt_alias_ignored = true;
|
|
1821
|
+
if (args.await_completion === true && !shouldQueue) {
|
|
1822
|
+
const timeoutMs = boundedTimeoutMs(args.timeout_ms);
|
|
1823
|
+
const pollIntervalMs = boundedPollIntervalMs(args.poll_interval_ms);
|
|
1824
|
+
const deadline = Date.now() + timeoutMs;
|
|
1825
|
+
let payload = await readTurnPayload(turn.turn_id, sessionId, args.lines);
|
|
1826
|
+
while (
|
|
1827
|
+
payload.ok === true &&
|
|
1828
|
+
!TERMINAL_TURN_STATUSES.has((payload.turn as TurnRecord).status) &&
|
|
1829
|
+
Date.now() < deadline
|
|
1830
|
+
) {
|
|
1831
|
+
const remainingMs = deadline - Date.now();
|
|
1832
|
+
await waitForTurnStateChange(
|
|
1833
|
+
namespaceDir,
|
|
1834
|
+
payload.turn as TurnRecord,
|
|
1835
|
+
Math.min(pollIntervalMs, remainingMs),
|
|
1836
|
+
);
|
|
1837
|
+
payload = await readTurnPayload(turn.turn_id, sessionId, args.lines);
|
|
1838
|
+
}
|
|
1839
|
+
const awaitedTurn = (payload.ok === true ? payload.turn : turn) as TurnRecord;
|
|
1840
|
+
base.awaited = true;
|
|
1841
|
+
base.status = awaitedTurn.status;
|
|
1842
|
+
base.turn = awaitedTurn;
|
|
1843
|
+
base.final_response = (awaitedTurn as unknown as Record<string, unknown>).final_response ?? null;
|
|
1844
|
+
base.evidence = (awaitedTurn as unknown as Record<string, unknown>).evidence ?? [];
|
|
1845
|
+
if (payload.ok === true) {
|
|
1846
|
+
base.session_state = payload.session_state;
|
|
1847
|
+
base.advisory_status = payload.advisory_status;
|
|
1848
|
+
}
|
|
1849
|
+
// Mirror skc_coordinator_await_turn timeout semantics: a still-active
|
|
1850
|
+
// turn at the deadline is a bounded timeout, not a completion.
|
|
1851
|
+
if (!TERMINAL_TURN_STATUSES.has(awaitedTurn.status)) {
|
|
1852
|
+
base.timed_out = true;
|
|
1853
|
+
base.reason = "timeout";
|
|
1854
|
+
base.ok = false;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return base;
|
|
1858
|
+
}
|
|
1590
1859
|
if (name === "skc_coordinator_start_session") {
|
|
1591
1860
|
requireCoordinatorMutation(config, "sessions", args);
|
|
1592
1861
|
const cwd = await assertCoordinatorWorkdir(config, args.cwd);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static built-in daemon controller map.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally a static map keyed by daemon kind rather than a mutable plugin
|
|
5
|
+
* registry: there is exactly one kind today (`telegram`). Promote to a richer
|
|
6
|
+
* registry only when a second daemon kind exists.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Settings } from "../config/settings";
|
|
10
|
+
import { type TelegramDaemonControlDeps, TelegramDaemonController } from "../notifications/telegram-daemon-control";
|
|
11
|
+
import type { BuiltInDaemonController, DaemonKind } from "./control-types";
|
|
12
|
+
|
|
13
|
+
export const BUILT_IN_DAEMON_KINDS = ["telegram"] as const satisfies readonly DaemonKind[];
|
|
14
|
+
|
|
15
|
+
export interface BuiltInDaemonControllerDeps {
|
|
16
|
+
telegram?: TelegramDaemonControlDeps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createBuiltInDaemonControllers(
|
|
20
|
+
settings: Settings,
|
|
21
|
+
deps: BuiltInDaemonControllerDeps = {},
|
|
22
|
+
): Record<DaemonKind, BuiltInDaemonController> {
|
|
23
|
+
return {
|
|
24
|
+
telegram: new TelegramDaemonController(settings, deps.telegram),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the controllers a command should act on. `--all` selects every
|
|
30
|
+
* built-in kind; otherwise the explicit `kinds` (defaulting to `telegram`).
|
|
31
|
+
*/
|
|
32
|
+
export function selectDaemonControllers(
|
|
33
|
+
settings: Settings,
|
|
34
|
+
kinds: DaemonKind[] | undefined,
|
|
35
|
+
all: boolean,
|
|
36
|
+
deps: BuiltInDaemonControllerDeps = {},
|
|
37
|
+
): BuiltInDaemonController[] {
|
|
38
|
+
const map = createBuiltInDaemonControllers(settings, deps);
|
|
39
|
+
if (all) return Object.values(map);
|
|
40
|
+
const selected = kinds && kinds.length > 0 ? kinds : (["telegram"] as DaemonKind[]);
|
|
41
|
+
return selected.map(kind => {
|
|
42
|
+
const controller = map[kind];
|
|
43
|
+
if (!controller) throw new Error(`unknown daemon kind: ${kind}`);
|
|
44
|
+
return controller;
|
|
45
|
+
});
|
|
46
|
+
}
|