@gajae-code/coding-agent 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/bin/gjc.js +4 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/extensibility/gjc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/gjc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +8 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +20 -1
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -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 +72 -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/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 +60 -0
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +10 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/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/index.d.ts +3 -1
- package/dist/types/utils/changelog.d.ts +1 -0
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +181 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli.ts +21 -4
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/model-profile-activation.ts +55 -7
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +3 -3
- package/src/defaults/gjc/skills/team/SKILL.md +5 -4
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/gjc-plugins/compiler.ts +351 -0
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +109 -0
- package/src/extensibility/gjc-plugins/installer.ts +434 -0
- package/src/extensibility/gjc-plugins/loader.ts +3 -1
- package/src/extensibility/gjc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/gjc-plugins/observability.ts +84 -0
- package/src/extensibility/gjc-plugins/paths.ts +1 -1
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/gjc-plugins/registry.ts +180 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/gjc-plugins/schema.ts +250 -20
- package/src/extensibility/gjc-plugins/session-validation.ts +147 -0
- package/src/extensibility/gjc-plugins/types.ts +199 -3
- package/src/extensibility/gjc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/gjc-runtime/launch-tmux.ts +61 -7
- package/src/gjc-runtime/psmux-detect.ts +239 -0
- package/src/gjc-runtime/team-runtime.ts +56 -23
- package/src/gjc-runtime/tmux-common.ts +27 -2
- package/src/gjc-runtime/tmux-sessions.ts +51 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +75 -15
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/main.ts +14 -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 +44 -11
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +50 -11
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +228 -0
- package/src/notifications/lifecycle-control-runtime.ts +400 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +433 -8
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +18 -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 +166 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +678 -7
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- 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/index.ts +3 -1
- package/src/utils/changelog.ts +8 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows psmux detection and tmux-binary resolution.
|
|
3
|
+
*
|
|
4
|
+
* Recent psmux releases (see docs/compatibility.md in the psmux repo) close
|
|
5
|
+
* the round-trip gap for set-option / show-options user options and the
|
|
6
|
+
* set-window-option profile values gjc emits, which is what unblocks the
|
|
7
|
+
* native Windows gjc --tmux path. This module detects that capability so gjc
|
|
8
|
+
* can pick psmux when tmux is missing on Windows, and so callers can decide
|
|
9
|
+
* whether to treat a given tmux binary as psmux (affecting e.g. the untagged
|
|
10
|
+
* diagnostic wording and namespace handling).
|
|
11
|
+
*
|
|
12
|
+
* The probe is intentionally lightweight: it runs a single tmux -V (or
|
|
13
|
+
* --version) once per process and caches the verdict. Cache invalidation
|
|
14
|
+
* knobs:
|
|
15
|
+
* - force: true re-probes on every call (used by tests).
|
|
16
|
+
* - GJC_PSMUX_FORCE_DETECT=1 re-probes each call.
|
|
17
|
+
* - GJC_PSMUX_DETECTION=off skips probing entirely.
|
|
18
|
+
*/
|
|
19
|
+
export declare const GJC_PSMUX_COMMAND_ENV = "GJC_PSMUX_COMMAND";
|
|
20
|
+
export declare const GJC_PSMUX_DETECTION_ENV = "GJC_PSMUX_DETECTION";
|
|
21
|
+
export declare const GJC_PSMUX_FORCE_DETECT_ENV = "GJC_PSMUX_FORCE_DETECT";
|
|
22
|
+
/** Names that psmux installs as the canonical executable / alias. */
|
|
23
|
+
export declare const PSMUX_BINARY_NAMES: readonly ["psmux", "pmux", "tmux"];
|
|
24
|
+
export type PsmuxSpawnRunner = (command: string, args: string[]) => {
|
|
25
|
+
exitCode: number | null;
|
|
26
|
+
stdout?: string;
|
|
27
|
+
stderr?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolves a tmux-class binary name (e.g. "psmux", "tmux") to an absolute
|
|
31
|
+
* filesystem path or returns null when the binary cannot be located. The
|
|
32
|
+
* default implementation uses `Bun.which`; production callers leave it
|
|
33
|
+
* alone and unit tests inject a stub via `__setBinaryResolverForTests`
|
|
34
|
+
* so the version-banner probe can be exercised hermetically.
|
|
35
|
+
*/
|
|
36
|
+
export type BinaryResolver = (candidate: string) => string | null;
|
|
37
|
+
/** @internal Test-only seam; production code never calls this. */
|
|
38
|
+
export declare function __setBinaryResolverForTests(resolver: BinaryResolver | null): void;
|
|
39
|
+
export declare function envDisabled(value: string | undefined): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Decide whether command resolves to a psmux binary by probing its version
|
|
42
|
+
* output. The result is cached per process unless force is set or
|
|
43
|
+
* GJC_PSMUX_FORCE_DETECT=1.
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectPsmux(command: string, options?: {
|
|
46
|
+
force?: boolean;
|
|
47
|
+
env?: NodeJS.ProcessEnv;
|
|
48
|
+
runner?: PsmuxSpawnRunner;
|
|
49
|
+
}): boolean;
|
|
50
|
+
export interface ResolveGjcTmuxBinaryOptions {
|
|
51
|
+
platform?: NodeJS.Platform;
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
runner?: PsmuxSpawnRunner;
|
|
54
|
+
}
|
|
55
|
+
export interface ResolvedTmuxBinary {
|
|
56
|
+
command: string;
|
|
57
|
+
isPsmux: boolean;
|
|
58
|
+
viaExplicitOverride: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the tmux command GJC should invoke. Honors the existing
|
|
62
|
+
* GJC_TMUX_COMMAND / GJC_TEAM_TMUX_COMMAND overrides; on Windows when no
|
|
63
|
+
* override is set, psmux (installed as psmux, pmux, or tmux) is picked
|
|
64
|
+
* automatically so the default gjc --tmux flow lands on a real multiplexer.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveGjcTmuxBinary(options?: ResolveGjcTmuxBinaryOptions): ResolvedTmuxBinary;
|
|
67
|
+
/** Test-only helper: drop the in-process detection cache. */
|
|
68
|
+
export declare function clearPsmuxDetectionCache(): void;
|
|
69
|
+
export interface PsmuxProbe {
|
|
70
|
+
command: string;
|
|
71
|
+
versionOutput: string;
|
|
72
|
+
isPsmux: boolean;
|
|
73
|
+
}
|
|
74
|
+
export declare function probePsmux(command: string, options?: {
|
|
75
|
+
env?: NodeJS.ProcessEnv;
|
|
76
|
+
runner?: PsmuxSpawnRunner;
|
|
77
|
+
force?: boolean;
|
|
78
|
+
}): PsmuxProbe;
|
|
@@ -281,6 +281,8 @@ export declare function persistGjcTeamModeStateSummary(snapshot: GjcTeamSnapshot
|
|
|
281
281
|
export declare function recoverGjcTeamStaleClaims(teamName: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcTeamLivenessRecoveryResult>;
|
|
282
282
|
type GjcTeamTaskMetadataInput = Partial<Pick<GjcTeamTask, "owner" | "lane" | "required_role" | "allowed_roles" | "depends_on" | "blocked_by">>;
|
|
283
283
|
export declare function resolveGjcWorkerCommand(cwd?: string, env?: NodeJS.ProcessEnv): string;
|
|
284
|
+
/** @internal Exported for unit tests. */
|
|
285
|
+
export declare function buildWorkerCommand(config: GjcTeamConfig, worker: GjcTeamWorker, platform?: NodeJS.Platform): string;
|
|
284
286
|
export type GjcWorkerCheckpointClassification = {
|
|
285
287
|
kind: "clean";
|
|
286
288
|
files: string[];
|
|
@@ -23,7 +23,26 @@ export interface TmuxCommandResult {
|
|
|
23
23
|
}
|
|
24
24
|
export type TmuxCommandRunner = (args: string[]) => TmuxCommandResult;
|
|
25
25
|
export declare function envDisabled(value: string | undefined): boolean;
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the tmux (or tmux-compatible multiplexer) command GJC should invoke.
|
|
28
|
+
*
|
|
29
|
+
* This is the shared entry point used by every GJC code path that needs to talk
|
|
30
|
+
* to a multiplexer: `gjc --tmux` planning, `gjc session ...`, `gjc team ...`,
|
|
31
|
+
* the lifecycle controller, and the harness resident owner. Routing all of
|
|
32
|
+
* them through the same resolver means a single `GJC_TMUX_COMMAND` override or
|
|
33
|
+
* a single Windows psmux / pmux detection wins for the whole process — the
|
|
34
|
+
* failure mode where `gjc --tmux` creates a psmux-backed session and then
|
|
35
|
+
* `gjc session status` fails because it queries literal `tmux` is closed off.
|
|
36
|
+
*
|
|
37
|
+
* Explicit `GJC_TMUX_COMMAND` / `GJC_TEAM_TMUX_COMMAND` overrides are honored on
|
|
38
|
+
* every platform. On native Windows without an override the resolver walks
|
|
39
|
+
* `psmux`, then `pmux`, then `tmux` and uses the first binary present on PATH.
|
|
40
|
+
* On POSIX the resolver returns `tmux` (the historical default) and only
|
|
41
|
+
* falls through to the platform-aware walker if the caller opts in.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveGjcTmuxCommand(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
|
|
44
|
+
export type { PsmuxProbe, ResolvedTmuxBinary, ResolveGjcTmuxBinaryOptions } from "./psmux-detect";
|
|
45
|
+
export { clearPsmuxDetectionCache, detectPsmux, probePsmux, resolveGjcTmuxBinary } from "./psmux-detect";
|
|
27
46
|
/**
|
|
28
47
|
* Build the exact-session target for tmux *option* commands
|
|
29
48
|
* (`show-options` / `set-option`) and `display-message -t`.
|
|
@@ -41,4 +41,22 @@ export declare function createGjcTmuxSession(env?: NodeJS.ProcessEnv): GjcTmuxSe
|
|
|
41
41
|
/** @internal */
|
|
42
42
|
export declare function readTmuxSessionTagsForGc(sessionName: string, env?: NodeJS.ProcessEnv): GjcTmuxSessionTagsForGc;
|
|
43
43
|
export declare function removeGjcTmuxSession(sessionName: string, env?: NodeJS.ProcessEnv): GjcTmuxSessionStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Force-close a GJC-managed tmux session, even if a live pane is attached.
|
|
46
|
+
*
|
|
47
|
+
* This is the lifecycle-control counterpart to {@link removeGjcTmuxSession}: it
|
|
48
|
+
* intentionally does NOT refuse live/attached panes (hard-kill is the contract),
|
|
49
|
+
* but it keeps every safety check so it can only ever kill a genuinely
|
|
50
|
+
* GJC-managed session:
|
|
51
|
+
* - re-reads the exact tmux profile immediately before kill (never a non-GJC
|
|
52
|
+
* session, even one that collides by name);
|
|
53
|
+
* - when `expectedSessionId` is given, requires the `@gjc-session-id` tag match;
|
|
54
|
+
* - when `expectedStateFile` is given, requires the `@gjc-session-state-file`
|
|
55
|
+
* tag match.
|
|
56
|
+
*
|
|
57
|
+
* Returns the prior status (for audit). Throws a tagged error otherwise:
|
|
58
|
+
* `gjc_tmux_session_not_found`, `gjc_tmux_session_not_managed`,
|
|
59
|
+
* `gjc_tmux_session_id_mismatch`, or `gjc_tmux_session_state_file_mismatch`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function forceCloseGjcTmuxSession(sessionName: string, env?: NodeJS.ProcessEnv, expectedSessionId?: string, expectedStateFile?: string): GjcTmuxSessionStatus;
|
|
44
62
|
export declare function attachGjcTmuxSession(sessionName: string, env?: NodeJS.ProcessEnv): never;
|
package/dist/types/main.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { SubmittedUserInput } from "./modes/types";
|
|
|
12
12
|
import { type CreateAgentSessionOptions, type CreateAgentSessionResult, createAgentSession, discoverAuthStorage } from "./sdk";
|
|
13
13
|
import type { AgentSession } from "./session/agent-session";
|
|
14
14
|
import type { AuthStorage } from "./session/auth-storage";
|
|
15
|
+
import { SessionManager } from "./session/session-manager";
|
|
15
16
|
export interface InteractiveModeNotify {
|
|
16
17
|
kind: "warn" | "error" | "info";
|
|
17
18
|
message: string;
|
|
@@ -48,6 +49,7 @@ export declare function applyStartupModelProfilesOrExit(args: Parameters<typeof
|
|
|
48
49
|
* tool registry and shadow the client-supplied servers (issue #1234).
|
|
49
50
|
*/
|
|
50
51
|
export declare function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSessionFactory;
|
|
52
|
+
export declare function createSessionManager(parsed: Args, cwd: string, activeSettings?: Settings): Promise<SessionManager | undefined>;
|
|
51
53
|
/**
|
|
52
54
|
* Research-mode (RLM) preset hook. Lets `gjc rlm` augment the session options
|
|
53
55
|
* (system prompt, restricted toolset, custom python tool) and assert the tool
|
|
@@ -35,9 +35,15 @@ export declare class ModelSelectorComponent extends Container {
|
|
|
35
35
|
sessionId?: string;
|
|
36
36
|
isFastForProvider?: (provider?: string) => boolean;
|
|
37
37
|
isFastForSubagentProvider?: (provider?: string) => boolean;
|
|
38
|
+
isCurrentModelFastModeActive?: () => boolean;
|
|
38
39
|
currentThinkingLevel?: ThinkingLevel;
|
|
39
40
|
activeModelProfile?: string;
|
|
40
41
|
});
|
|
42
|
+
refreshRoleAssignments(options?: {
|
|
43
|
+
currentModel?: Model;
|
|
44
|
+
currentThinkingLevel?: ThinkingLevel;
|
|
45
|
+
activeModelProfile?: string;
|
|
46
|
+
}): void;
|
|
41
47
|
handleInput(keyData: string): void;
|
|
42
48
|
getSearchInput(): Input;
|
|
43
49
|
__testSelectProfile(profileName: string, setDefault: boolean): Promise<void>;
|
|
@@ -49,10 +49,21 @@ export declare function finalizeTelegramHtml(message?: string): string | undefin
|
|
|
49
49
|
* `1. 1. …` and keeps the displayed number aligned with the button's real index.
|
|
50
50
|
*/
|
|
51
51
|
export declare function buttonLabel(label: string, index: number): string;
|
|
52
|
+
/** Numbered, escaped option list for the Telegram message body. */
|
|
53
|
+
export declare function numberedOptionList(labels: string[]): string;
|
|
54
|
+
/** Compact numeric button label; full option text belongs in the message body. */
|
|
55
|
+
export declare function choiceButtonLabel(index: number): string;
|
|
52
56
|
export interface InlineButton {
|
|
53
57
|
text: string;
|
|
54
58
|
callback_data: string;
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Lay out option callbacks as compact numeric buttons. Telegram mobile clients
|
|
62
|
+
* ellipsize long inline-keyboard labels and tall keyboards can be obscured by
|
|
63
|
+
* the composer, so the full choice text is rendered in the message body while
|
|
64
|
+
* the keyboard keeps only stable one-based tap targets.
|
|
65
|
+
*/
|
|
66
|
+
export declare function buildCompactChoiceGrid(labels: string[], callbackForIndex: (index: number) => string): InlineButton[][];
|
|
56
67
|
/**
|
|
57
68
|
* Lay out option labels as a numbered button grid. Long buttons take a
|
|
58
69
|
* full-width row; runs of short buttons are packed into rows of up to 3. The
|
|
@@ -12,12 +12,160 @@
|
|
|
12
12
|
* - `ask` (unattended/RPC): observes emitted workflow gates and resolves the real
|
|
13
13
|
* gate on a remote reply via `ctx.workflowGate`.
|
|
14
14
|
* - `turn_end` -> `action_needed` (kind `idle`, deduped per turn).
|
|
15
|
-
* - `session_shutdown` -> stop
|
|
15
|
+
* - `session_shutdown` -> `session_closed` frame, stop server, deregister answer source.
|
|
16
16
|
*
|
|
17
17
|
* Enable with Settings notifications config, `GJC_NOTIFICATIONS=1` (a token is
|
|
18
18
|
* generated), or `GJC_NOTIFICATIONS_TOKEN`.
|
|
19
19
|
*/
|
|
20
20
|
import type { ExtensionFactory } from "../extensibility/extensions";
|
|
21
|
+
/** Where a `session_create` should run. Discriminated by `kind`. */
|
|
22
|
+
export type SessionCreateTarget = {
|
|
23
|
+
kind: "existing_path";
|
|
24
|
+
path: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "worktree";
|
|
27
|
+
repo: string;
|
|
28
|
+
branch: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "plain_dir";
|
|
31
|
+
path: string;
|
|
32
|
+
};
|
|
33
|
+
/** Identifies the session a `session_close` targets. */
|
|
34
|
+
export interface SessionCloseTarget {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
/** Expected GJC-managed tmux session name (defense-in-depth match). */
|
|
37
|
+
tmuxSession?: string;
|
|
38
|
+
/** Expected `@gjc-session-state-file` tag (defense-in-depth match). */
|
|
39
|
+
sessionStateFile?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Identifies the session a `session_resume` targets. */
|
|
42
|
+
export interface SessionResumeTarget {
|
|
43
|
+
sessionIdOrPrefix: string;
|
|
44
|
+
/** Optional repo/working-dir hint to disambiguate matches. */
|
|
45
|
+
path?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Create a new session. */
|
|
48
|
+
export interface SessionCreateFrame {
|
|
49
|
+
type: "session_create";
|
|
50
|
+
requestId: string;
|
|
51
|
+
/** Deterministic lifecycle marker preallocated by the daemon before spawn. */
|
|
52
|
+
lifecycleRequestId: string;
|
|
53
|
+
/** Session id the daemon preallocated and propagates to the child. */
|
|
54
|
+
intendedSessionId: string;
|
|
55
|
+
/** Telegram update id (idempotency key on the daemon side). */
|
|
56
|
+
updateId: number;
|
|
57
|
+
chatId: string;
|
|
58
|
+
/** Control-endpoint token authorizing this frame. */
|
|
59
|
+
token: string;
|
|
60
|
+
target: SessionCreateTarget;
|
|
61
|
+
/** Reference to the daemon-written, once-consumed startup-prompt file. */
|
|
62
|
+
startupPromptRef?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Close (hard-kill, history preserved) a session. */
|
|
65
|
+
export interface SessionCloseFrame {
|
|
66
|
+
type: "session_close";
|
|
67
|
+
requestId: string;
|
|
68
|
+
updateId: number;
|
|
69
|
+
chatId: string;
|
|
70
|
+
token: string;
|
|
71
|
+
target: SessionCloseTarget;
|
|
72
|
+
/** Hard-kill even if a live pane is attached (GJC-managed only). */
|
|
73
|
+
force?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Resume a session (reattach if alive, else cold-restart from history). */
|
|
76
|
+
export interface SessionResumeFrame {
|
|
77
|
+
type: "session_resume";
|
|
78
|
+
requestId: string;
|
|
79
|
+
updateId: number;
|
|
80
|
+
chatId: string;
|
|
81
|
+
token: string;
|
|
82
|
+
target: SessionResumeTarget;
|
|
83
|
+
startupPromptRef?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Any client -> ingress lifecycle request frame. */
|
|
86
|
+
export type SessionLifecycleRequest = SessionCreateFrame | SessionCloseFrame | SessionResumeFrame;
|
|
87
|
+
/** Terminal status of a lifecycle request. */
|
|
88
|
+
export type LifecycleStatus = "ok" | "error";
|
|
89
|
+
/** A connected session's per-session endpoint, returned to the control client. */
|
|
90
|
+
export interface LifecycleEndpoint {
|
|
91
|
+
url: string;
|
|
92
|
+
token: string;
|
|
93
|
+
}
|
|
94
|
+
/** The Telegram topic/thread a session is surfaced in. */
|
|
95
|
+
export interface LifecycleTopic {
|
|
96
|
+
chatId: string;
|
|
97
|
+
threadId: string;
|
|
98
|
+
}
|
|
99
|
+
/** How a create request was correlated to its spawned session. */
|
|
100
|
+
export type MatchedBy = "spawn_marker" | "session_ready";
|
|
101
|
+
/** Response to a successful `session_create`. */
|
|
102
|
+
export interface SessionCreateResponseFrame {
|
|
103
|
+
type: "session_create_response";
|
|
104
|
+
requestId: string;
|
|
105
|
+
status: LifecycleStatus;
|
|
106
|
+
lifecycleRequestId: string;
|
|
107
|
+
sessionId: string;
|
|
108
|
+
matchedBy: MatchedBy;
|
|
109
|
+
endpoint: LifecycleEndpoint;
|
|
110
|
+
topic: LifecycleTopic;
|
|
111
|
+
target: SessionCreateTarget;
|
|
112
|
+
}
|
|
113
|
+
/** Response to a successful `session_close`. */
|
|
114
|
+
export interface SessionCloseResponseFrame {
|
|
115
|
+
type: "session_close_response";
|
|
116
|
+
requestId: string;
|
|
117
|
+
status: LifecycleStatus;
|
|
118
|
+
sessionId: string;
|
|
119
|
+
processGone: boolean;
|
|
120
|
+
historyPreserved: boolean;
|
|
121
|
+
endpointStale: boolean;
|
|
122
|
+
}
|
|
123
|
+
/** Whether a resume reattached to a live session or cold-restarted a dead one. */
|
|
124
|
+
export type ResumeMode = "reattached" | "cold_restarted";
|
|
125
|
+
/** Response to a successful `session_resume`. */
|
|
126
|
+
export interface SessionResumeResponseFrame {
|
|
127
|
+
type: "session_resume_response";
|
|
128
|
+
requestId: string;
|
|
129
|
+
status: LifecycleStatus;
|
|
130
|
+
sessionId: string;
|
|
131
|
+
mode: ResumeMode;
|
|
132
|
+
endpoint: LifecycleEndpoint;
|
|
133
|
+
topic: LifecycleTopic;
|
|
134
|
+
}
|
|
135
|
+
/** Machine-readable reason a lifecycle request failed. */
|
|
136
|
+
export type LifecycleErrorReason = "unauthorized" | "rate_limited" | "duplicate_conflict" | "invalid_target" | "ambiguous_target" | "spawn_failed" | "discovery_timeout" | "readiness_timeout" | "close_refused" | "not_found" | "terminal_uncertain";
|
|
137
|
+
/** A candidate returned with an `ambiguous_target` resume error. */
|
|
138
|
+
export interface ResumeCandidate {
|
|
139
|
+
sessionId: string;
|
|
140
|
+
path?: string;
|
|
141
|
+
/** Last-activity epoch-millis (session history file mtime), if known. */
|
|
142
|
+
mtimeMs?: number;
|
|
143
|
+
}
|
|
144
|
+
/** A structured lifecycle error frame. */
|
|
145
|
+
export interface SessionLifecycleErrorFrame {
|
|
146
|
+
type: "session_lifecycle_error";
|
|
147
|
+
requestId: string;
|
|
148
|
+
status: LifecycleStatus;
|
|
149
|
+
reason: LifecycleErrorReason;
|
|
150
|
+
message: string;
|
|
151
|
+
candidates?: ResumeCandidate[];
|
|
152
|
+
}
|
|
153
|
+
/** Any ingress -> client lifecycle response frame. */
|
|
154
|
+
export type SessionLifecycleResponse = SessionCreateResponseFrame | SessionCloseResponseFrame | SessionResumeResponseFrame | SessionLifecycleErrorFrame;
|
|
155
|
+
/**
|
|
156
|
+
* Replayable per-session readiness signal (mirror of the Rust `session_ready`
|
|
157
|
+
* frame). Buffered and replayed to late clients so WS-open alone never implies
|
|
158
|
+
* the session is live and surfaced.
|
|
159
|
+
*/
|
|
160
|
+
export interface SessionReadyFrame {
|
|
161
|
+
type: "session_ready";
|
|
162
|
+
sessionId: string;
|
|
163
|
+
lifecycleRequestId?: string;
|
|
164
|
+
startupPromptRef?: string;
|
|
165
|
+
repo?: string;
|
|
166
|
+
branch?: string;
|
|
167
|
+
title?: string;
|
|
168
|
+
}
|
|
21
169
|
/**
|
|
22
170
|
* Best-effort real repository name (no git spawn): resolves the main worktree
|
|
23
171
|
* root directory so linked worktrees report the repo (e.g. `gajae-code`)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paired-chat /session_* command grammar (G009).
|
|
3
|
+
*
|
|
4
|
+
* Pure parser + shared target validator for the Telegram session-lifecycle
|
|
5
|
+
* commands. The daemon parses an inbound paired-chat message here, then attaches
|
|
6
|
+
* transport identity (chatId/updateId/token/requestId) and routes the resulting
|
|
7
|
+
* frame to the orchestrator. Keeping this pure makes the grammar, the MVP
|
|
8
|
+
* prompt-rejection, and target validation unit-testable without the daemon.
|
|
9
|
+
*
|
|
10
|
+
* MVP scope: an initial prompt (`-- <prompt>`) is REJECTED with usage text — no
|
|
11
|
+
* prompt text ever enters a frame, audit, log, or response until daemon-owned
|
|
12
|
+
* 0600 prompt refs are designed.
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionCloseTarget, SessionCreateTarget, SessionLifecycleResponse, SessionResumeTarget } from "./index";
|
|
15
|
+
export type LifecycleCommandVerb = "session_create" | "session_close" | "session_resume";
|
|
16
|
+
/** A parsed, validated lifecycle command (transport identity added by caller). */
|
|
17
|
+
export type ParsedLifecycleCommand = {
|
|
18
|
+
kind: "create";
|
|
19
|
+
target: SessionCreateTarget;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "close";
|
|
22
|
+
target: SessionCloseTarget;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "resume";
|
|
25
|
+
target: SessionResumeTarget;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "recent";
|
|
28
|
+
which: "create" | "resume" | "all";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "usage";
|
|
31
|
+
message: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "reject";
|
|
34
|
+
reason: "invalid_target" | "prompt_unsupported";
|
|
35
|
+
message: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "none";
|
|
38
|
+
};
|
|
39
|
+
/** True when the text begins a /session_* command (cheap pre-gate). */
|
|
40
|
+
export declare function isLifecycleCommandText(text: string | undefined): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Parse a paired-chat message into a lifecycle command. Returns `none` for
|
|
43
|
+
* non-lifecycle text, `usage`/`reject` for malformed input (no side effect), or
|
|
44
|
+
* a validated `create`/`close`/`resume`/`recent` intent.
|
|
45
|
+
*
|
|
46
|
+
* The caller MUST have already enforced paired-chat authorization; this function
|
|
47
|
+
* performs grammar + target validation only.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseLifecycleCommand(text: string | undefined): ParsedLifecycleCommand;
|
|
50
|
+
/** The canonical usage text (exported for the daemon's help replies). */
|
|
51
|
+
export declare function lifecycleUsage(): string;
|
|
52
|
+
/**
|
|
53
|
+
* Shared target validator reused at the policy/effect boundary (after paired-chat
|
|
54
|
+
* auth, before any side effect). Returns null when valid, or an `invalid_target`
|
|
55
|
+
* reason. The orchestrator remains authoritative; this is a defensive pre-check
|
|
56
|
+
* the parser and any other entry point share.
|
|
57
|
+
*/
|
|
58
|
+
export declare function validateLifecycleTarget(verb: LifecycleCommandVerb, target: SessionCreateTarget | SessionCloseTarget | SessionResumeTarget): {
|
|
59
|
+
ok: true;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
reason: "invalid_target";
|
|
63
|
+
message: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Map a lifecycle response/error to a user-facing Telegram message (G010).
|
|
67
|
+
*
|
|
68
|
+
* Only derives text from sessionId, mode, reason, a safe message, and candidate
|
|
69
|
+
* {sessionId,path} — never a token or prompt. Each error reason gets tailored,
|
|
70
|
+
* actionable copy; an "in progress" pending response is surfaced distinctly.
|
|
71
|
+
*/
|
|
72
|
+
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 `gjc` argv for a create target (existing path / worktree / dir).
|
|
34
|
+
*
|
|
35
|
+
* The launched session id is carried via `GJC_SESSION_ID` in the child env (see
|
|
36
|
+
* {@link daemonSpawnCreate}); the root `gjc` 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` + GJC 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 (GJC-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 GJC 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>;
|