@gajae-code/coding-agent 0.12.11 → 0.12.13
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 +68 -0
- package/dist/types/cli/marketplace-hint.d.ts +28 -0
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock-gc.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/models-config-schema.d.ts +99 -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/gjc-runtime/deep-interview-state.d.ts +14 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +11 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/acp/acp-agent.d.ts +24 -1
- 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/html-format.d.ts +10 -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 +28 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -17
- package/dist/types/sdk/bus/telegram-reference.d.ts +20 -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 -2
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +40 -6
- package/dist/types/tools/index.d.ts +15 -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/fast-help.ts +1 -0
- package/src/cli/marketplace-hint.ts +39 -0
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/plugin-cli.ts +10 -0
- package/src/cli/update-cli.ts +90 -23
- package/src/cli.ts +23 -0
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +65 -26
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +46 -30
- package/src/config/model-registry.ts +17 -5
- package/src/config/models-config-schema.ts +9 -4
- 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/deep-interview/SKILL.md +1 -1
- 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/deep-interview-recorder.ts +9 -5
- package/src/gjc-runtime/deep-interview-state.ts +27 -3
- package/src/gjc-runtime/gc-render.ts +8 -1
- package/src/gjc-runtime/gc-runtime.ts +15 -1
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/main.ts +214 -70
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/acp/acp-agent.ts +123 -7
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/model-selector.ts +52 -5
- 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/print-mode.ts +10 -0
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/system/project-prompt.md +19 -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/html-format.ts +19 -3
- package/src/sdk/bus/index.ts +91 -12
- 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 +28 -7
- package/src/sdk/bus/telegram-daemon.ts +2007 -509
- package/src/sdk/bus/telegram-reference.ts +114 -15
- 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 +144 -29
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +220 -13
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +228 -7
- package/src/session/session-manager.ts +225 -60
- package/src/setup/provider-presets.json +3 -3
- package/src/task/index.ts +92 -44
- package/src/thinking-metadata.ts +2 -1
- package/src/tools/ask.ts +10 -0
- package/src/tools/index.ts +15 -0
- package/src/tools/todo-write.ts +4 -4
- package/src/utils/npm-registry.ts +660 -0
package/dist/types/main.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { PrintModeOptions } from "./modes/print-mode";
|
|
|
16
16
|
import { initTheme } from "./modes/theme/theme";
|
|
17
17
|
import type { SubmittedUserInput } from "./modes/types";
|
|
18
18
|
import type { MCPManager } from "./runtime-mcp";
|
|
19
|
-
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage } from "./sdk";
|
|
19
|
+
import { type CreateAgentSessionOptions, type CreateAgentSessionResult, createAgentSession, discoverAuthStorage } from "./sdk";
|
|
20
20
|
import type { AgentSession } from "./session/agent-session";
|
|
21
21
|
import { type ResumeSessionIdentity, type SessionDestination, type SessionDirectoryMigrationPolicy, type SessionInfo, SessionManager, type StrictSessionOpenResult } from "./session/session-manager";
|
|
22
22
|
import { runStartupCredentialAutoImportIfNeeded } from "./setup/credential-auto-import";
|
|
@@ -55,6 +55,7 @@ type StartupModelProfileArgs = {
|
|
|
55
55
|
parsedArgs: Pick<Args, "default" | "model" | "mpreset" | "thinking">;
|
|
56
56
|
startupModel?: CreateAgentSessionOptions["model"];
|
|
57
57
|
startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
58
|
+
preferCachedModels?: boolean;
|
|
58
59
|
};
|
|
59
60
|
export declare function applyStartupModelProfiles(args: StartupModelProfileArgs): Promise<void>;
|
|
60
61
|
export declare function applyStartupModelProfilesOrExit(args: StartupModelProfileArgs): Promise<void>;
|
|
@@ -74,6 +75,9 @@ export declare function applyStartupModelProfilesForRoot(args: StartupModelProfi
|
|
|
74
75
|
}): Promise<{
|
|
75
76
|
recoverableErrors: string[];
|
|
76
77
|
}>;
|
|
78
|
+
type DeferredModelProfileStartup = () => Promise<{
|
|
79
|
+
recoverableErrors: string[];
|
|
80
|
+
}>;
|
|
77
81
|
interface InteractiveModeFactoryOptions {
|
|
78
82
|
session: AgentSession;
|
|
79
83
|
version: string;
|
|
@@ -95,7 +99,7 @@ export declare const BARE_RESUME_INTERACTIVE_ERROR = "--resume requires an inter
|
|
|
95
99
|
/** Resolves only the managed agent root needed for pre-consent picker inventory. */
|
|
96
100
|
export declare function resolveManagedAgentDirForScope(_cwd: string): string;
|
|
97
101
|
export declare const BARE_RESUME_OPEN_ERROR = "Could not open the selected session. Use --resume <id>.";
|
|
98
|
-
export declare function runInteractiveMode(session: AgentSession, version: string, changelogMarkdown: string | undefined, notifs: (InteractiveModeNotify | null)[], startupUpdate: StartupUpdateOrchestrator, initialMessages: string[], setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void, lspServers: LspStartupServerInfo[] | undefined, mcpManager: MCPManager | undefined, eventBus?: EventBus, initialMessage?: string, initialImages?: ImageContent[], createInteractiveMode?: CreateInteractiveMode, resumeAction?: "continue-tail" | "open-idle"): Promise<void>;
|
|
102
|
+
export declare function runInteractiveMode(session: AgentSession, version: string, changelogMarkdown: string | undefined, notifs: (InteractiveModeNotify | null)[], startupUpdate: StartupUpdateOrchestrator, initialMessages: string[], setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void, lspServers: LspStartupServerInfo[] | undefined, mcpManager: MCPManager | undefined, eventBus?: EventBus, initialMessage?: string, initialImages?: ImageContent[], createInteractiveMode?: CreateInteractiveMode, resumeAction?: "continue-tail" | "open-idle", startDeferredMcpConfig?: CreateAgentSessionResult["startDeferredMcpConfig"], startDeferredModelProfiles?: DeferredModelProfileStartup): Promise<void>;
|
|
99
103
|
export declare function getChangelogForDisplay(parsed: Args): Promise<string | undefined>;
|
|
100
104
|
export declare function createSessionManager(parsed: Args, cwd: string, activeSettings?: Settings): Promise<SessionManager | undefined>;
|
|
101
105
|
/**
|
|
@@ -32,11 +32,32 @@ export interface TranscriptReplayContent {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
export declare function transcriptReplayContent(entry: unknown): TranscriptReplayContent;
|
|
35
|
+
/**
|
|
36
|
+
* Collect every page of `providers.list/active` (Q29, GJC >= 0.12.8) and
|
|
37
|
+
* return the providers with usable stored credentials or credentialless
|
|
38
|
+
* connection kinds, mirroring the TUI model picker's
|
|
39
|
+
* `modelRegistry.getAvailable()`. The openwebui-gjc-adapter applies the same
|
|
40
|
+
* filter to `/v1/models`. Q29 pages are byte-bounded and can span multiple
|
|
41
|
+
* pages when custom provider ids inflate the payload, so all pages are
|
|
42
|
+
* consumed before the provider set is built. Returns undefined only when the
|
|
43
|
+
* session host rejects the query with `operation_not_session_owned`; any
|
|
44
|
+
* other query failure or malformed page is thrown.
|
|
45
|
+
*/
|
|
46
|
+
export declare function collectActiveProviderIds(adapter: Pick<AcpSdkAdapter, "query">): Promise<ReadonlySet<string> | undefined>;
|
|
47
|
+
/**
|
|
48
|
+
* Collect every page of `models.list/current` (Q10) into one catalog so the
|
|
49
|
+
* active-provider filter never drops models that only appear on later pages.
|
|
50
|
+
* The SDK pages Q10 at a fixed byte target (256 KiB), which a fully
|
|
51
|
+
* configured catalog can exceed. Returns the same
|
|
52
|
+
* `{ result: { page: { items } } }` envelope `pageItems` consumes.
|
|
53
|
+
*/
|
|
54
|
+
export declare function collectModelCatalog(adapter: Pick<AcpSdkAdapter, "query">): Promise<unknown>;
|
|
35
55
|
/** Maps live canonical SDK config and the selected model catalog into the ACP 1.2.1 session state surface. */
|
|
36
|
-
export declare function acpSessionStateFromConfig(query: unknown, modelCatalogQuery?: unknown, modelPreset?: string): {
|
|
56
|
+
export declare function acpSessionStateFromConfig(query: unknown, modelCatalogQuery?: unknown, modelPreset?: string, activeProviders?: ReadonlySet<string>): {
|
|
37
57
|
configOptions: ({
|
|
38
58
|
id: string;
|
|
39
59
|
name: string;
|
|
60
|
+
category: "mode";
|
|
40
61
|
type: "select";
|
|
41
62
|
currentValue: string;
|
|
42
63
|
options: {
|
|
@@ -46,6 +67,7 @@ export declare function acpSessionStateFromConfig(query: unknown, modelCatalogQu
|
|
|
46
67
|
} | {
|
|
47
68
|
id: "model";
|
|
48
69
|
name: string;
|
|
70
|
+
category: "model";
|
|
49
71
|
type: "select";
|
|
50
72
|
currentValue: string;
|
|
51
73
|
options: {
|
|
@@ -73,6 +95,7 @@ export declare function acpSessionStateFromConfig(query: unknown, modelCatalogQu
|
|
|
73
95
|
} | {
|
|
74
96
|
id: "thinking";
|
|
75
97
|
name: string;
|
|
98
|
+
category: "thought_level";
|
|
76
99
|
type: "select";
|
|
77
100
|
currentValue: string;
|
|
78
101
|
options: {
|
|
@@ -35,7 +35,7 @@ export declare class SessionSelectorComponent extends Container {
|
|
|
35
35
|
private readonly onDelete?;
|
|
36
36
|
private readonly inspector?;
|
|
37
37
|
private readonly onSelection?;
|
|
38
|
-
constructor(sessions: SessionInfo[], onSelect: (sessionPath: string) => void
|
|
38
|
+
constructor(sessions: SessionInfo[], onSelect: (sessionPath: string) => void | Promise<void>, onCancel: () => void, onExit: () => void, onDelete?: ((session: SessionInfo) => Promise<boolean>) | undefined, inspector?: SessionInspector | undefined, onSelection?: ((selection: SessionSelectionResult) => void) | undefined);
|
|
39
39
|
setOnRequestRender(callback: () => void): void;
|
|
40
40
|
handleInput(keyData: string): void;
|
|
41
41
|
getSessionList(): SessionList;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-terminal agent status markers.
|
|
3
|
+
*
|
|
4
|
+
* Interactive turns announce their state as an OSC 777 sequence:
|
|
5
|
+
*
|
|
6
|
+
* ESC ] 777 ; notify;Terax;gjc;<event> BEL
|
|
7
|
+
*
|
|
8
|
+
* A terminal that understands the sequence (Terax reads it to drive its per-tab
|
|
9
|
+
* agent status and header bell) reacts; every other terminal discards the OSC
|
|
10
|
+
* as unknown, exactly like the OSC 133 prompt marks and OSC 7 cwd reports that
|
|
11
|
+
* shells emit unconditionally. So there is no host sniffing and no env gate:
|
|
12
|
+
* any host that wants agent status can parse the stream without gjc knowing it
|
|
13
|
+
* exists.
|
|
14
|
+
*
|
|
15
|
+
* `notify;Terax;` is the wire identifier of the only protocol that currently
|
|
16
|
+
* carries per-turn agent state, not a host check - the payload names gjc as the
|
|
17
|
+
* reporting agent.
|
|
18
|
+
*
|
|
19
|
+
* Interactive TUI only. Print and RPC mode stdout stays byte-for-byte parseable
|
|
20
|
+
* because they never reach these controllers.
|
|
21
|
+
*/
|
|
22
|
+
export type HostStatusEvent = "working" | "attention" | "finished";
|
|
23
|
+
export declare function emitHostStatus(event: HostStatusEvent, output?: Pick<NodeJS.WriteStream, "write">): void;
|
|
@@ -186,6 +186,8 @@ export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
|
186
186
|
export interface MCPToolCallResult {
|
|
187
187
|
content: MCPContent[];
|
|
188
188
|
isError?: boolean;
|
|
189
|
+
/** MCP result metadata, encoded as `_meta` on the wire. */
|
|
190
|
+
_meta?: Record<string, unknown>;
|
|
189
191
|
}
|
|
190
192
|
/** Expected configured-server transport or protocol failure. */
|
|
191
193
|
export declare class MCPExpectedFailure extends Error {
|
|
@@ -48,6 +48,16 @@ export interface SessionLifecycleTranscriptIdentity {
|
|
|
48
48
|
mtimeNs: string;
|
|
49
49
|
sha256: string;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* When a lifecycle-managed session publishes its replayable readiness signal.
|
|
53
|
+
*
|
|
54
|
+
* `immediate` is the stock contract. `deferred` prepares the session instead:
|
|
55
|
+
* the child publishes a distinct prepared signal, keeps `session_ready`
|
|
56
|
+
* withheld, and stays unusable for input until it is explicitly activated. It
|
|
57
|
+
* is broker-issued and session-scoped precisely so a prepared session can never
|
|
58
|
+
* be produced by an inherited process-global flag.
|
|
59
|
+
*/
|
|
60
|
+
export type SessionLifecycleReadiness = "immediate" | "deferred";
|
|
51
61
|
export interface SessionLifecycleLaunchRequest {
|
|
52
62
|
operation: "session.create" | "session.fork" | "session.resume";
|
|
53
63
|
sessionId: string;
|
|
@@ -64,6 +74,8 @@ export interface SessionLifecycleLaunchRequest {
|
|
|
64
74
|
modelPreset?: string;
|
|
65
75
|
mcpServers?: SessionLifecycleMcpServer[];
|
|
66
76
|
worktree?: SessionLifecycleWorktreeTarget;
|
|
77
|
+
/** Absent means the stock immediate contract; `deferred` prepares the session. */
|
|
78
|
+
readiness?: SessionLifecycleReadiness;
|
|
67
79
|
receivedAt: number;
|
|
68
80
|
requestedReadinessTimeoutMs: number;
|
|
69
81
|
semanticReadyDeadlineAt: number;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type ChatDaemonCommandBindInput, type ChatDaemonCommandOutcome } from "./chat-daemon-command-channel";
|
|
1
2
|
import { type ChatDaemonKind, renewChatDaemonHeartbeat } from "./chat-daemon-control";
|
|
2
3
|
import { type ChatDaemonRuntimeConfig } from "./chat-daemon-runtime";
|
|
3
4
|
export interface ChatDaemonRuntimeHandle {
|
|
4
5
|
start(): Promise<void>;
|
|
5
6
|
stop(): Promise<void>;
|
|
6
7
|
transportHealthy?(): boolean;
|
|
8
|
+
/** Executes operator commands that must run inside the owning daemon. */
|
|
9
|
+
bindExistingRoot?(request: ChatDaemonCommandBindInput): Promise<ChatDaemonCommandOutcome>;
|
|
7
10
|
}
|
|
8
11
|
export interface RunChatDaemonInternalDeps {
|
|
9
12
|
processPid?: number;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request/response control channel for chat-daemon commands that must be
|
|
3
|
+
* executed *by* the running owner instead of terminating it.
|
|
4
|
+
*
|
|
5
|
+
* `control.json` is the lifecycle channel: every request the owner recognizes
|
|
6
|
+
* there ends its serving loop (stop/reload). Adopting an existing Slack root
|
|
7
|
+
* must not stop a healthy daemon, so operator commands travel on this separate
|
|
8
|
+
* per-request channel and are answered in place.
|
|
9
|
+
*
|
|
10
|
+
* Invariants:
|
|
11
|
+
* - Every request is addressed to an exact owner (`ownerId`, `pid`,
|
|
12
|
+
* `incarnation`, daemon `generation`). A daemon that is not that exact owner
|
|
13
|
+
* answers `owner_changed` and performs no work, so a replaced or restarted
|
|
14
|
+
* owner can never satisfy a request captured against its predecessor. The
|
|
15
|
+
* same tuple is re-proven inside the commit fence and echoed in the answer,
|
|
16
|
+
* so the submitter can verify who actually acted.
|
|
17
|
+
* - `<id>.response.json` is a single-winner arbitration object created with
|
|
18
|
+
* `O_CREAT|O_EXCL`. The serving daemon must win it *before* it commits, and a
|
|
19
|
+
* submitter that has given up must win it before reporting failure. Whoever
|
|
20
|
+
* loses learns so definitively, which removes the commit-vs-timeout race
|
|
21
|
+
* without any sleep: a definitive `cancelled` answer proves no dispatch can
|
|
22
|
+
* follow within this submission's race, and a lost cancellation is reported
|
|
23
|
+
* as `unknown` rather than as a failure the caller could act on. The
|
|
24
|
+
* arbitration is scoped to that race under the trust boundary below; it is
|
|
25
|
+
* not durable settlement across a crashed and resurrected daemon.
|
|
26
|
+
* - Documents live in an owner-only (`0700`) directory below the agent
|
|
27
|
+
* directory, are bounded, and are rejected unless they are ordinary regular
|
|
28
|
+
* files. This matches the product's existing same-UID local trust boundary;
|
|
29
|
+
* it is not a defence against a hostile process running as the same user.
|
|
30
|
+
* - Only identifiers travel through the channel. Tokens, message bodies, and
|
|
31
|
+
* control secrets are never written here.
|
|
32
|
+
*
|
|
33
|
+
* The channel proves *correlation*, never authorship: every field a response
|
|
34
|
+
* echoes is copied verbatim out of the plaintext request published beside it,
|
|
35
|
+
* so a definitive answer is only actionable once the caller corroborates the
|
|
36
|
+
* mutation against an authority outside this directory.
|
|
37
|
+
*/
|
|
38
|
+
import { type ChatDaemonKind } from "./chat-daemon-control";
|
|
39
|
+
export declare const CHAT_DAEMON_COMMAND_VERSION = 1;
|
|
40
|
+
export declare const DEFAULT_CHAT_DAEMON_COMMAND_TIMEOUT_MS = 15000;
|
|
41
|
+
export type ChatDaemonCommandName = "bind-thread";
|
|
42
|
+
export type ChatDaemonCommandStatus = "ok" | "rejected" | "owner_changed" | "expired" | "outcome_unknown";
|
|
43
|
+
/** Exact owner authority a request is addressed to. */
|
|
44
|
+
export interface ChatDaemonCommandOwner {
|
|
45
|
+
ownerId: string;
|
|
46
|
+
pid: number;
|
|
47
|
+
incarnation: string;
|
|
48
|
+
generation: number;
|
|
49
|
+
}
|
|
50
|
+
export interface ChatDaemonCommandRequest extends ChatDaemonCommandOwner {
|
|
51
|
+
version: typeof CHAT_DAEMON_COMMAND_VERSION;
|
|
52
|
+
requestId: string;
|
|
53
|
+
kind: ChatDaemonKind;
|
|
54
|
+
command: ChatDaemonCommandName;
|
|
55
|
+
sessionId: string;
|
|
56
|
+
rootTs: string;
|
|
57
|
+
createdAt: number;
|
|
58
|
+
expiresAt: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The answer to exactly one request.
|
|
62
|
+
*
|
|
63
|
+
* The owner tuple, session, and root are the *addressed* request's, echoed
|
|
64
|
+
* verbatim. A response therefore carries the complete request envelope, and a
|
|
65
|
+
* submitter can prove that the document in front of it is correlated with its
|
|
66
|
+
* own request rather than with a replayed or concurrent one.
|
|
67
|
+
*
|
|
68
|
+
* That is correlation, not authentication. Every echoed field is public: it is
|
|
69
|
+
* copied out of the plaintext request published in the same command directory,
|
|
70
|
+
* so a stale or planted document can satisfy the envelope without the addressed
|
|
71
|
+
* daemon ever running. A caller that needs a definitive outcome must corroborate
|
|
72
|
+
* it against an authority outside this directory (the durable mutation itself).
|
|
73
|
+
*/
|
|
74
|
+
export interface ChatDaemonCommandResponse extends ChatDaemonCommandOwner {
|
|
75
|
+
version: typeof CHAT_DAEMON_COMMAND_VERSION;
|
|
76
|
+
requestId: string;
|
|
77
|
+
kind: ChatDaemonKind;
|
|
78
|
+
command: ChatDaemonCommandName;
|
|
79
|
+
sessionId: string;
|
|
80
|
+
rootTs: string;
|
|
81
|
+
status: ChatDaemonCommandStatus;
|
|
82
|
+
/** Machine-readable rejection category; never a message body or credential. */
|
|
83
|
+
code?: string;
|
|
84
|
+
endpointGeneration?: number;
|
|
85
|
+
teamId?: string;
|
|
86
|
+
channelId?: string;
|
|
87
|
+
completedAt: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Result of one handler dispatch, carrying explicit commit certainty.
|
|
91
|
+
*
|
|
92
|
+
* A failure is never just a code. `rejected` asserts that no mapping changed, so
|
|
93
|
+
* the caller may be told the binding definitively failed. `unknown` asserts the
|
|
94
|
+
* opposite: commit authority was exercised and the mapping may already be
|
|
95
|
+
* applied, so no definitive rejection may be reported for it. Handlers state
|
|
96
|
+
* this directly instead of leaving the channel to infer it from error text.
|
|
97
|
+
*/
|
|
98
|
+
export type ChatDaemonCommandOutcome = {
|
|
99
|
+
ok: true;
|
|
100
|
+
sessionId: string;
|
|
101
|
+
endpointGeneration: number;
|
|
102
|
+
teamId: string;
|
|
103
|
+
channelId: string;
|
|
104
|
+
rootTs: string;
|
|
105
|
+
} | {
|
|
106
|
+
ok: false;
|
|
107
|
+
certainty: "rejected";
|
|
108
|
+
code: string;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
certainty: "unknown";
|
|
112
|
+
code: string;
|
|
113
|
+
};
|
|
114
|
+
export interface ChatDaemonCommandBindInput {
|
|
115
|
+
sessionId: string;
|
|
116
|
+
rootTs: string;
|
|
117
|
+
/**
|
|
118
|
+
* Terminal authority for this exact request. It must be awaited inside the
|
|
119
|
+
* store fence, immediately before the commit, and the commit must be
|
|
120
|
+
* abandoned when it answers `false`. It re-proves the exact daemon owner
|
|
121
|
+
* tuple, that the request is still published and unexpired, and takes the
|
|
122
|
+
* single-winner response claim, after which no cancellation can succeed.
|
|
123
|
+
*/
|
|
124
|
+
commitAuthority?: () => Promise<boolean>;
|
|
125
|
+
}
|
|
126
|
+
/** Daemon-side executor. Implemented by the runtime that owns the live transports. */
|
|
127
|
+
export interface ChatDaemonCommandHandler {
|
|
128
|
+
bindExistingRoot(input: ChatDaemonCommandBindInput): Promise<ChatDaemonCommandOutcome>;
|
|
129
|
+
}
|
|
130
|
+
export declare function isChatDaemonCommandRequest(value: unknown): value is ChatDaemonCommandRequest;
|
|
131
|
+
export declare function isChatDaemonCommandResponse(value: unknown): value is ChatDaemonCommandResponse;
|
|
132
|
+
/**
|
|
133
|
+
* The owner-only directory the channel exchanges its documents in.
|
|
134
|
+
*
|
|
135
|
+
* `directory` is a plain pathname: this codebase already trusts same-UID local
|
|
136
|
+
* processes, and every document below it is bounded and validated, so the
|
|
137
|
+
* channel does not attempt hostile-filesystem hardening it cannot honestly
|
|
138
|
+
* provide from TypeScript.
|
|
139
|
+
*/
|
|
140
|
+
export interface ChatDaemonCommandScope {
|
|
141
|
+
readonly directory: string;
|
|
142
|
+
}
|
|
143
|
+
export interface OpenChatDaemonCommandScopeInput {
|
|
144
|
+
agentDir: string;
|
|
145
|
+
kind: ChatDaemonKind;
|
|
146
|
+
/** Creates the daemon and command directories owner-only when absent. */
|
|
147
|
+
create?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/** Capture the command directory, or report the channel unusable. */
|
|
150
|
+
export declare function openChatDaemonCommandScope(input: OpenChatDaemonCommandScopeInput): Promise<ChatDaemonCommandScope | undefined>;
|
|
151
|
+
/** Build a request addressed to one exact owner. */
|
|
152
|
+
export declare function buildChatDaemonCommandRequest(input: {
|
|
153
|
+
kind: ChatDaemonKind;
|
|
154
|
+
command: ChatDaemonCommandName;
|
|
155
|
+
owner: ChatDaemonCommandOwner;
|
|
156
|
+
sessionId: string;
|
|
157
|
+
rootTs: string;
|
|
158
|
+
now?: number;
|
|
159
|
+
ttlMs?: number;
|
|
160
|
+
requestId?: string;
|
|
161
|
+
}): ChatDaemonCommandRequest;
|
|
162
|
+
export interface SubmitChatDaemonCommandInput {
|
|
163
|
+
agentDir: string;
|
|
164
|
+
kind: ChatDaemonKind;
|
|
165
|
+
owner: ChatDaemonCommandOwner;
|
|
166
|
+
command: ChatDaemonCommandName;
|
|
167
|
+
sessionId: string;
|
|
168
|
+
rootTs: string;
|
|
169
|
+
timeoutMs?: number;
|
|
170
|
+
pollIntervalMs?: number;
|
|
171
|
+
/** Bounded wait after a lost cancellation, before the outcome is reported unknown. */
|
|
172
|
+
settleGraceMs?: number;
|
|
173
|
+
ttlMs?: number;
|
|
174
|
+
now?: () => number;
|
|
175
|
+
sleep?: (ms: number) => Promise<void>;
|
|
176
|
+
requestId?: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Terminal result of one submission.
|
|
180
|
+
*
|
|
181
|
+
* `cancelled` is the only definitive failure: the submitter won the response
|
|
182
|
+
* claim, so the addressed owner cannot dispatch this request from here on within
|
|
183
|
+
* this submission's race. `unknown` means the daemon won that claim first and its
|
|
184
|
+
* answer was not observed; the caller must not report a definitive failure,
|
|
185
|
+
* because a commit may already be applied.
|
|
186
|
+
*
|
|
187
|
+
* `untrusted` means the response object under this identifier is a complete,
|
|
188
|
+
* well-formed document that does not carry this request's envelope. It is
|
|
189
|
+
* channel corruption — stale or planted — so it may not be read as an answer of
|
|
190
|
+
* any status, and because the identifier is now occupied the real outcome is
|
|
191
|
+
* unknowable from here; the caller must treat it exactly like `unknown`.
|
|
192
|
+
*/
|
|
193
|
+
export type ChatDaemonCommandSubmission = {
|
|
194
|
+
outcome: "answered";
|
|
195
|
+
response: ChatDaemonCommandResponse;
|
|
196
|
+
} | {
|
|
197
|
+
outcome: "cancelled";
|
|
198
|
+
} | {
|
|
199
|
+
outcome: "unknown";
|
|
200
|
+
} | {
|
|
201
|
+
outcome: "untrusted";
|
|
202
|
+
code: "response_envelope_mismatch";
|
|
203
|
+
} | {
|
|
204
|
+
outcome: "unavailable";
|
|
205
|
+
code: "command_channel_unavailable" | "request_id_unavailable";
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Publish a command for the addressed owner and wait for its answer.
|
|
209
|
+
*
|
|
210
|
+
* The wait ends in exactly one of four states, and the commit-vs-timeout race is
|
|
211
|
+
* arbitrated by the exclusive creation of the response object rather than by any
|
|
212
|
+
* timing assumption.
|
|
213
|
+
*/
|
|
214
|
+
export declare function submitChatDaemonCommand(input: SubmitChatDaemonCommandInput): Promise<ChatDaemonCommandSubmission>;
|
|
215
|
+
export interface ServeChatDaemonCommandsInput {
|
|
216
|
+
agentDir: string;
|
|
217
|
+
kind: ChatDaemonKind;
|
|
218
|
+
/** The serving daemon's own proven authority. */
|
|
219
|
+
ownerId: string;
|
|
220
|
+
pid: number;
|
|
221
|
+
incarnation: string;
|
|
222
|
+
generation: number;
|
|
223
|
+
handler: ChatDaemonCommandHandler;
|
|
224
|
+
/**
|
|
225
|
+
* Re-proves that this process still holds the persisted owner record. It is
|
|
226
|
+
* checked before any work is dispatched *and again* inside the commit fence,
|
|
227
|
+
* so a daemon that lost ownership after dispatch mutates nothing.
|
|
228
|
+
*/
|
|
229
|
+
verifyOwnership?: () => Promise<boolean>;
|
|
230
|
+
now?: () => number;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Answer every pending request addressed to this exact owner. Requests aimed at
|
|
234
|
+
* any other owner identity are answered `owner_changed` without doing work, and
|
|
235
|
+
* expired requests are answered `expired`; neither performs a mutation.
|
|
236
|
+
*/
|
|
237
|
+
export declare function serveChatDaemonCommandsOnce(input: ServeChatDaemonCommandsInput): Promise<number>;
|
|
238
|
+
/**
|
|
239
|
+
* Answer every pending request against an already-captured command scope.
|
|
240
|
+
*
|
|
241
|
+
* Exposed so a caller that already holds the directory authority drives exactly
|
|
242
|
+
* the production path.
|
|
243
|
+
*/
|
|
244
|
+
export declare function serveChatDaemonCommandsAgainstScope(scope: ChatDaemonCommandScope, input: ServeChatDaemonCommandsInput): Promise<number>;
|
|
@@ -32,7 +32,11 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
32
32
|
* Discord generation 24 / slack generation 23 apply provider-completeness and
|
|
33
33
|
* effective-enable admission to chat daemon lifecycle controls. Discord
|
|
34
34
|
* generation 25 / slack generation 24 apply identity-bound exact replacement
|
|
35
|
-
* cleanup shared by managed-session and daemon filesystem authority.
|
|
35
|
+
* cleanup shared by managed-session and daemon filesystem authority. Discord
|
|
36
|
+
* generation 26 / slack generation 25 add the in-place operator command channel:
|
|
37
|
+
* an owner serves per-request commands inside its own serving loop and answers
|
|
38
|
+
* them against an exact owner tuple, so an owner at an earlier generation may
|
|
39
|
+
* not serve or answer a request captured against this contract.
|
|
36
40
|
*/
|
|
37
41
|
export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
|
|
38
42
|
export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
|
|
@@ -93,6 +97,12 @@ export declare function chatDaemonPaths(agentDir: string, kind: ChatDaemonKind):
|
|
|
93
97
|
state: string;
|
|
94
98
|
control: string;
|
|
95
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Configuration fingerprint that identifies which settings a daemon owner was
|
|
102
|
+
* started for. `undefined` means the current settings cannot configure that
|
|
103
|
+
* transport at all, so no owner can be authorized against them.
|
|
104
|
+
*/
|
|
105
|
+
export declare function chatDaemonIdentity(settings: Settings, kind: ChatDaemonKind): string | undefined;
|
|
96
106
|
export declare function readChatDaemonState(agentDir: string, kind: ChatDaemonKind): Promise<ChatDaemonState | undefined>;
|
|
97
107
|
export declare function readChatDaemonControlRequest(agentDir: string, kind: ChatDaemonKind): Promise<ChatDaemonControlRequest | undefined>;
|
|
98
108
|
export declare function writeChatDaemonControlRequest(agentDir: string, kind: ChatDaemonKind, request: ChatDaemonControlRequest): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SessionIndex } from "../broker/session-index";
|
|
2
2
|
import { type SdkSessionEndpoint } from "../client/discovery";
|
|
3
|
+
import type { ChatDaemonCommandBindInput, ChatDaemonCommandOutcome } from "./chat-daemon-command-channel";
|
|
3
4
|
import type { ChatDaemonKind } from "./chat-daemon-control";
|
|
4
5
|
import type { DiscordProvider } from "./discord-provider";
|
|
5
6
|
import { type SlackProviderClient } from "./slack-provider";
|
|
@@ -69,6 +70,12 @@ export declare class ChatDaemonRuntime {
|
|
|
69
70
|
private reconcile;
|
|
70
71
|
private attach;
|
|
71
72
|
private resolveEndpoint;
|
|
73
|
+
/**
|
|
74
|
+
* Adopt an operator-supplied Slack root for one attached session. Returns a
|
|
75
|
+
* machine-readable rejection instead of throwing so the daemon control plane
|
|
76
|
+
* can answer without exposing internal failure detail.
|
|
77
|
+
*/
|
|
78
|
+
bindExistingRoot(request: ChatDaemonCommandBindInput): Promise<ChatDaemonCommandOutcome>;
|
|
72
79
|
private schedule;
|
|
73
80
|
private enqueueFrame;
|
|
74
81
|
private handleFrame;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed control-plane discriminants that must never reach chat presentation.
|
|
3
|
+
*
|
|
4
|
+
* A chat daemon observes one session socket for user-visible events, but that
|
|
5
|
+
* socket also carries the SDK's own request/response traffic: `SdkClient`
|
|
6
|
+
* settles a pending request from an inbound frame and still forwards the very
|
|
7
|
+
* same frame to every `onFrame` observer. A runtime that projects unknown frame
|
|
8
|
+
* types into a generic notification therefore renders protocol answers — most
|
|
9
|
+
* visibly `GJC event replay result` — as chat content, and a chat transport
|
|
10
|
+
* with no mapping for that session publishes it as a brand-new root.
|
|
11
|
+
*
|
|
12
|
+
* Membership is exact, typed, and closed. It is never inferred from a `_result`
|
|
13
|
+
* or `_response` suffix: ordinary public frames are free to be named anything,
|
|
14
|
+
* and a fuzzy rule would silently swallow real user content. Every entry below
|
|
15
|
+
* names the source authority that emits it and the code path that proves it can
|
|
16
|
+
* be delivered to a chat daemon's frame observer.
|
|
17
|
+
*
|
|
18
|
+
* In scope (proven reachable at the chat-daemon session-client seam):
|
|
19
|
+
* - `event_replay_result` — `SessionSdkHost` (`sdk/host/host.ts`) answers the
|
|
20
|
+
* `event_replay` request that `ChatDaemonRuntime.attach()` issues on every
|
|
21
|
+
* attachment, so every attached session receives exactly this frame.
|
|
22
|
+
* - `control_response` — the same host's answer to the `control_request` that
|
|
23
|
+
* `ChatDaemonRuntime` sends for an authorized `/sdk control …` command, and
|
|
24
|
+
* the endpoint-stale refusal the session runtime writes for a fenced
|
|
25
|
+
* connection.
|
|
26
|
+
* - `query_response` — the host's answer to the matching `query_request`, and
|
|
27
|
+
* the same endpoint-stale refusal path.
|
|
28
|
+
* - `hello` — `ServerMessage::Hello` from the native session server
|
|
29
|
+
* (`crates/gjc-sdk/src/protocol.rs`), sent to every connection at accept time
|
|
30
|
+
* and re-observed by frame handlers whenever the client reconnects and
|
|
31
|
+
* adopts a new connection id.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately out of scope — typed server→client frames that exist but are
|
|
34
|
+
* unicast only to a connection that issued a request a chat daemon never
|
|
35
|
+
* sends, so no source path delivers them here: `session_activate_result`,
|
|
36
|
+
* `register_provider_result`, `lease_state`, `reverse_response`,
|
|
37
|
+
* `reverse_request`, `reverse_cancel`, `global_response`, `pong`, and
|
|
38
|
+
* `transport_error`. Add one only together with the source path that proves it
|
|
39
|
+
* reaches this seam.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CONTROL_PLANE_FRAME_TYPES: ReadonlySet<string>;
|
|
42
|
+
/** Whether an exact frame/event discriminant is protocol-only control plane. */
|
|
43
|
+
export declare function isControlPlaneFrameType(name: string | undefined): boolean;
|
|
@@ -70,4 +70,22 @@ export declare class ConversationStore<T extends ConversationRecord> {
|
|
|
70
70
|
delete(key: string, expectedGeneration: number): Promise<boolean>;
|
|
71
71
|
/** Apply a synchronous update under the mapping lock, retrying no stale state. */
|
|
72
72
|
transact(key: string, update: (current: T | undefined) => T | undefined): Promise<T | undefined>;
|
|
73
|
+
/**
|
|
74
|
+
* Apply one key update while atomically observing every mapping in the same
|
|
75
|
+
* store snapshot. The observation and the write share the cross-process file
|
|
76
|
+
* lock, so a caller can enforce uniqueness constraints that span keys.
|
|
77
|
+
*
|
|
78
|
+
* The update may be asynchronous so a caller can re-prove local authority
|
|
79
|
+
* (process, endpoint, or session state) inside the lock, immediately before
|
|
80
|
+
* its commit; returning `current` unchanged aborts without any mutation. It
|
|
81
|
+
* must never perform a remote/network call while holding the lock: the lock
|
|
82
|
+
* is a short fence, not a place to wait on a provider.
|
|
83
|
+
*
|
|
84
|
+
* Authority proven by the update is the last proof before the atomic
|
|
85
|
+
* replacement. Authority that rolls after it is an ordinary lifecycle event:
|
|
86
|
+
* the mapping records the endpoint generation it was proven under, so a newer
|
|
87
|
+
* generation fences it through the same monotonic compare-and-swap every
|
|
88
|
+
* other writer uses.
|
|
89
|
+
*/
|
|
90
|
+
transactWithSnapshot(key: string, update: (current: T | undefined, conversations: Readonly<Record<string, T>>) => T | undefined | Promise<T | undefined>): Promise<T | undefined>;
|
|
73
91
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prepare → bind → activate lifecycle for adopting an existing chat root.
|
|
3
|
+
*
|
|
4
|
+
* Stock startup publishes a session's readiness immediately, so the running
|
|
5
|
+
* chat daemon surfaces the session and creates its own root before an operator
|
|
6
|
+
* could ever name an existing one. A session that opts in is *prepared*
|
|
7
|
+
* instead: its id, endpoint, and broker registration are discoverable authority
|
|
8
|
+
* while readiness stays withheld, which leaves exactly one root claim for
|
|
9
|
+
* `gjc notify bind-thread` to fill. Activation then publishes readiness once,
|
|
10
|
+
* and the daemon adopts the bound root instead of replacing it.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here mutates a mapping. The gate only reads the daemon-owned store to
|
|
13
|
+
* prove a binding exists, and the activation client only asks the session's own
|
|
14
|
+
* host to publish the readiness it withheld.
|
|
15
|
+
*/
|
|
16
|
+
import type { SessionIndex } from "../broker/session-index";
|
|
17
|
+
import type { SdkSessionEndpoint, SdkSessionEndpointScope } from "../client/discovery";
|
|
18
|
+
import type { SessionActivationGate } from "../host";
|
|
19
|
+
import { type ActivatedPreparedSession, type PreparedSessionActivationClient } from "../session-activation";
|
|
20
|
+
import type { ConversationStore } from "./conversation-store";
|
|
21
|
+
import type { SlackConversation } from "./slack-conversation";
|
|
22
|
+
/** Explicit, session-scoped opt-in for withholding readiness until a root is bound. */
|
|
23
|
+
export declare const EXISTING_THREAD_BIND_ENV = "GJC_NOTIFY_BIND_EXISTING_THREAD";
|
|
24
|
+
/**
|
|
25
|
+
* Existing-thread binding is opt-in, and only the exact value `1` opts in.
|
|
26
|
+
*
|
|
27
|
+
* Every other value — absent, empty, `0`, or anything truthy-looking — keeps
|
|
28
|
+
* the stock immediate-readiness contract, so no session silently loses its
|
|
29
|
+
* root publication because of an ambiguous environment.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isExistingThreadBindingRequested(env?: NodeJS.ProcessEnv): boolean;
|
|
32
|
+
export interface SlackBindingActivationInput {
|
|
33
|
+
store: ConversationStore<SlackConversation>;
|
|
34
|
+
teamId: string;
|
|
35
|
+
channelId: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Authorize activation only against an applied binding for this exact session.
|
|
39
|
+
*
|
|
40
|
+
* The proof is the daemon-owned mapping itself: an active record for this
|
|
41
|
+
* session, in the configured workspace and channel, carrying a root and the
|
|
42
|
+
* exact endpoint generation being activated. A missing, foreign, or
|
|
43
|
+
* stale-generation mapping is a refusal, and an unreadable store raises so the
|
|
44
|
+
* caller reports unavailable authority instead of an authorization.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createSlackBindingActivationGate(input: SlackBindingActivationInput): SessionActivationGate;
|
|
47
|
+
export type { ActivatedPreparedSession, PreparedSessionActivationClient, SessionActivationErrorCode, } from "../session-activation";
|
|
48
|
+
export { requestPreparedSessionActivation, SessionActivationError } from "../session-activation";
|
|
49
|
+
export interface PreparedSessionActivationInput {
|
|
50
|
+
sessionIndex: SessionIndex;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
}
|
|
53
|
+
export interface PreparedSessionActivationDeps {
|
|
54
|
+
readEndpoint?: (repo: string, sessionId: string, scope?: SdkSessionEndpointScope) => Promise<SdkSessionEndpoint | null>;
|
|
55
|
+
connect?: (endpoint: {
|
|
56
|
+
url: string;
|
|
57
|
+
token: string;
|
|
58
|
+
}) => Promise<PreparedSessionActivationClient>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Ask a prepared session to publish the readiness it withheld.
|
|
62
|
+
*
|
|
63
|
+
* The same discovery authority the binding requires is resolved first, so an
|
|
64
|
+
* unindexed, terminated, or endpoint-rolled session is refused before any
|
|
65
|
+
* connection is opened. The request names the exact session and endpoint
|
|
66
|
+
* generation, and the answer is accepted only when it proves both, so a
|
|
67
|
+
* replacement session can never be activated in place of the requested one.
|
|
68
|
+
* Activation is idempotent: an exact retry answers `already` without a second
|
|
69
|
+
* readiness signal.
|
|
70
|
+
*/
|
|
71
|
+
export declare function activatePreparedSession(input: PreparedSessionActivationInput, deps?: PreparedSessionActivationDeps): Promise<ActivatedPreparedSession>;
|
|
@@ -43,6 +43,16 @@ export declare function truncateTelegramHtml(message: string, max?: number, mark
|
|
|
43
43
|
export declare function splitTelegramHtml(message: string, max?: number): string[];
|
|
44
44
|
/** Finalize an optional message: undefined passthrough, else safe-truncate. */
|
|
45
45
|
export declare function finalizeTelegramHtml(message?: string): string | undefined;
|
|
46
|
+
/** Multi-select state marker a transport may prepend to an option label. */
|
|
47
|
+
export declare const SELECTION_MARK_CHECKED = "\u2611";
|
|
48
|
+
export declare const SELECTION_MARK_UNCHECKED = "\u2610";
|
|
49
|
+
/**
|
|
50
|
+
* Drop a leading `N.`/`N)` index already embedded in an option label (e.g.
|
|
51
|
+
* deep-interview options pre-numbered by the ask tool) so the canonical
|
|
52
|
+
* one-based index can be applied instead. A selection marker stays in front of
|
|
53
|
+
* the text: the label is renumbered, not un-marked.
|
|
54
|
+
*/
|
|
55
|
+
export declare function renumberableOptionText(label: string): string;
|
|
46
56
|
/**
|
|
47
57
|
* One-based, plain-text button label (Telegram does not parse HTML in labels).
|
|
48
58
|
*
|
|
@@ -51,6 +51,16 @@ export interface NotificationExactUnlinkResult {
|
|
|
51
51
|
export declare function readNotificationEndpointFile(file: string): Promise<NotificationEndpointFile>;
|
|
52
52
|
/** Bump when the native exact-deletion identity contract changes. */
|
|
53
53
|
export declare const NATIVE_PATH_IDENTITY_CONTRACT_VERSION = 1;
|
|
54
|
+
/**
|
|
55
|
+
* Remove exactly the inspected regular file.
|
|
56
|
+
*
|
|
57
|
+
* Intermediate directory symlinks (e.g. multi-account layouts that share
|
|
58
|
+
* `notifications/` via a directory symlink) are resolved before the native
|
|
59
|
+
* no-follow unlink. Only the parent path is canonicalized; the final basename
|
|
60
|
+
* is rejoined so native exact_unlink still applies AT_SYMLINK_NOFOLLOW to the
|
|
61
|
+
* final path component at mutation time. A final-component file symlink stays
|
|
62
|
+
* `reparse_point` (including TOCTOU replacement after JS preflight).
|
|
63
|
+
*/
|
|
54
64
|
export declare function exactUnlinkNotificationFile(file: string, identity: NotificationEndpointFileIdentity, quarantineName: string): NotificationExactUnlinkResult;
|
|
55
65
|
/** Minimal filesystem surface the service needs; injectable for tests. */
|
|
56
66
|
export interface NotificationServiceFs {
|