@klarkxy/dsh-git-commit 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -16
- package/docs/README.zh-CN.md +28 -11
- package/lib/client.inner.cjs +923 -152
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +923 -152
- package/lib/contracts.d.ts +77 -2
- package/lib/contracts.js +38 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +21 -7
- package/lib/index.js +139 -42
- package/lib/index.js.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +20 -6
- package/lib/contracts2.d.ts +0 -54
package/lib/contracts.d.ts
CHANGED
|
@@ -1,2 +1,77 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { RpcResult } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
2
|
+
//#region src/contracts.d.ts
|
|
3
|
+
declare const PLUGIN_NAME = "@klarkxy/dsh-git-commit";
|
|
4
|
+
declare const FEATURE_ID = "git-commit";
|
|
5
|
+
declare const PURPOSE_ID = "git-commit.plan";
|
|
6
|
+
declare const RPC_CHANNEL = "/dsh-git-commit";
|
|
7
|
+
declare const GIT_COMMIT_CLIENT_SERVICE = "dshGitCommitClient";
|
|
8
|
+
declare const PROMPT_VERSION = "git-commit.v1";
|
|
9
|
+
declare const SCHEMA_VERSION = "commit-plan-json.v1";
|
|
10
|
+
declare const CONTRACT_VERSION = 1;
|
|
11
|
+
declare const DEFAULT_MAX_OUTPUT_TOKENS = 2048;
|
|
12
|
+
/** Total character budget for the change manifest sent to the model. */
|
|
13
|
+
declare const MAX_MANIFEST_CHARS = 48000;
|
|
14
|
+
/** Per-file preview cap for untracked files. */
|
|
15
|
+
declare const MAX_UNTRACKED_PREVIEW_CHARS = 2000;
|
|
16
|
+
/** Recent commit subjects supplied as style reference. */
|
|
17
|
+
declare const RECENT_LOG_COUNT = 10;
|
|
18
|
+
/** Hard cap for one generated commit message line. */
|
|
19
|
+
declare const MAX_MESSAGE_CHARS = 120;
|
|
20
|
+
/** git subprocess timeout. */
|
|
21
|
+
declare const GIT_TIMEOUT_MS = 30000;
|
|
22
|
+
declare const SETTINGS_KEY = "current";
|
|
23
|
+
/** Plugin-page model selection. Empty provider/model means "follow the default". */
|
|
24
|
+
interface CommitModelRoute {
|
|
25
|
+
readonly provider: string;
|
|
26
|
+
readonly model: string;
|
|
27
|
+
readonly reasoningEffort?: string;
|
|
28
|
+
}
|
|
29
|
+
interface GitCommitSettings {
|
|
30
|
+
readonly revision: number;
|
|
31
|
+
readonly model: CommitModelRoute;
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in single commit when the model produced no usable plan. Off by
|
|
34
|
+
* default: an unplanned commit is one the user never reviewed, so a failed
|
|
35
|
+
* or unusable model call rejects the run instead of writing to the index.
|
|
36
|
+
*/
|
|
37
|
+
readonly allowFallback: boolean;
|
|
38
|
+
}
|
|
39
|
+
declare const defaultModelRoute: () => CommitModelRoute;
|
|
40
|
+
declare const defaultSettings: () => GitCommitSettings;
|
|
41
|
+
/** Untrusted stored flags stay opt-in: anything but a literal true is false. */
|
|
42
|
+
declare function normalizeFallback(value: unknown): boolean;
|
|
43
|
+
/** Untrusted stored routes collapse to the default so a bad row never breaks the run. */
|
|
44
|
+
declare function normalizeModelRoute(value: unknown): CommitModelRoute;
|
|
45
|
+
interface CommitModelInfo {
|
|
46
|
+
readonly provider: string;
|
|
47
|
+
readonly model: string;
|
|
48
|
+
/** 'page' is the plugin-page selection; 'default' is the host chat model. */
|
|
49
|
+
readonly source: 'page' | 'default';
|
|
50
|
+
}
|
|
51
|
+
interface GitCommitStatus {
|
|
52
|
+
readonly available: boolean;
|
|
53
|
+
/** Machine-readable reason when unavailable: 'no-session' | 'no-cwd' | 'not-a-repo' | 'no-git'. */
|
|
54
|
+
readonly reason?: string;
|
|
55
|
+
readonly detail?: string;
|
|
56
|
+
readonly root?: string;
|
|
57
|
+
readonly branch?: string;
|
|
58
|
+
readonly workspace: {
|
|
59
|
+
readonly files: number;
|
|
60
|
+
};
|
|
61
|
+
readonly model?: CommitModelInfo;
|
|
62
|
+
}
|
|
63
|
+
interface CommitGroupResult {
|
|
64
|
+
readonly hash: string;
|
|
65
|
+
readonly message: string;
|
|
66
|
+
readonly files: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
interface CommitRunResult {
|
|
69
|
+
readonly branch: string;
|
|
70
|
+
readonly commits: readonly CommitGroupResult[];
|
|
71
|
+
/** True when the model plan was unavailable and a single fallback commit was used. */
|
|
72
|
+
readonly fallback: boolean;
|
|
73
|
+
readonly model?: CommitModelInfo;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { CONTRACT_VERSION, CommitGroupResult, CommitModelInfo, CommitModelRoute, CommitRunResult, DEFAULT_MAX_OUTPUT_TOKENS, FEATURE_ID, GIT_COMMIT_CLIENT_SERVICE, GIT_TIMEOUT_MS, GitCommitSettings, GitCommitStatus, MAX_MANIFEST_CHARS, MAX_MESSAGE_CHARS, MAX_UNTRACKED_PREVIEW_CHARS, PLUGIN_NAME, PROMPT_VERSION, PURPOSE_ID, RECENT_LOG_COUNT, RPC_CHANNEL, type RpcResult, SCHEMA_VERSION, SETTINGS_KEY, defaultModelRoute, defaultSettings, normalizeFallback, normalizeModelRoute };
|
|
77
|
+
//# sourceMappingURL=contracts.d.ts.map
|
package/lib/contracts.js
CHANGED
|
@@ -18,7 +18,44 @@ const RECENT_LOG_COUNT = 10;
|
|
|
18
18
|
const MAX_MESSAGE_CHARS = 120;
|
|
19
19
|
/** git subprocess timeout. */
|
|
20
20
|
const GIT_TIMEOUT_MS = 3e4;
|
|
21
|
+
const SETTINGS_KEY = "current";
|
|
22
|
+
const defaultModelRoute = () => ({
|
|
23
|
+
provider: "",
|
|
24
|
+
model: ""
|
|
25
|
+
});
|
|
26
|
+
const defaultSettings = () => ({
|
|
27
|
+
revision: 0,
|
|
28
|
+
model: defaultModelRoute(),
|
|
29
|
+
allowFallback: false
|
|
30
|
+
});
|
|
31
|
+
/** Untrusted stored flags stay opt-in: anything but a literal true is false. */
|
|
32
|
+
function normalizeFallback(value) {
|
|
33
|
+
return value === true;
|
|
34
|
+
}
|
|
35
|
+
function asRecord(value) {
|
|
36
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
37
|
+
}
|
|
38
|
+
function asText(value) {
|
|
39
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
40
|
+
}
|
|
41
|
+
/** Untrusted stored routes collapse to the default so a bad row never breaks the run. */
|
|
42
|
+
function normalizeModelRoute(value) {
|
|
43
|
+
const row = asRecord(value);
|
|
44
|
+
if (!row) return defaultModelRoute();
|
|
45
|
+
const provider = asText(row.provider) ?? "";
|
|
46
|
+
const model = asText(row.model) ?? "";
|
|
47
|
+
const effort = asText(row.reasoningEffort);
|
|
48
|
+
if (!provider || !model || provider.length > 250 || model.length > 250) return defaultModelRoute();
|
|
49
|
+
return effort && effort.length <= 80 ? {
|
|
50
|
+
provider,
|
|
51
|
+
model,
|
|
52
|
+
reasoningEffort: effort
|
|
53
|
+
} : {
|
|
54
|
+
provider,
|
|
55
|
+
model
|
|
56
|
+
};
|
|
57
|
+
}
|
|
21
58
|
//#endregion
|
|
22
|
-
export { CONTRACT_VERSION, DEFAULT_MAX_OUTPUT_TOKENS, FEATURE_ID, GIT_COMMIT_CLIENT_SERVICE, GIT_TIMEOUT_MS, MAX_MANIFEST_CHARS, MAX_MESSAGE_CHARS, MAX_UNTRACKED_PREVIEW_CHARS, PLUGIN_NAME, PROMPT_VERSION, PURPOSE_ID, RECENT_LOG_COUNT, RPC_CHANNEL, SCHEMA_VERSION };
|
|
59
|
+
export { CONTRACT_VERSION, DEFAULT_MAX_OUTPUT_TOKENS, FEATURE_ID, GIT_COMMIT_CLIENT_SERVICE, GIT_TIMEOUT_MS, MAX_MANIFEST_CHARS, MAX_MESSAGE_CHARS, MAX_UNTRACKED_PREVIEW_CHARS, PLUGIN_NAME, PROMPT_VERSION, PURPOSE_ID, RECENT_LOG_COUNT, RPC_CHANNEL, SCHEMA_VERSION, SETTINGS_KEY, defaultModelRoute, defaultSettings, normalizeFallback, normalizeModelRoute };
|
|
23
60
|
|
|
24
61
|
//# sourceMappingURL=contracts.js.map
|
package/lib/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["/** Browser-safe contracts. Shared RPC/result types stay on @klarkxy/dsh-
|
|
1
|
+
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["/** Browser-safe contracts. Shared RPC/result types stay on @klarkxy/dsh-plugin-kit. */\nexport type { RpcResult } from '@klarkxy/dsh-plugin-kit/contracts'\n\nexport const PLUGIN_NAME = '@klarkxy/dsh-git-commit'\nexport const FEATURE_ID = 'git-commit'\nexport const PURPOSE_ID = 'git-commit.plan'\nexport const RPC_CHANNEL = '/dsh-git-commit'\nexport const GIT_COMMIT_CLIENT_SERVICE = 'dshGitCommitClient'\nexport const PROMPT_VERSION = 'git-commit.v1'\nexport const SCHEMA_VERSION = 'commit-plan-json.v1'\nexport const CONTRACT_VERSION = 1\n\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 2048\n/** Total character budget for the change manifest sent to the model. */\nexport const MAX_MANIFEST_CHARS = 48_000\n/** Per-file preview cap for untracked files. */\nexport const MAX_UNTRACKED_PREVIEW_CHARS = 2_000\n/** Recent commit subjects supplied as style reference. */\nexport const RECENT_LOG_COUNT = 10\n/** Hard cap for one generated commit message line. */\nexport const MAX_MESSAGE_CHARS = 120\n/** git subprocess timeout. */\nexport const GIT_TIMEOUT_MS = 30_000\nexport const SETTINGS_KEY = 'current'\n\n/** Plugin-page model selection. Empty provider/model means \"follow the default\". */\nexport interface CommitModelRoute {\n readonly provider: string\n readonly model: string\n readonly reasoningEffort?: string\n}\n\nexport interface GitCommitSettings {\n readonly revision: number\n readonly model: CommitModelRoute\n /**\n * Opt-in single commit when the model produced no usable plan. Off by\n * default: an unplanned commit is one the user never reviewed, so a failed\n * or unusable model call rejects the run instead of writing to the index.\n */\n readonly allowFallback: boolean\n}\n\nexport const defaultModelRoute = (): CommitModelRoute => ({ provider: '', model: '' })\n\nexport const defaultSettings = (): GitCommitSettings => ({\n revision: 0, model: defaultModelRoute(), allowFallback: false,\n})\n\n/** Untrusted stored flags stay opt-in: anything but a literal true is false. */\nexport function normalizeFallback(value: unknown): boolean {\n return value === true\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : undefined\n}\n\nfunction asText(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n\n/** Untrusted stored routes collapse to the default so a bad row never breaks the run. */\nexport function normalizeModelRoute(value: unknown): CommitModelRoute {\n const row = asRecord(value)\n if (!row) return defaultModelRoute()\n const provider = asText(row.provider) ?? ''\n const model = asText(row.model) ?? ''\n const effort = asText(row.reasoningEffort)\n if (!provider || !model || provider.length > 250 || model.length > 250) return defaultModelRoute()\n return effort && effort.length <= 80 ? { provider, model, reasoningEffort: effort } : { provider, model }\n}\n\nexport interface CommitModelInfo {\n readonly provider: string\n readonly model: string\n /** 'page' is the plugin-page selection; 'default' is the host chat model. */\n readonly source: 'page' | 'default'\n}\n\nexport interface GitCommitStatus {\n readonly available: boolean\n /** Machine-readable reason when unavailable: 'no-session' | 'no-cwd' | 'not-a-repo' | 'no-git'. */\n readonly reason?: string\n readonly detail?: string\n readonly root?: string\n readonly branch?: string\n readonly workspace: { readonly files: number }\n readonly model?: CommitModelInfo\n}\n\nexport interface CommitGroupResult {\n readonly hash: string\n readonly message: string\n readonly files: readonly string[]\n}\n\nexport interface CommitRunResult {\n readonly branch: string\n readonly commits: readonly CommitGroupResult[]\n /** True when the model plan was unavailable and a single fallback commit was used. */\n readonly fallback: boolean\n readonly model?: CommitModelInfo\n}\n"],"mappings":";AAGA,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;AAC1B,MAAa,cAAc;AAC3B,MAAa,4BAA4B;AACzC,MAAa,iBAAiB;AAC9B,MAAa,iBAAiB;AAC9B,MAAa,mBAAmB;AAEhC,MAAa,4BAA4B;;AAEzC,MAAa,qBAAqB;;AAElC,MAAa,8BAA8B;;AAE3C,MAAa,mBAAmB;;AAEhC,MAAa,oBAAoB;;AAEjC,MAAa,iBAAiB;AAC9B,MAAa,eAAe;AAoB5B,MAAa,2BAA6C;CAAE,UAAU;CAAI,OAAO;AAAG;AAEpF,MAAa,yBAA4C;CACvD,UAAU;CAAG,OAAO,kBAAkB;CAAG,eAAe;AAC1D;;AAGA,SAAgB,kBAAkB,OAAyB;CACzD,OAAO,UAAU;AACnB;AAEA,SAAS,SAAS,OAAqD;CACrE,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAmC,KAAA;AAC1G;AAEA,SAAS,OAAO,OAAoC;CAClD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,KAAA;AACpE;;AAGA,SAAgB,oBAAoB,OAAkC;CACpE,MAAM,MAAM,SAAS,KAAK;CAC1B,IAAI,CAAC,KAAK,OAAO,kBAAkB;CACnC,MAAM,WAAW,OAAO,IAAI,QAAQ,KAAK;CACzC,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK;CACnC,MAAM,SAAS,OAAO,IAAI,eAAe;CACzC,IAAI,CAAC,YAAY,CAAC,SAAS,SAAS,SAAS,OAAO,MAAM,SAAS,KAAK,OAAO,kBAAkB;CACjG,OAAO,UAAU,OAAO,UAAU,KAAK;EAAE;EAAU;EAAO,iBAAiB;CAAO,IAAI;EAAE;EAAU;CAAM;AAC1G"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CommitRunResult, GitCommitSettings, GitCommitStatus, defaultSettings, normalizeFallback, normalizeModelRoute } from "./contracts.js";
|
|
2
|
+
import { LlmTextCaller } from "@klarkxy/dsh-plugin-kit";
|
|
3
|
+
import { RpcResult } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
3
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
5
|
//#region src/git.d.ts
|
|
5
6
|
interface GitRunOptions {
|
|
@@ -45,20 +46,33 @@ interface AgentStoreLike {
|
|
|
45
46
|
}
|
|
46
47
|
interface GitCommitServiceDeps {
|
|
47
48
|
readonly plugin: string;
|
|
48
|
-
readonly
|
|
49
|
+
readonly llm: LlmTextCaller;
|
|
50
|
+
readonly host?: unknown;
|
|
49
51
|
readonly sessions: SessionStoreLike;
|
|
50
52
|
readonly agents?: AgentStoreLike;
|
|
51
53
|
readonly run?: GitRunner;
|
|
52
54
|
readonly preview?: (root: string, path: string) => Promise<UntrackedPreview>;
|
|
55
|
+
/** Plugin-page settings row; absent keeps the shared purpose route. */
|
|
56
|
+
readonly settings?: {
|
|
57
|
+
load(): Promise<GitCommitSettings>;
|
|
58
|
+
save(settings: GitCommitSettings): Promise<void>;
|
|
59
|
+
};
|
|
53
60
|
}
|
|
54
61
|
declare class GitCommitService {
|
|
55
62
|
private readonly deps;
|
|
56
63
|
private readonly run;
|
|
57
64
|
private readonly preview;
|
|
58
|
-
private scope;
|
|
59
|
-
private disposePurpose;
|
|
60
65
|
private running;
|
|
66
|
+
private settings;
|
|
67
|
+
private settingsPending;
|
|
68
|
+
private readonly settingsReady;
|
|
69
|
+
private disposed;
|
|
61
70
|
constructor(deps: GitCommitServiceDeps);
|
|
71
|
+
/** Apply a freshly loaded settings row; the default stays until a row is read. */
|
|
72
|
+
adoptSettings(next: GitCommitSettings): void;
|
|
73
|
+
getSettings(): GitCommitSettings;
|
|
74
|
+
private selectedModel;
|
|
75
|
+
updateSettings(patch: Omit<GitCommitSettings, 'revision'>, expectedRevision: number): Promise<GitCommitSettings>;
|
|
62
76
|
start(): void;
|
|
63
77
|
dispose(): Promise<void>;
|
|
64
78
|
private resolveRepo;
|
|
@@ -88,7 +102,7 @@ declare function parsePlan(text: string, files: readonly ChangedFile[]): PlanGro
|
|
|
88
102
|
//#endregion
|
|
89
103
|
//#region src/index.d.ts
|
|
90
104
|
declare const name = "@klarkxy/dsh-git-commit";
|
|
91
|
-
declare const inject: readonly ["
|
|
105
|
+
declare const inject: readonly ["llm", "sessions", "agents", "storageDomain", "connection", "webServer"];
|
|
92
106
|
declare module '@deepseek-ai/cordis' {
|
|
93
107
|
interface Context {
|
|
94
108
|
gitCommit: GitCommitService;
|
|
@@ -97,5 +111,5 @@ declare module '@deepseek-ai/cordis' {
|
|
|
97
111
|
declare function handleRpc(service: GitCommitService, endpoint: string, payload: unknown, signal: AbortSignal): Promise<RpcResult>;
|
|
98
112
|
declare function apply(ctx: Context): void;
|
|
99
113
|
//#endregion
|
|
100
|
-
export { GitCommitService, apply, buildPlanInput, handleRpc, inject, name, parsePlan, parsePorcelain };
|
|
114
|
+
export { GitCommitService, apply, buildPlanInput, defaultSettings, handleRpc, inject, name, normalizeFallback, normalizeModelRoute, parsePlan, parsePorcelain };
|
|
101
115
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { DEFAULT_MAX_OUTPUT_TOKENS, MAX_MANIFEST_CHARS, MAX_UNTRACKED_PREVIEW_CHARS, PLUGIN_NAME,
|
|
2
|
-
import { registerHostRpc } from "@klarkxy/dsh-
|
|
1
|
+
import { DEFAULT_MAX_OUTPUT_TOKENS, MAX_MANIFEST_CHARS, MAX_UNTRACKED_PREVIEW_CHARS, PLUGIN_NAME, RPC_CHANNEL, SETTINGS_KEY, defaultSettings, normalizeFallback, normalizeModelRoute } from "./contracts.js";
|
|
2
|
+
import { registerHostRpc } from "@klarkxy/dsh-plugin-kit/host-rpc";
|
|
3
|
+
import { callLlmText, resolveFeatureModel } from "@klarkxy/dsh-plugin-kit";
|
|
4
|
+
import { modelMenuOverride } from "@klarkxy/dsh-plugin-kit/model-menu";
|
|
3
5
|
import { execFile } from "node:child_process";
|
|
4
6
|
import { createHash } from "node:crypto";
|
|
5
7
|
import { constants } from "node:fs";
|
|
6
8
|
import { copyFile, lstat, mkdtemp, open, readFile, readlink, realpath, rm } from "node:fs/promises";
|
|
7
9
|
import { tmpdir } from "node:os";
|
|
8
10
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
11
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
12
|
+
import { z } from "zod";
|
|
9
13
|
//#region src/git.ts
|
|
10
14
|
var GitError = class extends Error {
|
|
11
15
|
stderr;
|
|
@@ -565,12 +569,12 @@ var CommitRunError = class extends Error {
|
|
|
565
569
|
this.code = code;
|
|
566
570
|
}
|
|
567
571
|
};
|
|
568
|
-
function modelInfo(route) {
|
|
572
|
+
function modelInfo(route, source) {
|
|
569
573
|
if (!route?.provider || !route.model) return void 0;
|
|
570
574
|
return {
|
|
571
575
|
provider: route.provider,
|
|
572
576
|
model: route.model,
|
|
573
|
-
source
|
|
577
|
+
source
|
|
574
578
|
};
|
|
575
579
|
}
|
|
576
580
|
function looksChinese(subjects) {
|
|
@@ -586,30 +590,58 @@ var GitCommitService = class {
|
|
|
586
590
|
deps;
|
|
587
591
|
run;
|
|
588
592
|
preview;
|
|
589
|
-
scope;
|
|
590
|
-
disposePurpose;
|
|
591
593
|
running;
|
|
594
|
+
settings;
|
|
595
|
+
settingsPending = Promise.resolve();
|
|
596
|
+
settingsReady;
|
|
597
|
+
disposed = false;
|
|
592
598
|
constructor(deps) {
|
|
593
599
|
this.deps = deps;
|
|
594
600
|
this.run = deps.run ?? execGit;
|
|
595
601
|
this.preview = deps.preview ?? untrackedPreview;
|
|
602
|
+
this.settings = defaultSettings();
|
|
603
|
+
this.settingsReady = deps.settings?.load().then((loaded) => {
|
|
604
|
+
if (loaded) this.settings = structuredClone(loaded);
|
|
605
|
+
}, () => {
|
|
606
|
+
this.settings = defaultSettings();
|
|
607
|
+
}) ?? Promise.resolve();
|
|
596
608
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
this.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
609
|
+
/** Apply a freshly loaded settings row; the default stays until a row is read. */
|
|
610
|
+
adoptSettings(next) {
|
|
611
|
+
this.settings = structuredClone(next);
|
|
612
|
+
}
|
|
613
|
+
getSettings() {
|
|
614
|
+
return structuredClone(this.settings);
|
|
615
|
+
}
|
|
616
|
+
selectedModel() {
|
|
617
|
+
const page = modelMenuOverride(this.settings.model);
|
|
618
|
+
return {
|
|
619
|
+
route: resolveFeatureModel(this.deps.host, page),
|
|
620
|
+
source: page ? "page" : "default"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
async updateSettings(patch, expectedRevision) {
|
|
624
|
+
const detached = structuredClone(patch);
|
|
625
|
+
const result = this.settingsPending.then(async () => {
|
|
626
|
+
await this.settingsReady;
|
|
627
|
+
if (this.disposed) throw new CommitRunError("disposed", "dsh-git-commit: plugin disabled");
|
|
628
|
+
if (expectedRevision !== this.settings.revision) throw new CommitRunError("stale", "dsh-git-commit: settings changed; reload and retry");
|
|
629
|
+
const next = {
|
|
630
|
+
revision: expectedRevision + 1,
|
|
631
|
+
model: detached.model,
|
|
632
|
+
allowFallback: normalizeFallback(detached.allowFallback)
|
|
633
|
+
};
|
|
634
|
+
await this.deps.settings?.save(next);
|
|
635
|
+
this.settings = next;
|
|
636
|
+
return this.getSettings();
|
|
607
637
|
});
|
|
638
|
+
this.settingsPending = result.then(() => {}, () => {});
|
|
639
|
+
return result;
|
|
608
640
|
}
|
|
641
|
+
start() {}
|
|
609
642
|
async dispose() {
|
|
610
|
-
this.
|
|
611
|
-
this.
|
|
612
|
-
this.scope = void 0;
|
|
643
|
+
this.disposed = true;
|
|
644
|
+
await this.settingsPending;
|
|
613
645
|
await this.running?.catch(() => {});
|
|
614
646
|
}
|
|
615
647
|
async resolveRepo(sessionId) {
|
|
@@ -638,8 +670,8 @@ var GitCommitService = class {
|
|
|
638
670
|
throw error;
|
|
639
671
|
}
|
|
640
672
|
const changes = await workingTreeChanges(this.run, resolved.root);
|
|
641
|
-
|
|
642
|
-
|
|
673
|
+
const selected = this.selectedModel();
|
|
674
|
+
const model = modelInfo(selected.route, selected.source);
|
|
643
675
|
return {
|
|
644
676
|
available: true,
|
|
645
677
|
root: resolved.root,
|
|
@@ -664,7 +696,7 @@ var GitCommitService = class {
|
|
|
664
696
|
const resolved = await this.resolveRepo(sessionId);
|
|
665
697
|
const assertReady = () => {
|
|
666
698
|
signal?.throwIfAborted();
|
|
667
|
-
if (!this.
|
|
699
|
+
if (this.disposed || !this.deps.llm) throw new CommitRunError("cancelled", "dsh-git-commit: plugin is no longer active");
|
|
668
700
|
const currentSession = this.deps.sessions.get(sessionId);
|
|
669
701
|
if (currentSession !== session || normalizeCwd(currentSession?.header?.cwd) !== sessionCwd) throw new CommitRunError("session-changed", "dsh-git-commit: session workspace changed during planning");
|
|
670
702
|
if (this.deps.agents?.get(sessionId)?.status === "running") throw new CommitRunError("session-running", "dsh-git-commit: wait for the session turn to finish");
|
|
@@ -687,34 +719,33 @@ var GitCommitService = class {
|
|
|
687
719
|
const untracked = [];
|
|
688
720
|
for (const file of selected) if (file.status === "untracked") untracked.push(await this.preview(resolved.root, file.path));
|
|
689
721
|
let groups;
|
|
722
|
+
let planFailure = "no model route was available";
|
|
690
723
|
let model;
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
693
|
-
const result = await
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
724
|
+
const planned = this.selectedModel();
|
|
725
|
+
if (planned.route) try {
|
|
726
|
+
const result = await callLlmText(this.deps.llm, {
|
|
727
|
+
plugin: this.deps.plugin,
|
|
728
|
+
route: planned.route,
|
|
729
|
+
system: planSystemPrompt(),
|
|
730
|
+
text: buildPlanInput({
|
|
697
731
|
files: selected,
|
|
698
732
|
recentSubjects: subjects,
|
|
699
733
|
stat,
|
|
700
734
|
diff,
|
|
701
735
|
untracked
|
|
702
736
|
}),
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
promptVersion: PROMPT_VERSION,
|
|
707
|
-
schemaVersion: SCHEMA_VERSION,
|
|
708
|
-
signal,
|
|
709
|
-
priority: "interactive"
|
|
737
|
+
maxTokens: DEFAULT_MAX_OUTPUT_TOKENS,
|
|
738
|
+
signal: signal ?? new AbortController().signal,
|
|
739
|
+
sessionId
|
|
710
740
|
});
|
|
711
|
-
model = modelInfo(result.
|
|
712
|
-
if (result.receipt.status === "cancelled") throw new CommitRunError("cancelled", "dsh-git-commit: planning was cancelled");
|
|
741
|
+
model = modelInfo(result, planned.source);
|
|
713
742
|
groups = parsePlan(result.text, selected);
|
|
743
|
+
if (!groups) planFailure = "the model returned a plan that does not cover the change set";
|
|
714
744
|
} catch (error) {
|
|
715
745
|
if (error instanceof CommitRunError) throw error;
|
|
716
|
-
|
|
746
|
+
planFailure = error instanceof Error ? error.message : String(error);
|
|
717
747
|
}
|
|
748
|
+
if (!groups && !this.settings.allowFallback) throw new CommitRunError("plan-unavailable", `dsh-git-commit: no commit plan (${planFailure})`);
|
|
718
749
|
assertReady();
|
|
719
750
|
if ((await workspaceSnapshot(this.run, resolved.root, signal)).fingerprint !== initial.fingerprint) throw new CommitRunError("workspace-changed", "dsh-git-commit: workspace changed during planning; retry the commit");
|
|
720
751
|
const effective = groups ?? [fallbackGroup(selected, looksChinese(subjects) ? "zh" : "en")];
|
|
@@ -752,12 +783,47 @@ var GitCommitService = class {
|
|
|
752
783
|
}
|
|
753
784
|
};
|
|
754
785
|
//#endregion
|
|
786
|
+
//#region src/storage.ts
|
|
787
|
+
const modelRouteSchema = z.object({
|
|
788
|
+
provider: z.string().max(250),
|
|
789
|
+
model: z.string().max(250),
|
|
790
|
+
reasoningEffort: z.string().max(80).optional()
|
|
791
|
+
}).strict();
|
|
792
|
+
/** Version 1 stored no settings row, so a missing record fills defaults. */
|
|
793
|
+
const settingsSchema = z.object({
|
|
794
|
+
revision: z.number().int().nonnegative(),
|
|
795
|
+
model: modelRouteSchema,
|
|
796
|
+
allowFallback: z.boolean().default(false)
|
|
797
|
+
}).strict();
|
|
798
|
+
const updateSettingsSchema = z.object({
|
|
799
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
800
|
+
settings: z.object({
|
|
801
|
+
model: z.unknown(),
|
|
802
|
+
allowFallback: z.unknown().optional()
|
|
803
|
+
}).strict()
|
|
804
|
+
}).strict();
|
|
805
|
+
const gitCommitDomain = defineDomain({
|
|
806
|
+
name: "dsh_editor_git_commit",
|
|
807
|
+
version: 1,
|
|
808
|
+
tables: { settings: domainTable(settingsSchema) }
|
|
809
|
+
});
|
|
810
|
+
function parseSettings(value) {
|
|
811
|
+
const parsed = settingsSchema.safeParse(value);
|
|
812
|
+
if (!parsed.success) return defaultSettings();
|
|
813
|
+
return {
|
|
814
|
+
revision: parsed.data.revision,
|
|
815
|
+
model: normalizeModelRoute(parsed.data.model),
|
|
816
|
+
allowFallback: parsed.data.allowFallback
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
//#endregion
|
|
755
820
|
//#region src/index.ts
|
|
756
821
|
const name = PLUGIN_NAME;
|
|
757
822
|
const inject = [
|
|
758
|
-
"
|
|
823
|
+
"llm",
|
|
759
824
|
"sessions",
|
|
760
825
|
"agents",
|
|
826
|
+
"storageDomain",
|
|
761
827
|
"connection",
|
|
762
828
|
"webServer"
|
|
763
829
|
];
|
|
@@ -799,6 +865,27 @@ async function handleRpc(service, endpoint, payload, signal) {
|
|
|
799
865
|
value: await service.status(sessionId)
|
|
800
866
|
};
|
|
801
867
|
}
|
|
868
|
+
case "settings": return {
|
|
869
|
+
ok: true,
|
|
870
|
+
value: service.getSettings()
|
|
871
|
+
};
|
|
872
|
+
case "settings.update": {
|
|
873
|
+
const parsed = updateSettingsSchema.safeParse(payload);
|
|
874
|
+
if (!parsed.success) return {
|
|
875
|
+
ok: false,
|
|
876
|
+
error: {
|
|
877
|
+
code: "bad-request",
|
|
878
|
+
message: "settings patch is invalid"
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
return {
|
|
882
|
+
ok: true,
|
|
883
|
+
value: await service.updateSettings({
|
|
884
|
+
model: normalizeModelRoute(parsed.data.settings.model),
|
|
885
|
+
allowFallback: normalizeFallback(parsed.data.settings.allowFallback)
|
|
886
|
+
}, parsed.data.expectedRevision)
|
|
887
|
+
};
|
|
888
|
+
}
|
|
802
889
|
case "commit": {
|
|
803
890
|
const sessionId = stringField(payload, "sessionId");
|
|
804
891
|
if (!sessionId) return {
|
|
@@ -827,20 +914,30 @@ async function handleRpc(service, endpoint, payload, signal) {
|
|
|
827
914
|
}
|
|
828
915
|
function apply(ctx) {
|
|
829
916
|
const host = ctx;
|
|
917
|
+
const domainPromise = host.storageDomain.open(gitCommitDomain);
|
|
918
|
+
const tablePromise = domainPromise.then((domain) => domain.table("settings"));
|
|
830
919
|
const service = new GitCommitService({
|
|
831
920
|
plugin: PLUGIN_NAME,
|
|
832
|
-
|
|
921
|
+
llm: host.llm,
|
|
922
|
+
host,
|
|
833
923
|
sessions: host.sessions,
|
|
834
|
-
agents: host.agents
|
|
924
|
+
agents: host.agents,
|
|
925
|
+
settings: {
|
|
926
|
+
load: async () => parseSettings((await tablePromise).get(SETTINGS_KEY)),
|
|
927
|
+
save: async (settings) => {
|
|
928
|
+
await (await tablePromise).put(SETTINGS_KEY, settings);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
835
931
|
});
|
|
836
932
|
service.start();
|
|
837
933
|
ctx.provide("gitCommit", service);
|
|
838
934
|
ctx.effect(() => async () => {
|
|
839
935
|
await service.dispose();
|
|
936
|
+
await (await domainPromise).close();
|
|
840
937
|
}, "git-commit.lifecycle");
|
|
841
938
|
ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, (endpoint, payload, signal) => handleRpc(service, endpoint, payload, signal)), "git-commit.rpc");
|
|
842
939
|
}
|
|
843
940
|
//#endregion
|
|
844
|
-
export { GitCommitService, apply, buildPlanInput, handleRpc, inject, name, parsePlan, parsePorcelain };
|
|
941
|
+
export { GitCommitService, apply, buildPlanInput, defaultSettings, handleRpc, inject, name, normalizeFallback, normalizeModelRoute, parsePlan, parsePorcelain };
|
|
845
942
|
|
|
846
943
|
//# sourceMappingURL=index.js.map
|