codeep 2.18.1 → 2.20.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/config/providers.js +7 -4
- 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/tokenTracker.js +5 -3
- 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/config/providers.js
CHANGED
|
@@ -18,8 +18,6 @@ export const PROVIDERS = {
|
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
models: [
|
|
21
|
-
// GLM-5.3 is Coding-Plan only — the standalone pay-per-use API does not
|
|
22
|
-
// accept it yet, so it must NOT be copied into `z.ai-api`.
|
|
23
21
|
{ id: 'glm-5.3', name: 'GLM-5.3', description: 'Latest flagship for project-scale engineering (1M context)' },
|
|
24
22
|
{ id: 'glm-5.2', name: 'GLM-5.2', description: 'Previous flagship for project-scale engineering (1M context)' },
|
|
25
23
|
{ id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
|
|
@@ -49,10 +47,15 @@ export const PROVIDERS = {
|
|
|
49
47
|
},
|
|
50
48
|
},
|
|
51
49
|
models: [
|
|
52
|
-
|
|
50
|
+
// GLM-5.3 reached the standalone pay-per-use API on 2026-08-19 and is
|
|
51
|
+
// priced on docs.z.ai/guides/overview/pricing. The China platform
|
|
52
|
+
// (`z.ai-cn*`) bills separately and is not covered by that page, so it is
|
|
53
|
+
// left alone until its own listing is checked.
|
|
54
|
+
{ id: 'glm-5.3', name: 'GLM-5.3', description: 'Latest flagship for project-scale engineering (1M context)' },
|
|
55
|
+
{ id: 'glm-5.2', name: 'GLM-5.2', description: 'Previous flagship for project-scale engineering (1M context)' },
|
|
53
56
|
{ id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant' },
|
|
54
57
|
],
|
|
55
|
-
defaultModel: 'glm-5.
|
|
58
|
+
defaultModel: 'glm-5.3',
|
|
56
59
|
defaultProtocol: 'openai',
|
|
57
60
|
maxOutputTokens: 131_072,
|
|
58
61
|
envKey: 'ZAI_API_KEY',
|