fss-link 1.7.7 → 1.9.2

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.
@@ -10,21 +10,6 @@ FSS Link supports multiple LLM providers. Configure via CLI flags, environment v
10
10
  2. **Database config** — persistent, survives restarts
11
11
  3. **Environment variables** — traditional fallback
12
12
 
13
- ### Ollama
14
-
15
- Local models via Ollama. Default port 11434.
16
-
17
- ```bash
18
- # CLI
19
- fss-link --provider ollama --model qwen3:4b
20
-
21
- # Or set the context window for large conversations
22
- fss-link --provider ollama --model qwen3:4b
23
- # Then use /context 32768 inside the session
24
- ```
25
-
26
- **Context window**: Defaults to 32,768 tokens. Use the `/context` command to set a custom value that persists to the database.
27
-
28
13
  ### LM Studio
29
14
 
30
15
  Local models via LM Studio. Default port 1234.
@@ -65,7 +50,7 @@ fss-link --provider gemini-api-key --model gemini-2.0-flash
65
50
 
66
51
  ## Database Configuration
67
52
 
68
- FSS Link stores model configuration in a SQLite database at `~/.fss-link/config.db`. This is the persistent source of truth for model settings.
53
+ FSS Link stores model configuration in a SQLite database at `~/.fss-link/fss-link.db`. This is the persistent source of truth for model settings.
69
54
 
70
55
  <!-- bobai:image-card width="6" -->
71
56
  ![Database Persistence System](./assets/batch_0003_database-persistence-system-a.png)
@@ -77,19 +62,19 @@ Your model choice survives restarts. CLI flags override, environment fallback.
77
62
 
78
63
  ```bash
79
64
  # View current config
80
- sqlite3 ~/.fss-link/config.db "SELECT auth_type, model_name, endpoint_url, is_active FROM model_configs"
65
+ sqlite3 ~/.fss-link/fss-link.db "SELECT auth_type, model_name, endpoint_url, is_active FROM model_configs"
81
66
 
82
67
  # Switch active model
83
- sqlite3 ~/.fss-link/config.db "UPDATE model_configs SET is_active = 0"
84
- sqlite3 ~/.fss-link/config.db "UPDATE model_configs SET is_active = 1 WHERE auth_type = 'ollama' AND model_name = 'qwen3:4b'"
68
+ sqlite3 ~/.fss-link/fss-link.db "UPDATE model_configs SET is_active = 0"
69
+ sqlite3 ~/.fss-link/fss-link.db "UPDATE model_configs SET is_active = 1 WHERE auth_type = 'bob-ai' AND model_name = 'qwen3.5-35b-a3b'"
85
70
 
86
71
  # Change endpoint
87
- sqlite3 ~/.fss-link/config.db "UPDATE model_configs SET endpoint_url = 'http://your-server:1234/v1' WHERE auth_type = 'lm-studio'"
72
+ sqlite3 ~/.fss-link/fss-link.db "UPDATE model_configs SET endpoint_url = 'http://your-server:1234/v1' WHERE auth_type = 'lm-studio'"
88
73
  ```
89
74
 
90
75
  ### Database Location
91
76
 
92
- - Linux/macOS: `~/.fss-link/config.db`
77
+ - Linux/macOS: `~/.fss-link/fss-link.db`
93
78
  - Data directory: `~/.fss-link/`
94
79
  - Todos: `~/.fss-link/todos/`
95
80
 
@@ -103,10 +88,9 @@ The context window is the maximum number of tokens the model can process in a si
103
88
 
104
89
  1. **Manual override** in `settings.json` (`chatCompression.contextWindow`)
105
90
  2. **LM Studio API query** (auto-detected from server)
106
- 3. **Ollama `num_ctx`** from `/context` command or sampling params
107
- 4. **OpenAI-compatible `/models` endpoint** (vLLM `max_model_len`)
108
- 5. **Static lookup** for known models (Gemini variants)
109
- 6. **Provider defaults**: Ollama 32k, LM Studio 32k, OpenAI-compat 128k
91
+ 3. **OpenAI-compatible `/models` endpoint** (vLLM `max_model_len`)
92
+ 4. **Static lookup** for known models (Gemini variants)
93
+ 5. **Provider defaults**: LM Studio 32k, OpenAI-compat 128k
110
94
 
111
95
  ### Compression
112
96
 
@@ -128,7 +112,7 @@ Full detail on recent context, compressed summaries for older messages.
128
112
 
129
113
  ### The `/context` Command
130
114
 
