@sayknow-cli/coding-agent 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +2 -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/settings-schema.d.ts +39 -2
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/defaults/skc-defaults.d.ts +2 -13
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- 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/lsp/types.d.ts +2 -0
- 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/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- 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.d.ts +9 -1
- package/dist/types/notifications/engine.d.ts +59 -0
- 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/managed-daemon.d.ts +48 -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 +133 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/threaded-inbound.d.ts +19 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -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 +16 -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/ralplan-runtime.d.ts +1 -1
- 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/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/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +25 -0
- 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/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 +14 -10
- 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/scripts/verify-insane-vendor.ts +132 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/fast-help.ts +30 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/notify-cli.ts +152 -5
- package/src/cli/plugin-cli.ts +66 -3
- 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/commands/team.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +55 -7
- package/src/config/settings-schema.ts +30 -1
- 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/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/defaults/skc-defaults.ts +2 -27
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/shared-events.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/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +17 -13
- 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 +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 +56 -11
- 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/event-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +53 -11
- package/src/modes/interactive-mode.ts +50 -3
- 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/utils/context-usage.ts +2 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config.ts +23 -2
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +417 -45
- 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/managed-daemon.ts +163 -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 +984 -242
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/threaded-inbound.ts +60 -4
- package/src/notifications/threaded-render.ts +20 -2
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +248 -125
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +85 -22
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- 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 +25 -8
- package/src/skc-runtime/ultragoal-runtime.ts +75 -15
- package/src/skc-runtime/workflow-manifest.generated.json +29 -0
- package/src/skc-runtime/workflow-manifest.ts +7 -2
- 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/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +94 -1
- package/src/tools/index.ts +6 -1
- package/src/tools/telegram-send.ts +137 -0
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +159 -0
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- 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
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import * as fs from "node:fs";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
bold,
|
|
20
|
+
buildCompactChoiceGrid,
|
|
21
|
+
escapeHtml,
|
|
22
|
+
numberedOptionList,
|
|
23
|
+
TELEGRAM_PARSE_MODE,
|
|
24
|
+
truncateTelegramHtml,
|
|
25
|
+
} from "./html-format";
|
|
19
26
|
import { renderThreadedFrame } from "./threaded-render";
|
|
20
27
|
|
|
21
28
|
/** One inline-keyboard button. */
|
|
@@ -129,8 +136,9 @@ export function buildActionMessage(action: {
|
|
|
129
136
|
const text = `❓ ${bold(action.question ?? "Question")}`;
|
|
130
137
|
const options = action.options ?? [];
|
|
131
138
|
if (options.length === 0) return { text: truncateTelegramHtml(`${text}\n\n(reply with text)`) };
|
|
132
|
-
const
|
|
133
|
-
|
|
139
|
+
const body = `${text}\n\n${numberedOptionList(options)}`;
|
|
140
|
+
const inline_keyboard = buildCompactChoiceGrid(options, i => encodeCallbackData(action.id, i));
|
|
141
|
+
return { text: truncateTelegramHtml(body), inline_keyboard };
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
/** A protocol `reply` frame the client should send to the server. */
|
|
@@ -235,13 +243,23 @@ export function routeInboundUpdate(update: unknown, ctx: RouteInboundContext): R
|
|
|
235
243
|
return { kind: "ignore" };
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
/** Read `{url, token}` from an endpoint discovery file. */
|
|
239
|
-
export function readEndpoint(path: string): { url: string; token: string } {
|
|
240
|
-
const raw = JSON.parse(fs.readFileSync(path, "utf8")) as {
|
|
246
|
+
/** Read `{url, token, pid?, stale?}` from an endpoint discovery file. */
|
|
247
|
+
export function readEndpoint(path: string): { url: string; token: string; pid?: number; stale?: boolean } {
|
|
248
|
+
const raw = JSON.parse(fs.readFileSync(path, "utf8")) as {
|
|
249
|
+
url?: unknown;
|
|
250
|
+
token?: unknown;
|
|
251
|
+
pid?: unknown;
|
|
252
|
+
stale?: unknown;
|
|
253
|
+
};
|
|
241
254
|
if (typeof raw.url !== "string" || typeof raw.token !== "string") {
|
|
242
255
|
throw new Error(`invalid endpoint file: ${path}`);
|
|
243
256
|
}
|
|
244
|
-
return {
|
|
257
|
+
return {
|
|
258
|
+
url: raw.url,
|
|
259
|
+
token: raw.token,
|
|
260
|
+
pid: typeof raw.pid === "number" ? raw.pid : undefined,
|
|
261
|
+
stale: raw.stale === true,
|
|
262
|
+
};
|
|
245
263
|
}
|
|
246
264
|
|
|
247
265
|
/** Options for {@link runTelegramReferenceClient}. */
|
|
@@ -21,11 +21,30 @@ export interface InboundUpdate {
|
|
|
21
21
|
message?: {
|
|
22
22
|
message_id?: unknown;
|
|
23
23
|
text?: unknown;
|
|
24
|
+
caption?: unknown;
|
|
25
|
+
photo?: unknown;
|
|
26
|
+
document?: unknown;
|
|
27
|
+
video?: unknown;
|
|
28
|
+
audio?: unknown;
|
|
29
|
+
voice?: unknown;
|
|
30
|
+
animation?: unknown;
|
|
24
31
|
chat?: { id?: unknown };
|
|
25
32
|
message_thread_id?: unknown;
|
|
26
33
|
};
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
/** A downloadable media attachment referenced by an inbound message. */
|
|
37
|
+
export interface InboundAttachment {
|
|
38
|
+
/** Telegram file_id to resolve via getFile. */
|
|
39
|
+
fileId: string;
|
|
40
|
+
/** Source media kind; "photo" is always an image. */
|
|
41
|
+
kind: "photo" | "document" | "video" | "audio" | "voice" | "animation";
|
|
42
|
+
/** MIME type when Telegram provides one. */
|
|
43
|
+
mime?: string;
|
|
44
|
+
/** Original file name when provided. */
|
|
45
|
+
fileName?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/** Context for {@link decideThreadedInbound}. All lookups are injected. */
|
|
30
49
|
export interface ThreadedInboundCtx {
|
|
31
50
|
/** The single paired chat id (string-compared). */
|
|
@@ -38,7 +57,15 @@ export interface ThreadedInboundCtx {
|
|
|
38
57
|
|
|
39
58
|
/** Outcome of routing an inbound update. */
|
|
40
59
|
export type ThreadedInboundDecision =
|
|
41
|
-
| {
|
|
60
|
+
| {
|
|
61
|
+
kind: "inject";
|
|
62
|
+
sessionId: string;
|
|
63
|
+
text: string;
|
|
64
|
+
updateId: number;
|
|
65
|
+
threadId: string;
|
|
66
|
+
messageId?: number;
|
|
67
|
+
attachment?: InboundAttachment;
|
|
68
|
+
}
|
|
42
69
|
| { kind: "duplicate"; updateId: number }
|
|
43
70
|
| { kind: "ignore"; reason: string };
|
|
44
71
|
|
|
@@ -48,6 +75,29 @@ function asString(value: unknown): string | undefined {
|
|
|
48
75
|
return undefined;
|
|
49
76
|
}
|
|
50
77
|
|
|
78
|
+
function extractAttachment(message: NonNullable<InboundUpdate["message"]>): InboundAttachment | undefined {
|
|
79
|
+
if (Array.isArray(message.photo) && message.photo.length > 0) {
|
|
80
|
+
const photo = message.photo[message.photo.length - 1];
|
|
81
|
+
if (photo && typeof photo === "object" && "file_id" in photo && typeof photo.file_id === "string") {
|
|
82
|
+
return { fileId: photo.file_id, kind: "photo", mime: "image/jpeg" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const kind of ["document", "video", "audio", "voice", "animation"] as const) {
|
|
87
|
+
const file = message[kind];
|
|
88
|
+
if (file && typeof file === "object" && "file_id" in file && typeof file.file_id === "string") {
|
|
89
|
+
return {
|
|
90
|
+
fileId: file.file_id,
|
|
91
|
+
kind,
|
|
92
|
+
mime: "mime_type" in file && typeof file.mime_type === "string" ? file.mime_type : undefined,
|
|
93
|
+
fileName: "file_name" in file && typeof file.file_name === "string" ? file.file_name : undefined,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
51
101
|
/**
|
|
52
102
|
* Decide whether an inbound update should inject a user turn. Fail-closed:
|
|
53
103
|
* returns `ignore` (with a reason) or `duplicate` for anything that is not an
|
|
@@ -72,9 +122,15 @@ export function decideThreadedInbound(update: InboundUpdate, ctx: ThreadedInboun
|
|
|
72
122
|
const updateId = update.update_id;
|
|
73
123
|
if (ctx.isDuplicate(updateId)) return { kind: "duplicate", updateId };
|
|
74
124
|
|
|
75
|
-
const text =
|
|
76
|
-
|
|
125
|
+
const text =
|
|
126
|
+
typeof message.text === "string"
|
|
127
|
+
? message.text.trim()
|
|
128
|
+
: typeof message.caption === "string"
|
|
129
|
+
? message.caption.trim()
|
|
130
|
+
: "";
|
|
131
|
+
const attachment = extractAttachment(message);
|
|
132
|
+
if (!text && attachment === undefined) return { kind: "ignore", reason: "empty_text" };
|
|
77
133
|
|
|
78
134
|
const messageId = typeof message.message_id === "number" ? message.message_id : undefined;
|
|
79
|
-
return { kind: "inject", sessionId, text, updateId, threadId, messageId };
|
|
135
|
+
return { kind: "inject", sessionId, text, updateId, threadId, messageId, attachment };
|
|
80
136
|
}
|
|
@@ -15,15 +15,19 @@ import type { RateLimitLane } from "./rate-limit-pool";
|
|
|
15
15
|
|
|
16
16
|
/** A Telegram send derived from a threaded frame (topic id is applied by the daemon). */
|
|
17
17
|
export interface ThreadedSend {
|
|
18
|
-
method: "sendMessage" | "sendPhoto";
|
|
18
|
+
method: "sendMessage" | "sendPhoto" | "sendDocument";
|
|
19
19
|
/** Rate-limit lane for prioritisation/fairness. */
|
|
20
20
|
lane: RateLimitLane;
|
|
21
21
|
/** Message text (sendMessage) or photo caption (sendPhoto). */
|
|
22
22
|
text?: string;
|
|
23
23
|
/** Base64 image bytes for sendPhoto. */
|
|
24
24
|
photoBase64?: string;
|
|
25
|
+
/** Base64 file bytes for sendDocument. */
|
|
26
|
+
documentBase64?: string;
|
|
25
27
|
/** Image MIME type for sendPhoto. */
|
|
26
28
|
mime?: string;
|
|
29
|
+
/** Suggested document filename. */
|
|
30
|
+
fileName?: string;
|
|
27
31
|
/** Coalesce key for live edits (same key collapses to the latest). */
|
|
28
32
|
coalesceKey?: string;
|
|
29
33
|
/** True for the one-time identity header (the daemon pins it once). */
|
|
@@ -49,11 +53,12 @@ interface ThreadedFrame {
|
|
|
49
53
|
phase?: unknown;
|
|
50
54
|
text?: unknown;
|
|
51
55
|
messageRef?: unknown;
|
|
52
|
-
// image_attachment
|
|
56
|
+
// image_attachment / file_attachment
|
|
53
57
|
source?: unknown;
|
|
54
58
|
data?: unknown;
|
|
55
59
|
mime?: unknown;
|
|
56
60
|
caption?: unknown;
|
|
61
|
+
name?: unknown;
|
|
57
62
|
// config_update
|
|
58
63
|
verbosity?: unknown;
|
|
59
64
|
redact?: unknown;
|
|
@@ -141,6 +146,19 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
|
|
|
141
146
|
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
142
147
|
};
|
|
143
148
|
}
|
|
149
|
+
case "file_attachment": {
|
|
150
|
+
const data = str(frame.data);
|
|
151
|
+
if (!data) return undefined;
|
|
152
|
+
const caption = str(frame.caption);
|
|
153
|
+
return {
|
|
154
|
+
method: "sendDocument",
|
|
155
|
+
lane: "finalized",
|
|
156
|
+
documentBase64: data,
|
|
157
|
+
mime: str(frame.mime),
|
|
158
|
+
fileName: str(frame.name),
|
|
159
|
+
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
144
162
|
case "config_update": {
|
|
145
163
|
const verbosity = str(frame.verbosity);
|
|
146
164
|
const redact = typeof frame.redact === "boolean" ? `redact ${frame.redact ? "on" : "off"}` : undefined;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-session forum-topic registry for the threaded session surface.
|
|
3
3
|
*
|
|
4
|
-
* Each SKC session owns
|
|
5
|
-
* DM. The topic is created
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Each SKC session owns one active Telegram forum topic in the paired private
|
|
5
|
+
* DM. The topic is created via `createForumTopic`, reused while the session
|
|
6
|
+
* remains active, and removed from the registry when the daemon deletes it on
|
|
7
|
+
* shutdown. The registry also tracks whether the one-time identity header has
|
|
8
|
+
* already been pinned, so it is sent exactly once per active topic, even across
|
|
9
9
|
* reconnects.
|
|
10
10
|
*
|
|
11
11
|
* State is a plain serialisable map persisted beside the daemon state files;
|
|
@@ -65,20 +65,25 @@ export class TopicRegistry {
|
|
|
65
65
|
return this.byTopic.get(topicId);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** All session ids with a persisted topic record. */
|
|
69
|
+
sessionIds(): string[] {
|
|
70
|
+
return [...this.topics.keys()];
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
/** The existing topic record for a session, if any. */
|
|
69
74
|
get(sessionId: string): TopicRecord | undefined {
|
|
70
75
|
return this.topics.get(sessionId);
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
/**
|
|
74
|
-
* Return the existing topic for `sessionId`, or create one via
|
|
75
|
-
* (called only on first use).
|
|
76
|
-
* returned without invoking `create`.
|
|
79
|
+
* Return the existing active topic for `sessionId`, or create one via
|
|
80
|
+
* `create` (called only on first use).
|
|
77
81
|
*/
|
|
78
82
|
async getOrCreateTopic(
|
|
79
83
|
sessionId: string,
|
|
80
84
|
create: () => Promise<string>,
|
|
81
85
|
now: () => number = Date.now,
|
|
86
|
+
name?: string,
|
|
82
87
|
): Promise<TopicRecord> {
|
|
83
88
|
const existing = this.topics.get(sessionId);
|
|
84
89
|
if (existing) return existing;
|
|
@@ -90,7 +95,7 @@ export class TopicRegistry {
|
|
|
90
95
|
if (pending) return pending;
|
|
91
96
|
const promise = (async () => {
|
|
92
97
|
const topicId = await create();
|
|
93
|
-
const record: TopicRecord = { topicId, identitySent: false, createdAt: now() };
|
|
98
|
+
const record: TopicRecord = { topicId, name, identitySent: false, createdAt: now() };
|
|
94
99
|
this.topics.set(sessionId, record);
|
|
95
100
|
this.byTopic.set(topicId, sessionId);
|
|
96
101
|
return record;
|
|
@@ -126,6 +131,15 @@ export class TopicRegistry {
|
|
|
126
131
|
return true;
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
/** Remove a session topic record after Telegram deletes the topic. */
|
|
135
|
+
delete(sessionId: string): boolean {
|
|
136
|
+
const record = this.topics.get(sessionId);
|
|
137
|
+
if (!record) return false;
|
|
138
|
+
this.topics.delete(sessionId);
|
|
139
|
+
this.byTopic.delete(record.topicId);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
/** Serialise for atomic persistence beside the daemon state. */
|
|
130
144
|
serialize(): TopicRegistryState {
|
|
131
145
|
return { topics: Object.fromEntries(this.topics) };
|
|
@@ -36,8 +36,8 @@ This mode activates only when the assignment explicitly labels Executor as Ultra
|
|
|
36
36
|
|
|
37
37
|
When active:
|
|
38
38
|
- Start from the approved plan/spec/acceptance criteria, then user-facing contracts, then implementation code only as supporting evidence. Treat plan/code mismatches as blockers.
|
|
39
|
-
- Exercise the real user-facing invocation rather than inspecting internals alone. Live artifacts must be runtime-valid: GUI/web needs a real automation transcript plus non-uniform screenshot; CLI needs executed argv-only replay; native/desktop/TUI needs a real screenshot, PTY capture with control codes, or app-automation transcript. `inlineEvidence` is supplemental only and is never sole proof for live surfaces.
|
|
40
|
-
- For CLI evidence, emit argv-only replay JSON with `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, and `command` as a string array. Use only allowlisted deterministic executables/arguments
|
|
39
|
+
- Exercise the real user-facing invocation rather than inspecting internals alone. Live artifacts must be runtime-valid: GUI/web needs a real automation transcript plus non-uniform screenshot; CLI needs executed argv-only replay; native/desktop/TUI needs a real screenshot, PTY capture with control codes, or app-automation transcript. API/package surfaces need a real artifact file or typed receipt whose artifact `kind` contains `api`, `package`, `consumer`, `black-box`, or `test-report`; good kinds include `api-package-test-report`, `package-consumer-report`, and `black-box-api-receipt`. Algorithm/math surfaces need a real artifact file or typed receipt whose artifact `kind` contains `property`, `boundary`, `edge`, `adversarial`, `failure`, `math`, `algorithm`, or `test-report`; good kinds include `property-test-report` and `algorithm-boundary-report`. `inlineEvidence` is supplemental only and is never sole proof for live surfaces.
|
|
40
|
+
- For CLI evidence, emit argv-only replay JSON with `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, and `command` as a string array. Use only allowlisted deterministic executables/arguments: `bun --version`, `node --version`, deterministic `bun/node -e "console.log(...)"`, `npm|pnpm|yarn --version`, `npm|pnpm|yarn list`, read-only `git status|rev-parse|merge-base|diff|show|log` with safe args, and `skc read|status`. Mark any other command with audited `replayExempt` metadata plus a valid structural fallback artifact. `replayExempt` must use exact fields `reasonCode`, `reason`, `approvedBy`, and `fallbackArtifactRefs`; allowed `reasonCode` values are exactly `unsafe_side_effect`, `requires_credentials`, `requires_network`, `non_deterministic_external`, `destructive`, `interactive_only`, and `platform_unavailable`.
|
|
41
41
|
- Native/TUI evidence must be structural, not prose-only: screenshot, app transcript, or PTY artifact with terminal control codes.
|
|
42
42
|
- Do not call the `ask` tool while an Ultragoal run is active; record unresolved decisions with `skc ultragoal record-review-blockers`.
|
|
43
43
|
- Try to break the work with adversarial cases, not just happy-path confirmations.
|
|
@@ -24,6 +24,38 @@ import { toJsonRpcError } from "../../runtime-mcp/types";
|
|
|
24
24
|
*/
|
|
25
25
|
const CLOSE_WAIT_MS = 1_000;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Build a minimal environment for a no-inherit stdio MCP child. Only OS-level
|
|
29
|
+
* keys needed to locate/run an interpreter (PATH, HOME, temp, locale, and the
|
|
30
|
+
* Windows system essentials) are copied from the host; everything else
|
|
31
|
+
* (API keys, tokens, secrets) is withheld. Explicit `env` overrides win.
|
|
32
|
+
*/
|
|
33
|
+
function buildMinimalStdioEnv(explicit?: Record<string, string>): Record<string, string> {
|
|
34
|
+
const allow = [
|
|
35
|
+
"PATH",
|
|
36
|
+
"HOME",
|
|
37
|
+
"TMPDIR",
|
|
38
|
+
"TEMP",
|
|
39
|
+
"TMP",
|
|
40
|
+
"LANG",
|
|
41
|
+
"LC_ALL",
|
|
42
|
+
"LC_CTYPE",
|
|
43
|
+
"SHELL",
|
|
44
|
+
"USER",
|
|
45
|
+
"SystemRoot",
|
|
46
|
+
"SYSTEMROOT",
|
|
47
|
+
"PATHEXT",
|
|
48
|
+
"COMSPEC",
|
|
49
|
+
"WINDIR",
|
|
50
|
+
];
|
|
51
|
+
const env: Record<string, string> = {};
|
|
52
|
+
for (const key of allow) {
|
|
53
|
+
const value = Bun.env[key];
|
|
54
|
+
if (typeof value === "string") env[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return { ...env, ...explicit };
|
|
57
|
+
}
|
|
58
|
+
|
|
27
59
|
export class StdioTransport implements MCPTransport {
|
|
28
60
|
#process: OwnedProcess | null = null;
|
|
29
61
|
#pendingRequests = new Map<
|
|
@@ -63,10 +95,12 @@ export class StdioTransport implements MCPTransport {
|
|
|
63
95
|
if (this.#connected) return;
|
|
64
96
|
|
|
65
97
|
const args = this.config.args ?? [];
|
|
66
|
-
const env =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
const env = this.config.noInheritEnv
|
|
99
|
+
? buildMinimalStdioEnv(this.config.env)
|
|
100
|
+
: {
|
|
101
|
+
...Bun.env,
|
|
102
|
+
...this.config.env,
|
|
103
|
+
};
|
|
70
104
|
|
|
71
105
|
this.#process = spawnOwnedProcess([this.config.command, ...args], {
|
|
72
106
|
cwd: this.config.cwd ?? getProjectDir(),
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -81,6 +81,13 @@ export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
|
81
81
|
command: string;
|
|
82
82
|
args?: string[];
|
|
83
83
|
env?: Record<string, string>;
|
|
84
|
+
/**
|
|
85
|
+
* When true, the child process is NOT given the host environment. Only a
|
|
86
|
+
* minimal OS allowlist (PATH/HOME/temp/locale) plus any explicit `env` keys
|
|
87
|
+
* are passed. Used for third-party plugin-bundle MCP servers so they cannot
|
|
88
|
+
* read host secrets from the inherited environment.
|
|
89
|
+
*/
|
|
90
|
+
noInheritEnv?: boolean;
|
|
84
91
|
cwd?: string;
|
|
85
92
|
}
|
|
86
93
|
|
package/src/sdk.ts
CHANGED
|
@@ -73,7 +73,13 @@ import {
|
|
|
73
73
|
wrapRegisteredTools,
|
|
74
74
|
} from "./extensibility/extensions";
|
|
75
75
|
import { ExtensionRuntime } from "./extensibility/extensions/loader";
|
|
76
|
+
import { type ConstrainedPluginHook, loadConstrainedPluginHooks } from "./extensibility/skc-plugins/constrained-hooks";
|
|
76
77
|
import { resolveCurrentPhaseForParent } from "./extensibility/skc-plugins/injection";
|
|
78
|
+
import {
|
|
79
|
+
buildPluginMcpConfigs,
|
|
80
|
+
loadAlwaysOnPluginTools,
|
|
81
|
+
renderAlwaysOnSystemAppendices,
|
|
82
|
+
} from "./extensibility/skc-plugins/runtime-adapters";
|
|
77
83
|
import { loadActiveSubskillTools } from "./extensibility/skc-plugins/tools";
|
|
78
84
|
import { loadSkills, type Skill, type SkillWarning, setActiveSkills } from "./extensibility/skills";
|
|
79
85
|
import type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
@@ -748,6 +754,27 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
748
754
|
};
|
|
749
755
|
}
|
|
750
756
|
|
|
757
|
+
export function createPluginHooksExtension(hooks: ConstrainedPluginHook[]): ExtensionFactory {
|
|
758
|
+
return api => {
|
|
759
|
+
for (const hook of hooks) {
|
|
760
|
+
// Constrained plugin hooks register exactly their declared event handler
|
|
761
|
+
// through the standard extension API; the loader already denied every
|
|
762
|
+
// session-mutation/command/exec capability at load time. At execution we
|
|
763
|
+
// additionally enforce the declared `target`: a tool-scoped hook only
|
|
764
|
+
// fires for its declared tool, never for arbitrary tool events.
|
|
765
|
+
const target = hook.target;
|
|
766
|
+
const handler = target
|
|
767
|
+
? (event: { toolName?: string; tool?: { name?: string }; name?: string }, ...rest: unknown[]) => {
|
|
768
|
+
const toolName = event?.toolName ?? event?.tool?.name ?? event?.name;
|
|
769
|
+
if (toolName !== target) return undefined;
|
|
770
|
+
return (hook.handler as (...a: unknown[]) => unknown)(event, ...rest);
|
|
771
|
+
}
|
|
772
|
+
: hook.handler;
|
|
773
|
+
(api.on as (event: string, handler: (...args: unknown[]) => unknown) => void)(hook.event, handler);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
751
778
|
// Factory
|
|
752
779
|
|
|
753
780
|
/**
|
|
@@ -1233,6 +1260,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1233
1260
|
get model() {
|
|
1234
1261
|
return agent?.state.model ?? model;
|
|
1235
1262
|
},
|
|
1263
|
+
get serviceTier() {
|
|
1264
|
+
// Live parent service-tier intent (e.g. runtime `/fast on|off`), inherited
|
|
1265
|
+
// by `inherit` subagents. Only fall back to the startup tier when there is
|
|
1266
|
+
// no live agent yet — never `??`, or an intentional `/fast off`
|
|
1267
|
+
// (serviceTier === undefined) would be resurrected to the startup value.
|
|
1268
|
+
return agent ? agent.serviceTier : initialServiceTier;
|
|
1269
|
+
},
|
|
1236
1270
|
getAgentId: () => resolvedAgentId,
|
|
1237
1271
|
bashAllowedPrefixes: options.bashAllowedPrefixes,
|
|
1238
1272
|
bashRestrictionProfile: options.bashRestrictionProfile,
|
|
@@ -1327,14 +1361,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1327
1361
|
|
|
1328
1362
|
// MCP runtime discovery is quarantined for the SKC surface. Keep an
|
|
1329
1363
|
// explicitly supplied manager only for legacy in-process callers that own
|
|
1330
|
-
// lifecycle themselves; never discover project/user MCP configs here.
|
|
1331
|
-
|
|
1364
|
+
// lifecycle themselves; never discover project/user MCP configs here. The
|
|
1365
|
+
// owned manager for always-on plugin-bundle MCP servers is created further
|
|
1366
|
+
// below, after `customTools` is populated, so its tools can be surfaced as
|
|
1367
|
+
// always-on tools per the plugin product contract.
|
|
1368
|
+
let mcpManager: MCPManager | undefined = options.mcpManager;
|
|
1369
|
+
let ownsMcpManager = false;
|
|
1332
1370
|
const customTools: CustomTool[] = [];
|
|
1333
|
-
// Only top-level sessions own the global MCPManager. Subagents already
|
|
1334
|
-
// receive the parent's manager via `options.mcpManager`, and reassigning
|
|
1335
|
-
// the singleton to the same value is a no-op \u2014 keep the gate explicit
|
|
1336
|
-
// to mirror the AsyncJobManager ownership rule.
|
|
1337
|
-
if (mcpManager && !options.parentTaskPrefix) MCPManager.setInstance(mcpManager);
|
|
1338
1371
|
|
|
1339
1372
|
// Add image tools when the active model or configured image providers can generate images.
|
|
1340
1373
|
const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
|
|
@@ -1388,6 +1421,84 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1388
1421
|
}
|
|
1389
1422
|
}
|
|
1390
1423
|
|
|
1424
|
+
// Always-on SKC plugin bundle tools (validated registry surfaces). This is
|
|
1425
|
+
// additive and a no-op when no plugins are installed for the cwd. Surfaces
|
|
1426
|
+
// are hash-verified and collision-checked; declared names are authoritative.
|
|
1427
|
+
try {
|
|
1428
|
+
const pluginToolResult = await loadAlwaysOnPluginTools({
|
|
1429
|
+
cwd,
|
|
1430
|
+
reservedToolNames: [...getReservedSubskillToolNames(), ...customTools.map(tool => tool.name)],
|
|
1431
|
+
});
|
|
1432
|
+
if (pluginToolResult.tools.length > 0) customTools.push(...pluginToolResult.tools);
|
|
1433
|
+
for (const q of pluginToolResult.quarantine) {
|
|
1434
|
+
logger.warn("Quarantined SKC plugin surface", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
|
|
1435
|
+
}
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
logger.warn("Failed to load always-on SKC plugin tools", { error });
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Always-on SKC plugin-bundle MCP servers. Top-level sessions own a manager
|
|
1441
|
+
// and connect the validated servers; subagents inherit the parent's manager
|
|
1442
|
+
// via options.mcpManager and never spawn their own (prevents duplicate
|
|
1443
|
+
// processes and leaks). Per the plugin product contract, connected MCP tools
|
|
1444
|
+
// are surfaced as always-on tools rather than gated behind MCP selection.
|
|
1445
|
+
if (!mcpManager && !options.parentTaskPrefix) {
|
|
1446
|
+
try {
|
|
1447
|
+
const { configs, quarantine } = await buildPluginMcpConfigs({ cwd });
|
|
1448
|
+
for (const q of quarantine) {
|
|
1449
|
+
logger.warn("Quarantined SKC plugin MCP", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
|
|
1450
|
+
}
|
|
1451
|
+
if (Object.keys(configs).length > 0) {
|
|
1452
|
+
const owned = new MCPManager(cwd);
|
|
1453
|
+
try {
|
|
1454
|
+
const sources = Object.fromEntries(
|
|
1455
|
+
Object.keys(configs).map(name => [
|
|
1456
|
+
name,
|
|
1457
|
+
{ provider: "skc-plugins", providerName: "SKC plugin bundle", level: "project" as const },
|
|
1458
|
+
]),
|
|
1459
|
+
);
|
|
1460
|
+
const result = await owned.connectServers(configs, sources as never);
|
|
1461
|
+
for (const [server, err] of result.errors) {
|
|
1462
|
+
logger.warn("SKC plugin MCP connect failed", { path: `mcp:${server}`, error: err });
|
|
1463
|
+
}
|
|
1464
|
+
if (result.connectedServers.length > 0) {
|
|
1465
|
+
mcpManager = owned;
|
|
1466
|
+
ownsMcpManager = true;
|
|
1467
|
+
customTools.push(...(result.tools as CustomTool[]));
|
|
1468
|
+
} else {
|
|
1469
|
+
await owned.disconnectAll().catch(() => {});
|
|
1470
|
+
}
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
// Avoid leaking partially-started server processes on failure.
|
|
1473
|
+
await owned.disconnectAll().catch(() => {});
|
|
1474
|
+
throw error;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
logger.warn("Failed to wire SKC plugin MCP servers", { error });
|
|
1479
|
+
}
|
|
1480
|
+
} else if (options.parentTaskPrefix) {
|
|
1481
|
+
// Subagent: inherit the parent's always-on plugin MCP tools WITHOUT
|
|
1482
|
+
// owning the manager (no connect, no callbacks, no disposal). The
|
|
1483
|
+
// top-level session installed its manager as the process-global
|
|
1484
|
+
// instance; reading getTools() surfaces the same always-on tools so the
|
|
1485
|
+
// product decision holds for subagent sessions too.
|
|
1486
|
+
const inherited = mcpManager ?? MCPManager.instance();
|
|
1487
|
+
if (inherited) {
|
|
1488
|
+
try {
|
|
1489
|
+
const inheritedTools = inherited.getTools();
|
|
1490
|
+
if (inheritedTools.length > 0) customTools.push(...(inheritedTools as CustomTool[]));
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
logger.warn("Failed to inherit plugin MCP tools in subagent", { error });
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
// Only top-level sessions own the global MCPManager. Subagents already
|
|
1497
|
+
// receive the parent's manager via options.mcpManager; reassigning the
|
|
1498
|
+
// singleton to the same value is a no-op. Keep the gate explicit to mirror
|
|
1499
|
+
// the AsyncJobManager ownership rule.
|
|
1500
|
+
if (mcpManager && !options.parentTaskPrefix) MCPManager.setInstance(mcpManager);
|
|
1501
|
+
|
|
1391
1502
|
// Custom tool and extension discovery is quarantined from the public SKC utility surface.
|
|
1392
1503
|
// Explicit SDK extension factories are still honored; callers use them to
|
|
1393
1504
|
// register in-process tools/providers without enabling filesystem discovery.
|
|
@@ -1395,6 +1506,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1395
1506
|
if (customTools.length > 0) {
|
|
1396
1507
|
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1397
1508
|
}
|
|
1509
|
+
|
|
1510
|
+
// Always-on constrained plugin hooks (validated registry surfaces). Additive
|
|
1511
|
+
// and a no-op without installed plugins; the loader denies all dangerous APIs.
|
|
1512
|
+
try {
|
|
1513
|
+
const pluginHookResult = await loadConstrainedPluginHooks({ cwd });
|
|
1514
|
+
if (pluginHookResult.hooks.length > 0) {
|
|
1515
|
+
inlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));
|
|
1516
|
+
}
|
|
1517
|
+
for (const q of pluginHookResult.quarantine) {
|
|
1518
|
+
logger.warn("Quarantined SKC plugin hook", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
|
|
1519
|
+
}
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
logger.warn("Failed to load constrained SKC plugin hooks", { error });
|
|
1522
|
+
}
|
|
1398
1523
|
let notificationCfg: NotificationConfig | undefined;
|
|
1399
1524
|
try {
|
|
1400
1525
|
notificationCfg = getNotificationConfig(Settings.instance);
|
|
@@ -1693,6 +1818,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1693
1818
|
}
|
|
1694
1819
|
appendPrompt = parts.join("\n\n");
|
|
1695
1820
|
}
|
|
1821
|
+
let pluginSystemAppendices = "";
|
|
1822
|
+
try {
|
|
1823
|
+
pluginSystemAppendices = await renderAlwaysOnSystemAppendices({ cwd });
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
logger.warn("Failed to render SKC plugin system appendices", { error });
|
|
1826
|
+
}
|
|
1696
1827
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
1697
1828
|
cwd,
|
|
1698
1829
|
skills,
|
|
@@ -1703,6 +1834,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1703
1834
|
alwaysApplyRules,
|
|
1704
1835
|
skillsSettings: settings.getGroup("skills"),
|
|
1705
1836
|
appendSystemPrompt: appendPrompt,
|
|
1837
|
+
pluginAppendices: pluginSystemAppendices,
|
|
1706
1838
|
repeatToolDescriptions,
|
|
1707
1839
|
intentField,
|
|
1708
1840
|
mcpDiscoveryMode: false,
|
|
@@ -2059,6 +2191,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2059
2191
|
// AsyncJobManager on teardown; subagents inherit the parent's and
|
|
2060
2192
|
// **MUST NOT** tear it down.
|
|
2061
2193
|
ownedAsyncJobManager: asyncJobManager,
|
|
2194
|
+
// Only the owned plugin-bundle MCP manager is torn down on dispose;
|
|
2195
|
+
// subagents/callers that merely observe the global must not (see
|
|
2196
|
+
// AgentSession.dispose).
|
|
2197
|
+
ownedMcpManager: ownsMcpManager ? mcpManager : undefined,
|
|
2062
2198
|
scopedModels: options.scopedModels,
|
|
2063
2199
|
promptTemplates,
|
|
2064
2200
|
slashCommands,
|
|
@@ -2223,9 +2359,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2223
2359
|
// Wire MCP manager callbacks to session for reactive tool updates.
|
|
2224
2360
|
// Skip when reusing a parent's manager — the parent owns the callbacks.
|
|
2225
2361
|
if (mcpManager && !options.mcpManager) {
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2362
|
+
// The owned plugin-bundle manager surfaces its tools as always-on custom
|
|
2363
|
+
// tools (registered above), so it must NOT drive refreshMCPTools — that
|
|
2364
|
+
// path strips MCP bridge tools and re-gates them behind MCP selection,
|
|
2365
|
+
// which would deactivate the always-on plugin tools. Reactive tool
|
|
2366
|
+
// updates remain wired only for externally supplied managers.
|
|
2367
|
+
// The owned manager is disconnected by AgentSession.dispose via
|
|
2368
|
+
// ownedMcpManager; only externally supplied managers wire reactive
|
|
2369
|
+
// refreshMCPTools (the owned always-on path must not, or it would
|
|
2370
|
+
// deactivate the plugin tools).
|
|
2371
|
+
if (!ownsMcpManager) {
|
|
2372
|
+
mcpManager.setOnToolsChanged(tools => {
|
|
2373
|
+
void session.refreshMCPTools(tools);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2229
2376
|
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
2230
2377
|
mcpManager.setOnPromptsChanged(serverName => {
|
|
2231
2378
|
const promptCommands = buildMCPPromptCommands(mcpManager);
|