byterover-cli 1.0.4 → 1.0.5
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/README.md +13 -2
- package/dist/commands/curate.js +1 -1
- package/dist/commands/main.d.ts +13 -0
- package/dist/commands/main.js +53 -2
- package/dist/commands/query.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/domain/cipher/llm/registry.js +53 -2
- package/dist/core/domain/cipher/llm/types.d.ts +2 -0
- package/dist/core/domain/cipher/process/types.d.ts +7 -0
- package/dist/core/domain/cipher/session/session-metadata.d.ts +178 -0
- package/dist/core/domain/cipher/session/session-metadata.js +147 -0
- package/dist/core/domain/knowledge/markdown-writer.d.ts +15 -18
- package/dist/core/domain/knowledge/markdown-writer.js +232 -34
- package/dist/core/domain/knowledge/relation-parser.d.ts +25 -39
- package/dist/core/domain/knowledge/relation-parser.js +39 -61
- package/dist/core/domain/transport/schemas.d.ts +37 -2
- package/dist/core/domain/transport/schemas.js +23 -2
- package/dist/core/interfaces/cipher/i-session-persistence.d.ts +133 -0
- package/dist/core/interfaces/cipher/i-session-persistence.js +7 -0
- package/dist/core/interfaces/cipher/message-types.d.ts +6 -0
- package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -2
- package/dist/core/interfaces/i-context-file-reader.d.ts +3 -0
- package/dist/core/interfaces/usecase/{i-clear-use-case.d.ts → i-reset-use-case.d.ts} +1 -1
- package/dist/infra/cipher/agent/agent-schemas.d.ts +6 -6
- package/dist/infra/cipher/agent/service-initializer.js +4 -4
- package/dist/infra/cipher/file-system/context-tree-file-system-factory.js +3 -2
- package/dist/infra/cipher/file-system/file-system-service.js +1 -0
- package/dist/infra/cipher/http/internal-llm-http-service.js +3 -5
- package/dist/infra/cipher/interactive-loop.js +3 -1
- package/dist/infra/cipher/llm/context/context-manager.js +40 -16
- package/dist/infra/cipher/llm/formatters/gemini-formatter.d.ts +13 -0
- package/dist/infra/cipher/llm/formatters/gemini-formatter.js +98 -6
- package/dist/infra/cipher/llm/generators/byterover-content-generator.js +6 -2
- package/dist/infra/cipher/llm/thought-parser.d.ts +21 -0
- package/dist/infra/cipher/llm/thought-parser.js +27 -0
- package/dist/infra/cipher/llm/tool-output-processor.d.ts +10 -0
- package/dist/infra/cipher/llm/tool-output-processor.js +80 -7
- package/dist/infra/cipher/process/process-service.js +11 -3
- package/dist/infra/cipher/session/chat-session.d.ts +7 -2
- package/dist/infra/cipher/session/chat-session.js +90 -52
- package/dist/infra/cipher/session/session-metadata-store.d.ts +52 -0
- package/dist/infra/cipher/session/session-metadata-store.js +406 -0
- package/dist/infra/cipher/tools/implementations/curate-tool.js +113 -35
- package/dist/infra/cipher/tools/implementations/task-tool.js +1 -0
- package/dist/infra/context-tree/file-context-file-reader.js +4 -0
- package/dist/infra/core/task-processor.d.ts +2 -2
- package/dist/infra/process/process-manager.d.ts +10 -1
- package/dist/infra/process/process-manager.js +16 -6
- package/dist/infra/process/transport-handlers.js +31 -0
- package/dist/infra/repl/commands/index.js +5 -2
- package/dist/infra/repl/commands/new-command.d.ts +14 -0
- package/dist/infra/repl/commands/new-command.js +61 -0
- package/dist/infra/repl/commands/{clear-command.d.ts → reset-command.d.ts} +2 -2
- package/dist/infra/repl/commands/{clear-command.js → reset-command.js} +10 -10
- package/dist/infra/usecase/generate-rules-use-case.js +2 -2
- package/dist/infra/usecase/init-use-case.js +4 -4
- package/dist/infra/usecase/logout-use-case.js +1 -1
- package/dist/infra/usecase/push-use-case.js +1 -1
- package/dist/infra/usecase/{clear-use-case.d.ts → reset-use-case.d.ts} +5 -5
- package/dist/infra/usecase/{clear-use-case.js → reset-use-case.js} +5 -5
- package/dist/resources/prompts/curate.yml +68 -13
- package/dist/resources/tools/curate.txt +60 -15
- package/dist/tui/components/inline-prompts/inline-confirm.js +2 -2
- package/dist/tui/components/onboarding/onboarding-flow.js +1 -0
- package/dist/tui/views/command-view.js +15 -0
- package/dist/utils/file-validator.js +9 -7
- package/oclif.manifest.json +3 -3
- package/package.json +1 -1
- package/dist/config/context-tree-domains.d.ts +0 -29
- package/dist/config/context-tree-domains.js +0 -29
- /package/dist/core/interfaces/usecase/{i-clear-use-case.js → i-reset-use-case.js} +0 -0
package/README.md
CHANGED
|
@@ -203,11 +203,22 @@ This creates agent-specific rule files (e.g., `CLAUDE.md`, `.cursorrules`) that
|
|
|
203
203
|
| Command | Description |
|
|
204
204
|
|---------|-------------|
|
|
205
205
|
| `/gen-rules` | Generate rule files for AI coding agents (Claude Code, Cursor, etc.) |
|
|
206
|
-
| `/
|
|
206
|
+
| `/reset [-y] [directory]` | Reset context tree to default domains |
|
|
207
207
|
|
|
208
|
-
**
|
|
208
|
+
**Reset options:**
|
|
209
209
|
- `-y, --yes`: Skip confirmation prompt
|
|
210
210
|
|
|
211
|
+
### Session Management
|
|
212
|
+
|
|
213
|
+
| Command | Description |
|
|
214
|
+
|---------|-------------|
|
|
215
|
+
| `/new [-y]` | Start a fresh session (ends current session, clears conversation) |
|
|
216
|
+
|
|
217
|
+
**Options:**
|
|
218
|
+
- `-y, --yes`: Skip confirmation prompt
|
|
219
|
+
|
|
220
|
+
**Note:** This command does NOT affect the context tree—it only clears the conversation history and starts a new session.
|
|
221
|
+
|
|
211
222
|
### Project Setup
|
|
212
223
|
|
|
213
224
|
| Command | Description |
|
package/dist/commands/curate.js
CHANGED
|
@@ -14,7 +14,7 @@ export default class Curate extends Command {
|
|
|
14
14
|
};
|
|
15
15
|
static description = `Curate context to the context tree (connects to running brv instance)
|
|
16
16
|
|
|
17
|
-
Requires a running brv instance. Start one with: brv
|
|
17
|
+
Requires a running brv instance. Start one with: brv
|
|
18
18
|
|
|
19
19
|
Good examples:
|
|
20
20
|
- "Auth uses JWT with 24h expiry. Tokens stored in httpOnly cookies via authMiddleware.ts"
|
package/dist/commands/main.d.ts
CHANGED
|
@@ -14,4 +14,17 @@ export default class Main extends Command {
|
|
|
14
14
|
*/
|
|
15
15
|
static hidden: boolean;
|
|
16
16
|
run(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve session ID for the agent.
|
|
19
|
+
*
|
|
20
|
+
* Strategy:
|
|
21
|
+
* 1. Check for active session in .brv/sessions/active.json
|
|
22
|
+
* 2. If active session exists and is valid (not stale), resume it
|
|
23
|
+
* 3. If stale (process crashed), mark as interrupted and create new
|
|
24
|
+
* 4. If no active session, create new session
|
|
25
|
+
* 5. Run session cleanup on startup
|
|
26
|
+
*
|
|
27
|
+
* @returns Session ID to use
|
|
28
|
+
*/
|
|
29
|
+
private resolveSessionId;
|
|
17
30
|
}
|
package/dist/commands/main.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { DEFAULT_SESSION_RETENTION } from '../core/domain/cipher/session/session-metadata.js';
|
|
4
|
+
import { SessionMetadataStore } from '../infra/cipher/session/session-metadata-store.js';
|
|
2
5
|
import { ProjectConfigStore } from '../infra/config/file-config-store.js';
|
|
3
6
|
import { getProcessManager } from '../infra/process/index.js';
|
|
4
7
|
import { startRepl } from '../infra/repl/repl-startup.js';
|
|
@@ -6,7 +9,7 @@ import { FileGlobalConfigStore } from '../infra/storage/file-global-config-store
|
|
|
6
9
|
import { FileOnboardingPreferenceStore } from '../infra/storage/file-onboarding-preference-store.js';
|
|
7
10
|
import { createTokenStore } from '../infra/storage/token-store.js';
|
|
8
11
|
import { MixpanelTrackingService } from '../infra/tracking/mixpanel-tracking-service.js';
|
|
9
|
-
import { initSessionLog } from '../utils/process-logger.js';
|
|
12
|
+
import { initSessionLog, processManagerLog } from '../utils/process-logger.js';
|
|
10
13
|
/**
|
|
11
14
|
* Main command - Entry point for ByteRover CLI.
|
|
12
15
|
*
|
|
@@ -30,9 +33,13 @@ export default class Main extends Command {
|
|
|
30
33
|
this.log("Run 'brv --help' for available commands.");
|
|
31
34
|
return;
|
|
32
35
|
}
|
|
36
|
+
// Resolve session ID (auto-resume or create new)
|
|
37
|
+
const sessionId = await this.resolveSessionId();
|
|
38
|
+
processManagerLog(`Session ID resolved: ${sessionId}`);
|
|
33
39
|
// Start Transport and Agent processes (v0.5.0 architecture)
|
|
40
|
+
// Pass session ID to Agent via environment variable
|
|
34
41
|
const processManager = getProcessManager();
|
|
35
|
-
await processManager.start();
|
|
42
|
+
await processManager.start({ sessionId });
|
|
36
43
|
const tokenStore = createTokenStore();
|
|
37
44
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
38
45
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
@@ -52,4 +59,48 @@ export default class Main extends Command {
|
|
|
52
59
|
await processManager.stop();
|
|
53
60
|
}
|
|
54
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve session ID for the agent.
|
|
64
|
+
*
|
|
65
|
+
* Strategy:
|
|
66
|
+
* 1. Check for active session in .brv/sessions/active.json
|
|
67
|
+
* 2. If active session exists and is valid (not stale), resume it
|
|
68
|
+
* 3. If stale (process crashed), mark as interrupted and create new
|
|
69
|
+
* 4. If no active session, create new session
|
|
70
|
+
* 5. Run session cleanup on startup
|
|
71
|
+
*
|
|
72
|
+
* @returns Session ID to use
|
|
73
|
+
*/
|
|
74
|
+
async resolveSessionId() {
|
|
75
|
+
const sessionStore = new SessionMetadataStore();
|
|
76
|
+
// Run cleanup on startup (async, don't wait)
|
|
77
|
+
sessionStore.cleanupSessions(DEFAULT_SESSION_RETENTION).catch((error) => {
|
|
78
|
+
processManagerLog(`Session cleanup failed: ${error}`);
|
|
79
|
+
});
|
|
80
|
+
// Check for active session
|
|
81
|
+
const activeSession = await sessionStore.getActiveSession();
|
|
82
|
+
if (activeSession) {
|
|
83
|
+
// Check if the active session is stale (process not running)
|
|
84
|
+
const isStale = await sessionStore.isActiveSessionStale();
|
|
85
|
+
if (isStale) {
|
|
86
|
+
// Mark the old session as interrupted
|
|
87
|
+
processManagerLog(`Active session ${activeSession.sessionId} is stale, marking as interrupted`);
|
|
88
|
+
await sessionStore.markSessionInterrupted(activeSession.sessionId);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Valid active session - resume it
|
|
92
|
+
processManagerLog(`Resuming active session: ${activeSession.sessionId}`);
|
|
93
|
+
return activeSession.sessionId;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Create new session
|
|
97
|
+
const newSessionId = `agent-session-${randomUUID()}`;
|
|
98
|
+
processManagerLog(`Creating new session: ${newSessionId}`);
|
|
99
|
+
// Save session metadata
|
|
100
|
+
const metadata = sessionStore.createSessionMetadata(newSessionId);
|
|
101
|
+
await sessionStore.saveSession(metadata);
|
|
102
|
+
// Set as active session
|
|
103
|
+
await sessionStore.setActiveSession(newSessionId);
|
|
104
|
+
return newSessionId;
|
|
105
|
+
}
|
|
55
106
|
}
|
package/dist/commands/query.js
CHANGED
|
@@ -14,7 +14,7 @@ export default class Query extends Command {
|
|
|
14
14
|
};
|
|
15
15
|
static description = `Query and retrieve information from the context tree (connects to running brv instance)
|
|
16
16
|
|
|
17
|
-
Requires a running brv instance. Start one with: brv
|
|
17
|
+
Requires a running brv instance. Start one with: brv
|
|
18
18
|
|
|
19
19
|
Good:
|
|
20
20
|
- "How is user authentication implemented?"
|
package/dist/constants.d.ts
CHANGED
|
@@ -34,4 +34,4 @@ export declare const TRANSPORT_RECONNECTION_ATTEMPTS = 30;
|
|
|
34
34
|
export declare const TRANSPORT_PING_INTERVAL_MS = 5000;
|
|
35
35
|
export declare const TRANSPORT_PING_TIMEOUT_MS = 10000;
|
|
36
36
|
export declare const TRANSPORT_DEFAULT_TRANSPORTS: ('polling' | 'websocket')[];
|
|
37
|
-
export declare const DEFAULT_LLM_MODEL = "gemini-
|
|
37
|
+
export declare const DEFAULT_LLM_MODEL = "gemini-3-flash-preview";
|
package/dist/constants.js
CHANGED
|
@@ -43,4 +43,4 @@ export const TRANSPORT_PING_TIMEOUT_MS = 10_000; // 10s timeout - avoid false di
|
|
|
43
43
|
// HTTP polling may be blocked by IDE sandboxes causing "xhr poll error"
|
|
44
44
|
export const TRANSPORT_DEFAULT_TRANSPORTS = ['websocket'];
|
|
45
45
|
// LLM Model defaults
|
|
46
|
-
export const DEFAULT_LLM_MODEL = 'gemini-
|
|
46
|
+
export const DEFAULT_LLM_MODEL = 'gemini-3-flash-preview';
|
|
@@ -27,19 +27,54 @@ export const LLM_REGISTRY = {
|
|
|
27
27
|
supportedFileTypes: [],
|
|
28
28
|
},
|
|
29
29
|
gemini: {
|
|
30
|
-
defaultModel: 'gemini-
|
|
30
|
+
defaultModel: 'gemini-3-flash-preview',
|
|
31
31
|
models: [
|
|
32
|
-
// Gemini
|
|
32
|
+
// Gemini 3 series (Preview)
|
|
33
33
|
{
|
|
34
34
|
capabilities: {
|
|
35
35
|
supportsAudio: true,
|
|
36
36
|
supportsImages: true,
|
|
37
|
+
supportsMultimodalFunctionResponse: true,
|
|
37
38
|
supportsPdf: true,
|
|
38
39
|
supportsStreaming: true,
|
|
39
40
|
supportsThinking: true,
|
|
40
41
|
},
|
|
41
42
|
charsPerToken: 4,
|
|
42
43
|
default: true,
|
|
44
|
+
displayName: 'Gemini 3 Flash (Preview)',
|
|
45
|
+
maxInputTokens: 1_000_000,
|
|
46
|
+
maxOutputTokens: 8192,
|
|
47
|
+
name: 'gemini-3-flash-preview',
|
|
48
|
+
pricing: { inputPerM: 0.075, outputPerM: 0.3 },
|
|
49
|
+
supportedFileTypes: ['image', 'pdf', 'audio'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
capabilities: {
|
|
53
|
+
supportsAudio: true,
|
|
54
|
+
supportsImages: true,
|
|
55
|
+
supportsMultimodalFunctionResponse: true,
|
|
56
|
+
supportsPdf: true,
|
|
57
|
+
supportsStreaming: true,
|
|
58
|
+
supportsThinking: true,
|
|
59
|
+
},
|
|
60
|
+
charsPerToken: 4,
|
|
61
|
+
displayName: 'Gemini 3 Pro (Preview)',
|
|
62
|
+
maxInputTokens: 1_000_000,
|
|
63
|
+
maxOutputTokens: 8192,
|
|
64
|
+
name: 'gemini-3-pro-preview',
|
|
65
|
+
pricing: { inputPerM: 1.25, outputPerM: 5 },
|
|
66
|
+
supportedFileTypes: ['image', 'pdf', 'audio'],
|
|
67
|
+
},
|
|
68
|
+
// Gemini 2.5 series
|
|
69
|
+
{
|
|
70
|
+
capabilities: {
|
|
71
|
+
supportsAudio: true,
|
|
72
|
+
supportsImages: true,
|
|
73
|
+
supportsPdf: true,
|
|
74
|
+
supportsStreaming: true,
|
|
75
|
+
supportsThinking: true,
|
|
76
|
+
},
|
|
77
|
+
charsPerToken: 4,
|
|
43
78
|
displayName: 'Gemini 2.5 Flash',
|
|
44
79
|
maxInputTokens: 1_000_000,
|
|
45
80
|
maxOutputTokens: 8192,
|
|
@@ -47,6 +82,22 @@ export const LLM_REGISTRY = {
|
|
|
47
82
|
pricing: { inputPerM: 0.075, outputPerM: 0.3 },
|
|
48
83
|
supportedFileTypes: ['image', 'pdf', 'audio'],
|
|
49
84
|
},
|
|
85
|
+
{
|
|
86
|
+
capabilities: {
|
|
87
|
+
supportsAudio: true,
|
|
88
|
+
supportsImages: true,
|
|
89
|
+
supportsPdf: true,
|
|
90
|
+
supportsStreaming: true,
|
|
91
|
+
supportsThinking: true,
|
|
92
|
+
},
|
|
93
|
+
charsPerToken: 4,
|
|
94
|
+
displayName: 'Gemini 2.5 Pro',
|
|
95
|
+
maxInputTokens: 1_000_000,
|
|
96
|
+
maxOutputTokens: 8192,
|
|
97
|
+
name: 'gemini-2.5-pro',
|
|
98
|
+
pricing: { inputPerM: 1.25, outputPerM: 5 },
|
|
99
|
+
supportedFileTypes: ['image', 'pdf', 'audio'],
|
|
100
|
+
},
|
|
50
101
|
// Gemini 1.5 series
|
|
51
102
|
{
|
|
52
103
|
capabilities: {
|
|
@@ -28,6 +28,8 @@ export interface ModelCapabilities {
|
|
|
28
28
|
supportsAudio: boolean;
|
|
29
29
|
/** Whether the model supports image input */
|
|
30
30
|
supportsImages: boolean;
|
|
31
|
+
/** Whether the model supports multimodal data in function responses (Gemini 3+) */
|
|
32
|
+
supportsMultimodalFunctionResponse?: boolean;
|
|
31
33
|
/** Whether the model supports PDF input */
|
|
32
34
|
supportsPdf: boolean;
|
|
33
35
|
/** Whether the model supports streaming responses */
|
|
@@ -17,6 +17,13 @@ export interface ProcessConfig {
|
|
|
17
17
|
* Custom environment variables for all commands.
|
|
18
18
|
*/
|
|
19
19
|
environment: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Grace period in milliseconds to wait after SIGTERM before sending SIGKILL.
|
|
22
|
+
* Allows processes time to clean up gracefully.
|
|
23
|
+
*
|
|
24
|
+
* @default 5000 (5 seconds)
|
|
25
|
+
*/
|
|
26
|
+
killGracePeriod: number;
|
|
20
27
|
/**
|
|
21
28
|
* Maximum number of concurrent background processes.
|
|
22
29
|
* @default 5
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Metadata Types and Schemas
|
|
3
|
+
*
|
|
4
|
+
* Defines the data structures for persistent session management.
|
|
5
|
+
* Sessions are stored in .brv/sessions/ directory as JSON files.
|
|
6
|
+
*
|
|
7
|
+
* Design adapted from gemini-cli's ChatRecordingService pattern.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
/**
|
|
11
|
+
* Session status indicating lifecycle state.
|
|
12
|
+
*/
|
|
13
|
+
export type SessionStatus = 'active' | 'ended' | 'interrupted';
|
|
14
|
+
/**
|
|
15
|
+
* Active session pointer stored in .brv/sessions/active.json
|
|
16
|
+
* Points to the currently active session for auto-resume.
|
|
17
|
+
*/
|
|
18
|
+
export interface ActiveSessionPointer {
|
|
19
|
+
/** ISO timestamp when this session became active */
|
|
20
|
+
activatedAt: string;
|
|
21
|
+
/** PID of the process that activated this session (for stale detection) */
|
|
22
|
+
pid: number;
|
|
23
|
+
/**
|
|
24
|
+
* Unique token identifying this specific process instance.
|
|
25
|
+
* Used to detect PID reuse: if the PID exists but token differs,
|
|
26
|
+
* it's a different process that got the same PID after the original crashed.
|
|
27
|
+
* Optional for backward compatibility with existing session files.
|
|
28
|
+
*/
|
|
29
|
+
processToken?: string;
|
|
30
|
+
/** Session ID of the currently active session */
|
|
31
|
+
sessionId: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Session metadata stored in .brv/sessions/session-*.json
|
|
35
|
+
* Contains metadata about a session for listing and management.
|
|
36
|
+
*/
|
|
37
|
+
export interface SessionMetadata {
|
|
38
|
+
/** ISO timestamp when session was created */
|
|
39
|
+
createdAt: string;
|
|
40
|
+
/** ISO timestamp of last activity */
|
|
41
|
+
lastUpdated: string;
|
|
42
|
+
/** Number of messages in session (cached for quick display) */
|
|
43
|
+
messageCount: number;
|
|
44
|
+
/** Unique session identifier (UUID) */
|
|
45
|
+
sessionId: string;
|
|
46
|
+
/** Session lifecycle status */
|
|
47
|
+
status: SessionStatus;
|
|
48
|
+
/** Optional AI-generated summary */
|
|
49
|
+
summary?: string;
|
|
50
|
+
/** Session title (generated from first user message) */
|
|
51
|
+
title?: string;
|
|
52
|
+
/** Project working directory (for validation) */
|
|
53
|
+
workingDirectory: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Session info for display purposes (extends metadata with computed fields).
|
|
57
|
+
*/
|
|
58
|
+
export interface SessionInfo extends SessionMetadata {
|
|
59
|
+
/** Filename without extension */
|
|
60
|
+
file: string;
|
|
61
|
+
/** Full filename including .json extension */
|
|
62
|
+
fileName: string;
|
|
63
|
+
/** First user message content (cleaned) */
|
|
64
|
+
firstUserMessage?: string;
|
|
65
|
+
/** Display index in the list (1-based) */
|
|
66
|
+
index: number;
|
|
67
|
+
/** Whether this is the currently active session */
|
|
68
|
+
isCurrentSession: boolean;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Result of resolving a session selection.
|
|
72
|
+
*/
|
|
73
|
+
export interface SessionSelectionResult {
|
|
74
|
+
/** Display info string for user feedback */
|
|
75
|
+
displayInfo: string;
|
|
76
|
+
/** Loaded session metadata */
|
|
77
|
+
sessionData: SessionMetadata;
|
|
78
|
+
/** Full path to session metadata file */
|
|
79
|
+
sessionPath: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Schema for ActiveSessionPointer validation.
|
|
83
|
+
*/
|
|
84
|
+
export declare const ActiveSessionPointerSchema: z.ZodObject<{
|
|
85
|
+
activatedAt: z.ZodUnion<[z.ZodString, z.ZodString]>;
|
|
86
|
+
pid: z.ZodNumber;
|
|
87
|
+
processToken: z.ZodOptional<z.ZodString>;
|
|
88
|
+
sessionId: z.ZodString;
|
|
89
|
+
}, "strip", z.ZodTypeAny, {
|
|
90
|
+
sessionId: string;
|
|
91
|
+
pid: number;
|
|
92
|
+
activatedAt: string;
|
|
93
|
+
processToken?: string | undefined;
|
|
94
|
+
}, {
|
|
95
|
+
sessionId: string;
|
|
96
|
+
pid: number;
|
|
97
|
+
activatedAt: string;
|
|
98
|
+
processToken?: string | undefined;
|
|
99
|
+
}>;
|
|
100
|
+
/**
|
|
101
|
+
* Schema for SessionMetadata validation.
|
|
102
|
+
*/
|
|
103
|
+
export declare const SessionMetadataSchema: z.ZodObject<{
|
|
104
|
+
createdAt: z.ZodUnion<[z.ZodString, z.ZodString]>;
|
|
105
|
+
lastUpdated: z.ZodUnion<[z.ZodString, z.ZodString]>;
|
|
106
|
+
messageCount: z.ZodNumber;
|
|
107
|
+
sessionId: z.ZodString;
|
|
108
|
+
status: z.ZodEnum<["active", "ended", "interrupted"]>;
|
|
109
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
110
|
+
title: z.ZodOptional<z.ZodString>;
|
|
111
|
+
workingDirectory: z.ZodString;
|
|
112
|
+
}, "strip", z.ZodTypeAny, {
|
|
113
|
+
status: "active" | "ended" | "interrupted";
|
|
114
|
+
sessionId: string;
|
|
115
|
+
createdAt: string;
|
|
116
|
+
lastUpdated: string;
|
|
117
|
+
messageCount: number;
|
|
118
|
+
workingDirectory: string;
|
|
119
|
+
summary?: string | undefined;
|
|
120
|
+
title?: string | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
status: "active" | "ended" | "interrupted";
|
|
123
|
+
sessionId: string;
|
|
124
|
+
createdAt: string;
|
|
125
|
+
lastUpdated: string;
|
|
126
|
+
messageCount: number;
|
|
127
|
+
workingDirectory: string;
|
|
128
|
+
summary?: string | undefined;
|
|
129
|
+
title?: string | undefined;
|
|
130
|
+
}>;
|
|
131
|
+
/** Prefix for session metadata files */
|
|
132
|
+
export declare const SESSION_FILE_PREFIX = "session-";
|
|
133
|
+
/** Directory name for session storage */
|
|
134
|
+
export declare const SESSIONS_DIR = "sessions";
|
|
135
|
+
/** Filename for active session pointer */
|
|
136
|
+
export declare const ACTIVE_SESSION_FILE = "active.json";
|
|
137
|
+
/** Default session retention config */
|
|
138
|
+
export declare const DEFAULT_SESSION_RETENTION: {
|
|
139
|
+
/** Maximum age in days before auto-cleanup */
|
|
140
|
+
maxAgeDays: number;
|
|
141
|
+
/** Maximum number of sessions to keep */
|
|
142
|
+
maxCount: number;
|
|
143
|
+
/** Run cleanup on startup */
|
|
144
|
+
runOnStartup: boolean;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Generate a session filename from timestamp and session ID.
|
|
148
|
+
*
|
|
149
|
+
* @param sessionId - The session UUID
|
|
150
|
+
* @returns Filename in format: session-YYYY-MM-DDTHH-MM-SS-<uuid-prefix>.json
|
|
151
|
+
*/
|
|
152
|
+
export declare function generateSessionFilename(sessionId: string): string;
|
|
153
|
+
/**
|
|
154
|
+
* Parse a session filename to extract timestamp and UUID prefix.
|
|
155
|
+
*
|
|
156
|
+
* @param filename - The session filename
|
|
157
|
+
* @returns Parsed components or null if invalid format
|
|
158
|
+
*/
|
|
159
|
+
export declare function parseSessionFilename(filename: string): null | {
|
|
160
|
+
timestamp: string;
|
|
161
|
+
uuidPrefix: string;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Format a timestamp as relative time.
|
|
165
|
+
*
|
|
166
|
+
* @param timestamp - ISO timestamp string
|
|
167
|
+
* @param style - 'long' (e.g., "2 hours ago") or 'short' (e.g., "2h")
|
|
168
|
+
* @returns Formatted relative time string
|
|
169
|
+
*/
|
|
170
|
+
export declare function formatRelativeTime(timestamp: string, style?: 'long' | 'short'): string;
|
|
171
|
+
/**
|
|
172
|
+
* Clean and sanitize message content for display.
|
|
173
|
+
* Converts newlines to spaces, collapses whitespace, removes non-printable chars.
|
|
174
|
+
*
|
|
175
|
+
* @param message - Raw message content
|
|
176
|
+
* @returns Cleaned message suitable for display
|
|
177
|
+
*/
|
|
178
|
+
export declare function cleanMessageForTitle(message: string): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Metadata Types and Schemas
|
|
3
|
+
*
|
|
4
|
+
* Defines the data structures for persistent session management.
|
|
5
|
+
* Sessions are stored in .brv/sessions/ directory as JSON files.
|
|
6
|
+
*
|
|
7
|
+
* Design adapted from gemini-cli's ChatRecordingService pattern.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Zod Schemas for Validation
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Schema for ActiveSessionPointer validation.
|
|
15
|
+
*/
|
|
16
|
+
export const ActiveSessionPointerSchema = z.object({
|
|
17
|
+
activatedAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)),
|
|
18
|
+
pid: z.number().int().positive(),
|
|
19
|
+
// Optional for backward compatibility with existing session files (treated as stale if missing)
|
|
20
|
+
processToken: z.string().optional(),
|
|
21
|
+
sessionId: z.string().min(1),
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Schema for SessionMetadata validation.
|
|
25
|
+
*/
|
|
26
|
+
export const SessionMetadataSchema = z.object({
|
|
27
|
+
createdAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)),
|
|
28
|
+
lastUpdated: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)),
|
|
29
|
+
messageCount: z.number().int().nonnegative(),
|
|
30
|
+
sessionId: z.string().min(1),
|
|
31
|
+
status: z.enum(['active', 'ended', 'interrupted']),
|
|
32
|
+
summary: z.string().optional(),
|
|
33
|
+
title: z.string().optional(),
|
|
34
|
+
workingDirectory: z.string().min(1),
|
|
35
|
+
});
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Constants
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/** Prefix for session metadata files */
|
|
40
|
+
export const SESSION_FILE_PREFIX = 'session-';
|
|
41
|
+
/** Directory name for session storage */
|
|
42
|
+
export const SESSIONS_DIR = 'sessions';
|
|
43
|
+
/** Filename for active session pointer */
|
|
44
|
+
export const ACTIVE_SESSION_FILE = 'active.json';
|
|
45
|
+
/** Default session retention config */
|
|
46
|
+
export const DEFAULT_SESSION_RETENTION = {
|
|
47
|
+
/** Maximum age in days before auto-cleanup */
|
|
48
|
+
maxAgeDays: 30,
|
|
49
|
+
/** Maximum number of sessions to keep */
|
|
50
|
+
maxCount: 50,
|
|
51
|
+
/** Run cleanup on startup */
|
|
52
|
+
runOnStartup: true,
|
|
53
|
+
};
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Helper Functions
|
|
56
|
+
// ============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Generate a session filename from timestamp and session ID.
|
|
59
|
+
*
|
|
60
|
+
* @param sessionId - The session UUID
|
|
61
|
+
* @returns Filename in format: session-YYYY-MM-DDTHH-MM-SS-<uuid-prefix>.json
|
|
62
|
+
*/
|
|
63
|
+
export function generateSessionFilename(sessionId) {
|
|
64
|
+
const timestamp = new Date().toISOString().slice(0, 19).replaceAll(':', '-');
|
|
65
|
+
const uuidPrefix = sessionId.slice(0, 8);
|
|
66
|
+
return `${SESSION_FILE_PREFIX}${timestamp}-${uuidPrefix}.json`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse a session filename to extract timestamp and UUID prefix.
|
|
70
|
+
*
|
|
71
|
+
* @param filename - The session filename
|
|
72
|
+
* @returns Parsed components or null if invalid format
|
|
73
|
+
*/
|
|
74
|
+
export function parseSessionFilename(filename) {
|
|
75
|
+
if (!filename.startsWith(SESSION_FILE_PREFIX) || !filename.endsWith('.json')) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Remove prefix and .json suffix
|
|
79
|
+
const withoutPrefix = filename.slice(SESSION_FILE_PREFIX.length, -5);
|
|
80
|
+
// Format: YYYY-MM-DDTHH-MM-SS-<uuid-prefix>
|
|
81
|
+
// The UUID prefix is the last 8 characters after the last dash
|
|
82
|
+
const lastDashIndex = withoutPrefix.lastIndexOf('-');
|
|
83
|
+
if (lastDashIndex === -1) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const timestamp = withoutPrefix.slice(0, lastDashIndex);
|
|
87
|
+
const uuidPrefix = withoutPrefix.slice(lastDashIndex + 1);
|
|
88
|
+
if (uuidPrefix.length !== 8) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return { timestamp, uuidPrefix };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a timestamp as relative time.
|
|
95
|
+
*
|
|
96
|
+
* @param timestamp - ISO timestamp string
|
|
97
|
+
* @param style - 'long' (e.g., "2 hours ago") or 'short' (e.g., "2h")
|
|
98
|
+
* @returns Formatted relative time string
|
|
99
|
+
*/
|
|
100
|
+
export function formatRelativeTime(timestamp, style = 'long') {
|
|
101
|
+
const now = new Date();
|
|
102
|
+
const time = new Date(timestamp);
|
|
103
|
+
const diffMs = now.getTime() - time.getTime();
|
|
104
|
+
const diffSeconds = Math.floor(diffMs / 1000);
|
|
105
|
+
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
106
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
107
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
108
|
+
if (style === 'short') {
|
|
109
|
+
if (diffSeconds < 1)
|
|
110
|
+
return 'now';
|
|
111
|
+
if (diffSeconds < 60)
|
|
112
|
+
return `${diffSeconds}s`;
|
|
113
|
+
if (diffMinutes < 60)
|
|
114
|
+
return `${diffMinutes}m`;
|
|
115
|
+
if (diffHours < 24)
|
|
116
|
+
return `${diffHours}h`;
|
|
117
|
+
if (diffDays < 30)
|
|
118
|
+
return `${diffDays}d`;
|
|
119
|
+
const diffMonths = Math.floor(diffDays / 30);
|
|
120
|
+
return diffMonths < 12 ? `${diffMonths}mo` : `${Math.floor(diffMonths / 12)}y`;
|
|
121
|
+
}
|
|
122
|
+
if (diffDays > 0) {
|
|
123
|
+
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
124
|
+
}
|
|
125
|
+
if (diffHours > 0) {
|
|
126
|
+
return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
|
|
127
|
+
}
|
|
128
|
+
if (diffMinutes > 0) {
|
|
129
|
+
return `${diffMinutes} minute${diffMinutes === 1 ? '' : 's'} ago`;
|
|
130
|
+
}
|
|
131
|
+
return 'Just now';
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Clean and sanitize message content for display.
|
|
135
|
+
* Converts newlines to spaces, collapses whitespace, removes non-printable chars.
|
|
136
|
+
*
|
|
137
|
+
* @param message - Raw message content
|
|
138
|
+
* @returns Cleaned message suitable for display
|
|
139
|
+
*/
|
|
140
|
+
export function cleanMessageForTitle(message) {
|
|
141
|
+
return message
|
|
142
|
+
.replaceAll(/\n+/g, ' ')
|
|
143
|
+
.replaceAll(/\s+/g, ' ')
|
|
144
|
+
.replaceAll(/[^\u0020-\u007E]+/g, '') // Remove non-printable ASCII
|
|
145
|
+
.trim()
|
|
146
|
+
.slice(0, 100); // Limit length for title
|
|
147
|
+
}
|
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export interface RawConcept {
|
|
2
|
+
changes?: string[];
|
|
3
|
+
files?: string[];
|
|
4
|
+
flow?: string;
|
|
5
|
+
task?: string;
|
|
6
|
+
timestamp?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Narrative {
|
|
9
|
+
dependencies?: string;
|
|
10
|
+
features?: string;
|
|
11
|
+
structure?: string;
|
|
12
|
+
}
|
|
4
13
|
export interface ContextData {
|
|
5
14
|
name: string;
|
|
15
|
+
narrative?: Narrative;
|
|
16
|
+
rawConcept?: RawConcept;
|
|
6
17
|
relations?: string[];
|
|
7
18
|
snippets: string[];
|
|
8
19
|
}
|
|
9
|
-
/**
|
|
10
|
-
* Generates Markdown files for knowledge context.
|
|
11
|
-
*/
|
|
12
20
|
export declare const MarkdownWriter: {
|
|
13
|
-
/**
|
|
14
|
-
* Generate context.md content with snippets and optional relations.
|
|
15
|
-
* Used for both topics and subtopics in the knowledge hierarchy.
|
|
16
|
-
*/
|
|
17
21
|
generateContext(data: ContextData): string;
|
|
18
|
-
/**
|
|
19
|
-
* Merge two context.md contents into one.
|
|
20
|
-
* Combines snippets and relations, deduplicating where possible.
|
|
21
|
-
*
|
|
22
|
-
* @param sourceContent - Raw content from source context.md
|
|
23
|
-
* @param targetContent - Raw content from target context.md
|
|
24
|
-
* @returns Merged context.md content
|
|
25
|
-
*/
|
|
26
22
|
mergeContexts(sourceContent: string, targetContent: string): string;
|
|
23
|
+
parseContent(content: string, name?: string): ContextData;
|
|
27
24
|
};
|