codekin 0.2.2 → 0.3.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.
Files changed (64) hide show
  1. package/dist/assets/index-B4rYTSlV.css +1 -0
  2. package/dist/assets/index-CWc3yfPn.js +181 -0
  3. package/dist/index.html +2 -2
  4. package/package.json +8 -5
  5. package/server/dist/approval-manager.d.ts +71 -0
  6. package/server/dist/approval-manager.js +249 -0
  7. package/server/dist/approval-manager.js.map +1 -0
  8. package/server/dist/auth-routes.d.ts +11 -0
  9. package/server/dist/auth-routes.js +11 -0
  10. package/server/dist/auth-routes.js.map +1 -1
  11. package/server/dist/claude-process.js +2 -1
  12. package/server/dist/claude-process.js.map +1 -1
  13. package/server/dist/config.d.ts +1 -2
  14. package/server/dist/config.js +16 -4
  15. package/server/dist/config.js.map +1 -1
  16. package/server/dist/crypto-utils.d.ts +16 -1
  17. package/server/dist/crypto-utils.js +44 -1
  18. package/server/dist/crypto-utils.js.map +1 -1
  19. package/server/dist/docs-routes.d.ts +16 -0
  20. package/server/dist/docs-routes.js +141 -0
  21. package/server/dist/docs-routes.js.map +1 -0
  22. package/server/dist/session-manager.d.ts +37 -84
  23. package/server/dist/session-manager.js +89 -472
  24. package/server/dist/session-manager.js.map +1 -1
  25. package/server/dist/session-naming.d.ts +35 -0
  26. package/server/dist/session-naming.js +168 -0
  27. package/server/dist/session-naming.js.map +1 -0
  28. package/server/dist/session-persistence.d.ts +30 -0
  29. package/server/dist/session-persistence.js +93 -0
  30. package/server/dist/session-persistence.js.map +1 -0
  31. package/server/dist/session-routes.d.ts +2 -1
  32. package/server/dist/session-routes.js +11 -8
  33. package/server/dist/session-routes.js.map +1 -1
  34. package/server/dist/stepflow-handler.d.ts +3 -9
  35. package/server/dist/stepflow-handler.js +19 -54
  36. package/server/dist/stepflow-handler.js.map +1 -1
  37. package/server/dist/types.d.ts +3 -0
  38. package/server/dist/upload-routes.js +6 -3
  39. package/server/dist/upload-routes.js.map +1 -1
  40. package/server/dist/webhook-github.d.ts +23 -5
  41. package/server/dist/webhook-github.js +23 -5
  42. package/server/dist/webhook-github.js.map +1 -1
  43. package/server/dist/webhook-handler-base.d.ts +45 -0
  44. package/server/dist/webhook-handler-base.js +86 -0
  45. package/server/dist/webhook-handler-base.js.map +1 -0
  46. package/server/dist/webhook-handler.d.ts +3 -10
  47. package/server/dist/webhook-handler.js +6 -46
  48. package/server/dist/webhook-handler.js.map +1 -1
  49. package/server/dist/webhook-workspace.d.ts +8 -1
  50. package/server/dist/webhook-workspace.js +71 -47
  51. package/server/dist/webhook-workspace.js.map +1 -1
  52. package/server/dist/workflow-config.d.ts +2 -0
  53. package/server/dist/workflow-config.js +1 -1
  54. package/server/dist/workflow-config.js.map +1 -1
  55. package/server/dist/workflow-loader.d.ts +2 -0
  56. package/server/dist/workflow-loader.js +13 -9
  57. package/server/dist/workflow-loader.js.map +1 -1
  58. package/server/dist/workflow-routes.js +3 -2
  59. package/server/dist/workflow-routes.js.map +1 -1
  60. package/server/dist/ws-server.js +77 -16
  61. package/server/dist/ws-server.js.map +1 -1
  62. package/server/workflows/repo-health.weekly.md +111 -0
  63. package/dist/assets/index-CQdQ4FhF.css +0 -1
  64. package/dist/assets/index-D6pRORYQ.js +0 -115
