@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- 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.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-start the Telegram Remote gateway when `telegram.enabled` is on.
|
|
3
|
+
*
|
|
4
|
+
* Called once during skc startup (interactive / persistent modes only — not
|
|
5
|
+
* `-p` print or auto-print). Spawns the gateway as a detached background
|
|
6
|
+
* process, tracks its PID so repeated skc invocations don't double-spawn,
|
|
7
|
+
* and redirects output to a log file under the config root.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { getConfigRootDir, isCompiledBinary, logger } from "@sayknow-cli/utils";
|
|
13
|
+
import { settings } from "./settings";
|
|
14
|
+
import type { TelegramSettings } from "./settings-schema";
|
|
15
|
+
import { telegramSettingsToEnv, validateTelegramSettings } from "./telegram-env-bridge";
|
|
16
|
+
|
|
17
|
+
function readTelegramSettings(): TelegramSettings {
|
|
18
|
+
return {
|
|
19
|
+
enabled: settings.get("telegram.enabled"),
|
|
20
|
+
botToken: settings.get("telegram.botToken"),
|
|
21
|
+
allowedUserIds: settings.get("telegram.allowedUserIds"),
|
|
22
|
+
allowedChatIds: settings.get("telegram.allowedChatIds"),
|
|
23
|
+
backend: settings.get("telegram.backend"),
|
|
24
|
+
enableStop: settings.get("telegram.enableStop"),
|
|
25
|
+
enableRich: settings.get("telegram.enableRich"),
|
|
26
|
+
enablePush: settings.get("telegram.enablePush"),
|
|
27
|
+
registerCommands: settings.get("telegram.registerCommands"),
|
|
28
|
+
presets: settings.get("telegram.presets"),
|
|
29
|
+
rpcSocket: settings.get("telegram.rpcSocket"),
|
|
30
|
+
stateDir: settings.get("telegram.stateDir"),
|
|
31
|
+
pollTimeoutSec: settings.get("telegram.pollTimeoutSec"),
|
|
32
|
+
apiBase: settings.get("telegram.apiBase"),
|
|
33
|
+
coordinatorCommand: settings.get("telegram.coordinatorCommand") ?? "skc",
|
|
34
|
+
coordinatorArgs: settings.get("telegram.coordinatorArgs") ?? "mcp-serve,coordinator",
|
|
35
|
+
enableEditMessageText: settings.get("telegram.enableEditMessageText"),
|
|
36
|
+
richCallbackTtlMs: settings.get("telegram.richCallbackTtlMs"),
|
|
37
|
+
richCallbackMaxTokens: settings.get("telegram.richCallbackMaxTokens"),
|
|
38
|
+
defaultTaskMaxLen: settings.get("telegram.defaultTaskMaxLen"),
|
|
39
|
+
livenessMs: settings.get("telegram.livenessMs"),
|
|
40
|
+
followTtlMs: settings.get("telegram.followTtlMs"),
|
|
41
|
+
subscriptionsMax: settings.get("telegram.subscriptionsMax"),
|
|
42
|
+
longPollMs: settings.get("telegram.longPollMs"),
|
|
43
|
+
digestThreshold: settings.get("telegram.digestThreshold"),
|
|
44
|
+
allowAttachSocketArg: settings.get("telegram.allowAttachSocketArg"),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveGatewayCommand(): { cmd: string; args: string[] } {
|
|
49
|
+
if (isCompiledBinary()) {
|
|
50
|
+
return { cmd: "skc-telegram-remote", args: [] };
|
|
51
|
+
}
|
|
52
|
+
const cliPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../telegram-remote/src/cli.ts");
|
|
53
|
+
return { cmd: process.execPath, args: ["run", cliPath] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isAlreadyRunning(pidFile: string): boolean {
|
|
57
|
+
try {
|
|
58
|
+
const pid = Number.parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
59
|
+
if (!Number.isInteger(pid)) return false;
|
|
60
|
+
process.kill(pid, 0); // signal 0 — throws if the process is gone
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* If `telegram.enabled` is true and the gateway is not already running, spawn
|
|
69
|
+
* it in the background. Never throws — failures are logged as warnings so the
|
|
70
|
+
* main skc session is unaffected.
|
|
71
|
+
*/
|
|
72
|
+
export async function maybeAutostartTelegramRemote(): Promise<void> {
|
|
73
|
+
if (!settings.get("telegram.enabled")) return;
|
|
74
|
+
|
|
75
|
+
const config = readTelegramSettings();
|
|
76
|
+
const errors = validateTelegramSettings(config);
|
|
77
|
+
if (errors.length > 0) {
|
|
78
|
+
logger.warn("Telegram Remote enabled but misconfigured, skipping autostart", { errors });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const configRoot = getConfigRootDir();
|
|
83
|
+
const pidFile = path.join(configRoot, "telegram-remote.pid");
|
|
84
|
+
if (isAlreadyRunning(pidFile)) {
|
|
85
|
+
logger.info("Telegram Remote already running, skipping autostart");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const env = telegramSettingsToEnv(config);
|
|
90
|
+
const { cmd, args } = resolveGatewayCommand();
|
|
91
|
+
const logDir = path.join(configRoot, "logs");
|
|
92
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
93
|
+
const logFd = fs.openSync(path.join(logDir, "telegram-remote.log"), "a");
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
97
|
+
env: { ...process.env, ...env },
|
|
98
|
+
stdio: ["ignore", logFd, logFd],
|
|
99
|
+
detached: true,
|
|
100
|
+
});
|
|
101
|
+
proc.unref();
|
|
102
|
+
fs.writeFileSync(pidFile, String(proc.pid));
|
|
103
|
+
logger.info("Telegram Remote gateway auto-started", { pid: proc.pid });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.warn("Failed to auto-start Telegram Remote gateway", { error: String(err) });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge between skc settings (`telegram.*`) and the
|
|
3
|
+
* `@sayknow-cli/telegram-remote` gateway's environment-driven config
|
|
4
|
+
* (`loadConfigFromEnv` in packages/telegram-remote/src/config.ts).
|
|
5
|
+
*
|
|
6
|
+
* The gateway is a standalone process (`skc-telegram-remote`) that reads all
|
|
7
|
+
* configuration from `SKC_TELEGRAM_REMOTE_*` env vars. This module converts the
|
|
8
|
+
* typed settings group into those env vars so skc can spawn the gateway with a
|
|
9
|
+
* consistent, settings-page-driven configuration.
|
|
10
|
+
*/
|
|
11
|
+
import type { TelegramSettings } from "./settings-schema";
|
|
12
|
+
|
|
13
|
+
/** Convert telegram settings into the env-var map the gateway expects. */
|
|
14
|
+
export function telegramSettingsToEnv(s: TelegramSettings): Record<string, string> {
|
|
15
|
+
const env: Record<string, string> = {};
|
|
16
|
+
|
|
17
|
+
const str = (v: string | undefined, key: string): void => {
|
|
18
|
+
if (v !== undefined && v !== "") env[key] = v;
|
|
19
|
+
};
|
|
20
|
+
const num = (v: number, key: string): void => {
|
|
21
|
+
env[key] = String(v);
|
|
22
|
+
};
|
|
23
|
+
const bool = (v: boolean, key: string): void => {
|
|
24
|
+
env[key] = v ? "true" : "false";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Required
|
|
28
|
+
str(s.botToken, "SKC_TELEGRAM_REMOTE_BOT_TOKEN");
|
|
29
|
+
|
|
30
|
+
// Allowlists
|
|
31
|
+
str(s.allowedUserIds, "SKC_TELEGRAM_REMOTE_ALLOWED_USER_IDS");
|
|
32
|
+
str(s.allowedChatIds, "SKC_TELEGRAM_REMOTE_ALLOWED_CHAT_IDS");
|
|
33
|
+
|
|
34
|
+
// Presets
|
|
35
|
+
str(s.presets, "SKC_TELEGRAM_REMOTE_PRESETS");
|
|
36
|
+
|
|
37
|
+
// Toggles
|
|
38
|
+
bool(s.enableStop, "SKC_TELEGRAM_REMOTE_ENABLE_STOP");
|
|
39
|
+
bool(s.enableRich, "SKC_TELEGRAM_REMOTE_ENABLE_RICH");
|
|
40
|
+
bool(s.enablePush, "SKC_TELEGRAM_REMOTE_ENABLE_PUSH");
|
|
41
|
+
bool(s.registerCommands, "SKC_TELEGRAM_REMOTE_REGISTER_COMMANDS");
|
|
42
|
+
bool(s.enableEditMessageText, "SKC_TELEGRAM_REMOTE_ENABLE_EDIT_MESSAGE_TEXT");
|
|
43
|
+
bool(s.allowAttachSocketArg, "SKC_TELEGRAM_REMOTE_ALLOW_ATTACH_SOCKET_ARG");
|
|
44
|
+
|
|
45
|
+
// Backend + RPC
|
|
46
|
+
str(s.backend, "SKC_TELEGRAM_REMOTE_BACKEND");
|
|
47
|
+
str(s.rpcSocket, "SKC_TELEGRAM_REMOTE_RPC_SOCKET");
|
|
48
|
+
str(s.stateDir, "SKC_TELEGRAM_REMOTE_STATE_DIR");
|
|
49
|
+
num(s.livenessMs, "SKC_TELEGRAM_REMOTE_LIVENESS_MS");
|
|
50
|
+
|
|
51
|
+
// Transport
|
|
52
|
+
str(s.apiBase, "SKC_TELEGRAM_REMOTE_API_BASE");
|
|
53
|
+
num(s.pollTimeoutSec, "SKC_TELEGRAM_REMOTE_POLL_TIMEOUT_SEC");
|
|
54
|
+
num(s.longPollMs, "SKC_TELEGRAM_REMOTE_WATCH_TIMEOUT_MS");
|
|
55
|
+
|
|
56
|
+
// Rich callback tuning
|
|
57
|
+
num(s.richCallbackTtlMs, "SKC_TELEGRAM_REMOTE_RICH_CALLBACK_TTL_MS");
|
|
58
|
+
num(s.richCallbackMaxTokens, "SKC_TELEGRAM_REMOTE_RICH_CALLBACK_MAX_TOKENS");
|
|
59
|
+
|
|
60
|
+
// Push / subscriptions
|
|
61
|
+
num(s.followTtlMs, "SKC_TELEGRAM_REMOTE_FOLLOW_TTL_MS");
|
|
62
|
+
num(s.subscriptionsMax, "SKC_TELEGRAM_REMOTE_SUBSCRIPTIONS_MAX");
|
|
63
|
+
num(s.digestThreshold, "SKC_TELEGRAM_REMOTE_DIGEST_THRESHOLD");
|
|
64
|
+
|
|
65
|
+
// Coordinator subprocess
|
|
66
|
+
str(s.coordinatorCommand, "SKC_TELEGRAM_REMOTE_COORDINATOR_COMMAND");
|
|
67
|
+
str(s.coordinatorArgs, "SKC_TELEGRAM_REMOTE_COORDINATOR_ARGS");
|
|
68
|
+
|
|
69
|
+
// Preset task cap
|
|
70
|
+
num(s.defaultTaskMaxLen, "SKC_TELEGRAM_REMOTE_DEFAULT_TASK_MAX_LEN");
|
|
71
|
+
|
|
72
|
+
return env;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate telegram settings before spawning the gateway.
|
|
77
|
+
* Returns a list of human-readable error strings (empty when valid).
|
|
78
|
+
*/
|
|
79
|
+
export function validateTelegramSettings(s: TelegramSettings): string[] {
|
|
80
|
+
const errors: string[] = [];
|
|
81
|
+
|
|
82
|
+
if (!s.botToken) errors.push("Bot Token is required");
|
|
83
|
+
if (!s.allowedUserIds && !s.allowedChatIds) {
|
|
84
|
+
errors.push("At least one of Allowed User IDs or Allowed Chat IDs is required");
|
|
85
|
+
}
|
|
86
|
+
if (s.backend === "rpc") {
|
|
87
|
+
if (!s.rpcSocket) errors.push("RPC Socket is required for the RPC backend");
|
|
88
|
+
if (!s.stateDir) errors.push("State Directory is required for the RPC backend");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return errors;
|
|
92
|
+
}
|
|
@@ -119,7 +119,7 @@ interface TurnRecord {
|
|
|
119
119
|
target: string | null;
|
|
120
120
|
tmux_keys_sent?: boolean;
|
|
121
121
|
prompt_acknowledged?: boolean;
|
|
122
|
-
state?: "queued" | "tmux_keys_sent" | "acknowledged" | "unavailable";
|
|
122
|
+
state?: "queued" | "tmux_keys_sent" | "acknowledged" | "unavailable" | "unacknowledged";
|
|
123
123
|
attempts: Array<{
|
|
124
124
|
delivered: boolean;
|
|
125
125
|
created_at: string;
|
|
@@ -214,6 +214,9 @@ interface CoordinatorEventInput {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
|
|
217
|
+
const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
|
|
218
|
+
const DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 10_000;
|
|
219
|
+
const MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
217
220
|
const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
|
|
218
221
|
const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
|
|
219
222
|
const TURN_ID_PATTERN = /^turn-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -317,8 +320,14 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
317
320
|
properties: {
|
|
318
321
|
session_id: sessionId,
|
|
319
322
|
turn_id: { type: "string" },
|
|
320
|
-
timeout_ms: {
|
|
321
|
-
|
|
323
|
+
timeout_ms: {
|
|
324
|
+
type: "number",
|
|
325
|
+
description: "Bounded await timeout in milliseconds, capped at 30 minutes.",
|
|
326
|
+
},
|
|
327
|
+
poll_interval_ms: {
|
|
328
|
+
type: "number",
|
|
329
|
+
description: "Bounded polling interval in milliseconds, capped at 10 seconds.",
|
|
330
|
+
},
|
|
322
331
|
lines: { type: "number" },
|
|
323
332
|
},
|
|
324
333
|
required: ["turn_id"],
|
|
@@ -406,7 +415,10 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
406
415
|
after_seq: { type: "number" },
|
|
407
416
|
session_id: sessionId,
|
|
408
417
|
event_types: { type: "array", items: { type: "string" } },
|
|
409
|
-
timeout_ms: {
|
|
418
|
+
timeout_ms: {
|
|
419
|
+
type: "number",
|
|
420
|
+
description: "Bounded event long-poll timeout in milliseconds, capped at 30 seconds.",
|
|
421
|
+
},
|
|
410
422
|
limit: { type: "number" },
|
|
411
423
|
},
|
|
412
424
|
},
|
|
@@ -447,7 +459,8 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
447
459
|
await_completion: { type: "boolean", description: "If true, poll the turn until terminal or timeout." },
|
|
448
460
|
timeout_ms: {
|
|
449
461
|
type: "number",
|
|
450
|
-
description:
|
|
462
|
+
description:
|
|
463
|
+
"Bounded await timeout in milliseconds, capped at 30 minutes like skc_coordinator_await_turn.",
|
|
451
464
|
},
|
|
452
465
|
poll_interval_ms: { type: "number", description: "Bounded await polling interval." },
|
|
453
466
|
lines: { type: "number", description: "Bounded advisory tail lines returned with await/read payloads." },
|
|
@@ -1039,6 +1052,135 @@ async function markTurnTerminalFromSessionState(
|
|
|
1039
1052
|
return resolved;
|
|
1040
1053
|
}
|
|
1041
1054
|
|
|
1055
|
+
function runtimeStateAcknowledgesTurn(turn: TurnRecord, sessionState: CoordinatorSessionState | null): boolean {
|
|
1056
|
+
return (
|
|
1057
|
+
sessionState?.source === "agent_session_event" &&
|
|
1058
|
+
sessionState.current_turn_id === turn.turn_id &&
|
|
1059
|
+
(sessionState.state === "running" ||
|
|
1060
|
+
sessionState.state === "needs_user_input" ||
|
|
1061
|
+
sessionState.state === "completed" ||
|
|
1062
|
+
sessionState.state === "errored")
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async function markTurnAcknowledgedFromRuntimeState(
|
|
1067
|
+
namespaceDir: string,
|
|
1068
|
+
turn: TurnRecord,
|
|
1069
|
+
sessionState: CoordinatorSessionState,
|
|
1070
|
+
): Promise<TurnRecord> {
|
|
1071
|
+
if (turn.delivery.prompt_acknowledged === true && turn.delivery.state === "acknowledged") return turn;
|
|
1072
|
+
const timestamp = new Date().toISOString();
|
|
1073
|
+
const acknowledged: TurnRecord = {
|
|
1074
|
+
...turn,
|
|
1075
|
+
delivery: {
|
|
1076
|
+
...turn.delivery,
|
|
1077
|
+
delivered: true,
|
|
1078
|
+
prompt_acknowledged: true,
|
|
1079
|
+
state: "acknowledged",
|
|
1080
|
+
attempts: [
|
|
1081
|
+
...turn.delivery.attempts,
|
|
1082
|
+
{
|
|
1083
|
+
delivered: true,
|
|
1084
|
+
created_at: sessionState.updated_at,
|
|
1085
|
+
reason: "runtime_prompt_acknowledged",
|
|
1086
|
+
channel: "runtime_ack",
|
|
1087
|
+
tmux_keys_sent: turn.delivery.tmux_keys_sent,
|
|
1088
|
+
},
|
|
1089
|
+
],
|
|
1090
|
+
},
|
|
1091
|
+
updated_at: timestamp,
|
|
1092
|
+
};
|
|
1093
|
+
await writeTurnRecord(namespaceDir, acknowledged);
|
|
1094
|
+
await writeActiveTurn(namespaceDir, acknowledged);
|
|
1095
|
+
return acknowledged;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function turnAwaitingRuntimeAckExpired(turn: TurnRecord, nowMs: number, ackTimeoutMs: number): boolean {
|
|
1099
|
+
if (!ACTIVE_TURN_STATUSES.has(turn.status)) return false;
|
|
1100
|
+
if (turn.delivery.tmux_keys_sent !== true) return false;
|
|
1101
|
+
if (turn.delivery.prompt_acknowledged === true) return false;
|
|
1102
|
+
if (turn.delivery.state !== "tmux_keys_sent") return false;
|
|
1103
|
+
const deliveredAt =
|
|
1104
|
+
turn.delivery.attempts.findLast(attempt => attempt.channel === "tmux_keys")?.created_at ?? turn.updated_at;
|
|
1105
|
+
const deliveredMs = Date.parse(deliveredAt);
|
|
1106
|
+
return Number.isFinite(deliveredMs) && nowMs - deliveredMs >= ackTimeoutMs;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async function markTurnFailedForUnacknowledgedDelivery(
|
|
1110
|
+
namespaceDir: string,
|
|
1111
|
+
turn: TurnRecord,
|
|
1112
|
+
ackTimeoutMs: number,
|
|
1113
|
+
): Promise<TurnRecord> {
|
|
1114
|
+
const timestamp = new Date().toISOString();
|
|
1115
|
+
const message = `Tmux key delivery succeeded, but the SKC runtime did not acknowledge the prompt or emit turn_start within ${ackTimeoutMs}ms. The turn never started; stop waiting and inspect/retry the coordinator session.`;
|
|
1116
|
+
const failed: TurnRecord = {
|
|
1117
|
+
...turn,
|
|
1118
|
+
status: "failed",
|
|
1119
|
+
delivery: {
|
|
1120
|
+
...turn.delivery,
|
|
1121
|
+
delivered: false,
|
|
1122
|
+
queued: false,
|
|
1123
|
+
prompt_acknowledged: false,
|
|
1124
|
+
state: "unacknowledged",
|
|
1125
|
+
attempts: [
|
|
1126
|
+
...turn.delivery.attempts,
|
|
1127
|
+
{
|
|
1128
|
+
delivered: false,
|
|
1129
|
+
created_at: timestamp,
|
|
1130
|
+
reason: PROMPT_ACK_TIMEOUT_REASON,
|
|
1131
|
+
channel: "runtime_ack",
|
|
1132
|
+
tmux_keys_sent: true,
|
|
1133
|
+
},
|
|
1134
|
+
],
|
|
1135
|
+
},
|
|
1136
|
+
final_response: {
|
|
1137
|
+
text: message,
|
|
1138
|
+
format: "markdown",
|
|
1139
|
+
source: "coordinator_delivery_ack_timeout",
|
|
1140
|
+
artifact_path: null,
|
|
1141
|
+
truncated: false,
|
|
1142
|
+
},
|
|
1143
|
+
error: { code: PROMPT_ACK_TIMEOUT_REASON, message, recoverable: true },
|
|
1144
|
+
evidence: [
|
|
1145
|
+
...turn.evidence,
|
|
1146
|
+
{
|
|
1147
|
+
type: PROMPT_ACK_TIMEOUT_REASON,
|
|
1148
|
+
message,
|
|
1149
|
+
tmux_keys_sent: true,
|
|
1150
|
+
prompt_acknowledged: false,
|
|
1151
|
+
created_at: timestamp,
|
|
1152
|
+
},
|
|
1153
|
+
],
|
|
1154
|
+
liveness: { checked_at: timestamp, live: turn.liveness.live, reason: PROMPT_ACK_TIMEOUT_REASON },
|
|
1155
|
+
updated_at: timestamp,
|
|
1156
|
+
completed_at: timestamp,
|
|
1157
|
+
};
|
|
1158
|
+
await writeTurnRecord(namespaceDir, failed);
|
|
1159
|
+
await clearActiveTurn(namespaceDir, failed);
|
|
1160
|
+
await writeSessionState(namespaceDir, failed.session_id, "stale", {
|
|
1161
|
+
lastTurnId: failed.turn_id,
|
|
1162
|
+
live: failed.liveness.live,
|
|
1163
|
+
reason: PROMPT_ACK_TIMEOUT_REASON,
|
|
1164
|
+
});
|
|
1165
|
+
return failed;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function reconcileRuntimeAcknowledgement(
|
|
1169
|
+
namespaceDir: string,
|
|
1170
|
+
turn: TurnRecord,
|
|
1171
|
+
sessionState: CoordinatorSessionState | null,
|
|
1172
|
+
ackTimeoutMs: number,
|
|
1173
|
+
options: { failOnTimeout: boolean } = { failOnTimeout: true },
|
|
1174
|
+
): Promise<TurnRecord> {
|
|
1175
|
+
if (sessionState && runtimeStateAcknowledgesTurn(turn, sessionState)) {
|
|
1176
|
+
return await markTurnAcknowledgedFromRuntimeState(namespaceDir, turn, sessionState);
|
|
1177
|
+
}
|
|
1178
|
+
if (options.failOnTimeout && turnAwaitingRuntimeAckExpired(turn, Date.now(), ackTimeoutMs)) {
|
|
1179
|
+
return await markTurnFailedForUnacknowledgedDelivery(namespaceDir, turn, ackTimeoutMs);
|
|
1180
|
+
}
|
|
1181
|
+
return turn;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1042
1184
|
function shellQuote(value: string): string {
|
|
1043
1185
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
1044
1186
|
}
|
|
@@ -1086,16 +1228,33 @@ function asTerminalTurnStatus(status: unknown): TurnStatus | null {
|
|
|
1086
1228
|
return null;
|
|
1087
1229
|
}
|
|
1088
1230
|
|
|
1089
|
-
|
|
1231
|
+
export const COORDINATOR_AWAIT_TURN_TIMEOUT_MAX_MS = 30 * 60 * 1000;
|
|
1232
|
+
export const COORDINATOR_RUNTIME_PROMPT_ACK_TIMEOUT_MAX_MS = MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS;
|
|
1233
|
+
export const COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS = 30_000;
|
|
1234
|
+
export const COORDINATOR_POLL_INTERVAL_MAX_MS = 10_000;
|
|
1235
|
+
|
|
1236
|
+
function parsePositiveIntegerMs(value: unknown, fallback: number): number {
|
|
1090
1237
|
const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
|
|
1091
|
-
|
|
1092
|
-
return Math.min(parsed, 30_000);
|
|
1238
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
1093
1239
|
}
|
|
1094
1240
|
|
|
1095
|
-
function
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1241
|
+
export function boundedAwaitTurnTimeoutMs(value: unknown): number {
|
|
1242
|
+
return Math.min(parsePositiveIntegerMs(value, 1000), COORDINATOR_AWAIT_TURN_TIMEOUT_MAX_MS);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
export function boundedRuntimePromptAckTimeoutMs(value: unknown): number {
|
|
1246
|
+
return Math.min(
|
|
1247
|
+
parsePositiveIntegerMs(value, DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS),
|
|
1248
|
+
COORDINATOR_RUNTIME_PROMPT_ACK_TIMEOUT_MAX_MS,
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
export function boundedEventWatchTimeoutMs(value: unknown): number {
|
|
1253
|
+
return Math.min(parsePositiveIntegerMs(value, 1000), COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export function boundedPollIntervalMs(value: unknown): number {
|
|
1257
|
+
return Math.min(Math.max(parsePositiveIntegerMs(value, 100), 10), COORDINATOR_POLL_INTERVAL_MAX_MS);
|
|
1099
1258
|
}
|
|
1100
1259
|
async function runCommand(command: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
1101
1260
|
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
@@ -1387,7 +1546,9 @@ export async function readCoordinatorArtifact(
|
|
|
1387
1546
|
}
|
|
1388
1547
|
|
|
1389
1548
|
export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions = {}) {
|
|
1390
|
-
const
|
|
1549
|
+
const env = options.env ?? process.env;
|
|
1550
|
+
const config = buildCoordinatorMcpConfig(env);
|
|
1551
|
+
const promptAckTimeoutMs = boundedRuntimePromptAckTimeoutMs(env.SKC_COORDINATOR_MCP_PROMPT_ACK_TIMEOUT_MS);
|
|
1391
1552
|
const services = options.services ?? {};
|
|
1392
1553
|
const namespaceDir = coordinatorNamespacePath(config);
|
|
1393
1554
|
const commandRunner = services.commandRunner ?? runCommand;
|
|
@@ -1485,14 +1646,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1485
1646
|
await writeTurnRecord(namespaceDir, activeTurn);
|
|
1486
1647
|
await writeActiveTurn(namespaceDir, activeTurn);
|
|
1487
1648
|
const sessionState = await readSessionState(namespaceDir, activeTurn.session_id);
|
|
1488
|
-
const
|
|
1489
|
-
sessionState
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1649
|
+
const runtimeStateAlreadyAcknowledged =
|
|
1650
|
+
sessionState !== null && runtimeStateAcknowledgesTurn(activeTurn, sessionState);
|
|
1651
|
+
const resolvedTurn =
|
|
1652
|
+
runtimeStateAlreadyAcknowledged && sessionState
|
|
1653
|
+
? await markTurnAcknowledgedFromRuntimeState(namespaceDir, activeTurn, sessionState)
|
|
1654
|
+
: activeTurn;
|
|
1655
|
+
if (!runtimeStateAlreadyAcknowledged && !tmuxKeysSent) {
|
|
1656
|
+
await writeSessionState(namespaceDir, activeTurn.session_id, "stale", {
|
|
1493
1657
|
currentTurnId: activeTurn.turn_id,
|
|
1494
1658
|
live,
|
|
1495
|
-
reason:
|
|
1659
|
+
reason: "tmux_delivery_unavailable",
|
|
1496
1660
|
});
|
|
1497
1661
|
}
|
|
1498
1662
|
await appendCoordinatorEvent(namespaceDir, {
|
|
@@ -1505,7 +1669,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1505
1669
|
payloadRef: path.relative(namespaceDir, turnFile(namespaceDir, activeTurn.turn_id)),
|
|
1506
1670
|
metadata: { target: typeof target === "string" ? target : null, live },
|
|
1507
1671
|
});
|
|
1508
|
-
return
|
|
1672
|
+
return resolvedTurn;
|
|
1509
1673
|
}
|
|
1510
1674
|
|
|
1511
1675
|
async function promoteNextQueuedTurn(sessionId: string): Promise<TurnRecord | null> {
|
|
@@ -1533,33 +1697,67 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1533
1697
|
let resolvedTurn = turn;
|
|
1534
1698
|
let advisoryStatus: Record<string, unknown> = { live: false };
|
|
1535
1699
|
let sessionState = await readSessionState(namespaceDir, turn.session_id);
|
|
1700
|
+
resolvedTurn = await reconcileRuntimeAcknowledgement(
|
|
1701
|
+
namespaceDir,
|
|
1702
|
+
resolvedTurn,
|
|
1703
|
+
sessionState,
|
|
1704
|
+
promptAckTimeoutMs,
|
|
1705
|
+
{ failOnTimeout: false },
|
|
1706
|
+
);
|
|
1707
|
+
if (resolvedTurn !== turn) sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1536
1708
|
if (
|
|
1537
1709
|
sessionState &&
|
|
1538
|
-
ACTIVE_TURN_STATUSES.has(
|
|
1539
|
-
sessionState.current_turn_id ===
|
|
1710
|
+
ACTIVE_TURN_STATUSES.has(resolvedTurn.status) &&
|
|
1711
|
+
(sessionState.current_turn_id === resolvedTurn.turn_id ||
|
|
1712
|
+
(sessionState.state === "errored" &&
|
|
1713
|
+
sessionState.source === "agent_session_event" &&
|
|
1714
|
+
sessionState.current_turn_id == null)) &&
|
|
1540
1715
|
(sessionState.state === "completed" || sessionState.state === "errored")
|
|
1541
1716
|
) {
|
|
1542
|
-
resolvedTurn = await markTurnTerminalFromSessionState(namespaceDir,
|
|
1717
|
+
resolvedTurn = await markTurnTerminalFromSessionState(namespaceDir, resolvedTurn, sessionState);
|
|
1543
1718
|
sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1544
1719
|
} else if (
|
|
1545
1720
|
sessionState &&
|
|
1546
|
-
ACTIVE_TURN_STATUSES.has(
|
|
1547
|
-
sessionState.current_turn_id ===
|
|
1721
|
+
ACTIVE_TURN_STATUSES.has(resolvedTurn.status) &&
|
|
1722
|
+
sessionState.current_turn_id === resolvedTurn.turn_id &&
|
|
1548
1723
|
sessionState.state === "stale" &&
|
|
1549
1724
|
sessionState.reason === "tmux_delivery_unavailable" &&
|
|
1550
|
-
|
|
1725
|
+
resolvedTurn.delivery.state === "unavailable" &&
|
|
1551
1726
|
session &&
|
|
1552
1727
|
hasTmuxIdentity(session)
|
|
1553
1728
|
) {
|
|
1554
|
-
resolvedTurn = await markTurnFailedForUnavailableSession(
|
|
1729
|
+
resolvedTurn = await markTurnFailedForUnavailableSession(
|
|
1730
|
+
namespaceDir,
|
|
1731
|
+
resolvedTurn,
|
|
1732
|
+
"tmux_delivery_unavailable",
|
|
1733
|
+
);
|
|
1555
1734
|
sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1556
|
-
} else if (!session && ACTIVE_TURN_STATUSES.has(
|
|
1557
|
-
resolvedTurn = await markTurnFailedForUnavailableSession(namespaceDir,
|
|
1735
|
+
} else if (!session && ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
|
|
1736
|
+
resolvedTurn = await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "session_record_missing");
|
|
1558
1737
|
sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1559
1738
|
} else if (session) {
|
|
1560
1739
|
advisoryStatus = await inspectTmuxSession(session, boundedLineCount(lines), commandRunner);
|
|
1561
|
-
if (
|
|
1562
|
-
resolvedTurn
|
|
1740
|
+
if (
|
|
1741
|
+
ACTIVE_TURN_STATUSES.has(resolvedTurn.status) &&
|
|
1742
|
+
hasTmuxIdentity(session) &&
|
|
1743
|
+
advisoryStatus.live === false
|
|
1744
|
+
) {
|
|
1745
|
+
resolvedTurn = await markTurnFailedForUnavailableSession(
|
|
1746
|
+
namespaceDir,
|
|
1747
|
+
resolvedTurn,
|
|
1748
|
+
"tmux_session_missing",
|
|
1749
|
+
);
|
|
1750
|
+
sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
if (ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
|
|
1754
|
+
resolvedTurn = await reconcileRuntimeAcknowledgement(
|
|
1755
|
+
namespaceDir,
|
|
1756
|
+
resolvedTurn,
|
|
1757
|
+
sessionState,
|
|
1758
|
+
promptAckTimeoutMs,
|
|
1759
|
+
);
|
|
1760
|
+
if (!ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
|
|
1563
1761
|
sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1564
1762
|
}
|
|
1565
1763
|
}
|
|
@@ -1579,6 +1777,46 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1579
1777
|
};
|
|
1580
1778
|
}
|
|
1581
1779
|
|
|
1780
|
+
async function reconcileActiveTurnAcknowledgements(): Promise<void> {
|
|
1781
|
+
const turns = (await listJsonFiles(turnsDir(namespaceDir)))
|
|
1782
|
+
.map(turn => asRecord(turn) as TurnRecord | null)
|
|
1783
|
+
.filter((turn): turn is TurnRecord => turn !== null && ACTIVE_TURN_STATUSES.has(turn.status));
|
|
1784
|
+
for (const turn of turns) {
|
|
1785
|
+
let sessionState = await readSessionState(namespaceDir, turn.session_id);
|
|
1786
|
+
const resolvedTurn = await reconcileRuntimeAcknowledgement(
|
|
1787
|
+
namespaceDir,
|
|
1788
|
+
turn,
|
|
1789
|
+
sessionState,
|
|
1790
|
+
promptAckTimeoutMs,
|
|
1791
|
+
{ failOnTimeout: false },
|
|
1792
|
+
);
|
|
1793
|
+
if (!ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) continue;
|
|
1794
|
+
if (resolvedTurn !== turn) sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
|
|
1795
|
+
const session = asRecord(await readJsonFile(sessionFile(resolvedTurn.session_id)));
|
|
1796
|
+
if (
|
|
1797
|
+
sessionState &&
|
|
1798
|
+
sessionState.current_turn_id === resolvedTurn.turn_id &&
|
|
1799
|
+
sessionState.state === "stale" &&
|
|
1800
|
+
sessionState.reason === "tmux_delivery_unavailable" &&
|
|
1801
|
+
resolvedTurn.delivery.state === "unavailable" &&
|
|
1802
|
+
session &&
|
|
1803
|
+
hasTmuxIdentity(session)
|
|
1804
|
+
) {
|
|
1805
|
+
await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "tmux_delivery_unavailable");
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
if (!session) {
|
|
1809
|
+
await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "session_record_missing");
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (hasTmuxIdentity(session) && (await hasTmuxSession(session, commandRunner)) === false) {
|
|
1813
|
+
await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "tmux_session_missing");
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1816
|
+
await reconcileRuntimeAcknowledgement(namespaceDir, resolvedTurn, sessionState, promptAckTimeoutMs);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1582
1820
|
async function callTool(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
|
1583
1821
|
try {
|
|
1584
1822
|
if (name === "skc_coordinator_list_sessions") return { ok: true, sessions: await listSessions() };
|
|
@@ -1619,6 +1857,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1619
1857
|
};
|
|
1620
1858
|
}
|
|
1621
1859
|
if (name === "skc_coordinator_read_status") {
|
|
1860
|
+
await reconcileActiveTurnAcknowledgements();
|
|
1622
1861
|
const sessionId = args.session_id;
|
|
1623
1862
|
if (sessionId) {
|
|
1624
1863
|
const session = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
@@ -1651,6 +1890,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1651
1890
|
if (name === "skc_coordinator_read_artifact")
|
|
1652
1891
|
return await readCoordinatorArtifact(config, { path: args.path });
|
|
1653
1892
|
if (name === "skc_coordinator_read_coordination_status") {
|
|
1893
|
+
await reconcileActiveTurnAcknowledgements();
|
|
1654
1894
|
const sessions = jsonRecords(await listSessions());
|
|
1655
1895
|
const sessionStates = jsonRecords(await listJsonFiles(path.join(namespaceDir, "session-states")));
|
|
1656
1896
|
const turns = jsonRecords(await listJsonFiles(turnsDir(namespaceDir)));
|
|
@@ -1684,13 +1924,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1684
1924
|
};
|
|
1685
1925
|
}
|
|
1686
1926
|
if (name === "skc_coordinator_watch_events") {
|
|
1927
|
+
await reconcileActiveTurnAcknowledgements();
|
|
1687
1928
|
const limit = boundedEventLimit(args.limit);
|
|
1688
|
-
const timeoutMs =
|
|
1929
|
+
const timeoutMs = boundedEventWatchTimeoutMs(args.timeout_ms);
|
|
1689
1930
|
let events = await readCoordinatorEvents(namespaceDir);
|
|
1690
1931
|
let matched = filterCoordinatorEvents(events, args, limit);
|
|
1691
1932
|
let timedOut = false;
|
|
1692
1933
|
if (matched.length === 0 && timeoutMs > 0) {
|
|
1693
1934
|
await waitForCoordinatorEvents(namespaceDir, timeoutMs);
|
|
1935
|
+
await reconcileActiveTurnAcknowledgements();
|
|
1694
1936
|
events = await readCoordinatorEvents(namespaceDir);
|
|
1695
1937
|
matched = filterCoordinatorEvents(events, args, limit);
|
|
1696
1938
|
timedOut = matched.length === 0;
|
|
@@ -1819,7 +2061,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1819
2061
|
};
|
|
1820
2062
|
if (promptAliasIgnored) base.prompt_alias_ignored = true;
|
|
1821
2063
|
if (args.await_completion === true && !shouldQueue) {
|
|
1822
|
-
const timeoutMs =
|
|
2064
|
+
const timeoutMs = boundedAwaitTurnTimeoutMs(args.timeout_ms);
|
|
1823
2065
|
const pollIntervalMs = boundedPollIntervalMs(args.poll_interval_ms);
|
|
1824
2066
|
const deadline = Date.now() + timeoutMs;
|
|
1825
2067
|
let payload = await readTurnPayload(turn.turn_id, sessionId, args.lines);
|
|
@@ -1979,7 +2221,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
1979
2221
|
return await readTurnPayload(args.turn_id, args.session_id, args.lines);
|
|
1980
2222
|
}
|
|
1981
2223
|
if (name === "skc_coordinator_await_turn") {
|
|
1982
|
-
const timeoutMs =
|
|
2224
|
+
const timeoutMs = boundedAwaitTurnTimeoutMs(args.timeout_ms);
|
|
1983
2225
|
const pollIntervalMs = boundedPollIntervalMs(args.poll_interval_ms);
|
|
1984
2226
|
const deadline = Date.now() + timeoutMs;
|
|
1985
2227
|
let payload = await readTurnPayload(args.turn_id, args.session_id, args.lines);
|