131
- Set the context window for Ollama models:
115
+ Set the context window size:
132
116
 
133
117
  ```
134
118
  /context 32768 # Set to 32k
package/docs/README.md CHANGED
@@ -21,7 +21,7 @@ $ fss-link
21
21
  ## Key Features
22
22
 
23
23
  <!-- bobai:badges -->
24
- Ollama, LM Studio, OpenAI, Gemini, vLLM
24
+ LM Studio, BobAI, OpenAI, Gemini, vLLM
25
25
  <!-- /bobai:badges -->
26
26
 
27
27
  - **Multi-provider support** — Connect to any LLM (local or cloud)
@@ -40,7 +40,7 @@ Ollama, LM Studio, OpenAI, Gemini, vLLM
40
40
  <!-- bobai:image-card width="6" -->
41
41
  ![Multi-Provider Connection Hub](./assets/batch_0002_multiprovider-connection-hub-a.png)
42
42
  ### Connect Anywhere
43
- Local models via Ollama. Cloud providers (OpenAI, Gemini). Custom endpoints (vLLM, LM Studio).
43
+ Local models via LM Studio and BobAI. Cloud providers (OpenAI, Gemini). Custom endpoints (vLLM).
44
44
  <!-- /bobai:image-card -->
45
45
 
46
46
  ## Installation
@@ -65,14 +65,11 @@ npm install -g .
65
65
 
66
66
  ## Quick Start
67
67
 
68
- ### With Ollama (Local)
68
+ ### With BobAI / vLLM (Local)
69
69
 
70
70
  ```bash