@@ -1,7 +1,27 @@
1
1
  /**
2
- * Shared cryptographic utilities for webhook signature verification.
2
+ * Shared cryptographic utilities for webhook signature verification,
3
+ * session-scoped token derivation, and secret redaction.
3
4
  */
4
5
  import crypto from 'crypto';
6
+ /**
7
+ * Redact potential secrets from a string before logging.
8
+ * Matches common patterns: Bearer tokens, Authorization headers,
9
+ * URL-embedded passwords, and common API key formats.
10
+ */
11
+ const SECRET_PATTERNS = [
12
+ [/Bearer\s+\S+/gi, 'Bearer [REDACTED]'],
13
+ [/Authorization:\s*\S+/gi, 'Authorization: [REDACTED]'],
14
+ [/(https?:\/\/[^:]+):([^@]+)@/gi, '$1:[REDACTED]@'],
15
+ [/\b(sk-|pk-|api[_-]?key[=:]\s*)\S+/gi, '$1[REDACTED]'],
16
+ [/(password|passwd|pwd|secret|token)[=:]\s*\S+/gi, '$1=[REDACTED]'],
17
+ ];
18
+ export function redactSecrets(input) {
19
+ let result = input;
20
+ for (const [pattern, replacement] of SECRET_PATTERNS) {
21
+ result = result.replace(pattern, replacement);
22
+ }
23
+ return result;
24
+ }
5
25
  /**
6
26
  * Verify an HMAC-SHA256 signature over a payload.
7
27
  * Comparison uses timingSafeEqual to resist timing oracle attacks.
@@ -19,4 +39,27 @@ export function verifyHmacSignature(payload, signature, secret) {
19
39
  return false;
20
40
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
21
41
  }
42
+ /**
43
+ * Derive a session-scoped token from the master auth token.
44
+ * Uses HMAC-SHA256(masterToken, "session:" + sessionId) so that:
45
+ * - Each session gets a unique, unpredictable token
46
+ * - The token cannot be used to recover the master token
47
+ * - The server can verify it without storing extra state
48
+ */
49
+ export function deriveSessionToken(masterToken, sessionId) {
50
+ return crypto
51
+ .createHmac('sha256', masterToken)
52
+ .update(`session:${sessionId}`)
53
+ .digest('hex');
54
+ }
55
+ /**
56
+ * Verify a session-scoped token against the master token and session ID.
57
+ * Uses timing-safe comparison.
58
+ */
59
+ export function verifySessionToken(masterToken, sessionId, candidateToken) {
60
+ const expected = deriveSessionToken(masterToken, sessionId);
61
+ if (candidateToken.length !== expected.length)
62
+ return false;
63
+ return crypto.timingSafeEqual(Buffer.from(candidateToken), Buffer.from(expected));
64
+ }
22
65
  //# sourceMappingURL=crypto-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto-utils.js","sourceRoot":"","sources":["../crypto-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,SAAiB,EAAE,MAAc;IACpF,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM;SAChC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,KAAK,CAAC,CAAA;IAEhB,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACtD,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC9E,CAAC"}
