@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SessionCloseTarget, SessionCreateTarget, SessionLifecycleResponse, SessionResumeTarget } from "./index";
|
|
2
|
+
export type LifecycleCommandVerb = "session_create" | "session_close" | "session_resume";
|
|
3
|
+
/** A parsed, validated lifecycle command (transport identity added by caller). */
|
|
4
|
+
export type ParsedLifecycleCommand = {
|
|
5
|
+
kind: "create";
|
|
6
|
+
target: SessionCreateTarget;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "close";
|
|
9
|
+
target: SessionCloseTarget;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "resume";
|
|
12
|
+
target: SessionResumeTarget;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "recent";
|
|
15
|
+
which: "create" | "resume" | "all";
|
|
16
|
+
} | {
|
|
17
|
+
kind: "usage";
|
|
18
|
+
message: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "reject";
|
|
21
|
+
reason: "invalid_target" | "prompt_unsupported";
|
|
22
|
+
message: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "none";
|
|
25
|
+
};
|
|
26
|
+
/** True when the text begins a /session_* command (cheap pre-gate). */
|
|
27
|
+
export declare function isLifecycleCommandText(text: string | undefined): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Parse a paired-chat message into a lifecycle command. Returns `none` for
|
|
30
|
+
* non-lifecycle text, `usage`/`reject` for malformed input (no side effect), or
|
|
31
|
+
* a validated `create`/`close`/`resume`/`recent` intent.
|
|
32
|
+
*
|
|
33
|
+
* The caller MUST have already enforced paired-chat authorization; this function
|
|
34
|
+
* performs grammar + target validation only.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseLifecycleCommand(text: string | undefined): ParsedLifecycleCommand;
|
|
37
|
+
/** The canonical usage text (exported for the daemon's help replies). */
|
|
38
|
+
export declare function lifecycleUsage(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Shared target validator reused at the policy/effect boundary (after paired-chat
|
|
41
|
+
* auth, before any side effect). Returns null when valid, or an `invalid_target`
|
|
42
|
+
* reason. The orchestrator remains authoritative; this is a defensive pre-check
|
|
43
|
+
* the parser and any other entry point share.
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateLifecycleTarget(verb: LifecycleCommandVerb, target: SessionCreateTarget | SessionCloseTarget | SessionResumeTarget): {
|
|
46
|
+
ok: true;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: "invalid_target";
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
export declare function normalizeLifecyclePath(value: string): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Map a lifecycle response/error to a user-facing Telegram message (G010).
|
|
55
|
+
*
|
|
56
|
+
* Only derives text from sessionId, mode, reason, a safe message, and candidate
|
|
57
|
+
* {sessionId,path} — never a token or prompt. Each error reason gets tailored,
|
|
58
|
+
* actionable copy; an "in progress" pending response is surfaced distinctly.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatLifecycleOutcome(r: SessionLifecycleResponse): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ResumeCandidate, SessionCreateFrame, SessionLifecycleRequest, SessionLifecycleResponse } from "./index";
|
|
2
|
+
import { type AuditEvent, type CreateEffectResult, type LedgerStore, type LifecycleOutcome, type OrchestratorDeps, type ResumeEffectResult } from "./lifecycle-orchestrator";
|
|
3
|
+
/** Minimal view of the native control server this runtime depends on. */
|
|
4
|
+
export interface ControlServerLike {
|
|
5
|
+
onLifecycleRequest(cb: (err: Error | null, req: {
|
|
6
|
+
kind: string;
|
|
7
|
+
requestId: string;
|
|
8
|
+
payloadJson: string;
|
|
9
|
+
}) => void): void;
|
|
10
|
+
respond(responseJson: string): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A startable control server (the native NotificationControlServer, or a fake in
|
|
14
|
+
* tests). Extends {@link ControlServerLike} with the start/stop lifecycle the
|
|
15
|
+
* daemon owns.
|
|
16
|
+
*/
|
|
17
|
+
export interface LifecycleControlServer extends ControlServerLike {
|
|
18
|
+
start(): Promise<unknown>;
|
|
19
|
+
stop(): void;
|
|
20
|
+
}
|
|
21
|
+
/** Factory the daemon uses to construct a control server bound to its ownership. */
|
|
22
|
+
export type LifecycleControlServerFactory = (input: {
|
|
23
|
+
token: string;
|
|
24
|
+
ownerId: string;
|
|
25
|
+
agentDir: string;
|
|
26
|
+
}) => LifecycleControlServer;
|
|
27
|
+
/** Atomic + fsynced file-backed idempotency ledger store. */
|
|
28
|
+
export declare function fileLedgerStore(idempotencyFile: string): LedgerStore;
|
|
29
|
+
/** Append-only JSONL audit sink (0600). Never receives tokens or raw prompts. */
|
|
30
|
+
export declare function fileAudit(auditPath: string): (e: AuditEvent) => void;
|
|
31
|
+
/** Simple per-chat sliding-window create rate limiter. */
|
|
32
|
+
export declare function createRateLimiter(maxPerWindow: number, windowMs: number): (chatId: string, nowMs: number) => boolean;
|
|
33
|
+
/** Build the `skc` argv for a create target (existing path / worktree / dir).
|
|
34
|
+
*
|
|
35
|
+
* The launched session id is carried via `SKC_SESSION_ID` in the child env (see
|
|
36
|
+
* {@link daemonSpawnCreate}); the root `skc` launcher has no `--session-id`
|
|
37
|
+
* flag, so it must never appear in argv. Only flags the launch parser actually
|
|
38
|
+
* supports are emitted (`--worktree <branch>` for worktree targets). */
|
|
39
|
+
export declare function buildCreateArgv(frame: SessionCreateFrame, _ids: {
|
|
40
|
+
intendedSessionId: string;
|
|
41
|
+
startupPromptRef?: string;
|
|
42
|
+
}): {
|
|
43
|
+
cwd: string;
|
|
44
|
+
args: string[];
|
|
45
|
+
};
|
|
46
|
+
/** Real daemon-safe tmux launcher: detached `tmux new-session -d` + SKC tags. */
|
|
47
|
+
export declare function daemonSpawnCreate(env?: NodeJS.ProcessEnv): (frame: SessionCreateFrame, ids: {
|
|
48
|
+
lifecycleRequestId: string;
|
|
49
|
+
intendedSessionId: string;
|
|
50
|
+
startupPromptRef?: string;
|
|
51
|
+
}) => Promise<CreateEffectResult>;
|
|
52
|
+
/** Real force-close effect (SKC-managed only, id-matched). */
|
|
53
|
+
export declare function daemonCloseSession(env?: NodeJS.ProcessEnv): (target: {
|
|
54
|
+
sessionId: string;
|
|
55
|
+
tmuxSession?: string;
|
|
56
|
+
sessionStateFile?: string;
|
|
57
|
+
}) => Promise<{
|
|
58
|
+
processGone: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
/** Real resume effect: reattach if a live SKC session matches; else resolve the
|
|
61
|
+
* prefix against saved history and fail closed (`ambiguous`/`notFound`) before
|
|
62
|
+
* cold-restarting exactly one resolved session via the daemon-safe launcher. */
|
|
63
|
+
export declare function daemonResumeSession(env?: NodeJS.ProcessEnv, opts?: {
|
|
64
|
+
sessionsRoot?: string;
|
|
65
|
+
}): (target: {
|
|
66
|
+
sessionIdOrPrefix: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
}) => Promise<ResumeEffectResult | {
|
|
69
|
+
ambiguous: ResumeCandidate[];
|
|
70
|
+
} | {
|
|
71
|
+
notFound: true;
|
|
72
|
+
}>;
|
|
73
|
+
/** Translate an orchestrator outcome into a wire response frame. */
|
|
74
|
+
export declare function outcomeToResponse(frame: SessionLifecycleRequest, outcome: LifecycleOutcome): SessionLifecycleResponse;
|
|
75
|
+
/**
|
|
76
|
+
* Wire a control server's lifecycle requests through the orchestrator.
|
|
77
|
+
*
|
|
78
|
+
* Handlers run on a single serial queue (a promise chain): the daemon owns the
|
|
79
|
+
* one control endpoint, so serializing here makes each request's ledger
|
|
80
|
+
* read -> classify -> write atomic with respect to every other request. Two
|
|
81
|
+
* identical updates that arrive nearly simultaneously can no longer both
|
|
82
|
+
* classify as `new` and both spawn — the second sees the first's persisted
|
|
83
|
+
* `in_progress`/`success` entry and re-acks instead.
|
|
84
|
+
*/
|
|
85
|
+
export declare function attachLifecycleControl(server: ControlServerLike, deps: OrchestratorDeps): void;
|
|
86
|
+
/** Assemble real orchestrator deps for the daemon (ledger/audit under agentDir). */
|
|
87
|
+
export declare function buildOrchestratorDeps(input: {
|
|
88
|
+
pairedChatId: string;
|
|
89
|
+
agentNotificationsDir: string;
|
|
90
|
+
/** Root of saved session histories (`<agentDir>/sessions`), for resume resolution. */
|
|
91
|
+
sessionsRoot?: string;
|
|
92
|
+
env?: NodeJS.ProcessEnv;
|
|
93
|
+
}): OrchestratorDeps;
|
|
94
|
+
/**
|
|
95
|
+
* Default production factory: a real native NotificationControlServer bound to
|
|
96
|
+
* the daemon's control token, owner id, and agent dir.
|
|
97
|
+
*/
|
|
98
|
+
export declare const createNativeControlServer: LifecycleControlServerFactory;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { LifecycleErrorReason, ResumeCandidate, SessionCreateFrame, SessionLifecycleRequest } from "./index";
|
|
2
|
+
/** Durable idempotency state for a single lifecycle request. */
|
|
3
|
+
export type LedgerState = "in_progress" | "success" | "failure" | "terminal_uncertain";
|
|
4
|
+
/** One persisted idempotency entry, keyed by `chatId:updateId`. */
|
|
5
|
+
export interface LedgerEntry {
|
|
6
|
+
requestHash: string;
|
|
7
|
+
state: LedgerState;
|
|
8
|
+
requestId: string;
|
|
9
|
+
verb: SessionLifecycleRequest["type"];
|
|
10
|
+
intendedSessionId?: string;
|
|
11
|
+
startupPromptRef?: string;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
targetSummary: Record<string, unknown>;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
tmuxSession?: string;
|
|
17
|
+
sessionStateFile?: string;
|
|
18
|
+
endpointUrl?: string;
|
|
19
|
+
/** Close effect outcome: whether the tmux process is confirmed gone. */
|
|
20
|
+
processGone?: boolean;
|
|
21
|
+
reason?: LifecycleErrorReason;
|
|
22
|
+
}
|
|
23
|
+
/** The full on-disk ledger document. */
|
|
24
|
+
export interface LedgerDoc {
|
|
25
|
+
version: 1;
|
|
26
|
+
entries: Record<string, LedgerEntry>;
|
|
27
|
+
}
|
|
28
|
+
/** Persistence boundary: atomic + fsynced read/write of the ledger document. */
|
|
29
|
+
export interface LedgerStore {
|
|
30
|
+
read(): Promise<LedgerDoc>;
|
|
31
|
+
/** Write atomically (temp + fsync + rename) under a per-ledger lock. */
|
|
32
|
+
write(doc: LedgerDoc): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** One audit line. Tokens and raw prompts are NEVER included. */
|
|
35
|
+
export interface AuditEvent {
|
|
36
|
+
ts: string;
|
|
37
|
+
event: "accepted" | "rejected" | "duplicate_reack" | "rate_limited" | "spawn_started" | "recovered_in_progress" | "success" | "failure" | "terminal_uncertain";
|
|
38
|
+
chatId: string;
|
|
39
|
+
updateId: number;
|
|
40
|
+
requestId: string;
|
|
41
|
+
requestHash: string;
|
|
42
|
+
verb: SessionLifecycleRequest["type"];
|
|
43
|
+
targetSummary: Record<string, unknown>;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
tmuxSession?: string;
|
|
46
|
+
reason?: LifecycleErrorReason;
|
|
47
|
+
/** Prompt byte length only (never the prompt text). */
|
|
48
|
+
promptBytes?: number;
|
|
49
|
+
/** Prompt content hash only (never the prompt text). */
|
|
50
|
+
promptHash?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface CreateEffectResult {
|
|
53
|
+
sessionId: string;
|
|
54
|
+
tmuxSession: string;
|
|
55
|
+
sessionStateFile?: string;
|
|
56
|
+
endpointUrl: string;
|
|
57
|
+
topicThreadId: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ResumeEffectResult extends CreateEffectResult {
|
|
60
|
+
mode: "reattached" | "cold_restarted";
|
|
61
|
+
}
|
|
62
|
+
/** Injected effects + policy. Pure orchestration calls into these. */
|
|
63
|
+
export interface OrchestratorDeps {
|
|
64
|
+
/** The single paired chat id. Anything else is rejected before parsing. */
|
|
65
|
+
pairedChatId: string;
|
|
66
|
+
now: () => number;
|
|
67
|
+
store: LedgerStore;
|
|
68
|
+
audit: (event: AuditEvent) => Promise<void> | void;
|
|
69
|
+
/** Per-chat create rate limiter: returns true when allowed. */
|
|
70
|
+
allowCreate: (chatId: string, nowMs: number) => boolean;
|
|
71
|
+
/** Persist the once-consumed 0600 startup-prompt file; returns its ref. */
|
|
72
|
+
writeStartupPrompt: (requestId: string, prompt: string | undefined) => Promise<string | undefined>;
|
|
73
|
+
/** Spawn a session for a create/cold-restart. */
|
|
74
|
+
spawnCreate: (frame: SessionCreateFrame, ids: {
|
|
75
|
+
lifecycleRequestId: string;
|
|
76
|
+
intendedSessionId: string;
|
|
77
|
+
startupPromptRef?: string;
|
|
78
|
+
}) => Promise<CreateEffectResult>;
|
|
79
|
+
closeSession: (target: {
|
|
80
|
+
sessionId: string;
|
|
81
|
+
tmuxSession?: string;
|
|
82
|
+
sessionStateFile?: string;
|
|
83
|
+
}) => Promise<{
|
|
84
|
+
processGone: boolean;
|
|
85
|
+
}>;
|
|
86
|
+
resumeSession: (target: {
|
|
87
|
+
sessionIdOrPrefix: string;
|
|
88
|
+
path?: string;
|
|
89
|
+
}) => Promise<ResumeEffectResult | {
|
|
90
|
+
ambiguous: ResumeCandidate[];
|
|
91
|
+
} | {
|
|
92
|
+
notFound: true;
|
|
93
|
+
}>;
|
|
94
|
+
newLifecycleRequestId: () => string;
|
|
95
|
+
newSessionId: () => string;
|
|
96
|
+
}
|
|
97
|
+
/** A redaction-safe summary of a request target (never includes the token). */
|
|
98
|
+
export declare function summarizeTarget(frame: SessionLifecycleRequest): Record<string, unknown>;
|
|
99
|
+
/**
|
|
100
|
+
* Stable request hash over the meaningful (non-token) request content. Used to
|
|
101
|
+
* detect a duplicate update id reused with a DIFFERENT body (conflict).
|
|
102
|
+
*/
|
|
103
|
+
export declare function requestHash(frame: SessionLifecycleRequest): string;
|
|
104
|
+
export declare function ledgerKey(chatId: string, updateId: number): string;
|
|
105
|
+
/** How a freshly-arrived request relates to the durable ledger. */
|
|
106
|
+
export type DuplicateClass = {
|
|
107
|
+
kind: "new";
|
|
108
|
+
} | {
|
|
109
|
+
kind: "reack_success";
|
|
110
|
+
entry: LedgerEntry;
|
|
111
|
+
} | {
|
|
112
|
+
kind: "reack_failure";
|
|
113
|
+
entry: LedgerEntry;
|
|
114
|
+
} | {
|
|
115
|
+
kind: "in_progress";
|
|
116
|
+
entry: LedgerEntry;
|
|
117
|
+
} | {
|
|
118
|
+
kind: "terminal_uncertain";
|
|
119
|
+
entry: LedgerEntry;
|
|
120
|
+
} | {
|
|
121
|
+
kind: "conflict";
|
|
122
|
+
entry: LedgerEntry;
|
|
123
|
+
};
|
|
124
|
+
/** Classify a request against an existing ledger entry (pure). */
|
|
125
|
+
export declare function classifyDuplicate(existing: LedgerEntry | undefined, hash: string): DuplicateClass;
|
|
126
|
+
/** The structured outcome the daemon translates into a wire response frame. */
|
|
127
|
+
export type LifecycleOutcome = {
|
|
128
|
+
status: "ok";
|
|
129
|
+
entry: LedgerEntry;
|
|
130
|
+
mode?: "reattached" | "cold_restarted";
|
|
131
|
+
} | {
|
|
132
|
+
status: "error";
|
|
133
|
+
reason: LifecycleErrorReason;
|
|
134
|
+
message: string;
|
|
135
|
+
candidates?: ResumeCandidate[];
|
|
136
|
+
} | {
|
|
137
|
+
status: "pending";
|
|
138
|
+
entry: LedgerEntry;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Handle one authenticated lifecycle request. Enforces paired-chat gating,
|
|
142
|
+
* idempotency, and rate limiting BEFORE any side effect, then dispatches.
|
|
143
|
+
*/
|
|
144
|
+
export declare function handleLifecycleRequest(frame: SessionLifecycleRequest, deps: OrchestratorDeps): Promise<LifecycleOutcome>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Settings } from "../config/settings";
|
|
2
|
+
import { RateLimitPool } from "./rate-limit-pool";
|
|
3
|
+
import { type ThreadedSend } from "./threaded-render";
|
|
4
|
+
export interface ManagedNotificationDaemonFs {
|
|
5
|
+
readdir(path: string): Promise<string[]>;
|
|
6
|
+
}
|
|
7
|
+
export interface ManagedSessionSocket {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
token: string;
|
|
10
|
+
ws: WebSocket;
|
|
11
|
+
pending: Map<string, {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
actionId: string;
|
|
14
|
+
}>;
|
|
15
|
+
capable: boolean;
|
|
16
|
+
lastPongAt: number;
|
|
17
|
+
awaitingNonce: string | undefined;
|
|
18
|
+
pingTimer: ReturnType<typeof setInterval> | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface ManagedNotificationDaemonOptions {
|
|
21
|
+
settings: Settings;
|
|
22
|
+
fs: ManagedNotificationDaemonFs;
|
|
23
|
+
WebSocketImpl?: typeof WebSocket;
|
|
24
|
+
now?: () => number;
|
|
25
|
+
setIntervalImpl?: typeof setInterval;
|
|
26
|
+
clearIntervalImpl?: typeof clearInterval;
|
|
27
|
+
rateLimitPool?: RateLimitPool<{
|
|
28
|
+
send: ThreadedSend;
|
|
29
|
+
topicId?: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare abstract class ManagedNotificationDaemon {
|
|
33
|
+
protected readonly opts: ManagedNotificationDaemonOptions;
|
|
34
|
+
readonly sessions: Map<string, ManagedSessionSocket>;
|
|
35
|
+
readonly pool: RateLimitPool<{
|
|
36
|
+
send: ThreadedSend;
|
|
37
|
+
topicId?: string;
|
|
38
|
+
}>;
|
|
39
|
+
protected constructor(opts: ManagedNotificationDaemonOptions);
|
|
40
|
+
scanRoots(): Promise<void>;
|
|
41
|
+
connectSession(sessionId: string, url: string, token: string): ManagedSessionSocket;
|
|
42
|
+
protected handleSessionMessage(session: ManagedSessionSocket, msg: Record<string, unknown>): Promise<boolean>;
|
|
43
|
+
protected renderFrame(frame: Record<string, unknown>): ThreadedSend | undefined;
|
|
44
|
+
protected dropSession(session: ManagedSessionSocket): void;
|
|
45
|
+
protected abstract readRoots(): Promise<string[]>;
|
|
46
|
+
private sendHello;
|
|
47
|
+
private startLiveness;
|
|
48
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface NotificationOperatorTimerDeps {
|
|
2
|
+
now?: () => number;
|
|
3
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
4
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
5
|
+
setIntervalImpl?: typeof setInterval;
|
|
6
|
+
clearIntervalImpl?: typeof clearInterval;
|
|
7
|
+
}
|
|
8
|
+
export interface NotificationOperatorRuntimeState {
|
|
9
|
+
running: boolean;
|
|
10
|
+
stopRequested: boolean;
|
|
11
|
+
activeAbort: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface OperatorBackoffOptions {
|
|
14
|
+
initialMs: number;
|
|
15
|
+
maxMs: number;
|
|
16
|
+
factor?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class OperatorBackoffPolicy {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(opts: OperatorBackoffOptions);
|
|
21
|
+
next(): number;
|
|
22
|
+
reset(): void;
|
|
23
|
+
get currentMs(): number;
|
|
24
|
+
}
|
|
25
|
+
export interface OperatorRoute<TContext> {
|
|
26
|
+
name: string;
|
|
27
|
+
matches(event: Record<string, unknown>): boolean;
|
|
28
|
+
handle(context: TContext, event: Record<string, unknown>): Promise<void> | void;
|
|
29
|
+
}
|
|
30
|
+
export declare class OperatorEventRouter<TContext> {
|
|
31
|
+
readonly routes: OperatorRoute<TContext>[];
|
|
32
|
+
add(input: OperatorRoute<TContext>): this;
|
|
33
|
+
dispatch(context: TContext, event: Record<string, unknown>): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
export declare class NotificationOperatorRuntime {
|
|
36
|
+
#private;
|
|
37
|
+
constructor(deps?: NotificationOperatorTimerDeps);
|
|
38
|
+
get state(): NotificationOperatorRuntimeState;
|
|
39
|
+
start(): void;
|
|
40
|
+
stop(): void;
|
|
41
|
+
requestStop(): void;
|
|
42
|
+
get running(): boolean;
|
|
43
|
+
get stopRequested(): boolean;
|
|
44
|
+
createAbortController(): AbortController;
|
|
45
|
+
clearAbortController(controller: AbortController): void;
|
|
46
|
+
startInterval(name: string, intervalMs: number, tick: () => void): void;
|
|
47
|
+
stopInterval(name: string): void;
|
|
48
|
+
stopAllIntervals(): void;
|
|
49
|
+
runExclusive(name: string, fn: () => Promise<void>): Promise<void>;
|
|
50
|
+
sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
51
|
+
now(): number;
|
|
52
|
+
}
|
|
@@ -81,6 +81,8 @@ export declare class RateLimitPool<T = unknown> {
|
|
|
81
81
|
* single session monopolises a lane), consuming one token each.
|
|
82
82
|
*/
|
|
83
83
|
drain(nowMs?: number): RateLimitItem<T>[];
|
|
84
|
+
/** Remove queued items matching `predicate` without consuming tokens. Returns removed items in lane/FIFO order. */
|
|
85
|
+
removeWhere(predicate: (item: RateLimitItem<T>) => boolean): RateLimitItem<T>[];
|
|
84
86
|
private refill;
|
|
85
87
|
/** Pop the next item by lane priority + per-session round-robin fairness. */
|
|
86
88
|
private takeNext;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** One ranked recent-session entry surfaced to the picker. */
|
|
2
|
+
export interface RecentSessionEntry {
|
|
3
|
+
/** Session id (the `.jsonl` file stem). */
|
|
4
|
+
sessionId: string;
|
|
5
|
+
/** Working directory / repo path, when recoverable from the header. */
|
|
6
|
+
path?: string;
|
|
7
|
+
/** Branch, when recoverable from the header. */
|
|
8
|
+
branch?: string;
|
|
9
|
+
/** A short title (first user message), when recoverable. */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Absolute path of the session history (state) file. */
|
|
12
|
+
sessionStateFile: string;
|
|
13
|
+
/** Last-activity epoch-millis (history file mtime). */
|
|
14
|
+
mtimeMs: number;
|
|
15
|
+
/** True when a terminal breadcrumb points at this session file. */
|
|
16
|
+
currentTerminal?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface RecentActivityDeps {
|
|
19
|
+
/** Root holding `<encoded-cwd>/<sessionId>.jsonl` history files. */
|
|
20
|
+
sessionsRoot: string;
|
|
21
|
+
/** Optional breadcrumb session-file paths (current terminals). */
|
|
22
|
+
breadcrumbPaths?: string[];
|
|
23
|
+
/** Max entries to return (default 20). */
|
|
24
|
+
limit?: number;
|
|
25
|
+
/** Injection seam for tests. */
|
|
26
|
+
readFirstLine?: (file: string) => string | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* List recent sessions ranked by history-file mtime (newest first).
|
|
30
|
+
*
|
|
31
|
+
* Scans `<sessionsRoot>/<encoded-cwd>/<sessionId>.jsonl`, stats each file, and
|
|
32
|
+
* returns up to `limit` entries enriched with header metadata and a
|
|
33
|
+
* `currentTerminal` flag for any breadcrumb-referenced session file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listRecentSessions(deps: RecentActivityDeps): RecentSessionEntry[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import type { Settings } from "../config/settings";
|
|
3
3
|
import type { DaemonRuntimeInfo } from "../daemon/control-types";
|
|
4
|
+
import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
|
|
5
|
+
import { NotificationOperatorRuntime, OperatorBackoffPolicy } from "./operator-runtime";
|
|
4
6
|
import { type AliasTable, type CallbackRoute, type PendingAsk } from "./telegram-reference";
|
|
5
7
|
export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled";
|
|
6
8
|
export interface DaemonState {
|
|
@@ -58,6 +60,22 @@ export declare const CLIENT_PING_PONG_CAPABILITY = "client_ping_pong";
|
|
|
58
60
|
/** Protocol version the daemon advertises in its ClientHello. */
|
|
59
61
|
export declare const NOTIFICATION_PROTOCOL_VERSION = 2;
|
|
60
62
|
export declare function daemonPaths(agentDir: string): DaemonPaths;
|
|
63
|
+
/**
|
|
64
|
+
* Attach session-lifecycle control (create/close/resume) to the running daemon.
|
|
65
|
+
*
|
|
66
|
+
* Wires an already-started, authenticated control server to the lifecycle
|
|
67
|
+
* orchestrator with real daemon-side effects (tmux launcher / force-close /
|
|
68
|
+
* resume), a durable fsynced idempotency ledger + audit JSONL under the agent
|
|
69
|
+
* notifications dir, and strict paired-chat gating. The control server itself
|
|
70
|
+
* (NotificationControlServer) is owned/started by the daemon process; this
|
|
71
|
+
* function only connects it to policy. Returns the orchestrator deps for tests.
|
|
72
|
+
*/
|
|
73
|
+
export declare function startDaemonLifecycleControl(input: {
|
|
74
|
+
controlServer: ControlServerLike;
|
|
75
|
+
pairedChatId: string;
|
|
76
|
+
agentDir: string;
|
|
77
|
+
env?: NodeJS.ProcessEnv;
|
|
78
|
+
}): void;
|
|
61
79
|
export declare function registerNotificationRoot(input: {
|
|
62
80
|
settings: Settings;
|
|
63
81
|
cwd: string;
|
|
@@ -146,6 +164,40 @@ export interface BotApi {
|
|
|
146
164
|
signal?: AbortSignal;
|
|
147
165
|
}): Promise<unknown>;
|
|
148
166
|
}
|
|
167
|
+
export interface TelegramTransportOptions {
|
|
168
|
+
botToken: string;
|
|
169
|
+
apiBase?: string;
|
|
170
|
+
fetchImpl?: typeof fetch;
|
|
171
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
172
|
+
}
|
|
173
|
+
/** Telegram Bot API transport: HTTP JSON/multipart details stay out of daemon orchestration. */
|
|
174
|
+
export declare class TelegramBotTransport implements BotApi {
|
|
175
|
+
#private;
|
|
176
|
+
constructor(opts: TelegramTransportOptions);
|
|
177
|
+
call(method: string, body: unknown, opts?: {
|
|
178
|
+
signal?: AbortSignal;
|
|
179
|
+
}): Promise<unknown>;
|
|
180
|
+
}
|
|
181
|
+
export interface TelegramUpdatePollerOptions {
|
|
182
|
+
botApi: BotApi;
|
|
183
|
+
runtime: NotificationOperatorRuntime;
|
|
184
|
+
backoff: OperatorBackoffPolicy;
|
|
185
|
+
processUpdate: (update: unknown) => Promise<void>;
|
|
186
|
+
}
|
|
187
|
+
/** Owns getUpdates offset, conflict backoff, and per-update error isolation. */
|
|
188
|
+
export declare class TelegramUpdatePoller {
|
|
189
|
+
#private;
|
|
190
|
+
constructor(opts: TelegramUpdatePollerOptions);
|
|
191
|
+
pollOnce(signal?: AbortSignal): Promise<number>;
|
|
192
|
+
}
|
|
193
|
+
/** Mutable dispatch state shared by session frames and inbound Telegram updates. */
|
|
194
|
+
export declare class TelegramEventDispatchState {
|
|
195
|
+
readonly busy: Set<string>;
|
|
196
|
+
readonly inboundReactions: Map<number, {
|
|
197
|
+
messageId: number;
|
|
198
|
+
}>;
|
|
199
|
+
readonly seenUpdateIds: Set<number>;
|
|
200
|
+
}
|
|
149
201
|
/**
|
|
150
202
|
* Cooperative control seam for the daemon run loop. Implemented by the
|
|
151
203
|
* daemon-internal CLI / controller against the owner-scoped control-request
|
|
@@ -174,8 +226,17 @@ export interface TelegramDaemonOptions {
|
|
|
174
226
|
idleTimeoutMs?: number;
|
|
175
227
|
scanIntervalMs?: number;
|
|
176
228
|
pid?: number;
|
|
229
|
+
/** Liveness probe for skipping dead-PID endpoint records in {@link TelegramNotificationDaemon.scanRoots}. */
|
|
230
|
+
pidAlive?: (pid: number) => boolean;
|
|
177
231
|
botApi?: BotApi;
|
|
178
232
|
control?: DaemonControlHooks;
|
|
233
|
+
/**
|
|
234
|
+
* Factory for the session-lifecycle control server. Defaults to the real
|
|
235
|
+
* native NotificationControlServer; tests inject a fake to verify the
|
|
236
|
+
* owner-bound start/stop lifecycle without a socket. When `undefined` AND no
|
|
237
|
+
* default applies (e.g. lifecycle control disabled), no control server starts.
|
|
238
|
+
*/
|
|
239
|
+
createLifecycleControlServer?: LifecycleControlServerFactory | null;
|
|
179
240
|
}
|
|
180
241
|
interface SessionSocket {
|
|
181
242
|
sessionId: string;
|
|
@@ -199,34 +260,72 @@ export declare class TelegramNotificationDaemon {
|
|
|
199
260
|
readonly aliasTable: AliasTable;
|
|
200
261
|
readonly messageRoutes: Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>;
|
|
201
262
|
readonly sessions: Map<string, SessionSocket>;
|
|
263
|
+
private readonly runtime;
|
|
264
|
+
private readonly sessionRouter;
|
|
265
|
+
private readonly pollConflictBackoff;
|
|
266
|
+
private readonly loopBackoff;
|
|
202
267
|
private running;
|
|
203
|
-
private offset;
|
|
204
268
|
private readonly fsImpl;
|
|
205
269
|
private readonly botApi;
|
|
206
270
|
private readonly topics;
|
|
207
271
|
private readonly pool;
|
|
208
|
-
private readonly
|
|
209
|
-
private
|
|
210
|
-
|
|
211
|
-
private
|
|
212
|
-
|
|
272
|
+
private readonly poller;
|
|
273
|
+
private readonly dispatchState;
|
|
274
|
+
/** Identity-bearing sessions by repo/branch surface, used to avoid transient duplicate topics. */
|
|
275
|
+
private readonly topicOwnerByIdentity;
|
|
276
|
+
/** Non-identity frames held until identity creates the correct thread. */
|
|
277
|
+
private readonly pendingThreadedFrames;
|
|
278
|
+
/** True once the daemon has nudged the user to enable Threaded Mode. */
|
|
279
|
+
private threadedFallbackNoticeSent;
|
|
280
|
+
/** Sessions whose identity header was already sent flat (Threaded Mode off). */
|
|
281
|
+
private readonly flatIdentitySent;
|
|
282
|
+
/** Cached result of whether the paired chat is a private chat (flat-fallback gate). */
|
|
283
|
+
private pairedChatPrivate;
|
|
213
284
|
/** Sessions whose agent loop is currently busy (drives the typing indicator). */
|
|
214
|
-
private
|
|
285
|
+
private get busy();
|
|
215
286
|
/** Inbound update id → originating Telegram message, for delivery reactions. */
|
|
216
|
-
private
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
private
|
|
287
|
+
private get inboundReactions();
|
|
288
|
+
/**
|
|
289
|
+
* The owner-bound session-lifecycle control server (create/close/resume).
|
|
290
|
+
* Started in {@link run} after ownership is confirmed (so exactly one owner
|
|
291
|
+
* ever runs one), stopped in run()'s finally on any exit path.
|
|
292
|
+
*/
|
|
293
|
+
private controlServer;
|
|
294
|
+
/** True while lifecycle control is active, so the loop keeps polling at idle. */
|
|
295
|
+
private lifecycleControlActive;
|
|
296
|
+
/** Control token (in-memory) the loopback client presents; never persisted/logged. */
|
|
297
|
+
private controlToken;
|
|
298
|
+
/** Loopback WS client to the daemon's own control endpoint (Option A real wire path). */
|
|
299
|
+
private controlClient;
|
|
300
|
+
/** Pending lifecycle responses awaiting a control-endpoint reply, by requestId. */
|
|
301
|
+
private readonly pendingLifecycle;
|
|
302
|
+
/** Monotonic counter for unique lifecycle request ids. */
|
|
303
|
+
private lifecycleSeq;
|
|
223
304
|
/**
|
|
224
305
|
* Cooperatively stop the daemon: set the stop flag and abort the in-flight
|
|
225
306
|
* long poll so the run loop wakes immediately instead of waiting out the
|
|
226
307
|
* ~25s getUpdates timeout. Safe to call from a signal handler.
|
|
227
308
|
*/
|
|
228
309
|
requestStop(_reason?: "reload" | "stop" | "signal"): void;
|
|
310
|
+
private startLifecycleControl;
|
|
311
|
+
/** Stop the lifecycle control server (idempotent); called from run()'s finally. */
|
|
312
|
+
private stopLifecycleControl;
|
|
313
|
+
/**
|
|
314
|
+
* Connect the loopback control client and resolve responses by requestId.
|
|
315
|
+
* Resolves true once the socket is OPEN (bounded), false on error/timeout, so
|
|
316
|
+
* the caller only marks lifecycle control active when commands can be sent.
|
|
317
|
+
*/
|
|
318
|
+
private connectControlClient;
|
|
319
|
+
/** Send a lifecycle frame over the loopback client and await the response. */
|
|
320
|
+
private submitLifecycleFrame;
|
|
321
|
+
private nextLifecycleRequestId;
|
|
322
|
+
/** Build an authenticated lifecycle frame from a parsed command + identity. */
|
|
323
|
+
private buildLifecycleFrame;
|
|
324
|
+
private handleLifecycleCommand;
|
|
325
|
+
/** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
|
|
326
|
+
private formatLifecycleResponse;
|
|
229
327
|
constructor(opts: TelegramDaemonOptions);
|
|
328
|
+
private createSessionRouter;
|
|
230
329
|
loadAliases(): Promise<void>;
|
|
231
330
|
persistAliases(): Promise<void>;
|
|
232
331
|
scanRoots(): Promise<void>;
|
|
@@ -248,10 +347,30 @@ export declare class TelegramNotificationDaemon {
|
|
|
248
347
|
private dropSession;
|
|
249
348
|
private static readonly THREADED_FRAMES;
|
|
250
349
|
private topicNameFor;
|
|
350
|
+
private topicIdentityKey;
|
|
351
|
+
private topicIdentityBase;
|
|
352
|
+
private topicOwnerForIdentity;
|
|
353
|
+
private submitThreadedFrame;
|
|
354
|
+
private rememberPendingThreadedFrame;
|
|
355
|
+
private flushPendingThreadedFrames;
|
|
251
356
|
private ensureTopic;
|
|
357
|
+
private deleteTopic;
|
|
252
358
|
private persistTopics;
|
|
253
359
|
loadTopics(): Promise<void>;
|
|
360
|
+
private downloadTelegramFile;
|
|
361
|
+
/**
|
|
362
|
+
* Per-session private temp directories (mode 0700) holding inbound non-image
|
|
363
|
+
* attachments. Keyed by session id and reused across transient reconnects;
|
|
364
|
+
* removed when the daemon stops (see {@link cleanupAllAttachmentDirs}).
|
|
365
|
+
*/
|
|
366
|
+
private readonly attachmentDirs;
|
|
367
|
+
private ensureAttachmentDir;
|
|
368
|
+
private cleanupAllAttachmentDirs;
|
|
369
|
+
private resolveInboundAttachment;
|
|
254
370
|
private flushPool;
|
|
371
|
+
private deliverFlatFallback;
|
|
372
|
+
private pairedChatIsPrivate;
|
|
373
|
+
private notifyThreadedFallback;
|
|
255
374
|
private startFlushTimer;
|
|
256
375
|
private stopFlushTimer;
|
|
257
376
|
private runScan;
|
|
@@ -266,8 +385,6 @@ export declare class TelegramNotificationDaemon {
|
|
|
266
385
|
private sendStaleGuidance;
|
|
267
386
|
handleTelegramUpdate(update: unknown): Promise<void>;
|
|
268
387
|
pollOnce(signal?: AbortSignal): Promise<number>;
|
|
269
|
-
/** Abortable sleep honoring the injected timer; resolves early on abort. */
|
|
270
|
-
private sleep;
|
|
271
388
|
/** Sync the bot's Telegram command menu to what the daemon actually handles. */
|
|
272
389
|
registerBotCommands(): Promise<void>;
|
|
273
390
|
run(): Promise<void>;
|