@jamiexiongr/panda-hub 0.1.31 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/bin/panda-hub.cjs +1 -1
- package/dist/{chunk-R4XG3QOI.mjs → chunk-JKHCCCUZ.mjs} +2 -2
- package/dist/{chunk-HVRU7VLO.mjs → chunk-TYL3A3VC.mjs} +2606 -538
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-QDSFJEPG.mjs → src-FZ5WMACF.mjs} +28 -14
- package/dist/web/assets/{diagnostics-page-B1U4KFsP.js → diagnostics-page-4wwMLeXa.js} +1 -1
- package/dist/web/assets/index-Buoz6IYL.js +208 -0
- package/dist/web/assets/index-qres5phQ.css +1 -0
- package/dist/web/assets/{session-diff-preview-D2OLvjY4.js → session-diff-preview-5oB0rJ5W.js} +1 -1
- package/dist/web/assets/{session-file-markdown-preview-ChAPAUwv.js → session-file-markdown-preview-BoOjcJcb.js} +1 -1
- package/dist/web/assets/session-file-preview-page-DbdsZil_.js +38 -0
- package/dist/web/assets/{web-CVvrmG-0.js → web-BBYipy_G.js} +1 -1
- package/dist/web/assets/{web-CasgI1aI.js → web-BiBxkdSf.js} +1 -1
- package/dist/web/assets/{web-731MG9gc.js → web-Cv1t4xrp.js} +1 -1
- package/dist/web/assets/{web-gFMIJiWK.js → web-D9nPg8kF.js} +1 -1
- package/dist/web/assets/{web-DR5ufVxV.js → web-OEcsiG9z.js} +1 -1
- package/dist/web/assets/{xml-COG-BRHv.js → xml-BJn4g2Sn.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-2xq-OQM2.js +0 -148
- package/dist/web/assets/index-DBWUmekP.css +0 -1
- package/dist/web/assets/session-file-preview-page-DCpn44mO.js +0 -53
|
@@ -1576,6 +1576,20 @@ var getWorkspaceRootLabels = (state) => {
|
|
|
1576
1576
|
)
|
|
1577
1577
|
);
|
|
1578
1578
|
};
|
|
1579
|
+
var getProjectlessThreadIds = (state) => Array.isArray(state["projectless-thread-ids"]) ? state["projectless-thread-ids"].filter(
|
|
1580
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
1581
|
+
) : [];
|
|
1582
|
+
var getThreadWorkspaceRootHints = (state) => {
|
|
1583
|
+
const hints = state["thread-workspace-root-hints"];
|
|
1584
|
+
if (!hints || typeof hints !== "object" || Array.isArray(hints)) {
|
|
1585
|
+
return {};
|
|
1586
|
+
}
|
|
1587
|
+
return Object.fromEntries(
|
|
1588
|
+
Object.entries(hints).filter(
|
|
1589
|
+
(entry) => typeof entry[0] === "string" && entry[0].trim().length > 0 && typeof entry[1] === "string" && entry[1].trim().length > 0
|
|
1590
|
+
)
|
|
1591
|
+
);
|
|
1592
|
+
};
|
|
1579
1593
|
var readPandaThreadPrefs = async (codexHome) => {
|
|
1580
1594
|
try {
|
|
1581
1595
|
const raw = await fs2.readFile(pandaThreadPrefsPath(codexHome), "utf8");
|
|
@@ -1735,6 +1749,32 @@ var setWorkspaceRootVisibility = async (workspaceRoot, visible, codexHome) => {
|
|
|
1735
1749
|
codexHome
|
|
1736
1750
|
);
|
|
1737
1751
|
};
|
|
1752
|
+
var registerProjectlessThread = async (sessionId, workspaceRootHint, codexHome) => {
|
|
1753
|
+
const normalizedSessionId = sessionId.trim();
|
|
1754
|
+
const normalizedWorkspaceRootHint = workspaceRootHint.trim();
|
|
1755
|
+
if (!normalizedSessionId || !normalizedWorkspaceRootHint) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
const state = await readCodexGlobalState(codexHome);
|
|
1759
|
+
const nextProjectlessThreadIds = [
|
|
1760
|
+
.../* @__PURE__ */ new Set([
|
|
1761
|
+
...getProjectlessThreadIds(state),
|
|
1762
|
+
normalizedSessionId
|
|
1763
|
+
])
|
|
1764
|
+
];
|
|
1765
|
+
const nextWorkspaceRootHints = {
|
|
1766
|
+
...getThreadWorkspaceRootHints(state),
|
|
1767
|
+
[normalizedSessionId]: normalizePathKey(normalizedWorkspaceRootHint)
|
|
1768
|
+
};
|
|
1769
|
+
await writeCodexGlobalState(
|
|
1770
|
+
{
|
|
1771
|
+
...state,
|
|
1772
|
+
"projectless-thread-ids": nextProjectlessThreadIds,
|
|
1773
|
+
"thread-workspace-root-hints": nextWorkspaceRootHints
|
|
1774
|
+
},
|
|
1775
|
+
codexHome
|
|
1776
|
+
);
|
|
1777
|
+
};
|
|
1738
1778
|
var isWithinWorkspaceRoot = (targetPath, workspaceRoot) => {
|
|
1739
1779
|
const normalizedPath = normalizePathKey(targetPath);
|
|
1740
1780
|
const normalizedRoot = normalizePathKey(workspaceRoot);
|
|
@@ -1888,12 +1928,48 @@ var stripInjectedUserText = (value) => {
|
|
|
1888
1928
|
import path3 from "path";
|
|
1889
1929
|
|
|
1890
1930
|
// packages/protocol/src/index.ts
|
|
1891
|
-
var providerKindSchema = external_exports.enum(["codex", "claude"]);
|
|
1931
|
+
var providerKindSchema = external_exports.enum(["codex", "claude", "image"]);
|
|
1892
1932
|
var sessionModeSchema = external_exports.enum(["managed", "attached-live", "history-only"]);
|
|
1893
1933
|
var sessionHealthSchema = external_exports.enum(["active", "idle", "attention", "offline"]);
|
|
1894
1934
|
var sessionRunStateSchema = external_exports.enum(["idle", "running", "completed"]);
|
|
1895
1935
|
var attachmentKindSchema = external_exports.enum(["image", "file"]);
|
|
1896
1936
|
var skillScopeSchema = external_exports.enum(["user", "repo", "system", "admin"]);
|
|
1937
|
+
var imageModelVendorSchema = external_exports.enum(["openai", "xai", "gemini"]);
|
|
1938
|
+
var imageGenerationModelSchema = external_exports.string().trim().min(1).max(120);
|
|
1939
|
+
var imageGenerationQualitySchema = external_exports.enum(["auto", "low", "medium", "high"]);
|
|
1940
|
+
var imageGenerationSizeSchema = external_exports.enum([
|
|
1941
|
+
"auto",
|
|
1942
|
+
"1024x1024",
|
|
1943
|
+
"1536x1024",
|
|
1944
|
+
"1024x1536",
|
|
1945
|
+
"2048x2048",
|
|
1946
|
+
"2048x1152",
|
|
1947
|
+
"1152x2048",
|
|
1948
|
+
"3840x2160",
|
|
1949
|
+
"2160x3840"
|
|
1950
|
+
]);
|
|
1951
|
+
var imageEditInputFidelitySchema = external_exports.enum(["low", "high"]);
|
|
1952
|
+
var imageOutputFormatSchema = external_exports.enum(["png", "jpeg", "webp"]);
|
|
1953
|
+
var imageBackgroundSchema = external_exports.enum(["auto", "opaque"]);
|
|
1954
|
+
var imageModerationSchema = external_exports.enum(["auto", "low"]);
|
|
1955
|
+
var imageGenerationAspectRatioSchema = external_exports.enum([
|
|
1956
|
+
"auto",
|
|
1957
|
+
"1:1",
|
|
1958
|
+
"4:3",
|
|
1959
|
+
"3:4",
|
|
1960
|
+
"16:9",
|
|
1961
|
+
"9:16"
|
|
1962
|
+
]);
|
|
1963
|
+
var imageSessionAssetKindSchema = external_exports.enum(["generated", "source", "mask"]);
|
|
1964
|
+
var imageSessionSourceKindSchema = external_exports.enum(["upload", "session_asset"]);
|
|
1965
|
+
var imageSessionTurnStatusSchema = external_exports.enum(["pending", "completed", "failed"]);
|
|
1966
|
+
var imageSessionTurnOperationSchema = external_exports.enum(["generate", "edit"]);
|
|
1967
|
+
var imageProviderModelSchema = external_exports.object({
|
|
1968
|
+
id: imageGenerationModelSchema,
|
|
1969
|
+
vendor: imageModelVendorSchema.default("openai"),
|
|
1970
|
+
label: external_exports.string().trim().min(1).max(160),
|
|
1971
|
+
enabled: external_exports.boolean().default(true)
|
|
1972
|
+
});
|
|
1897
1973
|
var sessionCapabilitySchema = external_exports.object({
|
|
1898
1974
|
can_stream_live: external_exports.boolean(),
|
|
1899
1975
|
can_send_input: external_exports.boolean(),
|
|
@@ -1996,6 +2072,118 @@ var sessionInputAttachmentSchema = external_exports.object({
|
|
|
1996
2072
|
size_bytes: external_exports.number().int().nonnegative().nullable().default(null),
|
|
1997
2073
|
data_url: external_exports.string()
|
|
1998
2074
|
});
|
|
2075
|
+
var imageSessionAssetSchema = external_exports.object({
|
|
2076
|
+
id: external_exports.string(),
|
|
2077
|
+
session_id: external_exports.string(),
|
|
2078
|
+
turn_id: external_exports.string().nullable().default(null),
|
|
2079
|
+
kind: imageSessionAssetKindSchema,
|
|
2080
|
+
name: external_exports.string(),
|
|
2081
|
+
mime_type: external_exports.string().nullable().default(null),
|
|
2082
|
+
size_bytes: external_exports.number().int().nonnegative().nullable().default(null),
|
|
2083
|
+
width: external_exports.number().int().positive().nullable().default(null),
|
|
2084
|
+
height: external_exports.number().int().positive().nullable().default(null),
|
|
2085
|
+
content_url: external_exports.string(),
|
|
2086
|
+
created_at: external_exports.string()
|
|
2087
|
+
});
|
|
2088
|
+
var imageSessionSourceInputSchema = external_exports.object({
|
|
2089
|
+
id: external_exports.string(),
|
|
2090
|
+
kind: imageSessionSourceKindSchema,
|
|
2091
|
+
name: external_exports.string(),
|
|
2092
|
+
mime_type: external_exports.string().nullable().default(null),
|
|
2093
|
+
size_bytes: external_exports.number().int().nonnegative().nullable().default(null),
|
|
2094
|
+
data_url: external_exports.string().nullable().default(null),
|
|
2095
|
+
asset_id: external_exports.string().nullable().default(null),
|
|
2096
|
+
mask_data_url: external_exports.string().nullable().default(null)
|
|
2097
|
+
});
|
|
2098
|
+
var imageSessionTurnSchema = external_exports.object({
|
|
2099
|
+
id: external_exports.string(),
|
|
2100
|
+
session_id: external_exports.string(),
|
|
2101
|
+
created_at: external_exports.string(),
|
|
2102
|
+
completed_at: external_exports.string().nullable().default(null),
|
|
2103
|
+
prompt: external_exports.string(),
|
|
2104
|
+
prompt_suffix: external_exports.string().nullable().default(null),
|
|
2105
|
+
operation: imageSessionTurnOperationSchema,
|
|
2106
|
+
status: imageSessionTurnStatusSchema,
|
|
2107
|
+
error: external_exports.string().nullable().default(null),
|
|
2108
|
+
model: imageGenerationModelSchema,
|
|
2109
|
+
quality: imageGenerationQualitySchema,
|
|
2110
|
+
size: imageGenerationSizeSchema,
|
|
2111
|
+
input_fidelity: imageEditInputFidelitySchema.nullable().default(null),
|
|
2112
|
+
aspect_ratio: imageGenerationAspectRatioSchema,
|
|
2113
|
+
output_format: imageOutputFormatSchema.default("png"),
|
|
2114
|
+
output_compression: external_exports.number().int().min(0).max(100).nullable().default(null),
|
|
2115
|
+
background: imageBackgroundSchema.default("auto"),
|
|
2116
|
+
moderation: imageModerationSchema.default("auto"),
|
|
2117
|
+
n: external_exports.number().int().min(1).max(10).default(1),
|
|
2118
|
+
sources: external_exports.array(imageSessionAssetSchema).default([]),
|
|
2119
|
+
outputs: external_exports.array(imageSessionAssetSchema).default([])
|
|
2120
|
+
});
|
|
2121
|
+
var imageProviderChannelSchema = external_exports.object({
|
|
2122
|
+
id: external_exports.string(),
|
|
2123
|
+
remark: external_exports.string(),
|
|
2124
|
+
base_url: external_exports.string(),
|
|
2125
|
+
models: external_exports.array(imageProviderModelSchema).default([]),
|
|
2126
|
+
resolved_api_key_source: external_exports.enum(["stored", "env", "missing"]).default("missing"),
|
|
2127
|
+
has_stored_api_key: external_exports.boolean().default(false),
|
|
2128
|
+
updated_at: external_exports.string().nullable().default(null)
|
|
2129
|
+
});
|
|
2130
|
+
var imageProviderChannelUpdateSchema = external_exports.object({
|
|
2131
|
+
id: external_exports.string(),
|
|
2132
|
+
remark: external_exports.string().trim().min(1).max(80),
|
|
2133
|
+
base_url: external_exports.string(),
|
|
2134
|
+
models: external_exports.array(imageProviderModelSchema).default([]),
|
|
2135
|
+
api_key: external_exports.string().optional().nullable(),
|
|
2136
|
+
api_key_mode: external_exports.enum(["keep", "set", "clear"]).default("keep")
|
|
2137
|
+
});
|
|
2138
|
+
var imageProviderSettingsSchema = external_exports.object({
|
|
2139
|
+
active_channel_id: external_exports.string().nullable().default(null),
|
|
2140
|
+
base_url: external_exports.string(),
|
|
2141
|
+
channels: external_exports.array(imageProviderChannelSchema).default([]),
|
|
2142
|
+
model: imageGenerationModelSchema.default("gpt-image-2"),
|
|
2143
|
+
resolved_api_key_source: external_exports.enum(["stored", "env", "missing"]).default("missing"),
|
|
2144
|
+
has_stored_api_key: external_exports.boolean().default(false),
|
|
2145
|
+
updated_at: external_exports.string().nullable().default(null)
|
|
2146
|
+
});
|
|
2147
|
+
var imageProviderSettingsUpdateSchema = external_exports.object({
|
|
2148
|
+
active_channel_id: external_exports.string().nullable().default(null),
|
|
2149
|
+
channels: external_exports.array(imageProviderChannelUpdateSchema).default([])
|
|
2150
|
+
});
|
|
2151
|
+
var imageProviderTestResponseSchema = external_exports.object({
|
|
2152
|
+
ok: external_exports.boolean(),
|
|
2153
|
+
message: external_exports.string(),
|
|
2154
|
+
model_available: external_exports.boolean().default(false),
|
|
2155
|
+
resolved_base_url: external_exports.string()
|
|
2156
|
+
});
|
|
2157
|
+
var imageProviderModelListRequestSchema = external_exports.object({
|
|
2158
|
+
base_url: external_exports.string(),
|
|
2159
|
+
api_key: external_exports.string().optional().nullable(),
|
|
2160
|
+
api_key_mode: external_exports.enum(["keep", "set", "clear"]).default("keep"),
|
|
2161
|
+
channel_id: external_exports.string().nullable().default(null)
|
|
2162
|
+
});
|
|
2163
|
+
var imageProviderModelListResponseSchema = external_exports.object({
|
|
2164
|
+
models: external_exports.array(external_exports.object({
|
|
2165
|
+
id: external_exports.string(),
|
|
2166
|
+
label: external_exports.string()
|
|
2167
|
+
}))
|
|
2168
|
+
});
|
|
2169
|
+
var imageSessionCreateRequestSchema = external_exports.object({
|
|
2170
|
+
title: external_exports.string().trim().min(1).max(80).optional()
|
|
2171
|
+
});
|
|
2172
|
+
var imageSessionSubmitRequestSchema = external_exports.object({
|
|
2173
|
+
prompt: external_exports.string(),
|
|
2174
|
+
append_to_turn_id: external_exports.string().nullable().default(null),
|
|
2175
|
+
model: imageGenerationModelSchema.default("gpt-image-2"),
|
|
2176
|
+
quality: imageGenerationQualitySchema.default("auto"),
|
|
2177
|
+
size: imageGenerationSizeSchema.default("1024x1024"),
|
|
2178
|
+
input_fidelity: imageEditInputFidelitySchema.nullable().default(null),
|
|
2179
|
+
aspect_ratio: imageGenerationAspectRatioSchema.default("auto"),
|
|
2180
|
+
output_format: imageOutputFormatSchema.default("png"),
|
|
2181
|
+
output_compression: external_exports.number().int().min(0).max(100).nullable().default(null),
|
|
2182
|
+
background: imageBackgroundSchema.default("auto"),
|
|
2183
|
+
moderation: imageModerationSchema.default("auto"),
|
|
2184
|
+
n: external_exports.number().int().min(1).max(10).default(1),
|
|
2185
|
+
sources: external_exports.array(imageSessionSourceInputSchema).default([])
|
|
2186
|
+
});
|
|
1999
2187
|
var projectSkillSchema = external_exports.object({
|
|
2000
2188
|
name: external_exports.string(),
|
|
2001
2189
|
description: external_exports.string(),
|
|
@@ -2095,7 +2283,7 @@ var sessionRefSchema = external_exports.object({
|
|
|
2095
2283
|
id: external_exports.string(),
|
|
2096
2284
|
agent_id: external_exports.string(),
|
|
2097
2285
|
project_id: external_exports.string(),
|
|
2098
|
-
provider: providerKindSchema,
|
|
2286
|
+
provider: providerKindSchema.default("codex"),
|
|
2099
2287
|
archived: external_exports.boolean(),
|
|
2100
2288
|
title: external_exports.string(),
|
|
2101
2289
|
mode: sessionModeSchema,
|
|
@@ -2135,6 +2323,27 @@ var timelineEntrySchema = external_exports.object({
|
|
|
2135
2323
|
accent: external_exports.enum(["primary", "secondary", "muted"]),
|
|
2136
2324
|
attachments: external_exports.array(timelineAttachmentSchema).default([])
|
|
2137
2325
|
});
|
|
2326
|
+
var isMeaningfulUserTimelineEntry = (entry) => entry?.kind === "user" && (entry.body.trim().length > 0 || Array.isArray(entry.attachments) && entry.attachments.length > 0);
|
|
2327
|
+
var findTailTimelineAnchorEntryIndex = (entries) => {
|
|
2328
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
2329
|
+
if (isMeaningfulUserTimelineEntry(entries[index])) {
|
|
2330
|
+
return index;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return -1;
|
|
2334
|
+
};
|
|
2335
|
+
var hasEarlierVisibleConversationTurns = (entries) => {
|
|
2336
|
+
const anchorIndex = findTailTimelineAnchorEntryIndex(entries);
|
|
2337
|
+
if (anchorIndex < 0) {
|
|
2338
|
+
return false;
|
|
2339
|
+
}
|
|
2340
|
+
for (let index = 0; index < anchorIndex; index += 1) {
|
|
2341
|
+
if (isMeaningfulUserTimelineEntry(entries[index])) {
|
|
2342
|
+
return true;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
return false;
|
|
2346
|
+
};
|
|
2138
2347
|
var sessionTimelineViewSchema = external_exports.enum(["tail", "full_compact"]);
|
|
2139
2348
|
var sessionTimelineSnapshotSchema = external_exports.object({
|
|
2140
2349
|
session_id: external_exports.string(),
|
|
@@ -2719,6 +2928,7 @@ var workspaceSessionListItemSchema = sessionRefSchema.pick({
|
|
|
2719
2928
|
id: true,
|
|
2720
2929
|
agent_id: true,
|
|
2721
2930
|
project_id: true,
|
|
2931
|
+
provider: true,
|
|
2722
2932
|
archived: true,
|
|
2723
2933
|
title: true,
|
|
2724
2934
|
last_event_at: true,
|
|
@@ -2752,6 +2962,11 @@ var workspaceSessionDetailResponseSchema = external_exports.object({
|
|
|
2752
2962
|
generated_at: external_exports.string(),
|
|
2753
2963
|
session: workspaceSessionDetailSchema
|
|
2754
2964
|
});
|
|
2965
|
+
var imageSessionDetailResponseSchema = external_exports.object({
|
|
2966
|
+
generated_at: external_exports.string(),
|
|
2967
|
+
session: workspaceSessionDetailSchema,
|
|
2968
|
+
turns: external_exports.array(imageSessionTurnSchema).default([])
|
|
2969
|
+
});
|
|
2755
2970
|
var hubDirectorySnapshotSchema = external_exports.object({
|
|
2756
2971
|
generated_at: external_exports.string(),
|
|
2757
2972
|
agents: external_exports.array(agentNodeSchema)
|
|
@@ -3262,6 +3477,8 @@ var APP_SERVER_REQUEST_TIMEOUT_MS = 12e3;
|
|
|
3262
3477
|
var APP_SERVER_RUNTIME_DIAGNOSTIC_WINDOW_MS = 2 * 60 * 1e3;
|
|
3263
3478
|
var APP_SERVER_RUNTIME_DIAGNOSTIC_DEDUPE_MS = 4e3;
|
|
3264
3479
|
var ANSI_ESCAPE_PATTERN = /\u001b\[[0-9;]*m/g;
|
|
3480
|
+
var CODEX_VERSION_PROBE_TIMEOUT_MS = 2500;
|
|
3481
|
+
var APP_SERVER_CLIENT_NAME = "Codex Desktop";
|
|
3265
3482
|
var createDiagnosticHash = (value) => createHash("sha1").update(value).digest("hex").slice(0, 16);
|
|
3266
3483
|
var listCodexCommandCandidates = () => {
|
|
3267
3484
|
const locator = process.platform === "win32" ? "where.exe" : "which";
|
|
@@ -3278,6 +3495,25 @@ var listCodexCommandCandidates = () => {
|
|
|
3278
3495
|
return [];
|
|
3279
3496
|
}
|
|
3280
3497
|
};
|
|
3498
|
+
var detectCodexCliVersion = () => {
|
|
3499
|
+
try {
|
|
3500
|
+
const result = spawnSync("codex", ["--version"], {
|
|
3501
|
+
encoding: "utf8",
|
|
3502
|
+
windowsHide: true,
|
|
3503
|
+
shell: process.platform === "win32",
|
|
3504
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3505
|
+
timeout: CODEX_VERSION_PROBE_TIMEOUT_MS
|
|
3506
|
+
});
|
|
3507
|
+
if (result.status !== 0 || typeof result.stdout !== "string") {
|
|
3508
|
+
return null;
|
|
3509
|
+
}
|
|
3510
|
+
const normalized = result.stdout.trim();
|
|
3511
|
+
const match = /\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/.exec(normalized);
|
|
3512
|
+
return match?.[1] ?? null;
|
|
3513
|
+
} catch {
|
|
3514
|
+
return null;
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3281
3517
|
var YOLO_APPROVAL_POLICY = "never";
|
|
3282
3518
|
var YOLO_SANDBOX_POLICY = "danger-full-access";
|
|
3283
3519
|
var resolveExecutionOverrides = (yoloMode) => yoloMode ? {
|
|
@@ -4745,6 +4981,7 @@ var createCodexLiveSessionStream = (options) => {
|
|
|
4745
4981
|
let appServerInitializeTimer = null;
|
|
4746
4982
|
let appServerInitializationError = null;
|
|
4747
4983
|
let appServerStderrBuffer = "";
|
|
4984
|
+
let appServerClientVersion = detectCodexCliVersion();
|
|
4748
4985
|
const pendingAppServerRequests = /* @__PURE__ */ new Map();
|
|
4749
4986
|
const planSnapshotsBySessionId = /* @__PURE__ */ new Map();
|
|
4750
4987
|
const lastAppServerRequestBySessionId = /* @__PURE__ */ new Map();
|
|
@@ -5775,12 +6012,14 @@ ${nextFile.diff}`.trim();
|
|
|
5775
6012
|
return;
|
|
5776
6013
|
}
|
|
5777
6014
|
const codexCandidates = listCodexCommandCandidates();
|
|
6015
|
+
appServerClientVersion = detectCodexCliVersion();
|
|
5778
6016
|
logDiagnostic("info", "Starting Codex app-server bridge.", {
|
|
5779
6017
|
platform: process.platform,
|
|
5780
6018
|
execPath: process.execPath,
|
|
5781
6019
|
codexHome: resolveCodexEnvironmentHome(),
|
|
5782
6020
|
codexCommandFound: codexCandidates.length > 0,
|
|
5783
6021
|
codexCommandCandidates: codexCandidates,
|
|
6022
|
+
detectedCodexVersion: appServerClientVersion,
|
|
5784
6023
|
bridgeExecutable: process.platform === "win32" ? `${process.env.ComSpec || "cmd.exe"} /d /c codex` : "codex"
|
|
5785
6024
|
});
|
|
5786
6025
|
const appServerChild = spawnAppServerChild();
|
|
@@ -6285,8 +6524,8 @@ ${nextFile.diff}`.trim();
|
|
|
6285
6524
|
method: "initialize",
|
|
6286
6525
|
params: {
|
|
6287
6526
|
clientInfo: {
|
|
6288
|
-
name:
|
|
6289
|
-
version: "
|
|
6527
|
+
name: APP_SERVER_CLIENT_NAME,
|
|
6528
|
+
version: appServerClientVersion ?? "unknown"
|
|
6290
6529
|
},
|
|
6291
6530
|
capabilities: {
|
|
6292
6531
|
experimentalApi: true,
|
|
@@ -6765,11 +7004,11 @@ ${nextFile.diff}`.trim();
|
|
|
6765
7004
|
};
|
|
6766
7005
|
|
|
6767
7006
|
// packages/provider-codex/src/panda-session-service.ts
|
|
6768
|
-
import
|
|
7007
|
+
import fs9 from "fs/promises";
|
|
6769
7008
|
import { spawn as spawn4 } from "child_process";
|
|
6770
7009
|
import { createHash as createHash5 } from "crypto";
|
|
6771
7010
|
import os6 from "os";
|
|
6772
|
-
import
|
|
7011
|
+
import path11 from "path";
|
|
6773
7012
|
import Fastify from "fastify";
|
|
6774
7013
|
import compress from "@fastify/compress";
|
|
6775
7014
|
import cors from "@fastify/cors";
|
|
@@ -7164,162 +7403,1246 @@ var createHubPushSubscriptionStore = ({
|
|
|
7164
7403
|
};
|
|
7165
7404
|
};
|
|
7166
7405
|
|
|
7167
|
-
// packages/provider-codex/src/
|
|
7168
|
-
var
|
|
7169
|
-
var
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7406
|
+
// packages/provider-codex/src/image-provider-config.ts
|
|
7407
|
+
var DEFAULT_IMAGE_PROVIDER_BASE_URL = "https://api.openai.com/v1";
|
|
7408
|
+
var DEFAULT_IMAGE_PROVIDER_MODEL = "gpt-image-2";
|
|
7409
|
+
var DEFAULT_IMAGE_PROVIDER_MODELS = [
|
|
7410
|
+
{
|
|
7411
|
+
id: DEFAULT_IMAGE_PROVIDER_MODEL,
|
|
7412
|
+
vendor: "openai",
|
|
7413
|
+
label: DEFAULT_IMAGE_PROVIDER_MODEL,
|
|
7414
|
+
enabled: true
|
|
7175
7415
|
}
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7416
|
+
];
|
|
7417
|
+
var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
|
|
7418
|
+
var resolveApiKeySource = (storedApiKey) => storedApiKey?.trim() ? "stored" : resolveImageProviderApiKey() ? "env" : "missing";
|
|
7419
|
+
var normalizeImageProviderBaseUrl = (value) => {
|
|
7420
|
+
const trimmed = value?.trim() ?? "";
|
|
7421
|
+
if (!trimmed) {
|
|
7422
|
+
return DEFAULT_IMAGE_PROVIDER_BASE_URL;
|
|
7180
7423
|
}
|
|
7181
|
-
return
|
|
7424
|
+
return trimTrailingSlash(trimmed);
|
|
7182
7425
|
};
|
|
7183
|
-
var
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7426
|
+
var resolveImageProviderBaseUrl = (storedBaseUrl) => normalizeImageProviderBaseUrl(
|
|
7427
|
+
storedBaseUrl ?? process.env.PANDA_IMAGE_PROVIDER_BASE_URL ?? process.env.OPENAI_BASE_URL ?? process.env.OPENAI_API_BASE_URL ?? DEFAULT_IMAGE_PROVIDER_BASE_URL
|
|
7428
|
+
);
|
|
7429
|
+
var resolveImageProviderApiKey = () => process.env.PANDA_IMAGE_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() || "";
|
|
7430
|
+
var buildResolvedImageProviderChannel = (input) => ({
|
|
7431
|
+
id: input.id,
|
|
7432
|
+
remark: input.remark?.trim() || "Unnamed channel",
|
|
7433
|
+
base_url: resolveImageProviderBaseUrl(input.baseUrl),
|
|
7434
|
+
models: input.models?.length ? input.models : DEFAULT_IMAGE_PROVIDER_MODELS,
|
|
7435
|
+
resolved_api_key_source: resolveApiKeySource(input.storedApiKey),
|
|
7436
|
+
has_stored_api_key: Boolean(input.storedApiKey?.trim()),
|
|
7437
|
+
updated_at: input.updatedAt ?? null
|
|
7438
|
+
});
|
|
7439
|
+
var buildResolvedImageProviderSettings = (input) => ({
|
|
7440
|
+
...(() => {
|
|
7441
|
+
const channels = (input?.channels ?? []).map(
|
|
7442
|
+
(channel) => buildResolvedImageProviderChannel(channel)
|
|
7443
|
+
);
|
|
7444
|
+
const requestedActiveChannelId = input?.activeChannelId ?? null;
|
|
7445
|
+
const activeChannel = channels.find((channel) => channel.id === requestedActiveChannelId) ?? channels[0] ?? null;
|
|
7446
|
+
return {
|
|
7447
|
+
active_channel_id: activeChannel?.id ?? null,
|
|
7448
|
+
base_url: activeChannel?.base_url ?? resolveImageProviderBaseUrl(null),
|
|
7449
|
+
channels,
|
|
7450
|
+
model: DEFAULT_IMAGE_PROVIDER_MODEL,
|
|
7451
|
+
resolved_api_key_source: activeChannel?.resolved_api_key_source ?? resolveApiKeySource(null),
|
|
7452
|
+
has_stored_api_key: activeChannel?.has_stored_api_key ?? false,
|
|
7453
|
+
updated_at: input?.updatedAt ?? null
|
|
7195
7454
|
};
|
|
7196
|
-
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7455
|
+
})()
|
|
7456
|
+
});
|
|
7457
|
+
var getDefaultImageProviderModel = () => DEFAULT_IMAGE_PROVIDER_MODEL;
|
|
7199
7458
|
|
|
7200
|
-
// packages/provider-codex/src/
|
|
7201
|
-
import { createHash as createHash3, randomUUID } from "crypto";
|
|
7202
|
-
import { spawn as spawn2 } from "child_process";
|
|
7203
|
-
import fs6 from "fs/promises";
|
|
7204
|
-
import os4 from "os";
|
|
7459
|
+
// packages/provider-codex/src/image-provider-client.ts
|
|
7205
7460
|
import path7 from "path";
|
|
7206
|
-
var
|
|
7207
|
-
var
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
var RUN_WEBSITES_FILE_NAME = "websites.json";
|
|
7211
|
-
var RUN_COMMANDS_SCHEMA_VERSION = 1;
|
|
7212
|
-
var RUN_WEBSITES_SCHEMA_VERSION = 1;
|
|
7213
|
-
var MAX_TERMINAL_CHUNKS = 1200;
|
|
7214
|
-
var MAX_TERMINAL_PREVIEW_LINES = 5;
|
|
7215
|
-
var NODE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
7216
|
-
var PYTHON_COMMAND_PATTERN = /^\s*(?:py(?:\.exe)?|python(?:\d+(?:\.\d+)?)?(?:\.exe)?)\b/i;
|
|
7217
|
-
var isoNow3 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
7218
|
-
var normalizeProjectPathKey = (projectPath) => {
|
|
7219
|
-
const resolved = path7.resolve(projectPath);
|
|
7220
|
-
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7461
|
+
var toBlobBytes = (buffer) => Uint8Array.from(buffer);
|
|
7462
|
+
var createProviderUrl = (baseUrl, endpointPath) => {
|
|
7463
|
+
const normalizedBaseUrl = `${baseUrl.replace(/\/+$/, "")}/`;
|
|
7464
|
+
return new URL(endpointPath.replace(/^\/+/, ""), normalizedBaseUrl);
|
|
7221
7465
|
};
|
|
7222
|
-
var
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7466
|
+
var toErrorMessage = (fallback, body) => {
|
|
7467
|
+
if (body && typeof body === "object") {
|
|
7468
|
+
const message = body.error;
|
|
7469
|
+
if (typeof message === "string" && message.trim()) {
|
|
7470
|
+
return message;
|
|
7471
|
+
}
|
|
7472
|
+
if (message && typeof message === "object" && typeof message.message === "string" && message.message.trim()) {
|
|
7473
|
+
return message.message;
|
|
7474
|
+
}
|
|
7230
7475
|
}
|
|
7231
|
-
|
|
7232
|
-
|
|
7476
|
+
return fallback;
|
|
7477
|
+
};
|
|
7478
|
+
var previewText = (value, maxLength = 160) => {
|
|
7479
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
7480
|
+
if (normalized.length <= maxLength) {
|
|
7481
|
+
return normalized;
|
|
7233
7482
|
}
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7483
|
+
return `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
|
|
7484
|
+
};
|
|
7485
|
+
var redactApiKey = (apiKey) => {
|
|
7486
|
+
const trimmed = apiKey.trim();
|
|
7487
|
+
if (trimmed.length <= 8) {
|
|
7488
|
+
return "***";
|
|
7237
7489
|
}
|
|
7238
|
-
return
|
|
7490
|
+
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
|
|
7239
7491
|
};
|
|
7240
|
-
var
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7492
|
+
var summarizeImageRequest = (input, endpoint, prompt, mappedSize) => ({
|
|
7493
|
+
vendor: input.vendor,
|
|
7494
|
+
endpoint,
|
|
7495
|
+
baseUrl: input.baseUrl,
|
|
7496
|
+
model: input.model,
|
|
7497
|
+
promptPreview: previewText(prompt),
|
|
7498
|
+
promptLength: prompt.length,
|
|
7499
|
+
quality: input.quality,
|
|
7500
|
+
size: input.size,
|
|
7501
|
+
mappedSize,
|
|
7502
|
+
aspectRatio: input.aspectRatio,
|
|
7503
|
+
outputFormat: input.outputFormat,
|
|
7504
|
+
outputCompression: input.outputCompression ?? null,
|
|
7505
|
+
background: input.background,
|
|
7506
|
+
moderation: input.moderation,
|
|
7507
|
+
n: input.n,
|
|
7508
|
+
inputCount: input.inputs?.length ?? 0,
|
|
7509
|
+
hasMask: Boolean(input.mask),
|
|
7510
|
+
apiKeyPreview: redactApiKey(input.apiKey)
|
|
7511
|
+
});
|
|
7512
|
+
var summarizeResponseBody = (body) => {
|
|
7513
|
+
if (!body || typeof body !== "object") {
|
|
7514
|
+
return body;
|
|
7244
7515
|
}
|
|
7245
|
-
|
|
7516
|
+
const record = body;
|
|
7517
|
+
const data = Array.isArray(record.data) ? record.data.map((item, index) => {
|
|
7518
|
+
if (!item || typeof item !== "object") {
|
|
7519
|
+
return { index, type: typeof item };
|
|
7520
|
+
}
|
|
7521
|
+
const imageItem = item;
|
|
7522
|
+
return {
|
|
7523
|
+
index,
|
|
7524
|
+
hasB64Json: typeof imageItem.b64_json === "string" && imageItem.b64_json.length > 0,
|
|
7525
|
+
url: typeof imageItem.url === "string" && imageItem.url.trim() ? imageItem.url : null
|
|
7526
|
+
};
|
|
7527
|
+
}) : void 0;
|
|
7528
|
+
const error = typeof record.error === "string" ? record.error : record.error && typeof record.error === "object" ? {
|
|
7529
|
+
message: typeof record.error.message === "string" ? record.error.message : null,
|
|
7530
|
+
type: typeof record.error.type === "string" ? record.error.type : null,
|
|
7531
|
+
code: typeof record.error.code === "string" ? record.error.code : null
|
|
7532
|
+
} : void 0;
|
|
7533
|
+
return {
|
|
7534
|
+
created: typeof record.created === "number" || typeof record.created === "string" ? record.created : void 0,
|
|
7535
|
+
data,
|
|
7536
|
+
error
|
|
7537
|
+
};
|
|
7246
7538
|
};
|
|
7247
|
-
var
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7539
|
+
var logImageProviderRequest = (summary) => {
|
|
7540
|
+
console.info("[image-provider] request", summary);
|
|
7541
|
+
};
|
|
7542
|
+
var logImageProviderResponse = (summary, response, body) => {
|
|
7543
|
+
const payload = {
|
|
7544
|
+
endpoint: summary.endpoint,
|
|
7545
|
+
vendor: summary.vendor,
|
|
7546
|
+
model: summary.model,
|
|
7547
|
+
status: response.status,
|
|
7548
|
+
ok: response.ok,
|
|
7549
|
+
body: summarizeResponseBody(body)
|
|
7550
|
+
};
|
|
7551
|
+
if (response.ok) {
|
|
7552
|
+
console.info("[image-provider] response", payload);
|
|
7553
|
+
return;
|
|
7252
7554
|
}
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7555
|
+
console.error("[image-provider] response", payload);
|
|
7556
|
+
};
|
|
7557
|
+
var mapAspectRatioToSize = (size, aspectRatio) => {
|
|
7558
|
+
if (size !== "auto") {
|
|
7559
|
+
const sizeTier = (() => {
|
|
7560
|
+
switch (size) {
|
|
7561
|
+
case "1024x1024":
|
|
7562
|
+
case "1536x1024":
|
|
7563
|
+
case "1024x1536":
|
|
7564
|
+
return "1k";
|
|
7565
|
+
case "2048x2048":
|
|
7566
|
+
case "2048x1152":
|
|
7567
|
+
case "1152x2048":
|
|
7568
|
+
return "2k";
|
|
7569
|
+
case "3840x2160":
|
|
7570
|
+
case "2160x3840":
|
|
7571
|
+
return "4k";
|
|
7572
|
+
default:
|
|
7573
|
+
return null;
|
|
7574
|
+
}
|
|
7575
|
+
})();
|
|
7576
|
+
if (sizeTier === "1k") {
|
|
7577
|
+
switch (aspectRatio) {
|
|
7578
|
+
case "1:1":
|
|
7579
|
+
case "auto":
|
|
7580
|
+
return "1024x1024";
|
|
7581
|
+
case "4:3":
|
|
7582
|
+
return "1024x768";
|
|
7583
|
+
case "3:4":
|
|
7584
|
+
return "768x1024";
|
|
7585
|
+
case "16:9":
|
|
7586
|
+
return "1024x576";
|
|
7587
|
+
case "9:16":
|
|
7588
|
+
return "576x1024";
|
|
7589
|
+
default:
|
|
7590
|
+
return "1024x1024";
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
if (sizeTier === "2k") {
|
|
7594
|
+
switch (aspectRatio) {
|
|
7595
|
+
case "1:1":
|
|
7596
|
+
case "auto":
|
|
7597
|
+
return "2048x2048";
|
|
7598
|
+
case "4:3":
|
|
7599
|
+
return "2048x1536";
|
|
7600
|
+
case "3:4":
|
|
7601
|
+
return "1536x2048";
|
|
7602
|
+
case "16:9":
|
|
7603
|
+
return "2048x1152";
|
|
7604
|
+
case "9:16":
|
|
7605
|
+
return "1152x2048";
|
|
7606
|
+
default:
|
|
7607
|
+
return "2048x2048";
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
7610
|
+
if (sizeTier === "4k") {
|
|
7611
|
+
switch (aspectRatio) {
|
|
7612
|
+
case "1:1":
|
|
7613
|
+
return "3840x3840";
|
|
7614
|
+
case "4:3":
|
|
7615
|
+
return "3840x2880";
|
|
7616
|
+
case "3:4":
|
|
7617
|
+
return "2880x3840";
|
|
7618
|
+
case "16:9":
|
|
7619
|
+
case "auto":
|
|
7620
|
+
return "3840x2160";
|
|
7621
|
+
case "9:16":
|
|
7622
|
+
return "2160x3840";
|
|
7623
|
+
default:
|
|
7624
|
+
return "3840x2160";
|
|
7625
|
+
}
|
|
7257
7626
|
}
|
|
7258
|
-
return
|
|
7259
|
-
}
|
|
7260
|
-
|
|
7627
|
+
return size;
|
|
7628
|
+
}
|
|
7629
|
+
if (aspectRatio === "1:1") {
|
|
7630
|
+
return "1024x1024";
|
|
7261
7631
|
}
|
|
7632
|
+
if (aspectRatio === "16:9" || aspectRatio === "4:3") {
|
|
7633
|
+
return "1536x1024";
|
|
7634
|
+
}
|
|
7635
|
+
if (aspectRatio === "9:16" || aspectRatio === "3:4") {
|
|
7636
|
+
return "1024x1536";
|
|
7637
|
+
}
|
|
7638
|
+
return "auto";
|
|
7262
7639
|
};
|
|
7263
|
-
var
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
} catch {
|
|
7268
|
-
return null;
|
|
7640
|
+
var buildPromptSuffix = (options) => {
|
|
7641
|
+
const parts = [];
|
|
7642
|
+
if (options.aspectRatio !== "auto" && options.size === "auto") {
|
|
7643
|
+
parts.push(`?????${options.aspectRatio}`);
|
|
7269
7644
|
}
|
|
7645
|
+
return parts.join("?");
|
|
7270
7646
|
};
|
|
7271
|
-
var
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7647
|
+
var createDataUri = (mimeType, buffer) => `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
7648
|
+
var mapSizeToXaiResolution = (size) => {
|
|
7649
|
+
switch (size) {
|
|
7650
|
+
case "auto":
|
|
7651
|
+
return null;
|
|
7652
|
+
case "1024x1024":
|
|
7653
|
+
case "1536x1024":
|
|
7654
|
+
case "1024x1536":
|
|
7655
|
+
return "1k";
|
|
7656
|
+
case "2048x2048":
|
|
7657
|
+
case "2048x1152":
|
|
7658
|
+
case "1152x2048":
|
|
7659
|
+
return "2k";
|
|
7660
|
+
case "3840x2160":
|
|
7661
|
+
case "2160x3840":
|
|
7662
|
+
throw new Error("xAI \u5F53\u524D\u56FE\u7247 API \u4EC5\u652F\u6301 1k / 2k \u5206\u8FA8\u7387\uFF0C\u6682\u4E0D\u652F\u6301 4K \u8F93\u51FA\u3002");
|
|
7663
|
+
default:
|
|
7664
|
+
return null;
|
|
7665
|
+
}
|
|
7275
7666
|
};
|
|
7276
|
-
var
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7667
|
+
var createOutputOptions = (input) => ({
|
|
7668
|
+
output_format: input.outputFormat,
|
|
7669
|
+
...input.outputFormat === "jpeg" || input.outputFormat === "webp" ? { output_compression: input.outputCompression ?? 85 } : {},
|
|
7670
|
+
background: input.background,
|
|
7671
|
+
moderation: input.moderation
|
|
7672
|
+
});
|
|
7673
|
+
var shouldFallbackToMultipartForXaiEdit = (body) => {
|
|
7674
|
+
if (!body || typeof body !== "object") {
|
|
7675
|
+
return false;
|
|
7280
7676
|
}
|
|
7281
|
-
const
|
|
7282
|
-
const
|
|
7283
|
-
|
|
7284
|
-
|
|
7677
|
+
const error = body.error;
|
|
7678
|
+
const message = typeof error === "string" ? error : error && typeof error === "object" && typeof error.message === "string" ? error.message : "";
|
|
7679
|
+
const code = error && typeof error === "object" && typeof error.code === "string" ? error.code : "";
|
|
7680
|
+
const normalizedMessage = message.toLowerCase();
|
|
7681
|
+
return normalizedMessage.includes("failed to parse multipart form") || code === "convert_request_failed";
|
|
7682
|
+
};
|
|
7683
|
+
var createOpenAiCompatibleEditForm = (input, prompt, mappedSize) => {
|
|
7684
|
+
const form = new FormData();
|
|
7685
|
+
form.set("model", input.model);
|
|
7686
|
+
form.set("prompt", prompt);
|
|
7687
|
+
form.set("quality", input.quality);
|
|
7688
|
+
form.set("size", mappedSize);
|
|
7689
|
+
form.set("n", String(input.n));
|
|
7690
|
+
form.set("output_format", input.outputFormat);
|
|
7691
|
+
if (input.outputFormat === "jpeg" || input.outputFormat === "webp") {
|
|
7692
|
+
form.set("output_compression", String(input.outputCompression ?? 85));
|
|
7693
|
+
}
|
|
7694
|
+
form.set("background", input.background);
|
|
7695
|
+
form.set("moderation", input.moderation);
|
|
7696
|
+
if (input.model !== "gpt-image-2" && input.inputFidelity) {
|
|
7697
|
+
form.set("input_fidelity", input.inputFidelity);
|
|
7698
|
+
}
|
|
7699
|
+
for (const image of input.inputs ?? []) {
|
|
7700
|
+
const blob = new Blob([toBlobBytes(image.buffer)], {
|
|
7701
|
+
type: image.mimeType
|
|
7702
|
+
});
|
|
7703
|
+
form.append("image[]", blob, image.name);
|
|
7285
7704
|
}
|
|
7286
|
-
if (
|
|
7287
|
-
|
|
7705
|
+
if (input.mask) {
|
|
7706
|
+
const maskBlob = new Blob([toBlobBytes(input.mask.buffer)], {
|
|
7707
|
+
type: input.mask.mimeType
|
|
7708
|
+
});
|
|
7709
|
+
form.set("mask", maskBlob, input.mask.name);
|
|
7288
7710
|
}
|
|
7289
|
-
return
|
|
7711
|
+
return form;
|
|
7290
7712
|
};
|
|
7291
|
-
var
|
|
7292
|
-
|
|
7293
|
-
|
|
7713
|
+
var toOutputMimeType = (outputFormat) => {
|
|
7714
|
+
switch (outputFormat) {
|
|
7715
|
+
case "jpeg":
|
|
7716
|
+
return "image/jpeg";
|
|
7717
|
+
case "webp":
|
|
7718
|
+
return "image/webp";
|
|
7719
|
+
default:
|
|
7720
|
+
return "image/png";
|
|
7721
|
+
}
|
|
7294
7722
|
};
|
|
7295
|
-
var
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7723
|
+
var toOutputExtension = (outputFormat) => {
|
|
7724
|
+
switch (outputFormat) {
|
|
7725
|
+
case "jpeg":
|
|
7726
|
+
return ".jpg";
|
|
7727
|
+
case "webp":
|
|
7728
|
+
return ".webp";
|
|
7729
|
+
default:
|
|
7730
|
+
return ".png";
|
|
7299
7731
|
}
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7732
|
+
};
|
|
7733
|
+
var parseBase64Response = async (response, fallbackName, requestSummary, outputFormat) => {
|
|
7734
|
+
const json = await response.json().catch(() => null);
|
|
7735
|
+
logImageProviderResponse(requestSummary, response, json);
|
|
7736
|
+
if (!response.ok) {
|
|
7737
|
+
throw new Error(
|
|
7738
|
+
toErrorMessage(`Image request failed with ${response.status}`, json)
|
|
7739
|
+
);
|
|
7303
7740
|
}
|
|
7304
|
-
|
|
7741
|
+
const items = Array.isArray(json?.data) ? json.data : [];
|
|
7742
|
+
const outputs = [];
|
|
7743
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
7744
|
+
const item = items[index];
|
|
7745
|
+
if (typeof item?.b64_json === "string" && item.b64_json.trim()) {
|
|
7746
|
+
outputs.push({
|
|
7747
|
+
buffer: Buffer.from(item.b64_json, "base64"),
|
|
7748
|
+
mimeType: toOutputMimeType(outputFormat),
|
|
7749
|
+
name: `${fallbackName}-${index + 1}${toOutputExtension(outputFormat)}`
|
|
7750
|
+
});
|
|
7751
|
+
continue;
|
|
7752
|
+
}
|
|
7753
|
+
if (typeof item?.url === "string" && item.url.trim()) {
|
|
7754
|
+
const imageResponse = await fetch(item.url);
|
|
7755
|
+
if (!imageResponse.ok) {
|
|
7756
|
+
throw new Error(`Unable to download generated image (${imageResponse.status}).`);
|
|
7757
|
+
}
|
|
7758
|
+
const arrayBuffer = await imageResponse.arrayBuffer();
|
|
7759
|
+
const mimeType = imageResponse.headers.get("content-type") || "image/png";
|
|
7760
|
+
const extension = mimeType === "image/jpeg" ? ".jpg" : mimeType === "image/webp" ? ".webp" : ".png";
|
|
7761
|
+
outputs.push({
|
|
7762
|
+
buffer: Buffer.from(arrayBuffer),
|
|
7763
|
+
mimeType,
|
|
7764
|
+
name: `${fallbackName}-${index + 1}${extension}`
|
|
7765
|
+
});
|
|
7766
|
+
}
|
|
7767
|
+
}
|
|
7768
|
+
if (outputs.length === 0) {
|
|
7769
|
+
throw new Error("Image provider did not return any image data.");
|
|
7770
|
+
}
|
|
7771
|
+
return outputs;
|
|
7305
7772
|
};
|
|
7306
|
-
var
|
|
7307
|
-
const
|
|
7308
|
-
|
|
7773
|
+
var createHeaders = (apiKey, contentType) => {
|
|
7774
|
+
const headers = new Headers();
|
|
7775
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
7776
|
+
if (contentType) {
|
|
7777
|
+
headers.set("content-type", contentType);
|
|
7778
|
+
}
|
|
7779
|
+
return headers;
|
|
7309
7780
|
};
|
|
7310
|
-
var
|
|
7311
|
-
const
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7781
|
+
var listImageProviderModels = async (input) => {
|
|
7782
|
+
const response = await fetch(createProviderUrl(input.baseUrl, "/models"), {
|
|
7783
|
+
headers: createHeaders(input.apiKey)
|
|
7784
|
+
});
|
|
7785
|
+
const json = await response.json().catch(() => null);
|
|
7786
|
+
if (!response.ok) {
|
|
7787
|
+
throw new Error(
|
|
7788
|
+
toErrorMessage(`Model list request failed with ${response.status}`, json)
|
|
7789
|
+
);
|
|
7319
7790
|
}
|
|
7320
|
-
return 0;
|
|
7791
|
+
return (Array.isArray(json?.data) ? json.data : []).map((model) => typeof model?.id === "string" ? model.id.trim() : "").filter((id) => id.length > 0).map((id) => ({ id, label: id }));
|
|
7321
7792
|
};
|
|
7322
|
-
var
|
|
7793
|
+
var testImageProviderConnection = async (input) => {
|
|
7794
|
+
const response = await fetch(createProviderUrl(input.baseUrl, "/models"), {
|
|
7795
|
+
headers: createHeaders(input.apiKey)
|
|
7796
|
+
});
|
|
7797
|
+
const json = await response.json().catch(() => null);
|
|
7798
|
+
if (!response.ok) {
|
|
7799
|
+
throw new Error(
|
|
7800
|
+
toErrorMessage(`Connection test failed with ${response.status}`, json)
|
|
7801
|
+
);
|
|
7802
|
+
}
|
|
7803
|
+
const modelAvailable = Array.isArray(json?.data) ? json.data.some((item) => item?.id === input.model) : false;
|
|
7804
|
+
return {
|
|
7805
|
+
modelAvailable
|
|
7806
|
+
};
|
|
7807
|
+
};
|
|
7808
|
+
var submitXaiImageRequest = async (input, prompt, mappedSize, promptSuffix) => {
|
|
7809
|
+
const hasInputs = Array.isArray(input.inputs) && input.inputs.length > 0;
|
|
7810
|
+
if (input.mask) {
|
|
7811
|
+
throw new Error("\u5F53\u524D xAI / Grok \u56FE\u7247\u6E20\u9053\u6682\u4E0D\u652F\u6301\u8499\u7248\u7F16\u8F91\u3002");
|
|
7812
|
+
}
|
|
7813
|
+
const resolution = mapSizeToXaiResolution(input.size);
|
|
7814
|
+
if (!hasInputs) {
|
|
7815
|
+
const requestSummary2 = summarizeImageRequest(
|
|
7816
|
+
input,
|
|
7817
|
+
"/images/generations",
|
|
7818
|
+
prompt,
|
|
7819
|
+
resolution ?? "auto"
|
|
7820
|
+
);
|
|
7821
|
+
logImageProviderRequest(requestSummary2);
|
|
7822
|
+
const response2 = await fetch(createProviderUrl(input.baseUrl, "/images/generations"), {
|
|
7823
|
+
method: "POST",
|
|
7824
|
+
headers: createHeaders(input.apiKey, "application/json"),
|
|
7825
|
+
body: JSON.stringify({
|
|
7826
|
+
model: input.model,
|
|
7827
|
+
prompt,
|
|
7828
|
+
n: input.n,
|
|
7829
|
+
response_format: "b64_json",
|
|
7830
|
+
...input.aspectRatio !== "auto" ? { aspect_ratio: input.aspectRatio } : {},
|
|
7831
|
+
...resolution ? { resolution } : {}
|
|
7832
|
+
})
|
|
7833
|
+
});
|
|
7834
|
+
return {
|
|
7835
|
+
outputs: await parseBase64Response(response2, "generated-image", requestSummary2, "png"),
|
|
7836
|
+
promptSuffix: promptSuffix || null
|
|
7837
|
+
};
|
|
7838
|
+
}
|
|
7839
|
+
const images = (input.inputs ?? []).map((image) => ({
|
|
7840
|
+
type: "image_url",
|
|
7841
|
+
url: createDataUri(image.mimeType, image.buffer)
|
|
7842
|
+
}));
|
|
7843
|
+
const requestSummary = summarizeImageRequest(
|
|
7844
|
+
input,
|
|
7845
|
+
"/images/edits",
|
|
7846
|
+
prompt,
|
|
7847
|
+
resolution ?? "auto"
|
|
7848
|
+
);
|
|
7849
|
+
logImageProviderRequest(requestSummary);
|
|
7850
|
+
const response = await fetch(createProviderUrl(input.baseUrl, "/images/edits"), {
|
|
7851
|
+
method: "POST",
|
|
7852
|
+
headers: createHeaders(input.apiKey, "application/json"),
|
|
7853
|
+
body: JSON.stringify({
|
|
7854
|
+
model: input.model,
|
|
7855
|
+
prompt,
|
|
7856
|
+
n: input.n,
|
|
7857
|
+
...images.length === 1 ? { image: images[0] } : { images },
|
|
7858
|
+
...images.length > 1 && input.aspectRatio !== "auto" ? { aspect_ratio: input.aspectRatio } : {},
|
|
7859
|
+
...resolution ? { resolution } : {}
|
|
7860
|
+
})
|
|
7861
|
+
});
|
|
7862
|
+
if (!response.ok) {
|
|
7863
|
+
const errorBody = await response.clone().json().catch(() => null);
|
|
7864
|
+
logImageProviderResponse(requestSummary, response, errorBody);
|
|
7865
|
+
if (shouldFallbackToMultipartForXaiEdit(errorBody)) {
|
|
7866
|
+
const multipartSummary = summarizeImageRequest(
|
|
7867
|
+
input,
|
|
7868
|
+
"/images/edits (multipart fallback)",
|
|
7869
|
+
prompt,
|
|
7870
|
+
mappedSize
|
|
7871
|
+
);
|
|
7872
|
+
logImageProviderRequest(multipartSummary);
|
|
7873
|
+
console.warn("[image-provider] xai edit fallback to multipart", {
|
|
7874
|
+
model: input.model,
|
|
7875
|
+
baseUrl: input.baseUrl
|
|
7876
|
+
});
|
|
7877
|
+
const fallbackResponse = await fetch(createProviderUrl(input.baseUrl, "/images/edits"), {
|
|
7878
|
+
method: "POST",
|
|
7879
|
+
headers: createHeaders(input.apiKey),
|
|
7880
|
+
body: createOpenAiCompatibleEditForm(input, prompt, mappedSize)
|
|
7881
|
+
});
|
|
7882
|
+
return {
|
|
7883
|
+
outputs: await parseBase64Response(
|
|
7884
|
+
fallbackResponse,
|
|
7885
|
+
"edited-image",
|
|
7886
|
+
multipartSummary,
|
|
7887
|
+
input.outputFormat
|
|
7888
|
+
),
|
|
7889
|
+
promptSuffix: promptSuffix || null
|
|
7890
|
+
};
|
|
7891
|
+
}
|
|
7892
|
+
throw new Error(
|
|
7893
|
+
toErrorMessage(`Image request failed with ${response.status}`, errorBody)
|
|
7894
|
+
);
|
|
7895
|
+
}
|
|
7896
|
+
return {
|
|
7897
|
+
outputs: await parseBase64Response(
|
|
7898
|
+
response,
|
|
7899
|
+
"edited-image",
|
|
7900
|
+
requestSummary,
|
|
7901
|
+
input.outputFormat
|
|
7902
|
+
),
|
|
7903
|
+
promptSuffix: promptSuffix || null
|
|
7904
|
+
};
|
|
7905
|
+
};
|
|
7906
|
+
var submitImageRequest = async (input) => {
|
|
7907
|
+
const promptSuffix = buildPromptSuffix({
|
|
7908
|
+
size: input.size,
|
|
7909
|
+
aspectRatio: input.aspectRatio
|
|
7910
|
+
});
|
|
7911
|
+
const prompt = promptSuffix ? `${input.prompt.trim()}
|
|
7912
|
+
|
|
7913
|
+
${promptSuffix}` : input.prompt.trim();
|
|
7914
|
+
const mappedSize = mapAspectRatioToSize(input.size, input.aspectRatio);
|
|
7915
|
+
const hasInputs = Array.isArray(input.inputs) && input.inputs.length > 0;
|
|
7916
|
+
if (input.vendor === "xai") {
|
|
7917
|
+
return submitXaiImageRequest(input, prompt, mappedSize, promptSuffix);
|
|
7918
|
+
}
|
|
7919
|
+
if (input.vendor === "gemini") {
|
|
7920
|
+
throw new Error("\u5F53\u524D Gemini \u56FE\u7247\u6E20\u9053\u9700\u8981\u4E13\u7528 API \u9002\u914D\uFF0C\u6682\u4E0D\u652F\u6301\u76F4\u63A5\u8C03\u7528\u3002");
|
|
7921
|
+
}
|
|
7922
|
+
if (!hasInputs) {
|
|
7923
|
+
const requestSummary2 = summarizeImageRequest(
|
|
7924
|
+
input,
|
|
7925
|
+
"/images/generations",
|
|
7926
|
+
prompt,
|
|
7927
|
+
mappedSize
|
|
7928
|
+
);
|
|
7929
|
+
logImageProviderRequest(requestSummary2);
|
|
7930
|
+
const response2 = await fetch(createProviderUrl(input.baseUrl, "/images/generations"), {
|
|
7931
|
+
method: "POST",
|
|
7932
|
+
headers: createHeaders(input.apiKey, "application/json"),
|
|
7933
|
+
body: JSON.stringify({
|
|
7934
|
+
model: input.model,
|
|
7935
|
+
prompt,
|
|
7936
|
+
quality: input.quality,
|
|
7937
|
+
size: mappedSize,
|
|
7938
|
+
n: input.n,
|
|
7939
|
+
...createOutputOptions(input)
|
|
7940
|
+
})
|
|
7941
|
+
});
|
|
7942
|
+
return {
|
|
7943
|
+
outputs: await parseBase64Response(
|
|
7944
|
+
response2,
|
|
7945
|
+
"generated-image",
|
|
7946
|
+
requestSummary2,
|
|
7947
|
+
input.outputFormat
|
|
7948
|
+
),
|
|
7949
|
+
promptSuffix: promptSuffix || null
|
|
7950
|
+
};
|
|
7951
|
+
}
|
|
7952
|
+
const form = createOpenAiCompatibleEditForm(input, prompt, mappedSize);
|
|
7953
|
+
const requestSummary = summarizeImageRequest(
|
|
7954
|
+
input,
|
|
7955
|
+
"/images/edits",
|
|
7956
|
+
prompt,
|
|
7957
|
+
mappedSize
|
|
7958
|
+
);
|
|
7959
|
+
logImageProviderRequest(requestSummary);
|
|
7960
|
+
const response = await fetch(createProviderUrl(input.baseUrl, "/images/edits"), {
|
|
7961
|
+
method: "POST",
|
|
7962
|
+
headers: createHeaders(input.apiKey),
|
|
7963
|
+
body: form
|
|
7964
|
+
});
|
|
7965
|
+
return {
|
|
7966
|
+
outputs: await parseBase64Response(
|
|
7967
|
+
response,
|
|
7968
|
+
"edited-image",
|
|
7969
|
+
requestSummary,
|
|
7970
|
+
input.outputFormat
|
|
7971
|
+
),
|
|
7972
|
+
promptSuffix: promptSuffix || null
|
|
7973
|
+
};
|
|
7974
|
+
};
|
|
7975
|
+
var toMimeTypeFromFilename = (fileName) => {
|
|
7976
|
+
const extension = path7.extname(fileName).toLowerCase();
|
|
7977
|
+
switch (extension) {
|
|
7978
|
+
case ".jpg":
|
|
7979
|
+
case ".jpeg":
|
|
7980
|
+
return "image/jpeg";
|
|
7981
|
+
case ".webp":
|
|
7982
|
+
return "image/webp";
|
|
7983
|
+
case ".gif":
|
|
7984
|
+
return "image/gif";
|
|
7985
|
+
default:
|
|
7986
|
+
return "image/png";
|
|
7987
|
+
}
|
|
7988
|
+
};
|
|
7989
|
+
|
|
7990
|
+
// packages/provider-codex/src/image-session-store.ts
|
|
7991
|
+
import { randomUUID } from "crypto";
|
|
7992
|
+
import fs6 from "fs/promises";
|
|
7993
|
+
import path8 from "path";
|
|
7994
|
+
var STORE_VERSION = 2;
|
|
7995
|
+
var DEFAULT_STATE = {
|
|
7996
|
+
version: STORE_VERSION,
|
|
7997
|
+
settings: {
|
|
7998
|
+
active_channel_id: null,
|
|
7999
|
+
channels: [],
|
|
8000
|
+
updated_at: null
|
|
8001
|
+
},
|
|
8002
|
+
sessions: []
|
|
8003
|
+
};
|
|
8004
|
+
var isoNow3 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
8005
|
+
var DEFAULT_IMAGE_PROVIDER_MODELS2 = [
|
|
8006
|
+
{ id: "gpt-image-2", vendor: "openai", label: "gpt-image-2", enabled: true }
|
|
8007
|
+
];
|
|
8008
|
+
var imageModelVendors = /* @__PURE__ */ new Set(["openai", "xai", "gemini"]);
|
|
8009
|
+
var sanitizeStoredModels = (models) => {
|
|
8010
|
+
const source = Array.isArray(models) ? models : DEFAULT_IMAGE_PROVIDER_MODELS2;
|
|
8011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8012
|
+
const sanitized = source.flatMap((model) => {
|
|
8013
|
+
if (!model || typeof model !== "object") {
|
|
8014
|
+
return [];
|
|
8015
|
+
}
|
|
8016
|
+
const record = model;
|
|
8017
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
8018
|
+
if (!id || seen.has(id)) {
|
|
8019
|
+
return [];
|
|
8020
|
+
}
|
|
8021
|
+
seen.add(id);
|
|
8022
|
+
const vendor = imageModelVendors.has(record.vendor) ? record.vendor : "openai";
|
|
8023
|
+
const label = typeof record.label === "string" && record.label.trim() ? record.label.trim() : id;
|
|
8024
|
+
const enabled = record.enabled !== false;
|
|
8025
|
+
return [{ id, vendor, label, enabled }];
|
|
8026
|
+
});
|
|
8027
|
+
if (sanitized.length === 0) {
|
|
8028
|
+
return DEFAULT_IMAGE_PROVIDER_MODELS2;
|
|
8029
|
+
}
|
|
8030
|
+
if (sanitized.some((model) => model.enabled)) {
|
|
8031
|
+
return sanitized;
|
|
8032
|
+
}
|
|
8033
|
+
return sanitized.map(
|
|
8034
|
+
(model, index) => index === 0 ? { ...model, enabled: true } : model
|
|
8035
|
+
);
|
|
8036
|
+
};
|
|
8037
|
+
var sanitizeStoredChannel = (channel) => {
|
|
8038
|
+
const id = typeof channel?.id === "string" ? channel.id.trim() : "";
|
|
8039
|
+
if (!id) {
|
|
8040
|
+
return null;
|
|
8041
|
+
}
|
|
8042
|
+
return {
|
|
8043
|
+
id,
|
|
8044
|
+
remark: typeof channel?.remark === "string" && channel.remark.trim() ? channel.remark.trim() : "Unnamed channel",
|
|
8045
|
+
base_url: typeof channel?.base_url === "string" ? channel.base_url : "",
|
|
8046
|
+
models: sanitizeStoredModels(channel?.models),
|
|
8047
|
+
api_key: typeof channel?.api_key === "string" && channel.api_key.trim() ? channel.api_key : null,
|
|
8048
|
+
updated_at: typeof channel?.updated_at === "string" ? channel.updated_at : null
|
|
8049
|
+
};
|
|
8050
|
+
};
|
|
8051
|
+
var sanitizeStoredSettings = (settings) => {
|
|
8052
|
+
const channels = Array.isArray(settings?.channels) ? settings.channels.map((channel) => sanitizeStoredChannel(channel)).filter((channel) => Boolean(channel)) : [];
|
|
8053
|
+
const requestedActiveChannelId = typeof settings?.active_channel_id === "string" && settings.active_channel_id.trim() ? settings.active_channel_id : null;
|
|
8054
|
+
return {
|
|
8055
|
+
active_channel_id: channels.some((channel) => channel.id === requestedActiveChannelId) ? requestedActiveChannelId : channels[0]?.id ?? null,
|
|
8056
|
+
channels,
|
|
8057
|
+
updated_at: typeof settings?.updated_at === "string" ? settings.updated_at : null
|
|
8058
|
+
};
|
|
8059
|
+
};
|
|
8060
|
+
var imageSessionCapability = (options) => {
|
|
8061
|
+
const archived = options?.archived === true;
|
|
8062
|
+
const running = options?.running === true;
|
|
8063
|
+
return {
|
|
8064
|
+
can_stream_live: false,
|
|
8065
|
+
can_send_input: !archived && !running,
|
|
8066
|
+
can_interrupt: false,
|
|
8067
|
+
can_approve: false,
|
|
8068
|
+
can_reject: false,
|
|
8069
|
+
can_show_git: false,
|
|
8070
|
+
can_show_terminal: false
|
|
8071
|
+
};
|
|
8072
|
+
};
|
|
8073
|
+
var summarizePrompt = (prompt) => {
|
|
8074
|
+
const normalized = prompt.trim().replace(/\s+/g, " ");
|
|
8075
|
+
if (!normalized) {
|
|
8076
|
+
return "";
|
|
8077
|
+
}
|
|
8078
|
+
return normalized.length > 88 ? `${normalized.slice(0, 87).trimEnd()}...` : normalized;
|
|
8079
|
+
};
|
|
8080
|
+
var buildLatestAssistantMessage = (turn) => {
|
|
8081
|
+
if (!turn) {
|
|
8082
|
+
return null;
|
|
8083
|
+
}
|
|
8084
|
+
if (turn.status === "failed") {
|
|
8085
|
+
return turn.error ?? "Image generation failed";
|
|
8086
|
+
}
|
|
8087
|
+
if (turn.status === "pending") {
|
|
8088
|
+
return turn.operation === "edit" ? "Editing image..." : "Generating image...";
|
|
8089
|
+
}
|
|
8090
|
+
const outputCount = turn.output_asset_ids.length;
|
|
8091
|
+
if (outputCount <= 0) {
|
|
8092
|
+
return "Completed";
|
|
8093
|
+
}
|
|
8094
|
+
return `Generated ${outputCount} image(s)`;
|
|
8095
|
+
};
|
|
8096
|
+
var toSessionTitle = (title) => {
|
|
8097
|
+
const trimmed = title?.trim() ?? "";
|
|
8098
|
+
return trimmed || "New image";
|
|
8099
|
+
};
|
|
8100
|
+
var createSessionRef = (agentId, title) => {
|
|
8101
|
+
const timestamp = isoNow3();
|
|
8102
|
+
return {
|
|
8103
|
+
id: `image-session-${randomUUID()}`,
|
|
8104
|
+
agent_id: agentId,
|
|
8105
|
+
project_id: "",
|
|
8106
|
+
provider: "image",
|
|
8107
|
+
archived: false,
|
|
8108
|
+
title: toSessionTitle(title),
|
|
8109
|
+
mode: "managed",
|
|
8110
|
+
health: "active",
|
|
8111
|
+
branch: "image",
|
|
8112
|
+
worktree: "drawing",
|
|
8113
|
+
summary: "",
|
|
8114
|
+
latest_assistant_message: null,
|
|
8115
|
+
last_event_at: timestamp,
|
|
8116
|
+
pinned: false,
|
|
8117
|
+
run_state: "idle",
|
|
8118
|
+
run_state_changed_at: null,
|
|
8119
|
+
context_usage: null,
|
|
8120
|
+
subagent: null,
|
|
8121
|
+
capability: imageSessionCapability()
|
|
8122
|
+
};
|
|
8123
|
+
};
|
|
8124
|
+
var ensureDirectory = async (targetPath) => {
|
|
8125
|
+
await fs6.mkdir(targetPath, { recursive: true });
|
|
8126
|
+
};
|
|
8127
|
+
var parseDataUrl2 = (dataUrl) => {
|
|
8128
|
+
const match = /^data:([^;,]+)?(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(dataUrl.trim());
|
|
8129
|
+
if (!match) {
|
|
8130
|
+
throw new Error("Invalid image data URL.");
|
|
8131
|
+
}
|
|
8132
|
+
return {
|
|
8133
|
+
mimeType: match[1] ?? "application/octet-stream",
|
|
8134
|
+
buffer: Buffer.from(match[2] ?? "", "base64")
|
|
8135
|
+
};
|
|
8136
|
+
};
|
|
8137
|
+
var extensionFromMimeType = (mimeType) => {
|
|
8138
|
+
const normalized = mimeType?.trim().toLowerCase() ?? "";
|
|
8139
|
+
switch (normalized) {
|
|
8140
|
+
case "image/jpeg":
|
|
8141
|
+
return ".jpg";
|
|
8142
|
+
case "image/png":
|
|
8143
|
+
return ".png";
|
|
8144
|
+
case "image/webp":
|
|
8145
|
+
return ".webp";
|
|
8146
|
+
case "image/gif":
|
|
8147
|
+
return ".gif";
|
|
8148
|
+
case "image/svg+xml":
|
|
8149
|
+
return ".svg";
|
|
8150
|
+
default:
|
|
8151
|
+
return ".bin";
|
|
8152
|
+
}
|
|
8153
|
+
};
|
|
8154
|
+
var ImageSessionStore = class {
|
|
8155
|
+
constructor(storageFilePath, assetRootPath) {
|
|
8156
|
+
this.storageFilePath = storageFilePath;
|
|
8157
|
+
this.assetRootPath = assetRootPath;
|
|
8158
|
+
}
|
|
8159
|
+
state = DEFAULT_STATE;
|
|
8160
|
+
loaded = false;
|
|
8161
|
+
async load() {
|
|
8162
|
+
if (this.loaded) {
|
|
8163
|
+
return;
|
|
8164
|
+
}
|
|
8165
|
+
await ensureDirectory(path8.dirname(this.storageFilePath));
|
|
8166
|
+
await ensureDirectory(this.assetRootPath);
|
|
8167
|
+
try {
|
|
8168
|
+
const raw = await fs6.readFile(this.storageFilePath, "utf8");
|
|
8169
|
+
const parsed = JSON.parse(raw);
|
|
8170
|
+
const legacySettings = parsed.settings;
|
|
8171
|
+
const legacyBaseUrl = typeof legacySettings?.base_url === "string" ? legacySettings.base_url : "";
|
|
8172
|
+
const legacyApiKey = typeof legacySettings?.api_key === "string" && legacySettings.api_key.trim() ? legacySettings.api_key : null;
|
|
8173
|
+
const legacyUpdatedAt = typeof legacySettings?.updated_at === "string" ? legacySettings.updated_at : null;
|
|
8174
|
+
this.state = {
|
|
8175
|
+
version: STORE_VERSION,
|
|
8176
|
+
settings: parsed.version === 2 ? sanitizeStoredSettings(parsed.settings) : sanitizeStoredSettings({
|
|
8177
|
+
active_channel_id: legacyBaseUrl.trim() || legacyApiKey || legacyUpdatedAt ? "image-channel-default" : null,
|
|
8178
|
+
channels: legacyBaseUrl.trim() || legacyApiKey || legacyUpdatedAt ? [
|
|
8179
|
+
{
|
|
8180
|
+
id: "image-channel-default",
|
|
8181
|
+
remark: "\u699B\u6A3F\uE17B\u5A13\u72BB\u4EBE",
|
|
8182
|
+
base_url: legacyBaseUrl,
|
|
8183
|
+
models: DEFAULT_IMAGE_PROVIDER_MODELS2,
|
|
8184
|
+
api_key: legacyApiKey,
|
|
8185
|
+
updated_at: legacyUpdatedAt
|
|
8186
|
+
}
|
|
8187
|
+
] : [],
|
|
8188
|
+
updated_at: legacyUpdatedAt
|
|
8189
|
+
}),
|
|
8190
|
+
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : []
|
|
8191
|
+
};
|
|
8192
|
+
} catch {
|
|
8193
|
+
this.state = DEFAULT_STATE;
|
|
8194
|
+
await this.persist();
|
|
8195
|
+
}
|
|
8196
|
+
this.loaded = true;
|
|
8197
|
+
}
|
|
8198
|
+
async persist() {
|
|
8199
|
+
await ensureDirectory(path8.dirname(this.storageFilePath));
|
|
8200
|
+
await fs6.writeFile(this.storageFilePath, JSON.stringify(this.state, null, 2), "utf8");
|
|
8201
|
+
}
|
|
8202
|
+
ensureLoaded() {
|
|
8203
|
+
if (!this.loaded) {
|
|
8204
|
+
throw new Error("ImageSessionStore must be loaded before use.");
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
getSettings() {
|
|
8208
|
+
this.ensureLoaded();
|
|
8209
|
+
return this.state.settings;
|
|
8210
|
+
}
|
|
8211
|
+
getActiveSettingsChannel() {
|
|
8212
|
+
this.ensureLoaded();
|
|
8213
|
+
const activeChannelId = this.state.settings.active_channel_id;
|
|
8214
|
+
return this.state.settings.channels.find((channel) => channel.id === activeChannelId) ?? this.state.settings.channels[0] ?? null;
|
|
8215
|
+
}
|
|
8216
|
+
async updateSettings(input) {
|
|
8217
|
+
this.ensureLoaded();
|
|
8218
|
+
const existingChannelsById = new Map(
|
|
8219
|
+
this.state.settings.channels.map((channel) => [channel.id, channel])
|
|
8220
|
+
);
|
|
8221
|
+
const updatedAt = isoNow3();
|
|
8222
|
+
const nextChannels = input.channels.map((channel) => {
|
|
8223
|
+
const previous = existingChannelsById.get(channel.id);
|
|
8224
|
+
const nextApiKey = channel.apiKeyMode === "set" ? channel.apiKey?.trim() || null : channel.apiKeyMode === "clear" ? null : previous?.api_key ?? null;
|
|
8225
|
+
return {
|
|
8226
|
+
id: channel.id,
|
|
8227
|
+
remark: channel.remark.trim() || "Unnamed channel",
|
|
8228
|
+
base_url: channel.baseUrl,
|
|
8229
|
+
models: sanitizeStoredModels(channel.models),
|
|
8230
|
+
api_key: nextApiKey,
|
|
8231
|
+
updated_at: updatedAt
|
|
8232
|
+
};
|
|
8233
|
+
});
|
|
8234
|
+
this.state = {
|
|
8235
|
+
...this.state,
|
|
8236
|
+
settings: {
|
|
8237
|
+
active_channel_id: nextChannels.some(
|
|
8238
|
+
(channel) => channel.id === input.activeChannelId
|
|
8239
|
+
) ? input.activeChannelId : nextChannels[0]?.id ?? null,
|
|
8240
|
+
channels: nextChannels,
|
|
8241
|
+
updated_at: updatedAt
|
|
8242
|
+
}
|
|
8243
|
+
};
|
|
8244
|
+
await this.persist();
|
|
8245
|
+
return this.state.settings;
|
|
8246
|
+
}
|
|
8247
|
+
listSessions() {
|
|
8248
|
+
this.ensureLoaded();
|
|
8249
|
+
return [...this.state.sessions];
|
|
8250
|
+
}
|
|
8251
|
+
getSession(sessionId) {
|
|
8252
|
+
this.ensureLoaded();
|
|
8253
|
+
return this.state.sessions.find((session) => session.session.id === sessionId) ?? null;
|
|
8254
|
+
}
|
|
8255
|
+
async createSession(agentId, title) {
|
|
8256
|
+
this.ensureLoaded();
|
|
8257
|
+
const session = createSessionRef(agentId, title);
|
|
8258
|
+
const record = {
|
|
8259
|
+
session,
|
|
8260
|
+
created_at: session.last_event_at,
|
|
8261
|
+
updated_at: session.last_event_at,
|
|
8262
|
+
turns: [],
|
|
8263
|
+
assets: []
|
|
8264
|
+
};
|
|
8265
|
+
this.state = {
|
|
8266
|
+
...this.state,
|
|
8267
|
+
sessions: [record, ...this.state.sessions]
|
|
8268
|
+
};
|
|
8269
|
+
await this.persist();
|
|
8270
|
+
return record;
|
|
8271
|
+
}
|
|
8272
|
+
async updateSessionRecord(sessionId, updater) {
|
|
8273
|
+
this.ensureLoaded();
|
|
8274
|
+
let nextRecord = null;
|
|
8275
|
+
this.state = {
|
|
8276
|
+
...this.state,
|
|
8277
|
+
sessions: this.state.sessions.map((session) => {
|
|
8278
|
+
if (session.session.id !== sessionId) {
|
|
8279
|
+
return session;
|
|
8280
|
+
}
|
|
8281
|
+
nextRecord = updater(session);
|
|
8282
|
+
return nextRecord;
|
|
8283
|
+
})
|
|
8284
|
+
};
|
|
8285
|
+
if (!nextRecord) {
|
|
8286
|
+
throw new Error("Image session not found.");
|
|
8287
|
+
}
|
|
8288
|
+
await this.persist();
|
|
8289
|
+
return nextRecord;
|
|
8290
|
+
}
|
|
8291
|
+
async mutateSessionRef(sessionId, updater) {
|
|
8292
|
+
return this.updateSessionRecord(sessionId, (current) => ({
|
|
8293
|
+
...current,
|
|
8294
|
+
session: updater(current.session),
|
|
8295
|
+
updated_at: isoNow3()
|
|
8296
|
+
}));
|
|
8297
|
+
}
|
|
8298
|
+
async deleteSession(sessionId) {
|
|
8299
|
+
this.ensureLoaded();
|
|
8300
|
+
const existing = this.getSession(sessionId);
|
|
8301
|
+
if (!existing) {
|
|
8302
|
+
return false;
|
|
8303
|
+
}
|
|
8304
|
+
this.state = {
|
|
8305
|
+
...this.state,
|
|
8306
|
+
sessions: this.state.sessions.filter((session) => session.session.id !== sessionId)
|
|
8307
|
+
};
|
|
8308
|
+
await this.persist();
|
|
8309
|
+
await Promise.allSettled(
|
|
8310
|
+
existing.assets.map(
|
|
8311
|
+
(asset) => fs6.rm(path8.join(this.assetRootPath, asset.relative_path), { force: true })
|
|
8312
|
+
)
|
|
8313
|
+
);
|
|
8314
|
+
return true;
|
|
8315
|
+
}
|
|
8316
|
+
async saveDataUrlAsset(input) {
|
|
8317
|
+
const parsed = parseDataUrl2(input.dataUrl);
|
|
8318
|
+
const mimeType = input.mimeType?.trim() || parsed.mimeType;
|
|
8319
|
+
return this.saveBufferAsset({
|
|
8320
|
+
sessionId: input.sessionId,
|
|
8321
|
+
turnId: input.turnId ?? null,
|
|
8322
|
+
kind: input.kind,
|
|
8323
|
+
name: input.name,
|
|
8324
|
+
buffer: parsed.buffer,
|
|
8325
|
+
mimeType
|
|
8326
|
+
});
|
|
8327
|
+
}
|
|
8328
|
+
async saveBufferAsset(input) {
|
|
8329
|
+
const assetId = `image-asset-${randomUUID()}`;
|
|
8330
|
+
const extension = extensionFromMimeType(input.mimeType);
|
|
8331
|
+
const relativePath = path8.join(input.sessionId, `${assetId}${extension}`);
|
|
8332
|
+
const absolutePath = path8.join(this.assetRootPath, relativePath);
|
|
8333
|
+
await ensureDirectory(path8.dirname(absolutePath));
|
|
8334
|
+
await fs6.writeFile(absolutePath, input.buffer);
|
|
8335
|
+
const asset = {
|
|
8336
|
+
id: assetId,
|
|
8337
|
+
session_id: input.sessionId,
|
|
8338
|
+
turn_id: input.turnId ?? null,
|
|
8339
|
+
kind: input.kind,
|
|
8340
|
+
name: input.name,
|
|
8341
|
+
mime_type: input.mimeType ?? null,
|
|
8342
|
+
size_bytes: input.buffer.length,
|
|
8343
|
+
width: null,
|
|
8344
|
+
height: null,
|
|
8345
|
+
created_at: isoNow3(),
|
|
8346
|
+
relative_path: relativePath
|
|
8347
|
+
};
|
|
8348
|
+
const nextRecord = await this.updateSessionRecord(input.sessionId, (current) => ({
|
|
8349
|
+
...current,
|
|
8350
|
+
assets: [...current.assets, asset],
|
|
8351
|
+
updated_at: isoNow3()
|
|
8352
|
+
}));
|
|
8353
|
+
return nextRecord.assets.find((entry) => entry.id === asset.id) ?? asset;
|
|
8354
|
+
}
|
|
8355
|
+
findAsset(sessionId, assetId) {
|
|
8356
|
+
const session = this.getSession(sessionId);
|
|
8357
|
+
if (!session) {
|
|
8358
|
+
return null;
|
|
8359
|
+
}
|
|
8360
|
+
return session.assets.find((asset) => asset.id === assetId) ?? null;
|
|
8361
|
+
}
|
|
8362
|
+
async startTurn(input) {
|
|
8363
|
+
const turnId = `image-turn-${randomUUID()}`;
|
|
8364
|
+
const createdAt = isoNow3();
|
|
8365
|
+
return this.updateSessionRecord(input.sessionId, (current) => {
|
|
8366
|
+
const turn = {
|
|
8367
|
+
id: turnId,
|
|
8368
|
+
session_id: input.sessionId,
|
|
8369
|
+
created_at: createdAt,
|
|
8370
|
+
completed_at: null,
|
|
8371
|
+
prompt: input.prompt,
|
|
8372
|
+
prompt_suffix: input.promptSuffix ?? null,
|
|
8373
|
+
operation: input.operation,
|
|
8374
|
+
status: "pending",
|
|
8375
|
+
error: null,
|
|
8376
|
+
model: input.model,
|
|
8377
|
+
quality: input.quality,
|
|
8378
|
+
size: input.size,
|
|
8379
|
+
input_fidelity: input.inputFidelity ?? null,
|
|
8380
|
+
aspect_ratio: input.aspectRatio,
|
|
8381
|
+
output_format: input.outputFormat ?? "png",
|
|
8382
|
+
output_compression: input.outputCompression ?? null,
|
|
8383
|
+
background: input.background ?? "auto",
|
|
8384
|
+
moderation: input.moderation ?? "auto",
|
|
8385
|
+
n: input.n ?? 1,
|
|
8386
|
+
source_asset_ids: [...input.sourceAssetIds],
|
|
8387
|
+
output_asset_ids: []
|
|
8388
|
+
};
|
|
8389
|
+
const session = {
|
|
8390
|
+
...current.session,
|
|
8391
|
+
summary: summarizePrompt(input.prompt),
|
|
8392
|
+
latest_assistant_message: buildLatestAssistantMessage(turn),
|
|
8393
|
+
last_event_at: createdAt,
|
|
8394
|
+
run_state: "running",
|
|
8395
|
+
run_state_changed_at: createdAt,
|
|
8396
|
+
capability: imageSessionCapability({
|
|
8397
|
+
archived: current.session.archived,
|
|
8398
|
+
running: true
|
|
8399
|
+
})
|
|
8400
|
+
};
|
|
8401
|
+
return {
|
|
8402
|
+
...current,
|
|
8403
|
+
session,
|
|
8404
|
+
updated_at: createdAt,
|
|
8405
|
+
turns: [...current.turns, turn]
|
|
8406
|
+
};
|
|
8407
|
+
});
|
|
8408
|
+
}
|
|
8409
|
+
async finishTurn(input) {
|
|
8410
|
+
const completedAt = isoNow3();
|
|
8411
|
+
return this.updateSessionRecord(input.sessionId, (current) => {
|
|
8412
|
+
const turns = current.turns.map((turn) => {
|
|
8413
|
+
if (turn.id !== input.turnId) {
|
|
8414
|
+
return turn;
|
|
8415
|
+
}
|
|
8416
|
+
return {
|
|
8417
|
+
...turn,
|
|
8418
|
+
completed_at: completedAt,
|
|
8419
|
+
status: input.status,
|
|
8420
|
+
error: input.error ?? null,
|
|
8421
|
+
output_asset_ids: input.appendOutputs ? [...turn.output_asset_ids, ...input.outputAssetIds] : [...input.outputAssetIds]
|
|
8422
|
+
};
|
|
8423
|
+
});
|
|
8424
|
+
const latestTurn = turns.find((turn) => turn.id === input.turnId) ?? null;
|
|
8425
|
+
const session = {
|
|
8426
|
+
...current.session,
|
|
8427
|
+
latest_assistant_message: buildLatestAssistantMessage(latestTurn),
|
|
8428
|
+
last_event_at: completedAt,
|
|
8429
|
+
run_state: input.status === "failed" ? "completed" : "completed",
|
|
8430
|
+
run_state_changed_at: completedAt,
|
|
8431
|
+
capability: imageSessionCapability({
|
|
8432
|
+
archived: current.session.archived,
|
|
8433
|
+
running: false
|
|
8434
|
+
})
|
|
8435
|
+
};
|
|
8436
|
+
return {
|
|
8437
|
+
...current,
|
|
8438
|
+
session,
|
|
8439
|
+
updated_at: completedAt,
|
|
8440
|
+
turns
|
|
8441
|
+
};
|
|
8442
|
+
});
|
|
8443
|
+
}
|
|
8444
|
+
toProtocolAsset(asset, resolveContentUrl) {
|
|
8445
|
+
return {
|
|
8446
|
+
id: asset.id,
|
|
8447
|
+
session_id: asset.session_id,
|
|
8448
|
+
turn_id: asset.turn_id,
|
|
8449
|
+
kind: asset.kind,
|
|
8450
|
+
name: asset.name,
|
|
8451
|
+
mime_type: asset.mime_type,
|
|
8452
|
+
size_bytes: asset.size_bytes,
|
|
8453
|
+
width: asset.width,
|
|
8454
|
+
height: asset.height,
|
|
8455
|
+
content_url: resolveContentUrl(asset.id),
|
|
8456
|
+
created_at: asset.created_at
|
|
8457
|
+
};
|
|
8458
|
+
}
|
|
8459
|
+
toProtocolTurn(session, turn, resolveContentUrl) {
|
|
8460
|
+
const assetsById = new Map(session.assets.map((asset) => [asset.id, asset]));
|
|
8461
|
+
return {
|
|
8462
|
+
id: turn.id,
|
|
8463
|
+
session_id: turn.session_id,
|
|
8464
|
+
created_at: turn.created_at,
|
|
8465
|
+
completed_at: turn.completed_at,
|
|
8466
|
+
prompt: turn.prompt,
|
|
8467
|
+
prompt_suffix: turn.prompt_suffix,
|
|
8468
|
+
operation: turn.operation,
|
|
8469
|
+
status: turn.status,
|
|
8470
|
+
error: turn.error,
|
|
8471
|
+
model: turn.model,
|
|
8472
|
+
quality: turn.quality,
|
|
8473
|
+
size: turn.size,
|
|
8474
|
+
input_fidelity: turn.input_fidelity ?? null,
|
|
8475
|
+
aspect_ratio: turn.aspect_ratio,
|
|
8476
|
+
output_format: turn.output_format ?? "png",
|
|
8477
|
+
output_compression: turn.output_compression ?? null,
|
|
8478
|
+
background: turn.background ?? "auto",
|
|
8479
|
+
moderation: turn.moderation ?? "auto",
|
|
8480
|
+
n: turn.n ?? 1,
|
|
8481
|
+
sources: turn.source_asset_ids.map((assetId) => assetsById.get(assetId)).filter((asset) => Boolean(asset)).map((asset) => this.toProtocolAsset(asset, resolveContentUrl)),
|
|
8482
|
+
outputs: turn.output_asset_ids.map((assetId) => assetsById.get(assetId)).filter((asset) => Boolean(asset)).map((asset) => this.toProtocolAsset(asset, resolveContentUrl))
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
resolveAssetAbsolutePath(asset) {
|
|
8486
|
+
return path8.join(this.assetRootPath, asset.relative_path);
|
|
8487
|
+
}
|
|
8488
|
+
};
|
|
8489
|
+
|
|
8490
|
+
// packages/provider-codex/src/timeline-user-dedup.ts
|
|
8491
|
+
var normalizeTimelineEntryBody2 = (entry) => entry.body.replace(/\r\n/g, "\n").trim();
|
|
8492
|
+
var areAdjacentDuplicateUserEntries = (previous, next, maxSkewMs) => {
|
|
8493
|
+
if (!previous || previous.kind !== "user" || next.kind !== "user") {
|
|
8494
|
+
return false;
|
|
8495
|
+
}
|
|
8496
|
+
if (normalizeTimelineEntryBody2(previous) !== normalizeTimelineEntryBody2(next)) {
|
|
8497
|
+
return false;
|
|
8498
|
+
}
|
|
8499
|
+
const previousTime = +new Date(previous.timestamp);
|
|
8500
|
+
const nextTime = +new Date(next.timestamp);
|
|
8501
|
+
if (!Number.isFinite(previousTime) || !Number.isFinite(nextTime)) {
|
|
8502
|
+
return true;
|
|
8503
|
+
}
|
|
8504
|
+
return Math.abs(nextTime - previousTime) <= maxSkewMs;
|
|
8505
|
+
};
|
|
8506
|
+
var collapseDuplicateUserEntries = (entries, options) => {
|
|
8507
|
+
const maxSkewMs = options?.maxSkewMs ?? 5e3;
|
|
8508
|
+
const collapsed = [];
|
|
8509
|
+
for (const entry of entries) {
|
|
8510
|
+
const previous = collapsed[collapsed.length - 1];
|
|
8511
|
+
if (!areAdjacentDuplicateUserEntries(previous, entry, maxSkewMs)) {
|
|
8512
|
+
collapsed.push(entry);
|
|
8513
|
+
continue;
|
|
8514
|
+
}
|
|
8515
|
+
collapsed[collapsed.length - 1] = {
|
|
8516
|
+
...entry,
|
|
8517
|
+
attachments: mergeTimelineAttachments(previous?.attachments, entry.attachments)
|
|
8518
|
+
};
|
|
8519
|
+
}
|
|
8520
|
+
return collapsed;
|
|
8521
|
+
};
|
|
8522
|
+
|
|
8523
|
+
// packages/provider-codex/src/session-run-workbench.ts
|
|
8524
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
8525
|
+
import { spawn as spawn2 } from "child_process";
|
|
8526
|
+
import fs7 from "fs/promises";
|
|
8527
|
+
import os4 from "os";
|
|
8528
|
+
import path9 from "path";
|
|
8529
|
+
var PANDA_GLOBAL_DIRECTORY_NAME = "Project Workbench Data";
|
|
8530
|
+
var RUN_RESOURCES_DIRECTORY_NAME = "run-workbench";
|
|
8531
|
+
var RUN_RESOURCE_INDEX_FILE_NAME = "index.json";
|
|
8532
|
+
var RUN_COMMANDS_FILE_NAME = "commands.json";
|
|
8533
|
+
var RUN_WEBSITES_FILE_NAME = "websites.json";
|
|
8534
|
+
var RUN_COMMANDS_SCHEMA_VERSION = 1;
|
|
8535
|
+
var RUN_WEBSITES_SCHEMA_VERSION = 1;
|
|
8536
|
+
var MAX_TERMINAL_CHUNKS = 1200;
|
|
8537
|
+
var MAX_TERMINAL_PREVIEW_LINES = 5;
|
|
8538
|
+
var NODE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
8539
|
+
var PYTHON_COMMAND_PATTERN = /^\s*(?:py(?:\.exe)?|python(?:\d+(?:\.\d+)?)?(?:\.exe)?)\b/i;
|
|
8540
|
+
var isoNow4 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
8541
|
+
var normalizeProjectPathKey = (projectPath) => {
|
|
8542
|
+
const resolved = path9.resolve(projectPath);
|
|
8543
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
8544
|
+
};
|
|
8545
|
+
var buildProjectStorageKey = (projectPath) => createHash3("sha1").update(normalizeProjectPathKey(projectPath)).digest("hex").slice(0, 16);
|
|
8546
|
+
var sanitizeStorageName = (value) => value.normalize("NFKC").replace(/[<>:"/\\|?*\u0000-\u001F]/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
|
|
8547
|
+
var resolveUserDocumentsDirectory = () => {
|
|
8548
|
+
const home = os4.homedir();
|
|
8549
|
+
const userProfile = process.env.USERPROFILE?.trim() || home;
|
|
8550
|
+
if (process.platform === "win32") {
|
|
8551
|
+
const documents = process.env.PUBLIC?.trim() ? path9.join(userProfile, "Documents") : path9.join(userProfile, "Documents");
|
|
8552
|
+
return documents;
|
|
8553
|
+
}
|
|
8554
|
+
if (process.platform === "darwin") {
|
|
8555
|
+
return path9.join(home, "Documents");
|
|
8556
|
+
}
|
|
8557
|
+
const xdgDocuments = process.env.XDG_DOCUMENTS_DIR?.trim();
|
|
8558
|
+
if (xdgDocuments) {
|
|
8559
|
+
return xdgDocuments.replace(/^\$HOME\b/, home);
|
|
8560
|
+
}
|
|
8561
|
+
return path9.join(home, "Documents");
|
|
8562
|
+
};
|
|
8563
|
+
var resolvePandaGlobalStorageRoot = () => path9.join(resolveUserDocumentsDirectory(), PANDA_GLOBAL_DIRECTORY_NAME, RUN_RESOURCES_DIRECTORY_NAME);
|
|
8564
|
+
var normalizeStoredPort = (value) => {
|
|
8565
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
8566
|
+
return null;
|
|
8567
|
+
}
|
|
8568
|
+
return value;
|
|
8569
|
+
};
|
|
8570
|
+
var normalizePort = (value) => normalizeStoredPort(value);
|
|
8571
|
+
var normalizeUrl = (value) => {
|
|
8572
|
+
const trimmed = value?.trim() ?? "";
|
|
8573
|
+
if (!trimmed) {
|
|
8574
|
+
throw new Error("\u8BF7\u8F93\u5165\u7F51\u9875\u5730\u5740\u3002");
|
|
8575
|
+
}
|
|
8576
|
+
try {
|
|
8577
|
+
const parsed = new URL(trimmed);
|
|
8578
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
8579
|
+
throw new Error("\u7F51\u9875\u5730\u5740\u53EA\u652F\u6301 http \u6216 https\u3002");
|
|
8580
|
+
}
|
|
8581
|
+
return parsed.toString();
|
|
8582
|
+
} catch {
|
|
8583
|
+
throw new Error("\u7F51\u9875\u5730\u5740\u683C\u5F0F\u65E0\u6548\u3002");
|
|
8584
|
+
}
|
|
8585
|
+
};
|
|
8586
|
+
var readStoredJson = async (filePath) => {
|
|
8587
|
+
try {
|
|
8588
|
+
const raw = await fs7.readFile(filePath, "utf8");
|
|
8589
|
+
return JSON.parse(raw);
|
|
8590
|
+
} catch {
|
|
8591
|
+
return null;
|
|
8592
|
+
}
|
|
8593
|
+
};
|
|
8594
|
+
var writeStoredJson = async (filePath, payload) => {
|
|
8595
|
+
await fs7.mkdir(path9.dirname(filePath), { recursive: true });
|
|
8596
|
+
await fs7.writeFile(filePath, `${JSON.stringify(payload, null, 2)}
|
|
8597
|
+
`, "utf8");
|
|
8598
|
+
};
|
|
8599
|
+
var normalizeRelativeCommandCwd = (projectPath, cwd) => {
|
|
8600
|
+
const trimmed = cwd?.trim() ?? "";
|
|
8601
|
+
if (!trimmed) {
|
|
8602
|
+
return null;
|
|
8603
|
+
}
|
|
8604
|
+
const absoluteCandidate = path9.resolve(projectPath, trimmed);
|
|
8605
|
+
const relative = path9.relative(projectPath, absoluteCandidate);
|
|
8606
|
+
if (!relative || relative === ".") {
|
|
8607
|
+
return null;
|
|
8608
|
+
}
|
|
8609
|
+
if (relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
8610
|
+
throw new Error("\u547D\u4EE4\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u9879\u76EE\u5185\u3002");
|
|
8611
|
+
}
|
|
8612
|
+
return relative.split(path9.sep).join("/");
|
|
8613
|
+
};
|
|
8614
|
+
var resolveCommandAbsoluteCwd = (projectPath, cwd) => {
|
|
8615
|
+
const relative = normalizeRelativeCommandCwd(projectPath, cwd);
|
|
8616
|
+
return relative ? path9.resolve(projectPath, relative) : projectPath;
|
|
8617
|
+
};
|
|
8618
|
+
var normalizeNodeVersion = (value) => {
|
|
8619
|
+
const trimmed = value?.trim() ?? "";
|
|
8620
|
+
if (!trimmed) {
|
|
8621
|
+
return null;
|
|
8622
|
+
}
|
|
8623
|
+
const normalized = trimmed.replace(/^v/i, "");
|
|
8624
|
+
if (!NODE_VERSION_PATTERN.test(normalized)) {
|
|
8625
|
+
throw new Error("Node \u7248\u672C\u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 x.y.z\u3002");
|
|
8626
|
+
}
|
|
8627
|
+
return normalized;
|
|
8628
|
+
};
|
|
8629
|
+
var normalizeOptionalCommand = (value) => {
|
|
8630
|
+
const trimmed = value?.trim() ?? "";
|
|
8631
|
+
return trimmed || null;
|
|
8632
|
+
};
|
|
8633
|
+
var compareNodeVersionsDesc = (left, right) => {
|
|
8634
|
+
const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
|
|
8635
|
+
const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
|
|
8636
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
8637
|
+
const leftPart = leftParts[index] ?? 0;
|
|
8638
|
+
const rightPart = rightParts[index] ?? 0;
|
|
8639
|
+
if (leftPart !== rightPart) {
|
|
8640
|
+
return rightPart - leftPart;
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
return 0;
|
|
8644
|
+
};
|
|
8645
|
+
var dedupeNodeVersions = (versions) => [
|
|
7323
8646
|
...new Set(
|
|
7324
8647
|
versions.map((version) => normalizeNodeVersion(version)).filter((version) => Boolean(version))
|
|
7325
8648
|
)
|
|
@@ -7327,25 +8650,25 @@ var dedupeNodeVersions = (versions) => [
|
|
|
7327
8650
|
var resolveNvmWindowsSettingsPath = () => {
|
|
7328
8651
|
const configuredRoot = process.env.NVM_HOME?.trim();
|
|
7329
8652
|
if (configuredRoot) {
|
|
7330
|
-
return
|
|
8653
|
+
return path9.join(configuredRoot, "settings.txt");
|
|
7331
8654
|
}
|
|
7332
8655
|
const appData = process.env.AppData?.trim();
|
|
7333
8656
|
if (appData) {
|
|
7334
|
-
return
|
|
8657
|
+
return path9.join(appData, "nvm", "settings.txt");
|
|
7335
8658
|
}
|
|
7336
|
-
return
|
|
8659
|
+
return path9.join(os4.homedir(), "AppData", "Roaming", "nvm", "settings.txt");
|
|
7337
8660
|
};
|
|
7338
8661
|
var resolveNvmWindowsCandidateRoots = () => [
|
|
7339
8662
|
process.env.NVM_HOME?.trim() ?? "",
|
|
7340
|
-
process.env.LOCALAPPDATA?.trim() ?
|
|
7341
|
-
process.env.AppData?.trim() ?
|
|
7342
|
-
|
|
7343
|
-
|
|
8663
|
+
process.env.LOCALAPPDATA?.trim() ? path9.join(process.env.LOCALAPPDATA.trim(), "nvm") : "",
|
|
8664
|
+
process.env.AppData?.trim() ? path9.join(process.env.AppData.trim(), "nvm") : "",
|
|
8665
|
+
path9.join(os4.homedir(), "AppData", "Local", "nvm"),
|
|
8666
|
+
path9.join(os4.homedir(), "AppData", "Roaming", "nvm")
|
|
7344
8667
|
].filter(Boolean);
|
|
7345
8668
|
var resolveExistingNvmWindowsRoot = async () => {
|
|
7346
8669
|
for (const candidateRoot of resolveNvmWindowsCandidateRoots()) {
|
|
7347
8670
|
try {
|
|
7348
|
-
const stat = await
|
|
8671
|
+
const stat = await fs7.stat(candidateRoot);
|
|
7349
8672
|
if (stat.isDirectory()) {
|
|
7350
8673
|
return candidateRoot;
|
|
7351
8674
|
}
|
|
@@ -7360,7 +8683,7 @@ var readNvmWindowsSettings = async () => {
|
|
|
7360
8683
|
return { root: configuredRoot };
|
|
7361
8684
|
}
|
|
7362
8685
|
try {
|
|
7363
|
-
const raw = await
|
|
8686
|
+
const raw = await fs7.readFile(resolveNvmWindowsSettingsPath(), "utf8");
|
|
7364
8687
|
const entries = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
7365
8688
|
const separatorIndex = line.indexOf(":");
|
|
7366
8689
|
if (separatorIndex < 0) {
|
|
@@ -7386,7 +8709,7 @@ var readNvmWindowsVersionsFromRoot = async (root) => {
|
|
|
7386
8709
|
return [];
|
|
7387
8710
|
}
|
|
7388
8711
|
try {
|
|
7389
|
-
const entries = await
|
|
8712
|
+
const entries = await fs7.readdir(root, { withFileTypes: true });
|
|
7390
8713
|
return dedupeNodeVersions(
|
|
7391
8714
|
entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => /^v?\d+\.\d+\.\d+$/.test(name))
|
|
7392
8715
|
);
|
|
@@ -7400,7 +8723,7 @@ var prependProcessPath = (env, entry) => {
|
|
|
7400
8723
|
const currentPath = env[pathKey] ?? "";
|
|
7401
8724
|
return {
|
|
7402
8725
|
...env,
|
|
7403
|
-
[pathKey]: currentPath ? `${entry}${
|
|
8726
|
+
[pathKey]: currentPath ? `${entry}${path9.delimiter}${currentPath}` : entry
|
|
7404
8727
|
};
|
|
7405
8728
|
};
|
|
7406
8729
|
var applyCommandRuntimeEnv = (command, env) => {
|
|
@@ -7427,7 +8750,7 @@ var normalizeStoredRunWebsite = (value) => {
|
|
|
7427
8750
|
return parsed.data;
|
|
7428
8751
|
};
|
|
7429
8752
|
var readStoredRunResourceIndex = async (storageRoot) => {
|
|
7430
|
-
const indexPath =
|
|
8753
|
+
const indexPath = path9.join(storageRoot, RUN_RESOURCE_INDEX_FILE_NAME);
|
|
7431
8754
|
const parsed = await readStoredJson(indexPath);
|
|
7432
8755
|
const projects = Array.isArray(parsed?.projects) ? parsed.projects.map((entry) => {
|
|
7433
8756
|
if (!entry || typeof entry !== "object") {
|
|
@@ -7443,37 +8766,37 @@ var readStoredRunResourceIndex = async (storageRoot) => {
|
|
|
7443
8766
|
}
|
|
7444
8767
|
return {
|
|
7445
8768
|
project_id: projectId,
|
|
7446
|
-
project_path:
|
|
8769
|
+
project_path: path9.resolve(projectPath),
|
|
7447
8770
|
project_key: projectKey,
|
|
7448
8771
|
resource_dir: resourceDir,
|
|
7449
|
-
updated_at: typeof candidate.updated_at === "string" && candidate.updated_at.trim() ? candidate.updated_at :
|
|
8772
|
+
updated_at: typeof candidate.updated_at === "string" && candidate.updated_at.trim() ? candidate.updated_at : isoNow4()
|
|
7450
8773
|
};
|
|
7451
8774
|
}).filter((entry) => Boolean(entry)) : [];
|
|
7452
8775
|
return {
|
|
7453
8776
|
version: 1,
|
|
7454
|
-
updated_at: typeof parsed?.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at :
|
|
8777
|
+
updated_at: typeof parsed?.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at : isoNow4(),
|
|
7455
8778
|
projects
|
|
7456
8779
|
};
|
|
7457
8780
|
};
|
|
7458
8781
|
var writeStoredRunResourceIndex = async (storageRoot, index) => {
|
|
7459
|
-
await writeStoredJson(
|
|
8782
|
+
await writeStoredJson(path9.join(storageRoot, RUN_RESOURCE_INDEX_FILE_NAME), index);
|
|
7460
8783
|
};
|
|
7461
8784
|
var resolveProjectRunResourcePaths = async (input) => {
|
|
7462
8785
|
const storageRoot = resolvePandaGlobalStorageRoot();
|
|
7463
8786
|
const index = await readStoredRunResourceIndex(storageRoot);
|
|
7464
|
-
const normalizedProjectPath =
|
|
8787
|
+
const normalizedProjectPath = path9.resolve(input.projectPath);
|
|
7465
8788
|
const projectKey = buildProjectStorageKey(normalizedProjectPath);
|
|
7466
8789
|
const existingEntry = index.projects.find(
|
|
7467
8790
|
(entry) => normalizeProjectPathKey(entry.project_path) === normalizeProjectPathKey(normalizedProjectPath)
|
|
7468
8791
|
);
|
|
7469
|
-
const projectName = sanitizeStorageName(
|
|
8792
|
+
const projectName = sanitizeStorageName(path9.basename(normalizedProjectPath) || input.projectId) || input.projectId;
|
|
7470
8793
|
const resourceDir = existingEntry?.resource_dir?.trim() || `${projectName}-${projectKey}`;
|
|
7471
8794
|
const nextEntry = {
|
|
7472
8795
|
project_id: input.projectId,
|
|
7473
8796
|
project_path: normalizedProjectPath,
|
|
7474
8797
|
project_key: projectKey,
|
|
7475
8798
|
resource_dir: resourceDir,
|
|
7476
|
-
updated_at:
|
|
8799
|
+
updated_at: isoNow4()
|
|
7477
8800
|
};
|
|
7478
8801
|
const nextProjects = [
|
|
7479
8802
|
...index.projects.filter(
|
|
@@ -7483,26 +8806,26 @@ var resolveProjectRunResourcePaths = async (input) => {
|
|
|
7483
8806
|
].sort((left, right) => left.project_path.localeCompare(right.project_path, "zh-CN"));
|
|
7484
8807
|
await writeStoredRunResourceIndex(storageRoot, {
|
|
7485
8808
|
version: 1,
|
|
7486
|
-
updated_at:
|
|
8809
|
+
updated_at: isoNow4(),
|
|
7487
8810
|
projects: nextProjects
|
|
7488
8811
|
});
|
|
7489
|
-
const resourceRoot =
|
|
8812
|
+
const resourceRoot = path9.join(storageRoot, resourceDir);
|
|
7490
8813
|
return {
|
|
7491
8814
|
storageRoot,
|
|
7492
8815
|
resourceRoot,
|
|
7493
|
-
commandsPath:
|
|
7494
|
-
websitesPath:
|
|
8816
|
+
commandsPath: path9.join(resourceRoot, RUN_COMMANDS_FILE_NAME),
|
|
8817
|
+
websitesPath: path9.join(resourceRoot, RUN_WEBSITES_FILE_NAME)
|
|
7495
8818
|
};
|
|
7496
8819
|
};
|
|
7497
8820
|
var readStoredCatalog = async (configPath) => {
|
|
7498
8821
|
try {
|
|
7499
|
-
const raw = await
|
|
8822
|
+
const raw = await fs7.readFile(configPath, "utf8");
|
|
7500
8823
|
const parsed = JSON.parse(raw);
|
|
7501
8824
|
const rawCommands = Array.isArray(parsed.commands) ? parsed.commands : [];
|
|
7502
8825
|
const commands = rawCommands.map((entry) => normalizeStoredRunCommand(entry)).filter((entry) => Boolean(entry)).sort((left, right) => left.name.localeCompare(right.name, "zh-CN"));
|
|
7503
8826
|
return {
|
|
7504
8827
|
version: RUN_COMMANDS_SCHEMA_VERSION,
|
|
7505
|
-
updated_at: typeof parsed.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at :
|
|
8828
|
+
updated_at: typeof parsed.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at : isoNow4(),
|
|
7506
8829
|
commands
|
|
7507
8830
|
};
|
|
7508
8831
|
} catch {
|
|
@@ -7511,13 +8834,13 @@ var readStoredCatalog = async (configPath) => {
|
|
|
7511
8834
|
};
|
|
7512
8835
|
var readStoredWebsiteCatalog = async (configPath) => {
|
|
7513
8836
|
try {
|
|
7514
|
-
const raw = await
|
|
8837
|
+
const raw = await fs7.readFile(configPath, "utf8");
|
|
7515
8838
|
const parsed = JSON.parse(raw);
|
|
7516
8839
|
const rawWebsites = Array.isArray(parsed.websites) ? parsed.websites : [];
|
|
7517
8840
|
const websites = rawWebsites.map((entry) => normalizeStoredRunWebsite(entry)).filter((entry) => Boolean(entry)).sort((left, right) => left.name.localeCompare(right.name, "zh-CN"));
|
|
7518
8841
|
return {
|
|
7519
8842
|
version: RUN_WEBSITES_SCHEMA_VERSION,
|
|
7520
|
-
updated_at: typeof parsed.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at :
|
|
8843
|
+
updated_at: typeof parsed.updated_at === "string" && parsed.updated_at.trim() ? parsed.updated_at : isoNow4(),
|
|
7521
8844
|
websites
|
|
7522
8845
|
};
|
|
7523
8846
|
} catch {
|
|
@@ -7525,24 +8848,24 @@ var readStoredWebsiteCatalog = async (configPath) => {
|
|
|
7525
8848
|
}
|
|
7526
8849
|
};
|
|
7527
8850
|
var writeStoredCatalog = async (configPath, commands) => {
|
|
7528
|
-
await
|
|
8851
|
+
await fs7.mkdir(path9.dirname(configPath), { recursive: true });
|
|
7529
8852
|
const payload = {
|
|
7530
8853
|
version: RUN_COMMANDS_SCHEMA_VERSION,
|
|
7531
|
-
updated_at:
|
|
8854
|
+
updated_at: isoNow4(),
|
|
7532
8855
|
commands
|
|
7533
8856
|
};
|
|
7534
|
-
await
|
|
8857
|
+
await fs7.writeFile(configPath, `${JSON.stringify(payload, null, 2)}
|
|
7535
8858
|
`, "utf8");
|
|
7536
8859
|
return payload;
|
|
7537
8860
|
};
|
|
7538
8861
|
var writeStoredWebsiteCatalog = async (configPath, websites) => {
|
|
7539
|
-
await
|
|
8862
|
+
await fs7.mkdir(path9.dirname(configPath), { recursive: true });
|
|
7540
8863
|
const payload = {
|
|
7541
8864
|
version: RUN_WEBSITES_SCHEMA_VERSION,
|
|
7542
|
-
updated_at:
|
|
8865
|
+
updated_at: isoNow4(),
|
|
7543
8866
|
websites
|
|
7544
8867
|
};
|
|
7545
|
-
await
|
|
8868
|
+
await fs7.writeFile(configPath, `${JSON.stringify(payload, null, 2)}
|
|
7546
8869
|
`, "utf8");
|
|
7547
8870
|
return payload;
|
|
7548
8871
|
};
|
|
@@ -7581,10 +8904,10 @@ var sanitizeRunWebsiteDraft = (draft) => {
|
|
|
7581
8904
|
};
|
|
7582
8905
|
};
|
|
7583
8906
|
var createRunCommand = (projectPath, draft, source) => {
|
|
7584
|
-
const now =
|
|
8907
|
+
const now = isoNow4();
|
|
7585
8908
|
const sanitized = sanitizeRunCommandDraft(projectPath, draft);
|
|
7586
8909
|
return {
|
|
7587
|
-
id: `run-command-${
|
|
8910
|
+
id: `run-command-${randomUUID2()}`,
|
|
7588
8911
|
name: sanitized.name,
|
|
7589
8912
|
description: sanitized.description,
|
|
7590
8913
|
command: sanitized.command,
|
|
@@ -7610,14 +8933,14 @@ var updateRunCommand = (current, projectPath, draft) => {
|
|
|
7610
8933
|
shell: sanitized.shell,
|
|
7611
8934
|
node_version: sanitized.node_version,
|
|
7612
8935
|
port: sanitized.port,
|
|
7613
|
-
updated_at:
|
|
8936
|
+
updated_at: isoNow4()
|
|
7614
8937
|
};
|
|
7615
8938
|
};
|
|
7616
8939
|
var createRunWebsite = (draft, source) => {
|
|
7617
|
-
const now =
|
|
8940
|
+
const now = isoNow4();
|
|
7618
8941
|
const sanitized = sanitizeRunWebsiteDraft(draft);
|
|
7619
8942
|
return {
|
|
7620
|
-
id: `run-website-${
|
|
8943
|
+
id: `run-website-${randomUUID2()}`,
|
|
7621
8944
|
name: sanitized.name,
|
|
7622
8945
|
description: sanitized.description,
|
|
7623
8946
|
url: sanitized.url,
|
|
@@ -7633,7 +8956,7 @@ var updateRunWebsite = (current, draft) => {
|
|
|
7633
8956
|
name: sanitized.name,
|
|
7634
8957
|
description: sanitized.description,
|
|
7635
8958
|
url: sanitized.url,
|
|
7636
|
-
updated_at:
|
|
8959
|
+
updated_at: isoNow4()
|
|
7637
8960
|
};
|
|
7638
8961
|
};
|
|
7639
8962
|
var readProjectRunCommandCatalog = async (input) => {
|
|
@@ -7645,7 +8968,7 @@ var readProjectRunCommandCatalog = async (input) => {
|
|
|
7645
8968
|
const stored = await readStoredCatalog(configPath);
|
|
7646
8969
|
const payload = stored ?? {
|
|
7647
8970
|
version: RUN_COMMANDS_SCHEMA_VERSION,
|
|
7648
|
-
updated_at:
|
|
8971
|
+
updated_at: isoNow4(),
|
|
7649
8972
|
commands: []
|
|
7650
8973
|
};
|
|
7651
8974
|
return {
|
|
@@ -7665,7 +8988,7 @@ var readProjectRunWebsiteCatalog = async (input) => {
|
|
|
7665
8988
|
const stored = await readStoredWebsiteCatalog(configPath);
|
|
7666
8989
|
const payload = stored ?? {
|
|
7667
8990
|
version: RUN_WEBSITES_SCHEMA_VERSION,
|
|
7668
|
-
updated_at:
|
|
8991
|
+
updated_at: isoNow4(),
|
|
7669
8992
|
websites: []
|
|
7670
8993
|
};
|
|
7671
8994
|
return {
|
|
@@ -7901,9 +9224,9 @@ var resolveRunCommandExecution = async (projectPath, command, options) => {
|
|
|
7901
9224
|
shell
|
|
7902
9225
|
};
|
|
7903
9226
|
}
|
|
7904
|
-
const nodeDirectory =
|
|
9227
|
+
const nodeDirectory = path9.join(settings.root, `v${nodeVersion}`);
|
|
7905
9228
|
try {
|
|
7906
|
-
await
|
|
9229
|
+
await fs7.access(path9.join(nodeDirectory, "node.exe"));
|
|
7907
9230
|
} catch {
|
|
7908
9231
|
return {
|
|
7909
9232
|
command: resolvedCommand,
|
|
@@ -7985,13 +9308,13 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
7985
9308
|
terminals: /* @__PURE__ */ new Map(),
|
|
7986
9309
|
order: [],
|
|
7987
9310
|
activeTerminalId: null,
|
|
7988
|
-
updatedAt:
|
|
9311
|
+
updatedAt: isoNow4()
|
|
7989
9312
|
};
|
|
7990
9313
|
states.set(sessionId, created);
|
|
7991
9314
|
return created;
|
|
7992
9315
|
};
|
|
7993
9316
|
const emitSnapshot = (state) => {
|
|
7994
|
-
state.updatedAt =
|
|
9317
|
+
state.updatedAt = isoNow4();
|
|
7995
9318
|
options.onSnapshot(getSessionStateSnapshot(state));
|
|
7996
9319
|
};
|
|
7997
9320
|
const emitDelta = (state, terminal, chunks) => {
|
|
@@ -8025,7 +9348,7 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
8025
9348
|
if (!text) {
|
|
8026
9349
|
return;
|
|
8027
9350
|
}
|
|
8028
|
-
const timestamp =
|
|
9351
|
+
const timestamp = isoNow4();
|
|
8029
9352
|
const chunk = {
|
|
8030
9353
|
cursor: terminal.baseCursor + terminal.chunks.length,
|
|
8031
9354
|
stream,
|
|
@@ -8044,7 +9367,7 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
8044
9367
|
emitDelta(state, terminal, [chunk]);
|
|
8045
9368
|
};
|
|
8046
9369
|
const finalizeTerminal = (state, terminal, nextStatus, exitCode) => {
|
|
8047
|
-
const timestamp =
|
|
9370
|
+
const timestamp = isoNow4();
|
|
8048
9371
|
terminal.child = null;
|
|
8049
9372
|
terminal.meta.status = nextStatus;
|
|
8050
9373
|
terminal.meta.exit_code = exitCode;
|
|
@@ -8120,8 +9443,8 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
8120
9443
|
},
|
|
8121
9444
|
runCommand: (input) => {
|
|
8122
9445
|
const state = ensureState(input.sessionId, input.projectId);
|
|
8123
|
-
const timestamp =
|
|
8124
|
-
const terminalId = `terminal-${
|
|
9446
|
+
const timestamp = isoNow4();
|
|
9447
|
+
const terminalId = `terminal-${randomUUID2()}`;
|
|
8125
9448
|
const managed = {
|
|
8126
9449
|
meta: {
|
|
8127
9450
|
id: terminalId,
|
|
@@ -8182,7 +9505,7 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
8182
9505
|
});
|
|
8183
9506
|
managed.child = child;
|
|
8184
9507
|
managed.meta.status = "running";
|
|
8185
|
-
managed.meta.started_at =
|
|
9508
|
+
managed.meta.started_at = isoNow4();
|
|
8186
9509
|
managed.meta.updated_at = managed.meta.started_at;
|
|
8187
9510
|
emitSnapshot(state);
|
|
8188
9511
|
let hasFinalized = false;
|
|
@@ -8253,11 +9576,11 @@ var createSessionRunWorkbenchManager = (options) => {
|
|
|
8253
9576
|
};
|
|
8254
9577
|
|
|
8255
9578
|
// packages/provider-codex/src/dev-manager.ts
|
|
8256
|
-
import
|
|
9579
|
+
import fs8 from "fs/promises";
|
|
8257
9580
|
import net from "net";
|
|
8258
9581
|
import os5 from "os";
|
|
8259
|
-
import
|
|
8260
|
-
import { createHash as createHash4, randomUUID as
|
|
9582
|
+
import path10 from "path";
|
|
9583
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
|
|
8261
9584
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
8262
9585
|
var MANAGED_ENV_PASSTHROUGH_KEYS = [
|
|
8263
9586
|
"ALLUSERSPROFILE",
|
|
@@ -8308,7 +9631,7 @@ var MANAGED_ENV_STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
8308
9631
|
"npm_config_registry",
|
|
8309
9632
|
"NPM_CONFIG_REGISTRY"
|
|
8310
9633
|
]);
|
|
8311
|
-
var DEV_MANAGER_RELATIVE_ROOT =
|
|
9634
|
+
var DEV_MANAGER_RELATIVE_ROOT = path10.join("state", "panda", "dev-manager");
|
|
8312
9635
|
var CONFIG_FILE_NAME = "config.json";
|
|
8313
9636
|
var CREDENTIALS_FILE_NAME = "credentials.json";
|
|
8314
9637
|
var SERVICE_PIDS_FILE_NAME = "service-pids.json";
|
|
@@ -8328,7 +9651,7 @@ var RELEASE_RESTART_DELAY_MS = 1500;
|
|
|
8328
9651
|
var SNAPSHOT_NODE_RUNTIME_CACHE_MS = 3e4;
|
|
8329
9652
|
var SNAPSHOT_PACKAGE_VERSION_CACHE_MS = 3e4;
|
|
8330
9653
|
var SNAPSHOT_APK_ARTIFACT_CACHE_MS = 1e4;
|
|
8331
|
-
var
|
|
9654
|
+
var isoNow5 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
8332
9655
|
var defaultConfig = () => ({
|
|
8333
9656
|
repo_path: null,
|
|
8334
9657
|
nvm_version: null,
|
|
@@ -8355,12 +9678,12 @@ var defaultConfig = () => ({
|
|
|
8355
9678
|
release_agent_args: "",
|
|
8356
9679
|
updated_at: null
|
|
8357
9680
|
});
|
|
8358
|
-
var resolveStateRoot = (codexHome) =>
|
|
8359
|
-
var resolveConfigPath = (codexHome) =>
|
|
8360
|
-
var resolveCredentialsPath = (codexHome) =>
|
|
8361
|
-
var resolveServicePidsPath = (codexHome) =>
|
|
8362
|
-
var resolveJobsDirectory = (codexHome) =>
|
|
8363
|
-
var resolveHelperDirectory = (codexHome) =>
|
|
9681
|
+
var resolveStateRoot = (codexHome) => path10.join(codexHome, DEV_MANAGER_RELATIVE_ROOT);
|
|
9682
|
+
var resolveConfigPath = (codexHome) => path10.join(resolveStateRoot(codexHome), CONFIG_FILE_NAME);
|
|
9683
|
+
var resolveCredentialsPath = (codexHome) => path10.join(resolveStateRoot(codexHome), CREDENTIALS_FILE_NAME);
|
|
9684
|
+
var resolveServicePidsPath = (codexHome) => path10.join(resolveStateRoot(codexHome), SERVICE_PIDS_FILE_NAME);
|
|
9685
|
+
var resolveJobsDirectory = (codexHome) => path10.join(resolveStateRoot(codexHome), JOBS_DIRECTORY_NAME);
|
|
9686
|
+
var resolveHelperDirectory = (codexHome) => path10.join(resolveStateRoot(codexHome), HELPERS_DIRECTORY_NAME);
|
|
8364
9687
|
var normalizePort2 = (value, fallback) => {
|
|
8365
9688
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
8366
9689
|
return value;
|
|
@@ -8384,7 +9707,7 @@ var normalizeConfig = (input) => {
|
|
|
8384
9707
|
const repoPath = normalizeNullableText(input?.repo_path);
|
|
8385
9708
|
const updatedAt = normalizeNullableText(input?.updated_at);
|
|
8386
9709
|
return devManagerConfigSchema.parse({
|
|
8387
|
-
repo_path: repoPath ?
|
|
9710
|
+
repo_path: repoPath ? path10.resolve(repoPath) : null,
|
|
8388
9711
|
nvm_version: normalizeNullableText(input?.nvm_version),
|
|
8389
9712
|
dev_hub_port: devHubPort,
|
|
8390
9713
|
dev_hub_args: normalizeText(input?.dev_hub_args),
|
|
@@ -8412,25 +9735,25 @@ var normalizeConfig = (input) => {
|
|
|
8412
9735
|
};
|
|
8413
9736
|
var readJsonFile = async (filePath) => {
|
|
8414
9737
|
try {
|
|
8415
|
-
const raw = await
|
|
9738
|
+
const raw = await fs8.readFile(filePath, "utf8");
|
|
8416
9739
|
return JSON.parse(raw);
|
|
8417
9740
|
} catch {
|
|
8418
9741
|
return null;
|
|
8419
9742
|
}
|
|
8420
9743
|
};
|
|
8421
9744
|
var writeJsonFile = async (filePath, payload) => {
|
|
8422
|
-
await
|
|
8423
|
-
await
|
|
9745
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
9746
|
+
await fs8.writeFile(filePath, `${JSON.stringify(payload, null, 2)}
|
|
8424
9747
|
`, "utf8");
|
|
8425
9748
|
};
|
|
8426
|
-
var
|
|
8427
|
-
await
|
|
9749
|
+
var ensureDirectory2 = async (targetPath) => {
|
|
9750
|
+
await fs8.mkdir(targetPath, { recursive: true });
|
|
8428
9751
|
};
|
|
8429
9752
|
var validateRepoPath = async (repoPath) => {
|
|
8430
9753
|
if (!repoPath) {
|
|
8431
9754
|
return;
|
|
8432
9755
|
}
|
|
8433
|
-
const stat = await
|
|
9756
|
+
const stat = await fs8.stat(repoPath).catch(() => null);
|
|
8434
9757
|
if (!stat?.isDirectory()) {
|
|
8435
9758
|
throw new Error("\u5F00\u53D1\u7248\u4EE3\u7801\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u6216\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55\u3002");
|
|
8436
9759
|
}
|
|
@@ -8445,7 +9768,7 @@ var maskTokenHint = (token) => {
|
|
|
8445
9768
|
}
|
|
8446
9769
|
return `${normalized.slice(0, 4)}***${normalized.slice(-4)}`;
|
|
8447
9770
|
};
|
|
8448
|
-
var buildJobFilePath = (codexHome, jobId) =>
|
|
9771
|
+
var buildJobFilePath = (codexHome, jobId) => path10.join(resolveJobsDirectory(codexHome), `${jobId}.json`);
|
|
8449
9772
|
var readStoredJob = async (filePath) => {
|
|
8450
9773
|
const payload = await readJsonFile(filePath);
|
|
8451
9774
|
const parsed = devManagerJobSchema.safeParse(payload);
|
|
@@ -8453,25 +9776,25 @@ var readStoredJob = async (filePath) => {
|
|
|
8453
9776
|
};
|
|
8454
9777
|
var readAllJobs = async (codexHome) => {
|
|
8455
9778
|
const jobsDirectory = resolveJobsDirectory(codexHome);
|
|
8456
|
-
const entries = await
|
|
9779
|
+
const entries = await fs8.readdir(jobsDirectory, { withFileTypes: true }).catch(() => []);
|
|
8457
9780
|
const jobs = await Promise.all(
|
|
8458
|
-
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => readStoredJob(
|
|
9781
|
+
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => readStoredJob(path10.join(jobsDirectory, entry.name)))
|
|
8459
9782
|
);
|
|
8460
9783
|
return jobs.filter((job) => Boolean(job)).sort((left, right) => +new Date(right.created_at) - +new Date(left.created_at));
|
|
8461
9784
|
};
|
|
8462
9785
|
var createJobLogEntry = (level, message) => ({
|
|
8463
|
-
id: `dev-manager-log-${
|
|
8464
|
-
timestamp:
|
|
9786
|
+
id: `dev-manager-log-${randomUUID3()}`,
|
|
9787
|
+
timestamp: isoNow5(),
|
|
8465
9788
|
level,
|
|
8466
9789
|
message
|
|
8467
9790
|
});
|
|
8468
9791
|
var createManagedJob = (kind, title, options) => devManagerJobSchema.parse({
|
|
8469
|
-
id: `dev-manager-job-${
|
|
9792
|
+
id: `dev-manager-job-${randomUUID3()}`,
|
|
8470
9793
|
kind,
|
|
8471
9794
|
title,
|
|
8472
9795
|
status: "running",
|
|
8473
|
-
created_at:
|
|
8474
|
-
started_at:
|
|
9796
|
+
created_at: isoNow5(),
|
|
9797
|
+
started_at: isoNow5(),
|
|
8475
9798
|
finished_at: null,
|
|
8476
9799
|
summary: null,
|
|
8477
9800
|
error: null,
|
|
@@ -8639,7 +9962,7 @@ var probeUrl = async (url, options) => {
|
|
|
8639
9962
|
const resolvedUrl = withProbePath(url, options?.probePath ?? "/health");
|
|
8640
9963
|
if (!resolvedUrl) {
|
|
8641
9964
|
return {
|
|
8642
|
-
checked_at:
|
|
9965
|
+
checked_at: isoNow5(),
|
|
8643
9966
|
url: null,
|
|
8644
9967
|
ok: false,
|
|
8645
9968
|
status_code: null,
|
|
@@ -8658,7 +9981,7 @@ var probeUrl = async (url, options) => {
|
|
|
8658
9981
|
const durationMs = Date.now() - startedAt;
|
|
8659
9982
|
const body = await response.text().catch(() => "");
|
|
8660
9983
|
return {
|
|
8661
|
-
checked_at:
|
|
9984
|
+
checked_at: isoNow5(),
|
|
8662
9985
|
url: resolvedUrl,
|
|
8663
9986
|
ok: response.ok,
|
|
8664
9987
|
status_code: response.status,
|
|
@@ -8667,7 +9990,7 @@ var probeUrl = async (url, options) => {
|
|
|
8667
9990
|
};
|
|
8668
9991
|
} catch (error) {
|
|
8669
9992
|
return {
|
|
8670
|
-
checked_at:
|
|
9993
|
+
checked_at: isoNow5(),
|
|
8671
9994
|
url: resolvedUrl,
|
|
8672
9995
|
ok: false,
|
|
8673
9996
|
status_code: null,
|
|
@@ -8681,7 +10004,7 @@ var probeUrl = async (url, options) => {
|
|
|
8681
10004
|
var probeLocalPort = async (port, options) => {
|
|
8682
10005
|
if (!port) {
|
|
8683
10006
|
return {
|
|
8684
|
-
checked_at:
|
|
10007
|
+
checked_at: isoNow5(),
|
|
8685
10008
|
url: null,
|
|
8686
10009
|
ok: false,
|
|
8687
10010
|
status_code: null,
|
|
@@ -8704,7 +10027,7 @@ var probeLocalPort = async (port, options) => {
|
|
|
8704
10027
|
socket.setTimeout(options?.timeoutMs ?? PORT_PROBE_TIMEOUT_MS);
|
|
8705
10028
|
socket.once("connect", () => {
|
|
8706
10029
|
finish({
|
|
8707
|
-
checked_at:
|
|
10030
|
+
checked_at: isoNow5(),
|
|
8708
10031
|
url: `tcp://${LOCALHOST}:${String(port)}`,
|
|
8709
10032
|
ok: true,
|
|
8710
10033
|
status_code: null,
|
|
@@ -8714,7 +10037,7 @@ var probeLocalPort = async (port, options) => {
|
|
|
8714
10037
|
});
|
|
8715
10038
|
socket.once("timeout", () => {
|
|
8716
10039
|
finish({
|
|
8717
|
-
checked_at:
|
|
10040
|
+
checked_at: isoNow5(),
|
|
8718
10041
|
url: `tcp://${LOCALHOST}:${String(port)}`,
|
|
8719
10042
|
ok: false,
|
|
8720
10043
|
status_code: null,
|
|
@@ -8724,7 +10047,7 @@ var probeLocalPort = async (port, options) => {
|
|
|
8724
10047
|
});
|
|
8725
10048
|
socket.once("error", (error) => {
|
|
8726
10049
|
finish({
|
|
8727
|
-
checked_at:
|
|
10050
|
+
checked_at: isoNow5(),
|
|
8728
10051
|
url: `tcp://${LOCALHOST}:${String(port)}`,
|
|
8729
10052
|
ok: false,
|
|
8730
10053
|
status_code: null,
|
|
@@ -8761,8 +10084,8 @@ var buildSyntheticCommand = (command, nodeVersion) => ({
|
|
|
8761
10084
|
node_version: nodeVersion,
|
|
8762
10085
|
port: null,
|
|
8763
10086
|
source: "user",
|
|
8764
|
-
created_at:
|
|
8765
|
-
updated_at:
|
|
10087
|
+
created_at: isoNow5(),
|
|
10088
|
+
updated_at: isoNow5()
|
|
8766
10089
|
});
|
|
8767
10090
|
var resolveManagedCommand = async (input) => {
|
|
8768
10091
|
const execution = await resolveRunCommandExecution(
|
|
@@ -8817,14 +10140,14 @@ var buildIsolatedManagedEnv = (baseEnv, overrides) => {
|
|
|
8817
10140
|
return nextEnv;
|
|
8818
10141
|
};
|
|
8819
10142
|
var createTempNpmPublishUserConfig = async (token) => {
|
|
8820
|
-
const filePath =
|
|
10143
|
+
const filePath = path10.join(
|
|
8821
10144
|
os5.tmpdir(),
|
|
8822
10145
|
`panda-dev-manager-${process.pid}-${Date.now()}.npmrc`
|
|
8823
10146
|
);
|
|
8824
10147
|
const content = `//registry.npmjs.org/:_authToken=${token}
|
|
8825
10148
|
registry=${NPM_REGISTRY_URL}
|
|
8826
10149
|
`;
|
|
8827
|
-
await
|
|
10150
|
+
await fs8.writeFile(filePath, content, "utf8");
|
|
8828
10151
|
return filePath;
|
|
8829
10152
|
};
|
|
8830
10153
|
var readStoredConfig = async (codexHome) => {
|
|
@@ -8883,9 +10206,9 @@ var readApkArtifact = async (config) => {
|
|
|
8883
10206
|
if (!repoPath) {
|
|
8884
10207
|
return null;
|
|
8885
10208
|
}
|
|
8886
|
-
const apkPath =
|
|
8887
|
-
const latestJsonPath =
|
|
8888
|
-
const apkStat = await
|
|
10209
|
+
const apkPath = path10.join(repoPath, "release", "android", "panda-android-release.apk");
|
|
10210
|
+
const latestJsonPath = path10.join(repoPath, "release", "android", "latest.json");
|
|
10211
|
+
const apkStat = await fs8.stat(apkPath).catch(() => null);
|
|
8889
10212
|
if (!apkStat?.isFile()) {
|
|
8890
10213
|
return null;
|
|
8891
10214
|
}
|
|
@@ -8893,7 +10216,7 @@ var readApkArtifact = async (config) => {
|
|
|
8893
10216
|
const artifactId = createHash4("sha1").update([apkPath, String(apkStat.size), apkStat.mtime.toISOString()].join("::")).digest("hex").slice(0, 16);
|
|
8894
10217
|
return {
|
|
8895
10218
|
artifact_id: artifactId,
|
|
8896
|
-
file_name:
|
|
10219
|
+
file_name: path10.basename(apkPath),
|
|
8897
10220
|
size_bytes: Math.max(0, Math.round(apkStat.size)),
|
|
8898
10221
|
built_at: apkStat.mtime.toISOString(),
|
|
8899
10222
|
published_at: normalizeNullableText(manifest?.published_at),
|
|
@@ -9381,7 +10704,7 @@ var createDevManager = ({
|
|
|
9381
10704
|
const current = await readServiceProcessState(codexHome);
|
|
9382
10705
|
current[serviceKey] = {
|
|
9383
10706
|
root_pid: rootPid,
|
|
9384
|
-
started_at:
|
|
10707
|
+
started_at: isoNow5(),
|
|
9385
10708
|
command
|
|
9386
10709
|
};
|
|
9387
10710
|
await writeServiceProcessState(codexHome, current);
|
|
@@ -9617,7 +10940,7 @@ var createDevManager = ({
|
|
|
9617
10940
|
status: "succeeded",
|
|
9618
10941
|
summary,
|
|
9619
10942
|
error: null,
|
|
9620
|
-
finished_at:
|
|
10943
|
+
finished_at: isoNow5()
|
|
9621
10944
|
};
|
|
9622
10945
|
await writeJsonFile(filePath, currentJob);
|
|
9623
10946
|
return currentJob;
|
|
@@ -9628,7 +10951,7 @@ var createDevManager = ({
|
|
|
9628
10951
|
status: "failed",
|
|
9629
10952
|
summary: currentJob.summary ?? "\u6267\u884C\u5931\u8D25\u3002",
|
|
9630
10953
|
error,
|
|
9631
|
-
finished_at:
|
|
10954
|
+
finished_at: isoNow5()
|
|
9632
10955
|
};
|
|
9633
10956
|
await writeJsonFile(filePath, currentJob);
|
|
9634
10957
|
return currentJob;
|
|
@@ -9785,7 +11108,7 @@ var createDevManager = ({
|
|
|
9785
11108
|
})
|
|
9786
11109
|
]);
|
|
9787
11110
|
return {
|
|
9788
|
-
generated_at:
|
|
11111
|
+
generated_at: isoNow5(),
|
|
9789
11112
|
config,
|
|
9790
11113
|
credentials: {
|
|
9791
11114
|
has_npm_token: Boolean(credentials.npm_token),
|
|
@@ -9803,13 +11126,13 @@ var createDevManager = ({
|
|
|
9803
11126
|
const normalized = normalizeConfig({
|
|
9804
11127
|
...current.config,
|
|
9805
11128
|
...input,
|
|
9806
|
-
updated_at:
|
|
11129
|
+
updated_at: isoNow5()
|
|
9807
11130
|
});
|
|
9808
11131
|
await validateRepoPath(normalized.repo_path);
|
|
9809
11132
|
const nextCredentials = {
|
|
9810
11133
|
npm_token: input.clear_npm_token ? null : normalizeNullableText(input.npm_token) ?? current.credentials.npm_token
|
|
9811
11134
|
};
|
|
9812
|
-
await
|
|
11135
|
+
await ensureDirectory2(resolveStateRoot(codexHome));
|
|
9813
11136
|
await writeJsonFile(resolveConfigPath(codexHome), normalized);
|
|
9814
11137
|
await writeJsonFile(resolveCredentialsPath(codexHome), nextCredentials);
|
|
9815
11138
|
cachedApkArtifact = null;
|
|
@@ -9904,7 +11227,7 @@ var createDevManager = ({
|
|
|
9904
11227
|
await runCommandWithLogs(command, job, "npm \u53D1\u5E03");
|
|
9905
11228
|
await job.succeed("npm \u53D1\u5E03\u5B8C\u6210\u3002");
|
|
9906
11229
|
} finally {
|
|
9907
|
-
await
|
|
11230
|
+
await fs8.rm(userConfigPath, { force: true }).catch(() => void 0);
|
|
9908
11231
|
}
|
|
9909
11232
|
});
|
|
9910
11233
|
};
|
|
@@ -10009,7 +11332,7 @@ var createDevManager = ({
|
|
|
10009
11332
|
const releaseHubCommand = await resolveServiceCommand(config, "release-hub");
|
|
10010
11333
|
const releaseAgentCommand = await resolveServiceCommand(config, "release-agent");
|
|
10011
11334
|
const helperDirectory = resolveHelperDirectory(codexHome);
|
|
10012
|
-
await
|
|
11335
|
+
await ensureDirectory2(helperDirectory);
|
|
10013
11336
|
const job = createManagedJob(input.kind, input.title, {
|
|
10014
11337
|
disconnectExpected: true
|
|
10015
11338
|
});
|
|
@@ -10023,9 +11346,9 @@ var createDevManager = ({
|
|
|
10023
11346
|
)
|
|
10024
11347
|
]
|
|
10025
11348
|
});
|
|
10026
|
-
const helperSourcePath =
|
|
10027
|
-
const helperPayloadPath =
|
|
10028
|
-
await
|
|
11349
|
+
const helperSourcePath = path10.join(helperDirectory, "release-install-helper.cjs");
|
|
11350
|
+
const helperPayloadPath = path10.join(helperDirectory, `${job.id}.json`);
|
|
11351
|
+
await fs8.writeFile(helperSourcePath, createUpgradeHelperSource(), "utf8");
|
|
10029
11352
|
await writeJsonFile(helperPayloadPath, {
|
|
10030
11353
|
jobPath,
|
|
10031
11354
|
workflowLabel: input.workflowLabel,
|
|
@@ -10126,7 +11449,7 @@ var createDevManager = ({
|
|
|
10126
11449
|
}
|
|
10127
11450
|
return {
|
|
10128
11451
|
artifact,
|
|
10129
|
-
filePath:
|
|
11452
|
+
filePath: path10.join(config.repo_path, "release", "android", "panda-android-release.apk")
|
|
10130
11453
|
};
|
|
10131
11454
|
};
|
|
10132
11455
|
const executeAction = async (action) => {
|
|
@@ -10156,31 +11479,44 @@ var HUB_RECENT_SESSION_LIMIT = 24;
|
|
|
10156
11479
|
var DEFAULT_WORKSPACE_SESSION_PAGE_LIMIT = 24;
|
|
10157
11480
|
var MAX_WORKSPACE_SESSION_PAGE_LIMIT = 100;
|
|
10158
11481
|
var COMMAND_PANEL_TTL_MS = 30 * 6e4;
|
|
10159
|
-
var
|
|
11482
|
+
var CODEX_VERSION_PROBE_TIMEOUT_MS2 = 2500;
|
|
10160
11483
|
var CODEX_SOURCE_FETCH_TIMEOUT_MS = 8e3;
|
|
10161
11484
|
var GIT_COMMAND_TIMEOUT_MS = 2e4;
|
|
11485
|
+
var PROJECTLESS_SESSION_PROJECT_ID = "";
|
|
11486
|
+
var PROJECTLESS_WORKTREE = "projectless";
|
|
11487
|
+
var PROJECTLESS_DIRECTORY_ROOT = path11.join(os6.homedir(), "Documents", "Codex");
|
|
10162
11488
|
var HUB_AGENT_HEARTBEAT_INTERVAL_MS = Number(
|
|
10163
11489
|
process.env.PANDA_HUB_AGENT_HEARTBEAT_INTERVAL_MS ?? 15e3
|
|
10164
11490
|
);
|
|
10165
11491
|
var HUB_AGENT_HEARTBEAT_TIMEOUT_MS = Number(
|
|
10166
11492
|
process.env.PANDA_HUB_AGENT_HEARTBEAT_TIMEOUT_MS ?? 45e3
|
|
10167
11493
|
);
|
|
10168
|
-
var FORWARDING_DIAGNOSTIC_LOG_RELATIVE_PATH =
|
|
11494
|
+
var FORWARDING_DIAGNOSTIC_LOG_RELATIVE_PATH = path11.join(
|
|
10169
11495
|
"logs",
|
|
10170
11496
|
"panda",
|
|
10171
11497
|
"codex-forwarding-diagnostics.latest.log"
|
|
10172
11498
|
);
|
|
10173
|
-
var HUB_AGENT_REGISTRY_RELATIVE_PATH =
|
|
11499
|
+
var HUB_AGENT_REGISTRY_RELATIVE_PATH = path11.join(
|
|
10174
11500
|
"state",
|
|
10175
11501
|
"panda",
|
|
10176
11502
|
"hub-agent-registry.json"
|
|
10177
11503
|
);
|
|
10178
|
-
var HUB_PUSH_SUBSCRIPTIONS_RELATIVE_PATH =
|
|
11504
|
+
var HUB_PUSH_SUBSCRIPTIONS_RELATIVE_PATH = path11.join(
|
|
10179
11505
|
"state",
|
|
10180
11506
|
"panda",
|
|
10181
11507
|
"hub-push-subscriptions.json"
|
|
10182
11508
|
);
|
|
10183
|
-
var
|
|
11509
|
+
var IMAGE_SESSION_STORE_RELATIVE_PATH = path11.join(
|
|
11510
|
+
"state",
|
|
11511
|
+
"panda",
|
|
11512
|
+
"image-sessions.json"
|
|
11513
|
+
);
|
|
11514
|
+
var IMAGE_SESSION_ASSET_RELATIVE_PATH = path11.join(
|
|
11515
|
+
"state",
|
|
11516
|
+
"panda",
|
|
11517
|
+
"image-assets"
|
|
11518
|
+
);
|
|
11519
|
+
var HUB_WEB_PUSH_VAPID_RELATIVE_PATH = path11.join(
|
|
10184
11520
|
"state",
|
|
10185
11521
|
"panda",
|
|
10186
11522
|
"hub-web-push-vapid.json"
|
|
@@ -10192,6 +11528,7 @@ var HTTP_COMPRESSION_MIN_BYTES = 4 * 1024;
|
|
|
10192
11528
|
var DEFAULT_SESSION_TITLE_GENERATION_MODEL = "gpt-5.4-mini";
|
|
10193
11529
|
var SESSION_TITLE_GENERATION_TIMEOUT_MS = 25e3;
|
|
10194
11530
|
var SESSION_GENERATED_TITLE_MAX_LENGTH = 30;
|
|
11531
|
+
var IMAGE_SESSION_AUTO_TITLE_PLACEHOLDERS = /* @__PURE__ */ new Set(["New image", "\u65B0\u7ED8\u56FE"]);
|
|
10195
11532
|
var SESSION_FILE_PREVIEW_ROOT_PATH = "/";
|
|
10196
11533
|
var SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT = 256 * 1024;
|
|
10197
11534
|
var SESSION_FILE_PREVIEW_IMAGE_BYTE_LIMIT = 6 * 1024 * 1024;
|
|
@@ -10277,25 +11614,21 @@ Plan mode is enabled for this message only. Before doing substantial work, creat
|
|
|
10277
11614
|
var LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION = 2;
|
|
10278
11615
|
var REVIEW_VISIBLE_CODEX_COMMAND_ENTRY = {
|
|
10279
11616
|
name: "review",
|
|
10280
|
-
reason: "
|
|
11617
|
+
reason: "Review current workspace changes before continuing."
|
|
10281
11618
|
};
|
|
10282
11619
|
var FALLBACK_CODEX_COMMAND_CATALOG = [
|
|
10283
|
-
{ name: "compact", description: "
|
|
10284
|
-
{ name: "copy", description: "
|
|
10285
|
-
{ name: "diff", description: "
|
|
10286
|
-
{ name: "feedback", description: "
|
|
10287
|
-
{ name: "fork", description: "
|
|
10288
|
-
{ name: "init", description: "
|
|
10289
|
-
{ name: "mcp", description: "
|
|
10290
|
-
{ name: "model", description: "
|
|
10291
|
-
{ name: "new", description: "
|
|
10292
|
-
{ name: "permissions", description: "
|
|
10293
|
-
{ name: "
|
|
10294
|
-
{ name: "
|
|
10295
|
-
{ name: "review", description: "\u5BA1\u67E5\u5F53\u524D\u5DE5\u4F5C\u533A\u672A\u63D0\u4EA4\u53D8\u66F4\u3002", availability: "unsupported" },
|
|
10296
|
-
{ name: "skills", description: "\u6D4F\u89C8\u5F53\u524D\u9879\u76EE\u53EF\u7528\u6280\u80FD\u3002", availability: "supported" },
|
|
10297
|
-
{ name: "status", description: "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001\u3001\u6A21\u578B\u548C\u4E0A\u4E0B\u6587\u7A97\u53E3\u3002", availability: "supported" },
|
|
10298
|
-
{ name: "statusline", description: "\u67E5\u770B\u72B6\u6001\u680F\u914D\u7F6E\u3002", availability: "unsupported" }
|
|
11620
|
+
{ name: "compact", description: "Compact current conversation context.", availability: "supported" },
|
|
11621
|
+
{ name: "copy", description: "Copy the latest assistant response.", availability: "unsupported" },
|
|
11622
|
+
{ name: "diff", description: "Show recent workspace changes.", availability: "unsupported" },
|
|
11623
|
+
{ name: "feedback", description: "Send feedback about Codex.", availability: "unsupported" },
|
|
11624
|
+
{ name: "fork", description: "Fork the current conversation.", availability: "unsupported" },
|
|
11625
|
+
{ name: "init", description: "Initialize Codex project files.", availability: "unsupported" },
|
|
11626
|
+
{ name: "mcp", description: "Show available MCP servers and tools.", availability: "supported" },
|
|
11627
|
+
{ name: "model", description: "Switch the model for future turns.", availability: "supported" },
|
|
11628
|
+
{ name: "new", description: "Start a new conversation.", availability: "unsupported" },
|
|
11629
|
+
{ name: "permissions", description: "View or change approval policy.", availability: "unsupported" },
|
|
11630
|
+
{ name: "prompts", description: "Show prompt examples.", availability: "unsupported" },
|
|
11631
|
+
{ name: "status", description: "Show current session status.", availability: "supported" }
|
|
10299
11632
|
];
|
|
10300
11633
|
var PANDA_SUPPORTED_COMMANDS = /* @__PURE__ */ new Set([
|
|
10301
11634
|
"compact",
|
|
@@ -10310,28 +11643,28 @@ var DEFAULT_VISIBLE_CODEX_COMMAND_CONFIG = {
|
|
|
10310
11643
|
visible_commands: [
|
|
10311
11644
|
{
|
|
10312
11645
|
name: "model",
|
|
10313
|
-
reason: "
|
|
11646
|
+
reason: "Switch the active Codex CLI model."
|
|
10314
11647
|
},
|
|
10315
11648
|
{
|
|
10316
11649
|
name: "status",
|
|
10317
|
-
reason: "
|
|
11650
|
+
reason: "Show current session model and status."
|
|
10318
11651
|
},
|
|
10319
11652
|
REVIEW_VISIBLE_CODEX_COMMAND_ENTRY,
|
|
10320
11653
|
{
|
|
10321
11654
|
name: "skills",
|
|
10322
|
-
reason: "
|
|
11655
|
+
reason: "Show available project skills."
|
|
10323
11656
|
},
|
|
10324
11657
|
{
|
|
10325
11658
|
name: "mcp",
|
|
10326
|
-
reason: "
|
|
11659
|
+
reason: "Inspect MCP tools and services."
|
|
10327
11660
|
},
|
|
10328
11661
|
{
|
|
10329
11662
|
name: "rename",
|
|
10330
|
-
reason: "
|
|
11663
|
+
reason: "Rename the current conversation."
|
|
10331
11664
|
},
|
|
10332
11665
|
{
|
|
10333
11666
|
name: "compact",
|
|
10334
|
-
reason: "
|
|
11667
|
+
reason: "Compact long conversation context."
|
|
10335
11668
|
}
|
|
10336
11669
|
]
|
|
10337
11670
|
};
|
|
@@ -10364,6 +11697,11 @@ var startPandaSessionService = async ({
|
|
|
10364
11697
|
app.log.info({
|
|
10365
11698
|
diagnosticLogFilePath
|
|
10366
11699
|
}, "Panda Codex forwarding diagnostics will be written to file.");
|
|
11700
|
+
const imageSessionStore = new ImageSessionStore(
|
|
11701
|
+
path11.join(resolvedCodexHomePath, IMAGE_SESSION_STORE_RELATIVE_PATH),
|
|
11702
|
+
path11.join(resolvedCodexHomePath, IMAGE_SESSION_ASSET_RELATIVE_PATH)
|
|
11703
|
+
);
|
|
11704
|
+
await imageSessionStore.load();
|
|
10367
11705
|
let discoveredAgent = null;
|
|
10368
11706
|
let discoveredSessionFiles = {};
|
|
10369
11707
|
let activeSessionId = "";
|
|
@@ -10384,7 +11722,7 @@ var startPandaSessionService = async ({
|
|
|
10384
11722
|
approvals: []
|
|
10385
11723
|
};
|
|
10386
11724
|
const hubAgentRegistry = mode === "hub" ? createHubAgentRegistry({
|
|
10387
|
-
storageFilePath:
|
|
11725
|
+
storageFilePath: path11.join(
|
|
10388
11726
|
resolvedCodexHomePath,
|
|
10389
11727
|
HUB_AGENT_REGISTRY_RELATIVE_PATH
|
|
10390
11728
|
),
|
|
@@ -10392,7 +11730,7 @@ var startPandaSessionService = async ({
|
|
|
10392
11730
|
logger: app.log
|
|
10393
11731
|
}) : null;
|
|
10394
11732
|
const hubPushSubscriptionStore = mode === "hub" ? createHubPushSubscriptionStore({
|
|
10395
|
-
storageFilePath:
|
|
11733
|
+
storageFilePath: path11.join(
|
|
10396
11734
|
resolvedCodexHomePath,
|
|
10397
11735
|
HUB_PUSH_SUBSCRIPTIONS_RELATIVE_PATH
|
|
10398
11736
|
),
|
|
@@ -10400,7 +11738,7 @@ var startPandaSessionService = async ({
|
|
|
10400
11738
|
}) : null;
|
|
10401
11739
|
const hubWebPushNotifier = mode === "hub" ? await import("./hub-web-push-KM4VOXLQ.mjs").then(
|
|
10402
11740
|
({ createHubWebPushNotifier }) => createHubWebPushNotifier({
|
|
10403
|
-
storageFilePath:
|
|
11741
|
+
storageFilePath: path11.join(
|
|
10404
11742
|
resolvedCodexHomePath,
|
|
10405
11743
|
HUB_WEB_PUSH_VAPID_RELATIVE_PATH
|
|
10406
11744
|
),
|
|
@@ -10428,12 +11766,12 @@ var startPandaSessionService = async ({
|
|
|
10428
11766
|
let codexCommandCatalogRefreshPromise = null;
|
|
10429
11767
|
const wsClients = /* @__PURE__ */ new Set();
|
|
10430
11768
|
const emittedTurnCompletionKeys = /* @__PURE__ */ new Map();
|
|
10431
|
-
const
|
|
11769
|
+
const isoNow6 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
10432
11770
|
const sendSocketEvent = (client, type, payload) => {
|
|
10433
11771
|
client.send(
|
|
10434
11772
|
JSON.stringify({
|
|
10435
11773
|
type,
|
|
10436
|
-
timestamp:
|
|
11774
|
+
timestamp: isoNow6(),
|
|
10437
11775
|
payload
|
|
10438
11776
|
})
|
|
10439
11777
|
);
|
|
@@ -10441,7 +11779,7 @@ var startPandaSessionService = async ({
|
|
|
10441
11779
|
const broadcastEvent = (type, payload, options) => {
|
|
10442
11780
|
const message = JSON.stringify({
|
|
10443
11781
|
type,
|
|
10444
|
-
timestamp:
|
|
11782
|
+
timestamp: isoNow6(),
|
|
10445
11783
|
payload
|
|
10446
11784
|
});
|
|
10447
11785
|
for (const client of wsClients) {
|
|
@@ -10487,7 +11825,7 @@ var startPandaSessionService = async ({
|
|
|
10487
11825
|
};
|
|
10488
11826
|
const fileExists = async (targetPath) => {
|
|
10489
11827
|
try {
|
|
10490
|
-
return (await
|
|
11828
|
+
return (await fs9.stat(targetPath)).isFile();
|
|
10491
11829
|
} catch {
|
|
10492
11830
|
return false;
|
|
10493
11831
|
}
|
|
@@ -10503,29 +11841,29 @@ var startPandaSessionService = async ({
|
|
|
10503
11841
|
if (!pathname.startsWith("/")) {
|
|
10504
11842
|
pathname = `/${pathname}`;
|
|
10505
11843
|
}
|
|
10506
|
-
const normalizedPathname =
|
|
11844
|
+
const normalizedPathname = path11.posix.normalize(pathname);
|
|
10507
11845
|
if (normalizedPathname.startsWith("/..")) {
|
|
10508
11846
|
return null;
|
|
10509
11847
|
}
|
|
10510
|
-
const webUiRootPath =
|
|
11848
|
+
const webUiRootPath = path11.resolve(webUiRoot);
|
|
10511
11849
|
const relativePath = normalizedPathname === "/" ? "index.html" : normalizedPathname.replace(/^\/+/, "");
|
|
10512
|
-
const candidatePath =
|
|
10513
|
-
const candidateRelativePath =
|
|
10514
|
-
if (candidateRelativePath.startsWith("..") ||
|
|
11850
|
+
const candidatePath = path11.resolve(webUiRootPath, relativePath.split("/").join(path11.sep));
|
|
11851
|
+
const candidateRelativePath = path11.relative(webUiRootPath, candidatePath);
|
|
11852
|
+
if (candidateRelativePath.startsWith("..") || path11.isAbsolute(candidateRelativePath)) {
|
|
10515
11853
|
return null;
|
|
10516
11854
|
}
|
|
10517
|
-
const hasExtension =
|
|
11855
|
+
const hasExtension = path11.extname(relativePath) !== "";
|
|
10518
11856
|
if (await fileExists(candidatePath)) {
|
|
10519
11857
|
return {
|
|
10520
11858
|
filePath: candidatePath,
|
|
10521
11859
|
cacheControl: relativePath.startsWith("assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600",
|
|
10522
|
-
contentType: WEB_UI_CONTENT_TYPES[
|
|
11860
|
+
contentType: WEB_UI_CONTENT_TYPES[path11.extname(candidatePath).toLowerCase()] ?? "application/octet-stream"
|
|
10523
11861
|
};
|
|
10524
11862
|
}
|
|
10525
11863
|
if (hasExtension) {
|
|
10526
11864
|
return null;
|
|
10527
11865
|
}
|
|
10528
|
-
const indexPath =
|
|
11866
|
+
const indexPath = path11.join(webUiRootPath, "index.html");
|
|
10529
11867
|
if (!await fileExists(indexPath)) {
|
|
10530
11868
|
return null;
|
|
10531
11869
|
}
|
|
@@ -10552,34 +11890,34 @@ var startPandaSessionService = async ({
|
|
|
10552
11890
|
}
|
|
10553
11891
|
};
|
|
10554
11892
|
const isPathInsideProject = (projectPath, candidatePath) => {
|
|
10555
|
-
const resolvedProjectPath =
|
|
10556
|
-
const resolvedCandidatePath =
|
|
10557
|
-
const relativePath =
|
|
11893
|
+
const resolvedProjectPath = path11.resolve(projectPath);
|
|
11894
|
+
const resolvedCandidatePath = path11.resolve(projectPath, candidatePath);
|
|
11895
|
+
const relativePath = path11.relative(resolvedProjectPath, resolvedCandidatePath);
|
|
10558
11896
|
if (!relativePath) {
|
|
10559
11897
|
return true;
|
|
10560
11898
|
}
|
|
10561
|
-
return !relativePath.startsWith("..") && !
|
|
11899
|
+
return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
10562
11900
|
};
|
|
10563
11901
|
const isAbsolutePathInsideProject = (projectPath, candidatePath) => {
|
|
10564
|
-
const resolvedProjectPath =
|
|
10565
|
-
const resolvedCandidatePath =
|
|
10566
|
-
const relativePath =
|
|
11902
|
+
const resolvedProjectPath = path11.resolve(projectPath);
|
|
11903
|
+
const resolvedCandidatePath = path11.resolve(candidatePath);
|
|
11904
|
+
const relativePath = path11.relative(resolvedProjectPath, resolvedCandidatePath);
|
|
10567
11905
|
if (!relativePath) {
|
|
10568
11906
|
return true;
|
|
10569
11907
|
}
|
|
10570
|
-
return !relativePath.startsWith("..") && !
|
|
11908
|
+
return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
10571
11909
|
};
|
|
10572
11910
|
const normalizeSessionFilePreviewPath = (value) => normalizeGitWorkspacePath(value?.trim() ?? "").replace(/^\/+|\/+$/g, "");
|
|
10573
11911
|
const isMissingPathError = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
10574
11912
|
const resolveSessionFilePreviewPath = async (projectPath, requestedPath) => {
|
|
10575
11913
|
const normalizedPath = normalizeSessionFilePreviewPath(requestedPath);
|
|
10576
|
-
const absolutePath = normalizedPath ?
|
|
11914
|
+
const absolutePath = normalizedPath ? path11.resolve(projectPath, normalizedPath) : path11.resolve(projectPath);
|
|
10577
11915
|
if (!isAbsolutePathInsideProject(projectPath, absolutePath)) {
|
|
10578
|
-
throw new Error("
|
|
11916
|
+
throw new Error("Target path is outside the current project.");
|
|
10579
11917
|
}
|
|
10580
11918
|
let realPath;
|
|
10581
11919
|
try {
|
|
10582
|
-
realPath = await
|
|
11920
|
+
realPath = await fs9.realpath(absolutePath);
|
|
10583
11921
|
} catch (error) {
|
|
10584
11922
|
if (isMissingPathError(error)) {
|
|
10585
11923
|
throw new Error("File not found.");
|
|
@@ -10587,7 +11925,7 @@ var startPandaSessionService = async ({
|
|
|
10587
11925
|
throw error;
|
|
10588
11926
|
}
|
|
10589
11927
|
if (!isAbsolutePathInsideProject(projectPath, realPath)) {
|
|
10590
|
-
throw new Error("
|
|
11928
|
+
throw new Error("Target path is outside the current project.");
|
|
10591
11929
|
}
|
|
10592
11930
|
return {
|
|
10593
11931
|
normalizedPath,
|
|
@@ -10596,7 +11934,7 @@ var startPandaSessionService = async ({
|
|
|
10596
11934
|
};
|
|
10597
11935
|
};
|
|
10598
11936
|
const normalizeSessionFilePreviewExtension = (value) => {
|
|
10599
|
-
const extension =
|
|
11937
|
+
const extension = path11.extname(value).trim().toLowerCase();
|
|
10600
11938
|
return extension || null;
|
|
10601
11939
|
};
|
|
10602
11940
|
const detectSessionFilePreviewKind = (fileName, extension) => {
|
|
@@ -10644,7 +11982,7 @@ var startPandaSessionService = async ({
|
|
|
10644
11982
|
return suspiciousControlCount / buffer.length < 0.08;
|
|
10645
11983
|
};
|
|
10646
11984
|
const readPreviewBuffer = async (filePath, byteLimit) => {
|
|
10647
|
-
const handle = await
|
|
11985
|
+
const handle = await fs9.open(filePath, "r");
|
|
10648
11986
|
try {
|
|
10649
11987
|
const buffer = Buffer.alloc(byteLimit);
|
|
10650
11988
|
const { bytesRead } = await handle.read(buffer, 0, byteLimit, 0);
|
|
@@ -10655,7 +11993,7 @@ var startPandaSessionService = async ({
|
|
|
10655
11993
|
};
|
|
10656
11994
|
const readDirectoryHasChildren = async (directoryPath) => {
|
|
10657
11995
|
try {
|
|
10658
|
-
const entries = await
|
|
11996
|
+
const entries = await fs9.readdir(directoryPath);
|
|
10659
11997
|
return entries.length > 0;
|
|
10660
11998
|
} catch {
|
|
10661
11999
|
return false;
|
|
@@ -10665,10 +12003,10 @@ var startPandaSessionService = async ({
|
|
|
10665
12003
|
const nextRelativePath = normalizeGitWorkspacePath(
|
|
10666
12004
|
parentRelativePath ? `${parentRelativePath}/${entry.name}` : entry.name
|
|
10667
12005
|
);
|
|
10668
|
-
const entryPath =
|
|
12006
|
+
const entryPath = path11.join(parentRealPath, entry.name);
|
|
10669
12007
|
if (entry.isSymbolicLink()) {
|
|
10670
12008
|
try {
|
|
10671
|
-
const realPath = await
|
|
12009
|
+
const realPath = await fs9.realpath(entryPath);
|
|
10672
12010
|
if (!isAbsolutePathInsideProject(projectPath, realPath)) {
|
|
10673
12011
|
return null;
|
|
10674
12012
|
}
|
|
@@ -10676,7 +12014,7 @@ var startPandaSessionService = async ({
|
|
|
10676
12014
|
return null;
|
|
10677
12015
|
}
|
|
10678
12016
|
}
|
|
10679
|
-
const stat = await
|
|
12017
|
+
const stat = await fs9.stat(entryPath).catch(() => null);
|
|
10680
12018
|
if (!stat) {
|
|
10681
12019
|
return null;
|
|
10682
12020
|
}
|
|
@@ -10974,7 +12312,7 @@ var startPandaSessionService = async ({
|
|
|
10974
12312
|
["diff", "--no-ext-diff", "--no-color", "--binary", "HEAD", "--", filePath]
|
|
10975
12313
|
)).stdout;
|
|
10976
12314
|
const readUntrackedFileDiff = async (projectPath, filePath) => {
|
|
10977
|
-
const absolutePath =
|
|
12315
|
+
const absolutePath = path11.join(projectPath, filePath);
|
|
10978
12316
|
return (await runGitCommand(
|
|
10979
12317
|
projectPath,
|
|
10980
12318
|
["diff", "--no-index", "--no-color", "--binary", "--", "/dev/null", absolutePath],
|
|
@@ -10986,9 +12324,9 @@ var startPandaSessionService = async ({
|
|
|
10986
12324
|
["diff", "--numstat", "--find-renames", "HEAD", "--", filePath]
|
|
10987
12325
|
)).stdout);
|
|
10988
12326
|
const readUntrackedFileChangeCounts = async (projectPath, filePath) => {
|
|
10989
|
-
const absolutePath =
|
|
12327
|
+
const absolutePath = path11.join(projectPath, filePath);
|
|
10990
12328
|
try {
|
|
10991
|
-
const content = await
|
|
12329
|
+
const content = await fs9.readFile(absolutePath, "utf8");
|
|
10992
12330
|
return {
|
|
10993
12331
|
additions: countTextFileLines(content),
|
|
10994
12332
|
deletions: 0
|
|
@@ -11030,7 +12368,7 @@ var startPandaSessionService = async ({
|
|
|
11030
12368
|
ahead_count: branchStatus.aheadCount,
|
|
11031
12369
|
behind_count: branchStatus.behindCount,
|
|
11032
12370
|
files,
|
|
11033
|
-
updated_at:
|
|
12371
|
+
updated_at: isoNow6()
|
|
11034
12372
|
};
|
|
11035
12373
|
};
|
|
11036
12374
|
const readSessionGitWorkspaceFileDiff = async (session, project, filePath, previousPath) => {
|
|
@@ -11078,7 +12416,7 @@ var startPandaSessionService = async ({
|
|
|
11078
12416
|
head_oid: branchStatus.headOid,
|
|
11079
12417
|
upstream_head_oid: branchStatus.upstreamHeadOid,
|
|
11080
12418
|
commits: parseGitHistory(logOutput.stdout),
|
|
11081
|
-
updated_at:
|
|
12419
|
+
updated_at: isoNow6()
|
|
11082
12420
|
};
|
|
11083
12421
|
};
|
|
11084
12422
|
const readSessionGitHistoryFileDiff = async (session, project, commitOid, filePath, previousPath) => {
|
|
@@ -11136,7 +12474,7 @@ var startPandaSessionService = async ({
|
|
|
11136
12474
|
raw: trimmed
|
|
11137
12475
|
};
|
|
11138
12476
|
};
|
|
11139
|
-
const
|
|
12477
|
+
const detectCodexCliVersion2 = async () => new Promise((resolve) => {
|
|
11140
12478
|
let stdout = "";
|
|
11141
12479
|
let settled = false;
|
|
11142
12480
|
const command = process.platform === "win32" ? "codex" : "codex";
|
|
@@ -11154,7 +12492,7 @@ var startPandaSessionService = async ({
|
|
|
11154
12492
|
const timer = setTimeout(() => {
|
|
11155
12493
|
child.kill();
|
|
11156
12494
|
finalize(null);
|
|
11157
|
-
},
|
|
12495
|
+
}, CODEX_VERSION_PROBE_TIMEOUT_MS2);
|
|
11158
12496
|
child.stdout.on("data", (chunk) => {
|
|
11159
12497
|
stdout += chunk.toString("utf8");
|
|
11160
12498
|
});
|
|
@@ -11283,8 +12621,8 @@ var startPandaSessionService = async ({
|
|
|
11283
12621
|
const getPandaLocalBaseDirectory = async () => {
|
|
11284
12622
|
if (!pandaLocalBaseDirectoryPromise) {
|
|
11285
12623
|
pandaLocalBaseDirectoryPromise = (async () => {
|
|
11286
|
-
const baseDirectory = codexHome?.trim() || process.env.PANDA_CODEX_HOME?.trim() ||
|
|
11287
|
-
await
|
|
12624
|
+
const baseDirectory = codexHome?.trim() || process.env.PANDA_CODEX_HOME?.trim() || path11.join(os6.homedir(), ".panda");
|
|
12625
|
+
await fs9.mkdir(baseDirectory, { recursive: true });
|
|
11288
12626
|
return baseDirectory;
|
|
11289
12627
|
})();
|
|
11290
12628
|
}
|
|
@@ -11294,9 +12632,9 @@ var startPandaSessionService = async ({
|
|
|
11294
12632
|
if (!codexCommandCatalogCacheFilePathPromise) {
|
|
11295
12633
|
codexCommandCatalogCacheFilePathPromise = (async () => {
|
|
11296
12634
|
const baseDirectory = await getPandaLocalBaseDirectory();
|
|
11297
|
-
const cacheDirectory =
|
|
11298
|
-
await
|
|
11299
|
-
return
|
|
12635
|
+
const cacheDirectory = path11.join(baseDirectory, "cache");
|
|
12636
|
+
await fs9.mkdir(cacheDirectory, { recursive: true });
|
|
12637
|
+
return path11.join(cacheDirectory, "codex-command-catalog.json");
|
|
11300
12638
|
})();
|
|
11301
12639
|
}
|
|
11302
12640
|
return codexCommandCatalogCacheFilePathPromise;
|
|
@@ -11304,7 +12642,7 @@ var startPandaSessionService = async ({
|
|
|
11304
12642
|
const readCachedCodexCommandCatalog = async () => {
|
|
11305
12643
|
const cacheFilePath = await getCodexCommandCatalogCacheFilePath();
|
|
11306
12644
|
try {
|
|
11307
|
-
const raw = await
|
|
12645
|
+
const raw = await fs9.readFile(cacheFilePath, "utf8");
|
|
11308
12646
|
return codexCommandCatalogSchema.parse(JSON.parse(raw));
|
|
11309
12647
|
} catch {
|
|
11310
12648
|
return null;
|
|
@@ -11312,16 +12650,16 @@ var startPandaSessionService = async ({
|
|
|
11312
12650
|
};
|
|
11313
12651
|
const writeCachedCodexCommandCatalog = async (catalog) => {
|
|
11314
12652
|
const cacheFilePath = await getCodexCommandCatalogCacheFilePath();
|
|
11315
|
-
await
|
|
12653
|
+
await fs9.writeFile(cacheFilePath, `${JSON.stringify(catalog, null, 2)}
|
|
11316
12654
|
`, "utf8");
|
|
11317
12655
|
};
|
|
11318
12656
|
const getCodexVisibleCommandsConfigFilePath = async () => {
|
|
11319
12657
|
if (!codexVisibleCommandsConfigFilePathPromise) {
|
|
11320
12658
|
codexVisibleCommandsConfigFilePathPromise = (async () => {
|
|
11321
12659
|
const baseDirectory = await getPandaLocalBaseDirectory();
|
|
11322
|
-
const configDirectory =
|
|
11323
|
-
await
|
|
11324
|
-
return
|
|
12660
|
+
const configDirectory = path11.join(baseDirectory, "config");
|
|
12661
|
+
await fs9.mkdir(configDirectory, { recursive: true });
|
|
12662
|
+
return path11.join(configDirectory, "codex-visible-commands.json");
|
|
11325
12663
|
})();
|
|
11326
12664
|
}
|
|
11327
12665
|
return codexVisibleCommandsConfigFilePathPromise;
|
|
@@ -11389,13 +12727,13 @@ var startPandaSessionService = async ({
|
|
|
11389
12727
|
};
|
|
11390
12728
|
const writeVisibleCodexCommandConfig = async (config) => {
|
|
11391
12729
|
const configFilePath = await getCodexVisibleCommandsConfigFilePath();
|
|
11392
|
-
await
|
|
12730
|
+
await fs9.writeFile(configFilePath, `${JSON.stringify(config, null, 2)}
|
|
11393
12731
|
`, "utf8");
|
|
11394
12732
|
};
|
|
11395
12733
|
const readVisibleCodexCommandConfig = async () => {
|
|
11396
12734
|
const configFilePath = await getCodexVisibleCommandsConfigFilePath();
|
|
11397
12735
|
try {
|
|
11398
|
-
const raw = await
|
|
12736
|
+
const raw = await fs9.readFile(configFilePath, "utf8");
|
|
11399
12737
|
const parsed = parseVisibleCodexCommandConfig(JSON.parse(raw));
|
|
11400
12738
|
if (parsed) {
|
|
11401
12739
|
const migrated = migrateVisibleCodexCommandConfig(parsed);
|
|
@@ -11432,14 +12770,14 @@ var startPandaSessionService = async ({
|
|
|
11432
12770
|
const extractJsonObject = (value) => {
|
|
11433
12771
|
const trimmed = value.trim();
|
|
11434
12772
|
if (!trimmed) {
|
|
11435
|
-
throw new Error("Codex
|
|
12773
|
+
throw new Error("Codex did not return a command draft.");
|
|
11436
12774
|
}
|
|
11437
12775
|
const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
11438
12776
|
const candidate = fencedMatch?.[1]?.trim() || trimmed;
|
|
11439
12777
|
const firstBraceIndex = candidate.indexOf("{");
|
|
11440
12778
|
const lastBraceIndex = candidate.lastIndexOf("}");
|
|
11441
12779
|
if (firstBraceIndex < 0 || lastBraceIndex <= firstBraceIndex) {
|
|
11442
|
-
throw new Error("Codex
|
|
12780
|
+
throw new Error("Codex command draft is not valid JSON.");
|
|
11443
12781
|
}
|
|
11444
12782
|
return candidate.slice(firstBraceIndex, lastBraceIndex + 1);
|
|
11445
12783
|
};
|
|
@@ -11604,7 +12942,7 @@ var startPandaSessionService = async ({
|
|
|
11604
12942
|
].join("\n");
|
|
11605
12943
|
};
|
|
11606
12944
|
function resolveCodexHomePath(codexHome2) {
|
|
11607
|
-
return codexHome2?.trim() ||
|
|
12945
|
+
return codexHome2?.trim() || path11.join(os6.homedir(), ".codex");
|
|
11608
12946
|
}
|
|
11609
12947
|
function formatDiagnosticLogEntry(level, message, payload) {
|
|
11610
12948
|
const lines = [
|
|
@@ -11616,9 +12954,9 @@ var startPandaSessionService = async ({
|
|
|
11616
12954
|
`;
|
|
11617
12955
|
}
|
|
11618
12956
|
function createDiagnosticLogger(codexHome2, options) {
|
|
11619
|
-
const filePath =
|
|
11620
|
-
let writeQueue =
|
|
11621
|
-
() =>
|
|
12957
|
+
const filePath = path11.join(resolveCodexHomePath(codexHome2), options.relativePath);
|
|
12958
|
+
let writeQueue = fs9.mkdir(path11.dirname(filePath), { recursive: true }).then(
|
|
12959
|
+
() => fs9.writeFile(
|
|
11622
12960
|
filePath,
|
|
11623
12961
|
formatDiagnosticLogEntry("INFO", options.initMessage, {
|
|
11624
12962
|
filePath,
|
|
@@ -11628,7 +12966,7 @@ var startPandaSessionService = async ({
|
|
|
11628
12966
|
)
|
|
11629
12967
|
).catch(() => void 0);
|
|
11630
12968
|
const enqueueWrite = (level, message, payload) => {
|
|
11631
|
-
writeQueue = writeQueue.catch(() => void 0).then(() =>
|
|
12969
|
+
writeQueue = writeQueue.catch(() => void 0).then(() => fs9.appendFile(filePath, formatDiagnosticLogEntry(level, message, payload), "utf8")).catch(() => void 0);
|
|
11632
12970
|
};
|
|
11633
12971
|
return {
|
|
11634
12972
|
filePath,
|
|
@@ -11650,17 +12988,17 @@ var startPandaSessionService = async ({
|
|
|
11650
12988
|
}
|
|
11651
12989
|
const basenameFromPath = (targetPath) => {
|
|
11652
12990
|
const normalized = targetPath.replace(/[\\/]+$/, "");
|
|
11653
|
-
return
|
|
12991
|
+
return path11.basename(normalized) || normalized;
|
|
11654
12992
|
};
|
|
11655
12993
|
const truncateSummary = (value, maxLength = 88) => {
|
|
11656
12994
|
const trimmed = value.trim();
|
|
11657
12995
|
if (trimmed.length <= maxLength) {
|
|
11658
12996
|
return trimmed;
|
|
11659
12997
|
}
|
|
11660
|
-
return `${trimmed.slice(0, maxLength - 1).trimEnd()}
|
|
12998
|
+
return `${trimmed.slice(0, maxLength - 1).trimEnd()}...`;
|
|
11661
12999
|
};
|
|
11662
13000
|
const sanitizeGeneratedSessionTitle = (value, maxLength = SESSION_GENERATED_TITLE_MAX_LENGTH) => {
|
|
11663
|
-
const normalized = value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean).join(" ").replace(/^["'
|
|
13001
|
+
const normalized = value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean).join(" ").replace(/^[\s\"'\[\]()]+|[\s\"'\[\]()]+$/g, "").replace(/^(Title)\s*[:?]\s*/i, "").replace(/\s+/g, " ").trim();
|
|
11664
13002
|
if (!normalized) {
|
|
11665
13003
|
return null;
|
|
11666
13004
|
}
|
|
@@ -11673,18 +13011,18 @@ var startPandaSessionService = async ({
|
|
|
11673
13011
|
const message = input.message.trim();
|
|
11674
13012
|
const attachmentNames = input.attachments.map((attachment) => attachment.name.trim()).filter(Boolean).slice(0, 5);
|
|
11675
13013
|
const contextParts = [
|
|
11676
|
-
message ?
|
|
13014
|
+
message ? `First user message:
|
|
11677
13015
|
${message}` : null,
|
|
11678
|
-
attachmentNames.length > 0 ?
|
|
11679
|
-
|
|
13016
|
+
attachmentNames.length > 0 ? `Attachments:
|
|
13017
|
+
` + attachmentNames.map((name) => `- ${name}`).join("\n") : null
|
|
11680
13018
|
].filter(Boolean);
|
|
11681
13019
|
return [
|
|
11682
|
-
"
|
|
11683
|
-
"
|
|
11684
|
-
"1.
|
|
11685
|
-
"2.
|
|
11686
|
-
"3.
|
|
11687
|
-
"4.
|
|
13020
|
+
"Generate a short conversation title from the first message below.",
|
|
13021
|
+
"Requirements:",
|
|
13022
|
+
"1. Output only the title, without quotes or explanation.",
|
|
13023
|
+
"2. Summarize the user goal accurately.",
|
|
13024
|
+
"3. Keep it concise.",
|
|
13025
|
+
"4. Avoid vague words like help, issue, chat, or new conversation.",
|
|
11688
13026
|
"",
|
|
11689
13027
|
...contextParts
|
|
11690
13028
|
].join("\n");
|
|
@@ -11953,7 +13291,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
11953
13291
|
return nextOverlayEntries;
|
|
11954
13292
|
};
|
|
11955
13293
|
const readTimelineFromRollout = async (sessionId) => {
|
|
11956
|
-
const { readCodexTimeline } = await import("./src-
|
|
13294
|
+
const { readCodexTimeline } = await import("./src-FZ5WMACF.mjs");
|
|
11957
13295
|
return readCodexTimeline(sessionId, {
|
|
11958
13296
|
codexHome,
|
|
11959
13297
|
sessionFiles: discoveredSessionFiles
|
|
@@ -12045,7 +13383,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12045
13383
|
}
|
|
12046
13384
|
return {
|
|
12047
13385
|
session_id: sessionId,
|
|
12048
|
-
recovered_at:
|
|
13386
|
+
recovered_at: isoNow6(),
|
|
12049
13387
|
session_patch: materialized.sessionPatch,
|
|
12050
13388
|
timeline: materialized.timeline,
|
|
12051
13389
|
interactions: materialized.interactions,
|
|
@@ -12054,16 +13392,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12054
13392
|
terminal_snapshot: materialized.terminalSnapshot
|
|
12055
13393
|
};
|
|
12056
13394
|
};
|
|
12057
|
-
const findLastUserTimelineEntryIndex = (entries) => {
|
|
12058
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
12059
|
-
if (entries[index]?.kind === "user") {
|
|
12060
|
-
return index;
|
|
12061
|
-
}
|
|
12062
|
-
}
|
|
12063
|
-
return -1;
|
|
12064
|
-
};
|
|
12065
13395
|
const buildTailTimelineEntries = (entries) => {
|
|
12066
|
-
const lastUserIndex =
|
|
13396
|
+
const lastUserIndex = findTailTimelineAnchorEntryIndex(entries);
|
|
12067
13397
|
if (lastUserIndex < 0) {
|
|
12068
13398
|
return {
|
|
12069
13399
|
anchorEntryId: null,
|
|
@@ -12073,7 +13403,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12073
13403
|
}
|
|
12074
13404
|
return {
|
|
12075
13405
|
anchorEntryId: entries[lastUserIndex]?.id ?? null,
|
|
12076
|
-
hasEarlierEntries:
|
|
13406
|
+
hasEarlierEntries: hasEarlierVisibleConversationTurns(entries),
|
|
12077
13407
|
entries: entries.slice(lastUserIndex)
|
|
12078
13408
|
};
|
|
12079
13409
|
};
|
|
@@ -12113,7 +13443,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12113
13443
|
if (normalized.length <= limit) {
|
|
12114
13444
|
return normalized;
|
|
12115
13445
|
}
|
|
12116
|
-
return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}
|
|
13446
|
+
return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}...`;
|
|
12117
13447
|
};
|
|
12118
13448
|
const tryParseTimelineToolBody = (body) => {
|
|
12119
13449
|
try {
|
|
@@ -12168,7 +13498,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12168
13498
|
if (patchSummary.files.length === 1) {
|
|
12169
13499
|
return truncateTimelineToolText(patchSummary.files[0].path);
|
|
12170
13500
|
}
|
|
12171
|
-
return `${patchSummary.files.length}
|
|
13501
|
+
return `${patchSummary.files.length} files`;
|
|
12172
13502
|
}
|
|
12173
13503
|
const parsedBody = tryParseTimelineToolBody(entry.body);
|
|
12174
13504
|
if (parsedBody) {
|
|
@@ -12186,7 +13516,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12186
13516
|
}
|
|
12187
13517
|
return truncateTimelineToolText(entry.body || entry.title);
|
|
12188
13518
|
}
|
|
12189
|
-
return truncateTimelineToolText(entry.body || "\
|
|
13519
|
+
return truncateTimelineToolText(entry.body || "\u5BB8\u30E5\u53FF\u93B5\u0446\uE511");
|
|
12190
13520
|
};
|
|
12191
13521
|
const summarizeTimelineEntryTransport = (entry, options) => {
|
|
12192
13522
|
if (entry.kind !== "tool") {
|
|
@@ -12246,7 +13576,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12246
13576
|
] : tail.entries;
|
|
12247
13577
|
return {
|
|
12248
13578
|
session_id: sessionId,
|
|
12249
|
-
generated_at:
|
|
13579
|
+
generated_at: isoNow6(),
|
|
12250
13580
|
view,
|
|
12251
13581
|
anchor_entry_id: tail.anchorEntryId,
|
|
12252
13582
|
has_earlier_entries: view === "full_compact" ? false : tail.hasEarlierEntries,
|
|
@@ -12319,7 +13649,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12319
13649
|
}
|
|
12320
13650
|
return {
|
|
12321
13651
|
session_id: sessionId,
|
|
12322
|
-
generated_at:
|
|
13652
|
+
generated_at: isoNow6(),
|
|
12323
13653
|
session_patch: materialized.sessionPatch,
|
|
12324
13654
|
timeline: buildTimelineSnapshot(
|
|
12325
13655
|
sessionId,
|
|
@@ -12397,7 +13727,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12397
13727
|
const readSessionCompletionTitle = (sessionId) => {
|
|
12398
13728
|
const session = managedSessions.get(sessionId) ?? snapshot.sessions.find((item) => item.id === sessionId) ?? null;
|
|
12399
13729
|
const title = session?.title?.trim();
|
|
12400
|
-
return title || "\
|
|
13730
|
+
return title || "\u8930\u64B3\u58A0\u6D7C\u6C33\u763D";
|
|
12401
13731
|
};
|
|
12402
13732
|
const resolveCompletionReply = (input) => {
|
|
12403
13733
|
if (input.completionReason !== "completed") {
|
|
@@ -12448,12 +13778,12 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12448
13778
|
const formatCompletionPushBody = (value) => {
|
|
12449
13779
|
const normalized = (typeof value === "string" ? value : "").replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
|
|
12450
13780
|
if (!normalized) {
|
|
12451
|
-
return "
|
|
13781
|
+
return "Open to view the final response.";
|
|
12452
13782
|
}
|
|
12453
13783
|
if (normalized.length <= COMPLETION_PUSH_BODY_MAX_LENGTH) {
|
|
12454
13784
|
return normalized;
|
|
12455
13785
|
}
|
|
12456
|
-
return `${normalized.slice(0, COMPLETION_PUSH_BODY_MAX_LENGTH - 1).trimEnd()}
|
|
13786
|
+
return `${normalized.slice(0, COMPLETION_PUSH_BODY_MAX_LENGTH - 1).trimEnd()}...`;
|
|
12457
13787
|
};
|
|
12458
13788
|
const notifyCompletionPush = async (payload) => {
|
|
12459
13789
|
if (!hubPushSubscriptionStore || !hubWebPushNotifier?.publicConfig.supported) {
|
|
@@ -12464,7 +13794,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12464
13794
|
return;
|
|
12465
13795
|
}
|
|
12466
13796
|
const notificationPayload = {
|
|
12467
|
-
title: payload.sessionTitle || "Panda
|
|
13797
|
+
title: payload.sessionTitle || "Panda session completed",
|
|
12468
13798
|
body: formatCompletionPushBody(payload.finalReply),
|
|
12469
13799
|
url: `/session/${payload.sessionId}`,
|
|
12470
13800
|
tag: `session-completed:${payload.sessionId}:${payload.completedAt}`,
|
|
@@ -12517,7 +13847,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12517
13847
|
);
|
|
12518
13848
|
if (result.ok) {
|
|
12519
13849
|
hubPushSubscriptionStore.markDeliveryResult(endpoint, { success: true });
|
|
12520
|
-
return { ok: true, deliveredAt:
|
|
13850
|
+
return { ok: true, deliveredAt: isoNow6() };
|
|
12521
13851
|
}
|
|
12522
13852
|
hubPushSubscriptionStore.markDeliveryResult(endpoint, {
|
|
12523
13853
|
success: false,
|
|
@@ -12605,7 +13935,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12605
13935
|
session_id: sessionId,
|
|
12606
13936
|
change_set_id: changeSet.id,
|
|
12607
13937
|
file: match,
|
|
12608
|
-
empty_message: "
|
|
13938
|
+
empty_message: "No displayable patch content."
|
|
12609
13939
|
};
|
|
12610
13940
|
};
|
|
12611
13941
|
const upsertSessionInteractions = (sessionId, incoming) => {
|
|
@@ -12722,8 +14052,23 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12722
14052
|
}
|
|
12723
14053
|
return Math.max(0, parsed);
|
|
12724
14054
|
};
|
|
12725
|
-
const
|
|
12726
|
-
|
|
14055
|
+
const isProjectlessSession = (session) => session.project_id.trim().length === 0;
|
|
14056
|
+
const buildManagedSessionCapability = (options) => {
|
|
14057
|
+
const archived = options?.archived === true;
|
|
14058
|
+
const projectless = options?.projectless === true;
|
|
14059
|
+
const live = options?.live ?? !archived;
|
|
14060
|
+
return {
|
|
14061
|
+
can_stream_live: archived ? false : live,
|
|
14062
|
+
can_send_input: !archived,
|
|
14063
|
+
can_interrupt: !archived,
|
|
14064
|
+
can_approve: archived ? false : live,
|
|
14065
|
+
can_reject: archived ? false : live,
|
|
14066
|
+
can_show_git: projectless ? false : true,
|
|
14067
|
+
can_show_terminal: projectless ? false : !archived
|
|
14068
|
+
};
|
|
14069
|
+
};
|
|
14070
|
+
const buildWorkspaceProjectStatsEntry = (projectId, sessions, selectedSessionId) => {
|
|
14071
|
+
const projectSessions = sessions.filter((session) => session.project_id === projectId);
|
|
12727
14072
|
const visibleSessionCount = projectSessions.filter(
|
|
12728
14073
|
(session) => !session.archived && isWorkspaceVisibleSession(session, selectedSessionId)
|
|
12729
14074
|
).length;
|
|
@@ -12732,12 +14077,22 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12732
14077
|
(session) => !session.archived && !isWorkspaceVisibleSession(session, selectedSessionId)
|
|
12733
14078
|
).length;
|
|
12734
14079
|
return {
|
|
12735
|
-
project_id:
|
|
14080
|
+
project_id: projectId,
|
|
12736
14081
|
visible_session_count: visibleSessionCount,
|
|
12737
14082
|
archived_session_count: archivedSessionCount,
|
|
12738
14083
|
hidden_history_count: hiddenHistoryCount
|
|
12739
14084
|
};
|
|
12740
|
-
}
|
|
14085
|
+
};
|
|
14086
|
+
const buildWorkspaceProjectStats = (projects, sessions, selectedSessionId) => [
|
|
14087
|
+
...projects.map(
|
|
14088
|
+
(project) => buildWorkspaceProjectStatsEntry(project.id, sessions, selectedSessionId)
|
|
14089
|
+
),
|
|
14090
|
+
buildWorkspaceProjectStatsEntry(
|
|
14091
|
+
PROJECTLESS_SESSION_PROJECT_ID,
|
|
14092
|
+
sessions.filter((session) => isProjectlessSession(session)),
|
|
14093
|
+
selectedSessionId
|
|
14094
|
+
)
|
|
14095
|
+
];
|
|
12741
14096
|
const toWorkspaceProjectDirectory = (project) => ({
|
|
12742
14097
|
id: project.id,
|
|
12743
14098
|
agent_id: project.agent_id,
|
|
@@ -12752,10 +14107,12 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12752
14107
|
display_name: agent.display_name,
|
|
12753
14108
|
status: agent.status
|
|
12754
14109
|
});
|
|
14110
|
+
const normalizeSessionProvider = (provider) => provider === "claude" || provider === "image" ? provider : "codex";
|
|
12755
14111
|
const toWorkspaceSessionListItem = (session) => ({
|
|
12756
14112
|
id: session.id,
|
|
12757
14113
|
agent_id: session.agent_id,
|
|
12758
14114
|
project_id: session.project_id,
|
|
14115
|
+
provider: normalizeSessionProvider(session.provider),
|
|
12759
14116
|
archived: session.archived,
|
|
12760
14117
|
title: session.title,
|
|
12761
14118
|
last_event_at: session.last_event_at,
|
|
@@ -12770,7 +14127,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12770
14127
|
project_id: session.project_id,
|
|
12771
14128
|
archived: session.archived,
|
|
12772
14129
|
title: session.title,
|
|
12773
|
-
provider: session.provider,
|
|
14130
|
+
provider: normalizeSessionProvider(session.provider),
|
|
12774
14131
|
mode: session.mode,
|
|
12775
14132
|
health: session.health,
|
|
12776
14133
|
branch: session.branch,
|
|
@@ -12785,6 +14142,20 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12785
14142
|
context_usage: session.context_usage,
|
|
12786
14143
|
capability: session.capability
|
|
12787
14144
|
});
|
|
14145
|
+
const resolveImageModelVendor = (channel, modelId) => channel?.models.find((model) => model.id === modelId)?.vendor ?? (modelId.toLowerCase().startsWith("grok-") ? "xai" : "openai");
|
|
14146
|
+
const readImageSessionDetail = async (sessionId) => {
|
|
14147
|
+
const record = findImageSessionRecord(sessionId);
|
|
14148
|
+
if (!record) {
|
|
14149
|
+
return null;
|
|
14150
|
+
}
|
|
14151
|
+
return {
|
|
14152
|
+
generated_at: isoNow6(),
|
|
14153
|
+
session: toWorkspaceSessionDetail(record.session),
|
|
14154
|
+
turns: record.turns.map(
|
|
14155
|
+
(turn) => imageSessionStore.toProtocolTurn(record, turn, buildImageAssetUrl)
|
|
14156
|
+
)
|
|
14157
|
+
};
|
|
14158
|
+
};
|
|
12788
14159
|
const buildHubDirectorySnapshot = () => ({
|
|
12789
14160
|
generated_at: snapshot.generated_at,
|
|
12790
14161
|
agents: snapshot.agents
|
|
@@ -12811,7 +14182,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12811
14182
|
const projectIds = new Set(projects.map((project) => project.id));
|
|
12812
14183
|
const agentSessions = sortSessionsByActivity2(
|
|
12813
14184
|
snapshot.sessions.filter(
|
|
12814
|
-
(session) => session.agent_id === localAgentId && projectIds.has(session.project_id)
|
|
14185
|
+
(session) => session.agent_id === localAgentId && (isProjectlessSession(session) || projectIds.has(session.project_id))
|
|
12815
14186
|
)
|
|
12816
14187
|
);
|
|
12817
14188
|
const sessions = agentSessions.filter(
|
|
@@ -12822,7 +14193,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12822
14193
|
) ?? null : null;
|
|
12823
14194
|
const displayedSessions = selectedHiddenSession ? sortSessionsByActivity2([selectedHiddenSession, ...sessions]) : sessions;
|
|
12824
14195
|
return {
|
|
12825
|
-
generated_at:
|
|
14196
|
+
generated_at: isoNow6(),
|
|
12826
14197
|
agent: (() => {
|
|
12827
14198
|
const localAgent = snapshot.agents.find((agent) => agent.id === localAgentId) ?? null;
|
|
12828
14199
|
return localAgent ? toWorkspaceAgentSummary(localAgent) : null;
|
|
@@ -12838,12 +14209,17 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12838
14209
|
const offset = parseWorkspaceSessionCursor(input.cursor);
|
|
12839
14210
|
const selectedSessionId = input.selectedSessionId?.trim() ?? "";
|
|
12840
14211
|
const projectId = input.projectId?.trim() ?? "";
|
|
14212
|
+
const projectless = input.projectless === true;
|
|
12841
14213
|
const filteredSessions = sortSessionsByActivity2(
|
|
12842
14214
|
snapshot.sessions.filter((session) => {
|
|
12843
14215
|
if (session.agent_id !== localAgentId) {
|
|
12844
14216
|
return false;
|
|
12845
14217
|
}
|
|
12846
|
-
if (
|
|
14218
|
+
if (projectless) {
|
|
14219
|
+
if (!isProjectlessSession(session)) {
|
|
14220
|
+
return false;
|
|
14221
|
+
}
|
|
14222
|
+
} else if (projectId && session.project_id !== projectId) {
|
|
12847
14223
|
return false;
|
|
12848
14224
|
}
|
|
12849
14225
|
if (input.bucket === "archived") {
|
|
@@ -12856,19 +14232,26 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12856
14232
|
const nextOffset = offset + pageSessions.length;
|
|
12857
14233
|
return {
|
|
12858
14234
|
bucket: input.bucket,
|
|
12859
|
-
project_id: projectId || null,
|
|
14235
|
+
project_id: projectless ? PROJECTLESS_SESSION_PROJECT_ID : projectId || null,
|
|
12860
14236
|
sessions: pageSessions.map(toWorkspaceSessionListItem),
|
|
12861
14237
|
next_cursor: nextOffset < filteredSessions.length ? String(nextOffset) : null,
|
|
12862
14238
|
total_count: filteredSessions.length
|
|
12863
14239
|
};
|
|
12864
14240
|
};
|
|
12865
14241
|
const readWorkspaceSessionDetail = async (sessionId) => {
|
|
14242
|
+
const imageDetail = await readImageSessionDetail(sessionId);
|
|
14243
|
+
if (imageDetail) {
|
|
14244
|
+
return {
|
|
14245
|
+
generated_at: imageDetail.generated_at,
|
|
14246
|
+
session: imageDetail.session
|
|
14247
|
+
};
|
|
14248
|
+
}
|
|
12866
14249
|
const session = await findSnapshotSession(sessionId);
|
|
12867
14250
|
if (!session) {
|
|
12868
14251
|
return null;
|
|
12869
14252
|
}
|
|
12870
14253
|
return {
|
|
12871
|
-
generated_at:
|
|
14254
|
+
generated_at: isoNow6(),
|
|
12872
14255
|
session: toWorkspaceSessionDetail(session)
|
|
12873
14256
|
};
|
|
12874
14257
|
};
|
|
@@ -12910,7 +14293,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12910
14293
|
}
|
|
12911
14294
|
snapshot = {
|
|
12912
14295
|
...snapshot,
|
|
12913
|
-
generated_at:
|
|
14296
|
+
generated_at: isoNow6(),
|
|
12914
14297
|
agents: snapshot.agents.map((agent) => ({
|
|
12915
14298
|
...agent,
|
|
12916
14299
|
project_count: nextProjectCounts.get(agent.id) ?? 0,
|
|
@@ -12923,7 +14306,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12923
14306
|
}
|
|
12924
14307
|
snapshot = {
|
|
12925
14308
|
...snapshot,
|
|
12926
|
-
generated_at:
|
|
14309
|
+
generated_at: isoNow6(),
|
|
12927
14310
|
agents: discoveredAgent ? [
|
|
12928
14311
|
{
|
|
12929
14312
|
...discoveredAgent,
|
|
@@ -12992,7 +14375,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12992
14375
|
}
|
|
12993
14376
|
};
|
|
12994
14377
|
const createCommandPanel = (input) => {
|
|
12995
|
-
const submittedAt = input.submittedAt ??
|
|
14378
|
+
const submittedAt = input.submittedAt ?? isoNow6();
|
|
12996
14379
|
return {
|
|
12997
14380
|
panel_id: `command-panel-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
12998
14381
|
session_id: input.sessionId,
|
|
@@ -13035,10 +14418,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13035
14418
|
const refreshCodexCommandCatalog = async () => {
|
|
13036
14419
|
if (!codexCommandCatalogRefreshPromise) {
|
|
13037
14420
|
codexCommandCatalogRefreshPromise = (async () => {
|
|
13038
|
-
const cliVersion = await
|
|
14421
|
+
const cliVersion = await detectCodexCliVersion2();
|
|
13039
14422
|
const catalog = codexCommandCatalogSchema.parse({
|
|
13040
14423
|
cli_version: cliVersion,
|
|
13041
|
-
loaded_at:
|
|
14424
|
+
loaded_at: isoNow6(),
|
|
13042
14425
|
cache_ttl_ms: 0,
|
|
13043
14426
|
commands: await loadCodexCommandCatalog(cliVersion)
|
|
13044
14427
|
});
|
|
@@ -13065,6 +14448,21 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13065
14448
|
};
|
|
13066
14449
|
};
|
|
13067
14450
|
const renameSession = async (sessionId, nextName) => {
|
|
14451
|
+
const imageSession = findImageSessionRecord(sessionId);
|
|
14452
|
+
if (imageSession) {
|
|
14453
|
+
await imageSessionStore.mutateSessionRef(sessionId, (session) => ({
|
|
14454
|
+
...session,
|
|
14455
|
+
title: nextName
|
|
14456
|
+
}));
|
|
14457
|
+
syncImageSessionSnapshot(sessionId);
|
|
14458
|
+
broadcastEvent("session.updated", {
|
|
14459
|
+
sessionId,
|
|
14460
|
+
sessionPatch: {
|
|
14461
|
+
title: nextName
|
|
14462
|
+
}
|
|
14463
|
+
});
|
|
14464
|
+
return;
|
|
14465
|
+
}
|
|
13068
14466
|
if (managedSessions.has(sessionId)) {
|
|
13069
14467
|
const managedSession = managedSessions.get(sessionId);
|
|
13070
14468
|
managedSessions.set(sessionId, {
|
|
@@ -13096,28 +14494,276 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13096
14494
|
});
|
|
13097
14495
|
try {
|
|
13098
14496
|
const generated = await liveSessionStream.runOneShotPrompt({
|
|
13099
|
-
cwd: input.
|
|
14497
|
+
cwd: input.cwd,
|
|
13100
14498
|
prompt,
|
|
13101
14499
|
model: input.model,
|
|
13102
14500
|
timeoutMs: SESSION_TITLE_GENERATION_TIMEOUT_MS
|
|
13103
14501
|
});
|
|
13104
|
-
const nextTitle = sanitizeGeneratedSessionTitle(generated);
|
|
13105
|
-
if (!nextTitle || nextTitle === input.fallbackTitle) {
|
|
13106
|
-
return;
|
|
14502
|
+
const nextTitle = sanitizeGeneratedSessionTitle(generated);
|
|
14503
|
+
if (!nextTitle || nextTitle === input.fallbackTitle) {
|
|
14504
|
+
return;
|
|
14505
|
+
}
|
|
14506
|
+
const currentSession = await findSnapshotSession(input.sessionId);
|
|
14507
|
+
if (!currentSession || currentSession.title !== input.fallbackTitle) {
|
|
14508
|
+
return;
|
|
14509
|
+
}
|
|
14510
|
+
await renameSession(input.sessionId, nextTitle);
|
|
14511
|
+
} catch (error) {
|
|
14512
|
+
diagnosticLogger.warn({
|
|
14513
|
+
sessionId: input.sessionId,
|
|
14514
|
+
projectId: input.project?.id ?? PROJECTLESS_SESSION_PROJECT_ID,
|
|
14515
|
+
projectPath: input.cwd,
|
|
14516
|
+
model: input.model,
|
|
14517
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
14518
|
+
}, "Failed to generate background session title.");
|
|
14519
|
+
}
|
|
14520
|
+
};
|
|
14521
|
+
const shouldGenerateImageSessionTitle = (session, turnCount) => turnCount === 0 && IMAGE_SESSION_AUTO_TITLE_PLACEHOLDERS.has(session.title.trim());
|
|
14522
|
+
const submitImageSessionPrompt = async (input) => {
|
|
14523
|
+
const sessionRecord = findImageSessionRecord(input.sessionId);
|
|
14524
|
+
if (!sessionRecord) {
|
|
14525
|
+
throw new Error("Image session not found.");
|
|
14526
|
+
}
|
|
14527
|
+
if (sessionRecord.session.archived) {
|
|
14528
|
+
throw new Error("Archived image sessions cannot accept new input.");
|
|
14529
|
+
}
|
|
14530
|
+
const storedSettings = imageSessionStore.getSettings();
|
|
14531
|
+
const activeChannel = imageSessionStore.getActiveSettingsChannel();
|
|
14532
|
+
const resolvedSettings = buildResolvedImageProviderSettings({
|
|
14533
|
+
activeChannelId: storedSettings.active_channel_id,
|
|
14534
|
+
channels: storedSettings.channels.map((channel) => ({
|
|
14535
|
+
id: channel.id,
|
|
14536
|
+
remark: channel.remark,
|
|
14537
|
+
baseUrl: channel.base_url,
|
|
14538
|
+
storedApiKey: channel.api_key,
|
|
14539
|
+
models: channel.models,
|
|
14540
|
+
updatedAt: channel.updated_at
|
|
14541
|
+
})),
|
|
14542
|
+
updatedAt: storedSettings.updated_at
|
|
14543
|
+
});
|
|
14544
|
+
const apiKey = activeChannel?.api_key?.trim() || resolveImageProviderApiKey();
|
|
14545
|
+
if (!apiKey) {
|
|
14546
|
+
throw new Error("No image API key found. Configure one in settings or environment variables.");
|
|
14547
|
+
}
|
|
14548
|
+
const resolvedVendor = resolveImageModelVendor(activeChannel, input.model);
|
|
14549
|
+
const effectiveModeration = resolvedVendor === "openai" ? input.moderation : "auto";
|
|
14550
|
+
if (resolvedVendor === "xai" && (input.size === "3840x2160" || input.size === "2160x3840")) {
|
|
14551
|
+
throw new Error("xAI \u5F53\u524D\u56FE\u7247 API \u4EC5\u652F\u6301 1k / 2k \u5206\u8FA8\u7387\uFF0C\u6682\u4E0D\u652F\u6301 4K \u8F93\u51FA\u3002");
|
|
14552
|
+
}
|
|
14553
|
+
const sourceAssets = [];
|
|
14554
|
+
let maskAsset = null;
|
|
14555
|
+
let maskedSourceAssetId = null;
|
|
14556
|
+
for (const source of input.sources) {
|
|
14557
|
+
if (source.kind === "upload") {
|
|
14558
|
+
if (!source.data_url) {
|
|
14559
|
+
throw new Error(`Uploaded image ${source.name} is missing content.`);
|
|
14560
|
+
}
|
|
14561
|
+
const savedAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14562
|
+
sessionId: input.sessionId,
|
|
14563
|
+
kind: "source",
|
|
14564
|
+
name: source.name,
|
|
14565
|
+
dataUrl: source.data_url,
|
|
14566
|
+
mimeType: source.mime_type ?? null
|
|
14567
|
+
});
|
|
14568
|
+
sourceAssets.push(savedAsset);
|
|
14569
|
+
if (!maskAsset && source.mask_data_url) {
|
|
14570
|
+
maskAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14571
|
+
sessionId: input.sessionId,
|
|
14572
|
+
kind: "mask",
|
|
14573
|
+
name: `${source.name}-mask.png`,
|
|
14574
|
+
dataUrl: source.mask_data_url,
|
|
14575
|
+
mimeType: "image/png"
|
|
14576
|
+
});
|
|
14577
|
+
maskedSourceAssetId = savedAsset.id;
|
|
14578
|
+
}
|
|
14579
|
+
continue;
|
|
14580
|
+
}
|
|
14581
|
+
const existing = source.asset_id ? findImageAssetRecordById(source.asset_id)?.asset ?? null : null;
|
|
14582
|
+
if (!existing) {
|
|
14583
|
+
throw new Error(`Referenced image ${source.name} was not found.`);
|
|
14584
|
+
}
|
|
14585
|
+
sourceAssets.push(existing);
|
|
14586
|
+
if (!maskAsset && source.mask_data_url) {
|
|
14587
|
+
maskAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14588
|
+
sessionId: input.sessionId,
|
|
14589
|
+
kind: "mask",
|
|
14590
|
+
name: `${source.name}-mask.png`,
|
|
14591
|
+
dataUrl: source.mask_data_url,
|
|
14592
|
+
mimeType: "image/png"
|
|
14593
|
+
});
|
|
14594
|
+
maskedSourceAssetId = existing.id;
|
|
14595
|
+
}
|
|
14596
|
+
}
|
|
14597
|
+
const orderedSourceAssets = maskedSourceAssetId ? [
|
|
14598
|
+
...sourceAssets.filter((asset) => asset.id === maskedSourceAssetId),
|
|
14599
|
+
...sourceAssets.filter((asset) => asset.id !== maskedSourceAssetId)
|
|
14600
|
+
] : sourceAssets;
|
|
14601
|
+
const operation = orderedSourceAssets.length > 0 ? "edit" : "generate";
|
|
14602
|
+
const shouldGenerateTitle = shouldGenerateImageSessionTitle(
|
|
14603
|
+
sessionRecord.session,
|
|
14604
|
+
sessionRecord.turns.length
|
|
14605
|
+
);
|
|
14606
|
+
const appendTargetTurn = input.appendToTurnId ? sessionRecord.turns.find((turn) => turn.id === input.appendToTurnId) ?? null : null;
|
|
14607
|
+
if (input.appendToTurnId && !appendTargetTurn) {
|
|
14608
|
+
throw new Error("Image turn not found.");
|
|
14609
|
+
}
|
|
14610
|
+
if (appendTargetTurn && appendTargetTurn.status !== "completed") {
|
|
14611
|
+
throw new Error("Only completed image turns can be refreshed.");
|
|
14612
|
+
}
|
|
14613
|
+
const startedRecord = appendTargetTurn ? await imageSessionStore.mutateSessionRef(input.sessionId, (current) => ({
|
|
14614
|
+
...current,
|
|
14615
|
+
latest_assistant_message: appendTargetTurn.operation === "edit" ? "Editing image..." : "Generating image...",
|
|
14616
|
+
last_event_at: isoNow6(),
|
|
14617
|
+
run_state: "running",
|
|
14618
|
+
run_state_changed_at: isoNow6(),
|
|
14619
|
+
capability: {
|
|
14620
|
+
...current.capability,
|
|
14621
|
+
can_send_input: false
|
|
14622
|
+
}
|
|
14623
|
+
})) : await imageSessionStore.startTurn({
|
|
14624
|
+
sessionId: input.sessionId,
|
|
14625
|
+
prompt: input.prompt,
|
|
14626
|
+
operation,
|
|
14627
|
+
model: input.model,
|
|
14628
|
+
quality: input.quality,
|
|
14629
|
+
size: input.size,
|
|
14630
|
+
inputFidelity: input.inputFidelity,
|
|
14631
|
+
aspectRatio: input.aspectRatio,
|
|
14632
|
+
outputFormat: input.outputFormat,
|
|
14633
|
+
outputCompression: input.outputCompression,
|
|
14634
|
+
background: input.background,
|
|
14635
|
+
moderation: effectiveModeration,
|
|
14636
|
+
n: input.n,
|
|
14637
|
+
sourceAssetIds: orderedSourceAssets.map((asset) => asset.id)
|
|
14638
|
+
});
|
|
14639
|
+
const startedTurn = appendTargetTurn ?? startedRecord.turns[startedRecord.turns.length - 1];
|
|
14640
|
+
if (!startedTurn) {
|
|
14641
|
+
throw new Error("Unable to create image session turn.");
|
|
14642
|
+
}
|
|
14643
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14644
|
+
broadcastEvent("session.updated", {
|
|
14645
|
+
sessionId: input.sessionId,
|
|
14646
|
+
sessionPatch: {
|
|
14647
|
+
run_state: "running",
|
|
14648
|
+
run_state_changed_at: startedRecord.session.run_state_changed_at,
|
|
14649
|
+
last_event_at: startedRecord.session.last_event_at,
|
|
14650
|
+
summary: startedRecord.session.summary,
|
|
14651
|
+
latest_assistant_message: startedRecord.session.latest_assistant_message
|
|
14652
|
+
}
|
|
14653
|
+
});
|
|
14654
|
+
try {
|
|
14655
|
+
const providerInputs = await Promise.all(
|
|
14656
|
+
orderedSourceAssets.map(async (asset) => ({
|
|
14657
|
+
name: asset.name,
|
|
14658
|
+
mimeType: asset.mime_type ?? toMimeTypeFromFilename(asset.name),
|
|
14659
|
+
buffer: await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(asset))
|
|
14660
|
+
}))
|
|
14661
|
+
);
|
|
14662
|
+
const providerMask = maskAsset ? {
|
|
14663
|
+
name: maskAsset.name,
|
|
14664
|
+
mimeType: maskAsset.mime_type ?? "image/png",
|
|
14665
|
+
buffer: await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(maskAsset))
|
|
14666
|
+
} : null;
|
|
14667
|
+
const providerResult = await submitImageRequest({
|
|
14668
|
+
baseUrl: resolveImageProviderBaseUrl(activeChannel?.base_url ?? resolvedSettings.base_url),
|
|
14669
|
+
apiKey,
|
|
14670
|
+
prompt: input.prompt,
|
|
14671
|
+
model: input.model,
|
|
14672
|
+
vendor: resolvedVendor,
|
|
14673
|
+
quality: input.quality,
|
|
14674
|
+
size: input.size,
|
|
14675
|
+
inputFidelity: input.inputFidelity,
|
|
14676
|
+
aspectRatio: input.aspectRatio,
|
|
14677
|
+
outputFormat: input.outputFormat,
|
|
14678
|
+
outputCompression: input.outputCompression,
|
|
14679
|
+
background: input.background,
|
|
14680
|
+
moderation: effectiveModeration,
|
|
14681
|
+
n: input.n,
|
|
14682
|
+
inputs: providerInputs,
|
|
14683
|
+
mask: providerMask
|
|
14684
|
+
});
|
|
14685
|
+
const outputAssetIds = [];
|
|
14686
|
+
for (const output of providerResult.outputs) {
|
|
14687
|
+
const savedOutput = await imageSessionStore.saveBufferAsset({
|
|
14688
|
+
sessionId: input.sessionId,
|
|
14689
|
+
turnId: startedTurn.id,
|
|
14690
|
+
kind: "generated",
|
|
14691
|
+
name: output.name,
|
|
14692
|
+
buffer: output.buffer,
|
|
14693
|
+
mimeType: output.mimeType
|
|
14694
|
+
});
|
|
14695
|
+
outputAssetIds.push(savedOutput.id);
|
|
14696
|
+
}
|
|
14697
|
+
await imageSessionStore.finishTurn({
|
|
14698
|
+
sessionId: input.sessionId,
|
|
14699
|
+
turnId: startedTurn.id,
|
|
14700
|
+
status: "completed",
|
|
14701
|
+
outputAssetIds,
|
|
14702
|
+
appendOutputs: Boolean(appendTargetTurn)
|
|
14703
|
+
});
|
|
14704
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14705
|
+
const detail = await readImageSessionDetail(input.sessionId);
|
|
14706
|
+
if (!detail) {
|
|
14707
|
+
throw new Error("Unable to load image session detail.");
|
|
13107
14708
|
}
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
14709
|
+
broadcastEvent("session.updated", {
|
|
14710
|
+
sessionId: input.sessionId,
|
|
14711
|
+
sessionPatch: {
|
|
14712
|
+
run_state: detail.session.run_state,
|
|
14713
|
+
run_state_changed_at: detail.session.run_state_changed_at,
|
|
14714
|
+
last_event_at: detail.session.last_event_at,
|
|
14715
|
+
latest_assistant_message: detail.session.latest_assistant_message
|
|
14716
|
+
}
|
|
14717
|
+
});
|
|
14718
|
+
if (shouldGenerateTitle) {
|
|
14719
|
+
const baseDirectory = await getPandaLocalBaseDirectory();
|
|
14720
|
+
void generateSessionTitleInBackground({
|
|
14721
|
+
sessionId: input.sessionId,
|
|
14722
|
+
project: null,
|
|
14723
|
+
cwd: baseDirectory,
|
|
14724
|
+
fallbackTitle: sessionRecord.session.title,
|
|
14725
|
+
message: input.prompt,
|
|
14726
|
+
attachments: [],
|
|
14727
|
+
model: DEFAULT_SESSION_TITLE_GENERATION_MODEL
|
|
14728
|
+
});
|
|
13111
14729
|
}
|
|
13112
|
-
|
|
14730
|
+
return detail;
|
|
13113
14731
|
} catch (error) {
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
14732
|
+
if (appendTargetTurn) {
|
|
14733
|
+
await imageSessionStore.mutateSessionRef(input.sessionId, (current) => ({
|
|
14734
|
+
...current,
|
|
14735
|
+
latest_assistant_message: `Generated ${appendTargetTurn.output_asset_ids.length} image(s)`,
|
|
14736
|
+
last_event_at: isoNow6(),
|
|
14737
|
+
run_state: "completed",
|
|
14738
|
+
run_state_changed_at: isoNow6(),
|
|
14739
|
+
capability: {
|
|
14740
|
+
...current.capability,
|
|
14741
|
+
can_send_input: !current.archived
|
|
14742
|
+
}
|
|
14743
|
+
}));
|
|
14744
|
+
} else {
|
|
14745
|
+
await imageSessionStore.finishTurn({
|
|
14746
|
+
sessionId: input.sessionId,
|
|
14747
|
+
turnId: startedTurn.id,
|
|
14748
|
+
status: "failed",
|
|
14749
|
+
error: error instanceof Error ? error.message : "Image request failed.",
|
|
14750
|
+
outputAssetIds: []
|
|
14751
|
+
});
|
|
14752
|
+
}
|
|
14753
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14754
|
+
const detail = await readImageSessionDetail(input.sessionId);
|
|
14755
|
+
if (detail) {
|
|
14756
|
+
broadcastEvent("session.updated", {
|
|
14757
|
+
sessionId: input.sessionId,
|
|
14758
|
+
sessionPatch: {
|
|
14759
|
+
run_state: detail.session.run_state,
|
|
14760
|
+
run_state_changed_at: detail.session.run_state_changed_at,
|
|
14761
|
+
last_event_at: detail.session.last_event_at,
|
|
14762
|
+
latest_assistant_message: detail.session.latest_assistant_message
|
|
14763
|
+
}
|
|
14764
|
+
});
|
|
14765
|
+
}
|
|
14766
|
+
throw error;
|
|
13121
14767
|
}
|
|
13122
14768
|
};
|
|
13123
14769
|
const executeSessionCommand = async (session, project, rawInput) => {
|
|
@@ -13127,9 +14773,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13127
14773
|
sessionId: session.id,
|
|
13128
14774
|
commandName: "unknown",
|
|
13129
14775
|
commandText: rawInput,
|
|
13130
|
-
title: "
|
|
14776
|
+
title: "Unknown command",
|
|
13131
14777
|
status: "failed",
|
|
13132
|
-
body: "
|
|
14778
|
+
body: "Only input starting with / is recognized as a command."
|
|
13133
14779
|
});
|
|
13134
14780
|
}
|
|
13135
14781
|
const catalog = await readCodexCommandCatalog();
|
|
@@ -13139,9 +14785,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13139
14785
|
sessionId: session.id,
|
|
13140
14786
|
commandName: parsed.name,
|
|
13141
14787
|
commandText: parsed.raw,
|
|
13142
|
-
title:
|
|
14788
|
+
title: `Unknown command /${parsed.name}`,
|
|
13143
14789
|
status: "failed",
|
|
13144
|
-
body: "Panda
|
|
14790
|
+
body: "Panda does not recognize this Codex command yet."
|
|
13145
14791
|
});
|
|
13146
14792
|
}
|
|
13147
14793
|
if (command.availability === "unsupported") {
|
|
@@ -13149,10 +14795,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13149
14795
|
sessionId: session.id,
|
|
13150
14796
|
commandName: command.name,
|
|
13151
14797
|
commandText: parsed.raw,
|
|
13152
|
-
title: `/${command.name}
|
|
14798
|
+
title: `/${command.name} is not implemented`,
|
|
13153
14799
|
description: command.description,
|
|
13154
14800
|
status: "completed",
|
|
13155
|
-
body: "
|
|
14801
|
+
body: "This command is listed but not implemented in Panda yet."
|
|
13156
14802
|
});
|
|
13157
14803
|
}
|
|
13158
14804
|
if (command.name === "status") {
|
|
@@ -13162,17 +14808,17 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13162
14808
|
sessionId: session.id,
|
|
13163
14809
|
commandName: command.name,
|
|
13164
14810
|
commandText: parsed.raw,
|
|
13165
|
-
title: "
|
|
14811
|
+
title: "Current session status",
|
|
13166
14812
|
description: command.description,
|
|
13167
14813
|
status: "completed",
|
|
13168
14814
|
body: [
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
usage ?
|
|
14815
|
+
`Session: ${session.title}`,
|
|
14816
|
+
`Run state: ${session.run_state}`,
|
|
14817
|
+
`Model: ${config.model ?? "unset"}`,
|
|
14818
|
+
`Reasoning effort: ${config.reasoningEffort ?? "default"}`,
|
|
14819
|
+
`Approval policy: ${config.approvalPolicy ?? "default"}`,
|
|
14820
|
+
`Sandbox mode: ${config.sandboxMode ?? "default"}`,
|
|
14821
|
+
usage ? `Context: ${usage.used_tokens}/${usage.total_tokens} (${usage.percent_used.toFixed(1)}%)` : "Context: unavailable"
|
|
13176
14822
|
].join("\n")
|
|
13177
14823
|
});
|
|
13178
14824
|
}
|
|
@@ -13182,10 +14828,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13182
14828
|
sessionId: session.id,
|
|
13183
14829
|
commandName: command.name,
|
|
13184
14830
|
commandText: parsed.raw,
|
|
13185
|
-
title:
|
|
14831
|
+
title: `Skills (${skills.length})`,
|
|
13186
14832
|
description: command.description,
|
|
13187
14833
|
status: "completed",
|
|
13188
|
-
body: skills.length > 0 ? skills.map((skill) => `$${skill.name} ${skill.description}`.trim()).join("\n") : "
|
|
14834
|
+
body: skills.length > 0 ? skills.map((skill) => `$${skill.name} ${skill.description}`.trim()).join("\n") : "No project skills are currently available."
|
|
13189
14835
|
});
|
|
13190
14836
|
}
|
|
13191
14837
|
if (command.name === "mcp") {
|
|
@@ -13194,7 +14840,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13194
14840
|
sessionId: session.id,
|
|
13195
14841
|
commandName: command.name,
|
|
13196
14842
|
commandText: parsed.raw,
|
|
13197
|
-
title: `MCP
|
|
14843
|
+
title: `MCP servers (${servers.length})`,
|
|
13198
14844
|
description: command.description,
|
|
13199
14845
|
status: "completed",
|
|
13200
14846
|
body: servers.length > 0 ? servers.map(
|
|
@@ -13203,8 +14849,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13203
14849
|
`${server.toolCount} tools`,
|
|
13204
14850
|
`${server.resourceCount} resources`,
|
|
13205
14851
|
server.authStatus ? `auth: ${server.authStatus}` : null
|
|
13206
|
-
].filter(Boolean).join("
|
|
13207
|
-
).join("\n") : "
|
|
14852
|
+
].filter(Boolean).join(" ? ")
|
|
14853
|
+
).join("\n") : "No MCP servers are available."
|
|
13208
14854
|
});
|
|
13209
14855
|
}
|
|
13210
14856
|
if (command.name === "compact") {
|
|
@@ -13213,10 +14859,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13213
14859
|
sessionId: session.id,
|
|
13214
14860
|
commandName: command.name,
|
|
13215
14861
|
commandText: parsed.raw,
|
|
13216
|
-
title: "
|
|
14862
|
+
title: "Context compaction requested",
|
|
13217
14863
|
description: command.description,
|
|
13218
14864
|
status: "completed",
|
|
13219
|
-
body: "
|
|
14865
|
+
body: "The compaction request has been sent to Codex."
|
|
13220
14866
|
});
|
|
13221
14867
|
}
|
|
13222
14868
|
if (command.name === "rename") {
|
|
@@ -13226,23 +14872,23 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13226
14872
|
sessionId: session.id,
|
|
13227
14873
|
commandName: command.name,
|
|
13228
14874
|
commandText: parsed.raw,
|
|
13229
|
-
title: "
|
|
14875
|
+
title: "Session renamed",
|
|
13230
14876
|
description: command.description,
|
|
13231
14877
|
status: "completed",
|
|
13232
|
-
body:
|
|
14878
|
+
body: `Current session renamed to ${parsed.args}.`
|
|
13233
14879
|
});
|
|
13234
14880
|
}
|
|
13235
14881
|
const panel = createCommandPanel({
|
|
13236
14882
|
sessionId: session.id,
|
|
13237
14883
|
commandName: command.name,
|
|
13238
14884
|
commandText: parsed.raw,
|
|
13239
|
-
title: "
|
|
14885
|
+
title: "Rename current session",
|
|
13240
14886
|
description: command.description,
|
|
13241
14887
|
status: "awaiting_input",
|
|
13242
|
-
body: "
|
|
14888
|
+
body: "Enter a new session name, then submit it in this panel.",
|
|
13243
14889
|
inputType: "text",
|
|
13244
|
-
inputPlaceholder: "
|
|
13245
|
-
submitLabel: "
|
|
14890
|
+
inputPlaceholder: "Enter a new session name",
|
|
14891
|
+
submitLabel: "Rename"
|
|
13246
14892
|
});
|
|
13247
14893
|
return storeCommandPanel(panel, {
|
|
13248
14894
|
mode: "rename",
|
|
@@ -13255,10 +14901,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13255
14901
|
sessionId: session.id,
|
|
13256
14902
|
commandName: command.name,
|
|
13257
14903
|
commandText: parsed.raw,
|
|
13258
|
-
title: "
|
|
13259
|
-
description: "
|
|
14904
|
+
title: "Select model",
|
|
14905
|
+
description: "Choose the model used for future messages in this Panda session.",
|
|
13260
14906
|
status: "awaiting_input",
|
|
13261
|
-
body: models.length > 0 ? "
|
|
14907
|
+
body: models.length > 0 ? "Select one model from the list to update the session." : "No models are currently available.",
|
|
13262
14908
|
inputType: models.length > 0 ? "choice" : "none",
|
|
13263
14909
|
options: models.map((model) => ({
|
|
13264
14910
|
id: model.id,
|
|
@@ -13275,29 +14921,29 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13275
14921
|
sessionId: session.id,
|
|
13276
14922
|
commandName: command.name,
|
|
13277
14923
|
commandText: parsed.raw,
|
|
13278
|
-
title:
|
|
14924
|
+
title: "/" + command.name + " recognized",
|
|
13279
14925
|
description: command.description,
|
|
13280
14926
|
status: "completed",
|
|
13281
|
-
body: "
|
|
14927
|
+
body: "This command is recognized but not implemented in Panda yet."
|
|
13282
14928
|
});
|
|
13283
14929
|
};
|
|
13284
14930
|
const respondToSessionCommand = async (input) => {
|
|
13285
14931
|
const stored = readStoredCommandPanel(input.sessionId, input.panelId);
|
|
13286
14932
|
if (!stored) {
|
|
13287
|
-
throw new Error("
|
|
14933
|
+
throw new Error("Command panel has expired. Run the command again.");
|
|
13288
14934
|
}
|
|
13289
14935
|
if (stored.mode === "rename") {
|
|
13290
14936
|
const nextName = input.text?.trim() ?? "";
|
|
13291
14937
|
if (!nextName) {
|
|
13292
|
-
throw new Error("
|
|
14938
|
+
throw new Error("Please enter a new session name.");
|
|
13293
14939
|
}
|
|
13294
14940
|
await renameSession(input.sessionId, nextName);
|
|
13295
14941
|
clearStoredCommandPanel(input.panelId);
|
|
13296
14942
|
return {
|
|
13297
14943
|
...stored.panel,
|
|
13298
14944
|
status: "completed",
|
|
13299
|
-
body:
|
|
13300
|
-
updated_at:
|
|
14945
|
+
body: "Current session renamed to " + nextName + ".",
|
|
14946
|
+
updated_at: isoNow6(),
|
|
13301
14947
|
input_type: "none",
|
|
13302
14948
|
input_placeholder: null,
|
|
13303
14949
|
submit_label: null
|
|
@@ -13306,22 +14952,22 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13306
14952
|
if (stored.mode === "model") {
|
|
13307
14953
|
const selectedModelId = input.optionId?.trim() ?? "";
|
|
13308
14954
|
if (!selectedModelId) {
|
|
13309
|
-
throw new Error("
|
|
14955
|
+
throw new Error("Please select a model.");
|
|
13310
14956
|
}
|
|
13311
14957
|
const models = await liveSessionStream.listModels();
|
|
13312
14958
|
const selectedModel = models.find((model) => model.id === selectedModelId);
|
|
13313
14959
|
if (!selectedModel) {
|
|
13314
|
-
throw new Error("
|
|
14960
|
+
throw new Error("Selected model is no longer available. Reopen /model.");
|
|
13315
14961
|
}
|
|
13316
14962
|
clearStoredCommandPanel(input.panelId);
|
|
13317
14963
|
return {
|
|
13318
14964
|
...stored.panel,
|
|
13319
14965
|
status: "completed",
|
|
13320
14966
|
body: [
|
|
13321
|
-
|
|
13322
|
-
selectedModel.defaultReasoningEffort ?
|
|
14967
|
+
"Future messages will prefer " + selectedModel.label + ".",
|
|
14968
|
+
selectedModel.defaultReasoningEffort ? "Default reasoning effort: " + selectedModel.defaultReasoningEffort : null
|
|
13323
14969
|
].filter(Boolean).join("\n"),
|
|
13324
|
-
updated_at:
|
|
14970
|
+
updated_at: isoNow6(),
|
|
13325
14971
|
input_type: "none",
|
|
13326
14972
|
options: [],
|
|
13327
14973
|
effect: {
|
|
@@ -13335,8 +14981,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13335
14981
|
return {
|
|
13336
14982
|
...stored.panel,
|
|
13337
14983
|
status: "failed",
|
|
13338
|
-
body: "
|
|
13339
|
-
updated_at:
|
|
14984
|
+
body: "This command panel does not accept input.",
|
|
14985
|
+
updated_at: isoNow6(),
|
|
13340
14986
|
input_type: "none",
|
|
13341
14987
|
options: []
|
|
13342
14988
|
};
|
|
@@ -13352,7 +14998,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13352
14998
|
};
|
|
13353
14999
|
const upsertSnapshotSession = (session) => {
|
|
13354
15000
|
const nextSessions = snapshot.sessions.filter((item) => item.id !== session.id);
|
|
13355
|
-
nextSessions.push(
|
|
15001
|
+
nextSessions.push({
|
|
15002
|
+
...session,
|
|
15003
|
+
provider: normalizeSessionProvider(session.provider)
|
|
15004
|
+
});
|
|
13356
15005
|
snapshot = {
|
|
13357
15006
|
...snapshot,
|
|
13358
15007
|
sessions: nextSessions
|
|
@@ -13459,7 +15108,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13459
15108
|
lastSnapshotRefreshAt = Date.now();
|
|
13460
15109
|
return snapshot;
|
|
13461
15110
|
}
|
|
13462
|
-
const { discoverLocalCodexData } = await import("./src-
|
|
15111
|
+
const { discoverLocalCodexData } = await import("./src-FZ5WMACF.mjs");
|
|
13463
15112
|
const discovery = await discoverLocalCodexData({
|
|
13464
15113
|
agentId: localAgentId,
|
|
13465
15114
|
agentName: localAgentName,
|
|
@@ -13472,6 +15121,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13472
15121
|
transport
|
|
13473
15122
|
});
|
|
13474
15123
|
discoveredAgent = discovery.agent;
|
|
15124
|
+
discoveredAgent.provider_availability = ["codex", "image"];
|
|
13475
15125
|
discoveredSessionFiles = discovery.sessionFiles;
|
|
13476
15126
|
const mergedProjects = /* @__PURE__ */ new Map();
|
|
13477
15127
|
for (const project of discovery.projects) {
|
|
@@ -13487,10 +15137,24 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13487
15137
|
}
|
|
13488
15138
|
const mergedSessions = /* @__PURE__ */ new Map();
|
|
13489
15139
|
for (const session of discovery.sessions) {
|
|
13490
|
-
|
|
15140
|
+
const normalizedSession = applyLiveTrackerPatch(session);
|
|
15141
|
+
mergedSessions.set(session.id, {
|
|
15142
|
+
...normalizedSession,
|
|
15143
|
+
provider: normalizeSessionProvider(normalizedSession.provider)
|
|
15144
|
+
});
|
|
13491
15145
|
}
|
|
13492
15146
|
for (const session of managedSessions.values()) {
|
|
13493
|
-
|
|
15147
|
+
const normalizedSession = applyLiveTrackerPatch(session);
|
|
15148
|
+
mergedSessions.set(session.id, {
|
|
15149
|
+
...normalizedSession,
|
|
15150
|
+
provider: normalizeSessionProvider(normalizedSession.provider)
|
|
15151
|
+
});
|
|
15152
|
+
}
|
|
15153
|
+
for (const session of getImageSessionRefs()) {
|
|
15154
|
+
mergedSessions.set(session.id, {
|
|
15155
|
+
...session,
|
|
15156
|
+
provider: normalizeSessionProvider(session.provider)
|
|
15157
|
+
});
|
|
13494
15158
|
}
|
|
13495
15159
|
const threadPrefs = await readPandaThreadPrefs(codexHome);
|
|
13496
15160
|
const orderedProjects = sortByStoredWorkspaceOrder(
|
|
@@ -13498,7 +15162,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13498
15162
|
getOrderedWorkspaceRoots(threadPrefs)
|
|
13499
15163
|
);
|
|
13500
15164
|
snapshot = {
|
|
13501
|
-
generated_at:
|
|
15165
|
+
generated_at: isoNow6(),
|
|
13502
15166
|
agents: [],
|
|
13503
15167
|
projects: orderedProjects,
|
|
13504
15168
|
sessions: [...mergedSessions.values()],
|
|
@@ -13535,18 +15199,46 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13535
15199
|
return snapshot.projects.find((project) => project.id === projectId) ?? (await buildSnapshot({ force: true })).projects.find((project) => project.id === projectId) ?? null;
|
|
13536
15200
|
};
|
|
13537
15201
|
const readProjectForSession = async (session) => findSnapshotProject(session.project_id);
|
|
15202
|
+
const getImageSessionRefs = () => imageSessionStore.listSessions().map((record) => record.session);
|
|
15203
|
+
const findImageSessionRecord = (sessionId) => imageSessionStore.getSession(sessionId);
|
|
15204
|
+
const buildImageAssetUrl = (assetId) => {
|
|
15205
|
+
const baseUrl = directBaseUrl?.trim() || discoveredAgent?.direct_base_url?.trim() || "";
|
|
15206
|
+
const pathname = "/api/image-assets/" + encodeURIComponent(assetId);
|
|
15207
|
+
return baseUrl ? baseUrl.replace(/\/+$/, "") + pathname : pathname;
|
|
15208
|
+
};
|
|
15209
|
+
const syncImageSessionSnapshot = (sessionId) => {
|
|
15210
|
+
const record = findImageSessionRecord(sessionId);
|
|
15211
|
+
if (!record) {
|
|
15212
|
+
removeSnapshotSessions([sessionId]);
|
|
15213
|
+
return false;
|
|
15214
|
+
}
|
|
15215
|
+
upsertSnapshotSession(record.session);
|
|
15216
|
+
return true;
|
|
15217
|
+
};
|
|
15218
|
+
const findImageAssetRecordById = (assetId) => {
|
|
15219
|
+
for (const session of imageSessionStore.listSessions()) {
|
|
15220
|
+
const asset = session.assets.find((entry) => entry.id === assetId);
|
|
15221
|
+
if (asset) {
|
|
15222
|
+
return {
|
|
15223
|
+
session,
|
|
15224
|
+
asset
|
|
15225
|
+
};
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
return null;
|
|
15229
|
+
};
|
|
13538
15230
|
const readSessionFilePreviewTree = async (session, project, requestedPath) => {
|
|
13539
15231
|
const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
|
|
13540
|
-
const stat = await
|
|
15232
|
+
const stat = await fs9.stat(resolved.realPath).catch((error) => {
|
|
13541
15233
|
if (isMissingPathError(error)) {
|
|
13542
15234
|
throw new Error("File not found.");
|
|
13543
15235
|
}
|
|
13544
15236
|
throw error;
|
|
13545
15237
|
});
|
|
13546
15238
|
if (!stat.isDirectory()) {
|
|
13547
|
-
throw new Error("
|
|
15239
|
+
throw new Error("Target path is not a directory.");
|
|
13548
15240
|
}
|
|
13549
|
-
const entries = await
|
|
15241
|
+
const entries = await fs9.readdir(resolved.realPath, {
|
|
13550
15242
|
withFileTypes: true
|
|
13551
15243
|
});
|
|
13552
15244
|
const nodes = (await Promise.all(
|
|
@@ -13570,21 +15262,21 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13570
15262
|
root_path: SESSION_FILE_PREVIEW_ROOT_PATH,
|
|
13571
15263
|
parent_path: resolved.normalizedPath || null,
|
|
13572
15264
|
nodes,
|
|
13573
|
-
loaded_at:
|
|
15265
|
+
loaded_at: isoNow6()
|
|
13574
15266
|
};
|
|
13575
15267
|
};
|
|
13576
15268
|
const readSessionFilePreviewContent = async (session, project, requestedPath) => {
|
|
13577
15269
|
const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
|
|
13578
|
-
const stat = await
|
|
15270
|
+
const stat = await fs9.stat(resolved.realPath).catch((error) => {
|
|
13579
15271
|
if (isMissingPathError(error)) {
|
|
13580
15272
|
throw new Error("File not found.");
|
|
13581
15273
|
}
|
|
13582
15274
|
throw error;
|
|
13583
15275
|
});
|
|
13584
15276
|
if (!stat.isFile()) {
|
|
13585
|
-
throw new Error("
|
|
15277
|
+
throw new Error("Target path is not a file.");
|
|
13586
15278
|
}
|
|
13587
|
-
const fileName =
|
|
15279
|
+
const fileName = path11.basename(resolved.normalizedPath || resolved.realPath);
|
|
13588
15280
|
const extension = normalizeSessionFilePreviewExtension(fileName);
|
|
13589
15281
|
let fileKind = detectSessionFilePreviewKind(fileName, extension) ?? null;
|
|
13590
15282
|
let previewBuffer = null;
|
|
@@ -13610,10 +15302,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13610
15302
|
is_truncated: true,
|
|
13611
15303
|
content_text: null,
|
|
13612
15304
|
content_base64: null,
|
|
13613
|
-
loaded_at:
|
|
15305
|
+
loaded_at: isoNow6()
|
|
13614
15306
|
};
|
|
13615
15307
|
}
|
|
13616
|
-
const buffer2 = await
|
|
15308
|
+
const buffer2 = await fs9.readFile(resolved.realPath);
|
|
13617
15309
|
return {
|
|
13618
15310
|
session_id: session.id,
|
|
13619
15311
|
project_id: project.id,
|
|
@@ -13627,7 +15319,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13627
15319
|
is_truncated: false,
|
|
13628
15320
|
content_text: null,
|
|
13629
15321
|
content_base64: buffer2.toString("base64"),
|
|
13630
|
-
loaded_at:
|
|
15322
|
+
loaded_at: isoNow6()
|
|
13631
15323
|
};
|
|
13632
15324
|
}
|
|
13633
15325
|
if (fileKind === "binary") {
|
|
@@ -13644,7 +15336,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13644
15336
|
is_truncated: false,
|
|
13645
15337
|
content_text: null,
|
|
13646
15338
|
content_base64: null,
|
|
13647
|
-
loaded_at:
|
|
15339
|
+
loaded_at: isoNow6()
|
|
13648
15340
|
};
|
|
13649
15341
|
}
|
|
13650
15342
|
const buffer = previewBuffer ?? await readPreviewBuffer(resolved.realPath, SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT);
|
|
@@ -13661,7 +15353,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13661
15353
|
is_truncated: stat.size > SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT,
|
|
13662
15354
|
content_text: buffer.toString("utf8"),
|
|
13663
15355
|
content_base64: null,
|
|
13664
|
-
loaded_at:
|
|
15356
|
+
loaded_at: isoNow6()
|
|
13665
15357
|
};
|
|
13666
15358
|
};
|
|
13667
15359
|
const validateHubControlPlaneAuth = (request) => {
|
|
@@ -13675,10 +15367,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13675
15367
|
const ensureGitActionPath = (projectPath, targetPath) => {
|
|
13676
15368
|
const nextPath = normalizeGitWorkspacePath(targetPath?.trim() ?? "");
|
|
13677
15369
|
if (!nextPath) {
|
|
13678
|
-
throw new Error("
|
|
15370
|
+
throw new Error("Missing target file path.");
|
|
13679
15371
|
}
|
|
13680
15372
|
if (!isPathInsideProject(projectPath, nextPath)) {
|
|
13681
|
-
throw new Error("
|
|
15373
|
+
throw new Error("Target file is outside the current workspace.");
|
|
13682
15374
|
}
|
|
13683
15375
|
return nextPath;
|
|
13684
15376
|
};
|
|
@@ -13697,8 +15389,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13697
15389
|
};
|
|
13698
15390
|
const normalizeRollbackPatchText = (input) => {
|
|
13699
15391
|
const normalized = input.replace(/\r\n/g, "\n").trim();
|
|
13700
|
-
return normalized ?
|
|
13701
|
-
` : "";
|
|
15392
|
+
return normalized ? normalized + "\n" : "";
|
|
13702
15393
|
};
|
|
13703
15394
|
const buildRollbackPatchText = (changeSet) => {
|
|
13704
15395
|
if (changeSet.aggregated_diff.trim()) {
|
|
@@ -13716,35 +15407,35 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13716
15407
|
const candidates = [file.path, file.move_path].filter((value) => Boolean(value?.trim()));
|
|
13717
15408
|
for (const candidate of candidates) {
|
|
13718
15409
|
if (!isPathInsideProject(projectPath, candidate)) {
|
|
13719
|
-
throw new Error("
|
|
15410
|
+
throw new Error("This change set contains paths outside the workspace.");
|
|
13720
15411
|
}
|
|
13721
15412
|
}
|
|
13722
15413
|
}
|
|
13723
15414
|
};
|
|
13724
15415
|
const rollbackSessionChangeSet = async (projectPath, changeSet) => {
|
|
13725
15416
|
if (changeSet.status !== "completed") {
|
|
13726
|
-
throw new Error("
|
|
15417
|
+
throw new Error("This change set is not completed yet.");
|
|
13727
15418
|
}
|
|
13728
15419
|
if (changeSet.files.length === 0) {
|
|
13729
|
-
throw new Error("
|
|
15420
|
+
throw new Error("This change set has no file changes to roll back.");
|
|
13730
15421
|
}
|
|
13731
15422
|
ensureChangeSetPathsAreInProject(projectPath, changeSet);
|
|
13732
15423
|
const patchText = buildRollbackPatchText(changeSet);
|
|
13733
15424
|
if (!patchText) {
|
|
13734
|
-
throw new Error("
|
|
15425
|
+
throw new Error("This change set does not contain a reversible patch.");
|
|
13735
15426
|
}
|
|
13736
|
-
const tempDir = await
|
|
13737
|
-
const patchFilePath =
|
|
13738
|
-
await
|
|
15427
|
+
const tempDir = await fs9.mkdtemp(path11.join(os6.tmpdir(), "panda-change-set-rollback-"));
|
|
15428
|
+
const patchFilePath = path11.join(tempDir, "rollback.patch");
|
|
15429
|
+
await fs9.writeFile(patchFilePath, patchText, "utf8");
|
|
13739
15430
|
try {
|
|
13740
15431
|
const patchArgs = ["apply", "--reverse", "--whitespace=nowarn", "--binary", patchFilePath];
|
|
13741
15432
|
await runGitCommand(projectPath, [...patchArgs.slice(0, 1), "--check", ...patchArgs.slice(1)]);
|
|
13742
15433
|
await runGitCommand(projectPath, patchArgs);
|
|
13743
15434
|
} catch (error) {
|
|
13744
|
-
const reason = error instanceof Error && error.message.trim() ? error.message : "Git
|
|
13745
|
-
throw new Error(
|
|
15435
|
+
const reason = error instanceof Error && error.message.trim() ? error.message : "Git could not reverse-apply this patch.";
|
|
15436
|
+
throw new Error("Unable to safely roll back this change: " + reason);
|
|
13746
15437
|
} finally {
|
|
13747
|
-
await
|
|
15438
|
+
await fs9.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
|
|
13748
15439
|
}
|
|
13749
15440
|
};
|
|
13750
15441
|
const discardAllGitWorkspaceChanges = async (projectPath) => {
|
|
@@ -13756,7 +15447,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13756
15447
|
const commitAllGitWorkspaceChanges = async (projectPath, message) => {
|
|
13757
15448
|
const nextMessage = message.trim();
|
|
13758
15449
|
if (!nextMessage) {
|
|
13759
|
-
throw new Error("
|
|
15450
|
+
throw new Error("Please enter a commit message.");
|
|
13760
15451
|
}
|
|
13761
15452
|
await runGitCommand(projectPath, ["add", "-A"]);
|
|
13762
15453
|
await runGitCommand(projectPath, ["commit", "-m", nextMessage]);
|
|
@@ -13764,7 +15455,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13764
15455
|
const switchGitWorkspaceBranch = async (projectPath, branch) => {
|
|
13765
15456
|
const nextBranch = branch.trim();
|
|
13766
15457
|
if (!nextBranch) {
|
|
13767
|
-
throw new Error("
|
|
15458
|
+
throw new Error("Please select a branch.");
|
|
13768
15459
|
}
|
|
13769
15460
|
await runGitCommand(projectPath, ["switch", nextBranch]);
|
|
13770
15461
|
};
|
|
@@ -13796,11 +15487,11 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13796
15487
|
if (process.platform !== "win32") {
|
|
13797
15488
|
return [os6.homedir()];
|
|
13798
15489
|
}
|
|
13799
|
-
const candidates = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) =>
|
|
15490
|
+
const candidates = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) => letter + ":\\");
|
|
13800
15491
|
const checks = await Promise.all(
|
|
13801
15492
|
candidates.map(async (candidate) => {
|
|
13802
15493
|
try {
|
|
13803
|
-
const stat = await
|
|
15494
|
+
const stat = await fs9.stat(candidate);
|
|
13804
15495
|
return stat.isDirectory() ? candidate : null;
|
|
13805
15496
|
} catch {
|
|
13806
15497
|
return null;
|
|
@@ -13863,11 +15554,11 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13863
15554
|
};
|
|
13864
15555
|
const toDirectoryNode = async (targetPath) => {
|
|
13865
15556
|
try {
|
|
13866
|
-
const stat = await
|
|
15557
|
+
const stat = await fs9.stat(targetPath);
|
|
13867
15558
|
if (!stat.isDirectory()) {
|
|
13868
15559
|
return null;
|
|
13869
15560
|
}
|
|
13870
|
-
const entries = await
|
|
15561
|
+
const entries = await fs9.readdir(targetPath, { withFileTypes: true });
|
|
13871
15562
|
const hasChildren = entries.some((entry) => entry.isDirectory());
|
|
13872
15563
|
return {
|
|
13873
15564
|
path: targetPath,
|
|
@@ -13879,7 +15570,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13879
15570
|
}
|
|
13880
15571
|
};
|
|
13881
15572
|
const listDirectories = async (targetPath) => {
|
|
13882
|
-
const roots = !targetPath ? await listDriveRoots() : (await
|
|
15573
|
+
const roots = !targetPath ? await listDriveRoots() : (await fs9.readdir(targetPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path11.join(targetPath, entry.name));
|
|
13883
15574
|
const nodes = await Promise.all(
|
|
13884
15575
|
roots.sort((a, b) => a.localeCompare(b)).map(async (directoryPath) => toDirectoryNode(directoryPath))
|
|
13885
15576
|
);
|
|
@@ -13894,15 +15585,15 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13894
15585
|
if (!parentPath) {
|
|
13895
15586
|
throw new Error("Parent directory path is required.");
|
|
13896
15587
|
}
|
|
13897
|
-
const resolvedParentPath =
|
|
13898
|
-
const parentStat = await
|
|
15588
|
+
const resolvedParentPath = path11.resolve(parentPath);
|
|
15589
|
+
const parentStat = await fs9.stat(resolvedParentPath).catch(() => null);
|
|
13899
15590
|
if (!parentStat?.isDirectory()) {
|
|
13900
15591
|
throw new Error("Parent directory does not exist.");
|
|
13901
15592
|
}
|
|
13902
15593
|
const directoryName = input.name.trim();
|
|
13903
|
-
const targetPath =
|
|
15594
|
+
const targetPath = path11.join(resolvedParentPath, directoryName);
|
|
13904
15595
|
try {
|
|
13905
|
-
await
|
|
15596
|
+
await fs9.mkdir(targetPath);
|
|
13906
15597
|
} catch (error) {
|
|
13907
15598
|
const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : "";
|
|
13908
15599
|
if (errorCode === "EEXIST") {
|
|
@@ -14002,7 +15693,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14002
15693
|
can_interrupt: true,
|
|
14003
15694
|
can_approve: true,
|
|
14004
15695
|
can_reject: true,
|
|
14005
|
-
|
|
15696
|
+
can_show_git: session.capability.can_show_git,
|
|
15697
|
+
can_show_terminal: session.capability.can_show_terminal
|
|
14006
15698
|
}
|
|
14007
15699
|
});
|
|
14008
15700
|
const unarchiveManagedSession = (sessionId) => {
|
|
@@ -14093,7 +15785,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14093
15785
|
service: serviceName,
|
|
14094
15786
|
mode,
|
|
14095
15787
|
provider: "codex",
|
|
14096
|
-
timestamp:
|
|
15788
|
+
timestamp: isoNow6()
|
|
14097
15789
|
}));
|
|
14098
15790
|
app.get("/api/dev-manager", async (request, reply) => {
|
|
14099
15791
|
try {
|
|
@@ -14148,10 +15840,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14148
15840
|
reply.header("cache-control", "no-store");
|
|
14149
15841
|
reply.header(
|
|
14150
15842
|
"content-disposition",
|
|
14151
|
-
|
|
15843
|
+
'attachment; filename="' + download.artifact.file_name + '"'
|
|
14152
15844
|
);
|
|
14153
15845
|
reply.type("application/vnd.android.package-archive");
|
|
14154
|
-
return reply.send(await
|
|
15846
|
+
return reply.send(await fs9.readFile(download.filePath));
|
|
14155
15847
|
} catch (error) {
|
|
14156
15848
|
reply.code(409);
|
|
14157
15849
|
return {
|
|
@@ -14214,8 +15906,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14214
15906
|
agent_id: registered.agent.id,
|
|
14215
15907
|
heartbeat_interval_ms: HUB_AGENT_HEARTBEAT_INTERVAL_MS,
|
|
14216
15908
|
heartbeat_timeout_ms: HUB_AGENT_HEARTBEAT_TIMEOUT_MS,
|
|
14217
|
-
registered_at: registered.agent.registered_at ??
|
|
14218
|
-
received_at: registered.agent.last_seen_at ??
|
|
15909
|
+
registered_at: registered.agent.registered_at ?? isoNow6(),
|
|
15910
|
+
received_at: registered.agent.last_seen_at ?? isoNow6()
|
|
14219
15911
|
};
|
|
14220
15912
|
});
|
|
14221
15913
|
app.post("/api/agents/heartbeat", async (request, reply) => {
|
|
@@ -14240,8 +15932,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14240
15932
|
agent_id: registered.agent.id,
|
|
14241
15933
|
heartbeat_interval_ms: HUB_AGENT_HEARTBEAT_INTERVAL_MS,
|
|
14242
15934
|
heartbeat_timeout_ms: HUB_AGENT_HEARTBEAT_TIMEOUT_MS,
|
|
14243
|
-
registered_at: registered.agent.registered_at ??
|
|
14244
|
-
received_at: registered.agent.last_seen_at ??
|
|
15935
|
+
registered_at: registered.agent.registered_at ?? isoNow6(),
|
|
15936
|
+
received_at: registered.agent.last_seen_at ?? isoNow6()
|
|
14245
15937
|
};
|
|
14246
15938
|
});
|
|
14247
15939
|
app.post(
|
|
@@ -14335,10 +16027,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14335
16027
|
return { error: "Invalid Web Push test payload." };
|
|
14336
16028
|
}
|
|
14337
16029
|
const delivered = await deliverWebPushToStoredSubscription(parsed.data.endpoint, {
|
|
14338
|
-
title: "Panda
|
|
14339
|
-
body: "
|
|
16030
|
+
title: "Panda test push",
|
|
16031
|
+
body: "This is a test notification from Hub.",
|
|
14340
16032
|
url: "/",
|
|
14341
|
-
tag:
|
|
16033
|
+
tag: "panda-web-push-test:" + Date.now()
|
|
14342
16034
|
});
|
|
14343
16035
|
if (!delivered.ok) {
|
|
14344
16036
|
reply.code(delivered.error === "Web Push subscription not found." ? 404 : 502);
|
|
@@ -14695,7 +16387,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14695
16387
|
const targetPath = ensureGitActionPath(project.path, request.body.path);
|
|
14696
16388
|
const targetFile = workspace.files.find((file) => file.path === targetPath) ?? null;
|
|
14697
16389
|
if (!targetFile) {
|
|
14698
|
-
throw new Error("
|
|
16390
|
+
throw new Error("Target file has no pending changes.");
|
|
14699
16391
|
}
|
|
14700
16392
|
await discardGitWorkspaceFile(project.path, targetFile);
|
|
14701
16393
|
} else if (request.body.action === "discard-all") {
|
|
@@ -14706,16 +16398,16 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14706
16398
|
const workspace = await readSessionGitWorkspace(session, project);
|
|
14707
16399
|
const targetBranch = request.body.branch?.trim() ?? "";
|
|
14708
16400
|
if (!targetBranch) {
|
|
14709
|
-
throw new Error("
|
|
16401
|
+
throw new Error("Please select a branch.");
|
|
14710
16402
|
}
|
|
14711
16403
|
if (workspace.branch === targetBranch) {
|
|
14712
|
-
throw new Error("
|
|
16404
|
+
throw new Error("Already on this branch.");
|
|
14713
16405
|
}
|
|
14714
16406
|
if (workspace.files.length > 0) {
|
|
14715
|
-
throw new Error("
|
|
16407
|
+
throw new Error("Commit or discard current changes before switching branches.");
|
|
14716
16408
|
}
|
|
14717
16409
|
if (!workspace.branches.includes(targetBranch)) {
|
|
14718
|
-
throw new Error("
|
|
16410
|
+
throw new Error("Target branch does not exist.");
|
|
14719
16411
|
}
|
|
14720
16412
|
await switchGitWorkspaceBranch(project.path, targetBranch);
|
|
14721
16413
|
} else if (request.body.action === "push") {
|
|
@@ -14817,7 +16509,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14817
16509
|
});
|
|
14818
16510
|
const generation = parseGeneratedRunCommandDrafts(raw);
|
|
14819
16511
|
if (generation.commands.length === 0) {
|
|
14820
|
-
throw new Error("Codex
|
|
16512
|
+
throw new Error("Codex did not generate any savable project commands.");
|
|
14821
16513
|
}
|
|
14822
16514
|
const catalog = await replaceGeneratedProjectRunCommands({
|
|
14823
16515
|
sessionId,
|
|
@@ -14905,7 +16597,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14905
16597
|
const generation = parseGeneratedRunWebsiteDrafts(raw);
|
|
14906
16598
|
const normalizedWebsites = normalizeGeneratedWebsiteDrafts(generation.websites, request);
|
|
14907
16599
|
if (normalizedWebsites.length === 0) {
|
|
14908
|
-
throw new Error("Codex
|
|
16600
|
+
throw new Error("Codex did not generate any savable website URLs.");
|
|
14909
16601
|
}
|
|
14910
16602
|
const catalog = await replaceGeneratedProjectRunWebsites({
|
|
14911
16603
|
sessionId,
|
|
@@ -14947,7 +16639,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14947
16639
|
if (request.body.action === "run-command" || request.body.action === "run-kill-command") {
|
|
14948
16640
|
const commandId = request.body.commandId?.trim() ?? "";
|
|
14949
16641
|
if (!commandId) {
|
|
14950
|
-
throw new Error("
|
|
16642
|
+
throw new Error("Please select a command to run.");
|
|
14951
16643
|
}
|
|
14952
16644
|
const catalog = await readProjectRunCommandCatalog({
|
|
14953
16645
|
sessionId,
|
|
@@ -14956,12 +16648,12 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14956
16648
|
});
|
|
14957
16649
|
const command = catalog.commands.find((item) => item.id === commandId);
|
|
14958
16650
|
if (!command) {
|
|
14959
|
-
throw new Error("
|
|
16651
|
+
throw new Error("Target command does not exist.");
|
|
14960
16652
|
}
|
|
14961
16653
|
const isKillAction = request.body.action === "run-kill-command";
|
|
14962
16654
|
const killCommand = command.kill_command?.trim() ?? "";
|
|
14963
16655
|
if (isKillAction && !killCommand) {
|
|
14964
|
-
throw new Error("
|
|
16656
|
+
throw new Error("This command has no executable stop command.");
|
|
14965
16657
|
}
|
|
14966
16658
|
const resolved = await resolveRunCommandExecution(project.path, command, {
|
|
14967
16659
|
overrideCommand: isKillAction ? killCommand : null
|
|
@@ -14970,7 +16662,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14970
16662
|
sessionId,
|
|
14971
16663
|
projectId: project.id,
|
|
14972
16664
|
commandId: command.id,
|
|
14973
|
-
title: isKillAction ?
|
|
16665
|
+
title: isKillAction ? command.name + " - Stop" : command.name,
|
|
14974
16666
|
command: resolved.command,
|
|
14975
16667
|
cwd: resolved.cwd,
|
|
14976
16668
|
env: resolved.env,
|
|
@@ -14981,7 +16673,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14981
16673
|
}
|
|
14982
16674
|
const terminalId = request.body.terminalId?.trim() ?? "";
|
|
14983
16675
|
if (!terminalId) {
|
|
14984
|
-
throw new Error("
|
|
16676
|
+
throw new Error("Missing target terminal.");
|
|
14985
16677
|
}
|
|
14986
16678
|
if (request.body.action === "stop") {
|
|
14987
16679
|
return {
|
|
@@ -15117,6 +16809,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15117
16809
|
return readWorkspaceSessionPage({
|
|
15118
16810
|
bucket,
|
|
15119
16811
|
projectId: query.projectId ?? null,
|
|
16812
|
+
projectless: query.projectless === "1" || query.projectless === "true",
|
|
15120
16813
|
cursor: query.cursor,
|
|
15121
16814
|
limit: query.limit,
|
|
15122
16815
|
selectedSessionId: query.selectedSessionId ?? null
|
|
@@ -15211,7 +16904,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15211
16904
|
return { project: existing, created: false };
|
|
15212
16905
|
}
|
|
15213
16906
|
const project = {
|
|
15214
|
-
id:
|
|
16907
|
+
id: "project-" + slugify(name) + "-" + Date.now(),
|
|
15215
16908
|
agent_id: localAgentId,
|
|
15216
16909
|
name,
|
|
15217
16910
|
display_name: null,
|
|
@@ -15247,6 +16940,45 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15247
16940
|
return null;
|
|
15248
16941
|
}
|
|
15249
16942
|
};
|
|
16943
|
+
const ensureProjectlessConversationDirectory = async (title) => {
|
|
16944
|
+
const slugBase = slugify(title) || "chat";
|
|
16945
|
+
const targetPath = path11.join(
|
|
16946
|
+
PROJECTLESS_DIRECTORY_ROOT,
|
|
16947
|
+
slugBase + "-" + Date.now()
|
|
16948
|
+
);
|
|
16949
|
+
await fs9.mkdir(targetPath, { recursive: true });
|
|
16950
|
+
return targetPath;
|
|
16951
|
+
};
|
|
16952
|
+
const buildManagedSession = (input) => {
|
|
16953
|
+
const projectless = input.projectless === true || !input.project;
|
|
16954
|
+
const project = input.project ?? null;
|
|
16955
|
+
const isRunning = input.messageInput.length > 0 || input.attachments.length > 0;
|
|
16956
|
+
return {
|
|
16957
|
+
id: input.sessionId,
|
|
16958
|
+
agent_id: localAgentId,
|
|
16959
|
+
project_id: project?.id ?? PROJECTLESS_SESSION_PROJECT_ID,
|
|
16960
|
+
provider: "codex",
|
|
16961
|
+
archived: false,
|
|
16962
|
+
title: input.title,
|
|
16963
|
+
mode: "managed",
|
|
16964
|
+
health: "active",
|
|
16965
|
+
branch: project?.branch ?? "unknown",
|
|
16966
|
+
worktree: project?.worktree ?? PROJECTLESS_WORKTREE,
|
|
16967
|
+
summary: input.messageInput ? truncateSummary(input.messageInput) : input.attachments[0]?.name ?? "",
|
|
16968
|
+
latest_assistant_message: null,
|
|
16969
|
+
last_event_at: input.sessionTimestamp,
|
|
16970
|
+
pinned: false,
|
|
16971
|
+
run_state: isRunning ? "running" : "idle",
|
|
16972
|
+
run_state_changed_at: isRunning ? input.sessionTimestamp : null,
|
|
16973
|
+
context_usage: null,
|
|
16974
|
+
subagent: null,
|
|
16975
|
+
capability: buildManagedSessionCapability({
|
|
16976
|
+
archived: false,
|
|
16977
|
+
projectless,
|
|
16978
|
+
live: true
|
|
16979
|
+
})
|
|
16980
|
+
};
|
|
16981
|
+
};
|
|
15250
16982
|
app.post("/api/sessions", async (request, reply) => {
|
|
15251
16983
|
const title = request.body.title.trim();
|
|
15252
16984
|
const input = request.body.input?.trim() ?? "";
|
|
@@ -15298,7 +17030,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15298
17030
|
codexConfig: configDiagnostics
|
|
15299
17031
|
}, "Forwarding Panda session creation to Codex.");
|
|
15300
17032
|
let sessionId = "";
|
|
15301
|
-
const sessionTimestamp =
|
|
17033
|
+
const sessionTimestamp = isoNow6();
|
|
15302
17034
|
try {
|
|
15303
17035
|
const created = await liveSessionStream.startThread({
|
|
15304
17036
|
cwd: project.path,
|
|
@@ -15342,35 +17074,14 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15342
17074
|
request: requestDiagnostics,
|
|
15343
17075
|
codexConfig: configDiagnostics
|
|
15344
17076
|
}, "Successfully created Codex session from Panda.");
|
|
15345
|
-
const session = {
|
|
15346
|
-
|
|
15347
|
-
agent_id: localAgentId,
|
|
15348
|
-
project_id: project.id,
|
|
15349
|
-
provider: "codex",
|
|
15350
|
-
archived: false,
|
|
17077
|
+
const session = buildManagedSession({
|
|
17078
|
+
sessionId,
|
|
15351
17079
|
title,
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
latest_assistant_message: null,
|
|
15358
|
-
last_event_at: sessionTimestamp,
|
|
15359
|
-
pinned: false,
|
|
15360
|
-
run_state: input || attachments.length > 0 ? "running" : "idle",
|
|
15361
|
-
run_state_changed_at: input || attachments.length > 0 ? sessionTimestamp : null,
|
|
15362
|
-
context_usage: null,
|
|
15363
|
-
subagent: null,
|
|
15364
|
-
capability: {
|
|
15365
|
-
can_stream_live: true,
|
|
15366
|
-
can_send_input: true,
|
|
15367
|
-
can_interrupt: true,
|
|
15368
|
-
can_approve: true,
|
|
15369
|
-
can_reject: true,
|
|
15370
|
-
can_show_git: true,
|
|
15371
|
-
can_show_terminal: true
|
|
15372
|
-
}
|
|
15373
|
-
};
|
|
17080
|
+
messageInput: input,
|
|
17081
|
+
attachments,
|
|
17082
|
+
sessionTimestamp,
|
|
17083
|
+
project
|
|
17084
|
+
});
|
|
15374
17085
|
setManagedSessionActive(session.id);
|
|
15375
17086
|
managedSessions.set(session.id, session);
|
|
15376
17087
|
upsertSnapshotSession(session);
|
|
@@ -15378,6 +17089,126 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15378
17089
|
void generateSessionTitleInBackground({
|
|
15379
17090
|
sessionId: session.id,
|
|
15380
17091
|
project,
|
|
17092
|
+
cwd: project.path,
|
|
17093
|
+
fallbackTitle: title,
|
|
17094
|
+
message: input,
|
|
17095
|
+
attachments,
|
|
17096
|
+
model: titleGenerationModel
|
|
17097
|
+
});
|
|
17098
|
+
reply.code(201);
|
|
17099
|
+
return { session };
|
|
17100
|
+
});
|
|
17101
|
+
app.post("/api/chats", async (request, reply) => {
|
|
17102
|
+
const title = request.body.title.trim();
|
|
17103
|
+
const input = request.body.input?.trim() ?? "";
|
|
17104
|
+
const attachments = normalizeSessionInputAttachments(request.body.attachments);
|
|
17105
|
+
const model = request.body.model?.trim() || null;
|
|
17106
|
+
const titleGenerationModel = request.body.titleGenerationModel?.trim() || DEFAULT_SESSION_TITLE_GENERATION_MODEL;
|
|
17107
|
+
const reasoningEffort = request.body.reasoningEffort?.trim() || null;
|
|
17108
|
+
const requestedServiceTier = request.body.serviceTier;
|
|
17109
|
+
const normalizedRequestedServiceTier = normalizeServiceTier(requestedServiceTier);
|
|
17110
|
+
const serviceTier = resolvePandaServiceTier(normalizedRequestedServiceTier);
|
|
17111
|
+
const planMode = request.body.planMode === true;
|
|
17112
|
+
const yoloMode = request.body.yoloMode === true;
|
|
17113
|
+
if (!title) {
|
|
17114
|
+
reply.code(400);
|
|
17115
|
+
return { error: "Session title is required." };
|
|
17116
|
+
}
|
|
17117
|
+
if (!input && attachments.length === 0) {
|
|
17118
|
+
reply.code(400);
|
|
17119
|
+
return { error: "Message input or attachments are required." };
|
|
17120
|
+
}
|
|
17121
|
+
const requestDiagnostics = summarizeForwardingRequest(request);
|
|
17122
|
+
const prompt = buildTurnPrompt(input, planMode) || null;
|
|
17123
|
+
const payloadDiagnostics = summarizeForwardingPayload({
|
|
17124
|
+
title,
|
|
17125
|
+
input,
|
|
17126
|
+
prompt,
|
|
17127
|
+
attachments,
|
|
17128
|
+
model,
|
|
17129
|
+
titleGenerationModel,
|
|
17130
|
+
reasoningEffort,
|
|
17131
|
+
requestedServiceTier,
|
|
17132
|
+
normalizedRequestedServiceTier,
|
|
17133
|
+
effectiveServiceTier: serviceTier,
|
|
17134
|
+
planMode,
|
|
17135
|
+
yoloMode
|
|
17136
|
+
});
|
|
17137
|
+
const projectlessPath = await ensureProjectlessConversationDirectory(title);
|
|
17138
|
+
const configDiagnostics = await readCodexConfigDiagnostics(projectlessPath);
|
|
17139
|
+
diagnosticLogger.info({
|
|
17140
|
+
route: "create-chat",
|
|
17141
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17142
|
+
projectPath: projectlessPath,
|
|
17143
|
+
payload: payloadDiagnostics,
|
|
17144
|
+
request: requestDiagnostics,
|
|
17145
|
+
codexConfig: configDiagnostics
|
|
17146
|
+
}, "Forwarding Panda projectless chat creation to Codex.");
|
|
17147
|
+
let sessionId = "";
|
|
17148
|
+
const sessionTimestamp = isoNow6();
|
|
17149
|
+
try {
|
|
17150
|
+
const created = await liveSessionStream.startThread({
|
|
17151
|
+
cwd: projectlessPath,
|
|
17152
|
+
title,
|
|
17153
|
+
prompt,
|
|
17154
|
+
attachments,
|
|
17155
|
+
model,
|
|
17156
|
+
reasoningEffort,
|
|
17157
|
+
serviceTier,
|
|
17158
|
+
yoloMode
|
|
17159
|
+
});
|
|
17160
|
+
sessionId = created.sessionId;
|
|
17161
|
+
await Promise.all([
|
|
17162
|
+
appendSessionIndexUpdate(
|
|
17163
|
+
sessionId,
|
|
17164
|
+
{
|
|
17165
|
+
thread_name: title,
|
|
17166
|
+
updated_at: sessionTimestamp
|
|
17167
|
+
},
|
|
17168
|
+
codexHome
|
|
17169
|
+
),
|
|
17170
|
+
registerProjectlessThread(sessionId, PROJECTLESS_DIRECTORY_ROOT, codexHome)
|
|
17171
|
+
]);
|
|
17172
|
+
} catch (error) {
|
|
17173
|
+
diagnosticLogger.error({
|
|
17174
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
17175
|
+
route: "create-chat",
|
|
17176
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17177
|
+
projectPath: projectlessPath,
|
|
17178
|
+
payload: payloadDiagnostics,
|
|
17179
|
+
request: requestDiagnostics,
|
|
17180
|
+
codexConfig: configDiagnostics
|
|
17181
|
+
}, "Failed to create Codex projectless chat from Panda.");
|
|
17182
|
+
reply.code(502);
|
|
17183
|
+
return {
|
|
17184
|
+
error: error instanceof Error ? error.message : "Unable to start Codex chat."
|
|
17185
|
+
};
|
|
17186
|
+
}
|
|
17187
|
+
diagnosticLogger.info({
|
|
17188
|
+
route: "create-chat",
|
|
17189
|
+
sessionId,
|
|
17190
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17191
|
+
projectPath: projectlessPath,
|
|
17192
|
+
payload: payloadDiagnostics,
|
|
17193
|
+
request: requestDiagnostics,
|
|
17194
|
+
codexConfig: configDiagnostics
|
|
17195
|
+
}, "Successfully created Codex projectless chat from Panda.");
|
|
17196
|
+
const session = buildManagedSession({
|
|
17197
|
+
sessionId,
|
|
17198
|
+
title,
|
|
17199
|
+
messageInput: input,
|
|
17200
|
+
attachments,
|
|
17201
|
+
sessionTimestamp,
|
|
17202
|
+
projectless: true
|
|
17203
|
+
});
|
|
17204
|
+
setManagedSessionActive(session.id);
|
|
17205
|
+
managedSessions.set(session.id, session);
|
|
17206
|
+
upsertSnapshotSession(session);
|
|
17207
|
+
broadcastSnapshotChanged();
|
|
17208
|
+
void generateSessionTitleInBackground({
|
|
17209
|
+
sessionId: session.id,
|
|
17210
|
+
project: null,
|
|
17211
|
+
cwd: projectlessPath,
|
|
15381
17212
|
fallbackTitle: title,
|
|
15382
17213
|
message: input,
|
|
15383
17214
|
attachments,
|
|
@@ -15386,6 +17217,196 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15386
17217
|
reply.code(201);
|
|
15387
17218
|
return { session };
|
|
15388
17219
|
});
|
|
17220
|
+
app.get(
|
|
17221
|
+
"/api/image-provider/settings",
|
|
17222
|
+
async () => buildResolvedImageProviderSettings({
|
|
17223
|
+
activeChannelId: imageSessionStore.getSettings().active_channel_id,
|
|
17224
|
+
channels: imageSessionStore.getSettings().channels.map((channel) => ({
|
|
17225
|
+
id: channel.id,
|
|
17226
|
+
remark: channel.remark,
|
|
17227
|
+
baseUrl: channel.base_url,
|
|
17228
|
+
storedApiKey: channel.api_key,
|
|
17229
|
+
models: channel.models,
|
|
17230
|
+
updatedAt: channel.updated_at
|
|
17231
|
+
})),
|
|
17232
|
+
updatedAt: imageSessionStore.getSettings().updated_at
|
|
17233
|
+
})
|
|
17234
|
+
);
|
|
17235
|
+
app.post("/api/image-provider/settings", async (request, reply) => {
|
|
17236
|
+
const parsed = imageProviderSettingsUpdateSchema.safeParse(request.body ?? {});
|
|
17237
|
+
if (!parsed.success) {
|
|
17238
|
+
reply.code(400);
|
|
17239
|
+
return { error: "Invalid image provider settings payload." };
|
|
17240
|
+
}
|
|
17241
|
+
const saved = await imageSessionStore.updateSettings({
|
|
17242
|
+
activeChannelId: parsed.data.active_channel_id,
|
|
17243
|
+
channels: parsed.data.channels.map((channel) => ({
|
|
17244
|
+
id: channel.id,
|
|
17245
|
+
remark: channel.remark,
|
|
17246
|
+
baseUrl: normalizeImageProviderBaseUrl(channel.base_url),
|
|
17247
|
+
models: channel.models,
|
|
17248
|
+
apiKeyMode: channel.api_key_mode,
|
|
17249
|
+
apiKey: channel.api_key ?? null
|
|
17250
|
+
}))
|
|
17251
|
+
});
|
|
17252
|
+
return buildResolvedImageProviderSettings({
|
|
17253
|
+
activeChannelId: saved.active_channel_id,
|
|
17254
|
+
channels: saved.channels.map((channel) => ({
|
|
17255
|
+
id: channel.id,
|
|
17256
|
+
remark: channel.remark,
|
|
17257
|
+
baseUrl: channel.base_url,
|
|
17258
|
+
storedApiKey: channel.api_key,
|
|
17259
|
+
models: channel.models,
|
|
17260
|
+
updatedAt: channel.updated_at
|
|
17261
|
+
})),
|
|
17262
|
+
updatedAt: saved.updated_at
|
|
17263
|
+
});
|
|
17264
|
+
});
|
|
17265
|
+
app.post("/api/image-provider/models", async (request, reply) => {
|
|
17266
|
+
const parsed = imageProviderModelListRequestSchema.safeParse(request.body ?? {});
|
|
17267
|
+
if (!parsed.success) {
|
|
17268
|
+
reply.code(400);
|
|
17269
|
+
return { error: "Invalid image provider model list payload." };
|
|
17270
|
+
}
|
|
17271
|
+
const storedChannel = parsed.data.channel_id ? imageSessionStore.getSettings().channels.find((channel) => channel.id === parsed.data.channel_id) : null;
|
|
17272
|
+
const baseUrl = normalizeImageProviderBaseUrl(parsed.data.base_url);
|
|
17273
|
+
const requestApiKey = parsed.data.api_key_mode === "set" ? parsed.data.api_key?.trim() || "" : parsed.data.api_key_mode === "clear" ? "" : storedChannel?.api_key?.trim() || "";
|
|
17274
|
+
const apiKey = requestApiKey || resolveImageProviderApiKey();
|
|
17275
|
+
if (!apiKey) {
|
|
17276
|
+
reply.code(400);
|
|
17277
|
+
return { error: "\u672A\u68C0\u6D4B\u5230\u56FE\u7247\u63A5\u53E3 API Key\u3002" };
|
|
17278
|
+
}
|
|
17279
|
+
return imageProviderModelListResponseSchema.parse({
|
|
17280
|
+
models: await listImageProviderModels({ baseUrl, apiKey })
|
|
17281
|
+
});
|
|
17282
|
+
});
|
|
17283
|
+
app.post("/api/image-provider/test", async (request) => {
|
|
17284
|
+
const parsed = imageProviderSettingsUpdateSchema.safeParse(request.body ?? {});
|
|
17285
|
+
const storedSettings = imageSessionStore.getSettings();
|
|
17286
|
+
const requestSettings = parsed.success ? parsed.data : {
|
|
17287
|
+
active_channel_id: storedSettings.active_channel_id,
|
|
17288
|
+
channels: storedSettings.channels.map((channel) => ({
|
|
17289
|
+
id: channel.id,
|
|
17290
|
+
remark: channel.remark,
|
|
17291
|
+
base_url: channel.base_url,
|
|
17292
|
+
api_key: null,
|
|
17293
|
+
api_key_mode: "keep",
|
|
17294
|
+
models: channel.models
|
|
17295
|
+
}))
|
|
17296
|
+
};
|
|
17297
|
+
const testedChannelId = requestSettings.channels.some(
|
|
17298
|
+
(channel) => channel.id === requestSettings.active_channel_id
|
|
17299
|
+
) ? requestSettings.active_channel_id : requestSettings.channels[0]?.id ?? null;
|
|
17300
|
+
const testedChannel = requestSettings.channels.find(
|
|
17301
|
+
(channel) => channel.id === testedChannelId
|
|
17302
|
+
);
|
|
17303
|
+
const storedChannel = storedSettings.channels.find(
|
|
17304
|
+
(channel) => channel.id === testedChannelId
|
|
17305
|
+
);
|
|
17306
|
+
const baseUrl = normalizeImageProviderBaseUrl(testedChannel?.base_url);
|
|
17307
|
+
const storedApiKey = storedChannel?.api_key?.trim() || "";
|
|
17308
|
+
const requestApiKey = testedChannel?.api_key_mode === "set" ? testedChannel.api_key?.trim() || "" : testedChannel?.api_key_mode === "clear" ? "" : storedApiKey;
|
|
17309
|
+
const apiKey = requestApiKey || resolveImageProviderApiKey();
|
|
17310
|
+
if (!apiKey) {
|
|
17311
|
+
return {
|
|
17312
|
+
ok: false,
|
|
17313
|
+
message: "No image API key found. Configure one in settings or environment variables.",
|
|
17314
|
+
model_available: false,
|
|
17315
|
+
resolved_base_url: baseUrl
|
|
17316
|
+
};
|
|
17317
|
+
}
|
|
17318
|
+
try {
|
|
17319
|
+
const result = await testImageProviderConnection({
|
|
17320
|
+
baseUrl,
|
|
17321
|
+
apiKey,
|
|
17322
|
+
model: getDefaultImageProviderModel(),
|
|
17323
|
+
vendor: "openai"
|
|
17324
|
+
});
|
|
17325
|
+
return {
|
|
17326
|
+
ok: true,
|
|
17327
|
+
message: result.modelAvailable ? "Connection succeeded and gpt-image-2 was found." : "Connection succeeded but gpt-image-2 was not found.",
|
|
17328
|
+
model_available: result.modelAvailable,
|
|
17329
|
+
resolved_base_url: baseUrl
|
|
17330
|
+
};
|
|
17331
|
+
} catch (error) {
|
|
17332
|
+
return {
|
|
17333
|
+
ok: false,
|
|
17334
|
+
message: error instanceof Error ? error.message : "Image provider test failed.",
|
|
17335
|
+
model_available: false,
|
|
17336
|
+
resolved_base_url: baseUrl
|
|
17337
|
+
};
|
|
17338
|
+
}
|
|
17339
|
+
});
|
|
17340
|
+
app.post("/api/image-sessions", async (request, reply) => {
|
|
17341
|
+
if (mode === "hub") {
|
|
17342
|
+
reply.code(409);
|
|
17343
|
+
return { error: "Image sessions can only be created on agent." };
|
|
17344
|
+
}
|
|
17345
|
+
const parsed = imageSessionCreateRequestSchema.safeParse(request.body ?? {});
|
|
17346
|
+
if (!parsed.success) {
|
|
17347
|
+
reply.code(400);
|
|
17348
|
+
return { error: "Invalid image session payload." };
|
|
17349
|
+
}
|
|
17350
|
+
const record = await imageSessionStore.createSession(localAgentId, parsed.data.title ?? null);
|
|
17351
|
+
syncImageSessionSnapshot(record.session.id);
|
|
17352
|
+
reply.code(201);
|
|
17353
|
+
return { session: record.session };
|
|
17354
|
+
});
|
|
17355
|
+
app.get("/api/image-sessions/:sessionId", async (request, reply) => {
|
|
17356
|
+
const { sessionId } = request.params;
|
|
17357
|
+
const detail = await readImageSessionDetail(sessionId);
|
|
17358
|
+
if (!detail) {
|
|
17359
|
+
reply.code(404);
|
|
17360
|
+
return { error: "Image session not found." };
|
|
17361
|
+
}
|
|
17362
|
+
return detail;
|
|
17363
|
+
});
|
|
17364
|
+
app.post("/api/image-sessions/:sessionId/submit", async (request, reply) => {
|
|
17365
|
+
const { sessionId } = request.params;
|
|
17366
|
+
const parsed = imageSessionSubmitRequestSchema.safeParse(request.body ?? {});
|
|
17367
|
+
if (!parsed.success) {
|
|
17368
|
+
reply.code(400);
|
|
17369
|
+
return { error: "Invalid image session payload." };
|
|
17370
|
+
}
|
|
17371
|
+
const prompt = parsed.data.prompt.trim();
|
|
17372
|
+
if (!prompt) {
|
|
17373
|
+
reply.code(400);
|
|
17374
|
+
return { error: "Prompt is required." };
|
|
17375
|
+
}
|
|
17376
|
+
try {
|
|
17377
|
+
return await submitImageSessionPrompt({
|
|
17378
|
+
sessionId,
|
|
17379
|
+
prompt,
|
|
17380
|
+
appendToTurnId: parsed.data.append_to_turn_id,
|
|
17381
|
+
sources: parsed.data.sources,
|
|
17382
|
+
model: parsed.data.model,
|
|
17383
|
+
quality: parsed.data.quality,
|
|
17384
|
+
size: parsed.data.size,
|
|
17385
|
+
inputFidelity: parsed.data.input_fidelity,
|
|
17386
|
+
aspectRatio: parsed.data.aspect_ratio,
|
|
17387
|
+
outputFormat: parsed.data.output_format,
|
|
17388
|
+
outputCompression: parsed.data.output_compression,
|
|
17389
|
+
background: parsed.data.background,
|
|
17390
|
+
moderation: parsed.data.moderation,
|
|
17391
|
+
n: parsed.data.n
|
|
17392
|
+
});
|
|
17393
|
+
} catch (error) {
|
|
17394
|
+
reply.code(409);
|
|
17395
|
+
return {
|
|
17396
|
+
error: error instanceof Error ? error.message : "Unable to submit image session prompt."
|
|
17397
|
+
};
|
|
17398
|
+
}
|
|
17399
|
+
});
|
|
17400
|
+
app.get("/api/image-assets/:assetId", async (request, reply) => {
|
|
17401
|
+
const { assetId } = request.params;
|
|
17402
|
+
const found = findImageAssetRecordById(assetId);
|
|
17403
|
+
if (!found) {
|
|
17404
|
+
reply.code(404);
|
|
17405
|
+
return { error: "Image asset not found." };
|
|
17406
|
+
}
|
|
17407
|
+
reply.type(found.asset.mime_type ?? "application/octet-stream");
|
|
17408
|
+
return reply.send(await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(found.asset)));
|
|
17409
|
+
});
|
|
15389
17410
|
app.post(
|
|
15390
17411
|
"/api/sessions/:sessionId/actions",
|
|
15391
17412
|
async (request, reply) => {
|
|
@@ -15417,6 +17438,49 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15417
17438
|
reply.code(409);
|
|
15418
17439
|
return { error: "Running sessions cannot be changed right now." };
|
|
15419
17440
|
}
|
|
17441
|
+
if (session.provider === "image") {
|
|
17442
|
+
if (action === "pin" || action === "unpin") {
|
|
17443
|
+
const pinned = action === "pin";
|
|
17444
|
+
await imageSessionStore.mutateSessionRef(sessionId, (current) => ({
|
|
17445
|
+
...current,
|
|
17446
|
+
pinned
|
|
17447
|
+
}));
|
|
17448
|
+
syncImageSessionSnapshot(sessionId);
|
|
17449
|
+
broadcastEvent("session.updated", {
|
|
17450
|
+
sessionId,
|
|
17451
|
+
sessionPatch: {
|
|
17452
|
+
pinned
|
|
17453
|
+
}
|
|
17454
|
+
});
|
|
17455
|
+
return {
|
|
17456
|
+
ok: true,
|
|
17457
|
+
affectedSessionIds: [sessionId],
|
|
17458
|
+
nextSessionId: findNextSessionId(snapshot.sessions)
|
|
17459
|
+
};
|
|
17460
|
+
}
|
|
17461
|
+
if (action === "archive") {
|
|
17462
|
+
await imageSessionStore.mutateSessionRef(sessionId, (current) => ({
|
|
17463
|
+
...current,
|
|
17464
|
+
archived: true,
|
|
17465
|
+
health: "offline",
|
|
17466
|
+
run_state: "idle",
|
|
17467
|
+
run_state_changed_at: null,
|
|
17468
|
+
capability: {
|
|
17469
|
+
...current.capability,
|
|
17470
|
+
can_send_input: false
|
|
17471
|
+
}
|
|
17472
|
+
}));
|
|
17473
|
+
syncImageSessionSnapshot(sessionId);
|
|
17474
|
+
} else {
|
|
17475
|
+
await imageSessionStore.deleteSession(sessionId);
|
|
17476
|
+
removeSnapshotSessions([sessionId]);
|
|
17477
|
+
}
|
|
17478
|
+
return {
|
|
17479
|
+
ok: true,
|
|
17480
|
+
affectedSessionIds: [sessionId],
|
|
17481
|
+
nextSessionId: findNextSessionId(snapshot.sessions)
|
|
17482
|
+
};
|
|
17483
|
+
}
|
|
15420
17484
|
const affectedSessionIds = [
|
|
15421
17485
|
sessionId,
|
|
15422
17486
|
...session.subagent ? [] : collectDescendantSessionIds(sessionId, snapshot.sessions)
|
|
@@ -15827,8 +17891,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15827
17891
|
codexConfig: configDiagnostics
|
|
15828
17892
|
}, "Successfully forwarded Panda session input to Codex.");
|
|
15829
17893
|
setManagedSessionActive(sessionId);
|
|
15830
|
-
const runningAt =
|
|
15831
|
-
const summary = input ? truncateSummary(input) : attachments[0]?.name ?? "\
|
|
17894
|
+
const runningAt = isoNow6();
|
|
17895
|
+
const summary = input ? truncateSummary(input) : attachments[0]?.name ?? "\u95C4\u52EA\u6B22";
|
|
15832
17896
|
const overlayAttachments = buildInlineTimelineAttachments(attachments);
|
|
15833
17897
|
managedSessions.set(sessionId, {
|
|
15834
17898
|
...toSessionWithRunState(session, {
|
|
@@ -15853,9 +17917,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15853
17917
|
latest_assistant_message: null
|
|
15854
17918
|
});
|
|
15855
17919
|
const overlayUserEntry = {
|
|
15856
|
-
id:
|
|
17920
|
+
id: USER_OVERLAY_ENTRY_PREFIX + sessionId + ":" + Date.now(),
|
|
15857
17921
|
kind: "user",
|
|
15858
|
-
title: "
|
|
17922
|
+
title: "You",
|
|
15859
17923
|
body: input,
|
|
15860
17924
|
body_truncated: false,
|
|
15861
17925
|
detail_available: false,
|
|
@@ -15901,7 +17965,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15901
17965
|
};
|
|
15902
17966
|
}
|
|
15903
17967
|
setManagedSessionActive(sessionId);
|
|
15904
|
-
const completedAt =
|
|
17968
|
+
const completedAt = isoNow6();
|
|
15905
17969
|
const previousSessionState = {
|
|
15906
17970
|
run_state: session.run_state,
|
|
15907
17971
|
run_state_changed_at: session.run_state_changed_at,
|
|
@@ -15937,10 +18001,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15937
18001
|
}
|
|
15938
18002
|
});
|
|
15939
18003
|
const interruptEntry = {
|
|
15940
|
-
id:
|
|
18004
|
+
id: "entry-system-interrupt-" + Date.now(),
|
|
15941
18005
|
kind: "system",
|
|
15942
|
-
title: "
|
|
15943
|
-
body: "
|
|
18006
|
+
title: "Interrupt requested",
|
|
18007
|
+
body: "Interrupt request submitted.",
|
|
15944
18008
|
body_truncated: false,
|
|
15945
18009
|
detail_available: false,
|
|
15946
18010
|
patch_summary: null,
|
|
@@ -15976,7 +18040,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15976
18040
|
await liveSessionStream.ensureSessionTracker(sessionId, discoveredSessionFiles[sessionId]);
|
|
15977
18041
|
await liveSessionStream.interruptTurn(sessionId);
|
|
15978
18042
|
} catch (error) {
|
|
15979
|
-
const failedAt =
|
|
18043
|
+
const failedAt = isoNow6();
|
|
15980
18044
|
const failureMessage = error instanceof Error ? error.message : "Unable to interrupt Codex session.";
|
|
15981
18045
|
const nextRunState = previousSessionState.run_state === "running" ? "running" : previousSessionState.run_state;
|
|
15982
18046
|
const nextRunStateChangedAt = nextRunState === "running" ? failedAt : previousSessionState.run_state_changed_at;
|
|
@@ -16010,10 +18074,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16010
18074
|
}
|
|
16011
18075
|
});
|
|
16012
18076
|
const failureEntry = {
|
|
16013
|
-
id:
|
|
18077
|
+
id: "entry-system-interrupt-failed-" + Date.now(),
|
|
16014
18078
|
kind: "system",
|
|
16015
|
-
title: "
|
|
16016
|
-
body:
|
|
18079
|
+
title: "Interrupt failed",
|
|
18080
|
+
body: "Interrupt failed: " + failureMessage,
|
|
16017
18081
|
body_truncated: false,
|
|
16018
18082
|
detail_available: false,
|
|
16019
18083
|
patch_summary: null,
|
|
@@ -16209,7 +18273,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16209
18273
|
}
|
|
16210
18274
|
sendSocketEvent(client, "timeline.reset", {
|
|
16211
18275
|
sessionId: message.sessionId,
|
|
16212
|
-
entries: bootstrap.timeline.entries
|
|
18276
|
+
entries: bootstrap.timeline.entries,
|
|
18277
|
+
hasEarlierEntries: bootstrap.timeline.has_earlier_entries
|
|
16213
18278
|
});
|
|
16214
18279
|
sendSocketEvent(client, "interaction.reset", {
|
|
16215
18280
|
sessionId: message.sessionId,
|
|
@@ -16254,7 +18319,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16254
18319
|
}
|
|
16255
18320
|
reply.type(asset.contentType);
|
|
16256
18321
|
reply.header("cache-control", asset.cacheControl);
|
|
16257
|
-
return reply.send(await
|
|
18322
|
+
return reply.send(await fs9.readFile(asset.filePath));
|
|
16258
18323
|
});
|
|
16259
18324
|
}
|
|
16260
18325
|
await app.listen({
|
|
@@ -16283,6 +18348,8 @@ export {
|
|
|
16283
18348
|
writeCodexGlobalState,
|
|
16284
18349
|
getSavedWorkspaceRoots,
|
|
16285
18350
|
getWorkspaceRootLabels,
|
|
18351
|
+
getProjectlessThreadIds,
|
|
18352
|
+
getThreadWorkspaceRootHints,
|
|
16286
18353
|
readPandaThreadPrefs,
|
|
16287
18354
|
writePandaThreadPrefs,
|
|
16288
18355
|
getPinnedWorkspaceRoots,
|
|
@@ -16299,6 +18366,7 @@ export {
|
|
|
16299
18366
|
setWorkspaceRootOrder,
|
|
16300
18367
|
setWorkspaceRootLabel,
|
|
16301
18368
|
setWorkspaceRootVisibility,
|
|
18369
|
+
registerProjectlessThread,
|
|
16302
18370
|
isWithinWorkspaceRoot,
|
|
16303
18371
|
normalizeWorkspacePathKey,
|
|
16304
18372
|
sortByStoredWorkspaceOrder,
|