71
- # Start Ollama with a model
72
- ollama run qwen3:4b
73
-
74
- # Launch FSS Link
75
- fss-link --provider ollama --model qwen3:4b
71
+ export BOB_AI_BASE_URL="http://your-server:8000/v1"
72
+ fss-link --provider bob-ai --model qwen3.5-35b-a3b
76
73
  ```
77
74
 
78
75
  ### With LM Studio (Local)
@@ -186,6 +183,7 @@ npx vitest run packages/cli/src/config/database.test.ts
186
183
 
187
184
  - [Configuration Guide](CONFIGURATION.md) — providers, models, database, context settings
188
185
  - [Tools Reference](TOOLS.md) — all available tools and their parameters
186
+ - [Slash Commands](SLASH-COMMANDS.md) — `/resume`, `/rewind`, `/sessions`, and the Welcome Back dialog
189
187
  - [Tool Flow Diagrams](../fss-docs/tool-flow-diagrams/) — visual documentation with Mermaid diagrams
190
188
 
191
189
  ## License
@@ -0,0 +1,186 @@
1
+ # Slash Commands — Session Management
2
+
3
+ ## `/resume` — List and Resume Saved Sessions
4
+
5
+ Resume a previous conversation session by number, title, or session ID.
6
+
7
+ ### Usage
8
+
9
+ ```
10
+ /resume # List recent sessions
11
+ /resume <number> # Resume by list position
12
+ /resume <title substring> # Resume by title match
13
+ /resume <session-id> # Resume by exact session ID
14
+ ```
15
+
16
+ ### Alternatives
17
+
18
+ `/sessions`, `/continue` — all three names invoke the same command.
19
+
20
+ ### Listing Sessions
21
+
22
+ Running `/resume` with no arguments displays a numbered list of up to 50 recent sessions for the current project, sorted by most recently updated:
23
+
24
+ ```
25
+ Recent sessions:
26
+
27
+ 1. Implement auth middleware for API endpoints 2 hours ago 14 turns
28
+ 2. Fix login bug on password reset flow 1 day ago 8 turns
29
+ 3. Set up CI pipeline with GitHub Actions 3 days ago 22 turns
30
+
31
+ Type a number to resume, or /resume <partial title> to match by name.
32
+ ```
33
+
34
+ Titles are truncated at 55 characters. The list is scoped to the current project (identified by project hash).
35
+
36
+ ### Selection Methods
37
+
38
+ | Method | Example | Behavior |
39
+ |--------|---------|----------|
40
+ | **Numeric index** | `/resume 2` | Selects the second item in the listed order (1-based) |
41
+ | **Title substring** | `/resume auth` | First session whose title contains "auth" (case-insensitive) |
42
+ | **Exact session ID** | `/resume abc123def` | Matches the session's unique ID exactly |
43
+
44
+ ### Matching Rules
45
+
46
+ - **Numeric index** is checked first. If the number is out of range (fewer sessions than the number given), it falls through to title matching.
47
+ - **Title substring** uses `String.prototype.includes()` — any part of the title matching the input selects the first such session. There is no ranking or scoring; the first match wins.
48
+ - **Exact session ID** is the final fallback if neither numeric nor title matching succeeds.
49
+ - If no match is found at all, an error message is shown: `No session matched "foo". Use /resume to list sessions.`
50
+
51
+ ### Tab Completion
52
+
53
+ Pressing Tab while typing `/resume <partial>` shows completions for session titles that **start with** the partial text (`startsWith`). This is intentionally stricter than the action's own matching (`includes`) — tab completion may show fewer options than what `/resume <text>` would actually match.
54
+
55
+ ### What Happens When You Resume
56
+
57
+ The selected session's full conversation history is restored:
58
+ - The Gemini client's history is replaced with the saved history from the session
59
+ - The session ID is set so subsequent exchanges update the same session record
60
+ - Turn count is restored from the session record
61
+ - The UI displays the restored history as a scrollable message list
62
+
63
+ ### Session Storage
64
+
65
+ Sessions are stored in `~/.fss-link/sessions.db` (a dedicated SQLite database). Each session record includes:
66
+ - `id` — unique session identifier
67
+ - `title` — auto-generated from the first user message (truncated to 80 chars)
68
+ - `project_path`, `project_hash` — project identification
69
+ - `provider`, `model_name` — which model was used
70
+ - `created_at`, `updated_at` — timestamps
71
+ - `turn_count`, `total_input_tokens`, `total_output_tokens` — usage metrics
72
+ - `history_json` — full conversation history as JSON
73
+
74
+ Sessions are created on the first exchange in a conversation and updated after each subsequent exchange. An auto-save timer flushes changes every 30 seconds.
75
+
76
+ ---
77
+
78
+ ## `/rewind` — Rewind to a Previous Turn
79
+
80
+ Rewind the conversation to a selected past turn, discarding everything after it.
81
+
82
+ ### Usage
83
+
84
+ ```
85
+ /rewind # Open rewind dialog
86
+ ```
87
+
88
+ No arguments are accepted — `/rewind foo` behaves identically to `/rewind`.
89
+
90
+ ### How It Works
91
+
92
+ Running `/rewind` opens an interactive dialog that shows all user-initiated turns in the current conversation. Selecting a turn discards everything after it, effectively rewinding the conversation to that point.
93
+
94
+ ### Turn Extraction
95
+
96
+ The dialog filters the conversation history to show only meaningful user turns:
97
+ - **Included**: User messages with text content
98
+ - **Excluded**: Tool-response turns (all parts are `functionResponse`), system notes (`[System note:`), steering messages (`[Steering message`)
99
+
100
+ Turns are displayed in reverse-chronological order (most recent first), numbered T1, T2, T3… from oldest to newest.
101
+
102
+ ### Navigation & Search
103
+
104
+ | Key | Action |
105
+ |-----|--------|
106
+ | `↑` / `↓` | Move selection up/down |
107
+ | `Enter` | Confirm selection and rewind |
108
+ | `Esc` (first press) | Clear the search filter |
109
+ | `Esc` (second press) | Close the dialog without rewinding |
110
+ | `Backspace` | Delete one character from the search filter |
111
+ | Any printable character | Type into the search filter box |
112
+
113
+ ### Search
114
+
115
+ The dialog has a working text filter. Type any characters to narrow the visible turns:
116
+
117
+ - **Case-insensitive substring match** on turn text
118
+ - Selection resets to the top (index 0) when the filter changes
119
+ - Shows "No turns match" if the filter produces zero results
120
+ - First `Esc` clears the filter and restores the full list; second `Esc` closes the dialog
121
+
122
+ **Limitations:**
123
+ - Single-term only — multi-word queries like "auth middleware" won't work as two separate terms
124
+ - Searches only user message text, not tool calls, results, or delta events
125
+ - No ranking or scoring — just `includes` substring match
126
+
127
+ ### What Happens When You Rewind
128
+
129
+ 1. The conversation history is cleared
130
+ 2. A preview message is built from the selected turn's content
131
+ 3. All turns after the selected one are discarded
132
+ 4. The model can then continue from the rewound point
133
+
134
+ ### Known Behavior
135
+
136
+ The rewind point uses `fullHistory.slice(0, contentIndex)` — the selected turn is **included** in the rewound history. The dialog help text says "conversation after selected turn will be discarded," which is accurate: the selected turn itself is preserved, everything after it is removed.
137
+
138
+ ---
139
+
140
+ ## `/sessions` — Alias for `/resume`
141
+
142
+ `/sessions` and `/continue` are aliases for `/resume`. All three names invoke the same command with identical behavior. See `/resume` above for full details.
143
+
144
+ ---
145
+
146
+ ## Welcome Back Dialog — Project-Level Resume
147
+
148
+ The **Welcome Back dialog** is a separate feature from `/resume`. It appears automatically when you launch FSS Link in a project that has prior session history, offering a high-level choice to continue from where you left off or start fresh.
149
+
150
+ ### When It Appears
151
+
152
+ - On startup, when `enableWelcomeBack` is enabled in settings (default: on)
153
+ - Only when the project has existing conversation history
154
+ - Uses `ProjectSummaryParser` to analyze project state — not `sessions.db` directly
155
+
156
+ ### Options
157
+
158
+ | Option | Keyboard Shortcut | Behavior |
159
+ |--------|-------------------|----------|
160
+ | **Continue Project** | `C` | Resumes from the most recent session with contextual summary |
161
+ | **Start Fresh Session** | `R` | Begins a new conversation with no history |
162
+ | **Cancel** | `Q` / `Escape` | Exits without starting a session |
163
+
164
+ ### What It Shows
165
+
166
+ The dialog displays project-level information including:
167
+ - Project goal (if available)
168
+ - Time since last activity
169
+ - Session count and active sessions
170
+ - Momentum indicator (high / building / stale / none)
171
+ - Git status (branch, staged/unstaged changes, last commit)
172
+ - Efficiency metrics and recent activity summary
173
+ - Next suggestion (if available)
174
+
175
+ ### Key Differences from `/resume`
176
+
177
+ | Aspect | Welcome Back Dialog | `/resume` |
178
+ |--------|---------------------|-----------|
179
+ | **Trigger** | Automatic on project launch | Manual slash command |
180
+ | **Scope** | Project-level (one choice) | Per-session picker (up to 50) |
181
+ | **Data source** | `ProjectSummaryParser` (analysis of project state) | `sessions.db` (direct session records) |
182
+ | **Selection** | Continue / Restart / Cancel | Number, title substring, or session ID |
183
+ | **Context** | Smart summary built from project analysis | Full conversation history restored verbatim |
184
+ | **Search** | None — single decision point | Substring search across session titles |
185
+
186
+ The Welcome Back dialog is best for quick decisions at project launch. `/resume` is for browsing and selecting from multiple saved sessions within an active conversation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fss-link",
3
- "version": "1.7.7",
3
+ "version": "1.9.2",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/FSSCoding/fss-link.git"
14
14
  },
15
15
  "config": {
16
- "sandboxImageUri": "ghcr.io/fsscoding/fss-link:1.7.5"
16
+ "sandboxImageUri": "ghcr.io/fsscoding/fss-link:1.9.2"
17
17
  },
18
18
  "scripts": {
19
19
  "start": "node scripts/start.js",
@@ -82,6 +82,7 @@
82
82
  "LICENSE"
83
83
  ],
84
84
  "devDependencies": {
85
+ "@testing-library/dom": "^10.4.1",
85
86
  "@types/ink-testing-library": "^1.0.4",
86
87
  "@vitest/coverage-v8": "^4.1.8",
87
88
  "concurrently": "^9.2.0",
@@ -18,7 +18,7 @@
18
18
  // limitations under the License.
19
19
 
20
20
  import { execSync } from 'child_process';
21
- import { writeFileSync } from 'fs';
21
+ import { writeFileSync, rmSync } from 'fs';
22
22
  import { join } from 'path';
23
23
 
24
24
  if (!process.cwd().includes('packages')) {
@@ -26,6 +26,13 @@ if (!process.cwd().includes('packages')) {
26
26
  process.exit(1);
27
27
  }
28
28
 
29
+ // Clean dist first — `tsc --build` is incremental and trusts its
30
+ // .tsbuildinfo cache. Any prior partial `rm -rf dist` or interrupted build
31
+ // leaves that cache out of sync with what's actually on disk, which surfaces
32
+ // as TS5055 "Cannot write file ... because it would overwrite input file".
33
+ // Same fix already applied in prebundle-sync-dist.js for the bundle path.
34
+ rmSync(join(process.cwd(), 'dist'), { recursive: true, force: true });
35
+
29
36
  // build typescript files
30
37
  execSync('tsc --build', { stdio: 'inherit' });
31
38
 
@@ -24,6 +24,14 @@ const root = resolve(__dirname, '..');
24
24
  const packages = ['packages/core', 'packages/cli'];
25
25
 
26
26
  for (const pkg of packages) {
27
+ // Clean dist first to avoid incremental build cache corruption that
28
+ // produces mangled output (e.g. #; private fields, broken syntax).
29
+ // See: incremental tsbuildinfo gets out of sync with source changes.
30
+ const { execSync } = await import('child_process');
31
+ const { rm } = await import('fs/promises');
32
+ const { resolve } = await import('path');
33
+ const distDir = resolve(root, pkg, 'dist');
34
+ try { await rm(distDir, { recursive: true, force: true }); } catch {}
27
35
  const tsconfig = resolve(root, pkg, 'tsconfig.json');
28
36
  try {
29
37
  execSync(`npx tsc --noCheck -p ${tsconfig}`, {
@@ -36,33 +36,44 @@ if (!versionType) {
36
36
  // 2. Bump the version in the root and all workspace package.json files.
37
37
  run(`npm version ${versionType} --no-git-tag-version --allow-same-version`);
38
38
 
39
- // 3. Get all workspaces and filter out the ones we don't want to version.
40
- const workspacesToExclude = [];
39
+ // 3. Resolve workspace globs and collect all workspace names.
40
+ const workspacesToExclude = [
41
+ 'fss-link-vscode-ide-companion',
42
+ '@fsscoding/fss-link-web-client',
43
+ ];
41
44
  const rootPkg = readJson(resolve(process.cwd(), 'package.json'));
42
45
  const workspaceDirs = rootPkg.workspaces || [];
46
+ const { glob } = await import('glob');
43
47
  const workspacesToVersion = [];
44
- for (const dir of workspaceDirs) {
45
- const pkgPath = resolve(process.cwd(), dir, 'package.json');
46
- try {
47
- const wsPkg = readJson(pkgPath);
48
- if (!workspacesToExclude.includes(wsPkg.name)) {
49
- workspacesToVersion.push(wsPkg.name);
48
+ for (const pattern of workspaceDirs) {
49
+ const matches = await glob(pattern, { cwd: process.cwd(), absolute: false });
50
+ for (const dir of matches) {
51
+ const pkgPath = resolve(process.cwd(), dir, 'package.json');
52
+ try {
53
+ const wsPkg = readJson(pkgPath);
54
+ if (!workspacesToExclude.includes(wsPkg.name)) {
55
+ workspacesToVersion.push({ name: wsPkg.name, dir });
56
+ }
57
+ } catch {
58
+ // Skip directories without package.json
50
59
  }
51
- } catch {
52
- // Skip directories without package.json
53
60
  }
54
61
  }
55
62
 
56
- for (const workspaceName of workspacesToVersion) {
57
- run(
58
- `npm version ${versionType} --workspace ${workspaceName} --no-git-tag-version --allow-same-version`,
59
- );
60
- }
61
-
62
- // 4. Get the new version number from the root package.json
63
+ // 4. Get the new version number from the root package.json (AFTER npm version bump)
63
64
  const rootPackageJsonPath = resolve(process.cwd(), 'package.json');
64
65
  const newVersion = readJson(rootPackageJsonPath).version;
65
66
 
67
+ for (const { name, dir } of workspacesToVersion) {
68
+ // npm version doesn't support --legacy-peer-deps, so we update the file directly
69
+ // and let the final npm install reconcile everything
70
+ const pkgPath = resolve(process.cwd(), dir, 'package.json');
71
+ const wsPkg = readJson(pkgPath);
72
+ wsPkg.version = newVersion;
73
+ writeJson(pkgPath, wsPkg);
74
+ console.log(`Updated ${name} to v${newVersion}`);
75
+ }
76
+
66
77
  // 6. Update the sandboxImageUri in the root package.json
67
78
  const rootPackageJson = readJson(rootPackageJsonPath);
68
79
  if (rootPackageJson.config?.sandboxImageUri) {
@@ -86,6 +97,6 @@ if (cliPackageJson.config?.sandboxImageUri) {
86
97
 
87
98
  // 8. Run `npm install` to update package-lock.json.
88
99
  // --ignore-scripts prevents the prepare hook (bundle) from firing before deps exist.
89
- run('npm install --ignore-scripts');
100
+ run('npm install --ignore-scripts --legacy-peer-deps');
90
101
 
91
102
  console.log(`Successfully bumped versions to v${newVersion}.`);