1
+ {"version":3,"file":"crypto-utils.js","sourceRoot":"","sources":["../crypto-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B;;;;GAIG;AACH,MAAM,eAAe,GAA4B;IAC/C,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IACvC,CAAC,wBAAwB,EAAE,2BAA2B,CAAC;IACvD,CAAC,+BAA+B,EAAE,gBAAgB,CAAC;IACnD,CAAC,qCAAqC,EAAE,cAAc,CAAC;IACvD,CAAC,gDAAgD,EAAE,eAAe,CAAC;CACpE,CAAA;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,eAAe,EAAE,CAAC;QACrD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAC/C,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,SAAiB,EAAE,MAAc;IACpF,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM;SAChC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,KAAK,CAAC,CAAA;IAEhB,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACtD,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,SAAiB;IACvE,OAAO,MAAM;SACV,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC;SACjC,MAAM,CAAC,WAAW,SAAS,EAAE,CAAC;SAC9B,MAAM,CAAC,KAAK,CAAC,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,SAAiB,EAAE,cAAsB;IAC/F,MAAM,QAAQ,GAAG,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAC3D,IAAI,cAAc,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC3D,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AACnF,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * REST routes for the docs browser feature.
3
+ *
4
+ * Provides two endpoints:
5
+ * - GET /api/docs?repo=<path> — list markdown files in a repo
6
+ * - GET /api/docs/file?repo=<path>&file=<relPath> — get raw content of a markdown file
7
+ *
8
+ * Repo path and file path are passed as query parameters to avoid
9
+ * Express routing issues with encoded slashes in path segments.
10
+ */
11
+ import { Router } from 'express';
12
+ import type { Request } from 'express';
13
+ type VerifyFn = (token: string | undefined) => boolean;
14
+ type ExtractFn = (req: Request) => string | undefined;
15
+ export declare function createDocsRouter(verifyToken: VerifyFn, extractToken: ExtractFn): Router;
16
+ export {};
@@ -0,0 +1,141 @@
1
+ /**
2
+ * REST routes for the docs browser feature.
3
+ *
4
+ * Provides two endpoints:
5
+ * - GET /api/docs?repo=<path> — list markdown files in a repo
6
+ * - GET /api/docs/file?repo=<path>&file=<relPath> — get raw content of a markdown file
7
+ *
8
+ * Repo path and file path are passed as query parameters to avoid
9
+ * Express routing issues with encoded slashes in path segments.
10
+ */
11
+ import { Router } from 'express';
12
+ import { readFileSync, readdirSync, statSync } from 'fs';
13
+ import { join, resolve, relative, extname } from 'path';
14
+ /** Directories to exclude from the file listing. */
15
+ const EXCLUDED_DIRS = new Set([
16
+ 'node_modules', '.git', '.github', '.codekin', '.vscode',
17
+ '.idea', 'dist', 'build', 'coverage', '__pycache__', '.next',
18
+ '.nuxt', '.svelte-kit', 'vendor', '.cache',
19
+ ]);
20
+ /** Files pinned to the top of the list, in display order. */
21
+ const PINNED_FILES = ['CLAUDE.md', 'README.md'];
22
+ /**
23
+ * Recursively find all .md files in a directory, up to maxDepth levels deep.
24
+ * Excludes hidden directories and common non-source directories.
25
+ */
26
+ function findMarkdownFiles(root, maxDepth) {
27
+ const results = [];
28
+ function walk(dir, depth) {
29
+ if (depth > maxDepth)
30
+ return;
31
+ let entries;
32
+ try {
33
+ entries = readdirSync(dir);
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ for (const entry of entries) {
39
+ if (entry.startsWith('.') && entry !== '.')
40
+ continue;
41
+ const fullPath = join(dir, entry);
42
+ let stat;
43
+ try {
44
+ stat = statSync(fullPath);
45
+ }
46
+ catch {
47
+ continue;
48
+ }
49
+ if (stat.isDirectory()) {
50
+ if (!EXCLUDED_DIRS.has(entry)) {
51
+ walk(fullPath, depth + 1);
52
+ }
53
+ }
54
+ else if (stat.isFile() && extname(entry).toLowerCase() === '.md') {
55
+ results.push(relative(root, fullPath));
56
+ }
57
+ }
58
+ }
59
+ walk(root, 0);
60
+ return results;
61
+ }
62
+ export function createDocsRouter(verifyToken, extractToken) {
63
+ const router = Router();
64
+ // Auth middleware for all docs routes
65
+ router.use('/api/docs', (req, res, next) => {
66
+ if (!verifyToken(extractToken(req))) {
67
+ return res.status(401).json({ error: 'Unauthorized' });
68
+ }
69
+ next();
70
+ });
71
+ /**
72
+ * GET /api/docs?repo=<path>
73
+ * List markdown files in the repo. Pinned files appear first.
74
+ */
75
+ router.get('/api/docs', (req, res) => {
76
+ const repoPath = req.query.repo;
77
+ if (!repoPath) {
78
+ return res.status(400).json({ error: 'Missing repo query parameter' });
79
+ }
80
+ // Validate the repo path exists and is a directory
81
+ try {
82
+ const stat = statSync(repoPath);
83
+ if (!stat.isDirectory()) {
84
+ return res.status(400).json({ error: 'Not a directory' });
85
+ }
86
+ }
87
+ catch {
88
+ return res.status(404).json({ error: 'Repo not found' });
89
+ }
90
+ const mdFiles = findMarkdownFiles(repoPath, 3);
91
+ const pinnedSet = new Set(PINNED_FILES.map(f => f.toLowerCase()));
92
+ const pinned = [];
93
+ const rest = [];
94
+ for (const filePath of mdFiles) {
95
+ if (pinnedSet.has(filePath.toLowerCase())) {
96
+ pinned.push({ path: filePath, pinned: true });
97
+ }
98
+ else {
99
+ rest.push({ path: filePath, pinned: false });
100
+ }
101
+ }
102
+ // Sort pinned in the defined order, rest alphabetically
103
+ pinned.sort((a, b) => {
104
+ const ai = PINNED_FILES.findIndex(p => p.toLowerCase() === a.path.toLowerCase());
105
+ const bi = PINNED_FILES.findIndex(p => p.toLowerCase() === b.path.toLowerCase());
106
+ return ai - bi;
107
+ });
108
+ rest.sort((a, b) => a.path.localeCompare(b.path));
109
+ res.json({ files: [...pinned, ...rest] });
110
+ });
111
+ /**
112
+ * GET /api/docs/file?repo=<path>&file=<relPath>
113
+ * Get the raw content of a single markdown file.
114
+ */
115
+ router.get('/api/docs/file', (req, res) => {
116
+ const repoPath = req.query.repo;
117
+ const filePath = req.query.file;
118
+ if (!repoPath || !filePath) {
119
+ return res.status(400).json({ error: 'Missing repo or file query parameter' });
120
+ }
121
+ // Validate file extension
122
+ if (extname(filePath).toLowerCase() !== '.md') {
123
+ return res.status(400).json({ error: 'Only .md files are supported' });
124
+ }
125
+ // Path traversal guard
126
+ const resolved = resolve(repoPath, filePath);
127
+ const repoResolved = resolve(repoPath);
128
+ if (!resolved.startsWith(repoResolved + '/') && resolved !== repoResolved) {
129
+ return res.status(404).json({ error: 'File not found' });
130
+ }
131
+ try {
132
+ const content = readFileSync(resolved, 'utf-8');
133
+ res.json({ path: filePath, content });
134
+ }
135
+ catch {
136
+ res.status(404).json({ error: 'File not found' });
137
+ }
138
+ });
139
+ return router;
140
+ }
141
+ //# sourceMappingURL=docs-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-routes.js","sourceRoot":"","sources":["../docs-routes.ts"],"names":[],"mappings":"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"}
@@ -10,9 +10,10 @@
10
10
  * on server startup. Active sessions are automatically restarted after a
11
11
  * server restart with staggered delays.
12
12
  *
13
- * Auto-approval rules (tools and Bash commands) are stored per-repo (keyed by
14
- * workingDir) in ~/.codekin/repo-approvals.json, so they persist across
15
- * sessions sharing the same repo.
13
+ * Delegates to focused modules:
14
+ * - ApprovalManager: repo-level auto-approval rules for tools/commands
15
+ * - SessionNaming: AI-powered session name generation with retry logic
16
+ * - SessionPersistence: disk I/O for session state
16
17
  */
17
18
  import type { WebSocket } from 'ws';
18
19
  import { SessionArchive } from './session-archive.js';
@@ -25,10 +26,6 @@ export interface CreateSessionOptions {
25
26
  }
26
27
  export declare class SessionManager {
27
28
  private sessions;
28
- /** Repo-level auto-approval store, keyed by workingDir (repo path). */
29
- private repoApprovals;
30
- private _persistTimer;
31
- private _approvalPersistTimer;
32
29
  /** SQLite archive for closed sessions. */
33
30
  readonly archive: SessionArchive;
34
31
  /** Exposed so ws-server can pass its port to child Claude processes. */
@@ -39,42 +36,40 @@ export declare class SessionManager {
39
36
  _globalBroadcast: ((msg: WsServerMessage) => void) | null;
40
37
  /** Registered listeners notified when a session's Claude process exits. */
41
38
  private _exitListeners;
39
+ /** Delegated approval logic. */
40
+ private approvalManager;
41
+ /** Delegated naming logic. */
42
+ private sessionNaming;
43
+ /** Delegated persistence logic. */
44
+ private sessionPersistence;
42
45
  constructor();
43
- /** Get or create the approval entry for a repo (workingDir). */
44
- private getRepoApprovalEntry;
45
- /** Add an auto-approval rule for a repo and persist. */
46
- private addRepoApproval;
47
- /**
48
- * Command prefixes where prefix-based auto-approval is safe.
49
- * Only commands whose behavior is determined by later arguments (not by target)
50
- * should be listed here. Dangerous commands like rm, sudo, curl, etc. require
51
- * exact match to prevent escalation (e.g. approving `rm -rf /tmp/x` should NOT
52
- * also approve `rm -rf /`).
53
- */
54
- private static readonly SAFE_PREFIX_COMMANDS;
55
- /**
56
- * Check if a tool/command is auto-approved for a repo.
57
- * For Bash commands, uses prefix matching only for safe commands;
58
- * dangerous commands require exact match to prevent escalation.
59
- */
46
+ /** Check if a tool/command is auto-approved for a repo. */
60
47
  checkAutoApproval(workingDir: string, toolName: string, toolInput: Record<string, unknown>): boolean;
61
- /** Extract the command prefix (first two tokens) for prefix-based matching. */
62
- private commandPrefix;
63
- /**
64
- * Derive a glob pattern from a tool invocation for "Approve Pattern".
65
- * Returns a string like "cat *" or "git diff *", or null if no safe pattern applies.
66
- * Patterns use the format "<prefix> *" meaning "this prefix followed by anything".
67
- */
48
+ /** Derive a glob pattern from a tool invocation for "Approve Pattern". */
68
49
  derivePattern(toolName: string, toolInput: Record<string, unknown>): string | null;
69
- /**
70
- * Check if a bash command matches a stored pattern.
71
- * Patterns of the form "<prefix> *" match any command starting with <prefix>.
72
- */
73
- private matchesPattern;
74
- /** Save an "Always Allow" approval for a tool/command. */
75
- private saveAlwaysAllow;
76
- /** Save a pattern-based approval (e.g. "cat *") for a tool/command. */
77
- private savePatternApproval;
50
+ /** Return the auto-approved tools, commands, and patterns for a repo (workingDir). */
51
+ getApprovals(workingDir: string): {
52
+ tools: string[];
53
+ commands: string[];
54
+ patterns: string[];
55
+ };
56
+ /** Remove an auto-approval rule for a repo (workingDir) and persist to disk. */
57
+ removeApproval(workingDir: string, opts: {
58
+ tool?: string;
59
+ command?: string;
60
+ pattern?: string;
61
+ }, skipPersist?: boolean): 'invalid' | boolean;
62
+ /** Add an auto-approval rule for a repo and persist (used by tests via `as any`). */
63
+ private addRepoApproval;
64
+ /** Write repo-level approvals to disk. Exposed for shutdown. */
65
+ persistRepoApprovals(): void;
66
+ /** Schedule session naming via AI provider. */
67
+ scheduleSessionNaming(sessionId: string): void;
68
+ /** Re-trigger session naming on user interaction. */
69
+ retrySessionNamingOnInteraction(sessionId: string): void;
70
+ /** Write all sessions to disk as JSON (atomic rename to prevent corruption). */
71
+ persistToDisk(): void;
72
+ private persistToDiskDebounced;
78
73
  /** Create a new session and persist to disk. */
79
74
  create(name: string, workingDir: string, options?: CreateSessionOptions): Session;
80
75
  /** Register a listener called when any session's Claude process exits.
@@ -83,26 +78,6 @@ export declare class SessionManager {
83
78
  get(id: string): Session | undefined;
84
79
  list(): SessionInfo[];
85
80
  rename(sessionId: string, newName: string): boolean;
86
- /** Max naming retry attempts before giving up. */
87
- private static readonly MAX_NAMING_ATTEMPTS;
88
- /** Back-off delays for naming retries: 20s, 60s, 120s, 240s, 240s */
89
- private static readonly NAMING_DELAYS;
90
- /** Schedule session naming via the Anthropic API.
91
- * Used as a 20s fallback for long responses — fires immediately via result handler
92
- * for responses that finish sooner.
93
- * Automatically retries on failure with increasing delays. */
94
- scheduleSessionNaming(sessionId: string): void;
95
- /** Resolve the AI model to use for session naming based on available API keys.
96
- * Respects the user's preferred support provider setting.
97
- * Fallback priority: Groq (free/fast) → OpenAI → Gemini → Anthropic. */
98
- private getNamingModel;
99
- /** Execute the actual naming call. Called by the timer set in scheduleSessionNaming.
100
- * Uses the first available API provider for minimal cost and latency. */
101
- private executeSessionNaming;
102
- /** Re-trigger session naming when user interacts, if the session is still unnamed
103
- * and no naming timer is already pending. Uses a short delay (5s) since we already
104
- * have conversation context at this point. */
105
- retrySessionNamingOnInteraction(sessionId: string): void;
106
81
  /** Add a WebSocket client to a session. Returns the session or undefined if not found.
107
82
  * Re-broadcasts any pending approval/control prompts so the joining client sees them. */
108
83
  join(sessionId: string, ws: WebSocket): Session | undefined;
@@ -171,14 +146,14 @@ export declare class SessionManager {
171
146
  /** Update the model for a session and restart Claude with the new model. */
172
147
  setModel(sessionId: string, model: string): boolean;
173
148
  stopClaude(sessionId: string): void;
149
+ /** Check if an error result text matches a transient API error worth retrying. */
150
+ private isRetryableApiError;
174
151
  /**
175
152
  * Build a condensed text summary of a session's conversation history.
176
153
  * Used as context when auto-starting Claude for sessions without a saved
177
154
  * Claude session ID (so the CLI can't resume from its own storage).
178
155
  * Caps output at ~4000 chars, keeping the most recent exchanges.
179
156
  */
180
- /** Check if an error result text matches a transient API error worth retrying. */
181
- private isRetryableApiError;
182
157
  private buildSessionContext;
183
158
  private resetStallTimer;
184
159
  private clearStallTimer;
@@ -199,28 +174,6 @@ export declare class SessionManager {
199
174
  * in-progress state after session restore.
200
175
  */
201
176
  private completeInProgressTasks;
202
- /** Return the auto-approved tools, commands, and patterns for a repo (workingDir). */
203
- getApprovals(workingDir: string): {
204
- tools: string[];
205
- commands: string[];
206
- patterns: string[];
207
- };
208
- /** Remove an auto-approval rule for a repo (workingDir) and persist to disk.
209
- * Returns 'invalid' if no tool/command provided, or boolean indicating if something was deleted.
210
- * Pass skipPersist=true for bulk operations (caller must call persistRepoApprovals after). */
211
- removeApproval(workingDir: string, opts: {
212
- tool?: string;
213
- command?: string;
214
- pattern?: string;
215
- }, skipPersist?: boolean): 'invalid' | boolean;
216
- /** Write all sessions to disk as JSON (atomic rename to prevent corruption). */
217
- persistToDisk(): void;
218
- private persistToDiskDebounced;
219
- /** Write repo-level approvals to disk (atomic rename). */
220
- persistRepoApprovals(): void;
221
- private persistRepoApprovalsDebounced;
222
- private restoreFromDisk;
223
- private restoreRepoApprovalsFromDisk;
224
177
  /**
225
178
  * Auto-restart Claude processes for sessions that were active before a server
226
179
  * restart. Each session is started with a staggered delay to avoid flooding.