codeep 2.18.1 → 2.19.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/README.md +53 -16
- package/dist/acp/commands.js +11 -55
- package/dist/acp/protocol.d.ts +34 -0
- package/dist/acp/server.d.ts +6 -1
- package/dist/acp/server.js +97 -2
- package/dist/api/index.js +9 -0
- package/dist/commands/core/index.d.ts +19 -0
- package/dist/commands/core/index.js +28 -0
- package/dist/commands/core/keysync.d.ts +2 -0
- package/dist/commands/core/keysync.js +34 -0
- package/dist/commands/core/telemetry.d.ts +2 -0
- package/dist/commands/core/telemetry.js +34 -0
- package/dist/config/index.js +2 -2
- package/dist/renderer/App.d.ts +9 -48
- package/dist/renderer/App.js +113 -338
- package/dist/renderer/Screen.d.ts +13 -0
- package/dist/renderer/Screen.js +22 -0
- package/dist/renderer/commands/registry.js +3 -3
- package/dist/renderer/commands.js +19 -51
- package/dist/renderer/components/CommandAutocomplete.d.ts +46 -0
- package/dist/renderer/components/CommandAutocomplete.js +103 -0
- package/dist/renderer/components/HunkPicker.d.ts +48 -0
- package/dist/renderer/components/HunkPicker.js +140 -0
- package/dist/renderer/components/MentionPicker.d.ts +60 -0
- package/dist/renderer/components/MentionPicker.js +111 -0
- package/dist/renderer/components/PasteDialog.d.ts +43 -0
- package/dist/renderer/components/PasteDialog.js +70 -0
- package/dist/renderer/layout.js +1 -0
- package/dist/renderer/main.js +15 -39
- package/dist/utils/agent.js +121 -26
- package/dist/utils/agentChat.d.ts +11 -4
- package/dist/utils/agentChat.js +53 -25
- package/dist/utils/codeepCloud.d.ts +3 -0
- package/dist/utils/codeepCloud.js +62 -7
- package/dist/utils/personalities.d.ts +63 -5
- package/dist/utils/personalities.js +583 -31
- package/dist/utils/shell.d.ts +11 -1
- package/dist/utils/shell.js +169 -82
- package/dist/utils/ssrfGuard.d.ts +18 -0
- package/dist/utils/ssrfGuard.js +83 -0
- package/dist/utils/taskPlanner.d.ts +7 -1
- package/dist/utils/taskPlanner.js +16 -7
- package/dist/utils/toolExecution.d.ts +1 -0
- package/dist/utils/toolExecution.js +48 -88
- package/dist/utils/tools.d.ts +3 -3
- package/dist/utils/tools.js +18 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -677,9 +677,10 @@ Then call it as `/sec-review src/api/login.ts` (or `/sec` via the alias).
|
|
|
677
677
|
**Discovery:** `/commands` lists all available templates. Project files shadow
|
|
678
678
|
global files with the same name. Aliases also work for autocomplete.
|
|
679
679
|
|
|
680
|
-
### Personalities (`/personality
|
|
680
|
+
### Personalities and custom bots (`/personality`)
|
|
681
681
|
|
|
682
|
-
|
|
682
|
+
Built-in personalities change tone and priorities. A structured custom bot can
|
|
683
|
+
also pin a model, restrict runtime tools, and limit where it is available:
|
|
683
684
|
|
|
684
685
|
```
|
|
685
686
|
/personality # list available
|
|
@@ -694,23 +695,56 @@ Six built-in presets: `concise`, `verbose`, `security`, `senior-reviewer`,
|
|
|
694
695
|
`junior-mentor`, `ship-it`. The active one persists across sessions
|
|
695
696
|
(stored in `~/.codeep/config.json` as `activePersonality`).
|
|
696
697
|
|
|
697
|
-
**Custom
|
|
698
|
+
**Custom bots** — build one in **Dashboard → Agent Studio**, or drop a Markdown
|
|
699
|
+
file in
|
|
698
700
|
`.codeep/personalities/<name>.md` (project) or
|
|
699
701
|
`~/.codeep/personalities/<name>.md` (global):
|
|
700
702
|
|
|
701
703
|
```markdown
|
|
702
|
-
|
|
704
|
+
---
|
|
705
|
+
codeep: custom-bot/v1
|
|
706
|
+
description: Reviews changes without modifying the project.
|
|
707
|
+
model: automatic
|
|
708
|
+
tools: [files, git]
|
|
709
|
+
scope: selected
|
|
710
|
+
projects: [Codeep]
|
|
711
|
+
---
|
|
712
|
+
# PR Reviewer
|
|
713
|
+
|
|
714
|
+
## Responsibility
|
|
715
|
+
Review the requested change and cite concrete evidence.
|
|
716
|
+
|
|
717
|
+
## Always
|
|
718
|
+
- Call out untested behavior.
|
|
703
719
|
|
|
704
|
-
|
|
705
|
-
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
720
|
+
## Never
|
|
721
|
+
- Modify files or publish changes.
|
|
722
|
+
|
|
723
|
+
## Advanced instructions
|
|
724
|
+
Keep the tone collaborative and end with the highest-risk finding.
|
|
709
725
|
```
|
|
710
726
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
727
|
+
Portable tool groups are `files`, `terminal`, `tests`, `git`, `web`, and
|
|
728
|
+
`mcp`; unselected groups are removed from the model's runtime and checked
|
|
729
|
+
again before execution. Missing, empty, or malformed `tools` metadata in a v1
|
|
730
|
+
file creates a conversation-only bot. Use
|
|
731
|
+
`scope: all`, `selected`, or `personal`; selected scope matches the project
|
|
732
|
+
directory basename against `projects` (with optional `*` and `?` wildcards).
|
|
733
|
+
An exact `provider/model` applies only to
|
|
734
|
+
that run, while `automatic` inherits the current selection.
|
|
735
|
+
|
|
736
|
+
Portable v1 fails closed for unclassified capabilities: Skills, delegation,
|
|
737
|
+
and vision are not exposed to structured bots yet. MCP is available only when
|
|
738
|
+
the active ACP session has registered those exact tools.
|
|
739
|
+
|
|
740
|
+
Legacy prompt-only Markdown remains supported and unrestricted. The older
|
|
741
|
+
section-only Agent Studio format is recognized when it contains
|
|
742
|
+
`Responsibility` plus another standard section; without a Tools section it
|
|
743
|
+
also stays unrestricted until explicitly converted. Invalid structured model
|
|
744
|
+
or scope metadata makes the bot unavailable rather than silently widening its
|
|
745
|
+
runtime. An explicit unsupported `codeep` schema marker is also unavailable;
|
|
746
|
+
it is never reinterpreted as legacy. Project files shadow global files, which
|
|
747
|
+
shadow built-ins with the same name.
|
|
714
748
|
|
|
715
749
|
### Activity Insights (`/insights`, new in 2.0.3)
|
|
716
750
|
|
|
@@ -885,10 +919,13 @@ codeep account push # Upload local personalities + commands to codeep.dev
|
|
|
885
919
|
codeep account sync # Download them onto a new machine
|
|
886
920
|
```
|
|
887
921
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
922
|
+
A manual `sync` treats the dashboard personality as current. When a changed
|
|
923
|
+
cloud personality replaces a local file, Codeep first saves the local version
|
|
924
|
+
under `~/.codeep/backups/personalities/` and then swaps the new file in
|
|
925
|
+
atomically. Custom commands keep the older **additive** merge and are never
|
|
926
|
+
overwritten by a pull. View and edit custom bots in **Agent Studio**, and prune
|
|
927
|
+
commands under **Custom commands**, on the
|
|
928
|
+
[dashboard](https://codeep.dev/dashboard).
|
|
892
929
|
|
|
893
930
|
Hooks and MCP server configs are deliberately **not** synced: hooks run
|
|
894
931
|
arbitrary shell, and MCP configs often embed tokens, so both stay local to each
|
package/dist/acp/commands.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Slash command handler for ACP sessions.
|
|
3
3
|
// Mirrors CLI commands from renderer/commands.ts but returns plain text
|
|
4
4
|
// responses (no TUI) suitable for streaming back via session/update.
|
|
5
|
-
import { config, getCurrentProvider, getModelsForCurrentProvider, setProvider, setApiKey, isConfigured, listSessionsWithInfo, startNewSession, loadSession, saveSession, initializeAsProject, isManuallyInitializedProject, setProjectPermission, hasWritePermission, hasReadPermission,
|
|
5
|
+
import { config, getCurrentProvider, getModelsForCurrentProvider, setProvider, setApiKey, isConfigured, listSessionsWithInfo, startNewSession, loadSession, saveSession, initializeAsProject, isManuallyInitializedProject, setProjectPermission, hasWritePermission, hasReadPermission, } from '../config/index.js';
|
|
6
6
|
import { getProviderList, getProvider } from '../config/providers.js';
|
|
7
|
+
import { telemetryCommand } from '../commands/core/telemetry.js';
|
|
8
|
+
import { keysyncCommand } from '../commands/core/keysync.js';
|
|
7
9
|
import { getProjectContext } from '../utils/project.js';
|
|
8
10
|
import { loadCustomCommands } from '../utils/customCommands.js';
|
|
9
11
|
import { summarizeHooks } from '../utils/hooks.js';
|
|
@@ -210,60 +212,14 @@ export async function handleCommand(input, session, onChunk, abortSignal) {
|
|
|
210
212
|
return { handled: true, response: await setApiKeyCmd(args[0]) };
|
|
211
213
|
}
|
|
212
214
|
case 'telemetry': {
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
if (envOff) {
|
|
217
|
-
return { handled: true, response: 'Telemetry is forced **off** by the `CODEEP_NO_TELEMETRY` / `DO_NOT_TRACK` env var — unset it to change this. The config flag can\'t override an env var.' };
|
|
218
|
-
}
|
|
219
|
-
config.set('telemetry', sub === 'on');
|
|
220
|
-
return {
|
|
221
|
-
handled: true,
|
|
222
|
-
response: sub === 'on'
|
|
223
|
-
? 'Telemetry **on** — usage stats, session transcripts, progress and memory notes sync to codeep.dev.'
|
|
224
|
-
: 'Telemetry **off** — no automatic cloud uploads. Explicit `/account push` still works.',
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
if (sub && sub !== 'status') {
|
|
228
|
-
return { handled: true, response: 'Usage: `/telemetry` · `/telemetry on` · `/telemetry off`' };
|
|
229
|
-
}
|
|
230
|
-
const flag = config.get('telemetry') !== false;
|
|
231
|
-
const lines = [
|
|
232
|
-
`**Telemetry:** ${isTelemetryEnabled() ? 'on' : 'off'}`,
|
|
233
|
-
`- Config flag \`telemetry\`: ${flag}`,
|
|
234
|
-
];
|
|
235
|
-
if (envOff)
|
|
236
|
-
lines.push('- Forced **off** by `CODEEP_NO_TELEMETRY` / `DO_NOT_TRACK` (env overrides the flag).');
|
|
237
|
-
lines.push('', 'Toggle with `/telemetry on` | `/telemetry off`. Controls automatic uploads of usage stats, session transcripts, progress, and memory notes.');
|
|
238
|
-
return { handled: true, response: lines.join('\n') };
|
|
215
|
+
// Core semantics in commands/core/telemetry.ts — this surface only renders.
|
|
216
|
+
const result = telemetryCommand(args);
|
|
217
|
+
return { handled: true, response: result.message };
|
|
239
218
|
}
|
|
240
219
|
case 'keysync': {
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
if (envOff) {
|
|
245
|
-
return { handled: true, response: 'Cloud key sync is forced **off** by the `CODEEP_NO_KEY_SYNC` env var — unset it to change this. The config flag can\'t override an env var.' };
|
|
246
|
-
}
|
|
247
|
-
config.set('syncKeysToCloud', sub === 'on');
|
|
248
|
-
return {
|
|
249
|
-
handled: true,
|
|
250
|
-
response: sub === 'on'
|
|
251
|
-
? 'Cloud key sync **on** — `codeep account push`/`sync` will now upload/download API keys. Note: synced keys are stored server-readable on codeep.dev.'
|
|
252
|
-
: 'Cloud key sync **off** — API keys stay in your OS keychain only. (`codeep account purge-keys` wipes any keys already on the server.)',
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
if (sub && sub !== 'status') {
|
|
256
|
-
return { handled: true, response: 'Usage: `/keysync` · `/keysync on` · `/keysync off`' };
|
|
257
|
-
}
|
|
258
|
-
const flag = config.get('syncKeysToCloud') === true;
|
|
259
|
-
const lines = [
|
|
260
|
-
`**Cloud key sync:** ${isKeySyncEnabled() ? 'on' : 'off'}`,
|
|
261
|
-
`- Config flag \`syncKeysToCloud\`: ${flag}`,
|
|
262
|
-
];
|
|
263
|
-
if (envOff)
|
|
264
|
-
lines.push('- Forced **off** by `CODEEP_NO_KEY_SYNC` (env overrides the flag).');
|
|
265
|
-
lines.push('', 'OFF by default — API keys live only in your OS keychain unless enabled. When on, `codeep account push`/`sync` move keys, stored **server-readable** on codeep.dev.');
|
|
266
|
-
return { handled: true, response: lines.join('\n') };
|
|
220
|
+
// Core semantics in commands/core/keysync.ts — this surface only renders.
|
|
221
|
+
const result = keysyncCommand(args);
|
|
222
|
+
return { handled: true, response: result.message };
|
|
267
223
|
}
|
|
268
224
|
case 'login': {
|
|
269
225
|
const [providerId, apiKey] = args;
|
|
@@ -681,7 +637,7 @@ Anything else the agent should know — edge cases, gotchas, things to double-ch
|
|
|
681
637
|
}
|
|
682
638
|
// ─── Personalities + insights (2.0.3) ─────────────────────────────────────
|
|
683
639
|
case 'personality': {
|
|
684
|
-
const { formatPersonalityList, findPersonality } = await import('../utils/personalities.js');
|
|
640
|
+
const { formatPersonalityList, findPersonality, formatPersonalityActivation } = await import('../utils/personalities.js');
|
|
685
641
|
const sub = args[0]?.toLowerCase();
|
|
686
642
|
if (!sub) {
|
|
687
643
|
return { handled: true, response: formatPersonalityList(session.workspaceRoot) };
|
|
@@ -697,7 +653,7 @@ Anything else the agent should know — edge cases, gotchas, things to double-ch
|
|
|
697
653
|
config.set('activePersonality', p.name);
|
|
698
654
|
return {
|
|
699
655
|
handled: true,
|
|
700
|
-
response:
|
|
656
|
+
response: formatPersonalityActivation(p),
|
|
701
657
|
};
|
|
702
658
|
}
|
|
703
659
|
case 'agents': {
|
package/dist/acp/protocol.d.ts
CHANGED
|
@@ -268,6 +268,40 @@ export interface ListSessionsResult {
|
|
|
268
268
|
sessions: AcpSessionInfo[];
|
|
269
269
|
nextCursor?: string | null;
|
|
270
270
|
}
|
|
271
|
+
export interface ListPersonalitiesParams {
|
|
272
|
+
sessionId: string;
|
|
273
|
+
}
|
|
274
|
+
export interface AcpPersonalityInfo {
|
|
275
|
+
name: string;
|
|
276
|
+
displayName: string;
|
|
277
|
+
description: string;
|
|
278
|
+
structured: boolean;
|
|
279
|
+
/** True only when Tools was explicitly declared; [] then means conversation-only. */
|
|
280
|
+
restrictTools: boolean;
|
|
281
|
+
scope: 'builtin' | 'project' | 'global';
|
|
282
|
+
model: string;
|
|
283
|
+
tools: string[];
|
|
284
|
+
projectScope: 'all' | 'selected' | 'personal' | 'unspecified';
|
|
285
|
+
projects: string[];
|
|
286
|
+
available: boolean;
|
|
287
|
+
}
|
|
288
|
+
export interface ListPersonalitiesResult {
|
|
289
|
+
personalities: AcpPersonalityInfo[];
|
|
290
|
+
activePersonality: string | null;
|
|
291
|
+
}
|
|
292
|
+
export interface SetPersonalityParams {
|
|
293
|
+
sessionId: string;
|
|
294
|
+
personalityId: string | null;
|
|
295
|
+
}
|
|
296
|
+
export interface SetPersonalityResult {
|
|
297
|
+
activePersonality: string | null;
|
|
298
|
+
}
|
|
299
|
+
export interface SyncPersonalitiesParams {
|
|
300
|
+
sessionId: string;
|
|
301
|
+
}
|
|
302
|
+
export interface SyncPersonalitiesResult extends ListPersonalitiesResult {
|
|
303
|
+
updated: number;
|
|
304
|
+
}
|
|
271
305
|
export interface DeleteSessionParams {
|
|
272
306
|
sessionId: string;
|
|
273
307
|
cwd?: string;
|
package/dist/acp/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { SessionModeState, SessionConfigOption } from './protocol.js';
|
|
1
|
+
import { SessionModeState, SessionConfigOption, ListPersonalitiesResult } from './protocol.js';
|
|
2
|
+
import { type Personality } from '../utils/personalities.js';
|
|
2
3
|
export declare const AGENT_MODES: SessionModeState;
|
|
3
4
|
/**
|
|
4
5
|
* Format a tool call's parameters into a human-readable object for the
|
|
@@ -39,4 +40,8 @@ export declare function providerHasKey(providerId: string): boolean;
|
|
|
39
40
|
* Exported for unit testing (see server.test.ts).
|
|
40
41
|
*/
|
|
41
42
|
export declare function buildConfigOptions(): SessionConfigOption[];
|
|
43
|
+
/** Build the stable Codeep ACP personality extension payload for a workspace. */
|
|
44
|
+
export declare function buildPersonalityListResult(workspaceRoot: string): ListPersonalitiesResult;
|
|
45
|
+
/** Resolve an ACP selection using the same scope/model availability gate as list. */
|
|
46
|
+
export declare function resolvePersonalitySelection(personalityId: unknown, workspaceRoot: string): Personality | null;
|
|
42
47
|
export declare function startAcpServer(): Promise<void>;
|
package/dist/acp/server.js
CHANGED
|
@@ -17,10 +17,11 @@ import { autoSaveSession, config, getApiKey, getConfiguredProviders } from '../c
|
|
|
17
17
|
import { ApiError } from '../api/index.js';
|
|
18
18
|
import { PROVIDERS } from '../config/providers.js';
|
|
19
19
|
import { getCurrentVersion } from '../utils/update.js';
|
|
20
|
-
import { reportStats, syncSession, generateProjectId } from '../utils/codeepCloud.js';
|
|
20
|
+
import { reportStats, syncSession, generateProjectId, pullPersonalities } from '../utils/codeepCloud.js';
|
|
21
21
|
import { getCostBreakdown, getRecordCount, createTokenScope, runWithTokenScope } from '../utils/tokenTracker.js';
|
|
22
22
|
import { isGitRepository } from '../utils/git.js';
|
|
23
23
|
import { getProjectContext } from '../utils/project.js';
|
|
24
|
+
import { findPersonality, isPersonalityAvailable, loadAllPersonalities } from '../utils/personalities.js';
|
|
24
25
|
// ─── Slash commands advertised to Zed ────────────────────────────────────────
|
|
25
26
|
const AVAILABLE_COMMANDS = [
|
|
26
27
|
// Configuration
|
|
@@ -58,7 +59,7 @@ const AVAILABLE_COMMANDS = [
|
|
|
58
59
|
{ name: 'plan', description: 'Generate a numbered plan for a task — review before /go executes', input: { hint: '<task>' } },
|
|
59
60
|
{ name: 'go', description: 'Execute the pending plan from /plan' },
|
|
60
61
|
// Personalities + insights (2.0.3)
|
|
61
|
-
{ name: 'personality', description: 'List or switch
|
|
62
|
+
{ name: 'personality', description: 'List or switch a personality or structured custom bot', input: { hint: '[name | off]' } },
|
|
62
63
|
{ name: 'me', description: 'Your user profile — adapts the agent to you (reply language, style, stack)', input: { hint: '[init [project] | on | off | learn [on|off|project] | forget | sync]' } },
|
|
63
64
|
{ name: 'agents', description: 'List sub-agents the agent can delegate self-contained tasks to' },
|
|
64
65
|
{ name: 'insights', description: 'Activity summary over the last N days (default 7)', input: { hint: '[--days N]' } },
|
|
@@ -366,6 +367,34 @@ export function buildConfigOptions() {
|
|
|
366
367
|
},
|
|
367
368
|
];
|
|
368
369
|
}
|
|
370
|
+
/** Build the stable Codeep ACP personality extension payload for a workspace. */
|
|
371
|
+
export function buildPersonalityListResult(workspaceRoot) {
|
|
372
|
+
const personalities = loadAllPersonalities(workspaceRoot).map(personality => ({
|
|
373
|
+
name: personality.name,
|
|
374
|
+
displayName: personality.displayName,
|
|
375
|
+
description: personality.description,
|
|
376
|
+
structured: personality.structured === true,
|
|
377
|
+
restrictTools: personality.restrictTools === true,
|
|
378
|
+
scope: personality.scope,
|
|
379
|
+
model: personality.structured ? (personality.modelPreference ?? 'automatic') : 'automatic',
|
|
380
|
+
tools: personality.structured ? (personality.tools ?? []) : [],
|
|
381
|
+
projectScope: personality.projectScope ?? 'unspecified',
|
|
382
|
+
projects: personality.projects ?? [],
|
|
383
|
+
available: isPersonalityAvailable(personality, workspaceRoot),
|
|
384
|
+
}));
|
|
385
|
+
const configuredActive = config.get('activePersonality') ?? null;
|
|
386
|
+
const activePersonality = configuredActive && personalities.some(personality => personality.name === configuredActive && personality.available)
|
|
387
|
+
? configuredActive
|
|
388
|
+
: null;
|
|
389
|
+
return { personalities, activePersonality };
|
|
390
|
+
}
|
|
391
|
+
/** Resolve an ACP selection using the same scope/model availability gate as list. */
|
|
392
|
+
export function resolvePersonalitySelection(personalityId, workspaceRoot) {
|
|
393
|
+
if (typeof personalityId !== 'string')
|
|
394
|
+
return null;
|
|
395
|
+
const personality = findPersonality(personalityId, workspaceRoot);
|
|
396
|
+
return personality && isPersonalityAvailable(personality, workspaceRoot) ? personality : null;
|
|
397
|
+
}
|
|
369
398
|
// ─── Server ───────────────────────────────────────────────────────────────────
|
|
370
399
|
export function startAcpServer() {
|
|
371
400
|
const transport = new StdioTransport();
|
|
@@ -440,6 +469,15 @@ export function startAcpServer() {
|
|
|
440
469
|
case 'session/list_providers':
|
|
441
470
|
handleListProviders(req);
|
|
442
471
|
break;
|
|
472
|
+
case 'session/list_personalities':
|
|
473
|
+
handleListPersonalities(req);
|
|
474
|
+
break;
|
|
475
|
+
case 'session/set_personality':
|
|
476
|
+
handleSetPersonality(req);
|
|
477
|
+
break;
|
|
478
|
+
case 'session/sync_personalities':
|
|
479
|
+
handleSyncPersonalities(req);
|
|
480
|
+
break;
|
|
443
481
|
default:
|
|
444
482
|
process.stderr.write(`[codeep-acp] Unknown method: ${req.method}\n`);
|
|
445
483
|
transport.error(req.id, -32601, `Method not found: ${req.method}`);
|
|
@@ -783,6 +821,63 @@ export function startAcpServer() {
|
|
|
783
821
|
function handleListProviders(msg) {
|
|
784
822
|
handleListProvidersExternal(msg, handlerDeps);
|
|
785
823
|
}
|
|
824
|
+
// ── Codeep personality extensions ─────────────────────────────────────────
|
|
825
|
+
// These methods are intentionally additive to ACP v1. VS Code can render a
|
|
826
|
+
// native picker without scraping markdown from `/personality`; clients that
|
|
827
|
+
// do not know the extension continue using the slash command unchanged.
|
|
828
|
+
function personalityListResult(sessionId) {
|
|
829
|
+
const session = sessions.get(sessionId);
|
|
830
|
+
if (!session)
|
|
831
|
+
return null;
|
|
832
|
+
return buildPersonalityListResult(session.workspaceRoot);
|
|
833
|
+
}
|
|
834
|
+
function handleListPersonalities(msg) {
|
|
835
|
+
const params = (msg.params ?? {});
|
|
836
|
+
const result = personalityListResult(params.sessionId);
|
|
837
|
+
if (!result) {
|
|
838
|
+
transport.error(msg.id, -32602, `Unknown sessionId: ${params.sessionId}`);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
transport.respond(msg.id, result);
|
|
842
|
+
}
|
|
843
|
+
function handleSetPersonality(msg) {
|
|
844
|
+
const params = (msg.params ?? {});
|
|
845
|
+
const session = sessions.get(params.sessionId);
|
|
846
|
+
if (!session) {
|
|
847
|
+
transport.error(msg.id, -32602, `Unknown sessionId: ${params.sessionId}`);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (params.personalityId === null) {
|
|
851
|
+
config.set('activePersonality', null);
|
|
852
|
+
const result = { activePersonality: null };
|
|
853
|
+
transport.respond(msg.id, result);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const selected = resolvePersonalitySelection(params.personalityId, session.workspaceRoot);
|
|
857
|
+
if (!selected) {
|
|
858
|
+
transport.error(msg.id, -32602, `Personality is unknown or unavailable here: ${String(params.personalityId)}`);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const personalityId = params.personalityId.toLowerCase();
|
|
862
|
+
config.set('activePersonality', personalityId);
|
|
863
|
+
const result = { activePersonality: personalityId };
|
|
864
|
+
transport.respond(msg.id, result);
|
|
865
|
+
}
|
|
866
|
+
async function handleSyncPersonalities(msg) {
|
|
867
|
+
const params = (msg.params ?? {});
|
|
868
|
+
if (!sessions.has(params.sessionId)) {
|
|
869
|
+
transport.error(msg.id, -32602, `Unknown sessionId: ${params.sessionId}`);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const updated = await pullPersonalities();
|
|
873
|
+
if (updated === null) {
|
|
874
|
+
transport.error(msg.id, -32001, 'Personality sync failed or this device is not linked to codeep.dev.');
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const list = personalityListResult(params.sessionId);
|
|
878
|
+
const result = { updated, ...list };
|
|
879
|
+
transport.respond(msg.id, result);
|
|
880
|
+
}
|
|
786
881
|
// ── session/prompt ──────────────────────────────────────────────────────────
|
|
787
882
|
async function handleSessionPrompt(msg) {
|
|
788
883
|
const params = msg.params;
|
package/dist/api/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as http from 'node:http';
|
|
|
2
2
|
import * as https from 'node:https';
|
|
3
3
|
import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
|
|
4
4
|
import { withRetry, isNetworkError } from '../utils/retry.js';
|
|
5
|
+
import { checkApiRateLimit } from '../utils/ratelimit.js';
|
|
5
6
|
import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, reasoningParamsFor } from '../config/providers.js';
|
|
6
7
|
import { logApiRequest, logApiResponse } from '../utils/logger.js';
|
|
7
8
|
import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
|
|
@@ -154,6 +155,14 @@ export async function chat(message, history = [], onChunk, onRetry, projectConte
|
|
|
154
155
|
const protocol = config.get('protocol');
|
|
155
156
|
const model = config.get('model');
|
|
156
157
|
const providerId = config.get('provider');
|
|
158
|
+
// Global API throttle — the single choke point every surface funnels
|
|
159
|
+
// through (TUI chat, agent loop, ACP sessions, sub-agents, session
|
|
160
|
+
// titles). Without this, a runaway agent loop burns provider quota and
|
|
161
|
+
// hits provider-side 429s with nothing on our side slowing it down.
|
|
162
|
+
const rateCheck = checkApiRateLimit();
|
|
163
|
+
if (!rateCheck.allowed) {
|
|
164
|
+
throw new ApiError(rateCheck.message || 'API rate limit exceeded', 429);
|
|
165
|
+
}
|
|
157
166
|
const { isNoApiKeyProvider } = await import('../config/providers.js');
|
|
158
167
|
const apiKey = getApiKey() || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
159
168
|
if (!apiKey) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Result of executing a core command. Plain data — no UI. */
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
/** Outcome for the user, in a presentation-neutral voice. */
|
|
4
|
+
message: string;
|
|
5
|
+
/** Severity hints the surface's rendering (banner vs error style). */
|
|
6
|
+
kind?: 'ok' | 'info' | 'warn' | 'error';
|
|
7
|
+
/** Optional follow-up action the surface may offer (unused for now). */
|
|
8
|
+
hint?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Context a core command may need. All fields optional — commands declare
|
|
11
|
+
* what they use via their signature, surfaces pass what they have. */
|
|
12
|
+
export interface CoreCommandContext {
|
|
13
|
+
/** Raw args after the command name (e.g. ['on'] for `/telemetry on`). */
|
|
14
|
+
args: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function ok(message: string): CommandResult;
|
|
17
|
+
export declare function info(message: string): CommandResult;
|
|
18
|
+
export declare function warn(message: string): CommandResult;
|
|
19
|
+
export declare function error(message: string): CommandResult;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// commands/core/index.ts
|
|
2
|
+
// Shared command core — single implementation of command semantics used by
|
|
3
|
+
// BOTH dispatch surfaces (TUI renderer/commands.ts and ACP acp/commands.ts).
|
|
4
|
+
//
|
|
5
|
+
// Problem this solves: ~40 commands are implemented twice with different
|
|
6
|
+
// presentation (TUI: app.notify / interactive pickers; ACP: response
|
|
7
|
+
// strings). Every behavior change had to be made twice, and they drifted.
|
|
8
|
+
//
|
|
9
|
+
// Contract: a core command does validation + state mutation and returns a
|
|
10
|
+
// CommandResult — plain data describing the outcome. The surface adapter
|
|
11
|
+
// (TUI or ACP) renders that result in its own style. Side-effectful UI
|
|
12
|
+
// (pickers, dialogs) stays in the surface layer; pure decisions live here.
|
|
13
|
+
//
|
|
14
|
+
// Migration is incremental: each command moves over one at a time, in its
|
|
15
|
+
// own change, with the old inline branches deleted only once both surfaces
|
|
16
|
+
// call the core.
|
|
17
|
+
export function ok(message) {
|
|
18
|
+
return { message, kind: 'ok' };
|
|
19
|
+
}
|
|
20
|
+
export function info(message) {
|
|
21
|
+
return { message, kind: 'info' };
|
|
22
|
+
}
|
|
23
|
+
export function warn(message) {
|
|
24
|
+
return { message, kind: 'warn' };
|
|
25
|
+
}
|
|
26
|
+
export function error(message) {
|
|
27
|
+
return { message, kind: 'error' };
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// commands/core/keysync.ts
|
|
2
|
+
// Core semantics of `/keysync` — shared by TUI and ACP dispatch.
|
|
3
|
+
//
|
|
4
|
+
// Single source of truth for: env-forced-off check, config toggle, and the
|
|
5
|
+
// status report facts (including the server-readable-keys disclosure that
|
|
6
|
+
// must always accompany enabling sync).
|
|
7
|
+
import { config, keySyncForcedOffByEnv, isKeySyncEnabled } from '../../config/index.js';
|
|
8
|
+
import { ok, warn, info } from './index.js';
|
|
9
|
+
export function keysyncCommand(args) {
|
|
10
|
+
const sub = args[0]?.toLowerCase();
|
|
11
|
+
const envOff = keySyncForcedOffByEnv();
|
|
12
|
+
if (sub === 'on' || sub === 'off') {
|
|
13
|
+
if (envOff) {
|
|
14
|
+
return warn('Cloud key sync is forced off by CODEEP_NO_KEY_SYNC — unset that env var to change it. The config flag can\'t override an env var.');
|
|
15
|
+
}
|
|
16
|
+
config.set('syncKeysToCloud', sub === 'on');
|
|
17
|
+
return ok(sub === 'on'
|
|
18
|
+
? 'Cloud key sync on — `codeep account push`/`sync` will now upload/download API keys. Note: synced keys are stored server-readable on codeep.dev.'
|
|
19
|
+
: 'Cloud key sync off — API keys stay in your OS keychain only. (`codeep account purge-keys` wipes any keys already on the server.)');
|
|
20
|
+
}
|
|
21
|
+
if (sub && sub !== 'status') {
|
|
22
|
+
return info('Usage: /keysync · /keysync on · /keysync off');
|
|
23
|
+
}
|
|
24
|
+
const flag = config.get('syncKeysToCloud') === true;
|
|
25
|
+
const lines = [
|
|
26
|
+
`Cloud key sync: ${isKeySyncEnabled() ? 'on' : 'off'}`,
|
|
27
|
+
`- Config flag \`syncKeysToCloud\`: ${flag}`,
|
|
28
|
+
];
|
|
29
|
+
if (envOff) {
|
|
30
|
+
lines.push('- Forced off by `CODEEP_NO_KEY_SYNC` (env overrides the flag).');
|
|
31
|
+
}
|
|
32
|
+
lines.push('', 'OFF by default — API keys live only in your OS keychain unless enabled. When on, `codeep account push`/`sync` move keys, stored server-readable on codeep.dev.');
|
|
33
|
+
return info(lines.join('\n'));
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// commands/core/telemetry.ts
|
|
2
|
+
// Core semantics of `/telemetry` — shared by TUI and ACP dispatch.
|
|
3
|
+
//
|
|
4
|
+
// Single source of truth for: env-forced-off check, config toggle,
|
|
5
|
+
// and the status report facts. Presentation (markdown vs banner) is the
|
|
6
|
+
// surface's job; this returns plain-data CommandResults.
|
|
7
|
+
import { config, isTelemetryEnabled, telemetryForcedOffByEnv } from '../../config/index.js';
|
|
8
|
+
import { ok, warn, info } from './index.js';
|
|
9
|
+
export function telemetryCommand(args) {
|
|
10
|
+
const sub = args[0]?.toLowerCase();
|
|
11
|
+
const envOff = telemetryForcedOffByEnv();
|
|
12
|
+
if (sub === 'on' || sub === 'off') {
|
|
13
|
+
if (envOff) {
|
|
14
|
+
return warn('Telemetry is forced off by CODEEP_NO_TELEMETRY / DO_NOT_TRACK — unset that env var to change it. The config flag can\'t override an env var.');
|
|
15
|
+
}
|
|
16
|
+
config.set('telemetry', sub === 'on');
|
|
17
|
+
return ok(sub === 'on'
|
|
18
|
+
? 'Telemetry on — usage stats, session transcripts, progress and memory notes sync to codeep.dev.'
|
|
19
|
+
: 'Telemetry off — no automatic cloud uploads. Explicit /account push still works.');
|
|
20
|
+
}
|
|
21
|
+
if (sub && sub !== 'status') {
|
|
22
|
+
return info('Usage: /telemetry · /telemetry on · /telemetry off');
|
|
23
|
+
}
|
|
24
|
+
const flag = config.get('telemetry') !== false;
|
|
25
|
+
const lines = [
|
|
26
|
+
`Telemetry: ${isTelemetryEnabled() ? 'on' : 'off'}`,
|
|
27
|
+
`- Config flag \`telemetry\`: ${flag}`,
|
|
28
|
+
];
|
|
29
|
+
if (envOff) {
|
|
30
|
+
lines.push('- Forced off by `CODEEP_NO_TELEMETRY` / `DO_NOT_TRACK` (env overrides the flag).');
|
|
31
|
+
}
|
|
32
|
+
lines.push('', 'Toggle with `/telemetry on` | `/telemetry off`. Controls automatic uploads of usage stats, session transcripts, progress, and memory notes.');
|
|
33
|
+
return info(lines.join('\n'));
|
|
34
|
+
}
|
package/dist/config/index.js
CHANGED
|
@@ -182,8 +182,8 @@ function createConfig() {
|
|
|
182
182
|
maxTokens: 32768,
|
|
183
183
|
reasoningEffort: 'auto',
|
|
184
184
|
apiTimeout: 60000,
|
|
185
|
-
rateLimitApi:
|
|
186
|
-
rateLimitCommands:
|
|
185
|
+
rateLimitApi: 240, // API requests per minute — generous for a 50-iteration agent, still stops runaway loops
|
|
186
|
+
rateLimitCommands: 120, // Commands per minute
|
|
187
187
|
projectPermissions: [],
|
|
188
188
|
providerApiKeys: [],
|
|
189
189
|
configuredProviderIds: [],
|
package/dist/renderer/App.d.ts
CHANGED
|
@@ -20,37 +20,13 @@ export interface ConfirmOptions {
|
|
|
20
20
|
onConfirm: () => void;
|
|
21
21
|
onCancel?: () => void;
|
|
22
22
|
}
|
|
23
|
+
export { HunkPickerItem, HunkPickerOptions } from './components/HunkPicker';
|
|
24
|
+
import { type HunkPickerOptions } from './components/HunkPicker';
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
+
* Options for the interactive hunk picker — see components/HunkPicker.ts.
|
|
27
|
+
* (`onComplete` fires once with the accepted [path, hunkIndex] pairs so the
|
|
28
|
+
* caller can apply them via `applyHunksToFiles`.)
|
|
26
29
|
*/
|
|
27
|
-
export interface HunkPickerItem {
|
|
28
|
-
/** File path this hunk belongs to. */
|
|
29
|
-
path: string;
|
|
30
|
-
/** 0-based hunk index within the file diff. */
|
|
31
|
-
hunkIndex: number;
|
|
32
|
-
/** Human-readable hunk header, e.g. `@@ -12,3 +12,5 @@`. */
|
|
33
|
-
header: string;
|
|
34
|
-
/** Pre-formatted diff lines to display. */
|
|
35
|
-
lines: string[];
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Options for the interactive hunk picker. The picker walks the user
|
|
39
|
-
* through `items` one at a time; for each they accept (`y`/Enter) or
|
|
40
|
-
* skip (`n`). `a` accepts all remaining, `q`/Esc quits.
|
|
41
|
-
*
|
|
42
|
-
* `onComplete` fires once with the set of accepted `[path, hunkIndex]`
|
|
43
|
-
* pairs (possibly empty) so the caller can apply them via
|
|
44
|
-
* `applyHunksToFiles`.
|
|
45
|
-
*/
|
|
46
|
-
export interface HunkPickerOptions {
|
|
47
|
-
title: string;
|
|
48
|
-
items: HunkPickerItem[];
|
|
49
|
-
onComplete: (accepted: Array<{
|
|
50
|
-
path: string;
|
|
51
|
-
hunkIndex: number;
|
|
52
|
-
}>) => void;
|
|
53
|
-
}
|
|
54
30
|
export interface AppOptions {
|
|
55
31
|
onSubmit: (message: string) => Promise<void>;
|
|
56
32
|
onCommand: (command: string, args: string[]) => void;
|
|
@@ -93,30 +69,19 @@ export declare class App {
|
|
|
93
69
|
private appStartedAt;
|
|
94
70
|
/** Start of the current agent run; unlike app uptime, resets per task. */
|
|
95
71
|
private agentStartedAt;
|
|
96
|
-
private
|
|
97
|
-
private pasteInfoOpen;
|
|
72
|
+
private pasteDialog;
|
|
98
73
|
private codeBlockCounter;
|
|
99
74
|
private messageCache;
|
|
100
75
|
private helpOpen;
|
|
101
76
|
private helpScrollIndex;
|
|
102
77
|
private statusOpen;
|
|
103
78
|
private settingsState;
|
|
104
|
-
private
|
|
105
|
-
private
|
|
106
|
-
private autocompleteItems;
|
|
107
|
-
private showMentionAutocomplete;
|
|
108
|
-
private mentionIndex;
|
|
109
|
-
private mentionItems;
|
|
110
|
-
private mentionAtStart;
|
|
111
|
-
/** Project root for resolving `suggestMentions`. Cached per update. */
|
|
112
|
-
private mentionRoot;
|
|
79
|
+
private autocomplete;
|
|
80
|
+
private mention;
|
|
113
81
|
private confirmOpen;
|
|
114
82
|
private confirmOptions;
|
|
115
83
|
private confirmSelection;
|
|
116
|
-
private
|
|
117
|
-
private hunkPickerOptions;
|
|
118
|
-
private hunkPickerIndex;
|
|
119
|
-
private hunkPickerAccepted;
|
|
84
|
+
private hunkPicker;
|
|
120
85
|
private menuOpen;
|
|
121
86
|
private menuTitle;
|
|
122
87
|
/** Filtered view shown to the user; derived from `menuItemsAll` + `menuFilter`. */
|
|
@@ -449,10 +414,6 @@ export declare class App {
|
|
|
449
414
|
* Render inline confirmation dialog below status bar
|
|
450
415
|
*/
|
|
451
416
|
private renderInlineConfirm;
|
|
452
|
-
/**
|
|
453
|
-
* Render inline hunk picker (`/apply --interactive`).
|
|
454
|
-
* Shows the current hunk's diff + the y/n/a/q key legend.
|
|
455
|
-
*/
|
|
456
417
|
private renderInlineHunkPicker;
|
|
457
418
|
/**
|
|
458
419
|
* Render input line
|