@cmmd-center/forge 0.13.2 → 0.13.4
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/{BunHttpServer-VI75aqJw.mjs → BunHttpServer-CDh4Vioe.mjs} +6 -6
- package/dist/{BunHttpServer-BhE7Szci.cjs → BunHttpServer-DKvSpNd1.cjs} +6 -6
- package/dist/{BunPTY-oVISucl_.mjs → BunPTY-DkAIzLIx.mjs} +3 -3
- package/dist/{BunPTY-86ttlTi6.cjs → BunPTY-uyreNRGp.cjs} +3 -3
- package/dist/{BunServices-D0JTDujL.mjs → BunServices-D6PAheqS.mjs} +3 -3
- package/dist/{BunServices-BwE9KexF.cjs → BunServices-DDh7Whyx.cjs} +3 -3
- package/dist/{Cache-p0X77SKw.mjs → Cache-BVilO50s.mjs} +3 -3
- package/dist/{Cache-CWlxNDbC.cjs → Cache-DCF22Dfk.cjs} +3 -3
- package/dist/{Config-AqUR_lhX.mjs → Config-Cdfpphpo.mjs} +2 -2
- package/dist/{Config-CkEzdD9L.cjs → Config-CwKCCk14.cjs} +2 -2
- package/dist/{HttpServer-DNGCTTdk.mjs → HttpServer-ClsGgAq1.mjs} +3 -3
- package/dist/{HttpServer-DGoLR-kU.cjs → HttpServer-D1gSU7NX.cjs} +3 -3
- package/dist/{NodeHttpServer-Dw7ivXfX.mjs → NodeHttpServer-DT0TF8s4.mjs} +7 -7
- package/dist/{NodeHttpServer-BPjxt_DT.cjs → NodeHttpServer-Dfc7nSMq.cjs} +7 -7
- package/dist/{NodePTY-Cps0jsDA.cjs → NodePTY-S4tPEwZc.cjs} +4 -4
- package/dist/{NodePTY-CVH2yNYl.mjs → NodePTY-loxi8Vku.mjs} +4 -4
- package/dist/{NodeServices-CBNXOYLG.mjs → NodeServices-BahzLhbo.mjs} +3 -3
- package/dist/{NodeServices-Cw6SxEbl.cjs → NodeServices-Cb0rXFb2.cjs} +3 -3
- package/dist/{NodeServices-Ddr-tYRu.cjs → NodeServices-DE8jhdcU.cjs} +1 -1
- package/dist/{NodeSqliteClient-DGInbxQ3.mjs → NodeSqliteClient-DqFjB3u_.mjs} +6 -6
- package/dist/{NodeSqliteClient-CmxPzcBf.cjs → NodeSqliteClient-qjBWAOGq.cjs} +6 -6
- package/dist/{NodeTerminal-BEGtoMSS.mjs → NodeTerminal-BPw7bktz.mjs} +3 -3
- package/dist/{NodeTerminal-5iWbO0B2.cjs → NodeTerminal-rYbifme7.cjs} +3 -3
- package/dist/{PTY-BxYDiW3h.cjs → PTY-B4ZctbwQ.cjs} +2 -2
- package/dist/{PTY-rIPf52QR.mjs → PTY-BzdbZ0Ym.mjs} +2 -2
- package/dist/{Path-C6Zb2WHp.mjs → Path-C851t6sC.mjs} +2 -2
- package/dist/{Path-Be7zSwar.cjs → Path-CGCc_dDx.cjs} +2 -2
- package/dist/{Schema-dLi7Qssy.cjs → Schema-CDCJ3xZo.cjs} +7 -1
- package/dist/{Schema-CJyk7ah1.mjs → Schema-DOBWF8gQ.mjs} +2 -2
- package/dist/{SqlError-cyJT2IRY.mjs → SqlError-3aqibZrj.mjs} +3 -3
- package/dist/{SqlError-Bt0Bcd5C.cjs → SqlError-CzB6p9lL.cjs} +3 -3
- package/dist/{SqliteClient-B08FzZM4.cjs → SqliteClient-Cq1peWN1.cjs} +5 -5
- package/dist/{SqliteClient-CIsITMoQ.mjs → SqliteClient-DTWCGkrj.mjs} +5 -5
- package/dist/bin.cjs +1216 -205
- package/dist/bin.mjs +1239 -231
- package/dist/client/assets/{CanvasPanel-BrVen-8G.js → CanvasPanel-DkUmuXgO.js} +3 -3
- package/dist/client/assets/CompositeItem-DG5YBMcI.js +2 -0
- package/dist/client/assets/{DiffPanel-Bh9UFe_U.js → DiffPanel-DLCCD1ht.js} +3 -3
- package/dist/client/assets/DiffPanel.logic-dIDfHUQm.js +21 -0
- package/dist/client/assets/DiffPanelShell-Ba6NA1s7.js +2531 -0
- package/dist/client/assets/{DiffWorkerPoolProvider-CYkhfTpq.js → DiffWorkerPoolProvider-Bqy6DzWP.js} +52 -52
- package/dist/client/assets/PullRequestCodePanel-8SJWZPM5.js +5 -0
- package/dist/client/assets/canvasStateStore-BWZ8xdXr.js +2 -0
- package/dist/client/assets/{cmmdAuthRedirect-sqR8W6vY.js → cmmdAuthRedirect--srSJjFS.js} +2 -2
- package/dist/client/assets/diffTargetResolution-DA0SlQE0.js +2 -0
- package/dist/client/assets/index-C9Upewz-.js +151 -0
- package/dist/client/assets/index-DlyGyVH6.css +1 -0
- package/dist/client/assets/src-DwOYg6gv.js +3 -0
- package/dist/client/assets/toggle-Ozy-3fZ2.js +2 -0
- package/dist/client/assets/useTriggerFocusGuards-FzV_CNjl.js +2 -0
- package/dist/client/index.html +11 -11
- package/dist/forge-build.json +1 -1
- package/dist/{open-FIgTDosf.mjs → open-D8mGyWmt.mjs} +3 -3
- package/package.json +1 -1
- package/dist/client/assets/CompositeItem-BNZfMUvm.js +0 -2
- package/dist/client/assets/DiffPanel.logic-A3r3u_Vr.js +0 -21
- package/dist/client/assets/DiffPanelShell-B30xoHOs.js +0 -2531
- package/dist/client/assets/PullRequestCodePanel-T32DALQE.js +0 -5
- package/dist/client/assets/Virtualizer-DbDh3RHG.js +0 -2
- package/dist/client/assets/canvasStateStore-DN5mPB5B.js +0 -2
- package/dist/client/assets/index-Dk7T0zVg.css +0 -1
- package/dist/client/assets/index-FpNJGy15.js +0 -151
- package/dist/client/assets/src-CFqnisEM.js +0 -3
- package/dist/client/assets/toggle-CLUqZXdO.js +0 -2
- package/dist/client/assets/useTriggerFocusGuards-CaUimC-O.js +0 -2
package/dist/bin.cjs
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
const require_chunk = require("./chunk-BhaIgcZK.cjs");
|
|
5
|
-
const require_Schema$1 = require("./Schema-
|
|
6
|
-
const require_Path = require("./Path-
|
|
7
|
-
const require_NodeTerminal = require("./NodeTerminal-
|
|
8
|
-
const require_NodeServices = require("./NodeServices-
|
|
9
|
-
const require_HttpServer = require("./HttpServer-
|
|
10
|
-
const require_Config = require("./Config-
|
|
11
|
-
const require_Cache = require("./Cache-
|
|
12
|
-
const require_SqlError = require("./SqlError-
|
|
5
|
+
const require_Schema$1 = require("./Schema-CDCJ3xZo.cjs");
|
|
6
|
+
const require_Path = require("./Path-CGCc_dDx.cjs");
|
|
7
|
+
const require_NodeTerminal = require("./NodeTerminal-rYbifme7.cjs");
|
|
8
|
+
const require_NodeServices = require("./NodeServices-Cb0rXFb2.cjs");
|
|
9
|
+
const require_HttpServer = require("./HttpServer-D1gSU7NX.cjs");
|
|
10
|
+
const require_Config = require("./Config-CwKCCk14.cjs");
|
|
11
|
+
const require_Cache = require("./Cache-DCF22Dfk.cjs");
|
|
12
|
+
const require_SqlError = require("./SqlError-CzB6p9lL.cjs");
|
|
13
13
|
const require_Mime = require("./Mime-Bilnh8zE.cjs");
|
|
14
14
|
const require_esm = require("./esm-BZhXwX5p.cjs");
|
|
15
15
|
const require_runtimeControlAuthority = require("./runtimeControlAuthority-B3kNmYRT.cjs");
|
|
16
|
-
const require_PTY = require("./PTY-
|
|
16
|
+
const require_PTY = require("./PTY-B4ZctbwQ.cjs");
|
|
17
17
|
let node_child_process = require("node:child_process");
|
|
18
18
|
node_child_process = require_chunk.__toESM(node_child_process);
|
|
19
19
|
let node_stream = require("node:stream");
|
|
@@ -16544,7 +16544,7 @@ function makeNetServiceShape(createServer = node_net.createServer) {
|
|
|
16544
16544
|
}
|
|
16545
16545
|
//#endregion
|
|
16546
16546
|
//#region ../../packages/shared/src/releaseInfo.generated.ts
|
|
16547
|
-
const FORGE_RELEASE_VERSION = "0.13.
|
|
16547
|
+
const FORGE_RELEASE_VERSION = "0.13.4";
|
|
16548
16548
|
//#endregion
|
|
16549
16549
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16550
16550
|
/**
|
|
@@ -16668,6 +16668,70 @@ const CheckpointRef = makeEntityId("CheckpointRef");
|
|
|
16668
16668
|
*/
|
|
16669
16669
|
const ThreadEnvMode = require_Schema$1.Literals(["local", "worktree"]);
|
|
16670
16670
|
//#endregion
|
|
16671
|
+
//#region ../../packages/contracts/src/assets.ts
|
|
16672
|
+
/**
|
|
16673
|
+
* Addresses for the bytes a client cannot fetch by guessing.
|
|
16674
|
+
*
|
|
16675
|
+
* Forge already serves the bytes: attachments under `/attachments/*` and
|
|
16676
|
+
* project favicons at `GET /api/project-favicon?cwd=`. What it did not serve
|
|
16677
|
+
* was the question "what is the URL for this thing", which is the only way a
|
|
16678
|
+
* client that holds an attachment id or a workspace root can reach either.
|
|
16679
|
+
* The phone asks that question on the thread list for every project, so
|
|
16680
|
+
* until this existed the answer was an error the phone drew as a spinner.
|
|
16681
|
+
*
|
|
16682
|
+
* These schemas are the Forge side of `assets.*` in the vendored t3 contracts.
|
|
16683
|
+
* They must decode what that package encodes: same tags, same field names.
|
|
16684
|
+
*/
|
|
16685
|
+
const ASSET_PATH_MAX_LENGTH = 1024;
|
|
16686
|
+
const ASSET_ID_MAX_LENGTH = 256;
|
|
16687
|
+
const ASSET_URL_MAX_LENGTH = 4096;
|
|
16688
|
+
const AssetResource = require_Schema$1.Union([
|
|
16689
|
+
require_Schema$1.TaggedStruct("workspace-file", {
|
|
16690
|
+
threadId: ThreadId,
|
|
16691
|
+
path: TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_PATH_MAX_LENGTH))
|
|
16692
|
+
}),
|
|
16693
|
+
require_Schema$1.TaggedStruct("attachment", { attachmentId: TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_ID_MAX_LENGTH)) }),
|
|
16694
|
+
require_Schema$1.TaggedStruct("project-favicon", {
|
|
16695
|
+
cwd: TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_PATH_MAX_LENGTH)),
|
|
16696
|
+
path: require_Schema$1.optional(TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_PATH_MAX_LENGTH)))
|
|
16697
|
+
})
|
|
16698
|
+
]);
|
|
16699
|
+
const AssetCreateUrlInput = require_Schema$1.Struct({ resource: AssetResource });
|
|
16700
|
+
const AssetCreateUrlResult = require_Schema$1.Struct({
|
|
16701
|
+
relativeUrl: TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_URL_MAX_LENGTH)),
|
|
16702
|
+
expiresAt: require_Schema$1.Number,
|
|
16703
|
+
sourcePath: require_Schema$1.optional(TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(ASSET_PATH_MAX_LENGTH)))
|
|
16704
|
+
});
|
|
16705
|
+
/**
|
|
16706
|
+
* The resource cannot be addressed by URL on this server.
|
|
16707
|
+
*
|
|
16708
|
+
* Tag and message match the vendored client's `AssetAccessError` union so the
|
|
16709
|
+
* phone decodes the failure rather than falling through to a generic transport
|
|
16710
|
+
* error. `workspace-file` is the case that lands here.
|
|
16711
|
+
*/
|
|
16712
|
+
var AssetWorkspaceResolutionError = class extends require_Schema$1.TaggedErrorClass()("AssetWorkspaceResolutionError", {
|
|
16713
|
+
resource: AssetResource,
|
|
16714
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
16715
|
+
}) {
|
|
16716
|
+
get message() {
|
|
16717
|
+
return "Failed to resolve workspace.";
|
|
16718
|
+
}
|
|
16719
|
+
};
|
|
16720
|
+
/** The signing key that makes an asset URL self-authenticating could not be read. */
|
|
16721
|
+
var AssetSigningKeyLoadError = class extends require_Schema$1.TaggedErrorClass()("AssetSigningKeyLoadError", {
|
|
16722
|
+
resource: AssetResource,
|
|
16723
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
16724
|
+
}) {
|
|
16725
|
+
get message() {
|
|
16726
|
+
return "Failed to load the asset signing key.";
|
|
16727
|
+
}
|
|
16728
|
+
};
|
|
16729
|
+
const AssetAccessError = require_Schema$1.Union([AssetWorkspaceResolutionError, AssetSigningKeyLoadError]);
|
|
16730
|
+
const AssetsRpcSchemas = { createUrl: {
|
|
16731
|
+
input: AssetCreateUrlInput,
|
|
16732
|
+
output: AssetCreateUrlResult
|
|
16733
|
+
} };
|
|
16734
|
+
//#endregion
|
|
16671
16735
|
//#region ../../node_modules/.bun/effect@4.0.0-beta.51/node_modules/effect/dist/unstable/rpc/RpcMiddleware.js
|
|
16672
16736
|
/**
|
|
16673
16737
|
* @since 4.0.0
|
|
@@ -18200,6 +18264,7 @@ const ExecutionEnvironmentCapabilities = require_Schema$1.Struct({
|
|
|
18200
18264
|
threadPullRequestLinking: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
18201
18265
|
cloudMode: require_Schema$1.Boolean.pipe(require_Schema$1.withDecodingDefault(require_Schema$1.succeed(false))),
|
|
18202
18266
|
threadSettlement: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
18267
|
+
threadTitleRegeneration: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
18203
18268
|
posthogPublicKey: require_Schema$1.NullOr(require_Schema$1.String).pipe(require_Schema$1.withDecodingDefault(require_Schema$1.succeed(null))),
|
|
18204
18269
|
posthogHost: require_Schema$1.NullOr(require_Schema$1.String).pipe(require_Schema$1.withDecodingDefault(require_Schema$1.succeed(null))),
|
|
18205
18270
|
runtime: require_Schema$1.optionalKey(ExecutionEnvironmentRuntimeCapabilities),
|
|
@@ -19228,6 +19293,51 @@ require_Schema$1.Struct({
|
|
|
19228
19293
|
headRepositoryNameWithOwner: require_Schema$1.optional(require_Schema$1.NullOr(TrimmedNonEmptyString)),
|
|
19229
19294
|
headRepositoryOwnerLogin: require_Schema$1.optional(require_Schema$1.NullOr(TrimmedNonEmptyString))
|
|
19230
19295
|
});
|
|
19296
|
+
const SourceControlCloneProtocol = require_Schema$1.Literals([
|
|
19297
|
+
"auto",
|
|
19298
|
+
"ssh",
|
|
19299
|
+
"https"
|
|
19300
|
+
]);
|
|
19301
|
+
const SourceControlRepositoryInfo = require_Schema$1.Struct({
|
|
19302
|
+
provider: SourceControlProviderKind$1,
|
|
19303
|
+
nameWithOwner: TrimmedNonEmptyString,
|
|
19304
|
+
url: TrimmedNonEmptyString,
|
|
19305
|
+
sshUrl: TrimmedNonEmptyString
|
|
19306
|
+
});
|
|
19307
|
+
const SourceControlRepositoryLookupInput = require_Schema$1.Struct({
|
|
19308
|
+
provider: SourceControlProviderKind$1,
|
|
19309
|
+
repository: TrimmedNonEmptyString,
|
|
19310
|
+
cwd: require_Schema$1.optional(TrimmedNonEmptyString)
|
|
19311
|
+
});
|
|
19312
|
+
/**
|
|
19313
|
+
* Either name the repository (`provider` + `repository`) or paste a clone URL
|
|
19314
|
+
* (`remoteUrl`). Both are optional in the schema because the phone sends one
|
|
19315
|
+
* or the other depending on how the user got here; the server rejects a call
|
|
19316
|
+
* that supplies neither, and rejects a `remoteUrl` it cannot resolve to a
|
|
19317
|
+
* repository it is allowed to fetch a token for.
|
|
19318
|
+
*/
|
|
19319
|
+
const SourceControlCloneRepositoryInput = require_Schema$1.Struct({
|
|
19320
|
+
provider: require_Schema$1.optional(SourceControlProviderKind$1),
|
|
19321
|
+
repository: require_Schema$1.optional(TrimmedNonEmptyString),
|
|
19322
|
+
remoteUrl: require_Schema$1.optional(TrimmedNonEmptyString),
|
|
19323
|
+
destinationPath: TrimmedNonEmptyString,
|
|
19324
|
+
protocol: require_Schema$1.optional(SourceControlCloneProtocol)
|
|
19325
|
+
});
|
|
19326
|
+
const SourceControlCloneRepositoryResult = require_Schema$1.Struct({
|
|
19327
|
+
cwd: TrimmedNonEmptyString,
|
|
19328
|
+
remoteUrl: TrimmedNonEmptyString,
|
|
19329
|
+
repository: require_Schema$1.NullOr(SourceControlRepositoryInfo)
|
|
19330
|
+
});
|
|
19331
|
+
var SourceControlRepositoryError = class extends require_Schema$1.TaggedErrorClass()("SourceControlRepositoryError", {
|
|
19332
|
+
provider: SourceControlProviderKind$1,
|
|
19333
|
+
operation: require_Schema$1.String,
|
|
19334
|
+
detail: require_Schema$1.String,
|
|
19335
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
19336
|
+
}) {
|
|
19337
|
+
get message() {
|
|
19338
|
+
return `Source control repository operation ${this.operation} failed for ${this.provider}: ${this.detail}`;
|
|
19339
|
+
}
|
|
19340
|
+
};
|
|
19231
19341
|
//#endregion
|
|
19232
19342
|
//#region ../../packages/contracts/src/orchestration.ts
|
|
19233
19343
|
const ORCHESTRATION_WS_METHODS = {
|
|
@@ -19239,7 +19349,8 @@ const ORCHESTRATION_WS_METHODS = {
|
|
|
19239
19349
|
replayEvents: "orchestration.replayEvents",
|
|
19240
19350
|
getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot",
|
|
19241
19351
|
subscribeShell: "orchestration.subscribeShell",
|
|
19242
|
-
subscribeThread: "orchestration.subscribeThread"
|
|
19352
|
+
subscribeThread: "orchestration.subscribeThread",
|
|
19353
|
+
searchThreads: "orchestration.searchThreads"
|
|
19243
19354
|
};
|
|
19244
19355
|
const ProviderKind = require_Schema$1.Literals([
|
|
19245
19356
|
"codex",
|
|
@@ -20623,6 +20734,32 @@ const OrchestrationGetFullThreadDiffInput = require_Schema$1.Struct({
|
|
|
20623
20734
|
const OrchestrationGetFullThreadDiffResult = ThreadTurnDiff;
|
|
20624
20735
|
const OrchestrationReplayEventsInput = require_Schema$1.Struct({ fromSequenceExclusive: NonNegativeInt });
|
|
20625
20736
|
const OrchestrationReplayEventsResult = require_Schema$1.ArraySchema(OrchestrationEvent);
|
|
20737
|
+
/**
|
|
20738
|
+
* Which side of the conversation a search hit came from. `null`
|
|
20739
|
+
* `messageCreatedAt` on a match means it came from the thread's own metadata
|
|
20740
|
+
* (its title or its branch) rather than from a message.
|
|
20741
|
+
*/
|
|
20742
|
+
const OrchestrationThreadSearchSource = require_Schema$1.Literals(["user", "assistant"]);
|
|
20743
|
+
/**
|
|
20744
|
+
* The server's SQLite client is synchronous and single-connection, so both the
|
|
20745
|
+
* scan input and the response size are bounded: a search must not be able to
|
|
20746
|
+
* monopolize the connection every keystroke.
|
|
20747
|
+
*/
|
|
20748
|
+
const OrchestrationSearchThreadsInput = require_Schema$1.Struct({
|
|
20749
|
+
query: TrimmedString.check(require_Schema$1.isMinLength(2), require_Schema$1.isMaxLength(200)),
|
|
20750
|
+
limit: require_Schema$1.optionalKey(require_Schema$1.Int.check(require_Schema$1.isBetween({
|
|
20751
|
+
minimum: 1,
|
|
20752
|
+
maximum: 50
|
|
20753
|
+
})))
|
|
20754
|
+
});
|
|
20755
|
+
const OrchestrationThreadSearchMatch = require_Schema$1.Struct({
|
|
20756
|
+
threadId: ThreadId,
|
|
20757
|
+
projectId: ProjectId,
|
|
20758
|
+
source: OrchestrationThreadSearchSource,
|
|
20759
|
+
snippet: require_Schema$1.String.check(require_Schema$1.isMaxLength(240)),
|
|
20760
|
+
messageCreatedAt: require_Schema$1.NullOr(IsoDateTime)
|
|
20761
|
+
});
|
|
20762
|
+
const OrchestrationSearchThreadsResult = require_Schema$1.Struct({ matches: require_Schema$1.ArraySchema(OrchestrationThreadSearchMatch) });
|
|
20626
20763
|
const ProgressPercent = NonNegativeInt.check(require_Schema$1.isLessThanOrEqualTo(100));
|
|
20627
20764
|
const AgentProtocolProgress = require_Schema$1.Struct({
|
|
20628
20765
|
kind: require_Schema$1.Literal("PROGRESS"),
|
|
@@ -20698,6 +20835,10 @@ const OrchestrationRpcSchemas = {
|
|
|
20698
20835
|
getArchivedShellSnapshot: {
|
|
20699
20836
|
input: require_Schema$1.Struct({}),
|
|
20700
20837
|
output: OrchestrationShellSnapshot
|
|
20838
|
+
},
|
|
20839
|
+
searchThreads: {
|
|
20840
|
+
input: OrchestrationSearchThreadsInput,
|
|
20841
|
+
output: OrchestrationSearchThreadsResult
|
|
20701
20842
|
}
|
|
20702
20843
|
};
|
|
20703
20844
|
var OrchestrationGetSnapshotError = class extends require_Schema$1.TaggedErrorClass()("OrchestrationGetSnapshotError", {
|
|
@@ -20720,6 +20861,10 @@ var OrchestrationReplayEventsError = class extends require_Schema$1.TaggedErrorC
|
|
|
20720
20861
|
message: TrimmedNonEmptyString,
|
|
20721
20862
|
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
20722
20863
|
}) {};
|
|
20864
|
+
var OrchestrationSearchThreadsError = class extends require_Schema$1.TaggedErrorClass()("OrchestrationSearchThreadsError", {
|
|
20865
|
+
message: TrimmedNonEmptyString,
|
|
20866
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
20867
|
+
}) {};
|
|
20723
20868
|
//#endregion
|
|
20724
20869
|
//#region ../../packages/contracts/src/provider.ts
|
|
20725
20870
|
const ProviderSessionStatus = require_Schema$1.Literals([
|
|
@@ -20782,6 +20927,31 @@ const ProviderRespondToUserInputInput = require_Schema$1.Struct({
|
|
|
20782
20927
|
requestId: ApprovalRequestId,
|
|
20783
20928
|
answers: ProviderUserInputAnswers
|
|
20784
20929
|
});
|
|
20930
|
+
/**
|
|
20931
|
+
* Sending a thread to the people who maintain Forge.
|
|
20932
|
+
*
|
|
20933
|
+
* The vendored client's `/feedback` slash command calls this. Upstream it
|
|
20934
|
+
* uploads the transcript to the model provider; Forge routes it to the same
|
|
20935
|
+
* CMMD feedback intake the web app's feedback dialog already posts to, so one
|
|
20936
|
+
* report lands in one place no matter which surface filed it.
|
|
20937
|
+
*
|
|
20938
|
+
* `reason` is what the person typed after the command, and is the only part
|
|
20939
|
+
* of the report they wrote. The server fills in the rest.
|
|
20940
|
+
*/
|
|
20941
|
+
const ProviderUploadFeedbackInput = require_Schema$1.Struct({
|
|
20942
|
+
threadId: ThreadId,
|
|
20943
|
+
reason: require_Schema$1.optional(TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(4e3)))
|
|
20944
|
+
});
|
|
20945
|
+
const ProviderUploadFeedbackResult = require_Schema$1.Struct({ feedbackId: TrimmedNonEmptyString });
|
|
20946
|
+
var ProviderUploadFeedbackError = class extends require_Schema$1.TaggedErrorClass()("ProviderUploadFeedbackError", {
|
|
20947
|
+
threadId: ThreadId,
|
|
20948
|
+
reason: require_Schema$1.optional(TrimmedNonEmptyString.check(require_Schema$1.isMaxLength(500))),
|
|
20949
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
20950
|
+
}) {
|
|
20951
|
+
get message() {
|
|
20952
|
+
return this.reason ?? "Feedback could not be sent. Please try again.";
|
|
20953
|
+
}
|
|
20954
|
+
};
|
|
20785
20955
|
const ProviderEventKind = require_Schema$1.Literals([
|
|
20786
20956
|
"session",
|
|
20787
20957
|
"notification",
|
|
@@ -21261,6 +21431,46 @@ const TaskStartedPayload = require_Schema$1.Struct({
|
|
|
21261
21431
|
description: require_Schema$1.optional(TrimmedNonEmptyStringSchema$2),
|
|
21262
21432
|
taskType: require_Schema$1.optional(TrimmedNonEmptyStringSchema$2)
|
|
21263
21433
|
});
|
|
21434
|
+
/**
|
|
21435
|
+
* Watch-loop task types: Monitor-tool tasks plus background shells. A shell
|
|
21436
|
+
* that outlives its turn is in practice a watch loop, not delegated work.
|
|
21437
|
+
*/
|
|
21438
|
+
const MONITOR_TASK_TYPES = new Set([
|
|
21439
|
+
"monitor",
|
|
21440
|
+
"monitor_mcp",
|
|
21441
|
+
"local_bash",
|
|
21442
|
+
"shell"
|
|
21443
|
+
]);
|
|
21444
|
+
/** Task types that are neither agents nor watch loops (plan-mode bookkeeping). */
|
|
21445
|
+
const INERT_TASK_TYPES = new Set(["plan", "dream"]);
|
|
21446
|
+
/**
|
|
21447
|
+
* Agent-vs-background classification for a task row.
|
|
21448
|
+
*
|
|
21449
|
+
* The mobile client reads `agentKind` off the persisted activity payload and
|
|
21450
|
+
* treats a row without it as background, so a server that never stamps the
|
|
21451
|
+
* field leaves the Agents roster permanently empty however much subagent work
|
|
21452
|
+
* is running (FOR-36646). This is the stamp.
|
|
21453
|
+
*
|
|
21454
|
+
* A denylist, not an allowlist, and deliberately so: the agent-flavoured type
|
|
21455
|
+
* names drift with the SDKs (subagent, local_agent, local_workflow, and
|
|
21456
|
+
* whatever lands next), and an allowlist drops real subagents the day a new
|
|
21457
|
+
* name appears. Only the watch loops and the plan-mode bookkeeping types are
|
|
21458
|
+
* named here; everything else is delegated work.
|
|
21459
|
+
*
|
|
21460
|
+
* A task launched from inside a subagent (`agentId` set) is agent-internal
|
|
21461
|
+
* background work unless it is itself agent-flavoured, because a nested agent
|
|
21462
|
+
* can outlive its parent and has to stay in the roster.
|
|
21463
|
+
*
|
|
21464
|
+
* This mirrors `classifyTaskAgentKind` in the contracts the phone speaks. The
|
|
21465
|
+
* two packages do not import each other, so the sets are duplicated on
|
|
21466
|
+
* purpose; they have to agree, and the tests on both sides pin the same cases.
|
|
21467
|
+
*/
|
|
21468
|
+
function classifyTaskAgentKind(input) {
|
|
21469
|
+
const { taskType, agentId } = input;
|
|
21470
|
+
const nonAgentType = taskType !== void 0 && (MONITOR_TASK_TYPES.has(taskType) || INERT_TASK_TYPES.has(taskType));
|
|
21471
|
+
if (agentId !== void 0 && agentId.trim().length > 0) return taskType === void 0 || nonAgentType ? "background" : "agent";
|
|
21472
|
+
return nonAgentType ? "background" : "agent";
|
|
21473
|
+
}
|
|
21264
21474
|
const TaskProgressPayload = require_Schema$1.Struct({
|
|
21265
21475
|
taskId: RuntimeTaskId,
|
|
21266
21476
|
description: TrimmedNonEmptyStringSchema$2,
|
|
@@ -25989,6 +26199,8 @@ const WS_METHODS = {
|
|
|
25989
26199
|
projectsListEntries: "projects.listEntries",
|
|
25990
26200
|
serverDiscoverSourceControl: "server.discoverSourceControl",
|
|
25991
26201
|
serverReportClientActivity: "server.reportClientActivity",
|
|
26202
|
+
sourceControlLookupRepository: "sourceControl.lookupRepository",
|
|
26203
|
+
sourceControlCloneRepository: "sourceControl.cloneRepository",
|
|
25992
26204
|
projectsWriteFile: "projects.writeFile",
|
|
25993
26205
|
projectCheckoutDeletionPreview: "projectCheckoutDeletion.preview",
|
|
25994
26206
|
projectCheckoutDeletionExecute: "projectCheckoutDeletion.execute",
|
|
@@ -26092,7 +26304,9 @@ const WS_METHODS = {
|
|
|
26092
26304
|
subscribeServerConfig: "subscribeServerConfig",
|
|
26093
26305
|
subscribeServerLifecycle: "subscribeServerLifecycle",
|
|
26094
26306
|
subscribeAuthAccess: "subscribeAuthAccess",
|
|
26095
|
-
subscribeWorkspaceReadiness: "subscribeWorkspaceReadiness"
|
|
26307
|
+
subscribeWorkspaceReadiness: "subscribeWorkspaceReadiness",
|
|
26308
|
+
assetsCreateUrl: "assets.createUrl",
|
|
26309
|
+
providerUploadFeedback: "provider.uploadFeedback"
|
|
26096
26310
|
};
|
|
26097
26311
|
const WsServerUpsertKeybindingRpc = make$30(WS_METHODS.serverUpsertKeybinding, {
|
|
26098
26312
|
payload: ServerUpsertKeybindingInput,
|
|
@@ -26621,6 +26835,29 @@ const WsOrchestrationGetArchivedShellSnapshotRpc = make$30(ORCHESTRATION_WS_METH
|
|
|
26621
26835
|
success: OrchestrationRpcSchemas.getArchivedShellSnapshot.output,
|
|
26622
26836
|
error: OrchestrationGetSnapshotError
|
|
26623
26837
|
});
|
|
26838
|
+
const WsOrchestrationSearchThreadsRpc = make$30(ORCHESTRATION_WS_METHODS.searchThreads, {
|
|
26839
|
+
payload: OrchestrationSearchThreadsInput,
|
|
26840
|
+
success: OrchestrationRpcSchemas.searchThreads.output,
|
|
26841
|
+
error: OrchestrationSearchThreadsError
|
|
26842
|
+
});
|
|
26843
|
+
/**
|
|
26844
|
+
* Resolve `owner/repo` to the repository the caller is allowed to clone.
|
|
26845
|
+
*
|
|
26846
|
+
* Both of these run against the same CMMD-brokered GitHub credential the web
|
|
26847
|
+
* add-project flow uses, so a lookup that answers is a lookup whose clone can
|
|
26848
|
+
* also be authorized, and a repository the caller cannot reach is a failure
|
|
26849
|
+
* here rather than a clone that dies half way.
|
|
26850
|
+
*/
|
|
26851
|
+
const WsSourceControlLookupRepositoryRpc = make$30(WS_METHODS.sourceControlLookupRepository, {
|
|
26852
|
+
payload: SourceControlRepositoryLookupInput,
|
|
26853
|
+
success: SourceControlRepositoryInfo,
|
|
26854
|
+
error: SourceControlRepositoryError
|
|
26855
|
+
});
|
|
26856
|
+
const WsSourceControlCloneRepositoryRpc = make$30(WS_METHODS.sourceControlCloneRepository, {
|
|
26857
|
+
payload: SourceControlCloneRepositoryInput,
|
|
26858
|
+
success: SourceControlCloneRepositoryResult,
|
|
26859
|
+
error: SourceControlRepositoryError
|
|
26860
|
+
});
|
|
26624
26861
|
const WsOrchestrationSubscribeShellRpc = make$30(ORCHESTRATION_WS_METHODS.subscribeShell, {
|
|
26625
26862
|
payload: OrchestrationRpcSchemas.subscribeShell.input,
|
|
26626
26863
|
success: OrchestrationRpcSchemas.subscribeShell.output,
|
|
@@ -26701,7 +26938,15 @@ const WsRpcGroup = make$29(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
|
|
|
26701
26938
|
payload: UsageSummaryInput,
|
|
26702
26939
|
success: UsageSummary,
|
|
26703
26940
|
error: UsageReadError
|
|
26704
|
-
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsCodeGraphSummaryRpc, WsCodeGraphNeighborhoodRpc, WsCodeGraphSearchRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc
|
|
26941
|
+
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsCodeGraphSummaryRpc, WsCodeGraphNeighborhoodRpc, WsCodeGraphSearchRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$30(WS_METHODS.assetsCreateUrl, {
|
|
26942
|
+
payload: AssetsRpcSchemas.createUrl.input,
|
|
26943
|
+
success: AssetsRpcSchemas.createUrl.output,
|
|
26944
|
+
error: AssetAccessError
|
|
26945
|
+
}), make$30(WS_METHODS.providerUploadFeedback, {
|
|
26946
|
+
payload: ProviderUploadFeedbackInput,
|
|
26947
|
+
success: ProviderUploadFeedbackResult,
|
|
26948
|
+
error: ProviderUploadFeedbackError
|
|
26949
|
+
}), WsOrchestrationSearchThreadsRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc);
|
|
26705
26950
|
const RelayWsRpcGroup = WsRpcGroup.middleware(RelayAuthorizationMiddleware).middleware(RelayDeliveryMiddleware);
|
|
26706
26951
|
/**
|
|
26707
26952
|
* The direct-WebSocket mount of the same group, with per-method scope
|
|
@@ -31841,6 +32086,31 @@ const RELAY_METHOD_POLICY_BASES = [
|
|
|
31841
32086
|
method: ORCHESTRATION_WS_METHODS.subscribeThread,
|
|
31842
32087
|
requiredScope: "thread.read",
|
|
31843
32088
|
deliveryClass: "subscription"
|
|
32089
|
+
},
|
|
32090
|
+
{
|
|
32091
|
+
method: WS_METHODS.assetsCreateUrl,
|
|
32092
|
+
requiredScope: "thread.read",
|
|
32093
|
+
deliveryClass: "read"
|
|
32094
|
+
},
|
|
32095
|
+
{
|
|
32096
|
+
method: ORCHESTRATION_WS_METHODS.searchThreads,
|
|
32097
|
+
requiredScope: "thread.read",
|
|
32098
|
+
deliveryClass: "read"
|
|
32099
|
+
},
|
|
32100
|
+
{
|
|
32101
|
+
method: WS_METHODS.providerUploadFeedback,
|
|
32102
|
+
requiredScope: "thread.read",
|
|
32103
|
+
deliveryClass: "create-command"
|
|
32104
|
+
},
|
|
32105
|
+
{
|
|
32106
|
+
method: WS_METHODS.sourceControlLookupRepository,
|
|
32107
|
+
requiredScope: "git.read",
|
|
32108
|
+
deliveryClass: "read"
|
|
32109
|
+
},
|
|
32110
|
+
{
|
|
32111
|
+
method: WS_METHODS.sourceControlCloneRepository,
|
|
32112
|
+
requiredScope: "project.write",
|
|
32113
|
+
deliveryClass: "idempotent-command"
|
|
31844
32114
|
}
|
|
31845
32115
|
];
|
|
31846
32116
|
const FRAME_JOURNALED_METHODS = new Set([
|
|
@@ -34008,7 +34278,7 @@ function isRejectedCredential(cause) {
|
|
|
34008
34278
|
//#region src/connect/hiddenInput.ts
|
|
34009
34279
|
/** Ctrl-C arrives as this byte on a raw TTY rather than as a signal. */
|
|
34010
34280
|
const END_OF_TEXT = "";
|
|
34011
|
-
function firstLine(value) {
|
|
34281
|
+
function firstLine$1(value) {
|
|
34012
34282
|
const index = value.indexOf("\n");
|
|
34013
34283
|
const line = index === -1 ? value : value.slice(0, index);
|
|
34014
34284
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
@@ -34033,7 +34303,7 @@ function readHiddenLine(input) {
|
|
|
34033
34303
|
settled = true;
|
|
34034
34304
|
detach();
|
|
34035
34305
|
input.output.write("\n");
|
|
34036
|
-
resolve(firstLine(value));
|
|
34306
|
+
resolve(firstLine$1(value));
|
|
34037
34307
|
};
|
|
34038
34308
|
const onData = (chunk) => {
|
|
34039
34309
|
buffer += chunk.toString();
|
|
@@ -35465,7 +35735,7 @@ function stringify(value, fallback = "[unserializable]") {
|
|
|
35465
35735
|
return typeof fallback === "function" ? fallback(value) : fallback;
|
|
35466
35736
|
}
|
|
35467
35737
|
}
|
|
35468
|
-
function truncate$
|
|
35738
|
+
function truncate$2(str, max = 0) {
|
|
35469
35739
|
if (typeof str !== "string" || max === 0) return str;
|
|
35470
35740
|
return str.length <= max ? str : `${str.slice(0, max)}...`;
|
|
35471
35741
|
}
|
|
@@ -36072,7 +36342,7 @@ var Scope = class Scope {
|
|
|
36072
36342
|
const mergedBreadcrumb = {
|
|
36073
36343
|
timestamp: dateTimestampInSeconds(),
|
|
36074
36344
|
...breadcrumb,
|
|
36075
|
-
message: breadcrumb.message ? truncate$
|
|
36345
|
+
message: breadcrumb.message ? truncate$2(breadcrumb.message, 2048) : breadcrumb.message
|
|
36076
36346
|
};
|
|
36077
36347
|
this._breadcrumbs.push(mergedBreadcrumb);
|
|
36078
36348
|
if (this._breadcrumbs.length > maxCrumbs) {
|
|
@@ -38730,9 +39000,9 @@ function applyClientOptions(event, options) {
|
|
|
38730
39000
|
if (!event.release && release) event.release = release;
|
|
38731
39001
|
if (!event.dist && dist) event.dist = dist;
|
|
38732
39002
|
const request = event.request;
|
|
38733
|
-
if (request?.url && maxValueLength) request.url = truncate$
|
|
39003
|
+
if (request?.url && maxValueLength) request.url = truncate$2(request.url, maxValueLength);
|
|
38734
39004
|
if (maxValueLength) event.exception?.values?.forEach((exception) => {
|
|
38735
|
-
if (exception.value) exception.value = truncate$
|
|
39005
|
+
if (exception.value) exception.value = truncate$2(exception.value, maxValueLength);
|
|
38736
39006
|
});
|
|
38737
39007
|
}
|
|
38738
39008
|
function applyDebugIds(event, stackParser) {
|
|
@@ -62349,7 +62619,7 @@ const cacheResponseHook = (span, redisCommand, cmdArgs, response) => {
|
|
|
62349
62619
|
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey
|
|
62350
62620
|
});
|
|
62351
62621
|
const spanDescription = safeKey.join(", ");
|
|
62352
|
-
span.updateName(_redisOptions.maxCacheKeyLength ? truncate$
|
|
62622
|
+
span.updateName(_redisOptions.maxCacheKeyLength ? truncate$2(spanDescription, _redisOptions.maxCacheKeyLength) : spanDescription);
|
|
62353
62623
|
};
|
|
62354
62624
|
//#endregion
|
|
62355
62625
|
//#region ../../node_modules/.bun/@sentry+node@10.70.0+cfd4f484ffd29da6/node_modules/@sentry/node/build/esm/integrations/tracing/redis/vendored/semconv.js
|
|
@@ -65864,7 +66134,7 @@ function normalizeNumberish(value) {
|
|
|
65864
66134
|
}
|
|
65865
66135
|
//#endregion
|
|
65866
66136
|
//#region package.json
|
|
65867
|
-
var version$1 = "0.13.
|
|
66137
|
+
var version$1 = "0.13.4";
|
|
65868
66138
|
//#endregion
|
|
65869
66139
|
//#region src/sentry.ts
|
|
65870
66140
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -69400,6 +69670,103 @@ var BrowserTraceCollector = class extends require_Schema$1.Service()("forge/obse
|
|
|
69400
69670
|
*/
|
|
69401
69671
|
var ProjectFaviconResolver = class extends require_Schema$1.Service()("forge/project/Services/ProjectFaviconResolver") {};
|
|
69402
69672
|
//#endregion
|
|
69673
|
+
//#region src/auth/Services/ServerSecretStore.ts
|
|
69674
|
+
var SecretStoreError = class extends require_Schema$1.TaggedError("SecretStoreError") {};
|
|
69675
|
+
var ServerSecretStore = class extends require_Schema$1.Service()("forge/auth/Services/ServerSecretStore") {};
|
|
69676
|
+
//#endregion
|
|
69677
|
+
//#region src/auth/assetUrlToken.ts
|
|
69678
|
+
/**
|
|
69679
|
+
* A credential an image tag can carry.
|
|
69680
|
+
*
|
|
69681
|
+
* `/attachments/*` and `GET /api/project-favicon` both require an authenticated
|
|
69682
|
+
* request, and the clients that render those bytes hand a bare URL to an image
|
|
69683
|
+
* loader that sends no cookie and no Authorization header. Web gets away with
|
|
69684
|
+
* it because the browser attaches the session cookie itself. The phone holds a
|
|
69685
|
+
* bearer token it has no way to attach, so every asset it fetched would 401.
|
|
69686
|
+
*
|
|
69687
|
+
* So the URL carries its own proof. The token is signed with the same server
|
|
69688
|
+
* signing key as the session and WebSocket credentials, and it is deliberately
|
|
69689
|
+
* weaker than either of those: it names one resource, and presenting it grants
|
|
69690
|
+
* that resource and nothing else. A leaked favicon URL is a leaked favicon.
|
|
69691
|
+
*/
|
|
69692
|
+
const ASSET_TOKEN_KIND = "asset";
|
|
69693
|
+
const ASSET_URL_TOKEN_QUERY_PARAM = "assetToken";
|
|
69694
|
+
/**
|
|
69695
|
+
* An hour, against a client that re-asks every thirty minutes.
|
|
69696
|
+
*
|
|
69697
|
+
* The vendored client refreshes asset URLs on a thirty-minute interval, so a
|
|
69698
|
+
* shorter life would expire URLs the client still believes are good, and the
|
|
69699
|
+
* failure would show up as an image that worked and then stopped.
|
|
69700
|
+
*/
|
|
69701
|
+
const DEFAULT_ASSET_URL_TOKEN_TTL = require_Schema$1.hours(1);
|
|
69702
|
+
const SIGNING_SECRET_NAME$1 = "server-signing-key";
|
|
69703
|
+
const AssetTokenClaims = require_Schema$1.Struct({
|
|
69704
|
+
v: require_Schema$1.Literal(1),
|
|
69705
|
+
kind: require_Schema$1.Literal(ASSET_TOKEN_KIND),
|
|
69706
|
+
sid: require_Schema$1.String,
|
|
69707
|
+
res: require_Schema$1.String,
|
|
69708
|
+
iat: require_Schema$1.Number,
|
|
69709
|
+
exp: require_Schema$1.Number
|
|
69710
|
+
});
|
|
69711
|
+
const decodeAssetTokenClaims = require_Schema$1.decodeUnknownEffect(require_Schema$1.fromJsonString(AssetTokenClaims));
|
|
69712
|
+
/**
|
|
69713
|
+
* The canonical name of one addressable asset.
|
|
69714
|
+
*
|
|
69715
|
+
* Both the mint and the check compute this from their own side: the RPC from
|
|
69716
|
+
* the resource it was asked about, the route from the request it received. They
|
|
69717
|
+
* have to agree exactly, which is the point. A token minted for one attachment
|
|
69718
|
+
* cannot be replayed against another, or against a favicon.
|
|
69719
|
+
*
|
|
69720
|
+
* The key is built from the address, not the logical id, because the address is
|
|
69721
|
+
* what both sides actually hold. The attachments route reads its id straight
|
|
69722
|
+
* out of the URL path without percent-decoding, so keying on the decoded id
|
|
69723
|
+
* would let the two sides disagree about an id that encodes to something else.
|
|
69724
|
+
*/
|
|
69725
|
+
function assetResourceKey(resource) {
|
|
69726
|
+
return resource.kind === "attachment" ? `attachment:${resource.urlPathSegment}` : `project-favicon:${resource.cwd}`;
|
|
69727
|
+
}
|
|
69728
|
+
const resolveSigningSecret = require_Schema$1.gen(function* () {
|
|
69729
|
+
const serverConfig = yield* ServerConfig;
|
|
69730
|
+
const secretStore = yield* ServerSecretStore;
|
|
69731
|
+
return resolveConfiguredSigningKey(serverConfig.sessionSigningKey) ?? (yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32));
|
|
69732
|
+
});
|
|
69733
|
+
const issueAssetUrlToken = (input) => require_Schema$1.gen(function* () {
|
|
69734
|
+
const secret = yield* resolveSigningSecret;
|
|
69735
|
+
const issuedAt = yield* require_Schema$1.now;
|
|
69736
|
+
const expiresAt = require_Schema$1.add(issuedAt, { milliseconds: require_Schema$1.toMillis(input.ttl ?? DEFAULT_ASSET_URL_TOKEN_TTL) });
|
|
69737
|
+
const claims = {
|
|
69738
|
+
v: 1,
|
|
69739
|
+
kind: ASSET_TOKEN_KIND,
|
|
69740
|
+
sid: input.sessionId,
|
|
69741
|
+
res: input.resourceKey,
|
|
69742
|
+
iat: issuedAt.epochMilliseconds,
|
|
69743
|
+
exp: expiresAt.epochMilliseconds
|
|
69744
|
+
};
|
|
69745
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(claims));
|
|
69746
|
+
return {
|
|
69747
|
+
token: `${encodedPayload}.${signPayload(encodedPayload, secret)}`,
|
|
69748
|
+
expiresAt: expiresAt.epochMilliseconds
|
|
69749
|
+
};
|
|
69750
|
+
});
|
|
69751
|
+
/**
|
|
69752
|
+
* Whether this token opens this resource, right now.
|
|
69753
|
+
*
|
|
69754
|
+
* Returns a boolean rather than failing, because every negative answer means
|
|
69755
|
+
* the same thing to the caller: fall through to ordinary request
|
|
69756
|
+
* authentication. A malformed token from a stale client should behave like no
|
|
69757
|
+
* token at all, not like a different error.
|
|
69758
|
+
*/
|
|
69759
|
+
const verifyAssetUrlToken = (input) => require_Schema$1.gen(function* () {
|
|
69760
|
+
const [encodedPayload, signature] = input.token.split(".");
|
|
69761
|
+
if (!encodedPayload || !signature) return false;
|
|
69762
|
+
if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, yield* resolveSigningSecret))) return false;
|
|
69763
|
+
const claims = yield* decodeAssetTokenClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
69764
|
+
if (!claims) return false;
|
|
69765
|
+
if (claims.res !== input.resourceKey) return false;
|
|
69766
|
+
const now = yield* require_Schema$1.currentTimeMillis;
|
|
69767
|
+
return claims.exp > now;
|
|
69768
|
+
}).pipe(require_Schema$1.orElseSucceed(() => false));
|
|
69769
|
+
//#endregion
|
|
69403
69770
|
//#region src/environment/Services/ServerEnvironment.ts
|
|
69404
69771
|
var ServerEnvironment = class extends require_Schema$1.Service()("forge/environment/Services/ServerEnvironment") {};
|
|
69405
69772
|
//#endregion
|
|
@@ -69674,6 +70041,28 @@ const requireAuthenticatedRequest$1 = require_Schema$1.gen(function* () {
|
|
|
69674
70041
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
69675
70042
|
yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
69676
70043
|
});
|
|
70044
|
+
/**
|
|
70045
|
+
* Authenticate a request for asset bytes.
|
|
70046
|
+
*
|
|
70047
|
+
* Ordinary request authentication first, so the web app's cookie keeps working
|
|
70048
|
+
* exactly as it did and nothing about these routes changes for it. An asset
|
|
70049
|
+
* token is the fallback, for the clients that render these bytes through an
|
|
70050
|
+
* image loader with no way to attach a credential. See `auth/assetUrlToken.ts`
|
|
70051
|
+
* for why the token is scoped to one resource.
|
|
70052
|
+
*/
|
|
70053
|
+
const requireAssetAccess = (resourceKey) => require_Schema$1.gen(function* () {
|
|
70054
|
+
const request = yield* require_HttpServer.HttpServerRequest;
|
|
70055
|
+
const serverAuth = yield* ServerAuth;
|
|
70056
|
+
const url = require_HttpServer.toURL(request);
|
|
70057
|
+
const token = require_Schema$1.isSome(url) ? url.value.searchParams.get(ASSET_URL_TOKEN_QUERY_PARAM) : null;
|
|
70058
|
+
if (token && token.trim().length > 0) {
|
|
70059
|
+
if (yield* verifyAssetUrlToken({
|
|
70060
|
+
token,
|
|
70061
|
+
resourceKey
|
|
70062
|
+
})) return;
|
|
70063
|
+
}
|
|
70064
|
+
yield* serverAuth.authenticateHttpRequest(request);
|
|
70065
|
+
});
|
|
69677
70066
|
const serverEnvironmentRouteLayer = add$1("GET", "/.well-known/forge/environment", require_Schema$1.gen(function* () {
|
|
69678
70067
|
const descriptor = yield* require_Schema$1.service(ServerEnvironment).pipe(require_Schema$1.flatMap((serverEnvironment) => serverEnvironment.getDescriptor));
|
|
69679
70068
|
return require_HttpServer.jsonUnsafe(descriptor, { status: 200 });
|
|
@@ -69743,13 +70132,16 @@ const otlpTracesProxyRouteLayer = add$1("POST", OTLP_TRACES_PROXY_PATH, require_
|
|
|
69743
70132
|
captureServerError(cause, { route: "/api/observability/v1/traces" });
|
|
69744
70133
|
}).pipe(require_Schema$1.andThen(() => require_Schema$1.logError("otlp-traces unhandled cause", { cause }))))));
|
|
69745
70134
|
const attachmentsRouteLayer = add$1("GET", `${ATTACHMENTS_ROUTE_PREFIX}/*`, require_Schema$1.gen(function* () {
|
|
69746
|
-
yield* requireAuthenticatedRequest$1;
|
|
69747
70135
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
69748
70136
|
const url = require_HttpServer.toURL(request);
|
|
69749
70137
|
if (require_Schema$1.isNone(url)) return require_HttpServer.text("Bad Request", { status: 400 });
|
|
69750
70138
|
const config = yield* ServerConfig;
|
|
69751
70139
|
const normalizedRelativePath = normalizeAttachmentRelativePath(url.value.pathname.slice(12));
|
|
69752
70140
|
if (!normalizedRelativePath) return require_HttpServer.text("Invalid attachment path", { status: 400 });
|
|
70141
|
+
yield* requireAssetAccess(assetResourceKey({
|
|
70142
|
+
kind: "attachment",
|
|
70143
|
+
urlPathSegment: normalizedRelativePath
|
|
70144
|
+
}));
|
|
69753
70145
|
const isIdLookup = !normalizedRelativePath.includes("/") && !normalizedRelativePath.includes(".");
|
|
69754
70146
|
const filePath = isIdLookup ? resolveAttachmentPathById({
|
|
69755
70147
|
attachmentsDir: config.attachmentsDir,
|
|
@@ -69768,11 +70160,14 @@ const attachmentsRouteLayer = add$1("GET", `${ATTACHMENTS_ROUTE_PREFIX}/*`, requ
|
|
|
69768
70160
|
}).pipe(require_Schema$1.catchTag("AuthError", respondToAuthError)));
|
|
69769
70161
|
const projectFaviconRouteLayer = add$1("GET", "/api/project-favicon", require_Schema$1.gen(function* () {
|
|
69770
70162
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
69771
|
-
yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
69772
70163
|
const url = require_HttpServer.toURL(request);
|
|
69773
70164
|
if (require_Schema$1.isNone(url)) return require_HttpServer.text("Bad Request", { status: 400 });
|
|
69774
70165
|
const projectCwd = url.value.searchParams.get("cwd");
|
|
69775
70166
|
if (!projectCwd) return require_HttpServer.text("Missing cwd parameter", { status: 400 });
|
|
70167
|
+
yield* requireAssetAccess(assetResourceKey({
|
|
70168
|
+
kind: "project-favicon",
|
|
70169
|
+
cwd: projectCwd
|
|
70170
|
+
}));
|
|
69776
70171
|
const faviconFilePath = yield* (yield* ProjectFaviconResolver).resolvePath(projectCwd);
|
|
69777
70172
|
if (!faviconFilePath) return require_HttpServer.empty({
|
|
69778
70173
|
status: 204,
|
|
@@ -77504,6 +77899,195 @@ require_Schema$1.succeed$1(TraceDiagnostics, TraceDiagnostics.of({ read: (option
|
|
|
77504
77899
|
}
|
|
77505
77900
|
})) }));
|
|
77506
77901
|
//#endregion
|
|
77902
|
+
//#region src/assets/createAssetUrl.ts
|
|
77903
|
+
const PROJECT_FAVICON_ROUTE = "/api/project-favicon";
|
|
77904
|
+
/**
|
|
77905
|
+
* Turn a resource into an address a client can fetch.
|
|
77906
|
+
*
|
|
77907
|
+
* Nothing new is served here. `/attachments/*` and `GET /api/project-favicon`
|
|
77908
|
+
* already return these bytes; what did not exist was the step in between, and
|
|
77909
|
+
* without it a client holding an attachment id or a workspace root has no way
|
|
77910
|
+
* to name the URL. The vendored mobile client asks this for every project on
|
|
77911
|
+
* its thread list, so the gap was not an edge case, it was the most visited
|
|
77912
|
+
* screen.
|
|
77913
|
+
*
|
|
77914
|
+
* The URL is relative so it resolves against whichever base the client is
|
|
77915
|
+
* already talking to, and it carries a token because the clients that render
|
|
77916
|
+
* these bytes hand the URL to an image loader that attaches no credential of
|
|
77917
|
+
* its own. See `auth/assetUrlToken.ts`.
|
|
77918
|
+
*/
|
|
77919
|
+
const createAssetUrl = (sessionId, resource) => require_Schema$1.gen(function* () {
|
|
77920
|
+
if (resource._tag === "workspace-file") return yield* new AssetWorkspaceResolutionError({
|
|
77921
|
+
resource,
|
|
77922
|
+
cause: "Workspace files are read over projects.readFile, not by URL."
|
|
77923
|
+
});
|
|
77924
|
+
const address = resolveAddress(resource);
|
|
77925
|
+
const issued = yield* issueAssetUrlToken({
|
|
77926
|
+
sessionId,
|
|
77927
|
+
resourceKey: address.resourceKey
|
|
77928
|
+
}).pipe(require_Schema$1.mapError((cause) => new AssetSigningKeyLoadError({
|
|
77929
|
+
resource,
|
|
77930
|
+
cause
|
|
77931
|
+
})));
|
|
77932
|
+
return {
|
|
77933
|
+
relativeUrl: `${address.path}?${address.query}assetToken=${encodeURIComponent(issued.token)}`,
|
|
77934
|
+
expiresAt: issued.expiresAt,
|
|
77935
|
+
...resource._tag === "project-favicon" && resource.path ? { sourcePath: resource.path } : {}
|
|
77936
|
+
};
|
|
77937
|
+
});
|
|
77938
|
+
function resolveAddress(resource) {
|
|
77939
|
+
if (resource._tag === "attachment") {
|
|
77940
|
+
const urlPathSegment = encodeURIComponent(resource.attachmentId);
|
|
77941
|
+
return {
|
|
77942
|
+
resourceKey: assetResourceKey({
|
|
77943
|
+
kind: "attachment",
|
|
77944
|
+
urlPathSegment
|
|
77945
|
+
}),
|
|
77946
|
+
path: `${ATTACHMENTS_ROUTE_PREFIX}/${urlPathSegment}`,
|
|
77947
|
+
query: ""
|
|
77948
|
+
};
|
|
77949
|
+
}
|
|
77950
|
+
return {
|
|
77951
|
+
resourceKey: assetResourceKey({
|
|
77952
|
+
kind: "project-favicon",
|
|
77953
|
+
cwd: resource.cwd
|
|
77954
|
+
}),
|
|
77955
|
+
path: PROJECT_FAVICON_ROUTE,
|
|
77956
|
+
query: `cwd=${encodeURIComponent(resource.cwd)}&`
|
|
77957
|
+
};
|
|
77958
|
+
}
|
|
77959
|
+
//#endregion
|
|
77960
|
+
//#region src/cmmd/submitFeedback.ts
|
|
77961
|
+
/** The intake the web app's feedback dialog already posts to, reached directly. */
|
|
77962
|
+
const CMMD_FEEDBACK_PATH = "/api/forge/context/feedback";
|
|
77963
|
+
/**
|
|
77964
|
+
* The intake answers with the task it created.
|
|
77965
|
+
*
|
|
77966
|
+
* Web reaches it through the proxy registry, which unwraps `data` before the
|
|
77967
|
+
* caller sees it; this calls the gateway directly and does not, so both shapes
|
|
77968
|
+
* are read here rather than pinning the one that happens to arrive today.
|
|
77969
|
+
*/
|
|
77970
|
+
function readFeedbackId(response) {
|
|
77971
|
+
if (!response || typeof response !== "object") return null;
|
|
77972
|
+
const record = response;
|
|
77973
|
+
const envelope = record["data"];
|
|
77974
|
+
const id = (envelope && typeof envelope === "object" ? envelope : record)["id"];
|
|
77975
|
+
if (typeof id === "number") return String(id);
|
|
77976
|
+
if (typeof id === "string" && id.trim().length > 0) return id.trim();
|
|
77977
|
+
return null;
|
|
77978
|
+
}
|
|
77979
|
+
/**
|
|
77980
|
+
* File feedback against the caller's own CMMD account.
|
|
77981
|
+
*
|
|
77982
|
+
* The session's CMMD JWT is what scopes it. A server with no CMMD connection
|
|
77983
|
+
* has nowhere to send feedback and says so in a sentence, because the caller
|
|
77984
|
+
* draws this text straight into the thread the person is looking at.
|
|
77985
|
+
*/
|
|
77986
|
+
function submitCmmdFeedbackForSession(input) {
|
|
77987
|
+
return require_Schema$1.gen(function* () {
|
|
77988
|
+
const jwt = yield* resolveCmmdJwtForSession({
|
|
77989
|
+
sessionId: input.sessionId,
|
|
77990
|
+
...input.subject === void 0 ? {} : { subject: input.subject },
|
|
77991
|
+
logPrefix: "cmmd-feedback"
|
|
77992
|
+
}).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
77993
|
+
if (!jwt) return {
|
|
77994
|
+
ok: false,
|
|
77995
|
+
reason: "This server is not connected to CMMD, so there is nowhere to send feedback."
|
|
77996
|
+
};
|
|
77997
|
+
const body = {
|
|
77998
|
+
type: "bug",
|
|
77999
|
+
title: input.title,
|
|
78000
|
+
source: "forge",
|
|
78001
|
+
priority: "medium",
|
|
78002
|
+
...input.description ? { description: input.description } : {},
|
|
78003
|
+
...input.senderName ? { senderName: input.senderName } : {}
|
|
78004
|
+
};
|
|
78005
|
+
const response = yield* require_Schema$1.tryPromise(() => cmmdGateway.request({
|
|
78006
|
+
method: "POST",
|
|
78007
|
+
jwt,
|
|
78008
|
+
path: CMMD_FEEDBACK_PATH,
|
|
78009
|
+
body
|
|
78010
|
+
})).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
78011
|
+
if (response === null) return {
|
|
78012
|
+
ok: false,
|
|
78013
|
+
reason: "The feedback service did not accept the report. Please try again."
|
|
78014
|
+
};
|
|
78015
|
+
const feedbackId = readFeedbackId(response);
|
|
78016
|
+
if (feedbackId === null) return {
|
|
78017
|
+
ok: false,
|
|
78018
|
+
reason: "The feedback service answered without recording a report."
|
|
78019
|
+
};
|
|
78020
|
+
return {
|
|
78021
|
+
ok: true,
|
|
78022
|
+
feedbackId
|
|
78023
|
+
};
|
|
78024
|
+
});
|
|
78025
|
+
}
|
|
78026
|
+
//#endregion
|
|
78027
|
+
//#region src/provider/threadFeedback.ts
|
|
78028
|
+
const FEEDBACK_TITLE_MAX_CHARS = 120;
|
|
78029
|
+
function firstLine(text) {
|
|
78030
|
+
return text.split("\n", 1)[0]?.trim() ?? "";
|
|
78031
|
+
}
|
|
78032
|
+
function truncate$1(text, max) {
|
|
78033
|
+
return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}…`;
|
|
78034
|
+
}
|
|
78035
|
+
/**
|
|
78036
|
+
* A title someone triaging the intake can act on.
|
|
78037
|
+
*
|
|
78038
|
+
* What the person typed after `/feedback` first, because it is the only part
|
|
78039
|
+
* they wrote. The thread's title is the fallback, and a bare thread id is the
|
|
78040
|
+
* last resort, since a queue of "Forge feedback" rows tells a triager nothing.
|
|
78041
|
+
*/
|
|
78042
|
+
function feedbackTitle(input) {
|
|
78043
|
+
const reason = input.reason ? firstLine(input.reason) : "";
|
|
78044
|
+
if (reason.length > 0) return truncate$1(reason, FEEDBACK_TITLE_MAX_CHARS);
|
|
78045
|
+
const threadTitle = input.threadTitle?.trim() ?? "";
|
|
78046
|
+
if (threadTitle.length > 0) return truncate$1(`Forge feedback: ${threadTitle}`, FEEDBACK_TITLE_MAX_CHARS);
|
|
78047
|
+
return truncate$1(`Forge feedback: thread ${input.threadId}`, FEEDBACK_TITLE_MAX_CHARS);
|
|
78048
|
+
}
|
|
78049
|
+
/** The body, which always names the thread so the report can be traced back to it. */
|
|
78050
|
+
function feedbackDescription(input) {
|
|
78051
|
+
const lines = [`Filed from Forge thread \`${input.threadId}\`.`];
|
|
78052
|
+
if (input.threadTitle?.trim()) lines.push(`Thread title: ${input.threadTitle.trim()}`);
|
|
78053
|
+
if (input.reason?.trim()) lines.push("", input.reason.trim());
|
|
78054
|
+
return lines.join("\n");
|
|
78055
|
+
}
|
|
78056
|
+
/**
|
|
78057
|
+
* `/feedback`, served.
|
|
78058
|
+
*
|
|
78059
|
+
* The vendored client declared this method and called it; Forge answered
|
|
78060
|
+
* nothing, so the slash command printed the transport's own "unknown request
|
|
78061
|
+
* tag" string into the thread. Routing it to the CMMD intake the web feedback
|
|
78062
|
+
* dialog already uses means one report format and one destination, whichever
|
|
78063
|
+
* surface it came from.
|
|
78064
|
+
*
|
|
78065
|
+
* Every failure carries a sentence rather than a cause, because the client
|
|
78066
|
+
* draws `message` straight into the thread as an assistant reply.
|
|
78067
|
+
*/
|
|
78068
|
+
const uploadThreadFeedback = (orchestrationEngine, sessionId, subject, input) => require_Schema$1.gen(function* () {
|
|
78069
|
+
const threadTitle = (yield* orchestrationEngine.getReadModel().pipe(require_Schema$1.orElseSucceed(() => null)))?.threads.find((thread) => thread.id === input.threadId)?.title;
|
|
78070
|
+
const outcome = yield* submitCmmdFeedbackForSession({
|
|
78071
|
+
sessionId,
|
|
78072
|
+
...subject === void 0 ? {} : { subject },
|
|
78073
|
+
title: feedbackTitle({
|
|
78074
|
+
...input,
|
|
78075
|
+
threadId: input.threadId,
|
|
78076
|
+
...threadTitle ? { threadTitle } : {}
|
|
78077
|
+
}),
|
|
78078
|
+
description: feedbackDescription({
|
|
78079
|
+
...input,
|
|
78080
|
+
threadId: input.threadId,
|
|
78081
|
+
...threadTitle ? { threadTitle } : {}
|
|
78082
|
+
})
|
|
78083
|
+
});
|
|
78084
|
+
if (!outcome.ok) return yield* new ProviderUploadFeedbackError({
|
|
78085
|
+
threadId: input.threadId,
|
|
78086
|
+
reason: outcome.reason
|
|
78087
|
+
});
|
|
78088
|
+
return { feedbackId: outcome.feedbackId };
|
|
78089
|
+
});
|
|
78090
|
+
//#endregion
|
|
77507
78091
|
//#region src/cloudRuntime/listCloudRuntimes.ts
|
|
77508
78092
|
/**
|
|
77509
78093
|
* List the caller's Forge Cloud runtimes, through CMMD, as the caller.
|
|
@@ -87559,95 +88143,278 @@ function resolveGitHubCloneDir(baseDir, repoFullName, targetDir) {
|
|
|
87559
88143
|
const repoName = repoFullName.split("/")[1] ?? repoFullName;
|
|
87560
88144
|
return targetDir ?? (0, node_path.join)(baseDir, "Projects", repoName);
|
|
87561
88145
|
}
|
|
87562
|
-
|
|
87563
|
-
|
|
87564
|
-
|
|
87565
|
-
|
|
87566
|
-
|
|
87567
|
-
|
|
87568
|
-
|
|
87569
|
-
|
|
87570
|
-
|
|
87571
|
-
|
|
87572
|
-
|
|
87573
|
-
|
|
87574
|
-
|
|
87575
|
-
|
|
87576
|
-
|
|
87577
|
-
|
|
87578
|
-
|
|
87579
|
-
|
|
87580
|
-
|
|
87581
|
-
|
|
87582
|
-
|
|
87583
|
-
|
|
88146
|
+
/**
|
|
88147
|
+
* The public clone URLs for a repository.
|
|
88148
|
+
*
|
|
88149
|
+
* Never derived from an authenticated remote: `buildAuthenticatedRemoteUrl`
|
|
88150
|
+
* embeds an installation token in the URL, and a token that reaches a client is
|
|
88151
|
+
* a token that reaches wherever the client writes it down.
|
|
88152
|
+
*/
|
|
88153
|
+
function describeGitHubRepository(nameWithOwner) {
|
|
88154
|
+
return {
|
|
88155
|
+
nameWithOwner,
|
|
88156
|
+
url: `https://github.com/${nameWithOwner}`,
|
|
88157
|
+
sshUrl: `git@github.com:${nameWithOwner}.git`
|
|
88158
|
+
};
|
|
88159
|
+
}
|
|
88160
|
+
/**
|
|
88161
|
+
* Resolve `owner/repo` against the repositories CMMD says this caller may use.
|
|
88162
|
+
*
|
|
88163
|
+
* This asks the same brokered listing the web repository picker is populated
|
|
88164
|
+
* from, rather than github.com directly, so "found" here means the same thing
|
|
88165
|
+
* it means everywhere else in Forge: CMMD will vend this caller a token for it.
|
|
88166
|
+
* A repository that exists but that the caller cannot reach is a 404, which is
|
|
88167
|
+
* the answer they can act on.
|
|
88168
|
+
*/
|
|
88169
|
+
function lookupGitHubRepositoryForSession(input) {
|
|
88170
|
+
return require_Schema$1.gen(function* () {
|
|
88171
|
+
const requested = input.repository.trim().replace(/^\/+|\/+$/g, "");
|
|
88172
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(requested)) return {
|
|
88173
|
+
status: 400,
|
|
88174
|
+
repository: null,
|
|
88175
|
+
error: "Enter a repository as owner/name."
|
|
88176
|
+
};
|
|
88177
|
+
const jwt = input.runtimeGrantAuth ? null : yield* resolveJwt(input.sessionId, input.subject);
|
|
88178
|
+
if (!input.runtimeGrantAuth && !jwt) return {
|
|
88179
|
+
status: 401,
|
|
88180
|
+
repository: null,
|
|
88181
|
+
error: "CMMD not connected"
|
|
88182
|
+
};
|
|
88183
|
+
const auth = input.runtimeGrantAuth ?? {
|
|
87584
88184
|
kind: "cmmd-jwt",
|
|
87585
|
-
jwt
|
|
87586
|
-
}
|
|
87587
|
-
|
|
87588
|
-
|
|
87589
|
-
|
|
87590
|
-
|
|
87591
|
-
|
|
87592
|
-
|
|
87593
|
-
|
|
88185
|
+
jwt
|
|
88186
|
+
};
|
|
88187
|
+
const searchTerm = requested.split("/")[1] ?? requested;
|
|
88188
|
+
let result = yield* require_Schema$1.promise(() => fetchGitHubReposForAuth(auth, {
|
|
88189
|
+
search: searchTerm,
|
|
88190
|
+
organizationId: input.organizationId ?? ""
|
|
88191
|
+
}));
|
|
88192
|
+
if (!input.runtimeGrantAuth && shouldRetryWithRefreshedCmmdJwt(result.status)) {
|
|
88193
|
+
const refreshedJwt = yield* resolveJwt(input.sessionId, input.subject, true);
|
|
88194
|
+
if (refreshedJwt && refreshedJwt !== jwt) result = yield* require_Schema$1.promise(() => fetchGitHubReposForAuth({
|
|
88195
|
+
kind: "cmmd-jwt",
|
|
88196
|
+
jwt: refreshedJwt
|
|
88197
|
+
}, {
|
|
88198
|
+
search: searchTerm,
|
|
88199
|
+
organizationId: input.organizationId ?? ""
|
|
88200
|
+
}));
|
|
88201
|
+
}
|
|
88202
|
+
if (result.status !== 200) {
|
|
88203
|
+
const body = result.body;
|
|
88204
|
+
return {
|
|
88205
|
+
status: result.status,
|
|
88206
|
+
repository: null,
|
|
88207
|
+
error: typeof body.error === "string" ? body.error : "GitHub repositories are unavailable."
|
|
88208
|
+
};
|
|
88209
|
+
}
|
|
88210
|
+
const match = selectGitHubRepositoryMatch(result.body, requested);
|
|
88211
|
+
if (match === null) return {
|
|
88212
|
+
status: 404,
|
|
88213
|
+
repository: null,
|
|
88214
|
+
error: `Repository ${requested} was not found in the GitHub accounts connected to Forge.`
|
|
88215
|
+
};
|
|
88216
|
+
return {
|
|
88217
|
+
status: 200,
|
|
88218
|
+
repository: describeGitHubRepository(match),
|
|
88219
|
+
error: null
|
|
88220
|
+
};
|
|
88221
|
+
});
|
|
88222
|
+
}
|
|
88223
|
+
/**
|
|
88224
|
+
* Find the one repository in a brokered listing whose full name is the one
|
|
88225
|
+
* asked for.
|
|
88226
|
+
*
|
|
88227
|
+
* Exact and case-insensitive. The listing is a *search*, so it comes back with
|
|
88228
|
+
* near misses in it: asking for `owner/forge` and being handed `someone-else/
|
|
88229
|
+
* forge` because it sorted first is how a person ends up cloning a stranger's
|
|
88230
|
+
* repository, and a prefix match would do exactly that.
|
|
88231
|
+
*/
|
|
88232
|
+
function selectGitHubRepositoryMatch(body, requested) {
|
|
88233
|
+
const repos = body?.repos ?? [];
|
|
88234
|
+
const wanted = requested.trim().toLowerCase();
|
|
88235
|
+
for (const repo of repos) if (typeof repo.full_name === "string" && repo.full_name.toLowerCase() === wanted) return repo.full_name;
|
|
88236
|
+
return null;
|
|
88237
|
+
}
|
|
88238
|
+
/**
|
|
88239
|
+
* The repository a clone request is really for, or `null` if there is not one.
|
|
88240
|
+
*
|
|
88241
|
+
* A caller may name `owner/repo`, or paste a clone URL and let the server work
|
|
88242
|
+
* it out. Anything that does not resolve to a GitHub repository returns `null`
|
|
88243
|
+
* and must be refused rather than cloned: Forge clones through a CMMD-brokered
|
|
88244
|
+
* installation token for a named repository, and "just run `git clone` on this
|
|
88245
|
+
* URL instead" would be a second, unbrokered way to write to disk.
|
|
88246
|
+
*/
|
|
88247
|
+
function resolveGitHubCloneTarget(input) {
|
|
88248
|
+
const named = input.repository?.trim();
|
|
88249
|
+
if (named && /^[^/\s]+\/[^/\s]+$/.test(named)) return named;
|
|
88250
|
+
if (named) return null;
|
|
88251
|
+
const remoteUrl = input.remoteUrl?.trim();
|
|
88252
|
+
return remoteUrl ? parseRepoFullNameFromRemoteUrl(remoteUrl) : null;
|
|
88253
|
+
}
|
|
88254
|
+
/**
|
|
88255
|
+
* Clone `repoFullName` for an authenticated session.
|
|
88256
|
+
*
|
|
88257
|
+
* Authorization is the caller's own CMMD connection: the session's JWT (or a
|
|
88258
|
+
* runtime grant, when this VM is running under one) is exchanged with CMMD for
|
|
88259
|
+
* an installation token scoped to that one repository. A session with no CMMD
|
|
88260
|
+
* connection gets 401 and no clone; a repository CMMD will not issue a token
|
|
88261
|
+
* for gets CMMD's own refusal, not a `git clone` attempt.
|
|
88262
|
+
*/
|
|
88263
|
+
function runGitHubCloneForSession(input) {
|
|
88264
|
+
return require_Schema$1.gen(function* () {
|
|
88265
|
+
const { config, repoFullName, targetDir } = input;
|
|
88266
|
+
const token = yield* acquireInstallationTokenForSession(input);
|
|
88267
|
+
if (!token.ok) return token.outcome;
|
|
88268
|
+
const cloneDir = resolveGitHubCloneDir(config.baseDir, repoFullName, targetDir);
|
|
88269
|
+
yield* require_Schema$1.promise(() => (0, node_fs_promises.mkdir)((0, node_path.join)(cloneDir, ".."), { recursive: true }).catch(() => void 0));
|
|
88270
|
+
const target = {
|
|
88271
|
+
cloneDir,
|
|
88272
|
+
repoFullName,
|
|
88273
|
+
token: token.token,
|
|
88274
|
+
config
|
|
88275
|
+
};
|
|
88276
|
+
return (0, node_fs.existsSync)(cloneDir) ? yield* adoptExistingGitHubClone(target) : yield* cloneGitHubRepositoryInto(target);
|
|
88277
|
+
});
|
|
88278
|
+
}
|
|
88279
|
+
/**
|
|
88280
|
+
* The installation token for one repository, on the caller's own authority.
|
|
88281
|
+
*
|
|
88282
|
+
* A runtime grant is used as-is. A session resolves its CMMD JWT and retries
|
|
88283
|
+
* once with a refreshed one when CMMD answers 401 or 403, because the cached
|
|
88284
|
+
* JWT may simply have expired. The failure branch is already the HTTP outcome,
|
|
88285
|
+
* so a caller returns it rather than translating it.
|
|
88286
|
+
*/
|
|
88287
|
+
function acquireInstallationTokenForSession(input) {
|
|
88288
|
+
return require_Schema$1.gen(function* () {
|
|
88289
|
+
const { runtimeGrantAuth, repoFullName } = input;
|
|
88290
|
+
const jwt = runtimeGrantAuth ? null : yield* resolveJwt(input.sessionId, input.subject);
|
|
88291
|
+
if (!runtimeGrantAuth && !jwt) return {
|
|
88292
|
+
ok: false,
|
|
88293
|
+
outcome: {
|
|
88294
|
+
status: 401,
|
|
88295
|
+
body: { error: "CMMD not connected" }
|
|
88296
|
+
}
|
|
88297
|
+
};
|
|
88298
|
+
let token = yield* fetchInstallationTokenForAuth(runtimeGrantAuth ?? {
|
|
88299
|
+
kind: "cmmd-jwt",
|
|
88300
|
+
jwt
|
|
88301
|
+
}, repoFullName);
|
|
88302
|
+
if (!runtimeGrantAuth && !token.ok && shouldRetryWithRefreshedCmmdJwt(token.status)) {
|
|
88303
|
+
const refreshedJwt = yield* resolveJwt(input.sessionId, input.subject, true);
|
|
88304
|
+
if (refreshedJwt && refreshedJwt !== jwt) token = yield* fetchInstallationTokenForAuth({
|
|
88305
|
+
kind: "cmmd-jwt",
|
|
88306
|
+
jwt: refreshedJwt
|
|
88307
|
+
}, repoFullName);
|
|
88308
|
+
}
|
|
88309
|
+
if (!token.ok) return {
|
|
88310
|
+
ok: false,
|
|
88311
|
+
outcome: {
|
|
88312
|
+
status: token.status,
|
|
88313
|
+
body: gitHubTokenFailureBody(token.status, token.error)
|
|
88314
|
+
}
|
|
88315
|
+
};
|
|
88316
|
+
return {
|
|
88317
|
+
ok: true,
|
|
88318
|
+
token: token.token
|
|
88319
|
+
};
|
|
88320
|
+
});
|
|
88321
|
+
}
|
|
88322
|
+
/**
|
|
88323
|
+
* A directory already sits where the clone would go. Adopt it when it is a
|
|
88324
|
+
* checkout of the same repository, so a second "add project" is a no-op with
|
|
88325
|
+
* fresh credentials rather than a failure; refuse otherwise, naming the
|
|
88326
|
+
* folder, because overwriting it would destroy something the person made.
|
|
88327
|
+
*/
|
|
88328
|
+
function adoptExistingGitHubClone(target) {
|
|
88329
|
+
return require_Schema$1.gen(function* () {
|
|
88330
|
+
const { cloneDir, repoFullName } = target;
|
|
88331
|
+
const git = (args) => require_Schema$1.promise(() => runProcess("git", [
|
|
87594
88332
|
"-C",
|
|
87595
88333
|
cloneDir,
|
|
87596
|
-
|
|
87597
|
-
"--is-inside-work-tree"
|
|
88334
|
+
...args
|
|
87598
88335
|
], {
|
|
87599
88336
|
timeoutMs: 1e4,
|
|
87600
88337
|
allowNonZeroExit: true,
|
|
87601
88338
|
env: process.env
|
|
87602
88339
|
}));
|
|
87603
|
-
const
|
|
87604
|
-
|
|
87605
|
-
cloneDir,
|
|
88340
|
+
const workTree = yield* git(["rev-parse", "--is-inside-work-tree"]);
|
|
88341
|
+
const remote = yield* git([
|
|
87606
88342
|
"remote",
|
|
87607
88343
|
"get-url",
|
|
87608
88344
|
"origin"
|
|
87609
|
-
]
|
|
87610
|
-
|
|
87611
|
-
|
|
87612
|
-
|
|
87613
|
-
|
|
87614
|
-
|
|
87615
|
-
|
|
87616
|
-
|
|
87617
|
-
|
|
87618
|
-
|
|
87619
|
-
|
|
87620
|
-
|
|
87621
|
-
return json$6({
|
|
88345
|
+
]);
|
|
88346
|
+
if (!(workTree.code === 0 && workTree.stdout.trim() === "true" && remote.code === 0 && isGitHubRemoteForRepo(remote.stdout.trim(), repoFullName))) return {
|
|
88347
|
+
status: 409,
|
|
88348
|
+
body: {
|
|
88349
|
+
error: "A project folder with that name already exists, but it is not the selected GitHub repository. Rename the import, remove the VM folder, or choose a different repository.",
|
|
88350
|
+
local_path: cloneDir
|
|
88351
|
+
}
|
|
88352
|
+
};
|
|
88353
|
+
yield* applyGitCredentialsFor(target);
|
|
88354
|
+
return {
|
|
88355
|
+
status: 200,
|
|
88356
|
+
body: {
|
|
87622
88357
|
local_path: cloneDir,
|
|
87623
88358
|
existing: true
|
|
87624
|
-
}
|
|
87625
|
-
}
|
|
87626
|
-
|
|
87627
|
-
|
|
87628
|
-
|
|
87629
|
-
|
|
87630
|
-
|
|
87631
|
-
|
|
87632
|
-
|
|
87633
|
-
|
|
87634
|
-
|
|
87635
|
-
|
|
87636
|
-
|
|
87637
|
-
|
|
88359
|
+
}
|
|
88360
|
+
};
|
|
88361
|
+
});
|
|
88362
|
+
}
|
|
88363
|
+
function cloneGitHubRepositoryInto(target) {
|
|
88364
|
+
return require_Schema$1.gen(function* () {
|
|
88365
|
+
const { cloneDir, repoFullName, token } = target;
|
|
88366
|
+
const result = yield* require_Schema$1.promise(() => runProcess("git", buildAuthenticatedGitCloneArgs(repoFullName, cloneDir), {
|
|
88367
|
+
timeoutMs: 12e4,
|
|
88368
|
+
allowNonZeroExit: true,
|
|
88369
|
+
env: {
|
|
88370
|
+
...process.env,
|
|
88371
|
+
CMMD_FORGE_GIT_TOKEN: token,
|
|
88372
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
88373
|
+
}
|
|
88374
|
+
}));
|
|
88375
|
+
if (result.code !== 0) {
|
|
88376
|
+
const stderr = sanitizeGitCredentialText(result.stderr).trim().slice(0, 500);
|
|
88377
|
+
return {
|
|
88378
|
+
status: 500,
|
|
88379
|
+
body: { error: `git clone failed${stderr.length > 0 ? `: ${stderr}` : ""}` }
|
|
88380
|
+
};
|
|
87638
88381
|
}
|
|
87639
|
-
|
|
87640
|
-
|
|
87641
|
-
|
|
87642
|
-
|
|
88382
|
+
yield* applyGitCredentialsFor(target);
|
|
88383
|
+
return {
|
|
88384
|
+
status: 200,
|
|
88385
|
+
body: { local_path: cloneDir }
|
|
88386
|
+
};
|
|
88387
|
+
});
|
|
88388
|
+
}
|
|
88389
|
+
function applyGitCredentialsFor(target) {
|
|
88390
|
+
return applyGitCredentials({
|
|
88391
|
+
cloneDir: target.cloneDir,
|
|
88392
|
+
repoFullName: target.repoFullName,
|
|
88393
|
+
token: target.token,
|
|
88394
|
+
mode: target.config.forgeGitCredentialCache ? "cache" : "embed"
|
|
88395
|
+
});
|
|
88396
|
+
}
|
|
88397
|
+
const githubCloneRouteLayer = add$1("POST", "/.well-known/forge/github/clone", require_Schema$1.gen(function* () {
|
|
88398
|
+
const request = yield* require_HttpServer.HttpServerRequest;
|
|
88399
|
+
const session = yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
88400
|
+
const config = yield* ServerConfig;
|
|
88401
|
+
if (session.method === "proxy-asserted") {
|
|
88402
|
+
const forwardedJwt = normalizeForwardedCmmdJwt(request.headers);
|
|
88403
|
+
if (forwardedJwt !== void 0) yield* setCommandJwtForSession(session.sessionId, forwardedJwt).pipe(require_Schema$1.ignore({ log: true }));
|
|
87643
88404
|
}
|
|
87644
|
-
yield*
|
|
87645
|
-
|
|
88405
|
+
const rawBody = yield* request.json;
|
|
88406
|
+
const parsed = require_Schema$1.decodeUnknownExit(CloneBodySchema)(rawBody);
|
|
88407
|
+
if (parsed._tag === "Failure") return json$6({ error: "repo_full_name is required" }, 400);
|
|
88408
|
+
const { repo_full_name, target_dir } = parsed.value;
|
|
88409
|
+
const outcome = yield* runGitHubCloneForSession({
|
|
88410
|
+
config,
|
|
88411
|
+
sessionId: session.sessionId,
|
|
88412
|
+
subject: session.subject,
|
|
88413
|
+
runtimeGrantAuth: resolveRuntimeGitHubGrantAuth(config, request.headers),
|
|
87646
88414
|
repoFullName: repo_full_name,
|
|
87647
|
-
|
|
87648
|
-
mode: config.forgeGitCredentialCache ? "cache" : "embed"
|
|
88415
|
+
targetDir: target_dir
|
|
87649
88416
|
});
|
|
87650
|
-
return json$6(
|
|
88417
|
+
return json$6(outcome.body, outcome.status);
|
|
87651
88418
|
}).pipe(require_Schema$1.catchTag("AuthError", respondToAuthError)));
|
|
87652
88419
|
const githubCloneJobStartRouteLayer = add$1("POST", "/.well-known/forge/github/clone-jobs", require_Schema$1.gen(function* () {
|
|
87653
88420
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
@@ -96204,33 +96971,34 @@ function serializeNativeToolActivity(activity) {
|
|
|
96204
96971
|
}
|
|
96205
96972
|
};
|
|
96206
96973
|
}
|
|
96207
|
-
|
|
96208
|
-
|
|
96209
|
-
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
96974
|
+
function isReplicatedFailureActivity(activity) {
|
|
96975
|
+
return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
96210
96976
|
}
|
|
96211
96977
|
function failureDetail(activity) {
|
|
96212
96978
|
const payload = activity.payload;
|
|
96213
|
-
if (!payload || typeof payload !== "object"
|
|
96214
|
-
const
|
|
96215
|
-
|
|
96979
|
+
if (!payload || typeof payload !== "object") return null;
|
|
96980
|
+
const record = payload;
|
|
96981
|
+
const detail = typeof record.detail === "string" ? record.detail.trim() : "";
|
|
96982
|
+
if (detail) return detail;
|
|
96983
|
+
return (typeof record.message === "string" ? record.message.trim() : "") || null;
|
|
96216
96984
|
}
|
|
96217
96985
|
/**
|
|
96218
96986
|
* Replicates why a turn failed.
|
|
96219
96987
|
*
|
|
96220
|
-
* Without this a failure never crossed the replication boundary
|
|
96221
|
-
* activities were serialized
|
|
96988
|
+
* Without this a failure never crossed the replication boundary. Only `tool.`
|
|
96989
|
+
* activities were serialized, so durable history could not show one and every
|
|
96222
96990
|
* failure reached the user as an endless "Working" spinner. Measured in
|
|
96223
96991
|
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
96224
96992
|
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
96225
96993
|
* refusal and a dispatch into a deleted directory all looked identical.
|
|
96226
96994
|
*
|
|
96227
|
-
* `error_summary` is used
|
|
96228
|
-
*
|
|
96229
|
-
*
|
|
96230
|
-
*
|
|
96995
|
+
* `error_summary` is used because both the Forge protocol and CMMD's ingest
|
|
96996
|
+
* schema already accept it. Provider startup failures store `detail`, while
|
|
96997
|
+
* active runtime failures store `message`. The event has no separate detail
|
|
96998
|
+
* field, so either value is joined onto the summary.
|
|
96231
96999
|
*/
|
|
96232
|
-
function
|
|
96233
|
-
if (!
|
|
97000
|
+
function serializeFailureActivity(activity) {
|
|
97001
|
+
if (!isReplicatedFailureActivity(activity)) return null;
|
|
96234
97002
|
const summary = text(activity.summary, 4e3);
|
|
96235
97003
|
if (summary === null) return null;
|
|
96236
97004
|
const detail = failureDetail(activity);
|
|
@@ -96572,7 +97340,7 @@ function isTurnUsageTokens(value) {
|
|
|
96572
97340
|
function serializeRuntimeThreadEvents(thread) {
|
|
96573
97341
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
96574
97342
|
const failureActivities = thread.activities.flatMap((activity) => {
|
|
96575
|
-
const event =
|
|
97343
|
+
const event = serializeFailureActivity(activity);
|
|
96576
97344
|
return event === null ? [] : [event];
|
|
96577
97345
|
});
|
|
96578
97346
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
@@ -100840,6 +101608,31 @@ function claudeWriteToken(env = process.env) {
|
|
|
100840
101608
|
return accessor.read(env);
|
|
100841
101609
|
}
|
|
100842
101610
|
//#endregion
|
|
101611
|
+
//#region ../../packages/shared/src/claudeReadinessReason.ts
|
|
101612
|
+
/**
|
|
101613
|
+
* The reason literals Forge acts on, confirmed against CMMD-36441's readiness
|
|
101614
|
+
* service. If the route renames one, the rename lands here as a fall-through
|
|
101615
|
+
* to null, never as the wrong kind.
|
|
101616
|
+
*
|
|
101617
|
+
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
101618
|
+
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
101619
|
+
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
101620
|
+
* `stale` answer that simply has not landed yet. Those want opposite
|
|
101621
|
+
* handling, and both used to get "Reconnect Claude in Settings" because the
|
|
101622
|
+
* guard read only the delivery status.
|
|
101623
|
+
*/
|
|
101624
|
+
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
101625
|
+
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
101626
|
+
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101627
|
+
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101628
|
+
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
101629
|
+
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
101630
|
+
};
|
|
101631
|
+
function claudeReadinessReasonCode(reason) {
|
|
101632
|
+
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
101633
|
+
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
101634
|
+
}
|
|
101635
|
+
//#endregion
|
|
100843
101636
|
//#region src/egress/providerDeliveryReceipt.ts
|
|
100844
101637
|
const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
100845
101638
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -101014,82 +101807,6 @@ function providerDeliveryBindingsEqual(left, right) {
|
|
|
101014
101807
|
return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
|
|
101015
101808
|
}
|
|
101016
101809
|
//#endregion
|
|
101017
|
-
//#region src/provider/claudeReadinessReason.ts
|
|
101018
|
-
/**
|
|
101019
|
-
* The reason literals Forge has copy for, confirmed against CMMD-36441's
|
|
101020
|
-
* readiness service. If the route renames one, the rename lands here as a
|
|
101021
|
-
* fall-through to the generic sentence, never as a wrong sentence.
|
|
101022
|
-
*
|
|
101023
|
-
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
101024
|
-
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
101025
|
-
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
101026
|
-
* `stale` answer that simply has not landed yet. Those want opposite
|
|
101027
|
-
* sentences, and both used to get "Reconnect Claude in Settings" because the
|
|
101028
|
-
* guard read only the delivery status.
|
|
101029
|
-
*/
|
|
101030
|
-
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
101031
|
-
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
101032
|
-
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101033
|
-
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101034
|
-
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
101035
|
-
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
101036
|
-
};
|
|
101037
|
-
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
101038
|
-
/**
|
|
101039
|
-
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
101040
|
-
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
101041
|
-
* time is the only part of that sentence Forge repeats.
|
|
101042
|
-
*/
|
|
101043
|
-
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
101044
|
-
function claudeReadinessReasonCode(reason) {
|
|
101045
|
-
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
101046
|
-
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
101047
|
-
}
|
|
101048
|
-
function expiredAtFromReadinessMessage(message) {
|
|
101049
|
-
if (typeof message !== "string") return null;
|
|
101050
|
-
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
101051
|
-
}
|
|
101052
|
-
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
101053
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
101054
|
-
}
|
|
101055
|
-
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
101056
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
101057
|
-
}
|
|
101058
|
-
/**
|
|
101059
|
-
* The last allowlisted readiness reason this process saw.
|
|
101060
|
-
*
|
|
101061
|
-
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
101062
|
-
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
101063
|
-
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
101064
|
-
* from a blank one, so without this the card can only offer the generic
|
|
101065
|
-
* sentence while the guard already knows better.
|
|
101066
|
-
*/
|
|
101067
|
-
let latestVerdict = null;
|
|
101068
|
-
/**
|
|
101069
|
-
* Remember, or clear, what the readiness answer said.
|
|
101070
|
-
*
|
|
101071
|
-
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
101072
|
-
* the card go back to the generic sentence the moment CMMD applies a
|
|
101073
|
-
* credential: an applied answer carries no reason.
|
|
101074
|
-
*/
|
|
101075
|
-
function recordClaudeReadinessVerdict(readiness) {
|
|
101076
|
-
const code = claudeReadinessReasonCode(readiness.reason);
|
|
101077
|
-
latestVerdict = code === null ? null : {
|
|
101078
|
-
code,
|
|
101079
|
-
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
101080
|
-
};
|
|
101081
|
-
}
|
|
101082
|
-
/**
|
|
101083
|
-
* What the provider status card says about a delivered credential it cannot
|
|
101084
|
-
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
101085
|
-
* guessed time would be worse than the generic sentence.
|
|
101086
|
-
*/
|
|
101087
|
-
function claudeNativeCredentialUnusableMessage() {
|
|
101088
|
-
const verdict = latestVerdict;
|
|
101089
|
-
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
101090
|
-
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
101091
|
-
}
|
|
101092
|
-
//#endregion
|
|
101093
101810
|
//#region src/provider/providerReadiness.ts
|
|
101094
101811
|
const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
|
|
101095
101812
|
const STILL_APPLYING_STATUSES = new Set([
|
|
@@ -101134,10 +101851,18 @@ async function queryProviderReadiness(input) {
|
|
|
101134
101851
|
}
|
|
101135
101852
|
return classifyClaudeSnapshot(snapshot, sprite);
|
|
101136
101853
|
}
|
|
101137
|
-
|
|
101138
|
-
|
|
101854
|
+
/**
|
|
101855
|
+
* Whether CMMD allows a turn to start on this runtime. The delivery status is
|
|
101856
|
+
* not part of it: a refresh that failed over a credential the guest verified
|
|
101857
|
+
* and that has not expired reads as failed with nativeExecutable true, and the
|
|
101858
|
+
* turn is the probe (FOR-3085). The generation fence and the receipt below
|
|
101859
|
+
* still bind the answer.
|
|
101860
|
+
*/
|
|
101861
|
+
function claudeSnapshotAllowsTurn(snapshot) {
|
|
101862
|
+
return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
|
|
101139
101863
|
}
|
|
101140
|
-
|
|
101864
|
+
/** Why a turn may not start, for an answer that does not allow one. */
|
|
101865
|
+
function classifyClaudeTurnRefusal(snapshot) {
|
|
101141
101866
|
const code = claudeReadinessReasonCode(snapshot.reason);
|
|
101142
101867
|
if (code === "RUNTIME_UNAVAILABLE") return {
|
|
101143
101868
|
kind: "not_ready",
|
|
@@ -101165,7 +101890,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
|
|
|
101165
101890
|
};
|
|
101166
101891
|
}
|
|
101167
101892
|
function classifyClaudeSnapshot(snapshot, sprite) {
|
|
101168
|
-
if (!
|
|
101893
|
+
if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
|
|
101169
101894
|
const generationId = snapshot.generationId.trim();
|
|
101170
101895
|
if ((0, node_crypto.createHash)("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
|
|
101171
101896
|
kind: "not_ready",
|
|
@@ -104511,7 +105236,9 @@ const REJECT_UNCERTAIN_METHODS = new Set([
|
|
|
104511
105236
|
WS_METHODS.pullRequestsComment,
|
|
104512
105237
|
WS_METHODS.pullRequestsSubmitReview,
|
|
104513
105238
|
WS_METHODS.pullRequestsReplyToThread,
|
|
104514
|
-
WS_METHODS.projectCheckoutDeletionExecute
|
|
105239
|
+
WS_METHODS.projectCheckoutDeletionExecute,
|
|
105240
|
+
WS_METHODS.providerUploadFeedback,
|
|
105241
|
+
WS_METHODS.sourceControlCloneRepository
|
|
104515
105242
|
]);
|
|
104516
105243
|
function classifyInterruptedRelayCommand(input) {
|
|
104517
105244
|
if ((input.method === WS_METHODS.gitCreateBranch || input.method === WS_METHODS.vcsCreateRef) && input.deliveryClass === "create-command") return { kind: "reconcile-git-create-branch" };
|
|
@@ -105443,7 +106170,77 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
105443
106170
|
payload: initialSnapshot
|
|
105444
106171
|
}), liveEvents);
|
|
105445
106172
|
}), { "rpc.aggregate": "auth" }),
|
|
105446
|
-
[WS_METHODS.subscribeWorkspaceReadiness]: (_input) => currentUserId === void 0 ? require_Path.empty : workspaceReadinessState.subscribe(currentUserId)
|
|
106173
|
+
[WS_METHODS.subscribeWorkspaceReadiness]: (_input) => currentUserId === void 0 ? require_Path.empty : workspaceReadinessState.subscribe(currentUserId),
|
|
106174
|
+
[WS_METHODS.assetsCreateUrl]: (input) => observeRpcEffect(WS_METHODS.assetsCreateUrl, createAssetUrl(currentSessionId, input.resource), { "rpc.aggregate": "assets" }),
|
|
106175
|
+
[WS_METHODS.providerUploadFeedback]: (input) => observeRpcEffect(WS_METHODS.providerUploadFeedback, uploadThreadFeedback(orchestrationEngine, currentSessionId, currentUserId, input), { "rpc.aggregate": "provider" }),
|
|
106176
|
+
[ORCHESTRATION_WS_METHODS.searchThreads]: (input) => observeRpcEffect(ORCHESTRATION_WS_METHODS.searchThreads, require_Schema$1.gen(function* () {
|
|
106177
|
+
yield* startup.awaitCommandReady;
|
|
106178
|
+
return yield* projectionSnapshotQuery.searchThreads(input);
|
|
106179
|
+
}).pipe(require_Schema$1.mapError((cause) => new OrchestrationSearchThreadsError({
|
|
106180
|
+
message: "Failed to search threads",
|
|
106181
|
+
cause
|
|
106182
|
+
}))), { "rpc.aggregate": "orchestration" }),
|
|
106183
|
+
[WS_METHODS.sourceControlLookupRepository]: (input) => observeRpcEffect(WS_METHODS.sourceControlLookupRepository, require_Schema$1.gen(function* () {
|
|
106184
|
+
if (input.provider !== "github") return yield* new SourceControlRepositoryError({
|
|
106185
|
+
provider: input.provider,
|
|
106186
|
+
operation: "lookupRepository",
|
|
106187
|
+
detail: `Forge can only look up GitHub repositories. ${input.provider} is not connected.`
|
|
106188
|
+
});
|
|
106189
|
+
if (currentUserId === void 0) return yield* new SourceControlRepositoryError({
|
|
106190
|
+
provider: "github",
|
|
106191
|
+
operation: "lookupRepository",
|
|
106192
|
+
detail: "Sign in to Forge before looking up a repository."
|
|
106193
|
+
});
|
|
106194
|
+
const outcome = yield* lookupGitHubRepositoryForSession({
|
|
106195
|
+
sessionId: currentSessionId,
|
|
106196
|
+
subject: currentUserId,
|
|
106197
|
+
runtimeGrantAuth: null,
|
|
106198
|
+
repository: input.repository
|
|
106199
|
+
});
|
|
106200
|
+
if (outcome.repository === null) return yield* new SourceControlRepositoryError({
|
|
106201
|
+
provider: "github",
|
|
106202
|
+
operation: "lookupRepository",
|
|
106203
|
+
detail: outcome.error ?? "Repository lookup failed."
|
|
106204
|
+
});
|
|
106205
|
+
return {
|
|
106206
|
+
provider: "github",
|
|
106207
|
+
...outcome.repository
|
|
106208
|
+
};
|
|
106209
|
+
}), { "rpc.aggregate": "sourceControl" }),
|
|
106210
|
+
[WS_METHODS.sourceControlCloneRepository]: (input) => observeRpcEffect(WS_METHODS.sourceControlCloneRepository, require_Schema$1.gen(function* () {
|
|
106211
|
+
const provider = input.provider ?? "github";
|
|
106212
|
+
const repoFullName = resolveGitHubCloneTarget(input);
|
|
106213
|
+
const refuse = (detail) => new SourceControlRepositoryError({
|
|
106214
|
+
provider,
|
|
106215
|
+
operation: "cloneRepository",
|
|
106216
|
+
detail
|
|
106217
|
+
});
|
|
106218
|
+
if (provider !== "github") return yield* refuse(`Forge can only clone GitHub repositories. ${provider} is not connected.`);
|
|
106219
|
+
if (currentUserId === void 0) return yield* refuse("Sign in to Forge before cloning a repository.");
|
|
106220
|
+
if (repoFullName === null) return yield* refuse("Name the repository as owner/name, or paste a github.com clone URL.");
|
|
106221
|
+
const outcome = yield* runGitHubCloneForSession({
|
|
106222
|
+
config,
|
|
106223
|
+
sessionId: currentSessionId,
|
|
106224
|
+
subject: currentUserId,
|
|
106225
|
+
runtimeGrantAuth: null,
|
|
106226
|
+
repoFullName,
|
|
106227
|
+
targetDir: input.destinationPath
|
|
106228
|
+
});
|
|
106229
|
+
const localPath = outcome.body.local_path;
|
|
106230
|
+
if (outcome.status !== 200 || typeof localPath !== "string") {
|
|
106231
|
+
const detail = outcome.body.error;
|
|
106232
|
+
return yield* refuse(typeof detail === "string" ? detail : "Cloning the repository failed.");
|
|
106233
|
+
}
|
|
106234
|
+
const repository = describeGitHubRepository(repoFullName);
|
|
106235
|
+
return {
|
|
106236
|
+
cwd: localPath,
|
|
106237
|
+
remoteUrl: repository.url,
|
|
106238
|
+
repository: {
|
|
106239
|
+
provider: "github",
|
|
106240
|
+
...repository
|
|
106241
|
+
}
|
|
106242
|
+
};
|
|
106243
|
+
}), { "rpc.aggregate": "sourceControl" })
|
|
105447
106244
|
});
|
|
105448
106245
|
}));
|
|
105449
106246
|
const websocketRpcRouteLayer = require_Schema$1.unwrap(require_Schema$1.succeed(require_Schema$1.mergeAll(add$1("GET", "/ws", require_Schema$1.gen(function* () {
|
|
@@ -116531,8 +117328,8 @@ require_Schema$1.effectDiscard(runMigrations());
|
|
|
116531
117328
|
//#endregion
|
|
116532
117329
|
//#region src/persistence/Layers/Sqlite.ts
|
|
116533
117330
|
const defaultSqliteClientLoaders = {
|
|
116534
|
-
bun: () => Promise.resolve().then(() => require("./SqliteClient-
|
|
116535
|
-
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-
|
|
117331
|
+
bun: () => Promise.resolve().then(() => require("./SqliteClient-Cq1peWN1.cjs")),
|
|
117332
|
+
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-qjBWAOGq.cjs"))
|
|
116536
117333
|
};
|
|
116537
117334
|
const makeRuntimeSqliteLayer = require_Schema$1.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
116538
117335
|
const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
|
|
@@ -211732,6 +212529,83 @@ const makeProviderAuthProvenance = (input) => {
|
|
|
211732
212529
|
};
|
|
211733
212530
|
};
|
|
211734
212531
|
//#endregion
|
|
212532
|
+
//#region src/provider/claudeReadinessReason.ts
|
|
212533
|
+
/**
|
|
212534
|
+
* What Forge says about a delivered Claude credential that expired.
|
|
212535
|
+
*
|
|
212536
|
+
* CMMD's credential authority rotates the refresh token on every refresh, and a
|
|
212537
|
+
* runtime holds a one-time snapshot, so the snapshot's refresh token dies at
|
|
212538
|
+
* the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
|
|
212539
|
+
* the turn met a dead credential: the CLI said "OAuth session expired and could
|
|
212540
|
+
* not be refreshed", the thread said Claude was unavailable on this
|
|
212541
|
+
* Environment, and the provider card said the credential was "unavailable or
|
|
212542
|
+
* unusable". Three sentences, none of them the fact, which is that the
|
|
212543
|
+
* credential expired at a known time and a replacement is on its way.
|
|
212544
|
+
*
|
|
212545
|
+
* CMMD-36441 now answers the pre-turn readiness read with a typed reason and
|
|
212546
|
+
* queues a same-generation re-delivery. This module is the one place that
|
|
212547
|
+
* turns those reasons into words.
|
|
212548
|
+
*
|
|
212549
|
+
* Which kind of answer a reason names comes from
|
|
212550
|
+
* `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
|
|
212551
|
+
* reads too, so the two cannot disagree about a reason (FOR-3085). A reason
|
|
212552
|
+
* that maps to nothing falls through to the generic copy, and every sentence a
|
|
212553
|
+
* person reads is written here rather than forwarded from upstream. Only an
|
|
212554
|
+
* ISO timestamp is ever lifted out of CMMD's message, so no credential detail
|
|
212555
|
+
* in an upstream string can reach a browser.
|
|
212556
|
+
*/
|
|
212557
|
+
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
212558
|
+
/**
|
|
212559
|
+
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
212560
|
+
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
212561
|
+
* time is the only part of that sentence Forge repeats.
|
|
212562
|
+
*/
|
|
212563
|
+
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
212564
|
+
function expiredAtFromReadinessMessage(message) {
|
|
212565
|
+
if (typeof message !== "string") return null;
|
|
212566
|
+
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
212567
|
+
}
|
|
212568
|
+
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
212569
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
212570
|
+
}
|
|
212571
|
+
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
212572
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
212573
|
+
}
|
|
212574
|
+
/**
|
|
212575
|
+
* The last allowlisted readiness reason this process saw.
|
|
212576
|
+
*
|
|
212577
|
+
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
212578
|
+
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
212579
|
+
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
212580
|
+
* from a blank one, so without this the card can only offer the generic
|
|
212581
|
+
* sentence while the guard already knows better.
|
|
212582
|
+
*/
|
|
212583
|
+
let latestVerdict = null;
|
|
212584
|
+
/**
|
|
212585
|
+
* Remember, or clear, what the readiness answer said.
|
|
212586
|
+
*
|
|
212587
|
+
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
212588
|
+
* the card go back to the generic sentence the moment CMMD applies a
|
|
212589
|
+
* credential: an applied answer carries no reason.
|
|
212590
|
+
*/
|
|
212591
|
+
function recordClaudeReadinessVerdict(readiness) {
|
|
212592
|
+
const code = claudeReadinessReasonCode(readiness.reason);
|
|
212593
|
+
latestVerdict = code === null ? null : {
|
|
212594
|
+
code,
|
|
212595
|
+
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
212596
|
+
};
|
|
212597
|
+
}
|
|
212598
|
+
/**
|
|
212599
|
+
* What the provider status card says about a delivered credential it cannot
|
|
212600
|
+
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
212601
|
+
* guessed time would be worse than the generic sentence.
|
|
212602
|
+
*/
|
|
212603
|
+
function claudeNativeCredentialUnusableMessage() {
|
|
212604
|
+
const verdict = latestVerdict;
|
|
212605
|
+
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
212606
|
+
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
212607
|
+
}
|
|
212608
|
+
//#endregion
|
|
211735
212609
|
//#region src/provider/claudeCredentialStore.ts
|
|
211736
212610
|
/**
|
|
211737
212611
|
* Which Credential Store the Claude CLI uses on this machine, read the way the
|
|
@@ -287415,6 +288289,7 @@ function taskStartedToActivities(event) {
|
|
|
287415
288289
|
summary: taskStartedSummary(event.payload.taskType),
|
|
287416
288290
|
payload: {
|
|
287417
288291
|
taskId: event.payload.taskId,
|
|
288292
|
+
agentKind: classifyTaskAgentKind({ taskType: event.payload.taskType }),
|
|
287418
288293
|
...event.payload.taskType ? { taskType: event.payload.taskType } : {},
|
|
287419
288294
|
...event.payload.description ? { detail: truncateDetail(event.payload.description) } : {}
|
|
287420
288295
|
}
|
|
@@ -295274,7 +296149,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
295274
296149
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
295275
296150
|
*/
|
|
295276
296151
|
function readBakedBuildCommit() {
|
|
295277
|
-
return "
|
|
296152
|
+
return "c220fa73834b4651c7fc65f272c171028edc3e81";
|
|
295278
296153
|
}
|
|
295279
296154
|
async function resolveServerBuildCommit(input) {
|
|
295280
296155
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -295391,6 +296266,7 @@ function executionEnvironmentCapabilities(serverConfig) {
|
|
|
295391
296266
|
pullRequests: isExecutionPlane,
|
|
295392
296267
|
threadPullRequestLinking: isExecutionPlane,
|
|
295393
296268
|
threadSettlement: isExecutionPlane,
|
|
296269
|
+
threadTitleRegeneration: isExecutionPlane,
|
|
295394
296270
|
cloudMode: serverConfig.forgeCmmdRuntimeMode === true || isCloudShell,
|
|
295395
296271
|
runtime: {
|
|
295396
296272
|
topology: runtimeTopology(serverConfig),
|
|
@@ -295676,10 +296552,6 @@ const pulseSignalStateUpsertRouteLayer = add$1("POST", "/api/pulse/signals/state
|
|
|
295676
296552
|
});
|
|
295677
296553
|
}).pipe(require_Schema$1.catchTag("AuthError", respondToAuthError)));
|
|
295678
296554
|
//#endregion
|
|
295679
|
-
//#region src/auth/Services/ServerSecretStore.ts
|
|
295680
|
-
var SecretStoreError = class extends require_Schema$1.TaggedError("SecretStoreError") {};
|
|
295681
|
-
var ServerSecretStore = class extends require_Schema$1.Service()("forge/auth/Services/ServerSecretStore") {};
|
|
295682
|
-
//#endregion
|
|
295683
296555
|
//#region src/auth/Layers/ServerSecretStore.ts
|
|
295684
296556
|
const OWNED_FILE_RECEIPT_PREFIX = "forge-owned-secret-receipt:v1:";
|
|
295685
296557
|
const LEGACY_FILE_SECRET_NAMESPACE_PREFIX = "forge-secret-namespace:v1:";
|
|
@@ -302052,6 +302924,55 @@ const ProjectionCountsRowSchema = require_Schema$1.Struct({
|
|
|
302052
302924
|
const WorkspaceRootLookupInput = require_Schema$1.Struct({ workspaceRoot: require_Schema$1.String });
|
|
302053
302925
|
const ProjectIdLookupInput = require_Schema$1.Struct({ projectId: ProjectId });
|
|
302054
302926
|
const ThreadIdLookupInput = require_Schema$1.Struct({ threadId: ThreadId });
|
|
302927
|
+
const ThreadSearchQueryInput = require_Schema$1.Struct({
|
|
302928
|
+
pattern: require_Schema$1.String,
|
|
302929
|
+
limit: require_Schema$1.Number
|
|
302930
|
+
});
|
|
302931
|
+
const ThreadSearchMessageRowSchema = require_Schema$1.Struct({
|
|
302932
|
+
threadId: ThreadId,
|
|
302933
|
+
projectId: ProjectId,
|
|
302934
|
+
role: require_Schema$1.String,
|
|
302935
|
+
text: require_Schema$1.String,
|
|
302936
|
+
createdAt: IsoDateTime
|
|
302937
|
+
});
|
|
302938
|
+
const ThreadSearchMetadataRowSchema = require_Schema$1.Struct({
|
|
302939
|
+
threadId: ThreadId,
|
|
302940
|
+
projectId: ProjectId,
|
|
302941
|
+
title: require_Schema$1.String,
|
|
302942
|
+
branch: require_Schema$1.NullOr(require_Schema$1.String)
|
|
302943
|
+
});
|
|
302944
|
+
/** The contract caps a snippet at this length, so the server must too. */
|
|
302945
|
+
const THREAD_SEARCH_SNIPPET_LIMIT = 240;
|
|
302946
|
+
/** How many matches a search returns when the caller does not say. */
|
|
302947
|
+
const THREAD_SEARCH_DEFAULT_LIMIT = 20;
|
|
302948
|
+
/**
|
|
302949
|
+
* `LIKE` treats `%` and `_` as wildcards, so a person searching for a literal
|
|
302950
|
+
* underscore (every other identifier in a codebase) would otherwise match any
|
|
302951
|
+
* character. Escaped with a backslash, declared to SQL as `ESCAPE '\'`, and
|
|
302952
|
+
* lowercased on both sides because Postgres `LIKE` is case-sensitive where
|
|
302953
|
+
* SQLite's is not, and Forge runs on both.
|
|
302954
|
+
*/
|
|
302955
|
+
function toThreadSearchPattern(query) {
|
|
302956
|
+
return `%${query.toLowerCase().replace(/[\\%_]/g, (character) => `\\${character}`)}%`;
|
|
302957
|
+
}
|
|
302958
|
+
/**
|
|
302959
|
+
* A window of the text around the first hit, not the head of it.
|
|
302960
|
+
*
|
|
302961
|
+
* A match 4000 characters into an assistant message is invisible in a snippet
|
|
302962
|
+
* that always starts at character zero, which is the difference between a
|
|
302963
|
+
* result a person can recognize and a result they have to open to understand.
|
|
302964
|
+
*/
|
|
302965
|
+
function buildThreadSearchSnippet(text, lowerQuery) {
|
|
302966
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
302967
|
+
if (collapsed.length <= THREAD_SEARCH_SNIPPET_LIMIT) return collapsed;
|
|
302968
|
+
const matchIndex = collapsed.toLowerCase().indexOf(lowerQuery);
|
|
302969
|
+
if (matchIndex < 0) return `${collapsed.slice(0, THREAD_SEARCH_SNIPPET_LIMIT - 1)}…`;
|
|
302970
|
+
const leading = Math.floor((THREAD_SEARCH_SNIPPET_LIMIT - lowerQuery.length) / 2);
|
|
302971
|
+
const start = Math.max(0, matchIndex - Math.max(leading, 0));
|
|
302972
|
+
const windowed = collapsed.slice(start, start + THREAD_SEARCH_SNIPPET_LIMIT);
|
|
302973
|
+
const prefixed = start > 0 ? `…${windowed}` : windowed;
|
|
302974
|
+
return prefixed.length > THREAD_SEARCH_SNIPPET_LIMIT ? prefixed.slice(0, THREAD_SEARCH_SNIPPET_LIMIT) : prefixed;
|
|
302975
|
+
}
|
|
302055
302976
|
const ProjectionProjectLookupRowSchema = ProjectionProjectDbRowSchema;
|
|
302056
302977
|
const ProjectionThreadIdLookupRowSchema = require_Schema$1.Struct({ threadId: ThreadId });
|
|
302057
302978
|
const ProjectionThreadCheckpointContextThreadRowSchema = require_Schema$1.Struct({
|
|
@@ -302297,6 +303218,61 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
302297
303218
|
AND deleted_at IS NULL
|
|
302298
303219
|
AND archived_at IS NULL
|
|
302299
303220
|
ORDER BY created_at ASC, thread_id ASC
|
|
303221
|
+
`
|
|
303222
|
+
});
|
|
303223
|
+
/**
|
|
303224
|
+
* Message bodies containing the query.
|
|
303225
|
+
*
|
|
303226
|
+
* `role IN ('user', 'assistant')` is not a filter for tidiness: the match
|
|
303227
|
+
* shape on the wire names one of those two as the side the hit came from,
|
|
303228
|
+
* and a system message has no side to report. Ordered newest first because a
|
|
303229
|
+
* capped search should keep what the person is most likely still thinking
|
|
303230
|
+
* about.
|
|
303231
|
+
*/
|
|
303232
|
+
const searchThreadMessageRows = findAll({
|
|
303233
|
+
Request: ThreadSearchQueryInput,
|
|
303234
|
+
Result: ThreadSearchMessageRowSchema,
|
|
303235
|
+
execute: ({ pattern, limit }) => sql`
|
|
303236
|
+
SELECT
|
|
303237
|
+
m.thread_id AS "threadId",
|
|
303238
|
+
t.project_id AS "projectId",
|
|
303239
|
+
m.role AS "role",
|
|
303240
|
+
m.text AS "text",
|
|
303241
|
+
m.created_at AS "createdAt"
|
|
303242
|
+
FROM projection_thread_messages AS m
|
|
303243
|
+
JOIN projection_threads AS t ON t.thread_id = m.thread_id
|
|
303244
|
+
WHERE t.deleted_at IS NULL
|
|
303245
|
+
AND m.role IN ('user', 'assistant')
|
|
303246
|
+
AND LOWER(m.text) LIKE ${pattern} ESCAPE '\\'
|
|
303247
|
+
ORDER BY m.created_at DESC, m.message_id ASC
|
|
303248
|
+
LIMIT ${limit}
|
|
303249
|
+
`
|
|
303250
|
+
});
|
|
303251
|
+
/**
|
|
303252
|
+
* Threads whose own metadata contains the query.
|
|
303253
|
+
*
|
|
303254
|
+
* Title and branch, which is what the web command palette matches on. Title
|
|
303255
|
+
* is duplicated work for a client that already filters its in-memory list,
|
|
303256
|
+
* and harmless because matches are deduplicated by thread; branch is not,
|
|
303257
|
+
* because no client holds every branch.
|
|
303258
|
+
*/
|
|
303259
|
+
const searchThreadMetadataRows = findAll({
|
|
303260
|
+
Request: ThreadSearchQueryInput,
|
|
303261
|
+
Result: ThreadSearchMetadataRowSchema,
|
|
303262
|
+
execute: ({ pattern, limit }) => sql`
|
|
303263
|
+
SELECT
|
|
303264
|
+
thread_id AS "threadId",
|
|
303265
|
+
project_id AS "projectId",
|
|
303266
|
+
title,
|
|
303267
|
+
branch
|
|
303268
|
+
FROM projection_threads
|
|
303269
|
+
WHERE deleted_at IS NULL
|
|
303270
|
+
AND (
|
|
303271
|
+
LOWER(title) LIKE ${pattern} ESCAPE '\\'
|
|
303272
|
+
OR LOWER(branch) LIKE ${pattern} ESCAPE '\\'
|
|
303273
|
+
)
|
|
303274
|
+
ORDER BY updated_at DESC, thread_id ASC
|
|
303275
|
+
LIMIT ${limit}
|
|
302300
303276
|
`
|
|
302301
303277
|
});
|
|
302302
303278
|
/**
|
|
@@ -303043,6 +304019,40 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
303043
304019
|
};
|
|
303044
304020
|
const getShellSnapshot = () => readShellSnapshot("getShellSnapshot", listThreadRows);
|
|
303045
304021
|
const getArchivedShellSnapshot = () => readShellSnapshot("getArchivedShellSnapshot", listArchivedThreadRows);
|
|
304022
|
+
const searchThreads = (input) => {
|
|
304023
|
+
const limit = input.limit ?? THREAD_SEARCH_DEFAULT_LIMIT;
|
|
304024
|
+
const lowerQuery = input.query.toLowerCase();
|
|
304025
|
+
const request = {
|
|
304026
|
+
pattern: toThreadSearchPattern(input.query),
|
|
304027
|
+
limit
|
|
304028
|
+
};
|
|
304029
|
+
return require_Schema$1.all([searchThreadMessageRows(request).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.searchThreads:messages:query", "ProjectionSnapshotQuery.searchThreads:messages:decodeRows"))), searchThreadMetadataRows(request).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.searchThreads:metadata:query", "ProjectionSnapshotQuery.searchThreads:metadata:decodeRows")))]).pipe(require_Schema$1.map(([messageRows, metadataRows]) => {
|
|
304030
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
304031
|
+
for (const row of messageRows) {
|
|
304032
|
+
if (byThread.has(row.threadId)) continue;
|
|
304033
|
+
byThread.set(row.threadId, {
|
|
304034
|
+
threadId: row.threadId,
|
|
304035
|
+
projectId: row.projectId,
|
|
304036
|
+
source: row.role === "assistant" ? "assistant" : "user",
|
|
304037
|
+
snippet: buildThreadSearchSnippet(row.text, lowerQuery),
|
|
304038
|
+
messageCreatedAt: row.createdAt
|
|
304039
|
+
});
|
|
304040
|
+
}
|
|
304041
|
+
for (const row of metadataRows) {
|
|
304042
|
+
if (byThread.has(row.threadId)) continue;
|
|
304043
|
+
const branchMatched = row.branch?.toLowerCase().includes(lowerQuery) === true;
|
|
304044
|
+
const titleMatched = row.title.toLowerCase().includes(lowerQuery);
|
|
304045
|
+
byThread.set(row.threadId, {
|
|
304046
|
+
threadId: row.threadId,
|
|
304047
|
+
projectId: row.projectId,
|
|
304048
|
+
source: "user",
|
|
304049
|
+
snippet: buildThreadSearchSnippet(titleMatched || !branchMatched ? row.title : row.branch ?? row.title, lowerQuery),
|
|
304050
|
+
messageCreatedAt: null
|
|
304051
|
+
});
|
|
304052
|
+
}
|
|
304053
|
+
return { matches: [...byThread.values()].slice(0, limit) };
|
|
304054
|
+
}));
|
|
304055
|
+
};
|
|
303046
304056
|
const listActiveWorkerShells = () => sql.withTransaction(require_Schema$1.all([
|
|
303047
304057
|
listActiveWorkerThreadRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:decodeRows"))),
|
|
303048
304058
|
listActiveWorkerSessionRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:decodeRows"))),
|
|
@@ -303211,6 +304221,7 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
303211
304221
|
getSnapshot,
|
|
303212
304222
|
getShellSnapshot,
|
|
303213
304223
|
getArchivedShellSnapshot,
|
|
304224
|
+
searchThreads,
|
|
303214
304225
|
listActiveWorkerShells,
|
|
303215
304226
|
listStaleRunningSessionShells,
|
|
303216
304227
|
getCounts,
|
|
@@ -318475,13 +319486,13 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", require_
|
|
|
318475
319486
|
//#endregion
|
|
318476
319487
|
//#region src/server.ts
|
|
318477
319488
|
const PtyAdapterLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
318478
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-
|
|
318479
|
-
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-
|
|
319489
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-uyreNRGp.cjs")))).layer;
|
|
319490
|
+
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-S4tPEwZc.cjs")))).layer;
|
|
318480
319491
|
}));
|
|
318481
319492
|
const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
318482
319493
|
const config = yield* ServerConfig;
|
|
318483
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-
|
|
318484
|
-
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-
|
|
319494
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-DKvSpNd1.cjs")))).layer(bunHttpServerOptions(config));
|
|
319495
|
+
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-Dfc7nSMq.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
|
|
318485
319496
|
return NodeHttpServer.layer(NodeHttp.createServer, {
|
|
318486
319497
|
host: config.host,
|
|
318487
319498
|
port: config.port
|
|
@@ -318489,10 +319500,10 @@ const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* ()
|
|
|
318489
319500
|
}));
|
|
318490
319501
|
const PlatformServicesLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
318491
319502
|
if (typeof Bun !== "undefined") {
|
|
318492
|
-
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-
|
|
319503
|
+
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-DDh7Whyx.cjs")));
|
|
318493
319504
|
return layer;
|
|
318494
319505
|
} else {
|
|
318495
|
-
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-
|
|
319506
|
+
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-DE8jhdcU.cjs")));
|
|
318496
319507
|
return layer;
|
|
318497
319508
|
}
|
|
318498
319509
|
}));
|