@klarkxy/dsh-git-commit 0.1.5 → 0.2.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 +21 -4
- package/docs/README.zh-CN.md +18 -2
- package/lib/client.inner.cjs +11 -16
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +12 -178
- package/lib/contracts.d.ts +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +7 -2
- package/lib/index.js +60 -15
- package/lib/index.js.map +1 -1
- package/package.json +11 -9
package/lib/client.js
CHANGED
|
@@ -1,168 +1,7 @@
|
|
|
1
|
-
function __createDshNodeShims() {
|
|
2
|
-
class EventEmitter {
|
|
3
|
-
constructor() {
|
|
4
|
-
this._e = Object.create(null)
|
|
5
|
-
}
|
|
6
|
-
on(type, fn) {
|
|
7
|
-
(this._e[type] ||= []).push(fn)
|
|
8
|
-
return this
|
|
9
|
-
}
|
|
10
|
-
addListener(type, fn) {
|
|
11
|
-
return this.on(type, fn)
|
|
12
|
-
}
|
|
13
|
-
once(type, fn) {
|
|
14
|
-
const wrap = (...args) => {
|
|
15
|
-
this.off(type, wrap)
|
|
16
|
-
fn(...args)
|
|
17
|
-
}
|
|
18
|
-
return this.on(type, wrap)
|
|
19
|
-
}
|
|
20
|
-
off(type, fn) {
|
|
21
|
-
const list = this._e[type]
|
|
22
|
-
if (list) this._e[type] = list.filter((item) => item !== fn)
|
|
23
|
-
return this
|
|
24
|
-
}
|
|
25
|
-
removeListener(type, fn) {
|
|
26
|
-
return this.off(type, fn)
|
|
27
|
-
}
|
|
28
|
-
emit(type, ...args) {
|
|
29
|
-
const list = this._e[type]
|
|
30
|
-
if (!list) return false
|
|
31
|
-
for (const fn of list.slice()) fn(...args)
|
|
32
|
-
return true
|
|
33
|
-
}
|
|
34
|
-
removeAllListeners(type) {
|
|
35
|
-
if (type) delete this._e[type]
|
|
36
|
-
else this._e = Object.create(null)
|
|
37
|
-
return this
|
|
38
|
-
}
|
|
39
|
-
listeners(type) {
|
|
40
|
-
return (this._e[type] || []).slice()
|
|
41
|
-
}
|
|
42
|
-
setMaxListeners() {
|
|
43
|
-
return this
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
class Buffer extends Uint8Array {
|
|
48
|
-
static from(value, encodingOrOffset, length) {
|
|
49
|
-
if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
|
|
50
|
-
if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
|
|
51
|
-
if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
|
|
52
|
-
if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
|
|
53
|
-
return new Buffer(0)
|
|
54
|
-
}
|
|
55
|
-
static alloc(size, fill) {
|
|
56
|
-
const buf = new Buffer(size)
|
|
57
|
-
if (fill !== undefined && fill !== 0) {
|
|
58
|
-
if (typeof fill === 'string') {
|
|
59
|
-
const bytes = new TextEncoder().encode(fill)
|
|
60
|
-
for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
|
|
61
|
-
} else buf.fill(fill)
|
|
62
|
-
}
|
|
63
|
-
return buf
|
|
64
|
-
}
|
|
65
|
-
static allocUnsafe(size) {
|
|
66
|
-
return new Buffer(size)
|
|
67
|
-
}
|
|
68
|
-
static allocUnsafeSlow(size) {
|
|
69
|
-
return new Buffer(size)
|
|
70
|
-
}
|
|
71
|
-
static concat(list, totalLength) {
|
|
72
|
-
const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
|
|
73
|
-
const out = new Buffer(length)
|
|
74
|
-
let offset = 0
|
|
75
|
-
for (const item of list) {
|
|
76
|
-
out.set(item, offset)
|
|
77
|
-
offset += item.length
|
|
78
|
-
}
|
|
79
|
-
return out
|
|
80
|
-
}
|
|
81
|
-
static isBuffer(value) {
|
|
82
|
-
return value instanceof Buffer
|
|
83
|
-
}
|
|
84
|
-
static byteLength(value) {
|
|
85
|
-
if (typeof value === 'string') return new TextEncoder().encode(value).length
|
|
86
|
-
return value?.byteLength ?? value?.length ?? 0
|
|
87
|
-
}
|
|
88
|
-
static isEncoding(encoding) {
|
|
89
|
-
return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
|
|
90
|
-
}
|
|
91
|
-
toString(encoding) {
|
|
92
|
-
if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
93
|
-
if (encoding === 'base64') {
|
|
94
|
-
let binary = ''
|
|
95
|
-
this.forEach((byte) => {
|
|
96
|
-
binary += String.fromCharCode(byte)
|
|
97
|
-
})
|
|
98
|
-
return btoa(binary)
|
|
99
|
-
}
|
|
100
|
-
return new TextDecoder().decode(this)
|
|
101
|
-
}
|
|
102
|
-
slice(start, end) {
|
|
103
|
-
return Buffer.from(super.subarray(start, end))
|
|
104
|
-
}
|
|
105
|
-
copy(target, targetStart = 0, start = 0, end = this.length) {
|
|
106
|
-
const sliced = this.subarray(start, end)
|
|
107
|
-
target.set(sliced, targetStart)
|
|
108
|
-
return sliced.length
|
|
109
|
-
}
|
|
110
|
-
write(string, offset = 0) {
|
|
111
|
-
const bytes = new TextEncoder().encode(string)
|
|
112
|
-
const written = Math.min(bytes.length, this.length - offset)
|
|
113
|
-
this.set(bytes.subarray(0, written), offset)
|
|
114
|
-
return written
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function inherits(ctor, superCtor) {
|
|
119
|
-
ctor.super_ = superCtor
|
|
120
|
-
ctor.prototype = Object.create(superCtor.prototype, {
|
|
121
|
-
constructor: { value: ctor, writable: true, configurable: true },
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!globalThis.Buffer) globalThis.Buffer = Buffer
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
|
|
129
|
-
stream: {
|
|
130
|
-
Stream: EventEmitter,
|
|
131
|
-
Readable: EventEmitter,
|
|
132
|
-
Writable: EventEmitter,
|
|
133
|
-
Duplex: EventEmitter,
|
|
134
|
-
Transform: EventEmitter,
|
|
135
|
-
PassThrough: EventEmitter,
|
|
136
|
-
},
|
|
137
|
-
events: { EventEmitter },
|
|
138
|
-
util: {
|
|
139
|
-
deprecate(fn) {
|
|
140
|
-
return fn
|
|
141
|
-
},
|
|
142
|
-
inherits,
|
|
143
|
-
inspect() {
|
|
144
|
-
return ''
|
|
145
|
-
},
|
|
146
|
-
format(value) {
|
|
147
|
-
return String(value)
|
|
148
|
-
},
|
|
149
|
-
types: { isBuffer: Buffer.isBuffer },
|
|
150
|
-
},
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
1
|
window.__ModuleLoader__.load({
|
|
155
2
|
id: "@klarkxy/dsh-git-commit",
|
|
156
3
|
factory: (dshRequire) => {
|
|
157
|
-
var
|
|
158
|
-
var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
|
|
159
|
-
var require = function (id) {
|
|
160
|
-
if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
|
|
161
|
-
if (!__nodeShims) __nodeShims = __createDshNodeShims();
|
|
162
|
-
return __nodeShims[id];
|
|
163
|
-
}
|
|
164
|
-
return dshRequire(id);
|
|
165
|
-
};
|
|
4
|
+
var require = dshRequire;
|
|
166
5
|
var module = { exports: {} };
|
|
167
6
|
var exports = module.exports;
|
|
168
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
@@ -170,21 +9,14 @@ let react = require("react");
|
|
|
170
9
|
let react_dom = require("react-dom");
|
|
171
10
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
172
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
173
|
-
//#region ../dsh-
|
|
174
|
-
/**
|
|
175
|
-
* Shared plugin-page model menu.
|
|
176
|
-
*
|
|
177
|
-
* A feature plugin's own settings row exposes a model select. An empty
|
|
178
|
-
* selection means the host default chat model for that feature's own call.
|
|
179
|
-
*
|
|
180
|
-
* Browser-safe and React-optional: pure helpers live here so hosts and clients
|
|
181
|
-
* share one catalog contract without bundling React into the host build.
|
|
182
|
-
*/
|
|
12
|
+
//#region ../dsh-model-route/src/route.ts
|
|
183
13
|
const KEY_SEPARATOR = "";
|
|
184
|
-
|
|
14
|
+
/** Stable key for one route, safe to use as a select-option value. */
|
|
15
|
+
function modelRouteKey(provider, model) {
|
|
185
16
|
return provider && model ? `${provider}${KEY_SEPARATOR}${model}` : "";
|
|
186
17
|
}
|
|
187
|
-
|
|
18
|
+
/** Inverse of modelRouteKey; undefined for malformed keys. */
|
|
19
|
+
function parseModelRouteKey(value) {
|
|
188
20
|
const index = value.indexOf(KEY_SEPARATOR);
|
|
189
21
|
if (index <= 0) return void 0;
|
|
190
22
|
const provider = value.slice(0, index);
|
|
@@ -195,6 +27,8 @@ function parseModelMenuChoiceKey(value) {
|
|
|
195
27
|
model
|
|
196
28
|
};
|
|
197
29
|
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region ../dsh-model-route/src/catalog.ts
|
|
198
32
|
function asRecord(value) {
|
|
199
33
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
200
34
|
}
|
|
@@ -1216,7 +1050,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
1216
1050
|
children: text.model
|
|
1217
1051
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1218
1052
|
className: "dsh-ui-select",
|
|
1219
|
-
value:
|
|
1053
|
+
value: modelRouteKey(current.provider, current.model),
|
|
1220
1054
|
disabled: busy,
|
|
1221
1055
|
onChange: (event) => {
|
|
1222
1056
|
const key = event.target.value;
|
|
@@ -1227,16 +1061,16 @@ function GitCommitSettingsPanel(props) {
|
|
|
1227
1061
|
} });
|
|
1228
1062
|
return;
|
|
1229
1063
|
}
|
|
1230
|
-
const parsed =
|
|
1064
|
+
const parsed = parseModelRouteKey(key);
|
|
1231
1065
|
if (parsed) save({ model: parsed });
|
|
1232
1066
|
},
|
|
1233
1067
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1234
1068
|
value: "",
|
|
1235
1069
|
children: text.modelDefault
|
|
1236
1070
|
}), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1237
|
-
value:
|
|
1071
|
+
value: modelRouteKey(choice.provider, choice.model),
|
|
1238
1072
|
children: choice.label
|
|
1239
|
-
},
|
|
1073
|
+
}, modelRouteKey(choice.provider, choice.model)))]
|
|
1240
1074
|
})]
|
|
1241
1075
|
}),
|
|
1242
1076
|
selected && efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
package/lib/contracts.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ declare function normalizeModelRoute(value: unknown): CommitModelRoute;
|
|
|
45
45
|
interface CommitModelInfo {
|
|
46
46
|
readonly provider: string;
|
|
47
47
|
readonly model: string;
|
|
48
|
-
/** 'page' is the plugin-page selection; 'default'
|
|
48
|
+
/** 'page' is the plugin-page selection; 'default' follows the session or host chat model. */
|
|
49
49
|
readonly source: 'page' | 'default';
|
|
50
50
|
}
|
|
51
51
|
interface GitCommitStatus {
|
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-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'
|
|
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' follows the session or 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
|
@@ -66,11 +66,16 @@ declare class GitCommitService {
|
|
|
66
66
|
private settings;
|
|
67
67
|
private settingsPending;
|
|
68
68
|
private readonly settingsReady;
|
|
69
|
+
private settingsFailure;
|
|
70
|
+
private settingsAdopted;
|
|
69
71
|
private disposed;
|
|
70
72
|
constructor(deps: GitCommitServiceDeps);
|
|
71
|
-
/**
|
|
73
|
+
/** A refreshed row wins over a pending initial read. */
|
|
72
74
|
adoptSettings(next: GitCommitSettings): void;
|
|
75
|
+
/** Existing synchronous snapshot API; host operations wait for initialization first. */
|
|
73
76
|
getSettings(): GitCommitSettings;
|
|
77
|
+
ready(): Promise<void>;
|
|
78
|
+
private waitForSettings;
|
|
74
79
|
private selectedModel;
|
|
75
80
|
updateSettings(patch: Omit<GitCommitSettings, 'revision'>, expectedRevision: number): Promise<GitCommitSettings>;
|
|
76
81
|
start(): void;
|
|
@@ -102,7 +107,7 @@ declare function parsePlan(text: string, files: readonly ChangedFile[]): PlanGro
|
|
|
102
107
|
//#endregion
|
|
103
108
|
//#region src/index.d.ts
|
|
104
109
|
declare const name = "@klarkxy/dsh-git-commit";
|
|
105
|
-
declare const inject: readonly ["llm", "sessions", "agents", "storageDomain", "connection", "webServer"];
|
|
110
|
+
declare const inject: readonly ["llm", "sessions", "agents", "storageDomain", "connection", "webServer", "sessionProjections", "agentDefaultModel"];
|
|
106
111
|
declare module '@deepseek-ai/cordis' {
|
|
107
112
|
interface Context {
|
|
108
113
|
gitCommit: GitCommitService;
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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
2
|
import { registerHostRpc } from "@klarkxy/dsh-plugin-kit/host-rpc";
|
|
3
3
|
import { callLlmText, resolveFeatureModel } from "@klarkxy/dsh-plugin-kit";
|
|
4
|
-
import {
|
|
4
|
+
import { modelRouteOverride } from "@klarkxy/dsh-model-route";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { constants } from "node:fs";
|
|
@@ -594,6 +594,8 @@ var GitCommitService = class {
|
|
|
594
594
|
settings;
|
|
595
595
|
settingsPending = Promise.resolve();
|
|
596
596
|
settingsReady;
|
|
597
|
+
settingsFailure;
|
|
598
|
+
settingsAdopted = false;
|
|
597
599
|
disposed = false;
|
|
598
600
|
constructor(deps) {
|
|
599
601
|
this.deps = deps;
|
|
@@ -601,29 +603,64 @@ var GitCommitService = class {
|
|
|
601
603
|
this.preview = deps.preview ?? untrackedPreview;
|
|
602
604
|
this.settings = defaultSettings();
|
|
603
605
|
this.settingsReady = deps.settings?.load().then((loaded) => {
|
|
604
|
-
if (loaded) this.settings = structuredClone(loaded);
|
|
606
|
+
if (loaded && !this.settingsAdopted) this.settings = structuredClone(loaded);
|
|
605
607
|
}, () => {
|
|
606
|
-
this.
|
|
608
|
+
if (!this.settingsAdopted) this.settingsFailure = new CommitRunError("settings-unavailable", "dsh-git-commit: saved settings could not be read; reload the plugin before retrying");
|
|
607
609
|
}) ?? Promise.resolve();
|
|
608
610
|
}
|
|
609
|
-
/**
|
|
611
|
+
/** A refreshed row wins over a pending initial read. */
|
|
610
612
|
adoptSettings(next) {
|
|
613
|
+
if (this.disposed) throw new CommitRunError("disposed", "dsh-git-commit: plugin disabled");
|
|
614
|
+
this.settingsAdopted = true;
|
|
615
|
+
this.settingsFailure = void 0;
|
|
611
616
|
this.settings = structuredClone(next);
|
|
612
617
|
}
|
|
618
|
+
/** Existing synchronous snapshot API; host operations wait for initialization first. */
|
|
613
619
|
getSettings() {
|
|
620
|
+
if (this.settingsFailure) throw this.settingsFailure;
|
|
614
621
|
return structuredClone(this.settings);
|
|
615
622
|
}
|
|
616
|
-
|
|
617
|
-
|
|
623
|
+
async ready() {
|
|
624
|
+
await this.settingsReady;
|
|
625
|
+
if (this.settingsFailure) throw this.settingsFailure;
|
|
626
|
+
}
|
|
627
|
+
async waitForSettings(signal) {
|
|
628
|
+
signal?.throwIfAborted();
|
|
629
|
+
if (!signal) {
|
|
630
|
+
await this.ready();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
let onAbort = () => {};
|
|
634
|
+
const aborted = new Promise((_, reject) => {
|
|
635
|
+
onAbort = () => {
|
|
636
|
+
try {
|
|
637
|
+
signal.throwIfAborted();
|
|
638
|
+
} catch (error) {
|
|
639
|
+
reject(error);
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
643
|
+
if (signal.aborted) onAbort();
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
await Promise.race([this.settingsReady, aborted]);
|
|
647
|
+
} finally {
|
|
648
|
+
signal.removeEventListener("abort", onAbort);
|
|
649
|
+
}
|
|
650
|
+
signal.throwIfAborted();
|
|
651
|
+
if (this.settingsFailure) throw this.settingsFailure;
|
|
652
|
+
}
|
|
653
|
+
selectedModel(sessionId) {
|
|
654
|
+
const page = modelRouteOverride(this.settings.model);
|
|
618
655
|
return {
|
|
619
|
-
route: resolveFeatureModel(this.deps.host, page),
|
|
656
|
+
route: resolveFeatureModel(this.deps.host, page, sessionId),
|
|
620
657
|
source: page ? "page" : "default"
|
|
621
658
|
};
|
|
622
659
|
}
|
|
623
660
|
async updateSettings(patch, expectedRevision) {
|
|
624
661
|
const detached = structuredClone(patch);
|
|
625
662
|
const result = this.settingsPending.then(async () => {
|
|
626
|
-
await this.
|
|
663
|
+
await this.ready();
|
|
627
664
|
if (this.disposed) throw new CommitRunError("disposed", "dsh-git-commit: plugin disabled");
|
|
628
665
|
if (expectedRevision !== this.settings.revision) throw new CommitRunError("stale", "dsh-git-commit: settings changed; reload and retry");
|
|
629
666
|
const next = {
|
|
@@ -641,6 +678,7 @@ var GitCommitService = class {
|
|
|
641
678
|
start() {}
|
|
642
679
|
async dispose() {
|
|
643
680
|
this.disposed = true;
|
|
681
|
+
await this.settingsReady;
|
|
644
682
|
await this.settingsPending;
|
|
645
683
|
await this.running?.catch(() => {});
|
|
646
684
|
}
|
|
@@ -657,6 +695,7 @@ var GitCommitService = class {
|
|
|
657
695
|
}
|
|
658
696
|
}
|
|
659
697
|
async status(sessionId) {
|
|
698
|
+
await this.ready();
|
|
660
699
|
let resolved;
|
|
661
700
|
try {
|
|
662
701
|
resolved = await this.resolveRepo(sessionId);
|
|
@@ -670,7 +709,7 @@ var GitCommitService = class {
|
|
|
670
709
|
throw error;
|
|
671
710
|
}
|
|
672
711
|
const changes = await workingTreeChanges(this.run, resolved.root);
|
|
673
|
-
const selected = this.selectedModel();
|
|
712
|
+
const selected = this.selectedModel(sessionId);
|
|
674
713
|
const model = modelInfo(selected.route, selected.source);
|
|
675
714
|
return {
|
|
676
715
|
available: true,
|
|
@@ -691,6 +730,8 @@ var GitCommitService = class {
|
|
|
691
730
|
}
|
|
692
731
|
}
|
|
693
732
|
async runCommit(sessionId, signal) {
|
|
733
|
+
await this.waitForSettings(signal);
|
|
734
|
+
if (this.disposed) throw new CommitRunError("cancelled", "dsh-git-commit: plugin is no longer active");
|
|
694
735
|
const session = this.deps.sessions.get(sessionId);
|
|
695
736
|
const sessionCwd = normalizeCwd(session?.header?.cwd);
|
|
696
737
|
const resolved = await this.resolveRepo(sessionId);
|
|
@@ -721,7 +762,7 @@ var GitCommitService = class {
|
|
|
721
762
|
let groups;
|
|
722
763
|
let planFailure = "no model route was available";
|
|
723
764
|
let model;
|
|
724
|
-
const planned = this.selectedModel();
|
|
765
|
+
const planned = this.selectedModel(sessionId);
|
|
725
766
|
if (planned.route) try {
|
|
726
767
|
const result = await callLlmText(this.deps.llm, {
|
|
727
768
|
plugin: this.deps.plugin,
|
|
@@ -825,7 +866,9 @@ const inject = [
|
|
|
825
866
|
"agents",
|
|
826
867
|
"storageDomain",
|
|
827
868
|
"connection",
|
|
828
|
-
"webServer"
|
|
869
|
+
"webServer",
|
|
870
|
+
"sessionProjections",
|
|
871
|
+
"agentDefaultModel"
|
|
829
872
|
];
|
|
830
873
|
function stringField(payload, key) {
|
|
831
874
|
if (!payload || typeof payload !== "object") return void 0;
|
|
@@ -865,10 +908,12 @@ async function handleRpc(service, endpoint, payload, signal) {
|
|
|
865
908
|
value: await service.status(sessionId)
|
|
866
909
|
};
|
|
867
910
|
}
|
|
868
|
-
case "settings":
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
911
|
+
case "settings":
|
|
912
|
+
await service.ready();
|
|
913
|
+
return {
|
|
914
|
+
ok: true,
|
|
915
|
+
value: service.getSettings()
|
|
916
|
+
};
|
|
872
917
|
case "settings.update": {
|
|
873
918
|
const parsed = updateSettingsSchema.safeParse(payload);
|
|
874
919
|
if (!parsed.success) return {
|