@p4code/cli 0.1.9 → 0.1.11
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/dist/bin.mjs +1708 -1377
- package/dist/client/assets/{DiffPanel-Bb72saoS.js → DiffPanel-1GboPEhP.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-6mJ-Vsub.js → FilePreviewPanel-BZACyKlO.js} +2 -2
- package/dist/client/assets/{PreviewPanel-ogPpU8Ev.js → PreviewPanel-By-_LFU1.js} +2 -2
- package/dist/client/assets/{angular-html-BIzUKsg7.js → angular-html-DWf2nx2U.js} +2 -2
- package/dist/client/assets/{angular-ts-CSIA-4Zf.js → angular-ts-CIx5TGhT.js} +2 -2
- package/dist/client/assets/{apl-Du1uuPDy.js → apl-DafQ1-an.js} +2 -2
- package/dist/client/assets/arrow-right-qu8MQub2.js +2 -0
- package/dist/client/assets/{astro-bVpCfC8I.js → astro-Bep3tY5t.js} +2 -2
- package/dist/client/assets/{blade-KOd_hdA5.js → blade-DCuMesAS.js} +2 -2
- package/dist/client/assets/{c-BUOUjYKH.js → c-DZs6Etpe.js} +2 -2
- package/dist/client/assets/{cobol-DVr6VPEj.js → cobol-DFZb1h1z.js} +2 -2
- package/dist/client/assets/{coffee-CaWTiLWu.js → coffee-CR0gjEco.js} +2 -2
- package/dist/client/assets/{cpp-DOVxTtfq.js → cpp-U48aiClT.js} +2 -2
- package/dist/client/assets/{crystal-D_Q817Qu.js → crystal-CNaWREX9.js} +2 -2
- package/dist/client/assets/{css-Rez0Ry86.js → css-DQTD4ObL.js} +2 -2
- package/dist/client/assets/{edge-C5CKZJyo.js → edge-CGgCvH7u.js} +2 -2
- package/dist/client/assets/{elixir-D_eVXxgH.js → elixir-QEF-Vb-O.js} +2 -2
- package/dist/client/assets/{elm-BLkMkUJ0.js → elm-UIYYfgyY.js} +2 -2
- package/dist/client/assets/{erb-DGG0CxNR.js → erb-CObSr7xt.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-D0gz9Nvl.js → fileCommentAnnotations-BQPQAflx.js} +2 -2
- package/dist/client/assets/{git-rebase-D7H5ArnN.js → git-rebase-D-Kfui1X.js} +2 -2
- package/dist/client/assets/{glimmer-js-DOuFsyYy.js → glimmer-js-Bj7zsPRR.js} +2 -2
- package/dist/client/assets/{glimmer-ts-C_dgMrVH.js → glimmer-ts-BsEDRgeG.js} +2 -2
- package/dist/client/assets/{glsl-BJU0wvLz.js → glsl-D98j42v3.js} +2 -2
- package/dist/client/assets/{graphql-CXUg7dVm.js → graphql-B1Bl3yQr.js} +2 -2
- package/dist/client/assets/{hack-DEwTpBBH.js → hack-C5SdJhmY.js} +2 -2
- package/dist/client/assets/{haml-Br-Q1jfj.js → haml-fOHEdRRk.js} +2 -2
- package/dist/client/assets/{handlebars-U-qCMjLv.js → handlebars-BprpyHwi.js} +2 -2
- package/dist/client/assets/{html-CBhkruiu.js → html-DY94fJQ8.js} +2 -2
- package/dist/client/assets/{html-derivative-CtOWonuc.js → html-derivative-BijLMkmB.js} +2 -2
- package/dist/client/assets/{http-B-anb7K2.js → http-DgLF_F-Y.js} +2 -2
- package/dist/client/assets/{hurl-UbSvk6zv.js → hurl-Lf_gtANJ.js} +2 -2
- package/dist/client/assets/index-BWiBk__A.js +660 -0
- package/dist/client/assets/index-nkPMSYxq.css +1 -0
- package/dist/client/assets/{java-d4Ae9Kq8.js → java-CvbaU0rW.js} +2 -2
- package/dist/client/assets/{javascript-DENutVTd.js → javascript-Y_90CTbK.js} +2 -2
- package/dist/client/assets/{jinja-Bugh_6jh.js → jinja-Dz3cSyss.js} +2 -2
- package/dist/client/assets/{jison-5v-Z1oHG.js → jison-Cy5HwlPH.js} +2 -2
- package/dist/client/assets/{json-BhFmIQh2.js → json-BX9adxsl.js} +2 -2
- package/dist/client/assets/{jsx-ByAWu7n0.js → jsx-uMzR21MU.js} +2 -2
- package/dist/client/assets/{julia-DE4JKJ_I.js → julia-BfKnOvts.js} +2 -2
- package/dist/client/assets/{just-Ck-bQzBW.js → just-gPqomEDo.js} +2 -2
- package/dist/client/assets/{latex-0aopGn_u.js → latex-HAmg84zi.js} +2 -2
- package/dist/client/assets/{liquid-BOV6YY-4.js → liquid-CAfKVpt2.js} +2 -2
- package/dist/client/assets/{lua-BS79SG5v.js → lua-MJ-soRMY.js} +2 -2
- package/dist/client/assets/{marko-CxCHVwvS.js → marko-Bk2APM9P.js} +2 -2
- package/dist/client/assets/{mdc-D8ArMX_3.js → mdc-D-ETA9xK.js} +2 -2
- package/dist/client/assets/{nginx-54lPKP4l.js → nginx-NwHNwVvi.js} +2 -2
- package/dist/client/assets/{nim-CQW1n-W6.js → nim-uboMGtnG.js} +2 -2
- package/dist/client/assets/{perl-TGEf9BSy.js → perl-DZO_qrRW.js} +2 -2
- package/dist/client/assets/{php-CRSs0ljw.js → php-BudgHX29.js} +2 -2
- package/dist/client/assets/{pug-B94qB-T3.js → pug-1FhoiZN2.js} +2 -2
- package/dist/client/assets/{qml-Ce-jpaqY.js → qml-B6ycgh-7.js} +2 -2
- package/dist/client/assets/{r-Di-hq60i.js → r-M2HClZBy.js} +2 -2
- package/dist/client/assets/{razor-VXC_c_VY.js → razor-EiO_nOGU.js} +2 -2
- package/dist/client/assets/{regexp-DCuWLst8.js → regexp-BF12zF9R.js} +2 -2
- package/dist/client/assets/{rst-CaleKCxt.js → rst-BW8dKb0T.js} +2 -2
- package/dist/client/assets/{ruby-BNvjj0Xx.js → ruby-BcJiTtn_.js} +2 -2
- package/dist/client/assets/{sas-CN3Jyhxd.js → sas--CMYy3z3.js} +2 -2
- package/dist/client/assets/{scss-iGZoIRyN.js → scss-CBaO992p.js} +2 -2
- package/dist/client/assets/{shellscript-B3Nk58br.js → shellscript-IGSv-HCQ.js} +2 -2
- package/dist/client/assets/{shellsession-aV0EL5Me.js → shellsession-tY9I_iRt.js} +2 -2
- package/dist/client/assets/{soy-CK-MnjGK.js → soy-BvSCSsmo.js} +2 -2
- package/dist/client/assets/{sql-WiqY-pg6.js → sql-BuK6MJNF.js} +2 -2
- package/dist/client/assets/{stata-L2KCRIsj.js → stata-VS9vYyft.js} +2 -2
- package/dist/client/assets/{surrealql-y8rFKpHp.js → surrealql-D4pAe4SW.js} +2 -2
- package/dist/client/assets/{svelte-BUjzGJjC.js → svelte-05AEGqMV.js} +2 -2
- package/dist/client/assets/{templ-Lwa6_2Pi.js → templ-amlaVpnD.js} +2 -2
- package/dist/client/assets/{tex-C6_2o8N6.js → tex-B8w-z73a.js} +2 -2
- package/dist/client/assets/textarea-sGc0UkUx.js +46 -0
- package/dist/client/assets/{ts-tags-DHGXrdlY.js → ts-tags-CWOgRloL.js} +2 -2
- package/dist/client/assets/{tsx-D7a2s_rX.js → tsx-DcSMCm_G.js} +2 -2
- package/dist/client/assets/{twig-rodlH6R_.js → twig-BoFkWRc3.js} +2 -2
- package/dist/client/assets/{typescript-BSVnlc3E.js → typescript-CnosOGRY.js} +2 -2
- package/dist/client/assets/{vue-CPqhBG4f.js → vue-BkQYyvUc.js} +2 -2
- package/dist/client/assets/{vue-html-DlcYUp9j.js → vue-html-DQkUheyK.js} +2 -2
- package/dist/client/assets/{vue-vine-C33vB5eU.js → vue-vine-PP4GiTEY.js} +2 -2
- package/dist/client/assets/{xml-Dk7HbObH.js → xml-B3yPYALE.js} +2 -2
- package/dist/client/assets/{xsl-Ce_0ufBe.js → xsl-Cgr4RWex.js} +2 -2
- package/dist/client/assets/{yaml-hWCzaJrB.js → yaml-B7fPykX3.js} +2 -2
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/arrow-right-qNd2lFlZ.js +0 -2
- package/dist/client/assets/index-CADZUNMb.js +0 -664
- package/dist/client/assets/index-CPnR_Tju.css +0 -1
- package/dist/client/assets/textarea-C26NLrE0.js +0 -46
package/dist/bin.mjs
CHANGED
|
@@ -233,7 +233,7 @@ const make$73 = () => {
|
|
|
233
233
|
const layer$69 = Layer.sync(NetService, make$73);
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region package.json
|
|
236
|
-
var version = "0.1.
|
|
236
|
+
var version = "0.1.11";
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region src/config.ts
|
|
239
239
|
/**
|
|
@@ -5557,8 +5557,6 @@ const ClientSettingsSchema = Schema$1.Struct({
|
|
|
5557
5557
|
sidebarProjectSortOrder: SidebarProjectSortOrder.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_SORT_ORDER))),
|
|
5558
5558
|
sidebarThreadSortOrder: SidebarThreadSortOrder.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER))),
|
|
5559
5559
|
sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe(Schema$1.withDecodingDefault(Effect.succeed(6))),
|
|
5560
|
-
sidebarV2Enabled: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
|
|
5561
|
-
sidebarV2ConfiguredByUser: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
|
|
5562
5560
|
timestampFormat: TimestampFormat.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT))),
|
|
5563
5561
|
wordWrap: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true)))
|
|
5564
5562
|
});
|
|
@@ -5905,8 +5903,6 @@ Schema$1.Struct({
|
|
|
5905
5903
|
sidebarProjectSortOrder: Schema$1.optionalKey(SidebarProjectSortOrder),
|
|
5906
5904
|
sidebarThreadSortOrder: Schema$1.optionalKey(SidebarThreadSortOrder),
|
|
5907
5905
|
sidebarThreadPreviewCount: Schema$1.optionalKey(SidebarThreadPreviewCount),
|
|
5908
|
-
sidebarV2Enabled: Schema$1.optionalKey(Schema$1.Boolean),
|
|
5909
|
-
sidebarV2ConfiguredByUser: Schema$1.optionalKey(Schema$1.Boolean),
|
|
5910
5906
|
timestampFormat: Schema$1.optionalKey(TimestampFormat),
|
|
5911
5907
|
wordWrap: Schema$1.optionalKey(Schema$1.Boolean)
|
|
5912
5908
|
});
|
|
@@ -7542,6 +7538,84 @@ var ProjectWriteFileError = class extends Schema$1.TaggedErrorClass()("ProjectWr
|
|
|
7542
7538
|
}
|
|
7543
7539
|
};
|
|
7544
7540
|
//#endregion
|
|
7541
|
+
//#region ../../packages/contracts/src/threadControl.ts
|
|
7542
|
+
/**
|
|
7543
|
+
* Thread control over MCP - the self-scoped toolkit an agent uses to act on
|
|
7544
|
+
* the thread it is running in.
|
|
7545
|
+
*
|
|
7546
|
+
* Deliberately narrow. The reported need was an agent renaming its own thread;
|
|
7547
|
+
* that needs no cross-thread reach, and the per-session bearer token is the
|
|
7548
|
+
* only thing guarding `/mcp` on a remote-reachable server, so a capability
|
|
7549
|
+
* that could rename arbitrary threads would be a capability to vandalise the
|
|
7550
|
+
* workspace from one leaked token. Delete and create are withheld outright,
|
|
7551
|
+
* the same answer the task toolkit gave. Settle is absent for a structural
|
|
7552
|
+
* reason rather than a policy one: the decider refuses `thread.settle` while
|
|
7553
|
+
* the thread's session is starting or running, and a self-scoped MCP call
|
|
7554
|
+
* always arrives from its own live session, so the call could never succeed.
|
|
7555
|
+
*
|
|
7556
|
+
* Quick-append to memory lives here too: it is the other thing an agent wants
|
|
7557
|
+
* to do *from* a thread, and it shares the toolkit's capability and scope.
|
|
7558
|
+
*
|
|
7559
|
+
* @module contracts/threadControl
|
|
7560
|
+
*/
|
|
7561
|
+
const ThreadRenameInput = Schema$1.Struct({ title: TrimmedNonEmptyString.annotate({ description: "The new thread title. Replaces the current one." }) });
|
|
7562
|
+
const ThreadRenameResult = Schema$1.Struct({
|
|
7563
|
+
threadId: ThreadId,
|
|
7564
|
+
title: TrimmedNonEmptyString
|
|
7565
|
+
});
|
|
7566
|
+
/**
|
|
7567
|
+
* Which memory file an append lands in.
|
|
7568
|
+
*
|
|
7569
|
+
* A closed union rather than a filename, because the target directory also
|
|
7570
|
+
* holds credentials and per-machine settings - the same reason the hub sync's
|
|
7571
|
+
* memory allowlist exists. These two names must stay within that allowlist
|
|
7572
|
+
* (`MEMORY_FILE_NAMES` in `sync/memoryFile.ts`), so an appended file is one
|
|
7573
|
+
* the sync will carry to the user's other machines rather than refuse.
|
|
7574
|
+
*/
|
|
7575
|
+
const MemoryAppendTarget = Schema$1.Literals(["CLAUDE.md", "AGENTS.md"]);
|
|
7576
|
+
const MemoryAppendInput = Schema$1.Struct({
|
|
7577
|
+
text: TrimmedNonEmptyString.annotate({ description: "Markdown to append to the user-scope memory file, as its own block." }),
|
|
7578
|
+
file: Schema$1.optional(MemoryAppendTarget.annotate({ description: "Which memory file to append to. Defaults to CLAUDE.md." }))
|
|
7579
|
+
});
|
|
7580
|
+
const MemoryAppendResult = Schema$1.Struct({
|
|
7581
|
+
file: MemoryAppendTarget,
|
|
7582
|
+
/** Absolute path the append landed in, so the agent can cite or inspect it. */
|
|
7583
|
+
path: Schema$1.String
|
|
7584
|
+
});
|
|
7585
|
+
var ThreadToolUnavailableError = class extends Schema$1.TaggedErrorClass()("ThreadToolUnavailableError", {
|
|
7586
|
+
capability: Schema$1.Literal("threads"),
|
|
7587
|
+
environmentId: EnvironmentId,
|
|
7588
|
+
threadId: ThreadId,
|
|
7589
|
+
providerSessionId: TrimmedNonEmptyString,
|
|
7590
|
+
providerInstanceId: ProviderInstanceId
|
|
7591
|
+
}) {
|
|
7592
|
+
get message() {
|
|
7593
|
+
return `MCP credential does not grant the ${this.capability} capability.`;
|
|
7594
|
+
}
|
|
7595
|
+
};
|
|
7596
|
+
/** The orchestration engine declined or failed the rename command. */
|
|
7597
|
+
var ThreadRenameRejectedError = class extends Schema$1.TaggedErrorClass()("ThreadRenameRejectedError", {
|
|
7598
|
+
threadId: ThreadId,
|
|
7599
|
+
detail: Schema$1.String
|
|
7600
|
+
}) {
|
|
7601
|
+
get message() {
|
|
7602
|
+
return `Renaming thread ${this.threadId} failed: ${this.detail}`;
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
var MemoryAppendFailedError = class extends Schema$1.TaggedErrorClass()("MemoryAppendFailedError", {
|
|
7606
|
+
file: MemoryAppendTarget,
|
|
7607
|
+
detail: Schema$1.String
|
|
7608
|
+
}) {
|
|
7609
|
+
get message() {
|
|
7610
|
+
return `Appending to ${this.file} failed: ${this.detail}`;
|
|
7611
|
+
}
|
|
7612
|
+
};
|
|
7613
|
+
const ThreadControlToolError = Schema$1.Union([
|
|
7614
|
+
ThreadToolUnavailableError,
|
|
7615
|
+
ThreadRenameRejectedError,
|
|
7616
|
+
MemoryAppendFailedError
|
|
7617
|
+
]);
|
|
7618
|
+
//#endregion
|
|
7545
7619
|
//#region ../../packages/contracts/src/agentAsset.ts
|
|
7546
7620
|
/**
|
|
7547
7621
|
* User-scope agent assets — the things that make an agent behave the same on
|
|
@@ -28990,414 +29064,1060 @@ const staticAndDevRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* (
|
|
|
28990
29064
|
contentType
|
|
28991
29065
|
});
|
|
28992
29066
|
}));
|
|
28993
|
-
|
|
28994
|
-
|
|
28995
|
-
|
|
28996
|
-
|
|
28997
|
-
|
|
28998
|
-
operation: CheckpointDiffOperation,
|
|
28999
|
-
threadId: ThreadId
|
|
29000
|
-
}) {
|
|
29001
|
-
get message() {
|
|
29002
|
-
const result = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
|
|
29003
|
-
return `Checkpoint invariant violation in ${this.operation}: Computed ${result} result does not satisfy contract schema.`;
|
|
29004
|
-
}
|
|
29005
|
-
};
|
|
29006
|
-
/** Projection state no longer contains the requested checkpoint thread. */
|
|
29007
|
-
var CheckpointThreadNotFoundError = class extends Schema$1.TaggedErrorClass()("CheckpointThreadNotFoundError", {
|
|
29008
|
-
operation: CheckpointDiffOperation,
|
|
29009
|
-
threadId: ThreadId
|
|
29010
|
-
}) {
|
|
29011
|
-
get message() {
|
|
29012
|
-
return `Checkpoint invariant violation in ${this.operation}: Thread '${this.threadId}' not found.`;
|
|
29013
|
-
}
|
|
29014
|
-
};
|
|
29015
|
-
/** The checkpoint thread has no workspace path from which to compute a diff. */
|
|
29016
|
-
var CheckpointWorkspacePathMissingError = class extends Schema$1.TaggedErrorClass()("CheckpointWorkspacePathMissingError", {
|
|
29017
|
-
operation: CheckpointDiffOperation,
|
|
29018
|
-
threadId: ThreadId
|
|
29019
|
-
}) {
|
|
29020
|
-
get message() {
|
|
29021
|
-
const diff = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
|
|
29022
|
-
return `Checkpoint invariant violation in ${this.operation}: Workspace path missing for thread '${this.threadId}' when computing ${diff}.`;
|
|
29023
|
-
}
|
|
29024
|
-
};
|
|
29025
|
-
/** The requested turn lies beyond the latest available checkpoint. */
|
|
29026
|
-
var CheckpointTurnRangeUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointTurnRangeUnavailableError", {
|
|
29027
|
-
operation: CheckpointDiffOperation,
|
|
29028
|
-
threadId: ThreadId,
|
|
29029
|
-
requestedTurnCount: NonNegativeInt,
|
|
29030
|
-
availableTurnCount: NonNegativeInt
|
|
29031
|
-
}) {
|
|
29032
|
-
get message() {
|
|
29033
|
-
return `Checkpoint unavailable for thread ${this.threadId} turn ${this.requestedTurnCount}: Turn diff range exceeds current turn count: requested ${this.requestedTurnCount}, current ${this.availableTurnCount}.`;
|
|
29034
|
-
}
|
|
29035
|
-
};
|
|
29036
|
-
/** Expected checkpoint metadata does not contain the requested Git ref. */
|
|
29037
|
-
var CheckpointRefUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointRefUnavailableError", {
|
|
29038
|
-
operation: CheckpointDiffOperation,
|
|
29039
|
-
threadId: ThreadId,
|
|
29040
|
-
turnCount: NonNegativeInt,
|
|
29041
|
-
checkpoint: Schema$1.Literals(["from", "to"])
|
|
29042
|
-
}) {
|
|
29043
|
-
get message() {
|
|
29044
|
-
return `Checkpoint unavailable for thread ${this.threadId} turn ${this.turnCount}: Checkpoint ref is unavailable for turn ${this.turnCount}.`;
|
|
29067
|
+
(() => {
|
|
29068
|
+
try {
|
|
29069
|
+
return process.env.NODE_ENV === "development";
|
|
29070
|
+
} catch {
|
|
29071
|
+
return false;
|
|
29045
29072
|
}
|
|
29046
|
-
};
|
|
29047
|
-
|
|
29048
|
-
|
|
29049
|
-
const
|
|
29050
|
-
|
|
29051
|
-
|
|
29052
|
-
|
|
29053
|
-
|
|
29054
|
-
|
|
29055
|
-
|
|
29056
|
-
|
|
29057
|
-
}
|
|
29073
|
+
})();
|
|
29074
|
+
const COMMIT_METADATA_SPLIT = /(?=^From [a-f0-9]+ .+$)/m;
|
|
29075
|
+
const GIT_DIFF_FILE_BREAK_REGEX = /(?=^diff --git)/gm;
|
|
29076
|
+
const FILENAME_HEADER_REGEX = /^(---|\+\+\+)\s+([^\t\r\n]+)/;
|
|
29077
|
+
const FILENAME_HEADER_REGEX_GIT = /^(---|\+\+\+)\s+[ab]\/([^\t\r\n]+)/;
|
|
29078
|
+
const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
|
|
29079
|
+
const INDEX_LINE_METADATA = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: (\d+))?$/i;
|
|
29080
|
+
Object.freeze({
|
|
29081
|
+
fromStart: 0,
|
|
29082
|
+
fromEnd: 0
|
|
29083
|
+
});
|
|
29058
29084
|
//#endregion
|
|
29059
|
-
//#region
|
|
29060
|
-
function
|
|
29061
|
-
|
|
29062
|
-
|
|
29063
|
-
|
|
29064
|
-
if (
|
|
29065
|
-
}
|
|
29066
|
-
return remoteNames;
|
|
29067
|
-
}
|
|
29068
|
-
function parseRemoteNames(stdout) {
|
|
29069
|
-
return parseRemoteNamesInGitOrder(stdout).toSorted((a, b) => b.length - a.length);
|
|
29070
|
-
}
|
|
29071
|
-
function parseRemoteRefWithRemoteNames(ref, remoteNames) {
|
|
29072
|
-
const trimmedRef = ref.trim();
|
|
29073
|
-
if (trimmedRef.length === 0) return null;
|
|
29074
|
-
for (const remoteName of remoteNames) {
|
|
29075
|
-
const remotePrefix = `${remoteName}/`;
|
|
29076
|
-
if (!trimmedRef.startsWith(remotePrefix)) continue;
|
|
29077
|
-
const branchName = trimmedRef.slice(remotePrefix.length).trim();
|
|
29078
|
-
if (branchName.length === 0) return null;
|
|
29079
|
-
return {
|
|
29080
|
-
remoteRef: trimmedRef,
|
|
29081
|
-
remoteName,
|
|
29082
|
-
branchName
|
|
29083
|
-
};
|
|
29085
|
+
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/cleanLastNewline.js
|
|
29086
|
+
function cleanLastNewline(contents) {
|
|
29087
|
+
let end = contents.length;
|
|
29088
|
+
if (contents.charCodeAt(end - 1) === 10) {
|
|
29089
|
+
end--;
|
|
29090
|
+
if (contents.charCodeAt(end - 1) === 13) end--;
|
|
29084
29091
|
}
|
|
29085
|
-
return
|
|
29086
|
-
}
|
|
29087
|
-
function extractBranchNameFromRemoteRef(ref, options) {
|
|
29088
|
-
const normalized = ref.trim();
|
|
29089
|
-
if (normalized.length === 0) return "";
|
|
29090
|
-
if (normalized.startsWith("refs/remotes/")) return extractBranchNameFromRemoteRef(normalized.slice(13), options);
|
|
29091
|
-
const parsedRemoteRef = parseRemoteRefWithRemoteNames(normalized, options?.remoteName ? [options.remoteName] : options?.remoteNames ?? []);
|
|
29092
|
-
if (parsedRemoteRef) return parsedRemoteRef.branchName;
|
|
29093
|
-
const firstSlash = normalized.indexOf("/");
|
|
29094
|
-
if (firstSlash === -1) return normalized;
|
|
29095
|
-
return normalized.slice(firstSlash + 1).trim();
|
|
29092
|
+
return contents.slice(0, end);
|
|
29096
29093
|
}
|
|
29097
29094
|
//#endregion
|
|
29098
|
-
//#region
|
|
29099
|
-
const
|
|
29100
|
-
const
|
|
29101
|
-
const
|
|
29102
|
-
const
|
|
29103
|
-
|
|
29104
|
-
|
|
29105
|
-
|
|
29106
|
-
const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 12e4;
|
|
29107
|
-
const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 8e4;
|
|
29108
|
-
const WORKSPACE_FILES_MAX_OUTPUT_BYTES$1 = 12e4;
|
|
29109
|
-
const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15);
|
|
29110
|
-
const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5);
|
|
29111
|
-
const STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN = Duration.seconds(30);
|
|
29112
|
-
const STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN = Duration.minutes(15);
|
|
29113
|
-
const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2048;
|
|
29114
|
-
const REPOSITORY_PATHS_CACHE_CAPACITY = 2048;
|
|
29115
|
-
const REPOSITORY_PATHS_CACHE_TTL = Duration.minutes(10);
|
|
29116
|
-
const REPOSITORY_PATHS_REFRESH_COALESCE_TTL = Duration.seconds(5);
|
|
29117
|
-
const NON_REPOSITORY_PATHS_CACHE_TTL = Duration.seconds(1);
|
|
29118
|
-
const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
|
|
29119
|
-
const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
|
|
29120
|
-
const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
|
|
29121
|
-
const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
|
|
29122
|
-
const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
|
|
29123
|
-
GCM_INTERACTIVE: "never",
|
|
29124
|
-
GIT_ASKPASS: "",
|
|
29125
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
29126
|
-
SSH_ASKPASS: "",
|
|
29127
|
-
SSH_ASKPASS_REQUIRE: "never"
|
|
29128
|
-
});
|
|
29129
|
-
const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"];
|
|
29130
|
-
const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100;
|
|
29131
|
-
const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
|
|
29132
|
-
isRepo: false,
|
|
29133
|
-
hasOriginRemote: false,
|
|
29134
|
-
isDefaultBranch: false,
|
|
29135
|
-
branch: null,
|
|
29136
|
-
upstreamRef: null,
|
|
29137
|
-
hasWorkingTreeChanges: false,
|
|
29138
|
-
workingTree: {
|
|
29139
|
-
files: [],
|
|
29140
|
-
insertions: 0,
|
|
29141
|
-
deletions: 0
|
|
29142
|
-
},
|
|
29143
|
-
hasUpstream: false,
|
|
29144
|
-
aheadCount: 0,
|
|
29145
|
-
behindCount: 0,
|
|
29146
|
-
aheadOfDefaultCount: 0
|
|
29147
|
-
});
|
|
29148
|
-
const NON_REPOSITORY_REMOTE_STATUS_DETAILS = Object.freeze({
|
|
29149
|
-
isRepo: false,
|
|
29150
|
-
isDefaultBranch: false,
|
|
29151
|
-
branch: null,
|
|
29152
|
-
upstreamRef: null,
|
|
29153
|
-
hasUpstream: false,
|
|
29154
|
-
aheadCount: 0,
|
|
29155
|
-
behindCount: 0,
|
|
29156
|
-
aheadOfDefaultCount: 0
|
|
29157
|
-
});
|
|
29158
|
-
var StatusRemoteRefreshCacheKey = class extends Data.Class {};
|
|
29159
|
-
function statusUpstreamRefreshFailureCooldown(consecutiveFailures) {
|
|
29160
|
-
const exponent = Math.max(0, consecutiveFailures - 1);
|
|
29161
|
-
const cooldownMs = Duration.toMillis(STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN) * Math.pow(2, exponent);
|
|
29162
|
-
return Duration.min(Duration.millis(cooldownMs), STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN);
|
|
29163
|
-
}
|
|
29164
|
-
var GitRefsSnapshotCacheKey = class extends Data.Class {};
|
|
29165
|
-
var GitRefsRefreshCacheKey = class extends Data.Class {};
|
|
29166
|
-
function parseBranchAb(value) {
|
|
29167
|
-
const match = value.match(/^\+(\d+)\s+-(\d+)$/);
|
|
29168
|
-
if (!match) return {
|
|
29169
|
-
ahead: 0,
|
|
29170
|
-
behind: 0
|
|
29171
|
-
};
|
|
29172
|
-
return {
|
|
29173
|
-
ahead: Number(match[1] ?? "0"),
|
|
29174
|
-
behind: Number(match[2] ?? "0")
|
|
29175
|
-
};
|
|
29176
|
-
}
|
|
29177
|
-
function parseNumstatEntries(stdout) {
|
|
29178
|
-
const entries = [];
|
|
29179
|
-
for (const line of stdout.split(/\r?\n/g)) {
|
|
29180
|
-
if (line.trim().length === 0) continue;
|
|
29181
|
-
const [addedRaw, deletedRaw, ...pathParts] = line.split(" ");
|
|
29182
|
-
const rawPath = pathParts.length > 1 ? (pathParts.at(-1) ?? "").trim() : pathParts.join(" ").trim();
|
|
29183
|
-
if (rawPath.length === 0) continue;
|
|
29184
|
-
const added = Number.parseInt(addedRaw ?? "0", 10);
|
|
29185
|
-
const deleted = Number.parseInt(deletedRaw ?? "0", 10);
|
|
29186
|
-
const renameArrowIndex = rawPath.indexOf(" => ");
|
|
29187
|
-
const normalizedPath = renameArrowIndex >= 0 ? rawPath.slice(renameArrowIndex + 4).trim() : rawPath;
|
|
29188
|
-
entries.push({
|
|
29189
|
-
path: normalizedPath.length > 0 ? normalizedPath : rawPath,
|
|
29190
|
-
insertions: Number.isFinite(added) ? added : 0,
|
|
29191
|
-
deletions: Number.isFinite(deleted) ? deleted : 0
|
|
29192
|
-
});
|
|
29193
|
-
}
|
|
29194
|
-
return entries;
|
|
29195
|
-
}
|
|
29196
|
-
function parsePorcelainPath(line) {
|
|
29197
|
-
if (line.startsWith("? ") || line.startsWith("! ")) {
|
|
29198
|
-
const simple = line.slice(2).trim();
|
|
29199
|
-
return simple.length > 0 ? simple : null;
|
|
29200
|
-
}
|
|
29201
|
-
if (!(line.startsWith("1 ") || line.startsWith("2 ") || line.startsWith("u "))) return null;
|
|
29202
|
-
const tabIndex = line.indexOf(" ");
|
|
29203
|
-
if (tabIndex >= 0) {
|
|
29204
|
-
const [filePath] = line.slice(tabIndex + 1).split(" ");
|
|
29205
|
-
return filePath?.trim().length ? filePath.trim() : null;
|
|
29206
|
-
}
|
|
29207
|
-
const filePath = line.trim().split(/\s+/g).at(-1) ?? "";
|
|
29208
|
-
return filePath.length > 0 ? filePath : null;
|
|
29209
|
-
}
|
|
29210
|
-
function filterBranchesForListQuery(refs, query) {
|
|
29211
|
-
if (!query) return refs;
|
|
29212
|
-
const normalizedQuery = query.toLowerCase();
|
|
29213
|
-
return refs.filter((refName) => refName.name.toLowerCase().includes(normalizedQuery));
|
|
29214
|
-
}
|
|
29215
|
-
function paginateBranches(input) {
|
|
29216
|
-
const cursor = input.cursor ?? 0;
|
|
29217
|
-
const limit = input.limit ?? GIT_LIST_BRANCHES_DEFAULT_LIMIT;
|
|
29218
|
-
const totalCount = input.refs.length;
|
|
29219
|
-
const refs = input.refs.slice(cursor, cursor + limit);
|
|
29220
|
-
return {
|
|
29221
|
-
refs,
|
|
29222
|
-
nextCursor: cursor + refs.length < totalCount ? cursor + refs.length : null,
|
|
29223
|
-
totalCount
|
|
29224
|
-
};
|
|
29225
|
-
}
|
|
29226
|
-
function parseWorktreeBranchPaths(stdout) {
|
|
29227
|
-
const worktreePaths = /* @__PURE__ */ new Map();
|
|
29228
|
-
let currentPath = null;
|
|
29229
|
-
let currentBranch = null;
|
|
29230
|
-
let currentPrunable = false;
|
|
29231
|
-
const flush = () => {
|
|
29232
|
-
if (currentPath !== null && currentBranch !== null && !currentPrunable) worktreePaths.set(currentBranch, currentPath);
|
|
29233
|
-
currentPath = null;
|
|
29234
|
-
currentBranch = null;
|
|
29235
|
-
currentPrunable = false;
|
|
29236
|
-
};
|
|
29237
|
-
for (const field of stdout.split("\0")) if (field === "") flush();
|
|
29238
|
-
else if (field.startsWith("worktree ")) currentPath = field.slice(9);
|
|
29239
|
-
else if (field.startsWith("branch refs/heads/")) currentBranch = field.slice(18);
|
|
29240
|
-
else if (field === "prunable" || field.startsWith("prunable ")) currentPrunable = true;
|
|
29241
|
-
flush();
|
|
29242
|
-
return worktreePaths;
|
|
29243
|
-
}
|
|
29244
|
-
function splitNullSeparatedPaths$1(input, truncated) {
|
|
29245
|
-
const parts = input.split("\0");
|
|
29246
|
-
if (parts.length === 0) return [];
|
|
29247
|
-
if (truncated && parts[parts.length - 1]?.length) parts.pop();
|
|
29248
|
-
return parts.filter((value) => value.length > 0);
|
|
29249
|
-
}
|
|
29250
|
-
function splitNullSeparatedGitStdoutPaths(result) {
|
|
29251
|
-
return splitNullSeparatedPaths$1(result.stdout, result.stdoutTruncated);
|
|
29095
|
+
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/detachString.js
|
|
29096
|
+
const stringDetachEncoder = new TextEncoder();
|
|
29097
|
+
const stringDetachDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
29098
|
+
const SURROGATE_CODE_UNIT_PATTERN = /[\uD800-\uDFFF]/;
|
|
29099
|
+
const STRING_DETACH_INITIAL_BUFFER_SIZE = 1024;
|
|
29100
|
+
let stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
|
|
29101
|
+
function releaseStringDetachBuffer() {
|
|
29102
|
+
if (stringDetachBuffer.length !== STRING_DETACH_INITIAL_BUFFER_SIZE) stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
|
|
29252
29103
|
}
|
|
29253
|
-
function
|
|
29254
|
-
|
|
29255
|
-
|
|
29104
|
+
function detachString(value) {
|
|
29105
|
+
if (value.length === 0) return value;
|
|
29106
|
+
if (SURROGATE_CODE_UNIT_PATTERN.test(value)) return JSON.parse(JSON.stringify(value));
|
|
29107
|
+
const requiredByteLength = value.length * 3;
|
|
29108
|
+
if (stringDetachBuffer.length < requiredByteLength) stringDetachBuffer = new Uint8Array(requiredByteLength);
|
|
29109
|
+
const { written } = stringDetachEncoder.encodeInto(value, stringDetachBuffer);
|
|
29110
|
+
return stringDetachDecoder.decode(stringDetachBuffer.subarray(0, written));
|
|
29256
29111
|
}
|
|
29257
|
-
|
|
29258
|
-
|
|
29259
|
-
|
|
29260
|
-
|
|
29261
|
-
|
|
29262
|
-
|
|
29263
|
-
|
|
29264
|
-
const [, remoteName = "", remoteUrl = "", direction = ""] = match;
|
|
29265
|
-
if (direction !== "fetch" || remoteName.length === 0 || remoteUrl.length === 0) continue;
|
|
29266
|
-
remotes.set(remoteName, remoteUrl);
|
|
29112
|
+
//#endregion
|
|
29113
|
+
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js
|
|
29114
|
+
function processPatch(data, cacheKeyPrefix, throwOnError) {
|
|
29115
|
+
try {
|
|
29116
|
+
return _processPatch(data, cacheKeyPrefix, throwOnError);
|
|
29117
|
+
} finally {
|
|
29118
|
+
releaseStringDetachBuffer();
|
|
29267
29119
|
}
|
|
29268
|
-
return remotes;
|
|
29269
|
-
}
|
|
29270
|
-
function parseUpstreamRefWithRemoteNames(upstreamRef, remoteNames) {
|
|
29271
|
-
const parsed = parseRemoteRefWithRemoteNames(upstreamRef, remoteNames);
|
|
29272
|
-
if (!parsed) return null;
|
|
29273
|
-
return {
|
|
29274
|
-
upstreamRef,
|
|
29275
|
-
remoteName: parsed.remoteName,
|
|
29276
|
-
branchName: parsed.branchName
|
|
29277
|
-
};
|
|
29278
|
-
}
|
|
29279
|
-
function parseUpstreamRefByFirstSeparator(upstreamRef) {
|
|
29280
|
-
const separatorIndex = upstreamRef.indexOf("/");
|
|
29281
|
-
if (separatorIndex <= 0 || separatorIndex === upstreamRef.length - 1) return null;
|
|
29282
|
-
const remoteName = upstreamRef.slice(0, separatorIndex).trim();
|
|
29283
|
-
const branchName = upstreamRef.slice(separatorIndex + 1).trim();
|
|
29284
|
-
if (remoteName.length === 0 || branchName.length === 0) return null;
|
|
29285
|
-
return {
|
|
29286
|
-
upstreamRef,
|
|
29287
|
-
remoteName,
|
|
29288
|
-
branchName
|
|
29289
|
-
};
|
|
29290
29120
|
}
|
|
29291
|
-
function
|
|
29292
|
-
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
29298
|
-
|
|
29299
|
-
|
|
29121
|
+
function _processPatch(data, cacheKeyPrefix, throwOnError = false) {
|
|
29122
|
+
const isGitDiff = isGitDiffPatch(data);
|
|
29123
|
+
const rawFiles = isGitDiff ? splitGitDiffFiles(data) : splitUnifiedDiffFiles(data);
|
|
29124
|
+
let patchMetadata;
|
|
29125
|
+
const files = [];
|
|
29126
|
+
for (const fileOrPatchMetadata of rawFiles) {
|
|
29127
|
+
if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
|
|
29128
|
+
if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
|
|
29129
|
+
else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
|
|
29130
|
+
else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
|
|
29131
|
+
continue;
|
|
29132
|
+
} else if (!isGitDiff && !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)) {
|
|
29133
|
+
if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
|
|
29134
|
+
else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
|
|
29135
|
+
else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
|
|
29136
|
+
continue;
|
|
29137
|
+
}
|
|
29138
|
+
const currentFile = _processFile(fileOrPatchMetadata, {
|
|
29139
|
+
cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : void 0,
|
|
29140
|
+
isGitDiff,
|
|
29141
|
+
throwOnError
|
|
29142
|
+
});
|
|
29143
|
+
if (currentFile != null) files.push(currentFile);
|
|
29300
29144
|
}
|
|
29301
|
-
return null;
|
|
29302
|
-
}
|
|
29303
|
-
function deriveLocalBranchNameFromRemoteRef(branchName) {
|
|
29304
|
-
const separatorIndex = branchName.indexOf("/");
|
|
29305
|
-
if (separatorIndex <= 0 || separatorIndex === branchName.length - 1) return null;
|
|
29306
|
-
const localBranch = branchName.slice(separatorIndex + 1).trim();
|
|
29307
|
-
return localBranch.length > 0 ? localBranch : null;
|
|
29308
|
-
}
|
|
29309
|
-
function gitCommandContext(input) {
|
|
29310
29145
|
return {
|
|
29311
|
-
|
|
29312
|
-
|
|
29313
|
-
cwd: input.cwd,
|
|
29314
|
-
argumentCount: input.args.length
|
|
29146
|
+
patchMetadata,
|
|
29147
|
+
files
|
|
29315
29148
|
};
|
|
29316
29149
|
}
|
|
29317
|
-
function
|
|
29318
|
-
|
|
29319
|
-
const
|
|
29320
|
-
|
|
29321
|
-
const
|
|
29322
|
-
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
return stderr.toLowerCase().includes("not a git repository");
|
|
29332
|
-
}
|
|
29333
|
-
const nowUnixNano = DateTime.now.pipe(Effect.map((now) => BigInt(DateTime.toEpochMillis(now)) * 1000000n));
|
|
29334
|
-
const addCurrentSpanEvent = (name, attributes) => Effect.gen(function* () {
|
|
29335
|
-
const span = yield* Effect.currentSpan;
|
|
29336
|
-
const timestamp = yield* nowUnixNano;
|
|
29337
|
-
yield* Effect.sync(() => {
|
|
29338
|
-
span.event(name, timestamp, compactTraceAttributes(attributes));
|
|
29339
|
-
});
|
|
29340
|
-
}).pipe(Effect.catchTags({ NoSuchElementError: () => Effect.void }));
|
|
29341
|
-
function trace2ChildKey(record) {
|
|
29342
|
-
const childId = record.child_id;
|
|
29343
|
-
if (typeof childId === "number" || typeof childId === "string") return String(childId);
|
|
29344
|
-
const hookName = record.hook_name;
|
|
29345
|
-
return typeof hookName === "string" && hookName.trim().length > 0 ? hookName.trim() : null;
|
|
29346
|
-
}
|
|
29347
|
-
const Trace2Record = Schema$1.Record(Schema$1.String, Schema$1.Unknown);
|
|
29348
|
-
const createTrace2Monitor = Effect.fn("createTrace2Monitor")(function* (input, progress) {
|
|
29349
|
-
if (!progress?.onHookStarted && !progress?.onHookFinished) return {
|
|
29350
|
-
env: {},
|
|
29351
|
-
flush: Effect.void
|
|
29352
|
-
};
|
|
29353
|
-
const fs = yield* FileSystem.FileSystem;
|
|
29354
|
-
const path = yield* Path.Path;
|
|
29355
|
-
const traceFilePath = yield* fs.makeTempFileScoped({
|
|
29356
|
-
prefix: `p4code-git-trace2-${process.pid}-`,
|
|
29357
|
-
suffix: ".json"
|
|
29358
|
-
});
|
|
29359
|
-
const hookStartByChildKey = /* @__PURE__ */ new Map();
|
|
29360
|
-
const traceTailState = yield* Ref.make({
|
|
29361
|
-
processedChars: 0,
|
|
29362
|
-
remainder: ""
|
|
29363
|
-
});
|
|
29364
|
-
const handleTraceLine = Effect.fn("handleTraceLine")(function* (line) {
|
|
29365
|
-
const trimmedLine = line.trim();
|
|
29366
|
-
if (trimmedLine.length === 0) return;
|
|
29367
|
-
const traceRecord = decodeJsonResult(Trace2Record)(trimmedLine);
|
|
29368
|
-
if (Result.isFailure(traceRecord)) {
|
|
29369
|
-
yield* Effect.logDebug(`GitVcsDriver.trace2: failed to parse trace line for ${input.operation} in ${input.cwd} (${input.args.length} arguments)`, traceRecord.failure);
|
|
29370
|
-
return;
|
|
29371
|
-
}
|
|
29372
|
-
if (traceRecord.success.child_class !== "hook") return;
|
|
29373
|
-
const event = traceRecord.success.event;
|
|
29374
|
-
const childKey = trace2ChildKey(traceRecord.success);
|
|
29375
|
-
if (childKey === null) return;
|
|
29376
|
-
const started = hookStartByChildKey.get(childKey);
|
|
29377
|
-
const hookNameFromEvent = typeof traceRecord.success.hook_name === "string" ? traceRecord.success.hook_name.trim() : "";
|
|
29378
|
-
const hookName = hookNameFromEvent.length > 0 ? hookNameFromEvent : started?.hookName ?? "";
|
|
29379
|
-
if (hookName.length === 0) return;
|
|
29380
|
-
if (event === "child_start") {
|
|
29381
|
-
const now = yield* DateTime.now;
|
|
29382
|
-
hookStartByChildKey.set(childKey, {
|
|
29383
|
-
hookName,
|
|
29384
|
-
startedAtMs: DateTime.toEpochMillis(now)
|
|
29385
|
-
});
|
|
29386
|
-
yield* addCurrentSpanEvent("git.hook.started", { hookName });
|
|
29387
|
-
if (progress.onHookStarted) yield* progress.onHookStarted(hookName);
|
|
29388
|
-
return;
|
|
29150
|
+
function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString), oldFile, newFile, throwOnError = false } = {}) {
|
|
29151
|
+
let lastHunkEnd = 0;
|
|
29152
|
+
const hunks = splitAtLinePrefix(fileDiffString, "@@ ");
|
|
29153
|
+
let currentFile;
|
|
29154
|
+
const isPartial = oldFile == null || newFile == null;
|
|
29155
|
+
let deletionLineIndex = 0;
|
|
29156
|
+
let additionLineIndex = 0;
|
|
29157
|
+
for (const hunk of hunks) {
|
|
29158
|
+
const lines = splitWithNewlines(hunk);
|
|
29159
|
+
const firstLine = lines[0];
|
|
29160
|
+
if (firstLine == null) {
|
|
29161
|
+
if (throwOnError) throw Error("parsePatchContent: invalid hunk");
|
|
29162
|
+
else console.error("parsePatchContent: invalid hunk", hunk);
|
|
29163
|
+
continue;
|
|
29389
29164
|
}
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29395
|
-
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29400
|
-
|
|
29165
|
+
const fileHeader = parseHunkHeader(firstLine);
|
|
29166
|
+
let additionLines = 0;
|
|
29167
|
+
let deletionLines = 0;
|
|
29168
|
+
if (fileHeader == null || currentFile == null) {
|
|
29169
|
+
if (currentFile != null) {
|
|
29170
|
+
if (throwOnError) throw Error("parsePatchContent: Invalid hunk");
|
|
29171
|
+
else console.error("parsePatchContent: Invalid hunk", hunk);
|
|
29172
|
+
continue;
|
|
29173
|
+
}
|
|
29174
|
+
currentFile = {
|
|
29175
|
+
name: "",
|
|
29176
|
+
type: "change",
|
|
29177
|
+
hunks: [],
|
|
29178
|
+
splitLineCount: 0,
|
|
29179
|
+
unifiedLineCount: 0,
|
|
29180
|
+
isPartial,
|
|
29181
|
+
additionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(newFile.contents) : [],
|
|
29182
|
+
deletionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(oldFile.contents) : [],
|
|
29183
|
+
cacheKey: maybeDetachOptionalString(cacheKey)
|
|
29184
|
+
};
|
|
29185
|
+
if (currentFile.additionLines.length === 1 && newFile?.contents === "") currentFile.additionLines.length = 0;
|
|
29186
|
+
if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
|
|
29187
|
+
for (const line of lines) {
|
|
29188
|
+
if (line.startsWith("diff --git")) {
|
|
29189
|
+
const filenameMatch$1 = line.trim().match(ALTERNATE_FILE_NAMES_GIT);
|
|
29190
|
+
const prevName = filenameMatch$1?.[1] ?? filenameMatch$1?.[2];
|
|
29191
|
+
const name = filenameMatch$1?.[3] ?? filenameMatch$1?.[4];
|
|
29192
|
+
if (prevName == null || name == null) {
|
|
29193
|
+
if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
|
|
29194
|
+
else console.error("parsePatchContent: invalid git diff header", line);
|
|
29195
|
+
continue;
|
|
29196
|
+
}
|
|
29197
|
+
currentFile.name = detachString(name.trim());
|
|
29198
|
+
if (prevName !== name) currentFile.prevName = detachString(prevName.trim());
|
|
29199
|
+
continue;
|
|
29200
|
+
}
|
|
29201
|
+
const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX) : null;
|
|
29202
|
+
if (filenameMatch != null) {
|
|
29203
|
+
const [, type, fileName] = filenameMatch;
|
|
29204
|
+
if (type === "---" && fileName !== "/dev/null") {
|
|
29205
|
+
const detachedFileName = detachString(fileName.trim());
|
|
29206
|
+
currentFile.prevName = detachedFileName;
|
|
29207
|
+
currentFile.name = detachedFileName;
|
|
29208
|
+
} else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName.trim());
|
|
29209
|
+
} else if (isGitDiff) {
|
|
29210
|
+
if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
|
|
29211
|
+
if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
|
|
29212
|
+
if (line.startsWith("new file mode")) {
|
|
29213
|
+
currentFile.type = "new";
|
|
29214
|
+
currentFile.mode = detachString(line.slice(13).trim());
|
|
29215
|
+
}
|
|
29216
|
+
if (line.startsWith("deleted file mode")) {
|
|
29217
|
+
currentFile.type = "deleted";
|
|
29218
|
+
currentFile.mode = detachString(line.slice(17).trim());
|
|
29219
|
+
}
|
|
29220
|
+
if (line.startsWith("similarity index")) if (line.startsWith("similarity index 100%")) currentFile.type = "rename-pure";
|
|
29221
|
+
else currentFile.type = "rename-changed";
|
|
29222
|
+
if (line.startsWith("index ")) {
|
|
29223
|
+
const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? [];
|
|
29224
|
+
if (prevObjectId != null) currentFile.prevObjectId = detachString(prevObjectId);
|
|
29225
|
+
if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
|
|
29226
|
+
if (mode != null) currentFile.mode = detachString(mode);
|
|
29227
|
+
}
|
|
29228
|
+
if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(12).trim());
|
|
29229
|
+
if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(10).trim());
|
|
29230
|
+
}
|
|
29231
|
+
}
|
|
29232
|
+
continue;
|
|
29233
|
+
}
|
|
29234
|
+
let currentContent;
|
|
29235
|
+
let lastLineType;
|
|
29236
|
+
while (lines.length > 0 && (lines[lines.length - 1] === "\n" || lines[lines.length - 1] === "\r" || lines[lines.length - 1] === "\r\n" || lines[lines.length - 1] === "")) lines.pop();
|
|
29237
|
+
const { additionStart, deletionStart } = fileHeader;
|
|
29238
|
+
deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;
|
|
29239
|
+
additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;
|
|
29240
|
+
const hunkData = {
|
|
29241
|
+
collapsedBefore: 0,
|
|
29242
|
+
splitLineCount: 0,
|
|
29243
|
+
splitLineStart: 0,
|
|
29244
|
+
unifiedLineCount: 0,
|
|
29245
|
+
unifiedLineStart: 0,
|
|
29246
|
+
additionCount: fileHeader.additionCount,
|
|
29247
|
+
additionStart,
|
|
29248
|
+
additionLines,
|
|
29249
|
+
deletionCount: fileHeader.deletionCount,
|
|
29250
|
+
deletionStart,
|
|
29251
|
+
deletionLines,
|
|
29252
|
+
deletionLineIndex,
|
|
29253
|
+
additionLineIndex,
|
|
29254
|
+
hunkContent: [],
|
|
29255
|
+
hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),
|
|
29256
|
+
hunkSpecs: detachString(firstLine),
|
|
29257
|
+
noEOFCRAdditions: false,
|
|
29258
|
+
noEOFCRDeletions: false
|
|
29259
|
+
};
|
|
29260
|
+
let parsedAdditionLines = 0;
|
|
29261
|
+
let parsedDeletionLines = 0;
|
|
29262
|
+
for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {
|
|
29263
|
+
const rawLine = lines[lineIndex];
|
|
29264
|
+
if (parsedAdditionLines >= hunkData.additionCount && parsedDeletionLines >= hunkData.deletionCount && !rawLine.startsWith("\\")) {
|
|
29265
|
+
if (throwOnError && isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine)) throw Error("parsePatchContent: hunk has more lines than expected");
|
|
29266
|
+
break;
|
|
29267
|
+
}
|
|
29268
|
+
const firstChar = rawLine[0];
|
|
29269
|
+
if (firstChar !== "+" && firstChar !== "-" && firstChar !== " " && firstChar !== "\\") {
|
|
29270
|
+
if (throwOnError) throw Error("parsePatchContent: invalid hunk line");
|
|
29271
|
+
console.error(`parseLineType: Invalid firstChar: "${firstChar}", full line: "${rawLine}"`);
|
|
29272
|
+
console.error("processFile: invalid rawLine:", rawLine);
|
|
29273
|
+
continue;
|
|
29274
|
+
}
|
|
29275
|
+
const type = parseRawLineType(firstChar);
|
|
29276
|
+
if (type === "addition") {
|
|
29277
|
+
if (throwOnError && parsedAdditionLines >= hunkData.additionCount) throw Error("parsePatchContent: hunk has too many addition lines");
|
|
29278
|
+
const line = getParsedLineContent(rawLine);
|
|
29279
|
+
if (currentContent == null || currentContent.type !== "change") {
|
|
29280
|
+
currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
|
|
29281
|
+
hunkData.hunkContent.push(currentContent);
|
|
29282
|
+
}
|
|
29283
|
+
additionLineIndex++;
|
|
29284
|
+
parsedAdditionLines++;
|
|
29285
|
+
if (isPartial) currentFile.additionLines.push(line);
|
|
29286
|
+
currentContent.additions++;
|
|
29287
|
+
additionLines++;
|
|
29288
|
+
lastLineType = "addition";
|
|
29289
|
+
} else if (type === "deletion") {
|
|
29290
|
+
if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) throw Error("parsePatchContent: hunk has too many deletion lines");
|
|
29291
|
+
const line = getParsedLineContent(rawLine);
|
|
29292
|
+
if (currentContent == null || currentContent.type !== "change") {
|
|
29293
|
+
currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
|
|
29294
|
+
hunkData.hunkContent.push(currentContent);
|
|
29295
|
+
}
|
|
29296
|
+
deletionLineIndex++;
|
|
29297
|
+
parsedDeletionLines++;
|
|
29298
|
+
if (isPartial) currentFile.deletionLines.push(line);
|
|
29299
|
+
currentContent.deletions++;
|
|
29300
|
+
deletionLines++;
|
|
29301
|
+
lastLineType = "deletion";
|
|
29302
|
+
} else if (type === "context") {
|
|
29303
|
+
if (throwOnError && (parsedDeletionLines >= hunkData.deletionCount || parsedAdditionLines >= hunkData.additionCount)) throw Error("parsePatchContent: hunk has too many context lines");
|
|
29304
|
+
const line = getParsedLineContent(rawLine);
|
|
29305
|
+
if (currentContent == null || currentContent.type !== "context") {
|
|
29306
|
+
currentContent = createContentGroup("context", deletionLineIndex, additionLineIndex);
|
|
29307
|
+
hunkData.hunkContent.push(currentContent);
|
|
29308
|
+
}
|
|
29309
|
+
additionLineIndex++;
|
|
29310
|
+
deletionLineIndex++;
|
|
29311
|
+
parsedAdditionLines++;
|
|
29312
|
+
parsedDeletionLines++;
|
|
29313
|
+
if (isPartial) {
|
|
29314
|
+
currentFile.deletionLines.push(line);
|
|
29315
|
+
currentFile.additionLines.push(line);
|
|
29316
|
+
}
|
|
29317
|
+
currentContent.lines++;
|
|
29318
|
+
lastLineType = "context";
|
|
29319
|
+
} else if (type === "metadata" && currentContent != null) {
|
|
29320
|
+
if (currentContent.type === "context") {
|
|
29321
|
+
hunkData.noEOFCRAdditions = true;
|
|
29322
|
+
hunkData.noEOFCRDeletions = true;
|
|
29323
|
+
} else if (lastLineType === "deletion") hunkData.noEOFCRDeletions = true;
|
|
29324
|
+
else if (lastLineType === "addition") hunkData.noEOFCRAdditions = true;
|
|
29325
|
+
if (isPartial && (lastLineType === "addition" || lastLineType === "context")) {
|
|
29326
|
+
const lastIndex = currentFile.additionLines.length - 1;
|
|
29327
|
+
if (lastIndex >= 0) currentFile.additionLines[lastIndex] = cleanLastNewline(currentFile.additionLines[lastIndex]);
|
|
29328
|
+
}
|
|
29329
|
+
if (isPartial && (lastLineType === "deletion" || lastLineType === "context")) {
|
|
29330
|
+
const lastIndex = currentFile.deletionLines.length - 1;
|
|
29331
|
+
if (lastIndex >= 0) currentFile.deletionLines[lastIndex] = cleanLastNewline(currentFile.deletionLines[lastIndex]);
|
|
29332
|
+
}
|
|
29333
|
+
}
|
|
29334
|
+
}
|
|
29335
|
+
if (throwOnError && (parsedAdditionLines !== hunkData.additionCount || parsedDeletionLines !== hunkData.deletionCount)) throw Error("parsePatchContent: hunk line count mismatch");
|
|
29336
|
+
hunkData.additionLines = additionLines;
|
|
29337
|
+
hunkData.deletionLines = deletionLines;
|
|
29338
|
+
hunkData.collapsedBefore = Math.max(hunkData.additionStart - 1 - lastHunkEnd, 0);
|
|
29339
|
+
currentFile.hunks.push(hunkData);
|
|
29340
|
+
lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;
|
|
29341
|
+
for (const content of hunkData.hunkContent) if (content.type === "context") {
|
|
29342
|
+
hunkData.splitLineCount += content.lines;
|
|
29343
|
+
hunkData.unifiedLineCount += content.lines;
|
|
29344
|
+
} else {
|
|
29345
|
+
hunkData.splitLineCount += Math.max(content.additions, content.deletions);
|
|
29346
|
+
hunkData.unifiedLineCount += content.deletions + content.additions;
|
|
29347
|
+
}
|
|
29348
|
+
hunkData.splitLineStart = currentFile.splitLineCount + hunkData.collapsedBefore;
|
|
29349
|
+
hunkData.unifiedLineStart = currentFile.unifiedLineCount + hunkData.collapsedBefore;
|
|
29350
|
+
currentFile.splitLineCount += hunkData.collapsedBefore + hunkData.splitLineCount;
|
|
29351
|
+
currentFile.unifiedLineCount += hunkData.collapsedBefore + hunkData.unifiedLineCount;
|
|
29352
|
+
}
|
|
29353
|
+
if (currentFile == null) return;
|
|
29354
|
+
if (throwOnError && isPartial && !isGitDiff && currentFile.hunks.length === 0) throw Error("parsePatchContent: unified file has no hunks");
|
|
29355
|
+
if (currentFile.hunks.length > 0 && !isPartial && currentFile.additionLines.length > 0 && currentFile.deletionLines.length > 0) {
|
|
29356
|
+
const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];
|
|
29357
|
+
const lastHunkEnd$1 = lastHunk.additionStart + lastHunk.additionCount - 1;
|
|
29358
|
+
const totalFileLines = currentFile.additionLines.length;
|
|
29359
|
+
const collapsedAfter = Math.max(totalFileLines - lastHunkEnd$1, 0);
|
|
29360
|
+
currentFile.splitLineCount += collapsedAfter;
|
|
29361
|
+
currentFile.unifiedLineCount += collapsedAfter;
|
|
29362
|
+
}
|
|
29363
|
+
if (!isGitDiff) {
|
|
29364
|
+
if (currentFile.prevName != null && currentFile.name !== currentFile.prevName) if (currentFile.hunks.length > 0) currentFile.type = "rename-changed";
|
|
29365
|
+
else currentFile.type = "rename-pure";
|
|
29366
|
+
else if ((oldFile == null || oldFile.contents === "") && newFile != null && newFile.contents !== "") currentFile.type = "new";
|
|
29367
|
+
else if (oldFile != null && oldFile.contents !== "" && (newFile == null || newFile.contents === "")) currentFile.type = "deleted";
|
|
29368
|
+
}
|
|
29369
|
+
if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
|
|
29370
|
+
return currentFile;
|
|
29371
|
+
}
|
|
29372
|
+
/**
|
|
29373
|
+
* Parses a patch file string into an array of parsed patches.
|
|
29374
|
+
*
|
|
29375
|
+
* @param data - The raw patch file content (supports multi-commit patches)
|
|
29376
|
+
* @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,
|
|
29377
|
+
* each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.
|
|
29378
|
+
* This enables caching of rendered diff results in the worker pool.
|
|
29379
|
+
*/
|
|
29380
|
+
function parsePatchFiles(data, cacheKeyPrefix, throwOnError = false) {
|
|
29381
|
+
const patches = [];
|
|
29382
|
+
const rawPatches = hasCommitMetadataBoundary(data) ? data.split(COMMIT_METADATA_SPLIT) : [data];
|
|
29383
|
+
for (const patch of rawPatches) try {
|
|
29384
|
+
patches.push(processPatch(patch, cacheKeyPrefix != null ? `${cacheKeyPrefix}-${patches.length}` : void 0, throwOnError));
|
|
29385
|
+
} catch (error) {
|
|
29386
|
+
if (throwOnError) throw error;
|
|
29387
|
+
else console.error(error);
|
|
29388
|
+
}
|
|
29389
|
+
return patches;
|
|
29390
|
+
}
|
|
29391
|
+
function hasCommitMetadataBoundary(data) {
|
|
29392
|
+
return data.startsWith("From ") || data.includes("\nFrom ");
|
|
29393
|
+
}
|
|
29394
|
+
function splitFileContents(contents) {
|
|
29395
|
+
const lines = splitWithNewlines(contents);
|
|
29396
|
+
for (let index = 0; index < lines.length; index++) lines[index] = detachString(lines[index]);
|
|
29397
|
+
return lines;
|
|
29398
|
+
}
|
|
29399
|
+
function splitWithNewlines(contents) {
|
|
29400
|
+
if (contents.length === 0) return [""];
|
|
29401
|
+
const lines = [];
|
|
29402
|
+
let startIndex = 0;
|
|
29403
|
+
for (;;) {
|
|
29404
|
+
const newlineIndex = contents.indexOf("\n", startIndex);
|
|
29405
|
+
if (newlineIndex === -1) break;
|
|
29406
|
+
lines.push(contents.slice(startIndex, newlineIndex + 1));
|
|
29407
|
+
startIndex = newlineIndex + 1;
|
|
29408
|
+
}
|
|
29409
|
+
if (startIndex < contents.length) lines.push(contents.slice(startIndex));
|
|
29410
|
+
return lines;
|
|
29411
|
+
}
|
|
29412
|
+
function splitGitDiffFiles(contents) {
|
|
29413
|
+
return splitAtLinePrefix(contents, "diff --git");
|
|
29414
|
+
}
|
|
29415
|
+
function splitUnifiedDiffFiles(contents) {
|
|
29416
|
+
if (contents.length === 0) return [""];
|
|
29417
|
+
const parts = [];
|
|
29418
|
+
let partStartIndex = 0;
|
|
29419
|
+
let lineStartIndex = 0;
|
|
29420
|
+
let remainingDeletionLines = 0;
|
|
29421
|
+
let remainingAdditionLines = 0;
|
|
29422
|
+
let hasOpenedUnifiedFile = false;
|
|
29423
|
+
while (lineStartIndex < contents.length) {
|
|
29424
|
+
const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
|
|
29425
|
+
if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {
|
|
29426
|
+
if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {
|
|
29427
|
+
if (lineStartIndex > partStartIndex) parts.push(contents.slice(partStartIndex, lineStartIndex));
|
|
29428
|
+
partStartIndex = lineStartIndex;
|
|
29429
|
+
hasOpenedUnifiedFile = true;
|
|
29430
|
+
lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);
|
|
29431
|
+
continue;
|
|
29432
|
+
}
|
|
29433
|
+
if (hasOpenedUnifiedFile && contents.startsWith("@@ -", lineStartIndex)) {
|
|
29434
|
+
const fileHeader = parseHunkHeader(contents.slice(lineStartIndex, nextLineStartIndex));
|
|
29435
|
+
if (fileHeader != null) {
|
|
29436
|
+
remainingDeletionLines = fileHeader.deletionCount;
|
|
29437
|
+
remainingAdditionLines = fileHeader.additionCount;
|
|
29438
|
+
}
|
|
29439
|
+
}
|
|
29440
|
+
lineStartIndex = nextLineStartIndex;
|
|
29441
|
+
continue;
|
|
29442
|
+
}
|
|
29443
|
+
const firstChar = contents[lineStartIndex];
|
|
29444
|
+
if (firstChar === "\\") {
|
|
29445
|
+
lineStartIndex = nextLineStartIndex;
|
|
29446
|
+
continue;
|
|
29447
|
+
}
|
|
29448
|
+
if (firstChar === " ") {
|
|
29449
|
+
remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
|
|
29450
|
+
remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
|
|
29451
|
+
} else if (firstChar === "-") remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
|
|
29452
|
+
else if (firstChar === "+") remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
|
|
29453
|
+
lineStartIndex = nextLineStartIndex;
|
|
29454
|
+
}
|
|
29455
|
+
parts.push(contents.slice(partStartIndex));
|
|
29456
|
+
return parts;
|
|
29457
|
+
}
|
|
29458
|
+
function startsWithUnifiedDiffFileHeader(contents) {
|
|
29459
|
+
return isUnifiedDiffFileHeaderAt(contents, 0);
|
|
29460
|
+
}
|
|
29461
|
+
function isUnifiedDiffFileHeaderAt(contents, lineStartIndex) {
|
|
29462
|
+
const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
|
|
29463
|
+
return isUnifiedDiffHeaderLineAt(contents, lineStartIndex, "---") && isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, "+++");
|
|
29464
|
+
}
|
|
29465
|
+
function isUnifiedDiffHeaderLineAt(contents, lineStartIndex, prefix) {
|
|
29466
|
+
if (!contents.startsWith(prefix, lineStartIndex)) return false;
|
|
29467
|
+
const separator = contents[lineStartIndex + prefix.length];
|
|
29468
|
+
if (separator !== " " && separator !== " ") return false;
|
|
29469
|
+
for (let index = lineStartIndex + prefix.length + 1; index < contents.length; index++) {
|
|
29470
|
+
const char = contents[index];
|
|
29471
|
+
if (char === "\n" || char === "\r") break;
|
|
29472
|
+
if (char !== " " && char !== " ") return true;
|
|
29473
|
+
}
|
|
29474
|
+
return false;
|
|
29475
|
+
}
|
|
29476
|
+
function getNextLineStartIndex(contents, lineStartIndex) {
|
|
29477
|
+
const newlineIndex = contents.indexOf("\n", lineStartIndex);
|
|
29478
|
+
return newlineIndex === -1 ? contents.length : newlineIndex + 1;
|
|
29479
|
+
}
|
|
29480
|
+
function isHunkBodyLine(line) {
|
|
29481
|
+
const firstChar = line[0];
|
|
29482
|
+
return firstChar === "+" || firstChar === "-" || firstChar === " ";
|
|
29483
|
+
}
|
|
29484
|
+
function isFormatPatchVersionSeparator(line) {
|
|
29485
|
+
if (!line.startsWith("--")) return false;
|
|
29486
|
+
for (let index = 2; index < line.length; index++) {
|
|
29487
|
+
const char = line[index];
|
|
29488
|
+
if (char !== " " && char !== " " && char !== "\n" && char !== "\r") return false;
|
|
29489
|
+
}
|
|
29490
|
+
return true;
|
|
29491
|
+
}
|
|
29492
|
+
function parseHunkHeader(line) {
|
|
29493
|
+
if (!line.startsWith("@@ -")) return;
|
|
29494
|
+
let index = 4;
|
|
29495
|
+
const deletionStartResult = readPositiveInteger(line, index);
|
|
29496
|
+
if (deletionStartResult == null) return;
|
|
29497
|
+
const deletionStart = deletionStartResult.value;
|
|
29498
|
+
index = deletionStartResult.endIndex;
|
|
29499
|
+
let deletionCount = 1;
|
|
29500
|
+
if (line[index] === ",") {
|
|
29501
|
+
const deletionCountResult = readPositiveInteger(line, index + 1);
|
|
29502
|
+
if (deletionCountResult == null) return;
|
|
29503
|
+
deletionCount = deletionCountResult.value;
|
|
29504
|
+
index = deletionCountResult.endIndex;
|
|
29505
|
+
}
|
|
29506
|
+
if (line[index] !== " " || line[index + 1] !== "+") return;
|
|
29507
|
+
index += 2;
|
|
29508
|
+
const additionStartResult = readPositiveInteger(line, index);
|
|
29509
|
+
if (additionStartResult == null) return;
|
|
29510
|
+
const additionStart = additionStartResult.value;
|
|
29511
|
+
index = additionStartResult.endIndex;
|
|
29512
|
+
let additionCount = 1;
|
|
29513
|
+
if (line[index] === ",") {
|
|
29514
|
+
const additionCountResult = readPositiveInteger(line, index + 1);
|
|
29515
|
+
if (additionCountResult == null) return;
|
|
29516
|
+
additionCount = additionCountResult.value;
|
|
29517
|
+
index = additionCountResult.endIndex;
|
|
29518
|
+
}
|
|
29519
|
+
if (line[index] !== " " || line[index + 1] !== "@" || line[index + 2] !== "@") return;
|
|
29520
|
+
let hunkContext;
|
|
29521
|
+
const contextStartIndex = index + 3;
|
|
29522
|
+
if (line[contextStartIndex] === " ") hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));
|
|
29523
|
+
return {
|
|
29524
|
+
additionCount,
|
|
29525
|
+
additionStart,
|
|
29526
|
+
deletionCount,
|
|
29527
|
+
deletionStart,
|
|
29528
|
+
hunkContext
|
|
29529
|
+
};
|
|
29530
|
+
}
|
|
29531
|
+
function readPositiveInteger(value, startIndex) {
|
|
29532
|
+
let index = startIndex;
|
|
29533
|
+
let parsedValue = 0;
|
|
29534
|
+
for (; index < value.length; index++) {
|
|
29535
|
+
const digit = value.charCodeAt(index) - 48;
|
|
29536
|
+
if (digit < 0 || digit > 9) break;
|
|
29537
|
+
parsedValue = parsedValue * 10 + digit;
|
|
29538
|
+
}
|
|
29539
|
+
if (index === startIndex) return;
|
|
29540
|
+
return {
|
|
29541
|
+
value: parsedValue,
|
|
29542
|
+
endIndex: index
|
|
29543
|
+
};
|
|
29544
|
+
}
|
|
29545
|
+
function trimLineEnd(value) {
|
|
29546
|
+
if (value.endsWith("\r\n")) return value.slice(0, -2);
|
|
29547
|
+
if (value.endsWith("\n")) return value.slice(0, -1);
|
|
29548
|
+
return value;
|
|
29549
|
+
}
|
|
29550
|
+
function isGitDiffPatch(data) {
|
|
29551
|
+
return data.startsWith("diff --git") || data.includes("\ndiff --git");
|
|
29552
|
+
}
|
|
29553
|
+
function splitAtLinePrefix(contents, prefix) {
|
|
29554
|
+
if (contents.length === 0) return [""];
|
|
29555
|
+
const newlinePrefix = `\n${prefix}`;
|
|
29556
|
+
const firstBoundaryIndex = contents.startsWith(prefix) ? 0 : findLinePrefixIndex(contents, newlinePrefix, 0);
|
|
29557
|
+
if (firstBoundaryIndex === -1) return [contents];
|
|
29558
|
+
const parts = [];
|
|
29559
|
+
if (firstBoundaryIndex > 0) parts.push(contents.slice(0, firstBoundaryIndex));
|
|
29560
|
+
let startIndex = firstBoundaryIndex;
|
|
29561
|
+
for (;;) {
|
|
29562
|
+
const nextBoundaryIndex = findLinePrefixIndex(contents, newlinePrefix, startIndex + 1);
|
|
29563
|
+
if (nextBoundaryIndex === -1) break;
|
|
29564
|
+
parts.push(contents.slice(startIndex, nextBoundaryIndex));
|
|
29565
|
+
startIndex = nextBoundaryIndex;
|
|
29566
|
+
}
|
|
29567
|
+
parts.push(contents.slice(startIndex));
|
|
29568
|
+
return parts;
|
|
29569
|
+
}
|
|
29570
|
+
function findLinePrefixIndex(contents, newlinePrefix, fromIndex) {
|
|
29571
|
+
const index = contents.indexOf(newlinePrefix, fromIndex);
|
|
29572
|
+
return index === -1 ? -1 : index + 1;
|
|
29573
|
+
}
|
|
29574
|
+
function maybeDetachOptionalString(value) {
|
|
29575
|
+
return value == null ? value : detachString(value);
|
|
29576
|
+
}
|
|
29577
|
+
function parseRawLineType(firstChar) {
|
|
29578
|
+
return firstChar === " " ? "context" : firstChar === "\\" ? "metadata" : firstChar === "+" ? "addition" : "deletion";
|
|
29579
|
+
}
|
|
29580
|
+
function getParsedLineContent(rawLine) {
|
|
29581
|
+
const processedLine = rawLine.slice(1);
|
|
29582
|
+
return detachString(processedLine === "" ? "\n" : processedLine);
|
|
29583
|
+
}
|
|
29584
|
+
function createContentGroup(type, deletionLineIndex, additionLineIndex) {
|
|
29585
|
+
if (type === "change") return {
|
|
29586
|
+
type: "change",
|
|
29587
|
+
additions: 0,
|
|
29588
|
+
deletions: 0,
|
|
29589
|
+
additionLineIndex,
|
|
29590
|
+
deletionLineIndex
|
|
29591
|
+
};
|
|
29592
|
+
return {
|
|
29593
|
+
type: "context",
|
|
29594
|
+
lines: 0,
|
|
29595
|
+
additionLineIndex,
|
|
29596
|
+
deletionLineIndex
|
|
29597
|
+
};
|
|
29598
|
+
}
|
|
29599
|
+
//#endregion
|
|
29600
|
+
//#region src/checkpointing/ThreadChangedFiles.ts
|
|
29601
|
+
/**
|
|
29602
|
+
* Per-thread changed-file attribution for checkpoint diffs.
|
|
29603
|
+
*
|
|
29604
|
+
* A checkpoint snapshots the whole working tree (`git add -A`), so on a shared
|
|
29605
|
+
* worktree the diff between two of a thread's checkpoints also contains every
|
|
29606
|
+
* edit other threads made in the window. The thread's own tool activities are
|
|
29607
|
+
* per-thread by construction, and the inline changed-files summary the clients
|
|
29608
|
+
* derive from them is already the surface users trust. These helpers extract
|
|
29609
|
+
* that same path list on the server and cut a checkpoint diff down to it.
|
|
29610
|
+
*
|
|
29611
|
+
* `collectActivityChangedFilePaths` must stay in lockstep with
|
|
29612
|
+
* `collectChangedFiles` in `apps/web/src/session-logic.ts` (and the mobile
|
|
29613
|
+
* copy in `threadActivity.ts`): same field names, same nested keys, same depth
|
|
29614
|
+
* bound. The one deliberate difference is dropping the cap of 12 paths - that
|
|
29615
|
+
* cap exists for display, and a filter that stopped collecting would silently
|
|
29616
|
+
* drop real work from the diff.
|
|
29617
|
+
*
|
|
29618
|
+
* @module ThreadChangedFiles
|
|
29619
|
+
*/
|
|
29620
|
+
const CHANGED_FILE_FIELDS = [
|
|
29621
|
+
"path",
|
|
29622
|
+
"filePath",
|
|
29623
|
+
"relativePath",
|
|
29624
|
+
"filename",
|
|
29625
|
+
"newPath",
|
|
29626
|
+
"oldPath"
|
|
29627
|
+
];
|
|
29628
|
+
const NESTED_PAYLOAD_KEYS = [
|
|
29629
|
+
"item",
|
|
29630
|
+
"result",
|
|
29631
|
+
"input",
|
|
29632
|
+
"data",
|
|
29633
|
+
"changes",
|
|
29634
|
+
"files",
|
|
29635
|
+
"edits",
|
|
29636
|
+
"patch",
|
|
29637
|
+
"patches",
|
|
29638
|
+
"operations"
|
|
29639
|
+
];
|
|
29640
|
+
const MAX_COLLECT_DEPTH = 4;
|
|
29641
|
+
function asRecord$3(value) {
|
|
29642
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
29643
|
+
}
|
|
29644
|
+
function pushChangedFilePath(target, value) {
|
|
29645
|
+
if (typeof value !== "string") return;
|
|
29646
|
+
const normalized = value.trim();
|
|
29647
|
+
if (normalized.length === 0) return;
|
|
29648
|
+
target.add(normalized);
|
|
29649
|
+
}
|
|
29650
|
+
function collectChangedFilePaths(value, target, depth) {
|
|
29651
|
+
if (depth > MAX_COLLECT_DEPTH) return;
|
|
29652
|
+
if (Array.isArray(value)) {
|
|
29653
|
+
for (const entry of value) collectChangedFilePaths(entry, target, depth + 1);
|
|
29654
|
+
return;
|
|
29655
|
+
}
|
|
29656
|
+
const record = asRecord$3(value);
|
|
29657
|
+
if (!record) return;
|
|
29658
|
+
for (const field of CHANGED_FILE_FIELDS) pushChangedFilePath(target, record[field]);
|
|
29659
|
+
for (const nestedKey of NESTED_PAYLOAD_KEYS) if (nestedKey in record) collectChangedFilePaths(record[nestedKey], target, depth + 1);
|
|
29660
|
+
}
|
|
29661
|
+
/**
|
|
29662
|
+
* Extract the file paths a single activity payload names, exactly as the
|
|
29663
|
+
* clients do for the inline changed-files summary.
|
|
29664
|
+
*/
|
|
29665
|
+
function collectActivityChangedFilePaths(payload) {
|
|
29666
|
+
const target = /* @__PURE__ */ new Set();
|
|
29667
|
+
collectChangedFilePaths(asRecord$3(asRecord$3(payload)?.data), target, 0);
|
|
29668
|
+
return target;
|
|
29669
|
+
}
|
|
29670
|
+
/**
|
|
29671
|
+
* Normalize activity-named paths to the workspace-relative form git patches
|
|
29672
|
+
* use. Adapters report paths as the tool received them, which may be absolute;
|
|
29673
|
+
* a path outside the workspace cannot appear in the checkpoint diff and is
|
|
29674
|
+
* dropped rather than mismatched.
|
|
29675
|
+
*/
|
|
29676
|
+
function normalizeChangedFilePaths(changedPaths, workspaceCwd) {
|
|
29677
|
+
const workspacePrefix = workspaceCwd.endsWith("/") ? workspaceCwd : `${workspaceCwd}/`;
|
|
29678
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
29679
|
+
for (const changedPath of changedPaths) {
|
|
29680
|
+
let candidate = changedPath;
|
|
29681
|
+
if (candidate.startsWith(workspacePrefix)) candidate = candidate.slice(workspacePrefix.length);
|
|
29682
|
+
else if (candidate.startsWith("/")) continue;
|
|
29683
|
+
else if (candidate.startsWith("./")) candidate = candidate.slice(2);
|
|
29684
|
+
if (candidate.length > 0) normalized.add(candidate);
|
|
29685
|
+
}
|
|
29686
|
+
return normalized;
|
|
29687
|
+
}
|
|
29688
|
+
const DIFF_FILE_HEADER = /^diff --git /gm;
|
|
29689
|
+
function splitUnifiedDiffBlocks(diff) {
|
|
29690
|
+
const starts = [];
|
|
29691
|
+
for (const match of diff.matchAll(DIFF_FILE_HEADER)) starts.push(match.index);
|
|
29692
|
+
return starts.map((start, index) => diff.slice(start, starts[index + 1]));
|
|
29693
|
+
}
|
|
29694
|
+
function blockNamesPath(block, allowedPaths) {
|
|
29695
|
+
try {
|
|
29696
|
+
return parsePatchFiles(block).some((patch) => patch.files.some((file) => allowedPaths.has(file.name) || file.prevName !== void 0 && allowedPaths.has(file.prevName)));
|
|
29697
|
+
} catch {
|
|
29698
|
+
return true;
|
|
29699
|
+
}
|
|
29700
|
+
}
|
|
29701
|
+
/**
|
|
29702
|
+
* Cut a unified diff down to the files a thread's own activities name.
|
|
29703
|
+
*
|
|
29704
|
+
* `changedPaths` is the raw activity path list; normalization against
|
|
29705
|
+
* `workspaceCwd` happens here. The caller decides what an empty path list
|
|
29706
|
+
* means - this function treats it literally and returns an empty diff.
|
|
29707
|
+
*/
|
|
29708
|
+
function filterUnifiedDiffToChangedFiles(diff, changedPaths, workspaceCwd) {
|
|
29709
|
+
if (diff.length === 0) return diff;
|
|
29710
|
+
const allowedPaths = normalizeChangedFilePaths(changedPaths, workspaceCwd);
|
|
29711
|
+
return splitUnifiedDiffBlocks(diff).filter((block) => blockNamesPath(block, allowedPaths)).join("");
|
|
29712
|
+
}
|
|
29713
|
+
//#endregion
|
|
29714
|
+
//#region src/checkpointing/Errors.ts
|
|
29715
|
+
const CheckpointDiffOperation = Schema$1.Literals(["CheckpointDiffQuery.getTurnDiff", "CheckpointDiffQuery.getFullThreadDiff"]);
|
|
29716
|
+
/** The computed result does not satisfy the checkpoint RPC contract. */
|
|
29717
|
+
var CheckpointDiffResultInvalidError = class extends Schema$1.TaggedErrorClass()("CheckpointDiffResultInvalidError", {
|
|
29718
|
+
operation: CheckpointDiffOperation,
|
|
29719
|
+
threadId: ThreadId
|
|
29720
|
+
}) {
|
|
29721
|
+
get message() {
|
|
29722
|
+
const result = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
|
|
29723
|
+
return `Checkpoint invariant violation in ${this.operation}: Computed ${result} result does not satisfy contract schema.`;
|
|
29724
|
+
}
|
|
29725
|
+
};
|
|
29726
|
+
/** Projection state no longer contains the requested checkpoint thread. */
|
|
29727
|
+
var CheckpointThreadNotFoundError = class extends Schema$1.TaggedErrorClass()("CheckpointThreadNotFoundError", {
|
|
29728
|
+
operation: CheckpointDiffOperation,
|
|
29729
|
+
threadId: ThreadId
|
|
29730
|
+
}) {
|
|
29731
|
+
get message() {
|
|
29732
|
+
return `Checkpoint invariant violation in ${this.operation}: Thread '${this.threadId}' not found.`;
|
|
29733
|
+
}
|
|
29734
|
+
};
|
|
29735
|
+
/** The checkpoint thread has no workspace path from which to compute a diff. */
|
|
29736
|
+
var CheckpointWorkspacePathMissingError = class extends Schema$1.TaggedErrorClass()("CheckpointWorkspacePathMissingError", {
|
|
29737
|
+
operation: CheckpointDiffOperation,
|
|
29738
|
+
threadId: ThreadId
|
|
29739
|
+
}) {
|
|
29740
|
+
get message() {
|
|
29741
|
+
const diff = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
|
|
29742
|
+
return `Checkpoint invariant violation in ${this.operation}: Workspace path missing for thread '${this.threadId}' when computing ${diff}.`;
|
|
29743
|
+
}
|
|
29744
|
+
};
|
|
29745
|
+
/** The requested turn lies beyond the latest available checkpoint. */
|
|
29746
|
+
var CheckpointTurnRangeUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointTurnRangeUnavailableError", {
|
|
29747
|
+
operation: CheckpointDiffOperation,
|
|
29748
|
+
threadId: ThreadId,
|
|
29749
|
+
requestedTurnCount: NonNegativeInt,
|
|
29750
|
+
availableTurnCount: NonNegativeInt
|
|
29751
|
+
}) {
|
|
29752
|
+
get message() {
|
|
29753
|
+
return `Checkpoint unavailable for thread ${this.threadId} turn ${this.requestedTurnCount}: Turn diff range exceeds current turn count: requested ${this.requestedTurnCount}, current ${this.availableTurnCount}.`;
|
|
29754
|
+
}
|
|
29755
|
+
};
|
|
29756
|
+
/** Expected checkpoint metadata does not contain the requested Git ref. */
|
|
29757
|
+
var CheckpointRefUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointRefUnavailableError", {
|
|
29758
|
+
operation: CheckpointDiffOperation,
|
|
29759
|
+
threadId: ThreadId,
|
|
29760
|
+
turnCount: NonNegativeInt,
|
|
29761
|
+
checkpoint: Schema$1.Literals(["from", "to"])
|
|
29762
|
+
}) {
|
|
29763
|
+
get message() {
|
|
29764
|
+
return `Checkpoint unavailable for thread ${this.threadId} turn ${this.turnCount}: Checkpoint ref is unavailable for turn ${this.turnCount}.`;
|
|
29765
|
+
}
|
|
29766
|
+
};
|
|
29767
|
+
//#endregion
|
|
29768
|
+
//#region src/checkpointing/Utils.ts
|
|
29769
|
+
const CHECKPOINT_REFS_PREFIX = "refs/p4/checkpoints";
|
|
29770
|
+
function checkpointRefForThreadTurn(threadId, turnCount) {
|
|
29771
|
+
return CheckpointRef.make(`${CHECKPOINT_REFS_PREFIX}/${Encoding.encodeBase64Url(threadId)}/turn/${turnCount}`);
|
|
29772
|
+
}
|
|
29773
|
+
function resolveThreadWorkspaceCwd(input) {
|
|
29774
|
+
const worktreeCwd = input.thread.worktreePath ?? void 0;
|
|
29775
|
+
if (worktreeCwd) return worktreeCwd;
|
|
29776
|
+
return input.projects.find((project) => project.id === input.thread.projectId)?.workspaceRoot;
|
|
29777
|
+
}
|
|
29778
|
+
//#endregion
|
|
29779
|
+
//#region src/git/remoteRefs.ts
|
|
29780
|
+
function parseRemoteNamesInGitOrder(stdout) {
|
|
29781
|
+
const remoteNames = [];
|
|
29782
|
+
for (const line of stdout.split("\n")) {
|
|
29783
|
+
const remoteName = line.trim();
|
|
29784
|
+
if (remoteName.length > 0) remoteNames.push(remoteName);
|
|
29785
|
+
}
|
|
29786
|
+
return remoteNames;
|
|
29787
|
+
}
|
|
29788
|
+
function parseRemoteNames(stdout) {
|
|
29789
|
+
return parseRemoteNamesInGitOrder(stdout).toSorted((a, b) => b.length - a.length);
|
|
29790
|
+
}
|
|
29791
|
+
function parseRemoteRefWithRemoteNames(ref, remoteNames) {
|
|
29792
|
+
const trimmedRef = ref.trim();
|
|
29793
|
+
if (trimmedRef.length === 0) return null;
|
|
29794
|
+
for (const remoteName of remoteNames) {
|
|
29795
|
+
const remotePrefix = `${remoteName}/`;
|
|
29796
|
+
if (!trimmedRef.startsWith(remotePrefix)) continue;
|
|
29797
|
+
const branchName = trimmedRef.slice(remotePrefix.length).trim();
|
|
29798
|
+
if (branchName.length === 0) return null;
|
|
29799
|
+
return {
|
|
29800
|
+
remoteRef: trimmedRef,
|
|
29801
|
+
remoteName,
|
|
29802
|
+
branchName
|
|
29803
|
+
};
|
|
29804
|
+
}
|
|
29805
|
+
return null;
|
|
29806
|
+
}
|
|
29807
|
+
function extractBranchNameFromRemoteRef(ref, options) {
|
|
29808
|
+
const normalized = ref.trim();
|
|
29809
|
+
if (normalized.length === 0) return "";
|
|
29810
|
+
if (normalized.startsWith("refs/remotes/")) return extractBranchNameFromRemoteRef(normalized.slice(13), options);
|
|
29811
|
+
const parsedRemoteRef = parseRemoteRefWithRemoteNames(normalized, options?.remoteName ? [options.remoteName] : options?.remoteNames ?? []);
|
|
29812
|
+
if (parsedRemoteRef) return parsedRemoteRef.branchName;
|
|
29813
|
+
const firstSlash = normalized.indexOf("/");
|
|
29814
|
+
if (firstSlash === -1) return normalized;
|
|
29815
|
+
return normalized.slice(firstSlash + 1).trim();
|
|
29816
|
+
}
|
|
29817
|
+
//#endregion
|
|
29818
|
+
//#region src/vcs/GitVcsDriverCore.ts
|
|
29819
|
+
const DEFAULT_TIMEOUT_MS$5 = 3e4;
|
|
29820
|
+
const DEFAULT_MAX_OUTPUT_BYTES$1 = 1e6;
|
|
29821
|
+
const OUTPUT_TRUNCATED_MARKER$1 = "\n\n[truncated]";
|
|
29822
|
+
const PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES = 49e3;
|
|
29823
|
+
const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
|
|
29824
|
+
const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
|
|
29825
|
+
const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59e3;
|
|
29826
|
+
const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 12e4;
|
|
29827
|
+
const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 8e4;
|
|
29828
|
+
const WORKSPACE_FILES_MAX_OUTPUT_BYTES$1 = 12e4;
|
|
29829
|
+
const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15);
|
|
29830
|
+
const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5);
|
|
29831
|
+
const STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN = Duration.seconds(30);
|
|
29832
|
+
const STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN = Duration.minutes(15);
|
|
29833
|
+
const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2048;
|
|
29834
|
+
const REPOSITORY_PATHS_CACHE_CAPACITY = 2048;
|
|
29835
|
+
const REPOSITORY_PATHS_CACHE_TTL = Duration.minutes(10);
|
|
29836
|
+
const REPOSITORY_PATHS_REFRESH_COALESCE_TTL = Duration.seconds(5);
|
|
29837
|
+
const NON_REPOSITORY_PATHS_CACHE_TTL = Duration.seconds(1);
|
|
29838
|
+
const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
|
|
29839
|
+
const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
|
|
29840
|
+
const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
|
|
29841
|
+
const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
|
|
29842
|
+
const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
|
|
29843
|
+
GCM_INTERACTIVE: "never",
|
|
29844
|
+
GIT_ASKPASS: "",
|
|
29845
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
29846
|
+
SSH_ASKPASS: "",
|
|
29847
|
+
SSH_ASKPASS_REQUIRE: "never"
|
|
29848
|
+
});
|
|
29849
|
+
const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"];
|
|
29850
|
+
const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100;
|
|
29851
|
+
const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
|
|
29852
|
+
isRepo: false,
|
|
29853
|
+
hasOriginRemote: false,
|
|
29854
|
+
isDefaultBranch: false,
|
|
29855
|
+
branch: null,
|
|
29856
|
+
upstreamRef: null,
|
|
29857
|
+
hasWorkingTreeChanges: false,
|
|
29858
|
+
workingTree: {
|
|
29859
|
+
files: [],
|
|
29860
|
+
insertions: 0,
|
|
29861
|
+
deletions: 0
|
|
29862
|
+
},
|
|
29863
|
+
hasUpstream: false,
|
|
29864
|
+
aheadCount: 0,
|
|
29865
|
+
behindCount: 0,
|
|
29866
|
+
aheadOfDefaultCount: 0
|
|
29867
|
+
});
|
|
29868
|
+
const NON_REPOSITORY_REMOTE_STATUS_DETAILS = Object.freeze({
|
|
29869
|
+
isRepo: false,
|
|
29870
|
+
isDefaultBranch: false,
|
|
29871
|
+
branch: null,
|
|
29872
|
+
upstreamRef: null,
|
|
29873
|
+
hasUpstream: false,
|
|
29874
|
+
aheadCount: 0,
|
|
29875
|
+
behindCount: 0,
|
|
29876
|
+
aheadOfDefaultCount: 0
|
|
29877
|
+
});
|
|
29878
|
+
var StatusRemoteRefreshCacheKey = class extends Data.Class {};
|
|
29879
|
+
function statusUpstreamRefreshFailureCooldown(consecutiveFailures) {
|
|
29880
|
+
const exponent = Math.max(0, consecutiveFailures - 1);
|
|
29881
|
+
const cooldownMs = Duration.toMillis(STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN) * Math.pow(2, exponent);
|
|
29882
|
+
return Duration.min(Duration.millis(cooldownMs), STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN);
|
|
29883
|
+
}
|
|
29884
|
+
var GitRefsSnapshotCacheKey = class extends Data.Class {};
|
|
29885
|
+
var GitRefsRefreshCacheKey = class extends Data.Class {};
|
|
29886
|
+
function parseBranchAb(value) {
|
|
29887
|
+
const match = value.match(/^\+(\d+)\s+-(\d+)$/);
|
|
29888
|
+
if (!match) return {
|
|
29889
|
+
ahead: 0,
|
|
29890
|
+
behind: 0
|
|
29891
|
+
};
|
|
29892
|
+
return {
|
|
29893
|
+
ahead: Number(match[1] ?? "0"),
|
|
29894
|
+
behind: Number(match[2] ?? "0")
|
|
29895
|
+
};
|
|
29896
|
+
}
|
|
29897
|
+
function parseNumstatEntries(stdout) {
|
|
29898
|
+
const entries = [];
|
|
29899
|
+
for (const line of stdout.split(/\r?\n/g)) {
|
|
29900
|
+
if (line.trim().length === 0) continue;
|
|
29901
|
+
const [addedRaw, deletedRaw, ...pathParts] = line.split(" ");
|
|
29902
|
+
const rawPath = pathParts.length > 1 ? (pathParts.at(-1) ?? "").trim() : pathParts.join(" ").trim();
|
|
29903
|
+
if (rawPath.length === 0) continue;
|
|
29904
|
+
const added = Number.parseInt(addedRaw ?? "0", 10);
|
|
29905
|
+
const deleted = Number.parseInt(deletedRaw ?? "0", 10);
|
|
29906
|
+
const renameArrowIndex = rawPath.indexOf(" => ");
|
|
29907
|
+
const normalizedPath = renameArrowIndex >= 0 ? rawPath.slice(renameArrowIndex + 4).trim() : rawPath;
|
|
29908
|
+
entries.push({
|
|
29909
|
+
path: normalizedPath.length > 0 ? normalizedPath : rawPath,
|
|
29910
|
+
insertions: Number.isFinite(added) ? added : 0,
|
|
29911
|
+
deletions: Number.isFinite(deleted) ? deleted : 0
|
|
29912
|
+
});
|
|
29913
|
+
}
|
|
29914
|
+
return entries;
|
|
29915
|
+
}
|
|
29916
|
+
function parsePorcelainPath(line) {
|
|
29917
|
+
if (line.startsWith("? ") || line.startsWith("! ")) {
|
|
29918
|
+
const simple = line.slice(2).trim();
|
|
29919
|
+
return simple.length > 0 ? simple : null;
|
|
29920
|
+
}
|
|
29921
|
+
if (!(line.startsWith("1 ") || line.startsWith("2 ") || line.startsWith("u "))) return null;
|
|
29922
|
+
const tabIndex = line.indexOf(" ");
|
|
29923
|
+
if (tabIndex >= 0) {
|
|
29924
|
+
const [filePath] = line.slice(tabIndex + 1).split(" ");
|
|
29925
|
+
return filePath?.trim().length ? filePath.trim() : null;
|
|
29926
|
+
}
|
|
29927
|
+
const filePath = line.trim().split(/\s+/g).at(-1) ?? "";
|
|
29928
|
+
return filePath.length > 0 ? filePath : null;
|
|
29929
|
+
}
|
|
29930
|
+
function filterBranchesForListQuery(refs, query) {
|
|
29931
|
+
if (!query) return refs;
|
|
29932
|
+
const normalizedQuery = query.toLowerCase();
|
|
29933
|
+
return refs.filter((refName) => refName.name.toLowerCase().includes(normalizedQuery));
|
|
29934
|
+
}
|
|
29935
|
+
function paginateBranches(input) {
|
|
29936
|
+
const cursor = input.cursor ?? 0;
|
|
29937
|
+
const limit = input.limit ?? GIT_LIST_BRANCHES_DEFAULT_LIMIT;
|
|
29938
|
+
const totalCount = input.refs.length;
|
|
29939
|
+
const refs = input.refs.slice(cursor, cursor + limit);
|
|
29940
|
+
return {
|
|
29941
|
+
refs,
|
|
29942
|
+
nextCursor: cursor + refs.length < totalCount ? cursor + refs.length : null,
|
|
29943
|
+
totalCount
|
|
29944
|
+
};
|
|
29945
|
+
}
|
|
29946
|
+
function parseWorktreeBranchPaths(stdout) {
|
|
29947
|
+
const worktreePaths = /* @__PURE__ */ new Map();
|
|
29948
|
+
let currentPath = null;
|
|
29949
|
+
let currentBranch = null;
|
|
29950
|
+
let currentPrunable = false;
|
|
29951
|
+
const flush = () => {
|
|
29952
|
+
if (currentPath !== null && currentBranch !== null && !currentPrunable) worktreePaths.set(currentBranch, currentPath);
|
|
29953
|
+
currentPath = null;
|
|
29954
|
+
currentBranch = null;
|
|
29955
|
+
currentPrunable = false;
|
|
29956
|
+
};
|
|
29957
|
+
for (const field of stdout.split("\0")) if (field === "") flush();
|
|
29958
|
+
else if (field.startsWith("worktree ")) currentPath = field.slice(9);
|
|
29959
|
+
else if (field.startsWith("branch refs/heads/")) currentBranch = field.slice(18);
|
|
29960
|
+
else if (field === "prunable" || field.startsWith("prunable ")) currentPrunable = true;
|
|
29961
|
+
flush();
|
|
29962
|
+
return worktreePaths;
|
|
29963
|
+
}
|
|
29964
|
+
function splitNullSeparatedPaths$1(input, truncated) {
|
|
29965
|
+
const parts = input.split("\0");
|
|
29966
|
+
if (parts.length === 0) return [];
|
|
29967
|
+
if (truncated && parts[parts.length - 1]?.length) parts.pop();
|
|
29968
|
+
return parts.filter((value) => value.length > 0);
|
|
29969
|
+
}
|
|
29970
|
+
function splitNullSeparatedGitStdoutPaths(result) {
|
|
29971
|
+
return splitNullSeparatedPaths$1(result.stdout, result.stdoutTruncated);
|
|
29972
|
+
}
|
|
29973
|
+
function sanitizeRemoteName(value) {
|
|
29974
|
+
const sanitized = value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
29975
|
+
return sanitized.length > 0 ? sanitized : "fork";
|
|
29976
|
+
}
|
|
29977
|
+
function parseRemoteFetchUrls(stdout) {
|
|
29978
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
29979
|
+
for (const line of stdout.split("\n")) {
|
|
29980
|
+
const trimmed = line.trim();
|
|
29981
|
+
if (trimmed.length === 0) continue;
|
|
29982
|
+
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)$/.exec(trimmed);
|
|
29983
|
+
if (!match) continue;
|
|
29984
|
+
const [, remoteName = "", remoteUrl = "", direction = ""] = match;
|
|
29985
|
+
if (direction !== "fetch" || remoteName.length === 0 || remoteUrl.length === 0) continue;
|
|
29986
|
+
remotes.set(remoteName, remoteUrl);
|
|
29987
|
+
}
|
|
29988
|
+
return remotes;
|
|
29989
|
+
}
|
|
29990
|
+
function parseUpstreamRefWithRemoteNames(upstreamRef, remoteNames) {
|
|
29991
|
+
const parsed = parseRemoteRefWithRemoteNames(upstreamRef, remoteNames);
|
|
29992
|
+
if (!parsed) return null;
|
|
29993
|
+
return {
|
|
29994
|
+
upstreamRef,
|
|
29995
|
+
remoteName: parsed.remoteName,
|
|
29996
|
+
branchName: parsed.branchName
|
|
29997
|
+
};
|
|
29998
|
+
}
|
|
29999
|
+
function parseUpstreamRefByFirstSeparator(upstreamRef) {
|
|
30000
|
+
const separatorIndex = upstreamRef.indexOf("/");
|
|
30001
|
+
if (separatorIndex <= 0 || separatorIndex === upstreamRef.length - 1) return null;
|
|
30002
|
+
const remoteName = upstreamRef.slice(0, separatorIndex).trim();
|
|
30003
|
+
const branchName = upstreamRef.slice(separatorIndex + 1).trim();
|
|
30004
|
+
if (remoteName.length === 0 || branchName.length === 0) return null;
|
|
30005
|
+
return {
|
|
30006
|
+
upstreamRef,
|
|
30007
|
+
remoteName,
|
|
30008
|
+
branchName
|
|
30009
|
+
};
|
|
30010
|
+
}
|
|
30011
|
+
function parseTrackingBranchByUpstreamRef(stdout, upstreamRef) {
|
|
30012
|
+
for (const line of stdout.split("\n")) {
|
|
30013
|
+
const trimmedLine = line.trim();
|
|
30014
|
+
if (trimmedLine.length === 0) continue;
|
|
30015
|
+
const [branchNameRaw, upstreamBranchRaw = ""] = trimmedLine.split(" ");
|
|
30016
|
+
const branchName = branchNameRaw?.trim() ?? "";
|
|
30017
|
+
const candidateUpstreamRef = upstreamBranchRaw.trim();
|
|
30018
|
+
if (branchName.length === 0 || candidateUpstreamRef.length === 0) continue;
|
|
30019
|
+
if (candidateUpstreamRef === upstreamRef) return branchName;
|
|
30020
|
+
}
|
|
30021
|
+
return null;
|
|
30022
|
+
}
|
|
30023
|
+
function deriveLocalBranchNameFromRemoteRef(branchName) {
|
|
30024
|
+
const separatorIndex = branchName.indexOf("/");
|
|
30025
|
+
if (separatorIndex <= 0 || separatorIndex === branchName.length - 1) return null;
|
|
30026
|
+
const localBranch = branchName.slice(separatorIndex + 1).trim();
|
|
30027
|
+
return localBranch.length > 0 ? localBranch : null;
|
|
30028
|
+
}
|
|
30029
|
+
function gitCommandContext(input) {
|
|
30030
|
+
return {
|
|
30031
|
+
operation: input.operation,
|
|
30032
|
+
command: "git",
|
|
30033
|
+
cwd: input.cwd,
|
|
30034
|
+
argumentCount: input.args.length
|
|
30035
|
+
};
|
|
30036
|
+
}
|
|
30037
|
+
function parseDefaultBranchFromRemoteHeadRef(value, remoteName) {
|
|
30038
|
+
const trimmed = value.trim();
|
|
30039
|
+
const prefix = `refs/remotes/${remoteName}/`;
|
|
30040
|
+
if (!trimmed.startsWith(prefix)) return null;
|
|
30041
|
+
const refName = trimmed.slice(prefix.length).trim();
|
|
30042
|
+
return refName.length > 0 ? refName : null;
|
|
30043
|
+
}
|
|
30044
|
+
function isMissingGitCwdError(error) {
|
|
30045
|
+
if (!(error.cause instanceof PlatformError.PlatformError)) return false;
|
|
30046
|
+
const reason = error.cause.reason;
|
|
30047
|
+
if (reason._tag === "NotFound") return reason.pathOrDescriptor === error.cwd;
|
|
30048
|
+
return reason._tag === "BadResource" && reason.pathOrDescriptor === error.cwd && typeof reason.cause === "object" && reason.cause !== null && "code" in reason.cause && reason.cause.code === "ENOTDIR";
|
|
30049
|
+
}
|
|
30050
|
+
function isNonRepositoryGitStderr(stderr) {
|
|
30051
|
+
return stderr.toLowerCase().includes("not a git repository");
|
|
30052
|
+
}
|
|
30053
|
+
const nowUnixNano = DateTime.now.pipe(Effect.map((now) => BigInt(DateTime.toEpochMillis(now)) * 1000000n));
|
|
30054
|
+
const addCurrentSpanEvent = (name, attributes) => Effect.gen(function* () {
|
|
30055
|
+
const span = yield* Effect.currentSpan;
|
|
30056
|
+
const timestamp = yield* nowUnixNano;
|
|
30057
|
+
yield* Effect.sync(() => {
|
|
30058
|
+
span.event(name, timestamp, compactTraceAttributes(attributes));
|
|
30059
|
+
});
|
|
30060
|
+
}).pipe(Effect.catchTags({ NoSuchElementError: () => Effect.void }));
|
|
30061
|
+
function trace2ChildKey(record) {
|
|
30062
|
+
const childId = record.child_id;
|
|
30063
|
+
if (typeof childId === "number" || typeof childId === "string") return String(childId);
|
|
30064
|
+
const hookName = record.hook_name;
|
|
30065
|
+
return typeof hookName === "string" && hookName.trim().length > 0 ? hookName.trim() : null;
|
|
30066
|
+
}
|
|
30067
|
+
const Trace2Record = Schema$1.Record(Schema$1.String, Schema$1.Unknown);
|
|
30068
|
+
const createTrace2Monitor = Effect.fn("createTrace2Monitor")(function* (input, progress) {
|
|
30069
|
+
if (!progress?.onHookStarted && !progress?.onHookFinished) return {
|
|
30070
|
+
env: {},
|
|
30071
|
+
flush: Effect.void
|
|
30072
|
+
};
|
|
30073
|
+
const fs = yield* FileSystem.FileSystem;
|
|
30074
|
+
const path = yield* Path.Path;
|
|
30075
|
+
const traceFilePath = yield* fs.makeTempFileScoped({
|
|
30076
|
+
prefix: `p4code-git-trace2-${process.pid}-`,
|
|
30077
|
+
suffix: ".json"
|
|
30078
|
+
});
|
|
30079
|
+
const hookStartByChildKey = /* @__PURE__ */ new Map();
|
|
30080
|
+
const traceTailState = yield* Ref.make({
|
|
30081
|
+
processedChars: 0,
|
|
30082
|
+
remainder: ""
|
|
30083
|
+
});
|
|
30084
|
+
const handleTraceLine = Effect.fn("handleTraceLine")(function* (line) {
|
|
30085
|
+
const trimmedLine = line.trim();
|
|
30086
|
+
if (trimmedLine.length === 0) return;
|
|
30087
|
+
const traceRecord = decodeJsonResult(Trace2Record)(trimmedLine);
|
|
30088
|
+
if (Result.isFailure(traceRecord)) {
|
|
30089
|
+
yield* Effect.logDebug(`GitVcsDriver.trace2: failed to parse trace line for ${input.operation} in ${input.cwd} (${input.args.length} arguments)`, traceRecord.failure);
|
|
30090
|
+
return;
|
|
30091
|
+
}
|
|
30092
|
+
if (traceRecord.success.child_class !== "hook") return;
|
|
30093
|
+
const event = traceRecord.success.event;
|
|
30094
|
+
const childKey = trace2ChildKey(traceRecord.success);
|
|
30095
|
+
if (childKey === null) return;
|
|
30096
|
+
const started = hookStartByChildKey.get(childKey);
|
|
30097
|
+
const hookNameFromEvent = typeof traceRecord.success.hook_name === "string" ? traceRecord.success.hook_name.trim() : "";
|
|
30098
|
+
const hookName = hookNameFromEvent.length > 0 ? hookNameFromEvent : started?.hookName ?? "";
|
|
30099
|
+
if (hookName.length === 0) return;
|
|
30100
|
+
if (event === "child_start") {
|
|
30101
|
+
const now = yield* DateTime.now;
|
|
30102
|
+
hookStartByChildKey.set(childKey, {
|
|
30103
|
+
hookName,
|
|
30104
|
+
startedAtMs: DateTime.toEpochMillis(now)
|
|
30105
|
+
});
|
|
30106
|
+
yield* addCurrentSpanEvent("git.hook.started", { hookName });
|
|
30107
|
+
if (progress.onHookStarted) yield* progress.onHookStarted(hookName);
|
|
30108
|
+
return;
|
|
30109
|
+
}
|
|
30110
|
+
if (event === "child_exit") {
|
|
30111
|
+
hookStartByChildKey.delete(childKey);
|
|
30112
|
+
const code = traceRecord.success.exitCode;
|
|
30113
|
+
const exitCode = typeof code === "number" && Number.isInteger(code) ? code : null;
|
|
30114
|
+
const now = yield* DateTime.now;
|
|
30115
|
+
const durationMs = started ? Math.max(0, DateTime.toEpochMillis(now) - started.startedAtMs) : null;
|
|
30116
|
+
yield* addCurrentSpanEvent("git.hook.finished", {
|
|
30117
|
+
hookName: started?.hookName ?? hookName,
|
|
30118
|
+
exitCode,
|
|
30119
|
+
durationMs
|
|
30120
|
+
});
|
|
29401
30121
|
if (progress.onHookFinished) yield* progress.onHookFinished({
|
|
29402
30122
|
hookName: started?.hookName ?? hookName,
|
|
29403
30123
|
exitCode,
|
|
@@ -31596,6 +32316,32 @@ function buildTurnDiffResult(input, diff) {
|
|
|
31596
32316
|
const make$39 = Effect.gen(function* () {
|
|
31597
32317
|
const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
|
|
31598
32318
|
const checkpointStore = yield* CheckpointStore;
|
|
32319
|
+
const threadActivities = yield* ProjectionThreadActivityRepository;
|
|
32320
|
+
/**
|
|
32321
|
+
* The file paths this thread's own tool activities name, optionally limited
|
|
32322
|
+
* to a set of turns. This is the attribution a checkpoint cannot provide: a
|
|
32323
|
+
* checkpoint snapshots the whole tree, so on a shared worktree its diff also
|
|
32324
|
+
* contains other threads' edits, while activities are per-thread by
|
|
32325
|
+
* construction.
|
|
32326
|
+
*/
|
|
32327
|
+
const collectThreadChangedFilePaths = Effect.fn("CheckpointDiffQuery.collectThreadChangedFilePaths")(function* (threadId, turnIds) {
|
|
32328
|
+
const activities = yield* threadActivities.listByThreadId({ threadId });
|
|
32329
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
32330
|
+
for (const activity of activities) {
|
|
32331
|
+
if (activity.tone !== "tool") continue;
|
|
32332
|
+
if (turnIds !== null && (activity.turnId === null || !turnIds.has(activity.turnId))) continue;
|
|
32333
|
+
for (const changedPath of collectActivityChangedFilePaths(activity.payload)) changedPaths.add(changedPath);
|
|
32334
|
+
}
|
|
32335
|
+
return changedPaths;
|
|
32336
|
+
});
|
|
32337
|
+
/**
|
|
32338
|
+
* A thread whose activities name no paths at all gets the unfiltered diff.
|
|
32339
|
+
* That is the deliberate escape valve: a thread working purely through shell
|
|
32340
|
+
* commands never names the files it touched, and erasing its whole diff
|
|
32341
|
+
* would be worse than the shared-worktree contamination this filter exists
|
|
32342
|
+
* to remove.
|
|
32343
|
+
*/
|
|
32344
|
+
const attributeDiffToThread = (diff, changedPaths, workspaceCwd) => changedPaths.size === 0 ? diff : filterUnifiedDiffToChangedFiles(diff, changedPaths, workspaceCwd);
|
|
31599
32345
|
const getTurnDiff = Effect.fn("getTurnDiff")(function* (input) {
|
|
31600
32346
|
const operation = "CheckpointDiffQuery.getTurnDiff";
|
|
31601
32347
|
const ignoreWhitespace = input.ignoreWhitespace ?? true;
|
|
@@ -31649,13 +32395,16 @@ const make$39 = Effect.gen(function* () {
|
|
|
31649
32395
|
turnCount: input.toTurnCount,
|
|
31650
32396
|
checkpoint: "to"
|
|
31651
32397
|
});
|
|
31652
|
-
const
|
|
32398
|
+
const diff = yield* checkpointStore.diffCheckpoints({
|
|
31653
32399
|
cwd: workspaceCwd,
|
|
31654
32400
|
fromCheckpointRef,
|
|
31655
32401
|
toCheckpointRef,
|
|
31656
32402
|
fallbackFromToHead: false,
|
|
31657
32403
|
ignoreWhitespace
|
|
31658
|
-
}).pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints"))
|
|
32404
|
+
}).pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints"));
|
|
32405
|
+
const turnIdsInRange = new Set(threadContext.value.checkpoints.filter((checkpoint) => checkpoint.checkpointTurnCount > input.fromTurnCount && checkpoint.checkpointTurnCount <= input.toTurnCount).map((checkpoint) => checkpoint.turnId));
|
|
32406
|
+
const changedPaths = yield* collectThreadChangedFilePaths(input.threadId, turnIdsInRange);
|
|
32407
|
+
const turnDiff = buildTurnDiffResult(input, attributeDiffToThread(diff, changedPaths, workspaceCwd));
|
|
31659
32408
|
if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
|
|
31660
32409
|
operation,
|
|
31661
32410
|
threadId: input.threadId
|
|
@@ -31713,11 +32462,12 @@ const make$39 = Effect.gen(function* () {
|
|
|
31713
32462
|
fallbackFromToHead: false,
|
|
31714
32463
|
ignoreWhitespace
|
|
31715
32464
|
}).pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints"));
|
|
32465
|
+
const changedPaths = yield* collectThreadChangedFilePaths(input.threadId, null);
|
|
31716
32466
|
const turnDiff = buildTurnDiffResult({
|
|
31717
32467
|
threadId: input.threadId,
|
|
31718
32468
|
fromTurnCount: 0,
|
|
31719
32469
|
toTurnCount: input.toTurnCount
|
|
31720
|
-
}, diff);
|
|
32470
|
+
}, attributeDiffToThread(diff, changedPaths, workspaceCwd));
|
|
31721
32471
|
if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
|
|
31722
32472
|
operation,
|
|
31723
32473
|
threadId: input.threadId
|
|
@@ -32297,7 +33047,7 @@ const normalizeDispatchCommand = (command) => Effect.gen(function* () {
|
|
|
32297
33047
|
const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
|
|
32298
33048
|
taskId,
|
|
32299
33049
|
projectId: input.projectId ?? null,
|
|
32300
|
-
repositoryKey,
|
|
33050
|
+
repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
|
|
32301
33051
|
threadId: input.threadId ?? null,
|
|
32302
33052
|
title: input.title,
|
|
32303
33053
|
body: input.body ?? "",
|
|
@@ -35368,6 +36118,18 @@ const requireTaskCapability = Effect.fn("mcp.requireTaskCapability")(function* (
|
|
|
35368
36118
|
});
|
|
35369
36119
|
return invocation;
|
|
35370
36120
|
});
|
|
36121
|
+
/** Third of the trio, for the same reason the second exists. */
|
|
36122
|
+
const requireThreadCapability = Effect.fn("mcp.requireThreadCapability")(function* () {
|
|
36123
|
+
const invocation = yield* McpInvocationContext;
|
|
36124
|
+
if (!invocation.capabilities.has("threads")) return yield* new ThreadToolUnavailableError({
|
|
36125
|
+
capability: "threads",
|
|
36126
|
+
environmentId: invocation.environmentId,
|
|
36127
|
+
threadId: invocation.threadId,
|
|
36128
|
+
providerSessionId: invocation.providerSessionId,
|
|
36129
|
+
providerInstanceId: invocation.providerInstanceId
|
|
36130
|
+
});
|
|
36131
|
+
return invocation;
|
|
36132
|
+
});
|
|
35371
36133
|
//#endregion
|
|
35372
36134
|
//#region src/mcp/PreviewAutomationBroker.ts
|
|
35373
36135
|
var PreviewAutomationBroker = class extends Context.Service()("@p4code/cli/mcp/PreviewAutomationBroker") {};
|
|
@@ -44316,7 +45078,11 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
44316
45078
|
threadId: ThreadId.make(request.threadId),
|
|
44317
45079
|
providerSessionId,
|
|
44318
45080
|
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
44319
|
-
capabilities: /* @__PURE__ */ new Set([
|
|
45081
|
+
capabilities: /* @__PURE__ */ new Set([
|
|
45082
|
+
"preview",
|
|
45083
|
+
"tasks",
|
|
45084
|
+
"threads"
|
|
45085
|
+
]),
|
|
44320
45086
|
issuedAt
|
|
44321
45087
|
};
|
|
44322
45088
|
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
@@ -81339,7 +82105,7 @@ const invokeTargeted = (operation, input, timeoutMs) => {
|
|
|
81339
82105
|
const { tabId, ...operationInput } = input;
|
|
81340
82106
|
return invoke(operation, operationInput, timeoutMs, tabId);
|
|
81341
82107
|
};
|
|
81342
|
-
const handlers$
|
|
82108
|
+
const handlers$2 = {
|
|
81343
82109
|
preview_status: (input) => invokeTargeted("status", input ?? {}),
|
|
81344
82110
|
preview_open: (input) => invokeTargeted("open", normalizePreviewOpenInput(input)),
|
|
81345
82111
|
preview_navigate: (input) => invokeTargeted("navigate", input, input.timeoutMs),
|
|
@@ -81355,10 +82121,10 @@ const handlers$1 = {
|
|
|
81355
82121
|
preview_recording_start: (input) => invokeTargeted("recordingStart", input ?? {}),
|
|
81356
82122
|
preview_recording_stop: (input) => invokeTargeted("recordingStop", input ?? {})
|
|
81357
82123
|
};
|
|
81358
|
-
const { preview_snapshot, ...standardHandlers } = handlers$
|
|
82124
|
+
const { preview_snapshot, ...standardHandlers } = handlers$2;
|
|
81359
82125
|
const PreviewStandardToolkitHandlersLive = PreviewStandardToolkit.toLayer(standardHandlers);
|
|
81360
82126
|
const PreviewSnapshotToolkitHandlersLive = PreviewSnapshotToolkit.toLayer({ preview_snapshot });
|
|
81361
|
-
PreviewToolkit.toLayer(handlers$
|
|
82127
|
+
PreviewToolkit.toLayer(handlers$2);
|
|
81362
82128
|
//#endregion
|
|
81363
82129
|
//#region src/mcp/toolkits/tasks/tools.ts
|
|
81364
82130
|
const dependencies = [McpInvocationContext, TaskRepository];
|
|
@@ -81504,6 +82270,93 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
81504
82270
|
})
|
|
81505
82271
|
});
|
|
81506
82272
|
//#endregion
|
|
82273
|
+
//#region src/mcp/toolkits/threads/tools.ts
|
|
82274
|
+
/**
|
|
82275
|
+
* Self-scoped on purpose: neither tool takes a thread id. The thread acted on
|
|
82276
|
+
* is always the one in `McpInvocationScope`, exactly as `task_current` resolves
|
|
82277
|
+
* it, so a leaked per-session token cannot reach any other thread. Delete,
|
|
82278
|
+
* create and archive are deliberately not exposed; settle could never succeed
|
|
82279
|
+
* from here because the decider refuses it while the calling session is alive.
|
|
82280
|
+
*/
|
|
82281
|
+
const ThreadRenameTool = Tool.make("thread_rename", {
|
|
82282
|
+
description: "Rename this agent session's own thread. Use it when the seeded title no longer matches what the thread turned out to be about. Only the current thread can be renamed; no other thread is reachable.",
|
|
82283
|
+
parameters: ThreadRenameInput,
|
|
82284
|
+
success: ThreadRenameResult,
|
|
82285
|
+
failure: ThreadControlToolError,
|
|
82286
|
+
dependencies: [
|
|
82287
|
+
McpInvocationContext,
|
|
82288
|
+
OrchestrationEngineService,
|
|
82289
|
+
Crypto.Crypto
|
|
82290
|
+
]
|
|
82291
|
+
}).annotate(Tool.Title, "Rename this thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
82292
|
+
const MemoryAppendTool = Tool.make("memory_append", {
|
|
82293
|
+
description: "Append a block of markdown to the user-scope memory file (CLAUDE.md by default, AGENTS.md on request). Use it to record a durable preference or fact the user asked to remember. Appends only - it never rewrites or removes existing memory.",
|
|
82294
|
+
parameters: MemoryAppendInput,
|
|
82295
|
+
success: MemoryAppendResult,
|
|
82296
|
+
failure: ThreadControlToolError,
|
|
82297
|
+
dependencies: [
|
|
82298
|
+
McpInvocationContext,
|
|
82299
|
+
ServerSettingsService,
|
|
82300
|
+
FileSystem.FileSystem,
|
|
82301
|
+
Path.Path
|
|
82302
|
+
]
|
|
82303
|
+
}).annotate(Tool.Title, "Append to memory").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
82304
|
+
const ThreadToolkit = Toolkit.make(ThreadRenameTool, MemoryAppendTool);
|
|
82305
|
+
//#endregion
|
|
82306
|
+
//#region src/mcp/toolkits/threads/handlers.ts
|
|
82307
|
+
const DEFAULT_MEMORY_APPEND_TARGET = "CLAUDE.md";
|
|
82308
|
+
const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
82309
|
+
thread_rename: (input) => Effect.gen(function* () {
|
|
82310
|
+
const invocation = yield* requireThreadCapability();
|
|
82311
|
+
const engine = yield* OrchestrationEngineService;
|
|
82312
|
+
const crypto = yield* Crypto.Crypto;
|
|
82313
|
+
const commandId = CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
82314
|
+
yield* engine.dispatch({
|
|
82315
|
+
type: "thread.meta.update",
|
|
82316
|
+
commandId,
|
|
82317
|
+
threadId: invocation.threadId,
|
|
82318
|
+
title: input.title
|
|
82319
|
+
}).pipe(Effect.mapError((cause) => new ThreadRenameRejectedError({
|
|
82320
|
+
threadId: invocation.threadId,
|
|
82321
|
+
detail: cause.message
|
|
82322
|
+
})));
|
|
82323
|
+
return {
|
|
82324
|
+
threadId: invocation.threadId,
|
|
82325
|
+
title: input.title
|
|
82326
|
+
};
|
|
82327
|
+
}),
|
|
82328
|
+
memory_append: (input) => Effect.gen(function* () {
|
|
82329
|
+
yield* requireThreadCapability();
|
|
82330
|
+
const file = input.file ?? DEFAULT_MEMORY_APPEND_TARGET;
|
|
82331
|
+
if (!isMemoryFileName(file)) return yield* new MemoryAppendFailedError({
|
|
82332
|
+
file,
|
|
82333
|
+
detail: `${file} is not an allowlisted memory file.`
|
|
82334
|
+
});
|
|
82335
|
+
const memoryRoot = yield* resolveClaudeConfigDirPath({ homePath: (yield* (yield* ServerSettingsService).getSettings.pipe(Effect.orElseSucceed(() => void 0)))?.providers.claudeAgent.homePath ?? "" }, process.env);
|
|
82336
|
+
const path = yield* Path.Path;
|
|
82337
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
82338
|
+
const filePath = path.join(memoryRoot, file);
|
|
82339
|
+
const existing = yield* fileSystem.readFileString(filePath).pipe(Effect.orElseSucceed(() => ""));
|
|
82340
|
+
const block = input.text.trim();
|
|
82341
|
+
const contents = existing.trim().length === 0 ? `${block}\n` : `${existing.trimEnd()}\n\n${block}\n`;
|
|
82342
|
+
if (contents.length > 524288) return yield* new MemoryAppendFailedError({
|
|
82343
|
+
file,
|
|
82344
|
+
detail: `Appending would grow ${file} past ${AGENT_ASSET_FILE_MAX_CHARS} characters.`
|
|
82345
|
+
});
|
|
82346
|
+
yield* writeFileStringAtomically({
|
|
82347
|
+
filePath,
|
|
82348
|
+
contents
|
|
82349
|
+
}).pipe(Effect.mapError((cause) => new MemoryAppendFailedError({
|
|
82350
|
+
file,
|
|
82351
|
+
detail: cause.message
|
|
82352
|
+
})));
|
|
82353
|
+
return {
|
|
82354
|
+
file,
|
|
82355
|
+
path: filePath
|
|
82356
|
+
};
|
|
82357
|
+
})
|
|
82358
|
+
});
|
|
82359
|
+
//#endregion
|
|
81507
82360
|
//#region src/mcp/McpHttpServer.ts
|
|
81508
82361
|
const unauthorized = HttpServerResponse.jsonUnsafe({
|
|
81509
82362
|
error: "invalid_mcp_credential",
|
|
@@ -81607,12 +82460,13 @@ const PreviewStandardToolkitRegistrationLive = McpServer.toolkit(PreviewStandard
|
|
|
81607
82460
|
const PreviewSnapshotRegistrationLive = Layer.effectDiscard(registerPreviewSnapshot()).pipe(Layer.provide(PreviewSnapshotToolkitHandlersLive));
|
|
81608
82461
|
const PreviewToolkitRegistrationLive = Layer.mergeAll(PreviewStandardToolkitRegistrationLive, PreviewSnapshotRegistrationLive);
|
|
81609
82462
|
const TaskToolkitRegistrationLive = McpServer.toolkit(TaskToolkit).pipe(Layer.provide(TaskToolkitHandlersLive));
|
|
82463
|
+
const ThreadToolkitRegistrationLive = McpServer.toolkit(ThreadToolkit).pipe(Layer.provide(ThreadToolkitHandlersLive));
|
|
81610
82464
|
const McpTransportLive = McpServer.layerHttp({
|
|
81611
82465
|
name: "P4Code",
|
|
81612
82466
|
version,
|
|
81613
82467
|
path: "/mcp"
|
|
81614
82468
|
}).pipe(Layer.provide(McpAuthMiddlewareLive));
|
|
81615
|
-
const layer = Layer.mergeAll(PreviewToolkitRegistrationLive, TaskToolkitRegistrationLive).pipe(Layer.provideMerge(McpTransportLive));
|
|
82469
|
+
const layer = Layer.mergeAll(PreviewToolkitRegistrationLive, TaskToolkitRegistrationLive, ThreadToolkitRegistrationLive).pipe(Layer.provideMerge(McpTransportLive));
|
|
81616
82470
|
//#endregion
|
|
81617
82471
|
//#region src/orchestration/Services/CheckpointReactor.ts
|
|
81618
82472
|
/**
|
|
@@ -82946,992 +83800,460 @@ const make$2 = Effect.gen(function* () {
|
|
|
82946
83800
|
const requestedModelSelection = options?.modelSelection;
|
|
82947
83801
|
const resolveActiveSession = (threadId) => providerService.listSessions().pipe(Effect.map((sessions) => sessions.find((session) => session.threadId === threadId)));
|
|
82948
83802
|
const activeSession = yield* resolveActiveSession(threadId);
|
|
82949
|
-
const activeThreadSession = thread.session !== null && thread.session.status !== "stopped" && activeSession ? thread.session : null;
|
|
82950
|
-
if (activeThreadSession !== null && activeSession !== void 0 && (activeThreadSession.providerInstanceId === void 0 || activeSession.providerInstanceId === void 0)) return yield* new ProviderAdapterRequestError({
|
|
82951
|
-
provider: providerErrorLabel(activeThreadSession.providerName ?? void 0),
|
|
82952
|
-
method: "thread.turn.start",
|
|
82953
|
-
detail: `Thread '${threadId}' has an active provider session without a provider instance id.`
|
|
82954
|
-
});
|
|
82955
|
-
const currentInstanceId = activeThreadSession !== null && activeSession !== void 0 && activeSession.providerInstanceId !== void 0 ? activeSession.providerInstanceId : thread.modelSelection.instanceId;
|
|
82956
|
-
const desiredModelSelection = requestedModelSelection ?? thread.modelSelection;
|
|
82957
|
-
const desiredInstanceId = desiredModelSelection.instanceId;
|
|
82958
|
-
const currentInfo = yield* providerService.getInstanceInfo(currentInstanceId).pipe(Effect.mapError(() => new ProviderAdapterRequestError({
|
|
82959
|
-
provider: providerErrorLabelFromInstanceHint({
|
|
82960
|
-
instanceId: String(currentInstanceId),
|
|
82961
|
-
modelSelectionInstanceId: String(thread.modelSelection.instanceId),
|
|
82962
|
-
sessionProvider: thread.session?.providerName ?? void 0
|
|
82963
|
-
}),
|
|
82964
|
-
method: "thread.turn.start",
|
|
82965
|
-
detail: `Thread '${threadId}' references unknown provider instance '${currentInstanceId}'. The instance is not configured in this build.`
|
|
82966
|
-
})));
|
|
82967
|
-
const desiredInfo = yield* providerService.getInstanceInfo(desiredInstanceId).pipe(Effect.mapError(() => new ProviderAdapterRequestError({
|
|
82968
|
-
provider: providerErrorLabelFromInstanceHint({ instanceId: String(desiredModelSelection.instanceId) }),
|
|
82969
|
-
method: "thread.turn.start",
|
|
82970
|
-
detail: `Requested provider instance '${desiredInstanceId}' is not configured in this build.`
|
|
82971
|
-
})));
|
|
82972
|
-
const desiredDriverKind = desiredInfo.driverKind;
|
|
82973
|
-
if (!isProviderDriverKind(desiredDriverKind)) return yield* new ProviderAdapterRequestError({
|
|
82974
|
-
provider: providerErrorLabel(String(desiredDriverKind)),
|
|
82975
|
-
method: "thread.turn.start",
|
|
82976
|
-
detail: `Requested provider instance '${desiredInstanceId}' uses unknown provider driver '${desiredDriverKind}'. The driver is not installed in this build.`
|
|
82977
|
-
});
|
|
82978
|
-
const preferredProvider = desiredDriverKind;
|
|
82979
|
-
if (options?.pendingTurnStart === true && thread.session?.status !== "running") yield* setThreadSession({
|
|
82980
|
-
threadId,
|
|
82981
|
-
session: {
|
|
82982
|
-
threadId,
|
|
82983
|
-
status: "starting",
|
|
82984
|
-
providerName: activeSession?.provider ?? preferredProvider,
|
|
82985
|
-
providerInstanceId: activeSession?.providerInstanceId ?? desiredInstanceId,
|
|
82986
|
-
runtimeMode: desiredRuntimeMode,
|
|
82987
|
-
activeTurnId: null,
|
|
82988
|
-
lastError: null,
|
|
82989
|
-
updatedAt: createdAt
|
|
82990
|
-
},
|
|
82991
|
-
createdAt
|
|
82992
|
-
});
|
|
82993
|
-
if (thread.session !== null) yield* rejectStartedThreadModelChangeIfRequired({
|
|
82994
|
-
threadId,
|
|
82995
|
-
currentModelSelection: activeSession?.model !== void 0 ? {
|
|
82996
|
-
...thread.modelSelection,
|
|
82997
|
-
instanceId: currentInstanceId,
|
|
82998
|
-
model: activeSession.model
|
|
82999
|
-
} : thread.modelSelection,
|
|
83000
|
-
requestedModelSelection
|
|
83001
|
-
});
|
|
83002
|
-
if (thread.session !== null && requestedModelSelection !== void 0 && requestedModelSelection.instanceId !== currentInstanceId) {
|
|
83003
|
-
if (currentInfo.driverKind !== desiredInfo.driverKind) return yield* new ProviderAdapterRequestError({
|
|
83004
|
-
provider: preferredProvider,
|
|
83005
|
-
method: "thread.turn.start",
|
|
83006
|
-
detail: `Thread '${threadId}' is bound to driver '${currentInfo.driverKind}' and cannot switch to '${desiredInfo.driverKind}'.`
|
|
83007
|
-
});
|
|
83008
|
-
if (currentInfo.continuationIdentity.continuationKey !== desiredInfo.continuationIdentity.continuationKey) return yield* new ProviderAdapterRequestError({
|
|
83009
|
-
provider: preferredProvider,
|
|
83010
|
-
method: "thread.turn.start",
|
|
83011
|
-
detail: `Thread '${threadId}' cannot switch from instance '${currentInstanceId}' to '${desiredInstanceId}' because their provider resume state is incompatible.`
|
|
83012
|
-
});
|
|
83013
|
-
}
|
|
83014
|
-
const project = yield* resolveProject(thread.projectId);
|
|
83015
|
-
const effectiveCwd = resolveThreadWorkspaceCwd({
|
|
83016
|
-
thread,
|
|
83017
|
-
projects: project ? [project] : []
|
|
83018
|
-
});
|
|
83019
|
-
const startProviderSession = (input) => providerService.startSession(threadId, {
|
|
83020
|
-
threadId,
|
|
83021
|
-
...preferredProvider ? { provider: preferredProvider } : {},
|
|
83022
|
-
providerInstanceId: desiredInstanceId,
|
|
83023
|
-
...effectiveCwd ? { cwd: effectiveCwd } : {},
|
|
83024
|
-
modelSelection: desiredModelSelection,
|
|
83025
|
-
...input?.resumeCursor !== void 0 ? { resumeCursor: input.resumeCursor } : {},
|
|
83026
|
-
runtimeMode: desiredRuntimeMode
|
|
83027
|
-
});
|
|
83028
|
-
const bindSessionToThread = (session) => Effect.gen(function* () {
|
|
83029
|
-
if (session.providerInstanceId === void 0) return yield* new ProviderAdapterRequestError({
|
|
83030
|
-
provider: providerErrorLabel(session.provider),
|
|
83031
|
-
method: "thread.turn.start",
|
|
83032
|
-
detail: `Provider session '${session.threadId}' started without a provider instance id.`
|
|
83033
|
-
});
|
|
83034
|
-
yield* setThreadSession({
|
|
83035
|
-
threadId,
|
|
83036
|
-
session: {
|
|
83037
|
-
threadId,
|
|
83038
|
-
status: options?.pendingTurnStart === true && session.status === "ready" ? "starting" : mapProviderSessionStatusToOrchestrationStatus(session.status),
|
|
83039
|
-
providerName: session.provider,
|
|
83040
|
-
providerInstanceId: session.providerInstanceId,
|
|
83041
|
-
runtimeMode: desiredRuntimeMode,
|
|
83042
|
-
activeTurnId: null,
|
|
83043
|
-
lastError: session.lastError ?? null,
|
|
83044
|
-
updatedAt: session.updatedAt
|
|
83045
|
-
},
|
|
83046
|
-
createdAt
|
|
83047
|
-
});
|
|
83048
|
-
});
|
|
83049
|
-
const existingSessionThreadId = thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null;
|
|
83050
|
-
if (existingSessionThreadId) {
|
|
83051
|
-
const runtimeModeChanged = thread.runtimeMode !== thread.session?.runtimeMode;
|
|
83052
|
-
const cwdChanged = effectiveCwd !== activeSession?.cwd;
|
|
83053
|
-
const sessionModelSwitch = (yield* providerService.getCapabilities(desiredInstanceId)).sessionModelSwitch;
|
|
83054
|
-
const modelChanged = requestedModelSelection !== void 0 && requestedModelSelection.model !== activeSession?.model;
|
|
83055
|
-
const instanceChanged = requestedModelSelection !== void 0 && activeSession?.providerInstanceId !== requestedModelSelection.instanceId;
|
|
83056
|
-
const shouldRestartForModelChange = modelChanged && sessionModelSwitch === "unsupported";
|
|
83057
|
-
const previousModelSelection = threadModelSelections.get(threadId);
|
|
83058
|
-
const shouldRestartForModelSelectionChange = preferredProvider === "claudeAgent" && requestedModelSelection !== void 0 && !Equal.equals(previousModelSelection, requestedModelSelection);
|
|
83059
|
-
if (!runtimeModeChanged && !cwdChanged && !instanceChanged && !shouldRestartForModelChange && !shouldRestartForModelSelectionChange) return existingSessionThreadId;
|
|
83060
|
-
const resumeCursor = shouldRestartForModelChange ? void 0 : activeSession?.resumeCursor ?? void 0;
|
|
83061
|
-
yield* Effect.logInfo("provider command reactor restarting provider session", {
|
|
83062
|
-
threadId,
|
|
83063
|
-
existingSessionThreadId,
|
|
83064
|
-
currentProvider: activeSession?.provider,
|
|
83065
|
-
currentInstanceId,
|
|
83066
|
-
desiredInstanceId,
|
|
83067
|
-
desiredProvider: desiredModelSelection.instanceId,
|
|
83068
|
-
currentRuntimeMode: thread.session?.runtimeMode,
|
|
83069
|
-
desiredRuntimeMode: thread.runtimeMode,
|
|
83070
|
-
runtimeModeChanged,
|
|
83071
|
-
previousCwd: activeSession?.cwd,
|
|
83072
|
-
desiredCwd: effectiveCwd,
|
|
83073
|
-
cwdChanged,
|
|
83074
|
-
modelChanged,
|
|
83075
|
-
instanceChanged,
|
|
83076
|
-
shouldRestartForModelChange,
|
|
83077
|
-
shouldRestartForModelSelectionChange,
|
|
83078
|
-
hasResumeCursor: resumeCursor !== void 0
|
|
83079
|
-
});
|
|
83080
|
-
const restartedSession = yield* startProviderSession(resumeCursor !== void 0 ? { resumeCursor } : void 0);
|
|
83081
|
-
yield* Effect.logInfo("provider command reactor restarted provider session", {
|
|
83082
|
-
threadId,
|
|
83083
|
-
previousSessionId: existingSessionThreadId,
|
|
83084
|
-
restartedSessionThreadId: restartedSession.threadId,
|
|
83085
|
-
provider: restartedSession.provider,
|
|
83086
|
-
runtimeMode: restartedSession.runtimeMode,
|
|
83087
|
-
cwd: restartedSession.cwd
|
|
83088
|
-
});
|
|
83089
|
-
yield* bindSessionToThread(restartedSession);
|
|
83090
|
-
return restartedSession.threadId;
|
|
83091
|
-
}
|
|
83092
|
-
const startedSession = yield* startProviderSession(void 0);
|
|
83093
|
-
yield* bindSessionToThread(startedSession);
|
|
83094
|
-
return startedSession.threadId;
|
|
83095
|
-
});
|
|
83096
|
-
const buildSendTurnRequestForThread = Effect.fnUntraced(function* (input) {
|
|
83097
|
-
const thread = yield* resolveThread(input.threadId);
|
|
83098
|
-
if (!thread) return yield* Effect.die(/* @__PURE__ */ new Error(`Thread '${input.threadId}' was not found in read model.`));
|
|
83099
|
-
yield* ensureSessionForThread(input.threadId, input.createdAt, {
|
|
83100
|
-
...input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {},
|
|
83101
|
-
pendingTurnStart: true
|
|
83102
|
-
});
|
|
83103
|
-
if (input.modelSelection !== void 0) threadModelSelections.set(input.threadId, input.modelSelection);
|
|
83104
|
-
const normalizedInput = toNonEmptyProviderInput(input.messageText);
|
|
83105
|
-
const normalizedAttachments = input.attachments ?? [];
|
|
83106
|
-
const activeSession = yield* providerService.listSessions().pipe(Effect.map((sessions) => sessions.find((session) => session.threadId === input.threadId)));
|
|
83107
|
-
const sessionModelSwitch = activeSession === void 0 ? "in-session" : activeSession.providerInstanceId === void 0 ? yield* new ProviderAdapterRequestError({
|
|
83108
|
-
provider: providerErrorLabel(activeSession.provider),
|
|
83109
|
-
method: "thread.turn.start",
|
|
83110
|
-
detail: `Active provider session '${activeSession.threadId}' is missing a provider instance id.`
|
|
83111
|
-
}) : (yield* providerService.getCapabilities(activeSession.providerInstanceId)).sessionModelSwitch;
|
|
83112
|
-
const requestedModelSelection = input.modelSelection ?? threadModelSelections.get(input.threadId) ?? thread.modelSelection;
|
|
83113
|
-
const modelForTurn = sessionModelSwitch === "unsupported" && input.modelSelection === void 0 ? activeSession?.model !== void 0 ? {
|
|
83114
|
-
...requestedModelSelection,
|
|
83115
|
-
model: activeSession.model
|
|
83116
|
-
} : requestedModelSelection : input.modelSelection;
|
|
83117
|
-
return {
|
|
83118
|
-
threadId: input.threadId,
|
|
83119
|
-
...normalizedInput ? { input: normalizedInput } : {},
|
|
83120
|
-
...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
|
|
83121
|
-
...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
|
|
83122
|
-
...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {}
|
|
83123
|
-
};
|
|
83124
|
-
});
|
|
83125
|
-
const maybeGenerateAndRenameWorktreeBranchForFirstTurn = Effect.fn("maybeGenerateAndRenameWorktreeBranchForFirstTurn")(function* (input) {
|
|
83126
|
-
if (!input.branch || !input.worktreePath) return;
|
|
83127
|
-
if (!isTemporaryWorktreeBranch(input.branch)) return;
|
|
83128
|
-
const oldBranch = input.branch;
|
|
83129
|
-
const cwd = input.worktreePath;
|
|
83130
|
-
const attachments = input.attachments ?? [];
|
|
83131
|
-
yield* Effect.gen(function* () {
|
|
83132
|
-
const settings = yield* serverSettingsService.getSettings;
|
|
83133
|
-
const modelSelection = settings.sourceControlWriterModelSelection === null ? settings.textGenerationModelSelection : resolveSourceControlWriterModelSelection(settings, yield* providerRegistry.getProviders);
|
|
83134
|
-
const generated = yield* textGeneration.generateBranchName({
|
|
83135
|
-
cwd,
|
|
83136
|
-
message: input.messageText,
|
|
83137
|
-
...attachments.length > 0 ? { attachments } : {},
|
|
83138
|
-
modelSelection
|
|
83139
|
-
});
|
|
83140
|
-
if (!generated) return;
|
|
83141
|
-
const targetBranch = buildGeneratedWorktreeBranchName(generated.branch);
|
|
83142
|
-
if (targetBranch === oldBranch) return;
|
|
83143
|
-
const renamed = yield* gitWorkflow.renameBranch({
|
|
83144
|
-
cwd,
|
|
83145
|
-
oldBranch,
|
|
83146
|
-
newBranch: targetBranch
|
|
83147
|
-
});
|
|
83148
|
-
yield* orchestrationEngine.dispatch({
|
|
83149
|
-
type: "thread.meta.update",
|
|
83150
|
-
commandId: yield* serverCommandId("worktree-branch-rename"),
|
|
83151
|
-
threadId: input.threadId,
|
|
83152
|
-
branch: renamed.branch,
|
|
83153
|
-
worktreePath: cwd
|
|
83154
|
-
});
|
|
83155
|
-
yield* vcsStatusBroadcaster.refreshStatus(cwd).pipe(Effect.ignoreCause({ log: true }));
|
|
83156
|
-
}).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename worktree branch", {
|
|
83157
|
-
threadId: input.threadId,
|
|
83158
|
-
cwd,
|
|
83159
|
-
oldBranch,
|
|
83160
|
-
cause: Cause.pretty(cause)
|
|
83161
|
-
})));
|
|
83162
|
-
});
|
|
83163
|
-
const maybeGenerateThreadTitleForFirstTurn = Effect.fn("maybeGenerateThreadTitleForFirstTurn")(function* (input) {
|
|
83164
|
-
const attachments = input.attachments ?? [];
|
|
83165
|
-
yield* Effect.gen(function* () {
|
|
83166
|
-
const { textGenerationModelSelection: modelSelection } = yield* serverSettingsService.getSettings;
|
|
83167
|
-
const generated = yield* textGeneration.generateThreadTitle({
|
|
83168
|
-
cwd: input.cwd,
|
|
83169
|
-
message: input.messageText,
|
|
83170
|
-
...attachments.length > 0 ? { attachments } : {},
|
|
83171
|
-
modelSelection
|
|
83172
|
-
});
|
|
83173
|
-
if (!generated) return;
|
|
83174
|
-
const thread = yield* resolveThread(input.threadId);
|
|
83175
|
-
if (!thread) return;
|
|
83176
|
-
if (!canReplaceThreadTitle(thread.title, input.titleSeed)) return;
|
|
83177
|
-
yield* orchestrationEngine.dispatch({
|
|
83178
|
-
type: "thread.meta.update",
|
|
83179
|
-
commandId: yield* serverCommandId("thread-title-rename"),
|
|
83180
|
-
threadId: input.threadId,
|
|
83181
|
-
title: generated.title
|
|
83182
|
-
});
|
|
83183
|
-
}).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename thread title", {
|
|
83184
|
-
threadId: input.threadId,
|
|
83185
|
-
cwd: input.cwd,
|
|
83186
|
-
cause: Cause.pretty(cause)
|
|
83187
|
-
})));
|
|
83188
|
-
});
|
|
83189
|
-
const processTurnStartRequested = Effect.fn("processTurnStartRequested")(function* (event) {
|
|
83190
|
-
const key = turnStartKeyForEvent(event);
|
|
83191
|
-
if (yield* hasHandledTurnStartRecently(key)) return;
|
|
83192
|
-
const thread = yield* resolveThread(event.payload.threadId);
|
|
83193
|
-
if (!thread) return;
|
|
83194
|
-
const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
|
|
83195
|
-
if (!message || message.role !== "user") {
|
|
83196
|
-
yield* appendProviderFailureActivity({
|
|
83197
|
-
threadId: event.payload.threadId,
|
|
83198
|
-
kind: "provider.turn.start.failed",
|
|
83199
|
-
summary: "Provider turn start failed",
|
|
83200
|
-
detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
|
|
83201
|
-
turnId: null,
|
|
83202
|
-
createdAt: event.payload.createdAt
|
|
83203
|
-
});
|
|
83204
|
-
return;
|
|
83205
|
-
}
|
|
83206
|
-
if (thread.messages.filter((entry) => entry.role === "user").length === 1) {
|
|
83207
|
-
const project = yield* resolveProject(thread.projectId);
|
|
83208
|
-
const generationCwd = resolveThreadWorkspaceCwd({
|
|
83209
|
-
thread,
|
|
83210
|
-
projects: project ? [project] : []
|
|
83211
|
-
}) ?? process.cwd();
|
|
83212
|
-
const generationInput = {
|
|
83213
|
-
messageText: message.text,
|
|
83214
|
-
...message.attachments !== void 0 ? { attachments: message.attachments } : {},
|
|
83215
|
-
...event.payload.titleSeed !== void 0 ? { titleSeed: event.payload.titleSeed } : {}
|
|
83216
|
-
};
|
|
83217
|
-
yield* maybeGenerateAndRenameWorktreeBranchForFirstTurn({
|
|
83218
|
-
threadId: event.payload.threadId,
|
|
83219
|
-
branch: thread.branch,
|
|
83220
|
-
worktreePath: thread.worktreePath,
|
|
83221
|
-
...generationInput
|
|
83222
|
-
}).pipe(Effect.forkScoped);
|
|
83223
|
-
if (canReplaceThreadTitle(thread.title, event.payload.titleSeed)) yield* maybeGenerateThreadTitleForFirstTurn({
|
|
83224
|
-
threadId: event.payload.threadId,
|
|
83225
|
-
cwd: generationCwd,
|
|
83226
|
-
...generationInput
|
|
83227
|
-
}).pipe(Effect.forkScoped);
|
|
83228
|
-
}
|
|
83229
|
-
const handleTurnStartFailure = (cause) => {
|
|
83230
|
-
if (Cause.hasInterruptsOnly(cause)) return Effect.void;
|
|
83231
|
-
const detail = formatFailureDetail(cause);
|
|
83232
|
-
return setThreadSessionErrorOnTurnStartFailure({
|
|
83233
|
-
threadId: event.payload.threadId,
|
|
83234
|
-
detail,
|
|
83235
|
-
createdAt: event.payload.createdAt
|
|
83236
|
-
}).pipe(Effect.flatMap(() => appendProviderFailureActivity({
|
|
83237
|
-
threadId: event.payload.threadId,
|
|
83238
|
-
kind: "provider.turn.start.failed",
|
|
83239
|
-
summary: "Provider turn start failed",
|
|
83240
|
-
detail,
|
|
83241
|
-
turnId: null,
|
|
83242
|
-
createdAt: event.payload.createdAt
|
|
83243
|
-
})), Effect.asVoid);
|
|
83244
|
-
};
|
|
83245
|
-
const recoverTurnStartFailure = (cause) => handleTurnStartFailure(cause).pipe(Effect.catchCause((recoveryCause) => Effect.logWarning("provider command reactor failed to recover turn start failure", {
|
|
83246
|
-
eventType: event.type,
|
|
83247
|
-
threadId: event.payload.threadId,
|
|
83248
|
-
cause: Cause.pretty(recoveryCause),
|
|
83249
|
-
originalCause: Cause.pretty(cause)
|
|
83250
|
-
})));
|
|
83251
|
-
const sendTurnRequest = yield* buildSendTurnRequestForThread({
|
|
83252
|
-
threadId: event.payload.threadId,
|
|
83253
|
-
messageText: message.text,
|
|
83254
|
-
...message.attachments !== void 0 ? { attachments: message.attachments } : {},
|
|
83255
|
-
...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
|
|
83256
|
-
interactionMode: event.payload.interactionMode,
|
|
83257
|
-
createdAt: event.payload.createdAt
|
|
83258
|
-
}).pipe(Effect.map(Option.some), Effect.catchCause((cause) => handleTurnStartFailure(cause).pipe(Effect.as(Option.none()))));
|
|
83259
|
-
if (Option.isNone(sendTurnRequest)) return;
|
|
83260
|
-
yield* providerService.sendTurn(sendTurnRequest.value).pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
|
|
83261
|
-
});
|
|
83262
|
-
const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (event) {
|
|
83263
|
-
const thread = yield* resolveThread(event.payload.threadId);
|
|
83264
|
-
if (!thread) return;
|
|
83265
|
-
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
83266
|
-
threadId: event.payload.threadId,
|
|
83267
|
-
kind: "provider.turn.interrupt.failed",
|
|
83268
|
-
summary: "Provider turn interrupt failed",
|
|
83269
|
-
detail: "No active provider session is bound to this thread.",
|
|
83270
|
-
turnId: event.payload.turnId ?? null,
|
|
83271
|
-
createdAt: event.payload.createdAt
|
|
83272
|
-
});
|
|
83273
|
-
yield* providerService.interruptTurn({ threadId: event.payload.threadId });
|
|
83274
|
-
yield* appendTurnInterruptedActivity({
|
|
83275
|
-
threadId: event.payload.threadId,
|
|
83276
|
-
turnId: event.payload.turnId ?? null,
|
|
83277
|
-
createdAt: event.payload.createdAt
|
|
83278
|
-
});
|
|
83279
|
-
});
|
|
83280
|
-
const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (event) {
|
|
83281
|
-
const thread = yield* resolveThread(event.payload.threadId);
|
|
83282
|
-
if (!thread) return;
|
|
83283
|
-
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
83284
|
-
threadId: event.payload.threadId,
|
|
83285
|
-
kind: "provider.approval.respond.failed",
|
|
83286
|
-
summary: "Provider approval response failed",
|
|
83287
|
-
detail: "No active provider session is bound to this thread.",
|
|
83288
|
-
turnId: null,
|
|
83289
|
-
createdAt: event.payload.createdAt,
|
|
83290
|
-
requestId: event.payload.requestId
|
|
83291
|
-
});
|
|
83292
|
-
yield* providerService.respondToRequest({
|
|
83293
|
-
threadId: event.payload.threadId,
|
|
83294
|
-
requestId: event.payload.requestId,
|
|
83295
|
-
decision: event.payload.decision
|
|
83296
|
-
}).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
|
|
83297
|
-
threadId: event.payload.threadId,
|
|
83298
|
-
kind: "provider.approval.respond.failed",
|
|
83299
|
-
summary: "Provider approval response failed",
|
|
83300
|
-
detail: isUnknownPendingApprovalRequestError(cause) ? stalePendingRequestDetail("approval", event.payload.requestId) : Cause.pretty(cause),
|
|
83301
|
-
turnId: null,
|
|
83302
|
-
createdAt: event.payload.createdAt,
|
|
83303
|
-
requestId: event.payload.requestId
|
|
83304
|
-
})));
|
|
83305
|
-
});
|
|
83306
|
-
const processUserInputResponseRequested = Effect.fn("processUserInputResponseRequested")(function* (event) {
|
|
83307
|
-
const thread = yield* resolveThread(event.payload.threadId);
|
|
83308
|
-
if (!thread) return;
|
|
83309
|
-
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
83310
|
-
threadId: event.payload.threadId,
|
|
83311
|
-
kind: "provider.user-input.respond.failed",
|
|
83312
|
-
summary: "Provider user input response failed",
|
|
83313
|
-
detail: "No active provider session is bound to this thread.",
|
|
83314
|
-
turnId: null,
|
|
83315
|
-
createdAt: event.payload.createdAt,
|
|
83316
|
-
requestId: event.payload.requestId
|
|
83317
|
-
});
|
|
83318
|
-
yield* providerService.respondToUserInput({
|
|
83319
|
-
threadId: event.payload.threadId,
|
|
83320
|
-
requestId: event.payload.requestId,
|
|
83321
|
-
answers: event.payload.answers
|
|
83322
|
-
}).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
|
|
83323
|
-
threadId: event.payload.threadId,
|
|
83324
|
-
kind: "provider.user-input.respond.failed",
|
|
83325
|
-
summary: "Provider user input response failed",
|
|
83326
|
-
detail: isUnknownPendingUserInputRequestError(cause) ? stalePendingRequestDetail("user-input", event.payload.requestId) : Cause.pretty(cause),
|
|
83327
|
-
turnId: null,
|
|
83328
|
-
createdAt: event.payload.createdAt,
|
|
83329
|
-
requestId: event.payload.requestId
|
|
83803
|
+
const activeThreadSession = thread.session !== null && thread.session.status !== "stopped" && activeSession ? thread.session : null;
|
|
83804
|
+
if (activeThreadSession !== null && activeSession !== void 0 && (activeThreadSession.providerInstanceId === void 0 || activeSession.providerInstanceId === void 0)) return yield* new ProviderAdapterRequestError({
|
|
83805
|
+
provider: providerErrorLabel(activeThreadSession.providerName ?? void 0),
|
|
83806
|
+
method: "thread.turn.start",
|
|
83807
|
+
detail: `Thread '${threadId}' has an active provider session without a provider instance id.`
|
|
83808
|
+
});
|
|
83809
|
+
const currentInstanceId = activeThreadSession !== null && activeSession !== void 0 && activeSession.providerInstanceId !== void 0 ? activeSession.providerInstanceId : thread.modelSelection.instanceId;
|
|
83810
|
+
const desiredModelSelection = requestedModelSelection ?? thread.modelSelection;
|
|
83811
|
+
const desiredInstanceId = desiredModelSelection.instanceId;
|
|
83812
|
+
const currentInfo = yield* providerService.getInstanceInfo(currentInstanceId).pipe(Effect.mapError(() => new ProviderAdapterRequestError({
|
|
83813
|
+
provider: providerErrorLabelFromInstanceHint({
|
|
83814
|
+
instanceId: String(currentInstanceId),
|
|
83815
|
+
modelSelectionInstanceId: String(thread.modelSelection.instanceId),
|
|
83816
|
+
sessionProvider: thread.session?.providerName ?? void 0
|
|
83817
|
+
}),
|
|
83818
|
+
method: "thread.turn.start",
|
|
83819
|
+
detail: `Thread '${threadId}' references unknown provider instance '${currentInstanceId}'. The instance is not configured in this build.`
|
|
83330
83820
|
})));
|
|
83331
|
-
|
|
83332
|
-
|
|
83333
|
-
|
|
83334
|
-
|
|
83335
|
-
|
|
83336
|
-
|
|
83337
|
-
yield*
|
|
83338
|
-
|
|
83821
|
+
const desiredInfo = yield* providerService.getInstanceInfo(desiredInstanceId).pipe(Effect.mapError(() => new ProviderAdapterRequestError({
|
|
83822
|
+
provider: providerErrorLabelFromInstanceHint({ instanceId: String(desiredModelSelection.instanceId) }),
|
|
83823
|
+
method: "thread.turn.start",
|
|
83824
|
+
detail: `Requested provider instance '${desiredInstanceId}' is not configured in this build.`
|
|
83825
|
+
})));
|
|
83826
|
+
const desiredDriverKind = desiredInfo.driverKind;
|
|
83827
|
+
if (!isProviderDriverKind(desiredDriverKind)) return yield* new ProviderAdapterRequestError({
|
|
83828
|
+
provider: providerErrorLabel(String(desiredDriverKind)),
|
|
83829
|
+
method: "thread.turn.start",
|
|
83830
|
+
detail: `Requested provider instance '${desiredInstanceId}' uses unknown provider driver '${desiredDriverKind}'. The driver is not installed in this build.`
|
|
83831
|
+
});
|
|
83832
|
+
const preferredProvider = desiredDriverKind;
|
|
83833
|
+
if (options?.pendingTurnStart === true && thread.session?.status !== "running") yield* setThreadSession({
|
|
83834
|
+
threadId,
|
|
83339
83835
|
session: {
|
|
83340
|
-
threadId
|
|
83341
|
-
status: "
|
|
83342
|
-
providerName:
|
|
83343
|
-
|
|
83344
|
-
runtimeMode:
|
|
83836
|
+
threadId,
|
|
83837
|
+
status: "starting",
|
|
83838
|
+
providerName: activeSession?.provider ?? preferredProvider,
|
|
83839
|
+
providerInstanceId: activeSession?.providerInstanceId ?? desiredInstanceId,
|
|
83840
|
+
runtimeMode: desiredRuntimeMode,
|
|
83345
83841
|
activeTurnId: null,
|
|
83346
|
-
lastError:
|
|
83347
|
-
updatedAt:
|
|
83842
|
+
lastError: null,
|
|
83843
|
+
updatedAt: createdAt
|
|
83348
83844
|
},
|
|
83349
|
-
createdAt
|
|
83845
|
+
createdAt
|
|
83350
83846
|
});
|
|
83351
|
-
|
|
83352
|
-
|
|
83353
|
-
|
|
83354
|
-
|
|
83355
|
-
|
|
83356
|
-
|
|
83847
|
+
if (thread.session !== null) yield* rejectStartedThreadModelChangeIfRequired({
|
|
83848
|
+
threadId,
|
|
83849
|
+
currentModelSelection: activeSession?.model !== void 0 ? {
|
|
83850
|
+
...thread.modelSelection,
|
|
83851
|
+
instanceId: currentInstanceId,
|
|
83852
|
+
model: activeSession.model
|
|
83853
|
+
} : thread.modelSelection,
|
|
83854
|
+
requestedModelSelection
|
|
83357
83855
|
});
|
|
83358
|
-
|
|
83359
|
-
|
|
83360
|
-
|
|
83361
|
-
|
|
83362
|
-
|
|
83363
|
-
|
|
83364
|
-
|
|
83365
|
-
|
|
83366
|
-
|
|
83367
|
-
|
|
83368
|
-
|
|
83369
|
-
return;
|
|
83370
|
-
case "thread.turn-interrupt-requested":
|
|
83371
|
-
yield* processTurnInterruptRequested(event);
|
|
83372
|
-
return;
|
|
83373
|
-
case "thread.approval-response-requested":
|
|
83374
|
-
yield* processApprovalResponseRequested(event);
|
|
83375
|
-
return;
|
|
83376
|
-
case "thread.user-input-response-requested":
|
|
83377
|
-
yield* processUserInputResponseRequested(event);
|
|
83378
|
-
return;
|
|
83379
|
-
case "thread.session-stop-requested":
|
|
83380
|
-
yield* processSessionStopRequested(event);
|
|
83381
|
-
return;
|
|
83856
|
+
if (thread.session !== null && requestedModelSelection !== void 0 && requestedModelSelection.instanceId !== currentInstanceId) {
|
|
83857
|
+
if (currentInfo.driverKind !== desiredInfo.driverKind) return yield* new ProviderAdapterRequestError({
|
|
83858
|
+
provider: preferredProvider,
|
|
83859
|
+
method: "thread.turn.start",
|
|
83860
|
+
detail: `Thread '${threadId}' is bound to driver '${currentInfo.driverKind}' and cannot switch to '${desiredInfo.driverKind}'.`
|
|
83861
|
+
});
|
|
83862
|
+
if (currentInfo.continuationIdentity.continuationKey !== desiredInfo.continuationIdentity.continuationKey) return yield* new ProviderAdapterRequestError({
|
|
83863
|
+
provider: preferredProvider,
|
|
83864
|
+
method: "thread.turn.start",
|
|
83865
|
+
detail: `Thread '${threadId}' cannot switch from instance '${currentInstanceId}' to '${desiredInstanceId}' because their provider resume state is incompatible.`
|
|
83866
|
+
});
|
|
83382
83867
|
}
|
|
83383
|
-
|
|
83384
|
-
|
|
83385
|
-
|
|
83386
|
-
|
|
83387
|
-
eventType: event.type,
|
|
83388
|
-
cause: Cause.pretty(cause)
|
|
83868
|
+
const project = yield* resolveProject(thread.projectId);
|
|
83869
|
+
const effectiveCwd = resolveThreadWorkspaceCwd({
|
|
83870
|
+
thread,
|
|
83871
|
+
projects: project ? [project] : []
|
|
83389
83872
|
});
|
|
83390
|
-
|
|
83391
|
-
|
|
83392
|
-
|
|
83393
|
-
|
|
83394
|
-
|
|
83395
|
-
|
|
83873
|
+
const startProviderSession = (input) => providerService.startSession(threadId, {
|
|
83874
|
+
threadId,
|
|
83875
|
+
...preferredProvider ? { provider: preferredProvider } : {},
|
|
83876
|
+
providerInstanceId: desiredInstanceId,
|
|
83877
|
+
...effectiveCwd ? { cwd: effectiveCwd } : {},
|
|
83878
|
+
modelSelection: desiredModelSelection,
|
|
83879
|
+
...input?.resumeCursor !== void 0 ? { resumeCursor: input.resumeCursor } : {},
|
|
83880
|
+
runtimeMode: desiredRuntimeMode
|
|
83881
|
+
});
|
|
83882
|
+
const bindSessionToThread = (session) => Effect.gen(function* () {
|
|
83883
|
+
if (session.providerInstanceId === void 0) return yield* new ProviderAdapterRequestError({
|
|
83884
|
+
provider: providerErrorLabel(session.provider),
|
|
83885
|
+
method: "thread.turn.start",
|
|
83886
|
+
detail: `Provider session '${session.threadId}' started without a provider instance id.`
|
|
83887
|
+
});
|
|
83888
|
+
yield* setThreadSession({
|
|
83889
|
+
threadId,
|
|
83890
|
+
session: {
|
|
83891
|
+
threadId,
|
|
83892
|
+
status: options?.pendingTurnStart === true && session.status === "ready" ? "starting" : mapProviderSessionStatusToOrchestrationStatus(session.status),
|
|
83893
|
+
providerName: session.provider,
|
|
83894
|
+
providerInstanceId: session.providerInstanceId,
|
|
83895
|
+
runtimeMode: desiredRuntimeMode,
|
|
83896
|
+
activeTurnId: null,
|
|
83897
|
+
lastError: session.lastError ?? null,
|
|
83898
|
+
updatedAt: session.updatedAt
|
|
83899
|
+
},
|
|
83900
|
+
createdAt
|
|
83396
83901
|
});
|
|
83397
|
-
yield* Effect.forkScoped(Stream.runForEach(orchestrationEngine.streamDomainEvents, processEvent));
|
|
83398
|
-
}),
|
|
83399
|
-
drain: worker.drain
|
|
83400
|
-
};
|
|
83401
|
-
});
|
|
83402
|
-
const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$2);
|
|
83403
|
-
(() => {
|
|
83404
|
-
try {
|
|
83405
|
-
return process.env.NODE_ENV === "development";
|
|
83406
|
-
} catch {
|
|
83407
|
-
return false;
|
|
83408
|
-
}
|
|
83409
|
-
})();
|
|
83410
|
-
const COMMIT_METADATA_SPLIT = /(?=^From [a-f0-9]+ .+$)/m;
|
|
83411
|
-
const GIT_DIFF_FILE_BREAK_REGEX = /(?=^diff --git)/gm;
|
|
83412
|
-
const FILENAME_HEADER_REGEX = /^(---|\+\+\+)\s+([^\t\r\n]+)/;
|
|
83413
|
-
const FILENAME_HEADER_REGEX_GIT = /^(---|\+\+\+)\s+[ab]\/([^\t\r\n]+)/;
|
|
83414
|
-
const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
|
|
83415
|
-
const INDEX_LINE_METADATA = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: (\d+))?$/i;
|
|
83416
|
-
Object.freeze({
|
|
83417
|
-
fromStart: 0,
|
|
83418
|
-
fromEnd: 0
|
|
83419
|
-
});
|
|
83420
|
-
//#endregion
|
|
83421
|
-
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/cleanLastNewline.js
|
|
83422
|
-
function cleanLastNewline(contents) {
|
|
83423
|
-
let end = contents.length;
|
|
83424
|
-
if (contents.charCodeAt(end - 1) === 10) {
|
|
83425
|
-
end--;
|
|
83426
|
-
if (contents.charCodeAt(end - 1) === 13) end--;
|
|
83427
|
-
}
|
|
83428
|
-
return contents.slice(0, end);
|
|
83429
|
-
}
|
|
83430
|
-
//#endregion
|
|
83431
|
-
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/detachString.js
|
|
83432
|
-
const stringDetachEncoder = new TextEncoder();
|
|
83433
|
-
const stringDetachDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
83434
|
-
const SURROGATE_CODE_UNIT_PATTERN = /[\uD800-\uDFFF]/;
|
|
83435
|
-
const STRING_DETACH_INITIAL_BUFFER_SIZE = 1024;
|
|
83436
|
-
let stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
|
|
83437
|
-
function releaseStringDetachBuffer() {
|
|
83438
|
-
if (stringDetachBuffer.length !== STRING_DETACH_INITIAL_BUFFER_SIZE) stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
|
|
83439
|
-
}
|
|
83440
|
-
function detachString(value) {
|
|
83441
|
-
if (value.length === 0) return value;
|
|
83442
|
-
if (SURROGATE_CODE_UNIT_PATTERN.test(value)) return JSON.parse(JSON.stringify(value));
|
|
83443
|
-
const requiredByteLength = value.length * 3;
|
|
83444
|
-
if (stringDetachBuffer.length < requiredByteLength) stringDetachBuffer = new Uint8Array(requiredByteLength);
|
|
83445
|
-
const { written } = stringDetachEncoder.encodeInto(value, stringDetachBuffer);
|
|
83446
|
-
return stringDetachDecoder.decode(stringDetachBuffer.subarray(0, written));
|
|
83447
|
-
}
|
|
83448
|
-
//#endregion
|
|
83449
|
-
//#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js
|
|
83450
|
-
function processPatch(data, cacheKeyPrefix, throwOnError) {
|
|
83451
|
-
try {
|
|
83452
|
-
return _processPatch(data, cacheKeyPrefix, throwOnError);
|
|
83453
|
-
} finally {
|
|
83454
|
-
releaseStringDetachBuffer();
|
|
83455
|
-
}
|
|
83456
|
-
}
|
|
83457
|
-
function _processPatch(data, cacheKeyPrefix, throwOnError = false) {
|
|
83458
|
-
const isGitDiff = isGitDiffPatch(data);
|
|
83459
|
-
const rawFiles = isGitDiff ? splitGitDiffFiles(data) : splitUnifiedDiffFiles(data);
|
|
83460
|
-
let patchMetadata;
|
|
83461
|
-
const files = [];
|
|
83462
|
-
for (const fileOrPatchMetadata of rawFiles) {
|
|
83463
|
-
if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
|
|
83464
|
-
if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
|
|
83465
|
-
else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
|
|
83466
|
-
else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
|
|
83467
|
-
continue;
|
|
83468
|
-
} else if (!isGitDiff && !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)) {
|
|
83469
|
-
if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
|
|
83470
|
-
else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
|
|
83471
|
-
else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
|
|
83472
|
-
continue;
|
|
83473
|
-
}
|
|
83474
|
-
const currentFile = _processFile(fileOrPatchMetadata, {
|
|
83475
|
-
cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : void 0,
|
|
83476
|
-
isGitDiff,
|
|
83477
|
-
throwOnError
|
|
83478
83902
|
});
|
|
83479
|
-
|
|
83480
|
-
|
|
83481
|
-
|
|
83482
|
-
|
|
83483
|
-
|
|
83484
|
-
|
|
83485
|
-
|
|
83486
|
-
|
|
83487
|
-
|
|
83488
|
-
|
|
83489
|
-
|
|
83490
|
-
|
|
83491
|
-
|
|
83492
|
-
|
|
83493
|
-
|
|
83494
|
-
|
|
83495
|
-
|
|
83496
|
-
|
|
83497
|
-
|
|
83498
|
-
|
|
83499
|
-
|
|
83500
|
-
|
|
83501
|
-
|
|
83502
|
-
|
|
83503
|
-
|
|
83504
|
-
|
|
83505
|
-
|
|
83506
|
-
|
|
83507
|
-
|
|
83508
|
-
|
|
83509
|
-
}
|
|
83510
|
-
|
|
83511
|
-
|
|
83512
|
-
|
|
83513
|
-
|
|
83514
|
-
|
|
83515
|
-
|
|
83516
|
-
|
|
83517
|
-
|
|
83518
|
-
|
|
83519
|
-
|
|
83520
|
-
|
|
83521
|
-
if (currentFile.additionLines.length === 1 && newFile?.contents === "") currentFile.additionLines.length = 0;
|
|
83522
|
-
if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
|
|
83523
|
-
for (const line of lines) {
|
|
83524
|
-
if (line.startsWith("diff --git")) {
|
|
83525
|
-
const filenameMatch$1 = line.trim().match(ALTERNATE_FILE_NAMES_GIT);
|
|
83526
|
-
const prevName = filenameMatch$1?.[1] ?? filenameMatch$1?.[2];
|
|
83527
|
-
const name = filenameMatch$1?.[3] ?? filenameMatch$1?.[4];
|
|
83528
|
-
if (prevName == null || name == null) {
|
|
83529
|
-
if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
|
|
83530
|
-
else console.error("parsePatchContent: invalid git diff header", line);
|
|
83531
|
-
continue;
|
|
83532
|
-
}
|
|
83533
|
-
currentFile.name = detachString(name.trim());
|
|
83534
|
-
if (prevName !== name) currentFile.prevName = detachString(prevName.trim());
|
|
83535
|
-
continue;
|
|
83536
|
-
}
|
|
83537
|
-
const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX) : null;
|
|
83538
|
-
if (filenameMatch != null) {
|
|
83539
|
-
const [, type, fileName] = filenameMatch;
|
|
83540
|
-
if (type === "---" && fileName !== "/dev/null") {
|
|
83541
|
-
const detachedFileName = detachString(fileName.trim());
|
|
83542
|
-
currentFile.prevName = detachedFileName;
|
|
83543
|
-
currentFile.name = detachedFileName;
|
|
83544
|
-
} else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName.trim());
|
|
83545
|
-
} else if (isGitDiff) {
|
|
83546
|
-
if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
|
|
83547
|
-
if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
|
|
83548
|
-
if (line.startsWith("new file mode")) {
|
|
83549
|
-
currentFile.type = "new";
|
|
83550
|
-
currentFile.mode = detachString(line.slice(13).trim());
|
|
83551
|
-
}
|
|
83552
|
-
if (line.startsWith("deleted file mode")) {
|
|
83553
|
-
currentFile.type = "deleted";
|
|
83554
|
-
currentFile.mode = detachString(line.slice(17).trim());
|
|
83555
|
-
}
|
|
83556
|
-
if (line.startsWith("similarity index")) if (line.startsWith("similarity index 100%")) currentFile.type = "rename-pure";
|
|
83557
|
-
else currentFile.type = "rename-changed";
|
|
83558
|
-
if (line.startsWith("index ")) {
|
|
83559
|
-
const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? [];
|
|
83560
|
-
if (prevObjectId != null) currentFile.prevObjectId = detachString(prevObjectId);
|
|
83561
|
-
if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
|
|
83562
|
-
if (mode != null) currentFile.mode = detachString(mode);
|
|
83563
|
-
}
|
|
83564
|
-
if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(12).trim());
|
|
83565
|
-
if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(10).trim());
|
|
83566
|
-
}
|
|
83567
|
-
}
|
|
83568
|
-
continue;
|
|
83903
|
+
const existingSessionThreadId = thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null;
|
|
83904
|
+
if (existingSessionThreadId) {
|
|
83905
|
+
const runtimeModeChanged = thread.runtimeMode !== thread.session?.runtimeMode;
|
|
83906
|
+
const cwdChanged = effectiveCwd !== activeSession?.cwd;
|
|
83907
|
+
const sessionModelSwitch = (yield* providerService.getCapabilities(desiredInstanceId)).sessionModelSwitch;
|
|
83908
|
+
const modelChanged = requestedModelSelection !== void 0 && requestedModelSelection.model !== activeSession?.model;
|
|
83909
|
+
const instanceChanged = requestedModelSelection !== void 0 && activeSession?.providerInstanceId !== requestedModelSelection.instanceId;
|
|
83910
|
+
const shouldRestartForModelChange = modelChanged && sessionModelSwitch === "unsupported";
|
|
83911
|
+
const previousModelSelection = threadModelSelections.get(threadId);
|
|
83912
|
+
const shouldRestartForModelSelectionChange = preferredProvider === "claudeAgent" && requestedModelSelection !== void 0 && !Equal.equals(previousModelSelection, requestedModelSelection);
|
|
83913
|
+
if (!runtimeModeChanged && !cwdChanged && !instanceChanged && !shouldRestartForModelChange && !shouldRestartForModelSelectionChange) return existingSessionThreadId;
|
|
83914
|
+
const resumeCursor = shouldRestartForModelChange ? void 0 : activeSession?.resumeCursor ?? void 0;
|
|
83915
|
+
yield* Effect.logInfo("provider command reactor restarting provider session", {
|
|
83916
|
+
threadId,
|
|
83917
|
+
existingSessionThreadId,
|
|
83918
|
+
currentProvider: activeSession?.provider,
|
|
83919
|
+
currentInstanceId,
|
|
83920
|
+
desiredInstanceId,
|
|
83921
|
+
desiredProvider: desiredModelSelection.instanceId,
|
|
83922
|
+
currentRuntimeMode: thread.session?.runtimeMode,
|
|
83923
|
+
desiredRuntimeMode: thread.runtimeMode,
|
|
83924
|
+
runtimeModeChanged,
|
|
83925
|
+
previousCwd: activeSession?.cwd,
|
|
83926
|
+
desiredCwd: effectiveCwd,
|
|
83927
|
+
cwdChanged,
|
|
83928
|
+
modelChanged,
|
|
83929
|
+
instanceChanged,
|
|
83930
|
+
shouldRestartForModelChange,
|
|
83931
|
+
shouldRestartForModelSelectionChange,
|
|
83932
|
+
hasResumeCursor: resumeCursor !== void 0
|
|
83933
|
+
});
|
|
83934
|
+
const restartedSession = yield* startProviderSession(resumeCursor !== void 0 ? { resumeCursor } : void 0);
|
|
83935
|
+
yield* Effect.logInfo("provider command reactor restarted provider session", {
|
|
83936
|
+
threadId,
|
|
83937
|
+
previousSessionId: existingSessionThreadId,
|
|
83938
|
+
restartedSessionThreadId: restartedSession.threadId,
|
|
83939
|
+
provider: restartedSession.provider,
|
|
83940
|
+
runtimeMode: restartedSession.runtimeMode,
|
|
83941
|
+
cwd: restartedSession.cwd
|
|
83942
|
+
});
|
|
83943
|
+
yield* bindSessionToThread(restartedSession);
|
|
83944
|
+
return restartedSession.threadId;
|
|
83569
83945
|
}
|
|
83570
|
-
|
|
83571
|
-
|
|
83572
|
-
|
|
83573
|
-
|
|
83574
|
-
|
|
83575
|
-
|
|
83576
|
-
|
|
83577
|
-
|
|
83578
|
-
|
|
83579
|
-
|
|
83580
|
-
|
|
83581
|
-
|
|
83582
|
-
|
|
83583
|
-
|
|
83584
|
-
|
|
83585
|
-
|
|
83586
|
-
|
|
83587
|
-
|
|
83588
|
-
|
|
83589
|
-
|
|
83590
|
-
|
|
83591
|
-
|
|
83592
|
-
|
|
83593
|
-
|
|
83594
|
-
|
|
83946
|
+
const startedSession = yield* startProviderSession(void 0);
|
|
83947
|
+
yield* bindSessionToThread(startedSession);
|
|
83948
|
+
return startedSession.threadId;
|
|
83949
|
+
});
|
|
83950
|
+
const buildSendTurnRequestForThread = Effect.fnUntraced(function* (input) {
|
|
83951
|
+
const thread = yield* resolveThread(input.threadId);
|
|
83952
|
+
if (!thread) return yield* Effect.die(/* @__PURE__ */ new Error(`Thread '${input.threadId}' was not found in read model.`));
|
|
83953
|
+
yield* ensureSessionForThread(input.threadId, input.createdAt, {
|
|
83954
|
+
...input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {},
|
|
83955
|
+
pendingTurnStart: true
|
|
83956
|
+
});
|
|
83957
|
+
if (input.modelSelection !== void 0) threadModelSelections.set(input.threadId, input.modelSelection);
|
|
83958
|
+
const normalizedInput = toNonEmptyProviderInput(input.messageText);
|
|
83959
|
+
const normalizedAttachments = input.attachments ?? [];
|
|
83960
|
+
const activeSession = yield* providerService.listSessions().pipe(Effect.map((sessions) => sessions.find((session) => session.threadId === input.threadId)));
|
|
83961
|
+
const sessionModelSwitch = activeSession === void 0 ? "in-session" : activeSession.providerInstanceId === void 0 ? yield* new ProviderAdapterRequestError({
|
|
83962
|
+
provider: providerErrorLabel(activeSession.provider),
|
|
83963
|
+
method: "thread.turn.start",
|
|
83964
|
+
detail: `Active provider session '${activeSession.threadId}' is missing a provider instance id.`
|
|
83965
|
+
}) : (yield* providerService.getCapabilities(activeSession.providerInstanceId)).sessionModelSwitch;
|
|
83966
|
+
const requestedModelSelection = input.modelSelection ?? threadModelSelections.get(input.threadId) ?? thread.modelSelection;
|
|
83967
|
+
const modelForTurn = sessionModelSwitch === "unsupported" && input.modelSelection === void 0 ? activeSession?.model !== void 0 ? {
|
|
83968
|
+
...requestedModelSelection,
|
|
83969
|
+
model: activeSession.model
|
|
83970
|
+
} : requestedModelSelection : input.modelSelection;
|
|
83971
|
+
return {
|
|
83972
|
+
threadId: input.threadId,
|
|
83973
|
+
...normalizedInput ? { input: normalizedInput } : {},
|
|
83974
|
+
...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
|
|
83975
|
+
...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
|
|
83976
|
+
...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {}
|
|
83595
83977
|
};
|
|
83596
|
-
|
|
83597
|
-
|
|
83598
|
-
|
|
83599
|
-
|
|
83600
|
-
|
|
83601
|
-
|
|
83602
|
-
|
|
83603
|
-
|
|
83604
|
-
const
|
|
83605
|
-
|
|
83606
|
-
|
|
83607
|
-
|
|
83608
|
-
|
|
83609
|
-
|
|
83610
|
-
|
|
83611
|
-
|
|
83612
|
-
if (
|
|
83613
|
-
|
|
83614
|
-
|
|
83615
|
-
|
|
83616
|
-
|
|
83617
|
-
|
|
83618
|
-
|
|
83619
|
-
|
|
83620
|
-
|
|
83621
|
-
|
|
83622
|
-
|
|
83623
|
-
|
|
83624
|
-
|
|
83625
|
-
|
|
83626
|
-
|
|
83627
|
-
|
|
83628
|
-
|
|
83629
|
-
|
|
83630
|
-
|
|
83631
|
-
|
|
83632
|
-
|
|
83633
|
-
|
|
83634
|
-
|
|
83635
|
-
|
|
83636
|
-
|
|
83637
|
-
|
|
83638
|
-
|
|
83639
|
-
|
|
83640
|
-
|
|
83641
|
-
|
|
83642
|
-
|
|
83643
|
-
|
|
83644
|
-
|
|
83645
|
-
|
|
83646
|
-
|
|
83647
|
-
|
|
83648
|
-
|
|
83649
|
-
|
|
83650
|
-
|
|
83651
|
-
|
|
83652
|
-
|
|
83653
|
-
|
|
83654
|
-
|
|
83655
|
-
|
|
83656
|
-
|
|
83657
|
-
|
|
83658
|
-
|
|
83659
|
-
|
|
83660
|
-
|
|
83661
|
-
|
|
83662
|
-
|
|
83663
|
-
|
|
83664
|
-
|
|
83665
|
-
|
|
83666
|
-
|
|
83667
|
-
|
|
83668
|
-
|
|
83669
|
-
|
|
83978
|
+
});
|
|
83979
|
+
const maybeGenerateAndRenameWorktreeBranchForFirstTurn = Effect.fn("maybeGenerateAndRenameWorktreeBranchForFirstTurn")(function* (input) {
|
|
83980
|
+
if (!input.branch || !input.worktreePath) return;
|
|
83981
|
+
if (!isTemporaryWorktreeBranch(input.branch)) return;
|
|
83982
|
+
const oldBranch = input.branch;
|
|
83983
|
+
const cwd = input.worktreePath;
|
|
83984
|
+
const attachments = input.attachments ?? [];
|
|
83985
|
+
yield* Effect.gen(function* () {
|
|
83986
|
+
const settings = yield* serverSettingsService.getSettings;
|
|
83987
|
+
const modelSelection = settings.sourceControlWriterModelSelection === null ? settings.textGenerationModelSelection : resolveSourceControlWriterModelSelection(settings, yield* providerRegistry.getProviders);
|
|
83988
|
+
const generated = yield* textGeneration.generateBranchName({
|
|
83989
|
+
cwd,
|
|
83990
|
+
message: input.messageText,
|
|
83991
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
83992
|
+
modelSelection
|
|
83993
|
+
});
|
|
83994
|
+
if (!generated) return;
|
|
83995
|
+
const targetBranch = buildGeneratedWorktreeBranchName(generated.branch);
|
|
83996
|
+
if (targetBranch === oldBranch) return;
|
|
83997
|
+
const renamed = yield* gitWorkflow.renameBranch({
|
|
83998
|
+
cwd,
|
|
83999
|
+
oldBranch,
|
|
84000
|
+
newBranch: targetBranch
|
|
84001
|
+
});
|
|
84002
|
+
yield* orchestrationEngine.dispatch({
|
|
84003
|
+
type: "thread.meta.update",
|
|
84004
|
+
commandId: yield* serverCommandId("worktree-branch-rename"),
|
|
84005
|
+
threadId: input.threadId,
|
|
84006
|
+
branch: renamed.branch,
|
|
84007
|
+
worktreePath: cwd
|
|
84008
|
+
});
|
|
84009
|
+
yield* vcsStatusBroadcaster.refreshStatus(cwd).pipe(Effect.ignoreCause({ log: true }));
|
|
84010
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename worktree branch", {
|
|
84011
|
+
threadId: input.threadId,
|
|
84012
|
+
cwd,
|
|
84013
|
+
oldBranch,
|
|
84014
|
+
cause: Cause.pretty(cause)
|
|
84015
|
+
})));
|
|
84016
|
+
});
|
|
84017
|
+
const maybeGenerateThreadTitleForFirstTurn = Effect.fn("maybeGenerateThreadTitleForFirstTurn")(function* (input) {
|
|
84018
|
+
const attachments = input.attachments ?? [];
|
|
84019
|
+
yield* Effect.gen(function* () {
|
|
84020
|
+
const { textGenerationModelSelection: modelSelection } = yield* serverSettingsService.getSettings;
|
|
84021
|
+
const generated = yield* textGeneration.generateThreadTitle({
|
|
84022
|
+
cwd: input.cwd,
|
|
84023
|
+
message: input.messageText,
|
|
84024
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
84025
|
+
modelSelection
|
|
84026
|
+
});
|
|
84027
|
+
if (!generated) return;
|
|
84028
|
+
const thread = yield* resolveThread(input.threadId);
|
|
84029
|
+
if (!thread) return;
|
|
84030
|
+
if (!canReplaceThreadTitle(thread.title, input.titleSeed)) return;
|
|
84031
|
+
yield* orchestrationEngine.dispatch({
|
|
84032
|
+
type: "thread.meta.update",
|
|
84033
|
+
commandId: yield* serverCommandId("thread-title-rename"),
|
|
84034
|
+
threadId: input.threadId,
|
|
84035
|
+
title: generated.title
|
|
84036
|
+
});
|
|
84037
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename thread title", {
|
|
84038
|
+
threadId: input.threadId,
|
|
84039
|
+
cwd: input.cwd,
|
|
84040
|
+
cause: Cause.pretty(cause)
|
|
84041
|
+
})));
|
|
84042
|
+
});
|
|
84043
|
+
const processTurnStartRequested = Effect.fn("processTurnStartRequested")(function* (event) {
|
|
84044
|
+
const key = turnStartKeyForEvent(event);
|
|
84045
|
+
if (yield* hasHandledTurnStartRecently(key)) return;
|
|
84046
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84047
|
+
if (!thread) return;
|
|
84048
|
+
const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
|
|
84049
|
+
if (!message || message.role !== "user") {
|
|
84050
|
+
yield* appendProviderFailureActivity({
|
|
84051
|
+
threadId: event.payload.threadId,
|
|
84052
|
+
kind: "provider.turn.start.failed",
|
|
84053
|
+
summary: "Provider turn start failed",
|
|
84054
|
+
detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
|
|
84055
|
+
turnId: null,
|
|
84056
|
+
createdAt: event.payload.createdAt
|
|
84057
|
+
});
|
|
84058
|
+
return;
|
|
83670
84059
|
}
|
|
83671
|
-
if (
|
|
83672
|
-
|
|
83673
|
-
|
|
83674
|
-
|
|
83675
|
-
|
|
83676
|
-
|
|
83677
|
-
|
|
83678
|
-
|
|
83679
|
-
|
|
83680
|
-
|
|
83681
|
-
|
|
83682
|
-
|
|
84060
|
+
if (thread.messages.filter((entry) => entry.role === "user").length === 1) {
|
|
84061
|
+
const project = yield* resolveProject(thread.projectId);
|
|
84062
|
+
const generationCwd = resolveThreadWorkspaceCwd({
|
|
84063
|
+
thread,
|
|
84064
|
+
projects: project ? [project] : []
|
|
84065
|
+
}) ?? process.cwd();
|
|
84066
|
+
const generationInput = {
|
|
84067
|
+
messageText: message.text,
|
|
84068
|
+
...message.attachments !== void 0 ? { attachments: message.attachments } : {},
|
|
84069
|
+
...event.payload.titleSeed !== void 0 ? { titleSeed: event.payload.titleSeed } : {}
|
|
84070
|
+
};
|
|
84071
|
+
yield* maybeGenerateAndRenameWorktreeBranchForFirstTurn({
|
|
84072
|
+
threadId: event.payload.threadId,
|
|
84073
|
+
branch: thread.branch,
|
|
84074
|
+
worktreePath: thread.worktreePath,
|
|
84075
|
+
...generationInput
|
|
84076
|
+
}).pipe(Effect.forkScoped);
|
|
84077
|
+
if (canReplaceThreadTitle(thread.title, event.payload.titleSeed)) yield* maybeGenerateThreadTitleForFirstTurn({
|
|
84078
|
+
threadId: event.payload.threadId,
|
|
84079
|
+
cwd: generationCwd,
|
|
84080
|
+
...generationInput
|
|
84081
|
+
}).pipe(Effect.forkScoped);
|
|
83683
84082
|
}
|
|
83684
|
-
|
|
83685
|
-
|
|
83686
|
-
|
|
83687
|
-
|
|
83688
|
-
|
|
83689
|
-
|
|
83690
|
-
|
|
83691
|
-
|
|
83692
|
-
|
|
83693
|
-
|
|
83694
|
-
|
|
83695
|
-
|
|
83696
|
-
|
|
83697
|
-
|
|
83698
|
-
|
|
83699
|
-
|
|
83700
|
-
|
|
83701
|
-
|
|
83702
|
-
|
|
83703
|
-
|
|
83704
|
-
|
|
83705
|
-
|
|
83706
|
-
|
|
83707
|
-
|
|
83708
|
-
|
|
83709
|
-
|
|
83710
|
-
|
|
83711
|
-
|
|
83712
|
-
|
|
83713
|
-
|
|
83714
|
-
|
|
83715
|
-
|
|
83716
|
-
|
|
83717
|
-
const
|
|
83718
|
-
|
|
83719
|
-
|
|
83720
|
-
|
|
83721
|
-
|
|
83722
|
-
|
|
83723
|
-
|
|
83724
|
-
|
|
83725
|
-
|
|
83726
|
-
|
|
83727
|
-
|
|
83728
|
-
|
|
83729
|
-
|
|
83730
|
-
|
|
83731
|
-
|
|
83732
|
-
|
|
83733
|
-
|
|
83734
|
-
}
|
|
83735
|
-
function
|
|
83736
|
-
|
|
83737
|
-
|
|
83738
|
-
|
|
83739
|
-
|
|
83740
|
-
|
|
83741
|
-
|
|
83742
|
-
|
|
83743
|
-
|
|
83744
|
-
|
|
83745
|
-
|
|
83746
|
-
|
|
83747
|
-
|
|
83748
|
-
|
|
83749
|
-
|
|
83750
|
-
|
|
83751
|
-
|
|
83752
|
-
|
|
83753
|
-
|
|
83754
|
-
|
|
83755
|
-
|
|
83756
|
-
|
|
83757
|
-
|
|
83758
|
-
|
|
83759
|
-
|
|
83760
|
-
|
|
83761
|
-
|
|
83762
|
-
|
|
83763
|
-
|
|
83764
|
-
|
|
83765
|
-
|
|
83766
|
-
|
|
83767
|
-
|
|
83768
|
-
|
|
83769
|
-
|
|
83770
|
-
|
|
83771
|
-
|
|
83772
|
-
|
|
83773
|
-
|
|
83774
|
-
|
|
84083
|
+
const handleTurnStartFailure = (cause) => {
|
|
84084
|
+
if (Cause.hasInterruptsOnly(cause)) return Effect.void;
|
|
84085
|
+
const detail = formatFailureDetail(cause);
|
|
84086
|
+
return setThreadSessionErrorOnTurnStartFailure({
|
|
84087
|
+
threadId: event.payload.threadId,
|
|
84088
|
+
detail,
|
|
84089
|
+
createdAt: event.payload.createdAt
|
|
84090
|
+
}).pipe(Effect.flatMap(() => appendProviderFailureActivity({
|
|
84091
|
+
threadId: event.payload.threadId,
|
|
84092
|
+
kind: "provider.turn.start.failed",
|
|
84093
|
+
summary: "Provider turn start failed",
|
|
84094
|
+
detail,
|
|
84095
|
+
turnId: null,
|
|
84096
|
+
createdAt: event.payload.createdAt
|
|
84097
|
+
})), Effect.asVoid);
|
|
84098
|
+
};
|
|
84099
|
+
const recoverTurnStartFailure = (cause) => handleTurnStartFailure(cause).pipe(Effect.catchCause((recoveryCause) => Effect.logWarning("provider command reactor failed to recover turn start failure", {
|
|
84100
|
+
eventType: event.type,
|
|
84101
|
+
threadId: event.payload.threadId,
|
|
84102
|
+
cause: Cause.pretty(recoveryCause),
|
|
84103
|
+
originalCause: Cause.pretty(cause)
|
|
84104
|
+
})));
|
|
84105
|
+
const sendTurnRequest = yield* buildSendTurnRequestForThread({
|
|
84106
|
+
threadId: event.payload.threadId,
|
|
84107
|
+
messageText: message.text,
|
|
84108
|
+
...message.attachments !== void 0 ? { attachments: message.attachments } : {},
|
|
84109
|
+
...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
|
|
84110
|
+
interactionMode: event.payload.interactionMode,
|
|
84111
|
+
createdAt: event.payload.createdAt
|
|
84112
|
+
}).pipe(Effect.map(Option.some), Effect.catchCause((cause) => handleTurnStartFailure(cause).pipe(Effect.as(Option.none()))));
|
|
84113
|
+
if (Option.isNone(sendTurnRequest)) return;
|
|
84114
|
+
yield* providerService.sendTurn(sendTurnRequest.value).pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
|
|
84115
|
+
});
|
|
84116
|
+
const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (event) {
|
|
84117
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84118
|
+
if (!thread) return;
|
|
84119
|
+
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
84120
|
+
threadId: event.payload.threadId,
|
|
84121
|
+
kind: "provider.turn.interrupt.failed",
|
|
84122
|
+
summary: "Provider turn interrupt failed",
|
|
84123
|
+
detail: "No active provider session is bound to this thread.",
|
|
84124
|
+
turnId: event.payload.turnId ?? null,
|
|
84125
|
+
createdAt: event.payload.createdAt
|
|
84126
|
+
});
|
|
84127
|
+
yield* providerService.interruptTurn({ threadId: event.payload.threadId });
|
|
84128
|
+
yield* appendTurnInterruptedActivity({
|
|
84129
|
+
threadId: event.payload.threadId,
|
|
84130
|
+
turnId: event.payload.turnId ?? null,
|
|
84131
|
+
createdAt: event.payload.createdAt
|
|
84132
|
+
});
|
|
84133
|
+
});
|
|
84134
|
+
const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (event) {
|
|
84135
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84136
|
+
if (!thread) return;
|
|
84137
|
+
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
84138
|
+
threadId: event.payload.threadId,
|
|
84139
|
+
kind: "provider.approval.respond.failed",
|
|
84140
|
+
summary: "Provider approval response failed",
|
|
84141
|
+
detail: "No active provider session is bound to this thread.",
|
|
84142
|
+
turnId: null,
|
|
84143
|
+
createdAt: event.payload.createdAt,
|
|
84144
|
+
requestId: event.payload.requestId
|
|
84145
|
+
});
|
|
84146
|
+
yield* providerService.respondToRequest({
|
|
84147
|
+
threadId: event.payload.threadId,
|
|
84148
|
+
requestId: event.payload.requestId,
|
|
84149
|
+
decision: event.payload.decision
|
|
84150
|
+
}).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
|
|
84151
|
+
threadId: event.payload.threadId,
|
|
84152
|
+
kind: "provider.approval.respond.failed",
|
|
84153
|
+
summary: "Provider approval response failed",
|
|
84154
|
+
detail: isUnknownPendingApprovalRequestError(cause) ? stalePendingRequestDetail("approval", event.payload.requestId) : Cause.pretty(cause),
|
|
84155
|
+
turnId: null,
|
|
84156
|
+
createdAt: event.payload.createdAt,
|
|
84157
|
+
requestId: event.payload.requestId
|
|
84158
|
+
})));
|
|
84159
|
+
});
|
|
84160
|
+
const processUserInputResponseRequested = Effect.fn("processUserInputResponseRequested")(function* (event) {
|
|
84161
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84162
|
+
if (!thread) return;
|
|
84163
|
+
if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
|
|
84164
|
+
threadId: event.payload.threadId,
|
|
84165
|
+
kind: "provider.user-input.respond.failed",
|
|
84166
|
+
summary: "Provider user input response failed",
|
|
84167
|
+
detail: "No active provider session is bound to this thread.",
|
|
84168
|
+
turnId: null,
|
|
84169
|
+
createdAt: event.payload.createdAt,
|
|
84170
|
+
requestId: event.payload.requestId
|
|
84171
|
+
});
|
|
84172
|
+
yield* providerService.respondToUserInput({
|
|
84173
|
+
threadId: event.payload.threadId,
|
|
84174
|
+
requestId: event.payload.requestId,
|
|
84175
|
+
answers: event.payload.answers
|
|
84176
|
+
}).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
|
|
84177
|
+
threadId: event.payload.threadId,
|
|
84178
|
+
kind: "provider.user-input.respond.failed",
|
|
84179
|
+
summary: "Provider user input response failed",
|
|
84180
|
+
detail: isUnknownPendingUserInputRequestError(cause) ? stalePendingRequestDetail("user-input", event.payload.requestId) : Cause.pretty(cause),
|
|
84181
|
+
turnId: null,
|
|
84182
|
+
createdAt: event.payload.createdAt,
|
|
84183
|
+
requestId: event.payload.requestId
|
|
84184
|
+
})));
|
|
84185
|
+
});
|
|
84186
|
+
const processSessionStopRequested = Effect.fn("processSessionStopRequested")(function* (event) {
|
|
84187
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84188
|
+
if (!thread) return;
|
|
84189
|
+
const now = event.payload.createdAt;
|
|
84190
|
+
if (thread.session && thread.session.status !== "stopped") yield* providerService.stopSession({ threadId: thread.id });
|
|
84191
|
+
yield* setThreadSession({
|
|
84192
|
+
threadId: thread.id,
|
|
84193
|
+
session: {
|
|
84194
|
+
threadId: thread.id,
|
|
84195
|
+
status: "stopped",
|
|
84196
|
+
providerName: thread.session?.providerName ?? null,
|
|
84197
|
+
...thread.session?.providerInstanceId !== void 0 ? { providerInstanceId: thread.session.providerInstanceId } : {},
|
|
84198
|
+
runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
|
|
84199
|
+
activeTurnId: null,
|
|
84200
|
+
lastError: thread.session?.lastError ?? null,
|
|
84201
|
+
updatedAt: now
|
|
84202
|
+
},
|
|
84203
|
+
createdAt: now
|
|
84204
|
+
});
|
|
84205
|
+
});
|
|
84206
|
+
const processDomainEvent = Effect.fn("processDomainEvent")(function* (event) {
|
|
84207
|
+
yield* Effect.annotateCurrentSpan({
|
|
84208
|
+
"orchestration.event_type": event.type,
|
|
84209
|
+
"orchestration.thread_id": event.payload.threadId,
|
|
84210
|
+
...event.commandId ? { "orchestration.command_id": event.commandId } : {}
|
|
84211
|
+
});
|
|
84212
|
+
yield* increment(orchestrationEventsProcessedTotal, { eventType: event.type });
|
|
84213
|
+
switch (event.type) {
|
|
84214
|
+
case "thread.runtime-mode-set": {
|
|
84215
|
+
const thread = yield* resolveThread(event.payload.threadId);
|
|
84216
|
+
if (!thread?.session || thread.session.status === "stopped") return;
|
|
84217
|
+
const cachedModelSelection = threadModelSelections.get(event.payload.threadId);
|
|
84218
|
+
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, cachedModelSelection !== void 0 ? { modelSelection: cachedModelSelection } : {});
|
|
84219
|
+
return;
|
|
83775
84220
|
}
|
|
83776
|
-
|
|
83777
|
-
|
|
83778
|
-
|
|
83779
|
-
|
|
83780
|
-
|
|
83781
|
-
|
|
83782
|
-
|
|
84221
|
+
case "thread.turn-start-requested":
|
|
84222
|
+
yield* processTurnStartRequested(event);
|
|
84223
|
+
return;
|
|
84224
|
+
case "thread.turn-interrupt-requested":
|
|
84225
|
+
yield* processTurnInterruptRequested(event);
|
|
84226
|
+
return;
|
|
84227
|
+
case "thread.approval-response-requested":
|
|
84228
|
+
yield* processApprovalResponseRequested(event);
|
|
84229
|
+
return;
|
|
84230
|
+
case "thread.user-input-response-requested":
|
|
84231
|
+
yield* processUserInputResponseRequested(event);
|
|
84232
|
+
return;
|
|
84233
|
+
case "thread.session-stop-requested":
|
|
84234
|
+
yield* processSessionStopRequested(event);
|
|
84235
|
+
return;
|
|
83783
84236
|
}
|
|
83784
|
-
|
|
83785
|
-
|
|
83786
|
-
|
|
83787
|
-
|
|
83788
|
-
|
|
83789
|
-
|
|
83790
|
-
|
|
83791
|
-
|
|
83792
|
-
|
|
83793
|
-
}
|
|
83794
|
-
function startsWithUnifiedDiffFileHeader(contents) {
|
|
83795
|
-
return isUnifiedDiffFileHeaderAt(contents, 0);
|
|
83796
|
-
}
|
|
83797
|
-
function isUnifiedDiffFileHeaderAt(contents, lineStartIndex) {
|
|
83798
|
-
const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
|
|
83799
|
-
return isUnifiedDiffHeaderLineAt(contents, lineStartIndex, "---") && isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, "+++");
|
|
83800
|
-
}
|
|
83801
|
-
function isUnifiedDiffHeaderLineAt(contents, lineStartIndex, prefix) {
|
|
83802
|
-
if (!contents.startsWith(prefix, lineStartIndex)) return false;
|
|
83803
|
-
const separator = contents[lineStartIndex + prefix.length];
|
|
83804
|
-
if (separator !== " " && separator !== " ") return false;
|
|
83805
|
-
for (let index = lineStartIndex + prefix.length + 1; index < contents.length; index++) {
|
|
83806
|
-
const char = contents[index];
|
|
83807
|
-
if (char === "\n" || char === "\r") break;
|
|
83808
|
-
if (char !== " " && char !== " ") return true;
|
|
83809
|
-
}
|
|
83810
|
-
return false;
|
|
83811
|
-
}
|
|
83812
|
-
function getNextLineStartIndex(contents, lineStartIndex) {
|
|
83813
|
-
const newlineIndex = contents.indexOf("\n", lineStartIndex);
|
|
83814
|
-
return newlineIndex === -1 ? contents.length : newlineIndex + 1;
|
|
83815
|
-
}
|
|
83816
|
-
function isHunkBodyLine(line) {
|
|
83817
|
-
const firstChar = line[0];
|
|
83818
|
-
return firstChar === "+" || firstChar === "-" || firstChar === " ";
|
|
83819
|
-
}
|
|
83820
|
-
function isFormatPatchVersionSeparator(line) {
|
|
83821
|
-
if (!line.startsWith("--")) return false;
|
|
83822
|
-
for (let index = 2; index < line.length; index++) {
|
|
83823
|
-
const char = line[index];
|
|
83824
|
-
if (char !== " " && char !== " " && char !== "\n" && char !== "\r") return false;
|
|
83825
|
-
}
|
|
83826
|
-
return true;
|
|
83827
|
-
}
|
|
83828
|
-
function parseHunkHeader(line) {
|
|
83829
|
-
if (!line.startsWith("@@ -")) return;
|
|
83830
|
-
let index = 4;
|
|
83831
|
-
const deletionStartResult = readPositiveInteger(line, index);
|
|
83832
|
-
if (deletionStartResult == null) return;
|
|
83833
|
-
const deletionStart = deletionStartResult.value;
|
|
83834
|
-
index = deletionStartResult.endIndex;
|
|
83835
|
-
let deletionCount = 1;
|
|
83836
|
-
if (line[index] === ",") {
|
|
83837
|
-
const deletionCountResult = readPositiveInteger(line, index + 1);
|
|
83838
|
-
if (deletionCountResult == null) return;
|
|
83839
|
-
deletionCount = deletionCountResult.value;
|
|
83840
|
-
index = deletionCountResult.endIndex;
|
|
83841
|
-
}
|
|
83842
|
-
if (line[index] !== " " || line[index + 1] !== "+") return;
|
|
83843
|
-
index += 2;
|
|
83844
|
-
const additionStartResult = readPositiveInteger(line, index);
|
|
83845
|
-
if (additionStartResult == null) return;
|
|
83846
|
-
const additionStart = additionStartResult.value;
|
|
83847
|
-
index = additionStartResult.endIndex;
|
|
83848
|
-
let additionCount = 1;
|
|
83849
|
-
if (line[index] === ",") {
|
|
83850
|
-
const additionCountResult = readPositiveInteger(line, index + 1);
|
|
83851
|
-
if (additionCountResult == null) return;
|
|
83852
|
-
additionCount = additionCountResult.value;
|
|
83853
|
-
index = additionCountResult.endIndex;
|
|
83854
|
-
}
|
|
83855
|
-
if (line[index] !== " " || line[index + 1] !== "@" || line[index + 2] !== "@") return;
|
|
83856
|
-
let hunkContext;
|
|
83857
|
-
const contextStartIndex = index + 3;
|
|
83858
|
-
if (line[contextStartIndex] === " ") hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));
|
|
83859
|
-
return {
|
|
83860
|
-
additionCount,
|
|
83861
|
-
additionStart,
|
|
83862
|
-
deletionCount,
|
|
83863
|
-
deletionStart,
|
|
83864
|
-
hunkContext
|
|
83865
|
-
};
|
|
83866
|
-
}
|
|
83867
|
-
function readPositiveInteger(value, startIndex) {
|
|
83868
|
-
let index = startIndex;
|
|
83869
|
-
let parsedValue = 0;
|
|
83870
|
-
for (; index < value.length; index++) {
|
|
83871
|
-
const digit = value.charCodeAt(index) - 48;
|
|
83872
|
-
if (digit < 0 || digit > 9) break;
|
|
83873
|
-
parsedValue = parsedValue * 10 + digit;
|
|
83874
|
-
}
|
|
83875
|
-
if (index === startIndex) return;
|
|
83876
|
-
return {
|
|
83877
|
-
value: parsedValue,
|
|
83878
|
-
endIndex: index
|
|
83879
|
-
};
|
|
83880
|
-
}
|
|
83881
|
-
function trimLineEnd(value) {
|
|
83882
|
-
if (value.endsWith("\r\n")) return value.slice(0, -2);
|
|
83883
|
-
if (value.endsWith("\n")) return value.slice(0, -1);
|
|
83884
|
-
return value;
|
|
83885
|
-
}
|
|
83886
|
-
function isGitDiffPatch(data) {
|
|
83887
|
-
return data.startsWith("diff --git") || data.includes("\ndiff --git");
|
|
83888
|
-
}
|
|
83889
|
-
function splitAtLinePrefix(contents, prefix) {
|
|
83890
|
-
if (contents.length === 0) return [""];
|
|
83891
|
-
const newlinePrefix = `\n${prefix}`;
|
|
83892
|
-
const firstBoundaryIndex = contents.startsWith(prefix) ? 0 : findLinePrefixIndex(contents, newlinePrefix, 0);
|
|
83893
|
-
if (firstBoundaryIndex === -1) return [contents];
|
|
83894
|
-
const parts = [];
|
|
83895
|
-
if (firstBoundaryIndex > 0) parts.push(contents.slice(0, firstBoundaryIndex));
|
|
83896
|
-
let startIndex = firstBoundaryIndex;
|
|
83897
|
-
for (;;) {
|
|
83898
|
-
const nextBoundaryIndex = findLinePrefixIndex(contents, newlinePrefix, startIndex + 1);
|
|
83899
|
-
if (nextBoundaryIndex === -1) break;
|
|
83900
|
-
parts.push(contents.slice(startIndex, nextBoundaryIndex));
|
|
83901
|
-
startIndex = nextBoundaryIndex;
|
|
83902
|
-
}
|
|
83903
|
-
parts.push(contents.slice(startIndex));
|
|
83904
|
-
return parts;
|
|
83905
|
-
}
|
|
83906
|
-
function findLinePrefixIndex(contents, newlinePrefix, fromIndex) {
|
|
83907
|
-
const index = contents.indexOf(newlinePrefix, fromIndex);
|
|
83908
|
-
return index === -1 ? -1 : index + 1;
|
|
83909
|
-
}
|
|
83910
|
-
function maybeDetachOptionalString(value) {
|
|
83911
|
-
return value == null ? value : detachString(value);
|
|
83912
|
-
}
|
|
83913
|
-
function parseRawLineType(firstChar) {
|
|
83914
|
-
return firstChar === " " ? "context" : firstChar === "\\" ? "metadata" : firstChar === "+" ? "addition" : "deletion";
|
|
83915
|
-
}
|
|
83916
|
-
function getParsedLineContent(rawLine) {
|
|
83917
|
-
const processedLine = rawLine.slice(1);
|
|
83918
|
-
return detachString(processedLine === "" ? "\n" : processedLine);
|
|
83919
|
-
}
|
|
83920
|
-
function createContentGroup(type, deletionLineIndex, additionLineIndex) {
|
|
83921
|
-
if (type === "change") return {
|
|
83922
|
-
type: "change",
|
|
83923
|
-
additions: 0,
|
|
83924
|
-
deletions: 0,
|
|
83925
|
-
additionLineIndex,
|
|
83926
|
-
deletionLineIndex
|
|
83927
|
-
};
|
|
84237
|
+
});
|
|
84238
|
+
const processDomainEventSafely = (event) => processDomainEvent(event).pipe(Effect.catchCause((cause) => {
|
|
84239
|
+
if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause);
|
|
84240
|
+
return Effect.logWarning("provider command reactor failed to process event", {
|
|
84241
|
+
eventType: event.type,
|
|
84242
|
+
cause: Cause.pretty(cause)
|
|
84243
|
+
});
|
|
84244
|
+
}));
|
|
84245
|
+
const worker = yield* makeDrainableWorker(processDomainEventSafely);
|
|
83928
84246
|
return {
|
|
83929
|
-
|
|
83930
|
-
|
|
83931
|
-
|
|
83932
|
-
|
|
84247
|
+
start: Effect.fn("start")(function* () {
|
|
84248
|
+
const processEvent = Effect.fn("processEvent")(function* (event) {
|
|
84249
|
+
if (event.type === "thread.runtime-mode-set" || event.type === "thread.turn-start-requested" || event.type === "thread.turn-interrupt-requested" || event.type === "thread.approval-response-requested" || event.type === "thread.user-input-response-requested" || event.type === "thread.session-stop-requested") return yield* worker.enqueue(event);
|
|
84250
|
+
});
|
|
84251
|
+
yield* Effect.forkScoped(Stream.runForEach(orchestrationEngine.streamDomainEvents, processEvent));
|
|
84252
|
+
}),
|
|
84253
|
+
drain: worker.drain
|
|
83933
84254
|
};
|
|
83934
|
-
}
|
|
84255
|
+
});
|
|
84256
|
+
const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$2);
|
|
83935
84257
|
//#endregion
|
|
83936
84258
|
//#region src/checkpointing/Diffs.ts
|
|
83937
84259
|
function parseTurnDiffFilesFromUnifiedDiff(diff) {
|
|
@@ -83972,6 +84294,7 @@ const make$1 = Effect.gen(function* () {
|
|
|
83972
84294
|
const receiptBus = yield* RuntimeReceiptBus;
|
|
83973
84295
|
const workspaceEntries = yield* WorkspaceEntries;
|
|
83974
84296
|
const vcsStatusBroadcaster = yield* VcsStatusBroadcaster;
|
|
84297
|
+
const threadActivities = yield* ProjectionThreadActivityRepository;
|
|
83975
84298
|
const appendRevertFailureActivity = (input) => Effect.all({
|
|
83976
84299
|
commandId: serverCommandId("checkpoint-revert-failure"),
|
|
83977
84300
|
activityId: serverEventId
|
|
@@ -84060,13 +84383,21 @@ const make$1 = Effect.gen(function* () {
|
|
|
84060
84383
|
checkpointRef: targetCheckpointRef
|
|
84061
84384
|
});
|
|
84062
84385
|
yield* workspaceEntries.refresh(input.cwd);
|
|
84386
|
+
const turnChangedPaths = yield* threadActivities.listByThreadId({ threadId: input.threadId }).pipe(Effect.map((activities) => {
|
|
84387
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
84388
|
+
for (const activity of activities) {
|
|
84389
|
+
if (activity.tone !== "tool" || activity.turnId !== input.turnId) continue;
|
|
84390
|
+
for (const changedPath of collectActivityChangedFilePaths(activity.payload)) changedPaths.add(changedPath);
|
|
84391
|
+
}
|
|
84392
|
+
return normalizeChangedFilePaths(changedPaths, input.cwd);
|
|
84393
|
+
}), Effect.orElseSucceed(() => /* @__PURE__ */ new Set()));
|
|
84063
84394
|
const files = yield* checkpointStore.diffCheckpoints({
|
|
84064
84395
|
cwd: input.cwd,
|
|
84065
84396
|
fromCheckpointRef,
|
|
84066
84397
|
toCheckpointRef: targetCheckpointRef,
|
|
84067
84398
|
fallbackFromToHead: false,
|
|
84068
84399
|
ignoreWhitespace: false
|
|
84069
|
-
}).pipe(Effect.map((diff) => parseTurnDiffFilesFromUnifiedDiff(diff).map((file) => ({
|
|
84400
|
+
}).pipe(Effect.map((diff) => parseTurnDiffFilesFromUnifiedDiff(diff).filter((file) => turnChangedPaths.size === 0 || turnChangedPaths.has(file.path)).map((file) => ({
|
|
84070
84401
|
path: file.path,
|
|
84071
84402
|
kind: "modified",
|
|
84072
84403
|
additions: file.additions,
|