dsh-easygit-plugin 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 +13 -6
- package/README.zh-CN.md +13 -6
- package/{git-guide.cordis.yml → easygit.cordis.yml} +3 -3
- package/lib/client.js +541 -31
- package/lib/index.js +362 -30
- package/lib/types/client/index.d.ts +9 -1
- package/lib/types/client/view-model.d.ts +12 -1
- package/lib/types/host/actions.d.ts +8 -6
- package/lib/types/host/git-repository-service.d.ts +11 -2
- package/lib/types/host/plugin.d.ts +23 -3
- package/lib/types/host/proposal-service.d.ts +2 -1
- package/lib/types/shared/contracts.d.ts +99 -12
- package/package.json +4 -3
package/lib/index.js
CHANGED
|
@@ -329,6 +329,59 @@ var init_command_policy = __esm({
|
|
|
329
329
|
function isRepositoryAction(action) {
|
|
330
330
|
return REPOSITORY_ACTIONS.includes(action);
|
|
331
331
|
}
|
|
332
|
+
function repositoryMutationCommand(action, body) {
|
|
333
|
+
const paths = Array.isArray(body.paths) && body.paths.every((path) => typeof path === "string") ? body.paths.map(quoteShellArg).join(" ") : "";
|
|
334
|
+
if (action === "stage-paths" && paths) return "git add -- " + paths;
|
|
335
|
+
if (action === "unstage-paths" && paths) return "git reset HEAD -- " + paths;
|
|
336
|
+
if (action === "stage-all") return "git add -A";
|
|
337
|
+
if (action === "unstage-all") return "git reset HEAD -- :/";
|
|
338
|
+
if (action === "commit" && typeof body.message === "string") return "git commit -m " + quoteShellArg(body.message.trim());
|
|
339
|
+
if (action === "create-branch" && typeof body.name === "string" && typeof body.base === "string") {
|
|
340
|
+
return "git switch -c " + quoteShellArg(body.name) + " " + quoteShellArg(body.base);
|
|
341
|
+
}
|
|
342
|
+
if (action === "switch-branch" && typeof body.name === "string") return "git switch " + quoteShellArg(body.name);
|
|
343
|
+
if (action === "delete-branch" && typeof body.name === "string") {
|
|
344
|
+
return "git branch " + (body.force === true ? "-D" : "-d") + " -- " + quoteShellArg(body.name);
|
|
345
|
+
}
|
|
346
|
+
if (action === "fetch" && typeof body.remote === "string") return "git fetch " + quoteShellArg(body.remote);
|
|
347
|
+
if (action === "pull") return "git pull --ff-only";
|
|
348
|
+
if (action === "push") {
|
|
349
|
+
if (body.setUpstream === true && typeof body.remote === "string" && typeof body.branch === "string") {
|
|
350
|
+
return "git push -u " + quoteShellArg(body.remote) + " " + quoteShellArg(body.branch);
|
|
351
|
+
}
|
|
352
|
+
return "git push";
|
|
353
|
+
}
|
|
354
|
+
if (action === "rebase" && typeof body.target === "string") return "git rebase " + quoteShellArg(body.target);
|
|
355
|
+
if (action === "rebase-continue") return "git -c core.editor=true rebase --continue";
|
|
356
|
+
if (action === "rebase-abort") return "git rebase --abort";
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
async function attachRepositoryRecovery(action, sessionId, body, result, context, dependencies) {
|
|
360
|
+
const failure = asRecord(result);
|
|
361
|
+
const syncAction = action === "fetch" || action === "pull" || action === "push" || action === "rebase" || action === "rebase-continue" || action === "rebase-abort";
|
|
362
|
+
const recoverable = failure.code === "GIT_FAILED" || failure.code === "TIMEOUT" || failure.reason === "BRANCH_EXISTS" || syncAction && failure.code === "STATE_CONFLICT";
|
|
363
|
+
if (failure.ok !== false || !recoverable) return result;
|
|
364
|
+
const command = repositoryMutationCommand(action, body);
|
|
365
|
+
const operationId = typeof body.operationId === "string" ? body.operationId : "";
|
|
366
|
+
if (!command || !context || !operationId) return result;
|
|
367
|
+
try {
|
|
368
|
+
const handled = await dependencies.recoverFailedCommand(
|
|
369
|
+
sessionId,
|
|
370
|
+
context.workdir,
|
|
371
|
+
operationId,
|
|
372
|
+
action,
|
|
373
|
+
command,
|
|
374
|
+
typeof failure.message === "string" ? failure.message : "Git \u64CD\u4F5C\u5931\u8D25",
|
|
375
|
+
typeof failure.diagnostics === "string" ? failure.diagnostics : "",
|
|
376
|
+
typeof failure.code === "string" ? failure.code : "GIT_FAILED",
|
|
377
|
+
typeof failure.reason === "string" ? failure.reason : ""
|
|
378
|
+
);
|
|
379
|
+
return handled ? { ...failure, ...handled } : result;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.log("easygit \u4FEE\u6B63\u63D0\u8BAE\u751F\u6210\u5931\u8D25", errorMessage(error));
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
332
385
|
function errorMessage(error) {
|
|
333
386
|
return error instanceof Error ? error.message : String(error);
|
|
334
387
|
}
|
|
@@ -367,8 +420,7 @@ function sendJson(res, status, data) {
|
|
|
367
420
|
});
|
|
368
421
|
res.end(JSON.stringify(data));
|
|
369
422
|
}
|
|
370
|
-
async function dispatchRepositoryAction(action, sessionId, body, dependencies) {
|
|
371
|
-
const context = dependencies.repositoryContext(sessionId);
|
|
423
|
+
async function dispatchRepositoryAction(action, sessionId, body, context, dependencies) {
|
|
372
424
|
if (!context) return { ok: false, code: "SESSION_NOT_FOUND", message: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u4F1A\u8BDD\u7684\u4ED3\u5E93\u76EE\u5F55" };
|
|
373
425
|
const repository = dependencies.repository;
|
|
374
426
|
const base = { sessionId, workdir: context.workdir, operationId: body.operationId, sandboxPolicy: context.policy };
|
|
@@ -379,6 +431,7 @@ async function dispatchRepositoryAction(action, sessionId, body, dependencies) {
|
|
|
379
431
|
if (action === "get-commit-detail") return repository.getCommitDetail(context.workdir, body.hash, void 0, context.policy);
|
|
380
432
|
if (action === "get-commit-diff") return repository.getCommitDiff(context.workdir, body.hash, void 0, context.policy);
|
|
381
433
|
if (action === "get-stashes") return repository.getStashes(context.workdir, void 0, context.policy);
|
|
434
|
+
if (action === "get-sync-state") return repository.getSyncState(context.workdir, void 0, context.policy);
|
|
382
435
|
if (action === "stage-paths") return repository.stagePaths(base, body.paths);
|
|
383
436
|
if (action === "unstage-paths") return repository.unstagePaths(base, body.paths);
|
|
384
437
|
if (action === "stage-all") return repository.stageAll(base);
|
|
@@ -386,9 +439,24 @@ async function dispatchRepositoryAction(action, sessionId, body, dependencies) {
|
|
|
386
439
|
if (action === "commit") return repository.commit(base, body.message);
|
|
387
440
|
if (action === "create-branch") return repository.createBranch(base, body.name, body.base);
|
|
388
441
|
if (action === "switch-branch") return repository.switchBranch(base, body.name);
|
|
389
|
-
return repository.deleteBranch(base, body.name, body.force === true, body.confirmRisk === true);
|
|
442
|
+
if (action === "delete-branch") return repository.deleteBranch(base, body.name, body.force === true, body.confirmRisk === true);
|
|
443
|
+
if (action === "fetch") return repository.fetchRemote(base, body.remote);
|
|
444
|
+
if (action === "pull") return repository.pullFfOnly(base);
|
|
445
|
+
if (action === "push") return repository.pushCurrent(base, body.remote, body.branch, body.setUpstream === true);
|
|
446
|
+
if (action === "rebase") return repository.rebaseOnto(base, body.target, body.confirmRisk === true);
|
|
447
|
+
if (action === "rebase-continue") return repository.continueRebase(base, body.confirmRisk === true);
|
|
448
|
+
return repository.abortRebase(base, body.confirmRisk === true);
|
|
390
449
|
}
|
|
391
450
|
async function dispatchProposalAction(action, sessionId, body, dependencies) {
|
|
451
|
+
if (action === "request-analysis") {
|
|
452
|
+
const proposal = dependencies.findProposal(sessionId, body.proposalId);
|
|
453
|
+
if (!proposal || proposal.closed || proposal.status !== "failed" || proposal.needsAgentAnalysis !== true || !proposal.failure) {
|
|
454
|
+
return { status: 200, data: { ok: false, error: "\u8BE5\u5931\u8D25\u8BB0\u5F55\u5DF2\u4E0D\u518D\u7B49\u5F85 Agent \u5206\u6790" } };
|
|
455
|
+
}
|
|
456
|
+
proposal.analysisRequestedAt = Date.now();
|
|
457
|
+
await dependencies.flushProposal(sessionId);
|
|
458
|
+
return { status: 200, data: { ok: true } };
|
|
459
|
+
}
|
|
392
460
|
if (action === "state") {
|
|
393
461
|
const proposal = dependencies.latestPending(sessionId);
|
|
394
462
|
if (proposal && proposal.status === "pending" && proposal.copied === true && proposal.fingerprint) {
|
|
@@ -452,16 +520,16 @@ async function dispatchProposalAction(action, sessionId, body, dependencies) {
|
|
|
452
520
|
dependencies.resolveExecutionPolicy(sessionId),
|
|
453
521
|
() => dependencies.flushProposal(sessionId)
|
|
454
522
|
);
|
|
455
|
-
console.log("
|
|
523
|
+
console.log("easygit HTTP execute", proposal.proposalId, "ok=", result.ok);
|
|
456
524
|
return { status: 200, data: result };
|
|
457
525
|
}
|
|
458
526
|
return { status: 400, data: { ok: false, error: "unknown action: " + action } };
|
|
459
527
|
}
|
|
460
|
-
function
|
|
528
|
+
function registerEasyGitActions(webServer, dependencies) {
|
|
461
529
|
if (!webServer) return void 0;
|
|
462
530
|
return webServer.register({
|
|
463
531
|
kind: "prefix",
|
|
464
|
-
path: "/
|
|
532
|
+
path: "/easygit",
|
|
465
533
|
handler: async (req, res) => {
|
|
466
534
|
if (req.method !== "POST") {
|
|
467
535
|
sendJson(res, 405, { ok: false, error: "method not allowed" });
|
|
@@ -497,7 +565,9 @@ function registerGitGuideActions(webServer, dependencies) {
|
|
|
497
565
|
try {
|
|
498
566
|
await dependencies.proposalStorageReady;
|
|
499
567
|
if (isRepositoryAction(action)) {
|
|
500
|
-
|
|
568
|
+
const context = dependencies.repositoryContext(sessionId);
|
|
569
|
+
const result = await dispatchRepositoryAction(action, sessionId, body, context, dependencies);
|
|
570
|
+
sendJson(res, 200, await attachRepositoryRecovery(action, sessionId, body, result, context, dependencies));
|
|
501
571
|
return;
|
|
502
572
|
}
|
|
503
573
|
const response = await dispatchProposalAction(action, sessionId, body, dependencies);
|
|
@@ -522,6 +592,7 @@ var init_actions = __esm({
|
|
|
522
592
|
"get-commit-detail",
|
|
523
593
|
"get-commit-diff",
|
|
524
594
|
"get-stashes",
|
|
595
|
+
"get-sync-state",
|
|
525
596
|
"stage-paths",
|
|
526
597
|
"unstage-paths",
|
|
527
598
|
"stage-all",
|
|
@@ -529,7 +600,13 @@ var init_actions = __esm({
|
|
|
529
600
|
"commit",
|
|
530
601
|
"create-branch",
|
|
531
602
|
"switch-branch",
|
|
532
|
-
"delete-branch"
|
|
603
|
+
"delete-branch",
|
|
604
|
+
"fetch",
|
|
605
|
+
"pull",
|
|
606
|
+
"push",
|
|
607
|
+
"rebase",
|
|
608
|
+
"rebase-continue",
|
|
609
|
+
"rebase-abort"
|
|
533
610
|
];
|
|
534
611
|
}
|
|
535
612
|
});
|
|
@@ -538,6 +615,10 @@ var init_actions = __esm({
|
|
|
538
615
|
function isRecord(value) {
|
|
539
616
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
540
617
|
}
|
|
618
|
+
function isGitFailureContext(value) {
|
|
619
|
+
if (!isRecord(value)) return false;
|
|
620
|
+
return (value.source === "workbench" || value.source === "proposal") && typeof value.code === "string" && typeof value.action === "string" && typeof value.command === "string" && typeof value.message === "string" && typeof value.stdout === "string" && typeof value.stderr === "string" && typeof value.diagnostics === "string" && (value.exitCode === null || typeof value.exitCode === "number" && Number.isSafeInteger(value.exitCode)) && typeof value.timedOut === "boolean" && typeof value.mayHavePartialChanges === "boolean" && typeof value.occurredAt === "number" && Number.isSafeInteger(value.occurredAt);
|
|
621
|
+
}
|
|
541
622
|
function isStoredProposal(value, sessionId) {
|
|
542
623
|
if (!isRecord(value) || value.sessionId !== sessionId) return false;
|
|
543
624
|
if (typeof value.proposalId !== "string" || !/^g-[0-9a-f-]{36}$/i.test(value.proposalId)) return false;
|
|
@@ -548,7 +629,7 @@ function isStoredProposal(value, sessionId) {
|
|
|
548
629
|
if (!Array.isArray(value.reasons) || !value.reasons.every((reason) => typeof reason === "string")) return false;
|
|
549
630
|
if (!Array.isArray(value.steps) || value.steps.length === 0 || value.steps.length > 10) return false;
|
|
550
631
|
if (!value.steps.every((step) => isRecord(step) && typeof step.command === "string" && (step.result === null || isRecord(step.result)))) return false;
|
|
551
|
-
return typeof value.confirmed === "boolean" && typeof value.closed === "boolean" && typeof value.copied === "boolean" && typeof value.verified === "boolean" && (value.fingerprint === null || typeof value.fingerprint === "string") && (value.result === null || isRecord(value.result));
|
|
632
|
+
return typeof value.confirmed === "boolean" && typeof value.closed === "boolean" && typeof value.copied === "boolean" && typeof value.verified === "boolean" && (value.fingerprint === null || typeof value.fingerprint === "string") && (value.result === null || isRecord(value.result)) && (value.failure === void 0 || isGitFailureContext(value.failure)) && (value.recoverySuggestion === void 0 || typeof value.recoverySuggestion === "string") && (value.needsAgentAnalysis === void 0 || typeof value.needsAgentAnalysis === "boolean") && (value.analysisRequestedAt === void 0 || typeof value.analysisRequestedAt === "number" && Number.isSafeInteger(value.analysisRequestedAt));
|
|
552
633
|
}
|
|
553
634
|
var import_node_crypto, DEFAULT_PROPOSALS_PER_SESSION, DEFAULT_MAX_SESSIONS, DEFAULT_SESSION_TTL_MS, ProposalService, proposalService;
|
|
554
635
|
var init_proposal_service = __esm({
|
|
@@ -679,6 +760,10 @@ var init_proposal_service = __esm({
|
|
|
679
760
|
result: proposal.result,
|
|
680
761
|
closed: proposal.closed,
|
|
681
762
|
copied: proposal.copied,
|
|
763
|
+
...proposal.failure ? { failure: proposal.failure } : {},
|
|
764
|
+
...typeof proposal.recoverySuggestion === "string" ? { recoverySuggestion: proposal.recoverySuggestion } : {},
|
|
765
|
+
...proposal.needsAgentAnalysis === true ? { needsAgentAnalysis: true } : {},
|
|
766
|
+
...typeof proposal.analysisRequestedAt === "number" ? { analysisRequestedAt: proposal.analysisRequestedAt } : {},
|
|
682
767
|
status: proposal.status || (proposal.closed ? "dismissed" : "pending")
|
|
683
768
|
};
|
|
684
769
|
}
|
|
@@ -724,6 +809,15 @@ function validPathspec(path) {
|
|
|
724
809
|
function validBranchName(name) {
|
|
725
810
|
return typeof name === "string" && name.length > 0 && name.length <= 255 && !/[\0\r\n\s~^:?*\[\\]/.test(name) && !name.startsWith("-") && !name.startsWith(".") && !name.endsWith(".") && !name.includes("..") && !name.includes("@{") && !name.endsWith(".lock");
|
|
726
811
|
}
|
|
812
|
+
function validRemoteName(name) {
|
|
813
|
+
return typeof name === "string" && name.length > 0 && name.length <= 255 && /^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(name) && !name.includes("..") && !name.includes("@{") && !name.endsWith(".lock");
|
|
814
|
+
}
|
|
815
|
+
function conflictCount(files) {
|
|
816
|
+
return files.filter((file) => {
|
|
817
|
+
const status = file.indexStatus + file.workTreeStatus;
|
|
818
|
+
return status.includes("U") || status === "AA" || status === "DD";
|
|
819
|
+
}).length;
|
|
820
|
+
}
|
|
727
821
|
function validCommitHash(hash) {
|
|
728
822
|
return typeof hash === "string" && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(hash);
|
|
729
823
|
}
|
|
@@ -1007,6 +1101,103 @@ var init_git_repository_service = __esm({
|
|
|
1007
1101
|
});
|
|
1008
1102
|
return { ok: true, data: stashes };
|
|
1009
1103
|
}
|
|
1104
|
+
async getSyncState(workdir, signal, sandboxPolicy) {
|
|
1105
|
+
const summary = await this.getSummary(workdir, signal, sandboxPolicy);
|
|
1106
|
+
if (!summary.ok) return summary;
|
|
1107
|
+
const [remoteResult, upstreamResult, rebaseResult] = await Promise.all([
|
|
1108
|
+
this.run(workdir, "git remote", 15e3, 2e4, signal, sandboxPolicy),
|
|
1109
|
+
this.run(workdir, "git rev-parse --abbrev-ref --symbolic-full-name @{upstream}", 15e3, 4096, signal, sandboxPolicy),
|
|
1110
|
+
this.run(workdir, 'test -d "$(git rev-parse --git-path rebase-merge)" || test -d "$(git rev-parse --git-path rebase-apply)"', 15e3, 4096, signal, sandboxPolicy)
|
|
1111
|
+
]);
|
|
1112
|
+
if (remoteResult.exitCode !== 0) {
|
|
1113
|
+
return errorResult("GIT_FAILED", "\u65E0\u6CD5\u8BFB\u53D6\u8FDC\u7A0B\u4ED3\u5E93", redactAndLimit(outputOf(remoteResult), 8192));
|
|
1114
|
+
}
|
|
1115
|
+
const remotes = (remoteResult.stdout?.text ?? "").split("\n").map((entry) => redactAndLimit(entry.trim(), 255)).filter(Boolean);
|
|
1116
|
+
const upstream = upstreamResult.exitCode === 0 ? redactAndLimit(upstreamResult.stdout?.text ?? "", 512).trim() : "";
|
|
1117
|
+
let ahead = 0;
|
|
1118
|
+
let behind = 0;
|
|
1119
|
+
if (upstream) {
|
|
1120
|
+
const counts = await this.run(workdir, "git rev-list --left-right --count HEAD...@{upstream}", 15e3, 4096, signal, sandboxPolicy);
|
|
1121
|
+
if (counts.exitCode !== 0) return errorResult("GIT_FAILED", "\u65E0\u6CD5\u8BA1\u7B97\u672C\u5730\u4E0E\u4E0A\u6E38\u7684\u63D0\u4EA4\u5DEE\u5F02", redactAndLimit(outputOf(counts), 8192));
|
|
1122
|
+
const [aheadText = "0", behindText = "0"] = (counts.stdout?.text ?? "").trim().split(/\s+/);
|
|
1123
|
+
ahead = /^\d+$/.test(aheadText) ? Number(aheadText) : 0;
|
|
1124
|
+
behind = /^\d+$/.test(behindText) ? Number(behindText) : 0;
|
|
1125
|
+
}
|
|
1126
|
+
const conflicts = conflictCount(summary.data.files);
|
|
1127
|
+
return {
|
|
1128
|
+
ok: true,
|
|
1129
|
+
data: {
|
|
1130
|
+
topLevel: summary.data.topLevel,
|
|
1131
|
+
branch: summary.data.branch,
|
|
1132
|
+
head: summary.data.head,
|
|
1133
|
+
upstream,
|
|
1134
|
+
remotes,
|
|
1135
|
+
ahead,
|
|
1136
|
+
behind,
|
|
1137
|
+
dirty: summary.data.files.length > 0,
|
|
1138
|
+
conflictCount: conflicts,
|
|
1139
|
+
rebaseInProgress: rebaseResult.exitCode === 0,
|
|
1140
|
+
files: summary.data.files
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
async fetchRemote(request, remote) {
|
|
1145
|
+
if (!validRemoteName(remote)) return errorResult("INVALID_ARGUMENT", "\u8FDC\u7A0B\u4ED3\u5E93\u540D\u79F0\u4E0D\u5408\u6CD5");
|
|
1146
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1147
|
+
if (!state.ok) return state;
|
|
1148
|
+
if (!state.data.remotes.includes(remote)) return errorResult("STATE_CONFLICT", "\u8FDC\u7A0B\u4ED3\u5E93\u4E0D\u5B58\u5728", void 0, "NO_REMOTE");
|
|
1149
|
+
return this.mutateSync(request, "git fetch " + quoteShellArg(remote), "\u83B7\u53D6\u8FDC\u7A0B\u66F4\u65B0\u5931\u8D25");
|
|
1150
|
+
}
|
|
1151
|
+
async pullFfOnly(request) {
|
|
1152
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1153
|
+
if (!state.ok) return state;
|
|
1154
|
+
if (!state.data.branch) return errorResult("STATE_CONFLICT", "\u5206\u79BB HEAD \u72B6\u6001\u4E0D\u80FD\u76F4\u63A5\u62C9\u53D6", void 0, "DETACHED_HEAD");
|
|
1155
|
+
if (!state.data.upstream) return errorResult("STATE_CONFLICT", "\u5F53\u524D\u5206\u652F\u6CA1\u6709\u4E0A\u6E38\u8DDF\u8E2A\u5206\u652F", void 0, "NO_UPSTREAM");
|
|
1156
|
+
if (state.data.rebaseInProgress) return errorResult("STATE_CONFLICT", "Rebase \u8FDB\u884C\u4E2D\uFF0C\u8BF7\u5148\u7EE7\u7EED\u6216\u4E2D\u6B62", void 0, "REBASE_IN_PROGRESS");
|
|
1157
|
+
if (state.data.conflictCount > 0) return errorResult("STATE_CONFLICT", "\u5B58\u5728\u5C1A\u672A\u89E3\u51B3\u7684\u51B2\u7A81", void 0, "CONFLICTS_PRESENT");
|
|
1158
|
+
return this.mutateSync(request, "git pull --ff-only", "\u62C9\u53D6\u8FDC\u7A0B\u66F4\u65B0\u5931\u8D25");
|
|
1159
|
+
}
|
|
1160
|
+
async pushCurrent(request, remote, branch, setUpstream) {
|
|
1161
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1162
|
+
if (!state.ok) return state;
|
|
1163
|
+
if (!state.data.branch) return errorResult("STATE_CONFLICT", "\u5206\u79BB HEAD \u72B6\u6001\u4E0D\u80FD\u76F4\u63A5\u63A8\u9001", void 0, "DETACHED_HEAD");
|
|
1164
|
+
if (state.data.rebaseInProgress) return errorResult("STATE_CONFLICT", "Rebase \u8FDB\u884C\u4E2D\uFF0C\u8BF7\u5148\u7EE7\u7EED\u6216\u4E2D\u6B62", void 0, "REBASE_IN_PROGRESS");
|
|
1165
|
+
if (setUpstream) {
|
|
1166
|
+
if (state.data.upstream) return errorResult("STATE_CONFLICT", "\u5F53\u524D\u5206\u652F\u5DF2\u7ECF\u6709\u4E0A\u6E38\uFF0C\u8BF7\u4F7F\u7528\u666E\u901A\u63A8\u9001");
|
|
1167
|
+
if (!validRemoteName(remote) || !state.data.remotes.includes(remote)) return errorResult("STATE_CONFLICT", "\u8BF7\u9009\u62E9\u5B58\u5728\u7684\u8FDC\u7A0B\u4ED3\u5E93", void 0, "NO_REMOTE");
|
|
1168
|
+
if (!validBranchName(branch) || branch !== state.data.branch) return errorResult("INVALID_ARGUMENT", "\u53EA\u80FD\u4E3A\u5F53\u524D\u672C\u5730\u5206\u652F\u5EFA\u7ACB\u4E0A\u6E38");
|
|
1169
|
+
return this.mutateSync(request, "git push -u " + quoteShellArg(remote) + " " + quoteShellArg(branch), "\u63A8\u9001\u5E76\u5EFA\u7ACB\u4E0A\u6E38\u5931\u8D25");
|
|
1170
|
+
}
|
|
1171
|
+
if (!state.data.upstream) return errorResult("STATE_CONFLICT", "\u5F53\u524D\u5206\u652F\u6CA1\u6709\u4E0A\u6E38\u8DDF\u8E2A\u5206\u652F", void 0, "NO_UPSTREAM");
|
|
1172
|
+
return this.mutateSync(request, "git push", "\u63A8\u9001\u5931\u8D25");
|
|
1173
|
+
}
|
|
1174
|
+
async rebaseOnto(request, target, confirmRisk) {
|
|
1175
|
+
if (!validBranchName(target)) return errorResult("INVALID_ARGUMENT", "Rebase \u76EE\u6807\u5FC5\u987B\u662F\u5B89\u5168\u7684\u672C\u5730\u6216\u8FDC\u7A0B\u5206\u652F\u5F15\u7528");
|
|
1176
|
+
if (!confirmRisk) return errorResult("PERMISSION_DENIED", "Rebase \u4F1A\u91CD\u5199\u672C\u5730\u63D0\u4EA4\u5386\u53F2\uFF0C\u6267\u884C\u524D\u5FC5\u987B\u786E\u8BA4\u98CE\u9669");
|
|
1177
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1178
|
+
if (!state.ok) return state;
|
|
1179
|
+
if (!state.data.branch) return errorResult("STATE_CONFLICT", "\u5206\u79BB HEAD \u72B6\u6001\u4E0D\u80FD\u5F00\u59CB Rebase", void 0, "DETACHED_HEAD");
|
|
1180
|
+
if (state.data.rebaseInProgress) return errorResult("STATE_CONFLICT", "\u5DF2\u6709 Rebase \u6B63\u5728\u8FDB\u884C", void 0, "REBASE_IN_PROGRESS");
|
|
1181
|
+
if (state.data.dirty) return errorResult("STATE_CONFLICT", "Rebase \u524D\u9700\u8981\u63D0\u4EA4\u6216\u8D2E\u85CF\u5DE5\u4F5C\u533A\u6539\u52A8", void 0, "DIRTY_WORKTREE");
|
|
1182
|
+
const exists = await this.run(request.workdir, "git rev-parse --verify --quiet " + quoteShellArg(target + "^{commit}"), 15e3, 4096, request.signal, request.sandboxPolicy);
|
|
1183
|
+
if (exists.exitCode !== 0) return errorResult("STATE_CONFLICT", "Rebase \u76EE\u6807\u5F15\u7528\u4E0D\u5B58\u5728", void 0, "REF_NOT_FOUND");
|
|
1184
|
+
return this.mutateSync(request, "git rebase " + quoteShellArg(target), "Rebase \u5931\u8D25");
|
|
1185
|
+
}
|
|
1186
|
+
async continueRebase(request, confirmRisk) {
|
|
1187
|
+
if (!confirmRisk) return errorResult("PERMISSION_DENIED", "\u7EE7\u7EED Rebase \u524D\u5FC5\u987B\u786E\u8BA4\u5386\u53F2\u91CD\u5199\u98CE\u9669");
|
|
1188
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1189
|
+
if (!state.ok) return state;
|
|
1190
|
+
if (!state.data.rebaseInProgress) return errorResult("STATE_CONFLICT", "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FDB\u884C\u7684 Rebase", void 0, "NO_REBASE_IN_PROGRESS");
|
|
1191
|
+
if (state.data.conflictCount > 0) return errorResult("STATE_CONFLICT", "\u4ECD\u6709\u51B2\u7A81\u6587\u4EF6\uFF0C\u8BF7\u89E3\u51B3\u5E76\u6682\u5B58\u540E\u518D\u7EE7\u7EED", void 0, "CONFLICTS_PRESENT");
|
|
1192
|
+
return this.mutateSync(request, "git -c core.editor=true rebase --continue", "\u7EE7\u7EED Rebase \u5931\u8D25");
|
|
1193
|
+
}
|
|
1194
|
+
async abortRebase(request, confirmRisk) {
|
|
1195
|
+
if (!confirmRisk) return errorResult("PERMISSION_DENIED", "\u4E2D\u6B62 Rebase \u4F1A\u4E22\u5F03\u672C\u6B21\u53D8\u57FA\u8FC7\u7A0B\u4E2D\u7684\u4FEE\u6539\uFF0C\u6267\u884C\u524D\u5FC5\u987B\u786E\u8BA4\u98CE\u9669");
|
|
1196
|
+
const state = await this.getSyncState(request.workdir, request.signal, request.sandboxPolicy);
|
|
1197
|
+
if (!state.ok) return state;
|
|
1198
|
+
if (!state.data.rebaseInProgress) return errorResult("STATE_CONFLICT", "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FDB\u884C\u7684 Rebase", void 0, "NO_REBASE_IN_PROGRESS");
|
|
1199
|
+
return this.mutateSync(request, "git rebase --abort", "\u4E2D\u6B62 Rebase \u5931\u8D25");
|
|
1200
|
+
}
|
|
1010
1201
|
async stagePaths(request, paths) {
|
|
1011
1202
|
const valid = this.validatePaths(paths);
|
|
1012
1203
|
if (!valid.ok) return valid;
|
|
@@ -1031,6 +1222,18 @@ var init_git_repository_service = __esm({
|
|
|
1031
1222
|
}
|
|
1032
1223
|
async createBranch(request, name, base) {
|
|
1033
1224
|
if (!validBranchName(name) || !validBranchName(base)) return errorResult("INVALID_ARGUMENT", "\u5206\u652F\u540D\u548C\u57FA\u7840\u5206\u652F\u5FC5\u987B\u662F\u5B89\u5168\u7684\u672C\u5730 Git \u5F15\u7528");
|
|
1225
|
+
const repository = await this.getTopLevel(request.workdir, request.signal, request.sandboxPolicy);
|
|
1226
|
+
if (!repository.ok) return repository;
|
|
1227
|
+
const exists = await this.run(
|
|
1228
|
+
request.workdir,
|
|
1229
|
+
"git show-ref --verify --quiet " + quoteShellArg("refs/heads/" + name),
|
|
1230
|
+
15e3,
|
|
1231
|
+
4096,
|
|
1232
|
+
request.signal,
|
|
1233
|
+
request.sandboxPolicy
|
|
1234
|
+
);
|
|
1235
|
+
if (exists.exitCode === 0) return errorResult("STATE_CONFLICT", "\u540C\u540D\u672C\u5730\u5206\u652F\u5DF2\u7ECF\u5B58\u5728", void 0, "BRANCH_EXISTS");
|
|
1236
|
+
if (exists.exitCode !== 1) return errorResult("GIT_FAILED", "\u65E0\u6CD5\u68C0\u67E5\u76EE\u6807\u5206\u652F\u662F\u5426\u5B58\u5728", redactAndLimit(outputOf(exists), 8192));
|
|
1034
1237
|
return this.mutate(request, "git switch -c " + quoteShellArg(name) + " " + quoteShellArg(base), false, "\u521B\u5EFA\u5206\u652F\u5931\u8D25");
|
|
1035
1238
|
}
|
|
1036
1239
|
async switchBranch(request, name) {
|
|
@@ -1066,7 +1269,13 @@ var init_git_repository_service = __esm({
|
|
|
1066
1269
|
}
|
|
1067
1270
|
return { ok: true, data: paths };
|
|
1068
1271
|
}
|
|
1069
|
-
|
|
1272
|
+
mutate(request, command, requiresStagedContent = false, failureMessage = "Git \u64CD\u4F5C\u5931\u8D25") {
|
|
1273
|
+
return this.mutateAndRead(request, command, failureMessage, requiresStagedContent, () => this.getSummary(request.workdir, request.signal, request.sandboxPolicy));
|
|
1274
|
+
}
|
|
1275
|
+
mutateSync(request, command, failureMessage) {
|
|
1276
|
+
return this.mutateAndRead(request, command, failureMessage, false, () => this.getSyncState(request.workdir, request.signal, request.sandboxPolicy));
|
|
1277
|
+
}
|
|
1278
|
+
async mutateAndRead(request, command, failureMessage, requiresStagedContent, readResult) {
|
|
1070
1279
|
if (!request.sessionId || !request.workdir) return errorResult("SESSION_NOT_FOUND", "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u4F1A\u8BDD\u7684\u4ED3\u5E93\u76EE\u5F55");
|
|
1071
1280
|
if (typeof request.operationId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(request.operationId)) {
|
|
1072
1281
|
return errorResult("INVALID_ARGUMENT", "operationId \u5FC5\u987B\u662F 1\u2013128 \u4E2A\u5B89\u5168\u5B57\u7B26");
|
|
@@ -1095,8 +1304,8 @@ var init_git_repository_service = __esm({
|
|
|
1095
1304
|
}
|
|
1096
1305
|
const executed = await this.run(request.workdir, command, 12e4, MUTATION_OUTPUT_MAX_CHARS, request.signal, request.sandboxPolicy);
|
|
1097
1306
|
if (executed.exitCode !== 0) return errorResult(mutationErrorCode(executed), failureMessage, redactAndLimit(outputOf(executed), 8192));
|
|
1098
|
-
const
|
|
1099
|
-
return
|
|
1307
|
+
const result2 = await readResult();
|
|
1308
|
+
return result2.ok ? { ...result2, operationId: String(request.operationId) } : result2;
|
|
1100
1309
|
} finally {
|
|
1101
1310
|
release();
|
|
1102
1311
|
if (this.locks.get(lockKey) === queued) this.locks.delete(lockKey);
|
|
@@ -1330,10 +1539,28 @@ var require_plugin = __commonJS({
|
|
|
1330
1539
|
const failedStep = proposal.steps.find((step) => step.result?.ok === false);
|
|
1331
1540
|
const lastResult = failedStep ? failedStep.result : last?.result;
|
|
1332
1541
|
const diagnostics = ok ? "" : await captureDiagnostics(shell, proposal.workdir);
|
|
1542
|
+
const error = ok ? "" : redactSecrets(lastResult && (lastResult.stderr || lastResult.stdout) || "git \u9000\u51FA\u7801 " + (lastResult ? lastResult.exitCode : -1));
|
|
1543
|
+
const failure = ok ? void 0 : {
|
|
1544
|
+
source: "proposal",
|
|
1545
|
+
code: lastResult?.timedOut === true ? "TIMEOUT" : "GIT_FAILED",
|
|
1546
|
+
action: "execute",
|
|
1547
|
+
command: failedStep?.command || proposal.command,
|
|
1548
|
+
message: error,
|
|
1549
|
+
stdout: redactSecrets(lastResult ? lastResult.stdout : ""),
|
|
1550
|
+
stderr: redactSecrets(lastResult ? lastResult.stderr : ""),
|
|
1551
|
+
diagnostics,
|
|
1552
|
+
exitCode: lastResult ? lastResult.exitCode : null,
|
|
1553
|
+
timedOut: lastResult?.timedOut === true,
|
|
1554
|
+
mayHavePartialChanges: proposal.steps.some((step) => step !== failedStep && step.result?.ok === true),
|
|
1555
|
+
occurredAt: Date.now()
|
|
1556
|
+
};
|
|
1557
|
+
proposal.failure = failure;
|
|
1333
1558
|
const recovery = ok ? null : buildRecovery(proposal, failedStep, diagnostics);
|
|
1334
|
-
if (recovery && recovery.command) {
|
|
1335
|
-
const corrected = registerRecoveryProposal(proposal, recovery);
|
|
1559
|
+
if (recovery && recovery.command && failure) {
|
|
1560
|
+
const corrected = registerRecoveryProposal(proposal, recovery, failure);
|
|
1336
1561
|
if (corrected) recovery.proposalId = corrected.proposalId;
|
|
1562
|
+
} else if (!ok && failure) {
|
|
1563
|
+
proposal.needsAgentAnalysis = true;
|
|
1337
1564
|
}
|
|
1338
1565
|
if (typeof persistStatus === "function") await persistStatus();
|
|
1339
1566
|
return {
|
|
@@ -1347,14 +1574,19 @@ var require_plugin = __commonJS({
|
|
|
1347
1574
|
stdout: redactSecrets(lastResult ? lastResult.stdout : ""),
|
|
1348
1575
|
stderr: redactSecrets(lastResult ? lastResult.stderr : ""),
|
|
1349
1576
|
diagnostics,
|
|
1577
|
+
...failure ? { failure } : {},
|
|
1350
1578
|
recovery: recovery ? { suggestion: recovery.suggestion, command: recovery.command || "", proposalId: recovery.proposalId || null } : null,
|
|
1351
|
-
|
|
1579
|
+
...!ok && failure && !recovery?.command ? { analysis: { proposalId: proposal.proposalId } } : {},
|
|
1580
|
+
error
|
|
1352
1581
|
};
|
|
1353
1582
|
}
|
|
1354
1583
|
function buildRecovery(_proposal, failedStep, diagnostics) {
|
|
1355
1584
|
if (!failedStep || !failedStep.result) return null;
|
|
1356
|
-
const text = String(((failedStep.result.stderr || "") + " " + (failedStep.result.stdout || "") + " " + (diagnostics || "")).trim());
|
|
1357
1585
|
const cmd = String(failedStep.command || "");
|
|
1586
|
+
const text = String(((failedStep.result.stderr || "") + " " + (failedStep.result.stdout || "") + " " + (diagnostics || "")).trim());
|
|
1587
|
+
return buildRecoveryForCommand(cmd, text);
|
|
1588
|
+
}
|
|
1589
|
+
function buildRecoveryForCommand(cmd, text, reason = "") {
|
|
1358
1590
|
if (/只读文件系统|read-only file system|EROFS|cannot lock ref|cannot create .*\.lock/i.test(text)) {
|
|
1359
1591
|
return { suggestion: "\u6267\u884C\u73AF\u5883\u5BF9\u76EE\u6807\u76EE\u5F55\u53EA\u8BFB\uFF08\u6C99\u7BB1\u7B56\u7565\u6216\u6302\u8F7D\u95EE\u9898\uFF09\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u624B\u52A8\u6267\u884C\u8BE5\u547D\u4EE4\uFF0C\u6216\u8C03\u6574\u6267\u884C\u73AF\u5883\u7684\u6C99\u7BB1\u6743\u9650\u3002", command: null };
|
|
1360
1592
|
}
|
|
@@ -1362,11 +1594,13 @@ var require_plugin = __commonJS({
|
|
|
1362
1594
|
const corrected = cmd.replace(/git\s+add\s+/, "git add -f ");
|
|
1363
1595
|
if (corrected !== cmd) return { suggestion: "\u76EE\u6807\u6587\u4EF6\u88AB .gitignore \u5FFD\u7565\uFF1A\u6539\u7528 -f \u5F3A\u5236\u52A0\u5165\uFF08\u4EC5\u9488\u5BF9\u660E\u786E\u5217\u51FA\u7684\u6587\u4EF6\uFF09\u3002", command: corrected };
|
|
1364
1596
|
}
|
|
1365
|
-
if (/already
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1597
|
+
if (reason === "BRANCH_EXISTS" || /already exist(?:s)?|分支.*已(?:经)?存在/i.test(text)) {
|
|
1598
|
+
const parsed = parseCommand(cmd);
|
|
1599
|
+
const createFlag = parsed.ok ? parsed.args.findIndex((argument) => argument === "-c" || argument === "-b") : -1;
|
|
1600
|
+
const branch = parsed.ok && createFlag >= 2 ? parsed.args[createFlag + 1] : void 0;
|
|
1601
|
+
if (branch) {
|
|
1602
|
+
const corrected = "git switch " + quoteShellArg(branch);
|
|
1603
|
+
return { suggestion: "\u5206\u652F " + branch + " \u5DF2\u5B58\u5728\uFF1A\u6539\u4E3A\u5207\u6362\u5230\u73B0\u6709\u5206\u652F\uFF08\u6216\u6362\u4E00\u4E2A\u5206\u652F\u540D\uFF09\u3002", command: corrected };
|
|
1370
1604
|
}
|
|
1371
1605
|
}
|
|
1372
1606
|
if (/not a git repository|不是.*git 仓库|不是一个 git 仓库/i.test(text)) {
|
|
@@ -1389,7 +1623,51 @@ var require_plugin = __commonJS({
|
|
|
1389
1623
|
}
|
|
1390
1624
|
return null;
|
|
1391
1625
|
}
|
|
1392
|
-
function
|
|
1626
|
+
async function recoverFailedCommand(activeShell, sessionId, workdir, operationId, action, command, message, errorOutput, errorCode, reason) {
|
|
1627
|
+
const operationKey = workdir + "\0" + operationId;
|
|
1628
|
+
const existing = proposalService.list(sessionId)?.find((proposal2) => proposal2.recoveryOperationKey === operationKey);
|
|
1629
|
+
if (existing) {
|
|
1630
|
+
if (!existing.failure) return null;
|
|
1631
|
+
if (existing.needsAgentAnalysis === true) return { failure: existing.failure, analysis: { proposalId: existing.proposalId } };
|
|
1632
|
+
if (existing.closed || existing.status !== "pending") return { failure: existing.failure };
|
|
1633
|
+
return { failure: existing.failure, recovery: {
|
|
1634
|
+
suggestion: String(existing.recoverySuggestion || existing.explanation),
|
|
1635
|
+
command: existing.command,
|
|
1636
|
+
proposalId: existing.proposalId
|
|
1637
|
+
} };
|
|
1638
|
+
}
|
|
1639
|
+
const diagnostics = await captureDiagnostics(activeShell, workdir);
|
|
1640
|
+
const failure = {
|
|
1641
|
+
source: "workbench",
|
|
1642
|
+
code: errorCode,
|
|
1643
|
+
action,
|
|
1644
|
+
command,
|
|
1645
|
+
message,
|
|
1646
|
+
stdout: "",
|
|
1647
|
+
stderr: redactSecrets(errorOutput),
|
|
1648
|
+
diagnostics,
|
|
1649
|
+
exitCode: null,
|
|
1650
|
+
timedOut: errorCode === "TIMEOUT",
|
|
1651
|
+
mayHavePartialChanges: reason !== "BRANCH_EXISTS",
|
|
1652
|
+
occurredAt: Date.now()
|
|
1653
|
+
};
|
|
1654
|
+
const recovery = buildRecoveryForCommand(command, message + "\n" + errorOutput + "\n" + diagnostics, reason);
|
|
1655
|
+
if (!recovery?.command) {
|
|
1656
|
+
const failed = registerFailureProposal(sessionId, workdir, command, failure);
|
|
1657
|
+
if (failed) {
|
|
1658
|
+
failed.recoveryOperationKey = operationKey;
|
|
1659
|
+
await proposalService.flush(sessionId);
|
|
1660
|
+
}
|
|
1661
|
+
return { failure, ...failed ? { analysis: { proposalId: failed.proposalId } } : {} };
|
|
1662
|
+
}
|
|
1663
|
+
const proposal = registerRecoveryProposal({ sessionId, workdir }, recovery, failure);
|
|
1664
|
+
if (!proposal) return { failure };
|
|
1665
|
+
proposal.recoveryOperationKey = operationKey;
|
|
1666
|
+
proposal.recoverySuggestion = recovery.suggestion;
|
|
1667
|
+
await proposalService.flush(sessionId);
|
|
1668
|
+
return { failure, recovery: { suggestion: recovery.suggestion, command: recovery.command, proposalId: proposal.proposalId } };
|
|
1669
|
+
}
|
|
1670
|
+
function registerRecoveryProposal(failedProposal, recovery, failure) {
|
|
1393
1671
|
if (!recovery.command) return null;
|
|
1394
1672
|
const v = validateCommand(recovery.command);
|
|
1395
1673
|
if (!v.ok) return null;
|
|
@@ -1403,7 +1681,7 @@ var require_plugin = __commonJS({
|
|
|
1403
1681
|
intent: "\u4FEE\u6B63\u5EFA\u8BAE\uFF1A" + recovery.suggestion,
|
|
1404
1682
|
command: recovery.command.trim(),
|
|
1405
1683
|
steps: [{ command: recovery.command.trim(), result: null }],
|
|
1406
|
-
explanation: recovery.suggestion + "\uFF08\u7531\u6267\u884C\u5931\u8D25\u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u786E\u8BA4\u540E\u6267\u884C\uFF09",
|
|
1684
|
+
explanation: failure ? "\u539F\u547D\u4EE4\uFF1A" + failure.command + "\n\u9519\u8BEF\uFF1A" + (failure.stderr || failure.message) + "\n\u4FEE\u6B63\u539F\u56E0\uFF1A" + recovery.suggestion : recovery.suggestion + "\uFF08\u7531\u6267\u884C\u5931\u8D25\u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u786E\u8BA4\u540E\u6267\u884C\uFF09",
|
|
1407
1685
|
risk: risk.level,
|
|
1408
1686
|
reasons: risk.reasons,
|
|
1409
1687
|
confirmed: false,
|
|
@@ -1415,11 +1693,43 @@ var require_plugin = __commonJS({
|
|
|
1415
1693
|
fingerprint: null,
|
|
1416
1694
|
verified: false,
|
|
1417
1695
|
status: "pending",
|
|
1418
|
-
recovery: true
|
|
1696
|
+
recovery: true,
|
|
1697
|
+
recoverySuggestion: recovery.suggestion,
|
|
1698
|
+
...failure ? { failure } : {}
|
|
1699
|
+
};
|
|
1700
|
+
proposalService.closeOpen(sessionId);
|
|
1701
|
+
storeProposal(sessionId, proposal);
|
|
1702
|
+
console.log("easygit \u4FEE\u6B63\u5EFA\u8BAE\u767B\u8BB0", proposal.proposalId, "session=", sessionId, "risk=", risk.level);
|
|
1703
|
+
return proposal;
|
|
1704
|
+
}
|
|
1705
|
+
function registerFailureProposal(sessionId, workdir, command, failure) {
|
|
1706
|
+
if (proposalService.hasRunning(sessionId)) return null;
|
|
1707
|
+
const risk = classifyRisk(command);
|
|
1708
|
+
const proposal = {
|
|
1709
|
+
proposalId: proposalService.newId(),
|
|
1710
|
+
sessionId,
|
|
1711
|
+
intent: "Git \u64CD\u4F5C\u5931\u8D25\uFF0C\u7B49\u5F85\u5206\u6790",
|
|
1712
|
+
command,
|
|
1713
|
+
steps: [{ command, result: { ok: false, stderr: failure.stderr, stdout: failure.stdout, exitCode: failure.exitCode } }],
|
|
1714
|
+
explanation: "\u539F\u547D\u4EE4\uFF1A" + command + "\n\u9519\u8BEF\uFF1A" + (failure.stderr || failure.message),
|
|
1715
|
+
risk: risk.level,
|
|
1716
|
+
reasons: risk.reasons,
|
|
1717
|
+
confirmed: false,
|
|
1718
|
+
workdir,
|
|
1719
|
+
createdAt: failure.occurredAt,
|
|
1720
|
+
result: { ok: false, error: failure.message },
|
|
1721
|
+
closed: false,
|
|
1722
|
+
copied: false,
|
|
1723
|
+
fingerprint: null,
|
|
1724
|
+
verified: false,
|
|
1725
|
+
status: "failed",
|
|
1726
|
+
failure,
|
|
1727
|
+
recovery: true,
|
|
1728
|
+
needsAgentAnalysis: true
|
|
1419
1729
|
};
|
|
1420
1730
|
proposalService.closeOpen(sessionId);
|
|
1421
1731
|
storeProposal(sessionId, proposal);
|
|
1422
|
-
console.log("
|
|
1732
|
+
console.log("easygit \u5931\u8D25\u4E0A\u4E0B\u6587\u767B\u8BB0", proposal.proposalId, "session=", sessionId);
|
|
1423
1733
|
return proposal;
|
|
1424
1734
|
}
|
|
1425
1735
|
function storeProposal(sessionId, proposal) {
|
|
@@ -1447,7 +1757,7 @@ var require_plugin = __commonJS({
|
|
|
1447
1757
|
const backend = storage.backend.get("json");
|
|
1448
1758
|
if (!backend || !backend.kv || typeof backend.kv.open !== "function") throw new Error("JSON storage backend does not support key-value units");
|
|
1449
1759
|
unit = await backend.kv.open({
|
|
1450
|
-
name: "
|
|
1760
|
+
name: "easygit_proposals",
|
|
1451
1761
|
version: 1,
|
|
1452
1762
|
tables: ["proposals"],
|
|
1453
1763
|
hasGlobal: false
|
|
@@ -1460,7 +1770,7 @@ var require_plugin = __commonJS({
|
|
|
1460
1770
|
return ready;
|
|
1461
1771
|
}
|
|
1462
1772
|
var plugin2 = {
|
|
1463
|
-
name: "
|
|
1773
|
+
name: "easygit",
|
|
1464
1774
|
inject: ["shell", "tools"],
|
|
1465
1775
|
apply(ctx) {
|
|
1466
1776
|
const shell = ctx.get("shell");
|
|
@@ -1550,6 +1860,7 @@ var require_plugin = __commonJS({
|
|
|
1550
1860
|
if (prev && prev.some((proposal2) => proposal2.status === "running")) {
|
|
1551
1861
|
return { ok: false, proposalId: "", intent: String(args.intent || ""), command: "", steps: [], explanation: String(args.explanation || ""), risk: risk.level, reasons: risk.reasons, workdir: workdir || "", error: "\u540C\u4E00\u4F1A\u8BDD\u5DF2\u6709\u63D0\u8BAE\u6B63\u5728\u6267\u884C\uFF0C\u8BF7\u7B49\u5F85\u6267\u884C\u7ED3\u675F\u540E\u518D\u521B\u5EFA\u65B0\u63D0\u8BAE" };
|
|
1552
1862
|
}
|
|
1863
|
+
const analysisSource = prev?.find((candidate) => !candidate.closed && candidate.needsAgentAnalysis === true && typeof candidate.analysisRequestedAt === "number" && !!candidate.failure);
|
|
1553
1864
|
proposalService.closeOpen(sessionId);
|
|
1554
1865
|
const proposal = {
|
|
1555
1866
|
proposalId: proposalService.newId(),
|
|
@@ -1568,7 +1879,13 @@ var require_plugin = __commonJS({
|
|
|
1568
1879
|
copied: false,
|
|
1569
1880
|
fingerprint: null,
|
|
1570
1881
|
verified: false,
|
|
1571
|
-
status: "pending"
|
|
1882
|
+
status: "pending",
|
|
1883
|
+
...analysisSource?.failure ? {
|
|
1884
|
+
failure: analysisSource.failure,
|
|
1885
|
+
recovery: true,
|
|
1886
|
+
recoverySuggestion: String(args.explanation || ""),
|
|
1887
|
+
analyzedFailureProposalId: analysisSource.proposalId
|
|
1888
|
+
} : {}
|
|
1572
1889
|
};
|
|
1573
1890
|
storeProposal(sessionId, proposal);
|
|
1574
1891
|
await proposalService.flush(sessionId);
|
|
@@ -1645,7 +1962,7 @@ var require_plugin = __commonJS({
|
|
|
1645
1962
|
}
|
|
1646
1963
|
});
|
|
1647
1964
|
}
|
|
1648
|
-
const registerWebServer = (webServer) =>
|
|
1965
|
+
const registerWebServer = (webServer) => registerEasyGitActions(webServer, {
|
|
1649
1966
|
repository,
|
|
1650
1967
|
proposalStorageReady,
|
|
1651
1968
|
shell,
|
|
@@ -1658,6 +1975,18 @@ var require_plugin = __commonJS({
|
|
|
1658
1975
|
runChecks,
|
|
1659
1976
|
verifyProposal,
|
|
1660
1977
|
executeProposal: (activeShell, proposal, policy, persist) => executeRegisteredProposal(activeShell, proposal, void 0, policy, persist),
|
|
1978
|
+
recoverFailedCommand: (sessionId, workdir, operationId, action, command, message, errorOutput, errorCode, reason) => recoverFailedCommand(
|
|
1979
|
+
shell,
|
|
1980
|
+
sessionId,
|
|
1981
|
+
workdir,
|
|
1982
|
+
operationId,
|
|
1983
|
+
action,
|
|
1984
|
+
command,
|
|
1985
|
+
message,
|
|
1986
|
+
errorOutput,
|
|
1987
|
+
errorCode,
|
|
1988
|
+
reason
|
|
1989
|
+
),
|
|
1661
1990
|
resolveExecutionPolicy: (sessionId) => {
|
|
1662
1991
|
const agents = ctx.get("agents");
|
|
1663
1992
|
const agent = agents ? agents.get(sessionId) : void 0;
|
|
@@ -1689,12 +2018,15 @@ var require_plugin = __commonJS({
|
|
|
1689
2018
|
executeProposalSteps,
|
|
1690
2019
|
executeRegisteredProposal,
|
|
1691
2020
|
buildRecovery,
|
|
2021
|
+
buildRecoveryForCommand,
|
|
2022
|
+
recoverFailedCommand,
|
|
1692
2023
|
registerRecoveryProposal,
|
|
1693
2024
|
storeProposal,
|
|
1694
2025
|
findProposal,
|
|
1695
2026
|
proposalView,
|
|
1696
2027
|
latestPending,
|
|
1697
|
-
ProposalService
|
|
2028
|
+
ProposalService,
|
|
2029
|
+
GitRepositoryService
|
|
1698
2030
|
};
|
|
1699
2031
|
module2.exports = Object.assign(plugin2, { helpers });
|
|
1700
2032
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createPanelController } from './panel-controller';
|
|
2
|
-
import { appendCommandLog, beginTrackedRequest, buildFileTree, cancelTrackedRequest, clampWorkbenchRatio, commitFileTone, deriveCommitGraph, filterLocalBranches, isCurrentCommitRequest, isLatestRequest, isTrackedRequestCurrent, mutationCommand, nextCommitSelection, parseReviewRows, repositoryName, type AnyRecord } from './view-model';
|
|
2
|
+
import { appendCommandLog, analysisProposalId, beginTrackedRequest, buildAgentRepairPrompt, canDismissFailedProposal, buildFileTree, cancelTrackedRequest, clampWorkbenchRatio, commitFileTone, deriveCommitGraph, filterLocalBranches, failureContext, isCurrentCommitRequest, isLatestRequest, isTrackedRequestCurrent, mutationCommand, nextCommitSelection, openRecoveryProposal, parseReviewRows, pendingProposalTransition, recoveryProposalId, repositoryName, shouldShowAnalysisBanner, type AnyRecord } from './view-model';
|
|
3
3
|
type RefreshState = 'idle' | 'loading' | 'succeeded' | 'failed';
|
|
4
4
|
declare function injectStyles(): () => void;
|
|
5
5
|
declare function refreshButtonLabel(state: RefreshState): string;
|
|
@@ -22,6 +22,14 @@ declare const plugin: {
|
|
|
22
22
|
mutationCommand: typeof mutationCommand;
|
|
23
23
|
appendCommandLog: typeof appendCommandLog;
|
|
24
24
|
refreshButtonLabel: typeof refreshButtonLabel;
|
|
25
|
+
recoveryProposalId: typeof recoveryProposalId;
|
|
26
|
+
openRecoveryProposal: typeof openRecoveryProposal;
|
|
27
|
+
analysisProposalId: typeof analysisProposalId;
|
|
28
|
+
failureContext: typeof failureContext;
|
|
29
|
+
buildAgentRepairPrompt: typeof buildAgentRepairPrompt;
|
|
30
|
+
shouldShowAnalysisBanner: typeof shouldShowAnalysisBanner;
|
|
31
|
+
canDismissFailedProposal: typeof canDismissFailedProposal;
|
|
32
|
+
pendingProposalTransition: typeof pendingProposalTransition;
|
|
25
33
|
isCurrentCommitRequest: typeof isCurrentCommitRequest;
|
|
26
34
|
nextCommitSelection: typeof nextCommitSelection;
|
|
27
35
|
commitFileTone: typeof commitFileTone;
|