@gajae-code/coding-agent 0.12.11 → 0.12.12
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 +38 -0
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +24 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
- package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -0
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +23 -5
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/update-cli.ts +90 -23
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +18 -0
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +31 -15
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +11 -10
- package/src/main.ts +203 -67
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/utils/host-status.ts +38 -0
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +16 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/index.ts +70 -0
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +24 -7
- package/src/sdk/bus/telegram-daemon.ts +1961 -497
- package/src/sdk/bus/telegram-reference.ts +42 -6
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +139 -28
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +220 -9
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +206 -7
- package/src/session/session-manager.ts +148 -38
- package/src/setup/provider-presets.json +1 -1
- package/src/task/index.ts +92 -44
- package/src/tools/index.ts +8 -0
- package/src/utils/npm-registry.ts +660 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adoption of an operator-supplied Slack thread as a live session's root.
|
|
3
|
+
*
|
|
4
|
+
* Three separate authorities have to agree before a mapping exists:
|
|
5
|
+
* 1. *Configuration*: a complete Slack target (both tokens plus workspace and
|
|
6
|
+
* channel) must already be configured; binding never accepts a target.
|
|
7
|
+
* 2. *Daemon*: only the running owner may mutate mappings, and only while it is
|
|
8
|
+
* still the exact owner (`ownerId`/`pid`/`incarnation`/daemon generation)
|
|
9
|
+
* captured before the request was published.
|
|
10
|
+
* 3. *Session*: the SDK session must be attachable right now — indexed, live,
|
|
11
|
+
* non-terminal, with a readable, non-stale endpoint whose pid matches the
|
|
12
|
+
* indexed host and whose generation has not rolled.
|
|
13
|
+
*
|
|
14
|
+
* Provider verification happens before any lock is taken, and the mapping commit
|
|
15
|
+
* re-proves session authority inside the store lock, so an authority that
|
|
16
|
+
* changes mid-flight leaves no mapping behind.
|
|
17
|
+
*/
|
|
18
|
+
import type { Settings } from "../../config/settings";
|
|
19
|
+
import type { SessionIndex } from "../broker/session-index";
|
|
20
|
+
import { type SdkSessionEndpoint, type SdkSessionEndpointScope } from "../client/discovery";
|
|
21
|
+
import { type EnsureChatDaemonResult } from "./chat-daemon-control";
|
|
22
|
+
import { ConversationStore } from "./conversation-store";
|
|
23
|
+
import { type SlackConversation } from "./slack-conversation";
|
|
24
|
+
export type SlackThreadBindingErrorCode = "invalid_root" | "target_not_configured" | "daemon_unavailable" | "daemon_owner_changed" | "session_not_live" | "root_not_found" | "provider_unavailable" | "root_conflict" | "session_conflict" | "binding_outcome_unknown" | "binding_failed";
|
|
25
|
+
/** A fail-closed rejection while adopting an operator-supplied Slack thread. */
|
|
26
|
+
export declare class SlackThreadBindingError extends Error {
|
|
27
|
+
readonly code: SlackThreadBindingErrorCode;
|
|
28
|
+
constructor(code: SlackThreadBindingErrorCode, message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare function isBoundedSlackRootTs(value: string): boolean;
|
|
31
|
+
/** Reject a non-addressable root before any authority read or persistence. */
|
|
32
|
+
export declare function assertBoundedSlackRootTs(rootTs: string): void;
|
|
33
|
+
export interface SlackSessionBindingAuthority {
|
|
34
|
+
sessionId: string;
|
|
35
|
+
endpointGeneration: number;
|
|
36
|
+
pid: number;
|
|
37
|
+
repo: string;
|
|
38
|
+
/**
|
|
39
|
+
* The exact endpoint this authority was proven against. Callers must use it
|
|
40
|
+
* rather than re-reading discovery: a second read can observe a different
|
|
41
|
+
* process whose endpoint generation happens to repeat, and the numeric
|
|
42
|
+
* generation alone cannot tell the two apart.
|
|
43
|
+
*/
|
|
44
|
+
endpoint: SdkSessionEndpoint;
|
|
45
|
+
scope: SdkSessionEndpointScope;
|
|
46
|
+
endpointMtimeMs: number;
|
|
47
|
+
}
|
|
48
|
+
export interface SessionBindingAuthorityInput {
|
|
49
|
+
sessionIndex: SessionIndex;
|
|
50
|
+
sessionId: string;
|
|
51
|
+
readEndpoint?: (repo: string, sessionId: string, scope?: SdkSessionEndpointScope) => Promise<SdkSessionEndpoint | null>;
|
|
52
|
+
statEndpoint?: (endpointPath: string) => Promise<{
|
|
53
|
+
mtimeMs: number;
|
|
54
|
+
} | undefined>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve exact discovery/attachment authority for one session.
|
|
58
|
+
*
|
|
59
|
+
* `IndexedSession.live` alone is only pid liveness, which a terminated or
|
|
60
|
+
* unregistered session can still satisfy through pid reuse or a stale record.
|
|
61
|
+
* Adoption additionally requires an intact index replay, a non-terminal record,
|
|
62
|
+
* and a discovery endpoint that is present, well-formed, not marked stale, and
|
|
63
|
+
* owned by the same host pid the index recorded.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveSessionBindingAuthority(input: SessionBindingAuthorityInput): Promise<SlackSessionBindingAuthority | undefined>;
|
|
66
|
+
export interface SlackThreadClaimInput {
|
|
67
|
+
store: ConversationStore<SlackConversation>;
|
|
68
|
+
/** The session root-claim key shared with stock root publication. */
|
|
69
|
+
key: string;
|
|
70
|
+
teamId: string;
|
|
71
|
+
channelId: string;
|
|
72
|
+
sessionId: string;
|
|
73
|
+
rootTs: string;
|
|
74
|
+
endpointGeneration: number;
|
|
75
|
+
/**
|
|
76
|
+
* Re-proves session authority inside the store lock, immediately before
|
|
77
|
+
* commit. It may be invoked more than once for one claim: authority here is
|
|
78
|
+
* monotone — a rolled endpoint generation, a replaced daemon owner tuple and
|
|
79
|
+
* a retired single-winner request claim never come back — so repeating the
|
|
80
|
+
* proof can only ever narrow the window, never widen it. It must therefore be
|
|
81
|
+
* idempotent and must never perform a remote call.
|
|
82
|
+
*/
|
|
83
|
+
revalidate: () => Promise<boolean>;
|
|
84
|
+
now?: () => number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Claim an existing Slack root for a session without publishing a replacement.
|
|
88
|
+
*
|
|
89
|
+
* The claim transacts the same `intent:<sessionId>` key that stock root
|
|
90
|
+
* publication uses, so a bind and a concurrent first notification serialize on
|
|
91
|
+
* one invariant: whichever commits first owns the session's single root, and
|
|
92
|
+
* the loser observes it instead of creating a second one.
|
|
93
|
+
*/
|
|
94
|
+
export declare function claimSlackThreadBinding(input: SlackThreadClaimInput): Promise<SlackConversation>;
|
|
95
|
+
export interface ConfiguredSlackThreadBindingInput {
|
|
96
|
+
settings: Settings;
|
|
97
|
+
sessionId: string;
|
|
98
|
+
threadTs: string;
|
|
99
|
+
}
|
|
100
|
+
export interface ConfiguredSlackThreadBindingDeps {
|
|
101
|
+
ensureDaemon?: (settings: Settings) => Promise<EnsureChatDaemonResult>;
|
|
102
|
+
timeoutMs?: number;
|
|
103
|
+
pollIntervalMs?: number;
|
|
104
|
+
/** Bounded wait for a daemon that took terminal authority before the caller gave up. */
|
|
105
|
+
settleGraceMs?: number;
|
|
106
|
+
now?: () => number;
|
|
107
|
+
sleep?: (ms: number) => Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
/** Safe confirmation of an applied binding. It carries identifiers only. */
|
|
110
|
+
export interface BoundSlackThread {
|
|
111
|
+
sessionId: string;
|
|
112
|
+
endpointGeneration: number;
|
|
113
|
+
teamId: string;
|
|
114
|
+
channelId: string;
|
|
115
|
+
rootTs: string;
|
|
116
|
+
ownerId: string;
|
|
117
|
+
daemonGeneration: number;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Adopt an existing Slack root for a live session through the running daemon.
|
|
121
|
+
*
|
|
122
|
+
* The CLI never writes the mapping store: it proves the configured target and
|
|
123
|
+
* the exact current owner, then asks that owner to perform the mutation. A
|
|
124
|
+
* daemon that is no longer the captured owner answers `owner_changed`, so a
|
|
125
|
+
* replacement or restart between capture and execution can never apply the
|
|
126
|
+
* request.
|
|
127
|
+
*
|
|
128
|
+
* A submission that is not answered is cancelled through the channel's
|
|
129
|
+
* single-winner arbitration before it is reported as a failure. If that
|
|
130
|
+
* cancellation loses to the daemon's own commit authority, the outcome is
|
|
131
|
+
* reported as unknown instead of as a failure, because a mapping may already
|
|
132
|
+
* exist; re-running the same command observes the settled state idempotently.
|
|
133
|
+
*
|
|
134
|
+
* The command channel proves *correlation*, never authorship: every field a
|
|
135
|
+
* response echoes is copied verbatim out of the plaintext request published
|
|
136
|
+
* beside it. A definitive answer is therefore reported only after this caller
|
|
137
|
+
* itself observes the exact mutation in the conversation store — see
|
|
138
|
+
* `corroborateBoundMapping`.
|
|
139
|
+
*/
|
|
140
|
+
export declare function bindConfiguredSlackThread(input: ConfiguredSlackThreadBindingInput, deps?: ConfiguredSlackThreadBindingDeps): Promise<BoundSlackThread>;
|
|
@@ -23,6 +23,8 @@ export interface RunDaemonInternalDeps {
|
|
|
23
23
|
clearInterval?: (timer: Timer) => void;
|
|
24
24
|
/** Reads persisted daemon ownership state; defaults to the real reader. */
|
|
25
25
|
readDaemonState?: (settings: Settings) => Promise<DaemonState | undefined>;
|
|
26
|
+
/** Loads the verified machine-local identity; injectable so daemon tests do not touch the host. */
|
|
27
|
+
loadInstallationHostId?: () => Promise<string>;
|
|
26
28
|
}
|
|
27
29
|
export declare function createLightweightDaemonSettings(input: {
|
|
28
30
|
agentDir: string;
|
|
@@ -53,15 +53,32 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
53
53
|
* ownership, settlement, and descendant cleanup authority. Generation 38 also
|
|
54
54
|
* adds durable provider-intent admission without changing owner, reclaim,
|
|
55
55
|
* signal, or spawn authority.
|
|
56
|
+
* Generation 41 applies first-class provider-settings admission to Telegram
|
|
57
|
+
* lifecycle controls, plus cross-host topic-registry CAS convergence, host-and-epoch
|
|
58
|
+
* archive fencing, retained topic history, user-topic adoption provenance, and
|
|
59
|
+
* exact versionless shared-state upgrades with quarantined source snapshots.
|
|
56
60
|
* Generation 42 applies first-class provider-settings admission to Telegram
|
|
57
61
|
* lifecycle controls. Generation 43 applies identity-bound exact replacement
|
|
58
|
-
* cleanup shared by managed-session and daemon filesystem authority.
|
|
62
|
+
* cleanup shared by managed-session and daemon filesystem authority. Generation
|
|
63
|
+
* 44 makes callback recovery restart-safe by revoking persisted routes and
|
|
64
|
+
* callback receipts durable before routing and binds aliases to exact asks.
|
|
65
|
+
* Generation 46 stages accepted callback activation and makes callback
|
|
66
|
+
* consumption transactional under exact pending and lease authority. Generation
|
|
67
|
+
* 47 settles failed staged revocation and guards the complete callback authority
|
|
68
|
+
* and polling dependency chain. Generation 48 uses crash-durable callback
|
|
69
|
+
* receipts, a legacy-disjoint random alias namespace, and exact topic leases.
|
|
70
|
+
* Generation 49 drains every admitted session-message handler before final
|
|
71
|
+
* durable persistence and ownership release. Generation 50 resolves intermediate
|
|
72
|
+
* notifications-directory symlinks before native exact unlink while keeping
|
|
73
|
+
* final-component file symlinks fail-closed under AT_SYMLINK_NOFOLLOW (bounded
|
|
74
|
+
* #3761 multi-account activation repair). Generation 51 adds shared durable
|
|
75
|
+
* topic authority, archive recovery, and requires Telegram's documented error
|
|
76
|
+
* code for idempotent archive settlement.
|
|
59
77
|
*/
|
|
60
|
-
export declare const DAEMON_GENERATION =
|
|
78
|
+
export declare const DAEMON_GENERATION = 51;
|
|
61
79
|
/**
|
|
62
|
-
* Serving-compatibility boundary for daemon lifecycle requests. Epoch
|
|
63
|
-
*
|
|
64
|
-
* epoch
|
|
65
|
-
* reload of compatible live predecessors.
|
|
80
|
+
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 5
|
|
81
|
+
* requires the complete generation-36 topic authority contract, so older
|
|
82
|
+
* epoch-4 daemons cannot keep serving across an upgrade.
|
|
66
83
|
*/
|
|
67
|
-
export declare const SERVING_EPOCH =
|
|
84
|
+
export declare const SERVING_EPOCH = 5;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { type NativeExactFileIdentity, type NativeExactUnlinkResult } from "@gajae-code/natives";
|
|
2
3
|
import type { Settings } from "../../config/settings";
|
|
3
4
|
import type { DaemonRuntimeInfo } from "../../daemon/control-types";
|
|
4
5
|
import { HEARTBEAT_TTL_MS } from "./daemon-paths";
|
|
@@ -10,7 +11,7 @@ import { type ControlServerLike, type LifecycleControlServerFactory } from "./li
|
|
|
10
11
|
import type { OrchestratorDeps } from "./lifecycle-orchestrator";
|
|
11
12
|
import { NotificationOperatorRuntime } from "./operator-runtime";
|
|
12
13
|
import { type AliasTable, type CallbackRoute } from "./telegram-reference";
|
|
13
|
-
import { type TopicEndpointBinding } from "./topic-registry";
|
|
14
|
+
import { type TopicEndpointBinding, type TopicRegistryCasAuthority, type TopicRegistryState } from "./topic-registry";
|
|
14
15
|
export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
|
|
15
16
|
/** Detailed result for orchestration that must distinguish a #2028 handoff from a fresh spawn. */
|
|
16
17
|
export type EnsureTelegramDaemonDetailedResult = "spawned" | "reloaded" | "attached" | "disabled" | "blocked_identity";
|
|
@@ -45,6 +46,14 @@ export interface DaemonState {
|
|
|
45
46
|
servingEpoch?: number;
|
|
46
47
|
stoppedAt?: number;
|
|
47
48
|
}
|
|
49
|
+
interface ExactFileStat {
|
|
50
|
+
dev: bigint;
|
|
51
|
+
ino: bigint;
|
|
52
|
+
nlink: bigint;
|
|
53
|
+
size: bigint;
|
|
54
|
+
mtimeNs: bigint;
|
|
55
|
+
isFile(): boolean;
|
|
56
|
+
}
|
|
48
57
|
export interface TelegramDaemonFs {
|
|
49
58
|
mkdir(path: string, opts?: fs.MakeDirectoryOptions): Promise<void>;
|
|
50
59
|
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
@@ -52,10 +61,14 @@ export interface TelegramDaemonFs {
|
|
|
52
61
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
53
62
|
unlink(path: string): Promise<void>;
|
|
54
63
|
open(path: string, flags: string, mode?: number): Promise<{
|
|
64
|
+
sync?: () => Promise<void>;
|
|
55
65
|
close(): Promise<void>;
|
|
56
66
|
}>;
|
|
57
67
|
readdir(path: string): Promise<string[]>;
|
|
58
68
|
chmod(path: string, mode: number): Promise<void>;
|
|
69
|
+
/** Crash-atomic persistence seams. Implementations without them fail closed. */
|
|
70
|
+
fsyncFile?(path: string): Promise<void>;
|
|
71
|
+
fsyncDirectory?(path: string): Promise<void>;
|
|
59
72
|
stat?(path: string): Promise<{
|
|
60
73
|
mtimeMs: number;
|
|
61
74
|
size?: number;
|
|
@@ -64,8 +77,11 @@ export interface TelegramDaemonFs {
|
|
|
64
77
|
/** Hard-link count; required to prove a staging temp has no second name. */
|
|
65
78
|
nlink?: number;
|
|
66
79
|
ctimeMs?: number;
|
|
67
|
-
isDirectory
|
|
80
|
+
isDirectory?(): boolean;
|
|
68
81
|
}>;
|
|
82
|
+
lstat?(path: string, opts: {
|
|
83
|
+
bigint: true;
|
|
84
|
+
}): Promise<ExactFileStat>;
|
|
69
85
|
readEndpointFile?(path: string): Promise<NotificationEndpointFile>;
|
|
70
86
|
exactUnlink?(path: string, identity: NotificationEndpointFileIdentity, quarantineName?: string): Promise<NotificationExactUnlinkResult>;
|
|
71
87
|
}
|
|
@@ -151,6 +167,49 @@ export declare function startDaemonLifecycleControl(input: {
|
|
|
151
167
|
auditRedactionKey: Uint8Array;
|
|
152
168
|
env?: NodeJS.ProcessEnv;
|
|
153
169
|
}): OrchestratorDeps;
|
|
170
|
+
export declare class TopicRegistryDurabilityUnavailableError extends Error {
|
|
171
|
+
readonly code = "durability_unavailable";
|
|
172
|
+
constructor(cause: unknown);
|
|
173
|
+
}
|
|
174
|
+
type TopicRegistryExactReplace = (sourcePath: string, destinationPath: string, expectedSource: NativeExactFileIdentity, expectedDestination: NativeExactFileIdentity) => NativeExactUnlinkResult;
|
|
175
|
+
/** Publish topic authority only after its staged bytes and replacement are durable. */
|
|
176
|
+
export declare function writeTopicRegistryAtomic(fsImpl: TelegramDaemonFs, file: string, data: unknown, platform?: NodeJS.Platform, exactReplace?: TopicRegistryExactReplace, expectedDestination?: NativeExactFileIdentity): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Shared-volume topic authority backed by the existing cross-process file lock.
|
|
179
|
+
* A missing authority is the only valid bootstrap state; malformed and future
|
|
180
|
+
* snapshots are never interpreted as empty state.
|
|
181
|
+
*/
|
|
182
|
+
export declare class FilesystemTopicRegistryCasAuthority implements TopicRegistryCasAuthority {
|
|
183
|
+
#private;
|
|
184
|
+
private readonly file;
|
|
185
|
+
private readonly fsImpl;
|
|
186
|
+
private readonly platform;
|
|
187
|
+
private readonly exactReplace;
|
|
188
|
+
private readonly installationHostId;
|
|
189
|
+
constructor(file: string, input: {
|
|
190
|
+
installationHostId: string;
|
|
191
|
+
fs?: TelegramDaemonFs;
|
|
192
|
+
platform?: NodeJS.Platform;
|
|
193
|
+
exactReplace?: TopicRegistryExactReplace;
|
|
194
|
+
});
|
|
195
|
+
read(): Promise<TopicRegistryState>;
|
|
196
|
+
compareAndSet(expectedGeneration: number, next: TopicRegistryState): Promise<boolean>;
|
|
197
|
+
private readLocked;
|
|
198
|
+
}
|
|
199
|
+
export interface MachineIdentityDeps {
|
|
200
|
+
platform?: NodeJS.Platform;
|
|
201
|
+
readFile?: (file: string) => Promise<string>;
|
|
202
|
+
runCommand?: (command: string, args: readonly string[]) => {
|
|
203
|
+
exitCode: number;
|
|
204
|
+
stdout: Uint8Array;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** @internal */
|
|
208
|
+
export declare function parseWindowsMachineGuid(output: string): string | undefined;
|
|
209
|
+
/** @internal */
|
|
210
|
+
export declare function parseMacPlatformUuid(output: string): string | undefined;
|
|
211
|
+
/** Loads a verified machine-local identity without persisting the underlying machine ID. */
|
|
212
|
+
export declare function loadInstallationHostId(deps?: MachineIdentityDeps): Promise<string>;
|
|
154
213
|
export declare function tryCreateOwnershipLock(fsImpl: TelegramDaemonFs, file: string, initialization: OwnershipLockMetadata): Promise<boolean>;
|
|
155
214
|
type OwnershipLockMetadata = {
|
|
156
215
|
pid: number;
|
|
@@ -552,6 +611,8 @@ export interface TelegramSpawnOwnerInput {
|
|
|
552
611
|
roots?: string[];
|
|
553
612
|
tokenFingerprint: string;
|
|
554
613
|
chatId: string;
|
|
614
|
+
/** Ephemeral outbound-only validation destination for this owner launch. */
|
|
615
|
+
validationTestSupergroupChatId?: string;
|
|
555
616
|
}
|
|
556
617
|
export interface TelegramSpawnAcquisition {
|
|
557
618
|
readonly ownerId: string;
|
|
@@ -587,6 +648,7 @@ export declare function buildTelegramDaemonSpawnArgs(input: {
|
|
|
587
648
|
execPath?: string;
|
|
588
649
|
ownerId: string;
|
|
589
650
|
agentDir: string;
|
|
651
|
+
validationTestSupergroupChatId?: string;
|
|
590
652
|
}): {
|
|
591
653
|
command: string;
|
|
592
654
|
args: string[];
|
|
@@ -732,6 +794,12 @@ export interface TelegramDaemonOptions {
|
|
|
732
794
|
ownerId: string;
|
|
733
795
|
botToken: string;
|
|
734
796
|
chatId: string;
|
|
797
|
+
/**
|
|
798
|
+
* Exact ephemeral forum destination supplied to the daemon-internal validation
|
|
799
|
+
* command. Production ownership, pairing, and durable topic authority remain
|
|
800
|
+
* bound to `chatId`.
|
|
801
|
+
*/
|
|
802
|
+
validationTestSupergroupChatId?: string;
|
|
735
803
|
apiBase?: string;
|
|
736
804
|
fetchImpl?: typeof fetch;
|
|
737
805
|
fs?: TelegramDaemonFs;
|
|
@@ -793,6 +861,13 @@ export interface TelegramDaemonOptions {
|
|
|
793
861
|
topics?: {
|
|
794
862
|
nameTemplate?: string;
|
|
795
863
|
};
|
|
864
|
+
/**
|
|
865
|
+
* Optional compare-and-set store for installations that share topic state
|
|
866
|
+
* across hosts. When configured, every publication is fenced by it.
|
|
867
|
+
*/
|
|
868
|
+
topicRegistryAuthority?: TopicRegistryCasAuthority;
|
|
869
|
+
/** Stable host-local identity. Required whenever a shared authority is configured. */
|
|
870
|
+
installationHostId?: string;
|
|
796
871
|
}
|
|
797
872
|
interface SessionSocket {
|
|
798
873
|
/** Immutable key of the transport endpoint that owns this socket. */
|
|
@@ -841,6 +916,9 @@ export declare class TelegramNotificationDaemon {
|
|
|
841
916
|
private readonly opts;
|
|
842
917
|
readonly aliasTable: AliasTable;
|
|
843
918
|
readonly messageRoutes: Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>;
|
|
919
|
+
/** Restart-revoked aliases retained only to terminalize their old keyboards on an authoritative replay. */
|
|
920
|
+
private readonly reissueBacklog;
|
|
921
|
+
private aliasPersistenceQueue;
|
|
844
922
|
/** Telegram message id backing each streamed `${sessionId}:${coalesceKey}`, for in-place edits. */
|
|
845
923
|
private readonly liveMessages;
|
|
846
924
|
/** Endpoint-bound ownership for visible or dispatching tool bubbles. */
|
|
@@ -868,6 +946,8 @@ export declare class TelegramNotificationDaemon {
|
|
|
868
946
|
private readonly botApi;
|
|
869
947
|
private readonly effects;
|
|
870
948
|
private readonly topics;
|
|
949
|
+
/** Stable host-local identity; never persisted in shared topic authority. */
|
|
950
|
+
private installationHostId;
|
|
871
951
|
/** Serializes registry snapshots so an older atomic write cannot overwrite newer rename state. */
|
|
872
952
|
/** Legacy sockets have no durable token, so explicit teardown is their revocation fence. */
|
|
873
953
|
private readonly droppedSessions;
|
|
@@ -876,6 +956,8 @@ export declare class TelegramNotificationDaemon {
|
|
|
876
956
|
private recoveryBindingClaimQueue;
|
|
877
957
|
/** Durable compensation fences retry under supervision until persistence succeeds. */
|
|
878
958
|
private readonly compensationFenceRetries;
|
|
959
|
+
/** All archive paths for one session share one durable fence and remote dispatch. */
|
|
960
|
+
private readonly archiveFlights;
|
|
879
961
|
/** Daemon edit attempts that can race an accepted user service message. */
|
|
880
962
|
private readonly daemonRenameAttempts;
|
|
881
963
|
private readonly selectedAckPending;
|
|
@@ -906,8 +988,8 @@ export declare class TelegramNotificationDaemon {
|
|
|
906
988
|
private threadedFallbackNoticeSent;
|
|
907
989
|
/** Sessions whose identity header was already sent flat (Threaded Mode off). */
|
|
908
990
|
private readonly flatIdentitySent;
|
|
909
|
-
/** Cached
|
|
910
|
-
private
|
|
991
|
+
/** Cached delivery boundary for the private owner chat or validation forum. */
|
|
992
|
+
private pairedChatPrivacy;
|
|
911
993
|
/** Bot username from getMe, cached once at owner startup for group/forum command targeting. */
|
|
912
994
|
private botUsername;
|
|
913
995
|
/** Sessions whose agent loop is currently busy (drives the typing indicator). */
|
|
@@ -972,6 +1054,7 @@ export declare class TelegramNotificationDaemon {
|
|
|
972
1054
|
/** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
|
|
973
1055
|
private formatLifecycleResponse;
|
|
974
1056
|
private callBotApi;
|
|
1057
|
+
private validationTopicDestination;
|
|
975
1058
|
private readonly callBotApiClassified;
|
|
976
1059
|
private botApiWithOutcomeCollector;
|
|
977
1060
|
constructor(opts: TelegramDaemonOptions);
|
|
@@ -1005,6 +1088,12 @@ export declare class TelegramNotificationDaemon {
|
|
|
1005
1088
|
private beginToolActivityShutdown;
|
|
1006
1089
|
private dropSession;
|
|
1007
1090
|
private deleteMessageRoutes;
|
|
1091
|
+
revokeCallbackAliases(socketLease: {
|
|
1092
|
+
session: SessionSocket;
|
|
1093
|
+
token: number;
|
|
1094
|
+
logicalSessionId: string;
|
|
1095
|
+
}): void;
|
|
1096
|
+
private reissuePendingAction;
|
|
1008
1097
|
private static readonly THREADED_FRAMES;
|
|
1009
1098
|
private topicNameFor;
|
|
1010
1099
|
/**
|
|
@@ -1054,20 +1143,19 @@ export declare class TelegramNotificationDaemon {
|
|
|
1054
1143
|
private topicPastOrphanGrace;
|
|
1055
1144
|
private observeOrphanedTopic;
|
|
1056
1145
|
/** Best-effort delete of a session topic once its local notification endpoint shuts down. */
|
|
1057
|
-
|
|
1146
|
+
/** Join all close, compensation, orphan, and restart callers for one session. */
|
|
1147
|
+
private archiveTopic;
|
|
1058
1148
|
private persistTopics;
|
|
1059
1149
|
loadTopics(): Promise<void>;
|
|
1060
1150
|
/**
|
|
1061
|
-
* Rehydrate durable adoption intents after restart
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
* and is removed safely without touching the user topic.
|
|
1151
|
+
* Rehydrate durable adoption intents after restart. A sidecar whose topic
|
|
1152
|
+
* already committed is stale evidence and can be removed without touching
|
|
1153
|
+
* the retained user-created topic.
|
|
1065
1154
|
*/
|
|
1066
1155
|
loadAdoptionIntents(): Promise<void>;
|
|
1067
|
-
/** Periodic sweep of expired adoption-intent sidecars (files only; no Telegram API). */
|
|
1068
1156
|
private startAdoptionSweepTimer;
|
|
1069
1157
|
private stopAdoptionSweepTimer;
|
|
1070
|
-
/** Retry crash-interrupted topic
|
|
1158
|
+
/** Retry crash-interrupted topic archives only when the durable backoff permits it. */
|
|
1071
1159
|
private reconcilePendingTopicDeletes;
|
|
1072
1160
|
/** Download one Telegram file with the Bot API's 20 MiB ceiling and one end-to-end deadline. */
|
|
1073
1161
|
private downloadTelegramFile;
|
|
@@ -1122,13 +1210,13 @@ export declare class TelegramNotificationDaemon {
|
|
|
1122
1210
|
*/
|
|
1123
1211
|
private deliverFlatFallback;
|
|
1124
1212
|
/**
|
|
1125
|
-
* Resolve
|
|
1126
|
-
*
|
|
1127
|
-
* indeterminate `getChat` result fails closed for this attempt and is retried
|
|
1128
|
-
* later.
|
|
1213
|
+
* Resolve and cache the outbound delivery boundary. The validation forum is
|
|
1214
|
+
* explicit and topic-only; all inbound and flat paths remain private-only.
|
|
1129
1215
|
*/
|
|
1216
|
+
private validationMode;
|
|
1130
1217
|
private resolvePairedChatPrivacy;
|
|
1131
|
-
|
|
1218
|
+
private pairedChatAllowsTopics;
|
|
1219
|
+
/** Keep all flat delivery and inbound control paths private-only. */
|
|
1132
1220
|
private pairedChatIsPrivate;
|
|
1133
1221
|
/** Tell the user once (per daemon run) how to enable Threaded Mode. */
|
|
1134
1222
|
private notifyThreadedFallback;
|
|
@@ -51,16 +51,25 @@ export interface CallbackRoute {
|
|
|
51
51
|
sessionId: string;
|
|
52
52
|
actionId: string;
|
|
53
53
|
answer: TelegramCallbackAnswer;
|
|
54
|
+
/** Durable audit metadata only; never sufficient to route a callback after restart. */
|
|
55
|
+
chatId?: string;
|
|
56
|
+
messageId?: number;
|
|
57
|
+
ownerId?: string;
|
|
58
|
+
generation?: number;
|
|
54
59
|
}
|
|
55
60
|
export interface SerializedAliasTable {
|
|
56
|
-
version: 1;
|
|
61
|
+
version: 1 | 2;
|
|
57
62
|
next: number;
|
|
58
63
|
routes: Record<string, CallbackRoute>;
|
|
59
64
|
}
|
|
60
65
|
export interface AliasTable {
|
|
66
|
+
allocate(isReserved?: (alias: string) => boolean): string;
|
|
67
|
+
activate(alias: string, route: CallbackRoute): boolean;
|
|
61
68
|
put(route: CallbackRoute): string;
|
|
62
69
|
get(alias: string): CallbackRoute | undefined;
|
|
70
|
+
update(alias: string, patch: Partial<CallbackRoute>): boolean;
|
|
63
71
|
delete(alias: string): boolean;
|
|
72
|
+
clear(): void;
|
|
64
73
|
serialize(): SerializedAliasTable;
|
|
65
74
|
load(json: unknown): void;
|
|
66
75
|
entries(): Array<[string, CallbackRoute]>;
|