@klarkxy/dsh-git-commit 0.2.0 → 0.2.2
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 +14 -11
- package/docs/README.zh-CN.md +11 -10
- package/lib/client.inner.cjs +384 -63
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +384 -63
- package/lib/contracts.d.ts +44 -11
- package/lib/contracts.js +1 -27
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +7 -3
- package/lib/index.js +237 -70
- package/lib/index.js.map +1 -1
- package/package.json +10 -5
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
-
import {
|
|
3
|
+
import { notifyModelSettingsChanged, registerModelSettings } from "@klarkxy/dsh-plugin-kit/model-settings";
|
|
4
4
|
import { modelRouteOverride } from "@klarkxy/dsh-model-route";
|
|
5
|
+
import { callLlmText, resolveFeatureModel } from "@klarkxy/dsh-plugin-kit";
|
|
5
6
|
import { execFile } from "node:child_process";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
8
|
import { constants } from "node:fs";
|
|
@@ -343,6 +344,19 @@ async function untrackedPreview(root, path) {
|
|
|
343
344
|
};
|
|
344
345
|
}
|
|
345
346
|
}
|
|
347
|
+
/** Known commits or an uncertain current group must survive a rejected run. */
|
|
348
|
+
var PartialCommitError = class extends Error {
|
|
349
|
+
commits;
|
|
350
|
+
cause;
|
|
351
|
+
uncertain;
|
|
352
|
+
constructor(commits, cause, uncertain) {
|
|
353
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
354
|
+
this.name = "PartialCommitError";
|
|
355
|
+
this.commits = commits;
|
|
356
|
+
this.cause = cause;
|
|
357
|
+
this.uncertain = uncertain;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
346
360
|
async function indexBytes(path) {
|
|
347
361
|
try {
|
|
348
362
|
return await readFile(path);
|
|
@@ -369,26 +383,38 @@ async function headOid(run, root) {
|
|
|
369
383
|
*/
|
|
370
384
|
async function commitGroups(run, root, groups, selected, signal, verify) {
|
|
371
385
|
const commits = [];
|
|
386
|
+
let partialFailure;
|
|
387
|
+
const fail = (error) => {
|
|
388
|
+
if (error instanceof PartialCommitError) {
|
|
389
|
+
partialFailure = error;
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
if (commits.length === 0) throw error;
|
|
393
|
+
partialFailure = new PartialCommitError(commits, error);
|
|
394
|
+
throw partialFailure;
|
|
395
|
+
};
|
|
372
396
|
for (const group of groups) {
|
|
373
397
|
if (group.files.length === 0) continue;
|
|
374
398
|
const paths = group.files.flatMap((path) => {
|
|
375
399
|
const file = selected.find((file) => file.path === path);
|
|
376
400
|
return file?.status === "renamed" && file.origin ? [path, file.origin] : [path];
|
|
377
401
|
});
|
|
378
|
-
|
|
379
|
-
const gitPath = (await run([
|
|
380
|
-
"rev-parse",
|
|
381
|
-
"--git-path",
|
|
382
|
-
"index"
|
|
383
|
-
], {
|
|
384
|
-
cwd: root,
|
|
385
|
-
signal
|
|
386
|
-
})).trim();
|
|
387
|
-
const indexPath = isAbsolute(gitPath) ? gitPath : resolve(root, gitPath);
|
|
388
|
-
const backupDir = await mkdtemp(join(tmpdir(), "dsh-git-commit-"));
|
|
389
|
-
const backupPath = join(backupDir, "index");
|
|
390
|
-
let hadIndex = true;
|
|
402
|
+
let backupDir;
|
|
391
403
|
try {
|
|
404
|
+
signal?.throwIfAborted();
|
|
405
|
+
await verify?.(paths);
|
|
406
|
+
const gitPath = (await run([
|
|
407
|
+
"rev-parse",
|
|
408
|
+
"--git-path",
|
|
409
|
+
"index"
|
|
410
|
+
], {
|
|
411
|
+
cwd: root,
|
|
412
|
+
signal
|
|
413
|
+
})).trim();
|
|
414
|
+
const indexPath = isAbsolute(gitPath) ? gitPath : resolve(root, gitPath);
|
|
415
|
+
backupDir = await mkdtemp(join(tmpdir(), "dsh-git-commit-"));
|
|
416
|
+
const backupPath = join(backupDir, "index");
|
|
417
|
+
let hadIndex = true;
|
|
392
418
|
try {
|
|
393
419
|
await copyFile(indexPath, backupPath);
|
|
394
420
|
} catch (error) {
|
|
@@ -399,7 +425,10 @@ async function commitGroups(run, root, groups, selected, signal, verify) {
|
|
|
399
425
|
const originalIndex = await indexBytes(indexPath);
|
|
400
426
|
if (hadIndex ? !originalIndex?.equals(await readFile(backupPath)) : originalIndex !== void 0) throw new GitError("dsh-git-commit: index changed before committing; retry");
|
|
401
427
|
let stagedIndex = originalIndex;
|
|
428
|
+
let committed = false;
|
|
429
|
+
let commitInvoked = false;
|
|
402
430
|
try {
|
|
431
|
+
signal?.throwIfAborted();
|
|
403
432
|
await run([
|
|
404
433
|
"add",
|
|
405
434
|
"-A",
|
|
@@ -410,6 +439,8 @@ async function commitGroups(run, root, groups, selected, signal, verify) {
|
|
|
410
439
|
signal
|
|
411
440
|
});
|
|
412
441
|
stagedIndex = await indexBytes(indexPath);
|
|
442
|
+
signal?.throwIfAborted();
|
|
443
|
+
commitInvoked = true;
|
|
413
444
|
await run([
|
|
414
445
|
"commit",
|
|
415
446
|
"--quiet",
|
|
@@ -422,33 +453,71 @@ async function commitGroups(run, root, groups, selected, signal, verify) {
|
|
|
422
453
|
cwd: root,
|
|
423
454
|
signal
|
|
424
455
|
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
456
|
+
committed = true;
|
|
457
|
+
let hash = "";
|
|
458
|
+
try {
|
|
459
|
+
hash = (await run([
|
|
460
|
+
"rev-parse",
|
|
461
|
+
"--short",
|
|
462
|
+
"HEAD"
|
|
463
|
+
], { cwd: root })).trim();
|
|
464
|
+
} catch (error) {
|
|
465
|
+
commits.push({
|
|
466
|
+
hash: "",
|
|
467
|
+
skipped: false
|
|
468
|
+
});
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
if (!hash) {
|
|
472
|
+
commits.push({
|
|
473
|
+
hash: "",
|
|
474
|
+
skipped: false
|
|
475
|
+
});
|
|
476
|
+
throw new GitError("dsh-git-commit: commit was created but its hash could not be read");
|
|
477
|
+
}
|
|
433
478
|
commits.push({
|
|
434
479
|
hash,
|
|
435
480
|
skipped: false
|
|
436
481
|
});
|
|
437
482
|
} catch (error) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
483
|
+
if (!committed) {
|
|
484
|
+
let currentHead;
|
|
485
|
+
try {
|
|
486
|
+
currentHead = await headOid(run, root);
|
|
487
|
+
} catch {
|
|
488
|
+
if (commitInvoked) throw new PartialCommitError(commits, error, {
|
|
489
|
+
message: group.message,
|
|
490
|
+
files: [...group.files]
|
|
491
|
+
});
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
if (currentHead !== head) {
|
|
495
|
+
if (commitInvoked) throw new PartialCommitError(commits, error, {
|
|
496
|
+
message: group.message,
|
|
497
|
+
files: [...group.files]
|
|
498
|
+
});
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
const currentIndex = await indexBytes(indexPath);
|
|
502
|
+
const indexUntouched = currentIndex === void 0 ? stagedIndex === void 0 : currentIndex.equals(stagedIndex ?? Buffer.alloc(0));
|
|
503
|
+
if (currentHead === head && indexUntouched) {
|
|
504
|
+
if (hadIndex) await copyFile(backupPath, indexPath);
|
|
505
|
+
else await rm(indexPath, { force: true });
|
|
506
|
+
}
|
|
444
507
|
}
|
|
445
508
|
throw error;
|
|
446
509
|
}
|
|
510
|
+
} catch (error) {
|
|
511
|
+
fail(error);
|
|
447
512
|
} finally {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
513
|
+
if (backupDir) try {
|
|
514
|
+
await rm(backupDir, {
|
|
515
|
+
recursive: true,
|
|
516
|
+
force: true
|
|
517
|
+
});
|
|
518
|
+
} catch (error) {
|
|
519
|
+
fail(partialFailure ?? error);
|
|
520
|
+
}
|
|
452
521
|
}
|
|
453
522
|
}
|
|
454
523
|
return commits;
|
|
@@ -563,10 +632,13 @@ function parsePlan(text, files) {
|
|
|
563
632
|
//#region src/service.ts
|
|
564
633
|
var CommitRunError = class extends Error {
|
|
565
634
|
code;
|
|
566
|
-
|
|
635
|
+
/** Commits already created when this run stopped. Absent when nothing was written. */
|
|
636
|
+
partial;
|
|
637
|
+
constructor(code, message, partial) {
|
|
567
638
|
super(message);
|
|
568
|
-
this.name = "CommitRunError";
|
|
639
|
+
this.name = code === "cancelled" ? "AbortError" : "CommitRunError";
|
|
569
640
|
this.code = code;
|
|
641
|
+
this.partial = partial;
|
|
570
642
|
}
|
|
571
643
|
};
|
|
572
644
|
function modelInfo(route, source) {
|
|
@@ -597,6 +669,8 @@ var GitCommitService = class {
|
|
|
597
669
|
settingsFailure;
|
|
598
670
|
settingsAdopted = false;
|
|
599
671
|
disposed = false;
|
|
672
|
+
/** Aborted on unload so an in-flight model plan receives the same signal as a cancelled request. */
|
|
673
|
+
lifetime = new AbortController();
|
|
600
674
|
constructor(deps) {
|
|
601
675
|
this.deps = deps;
|
|
602
676
|
this.run = deps.run ?? execGit;
|
|
@@ -617,13 +691,21 @@ var GitCommitService = class {
|
|
|
617
691
|
}
|
|
618
692
|
/** Existing synchronous snapshot API; host operations wait for initialization first. */
|
|
619
693
|
getSettings() {
|
|
694
|
+
this.workSignal();
|
|
620
695
|
if (this.settingsFailure) throw this.settingsFailure;
|
|
621
696
|
return structuredClone(this.settings);
|
|
622
697
|
}
|
|
623
698
|
async ready() {
|
|
699
|
+
this.workSignal();
|
|
624
700
|
await this.settingsReady;
|
|
701
|
+
this.workSignal();
|
|
625
702
|
if (this.settingsFailure) throw this.settingsFailure;
|
|
626
703
|
}
|
|
704
|
+
/** Request cancellation and plugin unload share one signal. A disposed service rejects before any new work. */
|
|
705
|
+
workSignal(signal) {
|
|
706
|
+
if (this.disposed || this.lifetime.signal.aborted) throw new CommitRunError("disposed", "dsh-git-commit: plugin is no longer active");
|
|
707
|
+
return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
|
|
708
|
+
}
|
|
627
709
|
async waitForSettings(signal) {
|
|
628
710
|
signal?.throwIfAborted();
|
|
629
711
|
if (!signal) {
|
|
@@ -670,7 +752,10 @@ var GitCommitService = class {
|
|
|
670
752
|
};
|
|
671
753
|
await this.deps.settings?.save(next);
|
|
672
754
|
this.settings = next;
|
|
673
|
-
|
|
755
|
+
try {
|
|
756
|
+
this.deps.onSettingsChanged?.(next.revision);
|
|
757
|
+
} catch {}
|
|
758
|
+
return structuredClone(next);
|
|
674
759
|
});
|
|
675
760
|
this.settingsPending = result.then(() => {}, () => {});
|
|
676
761
|
return result;
|
|
@@ -678,27 +763,30 @@ var GitCommitService = class {
|
|
|
678
763
|
start() {}
|
|
679
764
|
async dispose() {
|
|
680
765
|
this.disposed = true;
|
|
766
|
+
this.lifetime.abort();
|
|
681
767
|
await this.settingsReady;
|
|
682
768
|
await this.settingsPending;
|
|
683
769
|
await this.running?.catch(() => {});
|
|
684
770
|
}
|
|
685
|
-
async resolveRepo(sessionId) {
|
|
771
|
+
async resolveRepo(sessionId, signal) {
|
|
686
772
|
const session = this.deps.sessions.get(sessionId);
|
|
687
773
|
if (!session) throw new CommitRunError("no-session", "dsh-git-commit: session is not live on this host");
|
|
688
774
|
const cwd = normalizeCwd(session.header?.cwd);
|
|
689
775
|
if (!cwd) throw new CommitRunError("no-cwd", "dsh-git-commit: session has no working directory");
|
|
690
776
|
try {
|
|
691
|
-
return await repoInfo(this.run, cwd);
|
|
777
|
+
return await repoInfo(this.run, cwd, signal);
|
|
692
778
|
} catch (error) {
|
|
779
|
+
signal?.throwIfAborted();
|
|
693
780
|
if (isGitMissing(error)) throw new CommitRunError("no-git", "dsh-git-commit: git executable not found");
|
|
694
781
|
throw new CommitRunError("not-a-repo", "dsh-git-commit: session directory is not inside a git repository");
|
|
695
782
|
}
|
|
696
783
|
}
|
|
697
|
-
async status(sessionId) {
|
|
698
|
-
|
|
784
|
+
async status(sessionId, signal) {
|
|
785
|
+
const work = this.workSignal(signal);
|
|
786
|
+
await this.waitForSettings(work);
|
|
699
787
|
let resolved;
|
|
700
788
|
try {
|
|
701
|
-
resolved = await this.resolveRepo(sessionId);
|
|
789
|
+
resolved = await this.resolveRepo(sessionId, work);
|
|
702
790
|
} catch (error) {
|
|
703
791
|
if (error instanceof CommitRunError) return {
|
|
704
792
|
available: false,
|
|
@@ -708,7 +796,8 @@ var GitCommitService = class {
|
|
|
708
796
|
};
|
|
709
797
|
throw error;
|
|
710
798
|
}
|
|
711
|
-
const changes = await workingTreeChanges(this.run, resolved.root);
|
|
799
|
+
const changes = await workingTreeChanges(this.run, resolved.root, work);
|
|
800
|
+
work.throwIfAborted();
|
|
712
801
|
const selected = this.selectedModel(sessionId);
|
|
713
802
|
const model = modelInfo(selected.route, selected.source);
|
|
714
803
|
return {
|
|
@@ -720,21 +809,27 @@ var GitCommitService = class {
|
|
|
720
809
|
};
|
|
721
810
|
}
|
|
722
811
|
async commit(sessionId, signal) {
|
|
812
|
+
if (this.disposed || this.lifetime.signal.aborted) throw new CommitRunError("disposed", "dsh-git-commit: plugin is no longer active");
|
|
723
813
|
if (this.running) throw new CommitRunError("busy", "dsh-git-commit: a commit run is already in progress");
|
|
724
|
-
const
|
|
814
|
+
const work = this.workSignal(signal);
|
|
815
|
+
const run = this.runCommit(sessionId, work);
|
|
725
816
|
this.running = run;
|
|
726
817
|
try {
|
|
727
818
|
return await run;
|
|
819
|
+
} catch (error) {
|
|
820
|
+
if (error instanceof CommitRunError && error.partial) throw error;
|
|
821
|
+
if (work.aborted) throw new CommitRunError("cancelled", "dsh-git-commit: commit run cancelled");
|
|
822
|
+
throw error;
|
|
728
823
|
} finally {
|
|
729
824
|
this.running = void 0;
|
|
730
825
|
}
|
|
731
826
|
}
|
|
732
827
|
async runCommit(sessionId, signal) {
|
|
733
828
|
await this.waitForSettings(signal);
|
|
734
|
-
if (this.disposed) throw new CommitRunError("cancelled", "dsh-git-commit: plugin is no longer active");
|
|
829
|
+
if (this.disposed || signal?.aborted) throw new CommitRunError("cancelled", "dsh-git-commit: plugin is no longer active");
|
|
735
830
|
const session = this.deps.sessions.get(sessionId);
|
|
736
831
|
const sessionCwd = normalizeCwd(session?.header?.cwd);
|
|
737
|
-
const resolved = await this.resolveRepo(sessionId);
|
|
832
|
+
const resolved = await this.resolveRepo(sessionId, signal);
|
|
738
833
|
const assertReady = () => {
|
|
739
834
|
signal?.throwIfAborted();
|
|
740
835
|
if (this.disposed || !this.deps.llm) throw new CommitRunError("cancelled", "dsh-git-commit: plugin is no longer active");
|
|
@@ -758,7 +853,10 @@ var GitCommitService = class {
|
|
|
758
853
|
const originalVersions = await pathVersions(this.run, resolved.root, paths, signal);
|
|
759
854
|
const [{ stat, diff }, subjects] = await Promise.all([diffAgainstHead(this.run, resolved.root, paths, signal), recentSubjects(this.run, resolved.root, 10, signal)]);
|
|
760
855
|
const untracked = [];
|
|
761
|
-
for (const file of selected)
|
|
856
|
+
for (const file of selected) {
|
|
857
|
+
assertReady();
|
|
858
|
+
if (file.status === "untracked") untracked.push(await this.preview(resolved.root, file.path));
|
|
859
|
+
}
|
|
762
860
|
let groups;
|
|
763
861
|
let planFailure = "no model route was available";
|
|
764
862
|
let model;
|
|
@@ -776,13 +874,14 @@ var GitCommitService = class {
|
|
|
776
874
|
untracked
|
|
777
875
|
}),
|
|
778
876
|
maxTokens: DEFAULT_MAX_OUTPUT_TOKENS,
|
|
779
|
-
signal
|
|
877
|
+
signal,
|
|
780
878
|
sessionId
|
|
781
879
|
});
|
|
782
880
|
model = modelInfo(result, planned.source);
|
|
783
881
|
groups = parsePlan(result.text, selected);
|
|
784
882
|
if (!groups) planFailure = "the model returned a plan that does not cover the change set";
|
|
785
883
|
} catch (error) {
|
|
884
|
+
assertReady();
|
|
786
885
|
if (error instanceof CommitRunError) throw error;
|
|
787
886
|
planFailure = error instanceof Error ? error.message : String(error);
|
|
788
887
|
}
|
|
@@ -790,30 +889,59 @@ var GitCommitService = class {
|
|
|
790
889
|
assertReady();
|
|
791
890
|
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");
|
|
792
891
|
const effective = groups ?? [fallbackGroup(selected, looksChinese(subjects) ? "zh" : "en")];
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
892
|
+
const describe = (executed) => {
|
|
893
|
+
const commits = [];
|
|
894
|
+
for (let index = 0; index < executed.length; index += 1) {
|
|
895
|
+
const result = executed[index];
|
|
896
|
+
const group = effective[index];
|
|
897
|
+
if (!result || !group || result.skipped) continue;
|
|
898
|
+
commits.push({
|
|
899
|
+
hash: result.hash,
|
|
900
|
+
message: group.message,
|
|
901
|
+
files: [...group.files]
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
return commits;
|
|
905
|
+
};
|
|
906
|
+
let executed;
|
|
907
|
+
try {
|
|
908
|
+
executed = await commitGroups(this.run, resolved.root, effective, selected, signal, async () => {
|
|
909
|
+
assertReady();
|
|
910
|
+
if (await this.run([
|
|
911
|
+
"symbolic-ref",
|
|
912
|
+
"--quiet",
|
|
913
|
+
"HEAD"
|
|
914
|
+
], {
|
|
915
|
+
cwd: resolved.root,
|
|
916
|
+
signal
|
|
917
|
+
}).catch(() => "DETACHED") !== branchRef) throw new CommitRunError("workspace-changed", "dsh-git-commit: branch changed during the commit run");
|
|
918
|
+
if (await pathVersions(this.run, resolved.root, paths, signal) !== originalVersions) throw new CommitRunError("workspace-changed", "dsh-git-commit: selected file changed during the commit run");
|
|
919
|
+
await workingTreeChanges(this.run, resolved.root, signal);
|
|
920
|
+
});
|
|
921
|
+
} catch (error) {
|
|
922
|
+
if (!(error instanceof PartialCommitError)) throw error;
|
|
923
|
+
const commits = describe(error.commits);
|
|
924
|
+
const cause = error.cause;
|
|
925
|
+
const cancelled = signal?.aborted || typeof cause === "object" && cause !== null && cause.code === "cancelled" || cause instanceof Error && cause.name === "AbortError";
|
|
926
|
+
const code = cause instanceof CommitRunError ? cause.code : cancelled ? "cancelled" : "commit-failed";
|
|
927
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
928
|
+
throw new CommitRunError(code, message, {
|
|
929
|
+
branch: resolved.branch,
|
|
930
|
+
commits,
|
|
931
|
+
fallback: groups === void 0,
|
|
932
|
+
failure: {
|
|
933
|
+
code,
|
|
934
|
+
message
|
|
935
|
+
},
|
|
936
|
+
remaining: effective.slice(error.commits.length + (error.uncertain ? 1 : 0)).map((group) => ({
|
|
937
|
+
message: group.message,
|
|
938
|
+
files: [...group.files]
|
|
939
|
+
})),
|
|
940
|
+
...error.uncertain ? { uncertain: [error.uncertain] } : {},
|
|
941
|
+
...model ? { model } : {}
|
|
815
942
|
});
|
|
816
943
|
}
|
|
944
|
+
const commits = describe(executed);
|
|
817
945
|
if (commits.length === 0) throw new CommitRunError("no-changes", "dsh-git-commit: nothing was committed");
|
|
818
946
|
return {
|
|
819
947
|
branch: resolved.branch,
|
|
@@ -880,7 +1008,8 @@ function fail(error) {
|
|
|
880
1008
|
ok: false,
|
|
881
1009
|
error: {
|
|
882
1010
|
code: error.code,
|
|
883
|
-
message: error.message
|
|
1011
|
+
message: error.message,
|
|
1012
|
+
...error.partial ? { partial: error.partial } : {}
|
|
884
1013
|
}
|
|
885
1014
|
};
|
|
886
1015
|
return {
|
|
@@ -905,7 +1034,7 @@ async function handleRpc(service, endpoint, payload, signal) {
|
|
|
905
1034
|
};
|
|
906
1035
|
return {
|
|
907
1036
|
ok: true,
|
|
908
|
-
value: await service.status(sessionId)
|
|
1037
|
+
value: await service.status(sessionId, signal)
|
|
909
1038
|
};
|
|
910
1039
|
}
|
|
911
1040
|
case "settings":
|
|
@@ -963,6 +1092,7 @@ function apply(ctx) {
|
|
|
963
1092
|
const tablePromise = domainPromise.then((domain) => domain.table("settings"));
|
|
964
1093
|
const service = new GitCommitService({
|
|
965
1094
|
plugin: PLUGIN_NAME,
|
|
1095
|
+
onSettingsChanged: (revision) => notifyModelSettingsChanged(ctx, PLUGIN_NAME, revision),
|
|
966
1096
|
llm: host.llm,
|
|
967
1097
|
host,
|
|
968
1098
|
sessions: host.sessions,
|
|
@@ -981,6 +1111,43 @@ function apply(ctx) {
|
|
|
981
1111
|
await (await domainPromise).close();
|
|
982
1112
|
}, "git-commit.lifecycle");
|
|
983
1113
|
ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, (endpoint, payload, signal) => handleRpc(service, endpoint, payload, signal)), "git-commit.rpc");
|
|
1114
|
+
ctx.effect(() => registerModelSettings(ctx, {
|
|
1115
|
+
ns: PLUGIN_NAME,
|
|
1116
|
+
title: {
|
|
1117
|
+
zh: "Git 提交",
|
|
1118
|
+
en: "Git Commit"
|
|
1119
|
+
},
|
|
1120
|
+
async read(signal) {
|
|
1121
|
+
await service.ready();
|
|
1122
|
+
signal.throwIfAborted();
|
|
1123
|
+
const settings = service.getSettings();
|
|
1124
|
+
return {
|
|
1125
|
+
revision: settings.revision,
|
|
1126
|
+
fields: [{
|
|
1127
|
+
path: ["model"],
|
|
1128
|
+
label: {
|
|
1129
|
+
zh: "提交模型",
|
|
1130
|
+
en: "Commit model"
|
|
1131
|
+
},
|
|
1132
|
+
current: settings.model,
|
|
1133
|
+
followLabel: {
|
|
1134
|
+
zh: "当前会话 / 宿主默认模型",
|
|
1135
|
+
en: "Current conversation / host default"
|
|
1136
|
+
},
|
|
1137
|
+
allowFollow: true
|
|
1138
|
+
}]
|
|
1139
|
+
};
|
|
1140
|
+
},
|
|
1141
|
+
async write(path, route, expectedRevision, signal) {
|
|
1142
|
+
if (path.length !== 1 || path[0] !== "model") throw new Error("Unknown Git Commit model setting");
|
|
1143
|
+
await service.ready();
|
|
1144
|
+
signal.throwIfAborted();
|
|
1145
|
+
return service.updateSettings({
|
|
1146
|
+
...service.getSettings(),
|
|
1147
|
+
model: route
|
|
1148
|
+
}, expectedRevision);
|
|
1149
|
+
}
|
|
1150
|
+
}), "git-commit.model-settings");
|
|
984
1151
|
}
|
|
985
1152
|
//#endregion
|
|
986
1153
|
export { GitCommitService, apply, buildPlanInput, defaultSettings, handleRpc, inject, name, normalizeFallback, normalizeModelRoute, parsePlan, parsePorcelain };
|