@sayknow-cli/coding-agent 0.6.5 → 0.6.7
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/CHANGELOG.md +29 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +79 -0
- package/dist/types/i18n/messages/en.d.ts +6 -29
- package/dist/types/modes/components/welcome.d.ts +34 -40
- package/dist/types/modes/interactive-mode.d.ts +7 -4
- package/dist/types/modes/types.d.ts +4 -0
- package/dist/types/sdk/broker/broker.d.ts +22 -0
- package/dist/types/sdk/broker/process-guard.d.ts +71 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/session/auth-storage-discovery.d.ts +15 -0
- package/dist/types/session/auto-fallback.d.ts +27 -0
- package/dist/types/session/fallback-chain-controller.d.ts +5 -0
- package/dist/types/session/response-language.d.ts +25 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +8 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/locate-core.d.ts +97 -0
- package/dist/types/tools/locate.d.ts +38 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/cli/setup-cli.ts +7 -4
- package/src/commands/sdk.ts +3 -0
- package/src/commands/setup.ts +4 -1
- package/src/config/keybindings.ts +7 -0
- package/src/config/settings-schema.ts +82 -0
- package/src/decisions/typesafe-backend.ts +38 -4
- package/src/i18n/messages/de.settings.ts +26 -0
- package/src/i18n/messages/de.ts +4 -27
- package/src/i18n/messages/en.ts +6 -29
- package/src/i18n/messages/es.settings.ts +26 -0
- package/src/i18n/messages/es.ts +4 -27
- package/src/i18n/messages/fr.settings.ts +26 -0
- package/src/i18n/messages/fr.ts +4 -27
- package/src/i18n/messages/ja.settings.ts +26 -0
- package/src/i18n/messages/ja.ts +4 -27
- package/src/i18n/messages/ko.settings.ts +25 -0
- package/src/i18n/messages/ko.ts +5 -28
- package/src/i18n/messages/zh.settings.ts +23 -0
- package/src/i18n/messages/zh.ts +4 -27
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/modes/action-registry.ts +1 -0
- package/src/modes/components/welcome.ts +385 -387
- package/src/modes/controllers/input-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +13 -0
- package/src/modes/interactive-mode.ts +160 -98
- package/src/modes/types.ts +4 -0
- package/src/prompts/system/system-prompt.md +7 -2
- package/src/prompts/tools/locate.md +12 -0
- package/src/sdk/broker/broker.ts +75 -1
- package/src/sdk/broker/process-guard.ts +160 -0
- package/src/sdk/broker/transport.ts +15 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +13 -1
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session.ts +4 -1
- package/src/session/agent-session.ts +118 -2
- package/src/session/auth-storage-discovery.ts +22 -7
- package/src/session/auto-fallback.ts +59 -0
- package/src/session/fallback-chain-controller.ts +5 -0
- package/src/session/response-language.ts +71 -0
- package/src/setup/model-onboarding-guidance.ts +29 -14
- package/src/setup/provider-onboarding.ts +5 -0
- package/src/slash-commands/builtin-registry.ts +112 -2
- package/src/tools/index.ts +3 -0
- package/src/tools/locate-core.ts +720 -0
- package/src/tools/locate.ts +197 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const MODEL_ONBOARDING_API_PROVIDER_COMMAND = "/provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>";
|
|
2
|
-
|
|
2
|
+
/** Built from the bundled preset catalog, so a new preset shows up here without editing copy. */
|
|
3
|
+
export declare const MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND: string;
|
|
3
4
|
export declare const MODEL_ONBOARDING_SETUP_COMMAND = "skc setup provider";
|
|
4
5
|
export declare const MODEL_ONBOARDING_OAUTH_COMMAND = "/provider login [provider-id] or /login [provider-id]";
|
|
5
6
|
/**
|
|
@@ -7,8 +8,14 @@ export declare const MODEL_ONBOARDING_OAUTH_COMMAND = "/provider login [provider
|
|
|
7
8
|
* user learns it exists is from the surfaces where they go to add credentials.
|
|
8
9
|
*/
|
|
9
10
|
export declare const MODEL_ONBOARDING_TYPESAFE_COMMAND = "/provider typesafe";
|
|
11
|
+
/** Where the models tried after a blocked one are shown and edited. */
|
|
12
|
+
export declare const MODEL_ONBOARDING_FALLBACK_COMMAND = "/fallback";
|
|
10
13
|
export declare function formatModelOnboardingGuidance(): string;
|
|
11
14
|
export declare function formatModelOnboardingInlineHint(): string;
|
|
12
15
|
export declare function formatNoModelOnboardingError(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Missing credentials for the provider the session tried to call. The fix for that
|
|
18
|
+
* provider comes first; the ways to add a different provider follow as alternatives.
|
|
19
|
+
*/
|
|
13
20
|
export declare function formatNoCredentialOnboardingError(providerId: string): string;
|
|
14
21
|
export declare function formatNoModelsAvailableFallback(): string;
|
|
@@ -46,6 +46,8 @@ export declare function normalizeProviderId(providerId: string): string;
|
|
|
46
46
|
export declare function parseProviderCompatibility(value: string): ProviderCompatibility;
|
|
47
47
|
export declare function findProviderPreset(value: string | undefined): ProviderPreset | undefined;
|
|
48
48
|
export declare function formatProviderPresetList(): string;
|
|
49
|
+
/** `minimax|minimax-cn|…` — the preset ids for a `--preset <…>` usage line, in catalog order. */
|
|
50
|
+
export declare function formatProviderPresetChoices(): string;
|
|
49
51
|
export declare function parseModelList(values: readonly string[]): string[];
|
|
50
52
|
export declare function redactSecret(secret: string): string;
|
|
51
53
|
export declare function validateModelApi(modelApi: Readonly<Record<string, string>> | undefined, models: readonly string[], presetId: string): void;
|
|
@@ -8,6 +8,7 @@ import type { OutputMeta } from "./output-meta";
|
|
|
8
8
|
declare const debugSchema: z.ZodObject<{
|
|
9
9
|
action: z.ZodEnum<{
|
|
10
10
|
output: "output";
|
|
11
|
+
sessions: "sessions";
|
|
11
12
|
pause: "pause";
|
|
12
13
|
scopes: "scopes";
|
|
13
14
|
variables: "variables";
|
|
@@ -34,7 +35,6 @@ declare const debugSchema: z.ZodObject<{
|
|
|
34
35
|
loaded_sources: "loaded_sources";
|
|
35
36
|
custom_request: "custom_request";
|
|
36
37
|
terminate: "terminate";
|
|
37
|
-
sessions: "sessions";
|
|
38
38
|
}>;
|
|
39
39
|
program: z.ZodOptional<z.ZodString>;
|
|
40
40
|
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -131,6 +131,7 @@ export declare class DebugTool implements AgentTool<typeof debugSchema, DebugToo
|
|
|
131
131
|
readonly parameters: z.ZodObject<{
|
|
132
132
|
action: z.ZodEnum<{
|
|
133
133
|
output: "output";
|
|
134
|
+
sessions: "sessions";
|
|
134
135
|
pause: "pause";
|
|
135
136
|
scopes: "scopes";
|
|
136
137
|
variables: "variables";
|
|
@@ -157,7 +158,6 @@ export declare class DebugTool implements AgentTool<typeof debugSchema, DebugToo
|
|
|
157
158
|
loaded_sources: "loaded_sources";
|
|
158
159
|
custom_request: "custom_request";
|
|
159
160
|
terminate: "terminate";
|
|
160
|
-
sessions: "sessions";
|
|
161
161
|
}>;
|
|
162
162
|
program: z.ZodOptional<z.ZodString>;
|
|
163
163
|
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { DecisionRequest, DecisionResult } from "../decisions/types";
|
|
2
|
+
/**
|
|
3
|
+
* Find where behaviour lives by asking Jev, the way `jevgrep` does, but inside SKC and
|
|
4
|
+
* without shipping source bodies: a folder is judged from its path and the names of the
|
|
5
|
+
* files in it; a file is judged from its path and its declaration lines (signatures),
|
|
6
|
+
* never its function bodies or comments.
|
|
7
|
+
*
|
|
8
|
+
* The walk narrows before it widens: large folders are split and only the subfolders Jev
|
|
9
|
+
* rates plausible are entered, so a question about the broker never uploads the TUI.
|
|
10
|
+
*/
|
|
11
|
+
export interface LocateParams {
|
|
12
|
+
query: string;
|
|
13
|
+
/** Absolute search root. */
|
|
14
|
+
root: string;
|
|
15
|
+
/** Most files to return. */
|
|
16
|
+
limit: number;
|
|
17
|
+
}
|
|
18
|
+
export interface LocateDeps {
|
|
19
|
+
/** Source files under the root, relative, `/`-separated. */
|
|
20
|
+
listFiles(root: string, signal?: AbortSignal): Promise<string[]>;
|
|
21
|
+
/** Declaration lines of one file (`{ line, text }`), empty when none can be extracted. */
|
|
22
|
+
outline(absolutePath: string, signal?: AbortSignal): Promise<OutlineLine[]>;
|
|
23
|
+
/** One Jev request. Null means the request failed or no key is configured. */
|
|
24
|
+
decide(request: DecisionRequest): Promise<DecisionResult | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Full text of a file for the local keyword score. Read and scored on this machine only;
|
|
27
|
+
* nothing from it is sent. Undefined when a file cannot or should not be read.
|
|
28
|
+
*/
|
|
29
|
+
readText?(absolutePath: string): Promise<string | undefined>;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
export interface OutlineLine {
|
|
33
|
+
line: number;
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
export interface LocatedFile {
|
|
37
|
+
path: string;
|
|
38
|
+
score: number;
|
|
39
|
+
outline: OutlineLine[];
|
|
40
|
+
/** Below {@link FILE_BAR}: a lead worth a glance, not a confident match. */
|
|
41
|
+
weak?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface LocateResult {
|
|
44
|
+
files: LocatedFile[];
|
|
45
|
+
/** Best-scoring files when none cleared the bar, so the caller still has leads. */
|
|
46
|
+
weakLeads: LocatedFile[];
|
|
47
|
+
foldersJudged: number;
|
|
48
|
+
filesJudged: number;
|
|
49
|
+
requests: number;
|
|
50
|
+
failedRequests: number;
|
|
51
|
+
/** Files considered at all after the folder walk (before any cap). */
|
|
52
|
+
candidates: number;
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
durationMs: number;
|
|
55
|
+
}
|
|
56
|
+
/** A folder with at most this many files underneath is not split further; its files are judged directly. */
|
|
57
|
+
export declare const LEAF_FOLDER_FILES = 40;
|
|
58
|
+
export declare const FILE_BAR = 0.5;
|
|
59
|
+
/** Files between this and {@link FILE_BAR} fill the remaining slots, marked weak. */
|
|
60
|
+
export declare const WEAK_FILE_BAR = 0.3;
|
|
61
|
+
/** Hard ceilings so one question can never turn into an unbounded bill. */
|
|
62
|
+
export declare const MAX_REQUESTS = 100;
|
|
63
|
+
/** Query terms for lexical hints: lowercase words of 4+ letters, cut to a 5-letter stem. */
|
|
64
|
+
export declare function queryTerms(query: string): string[];
|
|
65
|
+
/** How many query terms appear in `text`, with camelCase and snake_case identifiers split into words. */
|
|
66
|
+
export declare function termHits(text: string, terms: readonly string[]): number;
|
|
67
|
+
export declare function locateCode(params: LocateParams, deps: LocateDeps): Promise<LocateResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Declaration lines from a structural summary whose bodies and comments were all elided
|
|
70
|
+
* (`minBodyLines: 1`, `minCommentLines: 1`), plus the member lines of elided class-like
|
|
71
|
+
* bodies taken from `sourceLines`: the summariser folds a whole class body, so its methods
|
|
72
|
+
* would otherwise vanish. Only lines at the body's own member indentation are taken;
|
|
73
|
+
* method bodies sit deeper and never come along. Initialisers are cut, so values do not
|
|
74
|
+
* leave the machine. Over {@link MAX_OUTLINE_LINES}, top-level declarations and methods
|
|
75
|
+
* are kept before fields, so a long run of type members never hides what follows.
|
|
76
|
+
*/
|
|
77
|
+
export declare function outlineFromSegments(segments: ReadonlyArray<{
|
|
78
|
+
kind: string;
|
|
79
|
+
startLine: number;
|
|
80
|
+
endLine?: number;
|
|
81
|
+
text?: string | null;
|
|
82
|
+
}>, sourceLines?: readonly string[]): OutlineLine[];
|
|
83
|
+
/**
|
|
84
|
+
* 1-based numbers of lines that belong to a multi-line comment or string: `/* … *\/`
|
|
85
|
+
* blocks (including `/**` and Rust's `/*!`), Python triple-quoted docstrings and strings,
|
|
86
|
+
* and multi-line template literals. A declaration line that opens a string value
|
|
87
|
+
* (`x = """`) is not masked itself — its initialiser is cut later — but the lines after
|
|
88
|
+
* it are.
|
|
89
|
+
*/
|
|
90
|
+
export declare function maskedLines(lines: readonly string[]): Set<number>;
|
|
91
|
+
/**
|
|
92
|
+
* Fallback outline for a file the structural parser rejects (1-2% of TypeScript files,
|
|
93
|
+
* often large central ones). Keeps top-level declarations and members one indent level
|
|
94
|
+
* in, under the same rules as {@link outlineFromSegments}: no comments, imports, bodies,
|
|
95
|
+
* or initialiser values.
|
|
96
|
+
*/
|
|
97
|
+
export declare function outlineFromSource(sourceLines: readonly string[]): OutlineLine[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@sayknow-cli/agent-core";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import type { ToolSession } from ".";
|
|
4
|
+
import { type LocateResult } from "./locate-core";
|
|
5
|
+
declare const locateSchema: z.ZodObject<{
|
|
6
|
+
query: z.ZodString;
|
|
7
|
+
path: z.ZodOptional<z.ZodString>;
|
|
8
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
type LocateParams = z.infer<typeof locateSchema>;
|
|
11
|
+
export interface LocateToolDetails {
|
|
12
|
+
result: Omit<LocateResult, "files" | "weakLeads"> & {
|
|
13
|
+
files: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class LocateTool implements AgentTool<typeof locateSchema, LocateToolDetails> {
|
|
17
|
+
private readonly session;
|
|
18
|
+
readonly name = "locate";
|
|
19
|
+
readonly label = "Locate";
|
|
20
|
+
readonly summary = "Find code by describing what it does (Jev relevance judgement)";
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly parameters: z.ZodObject<{
|
|
23
|
+
query: z.ZodString;
|
|
24
|
+
path: z.ZodOptional<z.ZodString>;
|
|
25
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
readonly strict = true;
|
|
28
|
+
/**
|
|
29
|
+
* Offered only when it can work: a TypeSafe key is stored and `locate.enabled` is on.
|
|
30
|
+
* Without the hosted model every judgement would fall to an uncalibrated LLM, one
|
|
31
|
+
* request per folder batch, which costs more than the search saves.
|
|
32
|
+
*/
|
|
33
|
+
static createIf(session: ToolSession): LocateTool | null;
|
|
34
|
+
constructor(session: ToolSession);
|
|
35
|
+
execute(_toolCallId: string, params: LocateParams, signal?: AbortSignal): Promise<AgentToolResult<LocateToolDetails>>;
|
|
36
|
+
}
|
|
37
|
+
export declare function formatLocateResult(query: string, root: string, cwd: string, result: LocateResult): string;
|
|
38
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.7",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
55
55
|
"@babel/parser": "^7.29.3",
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
|
-
"@sayknow-cli/stats": "0.6.
|
|
58
|
-
"@sayknow-cli/agent-core": "0.6.
|
|
59
|
-
"@sayknow-cli/ai": "0.6.
|
|
60
|
-
"@sayknow-cli/natives": "0.6.
|
|
61
|
-
"@sayknow-cli/tui": "0.6.
|
|
62
|
-
"@sayknow-cli/utils": "0.6.
|
|
57
|
+
"@sayknow-cli/stats": "0.6.7",
|
|
58
|
+
"@sayknow-cli/agent-core": "0.6.7",
|
|
59
|
+
"@sayknow-cli/ai": "0.6.7",
|
|
60
|
+
"@sayknow-cli/natives": "0.6.7",
|
|
61
|
+
"@sayknow-cli/tui": "0.6.7",
|
|
62
|
+
"@sayknow-cli/utils": "0.6.7",
|
|
63
63
|
"@puppeteer/browsers": "^2.13.0",
|
|
64
64
|
"@types/turndown": "5.0.6",
|
|
65
65
|
"@xterm/headless": "^6.0.0",
|
|
@@ -38,6 +38,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
38
38
|
"local session-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
|
|
39
39
|
"slash_command:unstar":
|
|
40
40
|
"local session-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
|
|
41
|
+
"slash_command:fallback":
|
|
42
|
+
"local fallback-chain configuration (fallback.models / fallback.auto settings); no SDK operation counterpart, and SDK clients observe switches via model_fallback_switched",
|
|
41
43
|
"slash_command:import-session":
|
|
42
44
|
"local transcript-file import into a new session; ACP is explicitly disabled and no SDK operation reads caller-selected host paths",
|
|
43
45
|
"agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
|
|
@@ -193,6 +195,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
193
195
|
"internal profile and fallback-chain state, not a user-facing SDK control seam",
|
|
194
196
|
"agent_session:setConfiguredModelChain":
|
|
195
197
|
"internal profile and fallback-chain state, not a user-facing SDK control seam",
|
|
198
|
+
"agent_session:getDefaultFallbackChain":
|
|
199
|
+
"internal profile and fallback-chain state, not a user-facing SDK control seam",
|
|
196
200
|
"agent_session:setDefaultFallbackRuntimeModel":
|
|
197
201
|
"internal fallback runtime bookkeeping, not a user-facing SDK control seam",
|
|
198
202
|
"agent_session:seedDefaultFallbackResolution":
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
import { buildHostPluginSetup, formatHostPluginSetup, type HostPluginKind } from "../setup/host-plugin-setup";
|
|
34
34
|
import {
|
|
35
35
|
addApiCompatibleProvider,
|
|
36
|
+
formatProviderPresetChoices,
|
|
36
37
|
formatProviderPresetList,
|
|
37
38
|
formatProviderSetupResult,
|
|
39
|
+
PROVIDER_PRESETS,
|
|
38
40
|
parseProviderCompatibility,
|
|
39
41
|
} from "../setup/provider-onboarding";
|
|
40
42
|
|
|
@@ -119,7 +121,7 @@ function rejectProviderFlagsOutsideProvider(component: SetupComponent, flags: Se
|
|
|
119
121
|
console.error(chalk.red("Provider setup flags require the explicit `provider` component."));
|
|
120
122
|
console.error(
|
|
121
123
|
chalk.dim(
|
|
122
|
-
`Run: ${APP_NAME} setup provider --preset
|
|
124
|
+
`Run: ${APP_NAME} setup provider --preset <${formatProviderPresetChoices()}> or ${APP_NAME} setup provider --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <id>`,
|
|
123
125
|
),
|
|
124
126
|
);
|
|
125
127
|
process.exit(1);
|
|
@@ -761,8 +763,9 @@ ${chalk.bold("Components:")}
|
|
|
761
763
|
|
|
762
764
|
|
|
763
765
|
${chalk.bold("Provider example:")}
|
|
764
|
-
|
|
765
|
-
${APP_NAME} setup provider --preset
|
|
766
|
+
${PROVIDER_PRESETS.slice(0, 2)
|
|
767
|
+
.map(preset => ` ${APP_NAME} setup provider --preset ${preset.id}`)
|
|
768
|
+
.join("\n")}
|
|
766
769
|
MY_PROVIDER_KEY=sk-... ${APP_NAME} setup provider --compat openai --provider my-oai --base-url https://api.example.com/v1 --api-key-env MY_PROVIDER_KEY --model gpt-example
|
|
767
770
|
|
|
768
771
|
${chalk.bold("Hermes example:")}
|
|
@@ -775,7 +778,7 @@ ${chalk.bold("Options:")}
|
|
|
775
778
|
-c, --check Check if dependencies are installed without installing
|
|
776
779
|
-f, --force Overwrite existing default workflow skill files
|
|
777
780
|
--json Output status as JSON
|
|
778
|
-
--preset Provider preset:
|
|
781
|
+
--preset Provider preset: ${PROVIDER_PRESETS.map(preset => preset.id).join(", ")} (aliases: ${PROVIDER_PRESETS.flatMap(preset => preset.aliases).join(", ")})
|
|
779
782
|
--compat Provider compatibility: openai or anthropic
|
|
780
783
|
--provider Provider id to add to models.yml
|
|
781
784
|
--base-url Provider API base URL
|
package/src/commands/sdk.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
writeSessionLifecycleFailure,
|
|
19
19
|
writeSessionLifecycleReady,
|
|
20
20
|
} from "../sdk/broker/lifecycle";
|
|
21
|
+
import { exitIfSourceGone, startSessionHostGuard } from "../sdk/broker/process-guard";
|
|
21
22
|
import { processIncarnation } from "../sdk/broker/process-incarnation";
|
|
22
23
|
import { type CreateLifecycleAgentSessionResult, createLifecycleAgentSession } from "../sdk/lifecycle-session";
|
|
23
24
|
import { listManagedSessionCandidates, resolveManagedSessionScope } from "../sdk/session-directory";
|
|
@@ -505,6 +506,7 @@ export async function runSessionHost(
|
|
|
505
506
|
}
|
|
506
507
|
process.once("SIGTERM", stop);
|
|
507
508
|
process.once("SIGINT", stop);
|
|
509
|
+
startSessionHostGuard({ markerPath, pid: process.pid, effectMarker, incarnation, cwd, onLost: stop });
|
|
508
510
|
await new Promise<void>(() => {});
|
|
509
511
|
}
|
|
510
512
|
|
|
@@ -559,6 +561,7 @@ export default class Sdk extends Command {
|
|
|
559
561
|
const policy = (await Settings.loadForScope({ cwd, agentDir })).get("session.directoryMigration");
|
|
560
562
|
return policy === "disabled" ? "disabled" : "copy-retain";
|
|
561
563
|
},
|
|
564
|
+
beforePublicationTick: () => exitIfSourceGone(),
|
|
562
565
|
});
|
|
563
566
|
await broker.start();
|
|
564
567
|
if (!broker.ownsDiscovery) return;
|
package/src/commands/setup.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
5
|
import { runSetupCommand, type SetupCommandArgs, type SetupComponent } from "../cli/setup-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
|
+
import { PROVIDER_PRESETS } from "../setup/provider-onboarding";
|
|
7
8
|
|
|
8
9
|
const COMPONENTS: SetupComponent[] = [
|
|
9
10
|
"claude",
|
|
@@ -52,7 +53,9 @@ export default class Setup extends Command {
|
|
|
52
53
|
"skc-command": Flags.string({ description: "Command used to start `skc mcp-serve coordinator`" }),
|
|
53
54
|
target: Flags.string({ description: "Hermes config file target for config-only install" }),
|
|
54
55
|
"profile-dir": Flags.string({ description: "Hermes profile directory for full setup install" }),
|
|
55
|
-
preset: Flags.string({
|
|
56
|
+
preset: Flags.string({
|
|
57
|
+
description: `Provider preset: ${PROVIDER_PRESETS.map(preset => preset.id).join(", ")}`,
|
|
58
|
+
}),
|
|
56
59
|
compat: Flags.string({ description: "Provider compatibility: openai or anthropic" }),
|
|
57
60
|
provider: Flags.string({ description: "Provider id to add to models.yml" }),
|
|
58
61
|
"base-url": Flags.string({ description: "Provider API base URL" }),
|
|
@@ -43,6 +43,7 @@ interface AppKeybindings {
|
|
|
43
43
|
"app.session.tree": true;
|
|
44
44
|
"app.session.fork": true;
|
|
45
45
|
"app.session.resume": true;
|
|
46
|
+
"app.session.continue": true;
|
|
46
47
|
"app.session.observe": true;
|
|
47
48
|
"app.session.dashboard": true;
|
|
48
49
|
"app.jobs.open": true;
|
|
@@ -189,6 +190,12 @@ export const KEYBINDINGS = {
|
|
|
189
190
|
defaultKeys: "alt+r",
|
|
190
191
|
description: "Resume session",
|
|
191
192
|
},
|
|
193
|
+
// A Control chord on purpose: macOS terminals often turn Option into composed text,
|
|
194
|
+
// and continuing the last session is the one launch action that must always work.
|
|
195
|
+
"app.session.continue": {
|
|
196
|
+
defaultKeys: "ctrl+q",
|
|
197
|
+
description: "Continue the most recent session",
|
|
198
|
+
},
|
|
192
199
|
"app.session.observe": {
|
|
193
200
|
defaultKeys: "ctrl+s",
|
|
194
201
|
description: "Observe subagent sessions",
|
|
@@ -1223,8 +1223,39 @@ export const SETTINGS_SCHEMA = {
|
|
|
1223
1223
|
type: "number",
|
|
1224
1224
|
default: 3,
|
|
1225
1225
|
validate: (value: number) => Number.isInteger(value) && value > 0,
|
|
1226
|
+
ui: {
|
|
1227
|
+
tab: "model",
|
|
1228
|
+
label: "Fallback Attempts per Model",
|
|
1229
|
+
description:
|
|
1230
|
+
"Tries on each model in the fallback chain (including the first) before moving to the next one. Missing credentials skip a model immediately.",
|
|
1231
|
+
options: [
|
|
1232
|
+
{ value: "1", label: "1 try" },
|
|
1233
|
+
{ value: "2", label: "2 tries" },
|
|
1234
|
+
{ value: "3", label: "3 tries" },
|
|
1235
|
+
{ value: "5", label: "5 tries" },
|
|
1236
|
+
],
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* When the default model is blocked, keep going on another logged-in provider
|
|
1242
|
+
* after the configured chain and `fallback.models` run out. The extra entries
|
|
1243
|
+
* are computed per session and never written into the configured chain.
|
|
1244
|
+
*/
|
|
1245
|
+
"fallback.auto": {
|
|
1246
|
+
type: "boolean",
|
|
1247
|
+
default: true,
|
|
1248
|
+
ui: {
|
|
1249
|
+
tab: "model",
|
|
1250
|
+
label: "Automatic Model Fallback",
|
|
1251
|
+
description:
|
|
1252
|
+
"When the default model is blocked (no login, quota, auth or server errors), continue on a model from another logged-in provider. /fallback shows the chain.",
|
|
1253
|
+
},
|
|
1226
1254
|
},
|
|
1227
1255
|
|
|
1256
|
+
/** Models to try, in order, after the default chain. Edited with `/fallback`. */
|
|
1257
|
+
"fallback.models": { type: "array", default: EMPTY_STRING_ARRAY },
|
|
1258
|
+
|
|
1228
1259
|
// Retries
|
|
1229
1260
|
"retry.enabled": { type: "boolean", default: true },
|
|
1230
1261
|
|
|
@@ -1443,6 +1474,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
1443
1474
|
],
|
|
1444
1475
|
},
|
|
1445
1476
|
},
|
|
1477
|
+
"startup.welcomeMouse": {
|
|
1478
|
+
type: "boolean",
|
|
1479
|
+
default: true,
|
|
1480
|
+
ui: {
|
|
1481
|
+
tab: "interaction",
|
|
1482
|
+
label: "Clickable Launch Card",
|
|
1483
|
+
description:
|
|
1484
|
+
"Until the first prompt, capture the mouse so a recent session on the launch card opens with one click. Terminal text selection needs Shift held meanwhile. Off: open sessions with ↓ and Enter.",
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1446
1487
|
"startup.skipLogoAnimation": {
|
|
1447
1488
|
type: "boolean",
|
|
1448
1489
|
default: false,
|
|
@@ -3107,6 +3148,22 @@ export const SETTINGS_SCHEMA = {
|
|
|
3107
3148
|
},
|
|
3108
3149
|
},
|
|
3109
3150
|
|
|
3151
|
+
/**
|
|
3152
|
+
* The `locate` tool: find code by describing what it does, judged by Jev. Offered only
|
|
3153
|
+
* when a TypeSafe key is stored. Sends folder paths, file names and declaration lines
|
|
3154
|
+
* (never bodies or comments) to TypeSafe.
|
|
3155
|
+
*/
|
|
3156
|
+
"locate.enabled": {
|
|
3157
|
+
type: "boolean",
|
|
3158
|
+
default: true,
|
|
3159
|
+
ui: {
|
|
3160
|
+
tab: "tools",
|
|
3161
|
+
label: "Locate (Jev code search)",
|
|
3162
|
+
description:
|
|
3163
|
+
"Offer the locate tool when a TypeSafe key is stored. It sends folder paths, file names and declaration lines (no function bodies or comments) to TypeSafe to judge relevance.",
|
|
3164
|
+
},
|
|
3165
|
+
},
|
|
3166
|
+
|
|
3110
3167
|
"tools.essentialOverride": {
|
|
3111
3168
|
type: "array",
|
|
3112
3169
|
default: [] as string[],
|
|
@@ -3492,6 +3549,31 @@ export const SETTINGS_SCHEMA = {
|
|
|
3492
3549
|
// Providers
|
|
3493
3550
|
// ────────────────────────────────────────────────────────────────────────
|
|
3494
3551
|
|
|
3552
|
+
// Multi-account order. `SKC_CREDENTIAL_RANKING_MODE` overrides it per machine.
|
|
3553
|
+
"auth.credentialRankingMode": {
|
|
3554
|
+
type: "enum",
|
|
3555
|
+
values: ["balanced", "earliest-reset"] as const,
|
|
3556
|
+
default: "balanced",
|
|
3557
|
+
ui: {
|
|
3558
|
+
tab: "providers",
|
|
3559
|
+
label: "Multi-Account Order",
|
|
3560
|
+
description:
|
|
3561
|
+
"Which logged-in account of a provider a new session uses. A blocked or exhausted account is always skipped for the next one.",
|
|
3562
|
+
options: [
|
|
3563
|
+
{
|
|
3564
|
+
value: "balanced",
|
|
3565
|
+
label: "Balanced",
|
|
3566
|
+
description: "Least-used account first; spreads load and keeps headroom on every account",
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
value: "earliest-reset",
|
|
3570
|
+
label: "Earliest reset",
|
|
3571
|
+
description: "Account whose usage window resets soonest first, so quota is not lost at reset",
|
|
3572
|
+
},
|
|
3573
|
+
],
|
|
3574
|
+
},
|
|
3575
|
+
},
|
|
3576
|
+
|
|
3495
3577
|
// Secret handling
|
|
3496
3578
|
"secrets.enabled": {
|
|
3497
3579
|
type: "boolean",
|
|
@@ -119,7 +119,7 @@ export function createTypeSafeDecisionBackend(deps: TypeSafeBackendDeps): Decisi
|
|
|
119
119
|
name: "typesafe",
|
|
120
120
|
async decide(request: DecisionRequest): Promise<DecisionResult | null> {
|
|
121
121
|
validateQuestions(request.questions);
|
|
122
|
-
|
|
122
|
+
let apiKey = await deps.registry.getApiKeyForProvider(TYPESAFE_PROVIDER, deps.sessionId);
|
|
123
123
|
// No key means the user never added TypeSafe. That is not an error — the next
|
|
124
124
|
// backend (their logged-in model) handles it.
|
|
125
125
|
if (!apiKey) return null;
|
|
@@ -132,13 +132,26 @@ export function createTypeSafeDecisionBackend(deps: TypeSafeBackendDeps): Decisi
|
|
|
132
132
|
request.signal?.addEventListener("abort", abortOnCaller, { once: true });
|
|
133
133
|
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
134
134
|
const started = Date.now();
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const send = (key: string) =>
|
|
136
|
+
doFetch(`${baseUrl}/v1/systemone`, {
|
|
137
137
|
method: "POST",
|
|
138
|
-
headers: { Authorization: `Bearer ${
|
|
138
|
+
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
139
139
|
body: JSON.stringify({ state: request.state, model, questions: toWireQuestions(request.questions) }),
|
|
140
140
|
signal: controller.signal,
|
|
141
141
|
});
|
|
142
|
+
try {
|
|
143
|
+
let response = await send(apiKey);
|
|
144
|
+
if (response.status === 401 || response.status === 403) {
|
|
145
|
+
// A session reads the credential store once, at startup. A key replaced from
|
|
146
|
+
// another session (or `skc setup typesafe`) stays invisible here, so every
|
|
147
|
+
// request kept sending the revoked key. Reload once; retry only if it changed.
|
|
148
|
+
const refreshed = await reloadedKey(deps, apiKey);
|
|
149
|
+
if (refreshed && !controller.signal.aborted) {
|
|
150
|
+
await response.body?.cancel().catch(() => {});
|
|
151
|
+
apiKey = refreshed;
|
|
152
|
+
response = await send(apiKey);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
142
155
|
if (!response.ok) {
|
|
143
156
|
logger.debug("decisions/typesafe: request failed", {
|
|
144
157
|
status: response.status,
|
|
@@ -169,3 +182,24 @@ export function createTypeSafeDecisionBackend(deps: TypeSafeBackendDeps): Decisi
|
|
|
169
182
|
},
|
|
170
183
|
};
|
|
171
184
|
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Re-read stored credentials and return the TypeSafe key if it differs from the one
|
|
188
|
+
* that was just rejected. Undefined when nothing changed (the key really is bad) or the
|
|
189
|
+
* store cannot be reloaded, so a genuinely revoked key costs one extra read, not a retry.
|
|
190
|
+
*/
|
|
191
|
+
async function reloadedKey(deps: TypeSafeBackendDeps, rejected: string): Promise<string | undefined> {
|
|
192
|
+
const storage = (deps.registry as { authStorage?: { reload?: () => Promise<void> } }).authStorage;
|
|
193
|
+
if (typeof storage?.reload !== "function") return undefined;
|
|
194
|
+
try {
|
|
195
|
+
await storage.reload();
|
|
196
|
+
const current = await deps.registry.getApiKeyForProvider(TYPESAFE_PROVIDER, deps.sessionId);
|
|
197
|
+
if (current && current !== rejected) {
|
|
198
|
+
logger.debug("decisions/typesafe: picked up a key replaced since this session started");
|
|
199
|
+
return current;
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
logger.debug("decisions/typesafe: credential reload failed", { error: String(error) });
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
@@ -235,6 +235,31 @@ export const deSettings: Record<string, string> = {
|
|
|
235
235
|
"set:task.serviceTier:o:claude-only:l": "Priorität (nur Claude)",
|
|
236
236
|
"set:task.serviceTier:o:claude-only:d":
|
|
237
237
|
"Anthropic-Schnellmodus bei direkten Claude-Anfragen; sonst ignoriert (inkl. Bedrock/Vertex)",
|
|
238
|
+
"set:fallback.maxAttempts:l": "Fallback-Versuche pro Modell",
|
|
239
|
+
"set:fallback.maxAttempts:d":
|
|
240
|
+
"Versuche je Modell der Fallback-Kette (einschließlich des ersten), bevor das nächste drankommt. Modelle ohne Anmeldedaten werden sofort übersprungen.",
|
|
241
|
+
"set:fallback.maxAttempts:o:1:l": "1 Versuch",
|
|
242
|
+
"set:fallback.maxAttempts:o:2:l": "2 Versuche",
|
|
243
|
+
"set:fallback.maxAttempts:o:3:l": "3 Versuche",
|
|
244
|
+
"set:fallback.maxAttempts:o:5:l": "5 Versuche",
|
|
245
|
+
"set:fallback.auto:l": "Automatischer Modell-Fallback",
|
|
246
|
+
"set:fallback.auto:d":
|
|
247
|
+
"Ist das Standardmodell blockiert (keine Anmeldung, Kontingent, Auth- oder Serverfehler), mit einem Modell eines anderen angemeldeten Anbieters weitermachen. /fallback zeigt die Kette.",
|
|
248
|
+
"set:auth.credentialRankingMode:l": "Reihenfolge mehrerer Konten",
|
|
249
|
+
"set:auth.credentialRankingMode:d":
|
|
250
|
+
"Welches angemeldete Konto eines Anbieters eine neue Sitzung verwendet. Ein blockiertes oder erschöpftes Konto übergibt immer an das nächste.",
|
|
251
|
+
"set:auth.credentialRankingMode:o:balanced:l": "Ausgewogen",
|
|
252
|
+
"set:auth.credentialRankingMode:o:balanced:d":
|
|
253
|
+
"Am wenigsten genutztes Konto zuerst; verteilt die Last und lässt jedem Konto Reserve",
|
|
254
|
+
"set:auth.credentialRankingMode:o:earliest-reset:l": "Früheste Zurücksetzung",
|
|
255
|
+
"set:auth.credentialRankingMode:o:earliest-reset:d":
|
|
256
|
+
"Konto, dessen Nutzungsfenster zuerst zurückgesetzt wird, zuerst, damit kein Kontingent verfällt",
|
|
257
|
+
"set:locate.enabled:l": "Locate (Jev-Codesuche)",
|
|
258
|
+
"set:locate.enabled:d":
|
|
259
|
+
"Bietet das locate-Werkzeug an, wenn ein TypeSafe-Schlüssel gespeichert ist. Zur Relevanzbewertung werden Ordnerpfade, Dateinamen und Deklarationszeilen (ohne Funktionsrümpfe und Kommentare) an TypeSafe gesendet.",
|
|
260
|
+
"set:startup.welcomeMouse:l": "Klickbare Startkarte",
|
|
261
|
+
"set:startup.welcomeMouse:d":
|
|
262
|
+
"Bis zum ersten Prompt die Maus übernehmen, damit eine letzte Sitzung auf der Startkarte mit einem Klick öffnet. Terminaltext währenddessen mit gedrückter Umschalttaste markieren. Aus: mit ↓ und Enter öffnen.",
|
|
238
263
|
"set:retry.maxRetries:l": "Wiederholungsversuche",
|
|
239
264
|
"set:retry.maxRetries:d": "Maximale Wiederholungsversuche bei API-Fehlern",
|
|
240
265
|
"set:retry.maxRetries:o:1:l": "1 Wiederholung",
|
|
@@ -773,6 +798,7 @@ export const deSettings: Record<string, string> = {
|
|
|
773
798
|
"set:searxng.endpoint:l": "SearXNG-Endpunkt",
|
|
774
799
|
"set:searxng.endpoint:d": "Basis-URL für selbst gehostete Suche",
|
|
775
800
|
"cmd:model:d": "Modell auswählen (öffnet Auswahloberfläche)",
|
|
801
|
+
"cmd:fallback:d": "Modelle anzeigen oder bearbeiten, die bei blockiertem Standardmodell versucht werden",
|
|
776
802
|
"cmd:fast:d": "Prioritäts-Service-Tier umschalten (OpenAI service_tier=priority, Anthropic speed=fast)",
|
|
777
803
|
"cmd:export:d": "Sitzung in HTML-Datei exportieren",
|
|
778
804
|
"cmd:dump:d": "Sitzungstranskript in die Zwischenablage kopieren",
|
package/src/i18n/messages/de.ts
CHANGED
|
@@ -8,41 +8,18 @@ export const de: Partial<Record<MsgKey, string>> = {
|
|
|
8
8
|
"lang.zh": "中文 (简体)",
|
|
9
9
|
"lang.ja": "日本語",
|
|
10
10
|
"welcome.tagline": "Programmieren sollte sich wie Denken anfühlen.",
|
|
11
|
-
"welcome.workflows": "Workflows",
|
|
12
|
-
"welcome.wf.deepInterview": "Umfang · Interview → Spec",
|
|
13
|
-
"welcome.wf.ralplan": "Konsensplan",
|
|
14
|
-
"welcome.wf.ultragoal": "autonomer Build",
|
|
15
|
-
"welcome.wf.team": "parallele Agenten",
|
|
16
|
-
"welcome.flowKeys": "Flow-Tasten",
|
|
17
11
|
"welcome.commands": "Befehle",
|
|
18
|
-
"welcome.actions": "Aktionen",
|
|
19
|
-
"welcome.shell": "Shell",
|
|
20
|
-
"welcome.python": "Python",
|
|
21
12
|
"welcome.keymap": "Keymap",
|
|
22
13
|
"welcome.model": "Modell",
|
|
23
|
-
"welcome.reasoning": "Reasoning",
|
|
24
|
-
"welcome.projectPulse": "Projektpuls",
|
|
25
|
-
"welcome.noLsp": "Keine LSP-Server",
|
|
26
14
|
"welcome.sessionTrail": "Sitzungsverlauf",
|
|
27
15
|
"welcome.noSessions": "Keine gespeicherten Verläufe",
|
|
28
16
|
"welcome.allSessions": "{key} alle Sitzungen",
|
|
17
|
+
"welcome.continue": "{key} fortsetzen",
|
|
18
|
+
"welcome.pick": "{key} wählen",
|
|
19
|
+
"welcome.pickActive": "↑↓ bewegen · ⏎ öffnen · esc zurück",
|
|
20
|
+
"welcome.sessions": "Sitzungen",
|
|
29
21
|
"welcome.chooseModel": "Modell auswählen",
|
|
30
22
|
"welcome.modelHint": "ctrl+l zum Auswählen · / für Befehle",
|
|
31
|
-
"welcome.label.workspace": "Arbeitsbereich",
|
|
32
|
-
"welcome.label.branch": "Branch",
|
|
33
|
-
"welcome.label.commits": "Commits",
|
|
34
|
-
"welcome.label.model": "Modell",
|
|
35
|
-
"welcome.label.reasoning": "Reasoning",
|
|
36
|
-
"welcome.label.preset": "Preset",
|
|
37
|
-
"welcome.label.roles": "Rollen",
|
|
38
|
-
"welcome.label.tools": "Werkzeuge",
|
|
39
|
-
"welcome.noGit": "kein Git-Repository",
|
|
40
|
-
"welcome.rolesInherit": "Rollen folgen dem Standardmodell",
|
|
41
|
-
"welcome.skills": "Skills",
|
|
42
|
-
"welcome.rules": "Regeln",
|
|
43
|
-
"welcome.clean": "sauber",
|
|
44
|
-
"welcome.whatsNew": "Neu",
|
|
45
|
-
"welcome.readyPrompt": "Bereit für den nächsten Prompt",
|
|
46
23
|
"settings.tab.appearance": "Darstellung",
|
|
47
24
|
"settings.tab.model": "Modell",
|
|
48
25
|
"settings.tab.interaction": "Interaktion",
|