@threadbase-sh/streamer 1.63.0 → 1.64.0
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/dist/agent/agent-client.d.ts +14 -0
- package/dist/agent/agent-client.d.ts.map +1 -0
- package/dist/agent/agent-config.d.ts +24 -0
- package/dist/agent/agent-config.d.ts.map +1 -0
- package/dist/agent/conversation-writer.d.ts +13 -0
- package/dist/agent/conversation-writer.d.ts.map +1 -0
- package/dist/agent/dedupe.d.ts +6 -0
- package/dist/agent/dedupe.d.ts.map +1 -0
- package/dist/agent/errors.d.ts +23 -0
- package/dist/agent/errors.d.ts.map +1 -0
- package/dist/agent/handle-send-agent-input.d.ts +21 -0
- package/dist/agent/handle-send-agent-input.d.ts.map +1 -0
- package/dist/agent/handle-start-agent-session.d.ts +21 -0
- package/dist/agent/handle-start-agent-session.d.ts.map +1 -0
- package/dist/agent/history-mapper.d.ts +4 -0
- package/dist/agent/history-mapper.d.ts.map +1 -0
- package/dist/agent/payload-guard.d.ts +29 -0
- package/dist/agent/payload-guard.d.ts.map +1 -0
- package/dist/api/app.d.ts +28 -0
- package/dist/api/app.d.ts.map +1 -0
- package/dist/api/handlers/conversations.handlers.d.ts +69 -0
- package/dist/api/handlers/conversations.handlers.d.ts.map +1 -0
- package/dist/api/handlers/http-helpers.d.ts +50 -0
- package/dist/api/handlers/http-helpers.d.ts.map +1 -0
- package/dist/api/handlers/sessions.handlers.d.ts +230 -0
- package/dist/api/handlers/sessions.handlers.d.ts.map +1 -0
- package/dist/api/middleware/auth.middleware.d.ts +5 -0
- package/dist/api/middleware/auth.middleware.d.ts.map +1 -0
- package/dist/api/middleware/cors.middleware.d.ts +5 -0
- package/dist/api/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/api/middleware/error.middleware.d.ts +4 -0
- package/dist/api/middleware/error.middleware.d.ts.map +1 -0
- package/dist/api/routes/backup.routes.d.ts +5 -0
- package/dist/api/routes/backup.routes.d.ts.map +1 -0
- package/dist/api/routes/browse.routes.d.ts +5 -0
- package/dist/api/routes/browse.routes.d.ts.map +1 -0
- package/dist/api/routes/cacheAlert.routes.d.ts +5 -0
- package/dist/api/routes/cacheAlert.routes.d.ts.map +1 -0
- package/dist/api/routes/config.routes.d.ts +5 -0
- package/dist/api/routes/config.routes.d.ts.map +1 -0
- package/dist/api/routes/conversations.routes.d.ts +5 -0
- package/dist/api/routes/conversations.routes.d.ts.map +1 -0
- package/dist/api/routes/devices.routes.d.ts +17 -0
- package/dist/api/routes/devices.routes.d.ts.map +1 -0
- package/dist/api/routes/diagnostics.routes.d.ts +5 -0
- package/dist/api/routes/diagnostics.routes.d.ts.map +1 -0
- package/dist/api/routes/health.routes.d.ts +5 -0
- package/dist/api/routes/health.routes.d.ts.map +1 -0
- package/dist/api/routes/logs.routes.d.ts +3 -0
- package/dist/api/routes/logs.routes.d.ts.map +1 -0
- package/dist/api/routes/misc.routes.d.ts +77 -0
- package/dist/api/routes/misc.routes.d.ts.map +1 -0
- package/dist/api/routes/pair.routes.d.ts +5 -0
- package/dist/api/routes/pair.routes.d.ts.map +1 -0
- package/dist/api/routes/progress.routes.d.ts +39 -0
- package/dist/api/routes/progress.routes.d.ts.map +1 -0
- package/dist/api/routes/projects.routes.d.ts +5 -0
- package/dist/api/routes/projects.routes.d.ts.map +1 -0
- package/dist/api/routes/providers.routes.d.ts +19 -0
- package/dist/api/routes/providers.routes.d.ts.map +1 -0
- package/dist/api/routes/scanner.routes.d.ts +5 -0
- package/dist/api/routes/scanner.routes.d.ts.map +1 -0
- package/dist/api/routes/sessions.routes.d.ts +6 -0
- package/dist/api/routes/sessions.routes.d.ts.map +1 -0
- package/dist/api/routes/ws.routes.d.ts +7 -0
- package/dist/api/routes/ws.routes.d.ts.map +1 -0
- package/dist/api/types/api-deps.d.ts +114 -0
- package/dist/api/types/api-deps.d.ts.map +1 -0
- package/dist/auth.d.ts +81 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/browse.d.ts +17 -0
- package/dist/browse.d.ts.map +1 -0
- package/dist/claude-flags.d.ts +76 -0
- package/dist/claude-flags.d.ts.map +1 -0
- package/dist/cli.cjs +20 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/codex-pty-runner.d.ts +90 -0
- package/dist/codex-pty-runner.d.ts.map +1 -0
- package/dist/config/update-config.d.ts +13 -0
- package/dist/config/update-config.d.ts.map +1 -0
- package/dist/conversation-cache.d.ts +336 -0
- package/dist/conversation-cache.d.ts.map +1 -0
- package/dist/db/config.d.ts +11 -0
- package/dist/db/config.d.ts.map +1 -0
- package/dist/db/index.d.ts +5 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/migrations.d.ts +3 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/pool.d.ts +5 -0
- package/dist/db/pool.d.ts.map +1 -0
- package/dist/db/query-timing.d.ts +52 -0
- package/dist/db/query-timing.d.ts.map +1 -0
- package/dist/db/repositories/cacheMetadata.repository.d.ts +12 -0
- package/dist/db/repositories/cacheMetadata.repository.d.ts.map +1 -0
- package/dist/db/repositories/conversations.repository.d.ts +27 -0
- package/dist/db/repositories/conversations.repository.d.ts.map +1 -0
- package/dist/db/repositories/devices.repository.d.ts +137 -0
- package/dist/db/repositories/devices.repository.d.ts.map +1 -0
- package/dist/db/repositories/managed-sessions.repository.d.ts +127 -0
- package/dist/db/repositories/managed-sessions.repository.d.ts.map +1 -0
- package/dist/db/repositories/projects.repository.d.ts +28 -0
- package/dist/db/repositories/projects.repository.d.ts.map +1 -0
- package/dist/db/repositories/push.repository.d.ts +186 -0
- package/dist/db/repositories/push.repository.d.ts.map +1 -0
- package/dist/db/repositories/sessions.repository.d.ts +16 -0
- package/dist/db/repositories/sessions.repository.d.ts.map +1 -0
- package/dist/db/runtime-store.d.ts +80 -0
- package/dist/db/runtime-store.d.ts.map +1 -0
- package/dist/db/sqlite-migrate.d.ts +13 -0
- package/dist/db/sqlite-migrate.d.ts.map +1 -0
- package/dist/db/upload-records.d.ts +11 -0
- package/dist/db/upload-records.d.ts.map +1 -0
- package/dist/e2ee/noise.d.ts +261 -0
- package/dist/e2ee/noise.d.ts.map +1 -0
- package/dist/e2ee/pair-payload.d.ts +60 -0
- package/dist/e2ee/pair-payload.d.ts.map +1 -0
- package/dist/e2ee/pair-request.d.ts +24 -0
- package/dist/e2ee/pair-request.d.ts.map +1 -0
- package/dist/external-tails.d.ts +91 -0
- package/dist/external-tails.d.ts.map +1 -0
- package/dist/feature-flags.d.ts +212 -0
- package/dist/feature-flags.d.ts.map +1 -0
- package/dist/handlers/handleListProjects.d.ts +3 -0
- package/dist/handlers/handleListProjects.d.ts.map +1 -0
- package/dist/index.d.ts +18 -2571
- package/dist/index.d.ts.map +1 -0
- package/dist/lifecycle/constants.d.ts +21 -0
- package/dist/lifecycle/constants.d.ts.map +1 -0
- package/dist/lifecycle/process-liveness.d.ts +7 -0
- package/dist/lifecycle/process-liveness.d.ts.map +1 -0
- package/dist/live-session-manager.d.ts +59 -0
- package/dist/live-session-manager.d.ts.map +1 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/pair-store.d.ts +60 -0
- package/dist/pair-store.d.ts.map +1 -0
- package/dist/platform.d.ts +25 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/process-discovery.d.ts +35 -0
- package/dist/process-discovery.d.ts.map +1 -0
- package/dist/providers.d.ts +7 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/pty-host/protocol.d.ts +270 -0
- package/dist/pty-host/protocol.d.ts.map +1 -0
- package/dist/pty-host/remote-session-runner.d.ts +133 -0
- package/dist/pty-host/remote-session-runner.d.ts.map +1 -0
- package/dist/pty-host/socket.d.ts +54 -0
- package/dist/pty-host/socket.d.ts.map +1 -0
- package/dist/pty-host/spawn-host.d.ts +30 -0
- package/dist/pty-host/spawn-host.d.ts.map +1 -0
- package/dist/pty-manager.d.ts +65 -0
- package/dist/pty-manager.d.ts.map +1 -0
- package/dist/pty-shared.d.ts +27 -0
- package/dist/pty-shared.d.ts.map +1 -0
- package/dist/scanner-manager.d.ts +126 -0
- package/dist/scanner-manager.d.ts.map +1 -0
- package/dist/schemas/cacheAlert.schema.d.ts +14 -0
- package/dist/schemas/cacheAlert.schema.d.ts.map +1 -0
- package/dist/schemas/claudeFlags.schema.d.ts +7 -0
- package/dist/schemas/claudeFlags.schema.d.ts.map +1 -0
- package/dist/schemas/project.schema.d.ts +18 -0
- package/dist/schemas/project.schema.d.ts.map +1 -0
- package/dist/schemas/updateConfig.schema.d.ts +19 -0
- package/dist/schemas/updateConfig.schema.d.ts.map +1 -0
- package/dist/seal.d.ts +14 -0
- package/dist/seal.d.ts.map +1 -0
- package/dist/server-identity.d.ts +43 -0
- package/dist/server-identity.d.ts.map +1 -0
- package/dist/server-wiring.d.ts +184 -0
- package/dist/server-wiring.d.ts.map +1 -0
- package/dist/server.d.ts +390 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/services/backup/backup.d.ts +103 -0
- package/dist/services/backup/backup.d.ts.map +1 -0
- package/dist/services/cache/cacheMetadata.d.ts +8 -0
- package/dist/services/cache/cacheMetadata.d.ts.map +1 -0
- package/dist/services/cache-integrity/alertStore.d.ts +24 -0
- package/dist/services/cache-integrity/alertStore.d.ts.map +1 -0
- package/dist/services/cache-integrity/backup.d.ts +8 -0
- package/dist/services/cache-integrity/backup.d.ts.map +1 -0
- package/dist/services/cache-integrity/cacheIntegrityMonitor.d.ts +71 -0
- package/dist/services/cache-integrity/cacheIntegrityMonitor.d.ts.map +1 -0
- package/dist/services/conversations/conversationWatcher.d.ts +99 -0
- package/dist/services/conversations/conversationWatcher.d.ts.map +1 -0
- package/dist/services/conversations/findSearchTarget.d.ts +30 -0
- package/dist/services/conversations/findSearchTarget.d.ts.map +1 -0
- package/dist/services/conversations/isAgentConversation.d.ts +8 -0
- package/dist/services/conversations/isAgentConversation.d.ts.map +1 -0
- package/dist/services/conversations/pruneAgentConversations.d.ts +8 -0
- package/dist/services/conversations/pruneAgentConversations.d.ts.map +1 -0
- package/dist/services/conversations/refreshConversationCache.d.ts +27 -0
- package/dist/services/conversations/refreshConversationCache.d.ts.map +1 -0
- package/dist/services/conversations/shouldRefreshProjectsFromHdd.d.ts +33 -0
- package/dist/services/conversations/shouldRefreshProjectsFromHdd.d.ts.map +1 -0
- package/dist/services/diagnostics/diagnostics.d.ts +66 -0
- package/dist/services/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/services/projectChats/deriveProjectChatTitle.d.ts +15 -0
- package/dist/services/projectChats/deriveProjectChatTitle.d.ts.map +1 -0
- package/dist/services/projects/ensureProjectsForConversations.d.ts +15 -0
- package/dist/services/projects/ensureProjectsForConversations.d.ts.map +1 -0
- package/dist/services/providers/capabilities.d.ts +105 -0
- package/dist/services/providers/capabilities.d.ts.map +1 -0
- package/dist/services/providers/providerHealth.d.ts +72 -0
- package/dist/services/providers/providerHealth.d.ts.map +1 -0
- package/dist/services/push/apnsClient.d.ts +105 -0
- package/dist/services/push/apnsClient.d.ts.map +1 -0
- package/dist/services/push/expoPushSender.d.ts +37 -0
- package/dist/services/push/expoPushSender.d.ts.map +1 -0
- package/dist/services/push/liveActivityContentState.d.ts +60 -0
- package/dist/services/push/liveActivityContentState.d.ts.map +1 -0
- package/dist/services/push/liveActivityNotifier.d.ts +49 -0
- package/dist/services/push/liveActivityNotifier.d.ts.map +1 -0
- package/dist/services/push/liveActivityRenewal.d.ts +63 -0
- package/dist/services/push/liveActivityRenewal.d.ts.map +1 -0
- package/dist/services/push/liveActivitySender.d.ts +88 -0
- package/dist/services/push/liveActivitySender.d.ts.map +1 -0
- package/dist/services/push/waitingInputNotifier.d.ts +41 -0
- package/dist/services/push/waitingInputNotifier.d.ts.map +1 -0
- package/dist/services/questions/answersToKeystrokes.d.ts +8 -0
- package/dist/services/questions/answersToKeystrokes.d.ts.map +1 -0
- package/dist/services/questions/codexGateAnswers.d.ts +14 -0
- package/dist/services/questions/codexGateAnswers.d.ts.map +1 -0
- package/dist/services/questions/codexScreen.d.ts +74 -0
- package/dist/services/questions/codexScreen.d.ts.map +1 -0
- package/dist/services/questions/detectAskUserQuestion.d.ts +6 -0
- package/dist/services/questions/detectAskUserQuestion.d.ts.map +1 -0
- package/dist/services/questions/detectPermissionGate.d.ts +62 -0
- package/dist/services/questions/detectPermissionGate.d.ts.map +1 -0
- package/dist/services/questions/detectQuestionFromScreen.d.ts +30 -0
- package/dist/services/questions/detectQuestionFromScreen.d.ts.map +1 -0
- package/dist/services/questions/detectShellPrompt.d.ts +18 -0
- package/dist/services/questions/detectShellPrompt.d.ts.map +1 -0
- package/dist/services/questions/parseAgentPhase.d.ts +12 -0
- package/dist/services/questions/parseAgentPhase.d.ts.map +1 -0
- package/dist/services/questions/parseStatusLine.d.ts +19 -0
- package/dist/services/questions/parseStatusLine.d.ts.map +1 -0
- package/dist/services/questions/questionBroadcast.d.ts +18 -0
- package/dist/services/questions/questionBroadcast.d.ts.map +1 -0
- package/dist/services/questions/resolveAnswer.d.ts +16 -0
- package/dist/services/questions/resolveAnswer.d.ts.map +1 -0
- package/dist/services/search/searchQuery.d.ts +78 -0
- package/dist/services/search/searchQuery.d.ts.map +1 -0
- package/dist/services/security/capabilities.d.ts +50 -0
- package/dist/services/security/capabilities.d.ts.map +1 -0
- package/dist/services/sessions/autoResumeOnBoot.d.ts +28 -0
- package/dist/services/sessions/autoResumeOnBoot.d.ts.map +1 -0
- package/dist/services/sessions/codexRolloutOwner.d.ts +55 -0
- package/dist/services/sessions/codexRolloutOwner.d.ts.map +1 -0
- package/dist/services/sessions/conversationBusy.d.ts +32 -0
- package/dist/services/sessions/conversationBusy.d.ts.map +1 -0
- package/dist/services/sessions/idempotency.d.ts +67 -0
- package/dist/services/sessions/idempotency.d.ts.map +1 -0
- package/dist/services/sessions/reconcileSessions.d.ts +63 -0
- package/dist/services/sessions/reconcileSessions.d.ts.map +1 -0
- package/dist/services/sessions/rehydrateSessions.d.ts +68 -0
- package/dist/services/sessions/rehydrateSessions.d.ts.map +1 -0
- package/dist/services/sessions/resumeIdentity.d.ts +16 -0
- package/dist/services/sessions/resumeIdentity.d.ts.map +1 -0
- package/dist/session-registry-boot.d.ts +142 -0
- package/dist/session-registry-boot.d.ts.map +1 -0
- package/dist/session-store.d.ts +32 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-watchers.d.ts +60 -0
- package/dist/session-watchers.d.ts.map +1 -0
- package/dist/types.d.ts +616 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/uploads.d.ts +16 -0
- package/dist/uploads.d.ts.map +1 -0
- package/dist/utils/bootToken.d.ts +3 -0
- package/dist/utils/bootToken.d.ts.map +1 -0
- package/dist/utils/canonicalizeFilePath.d.ts +75 -0
- package/dist/utils/canonicalizeFilePath.d.ts.map +1 -0
- package/dist/utils/canonicalizeProjectPath.d.ts +13 -0
- package/dist/utils/canonicalizeProjectPath.d.ts.map +1 -0
- package/dist/utils/codexConversationLine.d.ts +38 -0
- package/dist/utils/codexConversationLine.d.ts.map +1 -0
- package/dist/utils/conversationEtag.d.ts +19 -0
- package/dist/utils/conversationEtag.d.ts.map +1 -0
- package/dist/utils/dates.d.ts +12 -0
- package/dist/utils/dates.d.ts.map +1 -0
- package/dist/utils/debounce.d.ts +15 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/deriveSessionName.d.ts +10 -0
- package/dist/utils/deriveSessionName.d.ts.map +1 -0
- package/dist/utils/fileIdentity.d.ts +36 -0
- package/dist/utils/fileIdentity.d.ts.map +1 -0
- package/dist/utils/isScannedSnapshotStale.d.ts +16 -0
- package/dist/utils/isScannedSnapshotStale.d.ts.map +1 -0
- package/dist/utils/scanProgressThrottle.d.ts +12 -0
- package/dist/utils/scanProgressThrottle.d.ts.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/ws-hub.d.ts +15 -0
- package/dist/ws-hub.d.ts.map +1 -0
- package/package.json +3 -4
- package/dist/index.d.cts +0 -2571
package/dist/index.d.ts
CHANGED
|
@@ -1,2571 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
getSessionStage(sessionId: string): Promise<string>;
|
|
20
|
-
}
|
|
21
|
-
interface AgentClientOpts {
|
|
22
|
-
temporalClient: Client;
|
|
23
|
-
taskQueue: string;
|
|
24
|
-
}
|
|
25
|
-
declare function createAgentClient({ temporalClient, taskQueue }: AgentClientOpts): AgentClient;
|
|
26
|
-
|
|
27
|
-
interface AgentConfig {
|
|
28
|
-
enabled: boolean;
|
|
29
|
-
temporal: {
|
|
30
|
-
address: string;
|
|
31
|
-
namespace: string;
|
|
32
|
-
taskQueue: string;
|
|
33
|
-
};
|
|
34
|
-
webhook: {
|
|
35
|
-
hmacSecret: string;
|
|
36
|
-
timestampSkewSeconds: number;
|
|
37
|
-
};
|
|
38
|
-
dedupe: {
|
|
39
|
-
perSessionCapacity: number;
|
|
40
|
-
};
|
|
41
|
-
payload: {
|
|
42
|
-
limitBytes: number;
|
|
43
|
-
trajectoryLogBytes: number;
|
|
44
|
-
trajectoryLogTurns: number;
|
|
45
|
-
};
|
|
46
|
-
sessionBusyRetryMs: number;
|
|
47
|
-
conversationsDir: string;
|
|
48
|
-
}
|
|
49
|
-
declare function readAgentConfig(env?: NodeJS.ProcessEnv): AgentConfig;
|
|
50
|
-
|
|
51
|
-
interface AppendArgs {
|
|
52
|
-
sessionId: string;
|
|
53
|
-
turnId: string;
|
|
54
|
-
content: string;
|
|
55
|
-
reviewerOverruled?: boolean;
|
|
56
|
-
}
|
|
57
|
-
interface ConversationWriter {
|
|
58
|
-
appendAssistantTurn(args: AppendArgs): Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
declare function createConversationWriter(opts: {
|
|
61
|
-
baseDir: string;
|
|
62
|
-
}): ConversationWriter;
|
|
63
|
-
|
|
64
|
-
interface ProgressDedupeLRU {
|
|
65
|
-
hasSeen(eventId: string): boolean;
|
|
66
|
-
readonly size: number;
|
|
67
|
-
}
|
|
68
|
-
declare function createProgressDedupeLRU(capacity: number): ProgressDedupeLRU;
|
|
69
|
-
|
|
70
|
-
type LogDest = "console" | "pino" | "both";
|
|
71
|
-
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
72
|
-
interface Logger {
|
|
73
|
-
debug(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;
|
|
74
|
-
info(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;
|
|
75
|
-
warn(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;
|
|
76
|
-
error(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;
|
|
77
|
-
log(level: LogLevel, msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;
|
|
78
|
-
pino: Logger$1;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Claude Code `--permission-mode` values, as accepted by CLI v2.1.x. */
|
|
82
|
-
declare const PERMISSION_MODES: readonly ["acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"];
|
|
83
|
-
type PermissionMode = (typeof PERMISSION_MODES)[number];
|
|
84
|
-
/** Claude Code `--effort` levels, as accepted by CLI v2.1.x. */
|
|
85
|
-
declare const EFFORT_LEVELS: readonly ["low", "medium", "high", "xhigh", "max"];
|
|
86
|
-
type EffortLevel = (typeof EFFORT_LEVELS)[number];
|
|
87
|
-
type FlagValueType = "boolean" | "string" | "enum" | "list";
|
|
88
|
-
/** How risky enabling a flag is. Drives the client's confirmation UX. */
|
|
89
|
-
type FlagRisk = "low" | "elevated" | "dangerous";
|
|
90
|
-
interface FlagDefinition {
|
|
91
|
-
/** Stable config/wire key. Never the CLI spelling. */
|
|
92
|
-
id: string;
|
|
93
|
-
/** The literal CLI token, e.g. "--add-dir". */
|
|
94
|
-
flag: string;
|
|
95
|
-
valueType: FlagValueType;
|
|
96
|
-
/** Allowed values when valueType === "enum". */
|
|
97
|
-
enumValues?: readonly string[];
|
|
98
|
-
/**
|
|
99
|
-
* Baseline risk. `permissionMode` is the exception: it is only dangerous for
|
|
100
|
-
* the values in DANGEROUS_PERMISSION_MODES, so clients must call
|
|
101
|
-
* `flagValueRisk()` rather than reading this field directly.
|
|
102
|
-
*/
|
|
103
|
-
risk: FlagRisk;
|
|
104
|
-
}
|
|
105
|
-
type ClaudeFlagValue = string | string[] | boolean;
|
|
106
|
-
type ClaudeFlagValues = Record<string, ClaudeFlagValue>;
|
|
107
|
-
|
|
108
|
-
/** Spec for one flag in the keyed registry. The id is the object key, not a field. */
|
|
109
|
-
interface FeatureFlagSpec {
|
|
110
|
-
/** Shipped to clients alongside the values so a UI can render it. */
|
|
111
|
-
description: string;
|
|
112
|
-
default: boolean;
|
|
113
|
-
/** Full env var name. */
|
|
114
|
-
env: string;
|
|
115
|
-
}
|
|
116
|
-
/** Wire shape: the keyed spec plus its id, for GET /api/config/feature-flags. */
|
|
117
|
-
interface FeatureFlagDefinition extends FeatureFlagSpec {
|
|
118
|
-
/** Stable config/wire key. Used in server.yaml, on the CLI, and over HTTP. */
|
|
119
|
-
id: FeatureFlagId;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Values arriving from a config source. PARTIAL on purpose: server.yaml, the
|
|
123
|
-
* CLI and ServerConfig each speak about the flags they mention and stay silent
|
|
124
|
-
* on the rest, which is what lets the precedence chain fall through.
|
|
125
|
-
*/
|
|
126
|
-
type FeatureFlagValues = Partial<Record<FeatureFlagId, boolean>>;
|
|
127
|
-
/** Every flag, resolved. Total — see resolveFeatureFlags(). */
|
|
128
|
-
type ResolvedFeatureFlags = Record<FeatureFlagId, boolean>;
|
|
129
|
-
/**
|
|
130
|
-
* Which rung of the precedence chain decided a flag.
|
|
131
|
-
*
|
|
132
|
-
* Kept because the resolved boolean alone cannot answer "why is this on?", and
|
|
133
|
-
* that is the first question asked of a flag that is on when nobody expected it.
|
|
134
|
-
* `override` is the legacy ServerConfig field (see resolveFeatureFlags).
|
|
135
|
-
*/
|
|
136
|
-
type FeatureFlagSource = "override" | "env" | "cli" | "yaml" | "default";
|
|
137
|
-
/**
|
|
138
|
-
* The registry, keyed by flag name.
|
|
139
|
-
*
|
|
140
|
-
* Prefer `FEATURE_FLAGS.ptyHost` over a string lookup. A typo is a compile
|
|
141
|
-
* error; `findFeatureFlag("…")` is only for untrusted yaml/CLI tokens.
|
|
142
|
-
*
|
|
143
|
-
* `as const` + `keyof` is what makes `flags.ptyHsot` a compile error instead of
|
|
144
|
-
* `undefined`. A TS enum would add a runtime object without a stronger type.
|
|
145
|
-
*/
|
|
146
|
-
declare const FEATURE_FLAGS: {
|
|
147
|
-
readonly codexSystemPrompt: {
|
|
148
|
-
readonly description: string;
|
|
149
|
-
readonly default: false;
|
|
150
|
-
readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT";
|
|
151
|
-
};
|
|
152
|
-
readonly sessionRehydration: {
|
|
153
|
-
readonly description: string;
|
|
154
|
-
readonly default: true;
|
|
155
|
-
readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION";
|
|
156
|
-
};
|
|
157
|
-
readonly liveActivityPush: {
|
|
158
|
-
readonly description: string;
|
|
159
|
-
readonly default: false;
|
|
160
|
-
readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH";
|
|
161
|
-
};
|
|
162
|
-
readonly e2ee: {
|
|
163
|
-
readonly description: string;
|
|
164
|
-
readonly default: false;
|
|
165
|
-
readonly env: "THREADBASE_FEATURE_E2EE";
|
|
166
|
-
};
|
|
167
|
-
readonly ptyHost: {
|
|
168
|
-
readonly description: string;
|
|
169
|
-
readonly default: false;
|
|
170
|
-
readonly env: "THREADBASE_FEATURE_PTY_HOST";
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
/**
|
|
174
|
-
* The registry's ids as a union, derived rather than declared.
|
|
175
|
-
*
|
|
176
|
-
* This is what makes `flags.ptyHsot` a compile error instead of `undefined`.
|
|
177
|
-
* Under the old `Record<string, boolean>` an index signature accepted any key,
|
|
178
|
-
* so a typo in a consumer read as falsy and silently disabled the feature it
|
|
179
|
-
* was meant to gate — the exact failure the "total map" contract exists to
|
|
180
|
-
* prevent, reachable through the one door that contract left open.
|
|
181
|
-
*/
|
|
182
|
-
type FeatureFlagId = keyof typeof FEATURE_FLAGS;
|
|
183
|
-
|
|
184
|
-
declare const CLAUDE_CODE_PROVIDER: "claude-code";
|
|
185
|
-
declare const CODEX_CLI_PROVIDER: "codex-cli";
|
|
186
|
-
type ProviderName = typeof CLAUDE_CODE_PROVIDER | typeof CODEX_CLI_PROVIDER;
|
|
187
|
-
declare function isProviderName(value: unknown): value is ProviderName;
|
|
188
|
-
declare function isProviderResumable(_provider: string | null | undefined, availabilityResumable: boolean): boolean;
|
|
189
|
-
|
|
190
|
-
type SessionStatus = "running" | "waiting_input" | "idle";
|
|
191
|
-
/**
|
|
192
|
-
* Phase axis *inside* `status === "running"` — what the agent is doing during a
|
|
193
|
-
* turn. Deliberately a separate field rather than new SessionStatus members:
|
|
194
|
-
* VALID_STATUSES rejects unknown values and the store drops sessions outside
|
|
195
|
-
* the requested set, so a new status string would make those sessions vanish
|
|
196
|
-
* from already-shipped apps. Additive fields are safe; additive values in a
|
|
197
|
-
* union a shipped client filters on are not.
|
|
198
|
-
*
|
|
199
|
-
* The full set is defined here even though Codex only ever emits `working`
|
|
200
|
-
* (its status bar is binary — Ready/Working, and claiming otherwise would be
|
|
201
|
-
* invention). Defining it up front keeps a two-valued provider from fixing the
|
|
202
|
-
* field's shape before Claude's richer footer lands. Consumers must ignore an
|
|
203
|
-
* unrecognised value rather than coerce it.
|
|
204
|
-
*
|
|
205
|
-
* This union lives in exactly one place. Two independently-maintained copies of
|
|
206
|
-
* a TUI-derived grammar have already drifted once (tb-mobile PR #647).
|
|
207
|
-
*/
|
|
208
|
-
type AgentPhase = "thinking" | "streaming" | "hooks" | "acting" | "working";
|
|
209
|
-
/**
|
|
210
|
-
* Process-lifetime axis for a managed session (C1 durable session runtime).
|
|
211
|
-
* Orthogonal to SessionStatus — see SessionResponse.lifecycle for why the two
|
|
212
|
-
* are separate, and docs/architecture/2026-07-24-durable-session-runtime.md.
|
|
213
|
-
*/
|
|
214
|
-
type SessionLifecycle = "attached" | "starting" | "detached" | "orphaned" | "resumable" | "completed" | "failed";
|
|
215
|
-
/**
|
|
216
|
-
* How a SessionStatus was derived (C3).
|
|
217
|
-
* See docs/architecture/2026-07-24-session-state-confidence.md.
|
|
218
|
-
*
|
|
219
|
-
* The runners already compute this at every transition — it was written to a log
|
|
220
|
-
* line and discarded, so a status reached by a timer expiring was indistinguishable
|
|
221
|
-
* on the wire from one reached by observing a prompt marker.
|
|
222
|
-
*/
|
|
223
|
-
type StatusSource$1 = "spawn" | "prompt-marker" | "screen-marker" | "user-input" | "process-exit" | "timeout-fallback" | "quiet-fallback" | "shutdown";
|
|
224
|
-
/**
|
|
225
|
-
* How much to trust the status.
|
|
226
|
-
*
|
|
227
|
-
* `observed` — something in the stream or the process told us.
|
|
228
|
-
* `inferred` — a timer expired and we picked the most likely state.
|
|
229
|
-
*
|
|
230
|
-
* Deliberately two buckets rather than a numeric score: a percentage would imply
|
|
231
|
-
* a calibration we have no data to support. The point is that a guess must never
|
|
232
|
-
* be presented as an observation.
|
|
233
|
-
*/
|
|
234
|
-
type StatusConfidence = "observed" | "inferred";
|
|
235
|
-
/** Confidence implied by each source. Inference is exactly the timer-driven paths. */
|
|
236
|
-
declare function confidenceForSource(source: StatusSource$1): StatusConfidence;
|
|
237
|
-
interface ManagedSession {
|
|
238
|
-
id: string;
|
|
239
|
-
provider?: ProviderName;
|
|
240
|
-
projectId?: string;
|
|
241
|
-
projectPath: string;
|
|
242
|
-
projectName: string;
|
|
243
|
-
branch: string;
|
|
244
|
-
status: SessionStatus;
|
|
245
|
-
startedAt: Date;
|
|
246
|
-
completedAt: Date | null;
|
|
247
|
-
promptCount: number;
|
|
248
|
-
lastOutput: string;
|
|
249
|
-
failureReason?: string;
|
|
250
|
-
/**
|
|
251
|
-
* Machine-readable companion to `failureReason`, set only when the runner
|
|
252
|
-
* recognised the failure. Currently just `codex_active_writer` (Codex's
|
|
253
|
-
* single-writer lock), which the resume path maps to a structured 409 rather
|
|
254
|
-
* than reporting a spawn as successful. Absent for every other failure.
|
|
255
|
-
*/
|
|
256
|
-
failureCode?: string;
|
|
257
|
-
sessionName?: string;
|
|
258
|
-
model?: string;
|
|
259
|
-
account?: string;
|
|
260
|
-
messageCount?: number;
|
|
261
|
-
preview?: string;
|
|
262
|
-
firstMessageText?: string;
|
|
263
|
-
firstMessageAt?: Date;
|
|
264
|
-
lastMessageText?: string;
|
|
265
|
-
lastMessageAt?: Date;
|
|
266
|
-
lastActivityAt?: Date;
|
|
267
|
-
/**
|
|
268
|
-
* How `status` was derived, and when (C3). Confidence is derived from the
|
|
269
|
-
* source via confidenceForSource — storing both would let them disagree.
|
|
270
|
-
*/
|
|
271
|
-
statusSource?: StatusSource$1;
|
|
272
|
-
statusUpdatedAt?: Date;
|
|
273
|
-
/**
|
|
274
|
-
* Agent phase within a running turn, scraped from the rendered screen.
|
|
275
|
-
* Optional internally (every existing construction site predates it), but
|
|
276
|
-
* managedToResponse emits it unconditionally as `?? null` — on the wire
|
|
277
|
-
* absence must never be a third state, because the client merges session
|
|
278
|
-
* frames and a merge cannot express a removed key.
|
|
279
|
-
*
|
|
280
|
-
* Cleared in markReady() for the running -> waiting_input turn end, which is
|
|
281
|
-
* the only exit a runner observes on screen. Every other way out of `running`
|
|
282
|
-
* — handleExit, putOnHold, failStartup — is enforced by SessionStore
|
|
283
|
-
* .updateManaged() instead: a phase exists only while the status is
|
|
284
|
-
* `running`, so leaving it clears the field.
|
|
285
|
-
*/
|
|
286
|
-
subStatus?: AgentPhase | null;
|
|
287
|
-
filePath?: string;
|
|
288
|
-
resumedFromConversationId?: string;
|
|
289
|
-
/**
|
|
290
|
-
* This session was seeded from the durable registry at boot, not spawned by
|
|
291
|
-
* this run — there is no PTY behind it and never was one in this process.
|
|
292
|
-
*
|
|
293
|
-
* INTERNAL. It never reaches the wire: `managedToResponse` translates it into
|
|
294
|
-
* the already-defined `ownership: "historical"` + `lifecycle: "resumable"`
|
|
295
|
-
* pair, so no client needs to learn a new field to render a recovered session.
|
|
296
|
-
* Cleared implicitly when a resume overwrites the stub with a real session.
|
|
297
|
-
*/
|
|
298
|
-
rehydrated?: boolean;
|
|
299
|
-
/**
|
|
300
|
-
* This live session was re-adopted from the pty-host during boot rather than
|
|
301
|
-
* spawned by this streamer process. Internal lifecycle provenance only.
|
|
302
|
-
*/
|
|
303
|
-
reconciled?: boolean;
|
|
304
|
-
/**
|
|
305
|
-
* What this session was doing when the streamer stopped it, for a stub whose
|
|
306
|
-
* `status` had to flatten to `idle`. Only ever set from a registry row whose
|
|
307
|
-
* `status_source` is `shutdown` — the one source that means "we ended this",
|
|
308
|
-
* as opposed to the agent finishing or a crash freezing the row mid-turn.
|
|
309
|
-
*/
|
|
310
|
-
interruptedStatus?: "running" | "waiting_input";
|
|
311
|
-
/**
|
|
312
|
-
* Set once a live session's underlying persisted conversation file is
|
|
313
|
-
* discovered after the fact (currently: fresh Codex sessions, whose
|
|
314
|
-
* rollout id isn't known until the CLI creates its own JSONL). Distinct
|
|
315
|
-
* from `resumedFromConversationId` (resume flow) and `conversationId` on
|
|
316
|
-
* `SessionResponse` (stable mobile deep-link alias, always === id for the
|
|
317
|
-
* lifetime of a live PTY) — must never be written into either of those.
|
|
318
|
-
*/
|
|
319
|
-
boundConversationId?: string;
|
|
320
|
-
/**
|
|
321
|
-
* Source conversation this session was FORKED from (`codex fork`). Distinct
|
|
322
|
-
* from `resumedFromConversationId`: a resume continues one conversation, a
|
|
323
|
-
* fork starts a second one whose history diverges from the source at the fork
|
|
324
|
-
* point — and the source keeps its own owner, which is the entire point.
|
|
325
|
-
*/
|
|
326
|
-
forkedFromConversationId?: string;
|
|
327
|
-
/**
|
|
328
|
-
* Multi-agent mode only. Per-session in-memory LRU of progress event ids
|
|
329
|
-
* seen by the webhook receiver. Used to drop Temporal-replay duplicates
|
|
330
|
-
* before they reach the WebSocket. See spec §7.1.
|
|
331
|
-
*/
|
|
332
|
-
progressDedupeIds?: ProgressDedupeLRU;
|
|
333
|
-
/** Multi-agent: current stage of the active turn (advisory; advisory wire field). */
|
|
334
|
-
stage?: Stage | string;
|
|
335
|
-
/** Multi-agent: ms since the session last emitted a stage transition. */
|
|
336
|
-
stalledSinceMs?: number;
|
|
337
|
-
/** Multi-agent: 1 or 2 when stage === "rework". */
|
|
338
|
-
reworkAttempt?: number;
|
|
339
|
-
/**
|
|
340
|
-
* Multi-agent: id of the in-flight turn, or null when idle. Set when
|
|
341
|
-
* `POST /api/sessions/:id/input` accepts a request; cleared by the webhook
|
|
342
|
-
* receiver on stage=done or terminal_failure. Undefined in PTY mode.
|
|
343
|
-
*/
|
|
344
|
-
currentTurnId?: string | null;
|
|
345
|
-
/**
|
|
346
|
-
* Multi-agent resume: stable identity of the underlying conversation
|
|
347
|
-
* (the JSONL filename), distinct from `id` which is per-orchestrator-instance.
|
|
348
|
-
* Undefined in PTY mode (PTY uses `resumedFromConversationId` instead).
|
|
349
|
-
*/
|
|
350
|
-
conversationId?: string;
|
|
351
|
-
}
|
|
352
|
-
interface DiscoveredProcess {
|
|
353
|
-
pid: number;
|
|
354
|
-
projectPath: string;
|
|
355
|
-
projectName: string;
|
|
356
|
-
branch: string;
|
|
357
|
-
conversationId: string | null;
|
|
358
|
-
startedAt: Date;
|
|
359
|
-
}
|
|
360
|
-
interface AskOption {
|
|
361
|
-
label: string;
|
|
362
|
-
description: string;
|
|
363
|
-
preview?: string;
|
|
364
|
-
}
|
|
365
|
-
interface AskQuestion {
|
|
366
|
-
question: string;
|
|
367
|
-
header: string;
|
|
368
|
-
multiSelect: boolean;
|
|
369
|
-
options: AskOption[];
|
|
370
|
-
}
|
|
371
|
-
interface PermissionOption {
|
|
372
|
-
index: number;
|
|
373
|
-
label: string;
|
|
374
|
-
answerKeys?: string;
|
|
375
|
-
}
|
|
376
|
-
interface UserMessage {
|
|
377
|
-
text: string;
|
|
378
|
-
ts: number;
|
|
379
|
-
}
|
|
380
|
-
type WSMessage = {
|
|
381
|
-
type: "terminal_output";
|
|
382
|
-
sessionId: string;
|
|
383
|
-
data: string;
|
|
384
|
-
seq?: number;
|
|
385
|
-
} | {
|
|
386
|
-
type: "session_update";
|
|
387
|
-
session?: SessionResponse;
|
|
388
|
-
sessionId?: string;
|
|
389
|
-
turnId?: string;
|
|
390
|
-
stage?: Stage | string;
|
|
391
|
-
stalledSinceMs?: number;
|
|
392
|
-
reworkAttempt?: number;
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Agent phase changed within a running turn. Scoped to that session's
|
|
396
|
-
* subscribers, like terminal_output and user_message.
|
|
397
|
-
*
|
|
398
|
-
* A minimal frame rather than a SessionResponse copy, deliberately:
|
|
399
|
-
* managedToResponse recomputes `elapsedMs` from `new Date()` on every call
|
|
400
|
-
* for a live session, so a session copy would differ on every tick whether
|
|
401
|
-
* or not the phase changed — and a client that merges frames would get a
|
|
402
|
-
* fresh object identity several times a second, re-rendering every consumer
|
|
403
|
-
* for the whole turn.
|
|
404
|
-
*
|
|
405
|
-
* `phase` is always present and is `null` when there is no phase. Absence
|
|
406
|
-
* must never carry meaning: clients merge session state, and a merge cannot
|
|
407
|
-
* express a removed key, so an omitted field would keep its previous value
|
|
408
|
-
* and the indicator would latch on a finished turn.
|
|
409
|
-
*/
|
|
410
|
-
| {
|
|
411
|
-
type: "session_phase";
|
|
412
|
-
sessionId: string;
|
|
413
|
-
phase: AgentPhase | null;
|
|
414
|
-
updatedAt: string;
|
|
415
|
-
} | {
|
|
416
|
-
type: "session_list";
|
|
417
|
-
sessions: readonly SessionResponse[];
|
|
418
|
-
} | {
|
|
419
|
-
type: "conversation_event";
|
|
420
|
-
sessionId: string;
|
|
421
|
-
line: string;
|
|
422
|
-
} | {
|
|
423
|
-
type: "conversation_events";
|
|
424
|
-
sessionId: string;
|
|
425
|
-
lines: string[];
|
|
426
|
-
seqs?: (number | null)[];
|
|
427
|
-
} | {
|
|
428
|
-
type: "conversation_updated";
|
|
429
|
-
conversationId: string;
|
|
430
|
-
messageCount: number;
|
|
431
|
-
lastActivity: string;
|
|
432
|
-
ownership: "external" | "managed";
|
|
433
|
-
} | {
|
|
434
|
-
type: "question";
|
|
435
|
-
sessionId: string;
|
|
436
|
-
toolUseId: string;
|
|
437
|
-
questions: AskQuestion[];
|
|
438
|
-
} | {
|
|
439
|
-
type: "question_cancelled";
|
|
440
|
-
sessionId: string;
|
|
441
|
-
toolUseId: string;
|
|
442
|
-
} | {
|
|
443
|
-
type: "permission";
|
|
444
|
-
sessionId: string;
|
|
445
|
-
prompt?: string;
|
|
446
|
-
detail?: string;
|
|
447
|
-
options: PermissionOption[];
|
|
448
|
-
cursor?: number;
|
|
449
|
-
} | {
|
|
450
|
-
type: "permission_cancelled";
|
|
451
|
-
sessionId: string;
|
|
452
|
-
} | {
|
|
453
|
-
type: "ping";
|
|
454
|
-
ts: number;
|
|
455
|
-
} | {
|
|
456
|
-
type: "user_message";
|
|
457
|
-
sessionId: string;
|
|
458
|
-
text: string;
|
|
459
|
-
ts: number;
|
|
460
|
-
} | {
|
|
461
|
-
type: "terminal_replay";
|
|
462
|
-
sessionId: string;
|
|
463
|
-
lines: string[];
|
|
464
|
-
userMessages?: UserMessage[];
|
|
465
|
-
seq?: number;
|
|
466
|
-
} | {
|
|
467
|
-
type: "session_ready";
|
|
468
|
-
session: SessionResponse;
|
|
469
|
-
} | {
|
|
470
|
-
type: "agent_output";
|
|
471
|
-
sessionId: string;
|
|
472
|
-
turnId: string;
|
|
473
|
-
role: "worker" | "reviewer" | "signoff";
|
|
474
|
-
content: string;
|
|
475
|
-
partial?: boolean;
|
|
476
|
-
reviewerOverruled?: boolean;
|
|
477
|
-
stage?: Stage | string;
|
|
478
|
-
reworkAttempt?: number;
|
|
479
|
-
} | {
|
|
480
|
-
type: "turn_failure";
|
|
481
|
-
sessionId: string;
|
|
482
|
-
turnId: string;
|
|
483
|
-
reason: string;
|
|
484
|
-
} | {
|
|
485
|
-
type: "cache_ready";
|
|
486
|
-
} | {
|
|
487
|
-
type: "scan_progress";
|
|
488
|
-
scanned: number;
|
|
489
|
-
total: number;
|
|
490
|
-
} | {
|
|
491
|
-
type: "cache_alert";
|
|
492
|
-
fingerprint: string;
|
|
493
|
-
severity: "high" | "low";
|
|
494
|
-
missingCount: number;
|
|
495
|
-
totalRows: number;
|
|
496
|
-
detectedAt: string;
|
|
497
|
-
sample: {
|
|
498
|
-
id: string;
|
|
499
|
-
title?: string;
|
|
500
|
-
}[];
|
|
501
|
-
} | {
|
|
502
|
-
type: "cache_alert_resolved";
|
|
503
|
-
fingerprint: string;
|
|
504
|
-
action: CacheAlertResolveAction;
|
|
505
|
-
};
|
|
506
|
-
/** The four cache-integrity resolution actions (POST /api/cache/alert/resolve). */
|
|
507
|
-
type CacheAlertResolveAction = "prune_all" | "prune_selected" | "ignore" | "reset_rescan";
|
|
508
|
-
type ServerWarmupState = "startup" | "cache_reset" | "conversation_refresh";
|
|
509
|
-
interface ServerWarmingUpResponse {
|
|
510
|
-
error: "Server is warming up";
|
|
511
|
-
code: "SERVER_WARMING_UP";
|
|
512
|
-
warmupState: ServerWarmupState;
|
|
513
|
-
}
|
|
514
|
-
interface SessionResponse {
|
|
515
|
-
id: string;
|
|
516
|
-
conversationId: string;
|
|
517
|
-
provider?: ProviderName;
|
|
518
|
-
projectId?: string;
|
|
519
|
-
status: SessionStatus;
|
|
520
|
-
projectPath: string;
|
|
521
|
-
projectName: string;
|
|
522
|
-
branch: string;
|
|
523
|
-
lastOutput: string;
|
|
524
|
-
elapsedMs: number;
|
|
525
|
-
promptCount: number;
|
|
526
|
-
startedAt: string;
|
|
527
|
-
completedAt: string | null;
|
|
528
|
-
ptyAttached: boolean;
|
|
529
|
-
/**
|
|
530
|
-
* Process-lifetime axis, orthogonal to `status` (C1).
|
|
531
|
-
*
|
|
532
|
-
* `status` answers "what is the agent doing" (running / waiting_input /
|
|
533
|
-
* idle); `lifecycle` answers "does this process still exist and do we own
|
|
534
|
-
* it". They were conflated before: `idle` meant finished, killed-to-save-
|
|
535
|
-
* resources, and externally-discovered all at once, so a client could not
|
|
536
|
-
* tell a completed session from one we terminated.
|
|
537
|
-
*
|
|
538
|
-
* Additive and optional — `ptyAttached` keeps its meaning (=== "attached"),
|
|
539
|
-
* so a client that ignores this behaves exactly as it did before. `"starting"`
|
|
540
|
-
* is additive in the same way: a session we hold no PTY for and have observed
|
|
541
|
-
* no exit for reports it instead of the `"completed"` it used to, so a client
|
|
542
|
-
* can tell "not attached yet" from "ended" (tb-mobile #508).
|
|
543
|
-
*/
|
|
544
|
-
/**
|
|
545
|
-
* How `status` was derived and how far to trust it (C3). Additive: `status`
|
|
546
|
-
* keeps its exact meaning, so a client ignoring these behaves as before.
|
|
547
|
-
* An `inferred` confidence means a timer expired and we assumed — not that
|
|
548
|
-
* anything in the stream confirmed the state.
|
|
549
|
-
*/
|
|
550
|
-
statusSource?: StatusSource$1;
|
|
551
|
-
statusConfidence?: StatusConfidence;
|
|
552
|
-
statusUpdatedAt?: string;
|
|
553
|
-
lifecycle?: SessionLifecycle;
|
|
554
|
-
/** How `lifecycle` was determined, so stale values are visible not implied. */
|
|
555
|
-
lifecycleSource?: "spawn" | "exit" | "probe" | "reconcile";
|
|
556
|
-
lifecycleUpdatedAt?: string;
|
|
557
|
-
/**
|
|
558
|
-
* ADDITIVE (plan Phase 5). What a recovered session was doing when the
|
|
559
|
-
* streamer stopped it, so a client can say "interrupted mid-response" instead
|
|
560
|
-
* of the `idle` its `status` is forced to report — a stub holds no PTY, and a
|
|
561
|
-
* novel SessionStatus value would be dropped by `?status=` filtering on
|
|
562
|
-
* already-shipped clients. Present only on rows whose `status_source` was
|
|
563
|
-
* `shutdown`; older clients ignore it.
|
|
564
|
-
*/
|
|
565
|
-
interruptedStatus?: "running" | "waiting_input";
|
|
566
|
-
failureReason?: string;
|
|
567
|
-
pid?: number;
|
|
568
|
-
sessionName?: string;
|
|
569
|
-
model?: string;
|
|
570
|
-
/**
|
|
571
|
-
* Reasoning-effort tier scraped from the live PTY status line (e.g. "high").
|
|
572
|
-
* Live sessions only — absent for historical/resumable conversations.
|
|
573
|
-
*/
|
|
574
|
-
effort?: string;
|
|
575
|
-
/**
|
|
576
|
-
* Active permission mode from the live PTY status line (e.g. "accept edits on").
|
|
577
|
-
* Live sessions only.
|
|
578
|
-
*/
|
|
579
|
-
permissionMode?: string;
|
|
580
|
-
/**
|
|
581
|
-
* Agent phase within a running turn, scraped from the rendered PTY screen.
|
|
582
|
-
*
|
|
583
|
-
* NOT optional, and always serialised — `null` when there is no phase. A
|
|
584
|
-
* client that merges session frames (`{...prev, ...next}`) cannot express a
|
|
585
|
-
* removed key, so an omitted field would keep its previous value and the
|
|
586
|
-
* indicator would latch on a finished turn. That is the bug tb-mobile PR #647
|
|
587
|
-
* shipped; absence must never carry meaning here.
|
|
588
|
-
*
|
|
589
|
-
* Consequently this must NOT be moved into the `...(x != null && { x })`
|
|
590
|
-
* guard block in managedToResponse: `!= null` catches null and undefined
|
|
591
|
-
* alike and would convert an explicit clear back into absence.
|
|
592
|
-
*/
|
|
593
|
-
subStatus: AgentPhase | null;
|
|
594
|
-
account?: string;
|
|
595
|
-
messageCount?: number;
|
|
596
|
-
preview?: string;
|
|
597
|
-
firstMessageText?: string;
|
|
598
|
-
firstMessageAt?: string;
|
|
599
|
-
lastMessageText?: string;
|
|
600
|
-
lastMessageAt?: string;
|
|
601
|
-
lastActivityAt?: string;
|
|
602
|
-
filePath?: string;
|
|
603
|
-
resumedFromConversationId?: string;
|
|
604
|
-
/** See `ManagedSession.forkedFromConversationId`. Additive; older clients ignore it. */
|
|
605
|
-
forkedFromConversationId?: string;
|
|
606
|
-
/** See `ManagedSession.boundConversationId` — never repurposes `conversationId`. */
|
|
607
|
-
boundConversationId?: string;
|
|
608
|
-
/**
|
|
609
|
-
* Who owns the underlying process. Additive — older clients ignore it, and it
|
|
610
|
-
* deliberately does NOT introduce a new `status` value: `VALID_STATUSES`
|
|
611
|
-
* rejects unknown values in `?status=` and the store drops sessions outside
|
|
612
|
-
* the requested set, so a new status string would make these sessions vanish
|
|
613
|
-
* from already-shipped apps.
|
|
614
|
-
* managed — this streamer spawned and holds the PTY
|
|
615
|
-
* external — a process we discovered but do not own (read-only)
|
|
616
|
-
* historical — a cached conversation, no process known
|
|
617
|
-
*/
|
|
618
|
-
ownership?: SessionOwnership;
|
|
619
|
-
/**
|
|
620
|
-
* Whether the process is believed to be running. Only ever "alive" when
|
|
621
|
-
* discovery actually saw it; never guessed from file activity.
|
|
622
|
-
*/
|
|
623
|
-
processLiveness?: ProcessLiveness;
|
|
624
|
-
/**
|
|
625
|
-
* INFERRED from JSONL writes, never authoritative: "active_writing" means the
|
|
626
|
-
* transcript grew recently, which cannot distinguish a generating agent from
|
|
627
|
-
* one blocked on a permission gate (gates are screen-only). Absent for
|
|
628
|
-
* sessions we own — their `status` is the authoritative signal.
|
|
629
|
-
*/
|
|
630
|
-
activity?: SessionActivity;
|
|
631
|
-
}
|
|
632
|
-
type SessionOwnership = "managed" | "external" | "historical";
|
|
633
|
-
type ProcessLiveness = "alive" | "gone" | "unknown";
|
|
634
|
-
interface SessionActivity {
|
|
635
|
-
state: "active_writing" | "quiet";
|
|
636
|
-
lastEventAt: string;
|
|
637
|
-
source: "jsonl";
|
|
638
|
-
}
|
|
639
|
-
interface ConversationListResponse {
|
|
640
|
-
conversations: unknown[];
|
|
641
|
-
hasMore: boolean;
|
|
642
|
-
offset: number;
|
|
643
|
-
total: number;
|
|
644
|
-
}
|
|
645
|
-
type SessionSortKey = "startedAt" | "lastActivityAt" | "projectName" | "status";
|
|
646
|
-
type SortOrder = "asc" | "desc";
|
|
647
|
-
interface SessionListPage {
|
|
648
|
-
/** Response copies, like `SessionStore.list()` — see the note there. */
|
|
649
|
-
sessions: readonly Readonly<SessionResponse>[];
|
|
650
|
-
nextCursor: string | null;
|
|
651
|
-
total: number;
|
|
652
|
-
}
|
|
653
|
-
interface SessionListQuery {
|
|
654
|
-
limit: number;
|
|
655
|
-
cursor?: string;
|
|
656
|
-
sortBy: SessionSortKey;
|
|
657
|
-
order: SortOrder;
|
|
658
|
-
status?: SessionStatus[];
|
|
659
|
-
}
|
|
660
|
-
interface SessionCursor {
|
|
661
|
-
k: string | number;
|
|
662
|
-
id: string;
|
|
663
|
-
}
|
|
664
|
-
interface ServerConfig {
|
|
665
|
-
port: number;
|
|
666
|
-
host?: string;
|
|
667
|
-
apiKey?: string;
|
|
668
|
-
/** 'cli' when --api-key was passed; rotation persists in-memory only and reverts on restart */
|
|
669
|
-
apiKeySource?: "config" | "cli";
|
|
670
|
-
localNoAuth?: boolean;
|
|
671
|
-
verbose?: boolean;
|
|
672
|
-
logMenubarRequests?: boolean;
|
|
673
|
-
browseRoot?: string;
|
|
674
|
-
publicUrl?: string;
|
|
675
|
-
browserCors?: string;
|
|
676
|
-
disableDb?: boolean;
|
|
677
|
-
scanProfiles?: Array<{
|
|
678
|
-
id: string;
|
|
679
|
-
label: string;
|
|
680
|
-
configDir: string;
|
|
681
|
-
enabled: boolean;
|
|
682
|
-
emoji: string;
|
|
683
|
-
}>;
|
|
684
|
-
codexRoots?: string[];
|
|
685
|
-
scannerPersistent?: boolean;
|
|
686
|
-
skipStartupWarmup?: boolean;
|
|
687
|
-
ptyGracePeriodMs?: number;
|
|
688
|
-
/**
|
|
689
|
-
* Re-start interrupted sessions at boot instead of listing them for the user
|
|
690
|
-
* to tap (plan Phase 7). A boolean here rather than the tri-state the loader
|
|
691
|
-
* returns: by the time a ServerConfig is built the "never asked" case has
|
|
692
|
-
* already resolved to false. Precedence: explicit here → `auto_resume_on_boot`
|
|
693
|
-
* in server.yaml → false. Never enabled implicitly — it is the one setting
|
|
694
|
-
* that starts an agent nobody asked for in that moment.
|
|
695
|
-
*/
|
|
696
|
-
autoResumeOnBoot?: boolean;
|
|
697
|
-
cacheDir?: string;
|
|
698
|
-
runtimeDbPath?: string;
|
|
699
|
-
tailSize?: number;
|
|
700
|
-
directoryScanDebounceMs?: number;
|
|
701
|
-
defaultSystemPrompt?: string;
|
|
702
|
-
codexSystemPromptEnabled?: boolean;
|
|
703
|
-
featureFlags?: FeatureFlagValues;
|
|
704
|
-
defaultPermissionMode?: PermissionMode;
|
|
705
|
-
defaultModel?: string;
|
|
706
|
-
defaultEffort?: EffortLevel;
|
|
707
|
-
claudeFlags?: ClaudeFlagValues;
|
|
708
|
-
claudeExtraArgs?: string;
|
|
709
|
-
}
|
|
710
|
-
interface PTYManagerOptions {
|
|
711
|
-
onOutput?: (sessionId: string, data: string) => void;
|
|
712
|
-
onStatusChange?: (session: ManagedSession) => void;
|
|
713
|
-
onReady?: (session: ManagedSession) => void;
|
|
714
|
-
onPermissionChange?: (sessionId: string, gate: {
|
|
715
|
-
prompt?: string;
|
|
716
|
-
detail?: string;
|
|
717
|
-
options: PermissionOption[];
|
|
718
|
-
cursor?: number;
|
|
719
|
-
} | null) => void;
|
|
720
|
-
/**
|
|
721
|
-
* Fired when the agent's phase within a running turn changes, including to
|
|
722
|
-
* `null` at turn end. Additive; absent in tests that omit it.
|
|
723
|
-
*
|
|
724
|
-
* Deliberately NOT routed through onStatusChange, even though that callback
|
|
725
|
-
* already exists and is already relayed across the pty-host boundary. Its
|
|
726
|
-
* handler writes a DB row per invocation with no same-status guard, refreshes
|
|
727
|
-
* the scanner index, broadcasts globally, and pokes the APNs and push
|
|
728
|
-
* notifiers — machinery built for a handful of transitions per session, not
|
|
729
|
-
* for a signal that can fire every SCRAPE_THROTTLE_MS.
|
|
730
|
-
*
|
|
731
|
-
* The server must broadcast this to that session's subscribers only
|
|
732
|
-
* (wsHub.broadcastToClients), as a minimal frame rather than a SessionResponse
|
|
733
|
-
* copy: managedToResponse recomputes elapsedMs on every call, so a session
|
|
734
|
-
* copy would differ every tick and re-render every client consumer of that
|
|
735
|
-
* session for the whole turn.
|
|
736
|
-
*/
|
|
737
|
-
onPhaseChange?: (sessionId: string, phase: AgentPhase | null) => void;
|
|
738
|
-
onLiveQuestion?: (sessionId: string, questions: AskQuestion[]) => void;
|
|
739
|
-
onLiveQuestionGone?: (sessionId: string) => void;
|
|
740
|
-
onUserMessage?: (sessionId: string, text: string, ts: number) => void;
|
|
741
|
-
logger?: Logger;
|
|
742
|
-
}
|
|
743
|
-
interface StartSessionOptions {
|
|
744
|
-
projectPath: string;
|
|
745
|
-
projectName?: string;
|
|
746
|
-
branch?: string;
|
|
747
|
-
/**
|
|
748
|
-
* Provider-side id to resume from, when it differs from `sessionId`. Codex
|
|
749
|
-
* keys a fresh session by a local placeholder UUID and only learns its real
|
|
750
|
-
* rollout id once it writes the file, so resuming needs the rollout id in
|
|
751
|
-
* argv while the session keeps the placeholder the client navigated to.
|
|
752
|
-
* Absent means "resume by the session id", which is what every Claude session
|
|
753
|
-
* does — PTYManager ignores this field entirely.
|
|
754
|
-
*/
|
|
755
|
-
resumeId?: string;
|
|
756
|
-
permissionMode?: PermissionMode;
|
|
757
|
-
model?: string;
|
|
758
|
-
effort?: EffortLevel;
|
|
759
|
-
claudeFlags?: ClaudeFlagValues;
|
|
760
|
-
claudeExtraArgs?: string;
|
|
761
|
-
}
|
|
762
|
-
interface StartForkSessionOptions {
|
|
763
|
-
/**
|
|
764
|
-
* Provider-side id of the conversation to fork FROM (for Codex, the rollout
|
|
765
|
-
* id — never a local placeholder). The forked session gets its own id; the
|
|
766
|
-
* two identities are deliberately kept distinct.
|
|
767
|
-
*/
|
|
768
|
-
forkFromId: string;
|
|
769
|
-
projectPath: string;
|
|
770
|
-
projectName?: string;
|
|
771
|
-
branch?: string;
|
|
772
|
-
}
|
|
773
|
-
interface StartFreshSessionOptions {
|
|
774
|
-
projectPath: string;
|
|
775
|
-
projectName?: string;
|
|
776
|
-
systemPrompt?: string;
|
|
777
|
-
permissionMode?: PermissionMode;
|
|
778
|
-
model?: string;
|
|
779
|
-
effort?: EffortLevel;
|
|
780
|
-
claudeFlags?: ClaudeFlagValues;
|
|
781
|
-
claudeExtraArgs?: string;
|
|
782
|
-
}
|
|
783
|
-
interface SessionRunner {
|
|
784
|
-
start(sessionId: string, options: StartSessionOptions): Promise<ManagedSession>;
|
|
785
|
-
startFresh(options: StartFreshSessionOptions): Promise<ManagedSession>;
|
|
786
|
-
sendInput(sessionId: string, input: string): number;
|
|
787
|
-
sendKeys(sessionId: string, keys: string): void;
|
|
788
|
-
cancel(sessionId: string): void;
|
|
789
|
-
killPid(pid: number): void;
|
|
790
|
-
putOnHold(sessionId: string): void;
|
|
791
|
-
getOutput(sessionId: string): string;
|
|
792
|
-
getOutputLines(sessionId: string, maxLines: number): Promise<string[]>;
|
|
793
|
-
getInputHistory(sessionId: string): UserMessage[];
|
|
794
|
-
getPid(sessionId: string): number | null;
|
|
795
|
-
getSession(sessionId: string): ManagedSession | null;
|
|
796
|
-
hasSession(sessionId: string): boolean;
|
|
797
|
-
listSessions(): ManagedSession[];
|
|
798
|
-
dispose(): void;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Scoped device capabilities (C5).
|
|
803
|
-
* See docs/architecture/2026-07-24-device-identity-and-capabilities.md.
|
|
804
|
-
*
|
|
805
|
-
* Authorization was all-or-nothing: authMiddleware asked one question — is this
|
|
806
|
-
* token the API key — and answered 401 or full access. A device paired merely to
|
|
807
|
-
* glance at session status held exactly the authority of the one driving the
|
|
808
|
-
* agent, because there was only ever one credential and no principal to scope.
|
|
809
|
-
*/
|
|
810
|
-
declare const CAPABILITIES: readonly ["history:read", "session:control", "fs:browse", "fs:upload", "notifications", "admin"];
|
|
811
|
-
type Capability = (typeof CAPABILITIES)[number];
|
|
812
|
-
type CapabilityPreset = "full" | "read-only";
|
|
813
|
-
/**
|
|
814
|
-
* The principal behind a request.
|
|
815
|
-
*
|
|
816
|
-
* `legacy` is the shared API key: it predates device identity, so it carries the
|
|
817
|
-
* full preset and no device id. Keeping it working is what lets this ship
|
|
818
|
-
* without breaking every already-paired device.
|
|
819
|
-
*/
|
|
820
|
-
interface Principal {
|
|
821
|
-
kind: "device" | "legacy";
|
|
822
|
-
deviceId?: string;
|
|
823
|
-
capabilities: Capability[];
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
interface LineSpan {
|
|
827
|
-
/** Absolute byte offset of the line's first byte in the file. */
|
|
828
|
-
byteOffset: number;
|
|
829
|
-
/** Byte length of the line content, excluding the trailing "\n". */
|
|
830
|
-
byteLength: number;
|
|
831
|
-
/** The decoded line text (no trailing "\n"). */
|
|
832
|
-
text: string;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
interface ConversationCacheOptions {
|
|
836
|
-
filterAgentConversations?: boolean;
|
|
837
|
-
agentEntrypoints?: ReadonlySet<string>;
|
|
838
|
-
onAgentFileDetected?: (filePath: string) => void;
|
|
839
|
-
}
|
|
840
|
-
interface ConversationListItem {
|
|
841
|
-
id: string;
|
|
842
|
-
filePath: string;
|
|
843
|
-
projectId: string | null;
|
|
844
|
-
projectPath: string | null;
|
|
845
|
-
projectName: string | null;
|
|
846
|
-
title: string | null;
|
|
847
|
-
model: string | null;
|
|
848
|
-
account: string | null;
|
|
849
|
-
branch: string | null;
|
|
850
|
-
messageCount: number;
|
|
851
|
-
lastActivity: string;
|
|
852
|
-
firstMessage: string | null;
|
|
853
|
-
lastMessage: string | null;
|
|
854
|
-
preview: string | null;
|
|
855
|
-
source: string | null;
|
|
856
|
-
provider: "claude-code" | "codex-cli";
|
|
857
|
-
}
|
|
858
|
-
interface CachedTailMessage {
|
|
859
|
-
role: string;
|
|
860
|
-
timestamp: string;
|
|
861
|
-
text: string;
|
|
862
|
-
content?: unknown[];
|
|
863
|
-
}
|
|
864
|
-
interface CachedTail {
|
|
865
|
-
conversationId: string;
|
|
866
|
-
messages: CachedTailMessage[];
|
|
867
|
-
tailSize: number;
|
|
868
|
-
}
|
|
869
|
-
/** A row of `conversation_file_state` — per-file offset-index resume state. */
|
|
870
|
-
interface FileStateRow {
|
|
871
|
-
path: string;
|
|
872
|
-
identity: string;
|
|
873
|
-
size: number;
|
|
874
|
-
mtime_ms: number;
|
|
875
|
-
byte_offset: number;
|
|
876
|
-
last_message_index: number;
|
|
877
|
-
}
|
|
878
|
-
/** A row of `conversation_message_index` — one indexed message's byte span. */
|
|
879
|
-
interface MessageIndexRow {
|
|
880
|
-
conversation_id: string;
|
|
881
|
-
message_index: number;
|
|
882
|
-
byte_offset: number;
|
|
883
|
-
byte_length: number;
|
|
884
|
-
uuid: string | null;
|
|
885
|
-
role: string | null;
|
|
886
|
-
ts: number | null;
|
|
887
|
-
}
|
|
888
|
-
interface ScannerMeta {
|
|
889
|
-
id: string;
|
|
890
|
-
sessionId?: string;
|
|
891
|
-
filePath: string;
|
|
892
|
-
projectPath?: string;
|
|
893
|
-
projectName?: string;
|
|
894
|
-
title?: string;
|
|
895
|
-
sessionName?: string;
|
|
896
|
-
model?: string;
|
|
897
|
-
account?: string;
|
|
898
|
-
gitBranch?: string;
|
|
899
|
-
messageCount?: number;
|
|
900
|
-
timestamp?: string;
|
|
901
|
-
firstMessage?: unknown;
|
|
902
|
-
lastMessage?: unknown;
|
|
903
|
-
preview?: string;
|
|
904
|
-
provider?: "claude-code" | "codex-cli";
|
|
905
|
-
}
|
|
906
|
-
declare class ConversationCache {
|
|
907
|
-
private db;
|
|
908
|
-
private tailSize;
|
|
909
|
-
private fileIndex;
|
|
910
|
-
private fileIndexLoaded;
|
|
911
|
-
private indexParseState;
|
|
912
|
-
private backfillInFlight;
|
|
913
|
-
private tailSeq;
|
|
914
|
-
private nameSeq;
|
|
915
|
-
private stmts;
|
|
916
|
-
private migrationsDir?;
|
|
917
|
-
private filterAgentConversations;
|
|
918
|
-
private agentEntrypoints;
|
|
919
|
-
private onAgentFileDetected?;
|
|
920
|
-
private constructor();
|
|
921
|
-
/**
|
|
922
|
-
* Expose the underlying handle so projects/cache_metadata repositories can
|
|
923
|
-
* share the same connection. Internal API; not part of the public surface.
|
|
924
|
-
*/
|
|
925
|
-
getDatabase(): Database.Database;
|
|
926
|
-
getFileState(path: string): FileStateRow | null;
|
|
927
|
-
upsertFileState(row: FileStateRow): void;
|
|
928
|
-
/** Drop a file's index rows + file_state (truncation / identity change). */
|
|
929
|
-
deleteFileIndex(path: string, conversationId: string): void;
|
|
930
|
-
/** Append/replace index rows in one transaction. */
|
|
931
|
-
appendMessageIndexRows(rows: MessageIndexRow[]): void;
|
|
932
|
-
/** Rows for message_index in [fromIndex, toIndex), ordered ascending. */
|
|
933
|
-
getMessageIndexWindow(conversationId: string, fromIndex: number, toIndex: number): MessageIndexRow[];
|
|
934
|
-
getIndexedMessageCount(conversationId: string): number;
|
|
935
|
-
/**
|
|
936
|
-
* Conversation id for a JSONL path — the filename stem (matches the pseudo-id
|
|
937
|
-
* updateFromLine derives and the uuid the detail read path resolves). The
|
|
938
|
-
* offset index keys on this so the window select and the cursor agree.
|
|
939
|
-
*/
|
|
940
|
-
static conversationIdForFile(filePath: string): string;
|
|
941
|
-
private isIndexableFile;
|
|
942
|
-
/**
|
|
943
|
-
* Incremental offset-index writer: extend the index for a burst of appended
|
|
944
|
-
* lines (one watcher read) using their byte spans. Each line is classified
|
|
945
|
-
* with the scanner's parseJsonlLine (a running per-file reducer state), so the
|
|
946
|
-
* message ordering can never drift from the scanner's. Message lines get an
|
|
947
|
-
* index row at the next message_index; non-message lines (summary/sidecar)
|
|
948
|
-
* get no row but still advance byte_offset. file_state is updated to the end
|
|
949
|
-
* of the last consumed span.
|
|
950
|
-
*
|
|
951
|
-
* Requires an up-to-date `stat` (identity/size/mtime) for the file so the read
|
|
952
|
-
* path can detect truncation/replacement.
|
|
953
|
-
*
|
|
954
|
-
* `readFrom` is the absolute byte offset the watcher read started at, and
|
|
955
|
-
* `endOffset` is where it ended (readFrom + consumed, i.e. the watcher's new
|
|
956
|
-
* entry.offset). CONTIGUITY GUARD: the read must begin exactly where the index
|
|
957
|
-
* left off (`readFrom === existing.byte_offset`, or 0 with no state). If it
|
|
958
|
-
* doesn't — the watcher attached at EOF after the server was down, or an
|
|
959
|
-
* append raced an in-flight backfill — extending would assign wrong
|
|
960
|
-
* message_index values over a hole. In that case this writes nothing and
|
|
961
|
-
* returns null so the caller drops the index and backfills.
|
|
962
|
-
*
|
|
963
|
-
* On success returns the message_index assigned to each input span (null for a
|
|
964
|
-
* non-message line) so the caller can stamp WS `seq`. Empty array when spans
|
|
965
|
-
* is empty. `endOffset` is stored verbatim as byte_offset so the watcher's
|
|
966
|
-
* offset and file_state.byte_offset are the same number by construction.
|
|
967
|
-
*/
|
|
968
|
-
extendMessageIndex(filePath: string, spans: LineSpan[], stat: Stats, readFrom: number, endOffset: number): (number | null)[] | null;
|
|
969
|
-
clearIndexParseState(filePath: string): void;
|
|
970
|
-
/**
|
|
971
|
-
* On-demand full backfill of the offset index for a file with no/stale
|
|
972
|
-
* file_state (cold conversation, or after a truncation/replacement). Rebuilds
|
|
973
|
-
* from byte 0: drops any existing rows, walks the whole file in chunks with a
|
|
974
|
-
* running parse state, yields to the event loop every ~1000 lines so a large
|
|
975
|
-
* file never blocks, and writes index rows + file_state.
|
|
976
|
-
*
|
|
977
|
-
* Single-flighted per path: concurrent callers await the same walk. The
|
|
978
|
-
* triggering detail request is served by the scanner fallback while this runs.
|
|
979
|
-
*/
|
|
980
|
-
backfillIndex(filePath: string): Promise<void>;
|
|
981
|
-
private runBackfill;
|
|
982
|
-
/**
|
|
983
|
-
* Windowed detail read straight from the offset index — the hot path.
|
|
984
|
-
* Returns the parsed messages for message_index in [fromIndex, toIndex) plus
|
|
985
|
-
* the total indexed count, or null when the index can't serve this file (no
|
|
986
|
-
* file_state, identity/size mismatch = truncation/replacement, or cold index)
|
|
987
|
-
* so the caller falls back to the scanner and enqueues a backfill.
|
|
988
|
-
*
|
|
989
|
-
* On a match it SQL-selects the window's byte ranges and preads exactly those
|
|
990
|
-
* ranges from the JSONL (never the whole file), parsing only the sliced lines.
|
|
991
|
-
* Returns messages in the same ConversationMessage shape parseJsonlLine
|
|
992
|
-
* produces during a scan, so the payload is identical to the scanner path.
|
|
993
|
-
*/
|
|
994
|
-
readMessageWindow(filePath: string, fromIndex: number, toIndex: number): {
|
|
995
|
-
messages: ConversationMessage[];
|
|
996
|
-
total: number;
|
|
997
|
-
fromIndex: number;
|
|
998
|
-
} | null;
|
|
999
|
-
private agentEntrypointsKey;
|
|
1000
|
-
private classifyAgentFile;
|
|
1001
|
-
isAgentFileCached(filePath: string): boolean;
|
|
1002
|
-
static open(dbPath: string, tailSize?: number, migrationsDir?: string, options?: ConversationCacheOptions): ConversationCache;
|
|
1003
|
-
close(): void;
|
|
1004
|
-
getPopularProjects(limit: number): Array<{
|
|
1005
|
-
path: string;
|
|
1006
|
-
name: string;
|
|
1007
|
-
sessionCount: number;
|
|
1008
|
-
}>;
|
|
1009
|
-
/** Every project with at least one cached conversation, most recently active
|
|
1010
|
-
* first. Paths are the raw `project_path` values, which is what
|
|
1011
|
-
* /api/conversations?project= matches on exactly — so a summary row is
|
|
1012
|
-
* always joinable against the page it describes. */
|
|
1013
|
-
listProjectSummaries(opts: {
|
|
1014
|
-
limit: number;
|
|
1015
|
-
offset: number;
|
|
1016
|
-
}): {
|
|
1017
|
-
projects: Array<{
|
|
1018
|
-
path: string;
|
|
1019
|
-
name: string;
|
|
1020
|
-
conversationCount: number;
|
|
1021
|
-
lastActivity: string;
|
|
1022
|
-
}>;
|
|
1023
|
-
total: number;
|
|
1024
|
-
};
|
|
1025
|
-
private ensureFileIndex;
|
|
1026
|
-
updateFromLine(filePath: string, rawLine: string): void;
|
|
1027
|
-
/**
|
|
1028
|
-
* Batched form of updateFromLine: applies a burst of newly-appended lines
|
|
1029
|
-
* (one chokidar read) in a single transaction with one message_count bump,
|
|
1030
|
-
* one meta write, and one tail read/write — instead of 2-4 synchronous
|
|
1031
|
-
* writes per line. Semantics are identical to replaying each line through
|
|
1032
|
-
* updateFromLine in order: the agent filter short-circuits the whole batch,
|
|
1033
|
-
* project context is backfilled last-wins, message_count increases by the
|
|
1034
|
-
* number of surviving message lines, and last_activity/last_message reflect
|
|
1035
|
-
* the newest message line by timestamp (a monotonic guard keeps them from
|
|
1036
|
-
* moving backward when an interleaved writer appends an older line — P0.3).
|
|
1037
|
-
*/
|
|
1038
|
-
updateFromLines(filePath: string, rawLines: string[]): void;
|
|
1039
|
-
upsertFromScannerMeta(metas: ScannerMeta[]): string[];
|
|
1040
|
-
deleteByFilePath(filePath: string): boolean;
|
|
1041
|
-
populateTailFromFile(convId: string, filePath: string): boolean;
|
|
1042
|
-
listConversations(opts: {
|
|
1043
|
-
project?: string;
|
|
1044
|
-
provider?: string;
|
|
1045
|
-
limit: number;
|
|
1046
|
-
offset: number;
|
|
1047
|
-
}): {
|
|
1048
|
-
conversations: ConversationListItem[];
|
|
1049
|
-
total: number;
|
|
1050
|
-
};
|
|
1051
|
-
/** Returns a map of filePath → { mtimeMs, size } for all rows that have
|
|
1052
|
-
* stat data stored. Used by the server to build the statCache passed to
|
|
1053
|
-
* ConversationScanner.scan() so unchanged files are skipped. */
|
|
1054
|
-
getFileStats(): Map<string, {
|
|
1055
|
-
mtimeMs: number;
|
|
1056
|
-
size: number;
|
|
1057
|
-
}>;
|
|
1058
|
-
getScannerStatCache(): Map<string, {
|
|
1059
|
-
stat: FileStatEntry;
|
|
1060
|
-
meta: ConversationMeta;
|
|
1061
|
-
}>;
|
|
1062
|
-
/**
|
|
1063
|
-
* Conversation id for a JSONL path, or null when no row exists yet. Resolves
|
|
1064
|
-
* by file_path (NOT conversationIdForFile) so codex rollout files — named
|
|
1065
|
-
* rollout-<ts>-<uuid>.jsonl, whose stem is not the row id — resolve correctly.
|
|
1066
|
-
*/
|
|
1067
|
-
getIdByFilePath(filePath: string): string | null;
|
|
1068
|
-
getMetaById(id: string): ConversationListItem | null;
|
|
1069
|
-
setConversationProjectId(conversationId: string, projectId: string): void;
|
|
1070
|
-
markAsStreamer(id: string): void;
|
|
1071
|
-
getLatestConversation(): {
|
|
1072
|
-
id: string;
|
|
1073
|
-
lastActivity: string | null;
|
|
1074
|
-
} | null;
|
|
1075
|
-
hasOrphanProjectId(): boolean;
|
|
1076
|
-
listConversationsForProjectBackfill(): Array<{
|
|
1077
|
-
id: string;
|
|
1078
|
-
projectPath: string | null;
|
|
1079
|
-
projectId: string | null;
|
|
1080
|
-
lastActivity: string | null;
|
|
1081
|
-
}>;
|
|
1082
|
-
getConversationTail(id: string): CachedTail | null;
|
|
1083
|
-
hasConversation(id: string): boolean;
|
|
1084
|
-
upsertSessionName(sessionId: string, name: string): void;
|
|
1085
|
-
getSessionName(sessionId: string): string | null;
|
|
1086
|
-
listSessionNames(): Record<string, string>;
|
|
1087
|
-
invalidate(id?: string): void;
|
|
1088
|
-
/**
|
|
1089
|
-
* Drop the cached row for a file. Two callers with opposite intent:
|
|
1090
|
-
* - a directory-watch "change" event (the file was appended to) — pass
|
|
1091
|
-
* `skipIfTailed: true`, which NEVER deletes (upsert-or-leave). A change
|
|
1092
|
-
* event fires on every external append; deleting here flickers the
|
|
1093
|
-
* conversation out of /api/conversations — whether it's a live-tailed row
|
|
1094
|
-
* the updateFromLines/warm-up path just wrote (CRITICAL #2; both watchers
|
|
1095
|
-
* fire on the same append with no ordering guarantee) OR a refresh-created
|
|
1096
|
-
* untailed row (a ?refresh=1 upsert never populates a tail, so the old
|
|
1097
|
-
* "delete when untailed" behavior made it vanish on its next append with no
|
|
1098
|
-
* client action). The live-tail path owns the row's content and the
|
|
1099
|
-
* debounced rescan re-derives metadata, so leaving the row loses nothing.
|
|
1100
|
-
* - a genuine unlink (the file is gone) — leave `skipIfTailed` false so the
|
|
1101
|
-
* row is always removed, otherwise a deleted session ghosts in the cache.
|
|
1102
|
-
*/
|
|
1103
|
-
invalidateByFilePath(filePath: string, opts?: {
|
|
1104
|
-
skipIfTailed?: boolean;
|
|
1105
|
-
}): string | null;
|
|
1106
|
-
/**
|
|
1107
|
-
* Drop rows whose `file_path` no longer exists on disk AND which have no
|
|
1108
|
-
* cached tail to fall back to. Rows with a tail are left alone so
|
|
1109
|
-
* `handleGetConversation` can still serve the cached tail even when the
|
|
1110
|
-
* JSONL has been deleted.
|
|
1111
|
-
*/
|
|
1112
|
-
pruneGhostFiles(exists?: (filePath: string) => boolean): string[];
|
|
1113
|
-
/**
|
|
1114
|
-
* Reconcile the cache against the authoritative set of conversation file
|
|
1115
|
-
* paths a fresh scan surfaced: drop any cached row whose `file_path` is not
|
|
1116
|
-
* in `livePaths` (removed from disk, or now filtered out — e.g. became an
|
|
1117
|
-
* agent JSONL). This is the "removed conversations" half of a ?refresh=1
|
|
1118
|
-
* reconcile; the additions/updates half is upsertFromScannerMeta.
|
|
1119
|
-
*
|
|
1120
|
-
* Skip semantics depend on whether the file still exists on disk:
|
|
1121
|
-
* - File GONE from disk → always removed, tail or not. This matches the old
|
|
1122
|
-
* invalidate()+rebuild behavior (a deleted conversation must disappear on
|
|
1123
|
-
* refresh) and keeps refresh=1 truthful about removals. NOTE: this is an
|
|
1124
|
-
* INTENTIONAL divergence from pruneGhostFiles(), which KEEPS tailed ghosts
|
|
1125
|
-
* so their cached history stays viewable on a background prune. refresh=1
|
|
1126
|
-
* has the opposite contract (mobile relies on removals being reflected), so
|
|
1127
|
-
* do not "unify" the two — they serve different purposes.
|
|
1128
|
-
* - File STILL on disk but absent from `livePaths` → the CRITICAL #2 race:
|
|
1129
|
-
* the scan snapshot predates a just-created (and now live-tailed) file.
|
|
1130
|
-
* A tailed row here is actively maintained from real content, so it is
|
|
1131
|
-
* kept — dropping it would flicker the active conversation out of
|
|
1132
|
-
* /api/conversations. An untailed on-disk row not in the snapshot is a
|
|
1133
|
-
* transient scan/discovery gap; it is left alone (not removed) and the
|
|
1134
|
-
* next reconcile picks it up, rather than risk removing a real file the
|
|
1135
|
-
* scan simply hasn't surfaced yet.
|
|
1136
|
-
* Returns the removed IDs.
|
|
1137
|
-
*/
|
|
1138
|
-
reconcileDeletions(livePaths: Set<string>, opts?: {
|
|
1139
|
-
exists?: (filePath: string) => boolean;
|
|
1140
|
-
}): string[];
|
|
1141
|
-
/**
|
|
1142
|
-
* Read-only: list cached rows whose `file_path` no longer exists on disk.
|
|
1143
|
-
* Unlike pruneGhostFiles/reconcileDeletions this mutates nothing — it just
|
|
1144
|
-
* reports drift for the CacheIntegrityMonitor to classify. `tailed` flags
|
|
1145
|
-
* rows that still have cached history (which pruneGhostFiles would keep).
|
|
1146
|
-
*/
|
|
1147
|
-
listMissingFiles(exists?: (filePath: string) => boolean): {
|
|
1148
|
-
id: string;
|
|
1149
|
-
filePath: string;
|
|
1150
|
-
title: string | null;
|
|
1151
|
-
tailed: boolean;
|
|
1152
|
-
}[];
|
|
1153
|
-
/**
|
|
1154
|
-
* Drop the given conversation ids outright — main row, tail, and message
|
|
1155
|
-
* index — regardless of whether they have a tail. Used by the cache-integrity
|
|
1156
|
-
* resolution actions (prune_all / prune_selected). Returns the count dropped.
|
|
1157
|
-
*/
|
|
1158
|
-
dropRowsById(ids: string[]): number;
|
|
1159
|
-
/**
|
|
1160
|
-
* Wipe all cached conversation state — meta, tails, and message index — and
|
|
1161
|
-
* reset the in-memory file index. Only called by the `reset_rescan`
|
|
1162
|
-
* resolution action, which repopulates from a fresh disk scan afterward.
|
|
1163
|
-
*/
|
|
1164
|
-
clearAll(): void;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
type CacheMetadataKey = "last_conversation_id" | "last_conversation_created_at" | "projects_last_indexed_at" | "conversations_last_indexed_at" | "conversations_dirty";
|
|
1168
|
-
declare class CacheMetadataRepository {
|
|
1169
|
-
private get;
|
|
1170
|
-
private upsert;
|
|
1171
|
-
private del;
|
|
1172
|
-
constructor(db: Database.Database);
|
|
1173
|
-
getCacheMetadata(key: CacheMetadataKey): string | null;
|
|
1174
|
-
setCacheMetadata(key: CacheMetadataKey, value: string): void;
|
|
1175
|
-
deleteCacheMetadata(key: CacheMetadataKey): void;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
* Thin repository wrapper around ConversationCache for the project-id flow.
|
|
1180
|
-
* The cache is the source of truth for conversation rows; this just exposes
|
|
1181
|
-
* a stable, repo-style API for services that don't want to know about the
|
|
1182
|
-
* cache class directly.
|
|
1183
|
-
*/
|
|
1184
|
-
declare class ConversationsRepository {
|
|
1185
|
-
private cache;
|
|
1186
|
-
constructor(cache: ConversationCache);
|
|
1187
|
-
updateConversationProjectId(args: {
|
|
1188
|
-
conversationId: string;
|
|
1189
|
-
projectId: string;
|
|
1190
|
-
}): void;
|
|
1191
|
-
listConversationsForProjectBackfill(): {
|
|
1192
|
-
id: string;
|
|
1193
|
-
projectPath: string | null;
|
|
1194
|
-
projectId: string | null;
|
|
1195
|
-
lastActivity: string | null;
|
|
1196
|
-
}[];
|
|
1197
|
-
getLatestConversation(): {
|
|
1198
|
-
id: string;
|
|
1199
|
-
lastActivity: string | null;
|
|
1200
|
-
} | null;
|
|
1201
|
-
hasOrphanRows(): boolean;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Paired-device registry (C5).
|
|
1206
|
-
* See docs/architecture/2026-07-24-device-identity-and-capabilities.md.
|
|
1207
|
-
*
|
|
1208
|
-
* Stores identity and authority for each paired device. Never stores a usable
|
|
1209
|
-
* credential: only the SHA-256 of a device token, so reading this table cannot
|
|
1210
|
-
* impersonate a device.
|
|
1211
|
-
*/
|
|
1212
|
-
interface DeviceRow {
|
|
1213
|
-
device_id: string;
|
|
1214
|
-
public_key: string;
|
|
1215
|
-
token_hash: string;
|
|
1216
|
-
name: string | null;
|
|
1217
|
-
capabilities: string;
|
|
1218
|
-
created_at: number;
|
|
1219
|
-
last_seen_at: number | null;
|
|
1220
|
-
revoked_at: number | null;
|
|
1221
|
-
/** Noise static public key, base64. Null on every row that predates E2EE. */
|
|
1222
|
-
e2ee_static_pub: string | null;
|
|
1223
|
-
/** The downgrade lock. 1 once this device has completed a handshake. */
|
|
1224
|
-
e2ee_required: number;
|
|
1225
|
-
e2ee_version: number | null;
|
|
1226
|
-
}
|
|
1227
|
-
/** A device as reported over the API. Deliberately carries no credential. */
|
|
1228
|
-
interface DeviceView {
|
|
1229
|
-
deviceId: string;
|
|
1230
|
-
name: string | null;
|
|
1231
|
-
capabilities: Capability[];
|
|
1232
|
-
createdAt: number;
|
|
1233
|
-
lastSeenAt: number | null;
|
|
1234
|
-
revokedAt: number | null;
|
|
1235
|
-
/**
|
|
1236
|
-
* Whether this device is pinned to encryption.
|
|
1237
|
-
*
|
|
1238
|
-
* A boolean, never the key and never a hash of it: `GET /api/devices` exists
|
|
1239
|
-
* so a user can spot a device they did not pair, and answering "is this one
|
|
1240
|
-
* encrypted" needs no key material to do it. Publishing the static key would
|
|
1241
|
-
* hand an attacker who reached this endpoint the value that identifies a
|
|
1242
|
-
* device.
|
|
1243
|
-
*/
|
|
1244
|
-
e2ee: boolean;
|
|
1245
|
-
}
|
|
1246
|
-
interface RegisteredDevice {
|
|
1247
|
-
deviceId: string;
|
|
1248
|
-
/** Returned to the client exactly once, at pairing. Never persisted raw. */
|
|
1249
|
-
deviceToken: string;
|
|
1250
|
-
capabilities: Capability[];
|
|
1251
|
-
}
|
|
1252
|
-
declare class DevicesRepository {
|
|
1253
|
-
private insertStmt;
|
|
1254
|
-
private byTokenHashStmt;
|
|
1255
|
-
private byIdStmt;
|
|
1256
|
-
private listStmt;
|
|
1257
|
-
private revokeStmt;
|
|
1258
|
-
private touchStmt;
|
|
1259
|
-
private deleteStmt;
|
|
1260
|
-
private deleteRevokedStmt;
|
|
1261
|
-
private byE2eeStaticPubStmt;
|
|
1262
|
-
private repairStmt;
|
|
1263
|
-
constructor(db: Database.Database);
|
|
1264
|
-
/**
|
|
1265
|
-
* Record a newly paired device and mint its token.
|
|
1266
|
-
*
|
|
1267
|
-
* The raw token is returned to the caller and never stored — this is the only
|
|
1268
|
-
* moment it exists outside the client.
|
|
1269
|
-
*/
|
|
1270
|
-
register(args: {
|
|
1271
|
-
publicKey: string;
|
|
1272
|
-
name?: string | null;
|
|
1273
|
-
preset?: CapabilityPreset;
|
|
1274
|
-
now?: number;
|
|
1275
|
-
/**
|
|
1276
|
-
* The device's Noise static public key, base64, when pairing completed a
|
|
1277
|
-
* handshake. Absent on a plaintext pairing, which stays exactly as it is
|
|
1278
|
-
* today: no key, no pin, no behaviour change.
|
|
1279
|
-
*/
|
|
1280
|
-
e2eeStaticPub?: string;
|
|
1281
|
-
e2eeVersion?: number;
|
|
1282
|
-
}): RegisteredDevice;
|
|
1283
|
-
/** The device that owns a Noise static key, or null. */
|
|
1284
|
-
getByE2eeStaticPub(staticPub: string): DeviceRow | null;
|
|
1285
|
-
/**
|
|
1286
|
-
* Resolve a presented token to a device, or null.
|
|
1287
|
-
*
|
|
1288
|
-
* Returns null for a revoked device, so revocation takes effect on the very
|
|
1289
|
-
* next request with no cache to go stale.
|
|
1290
|
-
*/
|
|
1291
|
-
authenticate(token: string): DeviceRow | null;
|
|
1292
|
-
get(deviceId: string): DeviceRow | null;
|
|
1293
|
-
/** All devices, including revoked ones — an audit surface needs the history. */
|
|
1294
|
-
list(): DeviceView[];
|
|
1295
|
-
/** Revoke one device. Others are untouched — no key rotation, no collateral. */
|
|
1296
|
-
revoke(deviceId: string, now?: number): boolean;
|
|
1297
|
-
/**
|
|
1298
|
-
* Erase one device's record outright.
|
|
1299
|
-
*
|
|
1300
|
-
* Deliberately separate from `revoke`, which is a soft delete that keeps the
|
|
1301
|
-
* row so `list()` can show what happened. That audit trail is the right
|
|
1302
|
-
* default — but it meant a `devices` row, including the user-supplied `name`
|
|
1303
|
-
* ("Ronen's iPhone"), had no removal path at all once the registry moved to
|
|
1304
|
-
* runtime.db, which no command deletes. This is that path.
|
|
1305
|
-
*
|
|
1306
|
-
* Erasure is NOT revocation: deleting a row frees its `token_hash`, so a
|
|
1307
|
-
* device whose token is still on a phone somewhere stops being *known* rather
|
|
1308
|
-
* than being *refused*. Revoke first, delete second, is the safe order, and
|
|
1309
|
-
* `deleteRevoked()` exists so that is the easy thing to do.
|
|
1310
|
-
*/
|
|
1311
|
-
delete(deviceId: string): boolean;
|
|
1312
|
-
/**
|
|
1313
|
-
* Erase every already-revoked device. The bulk companion to `delete`, and the
|
|
1314
|
-
* one that is safe by construction: a revoked device is already refused, so
|
|
1315
|
-
* removing its row cannot restore access to anything.
|
|
1316
|
-
*
|
|
1317
|
-
* Returns the number of rows removed.
|
|
1318
|
-
*/
|
|
1319
|
-
deleteRevoked(): number;
|
|
1320
|
-
touch(deviceId: string, now?: number): void;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
/**
|
|
1324
|
-
* Durable registry of managed sessions (C1 Phase 2).
|
|
1325
|
-
* See docs/architecture/2026-07-24-durable-session-runtime.md.
|
|
1326
|
-
*
|
|
1327
|
-
* Distinct from SessionsRepository, which wraps the in-memory SessionStore for
|
|
1328
|
-
* the live request path. This one exists so session identity and provenance
|
|
1329
|
-
* outlive the streamer process: without it, a restart loses startedAt,
|
|
1330
|
-
* promptCount, sessionName, the Codex rollout binding, and failureReason, and
|
|
1331
|
-
* the boot reconciler has nothing to reconcile against.
|
|
1332
|
-
*
|
|
1333
|
-
* Deliberately not stored here: the PTY output ring buffer and xterm screen.
|
|
1334
|
-
* Their authoritative copy is the provider's JSONL, and writing 64KiB of ANSI
|
|
1335
|
-
* on every chunk to duplicate it would be a write storm for no gain.
|
|
1336
|
-
*/
|
|
1337
|
-
/** How a persisted status was obtained — never inferred, always recorded. */
|
|
1338
|
-
type StatusSource = "spawn" | "transition" | "exit" | "shutdown" | "probe" | "reconcile";
|
|
1339
|
-
|
|
1340
|
-
interface ManagedSessionRow {
|
|
1341
|
-
session_id: string;
|
|
1342
|
-
provider: string;
|
|
1343
|
-
pid: number | null;
|
|
1344
|
-
cmdline: string | null;
|
|
1345
|
-
project_path: string;
|
|
1346
|
-
project_name: string;
|
|
1347
|
-
branch: string;
|
|
1348
|
-
status: string;
|
|
1349
|
-
status_source: string;
|
|
1350
|
-
status_updated_at: number;
|
|
1351
|
-
started_at: number;
|
|
1352
|
-
completed_at: number | null;
|
|
1353
|
-
last_activity_at: number | null;
|
|
1354
|
-
prompt_count: number;
|
|
1355
|
-
session_name: string | null;
|
|
1356
|
-
project_id: string | null;
|
|
1357
|
-
bound_conversation_id: string | null;
|
|
1358
|
-
resumed_from_conversation_id: string | null;
|
|
1359
|
-
failure_reason: string | null;
|
|
1360
|
-
streamer_instance_id: string;
|
|
1361
|
-
/**
|
|
1362
|
-
* Which machine boot `pid` was recorded during (migration 002). Optional
|
|
1363
|
-
* because rows written before it exists read back as null/absent, which the
|
|
1364
|
-
* reconciler treats exactly like a mismatch — never like a match.
|
|
1365
|
-
*/
|
|
1366
|
-
boot_token?: string | null;
|
|
1367
|
-
}
|
|
1368
|
-
interface RecordSpawnInput {
|
|
1369
|
-
session: ManagedSession;
|
|
1370
|
-
pid: number | null;
|
|
1371
|
-
cmdline: string | null;
|
|
1372
|
-
streamerInstanceId: string;
|
|
1373
|
-
}
|
|
1374
|
-
declare class ManagedSessionsRepository {
|
|
1375
|
-
private upsertStmt;
|
|
1376
|
-
private updateStatusStmt;
|
|
1377
|
-
private bindStmt;
|
|
1378
|
-
private getStmt;
|
|
1379
|
-
private listNonTerminalStmt;
|
|
1380
|
-
private listAllStmt;
|
|
1381
|
-
private pruneTerminalStmt;
|
|
1382
|
-
private listRecoverableStmt;
|
|
1383
|
-
private deleteStmt;
|
|
1384
|
-
constructor(db: Database.Database);
|
|
1385
|
-
/** Record a session at spawn, or refresh every field of an existing row. */
|
|
1386
|
-
recordSpawn({ session, pid, cmdline, streamerInstanceId }: RecordSpawnInput): void;
|
|
1387
|
-
/**
|
|
1388
|
-
* Persist a status transition. `source` is required rather than defaulted:
|
|
1389
|
-
* a status whose provenance is unknown is the thing this table exists to
|
|
1390
|
-
* prevent, and the reconciler reads it to decide how much to trust the value.
|
|
1391
|
-
*/
|
|
1392
|
-
recordStatus(sessionId: string, status: SessionStatus, source: StatusSource, fields?: {
|
|
1393
|
-
completedAt?: Date | null;
|
|
1394
|
-
lastActivityAt?: Date | null;
|
|
1395
|
-
promptCount?: number;
|
|
1396
|
-
failureReason?: string | null;
|
|
1397
|
-
/** Null/omitted keeps whatever is stored — it never clears a known name. */
|
|
1398
|
-
sessionName?: string | null;
|
|
1399
|
-
}): void;
|
|
1400
|
-
/**
|
|
1401
|
-
* Persist the Codex rollout id discovered after spawn.
|
|
1402
|
-
*
|
|
1403
|
-
* Its own statement rather than a `recordSpawn` re-run: the binding arrives
|
|
1404
|
-
* while the session is live, and re-upserting would also rewrite `cmdline`
|
|
1405
|
-
* with an id that is *not* in a fresh Codex process's argv, turning the
|
|
1406
|
-
* reconciler's identity check into a false `orphaned`. Without this write the
|
|
1407
|
-
* binding lives only in memory and dies with the streamer — which is the
|
|
1408
|
-
* whole reason a restarted Codex session could not be resumed (G6).
|
|
1409
|
-
*/
|
|
1410
|
-
recordBinding(sessionId: string, boundConversationId: string): void;
|
|
1411
|
-
get(sessionId: string): ManagedSessionRow | null;
|
|
1412
|
-
/**
|
|
1413
|
-
* Rows with no recorded completion — the reconciler's probe set.
|
|
1414
|
-
*
|
|
1415
|
-
* Capped. Callers must compare the result length against the limit and say so
|
|
1416
|
-
* when it clips: a silently truncated probe set reads as "we checked
|
|
1417
|
-
* everything" when it did not.
|
|
1418
|
-
*/
|
|
1419
|
-
listNonTerminal(limit?: number): ManagedSessionRow[];
|
|
1420
|
-
/** Every row, most recently touched first, for the diagnostics surface. */
|
|
1421
|
-
listAll(limit?: number): ManagedSessionRow[];
|
|
1422
|
-
/**
|
|
1423
|
-
* Delete terminal rows older than `olderThanMs`, returning how many went.
|
|
1424
|
-
*
|
|
1425
|
-
* Only rows carrying a `completed_at` are eligible, so nothing the reconciler
|
|
1426
|
-
* or rehydrator might still want is reachable from here — a row without one
|
|
1427
|
-
* is by definition unfinished business, however old it looks.
|
|
1428
|
-
*/
|
|
1429
|
-
pruneTerminal(olderThanMs?: number): number;
|
|
1430
|
-
/**
|
|
1431
|
-
* Rows a restart could bring back: still open, or closed by our own shutdown,
|
|
1432
|
-
* and touched no longer ago than `sinceMs`. Newest first, capped — the caller
|
|
1433
|
-
* decides which of these actually deserve rehydrating (`shouldRehydrate`).
|
|
1434
|
-
*/
|
|
1435
|
-
listRecoverable({ sinceMs, limit }: {
|
|
1436
|
-
sinceMs: number;
|
|
1437
|
-
limit: number;
|
|
1438
|
-
}): ManagedSessionRow[];
|
|
1439
|
-
delete(sessionId: string): void;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
/**
|
|
1443
|
-
* Persisted Project row. Mirrors the projects SQLite table.
|
|
1444
|
-
*/
|
|
1445
|
-
declare const ProjectSchema: z.ZodObject<{
|
|
1446
|
-
id: z.ZodString;
|
|
1447
|
-
path: z.ZodString;
|
|
1448
|
-
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1449
|
-
lastConversationId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1450
|
-
lastConversationCreatedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1451
|
-
lastIndexedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1452
|
-
latestMessageAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1453
|
-
latestMessageId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1454
|
-
createdAt: z.ZodString;
|
|
1455
|
-
updatedAt: z.ZodString;
|
|
1456
|
-
}, z.core.$strip>;
|
|
1457
|
-
type Project = z.infer<typeof ProjectSchema>;
|
|
1458
|
-
|
|
1459
|
-
interface UpsertProjectInput {
|
|
1460
|
-
lastConversationId?: string | null;
|
|
1461
|
-
lastConversationCreatedAt?: string | null;
|
|
1462
|
-
latestMessageAt?: string | null;
|
|
1463
|
-
latestMessageId?: string | null;
|
|
1464
|
-
name?: string | null;
|
|
1465
|
-
}
|
|
1466
|
-
declare class ProjectsRepository {
|
|
1467
|
-
private getByPath;
|
|
1468
|
-
private getById;
|
|
1469
|
-
private listAll;
|
|
1470
|
-
private insert;
|
|
1471
|
-
private update;
|
|
1472
|
-
constructor(db: Database.Database);
|
|
1473
|
-
getProjectByPath(rawPath: string): Project | null;
|
|
1474
|
-
getProjectById(id: string): Project | null;
|
|
1475
|
-
listProjects(): Project[];
|
|
1476
|
-
/**
|
|
1477
|
-
* Insert a project at the given canonical path, or update its metadata
|
|
1478
|
-
* if one already exists. Returns the persisted Project row.
|
|
1479
|
-
*
|
|
1480
|
-
* Idempotent: passing the same path twice returns the same project id.
|
|
1481
|
-
*/
|
|
1482
|
-
upsertProjectByPath(rawPath: string, input?: UpsertProjectInput): Project;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
/**
|
|
1486
|
-
* Token kinds. A device supplies three non-interchangeable types, and
|
|
1487
|
-
* conflating them fails only at send time with no signal at registration:
|
|
1488
|
-
*
|
|
1489
|
-
* - `expo` — Expo relay token, for ordinary push notifications.
|
|
1490
|
-
* - `liveactivity_start` — ActivityKit push-to-start token. App-wide, one per
|
|
1491
|
-
* device, long-lived. Starts an activity when none exists.
|
|
1492
|
-
* - `liveactivity_update` — ActivityKit per-activity update token, issued by
|
|
1493
|
-
* iOS after an activity starts, scoped to that one activity, short-lived.
|
|
1494
|
-
*/
|
|
1495
|
-
declare const PUSH_TOKEN_KINDS: readonly ["expo", "liveactivity_start", "liveactivity_update"];
|
|
1496
|
-
type PushTokenKind = (typeof PUSH_TOKEN_KINDS)[number];
|
|
1497
|
-
interface PushTokenRow {
|
|
1498
|
-
token: string;
|
|
1499
|
-
platform: string;
|
|
1500
|
-
device_id: string | null;
|
|
1501
|
-
registered_at: number;
|
|
1502
|
-
last_success_at: number | null;
|
|
1503
|
-
last_failure_at: number | null;
|
|
1504
|
-
last_failure_code: string | null;
|
|
1505
|
-
failure_streak: number;
|
|
1506
|
-
revoked_at: number | null;
|
|
1507
|
-
kind: PushTokenKind;
|
|
1508
|
-
activity_id: string | null;
|
|
1509
|
-
session_id: string | null;
|
|
1510
|
-
expires_at: number | null;
|
|
1511
|
-
stale_date: number | null;
|
|
1512
|
-
started_at: number | null;
|
|
1513
|
-
renewed_at: number | null;
|
|
1514
|
-
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Health as reported to a client. Deliberately omits the token itself — a push
|
|
1517
|
-
* token is a delivery credential, and a health endpoint has no reason to echo
|
|
1518
|
-
* one back.
|
|
1519
|
-
*/
|
|
1520
|
-
interface PushTokenHealth {
|
|
1521
|
-
platform: string;
|
|
1522
|
-
deviceId: string | null;
|
|
1523
|
-
registeredAt: number;
|
|
1524
|
-
lastSuccessAt: number | null;
|
|
1525
|
-
lastFailureAt: number | null;
|
|
1526
|
-
lastFailureCode: string | null;
|
|
1527
|
-
failureStreak: number;
|
|
1528
|
-
revokedAt: number | null;
|
|
1529
|
-
/**
|
|
1530
|
-
* Never delivered vs delivering vs failing vs revoked vs expired. The
|
|
1531
|
-
* distinction the user actually needs: "not yet" and "broken" look identical
|
|
1532
|
-
* without it.
|
|
1533
|
-
*/
|
|
1534
|
-
state: "never-delivered" | "healthy" | "failing" | "dead" | "revoked" | "expired";
|
|
1535
|
-
kind: PushTokenKind;
|
|
1536
|
-
/** Present only for per-activity Live Activity tokens. */
|
|
1537
|
-
activityId: string | null;
|
|
1538
|
-
sessionId: string | null;
|
|
1539
|
-
expiresAt: number | null;
|
|
1540
|
-
}
|
|
1541
|
-
declare class PushRepository {
|
|
1542
|
-
private upsertStmt;
|
|
1543
|
-
private getStmt;
|
|
1544
|
-
private listActiveStmt;
|
|
1545
|
-
private listAllStmt;
|
|
1546
|
-
private successStmt;
|
|
1547
|
-
private failureStmt;
|
|
1548
|
-
private revokeStmt;
|
|
1549
|
-
private claimEventStmt;
|
|
1550
|
-
private markDeliveredStmt;
|
|
1551
|
-
private listByKindSessionStmt;
|
|
1552
|
-
private listByKindStmt;
|
|
1553
|
-
private listRenewableStmt;
|
|
1554
|
-
private claimRenewalStmt;
|
|
1555
|
-
private expireStmt;
|
|
1556
|
-
private expireSessionActivitiesStmt;
|
|
1557
|
-
constructor(db: Database.Database);
|
|
1558
|
-
/**
|
|
1559
|
-
* Register or refresh a token.
|
|
1560
|
-
*
|
|
1561
|
-
* `kind` defaults to Expo so a released client posting `{ token, platform }`
|
|
1562
|
-
* keeps working — tb-mobile cannot be force-updated, and every client
|
|
1563
|
-
* predating Live Activities is registering an Expo relay token.
|
|
1564
|
-
*
|
|
1565
|
-
* Several rows per device is normal and intended: a device runs one activity
|
|
1566
|
-
* per live session, each with its own update token. The token itself is the
|
|
1567
|
-
* primary key, so distinct activities never collide.
|
|
1568
|
-
*/
|
|
1569
|
-
register(args: {
|
|
1570
|
-
token: string;
|
|
1571
|
-
platform: string;
|
|
1572
|
-
deviceId?: string | null;
|
|
1573
|
-
kind?: PushTokenKind;
|
|
1574
|
-
activityId?: string | null;
|
|
1575
|
-
sessionId?: string | null;
|
|
1576
|
-
expiresAt?: number | null;
|
|
1577
|
-
staleDate?: number | null;
|
|
1578
|
-
startedAt?: number | null;
|
|
1579
|
-
now?: number;
|
|
1580
|
-
}): void;
|
|
1581
|
-
get(token: string): PushTokenRow | null;
|
|
1582
|
-
/**
|
|
1583
|
-
* Expo tokens eligible for delivery — not revoked, not past the failure limit.
|
|
1584
|
-
*
|
|
1585
|
-
* Deliberately Expo-only. ActivityKit tokens go over direct APNs with a
|
|
1586
|
-
* different topic and are rejected by Expo's relay, so the ordinary
|
|
1587
|
-
* notification fan-out must not see them.
|
|
1588
|
-
*/
|
|
1589
|
-
listDeliverable(): PushTokenRow[];
|
|
1590
|
-
/** Live-activity tokens for one session, eligible for delivery. */
|
|
1591
|
-
listForSession(kind: PushTokenKind, sessionId: string, now?: number): PushTokenRow[];
|
|
1592
|
-
/**
|
|
1593
|
-
* Every deliverable token of one kind.
|
|
1594
|
-
*
|
|
1595
|
-
* Used for push-to-start, which is app-wide rather than session-scoped: the
|
|
1596
|
-
* activity does not exist yet, so there is no per-activity token to look up.
|
|
1597
|
-
*/
|
|
1598
|
-
listByKind(kind: PushTokenKind, now?: number): PushTokenRow[];
|
|
1599
|
-
/** Unrenewed activities with a renewal deadline, soonest first. */
|
|
1600
|
-
listRenewable(): PushTokenRow[];
|
|
1601
|
-
/**
|
|
1602
|
-
* Claim a row for renewal.
|
|
1603
|
-
*
|
|
1604
|
-
* Returns true exactly once per row. A restart re-arms timers from the
|
|
1605
|
-
* persisted deadline, so the same renewal can be attempted twice; the loser
|
|
1606
|
-
* gets false and must not send. Doing this as a conditional UPDATE rather
|
|
1607
|
-
* than read-then-write avoids the race where both attempts observe
|
|
1608
|
-
* "not yet renewed".
|
|
1609
|
-
*/
|
|
1610
|
-
claimRenewal(token: string, now?: number): boolean;
|
|
1611
|
-
/** Mark one token expired, so it stops being a delivery target. */
|
|
1612
|
-
expire(token: string, now?: number): void;
|
|
1613
|
-
/**
|
|
1614
|
-
* Expire every live activity for a session.
|
|
1615
|
-
*
|
|
1616
|
-
* Called when the session ends. Without this, a per-activity token outlives
|
|
1617
|
-
* its session and a later renewal sweep would resurrect an activity for a
|
|
1618
|
-
* session that is already gone.
|
|
1619
|
-
*/
|
|
1620
|
-
expireSessionActivities(sessionId: string, now?: number): void;
|
|
1621
|
-
/** Every token, including dead and revoked ones, for the health report. */
|
|
1622
|
-
listHealth(now?: number): PushTokenHealth[];
|
|
1623
|
-
recordSuccess(token: string, now?: number): void;
|
|
1624
|
-
recordFailure(token: string, code: string, now?: number): void;
|
|
1625
|
-
revoke(token: string, now?: number): boolean;
|
|
1626
|
-
/**
|
|
1627
|
-
* Claim an event id for delivery.
|
|
1628
|
-
*
|
|
1629
|
-
* Returns true exactly once per event id. A retry, a reconnect
|
|
1630
|
-
* reconciliation, or two triggers firing for the same underlying event all
|
|
1631
|
-
* get false and must not notify — the user should never be told twice about
|
|
1632
|
-
* one thing.
|
|
1633
|
-
*/
|
|
1634
|
-
claimEvent(eventId: string, sessionId: string | null, now?: number): boolean;
|
|
1635
|
-
markDelivered(eventId: string, now?: number): void;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
declare class SessionStore {
|
|
1639
|
-
private managed;
|
|
1640
|
-
private discovered;
|
|
1641
|
-
addManaged(session: ManagedSession): void;
|
|
1642
|
-
/**
|
|
1643
|
-
* Multi-agent mode only. Attach a dedupe LRU to a session record. Idempotent —
|
|
1644
|
-
* calling twice keeps the existing LRU (and its contents).
|
|
1645
|
-
*/
|
|
1646
|
-
initAgentSession(sessionId: string, dedupeCapacity: number): void;
|
|
1647
|
-
updateManaged(sessionId: string, updates: Partial<ManagedSession>): ManagedSession | null;
|
|
1648
|
-
removeManaged(sessionId: string): boolean;
|
|
1649
|
-
/**
|
|
1650
|
-
* The **live** stored record — mutating it mutates the store. Paired with
|
|
1651
|
-
* `get()`, which hands back a throwaway response copy. Prefer
|
|
1652
|
-
* `updateManaged()` for writes; this is for readers that need the internal
|
|
1653
|
-
* shape (Date fields, `rehydrated`, …) rather than the wire shape.
|
|
1654
|
-
*/
|
|
1655
|
-
getManaged(sessionId: string): ManagedSession | null;
|
|
1656
|
-
setDiscovered(processes: DiscoveredProcess[]): void;
|
|
1657
|
-
/**
|
|
1658
|
-
* The **live** stored records — mutating an element mutates the store. Paired
|
|
1659
|
-
* with `list()`, which hands back throwaway response copies.
|
|
1660
|
-
*/
|
|
1661
|
-
listManaged(): ManagedSession[];
|
|
1662
|
-
list(ptyAttachedIds: Set<string>): readonly Readonly<SessionResponse>[];
|
|
1663
|
-
get(sessionId: string, ptyAttachedIds: Set<string>): Readonly<SessionResponse> | null;
|
|
1664
|
-
paginate(ptyAttachedIds: Set<string>, query: SessionListQuery): SessionListPage;
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
/**
|
|
1668
|
-
* Sessions live in-memory in SessionStore (Postgres-backed persistence
|
|
1669
|
-
* was dropped per the SQLite-only direction). This repository wraps the
|
|
1670
|
-
* store with a stable, project-id-aware API for the new services.
|
|
1671
|
-
*/
|
|
1672
|
-
declare class SessionsRepository {
|
|
1673
|
-
private store;
|
|
1674
|
-
constructor(store: SessionStore);
|
|
1675
|
-
updateSessionProjectId(args: {
|
|
1676
|
-
sessionId: string;
|
|
1677
|
-
projectId: string;
|
|
1678
|
-
}): void;
|
|
1679
|
-
listManagedSessions(): ManagedSession[];
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
/**
|
|
1683
|
-
* `~/.threadbase/runtime.db` — the authoritative, non-derived half of the
|
|
1684
|
-
* streamer's SQLite state.
|
|
1685
|
-
*
|
|
1686
|
-
* Deliberately a separate file from `cache/cache.db`, and deliberately not
|
|
1687
|
-
* under `cache/`. The conversation cache is rebuildable from the provider
|
|
1688
|
-
* JSONLs at any time, which is why "delete the cache and restart" is reasonable
|
|
1689
|
-
* support advice and why the integrity monitor offers a reset-and-rescan
|
|
1690
|
-
* action. The managed-session registry is not rebuildable from anything on
|
|
1691
|
-
* disk, so it must not sit one plausible instruction away from deletion — nor
|
|
1692
|
-
* share a handle whose failure (a better-sqlite3 ABI mismatch, most commonly)
|
|
1693
|
-
* would silently disable session persistence along with the cache.
|
|
1694
|
-
*
|
|
1695
|
-
* Runs its own migrations from `src/db/runtime-migrations/`, tracked in this
|
|
1696
|
-
* file's own `schema_migrations` table.
|
|
1697
|
-
*/
|
|
1698
|
-
declare class RuntimeStore {
|
|
1699
|
-
private readonly db;
|
|
1700
|
-
private constructor();
|
|
1701
|
-
static open(dbPath: string, migrationsDir?: string): RuntimeStore;
|
|
1702
|
-
getDatabase(): Database.Database;
|
|
1703
|
-
/**
|
|
1704
|
-
* One-time move of `managed_sessions` rows out of a pre-split `cache.db`.
|
|
1705
|
-
*
|
|
1706
|
-
* Returns the number of rows copied.
|
|
1707
|
-
*/
|
|
1708
|
-
importLegacyManagedSessions(source: Database.Database): number;
|
|
1709
|
-
/**
|
|
1710
|
-
* One-time move of `devices` rows out of `cache.db`, where the registry used
|
|
1711
|
-
* to live (migration `011_create_devices.sql`).
|
|
1712
|
-
*
|
|
1713
|
-
* Losing this table invalidates every device token ever issued, and cache.db
|
|
1714
|
-
* is the file `tb-streamer cache clear` deletes and the integrity monitor
|
|
1715
|
-
* rebuilds — see `runtime-migrations/003_create_devices.sql`.
|
|
1716
|
-
*
|
|
1717
|
-
* Unlike `managed_sessions`, this one MOVES rather than copies: the source
|
|
1718
|
-
* rows are deleted once the copy is verified. A `devices` row carries a
|
|
1719
|
-
* user-supplied label ("Ronen's iPhone"), and leaving a second copy of that
|
|
1720
|
-
* on disk indefinitely — in the one file the user is told to delete when
|
|
1721
|
-
* something goes wrong — is more retained personal data than the rollback
|
|
1722
|
-
* path is worth. Recovering from a rollback is re-scanning a pairing QR.
|
|
1723
|
-
*
|
|
1724
|
-
* The delete is conditional on the copy being complete: `INSERT OR IGNORE`
|
|
1725
|
-
* can silently skip a row, so the destination count must match what was read
|
|
1726
|
-
* before anything is removed. A mismatch keeps the source and reports
|
|
1727
|
-
* `purged: false` rather than throwing — the import itself still succeeded,
|
|
1728
|
-
* and keeping data is the safe direction to fail in.
|
|
1729
|
-
*/
|
|
1730
|
-
importLegacyDevices(source: Database.Database): {
|
|
1731
|
-
copied: number;
|
|
1732
|
-
purged: boolean;
|
|
1733
|
-
};
|
|
1734
|
-
/**
|
|
1735
|
-
* Copy a whole table out of a pre-split `cache.db` into this file.
|
|
1736
|
-
*
|
|
1737
|
-
* The copy itself is non-destructive — the source is left in place, so an
|
|
1738
|
-
* older streamer rolled back onto the same machine still finds its data.
|
|
1739
|
-
* `importLegacyDevices` deletes the source afterwards for its own reasons;
|
|
1740
|
-
* `managed_sessions` does not. Runs only when this file's table is empty, so
|
|
1741
|
-
* a second boot is a no-op rather than a re-copy that would resurrect rows
|
|
1742
|
-
* deleted since.
|
|
1743
|
-
*
|
|
1744
|
-
* The table name is interpolated into SQL, so it is typed as a closed union
|
|
1745
|
-
* rather than `string` — the set of tables that can ever be lifted is known
|
|
1746
|
-
* at compile time, and that is what keeps a caller from making this a hole.
|
|
1747
|
-
*/
|
|
1748
|
-
private importLegacyTable;
|
|
1749
|
-
close(): void;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
interface HostHeartbeatState {
|
|
1753
|
-
registryState: "known" | "unknown";
|
|
1754
|
-
referencedSessionIds: string[];
|
|
1755
|
-
}
|
|
1756
|
-
/**
|
|
1757
|
-
* A duplex line channel. Deliberately not a socket: the runner is tested
|
|
1758
|
-
* against an in-memory pair, and PR 7 supplies the real one over a unix socket
|
|
1759
|
-
* (POSIX) or named pipe (Windows).
|
|
1760
|
-
*/
|
|
1761
|
-
interface HostTransport {
|
|
1762
|
-
send(line: string): void;
|
|
1763
|
-
onLine(handler: (line: string) => void): void;
|
|
1764
|
-
onClose(handler: () => void): void;
|
|
1765
|
-
close(): void;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
declare class LiveSessionManager {
|
|
1769
|
-
private runners;
|
|
1770
|
-
private remoteRunner;
|
|
1771
|
-
private options;
|
|
1772
|
-
constructor(options?: PTYManagerOptions);
|
|
1773
|
-
useRemoteRunner(transport: HostTransport): Promise<ManagedSession[]>;
|
|
1774
|
-
isRemote(): boolean;
|
|
1775
|
-
startRemoteHeartbeat(getState: () => HostHeartbeatState): void;
|
|
1776
|
-
start(sessionId: string, options: StartSessionOptions & {
|
|
1777
|
-
provider?: ProviderName;
|
|
1778
|
-
}): Promise<ManagedSession>;
|
|
1779
|
-
startFresh(options: StartFreshSessionOptions & {
|
|
1780
|
-
provider?: ProviderName;
|
|
1781
|
-
}): Promise<ManagedSession>;
|
|
1782
|
-
/**
|
|
1783
|
-
* Fork an existing conversation into a new session. Codex-only: `codex fork`
|
|
1784
|
-
* has no Claude Code equivalent, and there is no safe generic fallback — a
|
|
1785
|
-
* silent downgrade to resume would attach to the very writer the caller is
|
|
1786
|
-
* trying to leave alone.
|
|
1787
|
-
*/
|
|
1788
|
-
startFork(options: StartForkSessionOptions & {
|
|
1789
|
-
provider?: ProviderName;
|
|
1790
|
-
}): Promise<ManagedSession>;
|
|
1791
|
-
sendInput(sessionId: string, input: string): number;
|
|
1792
|
-
sendKeys(sessionId: string, keys: string): void;
|
|
1793
|
-
cancel(sessionId: string): void;
|
|
1794
|
-
killPid(pid: number): void;
|
|
1795
|
-
putOnHold(sessionId: string): void;
|
|
1796
|
-
getOutput(sessionId: string): string;
|
|
1797
|
-
getOutputLines(sessionId: string, maxLines: number): Promise<string[]>;
|
|
1798
|
-
getInputHistory(sessionId: string): UserMessage[];
|
|
1799
|
-
getSession(sessionId: string): ManagedSession | null;
|
|
1800
|
-
getPid(sessionId: string): number | null;
|
|
1801
|
-
hasSession(sessionId: string): boolean;
|
|
1802
|
-
listSessions(): ManagedSession[];
|
|
1803
|
-
dispose(): void;
|
|
1804
|
-
private runnerFor;
|
|
1805
|
-
/**
|
|
1806
|
-
* Refuse before spawning when the provider's CLI is not on this machine.
|
|
1807
|
-
*
|
|
1808
|
-
* Without this the spawn "succeeds": on POSIX execvp fails inside the forked
|
|
1809
|
-
* child, so a session appears, exits ~12ms later with code 1 and no output,
|
|
1810
|
-
* and the caller is told only that it "exited before becoming ready" — or,
|
|
1811
|
-
* on the Claude resume path, is told nothing at all, since that path answers
|
|
1812
|
-
* 200 before the process has had a chance to die. Every start route funnels
|
|
1813
|
-
* through here, so one check covers start, resume, adopt and fork.
|
|
1814
|
-
*
|
|
1815
|
-
* 503, not 500: the request was well-formed and the fault is this machine's
|
|
1816
|
-
* environment. `code` is what mobile branches on (it reads `errBody.code`),
|
|
1817
|
-
* and `PROVIDER_NOT_INSTALLED` is a remediation string it already knows.
|
|
1818
|
-
*/
|
|
1819
|
-
private assertProviderInstalled;
|
|
1820
|
-
private assertSupportedProvider;
|
|
1821
|
-
private activeRunners;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
declare class WSHub {
|
|
1825
|
-
private clients;
|
|
1826
|
-
private pingTimer;
|
|
1827
|
-
private pongTimers;
|
|
1828
|
-
addClient(ws: WebSocket): void;
|
|
1829
|
-
broadcast(message: WSMessage): void;
|
|
1830
|
-
broadcastToClients(clients: Iterable<WebSocket>, message: WSMessage): void;
|
|
1831
|
-
unicast(ws: WebSocket, message: WSMessage): void;
|
|
1832
|
-
get connectionCount(): number;
|
|
1833
|
-
dispose(): void;
|
|
1834
|
-
private startPing;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
/** One missing conversation the alert covers. */
|
|
1838
|
-
interface MissingEntry {
|
|
1839
|
-
id: string;
|
|
1840
|
-
filePath: string;
|
|
1841
|
-
title: string | null;
|
|
1842
|
-
tailed: boolean;
|
|
1843
|
-
}
|
|
1844
|
-
interface PendingAlert {
|
|
1845
|
-
fingerprint: string;
|
|
1846
|
-
severity: "high" | "low";
|
|
1847
|
-
detectedAt: string;
|
|
1848
|
-
missingCount: number;
|
|
1849
|
-
totalRows: number;
|
|
1850
|
-
backupPath?: string;
|
|
1851
|
-
/** Capped at 1000 entries in the persisted file. */
|
|
1852
|
-
missing: MissingEntry[];
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
type ResolveAction = CacheAlertResolveAction;
|
|
1856
|
-
/** The `cache_alert` WS variant, narrowed from the WSMessage union. */
|
|
1857
|
-
type CacheAlertWsMessage = Extract<WSMessage, {
|
|
1858
|
-
type: "cache_alert";
|
|
1859
|
-
}>;
|
|
1860
|
-
type ResolveResult = {
|
|
1861
|
-
ok: true;
|
|
1862
|
-
action: ResolveAction;
|
|
1863
|
-
pruned?: number;
|
|
1864
|
-
backupPath?: string;
|
|
1865
|
-
} | {
|
|
1866
|
-
alreadyResolved: true;
|
|
1867
|
-
} | {
|
|
1868
|
-
conflict: true;
|
|
1869
|
-
currentFingerprint: string;
|
|
1870
|
-
};
|
|
1871
|
-
declare class CacheIntegrityMonitor {
|
|
1872
|
-
private readonly cache;
|
|
1873
|
-
private readonly wsHub;
|
|
1874
|
-
private readonly log;
|
|
1875
|
-
private readonly cacheDir;
|
|
1876
|
-
private readonly rescan?;
|
|
1877
|
-
private readonly runDuringReset?;
|
|
1878
|
-
private _pending;
|
|
1879
|
-
private ignoredIds;
|
|
1880
|
-
private deferredUnlinks;
|
|
1881
|
-
private unlinkTimes;
|
|
1882
|
-
constructor(cache: ConversationCache, wsHub: WSHub, log: Logger, cacheDir: string, rescan?: (() => Promise<ScannerMeta[]>) | undefined, runDuringReset?: (<T>(operation: () => Promise<T>) => Promise<T>) | undefined);
|
|
1883
|
-
get pending(): PendingAlert | null;
|
|
1884
|
-
private persist;
|
|
1885
|
-
private classifySeverity;
|
|
1886
|
-
private sampleOf;
|
|
1887
|
-
private buildWsMessage;
|
|
1888
|
-
wsMessage(): CacheAlertWsMessage | null;
|
|
1889
|
-
healthzField(): {
|
|
1890
|
-
severity: "high" | "low";
|
|
1891
|
-
missingCount: number;
|
|
1892
|
-
fingerprint: string;
|
|
1893
|
-
detectedAt: string;
|
|
1894
|
-
} | undefined;
|
|
1895
|
-
/**
|
|
1896
|
-
* Scan the cache for rows whose file is gone, excluding ids the user chose to
|
|
1897
|
-
* ignore. If none remain, clear any stale pending alert and return (the caller
|
|
1898
|
-
* decides whether to run pruneGhostFiles). Otherwise classify severity, persist
|
|
1899
|
-
* the pending record, back up on high severity, and broadcast the alert.
|
|
1900
|
-
*/
|
|
1901
|
-
runDetection(detectedAt?: string): Promise<void>;
|
|
1902
|
-
/** Queue an unlink while an alert is pending — the row is not invalidated. */
|
|
1903
|
-
deferUnlink(filePath: string): void;
|
|
1904
|
-
/**
|
|
1905
|
-
* Record a live unlink while NO alert is pending. Crossing the storm threshold
|
|
1906
|
-
* (>= 10 unlinks within 30s) re-triggers detection.
|
|
1907
|
-
*/
|
|
1908
|
-
recordUnlink(filePath: string): void;
|
|
1909
|
-
private ensureBackup;
|
|
1910
|
-
private clearPending;
|
|
1911
|
-
private applyDeferredUnlinks;
|
|
1912
|
-
private broadcastResolved;
|
|
1913
|
-
/**
|
|
1914
|
-
* Apply the human's chosen resolution. Idempotent per fingerprint: no pending
|
|
1915
|
-
* alert → alreadyResolved; a different fingerprint → conflict. See the spec's
|
|
1916
|
-
* four-action semantics.
|
|
1917
|
-
*/
|
|
1918
|
-
resolve(fingerprint: string, action: ResolveAction, ids?: string[]): Promise<ResolveResult>;
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
/** Result for a single registry row. Pure data — nothing here acts. */
|
|
1922
|
-
interface ReconcileVerdict {
|
|
1923
|
-
sessionId: string;
|
|
1924
|
-
lifecycle: SessionLifecycle;
|
|
1925
|
-
/** Why this verdict was reached, for logs and the diagnostics surface. */
|
|
1926
|
-
reason: string;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
type ApiDeps = {
|
|
1930
|
-
apiKey: string;
|
|
1931
|
-
localNoAuth: boolean;
|
|
1932
|
-
logMenubarRequests: boolean;
|
|
1933
|
-
rotateApiKey: () => {
|
|
1934
|
-
newKey: string;
|
|
1935
|
-
persisted: boolean;
|
|
1936
|
-
};
|
|
1937
|
-
claudeFlagsConfig: () => {
|
|
1938
|
-
registry: readonly FlagDefinition[];
|
|
1939
|
-
values: ClaudeFlagValues;
|
|
1940
|
-
extraArgs: string | null;
|
|
1941
|
-
persisted: boolean;
|
|
1942
|
-
};
|
|
1943
|
-
setClaudeFlagsConfig: (values: ClaudeFlagValues, extraArgs: string | undefined) => {
|
|
1944
|
-
values: ClaudeFlagValues;
|
|
1945
|
-
extraArgs: string | null;
|
|
1946
|
-
persisted: boolean;
|
|
1947
|
-
};
|
|
1948
|
-
featureFlagsConfig: () => {
|
|
1949
|
-
registry: readonly FeatureFlagDefinition[];
|
|
1950
|
-
values: ResolvedFeatureFlags;
|
|
1951
|
-
sources: Record<FeatureFlagId, FeatureFlagSource>;
|
|
1952
|
-
};
|
|
1953
|
-
publicUrl: string | null;
|
|
1954
|
-
browseRoot: string | null;
|
|
1955
|
-
browserCors: string | undefined;
|
|
1956
|
-
ptyManager: LiveSessionManager;
|
|
1957
|
-
sessionStore: SessionStore;
|
|
1958
|
-
wsHub: WSHub;
|
|
1959
|
-
cache: () => ConversationCache | null;
|
|
1960
|
-
cacheMonitor: () => CacheIntegrityMonitor | null;
|
|
1961
|
-
/** Push registration + delivery state (C7). Null when the cache DB is unavailable. */
|
|
1962
|
-
pushRepo: () => PushRepository | null;
|
|
1963
|
-
/**
|
|
1964
|
-
* Whether Live Activity push is actually running on this server — the same
|
|
1965
|
-
* fact the boot log reports as `live_activity.enabled`. Asked of the server
|
|
1966
|
-
* rather than re-derived from the environment, because APNs credentials alone
|
|
1967
|
-
* do not enable it: the sender is only wired when the token store opened too.
|
|
1968
|
-
*/
|
|
1969
|
-
liveActivityPushEnabled: () => boolean;
|
|
1970
|
-
/** Paired-device registry (C5). Null when the cache DB is unavailable. */
|
|
1971
|
-
devicesRepo: () => DevicesRepository | null;
|
|
1972
|
-
projectsRepo: () => ProjectsRepository | null;
|
|
1973
|
-
conversationsRepo: () => ConversationsRepository | null;
|
|
1974
|
-
sessionsRepo: () => SessionsRepository | null;
|
|
1975
|
-
cacheMetadataRepo: () => CacheMetadataRepository | null;
|
|
1976
|
-
/** Authoritative session registry (~/.threadbase/runtime.db). Null when it failed to open. */
|
|
1977
|
-
runtimeStore: () => RuntimeStore | null;
|
|
1978
|
-
/** Rows of that registry. Null when it failed to open. */
|
|
1979
|
-
managedSessionsRepo: () => ManagedSessionsRepository | null;
|
|
1980
|
-
/** This boot's reconcile verdicts, by session id. Empty before it has run. */
|
|
1981
|
-
sessionVerdicts: () => Map<string, ReconcileVerdict>;
|
|
1982
|
-
ptyAttachedIds: () => Set<string>;
|
|
1983
|
-
handleListSessions: (url: URL, res: ServerResponse) => Promise<void>;
|
|
1984
|
-
handleSessionsCount: (res: ServerResponse) => void;
|
|
1985
|
-
handleGetRecentSessions: (url: URL, res: ServerResponse) => void;
|
|
1986
|
-
handleGetSessionNames: (res: ServerResponse) => void;
|
|
1987
|
-
handleGetSession: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
1988
|
-
handleGetOutput: (sessionId: string, res: ServerResponse) => void;
|
|
1989
|
-
handleSendInput: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1990
|
-
handleSendAnswer: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1991
|
-
handleCancel: (sessionId: string, res: ServerResponse) => void;
|
|
1992
|
-
handleStopSession: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
1993
|
-
handleSetSessionName: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1994
|
-
handleSetSessionModel: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1995
|
-
handleSetSessionEffort: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1996
|
-
handleUploadFile: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1997
|
-
handleAdopt: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
1998
|
-
handleFork: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1999
|
-
handleResume: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
2000
|
-
handleStartSession: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
2001
|
-
handleListConversations: (url: URL, res: ServerResponse) => Promise<void>;
|
|
2002
|
-
handleConversationsCount: (url: URL, res: ServerResponse) => Promise<void>;
|
|
2003
|
-
handleGetConversation: (id: string, url: URL, res: ServerResponse, ifNoneMatch?: string) => Promise<void>;
|
|
2004
|
-
handleSearch: (url: URL, res: ServerResponse) => Promise<void>;
|
|
2005
|
-
handleSearchTarget: (id: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
2006
|
-
handleListProjects: (url: URL, res: ServerResponse) => void;
|
|
2007
|
-
handleGetPopularProjects: (url: URL, res: ServerResponse) => void;
|
|
2008
|
-
handleGetProjectSummaries: (url: URL, res: ServerResponse) => void;
|
|
2009
|
-
handlePairStart: (res: ServerResponse) => void;
|
|
2010
|
-
handlePairExchange: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
2011
|
-
handleBrowse: (url: URL, res: ServerResponse) => Promise<void>;
|
|
2012
|
-
handleMkdir: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
2013
|
-
handleWsOpen: (ws: WebSocket) => void;
|
|
2014
|
-
handleWsMessage: (ws: WebSocket, raw: unknown, principal: Principal | null) => void;
|
|
2015
|
-
handleWsClose: (ws: WebSocket) => void;
|
|
2016
|
-
agentClient: AgentClient | null;
|
|
2017
|
-
conversationWriter: ConversationWriter | null;
|
|
2018
|
-
agentConfig: AgentConfig;
|
|
2019
|
-
};
|
|
2020
|
-
|
|
2021
|
-
type AppEnv = {
|
|
2022
|
-
Bindings: HttpBindings;
|
|
2023
|
-
Variables: {
|
|
2024
|
-
requestId?: string;
|
|
2025
|
-
validatedBody?: unknown;
|
|
2026
|
-
validatedQuery?: unknown;
|
|
2027
|
-
/** Who is making this request (C5). Set by authMiddleware. */
|
|
2028
|
-
principal?: Principal;
|
|
2029
|
-
};
|
|
2030
|
-
};
|
|
2031
|
-
|
|
2032
|
-
interface AgentDeps {
|
|
2033
|
-
sessionStore: {
|
|
2034
|
-
getManaged: (sessionId: string) => {
|
|
2035
|
-
id: string;
|
|
2036
|
-
progressDedupeIds?: {
|
|
2037
|
-
hasSeen: (id: string) => boolean;
|
|
2038
|
-
};
|
|
2039
|
-
currentTurnId?: string | null;
|
|
2040
|
-
} | null;
|
|
2041
|
-
};
|
|
2042
|
-
wsHub: {
|
|
2043
|
-
broadcast: (m: WSMessage) => void;
|
|
2044
|
-
};
|
|
2045
|
-
conversationWriter: {
|
|
2046
|
-
appendAssistantTurn: (a: {
|
|
2047
|
-
sessionId: string;
|
|
2048
|
-
turnId: string;
|
|
2049
|
-
content: string;
|
|
2050
|
-
reviewerOverruled?: boolean;
|
|
2051
|
-
}) => Promise<void>;
|
|
2052
|
-
} | null;
|
|
2053
|
-
agentConfig: {
|
|
2054
|
-
enabled: boolean;
|
|
2055
|
-
webhook: {
|
|
2056
|
-
hmacSecret: string;
|
|
2057
|
-
timestampSkewSeconds: number;
|
|
2058
|
-
};
|
|
2059
|
-
dedupe: {
|
|
2060
|
-
perSessionCapacity: number;
|
|
2061
|
-
};
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
declare const createProgressRoutes: (deps: ApiDeps & AgentDeps) => Hono<AppEnv, hono_types.BlankSchema, "/">;
|
|
2065
|
-
|
|
2066
|
-
declare function generateApiKey(): string;
|
|
2067
|
-
declare function validateApiKey(provided: string, expected: string): boolean;
|
|
2068
|
-
declare function loadOrCreateApiKey(): string;
|
|
2069
|
-
|
|
2070
|
-
interface DbConfig {
|
|
2071
|
-
connectionString: string;
|
|
2072
|
-
max: number;
|
|
2073
|
-
ssl?: string;
|
|
2074
|
-
statementTimeout?: number;
|
|
2075
|
-
instanceId: string;
|
|
2076
|
-
}
|
|
2077
|
-
declare function isDbEnabled(): boolean;
|
|
2078
|
-
declare function getDbConfig(): DbConfig | null;
|
|
2079
|
-
|
|
2080
|
-
declare function maskConnectionString(url: string): string;
|
|
2081
|
-
declare function createPool(config: DbConfig): Promise<Pool>;
|
|
2082
|
-
|
|
2083
|
-
declare function discoverClaudeProcesses(): Promise<DiscoveredProcess[]>;
|
|
2084
|
-
|
|
2085
|
-
declare class PTYManager implements SessionRunner {
|
|
2086
|
-
private sessions;
|
|
2087
|
-
private onOutput;
|
|
2088
|
-
private onStatusChange;
|
|
2089
|
-
private onReady;
|
|
2090
|
-
private onPermissionChange;
|
|
2091
|
-
private onPhaseChange;
|
|
2092
|
-
private onLiveQuestion;
|
|
2093
|
-
private onLiveQuestionGone;
|
|
2094
|
-
private onUserMessage;
|
|
2095
|
-
private permissionOpen;
|
|
2096
|
-
private oscTail;
|
|
2097
|
-
private lastDetectAt;
|
|
2098
|
-
private lastScreenQuestionKey;
|
|
2099
|
-
private shellPromptOpen;
|
|
2100
|
-
private closedGateKey;
|
|
2101
|
-
private pendingReady;
|
|
2102
|
-
private queuedInputs;
|
|
2103
|
-
private log;
|
|
2104
|
-
private firstChunkAt;
|
|
2105
|
-
private chunkIndex;
|
|
2106
|
-
private lastChunkAt;
|
|
2107
|
-
private quietCheckers;
|
|
2108
|
-
private readyFallbackTimers;
|
|
2109
|
-
private startPromises;
|
|
2110
|
-
constructor(options?: PTYManagerOptions);
|
|
2111
|
-
start(sessionId: string, options: StartSessionOptions): Promise<ManagedSession>;
|
|
2112
|
-
private doStart;
|
|
2113
|
-
startFresh(options: StartFreshSessionOptions): Promise<ManagedSession>;
|
|
2114
|
-
sendKeys(sessionId: string, keys: string): void;
|
|
2115
|
-
sendInput(sessionId: string, input: string): number;
|
|
2116
|
-
private writeSubmit;
|
|
2117
|
-
private flushQueuedInputs;
|
|
2118
|
-
cancel(sessionId: string): void;
|
|
2119
|
-
killPid(pid: number): void;
|
|
2120
|
-
putOnHold(sessionId: string): void;
|
|
2121
|
-
getOutput(sessionId: string): string;
|
|
2122
|
-
getOutputLines(sessionId: string, maxLines: number): Promise<string[]>;
|
|
2123
|
-
getInputHistory(sessionId: string): UserMessage[];
|
|
2124
|
-
getPid(sessionId: string): number | null;
|
|
2125
|
-
private recordUserMessage;
|
|
2126
|
-
getSession(sessionId: string): ManagedSession | null;
|
|
2127
|
-
hasSession(sessionId: string): boolean;
|
|
2128
|
-
listSessions(): ManagedSession[];
|
|
2129
|
-
dispose(): void;
|
|
2130
|
-
private handleOutput;
|
|
2131
|
-
private detectLivePrompts;
|
|
2132
|
-
private handleQuiet;
|
|
2133
|
-
private recheckReadyFromScreen;
|
|
2134
|
-
private armReadyFallback;
|
|
2135
|
-
private clearReadyFallback;
|
|
2136
|
-
/**
|
|
2137
|
-
* Record the agent's phase and notify only on a real change.
|
|
2138
|
-
*
|
|
2139
|
-
* The change guard is load-bearing, not an optimisation: the scrape pass runs
|
|
2140
|
-
* on every chunk, so an unguarded setter would fire the callback — and the
|
|
2141
|
-
* WS frame behind it — several times a second for the entire duration of a
|
|
2142
|
-
* turn while reporting the same value.
|
|
2143
|
-
*/
|
|
2144
|
-
private setPhase;
|
|
2145
|
-
private markReady;
|
|
2146
|
-
private handleExit;
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
interface ConversationWatcherEvents {
|
|
2150
|
-
/** Fires once per new newline-terminated line appended to a watched file. */
|
|
2151
|
-
onNewLine?: (filePath: string, line: string) => void;
|
|
2152
|
-
/**
|
|
2153
|
-
* Fires once per chokidar read with ALL new lines from that read, batched.
|
|
2154
|
-
* When set, it REPLACES the per-line onNewLine dispatch for that file —
|
|
2155
|
-
* callers pick one. Lets a burst of appended lines collapse into a single
|
|
2156
|
-
* downstream cache write + WebSocket broadcast.
|
|
2157
|
-
*/
|
|
2158
|
-
onNewLines?: (filePath: string, lines: string[]) => void;
|
|
2159
|
-
/**
|
|
2160
|
-
* Like onNewLines but also carries each line's absolute byte span in the
|
|
2161
|
-
* file (for the offset index). Fires ALONGSIDE onNewLines/onNewLine (it does
|
|
2162
|
-
* not replace them) so the cache tail write and the index extend can both
|
|
2163
|
-
* consume the same read. `readFrom` is the absolute byte offset the read
|
|
2164
|
-
* started at; `spans` are complete lines only (a torn trailing line is held
|
|
2165
|
-
* for the next read).
|
|
2166
|
-
*/
|
|
2167
|
-
onNewLineSpans?: (filePath: string, spans: LineSpan[], readFrom: number, endOffset: number) => void;
|
|
2168
|
-
/** Fires when chokidar reports an add/change/unlink at the directory level. */
|
|
2169
|
-
onConversationChanged?: (filePath: string) => void | Promise<void>;
|
|
2170
|
-
/** Fires when a tailed file is deleted (per-file watcher unlink event). */
|
|
2171
|
-
onFileDeleted?: (filePath: string) => void;
|
|
2172
|
-
/**
|
|
2173
|
-
* Fires when a tailed file shrank below our read offset (in-place truncation
|
|
2174
|
-
* or replacement by a shorter file). The tail has already reset to byte 0;
|
|
2175
|
-
* the consumer must discard any byte-offset index built for the old content,
|
|
2176
|
-
* which no longer describes this file.
|
|
2177
|
-
*/
|
|
2178
|
-
onTruncated?: (filePath: string) => void;
|
|
2179
|
-
/** Reported errors per file. */
|
|
2180
|
-
onError?: (filePath: string, error: Error) => void;
|
|
2181
|
-
}
|
|
2182
|
-
/**
|
|
2183
|
-
* Chokidar-backed replacement for src/file-watcher.ts.
|
|
2184
|
-
*
|
|
2185
|
-
* - watch(filePath) → tail a single JSONL file, emitting onNewLine
|
|
2186
|
-
* for each appended line.
|
|
2187
|
-
* - watchDirectory(dir) → mark cache dirty on add/change/unlink events
|
|
2188
|
-
* for any file inside the directory.
|
|
2189
|
-
*
|
|
2190
|
-
* Per the refactor plan, file watching is an OPTIMIZATION; correctness
|
|
2191
|
-
* still relies on refresh=1 / the latest HDD conversation id check.
|
|
2192
|
-
*/
|
|
2193
|
-
declare class ConversationWatcher {
|
|
2194
|
-
private files;
|
|
2195
|
-
private directories;
|
|
2196
|
-
private onNewLine;
|
|
2197
|
-
private onNewLines;
|
|
2198
|
-
private onNewLineSpans;
|
|
2199
|
-
private onConversationChanged;
|
|
2200
|
-
private onFileDeleted;
|
|
2201
|
-
private onTruncated;
|
|
2202
|
-
private onError;
|
|
2203
|
-
constructor(events?: ConversationWatcherEvents);
|
|
2204
|
-
watch(filePath: string): void;
|
|
2205
|
-
unwatch(filePath: string): void;
|
|
2206
|
-
/**
|
|
2207
|
-
* Re-drive the tail read for a file that's already being tailed. A per-file
|
|
2208
|
-
* chokidar handle can die silently (fs.watch stops firing after inode churn)
|
|
2209
|
-
* while the coarser directory watcher keeps reporting changes — calling this
|
|
2210
|
-
* from the directory-event path makes the tail self-healing. Reads are
|
|
2211
|
-
* offset-based and coalesced, so a redundant poke after a normal change
|
|
2212
|
-
* event is a cheap stat + no-op. Returns false for untailed paths.
|
|
2213
|
-
*/
|
|
2214
|
-
poke(filePath: string): boolean;
|
|
2215
|
-
/**
|
|
2216
|
-
* Watch a directory of conversation JSONL files. Fires
|
|
2217
|
-
* onConversationChanged for any add/change/unlink event so the caller
|
|
2218
|
-
* can mark the cache dirty without scanning everything immediately.
|
|
2219
|
-
*
|
|
2220
|
-
* **This costs one OS watch handle per file under `directory`, not one per
|
|
2221
|
-
* directory.** chokidar recurses the tree and registers a separate fs.watch
|
|
2222
|
-
* per entry, because a directory watch alone does not report writes to files
|
|
2223
|
-
* inside it — and per-file `change` events are exactly what the caller needs
|
|
2224
|
-
* (they drive poke()'s tail self-heal and the external-tail attach). So the
|
|
2225
|
-
* handle count tracks the size of the conversation corpus on disk, not the
|
|
2226
|
-
* number of live sessions, and it does not shrink until transcripts are
|
|
2227
|
-
* deleted. `ignoreInitial` suppresses the startup *events*, not the walk.
|
|
2228
|
-
*
|
|
2229
|
-
* Measured 2026-08-09 on the live macOS instance: 2131 open .jsonl fds
|
|
2230
|
-
* against 2133 files under the watched roots — 1:1, ~88% of all fds on the
|
|
2231
|
-
* process, at 2.0% of that box's 122 880 per-process ceiling. Comfortable
|
|
2232
|
-
* there. **Linux is the tight one**: these are inotify watches billed to the
|
|
2233
|
-
* per-user `max_user_watches`, which can be 8192 and is shared with every
|
|
2234
|
-
* other watcher the user runs. Exhaustion surfaces as ENOSPC on the `error`
|
|
2235
|
-
* event — which is why server.ts wires onError rather than leaving it unset.
|
|
2236
|
-
*
|
|
2237
|
-
* Before trading handles for a bound here, note the regression it invites: a
|
|
2238
|
-
* conversation excluded from the walk (by age or by an LRU cap) is one whose
|
|
2239
|
-
* external appends produce no event at all, and that failure is silent.
|
|
2240
|
-
*/
|
|
2241
|
-
watchDirectory(directory: string): void;
|
|
2242
|
-
unwatchDirectory(directory: string): void;
|
|
2243
|
-
dispose(): void;
|
|
2244
|
-
private readNewLines;
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
declare class StreamerServer {
|
|
2248
|
-
private httpServer;
|
|
2249
|
-
private ptyManager;
|
|
2250
|
-
private sessionStore;
|
|
2251
|
-
private wsHub;
|
|
2252
|
-
private fileWatcher;
|
|
2253
|
-
private sessionFileMap;
|
|
2254
|
-
private externalTails;
|
|
2255
|
-
private externalTailManager;
|
|
2256
|
-
private pendingLineSeqs;
|
|
2257
|
-
private pendingQuestions;
|
|
2258
|
-
private contendedSessions;
|
|
2259
|
-
private selfPtyEndedAt;
|
|
2260
|
-
private pendingQuestionKey;
|
|
2261
|
-
private pendingPermission;
|
|
2262
|
-
private pendingPermissionKey;
|
|
2263
|
-
private scannerManager;
|
|
2264
|
-
private sessionWatchers;
|
|
2265
|
-
private registryBoot;
|
|
2266
|
-
private conversationHandlers;
|
|
2267
|
-
private sessionHandlers;
|
|
2268
|
-
private binding;
|
|
2269
|
-
private activeWarmups;
|
|
2270
|
-
private nextWarmupId;
|
|
2271
|
-
private inFlightCacheWrites;
|
|
2272
|
-
private apiKey;
|
|
2273
|
-
private apiKeySource;
|
|
2274
|
-
private localNoAuth;
|
|
2275
|
-
private logMenubarRequests;
|
|
2276
|
-
private verbose;
|
|
2277
|
-
private scanProfiles;
|
|
2278
|
-
private dbPool;
|
|
2279
|
-
private dbInstanceId;
|
|
2280
|
-
private disableDb;
|
|
2281
|
-
private host;
|
|
2282
|
-
private skipStartupWarmup;
|
|
2283
|
-
private autoResumeOnBoot;
|
|
2284
|
-
private browseRoot;
|
|
2285
|
-
private publicUrl;
|
|
2286
|
-
private browserCors;
|
|
2287
|
-
private pairTokens;
|
|
2288
|
-
private exchangeAttempts;
|
|
2289
|
-
private sessionStartAttempts;
|
|
2290
|
-
private sessionInputAttempts;
|
|
2291
|
-
private ptyGracePeriodMs;
|
|
2292
|
-
private defaultSystemPrompt;
|
|
2293
|
-
private featureFlags;
|
|
2294
|
-
private featureFlagSources;
|
|
2295
|
-
private codexSystemPromptEnabled;
|
|
2296
|
-
private defaultPermissionMode;
|
|
2297
|
-
private defaultModel;
|
|
2298
|
-
private defaultEffort;
|
|
2299
|
-
private claudeFlags;
|
|
2300
|
-
private claudeExtraArgs;
|
|
2301
|
-
private claudeFlagsPersistable;
|
|
2302
|
-
private ptyGraceTimers;
|
|
2303
|
-
private ptyGraceDeferCounts;
|
|
2304
|
-
private sessionSubscribers;
|
|
2305
|
-
private lastAgentChunkAt;
|
|
2306
|
-
private terminalSeq;
|
|
2307
|
-
private idempotency;
|
|
2308
|
-
private sessionVerdicts;
|
|
2309
|
-
private idleReaperTimer;
|
|
2310
|
-
private clientIdToWs;
|
|
2311
|
-
private wsToClientId;
|
|
2312
|
-
private cache;
|
|
2313
|
-
private cacheMonitor;
|
|
2314
|
-
private projectsRepo;
|
|
2315
|
-
private conversationsRepo;
|
|
2316
|
-
private sessionsRepo;
|
|
2317
|
-
private managedSessionsRepo;
|
|
2318
|
-
private runtimeStore;
|
|
2319
|
-
private readonly streamerInstanceId;
|
|
2320
|
-
private cacheMetadataRepo;
|
|
2321
|
-
private pushRepo;
|
|
2322
|
-
private devicesRepo;
|
|
2323
|
-
private apnsClient;
|
|
2324
|
-
private liveActivityNotifier;
|
|
2325
|
-
private liveActivityRenewal;
|
|
2326
|
-
private waitingInputNotifier;
|
|
2327
|
-
private discoveryCache;
|
|
2328
|
-
private discoveryInFlight;
|
|
2329
|
-
private cacheDir;
|
|
2330
|
-
private runtimeDbPath;
|
|
2331
|
-
private tailSize;
|
|
2332
|
-
private directoryDebounceMs;
|
|
2333
|
-
private codexRoots;
|
|
2334
|
-
private includeAgents;
|
|
2335
|
-
private agentEntrypoints;
|
|
2336
|
-
private honoApp;
|
|
2337
|
-
private log;
|
|
2338
|
-
private agentConfig;
|
|
2339
|
-
private agentClient;
|
|
2340
|
-
private sessionStatusBus;
|
|
2341
|
-
constructor(config: ServerConfig & {
|
|
2342
|
-
apiKey: string;
|
|
2343
|
-
});
|
|
2344
|
-
private ptyAttachedIds;
|
|
2345
|
-
private rememberSelfPtyEnded;
|
|
2346
|
-
/**
|
|
2347
|
-
* Send a session_list to only the client that triggered this HTTP request
|
|
2348
|
-
* (identified by X-Client-Id header → registered WS socket). Falls back to
|
|
2349
|
-
* a full broadcast if no match exists (old clients, or no WS registered yet).
|
|
2350
|
-
*/
|
|
2351
|
-
private broadcastOrUnicastSessionList;
|
|
2352
|
-
private sessionListPayload;
|
|
2353
|
-
/**
|
|
2354
|
-
* Overlay boot-reconciliation verdicts onto session responses.
|
|
2355
|
-
*
|
|
2356
|
-
* A session left by a previous run is not in the in-memory store, so
|
|
2357
|
-
* SessionStore cannot classify it — it only ever sees what this run spawned.
|
|
2358
|
-
* Discovery may still surface the process, in which case the reconciler knows
|
|
2359
|
-
* strictly more about it than discovery does: it can tell `detached` (alive
|
|
2360
|
-
* and confirmed ours) from `orphaned` (alive but identity unconfirmed), which
|
|
2361
|
-
* a pid enumeration alone cannot.
|
|
2362
|
-
*
|
|
2363
|
-
* Only applied when the session is NOT live here: a session this run owns has
|
|
2364
|
-
* an authoritative lifecycle already, and a stale verdict must never override
|
|
2365
|
-
* it.
|
|
2366
|
-
*/
|
|
2367
|
-
private withReconciledLifecycle;
|
|
2368
|
-
private addSessionSubscriber;
|
|
2369
|
-
/**
|
|
2370
|
-
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
2371
|
-
*
|
|
2372
|
-
* APNS_KEY absent is the ordinary case on a dev machine and in CI, so this
|
|
2373
|
-
* logs once at info and leaves the feature off rather than failing: the server
|
|
2374
|
-
* must not refuse to boot over a missing optional push credential.
|
|
2375
|
-
*
|
|
2376
|
-
* The key is read from the environment as PEM contents and never from a path
|
|
2377
|
-
* on disk; neither it nor any device token is ever logged.
|
|
2378
|
-
*/
|
|
2379
|
-
private initLiveActivityPush;
|
|
2380
|
-
/**
|
|
2381
|
-
* Bring up "your turn" notifications over Expo's relay (#528).
|
|
2382
|
-
*
|
|
2383
|
-
* Unconditional, unlike Live Activity push: Expo holds the app's APNs and FCM
|
|
2384
|
-
* credentials, so a self-hosted streamer needs no credential of its own. The
|
|
2385
|
-
* access token is optional and only relevant if the Expo project has enhanced
|
|
2386
|
-
* security enabled — requiring one would lock out every self-hoster, since
|
|
2387
|
-
* they do not own the project. It is never logged.
|
|
2388
|
-
*/
|
|
2389
|
-
private initWaitingInputPush;
|
|
2390
|
-
/** Whether any live socket is subscribed to this session — "someone is looking". */
|
|
2391
|
-
private hasSessionSubscriber;
|
|
2392
|
-
/**
|
|
2393
|
-
* Release PTYs whose agent has been silent past IDLE_REAP_AFTER_MS.
|
|
2394
|
-
*
|
|
2395
|
-
* This is the bound that lets handleWsClose stop arming kill timers. The
|
|
2396
|
-
* distinction that matters: the old timer measured how long nobody was
|
|
2397
|
-
* *watching*, which is uncorrelated with whether work is in flight. This
|
|
2398
|
-
* measures how long the *agent* has produced nothing, and only ever considers
|
|
2399
|
-
* sessions that are already settled — a `running` PTY is skipped regardless of
|
|
2400
|
-
* age, so a long silent turn is never interrupted.
|
|
2401
|
-
*
|
|
2402
|
-
* Exposed (not private) so tests can drive one sweep deterministically instead
|
|
2403
|
-
* of waiting on the interval.
|
|
2404
|
-
*/
|
|
2405
|
-
reapIdleSessions(now?: number): string[];
|
|
2406
|
-
private startGraceTimer;
|
|
2407
|
-
get port(): number;
|
|
2408
|
-
private currentWarmupState;
|
|
2409
|
-
private beginWarmup;
|
|
2410
|
-
private finishWarmup;
|
|
2411
|
-
private withWarmup;
|
|
2412
|
-
private rejectIfWarmingUp;
|
|
2413
|
-
/**
|
|
2414
|
-
* Say which provider CLIs this machine can actually launch.
|
|
2415
|
-
*
|
|
2416
|
-
* The operator cannot discover this case unaided: under launchd/Task
|
|
2417
|
-
* Scheduler the service inherits a stripped PATH, so a CLI that works
|
|
2418
|
-
* perfectly in their terminal is invisible to the service, and every session
|
|
2419
|
-
* start dies milliseconds in. `/api/diagnostics` answers it too, but only for
|
|
2420
|
-
* someone who already suspects it.
|
|
2421
|
-
*
|
|
2422
|
-
* Availability only, never a version — `--version` costs a process spawn per
|
|
2423
|
-
* provider (85ms for claude here) and belongs on the first request that wants
|
|
2424
|
-
* it, not on boot.
|
|
2425
|
-
*
|
|
2426
|
-
* Called AFTER the port is bound, which is not cosmetic. This is the first
|
|
2427
|
-
* caller of the exe resolvers in the process, so the memo is cold by
|
|
2428
|
-
* definition and each provider pays one synchronous `which` / `where.exe`
|
|
2429
|
-
* (platform.ts) with a 3s timeout. On POSIX that is 3ms found, 7ms missing.
|
|
2430
|
-
* Windows is the risk — `where.exe` is slower, `execFileSync` blocks the
|
|
2431
|
-
* event loop, and Task Scheduler's stripped PATH is exactly where a miss
|
|
2432
|
-
* pays the full timeout — so the worst case is ~6s of two blocking lookups.
|
|
2433
|
-
* After `listen()` that delays the first requests on a box that cannot start
|
|
2434
|
-
* a session anyway; before it, it would have delayed binding the port.
|
|
2435
|
-
*/
|
|
2436
|
-
private logProviderAvailability;
|
|
2437
|
-
listen(port: number, opts?: {
|
|
2438
|
-
awaitReady?: boolean;
|
|
2439
|
-
}): Promise<void>;
|
|
2440
|
-
private bindWithRetry;
|
|
2441
|
-
private bindWithRetryLoop;
|
|
2442
|
-
private trackCacheWrite;
|
|
2443
|
-
close(): Promise<void>;
|
|
2444
|
-
private handleRequest;
|
|
2445
|
-
private handlePairStart;
|
|
2446
|
-
private handlePairExchange;
|
|
2447
|
-
private rotateApiKey;
|
|
2448
|
-
/**
|
|
2449
|
-
* The registry ships with the values so a client renders the list from one
|
|
2450
|
-
* round-trip, same as getClaudeFlagsConfig().
|
|
2451
|
-
*
|
|
2452
|
-
* Deliberately no `persisted` field: unlike claude-flags there is no PUT, and
|
|
2453
|
-
* the absence of that field is the signal that this endpoint is read-only.
|
|
2454
|
-
*/
|
|
2455
|
-
private getFeatureFlagsConfig;
|
|
2456
|
-
private getClaudeFlagsConfig;
|
|
2457
|
-
/**
|
|
2458
|
-
* Replace the per-server flag set. Applies to the NEXT spawn — a live PTY
|
|
2459
|
-
* keeps the argv it was started with.
|
|
2460
|
-
*
|
|
2461
|
-
* Mirrors rotateApiKey(): when the values were pinned by a CLI flag we still
|
|
2462
|
-
* apply them in memory but skip the server.yaml write, because the flag would
|
|
2463
|
-
* win again on restart and silently revert them.
|
|
2464
|
-
*
|
|
2465
|
-
* Logged with old→new at info level on purpose: this can disable the
|
|
2466
|
-
* permission prompts entirely, so it needs a forensic trail.
|
|
2467
|
-
*/
|
|
2468
|
-
private setClaudeFlagsConfig;
|
|
2469
|
-
/**
|
|
2470
|
-
* The three spawn options that a configured claude-flag can override, with
|
|
2471
|
-
* the boot-time CLI/yaml default as the fallback. Spread into every
|
|
2472
|
-
* start/resume/adopt call so all three paths agree.
|
|
2473
|
-
*
|
|
2474
|
-
* These ids are excluded from buildFlagArgs (SPAWN_POSITIONAL_FLAG_IDS)
|
|
2475
|
-
* precisely because they arrive here instead — the PTY spawn paths pass them
|
|
2476
|
-
* as explicit positionals, so emitting them from the allowlist too would
|
|
2477
|
-
* duplicate the flag.
|
|
2478
|
-
*
|
|
2479
|
-
* Narrowed with the type guards rather than cast: ClaudeFlagValues is a loose
|
|
2480
|
-
* Record by design, and while validateFlagValues already guarantees the shape
|
|
2481
|
-
* on the way in, TypeScript cannot see that through the record.
|
|
2482
|
-
*/
|
|
2483
|
-
private spawnFlagOverrides;
|
|
2484
|
-
private checkRateLimit;
|
|
2485
|
-
private checkExchangeRateLimit;
|
|
2486
|
-
private checkSessionStartRateLimit;
|
|
2487
|
-
private checkSessionInputRateLimit;
|
|
2488
|
-
private handleSessionsCount;
|
|
2489
|
-
/**
|
|
2490
|
-
* Live Codex sessions keep `SessionResponse.conversationId === managed.id`
|
|
2491
|
-
* (stable deep-link / PTY key) and store the rollout UUID separately as
|
|
2492
|
-
* `boundConversationId`. REST history is indexed under the rollout UUID, so
|
|
2493
|
-
* resolve the placeholder → bound id before looking up the scanner.
|
|
2494
|
-
*/
|
|
2495
|
-
private resolveConversationLookupId;
|
|
2496
|
-
/** File path for a live managed session (placeholder id or bound Codex id). */
|
|
2497
|
-
private findLiveSessionFilePath;
|
|
2498
|
-
/** True when a conversation UUID is the bound rollout of a live PTY session. */
|
|
2499
|
-
private isBoundConversationLive;
|
|
2500
|
-
/**
|
|
2501
|
-
* Broadcast conversation JSONL lines to WS clients. Codex rollout lines are
|
|
2502
|
-
* normalized to the Claude `type:user|assistant` shape mobile understands;
|
|
2503
|
-
* Claude lines pass through unchanged so seq alignment stays intact.
|
|
2504
|
-
*/
|
|
2505
|
-
private broadcastConversationLines;
|
|
2506
|
-
/**
|
|
2507
|
-
* Resolve a client-supplied session/conversation id into everything needed to
|
|
2508
|
-
* launch against it: the id the PROVIDER filed the history under, that
|
|
2509
|
-
* history's path, the project cwd, and which CLI owns it.
|
|
2510
|
-
*
|
|
2511
|
-
* Shared by resume and fork so the two can never disagree about identity —
|
|
2512
|
-
* which for Codex is the whole difficulty: the id a client navigated to may
|
|
2513
|
-
* be a local placeholder, and only the registry knows the rollout id behind
|
|
2514
|
-
* it.
|
|
2515
|
-
*/
|
|
2516
|
-
private resolveConversationTarget;
|
|
2517
|
-
/**
|
|
2518
|
-
* Block until a freshly spawned session reaches `waiting_input` (ready) or
|
|
2519
|
-
* `idle` (failed), or until `timeoutMs` elapses with the process still alive.
|
|
2520
|
-
*
|
|
2521
|
-
* "timeout" is not an error: it is the pre-existing asynchronous contract —
|
|
2522
|
-
* the session keeps booting and the caller answers with a pending shape.
|
|
2523
|
-
*/
|
|
2524
|
-
private waitForStartupOutcome;
|
|
2525
|
-
/**
|
|
2526
|
-
* Drop every trace of a managed session: in-memory store, durable registry
|
|
2527
|
-
* row, and the collision-probe markers that would otherwise outlive it.
|
|
2528
|
-
*
|
|
2529
|
-
* Used when a start never became usable (`abandonFailedStart`) and when stop
|
|
2530
|
-
* is asked to discard an empty session that has no cached conversation. The
|
|
2531
|
-
* registry delete is load-bearing — `rehydrateSessions` will bring the row
|
|
2532
|
-
* back on the next boot if it remains.
|
|
2533
|
-
*
|
|
2534
|
-
* Callers that kill the PTY (`putOnHold`) must do that *first*: onStatusChange
|
|
2535
|
-
* on idle writes `selfPtyEndedAt` and a registry status, and those have to
|
|
2536
|
-
* be cleared here afterwards.
|
|
2537
|
-
*/
|
|
2538
|
-
private forgetSession;
|
|
2539
|
-
/**
|
|
2540
|
-
* Drop every trace of a session that never became usable.
|
|
2541
|
-
*
|
|
2542
|
-
* The runner has already torn itself down (failStartup / handleExit); what
|
|
2543
|
-
* remains is server-side bookkeeping that would otherwise leave a dead
|
|
2544
|
-
* session in the list, a registry row claiming a spawn, and a `selfPtyEndedAt`
|
|
2545
|
-
* marker that would suppress the mtime collision signal on the NEXT resume —
|
|
2546
|
-
* i.e. it would help hide the very owner we just collided with.
|
|
2547
|
-
*/
|
|
2548
|
-
private abandonFailedStart;
|
|
2549
|
-
private enrichResumedSessionAsync;
|
|
2550
|
-
private processJsonlQuestions;
|
|
2551
|
-
private cancelPendingQuestion;
|
|
2552
|
-
private handleBrowse;
|
|
2553
|
-
private handleMkdir;
|
|
2554
|
-
/**
|
|
2555
|
-
* Retarget a LIVE session's model or effort by typing the corresponding
|
|
2556
|
-
* Claude Code slash command into its PTY.
|
|
2557
|
-
*
|
|
2558
|
-
* There is no CLI or IPC channel for this — `--model`/`--effort` are spawn
|
|
2559
|
-
* arguments — so the interactive `/model <x>` / `/effort <y>` commands are the
|
|
2560
|
-
* only way to change a session already running. Both accept an argument and
|
|
2561
|
-
* apply it without opening the picker (verified against Claude Code v2.1.220).
|
|
2562
|
-
*
|
|
2563
|
-
* Answers 202, not 200: the value is applied by the TUI on its next render, so
|
|
2564
|
-
* there is nothing truthful to echo back synchronously. Clients confirm with
|
|
2565
|
-
* `GET /api/sessions/:id`, which scrapes the applied value off the live status
|
|
2566
|
-
* line.
|
|
2567
|
-
*/
|
|
2568
|
-
private applyLiveSessionSetting;
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
export { type AgentClient, type AgentClientOpts, type AgentConfig, type AgentPhase, type AppendArgs, type AskOption, type AskQuestion, CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER, type CacheAlertResolveAction, type ConversationListResponse, ConversationWatcher, type ConversationWriter, type DbConfig, type DiscoveredProcess, LiveSessionManager, type ManagedSession, PTYManager, type PTYManagerOptions, type PermissionOption, type ProcessLiveness, type ProgressDedupeLRU, type ProviderName, type ServerConfig, type ServerWarmingUpResponse, type ServerWarmupState, type SessionActivity, type SessionCursor, type SessionLifecycle, type SessionListPage, type SessionListQuery, type SessionOwnership, type SessionResponse, type SessionRunner, type SessionSortKey, type SessionStatus, SessionStore, type SortOrder, type StartForkSessionOptions, type StartFreshSessionOptions, type StartSessionOptions, type StatusConfidence, type StatusSource$1 as StatusSource, StreamerServer, type UserMessage, WSHub, type WSMessage, confidenceForSource, createAgentClient, createConversationWriter, createPool, createProgressDedupeLRU, createProgressRoutes, discoverClaudeProcesses, generateApiKey, getDbConfig, isDbEnabled, isProviderName, isProviderResumable, loadOrCreateApiKey, maskConnectionString, readAgentConfig, validateApiKey };
|
|
1
|
+
export { type AgentClient, type AgentClientOpts, createAgentClient, } from "./agent/agent-client";
|
|
2
|
+
export { type AgentConfig, readAgentConfig } from "./agent/agent-config";
|
|
3
|
+
export { type AppendArgs, type ConversationWriter, createConversationWriter, } from "./agent/conversation-writer";
|
|
4
|
+
export { createProgressDedupeLRU, type ProgressDedupeLRU } from "./agent/dedupe";
|
|
5
|
+
export { createProgressRoutes } from "./api/routes/progress.routes";
|
|
6
|
+
export { generateApiKey, loadOrCreateApiKey, validateApiKey } from "./auth";
|
|
7
|
+
export type { DbConfig } from "./db";
|
|
8
|
+
export { createPool, getDbConfig, isDbEnabled, maskConnectionString } from "./db";
|
|
9
|
+
export { LiveSessionManager } from "./live-session-manager";
|
|
10
|
+
export { discoverClaudeProcesses } from "./process-discovery";
|
|
11
|
+
export { CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER, isProviderName, isProviderResumable, type ProviderName, } from "./providers";
|
|
12
|
+
export { PTYManager } from "./pty-manager";
|
|
13
|
+
export { StreamerServer } from "./server";
|
|
14
|
+
export { ConversationWatcher } from "./services/conversations/conversationWatcher";
|
|
15
|
+
export { SessionStore } from "./session-store";
|
|
16
|
+
export * from "./types";
|
|
17
|
+
export { WSHub } from "./ws-hub";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|